func TestReplicationControllerStop(t *testing.T) { fake := testclient.NewSimpleFake(&api.ReplicationController{ Spec: api.ReplicationControllerSpec{ Replicas: 0, }, }) reaper := ReplicationControllerReaper{fake, time.Millisecond, time.Millisecond} name := "foo" s, err := reaper.Stop("default", name, 0, nil) if err != nil { t.Errorf("unexpected error: %v", err) } expected := "foo stopped" if s != expected { t.Errorf("expected %s, got %s", expected, s) } if len(fake.Actions) != 6 { t.Errorf("unexpected actions: %v, expected 6 actions (get, get, update, get, get, delete)", fake.Actions) } for i, action := range []string{"get", "get", "update", "get", "get", "delete"} { if fake.Actions[i].Action != action+"-replicationController" { t.Errorf("unexpected action: %v, expected %s-replicationController", fake.Actions[i], action) } } }
func TestLimitRangerIgnoresSubresource(t *testing.T) { client := testclient.NewSimpleFake() indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}) handler := &limitRanger{ Handler: admission.NewHandler(admission.Create, admission.Update), client: client, limitFunc: Limit, indexer: indexer, } limitRange := validLimitRangeNoDefaults() testPod := validPod("testPod", 1, api.ResourceRequirements{}) indexer.Add(&limitRange) err := handler.Admit(admission.NewAttributesRecord(&testPod, "Pod", limitRange.Namespace, "testPod", "pods", "", admission.Update, nil)) if err == nil { t.Errorf("Expected an error since the pod did not specify resource limits in its update call") } err = handler.Admit(admission.NewAttributesRecord(&testPod, "Pod", limitRange.Namespace, "testPod", "pods", "status", admission.Update, nil)) if err != nil { t.Errorf("Should have ignored calls to any subresource of pod %v", err) } }
func TestExceedUsagePods(t *testing.T) { namespace := "default" client := testclient.NewSimpleFake(&api.PodList{ Items: []api.Pod{ { ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, Spec: api.PodSpec{ Volumes: []api.Volume{{Name: "vol"}}, Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}}, }, }, }, }) status := &api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } r := api.ResourcePods status.Hard[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1") _, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, "Pod", namespace, "name", "pods", "", admission.Create, nil), status, client) if err == nil { t.Errorf("Expected error because this would exceed your quota") } }
func TestIncrementUsagePods(t *testing.T) { namespace := "default" client := testclient.NewSimpleFake(&api.PodList{ Items: []api.Pod{ { ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, Spec: api.PodSpec{ Volumes: []api.Volume{{Name: "vol"}}, Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}}, }, }, }, }) status := &api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } r := api.ResourcePods status.Hard[r] = resource.MustParse("2") status.Used[r] = resource.MustParse("1") dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, "Pod", namespace, "name", "pods", "", admission.Create, nil), status, client) if err != nil { t.Errorf("Unexpected error: %v", err) } if !dirty { t.Errorf("Expected the status to get incremented, therefore should have been dirty") } quantity := status.Used[r] if quantity.Value() != int64(2) { t.Errorf("Expected new item count to be 2, but was %s", quantity.String()) } }
func TestIncrementUsageReplicationControllers(t *testing.T) { namespace := "default" client := testclient.NewSimpleFake(&api.ReplicationControllerList{ Items: []api.ReplicationController{ { ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, }, }, }) status := &api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } r := api.ResourceReplicationControllers status.Hard[r] = resource.MustParse("2") status.Used[r] = resource.MustParse("1") dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, "ReplicationController", namespace, "name", "replicationControllers", "", admission.Create, nil), status, client) if err != nil { t.Errorf("Unexpected error: %v", err) } if !dirty { t.Errorf("Expected the status to get incremented, therefore should have been dirty") } quantity := status.Used[r] if quantity.Value() != int64(2) { t.Errorf("Expected new item count to be 2, but was %s", quantity.String()) } }
func TestUnboundedMemory(t *testing.T) { namespace := "default" client := testclient.NewSimpleFake(&api.PodList{ Items: []api.Pod{ { ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, Spec: api.PodSpec{ Volumes: []api.Volume{{Name: "vol"}}, Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}}, }, }, }, }) status := &api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } r := api.ResourceMemory status.Hard[r] = resource.MustParse("10Gi") status.Used[r] = resource.MustParse("1Gi") newPod := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, Spec: api.PodSpec{ Volumes: []api.Volume{{Name: "vol"}}, Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("250m", "0")}}, }} _, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "name", "pods", "", admission.Create, nil), status, client) if err == nil { t.Errorf("Expected memory unbounded usage error") } }
// Test the case where the 'ready' file has been created and the pod volume dir // is a mountpoint. Mount should not be called. func TestPluginIdempotent(t *testing.T) { var ( testPodUID = types.UID("test_pod_uid2") testVolumeName = "test_volume_name" testNamespace = "test_secret_namespace" testName = "test_secret_name" volumeSpec = volumeSpec(testVolumeName, testName) secret = secret(testNamespace, testName) client = testclient.NewSimpleFake(&secret) pluginMgr = volume.VolumePluginMgr{} rootDir, host = newTestHost(t, client) ) pluginMgr.InitPlugins(ProbeVolumePlugins(), host) plugin, err := pluginMgr.FindPluginByName(secretPluginName) if err != nil { t.Errorf("Can't find the plugin by name") } podVolumeDir := fmt.Sprintf("%v/pods/test_pod_uid2/volumes/qingyuan~secret/test_volume_name", rootDir) podMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid2/plugins/qingyuan~secret/test_volume_name", rootDir) pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: testPodUID}} mounter := &mount.FakeMounter{} mounter.MountPoints = []mount.MountPoint{ { Path: podVolumeDir, }, } util.SetReady(podMetadataDir) builder, err := plugin.NewBuilder(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}, mounter) if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } if builder == nil { t.Errorf("Got a nil Builder") } volumePath := builder.GetPath() err = builder.SetUp() if err != nil { t.Errorf("Failed to setup volume: %v", err) } if len(mounter.Log) != 0 { t.Errorf("Unexpected calls made to mounter: %v", mounter.Log) } if _, err := os.Stat(volumePath); err != nil { if !os.IsNotExist(err) { t.Errorf("SetUp() failed unexpectedly: %v", err) } } else { t.Errorf("volume path should not exist: %v", volumePath) } }
// Test the case where the plugin's ready file exists, but the volume dir is not a // mountpoint, which is the state the system will be in after reboot. The dir // should be mounter and the secret data written to it. func TestPluginReboot(t *testing.T) { var ( testPodUID = types.UID("test_pod_uid3") testVolumeName = "test_volume_name" testNamespace = "test_secret_namespace" testName = "test_secret_name" volumeSpec = volumeSpec(testVolumeName, testName) secret = secret(testNamespace, testName) client = testclient.NewSimpleFake(&secret) pluginMgr = volume.VolumePluginMgr{} rootDir, host = newTestHost(t, client) ) pluginMgr.InitPlugins(ProbeVolumePlugins(), host) plugin, err := pluginMgr.FindPluginByName(secretPluginName) if err != nil { t.Errorf("Can't find the plugin by name") } pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: testPodUID}} builder, err := plugin.NewBuilder(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}, &mount.FakeMounter{}) if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } if builder == nil { t.Errorf("Got a nil Builder") } podMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid3/plugins/qingyuan~secret/test_volume_name", rootDir) util.SetReady(podMetadataDir) volumePath := builder.GetPath() if !strings.HasSuffix(volumePath, fmt.Sprintf("pods/test_pod_uid3/volumes/qingyuan~secret/test_volume_name")) { t.Errorf("Got unexpected path: %s", volumePath) } err = builder.SetUp() if err != nil { t.Errorf("Failed to setup volume: %v", err) } if _, err := os.Stat(volumePath); err != nil { if os.IsNotExist(err) { t.Errorf("SetUp() failed, volume path not created: %s", volumePath) } else { t.Errorf("SetUp() failed: %v", err) } } doTestSecretDataInVolume(volumePath, secret, t) doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) }
func TestDeniesInvalidServiceAccount(t *testing.T) { ns := "myns" // Build a test client that the admission plugin can use to look up the service account missing from its cache client := testclient.NewSimpleFake() admit := NewServiceAccount(client) pod := &api.Pod{} attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", string(api.ResourcePods), "", admission.Create, nil) err := admit.Admit(attrs) if err == nil { t.Errorf("Expected error for missing service account, got none") } }
func TestDescribeService(t *testing.T) { fake := testclient.NewSimpleFake(&api.Service{ ObjectMeta: api.ObjectMeta{ Name: "bar", Namespace: "foo", }, }) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := ServiceDescriber{c} out, err := d.Describe("foo", "bar") if err != nil { t.Errorf("unexpected error: %v", err) } if !strings.Contains(out, "Labels:") || !strings.Contains(out, "bar") { t.Errorf("unexpected out: %s", out) } }
func TestEndpointsFromZero(t *testing.T) { endpoint := api.Endpoints{ ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}, Subsets: []api.EndpointSubset{{ Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, Ports: []api.EndpointPort{{Port: 9000}}, }}, } fakeWatch := watch.NewFake() fakeWatch.Stop() fakeClient := testclient.NewSimpleFake(&api.EndpointsList{ ListMeta: api.ListMeta{ResourceVersion: "2"}, Items: []api.Endpoints{ endpoint, }, }) fakeClient.Watch = fakeWatch endpoints := make(chan EndpointsUpdate) source := SourceAPI{ s: servicesReflector{watcher: fakeClient.Services(api.NamespaceAll)}, e: endpointsReflector{watcher: fakeClient.Endpoints(api.NamespaceAll), endpoints: endpoints}} resourceVersion := "" ch := make(chan struct{}) go func() { source.e.run(&resourceVersion) close(ch) }() // should get endpoints SET actual := <-endpoints expected := EndpointsUpdate{Op: SET, Endpoints: []api.Endpoints{endpoint}} if !reflect.DeepEqual(expected, actual) { t.Errorf("expected %#v, got %#v", expected, actual) } // should have listed, then watched <-ch if resourceVersion != "2" { t.Errorf("unexpected resource version, got %#v", resourceVersion) } if !reflect.DeepEqual(fakeClient.Actions, []testclient.FakeAction{{"list-endpoints", nil}, {"watch-endpoints", "2"}}) { t.Errorf("unexpected actions, got %#v", fakeClient) } }
func TestExceedUsagePersistentVolumeClaims(t *testing.T) { namespace := "default" client := testclient.NewSimpleFake(&api.PersistentVolumeClaimList{ Items: []api.PersistentVolumeClaim{ { ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, }, }, }) status := &api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } r := api.ResourcePersistentVolumeClaims status.Hard[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1") _, err := IncrementUsage(admission.NewAttributesRecord(&api.PersistentVolumeClaim{}, "PersistentVolumeClaim", namespace, "name", "persistentVolumeClaims", "", admission.Create, nil), status, client) if err == nil { t.Errorf("Expected error for exceeding hard limits") } }
func TestExceedUsageServices(t *testing.T) { namespace := "default" client := testclient.NewSimpleFake(&api.ServiceList{ Items: []api.Service{ { ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, }, }, }) status := &api.ResourceQuotaStatus{ Hard: api.ResourceList{}, Used: api.ResourceList{}, } r := api.ResourceServices status.Hard[r] = resource.MustParse("1") status.Used[r] = resource.MustParse("1") _, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, "Service", namespace, "name", "services", "", admission.Create, nil), status, client) if err == nil { t.Errorf("Expected error because this would exceed usage") } }
func TestServicesFromZero(t *testing.T) { service := api.Service{ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}} fakeWatch := watch.NewFake() fakeWatch.Stop() fakeClient := testclient.NewSimpleFake(&api.ServiceList{ ListMeta: api.ListMeta{ResourceVersion: "2"}, Items: []api.Service{ service, }, }) fakeClient.Watch = fakeWatch services := make(chan ServiceUpdate) source := SourceAPI{ s: servicesReflector{watcher: fakeClient.Services(api.NamespaceAll), services: services}, e: endpointsReflector{watcher: fakeClient.Endpoints(api.NamespaceAll)}} resourceVersion := "" ch := make(chan struct{}) go func() { source.s.run(&resourceVersion) close(ch) }() // should get services SET actual := <-services expected := ServiceUpdate{Op: SET, Services: []api.Service{service}} if !reflect.DeepEqual(expected, actual) { t.Errorf("expected %#v, got %#v", expected, actual) } // should have listed, then watched <-ch if resourceVersion != "2" { t.Errorf("unexpected resource version, got %#v", resourceVersion) } if !reflect.DeepEqual(fakeClient.Actions, []testclient.FakeAction{{"list-services", nil}, {"watch-services", "2"}}) { t.Errorf("unexpected actions, got %#v", fakeClient) } }
func TestPodDescribeResultsSorted(t *testing.T) { // Arrange fake := testclient.NewSimpleFake(&api.EventList{ Items: []api.Event{ { Source: api.EventSource{Component: "qinglet"}, Message: "Item 1", FirstTimestamp: util.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), LastTimestamp: util.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), Count: 1, }, { Source: api.EventSource{Component: "scheduler"}, Message: "Item 2", FirstTimestamp: util.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)), LastTimestamp: util.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)), Count: 1, }, { Source: api.EventSource{Component: "qinglet"}, Message: "Item 3", FirstTimestamp: util.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)), LastTimestamp: util.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)), Count: 1, }, }, }) c := &describeClient{T: t, Namespace: "foo", Interface: fake} d := PodDescriber{c} // Act out, err := d.Describe("foo", "bar") // Assert if err != nil { t.Errorf("unexpected error: %v", err) } VerifyDatesInOrder(out, "\n" /* rowDelimiter */, "\t" /* columnDelimiter */, t) }
func TestFetchesUncachedServiceAccount(t *testing.T) { ns := "myns" // Build a test client that the admission plugin can use to look up the service account missing from its cache client := testclient.NewSimpleFake(&api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: DefaultServiceAccountName, Namespace: ns, }, }) admit := NewServiceAccount(client) pod := &api.Pod{} attrs := admission.NewAttributesRecord(pod, "Pod", ns, "myname", string(api.ResourcePods), "", admission.Create, nil) err := admit.Admit(attrs) if err != nil { t.Errorf("Unexpected error: %v", err) } if pod.Spec.ServiceAccountName != DefaultServiceAccountName { t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName) } }
func TestTokenGenerateAndValidate(t *testing.T) { expectedUserName := "******" expectedUserUID := "12345" // Related API objects serviceAccount := &api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: "my-service-account", UID: "12345", Namespace: "test", }, } secret := &api.Secret{ ObjectMeta: api.ObjectMeta{ Name: "my-secret", Namespace: "test", }, } // Generate the token generator := JWTTokenGenerator(getPrivateKey(privateKey)) token, err := generator.GenerateToken(*serviceAccount, *secret) if err != nil { t.Fatalf("error generating token: %v", err) } if len(token) == 0 { t.Fatalf("no token generated") } // "Save" the token secret.Data = map[string][]byte{ "token": []byte(token), } testCases := map[string]struct { Client client.Interface Keys []*rsa.PublicKey ExpectedErr bool ExpectedOK bool ExpectedUserName string ExpectedUserUID string }{ "no keys": { Client: nil, Keys: []*rsa.PublicKey{}, ExpectedErr: false, ExpectedOK: false, }, "invalid keys": { Client: nil, Keys: []*rsa.PublicKey{getPublicKey(otherPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "valid key": { Client: nil, Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, }, "rotated keys": { Client: nil, Keys: []*rsa.PublicKey{getPublicKey(otherPublicKey), getPublicKey(publicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, }, "valid lookup": { Client: testclient.NewSimpleFake(serviceAccount, secret), Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, }, "invalid secret lookup": { Client: testclient.NewSimpleFake(serviceAccount), Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: true, ExpectedOK: false, }, "invalid serviceaccount lookup": { Client: testclient.NewSimpleFake(secret), Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, ExpectedErr: true, ExpectedOK: false, }, } for k, tc := range testCases { getter := NewGetterFromClient(tc.Client) authenticator := JWTTokenAuthenticator(tc.Keys, tc.Client != nil, getter) user, ok, err := authenticator.AuthenticateToken(token) if (err != nil) != tc.ExpectedErr { t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err) continue } if ok != tc.ExpectedOK { t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok) continue } if err != nil || !ok { continue } if user.GetName() != tc.ExpectedUserName { t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, user.GetName()) continue } if user.GetUID() != tc.ExpectedUserUID { t.Errorf("%s: Expected userUID=%v, got %v", k, tc.ExpectedUserUID, user.GetUID()) continue } } }
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 qinglet 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 qinglet posted NotReady for a short period of time.", }, // Node created long time ago, and qinglet 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 qinglet 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, 10, 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.Action == "delete-pod" { podEvicted = true } } if item.expectedEvictPods != podEvicted { t.Errorf("expected pod eviction: %+v, got %+v for %+v", item.expectedEvictPods, podEvicted, item.description) } } }
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("Qinglet 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 qinglet 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("Qinglet 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, 10, 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 TestServiceAccountCreation(t *testing.T) { ns := api.NamespaceDefault defaultName := "default" managedName := "managed" activeNS := &api.Namespace{ ObjectMeta: api.ObjectMeta{Name: ns}, Status: api.NamespaceStatus{ Phase: api.NamespaceActive, }, } terminatingNS := &api.Namespace{ ObjectMeta: api.ObjectMeta{Name: ns}, Status: api.NamespaceStatus{ Phase: api.NamespaceTerminating, }, } defaultServiceAccount := &api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: defaultName, Namespace: ns, ResourceVersion: "1", }, } managedServiceAccount := &api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: managedName, Namespace: ns, ResourceVersion: "1", }, } unmanagedServiceAccount := &api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Name: "other-unmanaged", Namespace: ns, ResourceVersion: "1", }, } testcases := map[string]struct { ExistingNamespace *api.Namespace ExistingServiceAccounts []*api.ServiceAccount AddedNamespace *api.Namespace UpdatedNamespace *api.Namespace DeletedServiceAccount *api.ServiceAccount ExpectCreatedServiceAccounts []string }{ "new active namespace missing serviceaccounts": { ExistingServiceAccounts: []*api.ServiceAccount{}, AddedNamespace: activeNS, ExpectCreatedServiceAccounts: util.NewStringSet(defaultName, managedName).List(), }, "new active namespace missing serviceaccount": { ExistingServiceAccounts: []*api.ServiceAccount{managedServiceAccount}, AddedNamespace: activeNS, ExpectCreatedServiceAccounts: []string{defaultName}, }, "new active namespace with serviceaccounts": { ExistingServiceAccounts: []*api.ServiceAccount{defaultServiceAccount, managedServiceAccount}, AddedNamespace: activeNS, ExpectCreatedServiceAccounts: []string{}, }, "new terminating namespace": { ExistingServiceAccounts: []*api.ServiceAccount{}, AddedNamespace: terminatingNS, ExpectCreatedServiceAccounts: []string{}, }, "updated active namespace missing serviceaccounts": { ExistingServiceAccounts: []*api.ServiceAccount{}, UpdatedNamespace: activeNS, ExpectCreatedServiceAccounts: util.NewStringSet(defaultName, managedName).List(), }, "updated active namespace missing serviceaccount": { ExistingServiceAccounts: []*api.ServiceAccount{defaultServiceAccount}, UpdatedNamespace: activeNS, ExpectCreatedServiceAccounts: []string{managedName}, }, "updated active namespace with serviceaccounts": { ExistingServiceAccounts: []*api.ServiceAccount{defaultServiceAccount, managedServiceAccount}, UpdatedNamespace: activeNS, ExpectCreatedServiceAccounts: []string{}, }, "updated terminating namespace": { ExistingServiceAccounts: []*api.ServiceAccount{}, UpdatedNamespace: terminatingNS, ExpectCreatedServiceAccounts: []string{}, }, "deleted serviceaccount without namespace": { DeletedServiceAccount: defaultServiceAccount, ExpectCreatedServiceAccounts: []string{}, }, "deleted serviceaccount with active namespace": { ExistingNamespace: activeNS, DeletedServiceAccount: defaultServiceAccount, ExpectCreatedServiceAccounts: []string{defaultName}, }, "deleted serviceaccount with terminating namespace": { ExistingNamespace: terminatingNS, DeletedServiceAccount: defaultServiceAccount, ExpectCreatedServiceAccounts: []string{}, }, "deleted unmanaged serviceaccount with active namespace": { ExistingNamespace: activeNS, DeletedServiceAccount: unmanagedServiceAccount, ExpectCreatedServiceAccounts: []string{}, }, "deleted unmanaged serviceaccount with terminating namespace": { ExistingNamespace: terminatingNS, DeletedServiceAccount: unmanagedServiceAccount, ExpectCreatedServiceAccounts: []string{}, }, } for k, tc := range testcases { client := testclient.NewSimpleFake(defaultServiceAccount, managedServiceAccount) options := DefaultServiceAccountsControllerOptions() options.Names = util.NewStringSet(defaultName, managedName) controller := NewServiceAccountsController(client, options) if tc.ExistingNamespace != nil { controller.namespaces.Add(tc.ExistingNamespace) } for _, s := range tc.ExistingServiceAccounts { controller.serviceAccounts.Add(s) } if tc.AddedNamespace != nil { controller.namespaces.Add(tc.AddedNamespace) controller.namespaceAdded(tc.AddedNamespace) } if tc.UpdatedNamespace != nil { controller.namespaces.Add(tc.UpdatedNamespace) controller.namespaceUpdated(nil, tc.UpdatedNamespace) } if tc.DeletedServiceAccount != nil { controller.serviceAccountDeleted(tc.DeletedServiceAccount) } if len(tc.ExpectCreatedServiceAccounts) != len(client.Actions) { t.Errorf("%s: Expected to create accounts %#v. Actual actions were: %#v", k, tc.ExpectCreatedServiceAccounts, client.Actions) continue } for i, expectedName := range tc.ExpectCreatedServiceAccounts { action := client.Actions[i] if action.Action != "create-serviceaccount" { t.Errorf("%s: Unexpected action %s", k, action.Action) break } createdAccount := action.Value.(*api.ServiceAccount) if createdAccount.Name != expectedName { t.Errorf("%s: Expected %s to be created, got %s", k, expectedName, createdAccount.Name) } } } }
func TestTokenCreation(t *testing.T) { testcases := map[string]struct { ClientObjects []runtime.Object SecretsSyncPending bool ServiceAccountsSyncPending bool ExistingServiceAccount *api.ServiceAccount ExistingSecrets []*api.Secret AddedServiceAccount *api.ServiceAccount UpdatedServiceAccount *api.ServiceAccount DeletedServiceAccount *api.ServiceAccount AddedSecret *api.Secret UpdatedSecret *api.Secret DeletedSecret *api.Secret ExpectedActions []testclient.FakeAction }{ "new serviceaccount with no secrets": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, AddedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []testclient.FakeAction{ {Action: "create-secret", Value: createdTokenSecret()}, {Action: "get-serviceaccount", Value: "default"}, {Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(emptySecretReferences()))}, }, }, "new serviceaccount with no secrets with unsynced secret store": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, SecretsSyncPending: true, AddedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []testclient.FakeAction{ {Action: "create-secret", Value: createdTokenSecret()}, {Action: "get-serviceaccount", Value: "default"}, {Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(emptySecretReferences()))}, }, }, "new serviceaccount with missing secrets": { ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, AddedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []testclient.FakeAction{ {Action: "create-secret", Value: createdTokenSecret()}, {Action: "get-serviceaccount", Value: "default"}, {Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(missingSecretReferences()))}, }, }, "new serviceaccount with missing secrets with unsynced secret store": { ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, SecretsSyncPending: true, AddedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []testclient.FakeAction{}, }, "new serviceaccount with non-token secrets": { ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()}, AddedServiceAccount: serviceAccount(regularSecretReferences()), ExpectedActions: []testclient.FakeAction{ {Action: "create-secret", Value: createdTokenSecret()}, {Action: "get-serviceaccount", Value: "default"}, {Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(regularSecretReferences()))}, }, }, "new serviceaccount with token secrets": { ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences()), serviceAccountTokenSecret()}, ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, AddedServiceAccount: serviceAccount(tokenSecretReferences()), ExpectedActions: []testclient.FakeAction{}, }, "updated serviceaccount with no secrets": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, UpdatedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []testclient.FakeAction{ {Action: "create-secret", Value: createdTokenSecret()}, {Action: "get-serviceaccount", Value: "default"}, {Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(emptySecretReferences()))}, }, }, "updated serviceaccount with no secrets with unsynced secret store": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, SecretsSyncPending: true, UpdatedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []testclient.FakeAction{ {Action: "create-secret", Value: createdTokenSecret()}, {Action: "get-serviceaccount", Value: "default"}, {Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(emptySecretReferences()))}, }, }, "updated serviceaccount with missing secrets": { ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, UpdatedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []testclient.FakeAction{ {Action: "create-secret", Value: createdTokenSecret()}, {Action: "get-serviceaccount", Value: "default"}, {Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(missingSecretReferences()))}, }, }, "updated serviceaccount with missing secrets with unsynced secret store": { ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, SecretsSyncPending: true, UpdatedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []testclient.FakeAction{}, }, "updated serviceaccount with non-token secrets": { ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()}, UpdatedServiceAccount: serviceAccount(regularSecretReferences()), ExpectedActions: []testclient.FakeAction{ {Action: "create-secret", Value: createdTokenSecret()}, {Action: "get-serviceaccount", Value: "default"}, {Action: "update-serviceaccount", Value: serviceAccount(addTokenSecretReference(regularSecretReferences()))}, }, }, "updated serviceaccount with token secrets": { ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, UpdatedServiceAccount: serviceAccount(tokenSecretReferences()), ExpectedActions: []testclient.FakeAction{}, }, "deleted serviceaccount with no secrets": { DeletedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []testclient.FakeAction{}, }, "deleted serviceaccount with missing secrets": { DeletedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []testclient.FakeAction{}, }, "deleted serviceaccount with non-token secrets": { ClientObjects: []runtime.Object{opaqueSecret()}, DeletedServiceAccount: serviceAccount(regularSecretReferences()), ExpectedActions: []testclient.FakeAction{}, }, "deleted serviceaccount with token secrets": { ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, DeletedServiceAccount: serviceAccount(tokenSecretReferences()), ExpectedActions: []testclient.FakeAction{ {Action: "delete-secret", Value: "token-secret-1"}, }, }, "added secret without serviceaccount": { ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, AddedSecret: serviceAccountTokenSecret(), ExpectedActions: []testclient.FakeAction{ {Action: "get-serviceaccount", Value: "default"}, {Action: "delete-secret", Value: "token-secret-1"}, }, }, "added secret with serviceaccount": { ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecret(), ExpectedActions: []testclient.FakeAction{}, }, "added token secret without token data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecretWithoutTokenData(), ExpectedActions: []testclient.FakeAction{ {Action: "update-secret", Value: serviceAccountTokenSecret()}, }, }, "updated secret without serviceaccount": { ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, UpdatedSecret: serviceAccountTokenSecret(), ExpectedActions: []testclient.FakeAction{ {Action: "get-serviceaccount", Value: "default"}, {Action: "delete-secret", Value: "token-secret-1"}, }, }, "updated secret with serviceaccount": { ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecret(), ExpectedActions: []testclient.FakeAction{}, }, "updated token secret without token data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecretWithoutTokenData(), ExpectedActions: []testclient.FakeAction{ {Action: "update-secret", Value: serviceAccountTokenSecret()}, }, }, "deleted secret without serviceaccount": { DeletedSecret: serviceAccountTokenSecret(), ExpectedActions: []testclient.FakeAction{}, }, "deleted secret with serviceaccount with reference": { ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences())}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), DeletedSecret: serviceAccountTokenSecret(), ExpectedActions: []testclient.FakeAction{ {Action: "get-serviceaccount", Value: "default"}, {Action: "update-serviceaccount", Value: serviceAccount(emptySecretReferences())}, }, }, "deleted secret with serviceaccount without reference": { ExistingServiceAccount: serviceAccount(emptySecretReferences()), DeletedSecret: serviceAccountTokenSecret(), ExpectedActions: []testclient.FakeAction{}, }, } for k, tc := range testcases { // Re-seed to reset name generation rand.Seed(1) generator := &testGenerator{Token: "ABC"} client := testclient.NewSimpleFake(tc.ClientObjects...) controller := NewTokensController(client, DefaultTokenControllerOptions(generator)) // Tell the token controller whether its stores have been synced controller.serviceAccountsSynced = func() bool { return !tc.ServiceAccountsSyncPending } controller.secretsSynced = func() bool { return !tc.SecretsSyncPending } if tc.ExistingServiceAccount != nil { controller.serviceAccounts.Add(tc.ExistingServiceAccount) } for _, s := range tc.ExistingSecrets { controller.secrets.Add(s) } if tc.AddedServiceAccount != nil { controller.serviceAccountAdded(tc.AddedServiceAccount) } if tc.UpdatedServiceAccount != nil { controller.serviceAccountUpdated(nil, tc.UpdatedServiceAccount) } if tc.DeletedServiceAccount != nil { controller.serviceAccountDeleted(tc.DeletedServiceAccount) } if tc.AddedSecret != nil { controller.secretAdded(tc.AddedSecret) } if tc.UpdatedSecret != nil { controller.secretUpdated(nil, tc.UpdatedSecret) } if tc.DeletedSecret != nil { controller.secretDeleted(tc.DeletedSecret) } for i, action := range client.Actions { if len(tc.ExpectedActions) < i+1 { t.Errorf("%s: %d unexpected actions: %+v", k, len(client.Actions)-len(tc.ExpectedActions), client.Actions[i:]) break } expectedAction := tc.ExpectedActions[i] if expectedAction.Action != action.Action { t.Errorf("%s: Expected %s, got %s", k, expectedAction.Action, action.Action) continue } if !reflect.DeepEqual(expectedAction.Value, action.Value) { t.Errorf("%s: Expected\n\t%#v\ngot\n\t%#v", k, expectedAction.Value, action.Value) continue } } if len(tc.ExpectedActions) > len(client.Actions) { t.Errorf("%s: %d additional expected actions:%+v", k, len(tc.ExpectedActions)-len(client.Actions), tc.ExpectedActions[len(client.Actions):]) } } }