diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 2f570c2d4fed..ee41a92c24a9 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -179,4 +179,4 @@ jobs: if: github.event_name == 'push' && github.ref == 'refs/heads/main' # use `0.0.0` as the default latest version. # use `Always` image pull policy for latest version. - run: IMAGE_PULL_POLICY=Always OCI_REGISTRY=oci://docker.io/envoyproxy CHART_VERSION=v0.0.0-latest TAG=latest make helm-package helm-push + run: IMAGE_PULL_POLICY=Always OCI_REGISTRY=oci://docker.io/envoyproxy CHART_VERSION=v0.0.0-latest TAG=latest make helm-push 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 3954e3e411e5..1e85fb5ffab9 100644 --- a/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml @@ -160,7 +160,9 @@ gatewayClass: type: Accepted supportedFeatures: - GRPCRoute + - Gateway - GatewayPort8080 + - HTTPRoute - HTTPRouteBackendProtocolH2C - HTTPRouteBackendProtocolWebSocket - HTTPRouteBackendTimeout 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 6afabda49f4c..85b5c588ecbf 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 @@ -14,7 +14,9 @@ gatewayClass: type: Accepted supportedFeatures: - GRPCRoute + - Gateway - GatewayPort8080 + - HTTPRoute - HTTPRouteBackendProtocolH2C - HTTPRouteBackendProtocolWebSocket - HTTPRouteBackendTimeout 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 472c02fcfdbd..663ce5404e73 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 @@ -20,7 +20,9 @@ ], "supportedFeatures": [ "GRPCRoute", + "Gateway", "GatewayPort8080", + "HTTPRoute", "HTTPRouteBackendProtocolH2C", "HTTPRouteBackendProtocolWebSocket", "HTTPRouteBackendTimeout", 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 a15d2481e9a1..41be265b3657 100644 --- a/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml @@ -40,7 +40,9 @@ gatewayClass: type: Accepted supportedFeatures: - GRPCRoute + - Gateway - GatewayPort8080 + - HTTPRoute - HTTPRouteBackendProtocolH2C - HTTPRouteBackendProtocolWebSocket - HTTPRouteBackendTimeout 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 f45a99fa577c..1dd8cc46a939 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 @@ -14,7 +14,9 @@ gatewayClass: type: Accepted supportedFeatures: - GRPCRoute + - Gateway - GatewayPort8080 + - HTTPRoute - HTTPRouteBackendProtocolH2C - HTTPRouteBackendProtocolWebSocket - HTTPRouteBackendTimeout 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 feef2037c5b8..53dcd5748426 100644 --- a/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml @@ -33,7 +33,9 @@ gatewayClass: type: Accepted supportedFeatures: - GRPCRoute + - Gateway - GatewayPort8080 + - HTTPRoute - HTTPRouteBackendProtocolH2C - HTTPRouteBackendProtocolWebSocket - HTTPRouteBackendTimeout diff --git a/internal/gatewayapi/conformance/features.go b/internal/gatewayapi/conformance/suite.go similarity index 100% rename from internal/gatewayapi/conformance/features.go rename to internal/gatewayapi/conformance/suite.go diff --git a/internal/gatewayapi/conformance/support_level.go b/internal/gatewayapi/conformance/support_level.go new file mode 100644 index 000000000000..938b90dcf009 --- /dev/null +++ b/internal/gatewayapi/conformance/support_level.go @@ -0,0 +1,55 @@ +// 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/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" +) + +// SupportLevel represents the level of support for a feature. +// See https://gateway-api.sigs.k8s.io/concepts/conformance/#2-support-levels. +type SupportLevel string + +const ( + // Core features are portable and expected to be supported by every implementation of Gateway-API. + Core SupportLevel = "core" + + // Extended features are those that are portable but not universally supported across implementations. + // Those implementations that support the feature will have the same behavior and semantics. + // It is expected that some number of roadmap features will eventually migrate into the Core. + Extended SupportLevel = "extended" +) + +// ExtendedFeatures is a list of supported Gateway-API features that are considered Extended. +var ExtendedFeatures = sets.New[features.SupportedFeature](). + Insert(features.GatewayExtendedFeatures.UnsortedList()...). + Insert(features.HTTPRouteExtendedFeatures.UnsortedList()...). + Insert(features.MeshExtendedFeatures.UnsortedList()...) + +// GetTestSupportLevel returns the SupportLevel for a conformance test. +// The support level is determined by the highest support level of the features. +func GetTestSupportLevel(test suite.ConformanceTest) SupportLevel { + supportLevel := Core + + if ExtendedFeatures.HasAny(test.Features...) { + supportLevel = Extended + } + + return supportLevel +} + +// GetFeatureSupportLevel returns the SupportLevel for a feature. +func GetFeatureSupportLevel(feature features.SupportedFeature) SupportLevel { + supportLevel := Core + + if ExtendedFeatures.Has(feature) { + supportLevel = Extended + } + + return supportLevel +} diff --git a/internal/gatewayapi/status/gatewayclass.go b/internal/gatewayapi/status/gatewayclass.go index 9bcc76f3a0c8..ad8a032a27fb 100644 --- a/internal/gatewayapi/status/gatewayclass.go +++ b/internal/gatewayapi/status/gatewayclass.go @@ -20,6 +20,7 @@ import ( "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" "github.com/envoyproxy/gateway/internal/gatewayapi/conformance" ) @@ -73,13 +74,8 @@ var GatewaySupportedFeatures = getSupportedFeatures(conformance.EnvoyGatewaySuit 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) - } - } + unsupportedFeatures := getUnsupportedFeatures(gatewaySuite, skippedTests) + supportedFeatures.Delete(unsupportedFeatures...) ret := sets.New[gwapiv1.SupportedFeature]() for _, feature := range supportedFeatures.UnsortedList() { @@ -87,3 +83,22 @@ func getSupportedFeatures(gatewaySuite suite.ConformanceOptions, skippedTests [] } return sets.List(ret) } + +func getUnsupportedFeatures(gatewaySuite suite.ConformanceOptions, skippedTests []suite.ConformanceTest) []features.SupportedFeature { + unsupportedFeatures := gatewaySuite.ExemptFeatures.UnsortedList() + + for _, skippedTest := range skippedTests { + switch conformance.GetTestSupportLevel(skippedTest) { + case conformance.Core: + unsupportedFeatures = append(unsupportedFeatures, skippedTest.Features...) + case conformance.Extended: + for _, feature := range skippedTest.Features { + if conformance.GetFeatureSupportLevel(feature) == conformance.Extended { + unsupportedFeatures = append(unsupportedFeatures, feature) + } + } + } + } + + return unsupportedFeatures +} diff --git a/internal/gatewayapi/status/gatewayclass_test.go b/internal/gatewayapi/status/gatewayclass_test.go index 8e66d166f39a..f2a9dfa42e17 100644 --- a/internal/gatewayapi/status/gatewayclass_test.go +++ b/internal/gatewayapi/status/gatewayclass_test.go @@ -114,6 +114,30 @@ func TestGetSupportedFeatures(t *testing.T) { }, expectedResult: []gwapiv1.SupportedFeature{"Gateway"}, }, + { + name: "Core features remain supported with skipped extended tests", + gatewaySuite: suite.ConformanceOptions{ + SupportedFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute", "GatewayHTTPListenerIsolation"), + }, + skippedTests: []suite.ConformanceTest{ + { + Features: []features.SupportedFeature{"Gateway", "GatewayHTTPListenerIsolation", "HTTPRoute"}, + }, + }, + expectedResult: []gwapiv1.SupportedFeature{"Gateway", "HTTPRoute"}, + }, + { + name: "Core feature removed when skipping core test", + gatewaySuite: suite.ConformanceOptions{ + SupportedFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute"), + }, + skippedTests: []suite.ConformanceTest{ + { + Features: []features.SupportedFeature{"HTTPRoute"}, + }, + }, + expectedResult: []gwapiv1.SupportedFeature{"Gateway"}, + }, } for _, tc := range testCases { diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go index 55e6c258e65f..1765d2252eb7 100644 --- a/internal/xds/translator/route.go +++ b/internal/xds/translator/route.go @@ -386,7 +386,7 @@ func buildXdsURLRewriteAction(destName string, urlRewrite *ir.URLRewrite, pathMa if urlRewrite.Path.FullReplace != nil { routeAction.RegexRewrite = &matcherv3.RegexMatchAndSubstitute{ Pattern: &matcherv3.RegexMatcher{ - Regex: "/.+", + Regex: "^/.*$", }, Substitution: *urlRewrite.Path.FullReplace, } diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-fullpath.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-fullpath.routes.yaml index f8b81712daed..7c049365a8b1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-fullpath.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-url-fullpath.routes.yaml @@ -12,7 +12,7 @@ cluster: rewrite-route regexRewrite: pattern: - regex: /.+ + regex: ^/.*$ substitution: /rewrite upgradeConfigs: - upgradeType: websocket diff --git a/test/e2e/testdata/httproute-rewrite-full-path.yaml b/test/e2e/testdata/httproute-rewrite-full-path.yaml new file mode 100644 index 000000000000..7e8b1181609e --- /dev/null +++ b/test/e2e/testdata/httproute-rewrite-full-path.yaml @@ -0,0 +1,22 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: rewrite-full-path + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: PathPrefix + value: / + filters: + - type: URLRewrite + urlRewrite: + path: + type: ReplaceFullPath + replaceFullPath: /full-replace + backendRefs: + - name: infra-backend-v1 + port: 8080 diff --git a/test/e2e/tests/httproute-rewrite-full-path.go b/test/e2e/tests/httproute-rewrite-full-path.go new file mode 100644 index 000000000000..dd043571cbf0 --- /dev/null +++ b/test/e2e/tests/httproute-rewrite-full-path.go @@ -0,0 +1,59 @@ +// 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. + +//go:build e2e +// +build e2e + +package tests + +import ( + "testing" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" +) + +func init() { + ConformanceTests = append(ConformanceTests, HTTPRouteRewriteFullPath) +} + +var HTTPRouteRewriteFullPath = suite.ConformanceTest{ + ShortName: "HTTPRouteRewriteFullPath", + Description: "An HTTPRoute with path rewrite filter to replace full path", + Manifests: []string{"testdata/httproute-rewrite-full-path.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "rewrite-full-path", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) + + testCases := []http.ExpectedResponse{ + { + Request: http.Request{ + Path: "/", + }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Path: "/full-replace", + }, + }, + Backend: "infra-backend-v1", + Namespace: ns, + }, + } + for i := range testCases { + // Declare tc here to avoid loop variable + // reuse issues across parallel tests. + tc := testCases[i] + t.Run(tc.GetTestCaseName(i), func(t *testing.T) { + t.Parallel() + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc) + }) + } + }, +}