From 538c9254567df579a6dcfdb4534e80fab4727c0a Mon Sep 17 00:00:00 2001 From: David Cheung Date: Fri, 6 Sep 2024 05:05:59 +0000 Subject: [PATCH] Move MergeIR into intermediate package. --- pkg/i2gw/ingress2gateway.go | 75 ------------- .../intermediate_representation.go | 2 +- pkg/i2gw/intermediate/provider_apisix.go | 2 +- pkg/i2gw/intermediate/provider_gce.go | 4 +- .../intermediate/provider_ingressnginx.go | 2 +- pkg/i2gw/intermediate/provider_istio.go | 2 +- pkg/i2gw/intermediate/provider_kong.go | 2 +- pkg/i2gw/intermediate/provider_openapi3.go | 2 +- pkg/i2gw/intermediate/utils.go | 102 ++++++++++++++++++ 9 files changed, 110 insertions(+), 83 deletions(-) create mode 100644 pkg/i2gw/intermediate/utils.go diff --git a/pkg/i2gw/ingress2gateway.go b/pkg/i2gw/ingress2gateway.go index 2814fa5f..643a66a9 100644 --- a/pkg/i2gw/ingress2gateway.go +++ b/pkg/i2gw/ingress2gateway.go @@ -21,7 +21,6 @@ import ( "fmt" "maps" - "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/intermediate" "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/notifications" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" @@ -199,77 +198,3 @@ func mergeGateways(gatewaResources []GatewayResources) (map[types.NamespacedName return newGateways, errs } - -// MergeIRs accepts multiple IRs and creates a unique IR struct built -// as follows: -// - GatewayClasses, Routes, and ReferenceGrants are grouped into the same maps -// - Gateways may have the same NamespaceName even if they come from different -// ingresses, as they have a their GatewayClass' name as name. For this reason, -// if there are mutiple gateways named the same, their listeners are merged into -// a unique Gateway. -// -// This behavior is likely to change after https://github.com/kubernetes-sigs/gateway-api/pull/1863 takes place. -func MergeIRs(irs ...intermediate.IR) (intermediate.IR, field.ErrorList) { - mergedIRs := intermediate.IR{ - Gateways: make(map[types.NamespacedName]intermediate.GatewayContext), - GatewayClasses: make(map[types.NamespacedName]gatewayv1.GatewayClass), - HTTPRoutes: make(map[types.NamespacedName]intermediate.HTTPRouteContext), - Services: make(map[types.NamespacedName]intermediate.ProviderSpecificServiceIR), - TLSRoutes: make(map[types.NamespacedName]gatewayv1alpha2.TLSRoute), - TCPRoutes: make(map[types.NamespacedName]gatewayv1alpha2.TCPRoute), - UDPRoutes: make(map[types.NamespacedName]gatewayv1alpha2.UDPRoute), - ReferenceGrants: make(map[types.NamespacedName]gatewayv1beta1.ReferenceGrant), - } - var errs field.ErrorList - mergedIRs.Gateways, errs = mergeGatewayContexts(irs) - if len(errs) > 0 { - return intermediate.IR{}, errs - } - // TODO(issue #189): Perform merge on HTTPRoute and Service like Gateway. - for _, gr := range irs { - maps.Copy(mergedIRs.GatewayClasses, gr.GatewayClasses) - maps.Copy(mergedIRs.HTTPRoutes, gr.HTTPRoutes) - maps.Copy(mergedIRs.Services, gr.Services) - maps.Copy(mergedIRs.TLSRoutes, gr.TLSRoutes) - maps.Copy(mergedIRs.TCPRoutes, gr.TCPRoutes) - maps.Copy(mergedIRs.UDPRoutes, gr.UDPRoutes) - maps.Copy(mergedIRs.ReferenceGrants, gr.ReferenceGrants) - } - return mergedIRs, errs -} - -func mergeGatewayContexts(irs []intermediate.IR) (map[types.NamespacedName]intermediate.GatewayContext, field.ErrorList) { - newGatewayContexts := make(map[types.NamespacedName]intermediate.GatewayContext) - errs := field.ErrorList{} - - for _, currentIR := range irs { - for _, g := range currentIR.Gateways { - nn := types.NamespacedName{Namespace: g.Gateway.Namespace, Name: g.Gateway.Name} - if existingGatewayContext, ok := newGatewayContexts[nn]; ok { - g.Gateway.Spec.Listeners = append(g.Gateway.Spec.Listeners, existingGatewayContext.Gateway.Spec.Listeners...) - g.Gateway.Spec.Addresses = append(g.Gateway.Spec.Addresses, existingGatewayContext.Gateway.Spec.Addresses...) - g.ProviderSpecificIR = mergedGatewayIR(g.ProviderSpecificIR, existingGatewayContext.ProviderSpecificIR) - } - newGatewayContexts[nn] = intermediate.GatewayContext{Gateway: g.Gateway} - // 64 is the maximum number of listeners a Gateway can have - if len(g.Spec.Listeners) > 64 { - fieldPath := field.NewPath(fmt.Sprintf("%s/%s", nn.Namespace, nn.Name)).Child("spec").Child("listeners") - errs = append(errs, field.Invalid(fieldPath, g, "error while merging gateway listeners: a gateway cannot have more than 64 listeners")) - } - // 16 is the maximum number of addresses a Gateway can have - if len(g.Spec.Addresses) > 16 { - fieldPath := field.NewPath(fmt.Sprintf("%s/%s", nn.Namespace, nn.Name)).Child("spec").Child("addresses") - errs = append(errs, field.Invalid(fieldPath, g, "error while merging gateway listeners: a gateway cannot have more than 16 addresses")) - } - } - } - return newGatewayContexts, errs -} - -func mergedGatewayIR(current, existing intermediate.ProviderSpecificGatewayIR) intermediate.ProviderSpecificGatewayIR { - var mergedGatewayIR intermediate.ProviderSpecificGatewayIR - // TODO(issue #190): Find a different way to merge GatewayIR, instead of - // delegating them to each provider. - mergedGatewayIR.Gce = intermediate.MergeGceGatewayIR(current.Gce, existing.Gce) - return mergedGatewayIR -} diff --git a/pkg/i2gw/intermediate/intermediate_representation.go b/pkg/i2gw/intermediate/intermediate_representation.go index be430ae9..5fde45c5 100644 --- a/pkg/i2gw/intermediate/intermediate_representation.go +++ b/pkg/i2gw/intermediate/intermediate_representation.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ir +package intermediate import ( "k8s.io/apimachinery/pkg/types" diff --git a/pkg/i2gw/intermediate/provider_apisix.go b/pkg/i2gw/intermediate/provider_apisix.go index 20839b5b..f7166db6 100644 --- a/pkg/i2gw/intermediate/provider_apisix.go +++ b/pkg/i2gw/intermediate/provider_apisix.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ir +package intermediate type ApisixGatewayIR struct{} type ApisixHTTPRouteIR struct{} diff --git a/pkg/i2gw/intermediate/provider_gce.go b/pkg/i2gw/intermediate/provider_gce.go index 3d99bf47..fd82398f 100644 --- a/pkg/i2gw/intermediate/provider_gce.go +++ b/pkg/i2gw/intermediate/provider_gce.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ir +package intermediate type GceGatewayIR struct { EnableHTTPSRedirect bool @@ -22,7 +22,7 @@ type GceGatewayIR struct { type GceHTTPRouteIR struct{} type GceServiceIR struct{} -func MergeGceGatewayIR(current, existing *GceGatewayIR) *GceGatewayIR { +func mergeGceGatewayIR(current, existing *GceGatewayIR) *GceGatewayIR { // If either GceGatewayIR is nil, return the other one as the merged result. if current == nil { return existing diff --git a/pkg/i2gw/intermediate/provider_ingressnginx.go b/pkg/i2gw/intermediate/provider_ingressnginx.go index a896d1cf..4ac1207f 100644 --- a/pkg/i2gw/intermediate/provider_ingressnginx.go +++ b/pkg/i2gw/intermediate/provider_ingressnginx.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ir +package intermediate type IngressNginxGatewayIR struct{} type IngressNginxHTTPRouteIR struct{} diff --git a/pkg/i2gw/intermediate/provider_istio.go b/pkg/i2gw/intermediate/provider_istio.go index 87c8a7df..50078ec1 100644 --- a/pkg/i2gw/intermediate/provider_istio.go +++ b/pkg/i2gw/intermediate/provider_istio.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ir +package intermediate type IstioGatewayIR struct{} type IstioHTTPRouteIR struct{} diff --git a/pkg/i2gw/intermediate/provider_kong.go b/pkg/i2gw/intermediate/provider_kong.go index 5156d8b3..10fbd62b 100644 --- a/pkg/i2gw/intermediate/provider_kong.go +++ b/pkg/i2gw/intermediate/provider_kong.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ir +package intermediate type KongGatewayIR struct{} type KongHTTPRouteIR struct{} diff --git a/pkg/i2gw/intermediate/provider_openapi3.go b/pkg/i2gw/intermediate/provider_openapi3.go index 220ca9ed..9657a122 100644 --- a/pkg/i2gw/intermediate/provider_openapi3.go +++ b/pkg/i2gw/intermediate/provider_openapi3.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ir +package intermediate type Openapi3GatewayIR struct{} type Openapi3HTTPRouteIR struct{} diff --git a/pkg/i2gw/intermediate/utils.go b/pkg/i2gw/intermediate/utils.go new file mode 100644 index 00000000..bcd94e22 --- /dev/null +++ b/pkg/i2gw/intermediate/utils.go @@ -0,0 +1,102 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package intermediate + +import ( + "fmt" + "maps" + + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +// MergeIRs accepts multiple IRs and creates a unique IR struct built +// as follows: +// - GatewayClasses, Routes, and ReferenceGrants are grouped into the same maps +// - Gateways may have the same NamespaceName even if they come from different +// ingresses, as they have a their GatewayClass' name as name. For this reason, +// if there are mutiple gateways named the same, their listeners are merged into +// a unique Gateway. +// +// This behavior is likely to change after https://github.com/kubernetes-sigs/gateway-api/pull/1863 takes place. +func MergeIRs(irs ...IR) (IR, field.ErrorList) { + mergedIRs := IR{ + Gateways: make(map[types.NamespacedName]GatewayContext), + GatewayClasses: make(map[types.NamespacedName]gatewayv1.GatewayClass), + HTTPRoutes: make(map[types.NamespacedName]HTTPRouteContext), + Services: make(map[types.NamespacedName]ProviderSpecificServiceIR), + TLSRoutes: make(map[types.NamespacedName]gatewayv1alpha2.TLSRoute), + TCPRoutes: make(map[types.NamespacedName]gatewayv1alpha2.TCPRoute), + UDPRoutes: make(map[types.NamespacedName]gatewayv1alpha2.UDPRoute), + ReferenceGrants: make(map[types.NamespacedName]gatewayv1beta1.ReferenceGrant), + } + var errs field.ErrorList + mergedIRs.Gateways, errs = mergeGatewayContexts(irs) + if len(errs) > 0 { + return IR{}, errs + } + // TODO(issue #189): Perform merge on HTTPRoute and Service like Gateway. + for _, gr := range irs { + maps.Copy(mergedIRs.GatewayClasses, gr.GatewayClasses) + maps.Copy(mergedIRs.HTTPRoutes, gr.HTTPRoutes) + maps.Copy(mergedIRs.Services, gr.Services) + maps.Copy(mergedIRs.TLSRoutes, gr.TLSRoutes) + maps.Copy(mergedIRs.TCPRoutes, gr.TCPRoutes) + maps.Copy(mergedIRs.UDPRoutes, gr.UDPRoutes) + maps.Copy(mergedIRs.ReferenceGrants, gr.ReferenceGrants) + } + return mergedIRs, errs +} + +func mergeGatewayContexts(irs []IR) (map[types.NamespacedName]GatewayContext, field.ErrorList) { + newGatewayContexts := make(map[types.NamespacedName]GatewayContext) + errs := field.ErrorList{} + + for _, currentIR := range irs { + for _, g := range currentIR.Gateways { + nn := types.NamespacedName{Namespace: g.Gateway.Namespace, Name: g.Gateway.Name} + if existingGatewayContext, ok := newGatewayContexts[nn]; ok { + g.Gateway.Spec.Listeners = append(g.Gateway.Spec.Listeners, existingGatewayContext.Gateway.Spec.Listeners...) + g.Gateway.Spec.Addresses = append(g.Gateway.Spec.Addresses, existingGatewayContext.Gateway.Spec.Addresses...) + g.ProviderSpecificIR = mergedGatewayIR(g.ProviderSpecificIR, existingGatewayContext.ProviderSpecificIR) + } + newGatewayContexts[nn] = GatewayContext{Gateway: g.Gateway} + // 64 is the maximum number of listeners a Gateway can have + if len(g.Spec.Listeners) > 64 { + fieldPath := field.NewPath(fmt.Sprintf("%s/%s", nn.Namespace, nn.Name)).Child("spec").Child("listeners") + errs = append(errs, field.Invalid(fieldPath, g, "error while merging gateway listeners: a gateway cannot have more than 64 listeners")) + } + // 16 is the maximum number of addresses a Gateway can have + if len(g.Spec.Addresses) > 16 { + fieldPath := field.NewPath(fmt.Sprintf("%s/%s", nn.Namespace, nn.Name)).Child("spec").Child("addresses") + errs = append(errs, field.Invalid(fieldPath, g, "error while merging gateway listeners: a gateway cannot have more than 16 addresses")) + } + } + } + return newGatewayContexts, errs +} + +func mergedGatewayIR(current, existing ProviderSpecificGatewayIR) ProviderSpecificGatewayIR { + var mergedGatewayIR ProviderSpecificGatewayIR + // TODO(issue #190): Find a different way to merge GatewayIR, instead of + // delegating them to each provider. + mergedGatewayIR.Gce = mergeGceGatewayIR(current.Gce, existing.Gce) + return mergedGatewayIR +}