diff --git a/internal/gatewayapi/contexts.go b/internal/gatewayapi/contexts.go index 13141182e538..c5da81cce378 100644 --- a/internal/gatewayapi/contexts.go +++ b/internal/gatewayapi/contexts.go @@ -446,6 +446,88 @@ func (u *UDPRouteContext) GetRouteParentContext(forParentRef v1beta1.ParentRefer return ctx } +// TCPRouteContext wraps a TCPRoute and provides helper methods for +// accessing the route's parents. +type TCPRouteContext struct { + *v1alpha2.TCPRoute + + parentRefs map[v1beta1.ParentReference]*RouteParentContext +} + +func (u *TCPRouteContext) GetRouteType() string { + return KindTCPRoute +} + +// GetHostnames return empty string array because TCPRoute has no hostnames +func (u *TCPRouteContext) GetHostnames() []string { + return []string{""} +} + +func (u *TCPRouteContext) GetParentReferences() []v1beta1.ParentReference { + parentReferences := make([]v1beta1.ParentReference, len(u.Spec.ParentRefs)) + for idx, p := range u.Spec.ParentRefs { + parentReferences[idx] = UpgradeParentReference(p) + } + return parentReferences +} + +func (u *TCPRouteContext) GetRouteParentContext(forParentRef v1beta1.ParentReference) *RouteParentContext { + if u.parentRefs == nil { + u.parentRefs = make(map[v1beta1.ParentReference]*RouteParentContext) + } + + if ctx := u.parentRefs[forParentRef]; ctx != nil { + return ctx + } + + var parentRef *v1beta1.ParentReference + for i, p := range u.Spec.ParentRefs { + p := UpgradeParentReference(p) + if reflect.DeepEqual(p, forParentRef) { + upgraded := UpgradeParentReference(u.Spec.ParentRefs[i]) + parentRef = &upgraded + break + } + } + if parentRef == nil { + panic("parentRef not found") + } + + routeParentStatusIdx := -1 + for i := range u.Status.Parents { + p := UpgradeParentReference(u.Status.Parents[i].ParentRef) + defaultNamespace := v1beta1.Namespace(metav1.NamespaceDefault) + if forParentRef.Namespace == nil { + forParentRef.Namespace = &defaultNamespace + } + if p.Namespace == nil { + p.Namespace = &defaultNamespace + } + if reflect.DeepEqual(p, forParentRef) { + routeParentStatusIdx = i + break + } + } + if routeParentStatusIdx == -1 { + rParentStatus := v1alpha2.RouteParentStatus{ + // TODO: get this value from the config + ControllerName: v1alpha2.GatewayController(egv1alpha1.GatewayControllerName), + ParentRef: DowngradeParentReference(forParentRef), + } + u.Status.Parents = append(u.Status.Parents, rParentStatus) + routeParentStatusIdx = len(u.Status.Parents) - 1 + } + + ctx := &RouteParentContext{ + ParentReference: parentRef, + + tcpRoute: u.TCPRoute, + routeParentStatusIdx: routeParentStatusIdx, + } + u.parentRefs[forParentRef] = ctx + return ctx +} + // RouteParentContext wraps a ParentReference and provides helper methods for // setting conditions and other status information on the associated // HTTPRoute, TLSRoute etc. @@ -456,6 +538,7 @@ type RouteParentContext struct { // a single field pointing to *v1beta1.RouteStatus. httpRoute *v1beta1.HTTPRoute tlsRoute *v1alpha2.TLSRoute + tcpRoute *v1alpha2.TCPRoute udpRoute *v1alpha2.UDPRoute routeParentStatusIdx int @@ -516,6 +599,25 @@ func (r *RouteParentContext) SetCondition(route RouteContext, conditionType v1be } else { r.tlsRoute.Status.Parents[r.routeParentStatusIdx].Conditions = append(r.tlsRoute.Status.Parents[r.routeParentStatusIdx].Conditions, cond) } + case KindTCPRoute: + for i, existing := range r.tcpRoute.Status.Parents[r.routeParentStatusIdx].Conditions { + if existing.Type == cond.Type { + // return early if the condition is unchanged + if existing.Status == cond.Status && + existing.Reason == cond.Reason && + existing.Message == cond.Message { + return + } + idx = i + break + } + } + + if idx > -1 { + } else { + r.tcpRoute.Status.Parents[r.routeParentStatusIdx].Conditions[idx] = cond + r.tcpRoute.Status.Parents[r.routeParentStatusIdx].Conditions = append(r.tcpRoute.Status.Parents[r.routeParentStatusIdx].Conditions, cond) + } case KindUDPRoute: for i, existing := range r.udpRoute.Status.Parents[r.routeParentStatusIdx].Conditions { if existing.Type == cond.Type { @@ -544,6 +646,8 @@ func (r *RouteParentContext) ResetConditions(route RouteContext) { r.httpRoute.Status.Parents[r.routeParentStatusIdx].Conditions = make([]metav1.Condition, 0) case KindTLSRoute: r.tlsRoute.Status.Parents[r.routeParentStatusIdx].Conditions = make([]metav1.Condition, 0) + case KindTCPRoute: + r.tcpRoute.Status.Parents[r.routeParentStatusIdx].Conditions = make([]metav1.Condition, 0) case KindUDPRoute: r.udpRoute.Status.Parents[r.routeParentStatusIdx].Conditions = make([]metav1.Condition, 0) } @@ -556,6 +660,8 @@ func (r *RouteParentContext) IsAccepted(route RouteContext) bool { conditions = r.httpRoute.Status.Parents[r.routeParentStatusIdx].Conditions case KindTLSRoute: conditions = r.tlsRoute.Status.Parents[r.routeParentStatusIdx].Conditions + case KindTCPRoute: + conditions = r.tcpRoute.Status.Parents[r.routeParentStatusIdx].Conditions case KindUDPRoute: conditions = r.udpRoute.Status.Parents[r.routeParentStatusIdx].Conditions } diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go index 77b94123bbe0..9d9d17d8bcea 100644 --- a/internal/gatewayapi/helpers.go +++ b/internal/gatewayapi/helpers.go @@ -342,10 +342,14 @@ func irHTTPListenerName(listener *ListenerContext) string { return fmt.Sprintf("%s-%s-%s", listener.gateway.Namespace, listener.gateway.Name, listener.Name) } -func irTCPListenerName(listener *ListenerContext, tlsRoute *TLSRouteContext) string { +func irTLSListenerName(listener *ListenerContext, tlsRoute *TLSRouteContext) string { return fmt.Sprintf("%s-%s-%s-%s", listener.gateway.Namespace, listener.gateway.Name, listener.Name, tlsRoute.Name) } +func irTCPListenerName(listener *ListenerContext, tcpRoute *TCPRouteContext) string { + return fmt.Sprintf("%s-%s-%s-%s", listener.gateway.Namespace, listener.gateway.Name, listener.Name, tcpRoute.Name) +} + func irUDPListenerName(listener *ListenerContext, udpRoute *UDPRouteContext) string { return fmt.Sprintf("%s-%s-%s-%s", listener.gateway.Namespace, listener.gateway.Name, listener.Name, udpRoute.Name) } diff --git a/internal/gatewayapi/listener.go b/internal/gatewayapi/listener.go index 31415031ba2e..70af70610380 100644 --- a/internal/gatewayapi/listener.go +++ b/internal/gatewayapi/listener.go @@ -114,6 +114,8 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR XdsIRMap proto = ir.HTTPSProtocolType case v1beta1.TLSProtocolType: proto = ir.TLSProtocolType + case v1beta1.TCPProtocolType: + proto = ir.TCPProtocolType case v1beta1.UDPProtocolType: proto = ir.UDPProtocolType } diff --git a/internal/gatewayapi/resource.go b/internal/gatewayapi/resource.go index c0a7b37f6d98..34aab828b9fb 100644 --- a/internal/gatewayapi/resource.go +++ b/internal/gatewayapi/resource.go @@ -24,6 +24,7 @@ type Resources struct { HTTPRoutes []*v1beta1.HTTPRoute GRPCRoutes []*v1alpha2.GRPCRoute TLSRoutes []*v1alpha2.TLSRoute + TCPRoutes []*v1alpha2.TCPRoute UDPRoutes []*v1alpha2.UDPRoute ReferenceGrants []*v1alpha2.ReferenceGrant Namespaces []*v1.Namespace diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 6cc2d47a1ae0..3d5dc63984ec 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -20,6 +20,7 @@ var _ RoutesTranslator = (*Translator)(nil) type RoutesTranslator interface { ProcessHTTPRoutes(httpRoutes []*v1beta1.HTTPRoute, gateways []*GatewayContext, resources *Resources, xdsIR XdsIRMap) []*HTTPRouteContext ProcessTLSRoutes(tlsRoutes []*v1alpha2.TLSRoute, gateways []*GatewayContext, resources *Resources, xdsIR XdsIRMap) []*TLSRouteContext + ProcessTCPRoutes(tcpRoutes []*v1alpha2.TCPRoute, gateways []*GatewayContext, resources *Resources, xdsIR XdsIRMap) []*TCPRouteContext ProcessUDPRoutes(udpRoutes []*v1alpha2.UDPRoute, gateways []*GatewayContext, resources *Resources, xdsIR XdsIRMap) []*UDPRouteContext } @@ -375,7 +376,7 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour // Create the TCP Listener while parsing the TLSRoute since // the listener directly links to a routeDestination. irListener := &ir.TCPListener{ - Name: irTCPListenerName(listener, tlsRoute), + Name: irTLSListenerName(listener, tlsRoute), Address: "0.0.0.0", Port: uint32(containerPort), TLS: &ir.TLSInspectorConfig{ @@ -541,6 +542,131 @@ func (t *Translator) processUDPRouteParentRefs(udpRoute *UDPRouteContext, resour } } +func (t *Translator) ProcessTCPRoutes(tcpRoutes []*v1alpha2.TCPRoute, gateways []*GatewayContext, resources *Resources, + xdsIR XdsIRMap) []*TCPRouteContext { + var relevantTCPRoutes []*TCPRouteContext + + for _, tcp := range tcpRoutes { + if tcp == nil { + panic("received nil tcproute") + } + tcpRoute := &TCPRouteContext{TCPRoute: tcp} + + // Find out if this route attaches to one of our Gateway's listeners, + // and if so, get the list of listeners that allow it to attach for each + // parentRef. + relevantRoute := t.processAllowedListenersForParentRefs(tcpRoute, gateways, resources) + if !relevantRoute { + continue + } + + relevantTCPRoutes = append(relevantTCPRoutes, tcpRoute) + + t.processTCPRouteParentRefs(tcpRoute, resources, xdsIR) + } + + return relevantTCPRoutes +} + +func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resources *Resources, xdsIR XdsIRMap) { + for _, parentRef := range tcpRoute.parentRefs { + // Skip parent refs that did not accept the route + if !parentRef.IsAccepted(tcpRoute) { + continue + } + + // Need to compute Route rules within the parentRef loop because + // any conditions that come out of it have to go on each RouteParentStatus, + // not on the Route as a whole. + var routeDestinations []*ir.RouteDestination + + // compute backends + if len(tcpRoute.Spec.Rules) != 1 { + parentRef.SetCondition(tcpRoute, + v1beta1.RouteConditionResolvedRefs, + metav1.ConditionFalse, + "InvalidRule", + "One and only one rule is supported", + ) + continue + } + if len(tcpRoute.Spec.Rules[0].BackendRefs) != 1 { + parentRef.SetCondition(tcpRoute, + v1beta1.RouteConditionResolvedRefs, + metav1.ConditionFalse, + "InvalidBackend", + "One and only one backend is supported", + ) + continue + } + + backendRef := tcpRoute.Spec.Rules[0].BackendRefs[0] + // TODO: [v1alpha2-v1beta1] Replace with NamespaceDerefOr when TCPRoute graduates to v1beta1. + serviceNamespace := NamespaceDerefOrAlpha(backendRef.Namespace, tcpRoute.Namespace) + service := resources.GetService(serviceNamespace, string(backendRef.Name)) + + if !t.validateBackendRef(&backendRef, parentRef, tcpRoute, resources, serviceNamespace, KindTCPRoute) { + continue + } + + // weight is not used in tcp route destinations + routeDestinations = append(routeDestinations, &ir.RouteDestination{ + Host: service.Spec.ClusterIP, + Port: uint32(*backendRef.Port), + }) + + accepted := false + for _, listener := range parentRef.listeners { + // only one route is allowed for a TCP listener + if listener.AttachedRoutes() > 0 { + continue + } + if !listener.IsReady() { + continue + } + accepted = true + irKey := irStringKey(listener.gateway) + containerPort := servicePortToContainerPort(int32(listener.Port)) + // Create the TCP Listener while parsing the TCPRoute since + // the listener directly links to a routeDestination. + irListener := &ir.TCPListener{ + Name: irTCPListenerName(listener, tcpRoute), + Address: "0.0.0.0", + Port: uint32(containerPort), + Destinations: routeDestinations, + } + gwXdsIR := xdsIR[irKey] + gwXdsIR.TCP = append(gwXdsIR.TCP, irListener) + + // Theoretically there should only be one parent ref per + // Route that attaches to a given Listener, so fine to just increment here, but we + // might want to check to ensure we're not double-counting. + if len(routeDestinations) > 0 { + listener.IncrementAttachedRoutes() + } + } + + // If no negative conditions have been set, the route is considered "Accepted=True". + if accepted && parentRef.tcpRoute != nil && + len(parentRef.tcpRoute.Status.Parents[parentRef.routeParentStatusIdx].Conditions) == 0 { + parentRef.SetCondition(tcpRoute, + v1beta1.RouteConditionAccepted, + metav1.ConditionTrue, + v1beta1.RouteReasonAccepted, + "Route is accepted", + ) + } + if !accepted { + parentRef.SetCondition(tcpRoute, + v1beta1.RouteConditionAccepted, + metav1.ConditionFalse, + v1beta1.RouteReasonUnsupportedValue, + "Multiple routes on the same TCP listener", + ) + } + } +} + // processRuleRouteDestination takes a backendRef and translates it into a destination or sets error statuses and // returns the weight for the backend so that 500 error responses can be returned for invalid backends in // the same proportion as the backend would have otherwise received diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index 71c0edd00a05..ebe994b08429 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -111,6 +111,10 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) { key := utils.NamespacedName(tlsRoute) r.ProviderResources.TLSRouteStatuses.Store(key, tlsRoute) } + for _, tcpRoute := range result.TCPRoutes { + key := utils.NamespacedName(tcpRoute) + r.ProviderResources.TCPRouteStatuses.Store(key, tcpRoute) + } for _, udpRoute := range result.UDPRoutes { key := utils.NamespacedName(udpRoute) r.ProviderResources.UDPRouteStatuses.Store(key, udpRoute) diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 2d70eeb37a6c..71a9b3aa842b 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -15,6 +15,7 @@ const ( KindHTTPRoute = "HTTPRoute" KindGRPCRoute = "GRPCRoute" KindTLSRoute = "TLSRoute" + KindTCPRoute = "TCPRoute" KindUDPRoute = "UDPRoute" KindService = "Service" KindSecret = "Secret" @@ -62,13 +63,14 @@ type TranslateResult struct { Gateways []*v1beta1.Gateway HTTPRoutes []*v1beta1.HTTPRoute TLSRoutes []*v1alpha2.TLSRoute + TCPRoutes []*v1alpha2.TCPRoute UDPRoutes []*v1alpha2.UDPRoute XdsIR XdsIRMap InfraIR InfraIRMap } func newTranslateResult(gateways []*GatewayContext, - httpRoutes []*HTTPRouteContext, tlsRoutes []*TLSRouteContext, udpRoutes []*UDPRouteContext, xdsIR XdsIRMap, + httpRoutes []*HTTPRouteContext, tlsRoutes []*TLSRouteContext, tcpRoutes []*TCPRouteContext, udpRoutes []*UDPRouteContext, xdsIR XdsIRMap, infraIR InfraIRMap) *TranslateResult { translateResult := &TranslateResult{ XdsIR: xdsIR, @@ -84,6 +86,9 @@ func newTranslateResult(gateways []*GatewayContext, for _, tlsRoute := range tlsRoutes { translateResult.TLSRoutes = append(translateResult.TLSRoutes, tlsRoute.TLSRoute) } + for _, tcpRoute := range tcpRoutes { + translateResult.TCPRoutes = append(translateResult.TCPRoutes, tcpRoute.TCPRoute) + } for _, udpRoute := range udpRoutes { translateResult.UDPRoutes = append(translateResult.UDPRoutes, udpRoute.UDPRoute) } @@ -107,13 +112,16 @@ func (t *Translator) Translate(resources *Resources) *TranslateResult { // Process all relevant TLSRoutes. tlsRoutes := t.ProcessTLSRoutes(resources.TLSRoutes, gateways, resources, xdsIR) + // Process all relevant TCPRoutes. + tcpRoutes := t.ProcessTCPRoutes(resources.TCPRoutes, gateways, resources, xdsIR) + // Process all relevant UDPRoutes. udpRoutes := t.ProcessUDPRoutes(resources.UDPRoutes, gateways, resources, xdsIR) // Sort xdsIR based on the Gateway API spec sortXdsIRMap(xdsIR) - return newTranslateResult(gateways, httpRoutes, tlsRoutes, udpRoutes, xdsIR, infraIR) + return newTranslateResult(gateways, httpRoutes, tlsRoutes, tcpRoutes, udpRoutes, xdsIR, infraIR) } func (t *Translator) GetRelevantGateways(gateways []*v1beta1.Gateway) []*GatewayContext { diff --git a/internal/gatewayapi/zz_generated.deepcopy.go b/internal/gatewayapi/zz_generated.deepcopy.go index 288eeb96e24d..f6e3ace023cb 100644 --- a/internal/gatewayapi/zz_generated.deepcopy.go +++ b/internal/gatewayapi/zz_generated.deepcopy.go @@ -64,6 +64,17 @@ func (in *Resources) DeepCopyInto(out *Resources) { } } } + if in.TCPRoutes != nil { + in, out := &in.TCPRoutes, &out.TCPRoutes + *out = make([]*v1alpha2.TCPRoute, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(v1alpha2.TCPRoute) + (*in).DeepCopyInto(*out) + } + } + } if in.UDPRoutes != nil { in, out := &in.UDPRoutes, &out.UDPRoutes *out = make([]*v1alpha2.UDPRoute, len(*in)) diff --git a/internal/ir/infra.go b/internal/ir/infra.go index 4209a2f4a0ed..ef8d310d40ac 100644 --- a/internal/ir/infra.go +++ b/internal/ir/infra.go @@ -88,6 +88,9 @@ const ( // TLSProtocolType accepts TLS sessions over TCP. TLSProtocolType ProtocolType = "TLS" + // TCPProtocolType accepts TCP connection. + TCPProtocolType ProtocolType = "TCP" + // UDPProtocolType accepts UDP connection. UDPProtocolType ProtocolType = "UDP" ) diff --git a/internal/message/types.go b/internal/message/types.go index 7b8542cc8fa9..4efef215cedf 100644 --- a/internal/message/types.go +++ b/internal/message/types.go @@ -26,6 +26,7 @@ type ProviderResources struct { HTTPRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1b1.HTTPRoute] GRPCRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.GRPCRoute] TLSRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.TLSRoute] + TCPRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.TCPRoute] UDPRouteStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.UDPRoute] } @@ -54,6 +55,7 @@ func (p *ProviderResources) Close() { p.GatewayStatuses.Close() p.HTTPRouteStatuses.Close() p.TLSRouteStatuses.Close() + p.TCPRouteStatuses.Close() p.UDPRouteStatuses.Close() }