From a506d6d6e7949a39469737213096bdc2aad791ea Mon Sep 17 00:00:00 2001 From: Jassi Date: Mon, 17 Jul 2023 19:27:37 +0530 Subject: [PATCH] test(upgrade): add upgrade tests for systest/license package (#8902) Description: this PR adds upgrade tests for systest/license package using dgraphtest package. Closes: DGRAPHCORE-303 --- dgraphtest/cluster.go | 76 +++++++++++++-- dgraphtest/compose_cluster.go | 10 +- dgraphtest/local_cluster.go | 37 ++++--- go.mod | 2 +- go.sum | 3 +- systest/license/integration_test.go | 50 ++++++++++ systest/license/license_test.go | 143 +++++++++++----------------- systest/license/upgrade_test.go | 74 ++++++++++++++ 8 files changed, 284 insertions(+), 111 deletions(-) create mode 100644 systest/license/integration_test.go create mode 100644 systest/license/upgrade_test.go diff --git a/dgraphtest/cluster.go b/dgraphtest/cluster.go index 61b46696dfc..b1b80a40db8 100644 --- a/dgraphtest/cluster.go +++ b/dgraphtest/cluster.go @@ -21,6 +21,7 @@ import ( "context" "encoding/json" "fmt" + "io" "net/http" "os/exec" "strings" @@ -61,6 +62,8 @@ type HTTPClient struct { *HttpToken adminURL string graphqlURL string + licenseURL string + stateURL string } // GraphQLParams are used for making graphql requests to dgraph @@ -73,9 +76,17 @@ type GraphQLParams struct { type GraphQLResponse struct { Data json.RawMessage `json:"data,omitempty"` Errors x.GqlErrorList `json:"errors,omitempty"` + Code string `json:"code"` Extensions map[string]interface{} `json:"extensions,omitempty"` } +type LicenseResponse struct { + Data json.RawMessage `json:"data,omitempty"` + Errors x.GqlErrorList `json:"errors,omitempty"` + Code string `json:"code"` + Extensions map[string]interface{} `json:"license,omitempty"` +} + func (hc *HTTPClient) Login(user, password string, ns uint64) error { login := `mutation login($userId: String, $password: String, $namespace: Int, $refreshToken: String) { login(userId: $userId, password: $password, namespace: $namespace, refreshToken: $refreshToken) { @@ -210,17 +221,13 @@ func (hc *HTTPClient) ResetPassword(userID, newPass string, nsID uint64) (string return "", errors.New(result.ResetPassword.Message) } -// doPost makes a post request to the graphql admin endpoint -func (hc *HTTPClient) doPost(body []byte, admin bool) ([]byte, error) { - url := hc.graphqlURL - if admin { - url = hc.adminURL - } +// doPost makes a post request to the 'url' endpoint +func (hc *HTTPClient) doPost(body []byte, url string, contentType string) ([]byte, error) { req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(body)) if err != nil { return nil, errors.Wrapf(err, "error building req for endpoint [%v]", url) } - req.Header.Add("Content-Type", "application/json") + req.Header.Add("Content-Type", contentType) if hc.HttpToken != nil { req.Header.Add("X-Dgraph-AccessToken", hc.AccessJwt) @@ -236,7 +243,12 @@ func (hc *HTTPClient) RunGraphqlQuery(params GraphQLParams, admin bool) ([]byte, return nil, errors.Wrap(err, "error while marshalling params") } - respBody, err := hc.doPost(reqBody, admin) + url := hc.graphqlURL + if admin { + url = hc.adminURL + } + + respBody, err := hc.doPost(reqBody, url, "application/json") if err != nil { return nil, errors.Wrap(err, "error while running graphql query") } @@ -525,6 +537,54 @@ func (hc *HTTPClient) PostPersistentQuery(query, sha string) ([]byte, error) { return hc.RunGraphqlQuery(params, false) } +// Apply license using http endpoint +func (hc *HTTPClient) ApplyLicenseHTTP(licenseKey []byte) (*LicenseResponse, error) { + respBody, err := hc.doPost(licenseKey, hc.licenseURL, "application/text") + if err != nil { + return nil, errors.Wrap(err, "error applying license") + } + var enterpriseResponse LicenseResponse + if err = json.Unmarshal(respBody, &enterpriseResponse); err != nil { + return nil, errors.Wrap(err, "error unmarshaling the license response") + } + + return &enterpriseResponse, nil +} + +// Apply license using graphql endpoint +func (hc *HTTPClient) ApplyLicenseGraphQL(license []byte) ([]byte, error) { + params := GraphQLParams{ + Query: `mutation ($license: String!) { + enterpriseLicense(input: {license: $license}) { + response { + code + } + } + }`, + Variables: map[string]interface{}{ + "license": string(license), + }, + } + return hc.RunGraphqlQuery(params, true) +} + +func (hc *HTTPClient) GetZeroState() (*LicenseResponse, error) { + response, err := http.Get(hc.stateURL) + if err != nil { + return nil, errors.Wrap(err, "error getting zero state http response") + } + body, err := io.ReadAll(response.Body) + if err != nil { + return nil, errors.New("error reading zero state response body") + } + var stateResponse LicenseResponse + if err := json.Unmarshal(body, &stateResponse); err != nil { + return nil, errors.New("error unmarshaling zero state response") + } + + return &stateResponse, nil +} + // SetupSchema sets up DQL schema func (gc *GrpcClient) SetupSchema(dbSchema string) error { ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) diff --git a/dgraphtest/compose_cluster.go b/dgraphtest/compose_cluster.go index f81e8ab1828..e9ab3d021b5 100644 --- a/dgraphtest/compose_cluster.go +++ b/dgraphtest/compose_cluster.go @@ -40,7 +40,15 @@ func (c *ComposeCluster) Client() (*GrpcClient, func(), error) { func (c *ComposeCluster) HTTPClient() (*HTTPClient, error) { adminUrl := "http://" + testutil.SockAddrHttp + "/admin" graphQLUrl := "http://" + testutil.SockAddrHttp + "/graphql" - return &HTTPClient{adminURL: adminUrl, graphqlURL: graphQLUrl, HttpToken: &HttpToken{}}, nil + licenseUrl := "http://" + testutil.SockAddrZeroHttp + "/enterpriseLicense" + stateUrl := "http://" + testutil.SockAddrZeroHttp + "/state" + return &HTTPClient{ + adminURL: adminUrl, + graphqlURL: graphQLUrl, + licenseURL: licenseUrl, + stateURL: stateUrl, + HttpToken: &HttpToken{}, + }, nil } func (c *ComposeCluster) AlphasHealth() ([]string, error) { diff --git a/dgraphtest/local_cluster.go b/dgraphtest/local_cluster.go index cf6b112a8c4..1ee91f37897 100644 --- a/dgraphtest/local_cluster.go +++ b/dgraphtest/local_cluster.go @@ -619,34 +619,41 @@ func (c *LocalCluster) Client() (*GrpcClient, func(), error) { // HTTPClient creates an HTTP client func (c *LocalCluster) HTTPClient() (*HTTPClient, error) { - adminURL, err := c.adminURL() + adminURL, err := c.serverURL("alpha", "/admin") if err != nil { return nil, err } - graphqlURL, err := c.graphqlURL() + graphqlURL, err := c.serverURL("alpha", "/graphql") if err != nil { return nil, err } - return &HTTPClient{adminURL: adminURL, graphqlURL: graphqlURL}, nil -} - -// adminURL returns url to the graphql admin endpoint -func (c *LocalCluster) adminURL() (string, error) { - publicPort, err := publicPort(c.dcli, c.alphas[0], alphaHttpPort) + licenseURL, err := c.serverURL("zero", "/enterpriseLicense") if err != nil { - return "", err + return nil, err } - url := "http://localhost:" + publicPort + "/admin" - return url, nil + stateURL, err := c.serverURL("zero", "/state") + if err != nil { + return nil, err + } + + return &HTTPClient{ + adminURL: adminURL, + graphqlURL: graphqlURL, + licenseURL: licenseURL, + stateURL: stateURL, + }, nil } -// graphqlURL returns url to the graphql endpoint -func (c *LocalCluster) graphqlURL() (string, error) { - publicPort, err := publicPort(c.dcli, c.alphas[0], alphaHttpPort) +// serverURL returns url to the 'server' 'endpoint' +func (c *LocalCluster) serverURL(server, endpoint string) (string, error) { + pubPort, err := publicPort(c.dcli, c.alphas[0], alphaHttpPort) + if server == "zero" { + pubPort, err = publicPort(c.dcli, c.zeros[0], zeroHttpPort) + } if err != nil { return "", err } - url := "http://localhost:" + publicPort + "/graphql" + url := "http://localhost:" + pubPort + endpoint return url, nil } diff --git a/go.mod b/go.mod index 120bee62ab1..9f4ce6fbc35 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.7.1 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.8.4 github.com/twpayne/go-geom v1.0.5 github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c go.etcd.io/etcd v0.5.0-alpha.5.0.20190108173120-83c051b701d3 diff --git a/go.sum b/go.sum index eab5a0747da..6e9bfb54d7b 100644 --- a/go.sum +++ b/go.sum @@ -651,8 +651,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= diff --git a/systest/license/integration_test.go b/systest/license/integration_test.go new file mode 100644 index 00000000000..b8c1258e8f7 --- /dev/null +++ b/systest/license/integration_test.go @@ -0,0 +1,50 @@ +//go:build integration + +/* + * Copyright 2023 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/dgraph-io/dgraph/dgraphtest" +) + +type LicenseTestSuite struct { + suite.Suite + dc dgraphtest.Cluster +} + +func (lsuite *LicenseTestSuite) SetupTest() { + lsuite.dc = dgraphtest.NewComposeCluster() +} + +func (lsuite *LicenseTestSuite) TearDownTest() { +} + +func (lsuite *LicenseTestSuite) Upgrade() { + // Not implemented for integration tests +} + +func TestLicenseTestSuite(t *testing.T) { + suite.Run(t, new(LicenseTestSuite)) + if t.Failed() { + t.Fatal("TestLicenseTestSuite tests failed") + } +} diff --git a/systest/license/license_test.go b/systest/license/license_test.go index 0b29426b580..739a08f551f 100644 --- a/systest/license/license_test.go +++ b/systest/license/license_test.go @@ -1,4 +1,4 @@ -//go:build integration +//go:build integration || upgrade /* * Copyright 2023 Dgraph Labs, Inc. and Contributors @@ -19,15 +19,11 @@ package main import ( - "bytes" - "encoding/json" - "io" - "net/http" "testing" "github.com/stretchr/testify/require" - "github.com/dgraph-io/dgraph/testutil" + "github.com/dgraph-io/dgraph/dgraphtest" ) var expiredKey = []byte(`-----BEGIN PGP MESSAGE----- @@ -72,116 +68,93 @@ I8jcj3NZtGWFoxKq4laK/ruoeoHnWMznJyMm7mUd/5nzcU5QZU9yEEI= -----END PGP MESSAGE----- `) -type Location struct { - Line int `json:"line,omitempty"` - Column int `json:"column,omitempty"` +type testInp struct { + name string + licenseKey []byte + code string + user string + message string } -type GqlError struct { - Message string `json:"message"` - Locations []Location `json:"locations,omitempty"` - Path []interface{} `json:"path,omitempty"` - Extensions map[string]interface{} `json:"extensions,omitempty"` +var tests = []testInp{ + { + "Using expired entrerprise license key, should be able to extract user information", + expiredKey, + `Success`, + `Dgraph Test Key`, + ``, + }, + { + "Using invalid entrerprise license key should return an error", + invalidKey, + ``, + ``, + `while extracting enterprise details from the license: while reading PGP message from license file: openpgp: unsupported feature: public key version`, //nolint:lll + }, + { + "Using empty entrerprise license key should return an error", + []byte(``), + ``, + ``, + `while extracting enterprise details from the license: while decoding license file: EOF`, + }, } -type GqlErrorList []*GqlError +func (lsuite *LicenseTestSuite) TestEnterpriseLicenseWithHttpEndPoint() { + // run this test using the http endpoint -type responseStruct struct { - Errors GqlErrorList `json:"errors"` - Code string `json:"code"` - Message string `json:"message"` - License map[string]interface{} `json:"license"` -} + t := lsuite.T() -func TestEnterpriseLicense(t *testing.T) { - enterpriseLicenseURL := "http://" + testutil.SockAddrZeroHttp + "/enterpriseLicense" - - var tests = []struct { - name string - licenseKey []byte - code string - user string - message string - }{ - { - "Using expired entrerprise license key, should be able to extract user information", - expiredKey, - `Success`, - `Dgraph Test Key`, - ``, - }, - { - "Using invalid entrerprise license key should return an error", - invalidKey, - ``, - ``, - `while extracting enterprise details from the license: while reading PGP message from license file: openpgp: unsupported feature: public key version`, //nolint:lll - }, - { - "Using empty entrerprise license key should return an error", - []byte(``), - ``, - ``, - `while extracting enterprise details from the license: while decoding license file: EOF`, - }, - } + hcli, err := lsuite.dc.HTTPClient() + require.NoError(t, err) - // run these tests using the http endpoint for _, tt := range tests { + enterpriseResponse, err := hcli.ApplyLicenseHTTP(tt.licenseKey) - // Apply the license - response, err := http.Post(enterpriseLicenseURL, "application/text", bytes.NewBuffer(tt.licenseKey)) - require.NoError(t, err) - - var enterpriseResponse responseStruct - responseBody, err := io.ReadAll(response.Body) - require.NoError(t, err) - require.NoError(t, json.Unmarshal(responseBody, &enterpriseResponse)) - - // Check if the license is applied - require.Equal(t, enterpriseResponse.Code, tt.code) + if err == nil && enterpriseResponse.Code == `Success` { + // Check if the license is applied + require.Equal(t, enterpriseResponse.Code, tt.code) - if enterpriseResponse.Code == `Success` { // check the user information in case the license is applied // Expired license should not be enabled even after it is applied - assertLicenseNotEnabled(t, tt.user) + assertLicenseNotEnabled(t, hcli, tt.user) } else { // check the error message in case the license is not applied - require.Equal(t, enterpriseResponse.Errors[0].Message, tt.message) + require.Nil(t, enterpriseResponse) } } +} +func (lsuite *LicenseTestSuite) TestEnterpriseLicenseWithGraphqlEndPoint() { // this time, run them using the GraphQL admin endpoint - for _, tt := range tests { - // Apply the license - resp := testutil.EnterpriseLicense(t, string(tt.licenseKey)) + t := lsuite.T() + hcli, err := lsuite.dc.HTTPClient() + require.NoError(t, err) + + for _, tt := range tests { + resp, err := hcli.ApplyLicenseGraphQL(tt.licenseKey) if tt.code == `Success` { + require.NoError(t, err) // Check if the license is applied - testutil.CompareJSON(t, `{"enterpriseLicense":{"response":{"code":"Success"}}}`, - string(resp.Data)) + dgraphtest.CompareJSON(`{"enterpriseLicense":{"response":{"code":"Success"}}}`, string(resp)) // check the user information in case the license is applied // Expired license should not be enabled even after it is applied - assertLicenseNotEnabled(t, tt.user) + assertLicenseNotEnabled(t, hcli, tt.user) } else { - testutil.CompareJSON(t, `{"enterpriseLicense":null}`, string(resp.Data)) + dgraphtest.CompareJSON(`{"enterpriseLicense":null}`, string(resp)) // check the error message in case the license is not applied - require.Contains(t, resp.Errors[0].Message, tt.message) + require.Contains(t, err.Error(), tt.message) } } } -func assertLicenseNotEnabled(t *testing.T, user string) { - response, err := http.Get("http://" + testutil.SockAddrZeroHttp + "/state") - require.NoError(t, err) - - var stateResponse responseStruct - responseBody, err := io.ReadAll(response.Body) +func assertLicenseNotEnabled(t *testing.T, hcli *dgraphtest.HTTPClient, user string) { + response, err := hcli.GetZeroState() require.NoError(t, err) - require.NoError(t, json.Unmarshal(responseBody, &stateResponse)) - require.Equal(t, stateResponse.License["user"], user) - require.Equal(t, stateResponse.License["enabled"], false) + require.Equal(t, response.Extensions["user"], user) + require.Equal(t, response.Extensions["enabled"], false) } diff --git a/systest/license/upgrade_test.go b/systest/license/upgrade_test.go new file mode 100644 index 00000000000..135a94e6415 --- /dev/null +++ b/systest/license/upgrade_test.go @@ -0,0 +1,74 @@ +//go:build upgrade + +/* + * Copyright 2023 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "log" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/dgraph-io/dgraph/dgraphtest" + "github.com/dgraph-io/dgraph/x" +) + +type LicenseTestSuite struct { + suite.Suite + dc dgraphtest.Cluster + lc *dgraphtest.LocalCluster + uc dgraphtest.UpgradeCombo +} + +func (lsuite *LicenseTestSuite) SetupTest() { + conf := dgraphtest.NewClusterConfig().WithNumAlphas(1).WithNumZeros(1).WithReplicas(1). + WithEncryption().WithVersion(lsuite.uc.Before) + c, err := dgraphtest.NewLocalCluster(conf) + x.Panic(err) + if err := c.Start(); err != nil { + c.Cleanup(true) + panic(err) + } + + lsuite.dc = c + lsuite.lc = c +} + +func (lsuite *LicenseTestSuite) TearDownTest() { + lsuite.lc.Cleanup(lsuite.T().Failed()) +} + +func (lsuite *LicenseTestSuite) Upgrade() { + t := lsuite.T() + + if err := lsuite.lc.Upgrade(lsuite.uc.After, lsuite.uc.Strategy); err != nil { + t.Fatal(err) + } +} + +func TestLicenseTestSuite(t *testing.T) { + for _, uc := range dgraphtest.AllUpgradeCombos() { + log.Printf("running: backup in [%v], restore in [%v]", uc.Before, uc.After) + var tsuite LicenseTestSuite + tsuite.uc = uc + suite.Run(t, &tsuite) + if t.Failed() { + t.Fatal("TestLicenseTestSuite tests failed") + } + } +}