From 02e936741d80dc91789e11a61d41ed430eb36d12 Mon Sep 17 00:00:00 2001 From: Kobi Levi <56400138+levikobi@users.noreply.github.com> Date: Thu, 16 May 2024 00:24:24 +0300 Subject: [PATCH] feat: add supported features to gateway class (#2491) * feat: add supported features to gateway class Signed-off-by: Kobi Levi * remove skipped test features from supported features list Signed-off-by: Kobi Levi --------- Signed-off-by: Kobi Levi --- .../translate/out/default-resources.all.yaml | 21 +++++++ .../out/echo-gateway-api.cluster.yaml | 21 +++++++ .../translate/out/echo-gateway-api.route.json | 22 +++++++ .../translate/out/invalid-envoyproxy.all.yaml | 21 +++++++ .../out/rejected-http-route.route.yaml | 21 +++++++ .../translate/out/valid-envoyproxy.all.yaml | 21 +++++++ internal/cmd/egctl/translate_test.go | 8 +++ internal/gatewayapi/conformance/features.go | 37 ++++++++++++ internal/gatewayapi/status/gatewayclass.go | 26 ++++++++ .../gatewayapi/status/gatewayclass_test.go | 59 +++++++++++++++++++ .../provider/kubernetes/controller_test.go | 5 ++ test/conformance/conformance_test.go | 13 ++-- .../experimental_conformance_test.go | 13 ++-- 13 files changed, 272 insertions(+), 16 deletions(-) create mode 100644 internal/gatewayapi/conformance/features.go diff --git a/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml b/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml index cacca5bb7c4..de7dfd0e9c5 100644 --- a/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml @@ -158,6 +158,27 @@ gatewayClass: reason: Accepted status: "True" type: Accepted + supportedFeatures: + - GRPCRoute + - GatewayPort8080 + - HTTPRouteBackendProtocolH2C + - HTTPRouteBackendProtocolWebSocket + - HTTPRouteBackendTimeout + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRouteParentRefPort + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteRequestTimeout + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - ReferenceGrant + - TLSRoute gateways: - metadata: creationTimestamp: null diff --git a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml index 3d88f20f51d..6afabda49f4 100644 --- a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml +++ b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml @@ -12,6 +12,27 @@ gatewayClass: reason: Accepted status: "True" type: Accepted + supportedFeatures: + - GRPCRoute + - GatewayPort8080 + - HTTPRouteBackendProtocolH2C + - HTTPRouteBackendProtocolWebSocket + - HTTPRouteBackendTimeout + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRouteParentRefPort + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteRequestTimeout + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - ReferenceGrant + - TLSRoute gateways: - metadata: creationTimestamp: null diff --git a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json index 41dfd6683e7..472c02fcfdb 100644 --- a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json +++ b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json @@ -17,6 +17,28 @@ "reason": "Accepted", "message": "Valid GatewayClass" } + ], + "supportedFeatures": [ + "GRPCRoute", + "GatewayPort8080", + "HTTPRouteBackendProtocolH2C", + "HTTPRouteBackendProtocolWebSocket", + "HTTPRouteBackendTimeout", + "HTTPRouteDestinationPortMatching", + "HTTPRouteHostRewrite", + "HTTPRouteMethodMatching", + "HTTPRouteParentRefPort", + "HTTPRoutePathRedirect", + "HTTPRoutePathRewrite", + "HTTPRoutePortRedirect", + "HTTPRouteQueryParamMatching", + "HTTPRouteRequestMirror", + "HTTPRouteRequestMultipleMirrors", + "HTTPRouteRequestTimeout", + "HTTPRouteResponseHeaderModification", + "HTTPRouteSchemeRedirect", + "ReferenceGrant", + "TLSRoute" ] } }, diff --git a/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml b/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml index c7ad9cde133..a15d2481e9a 100644 --- a/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml @@ -38,6 +38,27 @@ gatewayClass: reason: InvalidParameters status: "False" type: Accepted + supportedFeatures: + - GRPCRoute + - GatewayPort8080 + - HTTPRouteBackendProtocolH2C + - HTTPRouteBackendProtocolWebSocket + - HTTPRouteBackendTimeout + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRouteParentRefPort + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteRequestTimeout + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - ReferenceGrant + - TLSRoute gateways: - metadata: creationTimestamp: null diff --git a/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml b/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml index c578d14aef5..f45a99fa577 100644 --- a/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml +++ b/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml @@ -12,6 +12,27 @@ gatewayClass: reason: Accepted status: "True" type: Accepted + supportedFeatures: + - GRPCRoute + - GatewayPort8080 + - HTTPRouteBackendProtocolH2C + - HTTPRouteBackendProtocolWebSocket + - HTTPRouteBackendTimeout + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRouteParentRefPort + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteRequestTimeout + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - ReferenceGrant + - TLSRoute gateways: - metadata: creationTimestamp: null diff --git a/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml b/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml index ef42d68c93e..feef2037c5b 100644 --- a/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml @@ -31,6 +31,27 @@ gatewayClass: reason: Accepted status: "True" type: Accepted + supportedFeatures: + - GRPCRoute + - GatewayPort8080 + - HTTPRouteBackendProtocolH2C + - HTTPRouteBackendProtocolWebSocket + - HTTPRouteBackendTimeout + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRouteParentRefPort + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteRequestTimeout + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - ReferenceGrant + - TLSRoute gateways: - metadata: creationTimestamp: null diff --git a/internal/cmd/egctl/translate_test.go b/internal/cmd/egctl/translate_test.go index 96707159a99..c7d51b0e83a 100644 --- a/internal/cmd/egctl/translate_test.go +++ b/internal/cmd/egctl/translate_test.go @@ -22,6 +22,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" + "github.com/envoyproxy/gateway/internal/gatewayapi/status" "github.com/envoyproxy/gateway/internal/utils/field" "github.com/envoyproxy/gateway/internal/utils/file" ) @@ -349,6 +350,13 @@ func TestTranslate(t *testing.T) { } want := &TranslationResult{} mustUnmarshal(t, requireTestDataOutFile(t, fn), want) + + // Supported features are dynamic, instead of hard-coding them in the output files + // we define them here. + if want.GatewayClass != nil { + want.GatewayClass.Status.SupportedFeatures = status.GatewaySupportedFeatures + } + opts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") require.Empty(t, cmp.Diff(want, got, opts)) }) diff --git a/internal/gatewayapi/conformance/features.go b/internal/gatewayapi/conformance/features.go new file mode 100644 index 00000000000..93b13751d7c --- /dev/null +++ b/internal/gatewayapi/conformance/features.go @@ -0,0 +1,37 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package conformance + +import ( + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/gateway-api/conformance/tests" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" +) + +// SkipTests is a list of tests that are skipped in the conformance suite. +var SkipTests = []suite.ConformanceTest{ + tests.GatewayStaticAddresses, + tests.GatewayHTTPListenerIsolation, // https://github.com/kubernetes-sigs/gateway-api/issues/3049 + tests.HTTPRouteBackendRequestHeaderModifier, // https://github.com/envoyproxy/gateway/issues/3338 +} + +func skipTestsShortNames(skipTests []suite.ConformanceTest) []string { + shortNames := make([]string, len(skipTests)) + for i, test := range skipTests { + shortNames[i] = test.ShortName + } + return shortNames +} + +// EnvoyGatewaySuite is the conformance suite configuration for the Gateway API. +var EnvoyGatewaySuite = suite.ConformanceOptions{ + SupportedFeatures: features.AllFeatures, + ExemptFeatures: sets.New[features.SupportedFeature](). + Insert(features.MeshCoreFeatures.UnsortedList()...). + Insert(features.MeshExtendedFeatures.UnsortedList()...), + SkipTests: skipTestsShortNames(SkipTests), +} diff --git a/internal/gatewayapi/status/gatewayclass.go b/internal/gatewayapi/status/gatewayclass.go index 35813c4bc40..9bcc76f3a0c 100644 --- a/internal/gatewayapi/status/gatewayclass.go +++ b/internal/gatewayapi/status/gatewayclass.go @@ -17,7 +17,11 @@ import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + + "github.com/envoyproxy/gateway/internal/gatewayapi/conformance" ) const ( @@ -32,6 +36,7 @@ const ( // for the provided GatewayClass. func SetGatewayClassAccepted(gc *gwapiv1.GatewayClass, accepted bool, reason, msg string) *gwapiv1.GatewayClass { gc.Status.Conditions = MergeConditions(gc.Status.Conditions, computeGatewayClassAcceptedCondition(gc, accepted, reason, msg)) + gc.Status.SupportedFeatures = GatewaySupportedFeatures return gc } @@ -61,3 +66,24 @@ func computeGatewayClassAcceptedCondition(gatewayClass *gwapiv1.GatewayClass, } } } + +// GatewaySupportedFeatures is a list of supported Gateway-API features, +// based on the running conformance tests suite. +var GatewaySupportedFeatures = getSupportedFeatures(conformance.EnvoyGatewaySuite, conformance.SkipTests) + +func getSupportedFeatures(gatewaySuite suite.ConformanceOptions, skippedTests []suite.ConformanceTest) []gwapiv1.SupportedFeature { + supportedFeatures := gatewaySuite.SupportedFeatures.Clone() + supportedFeatures.Delete(gatewaySuite.ExemptFeatures.UnsortedList()...) + + for _, skippedTest := range skippedTests { + for _, feature := range skippedTest.Features { + supportedFeatures.Delete(feature) + } + } + + ret := sets.New[gwapiv1.SupportedFeature]() + for _, feature := range supportedFeatures.UnsortedList() { + ret.Insert(gwapiv1.SupportedFeature(feature)) + } + return sets.List(ret) +} diff --git a/internal/gatewayapi/status/gatewayclass_test.go b/internal/gatewayapi/status/gatewayclass_test.go index 7b845408ecc..0ccce426353 100644 --- a/internal/gatewayapi/status/gatewayclass_test.go +++ b/internal/gatewayapi/status/gatewayclass_test.go @@ -10,7 +10,11 @@ import ( "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" ) func TestComputeGatewayClassAcceptedCondition(t *testing.T) { @@ -66,3 +70,58 @@ func TestComputeGatewayClassAcceptedCondition(t *testing.T) { assert.Equal(t, gc.Generation, got.ObservedGeneration) } } + +func TestGetSupportedFeatures(t *testing.T) { + testCases := []struct { + name string + gatewaySuite suite.ConformanceOptions + skippedTests []suite.ConformanceTest + expectedResult []gwapiv1.SupportedFeature + }{ + { + name: "No exempt features", + gatewaySuite: suite.ConformanceOptions{ + SupportedFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute"), + ExemptFeatures: sets.New[features.SupportedFeature](), + }, + expectedResult: []gwapiv1.SupportedFeature{"Gateway", "HTTPRoute"}, + }, + { + name: "All features exempt", + gatewaySuite: suite.ConformanceOptions{ + SupportedFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute"), + ExemptFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute"), + }, + expectedResult: []gwapiv1.SupportedFeature{}, + }, + { + name: "Some features exempt", + gatewaySuite: suite.ConformanceOptions{ + SupportedFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute", "GRPCRoute"), + ExemptFeatures: sets.New[features.SupportedFeature]("GRPCRoute"), + }, + expectedResult: []gwapiv1.SupportedFeature{"Gateway", "HTTPRoute"}, + }, + { + name: "Some features exempt with skipped tests", + gatewaySuite: suite.ConformanceOptions{ + SupportedFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute", "GRPCRoute"), + ExemptFeatures: sets.New[features.SupportedFeature]("GRPCRoute"), + }, + skippedTests: []suite.ConformanceTest{ + { + Features: []features.SupportedFeature{"HTTPRoute"}, + }, + }, + expectedResult: []gwapiv1.SupportedFeature{"Gateway"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := getSupportedFeatures(tc.gatewaySuite, tc.skippedTests) + + assert.ElementsMatch(t, tc.expectedResult, result, "The result should match the expected output for the test case.") + }) + } +} diff --git a/internal/provider/kubernetes/controller_test.go b/internal/provider/kubernetes/controller_test.go index 8e3a5f9b56f..860ba09e4b1 100644 --- a/internal/provider/kubernetes/controller_test.go +++ b/internal/provider/kubernetes/controller_test.go @@ -20,6 +20,7 @@ import ( "github.com/envoyproxy/gateway/internal/envoygateway" "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/gatewayapi" + "github.com/envoyproxy/gateway/internal/gatewayapi/status" "github.com/envoyproxy/gateway/internal/logging" ) @@ -189,6 +190,7 @@ func TestHasManagedClass(t *testing.T) { Status: metav1.ConditionTrue, }, }, + SupportedFeatures: status.GatewaySupportedFeatures, }, }, }, @@ -223,6 +225,7 @@ func TestHasManagedClass(t *testing.T) { Status: metav1.ConditionTrue, }, }, + SupportedFeatures: status.GatewaySupportedFeatures, }, }, }, @@ -275,6 +278,7 @@ func TestHasManagedClass(t *testing.T) { Status: metav1.ConditionTrue, }, }, + SupportedFeatures: status.GatewaySupportedFeatures, }, }, { @@ -297,6 +301,7 @@ func TestHasManagedClass(t *testing.T) { Status: metav1.ConditionFalse, }, }, + SupportedFeatures: status.GatewaySupportedFeatures, }, }, }, diff --git a/test/conformance/conformance_test.go b/test/conformance/conformance_test.go index 9faa95cf19b..ce7dce052ab 100644 --- a/test/conformance/conformance_test.go +++ b/test/conformance/conformance_test.go @@ -15,20 +15,17 @@ import ( "sigs.k8s.io/gateway-api/conformance" "sigs.k8s.io/gateway-api/conformance/tests" "sigs.k8s.io/gateway-api/conformance/utils/suite" - "sigs.k8s.io/gateway-api/pkg/features" + + internalconf "github.com/envoyproxy/gateway/internal/gatewayapi/conformance" ) func TestGatewayAPIConformance(t *testing.T) { flag.Parse() opts := conformance.DefaultOptions(t) - opts.SkipTests = []string{ - tests.GatewayStaticAddresses.ShortName, - tests.GatewayHTTPListenerIsolation.ShortName, // https://github.com/kubernetes-sigs/gateway-api/issues/3049 - tests.HTTPRouteBackendRequestHeaderModifier.ShortName, // https://github.com/envoyproxy/gateway/issues/3338 - } - opts.SupportedFeatures = features.AllFeatures - opts.ExemptFeatures = features.MeshCoreFeatures + opts.SkipTests = internalconf.EnvoyGatewaySuite.SkipTests + opts.SupportedFeatures = internalconf.EnvoyGatewaySuite.SupportedFeatures + opts.ExemptFeatures = internalconf.EnvoyGatewaySuite.ExemptFeatures cSuite, err := suite.NewConformanceTestSuite(opts) if err != nil { diff --git a/test/conformance/experimental_conformance_test.go b/test/conformance/experimental_conformance_test.go index 702e919bca4..770abbc1c8a 100644 --- a/test/conformance/experimental_conformance_test.go +++ b/test/conformance/experimental_conformance_test.go @@ -20,21 +20,18 @@ import ( "sigs.k8s.io/gateway-api/conformance/tests" "sigs.k8s.io/gateway-api/conformance/utils/flags" "sigs.k8s.io/gateway-api/conformance/utils/suite" - "sigs.k8s.io/gateway-api/pkg/features" "sigs.k8s.io/yaml" + + internalconf "github.com/envoyproxy/gateway/internal/gatewayapi/conformance" ) func TestExperimentalConformance(t *testing.T) { flag.Parse() opts := conformance.DefaultOptions(t) - opts.SkipTests = []string{ - tests.GatewayStaticAddresses.ShortName, - tests.GatewayHTTPListenerIsolation.ShortName, // https://github.com/kubernetes-sigs/gateway-api/issues/3049 - tests.HTTPRouteBackendRequestHeaderModifier.ShortName, // https://github.com/envoyproxy/gateway/issues/3338 - } - opts.SupportedFeatures = features.AllFeatures - opts.ExemptFeatures = features.MeshCoreFeatures + opts.SkipTests = internalconf.EnvoyGatewaySuite.SkipTests + opts.SupportedFeatures = internalconf.EnvoyGatewaySuite.SupportedFeatures + opts.ExemptFeatures = internalconf.EnvoyGatewaySuite.ExemptFeatures opts.ConformanceProfiles = sets.New( suite.GatewayHTTPConformanceProfileName, suite.GatewayTLSConformanceProfileName,