// Copyright (c) 2022 Alibaba Group Holding Ltd. // // 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 ingress import ( "context" "testing" "time" "github.com/google/go-cmp/cmp" "istio.io/api/networking/v1alpha3" "istio.io/istio/pilot/pkg/model" istiomodel "istio.io/istio/pilot/pkg/model" "istio.io/istio/pkg/config" "istio.io/istio/pkg/config/schema/gvk" "istio.io/istio/pkg/config/schema/gvr" schemakubeclient "istio.io/istio/pkg/config/schema/kubeclient" "istio.io/istio/pkg/kube/controllers" ktypes "istio.io/istio/pkg/kube/kubetypes" v1 "k8s.io/api/core/v1" "k8s.io/api/networking/v1beta1" ingress "k8s.io/api/networking/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/watch" listerv1 "k8s.io/client-go/listers/core/v1" networkinglister "k8s.io/client-go/listers/networking/v1beta1" "github.com/alibaba/higress/pkg/ingress/kube/annotations" "github.com/alibaba/higress/pkg/ingress/kube/common" "github.com/alibaba/higress/pkg/ingress/kube/secret" "github.com/alibaba/higress/pkg/ingress/kube/util" "github.com/alibaba/higress/pkg/kube" "github.com/stretchr/testify/require" ) func TestIngressControllerApplies(t *testing.T) { fakeClient := kube.NewFakeClient() localKubeClient, client := fakeClient, fakeClient options := common.Options{IngressClass: "mse", ClusterId: ""} secretController := secret.NewController(localKubeClient, options.ClusterId) ingressController := NewController(localKubeClient, client, options, secretController) testcases := map[string]func(*testing.T, common.IngressController){ "test apply canary ingress": testApplyCanaryIngress, "test apply default backend": testApplyDefaultBackend, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { tc(t, ingressController) }) } } func testApplyCanaryIngress(t *testing.T, c common.IngressController) { testcases := []struct { description string input struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig } expectNoError bool }{ { description: "convertOptions is nil", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: nil, wrapperConfig: nil, }, expectNoError: false, }, { description: "convertOptions is not nil but empty", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: &common.ConvertOptions{}, wrapperConfig: &common.WrapperConfig{ Config: &config.Config{}, AnnotationsConfig: &annotations.Ingress{}, }, }, expectNoError: false, }, { description: "valid canary ingress", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: &common.ConvertOptions{ IngressDomainCache: &common.IngressDomainCache{ Valid: make(map[string]*common.IngressDomainBuilder), Invalid: make([]model.IngressDomain, 0), }, Route2Ingress: map[string]*common.WrapperConfigWithRuleKey{}, VirtualServices: make(map[string]*common.WrapperVirtualService), Gateways: make(map[string]*common.WrapperGateway), IngressRouteCache: &common.IngressRouteCache{}, HTTPRoutes: map[string][]*common.WrapperHTTPRoute{ "test1": make([]*common.WrapperHTTPRoute, 0), }, }, wrapperConfig: &common.WrapperConfig{Config: &config.Config{ Spec: ingress.IngressSpec{Rules: []ingress.IngressRule{ { Host: "test1", IngressRuleValue: ingress.IngressRuleValue{ HTTP: &ingress.HTTPIngressRuleValue{ Paths: []ingress.HTTPIngressPath{ { Path: "/test", PathType: &defaultPathType, }, }, }, }, }, }, Backend: &ingress.IngressBackend{}, TLS: []ingress.IngressTLS{ { Hosts: []string{"test1", "test2"}, SecretName: "test", }, }}, }, AnnotationsConfig: &annotations.Ingress{}}, }, expectNoError: true, }, } for _, testcase := range testcases { err := c.ApplyCanaryIngress(testcase.input.options, testcase.input.wrapperConfig) if err != nil { require.Equal(t, testcase.expectNoError, false) } else { require.Equal(t, testcase.expectNoError, true) } } } func testApplyDefaultBackend(t *testing.T, c common.IngressController) { testcases := []struct { description string input struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig } expectNoError bool }{ { description: "convertOptions is nil", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: nil, wrapperConfig: nil, }, expectNoError: false, }, { description: "convertOptions is not nil but empty", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: &common.ConvertOptions{}, wrapperConfig: &common.WrapperConfig{ Config: &config.Config{}, AnnotationsConfig: &annotations.Ingress{}, }, }, expectNoError: false, }, { description: "valid default backend", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: &common.ConvertOptions{ IngressDomainCache: &common.IngressDomainCache{ Valid: make(map[string]*common.IngressDomainBuilder), Invalid: make([]model.IngressDomain, 0), }, Route2Ingress: map[string]*common.WrapperConfigWithRuleKey{}, VirtualServices: make(map[string]*common.WrapperVirtualService), Gateways: make(map[string]*common.WrapperGateway), IngressRouteCache: &common.IngressRouteCache{}, HTTPRoutes: make(map[string][]*common.WrapperHTTPRoute), }, wrapperConfig: &common.WrapperConfig{Config: &config.Config{ Spec: ingress.IngressSpec{Rules: []ingress.IngressRule{ { Host: "test1", IngressRuleValue: ingress.IngressRuleValue{ HTTP: &ingress.HTTPIngressRuleValue{ Paths: []ingress.HTTPIngressPath{ { Path: "/test", PathType: &defaultPathType, }, }, }, }, }, }, Backend: &ingress.IngressBackend{}, TLS: []ingress.IngressTLS{ { Hosts: []string{"test1", "test2"}, SecretName: "test", }, }}, }, AnnotationsConfig: &annotations.Ingress{}}, }, expectNoError: true, }, } for _, testcase := range testcases { err := c.ApplyDefaultBackend(testcase.input.options, testcase.input.wrapperConfig) if err != nil { require.Equal(t, testcase.expectNoError, false) } else { require.Equal(t, testcase.expectNoError, true) } } } func TestIngressControllerConventions(t *testing.T) { fakeClient := kube.NewFakeClient() localKubeClient, client := fakeClient, fakeClient options := common.Options{IngressClass: "mse", ClusterId: "", EnableStatus: true} secretController := secret.NewController(localKubeClient, options.ClusterId) ingressController := NewController(localKubeClient, client, options, secretController) testcases := map[string]func(*testing.T, common.IngressController){ "test convert Gateway": testConvertGateway, "test convert HTTPRoute": testConvertHTTPRoute, "test convert TrafficPolicy": testConvertTrafficPolicy, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { tc(t, ingressController) }) } } func testConvertGateway(t *testing.T, c common.IngressController) { testcases := []struct { description string input struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig } expectNoError bool }{ { description: "convertOptions is nil", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: nil, wrapperConfig: nil, }, expectNoError: false, }, { description: "convertOptions is not nil but empty", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: &common.ConvertOptions{}, wrapperConfig: &common.WrapperConfig{ Config: &config.Config{}, AnnotationsConfig: &annotations.Ingress{}, }, }, expectNoError: false, }, { description: "valid gateway convention", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: &common.ConvertOptions{ IngressDomainCache: &common.IngressDomainCache{ Valid: make(map[string]*common.IngressDomainBuilder), Invalid: make([]model.IngressDomain, 0), }, Gateways: make(map[string]*common.WrapperGateway), }, wrapperConfig: &common.WrapperConfig{Config: &config.Config{ Spec: ingress.IngressSpec{Rules: []ingress.IngressRule{ { Host: "test1", IngressRuleValue: ingress.IngressRuleValue{ HTTP: &ingress.HTTPIngressRuleValue{ Paths: []ingress.HTTPIngressPath{ { Path: "/test", }, }, }, }, }, }, Backend: &ingress.IngressBackend{}, TLS: []ingress.IngressTLS{ { Hosts: []string{"test1", "test2"}, SecretName: "test", }, }}, }, AnnotationsConfig: &annotations.Ingress{}}, }, expectNoError: true, }, } for _, testcase := range testcases { err := c.ConvertGateway(testcase.input.options, testcase.input.wrapperConfig, nil) if err != nil { require.Equal(t, testcase.expectNoError, false) } else { require.Equal(t, testcase.expectNoError, true) } } } func testConvertHTTPRoute(t *testing.T, c common.IngressController) { testcases := []struct { description string input struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig } expectNoError bool }{ { description: "convertOptions is nil", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: nil, wrapperConfig: nil, }, expectNoError: false, }, { description: "convertOptions is not nil but empty", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: &common.ConvertOptions{}, wrapperConfig: &common.WrapperConfig{ Config: &config.Config{}, AnnotationsConfig: &annotations.Ingress{}, }, }, expectNoError: false, }, { description: "valid httpRoute convention", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: &common.ConvertOptions{ IngressDomainCache: &common.IngressDomainCache{ Valid: make(map[string]*common.IngressDomainBuilder), Invalid: make([]model.IngressDomain, 0), }, Route2Ingress: map[string]*common.WrapperConfigWithRuleKey{}, VirtualServices: make(map[string]*common.WrapperVirtualService), Gateways: make(map[string]*common.WrapperGateway), IngressRouteCache: &common.IngressRouteCache{}, HTTPRoutes: make(map[string][]*common.WrapperHTTPRoute), }, wrapperConfig: &common.WrapperConfig{Config: &config.Config{ Spec: ingress.IngressSpec{Rules: []ingress.IngressRule{ { Host: "test1", IngressRuleValue: ingress.IngressRuleValue{ HTTP: &ingress.HTTPIngressRuleValue{ Paths: []ingress.HTTPIngressPath{ { Path: "/test", PathType: &defaultPathType, }, }, }, }, }, }, Backend: &ingress.IngressBackend{}, TLS: []ingress.IngressTLS{ { Hosts: []string{"test1", "test2"}, SecretName: "test", }, }}, }, AnnotationsConfig: &annotations.Ingress{}, }, }, expectNoError: true, }, } for _, testcase := range testcases { err := c.ConvertHTTPRoute(testcase.input.options, testcase.input.wrapperConfig) if err != nil { require.Equal(t, testcase.expectNoError, false) } else { require.Equal(t, testcase.expectNoError, true) } } } func testConvertTrafficPolicy(t *testing.T, c common.IngressController) { testcases := []struct { description string input struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig } expectNoError bool }{ { description: "convertOptions is nil", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: nil, wrapperConfig: nil, }, expectNoError: false, }, { description: "convertOptions is not nil but empty", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: &common.ConvertOptions{}, wrapperConfig: &common.WrapperConfig{ Config: &config.Config{}, AnnotationsConfig: &annotations.Ingress{}, }, }, expectNoError: true, }, { description: "valid trafficPolicy convention", input: struct { options *common.ConvertOptions wrapperConfig *common.WrapperConfig }{ options: &common.ConvertOptions{ IngressDomainCache: &common.IngressDomainCache{ Valid: make(map[string]*common.IngressDomainBuilder), Invalid: make([]model.IngressDomain, 0), }, Route2Ingress: map[string]*common.WrapperConfigWithRuleKey{}, VirtualServices: make(map[string]*common.WrapperVirtualService), Gateways: make(map[string]*common.WrapperGateway), IngressRouteCache: &common.IngressRouteCache{}, Service2TrafficPolicy: make(map[common.ServiceKey]*common.WrapperTrafficPolicy), HTTPRoutes: make(map[string][]*common.WrapperHTTPRoute), }, wrapperConfig: &common.WrapperConfig{Config: &config.Config{ Spec: ingress.IngressSpec{Rules: []ingress.IngressRule{ { Host: "test1", IngressRuleValue: ingress.IngressRuleValue{ HTTP: &ingress.HTTPIngressRuleValue{ Paths: []ingress.HTTPIngressPath{ { Path: "/test", PathType: &defaultPathType, Backend: ingress.IngressBackend{ ServiceName: "test", ServicePort: intstr.FromInt(8080), }, }, }, }, }, }, }, Backend: &ingress.IngressBackend{ ServiceName: "test", }, TLS: []ingress.IngressTLS{ { Hosts: []string{"test1", "test2"}, SecretName: "test", }, }}, }, AnnotationsConfig: &annotations.Ingress{ LoadBalance: &annotations.LoadBalanceConfig{}, }}, }, expectNoError: true, }, } for _, testcase := range testcases { err := c.ConvertTrafficPolicy(testcase.input.options, testcase.input.wrapperConfig) if err != nil { require.Equal(t, testcase.expectNoError, false) } else { require.Equal(t, testcase.expectNoError, true) } } } func TestIngressControllerGenerations(t *testing.T) { c := &controller{ options: common.Options{ IngressClass: "mse", SystemNamespace: "higress-system", }, ingresses: make(map[string]*v1beta1.Ingress), } testcases := map[string]func(*testing.T, *controller){ "test create DefaultRoute": testcreateDefaultRoute, "test create ServiceKey": testcreateServiceKey, "test backend to RouteDestination": testbackendToRouteDestination, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { tc(t, c) }) } } func testcreateDefaultRoute(t *testing.T, c *controller) { testcases := []struct { input struct { wrapper *common.WrapperConfig backend *ingress.IngressBackend host string } description string expect *common.WrapperHTTPRoute }{ { input: struct { wrapper *common.WrapperConfig backend *ingress.IngressBackend host string }{ wrapper: nil, backend: nil, host: "", }, description: "wrapperConfig is nil", expect: nil, }, { input: struct { wrapper *common.WrapperConfig backend *ingress.IngressBackend host string }{ wrapper: &common.WrapperConfig{}, backend: &ingress.IngressBackend{}, host: "test", }, description: "wrapperConfig is not nil but empty", expect: nil, }, { input: struct { wrapper *common.WrapperConfig backend *ingress.IngressBackend host string }{ wrapper: &common.WrapperConfig{ Config: &config.Config{ Meta: config.Meta{ Namespace: "higress-system", Name: "test", }, }, AnnotationsConfig: &annotations.Ingress{}}, backend: &ingress.IngressBackend{ ServiceName: "test", ServicePort: intstr.FromInt(8088), }, host: "test", }, description: "create expected httpRoute", expect: &common.WrapperHTTPRoute{ WrapperConfig: &common.WrapperConfig{ Config: &config.Config{ Meta: config.Meta{ Name: "test", Namespace: "higress-system", }, }, AnnotationsConfig: &annotations.Ingress{}, }, RawClusterId: "", ClusterId: "", ClusterName: "", Host: "test", OriginPath: "/", OriginPathType: "prefix", WeightTotal: 0, IsDefaultBackend: true, HTTPRoute: &v1alpha3.HTTPRoute{ Name: "test-default", Route: []*v1alpha3.HTTPRouteDestination{ { Weight: 100, Destination: &v1alpha3.Destination{ Port: &v1alpha3.PortSelector{ Number: 8088, }, Host: "test.higress-system.svc.cluster.local", }, }, }, }, }, }, } for _, testcase := range testcases { httpRoute := c.createDefaultRoute(testcase.input.wrapper, testcase.input.backend, testcase.input.host) require.Equal(t, testcase.expect, httpRoute) } } func testcreateServiceKey(t *testing.T, c *controller) { testcases := []struct { input struct { backend *ingress.IngressBackend namespace string } expectNoError bool description string }{ { description: "nil", expectNoError: false, input: struct { backend *ingress.IngressBackend namespace string }{ backend: nil, namespace: "", }, }, { description: "nil", expectNoError: false, input: struct { backend *ingress.IngressBackend namespace string }{ backend: &ingress.IngressBackend{}, namespace: "", }, }, { description: "create success", expectNoError: true, input: struct { backend *ingress.IngressBackend namespace string }{ backend: &ingress.IngressBackend{ ServiceName: "test", ServicePort: intstr.FromInt(8080), }, namespace: "default", }, }, } for _, testcase := range testcases { _, err := c.createServiceKey(testcase.input.backend, testcase.input.namespace) if err != nil { require.Equal(t, testcase.expectNoError, false) } else { require.Equal(t, testcase.expectNoError, true) } } } func testbackendToRouteDestination(t *testing.T, c *controller) { testcases := []struct { input struct { backend *ingress.IngressBackend namespace string builder *common.IngressRouteBuilder config *annotations.DestinationConfig } expectNoError bool description string }{ { description: "nil", expectNoError: false, input: struct { backend *ingress.IngressBackend namespace string builder *common.IngressRouteBuilder config *annotations.DestinationConfig }{ backend: nil, namespace: "", builder: nil, config: nil, }, }, { description: "nil", expectNoError: false, input: struct { backend *ingress.IngressBackend namespace string builder *common.IngressRouteBuilder config *annotations.DestinationConfig }{ backend: &ingress.IngressBackend{ServiceName: ""}, namespace: "", builder: nil, config: nil, }, }, { description: "create success", expectNoError: true, input: struct { backend *ingress.IngressBackend namespace string builder *common.IngressRouteBuilder config *annotations.DestinationConfig }{ backend: &ingress.IngressBackend{ ServiceName: "test", ServicePort: intstr.FromInt(8080), }, namespace: "default", builder: &common.IngressRouteBuilder{}, config: nil, }, }, } for _, testcase := range testcases { _, err := c.backendToRouteDestination( testcase.input.backend, testcase.input.namespace, testcase.input.builder, testcase.input.config, ) if err == common.InvalidBackendService { require.Equal(t, testcase.expectNoError, false) } else { require.Equal(t, testcase.expectNoError, true) } } } func TestIsCanaryRoute(t *testing.T) { testcases := []struct { input struct { canary *common.WrapperHTTPRoute route *common.WrapperHTTPRoute } expect bool description string }{ { input: struct { canary *common.WrapperHTTPRoute route *common.WrapperHTTPRoute }{ canary: nil, route: nil, }, expect: false, description: "both are nil", }, { input: struct { canary *common.WrapperHTTPRoute route *common.WrapperHTTPRoute }{ canary: &common.WrapperHTTPRoute{ OriginPathType: common.Exact, OriginPath: "/test", }, route: &common.WrapperHTTPRoute{ WrapperConfig: &common.WrapperConfig{ AnnotationsConfig: &annotations.Ingress{ Canary: nil, }, }, OriginPathType: common.Exact, OriginPath: "/test", }, }, expect: true, description: "canary is nil", }, { input: struct { canary *common.WrapperHTTPRoute route *common.WrapperHTTPRoute }{ canary: &common.WrapperHTTPRoute{ OriginPathType: common.Exact, OriginPath: "/test", }, route: &common.WrapperHTTPRoute{ WrapperConfig: &common.WrapperConfig{ AnnotationsConfig: &annotations.Ingress{ Canary: &annotations.CanaryConfig{ Enabled: true, }, }, }, OriginPathType: common.Exact, OriginPath: "/test", }, }, expect: false, description: "canary is not nil", }, } for _, testcase := range testcases { actual := isCanaryRoute(testcase.input.canary, testcase.input.route) require.Equal(t, testcase.expect, actual) } } func TestExtractTLSSecretName(t *testing.T) { testcases := []struct { input struct { host string tls []ingress.IngressTLS } expect string description string }{ { input: struct { host string tls []ingress.IngressTLS }{ host: "", tls: nil, }, expect: "", description: "both are nil", }, { input: struct { host string tls []ingress.IngressTLS }{ host: "test", tls: []ingress.IngressTLS{ { Hosts: []string{"test"}, SecretName: "test-secret", }, { Hosts: []string{"test1"}, SecretName: "test1-secret", }, }, }, expect: "test-secret", description: "found secret name", }, } for _, testcase := range testcases { actual := extractTLSSecretName(testcase.input.host, testcase.input.tls) require.Equal(t, testcase.expect, actual) } } func TestSetDefaultMSEIngressOptionalField(t *testing.T) { pathType := ingress.PathTypeImplementationSpecific testcases := []struct { input struct { ing *ingress.Ingress } expect *ingress.Ingress description string }{ { input: struct{ ing *ingress.Ingress }{ ing: nil, }, expect: nil, description: "nil", }, { input: struct{ ing *ingress.Ingress }{ ing: &ingress.Ingress{}, }, expect: &ingress.Ingress{}, description: "nil", }, { input: struct{ ing *ingress.Ingress }{ ing: &ingress.Ingress{ Spec: ingress.IngressSpec{ TLS: []ingress.IngressTLS{ { SecretName: "test", }, }, }, }, }, expect: &ingress.Ingress{ Spec: ingress.IngressSpec{ TLS: []ingress.IngressTLS{ { SecretName: "test", Hosts: []string{"*"}, }, }, }, }, description: "tls host is empty", }, { input: struct{ ing *ingress.Ingress }{ ing: &ingress.Ingress{ Spec: ingress.IngressSpec{ TLS: []ingress.IngressTLS{ { SecretName: "test", Hosts: []string{"www.example.com"}, }, }, }, }, }, expect: &ingress.Ingress{ Spec: ingress.IngressSpec{ TLS: []ingress.IngressTLS{ { SecretName: "test", Hosts: []string{"www.example.com"}, }, }, }, }, description: "tls host is not empty", }, { input: struct{ ing *ingress.Ingress }{ ing: &ingress.Ingress{ Spec: ingress.IngressSpec{ Rules: []ingress.IngressRule{ { IngressRuleValue: ingress.IngressRuleValue{ HTTP: nil, }, }, }, TLS: []ingress.IngressTLS{ { SecretName: "test", Hosts: []string{"www.example.com"}, }, }, }, }, }, expect: &ingress.Ingress{ Spec: ingress.IngressSpec{ Rules: []ingress.IngressRule{ { IngressRuleValue: ingress.IngressRuleValue{ HTTP: nil, }, }, }, TLS: []ingress.IngressTLS{ { SecretName: "test", Hosts: []string{"www.example.com"}, }, }, }, }, description: "http is nil", }, { input: struct{ ing *ingress.Ingress }{ ing: &ingress.Ingress{ Spec: ingress.IngressSpec{ Rules: []ingress.IngressRule{ { IngressRuleValue: ingress.IngressRuleValue{ HTTP: &ingress.HTTPIngressRuleValue{ Paths: []ingress.HTTPIngressPath{ { Path: "/test", PathType: &defaultPathType, Backend: ingress.IngressBackend{}, }, }, }, }, }, }, TLS: []ingress.IngressTLS{ { SecretName: "test", Hosts: []string{"www.example.com"}, }, }, }, }, }, expect: &ingress.Ingress{ Spec: ingress.IngressSpec{ Rules: []ingress.IngressRule{ { Host: "*", IngressRuleValue: ingress.IngressRuleValue{ HTTP: &ingress.HTTPIngressRuleValue{ Paths: []ingress.HTTPIngressPath{ { Path: "/test", PathType: &defaultPathType, Backend: ingress.IngressBackend{}, }, }, }, }, }, }, TLS: []ingress.IngressTLS{ { SecretName: "test", Hosts: []string{"www.example.com"}, }, }, }, }, description: "http is not nil but host is empty", }, { input: struct{ ing *ingress.Ingress }{ ing: &ingress.Ingress{ Spec: ingress.IngressSpec{ Rules: []ingress.IngressRule{ { IngressRuleValue: ingress.IngressRuleValue{ HTTP: &ingress.HTTPIngressRuleValue{ Paths: []ingress.HTTPIngressPath{ { Path: "/test", PathType: &pathType, Backend: ingress.IngressBackend{}, }, }, }, }, }, }, TLS: []ingress.IngressTLS{ { SecretName: "test", Hosts: []string{"www.example.com"}, }, }, }, }, }, expect: &ingress.Ingress{ Spec: ingress.IngressSpec{ Rules: []ingress.IngressRule{ { Host: "*", IngressRuleValue: ingress.IngressRuleValue{ HTTP: &ingress.HTTPIngressRuleValue{ Paths: []ingress.HTTPIngressPath{ { Path: "/test", PathType: &defaultPathType, Backend: ingress.IngressBackend{}, }, }, }, }, }, }, TLS: []ingress.IngressTLS{ { SecretName: "test", Hosts: []string{"www.example.com"}, }, }, }, }, description: "http path type is ImplementationSpecific", }, } for _, testcase := range testcases { setDefaultMSEIngressOptionalField(testcase.input.ing) require.Equal(t, testcase.expect, testcase.input.ing) } } func TestIngressControllerProcessing(t *testing.T) { fakeClient := kube.NewFakeClient() localKubeClient, _ := fakeClient, fakeClient options := common.Options{IngressClass: "mse", ClusterId: "", EnableStatus: true} secretController := secret.NewController(localKubeClient, options.ClusterId) opts := ktypes.InformerOptions{} ingressInformer := util.GetInformerFiltered(fakeClient, opts, gvrIngressV1Beta1, &ingress.Ingress{}, func(options metav1.ListOptions) (runtime.Object, error) { return fakeClient.Kube().NetworkingV1beta1().Ingresses(opts.Namespace).List(context.Background(), options) }, func(options metav1.ListOptions) (watch.Interface, error) { return fakeClient.Kube().NetworkingV1beta1().Ingresses(opts.Namespace).Watch(context.Background(), options) }) ingressLister := networkinglister.NewIngressLister(ingressInformer.Informer.GetIndexer()) serviceInformer := schemakubeclient.GetInformerFilteredFromGVR(fakeClient, opts, gvr.Service) serviceLister := listerv1.NewServiceLister(serviceInformer.Informer.GetIndexer()) ingressController := &controller{ options: options, ingresses: make(map[string]*ingress.Ingress), ingressInformer: ingressInformer, ingressLister: ingressLister, serviceInformer: serviceInformer, serviceLister: serviceLister, secretController: secretController, } ingressController.queue = controllers.NewQueue("ingress-test", controllers.WithReconciler(ingressController.onEvent), controllers.WithMaxAttempts(5)) _, _ = ingressController.ingressInformer.Informer.AddEventHandler(controllers.ObjectHandler(ingressController.queue.AddObject)) stopChan := make(chan struct{}) t.Cleanup(func() { time.Sleep(3 * time.Second) stopChan <- struct{}{} }) go ingressController.ingressInformer.Start(stopChan) go ingressController.serviceInformer.Start(stopChan) go ingressController.secretController.Informer().Run(stopChan) go ingressController.Run(stopChan) ingressController.RegisterEventHandler(gvk.VirtualService, func(c1, c2 config.Config, e istiomodel.Event) {}) ingressController.RegisterEventHandler(gvk.DestinationRule, func(c1, c2 config.Config, e istiomodel.Event) {}) ingressController.RegisterEventHandler(gvk.EnvoyFilter, func(c1, c2 config.Config, e istiomodel.Event) {}) ingressController.RegisterEventHandler(gvk.Gateway, func(c1, c2 config.Config, e istiomodel.Event) {}) svcObj, err := fakeClient.Kube().CoreV1().Services("default").Create(context.Background(), &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test"}}, metav1.CreateOptions{}) require.NoError(t, err) err = serviceInformer.Informer.GetStore().Add(svcObj) require.NoError(t, err) services, err := serviceLister.List(labels.Everything()) require.NoError(t, err) require.Equal(t, 1, len(services)) ingress1 := &ingress.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: "test-1", }, Spec: v1beta1.IngressSpec{ IngressClassName: &options.IngressClass, Rules: []v1beta1.IngressRule{ { Host: "test.com", IngressRuleValue: v1beta1.IngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{ Paths: []v1beta1.HTTPIngressPath{ { Path: "/test", }, }, }, }, }, }, }, } ingressObj, err := fakeClient.Kube().NetworkingV1beta1().Ingresses("default").Create(context.Background(), ingress1, metav1.CreateOptions{}) require.NoError(t, err) err = ingressController.ingressInformer.Informer.GetStore().Add(ingressObj) require.NoError(t, err) ingresses := ingressController.List() require.Equal(t, 1, len(ingresses)) ingress2 := &ingress.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: "test-2", Namespace: "test-2", }, Spec: v1beta1.IngressSpec{ IngressClassName: &options.IngressClass, Rules: []v1beta1.IngressRule{ { Host: "test.com", IngressRuleValue: v1beta1.IngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{ Paths: []v1beta1.HTTPIngressPath{ { Path: "/test", }, }, }, }, }, }, }, } err = ingressController.ingressInformer.Informer.GetStore().Add(ingress2) require.NoError(t, err) ingresses = ingressController.List() require.Equal(t, 2, len(ingresses)) } func TestShouldProcessIngressUpdate(t *testing.T) { c := controller{ options: common.Options{ IngressClass: "mse", }, ingresses: make(map[string]*v1beta1.Ingress), } ingressClass := "mse" ingress1 := &v1beta1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: "test-1", }, Spec: v1beta1.IngressSpec{ IngressClassName: &ingressClass, Rules: []v1beta1.IngressRule{ { Host: "test.com", IngressRuleValue: v1beta1.IngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{ Paths: []v1beta1.HTTPIngressPath{ { Path: "/test", }, }, }, }, }, }, }, } should, _ := c.shouldProcessIngressUpdate(ingress1) if !should { t.Fatal("should be true") } ingress2 := *ingress1 should, _ = c.shouldProcessIngressUpdate(&ingress2) if should { t.Fatal("should be false") } ingress3 := *ingress1 ingress3.Annotations = map[string]string{ "test": "true", } should, _ = c.shouldProcessIngressUpdate(&ingress3) if !should { t.Fatal("should be true") } } func TestCreateRuleKey(t *testing.T) { sep := "\n\n" wrapperHttpRoute := &common.WrapperHTTPRoute{ Host: "higress.com", OriginPathType: common.Prefix, OriginPath: "/foo", } annots := annotations.Annotations{ buildHigressAnnotationKey(annotations.MatchMethod): "GET PUT", buildHigressAnnotationKey("exact-" + annotations.MatchHeader + "-abc"): "123", buildHigressAnnotationKey("prefix-" + annotations.MatchHeader + "-def"): "456", buildHigressAnnotationKey("exact-" + annotations.MatchPseudoHeader + "-authority"): "foo.bar.com", buildHigressAnnotationKey("prefix-" + annotations.MatchPseudoHeader + "-scheme"): "htt", buildHigressAnnotationKey("exact-" + annotations.MatchQuery + "-region"): "beijing", buildHigressAnnotationKey("prefix-" + annotations.MatchQuery + "-user-id"): "user-", } expect := "higress.com-prefix-/foo" + sep + //host-pathType-path "GET PUT" + sep + // method "exact-:authority\tfoo.bar.com" + "\n" + "exact-abc\t123" + "\n" + "prefix-:scheme\thtt" + "\n" + "prefix-def\t456" + sep + // header "exact-region\tbeijing" + "\n" + "prefix-user-id\tuser-" + sep // params key := createRuleKey(annots, wrapperHttpRoute.PathFormat()) if diff := cmp.Diff(expect, key); diff != "" { t.Errorf("CreateRuleKey() mismatch (-want +got):\n%s", diff) } } func buildHigressAnnotationKey(key string) string { return annotations.HigressAnnotationsPrefix + "/" + key }