From 34304ec75574ea992ffb3b767f41e49ca3be7bfa Mon Sep 17 00:00:00 2001 From: David Cheung Date: Sun, 17 Mar 2024 23:19:10 +0000 Subject: [PATCH] Add converter in gce --- pkg/i2gw/provider.go | 2 +- pkg/i2gw/providers/common/converter.go | 16 +- pkg/i2gw/providers/gce/converter.go | 37 +- pkg/i2gw/providers/gce/converter_test.go | 559 ++++++++++++++++++ pkg/i2gw/providers/gce/gce.go | 4 - pkg/i2gw/providers/gce/types.go | 2 +- pkg/i2gw/providers/gce/utils.go | 20 +- pkg/i2gw/providers/gce/utils_test.go | 50 +- .../providers/kong/implementation_specific.go | 3 +- 9 files changed, 653 insertions(+), 40 deletions(-) diff --git a/pkg/i2gw/provider.go b/pkg/i2gw/provider.go index 1d7e6c61..5e4a2c25 100644 --- a/pkg/i2gw/provider.go +++ b/pkg/i2gw/provider.go @@ -77,7 +77,7 @@ type ResourceConverter interface { // ImplementationSpecificHTTPPathTypeMatchConverter is an option to customize the ingress implementationSpecific // match type conversion. -type ImplementationSpecificHTTPPathTypeMatchConverter func(*gatewayv1.HTTPPathMatch) +type ImplementationSpecificHTTPPathTypeMatchConverter func(*gatewayv1.HTTPPathMatch) error // ProviderImplementationSpecificOptions contains customized implementation-specific fields and functions. // These will be used by the common package to customize the provider-specific behavior for all the diff --git a/pkg/i2gw/providers/common/converter.go b/pkg/i2gw/providers/common/converter.go index ad4e1794..dbeb9830 100644 --- a/pkg/i2gw/providers/common/converter.go +++ b/pkg/i2gw/providers/common/converter.go @@ -22,7 +22,6 @@ import ( "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/types" @@ -135,14 +134,8 @@ type ingressPath struct { } func (a *ingressAggregator) addIngress(ingress networkingv1.Ingress) { - 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 - } + ingressClass := GetIngressClass(ingress) + for _, rule := range ingress.Spec.Rules { a.addIngressRule(ingress.Namespace, ingress.Name, ingressClass, rule, ingress.Spec) } @@ -377,7 +370,10 @@ func toHTTPRouteMatch(routePath networkingv1.HTTPIngressPath, path *field.Path, // is not given by the provider, an error is returned. case networkingv1.PathTypeImplementationSpecific: if toImplementationSpecificPathMatch != nil { - toImplementationSpecificPathMatch(match.Path) + err := toImplementationSpecificPathMatch(match.Path) + if err != nil { + return nil, field.Invalid(path.Child("pathType"), routePath.PathType, err.Error()) + } } else { return nil, field.Invalid(path.Child("pathType"), routePath.PathType, "implementationSpecific path type is not supported in generic translation, and your provider does not provide custom support to translate it") } diff --git a/pkg/i2gw/providers/gce/converter.go b/pkg/i2gw/providers/gce/converter.go index 71feff06..33546f8c 100644 --- a/pkg/i2gw/providers/gce/converter.go +++ b/pkg/i2gw/providers/gce/converter.go @@ -18,6 +18,9 @@ package gce import ( "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/util/validation/field" ) // converter implements the ToGatewayAPI function of i2gw.ResourceConverter interface. @@ -32,11 +35,37 @@ type converter struct { func newConverter(conf *i2gw.ProviderConf) converter { return converter{ conf: conf, - featureParsers: []i2gw.FeatureParser{ - // The list of feature parsers comes here. - }, + featureParsers: []i2gw.FeatureParser{}, implementationSpecificOptions: i2gw.ProviderImplementationSpecificOptions{ - // The list of the implementationSpecific ingress fields options comes here. + ToImplementationSpecificHTTPPathTypeMatch: implementationSpecificHTTPPathTypeMatch, }, } } + +func (c *converter) convert(storage *storage) (i2gw.GatewayResources, field.ErrorList) { + ingressList := []networkingv1.Ingress{} + for _, ing := range storage.Ingresses { + ingressList = append(ingressList, *ing) + } + + // Convert plain ingress resources to gateway resources, ignoring all + // provider-specific features. + gatewayResources, errs := common.ToGateway(ingressList, c.implementationSpecificOptions) + if len(errs) > 0 { + return i2gw.GatewayResources{}, errs + } + + errs = toGceGatewayClass(ingressList, &gatewayResources) + if len(errs) > 0 { + return i2gw.GatewayResources{}, errs + } + + for _, parseFeatureFunc := range c.featureParsers { + // Apply the feature parsing function to the gateway resources, one by one. + parseErrs := parseFeatureFunc(ingressList, &gatewayResources) + // Append the parsing errors to the error list. + errs = append(errs, parseErrs...) + } + + return gatewayResources, errs +} diff --git a/pkg/i2gw/providers/gce/converter_test.go b/pkg/i2gw/providers/gce/converter_test.go index 3ce35ba8..c342c5e5 100644 --- a/pkg/i2gw/providers/gce/converter_test.go +++ b/pkg/i2gw/providers/gce/converter_test.go @@ -15,3 +15,562 @@ limitations under the License. */ package gce + +import ( + "errors" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" + "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common" + networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/validation/field" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +func Test_ToGateway(t *testing.T) { + testNamespace := "default" + testHost := "test.mydomain.com" + testBackendServiceName := "test" + iPrefix := networkingv1.PathTypePrefix + implSpecificPathType := networkingv1.PathTypeImplementationSpecific + + gPathPrefix := gatewayv1.PathMatchPathPrefix + gExact := gatewayv1.PathMatchExact + + extIngClassIngressName := "gce-ingress-class" + intIngClassIngressName := "gce-internal-ingress-class" + noIngClassIngressName := "no-ingress-class" + + testCases := []struct { + name string + ingresses map[types.NamespacedName]*networkingv1.Ingress + expectedGatewayResources i2gw.GatewayResources + expectedErrors field.ErrorList + }{ + { + name: "gce ingress class", + ingresses: map[types.NamespacedName]*networkingv1.Ingress{ + {Namespace: testNamespace, Name: extIngClassIngressName}: { + ObjectMeta: metav1.ObjectMeta{ + Name: extIngClassIngressName, + Namespace: testNamespace, + Annotations: map[string]string{networkingv1beta1.AnnotationIngressClass: gceIngressClass}, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{{ + Host: testHost, + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{{ + Path: "/", + PathType: &iPrefix, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: testBackendServiceName, + Port: networkingv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }}, + }, + }, + }}, + }, + }, + }, + expectedGatewayResources: i2gw.GatewayResources{ + Gateways: map[types.NamespacedName]gatewayv1.Gateway{ + {Namespace: testNamespace, Name: gceIngressClass}: { + ObjectMeta: metav1.ObjectMeta{Name: gceIngressClass, Namespace: testNamespace}, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: gceL7GlobalExternalManagedGatewayClass, + Listeners: []gatewayv1.Listener{{ + Name: "test-mydomain-com-http", + Port: 80, + Protocol: gatewayv1.HTTPProtocolType, + Hostname: ptrTo(gatewayv1.Hostname(testHost)), + }}, + }, + }, + }, + HTTPRoutes: map[types.NamespacedName]gatewayv1.HTTPRoute{ + {Namespace: testNamespace, Name: fmt.Sprintf("%s-test-mydomain-com", extIngClassIngressName)}: { + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-test-mydomain-com", extIngClassIngressName), Namespace: testNamespace}, + Spec: gatewayv1.HTTPRouteSpec{ + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{{ + Name: gceIngressClass, + }}, + }, + Hostnames: []gatewayv1.Hostname{gatewayv1.Hostname(testHost)}, + Rules: []gatewayv1.HTTPRouteRule{ + { + Matches: []gatewayv1.HTTPRouteMatch{ + { + Path: &gatewayv1.HTTPPathMatch{ + Type: &gPathPrefix, + Value: ptrTo("/"), + }, + }, + }, + BackendRefs: []gatewayv1.HTTPBackendRef{ + { + BackendRef: gatewayv1.BackendRef{ + BackendObjectReference: gatewayv1.BackendObjectReference{ + Name: gatewayv1.ObjectName(testBackendServiceName), + Port: ptrTo(gatewayv1.PortNumber(80)), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "gce-internal ingress class", + ingresses: map[types.NamespacedName]*networkingv1.Ingress{ + {Namespace: testNamespace, Name: intIngClassIngressName}: { + ObjectMeta: metav1.ObjectMeta{ + Name: intIngClassIngressName, + Namespace: testNamespace, + Annotations: map[string]string{networkingv1beta1.AnnotationIngressClass: gceL7ILBIngressClass}, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{{ + Host: testHost, + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{{ + Path: "/", + PathType: &iPrefix, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: testBackendServiceName, + Port: networkingv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }}, + }, + }, + }}, + }, + }, + }, + expectedGatewayResources: i2gw.GatewayResources{ + Gateways: map[types.NamespacedName]gatewayv1.Gateway{ + {Namespace: testNamespace, Name: gceL7ILBIngressClass}: { + ObjectMeta: metav1.ObjectMeta{Name: gceL7ILBIngressClass, Namespace: testNamespace}, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: gceL7RegionalInternalGatewayClass, + Listeners: []gatewayv1.Listener{{ + Name: "test-mydomain-com-http", + Port: 80, + Protocol: gatewayv1.HTTPProtocolType, + Hostname: ptrTo(gatewayv1.Hostname(testHost)), + }}, + }, + }, + }, + HTTPRoutes: map[types.NamespacedName]gatewayv1.HTTPRoute{ + {Namespace: testNamespace, Name: "gce-internal-ingress-class-test-mydomain-com"}: { + ObjectMeta: metav1.ObjectMeta{Name: "gce-internal-ingress-class-test-mydomain-com", Namespace: testNamespace}, + Spec: gatewayv1.HTTPRouteSpec{ + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{{ + Name: gceL7ILBIngressClass, + }}, + }, + Hostnames: []gatewayv1.Hostname{gatewayv1.Hostname(testHost)}, + Rules: []gatewayv1.HTTPRouteRule{ + { + Matches: []gatewayv1.HTTPRouteMatch{ + { + Path: &gatewayv1.HTTPPathMatch{ + Type: &gPathPrefix, + Value: ptrTo("/"), + }, + }, + }, + BackendRefs: []gatewayv1.HTTPBackendRef{ + { + BackendRef: gatewayv1.BackendRef{ + BackendObjectReference: gatewayv1.BackendObjectReference{ + Name: gatewayv1.ObjectName(testBackendServiceName), + Port: ptrTo(gatewayv1.PortNumber(80)), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "no ingress class, default to gce ingress class behavior", + ingresses: map[types.NamespacedName]*networkingv1.Ingress{ + {Namespace: testNamespace, Name: noIngClassIngressName}: { + ObjectMeta: metav1.ObjectMeta{ + Name: noIngClassIngressName, + Namespace: testNamespace, + Annotations: map[string]string{networkingv1beta1.AnnotationIngressClass: gceIngressClass}, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{{ + Host: testHost, + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{{ + Path: "/", + PathType: &iPrefix, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: testBackendServiceName, + Port: networkingv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }}, + }, + }, + }}, + }, + }, + }, + expectedGatewayResources: i2gw.GatewayResources{ + Gateways: map[types.NamespacedName]gatewayv1.Gateway{ + {Namespace: testNamespace, Name: gceIngressClass}: { + ObjectMeta: metav1.ObjectMeta{Name: gceIngressClass, Namespace: testNamespace}, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: gceL7GlobalExternalManagedGatewayClass, + Listeners: []gatewayv1.Listener{{ + Name: "test-mydomain-com-http", + Port: 80, + Protocol: gatewayv1.HTTPProtocolType, + Hostname: ptrTo(gatewayv1.Hostname(testHost)), + }}, + }, + }, + }, + HTTPRoutes: map[types.NamespacedName]gatewayv1.HTTPRoute{ + {Namespace: testNamespace, Name: fmt.Sprintf("%s-test-mydomain-com", noIngClassIngressName)}: { + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-test-mydomain-com", noIngClassIngressName), Namespace: testNamespace}, + Spec: gatewayv1.HTTPRouteSpec{ + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{{ + Name: gceIngressClass, + }}, + }, + Hostnames: []gatewayv1.Hostname{gatewayv1.Hostname(testHost)}, + Rules: []gatewayv1.HTTPRouteRule{ + { + Matches: []gatewayv1.HTTPRouteMatch{ + { + Path: &gatewayv1.HTTPPathMatch{ + Type: &gPathPrefix, + Value: ptrTo("/"), + }, + }, + }, + BackendRefs: []gatewayv1.HTTPBackendRef{ + { + BackendRef: gatewayv1.BackendRef{ + BackendObjectReference: gatewayv1.BackendObjectReference{ + Name: gatewayv1.ObjectName(testBackendServiceName), + Port: ptrTo(gatewayv1.PortNumber(80)), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "gce implementation-specific with /*, map to / Prefix", + ingresses: map[types.NamespacedName]*networkingv1.Ingress{ + {Namespace: testNamespace, Name: extIngClassIngressName}: { + ObjectMeta: metav1.ObjectMeta{ + Name: extIngClassIngressName, + Namespace: testNamespace, + Annotations: map[string]string{networkingv1beta1.AnnotationIngressClass: gceIngressClass}, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{{ + Host: testHost, + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{{ + Path: "/*", + PathType: &implSpecificPathType, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: testBackendServiceName, + Port: networkingv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }}, + }, + }, + }}, + }, + }, + }, + expectedGatewayResources: i2gw.GatewayResources{ + Gateways: map[types.NamespacedName]gatewayv1.Gateway{ + {Namespace: testNamespace, Name: gceIngressClass}: { + ObjectMeta: metav1.ObjectMeta{Name: gceIngressClass, Namespace: testNamespace}, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: gceL7GlobalExternalManagedGatewayClass, + Listeners: []gatewayv1.Listener{{ + Name: "test-mydomain-com-http", + Port: 80, + Protocol: gatewayv1.HTTPProtocolType, + Hostname: ptrTo(gatewayv1.Hostname(testHost)), + }}, + }, + }, + }, + HTTPRoutes: map[types.NamespacedName]gatewayv1.HTTPRoute{ + {Namespace: testNamespace, Name: fmt.Sprintf("%s-test-mydomain-com", extIngClassIngressName)}: { + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-test-mydomain-com", extIngClassIngressName), Namespace: testNamespace}, + Spec: gatewayv1.HTTPRouteSpec{ + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{{ + Name: gceIngressClass, + }}, + }, + Hostnames: []gatewayv1.Hostname{gatewayv1.Hostname(testHost)}, + Rules: []gatewayv1.HTTPRouteRule{ + { + Matches: []gatewayv1.HTTPRouteMatch{ + { + Path: &gatewayv1.HTTPPathMatch{ + Type: &gPathPrefix, + Value: ptrTo("/"), + }, + }, + }, + BackendRefs: []gatewayv1.HTTPBackendRef{ + { + BackendRef: gatewayv1.BackendRef{ + BackendObjectReference: gatewayv1.BackendObjectReference{ + Name: gatewayv1.ObjectName(testBackendServiceName), + Port: ptrTo(gatewayv1.PortNumber(80)), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "gce implementation-specific with /foo/*, not supported", + ingresses: map[types.NamespacedName]*networkingv1.Ingress{ + {Namespace: testNamespace, Name: extIngClassIngressName}: { + ObjectMeta: metav1.ObjectMeta{ + Name: extIngClassIngressName, + Namespace: testNamespace, + Annotations: map[string]string{networkingv1beta1.AnnotationIngressClass: gceIngressClass}, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{{ + Host: testHost, + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{{ + Path: "/foo/*", + PathType: &implSpecificPathType, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: testBackendServiceName, + Port: networkingv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }}, + }, + }, + }}, + }, + }, + }, + expectedGatewayResources: i2gw.GatewayResources{ + Gateways: map[types.NamespacedName]gatewayv1.Gateway{}, + HTTPRoutes: map[types.NamespacedName]gatewayv1.HTTPRoute{}, + }, + expectedErrors: field.ErrorList{field.Invalid(field.NewPath(""), "", "")}, + }, + { + name: "gce implementation-specific without wildcard path, map to Prefix", + ingresses: map[types.NamespacedName]*networkingv1.Ingress{ + {Namespace: testNamespace, Name: extIngClassIngressName}: { + ObjectMeta: metav1.ObjectMeta{ + Name: extIngClassIngressName, + Namespace: testNamespace, + Annotations: map[string]string{networkingv1beta1.AnnotationIngressClass: gceIngressClass}, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{{ + Host: testHost, + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{{ + Path: "/foo", + PathType: &implSpecificPathType, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: testBackendServiceName, + Port: networkingv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }}, + }, + }, + }}, + }, + }, + }, + expectedGatewayResources: i2gw.GatewayResources{ + Gateways: map[types.NamespacedName]gatewayv1.Gateway{ + {Namespace: testNamespace, Name: gceIngressClass}: { + ObjectMeta: metav1.ObjectMeta{Name: gceIngressClass, Namespace: testNamespace}, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: gceL7GlobalExternalManagedGatewayClass, + Listeners: []gatewayv1.Listener{{ + Name: "test-mydomain-com-http", + Port: 80, + Protocol: gatewayv1.HTTPProtocolType, + Hostname: ptrTo(gatewayv1.Hostname(testHost)), + }}, + }, + }, + }, + HTTPRoutes: map[types.NamespacedName]gatewayv1.HTTPRoute{ + {Namespace: testNamespace, Name: fmt.Sprintf("%s-test-mydomain-com", extIngClassIngressName)}: { + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-test-mydomain-com", extIngClassIngressName), Namespace: testNamespace}, + Spec: gatewayv1.HTTPRouteSpec{ + CommonRouteSpec: gatewayv1.CommonRouteSpec{ + ParentRefs: []gatewayv1.ParentReference{{ + Name: gceIngressClass, + }}, + }, + Hostnames: []gatewayv1.Hostname{gatewayv1.Hostname(testHost)}, + Rules: []gatewayv1.HTTPRouteRule{ + { + Matches: []gatewayv1.HTTPRouteMatch{ + { + Path: &gatewayv1.HTTPPathMatch{ + Type: &gExact, + Value: ptrTo("/foo"), + }, + }, + }, + BackendRefs: []gatewayv1.HTTPBackendRef{ + { + BackendRef: gatewayv1.BackendRef{ + BackendObjectReference: gatewayv1.BackendObjectReference{ + Name: gatewayv1.ObjectName(testBackendServiceName), + Port: ptrTo(gatewayv1.PortNumber(80)), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + provider := NewProvider(&i2gw.ProviderConf{}) + gceProvider := provider.(*Provider) + gceProvider.storage = newResourcesStorage() + gceProvider.storage.Ingresses = tc.ingresses + + // TODO(#113) we pass an empty i2gw.InputResources temporarily until we change ToGatewayAPI function on the interface + gatewayResources, errs := provider.ToGatewayAPI() + + if len(errs) != len(tc.expectedErrors) { + t.Errorf("Expected %d errors, got %d: %+v", len(tc.expectedErrors), len(errs), errs) + } else { + for i, e := range errs { + if errors.Is(e, tc.expectedErrors[i]) { + t.Errorf("Unexpected error message at %d index. Got %s, want: %s", i, e, tc.expectedErrors[i]) + } + } + } + + if len(gatewayResources.HTTPRoutes) != len(tc.expectedGatewayResources.HTTPRoutes) { + t.Errorf("Expected %d HTTPRoutes, got %d: %+v", + len(tc.expectedGatewayResources.HTTPRoutes), len(gatewayResources.HTTPRoutes), gatewayResources.HTTPRoutes) + } else { + for i, got := range gatewayResources.HTTPRoutes { + key := types.NamespacedName{Namespace: got.Namespace, Name: got.Name} + want := tc.expectedGatewayResources.HTTPRoutes[key] + want.SetGroupVersionKind(common.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)) + } + } + } + + if len(gatewayResources.Gateways) != len(tc.expectedGatewayResources.Gateways) { + t.Errorf("Expected %d Gateways, got %d: %+v", + len(tc.expectedGatewayResources.Gateways), len(gatewayResources.Gateways), gatewayResources.Gateways) + } else { + for i, got := range gatewayResources.Gateways { + key := types.NamespacedName{Namespace: got.Namespace, Name: got.Name} + want := tc.expectedGatewayResources.Gateways[key] + want.SetGroupVersionKind(common.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)) + } + } + } + + }) + } +} + +func ptrTo[T any](a T) *T { + return &a +} diff --git a/pkg/i2gw/providers/gce/gce.go b/pkg/i2gw/providers/gce/gce.go index e6eaf34e..ca6dfb71 100644 --- a/pkg/i2gw/providers/gce/gce.go +++ b/pkg/i2gw/providers/gce/gce.go @@ -70,7 +70,3 @@ func (p *Provider) ReadResourcesFromFile(_ context.Context, filename string) err func (p *Provider) ToGatewayAPI() (i2gw.GatewayResources, field.ErrorList) { return p.converter.convert(p.storage) } - -func (c *converter) convert(storage *storage) (i2gw.GatewayResources, field.ErrorList) { - return i2gw.GatewayResources{}, field.ErrorList{} -} diff --git a/pkg/i2gw/providers/gce/types.go b/pkg/i2gw/providers/gce/types.go index d41ca3de..08b00be8 100644 --- a/pkg/i2gw/providers/gce/types.go +++ b/pkg/i2gw/providers/gce/types.go @@ -5,7 +5,7 @@ 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 + 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, diff --git a/pkg/i2gw/providers/gce/utils.go b/pkg/i2gw/providers/gce/utils.go index a43edbec..f93a708d 100644 --- a/pkg/i2gw/providers/gce/utils.go +++ b/pkg/i2gw/providers/gce/utils.go @@ -5,7 +5,7 @@ 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 + 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, @@ -18,7 +18,6 @@ package gce import ( "fmt" - "strings" "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw" "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common" @@ -59,7 +58,7 @@ func toGceGatewayClass(ingresses []networkingv1.Ingress, gatewayResources *i2gw. // case, we use it to map to the corresponding gateway class. // 2. Annotation does not exist. In this case, the ingress is defaulted // to use the GCE external ingress implementation, and should be - // mapped to `gke-l7-gxlb`. + // mapped to `gke-l7-global-external-managed`. for _, ingress := range ingresses { gwKey := types.NamespacedName{Namespace: ingress.Namespace, Name: common.GetIngressClass(ingress)} existingGateway := gatewayResources.Gateways[gwKey] @@ -104,18 +103,3 @@ func ingClassToGwyClassGCE(ingressClass string) (string, error) { return "", fmt.Errorf("Given GCE Ingress Class not supported") } } - -// checkImplementationSpecificPath checks if this ingress contains a -// ImplementationSpecific path with * and throws an error if it does. -func checkImplementationSpecificPath(ingress *networkingv1.Ingress) *field.Error { - for _, rule := range ingress.Spec.Rules { - for _, path := range rule.HTTP.Paths { - if path.PathType != nil && *path.PathType == networkingv1.PathTypeImplementationSpecific && strings.HasSuffix(path.Path, "/*") { - ingressName := fmt.Sprintf("%s/%s", ingress.Namespace, ingress.Name) - detail := "ImplementationSpecific Path with * is not supported for GCE ingress2gateway conversion" - return field.Invalid(field.NewPath(ingressName).Child("Spec", "Rules", "HTTP", "Paths", "Path"), path.Path, detail) - } - } - } - return nil -} diff --git a/pkg/i2gw/providers/gce/utils_test.go b/pkg/i2gw/providers/gce/utils_test.go index 623c457e..110de323 100644 --- a/pkg/i2gw/providers/gce/utils_test.go +++ b/pkg/i2gw/providers/gce/utils_test.go @@ -5,7 +5,7 @@ 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 + 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, @@ -140,6 +140,54 @@ func TestIsSupportedGCEIngressClass(t *testing.T) { } } +func TestIngClassToGwyClassGCE(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + ingressClass string + expectedGatewayClass string + expectedNil bool + }{ + { + desc: "gce ingress class", + ingressClass: gceIngressClass, + expectedGatewayClass: gceL7GlobalExternalManagedGatewayClass, + expectedNil: true, + }, + { + desc: "gce-internal ingress class", + ingressClass: gceL7ILBIngressClass, + expectedGatewayClass: gceL7RegionalInternalGatewayClass, + expectedNil: true, + }, + { + desc: "unexpected ingress class", + ingressClass: "unexpected", + expectedGatewayClass: "", + expectedNil: false, + }, + { + desc: "missing ingress class", + ingressClass: "", + expectedGatewayClass: gceL7GlobalExternalManagedGatewayClass, + expectedNil: true, + }, + } + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + gotGatewayClass, gotErr := ingClassToGwyClassGCE(tc.ingressClass) + if gotGatewayClass != tc.expectedGatewayClass { + t.Errorf("ingClassToGwyClassGCE() = %v, expected %v", gotGatewayClass, tc.expectedGatewayClass) + } + gotNil := gotErr == nil + if gotNil != tc.expectedNil { + t.Errorf("ingClassToGwyClassGCE() = %v, expected %v", gotNil, tc.expectedNil) + } + }) + } +} + // NewIngress returns an Ingress with the given spec. func NewIngress(name types.NamespacedName, annotations map[string]string, spec networkingv1.IngressSpec) networkingv1.Ingress { return networkingv1.Ingress{ diff --git a/pkg/i2gw/providers/kong/implementation_specific.go b/pkg/i2gw/providers/kong/implementation_specific.go index a1feba55..e46eaeda 100644 --- a/pkg/i2gw/providers/kong/implementation_specific.go +++ b/pkg/i2gw/providers/kong/implementation_specific.go @@ -23,7 +23,7 @@ import ( gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" ) -func implementationSpecificHTTPPathTypeMatch(path *gatewayv1.HTTPPathMatch) { +func implementationSpecificHTTPPathTypeMatch(path *gatewayv1.HTTPPathMatch) error { pmPrefix := gatewayv1.PathMatchPathPrefix pmRegex := gatewayv1.PathMatchRegularExpression if strings.HasPrefix(*path.Value, "/~") { @@ -32,4 +32,5 @@ func implementationSpecificHTTPPathTypeMatch(path *gatewayv1.HTTPPathMatch) { } else { path.Type = &pmPrefix } + return nil }