Skip to content

Commit

Permalink
Refactor ingress-nginx conversion logic
Browse files Browse the repository at this point in the history
  • Loading branch information
levikobi committed Jul 30, 2023
1 parent f155c6c commit d54ed81
Show file tree
Hide file tree
Showing 13 changed files with 953 additions and 1,182 deletions.
15 changes: 8 additions & 7 deletions pkg/i2gw/ingress2gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,24 @@ func Run(printer printers.ResourcePrinter, namespace string, inputFile string) e
return fmt.Errorf("no providers")
}

var resources IngressResources
resources := IngressResources{Ingresses: ingresses.Items}

for name, provider := range providerByName {

if err = provider.ReadResourcesFromCluster(context.Background(), &resources.CustomResources); err != nil {
return fmt.Errorf("failed to read %s resources from the cluster: %w", name, err)
}

gatewayResources, conversionErrs := provider.IngressToGateway(resources)
//httpRoutes = append(httpRoutes, gatewayResources.HTTPRoutes...)
//gateways = append(gateways, convertedGateways...)
errs = append(errs, conversionErrs...)

_ = gatewayResources
for _, gateway := range gatewayResources.Gateways {
gateways = append(gateways, gateway)
}
for _, route := range gatewayResources.HTTPRoutes {
httpRoutes = append(httpRoutes, route)
}
}

// TODO: Open a new issue - collate gateways (extract logic from toHTTPRoutesAndGateways).
// TODO: Open a new issue - collate gateways objects.

if len(errs) > 0 {
fmt.Printf("# Encountered %d errors\n", len(errs))
Expand Down
14 changes: 13 additions & 1 deletion pkg/i2gw/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,24 @@ type ResourceConverter interface {
IngressToGateway(resources IngressResources) (GatewayResources, field.ErrorList)
}

// IngressResources contains all Ingress related objects, and Provider specific
// custom resources.
type IngressResources struct {
Ingresses []networkingv1.Ingress
CustomResources interface{}
}

// GatewayResources contains all Gateway-API objects.
type GatewayResources struct {
Gateways map[GatewayKey]gatewayv1beta1.Gateway
HTTPRoutes map[HTTPRouteKey]gatewayv1beta1.HTTPRoute
}

// GatewayKey is a unique identifier for a gateway object.
// Constructed by namespace:class:name.
// Constructed by namespace:name.
type GatewayKey string

// GatewayToGatewayKey assembles the GatewayKey out of a Gateway.
func GatewayToGatewayKey(g gatewayv1beta1.Gateway) GatewayKey {
return GatewayKey(g.Namespace + ":" + g.Name)
}
Expand All @@ -90,6 +94,14 @@ func GatewayToGatewayKey(g gatewayv1beta1.Gateway) GatewayKey {
// Constructed by namespace:name.
type HTTPRouteKey string

// HTTPRouteToHTTPRouteKey assembles the HTTPRouteKey out of an HTTPRoute.
func HTTPRouteToHTTPRouteKey(r gatewayv1beta1.HTTPRoute) HTTPRouteKey {
return HTTPRouteKey(r.Namespace + ":" + r.Name)
}

// FeatureParser is a function that reads the IngressResources, and applies
// the appropriate modifications to the GatewayResources.
//
// Different FeatureParsers will run in undetermined order. The function must
// modify / create only the required fields of the gateway resources and nothing else.
type FeatureParser func(IngressResources, *GatewayResources) field.ErrorList
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ package common

import (
"fmt"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
"regexp"
"strings"

"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/pointer"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)

// IngressToGateway converts the received ingresses to i2gw.GatewayResources,
// without taking into consideration any provider specific logic.
func IngressToGateway(ingresses []networkingv1.Ingress) (i2gw.GatewayResources, field.ErrorList) {
aggregator := ingressAggregator{ruleGroups: map[ruleGroupKey]*ingressRuleGroup{}}

Expand Down Expand Up @@ -64,13 +64,13 @@ func IngressToGateway(ingresses []networkingv1.Ingress) (i2gw.GatewayResources,
}

var (
gatewayGVK = schema.GroupVersionKind{
GatewayGVK = schema.GroupVersionKind{
Group: "gateway.networking.k8s.io",
Version: "v1beta1",
Kind: "Gateway",
}

httpRouteGVK = schema.GroupVersionKind{
HTTPRouteGVK = schema.GroupVersionKind{
Group: "gateway.networking.k8s.io",
Version: "v1beta1",
Kind: "HTTPRoute",
Expand Down Expand Up @@ -197,7 +197,7 @@ func (a *ingressAggregator) toHTTPRoutesAndGateways() ([]gatewayv1beta1.HTTPRout
},
},
}
httpRoute.SetGroupVersionKind(httpRouteGVK)
httpRoute.SetGroupVersionKind(HTTPRouteGVK)

backendRef, err := toBackendRef(db.backend, field.NewPath(db.name, "paths", "backends").Index(i))
if err != nil {
Expand Down Expand Up @@ -229,13 +229,13 @@ func (a *ingressAggregator) toHTTPRoutesAndGateways() ([]gatewayv1beta1.HTTPRout
GatewayClassName: gatewayv1beta1.ObjectName(parts[1]),
},
}
gateway.SetGroupVersionKind(gatewayGVK)
gateway.SetGroupVersionKind(GatewayGVK)
gatewaysByKey[gwKey] = gateway
}
for _, listener := range listeners {
var listenerNamePrefix string
if listener.Hostname != nil && *listener.Hostname != "" {
listenerNamePrefix = fmt.Sprintf("%s-", nameFromHost(string(*listener.Hostname)))
listenerNamePrefix = fmt.Sprintf("%s-", NameFromHost(string(*listener.Hostname)))
}

gateway.Spec.Listeners = append(gateway.Spec.Listeners, gatewayv1beta1.Listener{
Expand Down Expand Up @@ -278,7 +278,7 @@ func (rg *ingressRuleGroup) toHTTPRoute() (gatewayv1beta1.HTTPRoute, field.Error

httpRoute := gatewayv1beta1.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: nameFromHost(rg.host),
Name: NameFromHost(rg.host),
Namespace: rg.namespace,
},
Spec: gatewayv1beta1.HTTPRouteSpec{},
Expand All @@ -288,7 +288,7 @@ func (rg *ingressRuleGroup) toHTTPRoute() (gatewayv1beta1.HTTPRoute, field.Error
},
},
}
httpRoute.SetGroupVersionKind(httpRouteGVK)
httpRoute.SetGroupVersionKind(HTTPRouteGVK)

if rg.ingressClass != "" {
httpRoute.Spec.ParentRefs = []gatewayv1beta1.ParentReference{{Name: gatewayv1beta1.ObjectName(rg.ingressClass)}}
Expand All @@ -309,7 +309,7 @@ func (rg *ingressRuleGroup) toHTTPRoute() (gatewayv1beta1.HTTPRoute, field.Error
Matches: []gatewayv1beta1.HTTPRouteMatch{*match},
}

backendRefs, errs := rg.calculateBackendRefWeight(paths)
backendRefs, errs := rg.configureBackendRef(paths)
errors = append(errors, errs...)
hrRule.BackendRefs = backendRefs

Expand All @@ -319,15 +319,10 @@ func (rg *ingressRuleGroup) toHTTPRoute() (gatewayv1beta1.HTTPRoute, field.Error
return httpRoute, errors
}

func (rg *ingressRuleGroup) calculateBackendRefWeight(paths []ingressPath) ([]gatewayv1beta1.HTTPBackendRef, field.ErrorList) {
func (rg *ingressRuleGroup) configureBackendRef(paths []ingressPath) ([]gatewayv1beta1.HTTPBackendRef, field.ErrorList) {
var errors field.ErrorList
var backendRefs []gatewayv1beta1.HTTPBackendRef

var numWeightedBackends, totalWeightSet int32

// This is the default value for nginx annotation nginx.ingress.kubernetes.io/canary-weight-total
var weightTotal = 100

for i, path := range paths {
backendRef, err := toBackendRef(path.path.Backend, field.NewPath("paths", "backends").Index(i))
if err != nil {
Expand All @@ -336,21 +331,6 @@ func (rg *ingressRuleGroup) calculateBackendRefWeight(paths []ingressPath) ([]ga
}
backendRefs = append(backendRefs, gatewayv1beta1.HTTPBackendRef{BackendRef: *backendRef})
}
if numWeightedBackends > 0 && numWeightedBackends < int32(len(backendRefs)) {
weightToSet := (int32(weightTotal) - totalWeightSet) / (int32(len(backendRefs)) - numWeightedBackends)
if weightToSet < 0 {
weightToSet = 0
}
for i := range backendRefs {
if backendRefs[i].Weight == nil {
backendRefs[i].Weight = &weightToSet
}
if *backendRefs[i].Weight > int32(weightTotal) {
backendRefs[i].Weight = pointer.Int32(int32(weightTotal))

}
}
}

return backendRefs, errors
}
Expand Down Expand Up @@ -403,17 +383,3 @@ func toBackendRef(ib networkingv1.IngressBackend, path *field.Path) (*gatewayv1b
},
}, nil
}

func nameFromHost(host string) string {
// replace all special chars with -
reg, _ := regexp.Compile("[^a-zA-Z0-9]+")
step1 := reg.ReplaceAllString(host, "-")
// remove all - at start of string
reg2, _ := regexp.Compile("^[^a-zA-Z0-9]+")
step2 := reg2.ReplaceAllString(step1, "")
// if nothing left, return "all-hosts"
if len(host) == 0 {
return "all-hosts"
}
return step2
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ package common

import (
"errors"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)
Expand All @@ -41,12 +41,13 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
ingresses []networkingv1.Ingress
expectedGatewayResources i2gw.GatewayResources
expectedErrors field.ErrorList
}{{
name: "empty",
ingresses: []networkingv1.Ingress{},
expectedGatewayResources: i2gw.GatewayResources{},
expectedErrors: field.ErrorList{},
},
}{
{
name: "empty",
ingresses: []networkingv1.Ingress{},
expectedGatewayResources: i2gw.GatewayResources{},
expectedErrors: field.ErrorList{},
},
{
name: "simple ingress",
ingresses: []networkingv1.Ingress{{
Expand Down Expand Up @@ -322,7 +323,7 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
} else {
for i, got := range gatewayResources.HTTPRoutes {
want := tc.expectedGatewayResources.HTTPRoutes[i2gw.HTTPRouteToHTTPRouteKey(got)]
want.SetGroupVersionKind(httpRouteGVK)
want.SetGroupVersionKind(HTTPRouteGVK)
if !apiequality.Semantic.DeepEqual(got, want) {
t.Errorf("Expected HTTPRoute %s to be %+v\n Got: %+v\n Diff: %s", i, want, got, cmp.Diff(want, got))
}
Expand All @@ -335,7 +336,7 @@ func Test_ingresses2GatewaysAndHttpRoutes(t *testing.T) {
} else {
for i, got := range gatewayResources.Gateways {
want := tc.expectedGatewayResources.Gateways[i2gw.GatewayToGatewayKey(got)]
want.SetGroupVersionKind(gatewayGVK)
want.SetGroupVersionKind(GatewayGVK)
if !apiequality.Semantic.DeepEqual(got, want) {
t.Errorf("Expected Gateway %s to be %+v\n Got: %+v\n Diff: %s", i, want, got, cmp.Diff(want, got))
}
Expand Down
121 changes: 121 additions & 0 deletions pkg/i2gw/providers/common/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
Copyright 2022 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 common

import (
"fmt"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
"k8s.io/apimachinery/pkg/util/validation/field"
"regexp"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)

func GetIngressClass(ingress networkingv1.Ingress) string {
var ingressClass string

if ingress.Spec.IngressClassName != nil && *ingress.Spec.IngressClassName != "" {
ingressClass = *ingress.Spec.IngressClassName
} else if _, ok := ingress.Annotations[networkingv1beta1.AnnotationIngressClass]; ok {
ingressClass = ingress.Annotations[networkingv1beta1.AnnotationIngressClass]
} else {
ingressClass = ingress.Name
}

return ingressClass
}

type IngressRuleGroup struct {
Namespace string
IngressClass string
Host string
TLS []networkingv1.IngressTLS
Rules []Rule
}

type Rule struct {
Ingress networkingv1.Ingress
IngressRule networkingv1.IngressRule
}

func GetRuleGroups(ingresses []networkingv1.Ingress) map[string]IngressRuleGroup {
ruleGroups := make(map[string]IngressRuleGroup)

for _, ingress := range ingresses {
ingressClass := GetIngressClass(ingress)

for _, rule := range ingress.Spec.Rules {

rgKey := fmt.Sprintf("%s/%s/%s", ingress.Namespace, ingressClass, rule.Host)
rg, ok := ruleGroups[rgKey]
if !ok {
rg = IngressRuleGroup{
Namespace: ingress.Namespace,
IngressClass: ingressClass,
Host: rule.Host,
}
ruleGroups[rgKey] = rg
}
rg.TLS = append(rg.TLS, ingress.Spec.TLS...)
rg.Rules = append(rg.Rules, Rule{
Ingress: ingress,
IngressRule: rule,
})

ruleGroups[rgKey] = rg
}

}

return ruleGroups
}

func NameFromHost(host string) string {
// replace all special chars with -
reg, _ := regexp.Compile("[^a-zA-Z0-9]+")
step1 := reg.ReplaceAllString(host, "-")
// remove all - at start of string
reg2, _ := regexp.Compile("^[^a-zA-Z0-9]+")
step2 := reg2.ReplaceAllString(step1, "")
// if nothing left, return "all-hosts"
if len(host) == 0 {
return "all-hosts"
}
return step2
}

func ToBackendRef(ib networkingv1.IngressBackend, path *field.Path) (*gatewayv1beta1.BackendRef, *field.Error) {
if ib.Service != nil {
if ib.Service.Port.Name != "" {
fieldPath := path.Child("service", "port")
return nil, field.Invalid(fieldPath, "name", fmt.Sprintf("named ports not supported: %s", ib.Service.Port.Name))
}
return &gatewayv1beta1.BackendRef{
BackendObjectReference: gatewayv1beta1.BackendObjectReference{
Name: gatewayv1beta1.ObjectName(ib.Service.Name),
Port: (*gatewayv1beta1.PortNumber)(&ib.Service.Port.Number),
},
}, nil
}
return &gatewayv1beta1.BackendRef{
BackendObjectReference: gatewayv1beta1.BackendObjectReference{
Group: (*gatewayv1beta1.Group)(ib.Resource.APIGroup),
Kind: (*gatewayv1beta1.Kind)(&ib.Resource.Kind),
Name: gatewayv1beta1.ObjectName(ib.Resource.Name),
},
}, nil
}
Loading

0 comments on commit d54ed81

Please sign in to comment.