// Outputs linesTotal lines of logs to stdout uniformly for duration func generateLogs(linesTotal int, duration time.Duration) { delay := duration / time.Duration(linesTotal) rand.Seed(time.Now().UnixNano()) tick := time.Tick(delay) for id := 0; id < linesTotal; id++ { glog.Info(generateLogLine(id)) <-tick } }
func (util *FlockerUtil) CreateVolume(c *flockerVolumeProvisioner) (datasetUUID string, volumeSizeGB int, labels map[string]string, err error) { if c.flockerClient == nil { c.flockerClient, err = c.plugin.newFlockerClient("") if err != nil { return } } nodes, err := c.flockerClient.ListNodes() if err != nil { return } if len(nodes) < 1 { err = fmt.Errorf("no nodes found inside the flocker cluster to provision a dataset") return } // select random node rand.Seed(time.Now().UTC().UnixNano()) node := nodes[rand.Intn(len(nodes))] glog.V(2).Infof("selected flocker node with UUID '%s' to provision dataset", node.UUID) capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] requestBytes := capacity.Value() volumeSizeGB = int(volume.RoundUpSize(requestBytes, 1024*1024*1024)) createOptions := &flockerApi.CreateDatasetOptions{ MaximumSize: requestBytes, Metadata: map[string]string{ "type": "k8s-dynamic-prov", "pvc": c.options.PVC.Name, }, Primary: node.UUID, } datasetState, err := c.flockerClient.CreateDataset(createOptions) if err != nil { return } datasetUUID = datasetState.DatasetID glog.V(2).Infof("successfully created Flocker dataset with UUID '%s'", datasetUUID) return }
func TestRestrictTargetPool(t *testing.T) { const maxInstances = 5 tests := []struct { instances []string want []string }{ { instances: []string{"1", "2", "3", "4", "5"}, want: []string{"1", "2", "3", "4", "5"}, }, { instances: []string{"1", "2", "3", "4", "5", "6"}, want: []string{"4", "3", "5", "2", "6"}, }, } for _, tc := range tests { rand.Seed(5) got := restrictTargetPool(append([]string{}, tc.instances...), maxInstances) if !reflect.DeepEqual(got, tc.want) { t.Errorf("restrictTargetPool(%v) => %v, want %v", tc.instances, got, tc.want) } } }
func TestComputeUpdate(t *testing.T) { const maxInstances = 5 const fakeZone = "us-moon1-f" tests := []struct { tp []string instances []string wantToAdd []string wantToRemove []string }{ { // Test adding all instances. tp: []string{}, instances: []string{"0", "1", "2"}, wantToAdd: []string{"0", "1", "2"}, wantToRemove: []string{}, }, { // Test node 1 coming back healthy. tp: []string{"0", "2"}, instances: []string{"0", "1", "2"}, wantToAdd: []string{"1"}, wantToRemove: []string{}, }, { // Test node 1 going healthy while node 4 needs to be removed. tp: []string{"0", "2", "4"}, instances: []string{"0", "1", "2"}, wantToAdd: []string{"1"}, wantToRemove: []string{"4"}, }, { // Test exceeding the TargetPool max of 5 (for the test), // which shuffles in 7, 5, 8 based on the deterministic // seed below. tp: []string{"0", "2", "4", "6"}, instances: []string{"0", "1", "2", "3", "5", "7", "8"}, wantToAdd: []string{"7", "5", "8"}, wantToRemove: []string{"4", "6"}, }, { // Test all nodes getting removed. tp: []string{"0", "1", "2", "3"}, instances: []string{}, wantToAdd: []string{}, wantToRemove: []string{"0", "1", "2", "3"}, }, } for _, tc := range tests { rand.Seed(5) // Arbitrary RNG seed for deterministic testing. // Dummy up the gceInstance slice. var instances []*gceInstance for _, inst := range tc.instances { instances = append(instances, &gceInstance{Name: inst, Zone: fakeZone}) } // Dummy up the TargetPool URL list. var urls []string for _, inst := range tc.tp { inst := &gceInstance{Name: inst, Zone: fakeZone} urls = append(urls, inst.makeComparableHostPath()) } gotAddInsts, gotRem := computeUpdate(&compute.TargetPool{Instances: urls}, instances, maxInstances) var wantAdd []string for _, inst := range tc.wantToAdd { inst := &gceInstance{Name: inst, Zone: fakeZone} wantAdd = append(wantAdd, inst.makeComparableHostPath()) } var gotAdd []string for _, inst := range gotAddInsts { gotAdd = append(gotAdd, inst.Instance) } if !reflect.DeepEqual(wantAdd, gotAdd) { t.Errorf("computeTargetPool(%v, %v) => added %v, wanted %v", tc.tp, tc.instances, gotAdd, wantAdd) } _ = gotRem // var gotRem []string // for _, inst := range gotRemInsts { // gotRem = append(gotRem, inst.Instance) // } // if !reflect.DeepEqual(tc.wantToRemove, gotRem) { // t.Errorf("computeTargetPool(%v, %v) => removed %v, wanted %v", tc.tp, tc.instances, gotRem, tc.wantToRemove) // } } }
func TestTokenCreation(t *testing.T) { testcases := map[string]struct { ClientObjects []runtime.Object IsAsync bool MaxRetries int Reactors []reaction 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 []core.Action }{ "new serviceaccount with no secrets": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, AddedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), }, }, "new serviceaccount with no secrets encountering create error": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, MaxRetries: 10, IsAsync: true, Reactors: []reaction{{ verb: "create", resource: "secrets", reactor: func(t *testing.T) core.ReactionFunc { i := 0 return func(core.Action) (bool, runtime.Object, error) { i++ if i < 3 { return true, nil, apierrors.NewForbidden(api.Resource("secrets"), "foo", errors.New("No can do")) } return false, nil, nil } }, }}, AddedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ // Attempt 1 core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), // Attempt 2 core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, namedCreatedTokenSecret("default-token-gziey")), // Attempt 3 core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, namedCreatedTokenSecret("default-token-oh43e")), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addNamedTokenSecretReference(emptySecretReferences(), "default-token-oh43e"))), }, }, "new serviceaccount with no secrets encountering unending create error": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, MaxRetries: 2, IsAsync: true, Reactors: []reaction{{ verb: "create", resource: "secrets", reactor: func(t *testing.T) core.ReactionFunc { return func(core.Action) (bool, runtime.Object, error) { return true, nil, apierrors.NewForbidden(api.Resource("secrets"), "foo", errors.New("No can do")) } }, }}, AddedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ // Attempt core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), // Retry 1 core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, namedCreatedTokenSecret("default-token-gziey")), // Retry 2 core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, namedCreatedTokenSecret("default-token-oh43e")), }, }, "new serviceaccount with missing secrets": { ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, AddedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))), }, }, "new serviceaccount with non-token secrets": { ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()}, AddedServiceAccount: serviceAccount(regularSecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(regularSecretReferences()))), }, }, "new serviceaccount with token secrets": { ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences()), serviceAccountTokenSecret()}, ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, AddedServiceAccount: serviceAccount(tokenSecretReferences()), ExpectedActions: []core.Action{}, }, "new serviceaccount with no secrets with resource conflict": { ClientObjects: []runtime.Object{updatedServiceAccount(emptySecretReferences()), createdTokenSecret()}, AddedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), }, }, "updated serviceaccount with no secrets": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, UpdatedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), }, }, "updated serviceaccount with missing secrets": { ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, UpdatedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))), }, }, "updated serviceaccount with non-token secrets": { ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()}, UpdatedServiceAccount: serviceAccount(regularSecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(regularSecretReferences()))), }, }, "updated serviceaccount with token secrets": { ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, UpdatedServiceAccount: serviceAccount(tokenSecretReferences()), ExpectedActions: []core.Action{}, }, "updated serviceaccount with no secrets with resource conflict": { ClientObjects: []runtime.Object{updatedServiceAccount(emptySecretReferences()), createdTokenSecret()}, UpdatedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), }, }, "deleted serviceaccount with no secrets": { DeletedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{}, }, "deleted serviceaccount with missing secrets": { DeletedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []core.Action{}, }, "deleted serviceaccount with non-token secrets": { ClientObjects: []runtime.Object{opaqueSecret()}, DeletedServiceAccount: serviceAccount(regularSecretReferences()), ExpectedActions: []core.Action{}, }, "deleted serviceaccount with token secrets": { ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, DeletedServiceAccount: serviceAccount(tokenSecretReferences()), ExpectedActions: []core.Action{ core.NewDeleteAction("secrets", api.NamespaceDefault, "token-secret-1"), }, }, "added secret without serviceaccount": { ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, AddedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewDeleteAction("secrets", api.NamespaceDefault, "token-secret-1"), }, }, "added secret with serviceaccount": { ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{}, }, "added token secret without token data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecretWithoutTokenData(), ExpectedActions: []core.Action{ core.NewGetAction("secrets", api.NamespaceDefault, "token-secret-1"), core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "added token secret without ca data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecretWithoutCAData(), ExpectedActions: []core.Action{ core.NewGetAction("secrets", api.NamespaceDefault, "token-secret-1"), core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "added token secret with mismatched ca data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")), ExpectedActions: []core.Action{ core.NewGetAction("secrets", api.NamespaceDefault, "token-secret-1"), core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "added token secret without namespace data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecretWithoutNamespaceData(), ExpectedActions: []core.Action{ core.NewGetAction("secrets", api.NamespaceDefault, "token-secret-1"), core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "added token secret with custom namespace data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")), ExpectedActions: []core.Action{ // no update is performed... the custom namespace is preserved }, }, "updated secret without serviceaccount": { ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, UpdatedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewDeleteAction("secrets", api.NamespaceDefault, "token-secret-1"), }, }, "updated secret with serviceaccount": { ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{}, }, "updated token secret without token data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecretWithoutTokenData(), ExpectedActions: []core.Action{ core.NewGetAction("secrets", api.NamespaceDefault, "token-secret-1"), core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "updated token secret without ca data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecretWithoutCAData(), ExpectedActions: []core.Action{ core.NewGetAction("secrets", api.NamespaceDefault, "token-secret-1"), core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "updated token secret with mismatched ca data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")), ExpectedActions: []core.Action{ core.NewGetAction("secrets", api.NamespaceDefault, "token-secret-1"), core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "updated token secret without namespace data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecretWithoutNamespaceData(), ExpectedActions: []core.Action{ core.NewGetAction("secrets", api.NamespaceDefault, "token-secret-1"), core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "updated token secret with custom namespace data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")), ExpectedActions: []core.Action{ // no update is performed... the custom namespace is preserved }, }, "deleted secret without serviceaccount": { DeletedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{}, }, "deleted secret with serviceaccount with reference": { ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences())}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), DeletedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(emptySecretReferences())), }, }, "deleted secret with serviceaccount without reference": { ExistingServiceAccount: serviceAccount(emptySecretReferences()), DeletedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), }, }, } for k, tc := range testcases { glog.Infof(k) // Re-seed to reset name generation utilrand.Seed(1) generator := &testGenerator{Token: "ABC"} client := fake.NewSimpleClientset(tc.ClientObjects...) for _, reactor := range tc.Reactors { client.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactor(t)) } controller := NewTokensController(client, TokensControllerOptions{TokenGenerator: generator, RootCA: []byte("CA Data"), MaxRetries: tc.MaxRetries}) if tc.ExistingServiceAccount != nil { controller.serviceAccounts.Add(tc.ExistingServiceAccount) } for _, s := range tc.ExistingSecrets { controller.secrets.Add(s) } if tc.AddedServiceAccount != nil { controller.serviceAccounts.Add(tc.AddedServiceAccount) controller.queueServiceAccountSync(tc.AddedServiceAccount) } if tc.UpdatedServiceAccount != nil { controller.serviceAccounts.Add(tc.UpdatedServiceAccount) controller.queueServiceAccountUpdateSync(nil, tc.UpdatedServiceAccount) } if tc.DeletedServiceAccount != nil { controller.serviceAccounts.Delete(tc.DeletedServiceAccount) controller.queueServiceAccountSync(tc.DeletedServiceAccount) } if tc.AddedSecret != nil { controller.secrets.Add(tc.AddedSecret) controller.queueSecretSync(tc.AddedSecret) } if tc.UpdatedSecret != nil { controller.secrets.Add(tc.UpdatedSecret) controller.queueSecretUpdateSync(nil, tc.UpdatedSecret) } if tc.DeletedSecret != nil { controller.secrets.Delete(tc.DeletedSecret) controller.queueSecretSync(tc.DeletedSecret) } // This is the longest we'll wait for async tests timeout := time.Now().Add(30 * time.Second) waitedForAdditionalActions := false for { if controller.syncServiceAccountQueue.Len() > 0 { controller.syncServiceAccount() } if controller.syncSecretQueue.Len() > 0 { controller.syncSecret() } // The queues still have things to work on if controller.syncServiceAccountQueue.Len() > 0 || controller.syncSecretQueue.Len() > 0 { continue } // If we expect this test to work asynchronously... if tc.IsAsync { // if we're still missing expected actions within our test timeout if len(client.Actions()) < len(tc.ExpectedActions) && time.Now().Before(timeout) { // wait for the expected actions (without hotlooping) time.Sleep(time.Millisecond) continue } // if we exactly match our expected actions, wait a bit to make sure no other additional actions show up if len(client.Actions()) == len(tc.ExpectedActions) && !waitedForAdditionalActions { time.Sleep(time.Second) waitedForAdditionalActions = true continue } } break } if controller.syncServiceAccountQueue.Len() > 0 { t.Errorf("%s: unexpected items in service account queue: %d", k, controller.syncServiceAccountQueue.Len()) } if controller.syncSecretQueue.Len() > 0 { t.Errorf("%s: unexpected items in secret queue: %d", k, controller.syncSecretQueue.Len()) } actions := client.Actions() for i, action := range actions { if len(tc.ExpectedActions) < i+1 { t.Errorf("%s: %d unexpected actions: %+v", k, len(actions)-len(tc.ExpectedActions), actions[i:]) break } expectedAction := tc.ExpectedActions[i] if !reflect.DeepEqual(expectedAction, action) { t.Errorf("%s: Expected\n\t%#v\ngot\n\t%#v", k, expectedAction, action) continue } } if len(tc.ExpectedActions) > len(actions) { t.Errorf("%s: %d additional expected actions", k, len(tc.ExpectedActions)-len(actions)) for _, a := range tc.ExpectedActions[len(actions):] { t.Logf(" %+v", a) } } } }
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 []core.Action }{ "new serviceaccount with no secrets": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, AddedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), }, }, "new serviceaccount with no secrets with unsynced secret store": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, SecretsSyncPending: true, AddedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), }, }, "new serviceaccount with missing secrets": { ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, AddedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))), }, }, "new serviceaccount with missing secrets with unsynced secret store": { ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, SecretsSyncPending: true, AddedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []core.Action{}, }, "new serviceaccount with non-token secrets": { ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()}, AddedServiceAccount: serviceAccount(regularSecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(regularSecretReferences()))), }, }, "new serviceaccount with token secrets": { ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences()), serviceAccountTokenSecret()}, ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, AddedServiceAccount: serviceAccount(tokenSecretReferences()), ExpectedActions: []core.Action{}, }, "new serviceaccount with no secrets with resource conflict": { ClientObjects: []runtime.Object{updatedServiceAccount(emptySecretReferences()), createdTokenSecret()}, AddedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), }, }, "updated serviceaccount with no secrets": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, UpdatedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), }, }, "updated serviceaccount with no secrets with unsynced secret store": { ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, SecretsSyncPending: true, UpdatedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), }, }, "updated serviceaccount with missing secrets": { ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, UpdatedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))), }, }, "updated serviceaccount with missing secrets with unsynced secret store": { ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, SecretsSyncPending: true, UpdatedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []core.Action{}, }, "updated serviceaccount with non-token secrets": { ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()}, UpdatedServiceAccount: serviceAccount(regularSecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(regularSecretReferences()))), }, }, "updated serviceaccount with token secrets": { ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, UpdatedServiceAccount: serviceAccount(tokenSecretReferences()), ExpectedActions: []core.Action{}, }, "updated serviceaccount with no secrets with resource conflict": { ClientObjects: []runtime.Object{updatedServiceAccount(emptySecretReferences()), createdTokenSecret()}, UpdatedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), }, }, "deleted serviceaccount with no secrets": { DeletedServiceAccount: serviceAccount(emptySecretReferences()), ExpectedActions: []core.Action{}, }, "deleted serviceaccount with missing secrets": { DeletedServiceAccount: serviceAccount(missingSecretReferences()), ExpectedActions: []core.Action{}, }, "deleted serviceaccount with non-token secrets": { ClientObjects: []runtime.Object{opaqueSecret()}, DeletedServiceAccount: serviceAccount(regularSecretReferences()), ExpectedActions: []core.Action{}, }, "deleted serviceaccount with token secrets": { ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, DeletedServiceAccount: serviceAccount(tokenSecretReferences()), ExpectedActions: []core.Action{ core.NewDeleteAction("secrets", api.NamespaceDefault, "token-secret-1"), }, }, "added secret without serviceaccount": { ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, AddedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewDeleteAction("secrets", api.NamespaceDefault, "token-secret-1"), }, }, "added secret with serviceaccount": { ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{}, }, "added token secret without token data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecretWithoutTokenData(), ExpectedActions: []core.Action{ core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "added token secret without ca data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecretWithoutCAData(), ExpectedActions: []core.Action{ core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "added token secret with mismatched ca data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")), ExpectedActions: []core.Action{ core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "added token secret without namespace data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecretWithoutNamespaceData(), ExpectedActions: []core.Action{ core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "added token secret with custom namespace data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), AddedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")), ExpectedActions: []core.Action{ // no update is performed... the custom namespace is preserved }, }, "updated secret without serviceaccount": { ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, UpdatedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewDeleteAction("secrets", api.NamespaceDefault, "token-secret-1"), }, }, "updated secret with serviceaccount": { ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{}, }, "updated token secret without token data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecretWithoutTokenData(), ExpectedActions: []core.Action{ core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "updated token secret without ca data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecretWithoutCAData(), ExpectedActions: []core.Action{ core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "updated token secret with mismatched ca data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")), ExpectedActions: []core.Action{ core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "updated token secret without namespace data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecretWithoutNamespaceData(), ExpectedActions: []core.Action{ core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), }, }, "updated token secret with custom namespace data": { ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), UpdatedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")), ExpectedActions: []core.Action{ // no update is performed... the custom namespace is preserved }, }, "deleted secret without serviceaccount": { DeletedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{}, }, "deleted secret with serviceaccount with reference": { ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences())}, ExistingServiceAccount: serviceAccount(tokenSecretReferences()), DeletedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{ core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(emptySecretReferences())), }, }, "deleted secret with serviceaccount without reference": { ExistingServiceAccount: serviceAccount(emptySecretReferences()), DeletedSecret: serviceAccountTokenSecret(), ExpectedActions: []core.Action{}, }, } for k, tc := range testcases { // Re-seed to reset name generation utilrand.Seed(1) generator := &testGenerator{Token: "ABC"} client := fake.NewSimpleClientset(tc.ClientObjects...) controller := NewTokensController(client, TokensControllerOptions{TokenGenerator: generator, RootCA: []byte("CA Data")}) // 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) } actions := client.Actions() for i, action := range actions { if len(tc.ExpectedActions) < i+1 { t.Errorf("%s: %d unexpected actions: %+v", k, len(actions)-len(tc.ExpectedActions), actions[i:]) break } expectedAction := tc.ExpectedActions[i] if !reflect.DeepEqual(expectedAction, action) { t.Errorf("%s: Expected\n\t%#v\ngot\n\t%#v", k, expectedAction, action) continue } } if len(tc.ExpectedActions) > len(actions) { t.Errorf("%s: %d additional expected actions:%+v", k, len(tc.ExpectedActions)-len(actions), tc.ExpectedActions[len(actions):]) } } }