diff --git a/apps/operator/internal/resourcebuilder/networking/gateway.go b/apps/operator/internal/resourcebuilder/networking/gateway.go index 9ccf803d..786a9425 100644 --- a/apps/operator/internal/resourcebuilder/networking/gateway.go +++ b/apps/operator/internal/resourcebuilder/networking/gateway.go @@ -100,8 +100,8 @@ func (p gatewayProvider) BuildRoutes(in RouteInput) ([]client.Object, error) { // Raw L4 ports: one TCP/UDP listener + matching route per port. for _, port := range l4Ports { - listenerName := gwapiv1.SectionName(fmt.Sprintf("l4-%d", *port.NodePort)) proto := portProtocol(port) + listenerName := l4ListenerName(port, proto) listenerProto := gwapiv1.TCPProtocolType routeKind := gwapiv1.Kind("TCPRoute") if proto == "UDP" { @@ -234,6 +234,14 @@ func hostListenerName(host v1.HostSpec) gwapiv1.SectionName { return gwapiv1.SectionName("https-" + hostnameKey(host)) } +// TCP listeners keep the name they had before UDP could share their port +func l4ListenerName(port v1.PortSpec, proto string) gwapiv1.SectionName { + if proto == "UDP" { + return gwapiv1.SectionName(fmt.Sprintf("l4-udp-%d", *port.NodePort)) + } + return gwapiv1.SectionName(fmt.Sprintf("l4-%d", *port.NodePort)) +} + func (p gatewayProvider) parentRef(gateway string, section *gwapiv1.SectionName) gwapiv1.ParentReference { return gwapiv1.ParentReference{ Group: ptr.To(gwapiv1.Group("gateway.networking.k8s.io")), diff --git a/apps/operator/internal/resourcebuilder/networking/networking_test.go b/apps/operator/internal/resourcebuilder/networking/networking_test.go index 2248680f..76a8a5c4 100644 --- a/apps/operator/internal/resourcebuilder/networking/networking_test.go +++ b/apps/operator/internal/resourcebuilder/networking/networking_test.go @@ -371,6 +371,45 @@ func TestGatewayL4UDP(t *testing.T) { } } +func TestGatewayL4TCPAndUDPOnSamePort(t *testing.T) { + svc := setName(&v1.Service{ + Spec: v1.ServiceSpec{ + Config: v1.ServiceConfigSpec{ + Ports: []v1.PortSpec{ + {Port: 8388, NodePort: ptr.To(int32(8388)), Protocol: ptr.To(corev1.ProtocolTCP)}, + {Port: 8388, NodePort: ptr.To(int32(8388)), Protocol: ptr.To(corev1.ProtocolUDP)}, + }, + }, + }, + }, "ss") + + objs, err := New(ProviderGateway, Config{GatewayClassName: "unbind"}).BuildRoutes(RouteInput{Service: svc}) + if err != nil { + t.Fatalf("BuildRoutes: %v", err) + } + listeners := map[gwapiv1.SectionName]gwapiv1.ProtocolType{} + for _, l := range findGateway(objs).Spec.Listeners { + listeners[l.Name] = l.Protocol + } + want := map[gwapiv1.SectionName]gwapiv1.ProtocolType{"l4-8388": gwapiv1.TCPProtocolType, "l4-udp-8388": gwapiv1.UDPProtocolType} + if !maps.Equal(listeners, want) { + t.Fatalf("listeners = %v, want %v", listeners, want) + } + + sections := map[string]gwapiv1.SectionName{} + for _, o := range objs { + switch r := o.(type) { + case *gwapiv1a2.TCPRoute: + sections["TCPRoute"] = *r.Spec.ParentRefs[0].SectionName + case *gwapiv1a2.UDPRoute: + sections["UDPRoute"] = *r.Spec.ParentRefs[0].SectionName + } + } + if sections["TCPRoute"] != "l4-8388" || sections["UDPRoute"] != "l4-udp-8388" { + t.Errorf("route sections = %v, want TCPRoute on l4-8388 and UDPRoute on l4-udp-8388", sections) + } +} + func wildcardService() *v1.Service { svc := setName(publicService(), "web") svc.Spec.Config.Hosts = append(svc.Spec.Config.Hosts, v1.HostSpec{Host: "*.example.com", Path: "/"})