func TestOverlappingRCs(t *testing.T) { client := client.NewOrDie(&client.Config{Host: "", Version: testapi.Version()}) for i := 0; i < 5; i++ { manager := NewReplicationManager(client, 10) manager.podStoreSynced = alwaysReady // Create 10 rcs, shuffled them randomly and insert them into the rc manager's store var controllers []*api.ReplicationController for j := 1; j < 10; j++ { controllerSpec := newReplicationController(1) controllerSpec.CreationTimestamp = util.Date(2014, time.December, j, 0, 0, 0, 0, time.Local) controllerSpec.Name = string(util.NewUUID()) controllers = append(controllers, controllerSpec) } shuffledControllers := shuffle(controllers) for j := range shuffledControllers { manager.rcStore.Store.Add(shuffledControllers[j]) } // Add a pod and make sure only the oldest rc is synced pods := newPodList(nil, 1, api.PodPending, controllers[0]) rcKey := getKey(controllers[0], t) manager.addPod(&pods.Items[0]) queueRC, _ := manager.queue.Get() if queueRC != rcKey { t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) } } }
func TestEtcdGetBuildWithDuration(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) start := util.Date(2015, time.April, 8, 12, 1, 1, 0, time.Local) completion := util.Date(2015, time.April, 8, 12, 1, 5, 0, time.Local) fakeClient.Set(makeTestDefaultBuildKey("foo"), runtime.EncodeOrDie(latest.Codec, &api.Build{ ObjectMeta: kapi.ObjectMeta{Name: "foo"}, StartTimestamp: &start, CompletionTimestamp: &completion, }), 0) registry := NewTestEtcd(fakeClient) build, err := registry.GetBuild(kapi.NewDefaultContext(), "foo") if err != nil { t.Errorf("unexpected error: %v", err) } else if build.Name != "foo" && build.Duration != time.Duration(4)*time.Second { t.Errorf("Unexpected build: %#v", build) } }
func TestEtcdListBuilds(t *testing.T) { fakeClient := tools.NewFakeEtcdClient(t) key := makeTestDefaultBuildListKey() start := util.Date(2015, time.April, 8, 12, 1, 1, 0, time.Local) completion := util.Date(2015, time.April, 8, 12, 1, 5, 0, time.Local) fakeClient.Data[key] = tools.EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ Nodes: []*etcd.Node{ { Value: runtime.EncodeOrDie(latest.Codec, &api.Build{ ObjectMeta: kapi.ObjectMeta{Name: "foo"}, StartTimestamp: &start, CompletionTimestamp: &completion, }), }, { Value: runtime.EncodeOrDie(latest.Codec, &api.Build{ ObjectMeta: kapi.ObjectMeta{Name: "bar"}, StartTimestamp: &start, CompletionTimestamp: &completion, }), }, }, }, }, E: nil, } registry := NewTestEtcd(fakeClient) builds, err := registry.ListBuilds(kapi.NewDefaultContext(), labels.Everything()) if err != nil { t.Errorf("unexpected error: %v", err) } duration := time.Duration(4) * time.Second if len(builds.Items) != 2 || builds.Items[0].Name != "foo" || builds.Items[1].Name != "bar" || builds.Items[0].Duration != duration || builds.Items[1].Duration != duration { t.Errorf("Unexpected build list: %#v", builds) } }
func TestLimitedLogAndRetryProcessing(t *testing.T) { updater := &buildUpdater{} err := errors.New("funky error") now := kutil.Now() retry := controller.Retry{0, kutil.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute()-10, now.Second(), now.Nanosecond(), now.Location())} if !limitedLogAndRetry(updater, 30*time.Minute)(&buildapi.Build{Status: buildapi.BuildStatusNew}, err, retry) { t.Error("Expected more retries!") } if updater.Build != nil { t.Fatal("BuildUpdater shouldn't be called!") } }
func TestLimitedLogAndRetryFinish(t *testing.T) { updater := &buildUpdater{} err := errors.New("funky error") now := kutil.Now() retry := controller.Retry{0, kutil.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute()-31, now.Second(), now.Nanosecond(), now.Location())} if limitedLogAndRetry(updater, 30*time.Minute)(&buildapi.Build{Status: buildapi.BuildStatusNew}, err, retry) { t.Error("Expected no more retries after reaching timeout!") } if updater.Build == nil { t.Fatal("BuildUpdater wasn't called!") } if updater.Build.Status != buildapi.BuildStatusFailed { t.Errorf("Expected status %s, got %s!", buildapi.BuildStatusFailed, updater.Build.Status) } if !strings.Contains(updater.Build.Message, err.Error()) { t.Errorf("Expected message to contain %v, got %s!", err, updater.Build.Status) } if updater.Build.CompletionTimestamp == nil { t.Error("Expected CompletionTimestamp to be set!") } }
func ExampleProcessTemplateParameters() { var template api.Template jsonData, _ := ioutil.ReadFile("../../examples/guestbook/template.json") json.Unmarshal(jsonData, &template) generators := map[string]Generator{ "expression": NewExpressionValueGenerator(rand.New(rand.NewSource(1337))), } processor := NewTemplateProcessor(generators) // Define custom parameter for the transformation: processor.AddParameter(&template, api.Parameter{Name: "CUSTOM_PARAM1", Value: "1"}) // Transform the template config into the result config config, _ := processor.Process(&template) // Reset the timestamp for the output comparison config.CreationTimestamp = util.Date(1980, 1, 1, 0, 0, 0, 0, time.UTC) result, _ := json.Marshal(config) fmt.Println(string(result)) // Output: // {"kind":"Config","id":"guestbook","creationTimestamp":"1980-01-01T00:00:00Z","name":"guestbook-example","description":"Example shows how to build a simple multi-tier application using Kubernetes and Docker","items":[{"kind":"Service","id":"frontend","creationTimestamp":null,"apiVersion":"v1beta1","port":5432,"selector":{"name":"frontend"},"containerPort":0},{"kind":"Service","id":"redismaster","creationTimestamp":null,"apiVersion":"v1beta1","port":10000,"selector":{"name":"redis-master"},"containerPort":0},{"kind":"Service","id":"redisslave","creationTimestamp":null,"apiVersion":"v1beta1","port":10001,"labels":{"name":"redisslave"},"selector":{"name":"redisslave"},"containerPort":0},{"kind":"Pod","id":"redis-master-2","creationTimestamp":null,"apiVersion":"v1beta1","labels":{"name":"redis-master"},"desiredState":{"manifest":{"version":"v1beta1","id":"redis-master-2","volumes":null,"containers":[{"name":"master","image":"dockerfile/redis","ports":[{"containerPort":6379}],"env":[{"name":"REDIS_PASSWORD","key":"REDIS_PASSWORD","value":"P8vxbV4C"}]}]},"restartpolicy":{}},"currentState":{"manifest":{"version":"","id":"","volumes":null,"containers":null},"restartpolicy":{}}},{"kind":"ReplicationController","id":"frontendController","creationTimestamp":null,"apiVersion":"v1beta1","desiredState":{"replicas":3,"replicaSelector":{"name":"frontend"},"podTemplate":{"desiredState":{"manifest":{"version":"v1beta1","id":"frontendController","volumes":null,"containers":[{"name":"php-redis","image":"brendanburns/php-redis","ports":[{"hostPort":8000,"containerPort":80}],"env":[{"name":"ADMIN_USERNAME","key":"ADMIN_USERNAME","value":"adminQ3H"},{"name":"ADMIN_PASSWORD","key":"ADMIN_PASSWORD","value":"dwNJiJwW"},{"name":"REDIS_PASSWORD","key":"REDIS_PASSWORD","value":"P8vxbV4C"}]}]},"restartpolicy":{}},"labels":{"name":"frontend"}}},"labels":{"name":"frontend"}},{"kind":"ReplicationController","id":"redisSlaveController","creationTimestamp":null,"apiVersion":"v1beta1","desiredState":{"replicas":2,"replicaSelector":{"name":"redisslave"},"podTemplate":{"desiredState":{"manifest":{"version":"v1beta1","id":"redisSlaveController","volumes":null,"containers":[{"name":"slave","image":"brendanburns/redis-slave","ports":[{"hostPort":6380,"containerPort":6379}],"env":[{"name":"REDIS_PASSWORD","key":"REDIS_PASSWORD","value":"P8vxbV4C"}]}]},"restartpolicy":{}},"labels":{"name":"redisslave"}}},"labels":{"name":"redisslave"}}]} }
func TestImageWithMetadata(t *testing.T) { tests := map[string]struct { image Image expectedImage Image expectError bool }{ "no manifest data": { image: Image{}, expectedImage: Image{}, }, "error unmarshalling manifest data": { image: Image{ DockerImageManifest: "{ no {{{ json here!!!", }, expectedImage: Image{}, expectError: true, }, "no history": { image: Image{ DockerImageManifest: `{"name": "library/ubuntu", "tag": "latest"}`, }, expectedImage: Image{}, }, "error unmarshalling v1 compat": { image: Image{ DockerImageManifest: `{"name": "library/ubuntu", "tag": "latest", "history": ["v1Compatibility": "{ not valid {{ json" }`, }, expectError: true, }, "happy path": { image: validImageWithManifestData(), expectedImage: Image{ ObjectMeta: kapi.ObjectMeta{ Name: "id", }, DockerImageManifest: "", DockerImageMetadata: DockerImage{ ID: "2d24f826cb16146e2016ff349a8a33ed5830f3b938d45c0f82943f4ab8c097e7", Parent: "117ee323aaa9d1b136ea55e4421f4ce413dfc6c0cc6b2186dea6c88d93e1ad7c", Comment: "", Created: util.Date(2015, 2, 21, 2, 11, 6, 735146646, time.UTC), Container: "c9a3eda5951d28aa8dbe5933be94c523790721e4f80886d0a8e7a710132a38ec", ContainerConfig: DockerConfig{ Hostname: "43bd710ec89a", Domainname: "", User: "", Memory: 0, MemorySwap: 0, CPUShares: 0, CPUSet: "", AttachStdin: false, AttachStdout: false, AttachStderr: false, PortSpecs: nil, ExposedPorts: nil, Tty: false, OpenStdin: false, StdinOnce: false, Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, Cmd: []string{"/bin/sh", "-c", "#(nop) CMD [/bin/bash]"}, Image: "117ee323aaa9d1b136ea55e4421f4ce413dfc6c0cc6b2186dea6c88d93e1ad7c", Volumes: nil, WorkingDir: "", Entrypoint: nil, NetworkDisabled: false, SecurityOpts: nil, OnBuild: []string{}, }, DockerVersion: "1.4.1", Author: "", Config: &DockerConfig{ Hostname: "43bd710ec89a", Domainname: "", User: "", Memory: 0, MemorySwap: 0, CPUShares: 0, CPUSet: "", AttachStdin: false, AttachStdout: false, AttachStderr: false, PortSpecs: nil, ExposedPorts: nil, Tty: false, OpenStdin: false, StdinOnce: false, Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, Cmd: []string{"/bin/bash"}, Image: "117ee323aaa9d1b136ea55e4421f4ce413dfc6c0cc6b2186dea6c88d93e1ad7c", Volumes: nil, WorkingDir: "", Entrypoint: nil, NetworkDisabled: false, OnBuild: []string{}, }, Architecture: "amd64", Size: 0, }, }, }, } for name, test := range tests { imageWithMetadata, err := ImageWithMetadata(test.image) gotError := err != nil if e, a := test.expectError, gotError; e != a { t.Fatalf("%s: expectError=%t, gotError=%t: %s", name, e, a, err) } if test.expectError { continue } if e, a := test.expectedImage, *imageWithMetadata; !kapi.Semantic.DeepEqual(e, a) { stringE := fmt.Sprintf("%#v", e) stringA := fmt.Sprintf("%#v", a) t.Errorf("%s: image: %s", name, util.StringDiff(stringE, stringA)) } } }
func TestSyncNodeStatusDeletePods(t *testing.T) { table := []struct { fakeNodeHandler *FakeNodeHandler fakeKubeletClient *FakeKubeletClient expectedRequestCount int expectedActions []client.FakeAction }{ { // Existing node is healthy, current probe is healthy too. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionFull, Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok", LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, }, }, }, }, Fake: client.Fake{ PodsList: api.PodList{Items: []api.Pod{*newPod("pod0", "node1")}}, }, }, fakeKubeletClient: &FakeKubeletClient{ Status: probe.Success, Err: nil, }, expectedRequestCount: 2, // List+Update expectedActions: nil, }, { // Existing node is healthy, current probe is unhealthy, i.e. node just becomes unhealthy. // Do not delete pods. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionFull, Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok", LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, }, }, }, }, Fake: client.Fake{ PodsList: api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}, }, }, fakeKubeletClient: &FakeKubeletClient{ Status: probe.Failure, Err: nil, }, expectedRequestCount: 2, // List+Update expectedActions: nil, }, { // Existing node unhealthy, current probe is unhealthy. Node is still within grace peroid. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionNone, Reason: "Node health check failed: kubelet /healthz endpoint returns not ok", // Here, last transition time is Now(). In node controller, the new condition's probe time is // also Now(). The two calls to Now() yields differnt time due to test execution, but the // time difference is within 5 minutes, which is the grace peroid. LastTransitionTime: util.Now(), }, }, }, }, }, Fake: client.Fake{ PodsList: api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}, }, }, fakeKubeletClient: &FakeKubeletClient{ Status: probe.Failure, Err: nil, }, expectedRequestCount: 2, // List+Update expectedActions: nil, }, { // Existing node unhealthy, current probe is unhealthy. Node exceeds grace peroid. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionNone, Reason: "Node health check failed: kubelet /healthz endpoint returns not ok", // Here, last transition time is in the past, and in node controller, the // new condition's probe time is Now(). The time difference is larger than // 5*min. The test will fail if system clock is wrong, but we don't yet have // ways to mock time in our tests. LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, }, }, }, }, Fake: client.Fake{ PodsList: api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}, }, }, fakeKubeletClient: &FakeKubeletClient{ Status: probe.Failure, Err: nil, }, expectedRequestCount: 2, // List+Update expectedActions: []client.FakeAction{{Action: "list-pods"}, {Action: "delete-pod", Value: "pod0"}}, }, } for _, item := range table { nodeController := NewNodeController(nil, "", []string{"node0"}, nil, item.fakeNodeHandler, item.fakeKubeletClient, 10, 5*time.Minute) nodeController.lookupIP = func(host string) ([]net.IP, error) { return nil, fmt.Errorf("lookup %v: no such host", host) } if err := nodeController.SyncNodeStatus(); 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 !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 TestEvictTimeoutedPods(t *testing.T) { table := []struct { fakeNodeHandler *FakeNodeHandler expectedRequestCount int expectedActions []client.FakeAction }{ // Node created long time ago, with no status. { fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, }, }, Fake: client.Fake{ PodsList: api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}, }, }, expectedRequestCount: 1, // List expectedActions: []client.FakeAction{{Action: "list-pods"}, {Action: "delete-pod", Value: "pod0"}}, }, // Node created recently, with no status. { fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{ Name: "node0", CreationTimestamp: util.Now(), }, }, }, Fake: client.Fake{ PodsList: api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}, }, }, expectedRequestCount: 1, // List expectedActions: nil, }, // Node created long time ago, with status updated long time ago. { 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.ConditionFull, LastProbeTime: util.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC), }, }, }, }, }, Fake: client.Fake{ PodsList: api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}, }, }, expectedRequestCount: 1, // List expectedActions: []client.FakeAction{{Action: "list-pods"}, {Action: "delete-pod", Value: "pod0"}}, }, // Node created long time ago, with status updated recently. { 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.ConditionFull, LastProbeTime: util.Now(), }, }, }, }, }, Fake: client.Fake{ PodsList: api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}, }, }, expectedRequestCount: 1, // List expectedActions: nil, }, } for _, item := range table { nodeController := NewNodeController(nil, "", []string{"node0"}, nil, item.fakeNodeHandler, nil, 10, 5*time.Minute) if err := nodeController.EvictTimeoutedPods(); 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 !reflect.DeepEqual(item.expectedActions, item.fakeNodeHandler.Actions) { t.Errorf("actions differs, expected %+v, got %+v", item.expectedActions, item.fakeNodeHandler.Actions) } } }
func TestSyncNodeStatusTransitionTime(t *testing.T) { table := []struct { fakeNodeHandler *FakeNodeHandler fakeKubeletClient *FakeKubeletClient expectedRequestCount int expectedTransitionTimeChange bool }{ { // Existing node is healthy, current probe is healthy too. // Existing node is schedulable, again explicitly mark node as schedulable. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, Spec: api.NodeSpec{Unschedulable: false}, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionFull, Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok", LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, { Type: api.NodeSchedulable, Status: api.ConditionFull, Reason: "Node is schedulable by default", LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, }, }, }, }, }, fakeKubeletClient: &FakeKubeletClient{ Status: probe.Success, Err: nil, }, expectedRequestCount: 2, // List+Update expectedTransitionTimeChange: false, }, { // Existing node is healthy, current probe is unhealthy. // Existing node is schedulable, mark node as unschedulable. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{ { ObjectMeta: api.ObjectMeta{Name: "node0"}, Spec: api.NodeSpec{Unschedulable: true}, Status: api.NodeStatus{ Conditions: []api.NodeCondition{ { Type: api.NodeReady, Status: api.ConditionFull, Reason: "Node health check succeeded: kubelet /healthz endpoint returns ok", LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, { Type: api.NodeSchedulable, Status: api.ConditionFull, Reason: "Node is schedulable by default", LastTransitionTime: util.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), }, }, }, }, }, }, fakeKubeletClient: &FakeKubeletClient{ Status: probe.Failure, Err: nil, }, expectedRequestCount: 2, // List+Update expectedTransitionTimeChange: true, }, } for _, item := range table { nodeController := NewNodeController(nil, "", []string{"node0"}, nil, item.fakeNodeHandler, item.fakeKubeletClient, 10, time.Minute) nodeController.lookupIP = func(host string) ([]net.IP, error) { return nil, fmt.Errorf("lookup %v: no such host", host) } if err := nodeController.SyncNodeStatus(); 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) } for i := range item.fakeNodeHandler.UpdatedNodes { conditions := item.fakeNodeHandler.UpdatedNodes[i].Status.Conditions for j := range conditions { condition := conditions[j] if item.expectedTransitionTimeChange { if !condition.LastTransitionTime.After(time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC)) { t.Errorf("unexpected last transition timestamp %v", condition.LastTransitionTime) } } else { if !condition.LastTransitionTime.Equal(time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC)) { t.Errorf("unexpected last transition timestamp %v", condition.LastTransitionTime) } } } } } }
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) } } }
func TestDescribeBuildDuration(t *testing.T) { type testBuild struct { build *buildapi.Build output string } creation := kutil.Date(2015, time.April, 9, 6, 0, 0, 0, time.Local) // now a minute ago minuteAgo := kutil.Unix(kutil.Now().Rfc3339Copy().Time.Unix()-60, 0) start := kutil.Date(2015, time.April, 9, 6, 1, 0, 0, time.Local) completion := kutil.Date(2015, time.April, 9, 6, 2, 0, 0, time.Local) duration := completion.Rfc3339Copy().Time.Sub(start.Rfc3339Copy().Time) zeroDuration := time.Duration(0) tests := []testBuild{ { // 0 - build new &buildapi.Build{ ObjectMeta: kapi.ObjectMeta{CreationTimestamp: minuteAgo}, Status: buildapi.BuildStatusNew, Duration: zeroDuration, }, "waiting for 1m0s", }, { // 1 - build pending &buildapi.Build{ ObjectMeta: kapi.ObjectMeta{CreationTimestamp: minuteAgo}, Status: buildapi.BuildStatusPending, Duration: zeroDuration, }, "waiting for 1m0s", }, { // 2 - build running &buildapi.Build{ ObjectMeta: kapi.ObjectMeta{CreationTimestamp: creation}, StartTimestamp: &start, Status: buildapi.BuildStatusRunning, Duration: duration, }, "running for 1m0s", }, { // 3 - build completed &buildapi.Build{ ObjectMeta: kapi.ObjectMeta{CreationTimestamp: creation}, StartTimestamp: &start, CompletionTimestamp: &completion, Status: buildapi.BuildStatusComplete, Duration: duration, }, "1m0s", }, { // 4 - build failed &buildapi.Build{ ObjectMeta: kapi.ObjectMeta{CreationTimestamp: creation}, StartTimestamp: &start, CompletionTimestamp: &completion, Status: buildapi.BuildStatusFailed, Duration: duration, }, "1m0s", }, { // 5 - build error &buildapi.Build{ ObjectMeta: kapi.ObjectMeta{CreationTimestamp: creation}, StartTimestamp: &start, CompletionTimestamp: &completion, Status: buildapi.BuildStatusError, Duration: duration, }, "1m0s", }, { // 6 - build cancelled before running, start time wasn't set yet &buildapi.Build{ ObjectMeta: kapi.ObjectMeta{CreationTimestamp: creation}, CompletionTimestamp: &completion, Status: buildapi.BuildStatusCancelled, Duration: duration, }, "waited for 2m0s", }, { // 7 - build cancelled while running, start time is set already &buildapi.Build{ ObjectMeta: kapi.ObjectMeta{CreationTimestamp: creation}, StartTimestamp: &start, CompletionTimestamp: &completion, Status: buildapi.BuildStatusCancelled, Duration: duration, }, "1m0s", }, { // 8 - build failed before running, start time wasn't set yet &buildapi.Build{ ObjectMeta: kapi.ObjectMeta{CreationTimestamp: creation}, CompletionTimestamp: &completion, Status: buildapi.BuildStatusFailed, Duration: duration, }, "waited for 2m0s", }, { // 9 - build error before running, start time wasn't set yet &buildapi.Build{ ObjectMeta: kapi.ObjectMeta{CreationTimestamp: creation}, CompletionTimestamp: &completion, Status: buildapi.BuildStatusError, Duration: duration, }, "waited for 2m0s", }, } for i, tc := range tests { if actual, expected := describeBuildDuration(tc.build), tc.output; actual != expected { t.Errorf("(%d) expected duration output %s, got %s", i, expected, actual) } } }
func TestFormatImageStreamTags(t *testing.T) { repo := imageapi.ImageStream{ Spec: imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "spec1": { From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Namespace: "foo", Name: "bar:latest", }, }, "spec2": { From: &kapi.ObjectReference{ Kind: "ImageStreamImage", Namespace: "mysql", Name: "latest@sha256:e52c6534db85036dabac5e71ff14e720db94def2d90f986f3548425ea27b3719", }, }, }, }, Status: imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ imageapi.DefaultImageTag: { Items: []imageapi.TagEvent{ { Created: util.Date(2015, 3, 24, 9, 38, 0, 0, time.UTC), DockerImageReference: "registry:5000/foo/bar@sha256:4bd26aef1ce78b4f05ede83496276f11e3343441574ca1ce89dffd146c708c16", Image: "sha256:4bd26aef1ce78b4f05ede83496276f11e3343441574ca1ce89dffd146c708c16", }, { Created: util.Date(2015, 3, 23, 7, 15, 0, 0, time.UTC), DockerImageReference: "registry:5000/foo/bar@sha256:062b80555a5dd7f5d58e78b266785a399277ff8c3e402ce5fa5d8571788e6bad", Image: "sha256:062b80555a5dd7f5d58e78b266785a399277ff8c3e402ce5fa5d8571788e6bad", }, }, }, "spec1": { Items: []imageapi.TagEvent{ { Created: util.Date(2015, 3, 24, 9, 38, 0, 0, time.UTC), DockerImageReference: "registry:5000/foo/bar@sha256:4bd26aef1ce78b4f05ede83496276f11e3343441574ca1ce89dffd146c708c16", Image: "sha256:4bd26aef1ce78b4f05ede83496276f11e3343441574ca1ce89dffd146c708c16", }, }, }, "spec2": { Items: []imageapi.TagEvent{ { Created: util.Date(2015, 3, 24, 9, 38, 0, 0, time.UTC), DockerImageReference: "mysql:latest", Image: "sha256:e52c6534db85036dabac5e71ff14e720db94def2d90f986f3548425ea27b3719", }, }, }, }, }, } out := new(tabwriter.Writer) b := make([]byte, 1024) buf := bytes.NewBuffer(b) out.Init(buf, 0, 8, 1, '\t', 0) formatImageStreamTags(out, &repo) out.Flush() actual := string(buf.String()) t.Logf("\n%s", actual) }