diff --git a/apis/v1/grpcroute_types.go b/apis/v1/grpcroute_types.go index 0cae5e5020..c6eeb0f3ea 100644 --- a/apis/v1/grpcroute_types.go +++ b/apis/v1/grpcroute_types.go @@ -156,7 +156,6 @@ type GRPCRouteRule struct { // // Support: Extended // +optional - // Name *SectionName `json:"name,omitempty"` // Matches define conditions used for matching the rule against incoming diff --git a/apis/v1/httproute_types.go b/apis/v1/httproute_types.go index 6fec27d6e0..f0aa550559 100644 --- a/apis/v1/httproute_types.go +++ b/apis/v1/httproute_types.go @@ -138,7 +138,6 @@ type HTTPRouteRule struct { // // Support: Extended // +optional - // Name *SectionName `json:"name,omitempty"` // Matches define conditions used for matching the rule against incoming diff --git a/config/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml b/config/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml index 3bbc0af4ca..4f7d8f1323 100644 --- a/config/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml +++ b/config/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml @@ -1760,6 +1760,15 @@ spec: type: object maxItems: 64 type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string type: object maxItems: 16 type: array diff --git a/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml b/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml index 7f3b970a7a..8533381fc2 100644 --- a/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml +++ b/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml @@ -2432,6 +2432,15 @@ spec: type: object maxItems: 64 type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string timeouts: description: |- Timeouts defines the timeouts that can be configured for an HTTP request. @@ -5227,6 +5236,15 @@ spec: type: object maxItems: 64 type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string timeouts: description: |- Timeouts defines the timeouts that can be configured for an HTTP request. diff --git a/conformance/tests/grpcroute-named-rule.go b/conformance/tests/grpcroute-named-rule.go new file mode 100644 index 0000000000..223e22b595 --- /dev/null +++ b/conformance/tests/grpcroute-named-rule.go @@ -0,0 +1,72 @@ +/* +Copyright 2025 The Kubernetes Authors. + +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 tests + +import ( + "testing" + + "k8s.io/apimachinery/pkg/types" + + v1 "sigs.k8s.io/gateway-api/apis/v1" + pb "sigs.k8s.io/gateway-api/conformance/echo-basic/grpcechoserver" + "sigs.k8s.io/gateway-api/conformance/utils/grpc" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" +) + +func init() { + ConformanceTests = append(ConformanceTests, GRPCRouteNamedRule) +} + +var GRPCRouteNamedRule = suite.ConformanceTest{ + ShortName: "GRPCRouteNamedRule", + Description: "An GRPCRoute with a named GRPCRouteRule", + Manifests: []string{"tests/grpcroute-named-rule.yaml"}, + Features: []features.FeatureName{ + features.SupportGateway, + features.SupportGRPCRoute, + features.SupportGRPCRouteNamedRouteRule, + }, + Provisional: true, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "grpc-named-rules", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &v1.GRPCRoute{}, routeNN) + + testCases := []grpc.ExpectedResponse{ + { + EchoRequest: &pb.EchoRequest{}, + Backend: "grpc-infra-backend-v1", + Namespace: ns, + }, { + EchoTwoRequest: &pb.EchoRequest{}, + Backend: "grpc-infra-backend-v2", + Namespace: ns, + }, + } + + for i := range testCases { + tc := testCases[i] + t.Run(tc.GetTestCaseName(i), func(t *testing.T) { + t.Parallel() + grpc.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.GRPCClient, suite.TimeoutConfig, gwAddr, tc) + }) + } + }, +} diff --git a/conformance/tests/grpcroute-named-rule.yaml b/conformance/tests/grpcroute-named-rule.yaml new file mode 100644 index 0000000000..fac94dbe2c --- /dev/null +++ b/conformance/tests/grpcroute-named-rule.yaml @@ -0,0 +1,24 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: GRPCRoute +metadata: + name: grpc-named-rules + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - name: named-rule + matches: + - method: + service: gateway_api_conformance.echo_basic.grpcecho.GrpcEcho + method: Echo + backendRefs: + - name: grpc-infra-backend-v1 + port: 8080 + - matches: + - method: + service: gateway_api_conformance.echo_basic.grpcecho.GrpcEcho + method: EchoTwo + backendRefs: + - name: grpc-infra-backend-v2 + port: 8080 diff --git a/conformance/tests/httproute-named-rule.go b/conformance/tests/httproute-named-rule.go new file mode 100644 index 0000000000..77cdccc567 --- /dev/null +++ b/conformance/tests/httproute-named-rule.go @@ -0,0 +1,71 @@ +/* +Copyright 2025 The Kubernetes Authors. + +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 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" + "sigs.k8s.io/gateway-api/pkg/features" +) + +func init() { + ConformanceTests = append(ConformanceTests, HTTPRouteNamedRule) +} + +var HTTPRouteNamedRule = suite.ConformanceTest{ + ShortName: "HTTPRouteNamedRule", + Description: "An HTTPRoute with a named HTTPRouteRule", + Manifests: []string{"tests/httproute-named-rule.yaml"}, + Features: []features.FeatureName{ + features.SupportGateway, + features.SupportHTTPRoute, + features.SupportHTTPRouteNamedRouteRule, + }, + Provisional: true, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "http-named-rules", 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: "/named"}, + Response: http.Response{StatusCode: 200}, + Namespace: ns, + }, { + Request: http.Request{Path: "/unnamed"}, + Response: http.Response{StatusCode: 200}, + Namespace: ns, + }, + } + + for i := range testCases { + tc := testCases[i] + t.Run(tc.GetTestCaseName(i), func(t *testing.T) { + t.Parallel() + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc) + }) + } + }, +} diff --git a/conformance/tests/httproute-named-rule.yaml b/conformance/tests/httproute-named-rule.yaml new file mode 100644 index 0000000000..eb95c2cf03 --- /dev/null +++ b/conformance/tests/httproute-named-rule.yaml @@ -0,0 +1,24 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-named-rules + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - name: named-rule + matches: + - path: + type: PathPrefix + value: /named + backendRefs: + - name: infra-backend-v1 + port: 8080 + - matches: + - path: + type: PathPrefix + value: /unnamed + backendRefs: + - name: infra-backend-v2 + port: 8080 diff --git a/conformance/tests/mesh/httproute-named-rule.go b/conformance/tests/mesh/httproute-named-rule.go new file mode 100644 index 0000000000..9c7faf122e --- /dev/null +++ b/conformance/tests/mesh/httproute-named-rule.go @@ -0,0 +1,68 @@ +/* +Copyright 2025 The Kubernetes Authors. + +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 meshtests + +import ( + "testing" + + "sigs.k8s.io/gateway-api/conformance/utils/echo" + "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" +) + +func init() { + MeshConformanceTests = append(MeshConformanceTests, MeshHTTPRouteNamedRule) +} + +var MeshHTTPRouteNamedRule = suite.ConformanceTest{ + ShortName: "MeshHTTPRouteNamedRule", + Description: "An HTTPRoute with a named HTTPRouteRule", + Manifests: []string{"tests/mesh/httproute-named-rule.yaml"}, + Features: []features.FeatureName{ + features.SupportMesh, + features.SupportHTTPRoute, + features.SupportMeshHTTPRouteNamedRouteRule, + }, + Provisional: true, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-mesh" + client := echo.ConnectToApp(t, suite, echo.MeshAppEchoV1) + + testCases := []http.ExpectedResponse{ + { + Request: http.Request{Path: "/named"}, + ExpectedRequest: &http.ExpectedRequest{Request: http.Request{Path: "/named"}}, + Backend: "echo-v1", + Namespace: ns, + }, { + Request: http.Request{Path: "/unnamed"}, + ExpectedRequest: &http.ExpectedRequest{Request: http.Request{Path: "/named"}}, + Backend: "echo-v2", + Namespace: ns, + }, + } + + for i := range testCases { + tc := testCases[i] + t.Run(tc.GetTestCaseName(i), func(t *testing.T) { + t.Parallel() + client.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, suite.TimeoutConfig) + }) + } + }, +} diff --git a/conformance/tests/mesh/httproute-named-rule.yaml b/conformance/tests/mesh/httproute-named-rule.yaml new file mode 100644 index 0000000000..9075968512 --- /dev/null +++ b/conformance/tests/mesh/httproute-named-rule.yaml @@ -0,0 +1,27 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: mesh-http-named-rules + namespace: gateway-conformance-mesh +spec: + parentRefs: + - group: "" + kind: Service + name: echo + port: 80 + rules: + - name: named-rule + matches: + - path: + type: PathPrefix + value: /named + backendRefs: + - name: echo-v1 + port: 8080 + - matches: + - path: + type: PathPrefix + value: /unnamed + backendRefs: + - name: echo-v2 + port: 8080 diff --git a/geps/gep-995/index.md b/geps/gep-995/index.md index 6236d1a279..9df7f3abaa 100644 --- a/geps/gep-995/index.md +++ b/geps/gep-995/index.md @@ -1,7 +1,7 @@ # GEP-995: Named route rules * Issue: [#995](https://github.com/kubernetes-sigs/gateway-api/issues/995) -* Status: Experimental +* Status: Standard ## TLDR diff --git a/geps/gep-995/metadata.yaml b/geps/gep-995/metadata.yaml index 547d7bfdfa..2552744a95 100644 --- a/geps/gep-995/metadata.yaml +++ b/geps/gep-995/metadata.yaml @@ -2,7 +2,7 @@ apiVersion: internal.gateway.networking.k8s.io/v1alpha1 kind: GEPDetails number: 995 name: Named route rules -status: Experimental +status: Standard authors: - guicassolato changelog: diff --git a/mkdocs.yml b/mkdocs.yml index 4a4a7de35d..308f7b7e0b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -134,7 +134,6 @@ nav: - geps/gep-91/index.md - geps/gep-3567/index.md - Experimental: - - geps/gep-995/index.md - geps/gep-1619/index.md - geps/gep-1713/index.md - geps/gep-1731/index.md @@ -155,6 +154,7 @@ nav: - geps/gep-820/index.md - geps/gep-851/index.md - geps/gep-957/index.md + - geps/gep-995/index.md - geps/gep-1016/index.md - geps/gep-1294/index.md - geps/gep-1323/index.md diff --git a/pkg/features/grpcroute.go b/pkg/features/grpcroute.go index ffa3f38318..3ee21b41f5 100644 --- a/pkg/features/grpcroute.go +++ b/pkg/features/grpcroute.go @@ -19,7 +19,7 @@ package features import "k8s.io/apimachinery/pkg/util/sets" // ----------------------------------------------------------------------------- -// Features - GRPCRoute Conformance +// Features - GRPCRoute Conformance (Core) // ----------------------------------------------------------------------------- const ( @@ -38,3 +38,25 @@ var GRPCRouteFeature = Feature{ var GRPCRouteCoreFeatures = sets.New( GRPCRouteFeature, ) + +// ----------------------------------------------------------------------------- +// Features - GRPCRoute Conformance (Extended) +// ----------------------------------------------------------------------------- + +const ( + // This option indicates support for the name field in the GRPCRouteRule (extended conformance) + SupportGRPCRouteNamedRouteRule FeatureName = "GRPCRouteNamedRouteRule" +) + +// GRPCRouteNamedRouteRule contains metadata for the SupportGRPCRouteNamedRouteRule feature. +var GRPCRouteNamedRouteRule = Feature{ + Name: SupportGRPCRouteNamedRouteRule, + Channel: FeatureChannelStandard, +} + +// GRPCRouteExtendedFeatures includes all extended features for GRPCRoute +// conformance and can be used to opt-in to run all GRPCRoute extended features tests. +// This does not include any Core Features. +var GRPCRouteExtendedFeatures = sets.New( + GRPCRouteNamedRouteRule, +) diff --git a/pkg/features/httproute.go b/pkg/features/httproute.go index dbc62ce1a0..6c484da96c 100644 --- a/pkg/features/httproute.go +++ b/pkg/features/httproute.go @@ -97,6 +97,9 @@ const ( // This option indicates support for HTTPRoute with a backendref with an appProtocol 'kubernetes.io/ws' (extended conformance) SupportHTTPRouteBackendProtocolWebSocket FeatureName = "HTTPRouteBackendProtocolWebSocket" + + // This option indicates support for the name field in the HTTPRouteRule (extended conformance) + SupportHTTPRouteNamedRouteRule FeatureName = "HTTPRouteNamedRouteRule" ) var ( @@ -190,6 +193,11 @@ var ( Name: SupportHTTPRouteBackendProtocolWebSocket, Channel: FeatureChannelStandard, } + // HTTPRouteNamedRouteRule contains metadata for the SupportHTTPRouteNamedRouteRule feature. + HTTPRouteNamedRouteRule = Feature{ + Name: SupportHTTPRouteNamedRouteRule, + Channel: FeatureChannelStandard, + } ) // HTTPRouteExtendedFeatures includes all extended features for HTTPRoute @@ -214,4 +222,5 @@ var HTTPRouteExtendedFeatures = sets.New( HTTPRouteParentRefPortFeature, HTTPRouteBackendProtocolH2CFeature, HTTPRouteBackendProtocolWebSocketFeature, + HTTPRouteNamedRouteRule, ) diff --git a/pkg/features/mesh.go b/pkg/features/mesh.go index d191dc3846..c87e253c87 100644 --- a/pkg/features/mesh.go +++ b/pkg/features/mesh.go @@ -60,6 +60,8 @@ const ( SupportMeshHTTPRouteBackendRequestHeaderModification FeatureName = "MeshHTTPRouteBackendRequestHeaderModification" // This option indicates mesh support for HTTPRoute query param matching (extended conformance). SupportMeshHTTPRouteQueryParamMatching FeatureName = "MeshHTTPRouteQueryParamMatching" + // This option indicates support for the name field in the HTTPRouteRule (extended conformance) + SupportMeshHTTPRouteNamedRouteRule FeatureName = "MeshHTTPRouteNamedRouteRule" ) var ( @@ -103,11 +105,18 @@ var ( Name: SupportMeshHTTPRouteBackendRequestHeaderModification, Channel: FeatureChannelStandard, } + // MeshHTTPRouteRedirectPath contains metadata for the MeshHTTPRouteRedirectPath feature. MeshHTTPRouteQueryParamMatching = Feature{ Name: SupportMeshHTTPRouteQueryParamMatching, Channel: FeatureChannelStandard, } + + // MeshHTTPRouteNamedRouteRule contains metadata for the MeshHTTPRouteNamedRouteRule feature. + MeshHTTPRouteNamedRouteRule = Feature{ + Name: SupportMeshHTTPRouteNamedRouteRule, + Channel: FeatureChannelStandard, + } ) // MeshExtendedFeatures includes all the supported features for the service mesh at @@ -120,4 +129,5 @@ var MeshExtendedFeatures = sets.New( MeshHTTPRouteRedirectPath, MeshHTTPRouteBackendRequestHeaderModification, MeshHTTPRouteQueryParamMatching, + MeshHTTPRouteNamedRouteRule, ) diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index f4a7d0d4f2..64865fea92 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -3409,7 +3409,7 @@ func schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteRule(ref common.ReferenceCall Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "Name is the name of the route rule. This name MUST be unique within a Route if it is set.\n\nSupport: Extended ", + Description: "Name is the name of the route rule. This name MUST be unique within a Route if it is set.\n\nSupport: Extended", Type: []string{"string"}, Format: "", }, @@ -4988,7 +4988,7 @@ func schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteRule(ref common.ReferenceCall Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "Name is the name of the route rule. This name MUST be unique within a Route if it is set.\n\nSupport: Extended ", + Description: "Name is the name of the route rule. This name MUST be unique within a Route if it is set.\n\nSupport: Extended", Type: []string{"string"}, Format: "", },