diff --git a/.circleci/config.yml b/.circleci/config.yml index 96ef09253fc1..970ae4562448 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -789,6 +789,11 @@ jobs: test: executor: golang resource_class: medium + parameters: + target: + description: The test make target. + type: string + default: test steps: - checkout - restore_cache: @@ -804,7 +809,7 @@ jobs: command: make dev/tools - run: name: "Run unit tests" - command: GO_TEST_OPTS='-p 2' make test + command: GO_TEST_OPTS='-p 2' make << parameters.target >> - store_artifacts: path: build/coverage destination: /coverage @@ -1492,6 +1497,12 @@ workflows: <<: *release_workflow_filters requires: - build + - test: + <<: *helm_release_workflow_filters + requires: + - go_cache + name: test-release + target: test/release - test: <<: *release_workflow_filters requires: @@ -1506,6 +1517,7 @@ workflows: - api_check - check - test + - test-release - integration - helm-release: <<: *helm_release_workflow_filters diff --git a/mk/test.mk b/mk/test.mk index 40be95468c3b..e3f41ab2ada1 100644 --- a/mk/test.mk +++ b/mk/test.mk @@ -69,6 +69,10 @@ test/kumactl: test/kuma ## Dev: Run `kumactl` tests only ${COVERAGE_INTEGRATION_PROFILE}: mkdir -p "$(shell dirname "$(COVERAGE_INTEGRATION_PROFILE)")" +.PHONY: test/release +test/release: # Dev: Run release tests + $(GO_TEST) $(GO_TEST_OPTS) -tags=release ./test/release/... + .PHONY: integration integration: ${COVERAGE_INTEGRATION_PROFILE} ## Dev: Run integration tests tools/test/run-integration-tests.sh '$(GO_TEST) -tags=integration,gateway -race -covermode=atomic -count=1 -coverpkg=./... -coverprofile=$(COVERAGE_INTEGRATION_PROFILE) $(PKG_LIST)' diff --git a/pkg/api-server/testdata/versions.json b/pkg/api-server/testdata/versions.json new file mode 100644 index 000000000000..f80b0dd05b8c --- /dev/null +++ b/pkg/api-server/testdata/versions.json @@ -0,0 +1,40 @@ +{ + "kumaDp": { + "1.0.0": { + "envoy": "1.16.0" + }, + "1.0.1": { + "envoy": "1.16.0" + }, + "1.0.2": { + "envoy": "1.16.1" + }, + "1.0.3": { + "envoy": "1.16.1" + }, + "1.0.4": { + "envoy": "1.16.1" + }, + "1.0.5": { + "envoy": "1.16.2" + }, + "1.0.6": { + "envoy": "1.16.2" + }, + "1.0.7": { + "envoy": "1.16.2" + }, + "1.0.8": { + "envoy": "1.16.2" + }, + "~1.1.0": { + "envoy": "~1.17.0" + }, + "~1.2.0": { + "envoy": "~1.18.0" + }, + "~1.3.0": { + "envoy": "~1.18.4" + } + } +} diff --git a/pkg/api-server/versions_ws.go b/pkg/api-server/versions_ws.go index 8318ddb32e4e..6eff41ce5c3b 100644 --- a/pkg/api-server/versions_ws.go +++ b/pkg/api-server/versions_ws.go @@ -2,52 +2,16 @@ package api_server import ( "github.com/emicklei/go-restful" -) -var Versions = []byte(`{ - "kumaDp": { - "1.0.0": { - "envoy": "1.16.0" - }, - "1.0.1": { - "envoy": "1.16.0" - }, - "1.0.2": { - "envoy": "1.16.1" - }, - "1.0.3": { - "envoy": "1.16.1" - }, - "1.0.4": { - "envoy": "1.16.1" - }, - "1.0.5": { - "envoy": "1.16.2" - }, - "1.0.6": { - "envoy": "1.16.2" - }, - "1.0.7": { - "envoy": "1.16.2" - }, - "1.0.8": { - "envoy": "1.16.2" - }, - "~1.1.0": { - "envoy": "~1.17.0" - }, - "~1.2.0": { - "envoy": "~1.18.0" - } - } -}`) + "github.com/kumahq/kuma/pkg/version" +) func versionsWs() *restful.WebService { ws := new(restful.WebService).Path("/versions") ws.Route(ws.GET("").To(func(req *restful.Request, resp *restful.Response) { resp.AddHeader("content-type", "application/json") - if _, err := resp.Write(Versions); err != nil { + if err := resp.WriteAsJson(version.CompatibilityMatrix); err != nil { log.Error(err, "Could not write the index response") } })) diff --git a/pkg/api-server/versions_ws_test.go b/pkg/api-server/versions_ws_test.go index 09ce848eb052..402b20d5dc0f 100644 --- a/pkg/api-server/versions_ws_test.go +++ b/pkg/api-server/versions_ws_test.go @@ -1,92 +1,20 @@ package api_server_test import ( - "encoding/json" "fmt" + "io/ioutil" "net/http" - "strings" + "path" - "github.com/Masterminds/semver/v3" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/pkg/errors" config "github.com/kumahq/kuma/pkg/config/api-server" "github.com/kumahq/kuma/pkg/metrics" "github.com/kumahq/kuma/pkg/plugins/resources/memory" + "github.com/kumahq/kuma/pkg/test/matchers" ) -func buildConstraints(versions map[string]envoyVersion) ([]*semver.Constraints, error) { - var errs []string - var constraints []*semver.Constraints - - for c := range versions { - constraint, err := semver.NewConstraint(c) - if err != nil { - errs = append(errs, err.Error()) - continue - } - - constraints = append(constraints, constraint) - } - - if len(errs) > 0 { - return nil, errors.Errorf("couldn't build constraints:\n%s", strings.Join(errs, "\n")) - } - - return constraints, nil -} - -func getConstraint(constraints []*semver.Constraints, version string) (*semver.Constraints, error) { - v, err := semver.NewVersion(version) - if err != nil { - return nil, err - } - - var matchedConstrain []*semver.Constraints - - for _, constraint := range constraints { - if constraint.Check(v) { - matchedConstrain = append(matchedConstrain, constraint) - } - } - - if len(matchedConstrain) == 0 { - return nil, errors.Errorf("no constraints for version: %s found", version) - } - - if len(matchedConstrain) > 1 { - var matched []string - for _, c := range matchedConstrain { - matched = append(matched, c.String()) - } - - return nil, errors.Errorf( - "more than one constraint for version: %s\n%s", - version, - strings.Join(matched, "\n"), - ) - } - - return matchedConstrain[0], nil -} - -func validateConstrainForEnvoy(constrain string, version string) error { - if constrain != version { - return errors.Errorf("envoy version in constrain: %s doesn't equal expected one: %s", constrain, version) - } - - return nil -} - -type envoyVersion struct { - Envoy string -} - -type versions struct { - KumaDp map[string]envoyVersion -} - var _ = Describe("Versions WS", func() { It("should return the supported versions", func() { // setup @@ -102,80 +30,17 @@ var _ = Describe("Versions WS", func() { Expect(err).ToNot(HaveOccurred()) }() - // wait for the server + // when + var resp *http.Response Eventually(func() error { - _, err := http.Get(fmt.Sprintf("http://%s/versions", apiServer.Address())) + r, err := http.Get(fmt.Sprintf("http://%s/versions", apiServer.Address())) + resp = r return err }, "3s").ShouldNot(HaveOccurred()) - // when - resp, err := http.Get(fmt.Sprintf("http://%s/versions", apiServer.Address())) - Expect(err).ToNot(HaveOccurred()) - // then - var data versions - - Expect(json.NewDecoder(resp.Body).Decode(&data)).ToNot(HaveOccurred()) - - constraints, err := buildConstraints(data.KumaDp) + bytes, err := ioutil.ReadAll(resp.Body) Expect(err).ToNot(HaveOccurred()) - - Expect(data).ToNot(BeNil()) - Expect(data.KumaDp).ToNot(BeNil()) - - // 1.0.0 - constrain, err := getConstraint(constraints, "1.0.0") - Expect(err).NotTo(HaveOccurred()) - Expect(validateConstrainForEnvoy(data.KumaDp[constrain.String()].Envoy, "1.16.0")).To(Succeed()) - - // 1.0.1 - constrain, err = getConstraint(constraints, "1.0.1") - Expect(err).NotTo(HaveOccurred()) - Expect(validateConstrainForEnvoy(data.KumaDp[constrain.String()].Envoy, "1.16.0")).To(Succeed()) - - // 1.0.2 - constrain, err = getConstraint(constraints, "1.0.2") - Expect(err).NotTo(HaveOccurred()) - Expect(validateConstrainForEnvoy(data.KumaDp[constrain.String()].Envoy, "1.16.1")).To(Succeed()) - - // 1.0.3 - constrain, err = getConstraint(constraints, "1.0.3") - Expect(err).NotTo(HaveOccurred()) - Expect(validateConstrainForEnvoy(data.KumaDp[constrain.String()].Envoy, "1.16.1")).To(Succeed()) - - // 1.0.4 - constrain, err = getConstraint(constraints, "1.0.4") - Expect(err).NotTo(HaveOccurred()) - Expect(validateConstrainForEnvoy(data.KumaDp[constrain.String()].Envoy, "1.16.1")).To(Succeed()) - - // 1.0.5 - constrain, err = getConstraint(constraints, "1.0.5") - Expect(err).NotTo(HaveOccurred()) - Expect(validateConstrainForEnvoy(data.KumaDp[constrain.String()].Envoy, "1.16.2")).To(Succeed()) - - // 1.0.6 - constrain, err = getConstraint(constraints, "1.0.6") - Expect(err).NotTo(HaveOccurred()) - Expect(validateConstrainForEnvoy(data.KumaDp[constrain.String()].Envoy, "1.16.2")).To(Succeed()) - - // 1.0.7 - constrain, err = getConstraint(constraints, "1.0.7") - Expect(err).NotTo(HaveOccurred()) - Expect(validateConstrainForEnvoy(data.KumaDp[constrain.String()].Envoy, "1.16.2")).To(Succeed()) - - // 1.0.8 - constrain, err = getConstraint(constraints, "1.0.8") - Expect(err).NotTo(HaveOccurred()) - Expect(validateConstrainForEnvoy(data.KumaDp[constrain.String()].Envoy, "1.16.2")).To(Succeed()) - - // ~1.1.0 - constrain, err = getConstraint(constraints, "1.1.0") - Expect(err).NotTo(HaveOccurred()) - Expect(validateConstrainForEnvoy(data.KumaDp[constrain.String()].Envoy, "~1.17.0")).To(Succeed()) - - // ~1.2.0 - constrain, err = getConstraint(constraints, "1.2.0") - Expect(err).NotTo(HaveOccurred()) - Expect(validateConstrainForEnvoy(data.KumaDp[constrain.String()].Envoy, "~1.18.0")).To(Succeed()) + Expect(bytes).To(matchers.MatchGoldenJSON(path.Join("testdata", "versions.json"))) }) }) diff --git a/pkg/version/compatibility.go b/pkg/version/compatibility.go new file mode 100644 index 000000000000..2e00c95c6261 --- /dev/null +++ b/pkg/version/compatibility.go @@ -0,0 +1,96 @@ +package version + +import ( + "strings" + + "github.com/Masterminds/semver/v3" + "github.com/pkg/errors" +) + +type DataplaneCompatibility struct { + Envoy string `json:"envoy"` +} + +type Compatibility struct { + KumaDP map[string]DataplaneCompatibility `json:"kumaDp"` +} + +var CompatibilityMatrix = Compatibility{ + KumaDP: map[string]DataplaneCompatibility{ + "1.0.0": { + Envoy: "1.16.0", + }, + "1.0.1": { + Envoy: "1.16.0", + }, + "1.0.2": { + Envoy: "1.16.1", + }, + "1.0.3": { + Envoy: "1.16.1", + }, + "1.0.4": { + Envoy: "1.16.1", + }, + "1.0.5": { + Envoy: "1.16.2", + }, + "1.0.6": { + Envoy: "1.16.2", + }, + "1.0.7": { + Envoy: "1.16.2", + }, + "1.0.8": { + Envoy: "1.16.2", + }, + "~1.1.0": { + Envoy: "~1.17.0", + }, + "~1.2.0": { + Envoy: "~1.18.0", + }, + "~1.3.0": { + Envoy: "~1.18.4", + }, + }, +} + +// DataplaneConstraints returns which Envoy should be used with given version of Kuma. +// This information is later used in the GUI as a warning. +// Kuma ships with given Envoy version, but user can use their own Envoy version (especially on Universal) +// therefore we need to inform them that they are not using compatible version. +func (c Compatibility) DataplaneConstraints(version string) (*DataplaneCompatibility, error) { + v, err := semver.NewVersion(version) + if err != nil { + return nil, errors.Wrapf(err, "could not build a constraint %s", version) + } + + var matchedCompat []DataplaneCompatibility + for constraintRaw, dpCompat := range c.KumaDP { + constraint, err := semver.NewConstraint(constraintRaw) + if err != nil { + return nil, errors.Wrapf(err, "could not build a constraint %s", constraintRaw) + } + if constraint.Check(v) { + matchedCompat = append(matchedCompat, dpCompat) + } + } + + if len(matchedCompat) == 0 { + return nil, errors.Errorf("no constraints for version: %s found", version) + } + + if len(matchedCompat) > 1 { + var matched []string + for _, c := range matchedCompat { + matched = append(matched, c.Envoy) + } + return nil, errors.Errorf( + "more than one constraint for version %s: %s", + version, + strings.Join(matched, ", "), + ) + } + return &matchedCompat[0], nil +} diff --git a/pkg/version/compatibility_test.go b/pkg/version/compatibility_test.go new file mode 100644 index 000000000000..a8b8786b9098 --- /dev/null +++ b/pkg/version/compatibility_test.go @@ -0,0 +1,70 @@ +package version_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + + "github.com/kumahq/kuma/pkg/version" +) + +var _ = Describe("Compatibility", func() { + type testCase struct { + dpVersion string + expectedEnvoyConstraint string + } + DescribeTable("should return the supported versions", + func(given testCase) { + // when + dpCompatibility, err := version.CompatibilityMatrix.DataplaneConstraints(given.dpVersion) + + // then + Expect(err).ToNot(HaveOccurred()) + Expect(dpCompatibility.Envoy).To(Equal(given.expectedEnvoyConstraint)) + }, + Entry("1.2.0", testCase{ + dpVersion: "1.2.0", + expectedEnvoyConstraint: "~1.18.0", + }), + Entry("1.3.0", testCase{ + dpVersion: "1.3.0", + expectedEnvoyConstraint: "~1.18.4", + }), + ) + + It("should return error when there is no compatibility information for given version", func() { + // when + _, err := version.CompatibilityMatrix.DataplaneConstraints("100.0.0") + + // then + Expect(err).To(MatchError("no constraints for version: 100.0.0 found")) + }) + + It("should return error when version is invalid", func() { + // when + _, err := version.CompatibilityMatrix.DataplaneConstraints("!@#") + + // then + Expect(err).To(MatchError(`could not build a constraint !@#: Invalid Semantic Version`)) + }) + + It("should throw an error when there are multiple matching constraints", func() { + // given + compatibility := version.Compatibility{ + KumaDP: map[string]version.DataplaneCompatibility{ + "1.0.0": { + Envoy: "1.16.0", + }, + "~1.0.0": { + Envoy: "1.16.0", + }, + }, + } + + // when + _, err := compatibility.DataplaneConstraints("1.0.0") + + // then + Expect(err).To(MatchError("more than one constraint for version 1.0.0: 1.16.0, 1.16.0")) + }) +}) diff --git a/pkg/version/version_suite_test.go b/pkg/version/version_suite_test.go new file mode 100644 index 000000000000..cddbdf4586fe --- /dev/null +++ b/pkg/version/version_suite_test.go @@ -0,0 +1,11 @@ +package version_test + +import ( + "testing" + + "github.com/kumahq/kuma/pkg/test" +) + +func TestCompatibility(t *testing.T) { + test.RunSpecs(t, "Compatibility Suite") +} diff --git a/test/release/compatibility_test.go b/test/release/compatibility_test.go new file mode 100644 index 000000000000..7c3034301d82 --- /dev/null +++ b/test/release/compatibility_test.go @@ -0,0 +1,23 @@ +package release_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/kumahq/kuma/pkg/version" +) + +var _ = Describe("Compatibility", func() { + + It("current version is defined in CompatibilityMatrix", func() { + // given + currentVersion := version.Build.Version + + // when + dpComptibility, err := version.CompatibilityMatrix.DataplaneConstraints(currentVersion) + + // then + Expect(err).ToNot(HaveOccurred()) + Expect(dpComptibility).ToNot(BeNil(), "Update version.CompatibilityMatrix so there is information about current compatible Envoy for this Kuma release.") + }) +}) diff --git a/test/release/doc.go b/test/release/doc.go new file mode 100644 index 000000000000..719ae4ba034c --- /dev/null +++ b/test/release/doc.go @@ -0,0 +1,4 @@ +package release + +// Keep this one empty here to make go test happy that there are non-test files in this folder +// This test suite is meant to be run just before release to validate if the codebase is properly adjusted to the release. diff --git a/test/release/release_suite_test.go b/test/release/release_suite_test.go new file mode 100644 index 000000000000..199927772a24 --- /dev/null +++ b/test/release/release_suite_test.go @@ -0,0 +1,13 @@ +// +build release + +package release_test + +import ( + "testing" + + "github.com/kumahq/kuma/pkg/test" +) + +func TestRelease(t *testing.T) { + test.RunSpecs(t, "Release Suite") +}