Skip to content

Commit

Permalink
feat: add TCPRoute support for gwapi translator
Browse files Browse the repository at this point in the history
Signed-off-by: bitliu <bitliu@tencent.com>
  • Loading branch information
Xunzhuo committed Jan 3, 2023
1 parent 5ffa0e6 commit 038e628
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 4 deletions.
106 changes: 106 additions & 0 deletions internal/gatewayapi/contexts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
}
Expand Down
6 changes: 5 additions & 1 deletion internal/gatewayapi/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 2 additions & 0 deletions internal/gatewayapi/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions internal/gatewayapi/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
128 changes: 127 additions & 1 deletion internal/gatewayapi/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions internal/gatewayapi/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 10 additions & 2 deletions internal/gatewayapi/translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
KindHTTPRoute = "HTTPRoute"
KindGRPCRoute = "GRPCRoute"
KindTLSRoute = "TLSRoute"
KindTCPRoute = "TCPRoute"
KindUDPRoute = "UDPRoute"
KindService = "Service"
KindSecret = "Secret"
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit 038e628

Please sign in to comment.