mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-30 21:30:16 +00:00 
			
		
		
		
	Automatic merge from submit-queue (batch tested with PRs 59740, 59728, 60080, 60086, 58714). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. more concise to merge the slice **What this PR does / why we need it**: more concise to merge the slice **Special notes for your reviewer**:
		
			
				
	
	
		
			797 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			797 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2015 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 service
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"testing"
 | |
| 
 | |
| 	"k8s.io/api/core/v1"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/apimachinery/pkg/types"
 | |
| 	"k8s.io/client-go/informers"
 | |
| 	"k8s.io/client-go/kubernetes/fake"
 | |
| 	"k8s.io/client-go/tools/record"
 | |
| 	"k8s.io/kubernetes/pkg/api/testapi"
 | |
| 	fakecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake"
 | |
| 	"k8s.io/kubernetes/pkg/controller"
 | |
| )
 | |
| 
 | |
| const region = "us-central"
 | |
| 
 | |
| func newService(name string, uid types.UID, serviceType v1.ServiceType) *v1.Service {
 | |
| 	return &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "default", UID: uid, SelfLink: testapi.Default.SelfLink("services", name)}, Spec: v1.ServiceSpec{Type: serviceType}}
 | |
| }
 | |
| 
 | |
| //Wrap newService so that you dont have to call default argumetns again and again.
 | |
| func defaultExternalService() *v1.Service {
 | |
| 
 | |
| 	return newService("external-balancer", types.UID("123"), v1.ServiceTypeLoadBalancer)
 | |
| 
 | |
| }
 | |
| 
 | |
| func alwaysReady() bool { return true }
 | |
| 
 | |
| func newController() (*ServiceController, *fakecloud.FakeCloud, *fake.Clientset) {
 | |
| 	cloud := &fakecloud.FakeCloud{}
 | |
| 	cloud.Region = region
 | |
| 
 | |
| 	client := fake.NewSimpleClientset()
 | |
| 
 | |
| 	informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
 | |
| 	serviceInformer := informerFactory.Core().V1().Services()
 | |
| 	nodeInformer := informerFactory.Core().V1().Nodes()
 | |
| 
 | |
| 	controller, _ := New(cloud, client, serviceInformer, nodeInformer, "test-cluster")
 | |
| 	controller.nodeListerSynced = alwaysReady
 | |
| 	controller.serviceListerSynced = alwaysReady
 | |
| 	controller.eventRecorder = record.NewFakeRecorder(100)
 | |
| 
 | |
| 	controller.init()
 | |
| 	cloud.Calls = nil     // ignore any cloud calls made in init()
 | |
| 	client.ClearActions() // ignore any client calls made in init()
 | |
| 
 | |
| 	return controller, cloud, client
 | |
| }
 | |
| 
 | |
| func TestCreateExternalLoadBalancer(t *testing.T) {
 | |
| 	table := []struct {
 | |
| 		service             *v1.Service
 | |
| 		expectErr           bool
 | |
| 		expectCreateAttempt bool
 | |
| 	}{
 | |
| 		{
 | |
| 			service: &v1.Service{
 | |
| 				ObjectMeta: metav1.ObjectMeta{
 | |
| 					Name:      "no-external-balancer",
 | |
| 					Namespace: "default",
 | |
| 				},
 | |
| 				Spec: v1.ServiceSpec{
 | |
| 					Type: v1.ServiceTypeClusterIP,
 | |
| 				},
 | |
| 			},
 | |
| 			expectErr:           false,
 | |
| 			expectCreateAttempt: false,
 | |
| 		},
 | |
| 		{
 | |
| 			service: &v1.Service{
 | |
| 				ObjectMeta: metav1.ObjectMeta{
 | |
| 					Name:      "udp-service",
 | |
| 					Namespace: "default",
 | |
| 					SelfLink:  testapi.Default.SelfLink("services", "udp-service"),
 | |
| 				},
 | |
| 				Spec: v1.ServiceSpec{
 | |
| 					Ports: []v1.ServicePort{{
 | |
| 						Port:     80,
 | |
| 						Protocol: v1.ProtocolUDP,
 | |
| 					}},
 | |
| 					Type: v1.ServiceTypeLoadBalancer,
 | |
| 				},
 | |
| 			},
 | |
| 			expectErr:           false,
 | |
| 			expectCreateAttempt: true,
 | |
| 		},
 | |
| 		{
 | |
| 			service: &v1.Service{
 | |
| 				ObjectMeta: metav1.ObjectMeta{
 | |
| 					Name:      "basic-service1",
 | |
| 					Namespace: "default",
 | |
| 					SelfLink:  testapi.Default.SelfLink("services", "basic-service1"),
 | |
| 				},
 | |
| 				Spec: v1.ServiceSpec{
 | |
| 					Ports: []v1.ServicePort{{
 | |
| 						Port:     80,
 | |
| 						Protocol: v1.ProtocolTCP,
 | |
| 					}},
 | |
| 					Type: v1.ServiceTypeLoadBalancer,
 | |
| 				},
 | |
| 			},
 | |
| 			expectErr:           false,
 | |
| 			expectCreateAttempt: true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, item := range table {
 | |
| 		controller, cloud, client := newController()
 | |
| 		err := controller.createLoadBalancerIfNeeded("foo/bar", item.service)
 | |
| 		if !item.expectErr && err != nil {
 | |
| 			t.Errorf("unexpected error: %v", err)
 | |
| 		} else if item.expectErr && err == nil {
 | |
| 			t.Errorf("expected error creating %v, got nil", item.service)
 | |
| 		}
 | |
| 		actions := client.Actions()
 | |
| 		if !item.expectCreateAttempt {
 | |
| 			if len(cloud.Calls) > 0 {
 | |
| 				t.Errorf("unexpected cloud provider calls: %v", cloud.Calls)
 | |
| 			}
 | |
| 			if len(actions) > 0 {
 | |
| 				t.Errorf("unexpected client actions: %v", actions)
 | |
| 			}
 | |
| 		} else {
 | |
| 			var balancer *fakecloud.FakeBalancer
 | |
| 			for k := range cloud.Balancers {
 | |
| 				if balancer == nil {
 | |
| 					b := cloud.Balancers[k]
 | |
| 					balancer = &b
 | |
| 				} else {
 | |
| 					t.Errorf("expected one load balancer to be created, got %v", cloud.Balancers)
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 			if balancer == nil {
 | |
| 				t.Errorf("expected one load balancer to be created, got none")
 | |
| 			} else if balancer.Name != controller.loadBalancerName(item.service) ||
 | |
| 				balancer.Region != region ||
 | |
| 				balancer.Ports[0].Port != item.service.Spec.Ports[0].Port {
 | |
| 				t.Errorf("created load balancer has incorrect parameters: %v", balancer)
 | |
| 			}
 | |
| 			actionFound := false
 | |
| 			for _, action := range actions {
 | |
| 				if action.GetVerb() == "update" && action.GetResource().Resource == "services" {
 | |
| 					actionFound = true
 | |
| 				}
 | |
| 			}
 | |
| 			if !actionFound {
 | |
| 				t.Errorf("expected updated service to be sent to client, got these actions instead: %v", actions)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TODO: Finish converting and update comments
 | |
| func TestUpdateNodesInExternalLoadBalancer(t *testing.T) {
 | |
| 	nodes := []*v1.Node{
 | |
| 		{ObjectMeta: metav1.ObjectMeta{Name: "node0"}},
 | |
| 		{ObjectMeta: metav1.ObjectMeta{Name: "node1"}},
 | |
| 		{ObjectMeta: metav1.ObjectMeta{Name: "node73"}},
 | |
| 	}
 | |
| 	table := []struct {
 | |
| 		services            []*v1.Service
 | |
| 		expectedUpdateCalls []fakecloud.FakeUpdateBalancerCall
 | |
| 	}{
 | |
| 		{
 | |
| 			// No services present: no calls should be made.
 | |
| 			services:            []*v1.Service{},
 | |
| 			expectedUpdateCalls: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			// Services do not have external load balancers: no calls should be made.
 | |
| 			services: []*v1.Service{
 | |
| 				newService("s0", "111", v1.ServiceTypeClusterIP),
 | |
| 				newService("s1", "222", v1.ServiceTypeNodePort),
 | |
| 			},
 | |
| 			expectedUpdateCalls: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			// Services does have an external load balancer: one call should be made.
 | |
| 			services: []*v1.Service{
 | |
| 				newService("s0", "333", v1.ServiceTypeLoadBalancer),
 | |
| 			},
 | |
| 			expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{
 | |
| 				{Service: newService("s0", "333", v1.ServiceTypeLoadBalancer), Hosts: nodes},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			// Three services have an external load balancer: three calls.
 | |
| 			services: []*v1.Service{
 | |
| 				newService("s0", "444", v1.ServiceTypeLoadBalancer),
 | |
| 				newService("s1", "555", v1.ServiceTypeLoadBalancer),
 | |
| 				newService("s2", "666", v1.ServiceTypeLoadBalancer),
 | |
| 			},
 | |
| 			expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{
 | |
| 				{Service: newService("s0", "444", v1.ServiceTypeLoadBalancer), Hosts: nodes},
 | |
| 				{Service: newService("s1", "555", v1.ServiceTypeLoadBalancer), Hosts: nodes},
 | |
| 				{Service: newService("s2", "666", v1.ServiceTypeLoadBalancer), Hosts: nodes},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			// Two services have an external load balancer and two don't: two calls.
 | |
| 			services: []*v1.Service{
 | |
| 				newService("s0", "777", v1.ServiceTypeNodePort),
 | |
| 				newService("s1", "888", v1.ServiceTypeLoadBalancer),
 | |
| 				newService("s3", "999", v1.ServiceTypeLoadBalancer),
 | |
| 				newService("s4", "123", v1.ServiceTypeClusterIP),
 | |
| 			},
 | |
| 			expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{
 | |
| 				{Service: newService("s1", "888", v1.ServiceTypeLoadBalancer), Hosts: nodes},
 | |
| 				{Service: newService("s3", "999", v1.ServiceTypeLoadBalancer), Hosts: nodes},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			// One service has an external load balancer and one is nil: one call.
 | |
| 			services: []*v1.Service{
 | |
| 				newService("s0", "234", v1.ServiceTypeLoadBalancer),
 | |
| 				nil,
 | |
| 			},
 | |
| 			expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{
 | |
| 				{Service: newService("s0", "234", v1.ServiceTypeLoadBalancer), Hosts: nodes},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 	for _, item := range table {
 | |
| 		controller, cloud, _ := newController()
 | |
| 
 | |
| 		var services []*v1.Service
 | |
| 		services = append(services, item.services...)
 | |
| 
 | |
| 		if err := controller.updateLoadBalancerHosts(services, nodes); err != nil {
 | |
| 			t.Errorf("unexpected error: %v", err)
 | |
| 		}
 | |
| 		if !reflect.DeepEqual(item.expectedUpdateCalls, cloud.UpdateCalls) {
 | |
| 			t.Errorf("expected update calls mismatch, expected %+v, got %+v", item.expectedUpdateCalls, cloud.UpdateCalls)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestGetNodeConditionPredicate(t *testing.T) {
 | |
| 	tests := []struct {
 | |
| 		node         v1.Node
 | |
| 		expectAccept bool
 | |
| 		name         string
 | |
| 	}{
 | |
| 		{
 | |
| 			node:         v1.Node{},
 | |
| 			expectAccept: false,
 | |
| 			name:         "empty",
 | |
| 		},
 | |
| 		{
 | |
| 			node: v1.Node{
 | |
| 				Status: v1.NodeStatus{
 | |
| 					Conditions: []v1.NodeCondition{
 | |
| 						{Type: v1.NodeReady, Status: v1.ConditionTrue},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			expectAccept: true,
 | |
| 			name:         "basic",
 | |
| 		},
 | |
| 		{
 | |
| 			node: v1.Node{
 | |
| 				Spec: v1.NodeSpec{Unschedulable: true},
 | |
| 				Status: v1.NodeStatus{
 | |
| 					Conditions: []v1.NodeCondition{
 | |
| 						{Type: v1.NodeReady, Status: v1.ConditionTrue},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 			expectAccept: false,
 | |
| 			name:         "unschedulable",
 | |
| 		},
 | |
| 	}
 | |
| 	pred := getNodeConditionPredicate()
 | |
| 	for _, test := range tests {
 | |
| 		accept := pred(&test.node)
 | |
| 		if accept != test.expectAccept {
 | |
| 			t.Errorf("Test failed for %s, expected %v, saw %v", test.name, test.expectAccept, accept)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TODO(a-robinson): Add tests for update/sync/delete.
 | |
| 
 | |
| func TestProcessServiceUpdate(t *testing.T) {
 | |
| 
 | |
| 	var controller *ServiceController
 | |
| 
 | |
| 	//A pair of old and new loadbalancer IP address
 | |
| 	oldLBIP := "192.168.1.1"
 | |
| 	newLBIP := "192.168.1.11"
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		testName   string
 | |
| 		key        string
 | |
| 		updateFn   func(*v1.Service) *v1.Service //Manipulate the structure
 | |
| 		svc        *v1.Service
 | |
| 		expectedFn func(*v1.Service, error) error //Error comparison function
 | |
| 	}{
 | |
| 		{
 | |
| 			testName: "If updating a valid service",
 | |
| 			key:      "validKey",
 | |
| 			svc:      defaultExternalService(),
 | |
| 			updateFn: func(svc *v1.Service) *v1.Service {
 | |
| 
 | |
| 				controller, _, _ = newController()
 | |
| 				controller.cache.getOrCreate("validKey")
 | |
| 				return svc
 | |
| 
 | |
| 			},
 | |
| 			expectedFn: func(svc *v1.Service, err error) error {
 | |
| 				return err
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "If Updating Loadbalancer IP",
 | |
| 			key:      "default/sync-test-name",
 | |
| 			svc:      newService("sync-test-name", types.UID("sync-test-uid"), v1.ServiceTypeLoadBalancer),
 | |
| 			updateFn: func(svc *v1.Service) *v1.Service {
 | |
| 
 | |
| 				svc.Spec.LoadBalancerIP = oldLBIP
 | |
| 
 | |
| 				keyExpected := svc.GetObjectMeta().GetNamespace() + "/" + svc.GetObjectMeta().GetName()
 | |
| 				controller.enqueueService(svc)
 | |
| 				cachedServiceTest := controller.cache.getOrCreate(keyExpected)
 | |
| 				cachedServiceTest.state = svc
 | |
| 				controller.cache.set(keyExpected, cachedServiceTest)
 | |
| 
 | |
| 				keyGot, quit := controller.queue.Get()
 | |
| 				if quit {
 | |
| 					t.Fatalf("get no queue element")
 | |
| 				}
 | |
| 				if keyExpected != keyGot.(string) {
 | |
| 					t.Fatalf("get service key error, expected: %s, got: %s", keyExpected, keyGot.(string))
 | |
| 				}
 | |
| 
 | |
| 				newService := svc.DeepCopy()
 | |
| 
 | |
| 				newService.Spec.LoadBalancerIP = newLBIP
 | |
| 				return newService
 | |
| 
 | |
| 			},
 | |
| 			expectedFn: func(svc *v1.Service, err error) error {
 | |
| 
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 
 | |
| 				keyExpected := svc.GetObjectMeta().GetNamespace() + "/" + svc.GetObjectMeta().GetName()
 | |
| 
 | |
| 				cachedServiceGot, exist := controller.cache.get(keyExpected)
 | |
| 				if !exist {
 | |
| 					return fmt.Errorf("update service error, queue should contain service: %s", keyExpected)
 | |
| 				}
 | |
| 				if cachedServiceGot.state.Spec.LoadBalancerIP != newLBIP {
 | |
| 					return fmt.Errorf("update LoadBalancerIP error, expected: %s, got: %s", newLBIP, cachedServiceGot.state.Spec.LoadBalancerIP)
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		newSvc := tc.updateFn(tc.svc)
 | |
| 		svcCache := controller.cache.getOrCreate(tc.key)
 | |
| 		obtErr := controller.processServiceUpdate(svcCache, newSvc, tc.key)
 | |
| 		if err := tc.expectedFn(newSvc, obtErr); err != nil {
 | |
| 			t.Errorf("%v processServiceUpdate() %v", tc.testName, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| func TestSyncService(t *testing.T) {
 | |
| 
 | |
| 	var controller *ServiceController
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		testName   string
 | |
| 		key        string
 | |
| 		updateFn   func()            //Function to manipulate the controller element to simulate error
 | |
| 		expectedFn func(error) error //Expected function if returns nil then test passed, failed otherwise
 | |
| 	}{
 | |
| 		{
 | |
| 			testName: "if an invalid service name is synced",
 | |
| 			key:      "invalid/key/string",
 | |
| 			updateFn: func() {
 | |
| 				controller, _, _ = newController()
 | |
| 
 | |
| 			},
 | |
| 			expectedFn: func(e error) error {
 | |
| 				//TODO: Expected error is of the format fmt.Errorf("unexpected key format: %q", "invalid/key/string"),
 | |
| 				//TODO: should find a way to test for dependent package errors in such a way that it wont break
 | |
| 				//TODO:	our tests, currently we only test if there is an error.
 | |
| 				//Error should be non-nil
 | |
| 				if e == nil {
 | |
| 					return fmt.Errorf("Expected=unexpected key format: %q, Obtained=nil", "invalid/key/string")
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 		/* We cannot open this test case as syncService(key) currently runtime.HandleError(err) and suppresses frequently occurring errors
 | |
| 		{
 | |
| 			testName: "if an invalid service is synced",
 | |
| 			key: "somethingelse",
 | |
| 			updateFn: func() {
 | |
| 				controller, _, _ = newController()
 | |
| 				srv := controller.cache.getOrCreate("external-balancer")
 | |
| 				srv.state = defaultExternalService()
 | |
| 			},
 | |
| 			expectedErr: fmt.Errorf("Service somethingelse not in cache even though the watcher thought it was. Ignoring the deletion."),
 | |
| 		},
 | |
| 		*/
 | |
| 
 | |
| 		//TODO: see if we can add a test for valid but error throwing service, its difficult right now because synCService() currently runtime.HandleError
 | |
| 		{
 | |
| 			testName: "if valid service",
 | |
| 			key:      "external-balancer",
 | |
| 			updateFn: func() {
 | |
| 				testSvc := defaultExternalService()
 | |
| 				controller, _, _ = newController()
 | |
| 				controller.enqueueService(testSvc)
 | |
| 				svc := controller.cache.getOrCreate("external-balancer")
 | |
| 				svc.state = testSvc
 | |
| 			},
 | |
| 			expectedFn: func(e error) error {
 | |
| 				//error should be nil
 | |
| 				if e != nil {
 | |
| 					return fmt.Errorf("Expected=nil, Obtained=%v", e)
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 
 | |
| 		tc.updateFn()
 | |
| 		obtainedErr := controller.syncService(tc.key)
 | |
| 
 | |
| 		//expected matches obtained ??.
 | |
| 		if exp := tc.expectedFn(obtainedErr); exp != nil {
 | |
| 			t.Errorf("%v Error:%v", tc.testName, exp)
 | |
| 		}
 | |
| 
 | |
| 		//Post processing, the element should not be in the sync queue.
 | |
| 		_, exist := controller.cache.get(tc.key)
 | |
| 		if exist {
 | |
| 			t.Fatalf("%v working Queue should be empty, but contains %s", tc.testName, tc.key)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestProcessServiceDeletion(t *testing.T) {
 | |
| 
 | |
| 	var controller *ServiceController
 | |
| 	var cloud *fakecloud.FakeCloud
 | |
| 	// Add a global svcKey name
 | |
| 	svcKey := "external-balancer"
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		testName   string
 | |
| 		updateFn   func(*ServiceController) // Update function used to manupulate srv and controller values
 | |
| 		expectedFn func(svcErr error) error // Function to check if the returned value is expected
 | |
| 	}{
 | |
| 		{
 | |
| 			testName: "If an non-existent service is deleted",
 | |
| 			updateFn: func(controller *ServiceController) {
 | |
| 				// Does not do anything
 | |
| 			},
 | |
| 			expectedFn: func(svcErr error) error {
 | |
| 				return svcErr
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "If cloudprovided failed to delete the service",
 | |
| 			updateFn: func(controller *ServiceController) {
 | |
| 
 | |
| 				svc := controller.cache.getOrCreate(svcKey)
 | |
| 				svc.state = defaultExternalService()
 | |
| 				cloud.Err = fmt.Errorf("Error Deleting the Loadbalancer")
 | |
| 
 | |
| 			},
 | |
| 			expectedFn: func(svcErr error) error {
 | |
| 
 | |
| 				expectedError := "Error Deleting the Loadbalancer"
 | |
| 
 | |
| 				if svcErr == nil || svcErr.Error() != expectedError {
 | |
| 					return fmt.Errorf("Expected=%v Obtained=%v", expectedError, svcErr)
 | |
| 				}
 | |
| 
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "If delete was successful",
 | |
| 			updateFn: func(controller *ServiceController) {
 | |
| 
 | |
| 				testSvc := defaultExternalService()
 | |
| 				controller.enqueueService(testSvc)
 | |
| 				svc := controller.cache.getOrCreate(svcKey)
 | |
| 				svc.state = testSvc
 | |
| 				controller.cache.set(svcKey, svc)
 | |
| 
 | |
| 			},
 | |
| 			expectedFn: func(svcErr error) error {
 | |
| 				if svcErr != nil {
 | |
| 					return fmt.Errorf("Expected=nil Obtained=%v", svcErr)
 | |
| 				}
 | |
| 
 | |
| 				// It should no longer be in the workqueue.
 | |
| 				_, exist := controller.cache.get(svcKey)
 | |
| 				if exist {
 | |
| 					return fmt.Errorf("delete service error, queue should not contain service: %s any more", svcKey)
 | |
| 				}
 | |
| 
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		//Create a new controller.
 | |
| 		controller, cloud, _ = newController()
 | |
| 		tc.updateFn(controller)
 | |
| 		obtainedErr := controller.processServiceDeletion(svcKey)
 | |
| 		if err := tc.expectedFn(obtainedErr); err != nil {
 | |
| 			t.Errorf("%v processServiceDeletion() %v", tc.testName, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| func TestDoesExternalLoadBalancerNeedsUpdate(t *testing.T) {
 | |
| 
 | |
| 	var oldSvc, newSvc *v1.Service
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		testName            string //Name of the test case
 | |
| 		updateFn            func() //Function to update the service object
 | |
| 		expectedNeedsUpdate bool   //needsupdate always returns bool
 | |
| 
 | |
| 	}{
 | |
| 		{
 | |
| 			testName: "If the service type is changed from LoadBalancer to ClusterIP",
 | |
| 			updateFn: func() {
 | |
| 				oldSvc = defaultExternalService()
 | |
| 				newSvc = defaultExternalService()
 | |
| 				newSvc.Spec.Type = v1.ServiceTypeClusterIP
 | |
| 			},
 | |
| 			expectedNeedsUpdate: true,
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "If the Ports are different",
 | |
| 			updateFn: func() {
 | |
| 				oldSvc = defaultExternalService()
 | |
| 				newSvc = defaultExternalService()
 | |
| 				oldSvc.Spec.Ports = []v1.ServicePort{
 | |
| 					{
 | |
| 						Port: 8000,
 | |
| 					},
 | |
| 					{
 | |
| 						Port: 9000,
 | |
| 					},
 | |
| 					{
 | |
| 						Port: 10000,
 | |
| 					},
 | |
| 				}
 | |
| 				newSvc.Spec.Ports = []v1.ServicePort{
 | |
| 					{
 | |
| 						Port: 8001,
 | |
| 					},
 | |
| 					{
 | |
| 						Port: 9001,
 | |
| 					},
 | |
| 					{
 | |
| 						Port: 10001,
 | |
| 					},
 | |
| 				}
 | |
| 
 | |
| 			},
 | |
| 			expectedNeedsUpdate: true,
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "If externel ip counts are different",
 | |
| 			updateFn: func() {
 | |
| 				oldSvc = defaultExternalService()
 | |
| 				newSvc = defaultExternalService()
 | |
| 				oldSvc.Spec.ExternalIPs = []string{"old.IP.1"}
 | |
| 				newSvc.Spec.ExternalIPs = []string{"new.IP.1", "new.IP.2"}
 | |
| 			},
 | |
| 			expectedNeedsUpdate: true,
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "If externel ips are different",
 | |
| 			updateFn: func() {
 | |
| 				oldSvc = defaultExternalService()
 | |
| 				newSvc = defaultExternalService()
 | |
| 				oldSvc.Spec.ExternalIPs = []string{"old.IP.1", "old.IP.2"}
 | |
| 				newSvc.Spec.ExternalIPs = []string{"new.IP.1", "new.IP.2"}
 | |
| 			},
 | |
| 			expectedNeedsUpdate: true,
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "If UID is different",
 | |
| 			updateFn: func() {
 | |
| 				oldSvc = defaultExternalService()
 | |
| 				newSvc = defaultExternalService()
 | |
| 				oldSvc.UID = types.UID("UID old")
 | |
| 				newSvc.UID = types.UID("UID new")
 | |
| 			},
 | |
| 			expectedNeedsUpdate: true,
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "If ExternalTrafficPolicy is different",
 | |
| 			updateFn: func() {
 | |
| 				oldSvc = defaultExternalService()
 | |
| 				newSvc = defaultExternalService()
 | |
| 				newSvc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal
 | |
| 			},
 | |
| 			expectedNeedsUpdate: true,
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "If HealthCheckNodePort is different",
 | |
| 			updateFn: func() {
 | |
| 				oldSvc = defaultExternalService()
 | |
| 				newSvc = defaultExternalService()
 | |
| 				newSvc.Spec.HealthCheckNodePort = 30123
 | |
| 			},
 | |
| 			expectedNeedsUpdate: true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	controller, _, _ := newController()
 | |
| 	for _, tc := range testCases {
 | |
| 		tc.updateFn()
 | |
| 		obtainedResult := controller.needsUpdate(oldSvc, newSvc)
 | |
| 		if obtainedResult != tc.expectedNeedsUpdate {
 | |
| 			t.Errorf("%v needsUpdate() should have returned %v but returned %v", tc.testName, tc.expectedNeedsUpdate, obtainedResult)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| //All the testcases for ServiceCache uses a single cache, these below test cases should be run in order,
 | |
| //as tc1 (addCache would add elements to the cache)
 | |
| //and tc2 (delCache would remove element from the cache without it adding automatically)
 | |
| //Please keep this in mind while adding new test cases.
 | |
| func TestServiceCache(t *testing.T) {
 | |
| 
 | |
| 	//ServiceCache a common service cache for all the test cases
 | |
| 	sc := &serviceCache{serviceMap: make(map[string]*cachedService)}
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		testName     string
 | |
| 		setCacheFn   func()
 | |
| 		checkCacheFn func() error
 | |
| 	}{
 | |
| 		{
 | |
| 			testName: "Add",
 | |
| 			setCacheFn: func() {
 | |
| 				cS := sc.getOrCreate("addTest")
 | |
| 				cS.state = defaultExternalService()
 | |
| 			},
 | |
| 			checkCacheFn: func() error {
 | |
| 				//There must be exactly one element
 | |
| 				if len(sc.serviceMap) != 1 {
 | |
| 					return fmt.Errorf("Expected=1 Obtained=%d", len(sc.serviceMap))
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "Del",
 | |
| 			setCacheFn: func() {
 | |
| 				sc.delete("addTest")
 | |
| 
 | |
| 			},
 | |
| 			checkCacheFn: func() error {
 | |
| 				//Now it should have no element
 | |
| 				if len(sc.serviceMap) != 0 {
 | |
| 					return fmt.Errorf("Expected=0 Obtained=%d", len(sc.serviceMap))
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "Set and Get",
 | |
| 			setCacheFn: func() {
 | |
| 				sc.set("addTest", &cachedService{state: defaultExternalService()})
 | |
| 			},
 | |
| 			checkCacheFn: func() error {
 | |
| 				//Now it should have one element
 | |
| 				Cs, bool := sc.get("addTest")
 | |
| 				if !bool {
 | |
| 					return fmt.Errorf("is Available Expected=true Obtained=%v", bool)
 | |
| 				}
 | |
| 				if Cs == nil {
 | |
| 					return fmt.Errorf("CachedService expected:non-nil Obtained=nil")
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			testName: "ListKeys",
 | |
| 			setCacheFn: func() {
 | |
| 				//Add one more entry here
 | |
| 				sc.set("addTest1", &cachedService{state: defaultExternalService()})
 | |
| 			},
 | |
| 			checkCacheFn: func() error {
 | |
| 				//It should have two elements
 | |
| 				keys := sc.ListKeys()
 | |
| 				if len(keys) != 2 {
 | |
| 					return fmt.Errorf("Elementes Expected=2 Obtained=%v", len(keys))
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			testName:   "GetbyKeys",
 | |
| 			setCacheFn: nil, //Nothing to set
 | |
| 			checkCacheFn: func() error {
 | |
| 				//It should have two elements
 | |
| 				svc, isKey, err := sc.GetByKey("addTest")
 | |
| 				if svc == nil || isKey == false || err != nil {
 | |
| 					return fmt.Errorf("Expected(non-nil, true, nil) Obtained(%v,%v,%v)", svc, isKey, err)
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			testName:   "allServices",
 | |
| 			setCacheFn: nil, //Nothing to set
 | |
| 			checkCacheFn: func() error {
 | |
| 				//It should return two elements
 | |
| 				svcArray := sc.allServices()
 | |
| 				if len(svcArray) != 2 {
 | |
| 					return fmt.Errorf("Expected(2) Obtained(%v)", len(svcArray))
 | |
| 				}
 | |
| 				return nil
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		if tc.setCacheFn != nil {
 | |
| 			tc.setCacheFn()
 | |
| 		}
 | |
| 		if err := tc.checkCacheFn(); err != nil {
 | |
| 			t.Errorf("%v returned %v", tc.testName, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| //Test a utility functions as its not easy to unit test nodeSyncLoop directly
 | |
| func TestNodeSlicesEqualForLB(t *testing.T) {
 | |
| 	numNodes := 10
 | |
| 	nArray := make([]*v1.Node, numNodes)
 | |
| 	mArray := make([]*v1.Node, numNodes)
 | |
| 	for i := 0; i < numNodes; i++ {
 | |
| 		nArray[i] = &v1.Node{}
 | |
| 		nArray[i].Name = fmt.Sprintf("node%d", i)
 | |
| 	}
 | |
| 	for i := 0; i < numNodes; i++ {
 | |
| 		mArray[i] = &v1.Node{}
 | |
| 		mArray[i].Name = fmt.Sprintf("node%d", i+1)
 | |
| 	}
 | |
| 
 | |
| 	if !nodeSlicesEqualForLB(nArray, nArray) {
 | |
| 		t.Errorf("nodeSlicesEqualForLB() Expected=true Obtained=false")
 | |
| 	}
 | |
| 	if nodeSlicesEqualForLB(nArray, mArray) {
 | |
| 		t.Errorf("nodeSlicesEqualForLB() Expected=false Obtained=true")
 | |
| 	}
 | |
| }
 |