Documentation ¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ConformanceTests []suite.ConformanceTest
var GRPCExactMethodMatching = suite.ConformanceTest{ ShortName: "GRPCExactMethodMatching", Description: "A single GRPCRoute with exact method matching for different backends", Manifests: []string{"tests/grpcroute-exact-method-matching.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportGRPCRoute, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "exact-matching", 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, }, { EchoThreeRequest: &pb.EchoRequest{}, Response: grpc.Response{Code: codes.Unimplemented}, }, } 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) }) } }, }
var GRPCRouteHeaderMatching = suite.ConformanceTest{ ShortName: "GRPCRouteHeaderMatching", Description: "A single GRPCRoute with header matching for different backends", Manifests: []string{"tests/grpcroute-header-matching.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportGRPCRoute, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "grpc-header-matching", 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{}, RequestMetadata: &grpc.RequestMetadata{ Metadata: map[string]string{"Version": "one"}, }, Backend: "grpc-infra-backend-v1", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Metadata: map[string]string{"Version": "two"}, }, Backend: "grpc-infra-backend-v2", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Metadata: map[string]string{"Version": "two", "Color": "orange"}, }, Backend: "grpc-infra-backend-v1", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Metadata: map[string]string{"Version": "two", "Color": "blue"}, }, Backend: "grpc-infra-backend-v2", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Metadata: map[string]string{"Color": "orange"}, }, Response: grpc.Response{Code: codes.Unimplemented}, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Metadata: map[string]string{"Some-Other-Header": "one"}, }, Response: grpc.Response{Code: codes.Unimplemented}, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Metadata: map[string]string{"Color": "blue"}, }, Backend: "grpc-infra-backend-v1", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Metadata: map[string]string{"Color": "green"}, }, Backend: "grpc-infra-backend-v1", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Metadata: map[string]string{"Color": "red"}, }, Backend: "grpc-infra-backend-v2", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Metadata: map[string]string{"Color": "yellow"}, }, Backend: "grpc-infra-backend-v2", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Metadata: map[string]string{"Color": "purple"}, }, Response: grpc.Response{Code: codes.Unimplemented}, }} 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) }) } }, }
var GRPCRouteListenerHostnameMatching = suite.ConformanceTest{ ShortName: "GRPCRouteListenerHostnameMatching", Description: "Multiple GRPC listeners with the same port and different hostnames, each with a different GRPCRoute", Manifests: []string{"tests/grpcroute-listener-hostname-matching.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportGRPCRoute, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ns}) gwNN := types.NamespacedName{Name: "grpcroute-listener-hostname-matching", Namespace: ns} _ = kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN, "listener-1"), &v1.GRPCRoute{}, types.NamespacedName{Namespace: ns, Name: "backend-v1"}, ) _ = kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN, "listener-2"), &v1.GRPCRoute{}, types.NamespacedName{Namespace: ns, Name: "backend-v2"}, ) gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN, "listener-3", "listener-4"), &v1.GRPCRoute{}, types.NamespacedName{Namespace: ns, Name: "backend-v3"}, ) testCases := []grpc.ExpectedResponse{{ EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Authority: "bar.com", }, Backend: "grpc-infra-backend-v1", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Authority: "foo.bar.com", }, Backend: "grpc-infra-backend-v2", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Authority: "baz.bar.com", }, Backend: "grpc-infra-backend-v3", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Authority: "boo.bar.com", }, Backend: "grpc-infra-backend-v3", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Authority: "multiple.prefixes.bar.com", }, Backend: "grpc-infra-backend-v3", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Authority: "multiple.prefixes.foo.com", }, Backend: "grpc-infra-backend-v3", Namespace: ns, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Authority: "foo.com", }, Response: grpc.Response{Code: codes.Unimplemented}, }, { EchoRequest: &pb.EchoRequest{}, RequestMetadata: &grpc.RequestMetadata{ Authority: "no.matching.host", }, Response: grpc.Response{Code: codes.Unimplemented}, }} 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) }) } }, }
var GatewayClassObservedGenerationBump = suite.ConformanceTest{ ShortName: "GatewayClassObservedGenerationBump", Features: []features.FeatureName{ features.SupportGateway, }, Description: "A GatewayClass should update the observedGeneration in all of it's Status.Conditions after an update to the spec", Manifests: []string{"tests/gatewayclass-observed-generation-bump.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { gwc := types.NamespacedName{Name: "gatewayclass-observed-generation-bump"} t.Run("observedGeneration should increment", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), s.TimeoutConfig.LatestObservedGenerationSet) defer cancel() kubernetes.GWCMustHaveAcceptedConditionAny(t, s.Client, s.TimeoutConfig, gwc.Name) original := &v1.GatewayClass{} err := s.Client.Get(ctx, gwc, original) require.NoErrorf(t, err, "error getting GatewayClass: %v", err) kubernetes.GatewayClassMustHaveLatestConditions(t, original) mutate := original.DeepCopy() desc := "new" mutate.Spec.Description = &desc err = s.Client.Patch(ctx, mutate, client.MergeFrom(original)) require.NoErrorf(t, err, "error patching the GatewayClass: %v", err) kubernetes.GWCMustHaveAcceptedConditionAny(t, s.Client, s.TimeoutConfig, gwc.Name) updated := &v1.GatewayClass{} err = s.Client.Get(ctx, gwc, updated) require.NoErrorf(t, err, "error getting GatewayClass: %v", err) kubernetes.GatewayClassMustHaveLatestConditions(t, updated) require.NotEqual(t, original.Generation, updated.Generation, "generation should change after an update") }) }, }
var GatewayHTTPListenerIsolation = suite.ConformanceTest{ ShortName: "GatewayHTTPListenerIsolation", Description: "Listener isolation for HTTP listeners with multiple listeners and HTTPRoutes", Features: []features.FeatureName{ features.SupportGateway, features.SupportGatewayHTTPListenerIsolation, features.SupportHTTPRoute, }, Manifests: []string{ "tests/gateway-http-listener-isolation.yaml", "tests/gateway-http-listener-isolation-with-hostname-intersection.yaml", }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ns}) testCases := []http.ExpectedResponse{ { Request: http.Request{Host: "bar.com", Path: "/empty-hostname"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Host: "bar.com", Path: "/wildcard-example-com"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "bar.com", Path: "/wildcard-foo-example-com"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "bar.com", Path: "/abc-foo-example-com"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "bar.example.com", Path: "/empty-hostname"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "bar.example.com", Path: "/wildcard-example-com"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Host: "bar.example.com", Path: "/wildcard-foo-example-com"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "bar.example.com", Path: "/abc-foo-example-com"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "bar.foo.example.com", Path: "/empty-hostname"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "bar.foo.example.com", Path: "/wildcard-example-com"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "bar.foo.example.com", Path: "/wildcard-foo-example-com"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Host: "bar.foo.example.com", Path: "/abc-foo-example-com"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "abc.foo.example.com", Path: "/empty-hostname"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "abc.foo.example.com", Path: "/wildcard-example-com"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "abc.foo.example.com", Path: "/wildcard-foo-example-com"}, Response: http.Response{StatusCode: 404}, Namespace: ns, }, { Request: http.Request{Host: "abc.foo.example.com", Path: "/abc-foo-example-com"}, Backend: "infra-backend-v1", Namespace: ns, }, } t.Run("hostnames are configured only in listeners", func(t *testing.T) { gwNN := types.NamespacedName{Name: "http-listener-isolation", Namespace: ns} routes := []types.NamespacedName{ {Namespace: ns, Name: "attaches-to-empty-hostname"}, {Namespace: ns, Name: "attaches-to-wildcard-example-com"}, {Namespace: ns, Name: "attaches-to-wildcard-foo-example-com"}, {Namespace: ns, Name: "attaches-to-abc-foo-example-com"}, } gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routes...) for _, routeNN := range routes { kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) } 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) }) } }) t.Run("intersecting hostnames are configured in listeners and HTTPRoutes", func(t *testing.T) { gwNN := types.NamespacedName{Name: "http-listener-isolation-with-hostname-intersection", Namespace: ns} routes := []types.NamespacedName{ {Namespace: ns, Name: "attaches-to-empty-hostname-with-hostname-intersection"}, {Namespace: ns, Name: "attaches-to-wildcard-example-com-with-hostname-intersection"}, {Namespace: ns, Name: "attaches-to-wildcard-foo-example-com-with-hostname-intersection"}, {Namespace: ns, Name: "attaches-to-abc-foo-example-com-with-hostname-intersection"}, } gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routes...) for _, routeNN := range routes { kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) } 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) }) } }) }, }
var GatewayInfrastructure = suite.ConformanceTest{ ShortName: "GatewayInfrastructure", Description: "Propagation of metadata from Gateway infrastructure to generated components", Features: []features.FeatureName{ features.SupportGateway, features.SupportGatewayInfrastructurePropagation, }, Provisional: true, Manifests: []string{ "tests/gateway-infrastructure.yaml", }, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" kubernetes.NamespacesMustBeReady(t, s.Client, s.TimeoutConfig, []string{ns}) gwNN := types.NamespacedName{ Name: "gateway-with-infrastructure-metadata", Namespace: "gateway-conformance-infra", } ctx, cancel := context.WithTimeout(context.Background(), s.TimeoutConfig.DefaultTestTimeout) defer cancel() t.Logf("waiting for namespace %s and Gateway %s to be ready for testing", gwNN.Namespace, gwNN.Name) kubernetes.GatewayMustHaveLatestConditions(t, s.Client, s.TimeoutConfig, gwNN) t.Logf("retrieving Gateway %s/%s", gwNN.Namespace, gwNN.Name) currentGW := &v1.Gateway{} err := s.Client.Get(ctx, gwNN, currentGW) require.NoError(t, err, "error getting Gateway: %v", err) t.Logf("verifying that the Gateway %s/%s is accepted with infrastructure declared", gwNN.Namespace, gwNN.Name) kubernetes.GatewayMustHaveCondition(t, s.Client, s.TimeoutConfig, gwNN, metav1.Condition{ Type: string(v1.GatewayConditionAccepted), Status: metav1.ConditionTrue, }) t.Logf("verifying that generated resources for Gateway %s/%s have the proper gateway name label", gwNN.Namespace, gwNN.Name) annotations := make(map[string]string, len(currentGW.Spec.Infrastructure.Annotations)) labels := make(map[string]string, len(currentGW.Spec.Infrastructure.Labels)) for k, v := range currentGW.Spec.Infrastructure.Annotations { annotations[string(k)] = string(v) } for k, v := range currentGW.Spec.Infrastructure.Labels { labels[string(k)] = string(v) } var foundResource bool saList := corev1.ServiceAccountList{} podList := corev1.PodList{} serviceList := corev1.ServiceList{} err = s.Client.List(ctx, &saList, client.MatchingLabels{"gateway.networking.k8s.io/gateway-name": gwNN.Name}, client.InNamespace(ns)) require.NoError(t, err, "error listing ServiceAccounts") err = s.Client.List(ctx, &podList, client.MatchingLabels{"gateway.networking.k8s.io/gateway-name": gwNN.Name}, client.InNamespace(ns)) require.NoError(t, err, "error listing Pods") err = s.Client.List(ctx, &serviceList, client.MatchingLabels{"gateway.networking.k8s.io/gateway-name": gwNN.Name}, client.InNamespace(ns)) require.NoError(t, err, "error listing Services") if len(saList.Items) > 0 { foundResource = true sa := saList.Items[0] require.Subsetf(t, sa.Labels, labels, "expected Pod label set %v to contain all Gateway infrastructure labels %v", sa.Labels, labels) require.Subsetf(t, sa.Annotations, annotations, "expected Pod annotation set %v to contain all Gateway infrastructure annotations %v", sa.Annotations, annotations) } if len(podList.Items) > 0 { foundResource = true pod := podList.Items[0] require.Subsetf(t, pod.Labels, labels, "expected Pod label set %v to contain all Gateway infrastructure labels %v", pod.Labels, labels) require.Subsetf(t, pod.Annotations, annotations, "expected Pod annotation set %v to contain all Gateway infrastructure annotations %v", pod.Annotations, annotations) } if len(serviceList.Items) > 0 { foundResource = true service := serviceList.Items[0] require.Subsetf(t, service.Labels, labels, "expected Pod label set %v to contain all Gateway infrastructure labels %v", service.Labels, labels) require.Subsetf(t, service.Annotations, annotations, "expected Pod annotation set %v to contain all Gateway infrastructure annotations %v", service.Annotations, annotations) } require.True(t, foundResource, "expected to find a ServiceAccount, Pod, or Service with the gateway-name label") }, }
var GatewayInvalidRouteKind = suite.ConformanceTest{ ShortName: "GatewayInvalidRouteKind", Description: "A Gateway in the gateway-conformance-infra namespace should fail to become ready an invalid Route kind is specified.", Features: []features.FeatureName{ features.SupportGateway, }, Manifests: []string{"tests/gateway-invalid-route-kind.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { t.Run("Gateway listener should have a false ResolvedRefs condition with reason InvalidRouteKinds and no supportedKinds", func(t *testing.T) { gwNN := types.NamespacedName{Name: "gateway-only-invalid-route-kind", Namespace: "gateway-conformance-infra"} listeners := []v1.ListenerStatus{{ Name: v1.SectionName("http"), SupportedKinds: []v1.RouteGroupKind{}, Conditions: []metav1.Condition{{ Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(v1.ListenerReasonInvalidRouteKinds), }}, AttachedRoutes: 0, }} kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) }) t.Run("Gateway listener should have a false ResolvedRefs condition with reason InvalidRouteKinds and HTTPRoute must be put in the supportedKinds", func(t *testing.T) { gwNN := types.NamespacedName{Name: "gateway-supported-and-invalid-route-kind", Namespace: "gateway-conformance-infra"} listeners := []v1.ListenerStatus{{ Name: v1.SectionName("http"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{{ Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(v1.ListenerReasonInvalidRouteKinds), }}, AttachedRoutes: 0, }} kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) }) }, }
var GatewayInvalidTLSConfiguration = suite.ConformanceTest{ ShortName: "GatewayInvalidTLSConfiguration", Description: "A Gateway should fail to become ready if the Gateway has an invalid TLS configuration", Features: []features.FeatureName{ features.SupportGateway, }, Manifests: []string{"tests/gateway-invalid-tls-configuration.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { listeners := []v1.ListenerStatus{{ Name: v1.SectionName("https"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{{ Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(v1.ListenerReasonInvalidCertificateRef), }}, AttachedRoutes: 0, }} testCases := []struct { name string gatewayNamespacedName types.NamespacedName }{ { name: "Nonexistent secret referenced as CertificateRef in a Gateway listener", gatewayNamespacedName: types.NamespacedName{Name: "gateway-certificate-nonexistent-secret", Namespace: "gateway-conformance-infra"}, }, { name: "Unsupported group resource referenced as CertificateRef in a Gateway listener", gatewayNamespacedName: types.NamespacedName{Name: "gateway-certificate-unsupported-group", Namespace: "gateway-conformance-infra"}, }, { name: "Unsupported kind resource referenced as CertificateRef in a Gateway listener", gatewayNamespacedName: types.NamespacedName{Name: "gateway-certificate-unsupported-kind", Namespace: "gateway-conformance-infra"}, }, { name: "Malformed secret referenced as CertificateRef in a Gateway listener", gatewayNamespacedName: types.NamespacedName{Name: "gateway-certificate-malformed-secret", Namespace: "gateway-conformance-infra"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, tc.gatewayNamespacedName, listeners) }) } }, }
var GatewayModifyListeners = suite.ConformanceTest{ ShortName: "GatewayModifyListeners", Description: "A Gateway in the gateway-conformance-infra namespace should handle adding and removing listeners.", Features: []features.FeatureName{ features.SupportGateway, }, Manifests: []string{"tests/gateway-modify-listeners.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { t.Run("should be able to add a listener that then becomes available for routing traffic", func(t *testing.T) { gwNN := types.NamespacedName{Name: "gateway-add-listener", Namespace: "gateway-conformance-infra"} namespaces := []string{"gateway-conformance-infra"} kubernetes.NamespacesMustBeReady(t, s.Client, s.TimeoutConfig, namespaces) kubernetes.GatewayMustHaveLatestConditions(t, s.Client, s.TimeoutConfig, gwNN) ctx, cancel := context.WithTimeout(context.Background(), s.TimeoutConfig.DefaultTestTimeout) defer cancel() original := &v1.Gateway{} err := s.Client.Get(ctx, gwNN, original) require.NoErrorf(t, err, "error getting Gateway: %v", err) all := v1.NamespacesFromAll mutate := original.DeepCopy() hostname := v1.Hostname("data.test.com") mutate.Spec.Listeners = append(mutate.Spec.Listeners, v1.Listener{ Name: "http", Port: 80, Protocol: v1.HTTPProtocolType, Hostname: &hostname, AllowedRoutes: &v1.AllowedRoutes{ Namespaces: &v1.RouteNamespaces{From: &all}, }, }) err = s.Client.Patch(ctx, mutate, client.MergeFrom(original)) require.NoErrorf(t, err, "error patching the Gateway: %v", err) kubernetes.NamespacesMustBeReady(t, s.Client, s.TimeoutConfig, namespaces) listeners := []v1.ListenerStatus{ { Name: v1.SectionName("https"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{ { Type: string(v1.ListenerConditionAccepted), Status: metav1.ConditionTrue, Reason: "", }, { Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionTrue, Reason: "", }, }, AttachedRoutes: 1, }, { Name: v1.SectionName("http"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{ { Type: string(v1.ListenerConditionAccepted), Status: metav1.ConditionTrue, Reason: "", }, { Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionTrue, Reason: "", }, }, AttachedRoutes: 1, }, } kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) kubernetes.GatewayMustHaveLatestConditions(t, s.Client, s.TimeoutConfig, gwNN) updated := &v1.Gateway{} err = s.Client.Get(ctx, gwNN, updated) require.NoErrorf(t, err, "error getting Gateway: %v", err) require.NotEqual(t, original.Generation, updated.Generation, "generation should change after an update") }) t.Run("should be able to remove listeners, which would then stop routing the relevant traffic", func(t *testing.T) { gwNN := types.NamespacedName{Name: "gateway-remove-listener", Namespace: "gateway-conformance-infra"} namespaces := []string{"gateway-conformance-infra"} kubernetes.NamespacesMustBeReady(t, s.Client, s.TimeoutConfig, namespaces) kubernetes.GatewayMustHaveLatestConditions(t, s.Client, s.TimeoutConfig, gwNN) ctx, cancel := context.WithTimeout(context.Background(), s.TimeoutConfig.DefaultTestTimeout) defer cancel() original := &v1.Gateway{} err := s.Client.Get(ctx, gwNN, original) require.NoErrorf(t, err, "error getting Gateway: %v", err) mutate := original.DeepCopy() require.Lenf(t, mutate.Spec.Listeners, 2, "the gateway must have 2 listeners") // remove the "https" Gateway listener, leaving only the "http" listener var newListeners []v1.Listener for _, listener := range mutate.Spec.Listeners { if listener.Name == "http" { newListeners = append(newListeners, listener) } } mutate.Spec.Listeners = newListeners err = s.Client.Patch(ctx, mutate, client.MergeFrom(original)) require.NoErrorf(t, err, "error patching the Gateway: %v", err) kubernetes.NamespacesMustBeReady(t, s.Client, s.TimeoutConfig, namespaces) listeners := []v1.ListenerStatus{ { Name: v1.SectionName("http"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{ { Type: string(v1.ListenerConditionAccepted), Status: metav1.ConditionTrue, Reason: "", }, { Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionTrue, Reason: "", }, }, AttachedRoutes: 1, }, } kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) kubernetes.GatewayMustHaveLatestConditions(t, s.Client, s.TimeoutConfig, gwNN) updated := &v1.Gateway{} err = s.Client.Get(ctx, gwNN, updated) require.NoErrorf(t, err, "error getting Gateway: %v", err) require.NotEqual(t, original.Generation, updated.Generation, "generation should change after an update") }) }, }
var GatewayObservedGenerationBump = suite.ConformanceTest{ ShortName: "GatewayObservedGenerationBump", Description: "A Gateway in the gateway-conformance-infra namespace should update the observedGeneration in all of its Status.Conditions after an update to the spec", Features: []features.FeatureName{ features.SupportGateway, }, Manifests: []string{"tests/gateway-observed-generation-bump.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { gwNN := types.NamespacedName{Name: "gateway-observed-generation-bump", Namespace: "gateway-conformance-infra"} t.Run("observedGeneration should increment", func(t *testing.T) { namespaces := []string{"gateway-conformance-infra"} kubernetes.NamespacesMustBeReady(t, s.Client, s.TimeoutConfig, namespaces) kubernetes.GatewayMustHaveLatestConditions(t, s.Client, s.TimeoutConfig, gwNN) ctx, cancel := context.WithTimeout(context.Background(), s.TimeoutConfig.LatestObservedGenerationSet) defer cancel() original := &v1.Gateway{} err := s.Client.Get(ctx, gwNN, original) require.NoErrorf(t, err, "error getting Gateway: %v", err) all := v1.NamespacesFromAll mutate := original.DeepCopy() mutate.Spec.Listeners = append(mutate.Spec.Listeners, v1.Listener{ Name: "alternate", Hostname: ptr.To[v1.Hostname]("foo.com"), Port: 80, Protocol: v1.HTTPProtocolType, AllowedRoutes: &v1.AllowedRoutes{ Namespaces: &v1.RouteNamespaces{From: &all}, }, }) err = s.Client.Patch(ctx, mutate, client.MergeFrom(original)) require.NoErrorf(t, err, "error patching the Gateway: %v", err) kubernetes.NamespacesMustBeReady(t, s.Client, s.TimeoutConfig, namespaces) kubernetes.GatewayMustHaveLatestConditions(t, s.Client, s.TimeoutConfig, gwNN) updated := &v1.Gateway{} err = s.Client.Get(ctx, gwNN, updated) require.NoErrorf(t, err, "error getting Gateway: %v", err) require.NotEqual(t, original.Generation, updated.Generation, "generation should change after an update") }) }, }
var GatewaySecretInvalidReferenceGrant = suite.ConformanceTest{ ShortName: "GatewaySecretInvalidReferenceGrant", Description: "A Gateway in the gateway-conformance-infra namespace should fail to become ready if the Gateway has a certificateRef for a Secret in the gateway-conformance-web-backend namespace and a ReferenceGrant exists but does not grant permission to that specific Secret", Features: []features.FeatureName{ features.SupportGateway, features.SupportReferenceGrant, }, Manifests: []string{"tests/gateway-secret-invalid-reference-grant.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { gwNN := types.NamespacedName{Name: "gateway-secret-invalid-reference-grant", Namespace: "gateway-conformance-infra"} t.Run("Gateway listener should have a false ResolvedRefs condition with reason RefNotPermitted", func(t *testing.T) { listeners := []v1.ListenerStatus{{ Name: v1.SectionName("https"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{{ Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(v1.ListenerReasonRefNotPermitted), }}, AttachedRoutes: 0, }} kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) }) }, }
var GatewaySecretMissingReferenceGrant = suite.ConformanceTest{ ShortName: "GatewaySecretMissingReferenceGrant", Description: "A Gateway in the gateway-conformance-infra namespace should fail to become programmed if the Gateway has a certificateRef for a Secret in the gateway-conformance-web-backend namespace and a ReferenceGrant granting permission to the Secret does not exist", Features: []features.FeatureName{ features.SupportGateway, features.SupportReferenceGrant, }, Manifests: []string{"tests/gateway-secret-missing-reference-grant.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { gwNN := types.NamespacedName{Name: "gateway-secret-missing-reference-grant", Namespace: "gateway-conformance-infra"} t.Run("Gateway listener should have a false ResolvedRefs condition with reason RefNotPermitted", func(t *testing.T) { listeners := []v1.ListenerStatus{{ Name: v1.SectionName("https"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{{ Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(v1.ListenerReasonRefNotPermitted), }}, AttachedRoutes: 0, }} kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) }) }, }
var GatewaySecretReferenceGrantAllInNamespace = suite.ConformanceTest{ ShortName: "GatewaySecretReferenceGrantAllInNamespace", Description: "A Gateway in the gateway-conformance-infra namespace should become programmed if the Gateway has a certificateRef for a Secret in the gateway-conformance-web-backend namespace and a ReferenceGrant granting permission to all Secrets in the namespace exists", Features: []features.FeatureName{ features.SupportGateway, features.SupportReferenceGrant, }, Manifests: []string{"tests/gateway-secret-reference-grant-all-in-namespace.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { gwNN := types.NamespacedName{Name: "gateway-secret-reference-grant-all-in-namespace", Namespace: "gateway-conformance-infra"} t.Run("Gateway listener should have a true ResolvedRefs condition and a true Programmed condition", func(t *testing.T) { listeners := []v1.ListenerStatus{{ Name: v1.SectionName("https"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{ { Type: string(v1.ListenerConditionProgrammed), Status: metav1.ConditionTrue, Reason: string(v1.ListenerReasonProgrammed), }, { Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionTrue, Reason: "", }, }, AttachedRoutes: 0, }} kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) }) }, }
var GatewaySecretReferenceGrantSpecific = suite.ConformanceTest{ ShortName: "GatewaySecretReferenceGrantSpecific", Description: "A Gateway in the gateway-conformance-infra namespace should become programmed if the Gateway has a certificateRef for a Secret in the gateway-conformance-web-backend namespace and a ReferenceGrant granting permission to the specific Secret exists", Features: []features.FeatureName{ features.SupportGateway, features.SupportReferenceGrant, }, Manifests: []string{"tests/gateway-secret-reference-grant-specific.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { gwNN := types.NamespacedName{Name: "gateway-secret-reference-grant-specific", Namespace: "gateway-conformance-infra"} t.Run("Gateway listener should have a true ResolvedRefs condition and a true Programmed condition", func(t *testing.T) { listeners := []v1.ListenerStatus{{ Name: v1.SectionName("https"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{ { Type: string(v1.ListenerConditionProgrammed), Status: metav1.ConditionTrue, Reason: string(v1.ListenerReasonProgrammed), }, { Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionTrue, Reason: "", }, }, AttachedRoutes: 0, }} kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) }) }, }
var GatewayStaticAddresses = suite.ConformanceTest{ ShortName: "GatewayStaticAddresses", Description: "A Gateway in the gateway-conformance-infra namespace should be able to use previously determined addresses.", Features: []features.FeatureName{ features.SupportGateway, features.SupportGatewayStaticAddresses, }, Manifests: []string{ "tests/gateway-static-addresses.yaml", }, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { gwNN := types.NamespacedName{ Name: "gateway-static-addresses", Namespace: "gateway-conformance-infra", } ctx, cancel := context.WithTimeout(context.Background(), s.TimeoutConfig.DefaultTestTimeout) defer cancel() t.Logf("waiting for namespace %s and Gateway %s to be ready for testing", gwNN.Namespace, gwNN.Name) kubernetes.GatewayMustHaveLatestConditions(t, s.Client, s.TimeoutConfig, gwNN) t.Logf("retrieving Gateway %s/%s and noting the provided addresses", gwNN.Namespace, gwNN.Name) currentGW := &v1.Gateway{} err := s.Client.Get(ctx, gwNN, currentGW) require.NoError(t, err, "error getting Gateway: %v", err) require.Len(t, currentGW.Spec.Addresses, 3, "expected 3 addresses on the Gateway, one invalid, one usable and one unusable. somehow got %d", len(currentGW.Spec.Addresses)) invalidAddress := currentGW.Spec.Addresses[0] unusableAddress := currentGW.Spec.Addresses[1] usableAddress := currentGW.Spec.Addresses[2] t.Logf("verifying that the Gateway %s/%s is NOT accepted due to an address type that the implementation doesn't support", gwNN.Namespace, gwNN.Name) kubernetes.GatewayMustHaveCondition(t, s.Client, s.TimeoutConfig, gwNN, metav1.Condition{ Type: string(v1.GatewayConditionAccepted), Status: metav1.ConditionFalse, Reason: string(v1.GatewayReasonUnsupportedAddress), }) t.Logf("patching Gateway %s/%s to remove the invalid address %s", gwNN.Namespace, gwNN.Name, invalidAddress.Value) updatedGW := currentGW.DeepCopy() updatedGW.Spec.Addresses = filterAddr(currentGW.Spec.Addresses, invalidAddress) err = s.Client.Patch(ctx, updatedGW, client.MergeFrom(currentGW)) require.NoError(t, err, "failed to patch Gateway: %v", err) kubernetes.GatewayMustHaveLatestConditions(t, s.Client, s.TimeoutConfig, gwNN) t.Logf("verifying that the Gateway %s/%s is now accepted, but is not programmed due to an address that can't be used", gwNN.Namespace, gwNN.Name) err = s.Client.Get(ctx, gwNN, currentGW) require.NoError(t, err, "error getting Gateway: %v", err) kubernetes.GatewayMustHaveCondition(t, s.Client, s.TimeoutConfig, gwNN, metav1.Condition{ Type: string(v1.GatewayConditionAccepted), Status: metav1.ConditionTrue, Reason: string(v1.GatewayReasonAccepted), }) kubernetes.GatewayMustHaveCondition(t, s.Client, s.TimeoutConfig, gwNN, metav1.Condition{ Type: string(v1.GatewayConditionProgrammed), Status: metav1.ConditionFalse, Reason: string(v1.GatewayReasonAddressNotUsable), }) t.Logf("patching Gateway %s/%s to remove the unusable address %s", gwNN.Namespace, gwNN.Name, unusableAddress.Value) updatedGW = currentGW.DeepCopy() updatedGW.Spec.Addresses = filterAddr(currentGW.Spec.Addresses, unusableAddress) err = s.Client.Patch(ctx, updatedGW, client.MergeFrom(currentGW)) require.NoError(t, err, "failed to patch Gateway: %v", err) kubernetes.GatewayMustHaveLatestConditions(t, s.Client, s.TimeoutConfig, gwNN) t.Logf("verifying that the Gateway %s/%s is accepted and programmed with the usable static address %s assigned", gwNN.Namespace, gwNN.Name, usableAddress.Value) err = s.Client.Get(ctx, gwNN, currentGW) require.NoError(t, err, "error getting Gateway: %v", err) kubernetes.GatewayMustHaveCondition(t, s.Client, s.TimeoutConfig, gwNN, metav1.Condition{ Type: string(v1.GatewayConditionAccepted), Status: metav1.ConditionTrue, Reason: string(v1.GatewayReasonAccepted), }) kubernetes.GatewayMustHaveCondition(t, s.Client, s.TimeoutConfig, gwNN, metav1.Condition{ Type: string(v1.GatewayConditionProgrammed), Status: metav1.ConditionTrue, Reason: string(v1.GatewayReasonProgrammed), }) kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, finalExpectedListenerState) require.Len(t, currentGW.Spec.Addresses, 1, "expected only 1 address left specified on Gateway") statusAddresses := extractStatusAddresses(currentGW.Status.Addresses) require.NotContains(t, statusAddresses, unusableAddress.Value, "should contain the unusable address") require.NotContains(t, statusAddresses, invalidAddress.Value, "should contain the invalid address") require.Contains(t, statusAddresses, usableAddress.Value, "should contain the usable address") for _, addr := range currentGW.Status.Addresses { if usableAddress.Value != addr.Value { continue } require.Equal(t, usableAddress.Type, addr.Type, "expected address type to match the usable address") } }, }
GatewayStaticAddresses tests the implementation's support of deploying Gateway resources with static addresses, or in other words addresses provided via the specification rather than relying on the underlying implementation/network to dynamically assign the Gateway an address.
Running this test against your own implementation is currently a little bit messy, as at the time of writing we didn't have great ways to provide the test suite with things like known good, or known bad addresses to run the test with (as we obviously can't determine that for the implementation).
As such, if you're trying to enable this test for yourself and you're getting confused about how to provide addresses, you'll actually do that in the conformance test suite BEFORE you even set up and run your tests. Make sure you populate the following test suite fields:
- suite.UsableNetworkAddresses
- suite.UnusableNetworkAddresses
With appropriate network addresses for your network environment.
var GatewayWithAttachedRoutes = suite.ConformanceTest{ ShortName: "GatewayWithAttachedRoutes", Description: "A Gateway in the gateway-conformance-infra namespace should be attached to routes.", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/gateway-with-attached-routes.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { t.Run("Gateway listener should have one valid http routes attached", func(t *testing.T) { gwNN := types.NamespacedName{Name: "gateway-with-one-attached-route", Namespace: "gateway-conformance-infra"} listeners := []v1.ListenerStatus{{ Name: v1.SectionName("http"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{ { Type: string(v1.ListenerConditionAccepted), Status: metav1.ConditionTrue, Reason: "", }, { Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionTrue, Reason: "", }, }, AttachedRoutes: 1, }} kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) }) t.Run("Gateway listener should have two valid http routes attached", func(t *testing.T) { gwNN := types.NamespacedName{Name: "gateway-with-two-attached-routes", Namespace: "gateway-conformance-infra"} listeners := []v1.ListenerStatus{{ Name: v1.SectionName("http"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{ { Type: string(v1.ListenerConditionAccepted), Status: metav1.ConditionTrue, Reason: "", }, { Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionTrue, Reason: "", }, }, AttachedRoutes: 2, }} kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) }) t.Run("Gateway listener should have AttachedRoutes set even when Gateway has unresolved refs", func(t *testing.T) { gwNN := types.NamespacedName{Name: "unresolved-gateway-with-one-attached-unresolved-route", Namespace: "gateway-conformance-infra"} listeners := []v1.ListenerStatus{{ Name: v1.SectionName("tls"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{ { Type: string(v1.ListenerConditionProgrammed), Status: metav1.ConditionFalse, Reason: "", }, { Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: "", }, }, AttachedRoutes: 1, }} kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) hrouteNN := types.NamespacedName{Name: "http-route-4", Namespace: "gateway-conformance-infra"} unresolved := metav1.Condition{ Type: string(v1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: "", } kubernetes.HTTPRouteMustHaveCondition(t, s.Client, s.TimeoutConfig, hrouteNN, gwNN, unresolved) }) }, }
var GatewayWithAttachedRoutesWithPort8080 = suite.ConformanceTest{ ShortName: "GatewayWithAttachedRoutesWithPort8080", Description: "A Gateway in the gateway-conformance-infra namespace should be attached to routes.", Features: []features.FeatureName{ features.SupportGateway, features.SupportGatewayPort8080, features.SupportHTTPRoute, }, Manifests: []string{"tests/gateway-with-attached-routes-with-port-8080.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { t.Run("Gateway listener should have attached route by specifying the sectionName", func(t *testing.T) { gwNN := types.NamespacedName{Name: "gateway-with-two-listeners-and-one-attached-route", Namespace: "gateway-conformance-infra"} listeners := []v1.ListenerStatus{ { Name: v1.SectionName("http-unattached"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{ { Type: string(v1.ListenerConditionAccepted), Status: metav1.ConditionTrue, Reason: "", }, { Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionTrue, Reason: "", }, }, AttachedRoutes: 0, }, { Name: v1.SectionName("http"), SupportedKinds: []v1.RouteGroupKind{{ Group: (*v1.Group)(&v1.GroupVersion.Group), Kind: v1.Kind("HTTPRoute"), }}, Conditions: []metav1.Condition{ { Type: string(v1.ListenerConditionAccepted), Status: metav1.ConditionTrue, Reason: "", }, { Type: string(v1.ListenerConditionResolvedRefs), Status: metav1.ConditionTrue, Reason: "", }, }, AttachedRoutes: 1, }, } kubernetes.GatewayStatusMustHaveListeners(t, s.Client, s.TimeoutConfig, gwNN, listeners) }) }, }
var HTTPRouteBackendProtocolH2C = suite.ConformanceTest{ ShortName: "HTTPRouteBackendProtocolH2C", Description: "A HTTPRoute with a BackendRef that has an appProtocol kubernetes.io/h2c should be functional", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteBackendProtocolH2C, }, Manifests: []string{ "tests/httproute-backend-protocol-h2c.yaml", }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "backend-protocol-h2c", Namespace: ns} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) t.Run("http2 prior knowledge request should reach backend", func(t *testing.T) { http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{ Request: http.Request{ Path: "/", Protocol: roundtripper.H2CPriorKnowledgeProtocol, }, Response: http.Response{StatusCode: 200}, Backend: "infra-backend-v1", Namespace: "gateway-conformance-infra", }) }) }, }
var HTTPRouteBackendProtocolWebSocket = suite.ConformanceTest{ ShortName: "HTTPRouteBackendProtocolWebSocket", Description: "A HTTPRoute with a BackendRef that has an appProtocol kubernetes.io/ws should be functional", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteBackendProtocolWebSocket, }, Manifests: []string{ "tests/httproute-backend-protocol-websocket.yaml", }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "backend-protocol-ws", Namespace: ns} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) threshold := suite.TimeoutConfig.RequiredConsecutiveSuccesses maxTimeToConsistency := suite.TimeoutConfig.MaxTimeToConsistency t.Run("websocket connection should reach backend", func(t *testing.T) { http.AwaitConvergence(t, threshold, maxTimeToConsistency, func(_ time.Duration) bool { origin := fmt.Sprintf("ws://gateway/%s", t.Name()) remote := fmt.Sprintf("ws://%s/ws", gwAddr) ws, err := websocket.Dial(remote, "", origin) if err != nil { t.Log("failed to dial", err) return false } defer ws.Close() // Send text frame var ( textMessage = "Websocket Support!" textReply string ) if err := websocket.Message.Send(ws, textMessage); err != nil { t.Log("failed to send text frame", err) return false } if err := websocket.Message.Receive(ws, &textReply); err != nil { t.Log("failed to receive text frame", err) return false } if textMessage != textReply { t.Logf("unexpected reply - want: %s got: %s", textMessage, textReply) return false } // Send byte frame var ( binaryMessage = []byte{1, 2, 3, 4, 5, 6, 7} binaryReply []byte ) if err := websocket.Message.Send(ws, binaryMessage); err != nil { t.Log("failed to send binary frame", err) return false } if err := websocket.Message.Receive(ws, &binaryReply); err != nil { t.Log("failed to receive binary frame", err) } if !bytes.Equal(binaryMessage, binaryReply) { t.Logf("unexpected reply - want: %#v got: %#v", binaryMessage, binaryReply) return false } return true }) }) }, }
var HTTPRouteBackendRequestHeaderModifier = suite.ConformanceTest{ ShortName: "HTTPRouteBackendRequestHeaderModifier", Description: "An HTTPRoute backend has request header modifier filters applied correctly", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteBackendRequestHeaderModification, }, Manifests: []string{"tests/httproute-request-header-modifier-backend.yaml"}, Test: HTTPRouteRequestHeaderModifier.Test, }
var HTTPRouteCrossNamespace = suite.ConformanceTest{ ShortName: "HTTPRouteCrossNamespace", Description: "A single HTTPRoute in the gateway-conformance-web-backend namespace should attach to Gateway in another namespace", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-cross-namespace.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "cross-namespace", Namespace: "gateway-conformance-web-backend"} gwNN := types.NamespacedName{Name: "backend-namespaces", Namespace: "gateway-conformance-infra"} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) t.Run("Simple HTTP request should reach web-backend", func(t *testing.T) { http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{ Request: http.Request{Path: "/"}, Response: http.Response{StatusCode: 200}, Backend: "web-backend", Namespace: "gateway-conformance-web-backend", }) }) }, }
var HTTPRouteDisallowedKind = suite.ConformanceTest{ ShortName: "HTTPRouteDisallowedKind", Description: "A single HTTPRoute in the gateway-conformance-infra namespace should fail to attach to a Gateway with no listeners that allow the HTTPRoute kind", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportTLSRoute, }, Manifests: []string{"tests/httproute-disallowed-kind.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{"gateway-conformance-infra"}) routeNN := types.NamespacedName{Name: "disallowed-kind", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "tlsroutes-only", Namespace: "gateway-conformance-infra"} kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) t.Run("Route should not have been accepted with reason NotAllowedByListeners", func(t *testing.T) { kubernetes.HTTPRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, metav1.Condition{ Type: string(v1.RouteConditionAccepted), Status: metav1.ConditionFalse, Reason: string(v1.RouteReasonNotAllowedByListeners), }) }) t.Run("Route should not have Parents set in status", func(t *testing.T) { kubernetes.HTTPRouteMustHaveNoAcceptedParents(t, suite.Client, suite.TimeoutConfig, routeNN) }) t.Run("Gateway should have 0 Routes attached", func(t *testing.T) { kubernetes.GatewayMustHaveZeroRoutes(t, suite.Client, suite.TimeoutConfig, gwNN) }) }, }
var HTTPRouteExactPathMatching = suite.ConformanceTest{ ShortName: "HTTPRouteExactPathMatching", Description: "A single HTTPRoute with exact path matching for different backends", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-exact-path-matching.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "exact-matching", 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: "/one"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/two"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Path: "/one/example"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Path: "/two/"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Path: "/Two"}, Response: http.Response{StatusCode: 404}, }, } 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) }) } }, }
var HTTPRouteHTTPSListener = suite.ConformanceTest{ ShortName: "HTTPRouteHTTPSListener", Description: "HTTPRoute attaches to a Gateway's HTTPS listener in the same namespace", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-https-listener.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "httproute-https-test", Namespace: ns} routeNoHostNN := types.NamespacedName{Name: "httproute-https-test-no-hostname", Namespace: ns} gwNN := types.NamespacedName{Name: "same-namespace-with-https-listener", Namespace: ns} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN, routeNoHostNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNoHostNN, gwNN) certNN := types.NamespacedName{Name: "tls-validity-checks-certificate", Namespace: ns} cPem, keyPem, err := GetTLSSecret(suite.Client, certNN) if err != nil { t.Fatalf("unexpected error finding TLS secret: %v", err) } cases := []struct { host string statusCode int backend string }{ {host: "example.org", statusCode: 200, backend: "infra-backend-v1"}, {host: "unknown-example.org", statusCode: 404}, {host: "second-example.org", statusCode: 200, backend: "infra-backend-v2"}, } for i, tc := range cases { expected := http.ExpectedResponse{ Request: http.Request{Host: tc.host, Path: "/"}, Response: http.Response{StatusCode: tc.statusCode}, Backend: tc.backend, Namespace: "gateway-conformance-infra", } t.Run(expected.GetTestCaseName(i), func(t *testing.T) { tls.MakeTLSRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, cPem, keyPem, tc.host, expected) }) } }, }
var HTTPRouteHeaderMatching = suite.ConformanceTest{ ShortName: "HTTPRouteHeaderMatching", Description: "A single HTTPRoute with header matching for different backends", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-header-matching.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "header-matching", 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: "/", Headers: map[string]string{"Version": "one"}}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Version": "two"}}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Version": "two", "Color": "orange"}}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Version": "two", "Color": "blue"}}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Color": "orange"}}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Some-Other-Header": "one"}}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Color": "blue"}}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Color": "green"}}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Color": "red"}}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Color": "yellow"}}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Color": "purple"}}, Response: http.Response{StatusCode: 404}, }} 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) }) } }, }
var HTTPRouteHostnameIntersection = suite.ConformanceTest{ ShortName: "HTTPRouteHostnameIntersection", Description: "HTTPRoutes should attach to listeners only if they have intersecting hostnames, and should accept requests only for the intersecting hostnames", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-hostname-intersection.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" gwNN := types.NamespacedName{Name: "httproute-hostname-intersection", Namespace: ns} kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ns}) t.Run("HTTPRoutes that do intersect with listener hostnames", func(t *testing.T) { routes := []types.NamespacedName{ {Namespace: ns, Name: "specific-host-matches-listener-specific-host"}, {Namespace: ns, Name: "specific-host-matches-listener-wildcard-host"}, {Namespace: ns, Name: "wildcard-host-matches-listener-specific-host"}, {Namespace: ns, Name: "wildcard-host-matches-listener-wildcard-host"}, } gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routes...) for _, routeNN := range routes { kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) } var testCases []http.ExpectedResponse testCases = append(testCases, http.ExpectedResponse{ Request: http.Request{Host: "very.specific.com", Path: "/s1"}, Backend: "infra-backend-v1", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "very.specific.com:1234", Path: "/s1"}, Backend: "infra-backend-v1", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "non.matching.com", Path: "/s1"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "foo.nonmatchingwildcard.io", Path: "/s1"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "foo.wildcard.io", Path: "/s1"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "very.specific.com", Path: "/non-matching-prefix"}, Response: http.Response{StatusCode: 404}, }, ) testCases = append(testCases, http.ExpectedResponse{ Request: http.Request{Host: "foo.wildcard.io", Path: "/s2"}, Backend: "infra-backend-v2", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "bar.wildcard.io", Path: "/s2"}, Backend: "infra-backend-v2", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "foo.bar.wildcard.io", Path: "/s2"}, Backend: "infra-backend-v2", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "non.matching.com", Path: "/s2"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "wildcard.io", Path: "/s2"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "very.specific.com", Path: "/s2"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "foo.wildcard.io", Path: "/non-matching-prefix"}, Response: http.Response{StatusCode: 404}, }, ) testCases = append(testCases, http.ExpectedResponse{ Request: http.Request{Host: "very.specific.com", Path: "/s3"}, Backend: "infra-backend-v3", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "non.matching.com", Path: "/s3"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "foo.specific.com", Path: "/s3"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "foo.wildcard.io", Path: "/s3"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "very.specific.com", Path: "/non-matching-prefix"}, Response: http.Response{StatusCode: 404}, }, ) testCases = append(testCases, http.ExpectedResponse{ Request: http.Request{Host: "foo.anotherwildcard.io", Path: "/s4"}, Backend: "infra-backend-v1", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "bar.anotherwildcard.io", Path: "/s4"}, Backend: "infra-backend-v1", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "foo.bar.anotherwildcard.io", Path: "/s4"}, Backend: "infra-backend-v1", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "anotherwildcard.io", Path: "/s4"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "foo.wildcard.io", Path: "/s4"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "very.specific.com", Path: "/s4"}, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "foo.anotherwildcard.io", Path: "/non-matching-prefix"}, Response: http.Response{StatusCode: 404}, }, ) 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) }) } }) t.Run("HTTPRoutes that do not intersect with listener hostnames", func(t *testing.T) { gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN)) routeNN := types.NamespacedName{Namespace: ns, Name: "no-intersecting-hosts"} parents := []v1.RouteParentStatus{{ ParentRef: parentRefTo(gwNN), ControllerName: v1.GatewayController(suite.ControllerName), Conditions: []metav1.Condition{ { Type: string(v1.RouteConditionAccepted), Status: metav1.ConditionFalse, Reason: string(v1.RouteReasonNoMatchingListenerHostname), }, }, }} kubernetes.HTTPRouteMustHaveParents(t, suite.Client, suite.TimeoutConfig, routeNN, parents, true) testCases := []http.ExpectedResponse{ { Request: http.Request{Host: "specific.but.wrong.com", Path: "/s5"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "wildcard.io", Path: "/s5"}, Response: http.Response{StatusCode: 404}, }, } 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) }) } }) t.Run("HTTPRoutes intersects with an unspecified hostname listener", func(t *testing.T) { routes := []types.NamespacedName{ {Namespace: ns, Name: "httproute-hostname-intersection-all"}, } gwNN := types.NamespacedName{Name: "httproute-hostname-intersection-all", Namespace: ns} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routes...) for _, routeNN := range routes { kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) } var testCases []http.ExpectedResponse testCases = append(testCases, http.ExpectedResponse{ Request: http.Request{Host: "first.com", Path: "/"}, Backend: "infra-backend-v2", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "sub.first.com", Path: "/"}, Backend: "infra-backend-v2", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "second.com", Path: "/"}, Backend: "infra-backend-v2", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "sub.second.com", Path: "/"}, Backend: "infra-backend-v2", Namespace: ns, }, http.ExpectedResponse{ Request: http.Request{Host: "third.com", Path: "/"}, Namespace: ns, Response: http.Response{StatusCode: 404}, }, http.ExpectedResponse{ Request: http.Request{Host: "sub.third.com", Path: "/"}, Namespace: ns, Response: http.Response{StatusCode: 404}, }, ) 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) }) } }) }, }
var HTTPRouteInvalidBackendRefUnknownKind = suite.ConformanceTest{ ShortName: "HTTPRouteInvalidBackendRefUnknownKind", Description: "A single HTTPRoute in the gateway-conformance-infra namespace should set a ResolvedRefs status False with reason InvalidKind when attempting to bind to a Gateway in the same namespace if the route has a BackendRef that points to an unknown Kind.", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-invalid-backendref-unknown-kind.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "invalid-backend-ref-unknown-kind", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: "gateway-conformance-infra"} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) t.Run("HTTPRoute with Invalid Kind has a ResolvedRefs Condition with status False and Reason InvalidKind", func(t *testing.T) { resolvedRefsCond := metav1.Condition{ Type: string(v1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(v1.RouteReasonInvalidKind), } kubernetes.HTTPRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, resolvedRefsCond) }) t.Run("HTTP Request to invalid backend with invalid Kind receives a 500", func(t *testing.T) { http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{ Request: http.Request{ Method: "GET", Path: "/v2", }, Response: http.Response{StatusCode: 500}, }) }) }, }
var HTTPRouteInvalidCrossNamespaceBackendRef = suite.ConformanceTest{ ShortName: "HTTPRouteInvalidCrossNamespaceBackendRef", Description: "A single HTTPRoute in the gateway-conformance-infra namespace should set a ResolvedRefs status False with reason RefNotPermitted when attempting to bind to a Gateway in the same namespace if the route has a BackendRef Service in the gateway-conformance-web-backend namespace and a ReferenceGrant granting permission to route to that Service does not exist", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportReferenceGrant, }, Manifests: []string{"tests/httproute-invalid-cross-namespace-backend-ref.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "invalid-cross-namespace-backend-ref", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: "gateway-conformance-infra"} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) t.Run("HTTPRoute with a cross-namespace BackendRef and no ReferenceGrant has a ResolvedRefs Condition with status False and Reason RefNotPermitted", func(t *testing.T) { resolvedRefsCond := metav1.Condition{ Type: string(v1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(v1.RouteReasonRefNotPermitted), } kubernetes.HTTPRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, resolvedRefsCond) }) t.Run("HTTP Request to invalid cross-namespace backend must receive a 500", func(t *testing.T) { http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{ Request: http.Request{ Method: "GET", Path: "/", }, Response: http.Response{StatusCode: 500}, }) }) }, }
var HTTPRouteInvalidCrossNamespaceParentRef = suite.ConformanceTest{ ShortName: "HTTPRouteInvalidCrossNamespaceParentRef", Description: "A single HTTPRoute in the gateway-conformance-web-backend namespace should fail to attach to a Gateway in another namespace that it is not allowed to", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-invalid-cross-namespace-parent-ref.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { gwNN := types.NamespacedName{Name: "same-namespace", Namespace: "gateway-conformance-infra"} routeNN := types.NamespacedName{Name: "invalid-cross-namespace-parent-ref", Namespace: "gateway-conformance-web-backend"} kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) t.Run("HTTPRoute should have an Accepted: false condition with reason NotAllowedByListeners", func(t *testing.T) { acceptedCond := metav1.Condition{ Type: string(v1.RouteConditionAccepted), Status: metav1.ConditionFalse, Reason: string(v1.RouteReasonNotAllowedByListeners), } kubernetes.HTTPRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, acceptedCond) }) t.Run("Route should not have Parents set in status", func(t *testing.T) { kubernetes.HTTPRouteMustHaveNoAcceptedParents(t, suite.Client, suite.TimeoutConfig, routeNN) }) t.Run("Gateway should have 0 Routes attached", func(t *testing.T) { kubernetes.GatewayMustHaveZeroRoutes(t, suite.Client, suite.TimeoutConfig, gwNN) }) }, }
var HTTPRouteInvalidNonExistentBackendRef = suite.ConformanceTest{ ShortName: "HTTPRouteInvalidNonExistentBackendRef", Description: "A single HTTPRoute in the gateway-conformance-infra namespace should set a ResolvedRefs status False with reason BackendNotFound and return 500 when binding to a Gateway in the same namespace if the route has a BackendRef Service that does not exist", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-invalid-nonexistent-backendref.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "invalid-nonexistent-backend-ref", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: "gateway-conformance-infra"} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) t.Run("HTTPRoute with only a nonexistent BackendRef has a ResolvedRefs Condition with status False and Reason BackendNotFound", func(t *testing.T) { resolvedRefsCond := metav1.Condition{ Type: string(v1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(v1.RouteReasonBackendNotFound), } kubernetes.HTTPRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, resolvedRefsCond) }) t.Run("HTTP Request to invalid nonexistent backend receive a 500", func(t *testing.T) { http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{ Request: http.Request{ Method: "GET", Path: "/", }, Response: http.Response{StatusCode: 500}, }) }) }, }
var HTTPRouteInvalidParentRefNotMatchingListenerPort = suite.ConformanceTest{ ShortName: "HTTPRouteInvalidParentRefNotMatchingListenerPort", Description: "A single HTTPRoute in the gateway-conformance-infra namespace should set the Accepted status to False with reason NoMatchingParent when attempting to bind to a Gateway that does not have a matching ListenerPort.", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteDestinationPortMatching, }, Manifests: []string{"tests/httproute-invalid-parentref-not-matching-listener-port.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "httproute-listener-not-matching-route-port", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: "gateway-conformance-infra"} kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) t.Run("HTTPRoute with no matching port in ParentRef has an Accepted Condition with status False and Reason NoMatchingParent", func(t *testing.T) { acceptedCond := metav1.Condition{ Type: string(v1.RouteConditionAccepted), Status: metav1.ConditionFalse, Reason: string(v1.RouteReasonNoMatchingParent), } kubernetes.HTTPRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, acceptedCond) }) t.Run("Route should not have Parents accepted in status", func(t *testing.T) { kubernetes.HTTPRouteMustHaveNoAcceptedParents(t, suite.Client, suite.TimeoutConfig, routeNN) }) t.Run("Gateway should have 0 Routes attached", func(t *testing.T) { kubernetes.GatewayMustHaveZeroRoutes(t, suite.Client, suite.TimeoutConfig, gwNN) }) }, }
var HTTPRouteInvalidParentRefNotMatchingSectionName = suite.ConformanceTest{ ShortName: "HTTPRouteInvalidParentRefNotMatchingSectionName", Description: "A single HTTPRoute in the gateway-conformance-infra namespace should set the Accepted status to False with reason NoMatchingParent when attempting to bind to a Gateway that does not have a matching SectionName.", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-invalid-parentref-not-matching-section-name.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "httproute-listener-not-matching-section-name", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: "gateway-conformance-infra"} t.Run("HTTPRoute with no matching sectionName in ParentRef has an Accepted Condition with status False and Reason NoMatchingParent", func(t *testing.T) { resolvedRefsCond := metav1.Condition{ Type: string(v1.RouteConditionAccepted), Status: metav1.ConditionFalse, Reason: string(v1.RouteReasonNoMatchingParent), } kubernetes.HTTPRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, resolvedRefsCond) }) t.Run("Route should not have Parents accepted in status", func(t *testing.T) { kubernetes.HTTPRouteMustHaveNoAcceptedParents(t, suite.Client, suite.TimeoutConfig, routeNN) }) t.Run("Gateway should have 0 Routes attached", func(t *testing.T) { kubernetes.GatewayMustHaveZeroRoutes(t, suite.Client, suite.TimeoutConfig, gwNN) }) }, }
var HTTPRouteInvalidParentRefSectionNameNotMatchingPort = suite.ConformanceTest{ ShortName: "HTTPRouteInvalidParentRefSectionNameNotMatchingPort", Description: "A single HTTPRoute in the gateway-conformance-infra namespace should set the Accepted status to False with reason NoMatchingParent when attempting to bind to a Gateway that SectionName does not match Port value.", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteParentRefPort, }, Manifests: []string{"tests/httproute-invalid-parentref-section-name-not-matching-port.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "httproute-listener-section-name-not-matching-port", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "gateway-with-one-not-matching-port-and-section-name-route", Namespace: "gateway-conformance-infra"} t.Run("HTTPRoute with sectionName does not match Port in ParentRef has an Accepted Condition with status False and Reason NoMatchingParent", func(t *testing.T) { acceptedCond := metav1.Condition{ Type: string(v1.RouteConditionAccepted), Status: metav1.ConditionFalse, Reason: string(v1.RouteReasonNoMatchingParent), } kubernetes.HTTPRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, acceptedCond) }) t.Run("Route should not have Parents accepted in status", func(t *testing.T) { kubernetes.HTTPRouteMustHaveNoAcceptedParents(t, suite.Client, suite.TimeoutConfig, routeNN) }) t.Run("Gateway should have 0 Routes attached", func(t *testing.T) { kubernetes.GatewayMustHaveZeroRoutes(t, suite.Client, suite.TimeoutConfig, gwNN) }) }, }
var HTTPRouteInvalidReferenceGrant = suite.ConformanceTest{ ShortName: "HTTPRouteInvalidReferenceGrant", Description: "A single HTTPRoute in the gateway-conformance-infra namespace, with a backendRef in another namespace without valid ReferenceGrant, should have the ResolvedRefs condition set to False and not forward HTTP requests to any backend", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportReferenceGrant, }, Manifests: []string{"tests/httproute-invalid-reference-grant.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "reference-grant", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: "gateway-conformance-infra"} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) t.Run("HTTPRoute with BackendRef in another namespace and no ReferenceGrant covering the Service has a ResolvedRefs Condition with status False and Reason RefNotPermitted", func(t *testing.T) { resolvedRefsCond := metav1.Condition{ Type: string(v1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(v1.RouteReasonRefNotPermitted), } kubernetes.HTTPRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, resolvedRefsCond) }) t.Run("Simple HTTP request not should reach web-backend", func(t *testing.T) { http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{ Request: http.Request{ Method: "GET", Path: "/", }, Response: http.Response{StatusCode: 500}, Backend: "web-backend", Namespace: "gateway-conformance-web-backend", }) }) }, }
var HTTPRouteListenerHostnameMatching = suite.ConformanceTest{ ShortName: "HTTPRouteListenerHostnameMatching", Description: "Multiple HTTP listeners with the same port and different hostnames, each with a different HTTPRoute", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-listener-hostname-matching.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ns}) routeNN1 := types.NamespacedName{Name: "backend-v1", Namespace: ns} routeNN2 := types.NamespacedName{Name: "backend-v2", Namespace: ns} routeNN3 := types.NamespacedName{Name: "backend-v3", Namespace: ns} gwNN := types.NamespacedName{Name: "httproute-listener-hostname-matching", Namespace: ns} kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN, "listener-1"), routeNN1) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN1, gwNN) kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN, "listener-2"), routeNN2) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN2, gwNN) gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN, "listener-3", "listener-4"), routeNN3) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN3, gwNN) testCases := []http.ExpectedResponse{{ Request: http.Request{Host: "bar.com", Path: "/"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Host: "foo.bar.com", Path: "/"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Host: "baz.bar.com", Path: "/"}, Backend: "infra-backend-v3", Namespace: ns, }, { Request: http.Request{Host: "boo.bar.com", Path: "/"}, Backend: "infra-backend-v3", Namespace: ns, }, { Request: http.Request{Host: "multiple.prefixes.bar.com", Path: "/"}, Backend: "infra-backend-v3", Namespace: ns, }, { Request: http.Request{Host: "multiple.prefixes.foo.com", Path: "/"}, Backend: "infra-backend-v3", Namespace: ns, }, { Request: http.Request{Host: "foo.com", Path: "/"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Host: "no.matching.host", Path: "/"}, Response: http.Response{StatusCode: 404}, }} 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) }) } }, }
var HTTPRouteListenerPortMatching = suite.ConformanceTest{ ShortName: "HTTPRouteListenerPortMatching", Description: "Multiple HTTP listeners with different ports, each with a different HTTPRoute", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteParentRefPort, }, Manifests: []string{"tests/httproute-listener-port-matching.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ns}) routeNN1 := types.NamespacedName{Name: "backend-v1", Namespace: ns} routeNN2 := types.NamespacedName{Name: "backend-v2", Namespace: ns} routeNN3 := types.NamespacedName{Name: "backend-v3", Namespace: ns} gwNN := types.NamespacedName{Name: "httproute-listener-port-matching", Namespace: ns} gwAddr80 := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN, "listener-1"), routeNN1) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN1, gwNN) gwAddr8080 := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN, "listener-2"), routeNN2) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN2, gwNN) gwAddr8090 := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN, "listener-4"), routeNN3) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN3, gwNN) testCases := []http.ExpectedResponse{{ Request: http.Request{Host: "foo.com", Path: "/"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Host: "foo.com:8080", Path: "/"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Host: "bar.com:8080", Path: "/"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Host: "foo.com:8090", Path: "/"}, Backend: "infra-backend-v3", Namespace: ns, }, { Request: http.Request{Host: "bar.com:8090", Path: "/"}, Response: http.Response{StatusCode: 404}, }} for i := range testCases { tc := testCases[i] t.Run(tc.GetTestCaseName(i), func(t *testing.T) { t.Parallel() gwAddr := gwAddr80 hostport := strings.Split(tc.Request.Host, ":") if len(hostport) == 2 { switch hostport[1] { case "8080": gwAddr = gwAddr8080 case "8090": gwAddr = gwAddr8090 } } http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc) }) } }, }
var HTTPRouteMatching = suite.ConformanceTest{ ShortName: "HTTPRouteMatching", Description: "A single HTTPRoute with path and header matching for different backends", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-matching.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "matching", 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: "/"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/example"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Version": "one"}}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/v2"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/v2/example"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/", Headers: map[string]string{"Version": "two"}}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/v2/"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/v2example"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/foo/v2/example"}, Backend: "infra-backend-v1", 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) }) } }, }
var HTTPRouteMatchingAcrossRoutes = suite.ConformanceTest{ ShortName: "HTTPRouteMatchingAcrossRoutes", Description: "Two HTTPRoutes with path matching for different backends", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-matching-across-routes.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN1 := types.NamespacedName{Name: "matching-part1", Namespace: ns} routeNN2 := types.NamespacedName{Name: "matching-part2", Namespace: ns} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN1, routeNN2) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN1, gwNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN2, gwNN) testCases := []http.ExpectedResponse{{ Request: http.Request{ Host: "example.com", Path: "/", }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Host: "example.com", Path: "/example", }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Host: "example.net", Path: "/example", }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Host: "example.com", Path: "/example", Headers: map[string]string{"Version": "one"}, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Host: "example.com", Path: "/v2", }, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{ Host: "example.net", Path: "/v2", }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Host: "example.com", Path: "/v2/example", }, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{ Host: "example.com", Path: "/", Headers: map[string]string{"Version": "two"}, }, Backend: "infra-backend-v2", 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) }) } }, }
var HTTPRouteMethodMatching = suite.ConformanceTest{ ShortName: "HTTPRouteMethodMatching", Description: "A single HTTPRoute with method matching for different backends", Manifests: []string{"tests/httproute-method-matching.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteMethodMatching, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "method-matching", 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{Method: "POST", Path: "/"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Method: "GET", Path: "/"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Method: "HEAD", Path: "/"}, Response: http.Response{StatusCode: 404}, }, } testCases = append(testCases, []http.ExpectedResponse{ { Request: http.Request{Path: "/path1", Method: "GET"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Headers: map[string]string{"version": "one"}, Path: "/", Method: "PUT"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Headers: map[string]string{"version": "two"}, Path: "/path2", Method: "POST"}, Backend: "infra-backend-v3", Namespace: ns, }, }...) testCases = append(testCases, []http.ExpectedResponse{ { Request: http.Request{Path: "/path3", Method: "PATCH"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Headers: map[string]string{"version": "three"}, Path: "/path4", Method: "DELETE"}, Backend: "infra-backend-v1", Namespace: ns, }, }...) testCases = append(testCases, []http.ExpectedResponse{ { Request: http.Request{Path: "/", Method: "PUT"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Path: "/path4", Method: "DELETE"}, Response: http.Response{StatusCode: 404}, }, }...) testCases = append(testCases, []http.ExpectedResponse{ { Request: http.Request{Path: "/path5", Method: "PATCH"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Headers: map[string]string{"version": "four"}, Path: "/", Method: "PATCH"}, Backend: "infra-backend-v2", 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) }) } }, }
var HTTPRouteObservedGenerationBump = suite.ConformanceTest{ ShortName: "HTTPRouteObservedGenerationBump", Description: "A HTTPRoute in the gateway-conformance-infra namespace should update the observedGeneration in all of it's Status.Conditions after an update to the spec", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-observed-generation-bump.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "observed-generation-bump", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: "gateway-conformance-infra"} t.Run("observedGeneration should increment", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), suite.TimeoutConfig.LatestObservedGenerationSet) defer cancel() namespaces := []string{"gateway-conformance-infra"} kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, namespaces) original := &v1.HTTPRoute{} err := suite.Client.Get(ctx, routeNN, original) require.NoErrorf(t, err, "error getting HTTPRoute: %v", err) kubernetes.HTTPRouteMustHaveLatestConditions(t, original) mutate := original.DeepCopy() mutate.Spec.Rules[0].BackendRefs[0].Name = "infra-backend-v2" err = suite.Client.Patch(ctx, mutate, client.MergeFrom(original)) require.NoErrorf(t, err, "error patching the HTTPRoute: %v", err) kubernetes.HTTPRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, metav1.Condition{ Type: string(v1.RouteConditionAccepted), Status: metav1.ConditionTrue, Reason: "", }) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) updated := &v1.HTTPRoute{} err = suite.Client.Get(ctx, routeNN, updated) require.NoErrorf(t, err, "error getting Gateway: %v", err) kubernetes.HTTPRouteMustHaveLatestConditions(t, updated) require.NotEqual(t, original.Generation, updated.Generation, "generation should change after an update") }) }, }
var HTTPRoutePartiallyInvalidViaInvalidReferenceGrant = suite.ConformanceTest{ ShortName: "HTTPRoutePartiallyInvalidViaInvalidReferenceGrant", Description: "A single HTTPRoute in the gateway-conformance-infra namespace should attach to a Gateway in the same namespace if the route has a backendRef Service in the gateway-conformance-app-backend namespace and a ReferenceGrant exists but does not grant permission to route to that specific Service", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportReferenceGrant, }, Manifests: []string{"tests/httproute-partially-invalid-via-invalid-reference-grant.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "invalid-reference-grant", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: "gateway-conformance-infra"} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, s.Client, s.TimeoutConfig, s.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) t.Run("HTTPRoute with BackendRef in another namespace and no ReferenceGrant covering the Service has a ResolvedRefs Condition with status False and Reason RefNotPermitted", func(t *testing.T) { resolvedRefsCond := metav1.Condition{ Type: string(v1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(v1.RouteReasonRefNotPermitted), } kubernetes.HTTPRouteMustHaveCondition(t, s.Client, s.TimeoutConfig, routeNN, gwNN, resolvedRefsCond) }) t.Run("HTTP Request to invalid backend with missing referenceGrant should receive a 500", func(t *testing.T) { http.MakeRequestAndExpectEventuallyConsistentResponse(t, s.RoundTripper, s.TimeoutConfig, gwAddr, http.ExpectedResponse{ Request: http.Request{ Method: "GET", Path: "/v2", }, Response: http.Response{StatusCode: 500}, }) }) t.Run("HTTP Request to valid sibling backend should succeed", func(t *testing.T) { http.MakeRequestAndExpectEventuallyConsistentResponse(t, s.RoundTripper, s.TimeoutConfig, gwAddr, http.ExpectedResponse{ Request: http.Request{ Method: "GET", Path: "/", }, Response: http.Response{StatusCode: 200}, Backend: "app-backend-v1", Namespace: "gateway-conformance-app-backend", }) }) }, }
var HTTPRoutePathMatchOrder = suite.ConformanceTest{ ShortName: "HTTPRoutePathMatchOrder", Description: "An HTTPRoute where there are multiple matches routing to any given backend follows match order precedence", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-path-match-order.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Namespace: ns, Name: "path-matching-order"} gwNN := types.NamespacedName{Namespace: ns, Name: "same-namespace"} 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: "/match/exact/one"}, Backend: "infra-backend-v3", Namespace: ns, }, { Request: http.Request{Path: "/match/exact"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/match"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/match/prefix/one/any"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/match/prefix/any"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/match/any"}, Backend: "infra-backend-v3", 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) }) } }, }
var HTTPRouteQueryParamMatching = suite.ConformanceTest{ ShortName: "HTTPRouteQueryParamMatching", Description: "A single HTTPRoute with query param matching for different backends", Manifests: []string{"tests/httproute-query-param-matching.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteQueryParamMatching, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Namespace: ns, Name: "query-param-matching"} gwNN := types.NamespacedName{Namespace: ns, Name: "same-namespace"} 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: "/?animal=whale"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/?animal=dolphin"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/?animal=dolphin&color=blue"}, Backend: "infra-backend-v3", Namespace: ns, }, { Request: http.Request{Path: "/?ANIMAL=Whale"}, Backend: "infra-backend-v3", Namespace: ns, }, { Request: http.Request{Path: "/?animal=whale&otherparam=irrelevant"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Path: "/?animal=dolphin&color=yellow"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Path: "/?color=blue"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Path: "/?animal=dog"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Path: "/?animal=whaledolphin"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Path: "/"}, Response: http.Response{StatusCode: 404}, }} testCases = append(testCases, []http.ExpectedResponse{ { Request: http.Request{Path: "/path1?animal=whale"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Headers: map[string]string{"version": "one"}, Path: "/?animal=whale"}, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{Headers: map[string]string{"version": "two"}, Path: "/path2?animal=whale"}, Backend: "infra-backend-v3", Namespace: ns, }, }...) testCases = append(testCases, []http.ExpectedResponse{ { Request: http.Request{Path: "/path3?animal=shark"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Headers: map[string]string{"version": "three"}, Path: "/path4?animal=kraken"}, Backend: "infra-backend-v1", Namespace: ns, }, }...) testCases = append(testCases, []http.ExpectedResponse{ { Request: http.Request{Path: "/?animal=shark"}, Response: http.Response{StatusCode: 404}, }, { Request: http.Request{Path: "/path4?animal=kraken"}, Response: http.Response{StatusCode: 404}, }, }...) testCases = append(testCases, []http.ExpectedResponse{ { Request: http.Request{Path: "/path5?animal=hydra"}, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{Headers: map[string]string{"version": "four"}, Path: "/?animal=hydra"}, Backend: "infra-backend-v3", 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) }) } }, }
var HTTPRouteRedirectHostAndStatus = suite.ConformanceTest{ ShortName: "HTTPRouteRedirectHostAndStatus", Description: "An HTTPRoute with hostname and statusCode redirect filters", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-redirect-host-and-status.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "redirect-host-and-status", 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: "/hostname-redirect", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 302, }, RedirectRequest: &roundtripper.RedirectRequest{ Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Path: "/host-and-status", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 301, }, RedirectRequest: &roundtripper.RedirectRequest{ Host: "example.org", }, 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) }) } }, }
var HTTPRouteRedirectPath = suite.ConformanceTest{ ShortName: "HTTPRouteRedirectPath", Description: "An HTTPRoute with scheme redirect filter", Manifests: []string{"tests/httproute-redirect-path.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRoutePathRedirect, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "redirect-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: "/original-prefix/lemon", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 302, }, RedirectRequest: &roundtripper.RedirectRequest{ Path: "/replacement-prefix/lemon", }, Namespace: ns, }, { Request: http.Request{ Path: "/full/path/original", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 302, }, RedirectRequest: &roundtripper.RedirectRequest{ Path: "/full-path-replacement", }, Namespace: ns, }, { Request: http.Request{ Path: "/path-and-host", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 302, }, RedirectRequest: &roundtripper.RedirectRequest{ Host: "example.org", Path: "/replacement-prefix", }, Namespace: ns, }, { Request: http.Request{ Path: "/path-and-status", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 301, }, RedirectRequest: &roundtripper.RedirectRequest{ Path: "/replacement-prefix", }, Namespace: ns, }, { Request: http.Request{ Path: "/full-path-and-host", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 302, }, RedirectRequest: &roundtripper.RedirectRequest{ Host: "example.org", Path: "/replacement-full", }, Namespace: ns, }, { Request: http.Request{ Path: "/full-path-and-status", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 301, }, RedirectRequest: &roundtripper.RedirectRequest{ Path: "/replacement-full", }, 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) }) } }, }
var HTTPRouteRedirectPort = suite.ConformanceTest{ ShortName: "HTTPRouteRedirectPort", Description: "An HTTPRoute with a port redirect filter", Manifests: []string{"tests/httproute-redirect-port.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRoutePortRedirect, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "redirect-port", 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: "/port", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 302, }, RedirectRequest: &roundtripper.RedirectRequest{ Port: "8083", }, Namespace: ns, }, { Request: http.Request{ Path: "/port-and-host", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 302, }, RedirectRequest: &roundtripper.RedirectRequest{ Host: "example.org", Port: "8083", }, Namespace: ns, }, { Request: http.Request{ Path: "/port-and-status", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 301, }, RedirectRequest: &roundtripper.RedirectRequest{ Port: "8083", }, Namespace: ns, }, { Request: http.Request{ Path: "/port-and-host-and-status", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 302, }, RedirectRequest: &roundtripper.RedirectRequest{ Port: "8083", Host: "example.org", }, 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) }) } }, }
var HTTPRouteRedirectPortAndScheme = suite.ConformanceTest{ ShortName: "HTTPRouteRedirectPortAndScheme", Description: "An HTTPRoute with port and scheme redirect filter", Manifests: []string{"tests/httproute-redirect-port-and-scheme.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRoutePortRedirect, features.SupportGatewayPort8080, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} routeNN := types.NamespacedName{Name: "http-route-for-listener-on-port-80", Namespace: ns} gwAddr80 := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) gwNN = types.NamespacedName{Name: "same-namespace-with-http-listener-on-8080", Namespace: ns} routeNN = types.NamespacedName{Name: "http-route-for-listener-on-port-8080", Namespace: ns} gwAddr8080 := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) gwNN = types.NamespacedName{Name: "same-namespace-with-https-listener", Namespace: ns} routeNN = types.NamespacedName{Name: "http-route-for-listener-on-port-443", Namespace: ns} gwAddr443 := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) certNN := types.NamespacedName{Name: "tls-validity-checks-certificate", Namespace: ns} cPem, keyPem, err := GetTLSSecret(suite.Client, certNN) if err != nil { t.Fatalf("unexpected error finding TLS secret: %v", err) } testCases := []http.ExpectedResponse{ { Request: http.Request{ Path: "/scheme-nil-and-port-nil", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "http", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Path: "/scheme-nil-and-port-80", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "http", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Path: "/scheme-nil-and-port-8080", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "http", Port: "8080", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Path: "/scheme-https-and-port-nil", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "https", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Path: "/scheme-https-and-port-443", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "https", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Path: "/scheme-https-and-port-8443", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "https", Port: "8443", Host: "example.org", }, Namespace: ns, }, } for i := range testCases { tc := testCases[i] t.Run("http-listener-on-80/"+tc.GetTestCaseName(i), func(t *testing.T) { t.Parallel() http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr80, tc) }) } testCases = []http.ExpectedResponse{ { Request: http.Request{ Path: "/scheme-nil-and-port-nil", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "http", Port: "8080", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Path: "/scheme-nil-and-port-80", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "http", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Path: "/scheme-https-and-port-nil", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "https", Host: "example.org", }, Namespace: ns, }, } for i := range testCases { tc := testCases[i] t.Run("http-listener-on-8080/"+tc.GetTestCaseName(i), func(t *testing.T) { t.Parallel() http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr8080, tc) }) } testCases = []http.ExpectedResponse{ { Request: http.Request{ Host: "example.org", Path: "/scheme-nil-and-port-nil", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "https", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Host: "example.org", Path: "/scheme-nil-and-port-443", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "https", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Host: "example.org", Path: "/scheme-nil-and-port-8443", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "https", Port: "8443", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Host: "example.org", Path: "/scheme-http-and-port-nil", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "http", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Host: "example.org", Path: "/scheme-http-and-port-80", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "http", Host: "example.org", }, Namespace: ns, }, { Request: http.Request{ Host: "example.org", Path: "/scheme-http-and-port-8080", UnfollowRedirect: true, }, Response: http.Response{StatusCode: 302}, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "http", Port: "8080", Host: "example.org", }, Namespace: ns, }, } for i := range testCases { tc := testCases[i] t.Run("https-listener-on-443/"+tc.GetTestCaseName(i), func(t *testing.T) { t.Parallel() tls.MakeTLSRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr443, cPem, keyPem, "example.org", tc) }) } }, }
var HTTPRouteRedirectScheme = suite.ConformanceTest{ ShortName: "HTTPRouteRedirectScheme", Description: "An HTTPRoute with a scheme redirect filter", Manifests: []string{"tests/httproute-redirect-scheme.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteSchemeRedirect, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "redirect-scheme", 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: "/scheme", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 302, }, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "https", }, Namespace: ns, }, { Request: http.Request{ Path: "/scheme-and-host", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 302, }, RedirectRequest: &roundtripper.RedirectRequest{ Host: "example.org", Scheme: "https", }, Namespace: ns, }, { Request: http.Request{ Path: "/scheme-and-status", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 301, }, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "https", }, Namespace: ns, }, { Request: http.Request{ Path: "/scheme-and-host-and-status", UnfollowRedirect: true, }, Response: http.Response{ StatusCode: 302, }, RedirectRequest: &roundtripper.RedirectRequest{ Scheme: "https", Host: "example.org", }, 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) }) } }, }
var HTTPRouteReferenceGrant = suite.ConformanceTest{ ShortName: "HTTPRouteReferenceGrant", Description: "A single HTTPRoute in the gateway-conformance-infra namespace, with a backendRef in the gateway-conformance-web-backend namespace, should attach to Gateway in the gateway-conformance-infra namespace", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportReferenceGrant, }, Manifests: []string{"tests/httproute-reference-grant.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "reference-grant", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: "gateway-conformance-infra"} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) t.Run("Simple HTTP request should reach web-backend", func(t *testing.T) { http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{ Request: http.Request{ Method: "GET", Path: "/", }, Response: http.Response{StatusCode: 200}, Backend: "web-backend", Namespace: "gateway-conformance-web-backend", }) }) ctx, cancel := context.WithTimeout(context.Background(), suite.TimeoutConfig.DeleteTimeout) defer cancel() rg := v1beta1.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "reference-grant", Namespace: "gateway-conformance-web-backend", }, } require.NoError(t, suite.Client.Delete(ctx, &rg)) t.Run("Simple HTTP request should return 500 after deleting the relevant reference grant", func(t *testing.T) { http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{ Request: http.Request{ Method: "GET", Path: "/", }, Response: http.Response{StatusCode: 500}, }) }) }, }
var HTTPRouteRequestHeaderModifier = suite.ConformanceTest{ ShortName: "HTTPRouteRequestHeaderModifier", Description: "An HTTPRoute has request header modifier filters applied correctly", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-request-header-modifier.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "request-header-modifier", 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: "/set", Headers: map[string]string{ "Some-Other-Header": "val", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/set", Headers: map[string]string{ "Some-Other-Header": "val", "X-Header-Set": "set-overwrites-values", }, }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/set", Headers: map[string]string{ "Some-Other-Header": "val", "X-Header-Set": "some-other-value", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/set", Headers: map[string]string{ "Some-Other-Header": "val", "X-Header-Set": "set-overwrites-values", }, }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/add", Headers: map[string]string{ "Some-Other-Header": "val", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/add", Headers: map[string]string{ "Some-Other-Header": "val", "X-Header-Add": "add-appends-values", }, }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/add", Headers: map[string]string{ "Some-Other-Header": "val", "X-Header-Add": "some-other-value", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/add", Headers: map[string]string{ "Some-Other-Header": "val", "X-Header-Add": "some-other-value,add-appends-values", }, }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/remove", Headers: map[string]string{ "X-Header-Remove": "val", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/remove", }, AbsentHeaders: []string{"X-Header-Remove"}, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/multiple", Headers: map[string]string{ "X-Header-Set-2": "set-val-2", "X-Header-Add-2": "add-val-2", "X-Header-Remove-2": "remove-val-2", "Another-Header": "another-header-val", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/multiple", Headers: map[string]string{ "X-Header-Set-1": "header-set-1", "X-Header-Set-2": "header-set-2", "X-Header-Add-1": "header-add-1", "X-Header-Add-2": "add-val-2,header-add-2", "X-Header-Add-3": "header-add-3", "Another-Header": "another-header-val", }, }, AbsentHeaders: []string{"X-Header-Remove-1", "X-Header-Remove-2"}, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/case-insensitivity", Headers: map[string]string{ "x-header-set": "original-val-set", "x-header-add": "original-val-add", "x-header-remove": "original-val-remove", "Another-Header": "another-header-val", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/case-insensitivity", Headers: map[string]string{ "X-Header-Set": "header-set", "X-Header-Add": "original-val-add,header-add", "Another-Header": "another-header-val", }, }, AbsentHeaders: []string{"x-header-remove", "X-Header-Remove"}, }, Backend: "infra-backend-v1", 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) }) } }, }
var HTTPRouteRequestMirror = suite.ConformanceTest{ ShortName: "HTTPRouteRequestMirror", Description: "An HTTPRoute with request mirror filter", Manifests: []string{"tests/httproute-request-mirror.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteRequestMirror, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "request-mirror", Namespace: ns} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) testCases := []http.ExpectedResponse{ { Request: http.Request{ Path: "/mirror", }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/mirror", }, }, Backend: "infra-backend-v1", MirroredTo: []http.BackendRef{{ Name: "infra-backend-v2", Namespace: ns, }}, Namespace: ns, }, { Request: http.Request{ Path: "/mirror-and-modify-headers", Headers: map[string]string{ "X-Header-Remove": "remove-val", "X-Header-Add-Append": "append-val-1", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/mirror-and-modify-headers", Headers: map[string]string{ "X-Header-Add": "header-val-1", "X-Header-Add-Append": "append-val-1,header-val-2", "X-Header-Set": "set-overwrites-values", }, }, AbsentHeaders: []string{"X-Header-Remove"}, }, Namespace: ns, Backend: "infra-backend-v1", MirroredTo: []http.BackendRef{{ Name: "infra-backend-v2", 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) http.ExpectMirroredRequest(t, suite.Client, suite.Clientset, tc.MirroredTo, tc.Request.Path) }) } }, }
var HTTPRouteRequestMultipleMirrors = suite.ConformanceTest{ ShortName: "HTTPRouteRequestMultipleMirrors", Description: "An HTTPRoute with multiple request mirror filters", Manifests: []string{"tests/httproute-request-multiple-mirrors.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteRequestMirror, features.SupportHTTPRouteRequestMultipleMirrors, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "request-multiple-mirrors", Namespace: ns} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) testCases := []http.ExpectedResponse{ { Request: http.Request{ Path: "/multi-mirror", }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/multi-mirror", }, }, Backend: "infra-backend-v1", MirroredTo: []http.BackendRef{ { Name: "infra-backend-v2", Namespace: ns, }, { Name: "infra-backend-v3", Namespace: ns, }, }, Namespace: ns, }, { Request: http.Request{ Path: "/multi-mirror-and-modify-request-headers", Headers: map[string]string{ "X-Header-Remove": "remove-val", "X-Header-Add-Append": "append-val-1", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/multi-mirror-and-modify-request-headers", Headers: map[string]string{ "X-Header-Add": "header-val-1", "X-Header-Add-Append": "append-val-1,header-val-2", "X-Header-Set": "set-overwrites-values", }, }, AbsentHeaders: []string{"X-Header-Remove"}, }, Namespace: ns, Backend: "infra-backend-v1", MirroredTo: []http.BackendRef{ { Name: "infra-backend-v2", Namespace: ns, }, { Name: "infra-backend-v3", 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) http.ExpectMirroredRequest(t, suite.Client, suite.Clientset, tc.MirroredTo, tc.Request.Path) }) } }, }
var HTTPRouteResponseHeaderModifier = suite.ConformanceTest{ ShortName: "HTTPRouteResponseHeaderModifier", Description: "An HTTPRoute has response header modifier filters applied correctly", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteResponseHeaderModification, }, Manifests: []string{"tests/httproute-response-header-modifier.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "response-header-modifier", 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: "/set", }, BackendSetResponseHeaders: map[string]string{ "Some-Other-Header": "val", }, Response: http.Response{ Headers: map[string]string{ "Some-Other-Header": "val", "X-Header-Set": "set-overwrites-values", }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/set", }, BackendSetResponseHeaders: map[string]string{ "Some-Other-Header": "val", "X-Header-Set": "some-other-value", }, Response: http.Response{ Headers: map[string]string{ "Some-Other-Header": "val", "X-Header-Set": "set-overwrites-values", }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/add", }, BackendSetResponseHeaders: map[string]string{ "Some-Other-Header": "val", }, Response: http.Response{ Headers: map[string]string{ "Some-Other-Header": "val", "X-Header-Add": "add-appends-values", }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/add", }, BackendSetResponseHeaders: map[string]string{ "Some-Other-Header": "val", "X-Header-Add": "some-other-value", }, Response: http.Response{ Headers: map[string]string{ "Some-Other-Header": "val", "X-Header-Add": "some-other-value,add-appends-values", }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/remove", }, BackendSetResponseHeaders: map[string]string{ "X-Header-Remove": "val", }, Response: http.Response{ AbsentHeaders: []string{"X-Header-Remove"}, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/multiple", }, BackendSetResponseHeaders: map[string]string{ "X-Header-Set-2": "set-val-2", "X-Header-Add-2": "add-val-2", "X-Header-Remove-2": "remove-val-2", "Another-Header": "another-header-val", "X-Header-Remove-1": "val", }, Response: http.Response{ Headers: map[string]string{ "X-Header-Set-1": "header-set-1", "X-Header-Set-2": "header-set-2", "X-Header-Add-1": "header-add-1", "X-Header-Add-2": "add-val-2,header-add-2", "X-Header-Add-3": "header-add-3", "Another-Header": "another-header-val", }, AbsentHeaders: []string{"X-Header-Remove-1", "X-Header-Remove-2"}, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/case-insensitivity", }, BackendSetResponseHeaders: map[string]string{ "x-header-set": "original-val-set", "x-header-add": "original-val-add", "x-header-remove": "original-val-remove", "Another-Header": "another-header-val", }, Response: http.Response{ Headers: map[string]string{ "X-Header-Set": "header-set", "X-Header-Add": "original-val-add,header-add", "X-Lowercase-Add": "lowercase-add", "X-Mixedcase-Add-1": "mixedcase-add-1", "X-Mixedcase-Add-2": "mixedcase-add-2", "X-Uppercase-Add": "uppercase-add", "Another-Header": "another-header-val", }, AbsentHeaders: []string{"x-header-remove", "X-Header-Remove"}, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/response-and-request-header-modifiers", Headers: map[string]string{ "X-Header-Remove": "remove-val", "X-Header-Add-Append": "append-val-1", "X-Header-Echo": "echo", }, }, BackendSetResponseHeaders: map[string]string{ "X-Header-Set-2": "set-val-2", "X-Header-Add-2": "add-val-2", "X-Header-Remove-2": "remove-val-2", "Another-Header": "another-header-val", "X-Header-Remove-1": "remove-val-1", "X-Header-Echo": "echo", }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/response-and-request-header-modifiers", Headers: map[string]string{ "X-Header-Add": "header-val-1", "X-Header-Set": "set-overwrites-values", "X-Header-Add-Append": "append-val-1,header-val-2", "X-Header-Echo": "echo", }, }, AbsentHeaders: []string{"X-Header-Remove"}, }, Response: http.Response{ Headers: map[string]string{ "X-Header-Set-1": "header-set-1", "X-Header-Set-2": "header-set-2", "X-Header-Add-1": "header-add-1", "X-Header-Add-2": "add-val-2,header-add-2", "Another-Header": "another-header-val", "X-Header-Echo": "echo", }, AbsentHeaders: []string{"X-Header-Remove-1", "X-Header-Remove-2"}, }, Backend: "infra-backend-v1", 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) }) } }, }
var HTTPRouteRewriteHost = suite.ConformanceTest{ ShortName: "HTTPRouteRewriteHost", Description: "An HTTPRoute with hostname rewrite filter", Manifests: []string{"tests/httproute-rewrite-host.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteHostRewrite, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "rewrite-host", 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: "/one", Host: "rewrite.example", }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/one", Host: "one.example.org", }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/two", Host: "rewrite.example", }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/two", Host: "example.org", }, }, Backend: "infra-backend-v2", Namespace: ns, }, { Request: http.Request{ Path: "/rewrite-host-and-modify-headers", Host: "rewrite.example", Headers: map[string]string{ "X-Header-Remove": "remove-val", "X-Header-Add-Append": "append-val-1", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/rewrite-host-and-modify-headers", Host: "test.example.org", Headers: map[string]string{ "X-Header-Add": "header-val-1", "X-Header-Add-Append": "append-val-1,header-val-2", "X-Header-Set": "set-overwrites-values", }, }, AbsentHeaders: []string{"X-Header-Remove"}, }, Backend: "infra-backend-v2", 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) }) } }, }
var HTTPRouteRewritePath = suite.ConformanceTest{ ShortName: "HTTPRouteRewritePath", Description: "An HTTPRoute with path rewrite filter", Manifests: []string{"tests/httproute-rewrite-path.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRoutePathRewrite, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "rewrite-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: "/prefix/one/two", }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/one/two", }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/strip-prefix/three", }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/three", }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/strip-prefix", }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/", }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/full/one/two", }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/one", }, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/full/rewrite-path-and-modify-headers/test", Headers: map[string]string{ "X-Header-Remove": "remove-val", "X-Header-Add-Append": "append-val-1", "X-Header-Set": "set-val", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/test", Headers: map[string]string{ "X-Header-Add": "header-val-1", "X-Header-Add-Append": "append-val-1,header-val-2", "X-Header-Set": "set-overwrites-values", }, }, AbsentHeaders: []string{"X-Header-Remove"}, }, Backend: "infra-backend-v1", Namespace: ns, }, { Request: http.Request{ Path: "/prefix/rewrite-path-and-modify-headers/one", Headers: map[string]string{ "X-Header-Remove": "remove-val", "X-Header-Add-Append": "append-val-1", "X-Header-Set": "set-val", }, }, ExpectedRequest: &http.ExpectedRequest{ Request: http.Request{ Path: "/prefix/one", Headers: map[string]string{ "X-Header-Add": "header-val-1", "X-Header-Add-Append": "append-val-1,header-val-2", "X-Header-Set": "set-overwrites-values", }, }, AbsentHeaders: []string{"X-Header-Remove"}, }, Backend: "infra-backend-v1", 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) }) } }, }
var HTTPRouteServiceTypes = suite.ConformanceTest{ ShortName: "HTTPRouteServiceTypes", Description: "A single HTTPRoute should be able to route traffic to various service type backends", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-service-types.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { var ( typeManualEndpointSlices = []string{ "manual-endpointslices", "headless-manual-endpointslices", } typeManaged = []string{ "headless", } serviceTypes = make([]string, 0, len(typeManualEndpointSlices)+len(typeManaged)) ctx = context.TODO() ns = "gateway-conformance-infra" routeNN = types.NamespacedName{Name: "service-types", Namespace: ns} gwNN = types.NamespacedName{Name: "same-namespace", Namespace: ns} ) serviceTypes = append(serviceTypes, typeManualEndpointSlices...) serviceTypes = append(serviceTypes, typeManaged...) gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) deployment := &appsv1.Deployment{} err := suite.Client.Get(ctx, client.ObjectKey{Namespace: ns, Name: "infra-backend-v1"}, deployment) require.NoError(t, err, "Failed to fetch Deployment 'infra-backend-v1'") selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector) require.NoError(t, err, "Failed to parse Deployment selector") pods := &corev1.PodList{} err = suite.Client.List(ctx, pods, client.MatchingLabelsSelector{Selector: selector}, client.InNamespace(ns)) require.NoError(t, err, "Failed to list 'infra-backend-v1' Pods") require.NotEmpty(t, pods, "Expected 'infra-backend-v1' to have running Pods") setupEndpointSlices(t, suite.Client, typeManualEndpointSlices, ns, pods) for i, path := range serviceTypes { expected := http.ExpectedResponse{ Request: http.Request{Path: "/" + path}, Response: http.Response{StatusCode: 200}, Backend: "infra-backend-v1", Namespace: "gateway-conformance-infra", } t.Run(expected.GetTestCaseName(i), func(t *testing.T) { t.Parallel() http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expected) }) } }, }
var HTTPRouteSimpleSameNamespace = suite.ConformanceTest{ ShortName: "HTTPRouteSimpleSameNamespace", Description: "A single HTTPRoute in the gateway-conformance-infra namespace attaches to a Gateway in the same namespace", Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Manifests: []string{"tests/httproute-simple-same-namespace.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := v1beta1.Namespace("gateway-conformance-infra") routeNN := types.NamespacedName{Name: "gateway-conformance-infra-test", Namespace: string(ns)} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: string(ns)} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) t.Run("Simple HTTP request should reach infra-backend", func(t *testing.T) { http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, http.ExpectedResponse{ Request: http.Request{Path: "/"}, Response: http.Response{StatusCode: 200}, Backend: "infra-backend-v1", Namespace: "gateway-conformance-infra", }) }) }, }
var HTTPRouteTimeoutBackendRequest = suite.ConformanceTest{ ShortName: "HTTPRouteTimeoutBackendRequest", Description: "An HTTPRoute with backend request timeout", Manifests: []string{"tests/httproute-timeout-backend-request.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteBackendTimeout, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "backend-request-timeout", 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: "/backend-timeout"}, Response: http.Response{StatusCode: 200}, Namespace: ns, }, { Request: http.Request{Path: "/backend-timeout?delay=1s"}, Response: http.Response{StatusCode: 504}, Namespace: ns, }, { Request: http.Request{Path: "/disable-backend-timeout?delay=1s"}, 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) }) } }, }
var HTTPRouteTimeoutRequest = suite.ConformanceTest{ ShortName: "HTTPRouteTimeoutRequest", Description: "An HTTPRoute with request timeout", Manifests: []string{"tests/httproute-timeout-request.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, features.SupportHTTPRouteRequestTimeout, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "request-timeout", 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: "/request-timeout"}, Response: http.Response{StatusCode: 200}, Namespace: ns, }, { Request: http.Request{Path: "/request-timeout?delay=1s"}, Response: http.Response{StatusCode: 504}, Namespace: ns, }, { Request: http.Request{Path: "/disable-request-timeout?delay=1s"}, 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) }) } }, }
var HTTPRouteWeight = suite.ConformanceTest{ ShortName: "HTTPRouteWeight", Description: "An HTTPRoute with weighted backends", Manifests: []string{"tests/httproute-weight.yaml"}, Features: []features.FeatureName{ features.SupportGateway, features.SupportHTTPRoute, }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { var ( ns = "gateway-conformance-infra" routeNN = types.NamespacedName{Name: "weighted-backends", 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) t.Run("Requests should have a distribution that matches the weight", func(t *testing.T) { expected := http.ExpectedResponse{ Request: http.Request{Path: "/"}, Response: http.Response{StatusCode: 200}, Namespace: "gateway-conformance-infra", } http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expected) for i := 0; i < 10; i++ { if err := testDistribution(t, suite, gwAddr, expected); err != nil { t.Logf("Traffic distribution test failed (%d/10): %s", i+1, err) } else { return } } t.Fatal("Weighted distribution tests failed") }) }, }
var MeshBasic = suite.ConformanceTest{ ShortName: "MeshBasic", Description: "A mesh client can communicate with a mesh server. This tests basic reachability with no configuration applied.", Features: []features.FeatureName{ features.SupportMesh, }, Manifests: []string{}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { client := echo.ConnectToApp(t, s, echo.MeshAppEchoV1) cases := []http.ExpectedResponse{{ Request: http.Request{ Host: "echo", Method: "GET", }, Response: http.Response{ StatusCode: 200, }, }} for i := range cases { tc := cases[i] t.Run(tc.GetTestCaseName(i), func(t *testing.T) { client.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, s.TimeoutConfig) }) } }, }
var MeshConsumerRoute = suite.ConformanceTest{ ShortName: "MeshConsumerRoute", Description: "An HTTPRoute in a namespace other than its parentRef's namespace only affects requests from the route's namespace", Features: []features.FeatureName{ features.SupportMesh, features.SupportMeshConsumerRoute, features.SupportHTTPRoute, features.SupportHTTPRouteResponseHeaderModification, }, Manifests: []string{"tests/mesh-consumer-route.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { consumerClient := echo.ConnectToAppInNamespace(t, s, echo.MeshAppEchoV1, "gateway-conformance-mesh-consumer") consumerCases := []http.ExpectedResponse{ { TestCaseName: "request from consumer route's namespace modified by HTTPRoute", Request: http.Request{ Host: "echo-v1.gateway-conformance-mesh", Method: "GET", Path: "/", }, Response: http.Response{ StatusCode: 200, Headers: map[string]string{ "X-Header-Set": "set", }, }, Backend: "echo-v1", }, } producerClient := echo.ConnectToAppInNamespace(t, s, echo.MeshAppEchoV1, "gateway-conformance-mesh") producerCases := []http.ExpectedResponse{ { TestCaseName: "request not from consumer route's namespace not modified by HTTPRoute", Request: http.Request{ Host: "echo-v1.gateway-conformance-mesh", Method: "GET", Path: "/", }, Response: http.Response{ StatusCode: 200, AbsentHeaders: []string{"X-Header-Set"}, }, Backend: "echo-v1", }, } for i, tc := range consumerCases { t.Run(tc.GetTestCaseName(i), func(t *testing.T) { consumerClient.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, s.TimeoutConfig) }) } for i, tc := range producerCases { t.Run(tc.GetTestCaseName(i), func(t *testing.T) { producerClient.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, s.TimeoutConfig) }) } }, }
var MeshFrontend = suite.ConformanceTest{ ShortName: "MeshFrontend", Description: "Mesh rules should only apply to the associated frontend", Features: []features.FeatureName{ features.SupportMesh, features.SupportHTTPRoute, features.SupportHTTPRouteResponseHeaderModification, }, Manifests: []string{"tests/mesh-frontend.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { client := echo.ConnectToApp(t, s, echo.MeshAppEchoV1) v2 := echo.ConnectToApp(t, s, echo.MeshAppEchoV2) cases := []http.ExpectedResponse{ { TestCaseName: "Send to service", Request: http.Request{ Host: "echo-v2", Method: "GET", }, Response: http.Response{ StatusCode: 200, Headers: map[string]string{ "X-Header-Set": "set", }, }, Backend: "echo-v2", }, { TestCaseName: "Send to pod IP", Request: http.Request{ Host: http.Ipv6SafeHost(v2.Address) + ":8080", Method: "GET", }, Response: http.Response{ StatusCode: 200, AbsentHeaders: []string{"X-Header-Set"}, }, Backend: "echo-v2", }, } for i := range cases { tc := cases[i] t.Run(tc.GetTestCaseName(i), func(t *testing.T) { client.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, s.TimeoutConfig) }) } }, }
var MeshFrontendHostname = suite.ConformanceTest{ ShortName: "MeshFrontendHostname", Description: "Mesh parentRef matches Service IP (not Host)", Features: []features.FeatureName{ features.SupportMesh, features.SupportMeshClusterIPMatching, features.SupportHTTPRoute, features.SupportHTTPRouteResponseHeaderModification, }, Manifests: []string{"tests/mesh-frontend.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { client := echo.ConnectToApp(t, s, echo.MeshAppEchoV1) cases := []http.ExpectedResponse{ { TestCaseName: "Send to service with wrong hostname", Request: http.Request{ Host: "echo-v2", Headers: map[string]string{ "Host": "echo-v1", }, Method: "GET", }, Response: http.Response{ StatusCode: 200, Headers: map[string]string{ "X-Header-Set": "set", }, }, Backend: "echo-v2", }, { TestCaseName: "Send to other service with matching hostname", Request: http.Request{ Host: "echo-v1", Headers: map[string]string{ "Host": "echo-v2", }, Method: "GET", }, Response: http.Response{ StatusCode: 200, AbsentHeaders: []string{"X-Header-Set"}, }, Backend: "echo-v1", }, } for i := range cases { tc := cases[i] t.Run(tc.GetTestCaseName(i), func(t *testing.T) { client.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, s.TimeoutConfig) }) } }, }
var MeshPorts = suite.ConformanceTest{ ShortName: "MeshPorts", Description: "A mesh route can optionally configure 'port' in parentRef", Features: []features.FeatureName{ features.SupportMesh, features.SupportHTTPRoute, features.SupportHTTPRouteParentRefPort, features.SupportHTTPRouteResponseHeaderModification, }, Manifests: []string{"tests/mesh-ports.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { client := echo.ConnectToApp(t, s, echo.MeshAppEchoV1) cases := []http.ExpectedResponse{ { TestCaseName: "Explicit port set, send to that port", Request: http.Request{ Host: "echo-v1", Method: "GET", }, Response: http.Response{ StatusCode: 200, Headers: map[string]string{ "X-Header-Set": "v1", }, }, Backend: "echo-v1", }, { TestCaseName: "Explicit port, send to an excluded port", Request: http.Request{ Host: "echo-v1:8080", Method: "GET", }, Response: http.Response{ StatusCode: 200, AbsentHeaders: []string{"X-Header-Set"}, }, Backend: "echo-v1", }, { TestCaseName: "No port set", Request: http.Request{ Host: "echo-v2", Method: "GET", }, Response: http.Response{ StatusCode: 200, Headers: map[string]string{ "X-Header-Set": "v2", }, }, Backend: "echo-v2", }, { TestCaseName: "No port set", Request: http.Request{ Host: "echo-v2:8080", Method: "GET", }, Response: http.Response{ StatusCode: 200, Headers: map[string]string{ "X-Header-Set": "v2", }, }, Backend: "echo-v2", }, } for i := range cases { tc := cases[i] t.Run(tc.GetTestCaseName(i), func(t *testing.T) { client.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, s.TimeoutConfig) }) } }, }
var MeshTrafficSplit = suite.ConformanceTest{ ShortName: "MeshTrafficSplit", Description: "A mesh client can send traffic to a Service which is split between two versions", Features: []features.FeatureName{ features.SupportMesh, features.SupportHTTPRoute, }, Manifests: []string{"tests/mesh-split.yaml"}, Test: func(t *testing.T, s *suite.ConformanceTestSuite) { client := echo.ConnectToApp(t, s, echo.MeshAppEchoV1) cases := []http.ExpectedResponse{ { Request: http.Request{ Host: "echo", Method: "GET", Path: "/v1", }, Response: http.Response{ StatusCode: 200, }, Backend: "echo-v1", }, { Request: http.Request{ Host: "echo", Method: "GET", Path: "/v2", }, Response: http.Response{ StatusCode: 200, }, Backend: "echo-v2", }, } for i := range cases { tc := cases[i] t.Run(tc.GetTestCaseName(i), func(t *testing.T) { client.MakeRequestAndExpectEventuallyConsistentResponse(t, tc, s.TimeoutConfig) }) } }, }
var TLSRouteInvalidReferenceGrant = suite.ConformanceTest{ ShortName: "TLSRouteInvalidReferenceGrant", Description: "A single TLSRoute in the gateway-conformance-infra namespace, with a backendRef in another namespace without valid ReferenceGrant, should have the ResolvedRefs condition set to False", Features: []features.FeatureName{ features.SupportGateway, features.SupportTLSRoute, features.SupportReferenceGrant, }, Manifests: []string{"tests/tlsroute-invalid-reference-grant.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { routeNN := types.NamespacedName{Name: "gateway-conformance-infra-test", Namespace: "gateway-conformance-infra"} gwNN := types.NamespacedName{Name: "gateway-tlsroute-referencegrant", Namespace: "gateway-conformance-infra"} kubernetes.GatewayAndTLSRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) t.Run("TLSRoute with BackendRef in another namespace and no ReferenceGrant covering the Service has a ResolvedRefs Condition with status False and Reason RefNotPermitted", func(t *testing.T) { resolvedRefsCond := metav1.Condition{ Type: string(v1beta1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(v1beta1.RouteReasonRefNotPermitted), } kubernetes.TLSRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, resolvedRefsCond) }) }, }
var TLSRouteSimpleSameNamespace = suite.ConformanceTest{ ShortName: "TLSRouteSimpleSameNamespace", Description: "A single TLSRoute in the gateway-conformance-infra namespace attaches to a Gateway in the same namespace", Features: []features.FeatureName{ features.SupportGateway, features.SupportTLSRoute, }, Manifests: []string{"tests/tlsroute-simple-same-namespace.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "gateway-conformance-infra-test", Namespace: ns} gwNN := types.NamespacedName{Name: "gateway-tlsroute", Namespace: ns} certNN := types.NamespacedName{Name: "tls-passthrough-checks-certificate", Namespace: ns} kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ns}) gwAddr, hostnames := kubernetes.GatewayAndTLSRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) if len(hostnames) != 1 { t.Fatalf("unexpected error in test configuration, found %d hostnames", len(hostnames)) } serverStr := string(hostnames[0]) cPem, keyPem, err := GetTLSSecret(suite.Client, certNN) if err != nil { t.Fatalf("unexpected error finding TLS secret: %v", err) } t.Run("Simple TLS request matching TLSRoute should reach infra-backend", func(t *testing.T) { tls.MakeTLSRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, cPem, keyPem, serverStr, http.ExpectedResponse{ Request: http.Request{Host: serverStr, Path: "/"}, Backend: "tls-backend", Namespace: "gateway-conformance-infra", }) }) }, }
var UDPRouteTest = suite.ConformanceTest{ ShortName: "UDPRoute", Description: "Make sure UDPRoute is working", Manifests: []string{"tests/udproute-simple.yaml"}, Features: []features.FeatureName{ features.SupportUDPRoute, features.SupportGateway, }, Provisional: true, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { t.Run("Simple UDP request matching UDPRoute should reach coredns backend", func(t *testing.T) { namespace := "gateway-conformance-infra" domain := "foo.bar.com." routeNN := types.NamespacedName{Name: "udp-coredns", Namespace: namespace} gwNN := types.NamespacedName{Name: "udp-gateway", Namespace: namespace} gwAddr := kubernetes.GatewayAndUDPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) msg := new(dns.Msg) msg.SetQuestion(domain, dns.TypeA) if err := wait.PollUntilContextTimeout(context.TODO(), time.Second, time.Minute, true, func(_ context.Context) (done bool, err error) { t.Logf("performing DNS query %s on %s", domain, gwAddr) _, err = dns.Exchange(msg, gwAddr) if err != nil { t.Logf("failed to perform a UDP query: %v", err) return false, nil } return true, nil }); err != nil { t.Errorf("failed to perform DNS query: %v", err) } }) }, }
Functions ¶
func GetTLSSecret ¶ added in v0.6.1
GetTLSSecret fetches the named Secret and converts both cert and key to []byte
Types ¶
This section is empty.
Source Files ¶
- gateway-http-listener-isolation.go
- gateway-infrastructure.go
- gateway-invalid-route-kind.go
- gateway-invalid-tls-configuration.go
- gateway-modify-listeners.go
- gateway-observed-generation-bump.go
- gateway-secret-invalid-reference-grant.go
- gateway-secret-missing-reference-grant.go
- gateway-secret-reference-grant-all-in-namespace.go
- gateway-secret-reference-grant-specific.go
- gateway-static-addresses.go
- gateway-with-attached-routes.go
- gatewayclass-observed-generation-bump.go
- grpcroute-exact-method-matching.go
- grpcroute-header-matching.go
- grpcroute-listener-hostname-matching.go
- httproute-backend-protocol-h2c.go
- httproute-backend-protocol-websocket.go
- httproute-cross-namespace.go
- httproute-disallowed-kind.go
- httproute-exact-path-matching.go
- httproute-header-matching.go
- httproute-hostname-intersection.go
- httproute-https-listener.go
- httproute-invalid-backendref-unknown-kind.go
- httproute-invalid-cross-namespace-backend-ref.go
- httproute-invalid-cross-namespace-parent-ref.go
- httproute-invalid-nonexistent-backendref.go
- httproute-invalid-parentref-not-matching-listener-port.go
- httproute-invalid-parentref-not-matching-section-name.go
- httproute-invalid-parentref-section-name-not-matching-port.go
- httproute-invalid-reference-grant.go
- httproute-listener-hostname-matching.go
- httproute-listener-port-matching.go
- httproute-matching-across-routes.go
- httproute-matching.go
- httproute-method-matching.go
- httproute-observed-generation-bump.go
- httproute-partially-invalid-via-invalid-reference-grant.go
- httproute-path-match-order.go
- httproute-query-param-matching.go
- httproute-redirect-host-and-status.go
- httproute-redirect-path.go
- httproute-redirect-port-and-scheme.go
- httproute-redirect-port.go
- httproute-redirect-scheme.go
- httproute-reference-grant.go
- httproute-request-header-modifier.go
- httproute-request-mirror.go
- httproute-request-multiple-mirrors.go
- httproute-response-header-modifier.go
- httproute-rewrite-host.go
- httproute-rewrite-path.go
- httproute-service-types.go
- httproute-simple-same-namespace.go
- httproute-timeout-backend-request.go
- httproute-timeout-request.go
- httproute-weight.go
- main.go
- mesh-basic.go
- mesh-consumer-route.go
- mesh-frontend-hostname.go
- mesh-frontend.go
- mesh-ports.go
- mesh-split.go
- tlsroute-invalid-reference-grant.go
- tlsroute-simple-same-namespace.go
- udproute-simple.go