diff --git a/internal/gatewayapi/route_test.go b/internal/gatewayapi/route_test.go new file mode 100644 index 00000000000..f0292c9a54e --- /dev/null +++ b/internal/gatewayapi/route_test.go @@ -0,0 +1,141 @@ +package gatewayapi + +import ( + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + "github.com/envoyproxy/gateway/internal/ir" +) + +func TestFilterEndpointsByIPFamily(t *testing.T) { + tests := []struct { + name string + endpointSlices []*discoveryv1.EndpointSlice + ipFamilyPolicy *corev1.IPFamilyPolicy + expected int + }{ + { + name: "PreferDualStack", + endpointSlices: []*discoveryv1.EndpointSlice{ + {AddressType: discoveryv1.AddressTypeIPv4}, + {AddressType: discoveryv1.AddressTypeIPv6}, + }, + ipFamilyPolicy: ptr.To(corev1.IPFamilyPolicyPreferDualStack), + expected: 2, + }, + { + name: "SingleStack IPv4", + endpointSlices: []*discoveryv1.EndpointSlice{ + {AddressType: discoveryv1.AddressTypeIPv4}, + {AddressType: discoveryv1.AddressTypeIPv6}, + }, + ipFamilyPolicy: ptr.To(corev1.IPFamilyPolicySingleStack), + expected: 1, + }, + { + name: "RequireDualStack", + endpointSlices: []*discoveryv1.EndpointSlice{ + {AddressType: discoveryv1.AddressTypeIPv4}, + {AddressType: discoveryv1.AddressTypeIPv6}, + }, + ipFamilyPolicy: ptr.To(corev1.IPFamilyPolicyRequireDualStack), + expected: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filtered := filterEndpointsByIPFamily(tt.endpointSlices, tt.ipFamilyPolicy) + if len(filtered) != tt.expected { + t.Errorf("Expected %d EndpointSlices, got %d", tt.expected, len(filtered)) + } + }) + } +} + +func TestGetIREndpointsFromEndpointSlices(t *testing.T) { + tests := []struct { + name string + endpointSlices []*discoveryv1.EndpointSlice + portName string + portProtocol corev1.Protocol + expectedEndpoints int + expectedAddrType ir.DestinationAddressType + }{ + { + name: "All IP endpoints", + endpointSlices: []*discoveryv1.EndpointSlice{ + { + ObjectMeta: metav1.ObjectMeta{Name: "slice1"}, + AddressType: discoveryv1.AddressTypeIPv4, + Endpoints: []discoveryv1.Endpoint{ + {Addresses: []string{"192.0.2.1"}}, + {Addresses: []string{"192.0.2.2"}}, + }, + Ports: []discoveryv1.EndpointPort{ + {Name: ptr.To("http"), Port: ptr.To(int32(80)), Protocol: ptr.To(corev1.ProtocolTCP)}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "slice2"}, + AddressType: discoveryv1.AddressTypeIPv6, + Endpoints: []discoveryv1.Endpoint{ + {Addresses: []string{"2001:db8::1"}}, + }, + Ports: []discoveryv1.EndpointPort{ + {Name: ptr.To("http"), Port: ptr.To(int32(80)), Protocol: ptr.To(corev1.ProtocolTCP)}, + }, + }, + }, + portName: "http", + portProtocol: corev1.ProtocolTCP, + expectedEndpoints: 3, + expectedAddrType: ir.IP, + }, + { + name: "Mixed IP and FQDN endpoints", + endpointSlices: []*discoveryv1.EndpointSlice{ + { + ObjectMeta: metav1.ObjectMeta{Name: "slice1"}, + AddressType: discoveryv1.AddressTypeIPv4, + Endpoints: []discoveryv1.Endpoint{ + {Addresses: []string{"192.0.2.1"}}, + }, + Ports: []discoveryv1.EndpointPort{ + {Name: ptr.To("http"), Port: ptr.To(int32(80)), Protocol: ptr.To(corev1.ProtocolTCP)}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "slice2"}, + AddressType: discoveryv1.AddressTypeFQDN, + Endpoints: []discoveryv1.Endpoint{ + {Addresses: []string{"example.com"}}, + }, + Ports: []discoveryv1.EndpointPort{ + {Name: ptr.To("http"), Port: ptr.To(int32(80)), Protocol: ptr.To(corev1.ProtocolTCP)}, + }, + }, + }, + portName: "http", + portProtocol: corev1.ProtocolTCP, + expectedEndpoints: 2, + expectedAddrType: ir.MIXED, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + endpoints, addrType := getIREndpointsFromEndpointSlices(tt.endpointSlices, tt.portName, tt.portProtocol) + + assert.Equal(t, tt.expectedEndpoints, len(endpoints), "Unexpected number of endpoints") + assert.Equal(t, tt.expectedAddrType, *addrType, "Unexpected address type") + + // Additional checks can be added here, e.g., verifying the content of endpoints + }) + } +} diff --git a/test/e2e/testdata/backend-dualstack.yaml b/test/e2e/testdata/backend-dualstack.yaml new file mode 100644 index 00000000000..7e7fabf46e8 --- /dev/null +++ b/test/e2e/testdata/backend-dualstack.yaml @@ -0,0 +1,120 @@ +--- +# IPv6 Service +apiVersion: v1 +kind: Service +metadata: + name: infra-backend-v1-clusterip-ipv6 + namespace: gateway-conformance-infra +spec: + clusterIP: fd00:10:96::1411 + ipFamilies: + - IPv6 + ipFamilyPolicy: SingleStack + ports: + - port: 8080 + targetPort: 3000 + selector: + app: infra-backend-v1 +--- +# Dual Stack Service +apiVersion: v1 +kind: Service +metadata: + name: infra-backend-v1-clusterip-dualstack + namespace: gateway-conformance-infra +spec: + clusterIP: 10.96.0.100 + clusterIPs: + - 10.96.0.100 + - fd00:10:96::100 + ipFamilies: + - IPv4 + - IPv6 + ipFamilyPolicy: RequireDualStack + ports: + - port: 8080 + targetPort: 3000 + selector: + app: infra-backend-v1 +--- +# IPv4 Service +apiVersion: v1 +kind: Service +metadata: + name: infra-backend-v1-clusterip-ipv4 + namespace: gateway-conformance-infra +spec: + clusterIP: 10.96.0.101 + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - port: 8080 + targetPort: 3000 + selector: + app: infra-backend-v1 +--- +# HTTPRoute for IPv6 +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: httproute-to-backend-ipv6-service + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: PathPrefix + value: /backend-ipv6 + backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-ipv6 +--- +# HTTPRoute for Dual Stack +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: httproute-to-backend-dualstack-service + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: PathPrefix + value: /backend-dualstack + backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-dualstack +--- +# Backend for IPv6 +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: backend-ipv6 + namespace: gateway-conformance-infra +spec: + endpoints: + - ip: + address: "fd00:10:96::1411" + port: 8080 +--- +# Backend for Dual Stack +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: backend-dualstack + namespace: gateway-conformance-infra +spec: + endpoints: + - ip: + address: "10.96.0.100" + port: 8080 + - ip: + address: "fd00:10:96::100" + port: 8080 \ No newline at end of file diff --git a/test/e2e/testdata/httproute-dualstack.yaml b/test/e2e/testdata/httproute-dualstack.yaml new file mode 100644 index 00000000000..50410d147bc --- /dev/null +++ b/test/e2e/testdata/httproute-dualstack.yaml @@ -0,0 +1,96 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: ipv6-only-route + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - backendRefs: + - name: infra-backend-v1-clusterip-ipv6 + port: 8080 + matches: + - path: + type: PathPrefix + value: /ipv6-only +--- +apiVersion: v1 +kind: Service +metadata: + name: infra-backend-v1-clusterip-ipv6 + namespace: gateway-conformance-infra +spec: + ipFamilies: + - IPv6 + ipFamilyPolicy: SingleStack + ports: + - port: 8080 + targetPort: 3000 + selector: + app: infra-backend-v1 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: dual-stack-route + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - backendRefs: + - name: infra-backend-v1-clusterip-dualstack + port: 8080 + matches: + - path: + type: PathPrefix + value: /dual-stack +--- +apiVersion: v1 +kind: Service +metadata: + name: infra-backend-v1-clusterip-dualstack + namespace: gateway-conformance-infra +spec: + ipFamilies: + - IPv4 + - IPv6 + ipFamilyPolicy: RequireDualStack + ports: + - port: 8080 + targetPort: 3000 + selector: + app: infra-backend-v1 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: ipv4-only-route + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - backendRefs: + - name: infra-backend-v1-clusterip-ipv4 + port: 8080 + matches: + - path: + type: PathPrefix + value: /ipv4-only +--- +apiVersion: v1 +kind: Service +metadata: + name: infra-backend-v1-clusterip-ipv4 + namespace: gateway-conformance-infra +spec: + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - port: 8080 + targetPort: 3000 + selector: + app: infra-backend-v1