From 27120f640b9efd9c6f07213d9f0395d39327647e Mon Sep 17 00:00:00 2001 From: Zachary Seguin Date: Fri, 22 Jan 2021 11:15:26 -0500 Subject: [PATCH] feat(tests): Add initial unit tests --- go.sum | 4 + pkg/controller/handler_test.go | 242 +++++++++++++++++++++++++++++++++ pkg/controller/utils_test.go | 48 +++++++ 3 files changed, 294 insertions(+) create mode 100644 pkg/controller/handler_test.go create mode 100644 pkg/controller/utils_test.go diff --git a/go.sum b/go.sum index c399208..3d82b03 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -182,6 +183,7 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -499,6 +501,8 @@ k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg= k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= +k8s.io/client-go v1.5.1 h1:XaX/lo2/u3/pmFau8HN+sB5C/b4dc4Dmm2eXjBH4p1E= +k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= diff --git a/pkg/controller/handler_test.go b/pkg/controller/handler_test.go new file mode 100644 index 0000000..432e2c0 --- /dev/null +++ b/pkg/controller/handler_test.go @@ -0,0 +1,242 @@ +package controller + +import ( + // "log" + + "reflect" + "testing" + "time" + + istionetworkingv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" + istio "istio.io/client-go/pkg/clientset/versioned" + istiofake "istio.io/client-go/pkg/clientset/versioned/fake" + istioscheme "istio.io/client-go/pkg/clientset/versioned/scheme" + istioinformers "istio.io/client-go/pkg/informers/externalversions" + istionetworkinginformers "istio.io/client-go/pkg/informers/externalversions/networking/v1beta1" + corev1 "k8s.io/api/core/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + kubeinformers "k8s.io/client-go/informers" + corev1informers "k8s.io/client-go/informers/core/v1" + networkinginformers "k8s.io/client-go/informers/networking/v1beta1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" +) + +type system struct { + kubeclient kubernetes.Interface + istioclient istio.Interface + + ingressesInformer networkinginformers.IngressInformer + servicesInformer corev1informers.ServiceInformer + virtualServicesInformer istionetworkinginformers.VirtualServiceInformer + + controller *Controller +} + +func setup(kubeObjects, istioObjects []runtime.Object) *system { + kubeclient := fake.NewSimpleClientset(kubeObjects...) + istioclient := istiofake.NewSimpleClientset(istioObjects...) + + kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeclient, time.Duration(0)) + istioInformerFactory := istioinformers.NewSharedInformerFactory(istioclient, time.Duration(0)) + + ingressesInformer := kubeInformerFactory.Networking().V1beta1().Ingresses() + servicesInformer := kubeInformerFactory.Core().V1().Services() + virtualServicesInformer := istioInformerFactory.Networking().V1beta1().VirtualServices() + + controller := NewController( + kubeclient, nil, + "cluster.local", + "default-gateway", "istio", 100, + ingressesInformer, + servicesInformer, + virtualServicesInformer) + + return &system{ + kubeclient: kubeclient, + istioclient: istioclient, + ingressesInformer: ingressesInformer, + servicesInformer: servicesInformer, + controller: controller, + virtualServicesInformer: virtualServicesInformer, + } +} + +func TestGenerateVirtualServiceBasic(t *testing.T) { + // Setup test + var ingress string = ` +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: test-ing + namespace: test-ns +spec: + rules: + - host: example.ca + http: + paths: + - backend: + serviceName: test-svc + servicePort: 80 +` + + var vs string = ` +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: test-ing + namespace: test-ns +spec: + gateways: + - istio-system/ingress + hosts: + - example.ca + http: + - match: + - authority: + exact: example.ca + route: + - destination: + host: test-svc.test-ns.svc.cluster.local + port: + number: 80 + weight: 100 +` + + // Convert to objects + decode := scheme.Codecs.UniversalDeserializer().Decode + obj, _, err := decode([]byte(ingress), nil, nil) + + if err != nil { + t.Fatalf("failed creating test object: %v", err) + } + + test := obj.(*networkingv1beta1.Ingress) + + decode = istioscheme.Codecs.UniversalDeserializer().Decode + obj, _, err = decode([]byte(vs), nil, nil) + + if err != nil { + t.Fatalf("failed creating expected object: %v", err) + } + + expected := obj.(*istionetworkingv1beta1.VirtualService) + + // Generate VirtualService from Ingress + system := setup(nil, nil) + result, err := system.controller.generateVirtualService(test, []string{"istio-system/ingress"}) + if err != nil { + t.Fatalf("got unexpected error converting ingress: %v", err) + } + + // Compare generated VirtualService against expected + if result.Name != expected.Name { + t.Errorf("got %q for VirtualService.Name, but expected %q", result.Name, expected.Name) + } + + if result.Namespace != expected.Namespace { + t.Errorf("got %q for VirtualService.Namespace, but expected %q", result.Namespace, expected.Namespace) + } + + if !metav1.IsControlledBy(result, test) { + t.Errorf("VirtualService was not owned by the Ingress") + } + + if !reflect.DeepEqual(result.Spec, expected.Spec) { + t.Errorf("got %v but expected %v", result.Spec, expected.Spec) + } +} + +func TestGenerateVirtualServiceServicePortName(t *testing.T) { + // Setup test + var service string = ` +apiVersion: v1 +kind: Service +metadata: + name: test-svc + namespace: test-ns +spec: + ports: + - name: http + port: 80 +` + var ingress string = ` +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: test-ing + namespace: test-ns +spec: + rules: + - host: example.ca + http: + paths: + - backend: + serviceName: test-svc + servicePort: http +` + + var vs string = ` +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: test-ing + namespace: test-ns +spec: + gateways: + - istio-system/ingress + hosts: + - example.ca + http: + - match: + - authority: + exact: example.ca + route: + - destination: + host: test-svc.test-ns.svc.cluster.local + port: + number: 80 + weight: 100 +` + // Convert to objects + decode := scheme.Codecs.UniversalDeserializer().Decode + + obj, _, err := decode([]byte(service), nil, nil) + if err != nil { + t.Fatalf("failed creating service object: %v", err) + } + svc := obj.(*corev1.Service) + + obj, _, err = decode([]byte(ingress), nil, nil) + if err != nil { + t.Fatalf("failed creating test object: %v", err) + } + test := obj.(*networkingv1beta1.Ingress) + + decode = istioscheme.Codecs.UniversalDeserializer().Decode + obj, _, err = decode([]byte(vs), nil, nil) + + if err != nil { + t.Fatalf("failed creating expected object: %v", err) + } + + expected := obj.(*istionetworkingv1beta1.VirtualService) + + // Register service + system := setup([]runtime.Object{svc}, nil) + system.servicesInformer.Informer().GetIndexer().Add(svc) + + // Generate VirtualService from Ingress + result, err := system.controller.generateVirtualService(test, []string{"istio-system/ingress"}) + if err != nil { + t.Fatalf("got unexpected error converting ingress: %v", err) + } + + // Compare generated VirtualService against expected + if !reflect.DeepEqual(result.Spec, expected.Spec) { + t.Errorf("got %v but expected %v", result.Spec, expected.Spec) + } +} diff --git a/pkg/controller/utils_test.go b/pkg/controller/utils_test.go new file mode 100644 index 0000000..d0e9fc4 --- /dev/null +++ b/pkg/controller/utils_test.go @@ -0,0 +1,48 @@ +package controller + +import ( + "testing" +) + +func TestStringInArray(t *testing.T) { + tests := []struct { + str string + arr []string + expected bool + }{ + {"a", []string{"a", "b", "c"}, true}, + {"d", []string{"a", "b", "c"}, false}, + {"alpha", []string{"alpha", "bravo", "charlie"}, true}, + {"alpha", []string{}, false}, + {"", []string{"one", "two", "three", "four", "five"}, false}, + {"", []string{"one", "two", "", "four", "five"}, true}, + } + + for _, test := range tests { + result := stringInArray(test.str, test.arr) + if result != test.expected { + t.Errorf("got %t for %q in %q, expected %t", result, test.str, test.arr, test.expected) + } + } +} + +func TestStringArrayEquals(t *testing.T) { + tests := []struct { + a []string + b []string + expected bool + }{ + {[]string{"a", "b", "c"}, []string{"a", "b", "c"}, true}, + {[]string{"a", "b", "c"}, []string{"a", "c", "b"}, false}, + {[]string{}, []string{}, true}, + {[]string{"a"}, []string{}, false}, + {[]string{}, []string{"a"}, false}, + } + + for _, test := range tests { + result := stringArrayEquals(test.a, test.b) + if result != test.expected { + t.Errorf("got %t for %q equals %q, expected %t", result, test.a, test.b, test.expected) + } + } +}