tests

package
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 29, 2024 License: Apache-2.0 Imports: 36 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ConformanceTests []suite.ConformanceTest
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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")
		})
	},
}
View Source
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)
				})
			}
		})
	},
}
View Source
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")
	},
}
View Source
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)
		})
	},
}
View Source
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)
			})
		}
	},
}
View Source
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")
		})
	},
}
View Source
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")
		})
	},
}
View Source
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)
		})
	},
}
View Source
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)
		})
	},
}
View Source
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)
		})
	},
}
View Source
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)
		})
	},
}
View Source
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.

View Source
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)
		})
	},
}
View Source
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)
		})
	},
}
View Source
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",
			})
		})
	},
}
View Source
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
			})
		})
	},
}
View Source
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,
}
View Source
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",
			})
		})
	},
}
View Source
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)
		})
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
				})
			}
		})
	},
}
View Source
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},
			})
		})
	},
}
View Source
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},
			})
		})
	},
}
View Source
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)
		})
	},
}
View Source
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},
			})
		})
	},
}
View Source
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)
		})
	},
}
View Source
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)
		})
	},
}
View Source
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)
		})
	},
}
View Source
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",
			})
		})
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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")
		})
	},
}
View Source
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",
			})
		})
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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},
			})
		})
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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",
			})
		})
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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")
		})
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
			})
		}
	},
}
View Source
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)
		})
	},
}
View Source
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",
				})
		})
	},
}
View Source
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

func GetTLSSecret(client client.Client, secretName types.NamespacedName) ([]byte, []byte, error)

GetTLSSecret fetches the named Secret and converts both cert and key to []byte

Types

This section is empty.

Source Files

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL