func TestEvictNode(t *testing.T) { evictor := NewPodEvictor(util.NewFakeRateLimiter()) evictor.AddNodeToEvict("first") evictor.AddNodeToEvict("second") evictor.AddNodeToEvict("third") evictor.RemoveNodeToEvict("second") deletedMap := util.NewStringSet() evictor.TryEvict(func(nodeName string) { deletedMap.Insert(nodeName) }) setPattern := util.NewStringSet("first", "third") if len(deletedMap) != len(setPattern) { t.Fatalf("Map %v should have length %d", evictor.queue.set, len(setPattern)) } if !CheckSetEq(setPattern, deletedMap) { t.Errorf("Invalid map. Got %v, expected %v", deletedMap, setPattern) } }
func TestAddNode(t *testing.T) { evictor := NewPodEvictor(util.NewFakeRateLimiter()) evictor.AddNodeToEvict("first") evictor.AddNodeToEvict("second") evictor.AddNodeToEvict("third") queuePattern := []string{"first", "second", "third"} if len(evictor.queue.queue) != len(queuePattern) { t.Fatalf("Queue %v should have lenght %d", evictor.queue.queue, len(queuePattern)) } if !CheckQueueEq(queuePattern, evictor.queue.queue) { t.Errorf("Invalid queue. Got %v, expected %v", evictor.queue.queue, queuePattern) } setPattern := util.NewStringSet("first", "second", "third") if len(evictor.queue.set) != len(setPattern) { t.Fatalf("Map %v should have length %d", evictor.queue.set, len(setPattern)) } if !CheckSetEq(setPattern, evictor.queue.set) { t.Errorf("Invalid map. Got %v, expected %v", evictor.queue.set, setPattern) } }
func TestPopulateNodeAddresses(t *testing.T) { table := []struct { nodes *api.NodeList fakeCloud *fake_cloud.FakeCloud expectedFail bool expectedAddresses []api.NodeAddress }{ { nodes: &api.NodeList{Items: []api.Node{*newNode("node0"), *newNode("node1")}}, fakeCloud: &fake_cloud.FakeCloud{Addresses: []api.NodeAddress{{Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}}}, expectedAddresses: []api.NodeAddress{ {Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}, }, }, { nodes: &api.NodeList{Items: []api.Node{*newNode("node0"), *newNode("node1")}}, fakeCloud: &fake_cloud.FakeCloud{Err: ErrQueryIPAddress}, expectedAddresses: nil, }, } for _, item := range table { nodeController := NewNodeController(item.fakeCloud, ".*", nil, nil, nil, 10, time.Minute, util.NewFakeRateLimiter(), testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, "") result, err := nodeController.populateAddresses(item.nodes) // In case of IP querying error, we should continue. if err != nil { t.Errorf("unexpected error: %v", err) } for _, node := range result.Items { if !reflect.DeepEqual(item.expectedAddresses, node.Status.Addresses) { t.Errorf("expect HostIP %s, got %s", item.expectedAddresses, node.Status.Addresses) } } } }
func startComponents(firstManifestURL, secondManifestURL, apiVersion string) (string, string) { // Setup servers := []string{} glog.Infof("Creating etcd client pointing to %v", servers) handler := delegateHandler{} apiServer := httptest.NewServer(&handler) etcdClient := etcd.NewClient(servers) sleep := 4 * time.Second ok := false for i := 0; i < 3; i++ { keys, err := etcdClient.Get("/", false, false) if err != nil { glog.Warningf("Unable to list root etcd keys: %v", err) if i < 2 { time.Sleep(sleep) sleep = sleep * sleep } continue } for _, node := range keys.Node.Nodes { if _, err := etcdClient.Delete(node.Key, true); err != nil { glog.Fatalf("Unable delete key: %v", err) } } ok = true break } if !ok { glog.Fatalf("Failed to connect to etcd") } cl := client.NewOrDie(&client.Config{Host: apiServer.URL, Version: apiVersion}) etcdStorage, err := master.NewEtcdStorage(etcdClient, latest.InterfacesFor, latest.Version, etcdtest.PathPrefix()) if err != nil { glog.Fatalf("Unable to get etcd storage: %v", err) } expEtcdStorage, err := master.NewEtcdStorage(etcdClient, explatest.InterfacesFor, explatest.Version, etcdtest.PathPrefix()) if err != nil { glog.Fatalf("Unable to get etcd storage for experimental: %v", err) } // Master host, port, err := net.SplitHostPort(strings.TrimLeft(apiServer.URL, "http://")) if err != nil { glog.Fatalf("Unable to parse URL '%v': %v", apiServer.URL, err) } portNumber, err := strconv.Atoi(port) if err != nil { glog.Fatalf("Nonnumeric port? %v", err) } publicAddress := net.ParseIP(host) if publicAddress == nil { glog.Fatalf("no public address for %s", host) } // Create a master and install handlers into mux. m := master.New(&master.Config{ DatabaseStorage: etcdStorage, ExpDatabaseStorage: expEtcdStorage, KubeletClient: fakeKubeletClient{}, EnableCoreControllers: true, EnableLogsSupport: false, EnableProfiling: true, APIPrefix: "/api", ExpAPIPrefix: "/experimental", Authorizer: apiserver.NewAlwaysAllowAuthorizer(), AdmissionControl: admit.NewAlwaysAdmit(), ReadWritePort: portNumber, PublicAddress: publicAddress, CacheTimeout: 2 * time.Second, }) handler.delegate = m.Handler // Scheduler schedulerConfigFactory := factory.NewConfigFactory(cl, nil) schedulerConfig, err := schedulerConfigFactory.Create() if err != nil { glog.Fatalf("Couldn't create scheduler config: %v", err) } eventBroadcaster := record.NewBroadcaster() schedulerConfig.Recorder = eventBroadcaster.NewRecorder(api.EventSource{Component: "scheduler"}) eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartRecordingToSink(cl.Events("")) scheduler.New(schedulerConfig).Run() endpoints := endpointcontroller.NewEndpointController(cl) // ensure the service endpoints are sync'd several times within the window that the integration tests wait go endpoints.Run(3, util.NeverStop) controllerManager := replicationControllerPkg.NewReplicationManager(cl, replicationControllerPkg.BurstReplicas) // TODO: Write an integration test for the replication controllers watch. go controllerManager.Run(3, util.NeverStop) nodeController := nodecontroller.NewNodeController(nil, cl, 5*time.Minute, nodecontroller.NewPodEvictor(util.NewFakeRateLimiter()), 40*time.Second, 60*time.Second, 5*time.Second, nil, false) nodeController.Run(5 * time.Second) cadvisorInterface := new(cadvisor.Fake) // Kubelet (localhost) testRootDir := makeTempDirOrDie("kubelet_integ_1.", "") configFilePath := makeTempDirOrDie("config", testRootDir) glog.Infof("Using %s as root dir for kubelet #1", testRootDir) fakeDocker1.VersionInfo = docker.Env{"ApiVersion=1.15"} kcfg := kubeletapp.SimpleKubelet(cl, &fakeDocker1, "localhost", testRootDir, firstManifestURL, "127.0.0.1", 10250, api.NamespaceDefault, empty_dir.ProbeVolumePlugins(), nil, cadvisorInterface, configFilePath, nil, kubecontainer.FakeOS{}) kubeletapp.RunKubelet(kcfg, nil) // Kubelet (machine) // Create a second kubelet so that the guestbook example's two redis slaves both // have a place they can schedule. testRootDir = makeTempDirOrDie("kubelet_integ_2.", "") glog.Infof("Using %s as root dir for kubelet #2", testRootDir) fakeDocker2.VersionInfo = docker.Env{"ApiVersion=1.15"} kcfg = kubeletapp.SimpleKubelet(cl, &fakeDocker2, "127.0.0.1", testRootDir, secondManifestURL, "127.0.0.1", 10251, api.NamespaceDefault, empty_dir.ProbeVolumePlugins(), nil, cadvisorInterface, "", nil, kubecontainer.FakeOS{}) kubeletapp.RunKubelet(kcfg, nil) return apiServer.URL, configFilePath }
func TestMonitorNodeStatusUpdateStatus(t *testing.T) { fakeNow := util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) table := []struct { fakeNodeHandler *FakeNodeHandler timeToPass time.Duration newNodeStatus api.NodeStatus expectedEvictPods bool expectedRequestCount int expectedNodes []*api.Node }{ // Node created long time ago, without status: // Expect Unknown status posted from node controller. { fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, }, }, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), }, expectedRequestCount: 2, // List+Update expectedNodes: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionUnknown, Reason: fmt.Sprintf("Kubelet never posted node status."), LastHeartbeatTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), LastTransitionTime: fakeNow, }, }, }, }, }, }, // Node created recently, without status. // Expect no action from node controller (within startup grace period). { fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: fakeNow, }, }, }, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), }, expectedRequestCount: 1, // List expectedNodes: nil, }, // Node created long time ago, with status updated by kubelet exceeds grace period. // Expect Unknown status posted from node controller. { fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionTrue, // Node status hasn't been updated for 1hr. LastHeartbeatTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), LastTransitionTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), }, }, Capacity: api.ResourceList{ api.ResourceName(api.ResourceCPU): resource.MustParse("10"), api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), }, }, Spec: api.NodeSpec{ ExternalID: "node0", }, }, }, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), }, expectedRequestCount: 3, // (List+)List+Update timeToPass: time.Hour, newNodeStatus: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionTrue, // Node status hasn't been updated for 1hr. LastHeartbeatTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), LastTransitionTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), }, }, Capacity: api.ResourceList{ api.ResourceName(api.ResourceCPU): resource.MustParse("10"), api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), }, }, expectedNodes: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionUnknown, Reason: fmt.Sprintf("Kubelet stopped posting node status."), LastHeartbeatTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), LastTransitionTime: util.Time{util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)}, }, }, Capacity: api.ResourceList{ api.ResourceName(api.ResourceCPU): resource.MustParse("10"), api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), }, }, Spec: api.NodeSpec{ ExternalID: "node0", }, }, }, }, // Node created long time ago, with status updated recently. // Expect no action from node controller (within monitor grace period). { fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionTrue, // Node status has just been updated. LastHeartbeatTime: fakeNow, LastTransitionTime: fakeNow, }, }, Capacity: api.ResourceList{ api.ResourceName(api.ResourceCPU): resource.MustParse("10"), api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), }, }, Spec: api.NodeSpec{ ExternalID: "node0", }, }, }, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), }, expectedRequestCount: 1, // List expectedNodes: nil, }, } for _, item := range table { nodeController := NewNodeController(nil, item.fakeNodeHandler, 5*time.Minute, NewPodEvictor(util.NewFakeRateLimiter()), testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, false) nodeController.now = func() util.Time { return fakeNow } if err := nodeController.monitorNodeStatus(); err != nil { t.Errorf("unexpected error: %v", err) } if item.timeToPass > 0 { nodeController.now = func() util.Time { return util.Time{Time: fakeNow.Add(item.timeToPass)} } item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus if err := nodeController.monitorNodeStatus(); err != nil { t.Errorf("unexpected error: %v", err) } } if item.expectedRequestCount != item.fakeNodeHandler.RequestCount { t.Errorf("expected %v call, but got %v.", item.expectedRequestCount, item.fakeNodeHandler.RequestCount) } if len(item.fakeNodeHandler.UpdatedNodes) > 0 && !api.Semantic.DeepEqual(item.expectedNodes, item.fakeNodeHandler.UpdatedNodes) { t.Errorf("expected nodes %+v, got %+v", item.expectedNodes[0], item.fakeNodeHandler.UpdatedNodes[0]) } } }
func TestMonitorNodeStatusEvictPods(t *testing.T) { fakeNow := util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) evictionTimeout := 10 * time.Minute table := []struct { fakeNodeHandler *FakeNodeHandler timeToPass time.Duration newNodeStatus api.NodeStatus expectedEvictPods bool description string }{ // Node created recently, with no status (happens only at cluster startup). { fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: fakeNow, }, }, }, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), }, timeToPass: 0, newNodeStatus: api.NodeStatus{}, expectedEvictPods: false, description: "Node created recently, with no status.", }, // Node created long time ago, and kubelet posted NotReady for a short period of time. { fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionFalse, LastHeartbeatTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), LastTransitionTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), }, }, }, }, }, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), }, timeToPass: evictionTimeout, newNodeStatus: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionFalse, // Node status has just been updated, and is NotReady for 10min. LastHeartbeatTime: util.Date(2015, 1, 1, 12, 9, 0, 0, time.UTC), LastTransitionTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), }, }, }, expectedEvictPods: false, description: "Node created long time ago, and kubelet posted NotReady for a short period of time.", }, // Node created long time ago, and kubelet posted NotReady for a long period of time. { fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionFalse, LastHeartbeatTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), LastTransitionTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), }, }, }, }, }, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), }, timeToPass: time.Hour, newNodeStatus: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionFalse, // Node status has just been updated, and is NotReady for 1hr. LastHeartbeatTime: util.Date(2015, 1, 1, 12, 59, 0, 0, time.UTC), LastTransitionTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), }, }, }, expectedEvictPods: true, description: "Node created long time ago, and kubelet posted NotReady for a long period of time.", }, // Node created long time ago, node controller posted Unknown for a short period of time. { fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionUnknown, LastHeartbeatTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), LastTransitionTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), }, }, }, }, }, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), }, timeToPass: evictionTimeout - testNodeMonitorGracePeriod, newNodeStatus: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionUnknown, // Node status was updated by nodecontroller 10min ago LastHeartbeatTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), LastTransitionTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), }, }, }, expectedEvictPods: false, description: "Node created long time ago, node controller posted Unknown for a short period of time.", }, // Node created long time ago, node controller posted Unknown for a long period of time. { fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionUnknown, LastHeartbeatTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), LastTransitionTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), }, }, }, }, }, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), }, timeToPass: 60 * time.Minute, newNodeStatus: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionUnknown, // Node status was updated by nodecontroller 1hr ago LastHeartbeatTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), LastTransitionTime: util.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), }, }, }, expectedEvictPods: true, description: "Node created long time ago, node controller posted Unknown for a long period of time.", }, } for _, item := range table { podEvictor := NewPodEvictor(util.NewFakeRateLimiter()) nodeController := NewNodeController(nil, item.fakeNodeHandler, evictionTimeout, podEvictor, testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, false) nodeController.now = func() util.Time { return fakeNow } if err := nodeController.monitorNodeStatus(); err != nil { t.Errorf("unexpected error: %v", err) } if item.timeToPass > 0 { nodeController.now = func() util.Time { return util.Time{Time: fakeNow.Add(item.timeToPass)} } item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus } if err := nodeController.monitorNodeStatus(); err != nil { t.Errorf("unexpected error: %v", err) } podEvictor.TryEvict(func(nodeName string) { nodeController.deletePods(nodeName) }) podEvicted := false for _, action := range item.fakeNodeHandler.Actions() { if action.GetVerb() == "delete" && action.GetResource() == "pods" { podEvicted = true } } if item.expectedEvictPods != podEvicted { t.Errorf("expected pod eviction: %+v, got %+v for %+v", item.expectedEvictPods, podEvicted, item.description) } } }
// of time. This sample size is not actually large enough to // reliably measure tails (it may give false positives, but not // false negatives), but it should catch low hanging fruit. // // Note that these are fixed and do not depend on the // size of the cluster. Setting parallelTrials larger // distorts the measurements. Perhaps this wouldn't be // true on HA clusters. totalTrials = 200 parallelTrials = 15 minSampleSize = 100 ) // Turn off rate limiting--it interferes with our measurements. oldThrottle := f.Client.RESTClient.Throttle f.Client.RESTClient.Throttle = util.NewFakeRateLimiter() defer func() { f.Client.RESTClient.Throttle = oldThrottle }() failing := util.NewStringSet() d, err := runServiceLatencies(f, parallelTrials, totalTrials) if err != nil { failing.Insert(fmt.Sprintf("Not all RC/pod/service trials succeeded: %v", err)) } dSorted := durations(d) sort.Sort(dSorted) n := len(dSorted) if n < minSampleSize { failing.Insert(fmt.Sprintf("Did not get a good sample size: %v", dSorted)) } if n < 2 { failing.Insert("Less than two runs succeeded; aborting.")
func TestSyncCloudNodesReconcilesExternalService(t *testing.T) { table := []struct { fakeNodeHandler *FakeNodeHandler fakeCloud *fake_cloud.FakeCloud matchRE string expectedClientActions []testclient.FakeAction expectedUpdateCalls []fake_cloud.FakeUpdateBalancerCall }{ { // Set of nodes does not change: do nothing. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0"), newNode("node1")}, Fake: testclient.NewSimpleFake(&api.ServiceList{Items: []api.Service{*newService("service0", types.UID(""), true), *newService("service1", types.UID(""), false)}})}, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0", "node1"}, }, matchRE: ".*", expectedClientActions: nil, expectedUpdateCalls: nil, }, { // Delete "node1", target pool for "service0" should shrink. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0"), newNode("node1")}, Fake: testclient.NewSimpleFake(&api.ServiceList{Items: []api.Service{*newService("service0", types.UID("2c104a7c-e79e-11e4-8187-42010af0068a"), true), *newService("service1", types.UID(""), false)}})}, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0"}, }, matchRE: ".*", expectedClientActions: []testclient.FakeAction{{Action: "list-pods"}, {Action: "list-services"}}, expectedUpdateCalls: []fake_cloud.FakeUpdateBalancerCall{ {Name: "a2c104a7ce79e11e4818742010af0068", Hosts: []string{"node0"}}, }, }, { // Add "node1", target pool for "service0" should grow. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0")}, Fake: testclient.NewSimpleFake(&api.ServiceList{Items: []api.Service{*newService("service0", types.UID("2c104a7c-e79e-11e4-8187-42010af0068a"), true), *newService("service1", types.UID(""), false)}})}, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0", "node1"}, }, matchRE: ".*", expectedClientActions: []testclient.FakeAction{{Action: "list-services"}}, expectedUpdateCalls: []fake_cloud.FakeUpdateBalancerCall{ {Name: "a2c104a7ce79e11e4818742010af0068", Hosts: []string{"node0", "node1"}}, }, }, } for _, item := range table { nodeController := NewNodeController(item.fakeCloud, item.matchRE, nil, &api.NodeResources{}, item.fakeNodeHandler, 10, time.Minute, util.NewFakeRateLimiter(), testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, "kubernetes") if err := nodeController.syncCloudNodes(); err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(item.expectedClientActions, item.fakeNodeHandler.Actions) { t.Errorf("expected client actions mismatch, expected %+v, got %+v", item.expectedClientActions, item.fakeNodeHandler.Actions) } if !reflect.DeepEqual(item.expectedUpdateCalls, item.fakeCloud.UpdateCalls) { t.Errorf("expected update calls mismatch, expected %+v, got %+v", item.expectedUpdateCalls, item.fakeCloud.UpdateCalls) } } }
func TestSyncCloudNodesEvictPods(t *testing.T) { table := []struct { fakeNodeHandler *FakeNodeHandler fakeCloud *fake_cloud.FakeCloud matchRE string expectedRequestCount int expectedDeleted []string expectedActions []testclient.FakeAction }{ { // No node to delete: do nothing. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0"), newNode("node1")}, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0"), *newPod("pod1", "node1")}}), }, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0", "node1"}, }, matchRE: ".*", expectedRequestCount: 1, // List expectedDeleted: []string{}, expectedActions: nil, }, { // Delete node1, and pod0 is running on it. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0"), newNode("node1")}, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node1")}}), }, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0"}, }, matchRE: ".*", expectedRequestCount: 2, // List + Delete expectedDeleted: []string{"node1"}, expectedActions: []testclient.FakeAction{{Action: "list-pods"}, {Action: "delete-pod", Value: "pod0"}, {Action: "list-services"}}, }, { // Delete node1, but pod0 is running on node0. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0"), newNode("node1")}, Fake: testclient.NewSimpleFake(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), }, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0"}, }, matchRE: ".*", expectedRequestCount: 2, // List + Delete expectedDeleted: []string{"node1"}, expectedActions: []testclient.FakeAction{{Action: "list-pods"}, {Action: "list-services"}}, }, } for _, item := range table { if item.fakeNodeHandler.Fake == nil { item.fakeNodeHandler.Fake = testclient.NewSimpleFake() } nodeController := NewNodeController(item.fakeCloud, item.matchRE, nil, &api.NodeResources{}, item.fakeNodeHandler, 10, time.Minute, util.NewFakeRateLimiter(), testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, "") if err := nodeController.syncCloudNodes(); err != nil { t.Errorf("unexpected error: %v", err) } if item.fakeNodeHandler.RequestCount != item.expectedRequestCount { t.Errorf("expected %v call, but got %v.", item.expectedRequestCount, item.fakeNodeHandler.RequestCount) } nodes := sortedNodeNames(item.fakeNodeHandler.DeletedNodes) if !reflect.DeepEqual(item.expectedDeleted, nodes) { t.Errorf("expected node list %+v, got %+v", item.expectedDeleted, nodes) } if !reflect.DeepEqual(item.expectedActions, item.fakeNodeHandler.Actions) { t.Errorf("time out waiting for deleting pods, expected %+v, got %+v", item.expectedActions, item.fakeNodeHandler.Actions) } } }
func TestSyncCloudNodes(t *testing.T) { table := []struct { fakeNodeHandler *FakeNodeHandler fakeCloud *fake_cloud.FakeCloud matchRE string expectedRequestCount int expectedNameCreated []string expectedExtIDCreated []string expectedAddrsCreated []string expectedDeleted []string }{ { // 1 existing node, 1 cloud nodes: do nothing. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0")}, }, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0"}, ExtID: map[string]string{ "node0": "ext-node0", "node1": "ext-node1", }, Addresses: []api.NodeAddress{{Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}}, }, matchRE: ".*", expectedRequestCount: 1, // List expectedNameCreated: []string{}, expectedExtIDCreated: []string{}, expectedAddrsCreated: []string{}, expectedDeleted: []string{}, }, { // 1 existing node, 2 cloud nodes: create 1. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0")}, }, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0", "node1"}, ExtID: map[string]string{ "node0": "ext-node0", "node1": "ext-node1", }, Addresses: []api.NodeAddress{{Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}}, }, matchRE: ".*", expectedRequestCount: 2, // List + Create expectedNameCreated: []string{"node1"}, expectedExtIDCreated: []string{"ext-node1"}, expectedAddrsCreated: []string{"1.2.3.4"}, expectedDeleted: []string{}, }, { // 2 existing nodes, 1 cloud node: delete 1. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0"), newNode("node1")}, }, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0"}, ExtID: map[string]string{ "node0": "ext-node0", "node1": "ext-node1", }, Addresses: []api.NodeAddress{{Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}}, }, matchRE: ".*", expectedRequestCount: 2, // List + Delete expectedNameCreated: []string{}, expectedExtIDCreated: []string{}, expectedAddrsCreated: []string{}, expectedDeleted: []string{"node1"}, }, { // 1 existing node, 3 cloud nodes but only 2 match regex: delete 1. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0")}, }, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0", "node1", "fake"}, ExtID: map[string]string{ "node0": "ext-node0", "node1": "ext-node1", "fake": "ext-fake", }, Addresses: []api.NodeAddress{{Type: api.NodeLegacyHostIP, Address: "1.2.3.4"}}, }, matchRE: "node[0-9]+", expectedRequestCount: 2, // List + Create expectedNameCreated: []string{"node1"}, expectedExtIDCreated: []string{"ext-node1"}, expectedAddrsCreated: []string{"1.2.3.4"}, expectedDeleted: []string{}, }, } for _, item := range table { if item.fakeNodeHandler.Fake == nil { item.fakeNodeHandler.Fake = testclient.NewSimpleFake() } nodeController := NewNodeController(item.fakeCloud, item.matchRE, nil, &api.NodeResources{}, item.fakeNodeHandler, 10, time.Minute, util.NewFakeRateLimiter(), testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, "") if err := nodeController.syncCloudNodes(); err != nil { t.Errorf("unexpected error: %v", err) } if item.fakeNodeHandler.RequestCount != item.expectedRequestCount { t.Errorf("expected %v call, but got %v.", item.expectedRequestCount, item.fakeNodeHandler.RequestCount) } nodes := sortedNodeNames(item.fakeNodeHandler.CreatedNodes) if !reflect.DeepEqual(item.expectedNameCreated, nodes) { t.Errorf("expected node list %+v, got %+v", item.expectedNameCreated, nodes) } nodeExtIDs := sortedNodeExternalIDs(item.fakeNodeHandler.CreatedNodes) if !reflect.DeepEqual(item.expectedExtIDCreated, nodeExtIDs) { t.Errorf("expected node external id list %+v, got %+v", item.expectedExtIDCreated, nodeExtIDs) } nodeAddrs := sortedNodeAddresses(item.fakeNodeHandler.CreatedNodes) if !reflect.DeepEqual(item.expectedAddrsCreated, nodeAddrs) { t.Errorf("expected node address list %+v, got %+v", item.expectedAddrsCreated, nodeAddrs) } nodes = sortedNodeNames(item.fakeNodeHandler.DeletedNodes) if !reflect.DeepEqual(item.expectedDeleted, nodes) { t.Errorf("expected node list %+v, got %+v", item.expectedDeleted, nodes) } } }
func TestCreateGetCloudNodesWithSpec(t *testing.T) { resourceList := api.ResourceList{ api.ResourceCPU: *resource.NewMilliQuantity(1000, resource.DecimalSI), api.ResourceMemory: *resource.NewQuantity(3000, resource.DecimalSI), } table := []struct { fakeCloud *fake_cloud.FakeCloud machines []string expectedNodes *api.NodeList }{ { fakeCloud: &fake_cloud.FakeCloud{}, expectedNodes: &api.NodeList{}, }, { fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0"}, NodeResources: &api.NodeResources{Capacity: resourceList}, }, expectedNodes: &api.NodeList{ Items: []api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, Status: api.NodeStatus{Capacity: resourceList}, }, }, }, }, { fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0", "node1"}, NodeResources: &api.NodeResources{Capacity: resourceList}, }, expectedNodes: &api.NodeList{ Items: []api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, Status: api.NodeStatus{Capacity: resourceList}, }, { ObjectMeta: api.ObjectMeta{Name: "node1"}, Status: api.NodeStatus{Capacity: resourceList}, }, }, }, }, } for _, item := range table { nodeController := NewNodeController(item.fakeCloud, ".*", nil, &api.NodeResources{}, nil, 10, time.Minute, util.NewFakeRateLimiter(), testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, "") nodes, err := nodeController.getCloudNodesWithSpec() if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(item.expectedNodes, nodes) { t.Errorf("expected node list %+v, got %+v", item.expectedNodes, nodes) } } }
func TestCreateGetStaticNodesWithSpec(t *testing.T) { table := []struct { machines []string expectedNodes *api.NodeList }{ { machines: []string{}, expectedNodes: &api.NodeList{}, }, { machines: []string{"node0"}, expectedNodes: &api.NodeList{ Items: []api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, Spec: api.NodeSpec{ ExternalID: "node0", }, Status: api.NodeStatus{ Capacity: api.ResourceList{ api.ResourceName(api.ResourceCPU): resource.MustParse("10"), api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), }, }, }, }, }, }, { machines: []string{"node0", "node1"}, expectedNodes: &api.NodeList{ Items: []api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, Spec: api.NodeSpec{ ExternalID: "node0", }, Status: api.NodeStatus{ Capacity: api.ResourceList{ api.ResourceName(api.ResourceCPU): resource.MustParse("10"), api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), }, }, }, { ObjectMeta: api.ObjectMeta{Name: "node1"}, Spec: api.NodeSpec{ ExternalID: "node1", }, Status: api.NodeStatus{ Capacity: api.ResourceList{ api.ResourceName(api.ResourceCPU): resource.MustParse("10"), api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), }, }, }, }, }, }, } resources := api.NodeResources{ Capacity: api.ResourceList{ api.ResourceName(api.ResourceCPU): resource.MustParse("10"), api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), }, } for _, item := range table { nodeController := NewNodeController(nil, "", item.machines, &resources, nil, 10, time.Minute, util.NewFakeRateLimiter(), testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, "") nodes, err := nodeController.getStaticNodesWithSpec() if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(item.expectedNodes, nodes) { t.Errorf("expected node list %+v, got %+v", item.expectedNodes, nodes) } } }
func TestRegisterNodes(t *testing.T) { table := []struct { fakeNodeHandler *FakeNodeHandler machines []string retryCount int expectedRequestCount int expectedCreateCount int expectedFail bool }{ { // Register two nodes normally. machines: []string{"node0", "node1"}, fakeNodeHandler: &FakeNodeHandler{ CreateHook: func(fake *FakeNodeHandler, node *api.Node) bool { return true }, }, retryCount: 1, expectedRequestCount: 2, expectedCreateCount: 2, expectedFail: false, }, { // Canonicalize node names. machines: []string{"NODE0", "node1"}, fakeNodeHandler: &FakeNodeHandler{ CreateHook: func(fake *FakeNodeHandler, node *api.Node) bool { if node.Name == "NODE0" { return false } return true }, }, retryCount: 1, expectedRequestCount: 2, expectedCreateCount: 2, expectedFail: false, }, { // No machine to register. machines: []string{}, fakeNodeHandler: &FakeNodeHandler{ CreateHook: func(fake *FakeNodeHandler, node *api.Node) bool { return true }, }, retryCount: 1, expectedRequestCount: 0, expectedCreateCount: 0, expectedFail: false, }, { // Fail the first two requests. machines: []string{"node0", "node1"}, fakeNodeHandler: &FakeNodeHandler{ CreateHook: func(fake *FakeNodeHandler, node *api.Node) bool { if fake.RequestCount == 0 || fake.RequestCount == 1 { return false } return true }, }, retryCount: 10, expectedRequestCount: 4, expectedCreateCount: 2, expectedFail: false, }, { // One node already exists machines: []string{"node0", "node1"}, fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node1", }, }, }, }, retryCount: 10, expectedRequestCount: 2, expectedCreateCount: 1, expectedFail: false, }, { // The first node always fails. machines: []string{"node0", "node1"}, fakeNodeHandler: &FakeNodeHandler{ CreateHook: func(fake *FakeNodeHandler, node *api.Node) bool { if node.Name == "node0" { return false } return true }, }, retryCount: 2, expectedRequestCount: 3, // 2 for node0, 1 for node1 expectedCreateCount: 1, expectedFail: true, }, } for _, item := range table { nodes := api.NodeList{} for _, machine := range item.machines { nodes.Items = append(nodes.Items, *newNode(machine)) } nodeController := NewNodeController(nil, "", item.machines, &api.NodeResources{}, item.fakeNodeHandler, 10, time.Minute, util.NewFakeRateLimiter(), testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, "") err := nodeController.registerNodes(&nodes, item.retryCount, time.Millisecond) if !item.expectedFail && err != nil { t.Errorf("unexpected error: %v", err) } if item.expectedFail && err == nil { t.Errorf("unexpected non-error") } if item.fakeNodeHandler.RequestCount != item.expectedRequestCount { t.Errorf("expected %v calls, but got %v.", item.expectedRequestCount, item.fakeNodeHandler.RequestCount) } if len(item.fakeNodeHandler.CreatedNodes) != item.expectedCreateCount { t.Errorf("expected %v nodes, but got %v.", item.expectedCreateCount, item.fakeNodeHandler.CreatedNodes) } } }