k8s.io/kubernetes@v1.29.3/pkg/controller/serviceaccount/tokens_controller_test.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package serviceaccount 18 19 import ( 20 "reflect" 21 "testing" 22 "time" 23 24 "gopkg.in/square/go-jose.v2/jwt" 25 v1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/util/dump" 30 utilrand "k8s.io/apimachinery/pkg/util/rand" 31 "k8s.io/client-go/informers" 32 "k8s.io/client-go/kubernetes/fake" 33 core "k8s.io/client-go/testing" 34 "k8s.io/kubernetes/pkg/controller" 35 "k8s.io/kubernetes/test/utils/ktesting" 36 ) 37 38 type testGenerator struct { 39 Token string 40 Err error 41 } 42 43 func (t *testGenerator) GenerateToken(sc *jwt.Claims, pc interface{}) (string, error) { 44 return t.Token, t.Err 45 } 46 47 // emptySecretReferences is used by a service account without any secrets 48 func emptySecretReferences() []v1.ObjectReference { 49 return []v1.ObjectReference{} 50 } 51 52 // missingSecretReferences is used by a service account that references secrets which do no exist 53 func missingSecretReferences() []v1.ObjectReference { 54 return []v1.ObjectReference{{Name: "missing-secret-1"}} 55 } 56 57 // regularSecretReferences is used by a service account that references secrets which are not ServiceAccountTokens 58 func regularSecretReferences() []v1.ObjectReference { 59 return []v1.ObjectReference{{Name: "regular-secret-1"}} 60 } 61 62 // tokenSecretReferences is used by a service account that references a ServiceAccountToken secret 63 func tokenSecretReferences() []v1.ObjectReference { 64 return []v1.ObjectReference{{Name: "token-secret-1"}} 65 } 66 67 // serviceAccount returns a service account with the given secret refs 68 func serviceAccount(secretRefs []v1.ObjectReference) *v1.ServiceAccount { 69 return &v1.ServiceAccount{ 70 ObjectMeta: metav1.ObjectMeta{ 71 Name: "default", 72 UID: "12345", 73 Namespace: "default", 74 ResourceVersion: "1", 75 }, 76 Secrets: secretRefs, 77 } 78 } 79 80 // updatedServiceAccount returns a service account with the resource version modified 81 func updatedServiceAccount(secretRefs []v1.ObjectReference) *v1.ServiceAccount { 82 sa := serviceAccount(secretRefs) 83 sa.ResourceVersion = "2" 84 return sa 85 } 86 87 // opaqueSecret returns a persisted non-ServiceAccountToken secret named "regular-secret-1" 88 func opaqueSecret() *v1.Secret { 89 return &v1.Secret{ 90 ObjectMeta: metav1.ObjectMeta{ 91 Name: "regular-secret-1", 92 Namespace: "default", 93 UID: "23456", 94 ResourceVersion: "1", 95 }, 96 Type: "Opaque", 97 Data: map[string][]byte{ 98 "mykey": []byte("mydata"), 99 }, 100 } 101 } 102 103 // serviceAccountTokenSecret returns an existing ServiceAccountToken secret named "token-secret-1" 104 func serviceAccountTokenSecret() *v1.Secret { 105 return &v1.Secret{ 106 ObjectMeta: metav1.ObjectMeta{ 107 Name: "token-secret-1", 108 Namespace: "default", 109 UID: "23456", 110 ResourceVersion: "1", 111 Annotations: map[string]string{ 112 v1.ServiceAccountNameKey: "default", 113 v1.ServiceAccountUIDKey: "12345", 114 }, 115 }, 116 Type: v1.SecretTypeServiceAccountToken, 117 Data: map[string][]byte{ 118 "token": []byte("ABC"), 119 "ca.crt": []byte("CA Data"), 120 "namespace": []byte("default"), 121 }, 122 } 123 } 124 125 // serviceAccountTokenSecretWithoutTokenData returns an existing ServiceAccountToken secret that lacks token data 126 func serviceAccountTokenSecretWithoutTokenData() *v1.Secret { 127 secret := serviceAccountTokenSecret() 128 delete(secret.Data, v1.ServiceAccountTokenKey) 129 return secret 130 } 131 132 // serviceAccountTokenSecretWithoutCAData returns an existing ServiceAccountToken secret that lacks ca data 133 func serviceAccountTokenSecretWithoutCAData() *v1.Secret { 134 secret := serviceAccountTokenSecret() 135 delete(secret.Data, v1.ServiceAccountRootCAKey) 136 return secret 137 } 138 139 // serviceAccountTokenSecretWithCAData returns an existing ServiceAccountToken secret with the specified ca data 140 func serviceAccountTokenSecretWithCAData(data []byte) *v1.Secret { 141 secret := serviceAccountTokenSecret() 142 secret.Data[v1.ServiceAccountRootCAKey] = data 143 return secret 144 } 145 146 // serviceAccountTokenSecretWithoutNamespaceData returns an existing ServiceAccountToken secret that lacks namespace data 147 func serviceAccountTokenSecretWithoutNamespaceData() *v1.Secret { 148 secret := serviceAccountTokenSecret() 149 delete(secret.Data, v1.ServiceAccountNamespaceKey) 150 return secret 151 } 152 153 // serviceAccountTokenSecretWithNamespaceData returns an existing ServiceAccountToken secret with the specified namespace data 154 func serviceAccountTokenSecretWithNamespaceData(data []byte) *v1.Secret { 155 secret := serviceAccountTokenSecret() 156 secret.Data[v1.ServiceAccountNamespaceKey] = data 157 return secret 158 } 159 160 type reaction struct { 161 verb string 162 resource string 163 reactor func(t *testing.T) core.ReactionFunc 164 } 165 166 func TestTokenCreation(t *testing.T) { 167 testcases := map[string]struct { 168 ClientObjects []runtime.Object 169 170 IsAsync bool 171 MaxRetries int 172 173 Reactors []reaction 174 175 ExistingServiceAccount *v1.ServiceAccount 176 ExistingSecrets []*v1.Secret 177 178 AddedServiceAccount *v1.ServiceAccount 179 UpdatedServiceAccount *v1.ServiceAccount 180 DeletedServiceAccount *v1.ServiceAccount 181 AddedSecret *v1.Secret 182 AddedSecretLocal *v1.Secret 183 UpdatedSecret *v1.Secret 184 DeletedSecret *v1.Secret 185 186 ExpectedActions []core.Action 187 }{ 188 "new serviceaccount with no secrets": { 189 ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences())}, 190 191 AddedServiceAccount: serviceAccount(emptySecretReferences()), 192 ExpectedActions: []core.Action{}, 193 }, 194 "new serviceaccount with missing secrets": { 195 ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences())}, 196 197 AddedServiceAccount: serviceAccount(missingSecretReferences()), 198 ExpectedActions: []core.Action{}, 199 }, 200 "new serviceaccount with missing secrets and a local secret in the cache": { 201 ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences())}, 202 203 AddedServiceAccount: serviceAccount(tokenSecretReferences()), 204 AddedSecretLocal: serviceAccountTokenSecret(), 205 ExpectedActions: []core.Action{}, 206 }, 207 "new serviceaccount with non-token secrets": { 208 ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), opaqueSecret()}, 209 210 AddedServiceAccount: serviceAccount(regularSecretReferences()), 211 ExpectedActions: []core.Action{}, 212 }, 213 "new serviceaccount with token secrets": { 214 ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences()), serviceAccountTokenSecret()}, 215 ExistingSecrets: []*v1.Secret{serviceAccountTokenSecret()}, 216 217 AddedServiceAccount: serviceAccount(tokenSecretReferences()), 218 ExpectedActions: []core.Action{}, 219 }, 220 "updated serviceaccount with no secrets": { 221 ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences())}, 222 223 UpdatedServiceAccount: serviceAccount(emptySecretReferences()), 224 ExpectedActions: []core.Action{}, 225 }, 226 "updated serviceaccount with missing secrets": { 227 ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences())}, 228 229 UpdatedServiceAccount: serviceAccount(missingSecretReferences()), 230 ExpectedActions: []core.Action{}, 231 }, 232 "updated serviceaccount with non-token secrets": { 233 ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), opaqueSecret()}, 234 235 UpdatedServiceAccount: serviceAccount(regularSecretReferences()), 236 ExpectedActions: []core.Action{}, 237 }, 238 "updated serviceaccount with token secrets": { 239 ExistingSecrets: []*v1.Secret{serviceAccountTokenSecret()}, 240 241 UpdatedServiceAccount: serviceAccount(tokenSecretReferences()), 242 ExpectedActions: []core.Action{}, 243 }, 244 "updated serviceaccount with no secrets with resource conflict": { 245 ClientObjects: []runtime.Object{updatedServiceAccount(emptySecretReferences())}, 246 IsAsync: true, 247 MaxRetries: 1, 248 249 UpdatedServiceAccount: serviceAccount(emptySecretReferences()), 250 ExpectedActions: []core.Action{}, 251 }, 252 253 "deleted serviceaccount with no secrets": { 254 DeletedServiceAccount: serviceAccount(emptySecretReferences()), 255 ExpectedActions: []core.Action{}, 256 }, 257 "deleted serviceaccount with missing secrets": { 258 DeletedServiceAccount: serviceAccount(missingSecretReferences()), 259 ExpectedActions: []core.Action{}, 260 }, 261 "deleted serviceaccount with non-token secrets": { 262 ClientObjects: []runtime.Object{opaqueSecret()}, 263 264 DeletedServiceAccount: serviceAccount(regularSecretReferences()), 265 ExpectedActions: []core.Action{}, 266 }, 267 "deleted serviceaccount with token secrets": { 268 ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, 269 ExistingSecrets: []*v1.Secret{serviceAccountTokenSecret()}, 270 271 DeletedServiceAccount: serviceAccount(tokenSecretReferences()), 272 ExpectedActions: []core.Action{ 273 core.NewDeleteActionWithOptions( 274 schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, 275 metav1.NamespaceDefault, "token-secret-1", 276 *metav1.NewPreconditionDeleteOptions("23456")), 277 }, 278 }, 279 280 "added secret without serviceaccount": { 281 ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, 282 283 AddedSecret: serviceAccountTokenSecret(), 284 ExpectedActions: []core.Action{ 285 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), 286 core.NewDeleteActionWithOptions( 287 schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, 288 metav1.NamespaceDefault, "token-secret-1", 289 *metav1.NewPreconditionDeleteOptions("23456")), 290 }, 291 }, 292 "added secret with serviceaccount": { 293 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 294 295 AddedSecret: serviceAccountTokenSecret(), 296 ExpectedActions: []core.Action{}, 297 }, 298 "added token secret without token data": { 299 ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, 300 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 301 302 AddedSecret: serviceAccountTokenSecretWithoutTokenData(), 303 ExpectedActions: []core.Action{ 304 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), 305 core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), 306 }, 307 }, 308 "added token secret without ca data": { 309 ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()}, 310 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 311 312 AddedSecret: serviceAccountTokenSecretWithoutCAData(), 313 ExpectedActions: []core.Action{ 314 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), 315 core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), 316 }, 317 }, 318 "added token secret with mismatched ca data": { 319 ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))}, 320 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 321 322 AddedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")), 323 ExpectedActions: []core.Action{ 324 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), 325 core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), 326 }, 327 }, 328 "added token secret without namespace data": { 329 ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, 330 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 331 332 AddedSecret: serviceAccountTokenSecretWithoutNamespaceData(), 333 ExpectedActions: []core.Action{ 334 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), 335 core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), 336 }, 337 }, 338 "added token secret with custom namespace data": { 339 ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, 340 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 341 342 AddedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")), 343 ExpectedActions: []core.Action{ 344 // no update is performed... the custom namespace is preserved 345 }, 346 }, 347 348 "updated secret without serviceaccount": { 349 ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, 350 351 UpdatedSecret: serviceAccountTokenSecret(), 352 ExpectedActions: []core.Action{ 353 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), 354 core.NewDeleteActionWithOptions( 355 schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, 356 metav1.NamespaceDefault, "token-secret-1", 357 *metav1.NewPreconditionDeleteOptions("23456")), 358 }, 359 }, 360 "updated secret with serviceaccount": { 361 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 362 363 UpdatedSecret: serviceAccountTokenSecret(), 364 ExpectedActions: []core.Action{}, 365 }, 366 "updated token secret without token data": { 367 ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, 368 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 369 370 UpdatedSecret: serviceAccountTokenSecretWithoutTokenData(), 371 ExpectedActions: []core.Action{ 372 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), 373 core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), 374 }, 375 }, 376 "updated token secret without ca data": { 377 ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()}, 378 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 379 380 UpdatedSecret: serviceAccountTokenSecretWithoutCAData(), 381 ExpectedActions: []core.Action{ 382 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), 383 core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), 384 }, 385 }, 386 "updated token secret with mismatched ca data": { 387 ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))}, 388 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 389 390 UpdatedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")), 391 ExpectedActions: []core.Action{ 392 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), 393 core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), 394 }, 395 }, 396 "updated token secret without namespace data": { 397 ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, 398 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 399 400 UpdatedSecret: serviceAccountTokenSecretWithoutNamespaceData(), 401 ExpectedActions: []core.Action{ 402 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"), 403 core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()), 404 }, 405 }, 406 "updated token secret with custom namespace data": { 407 ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, 408 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 409 410 UpdatedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")), 411 ExpectedActions: []core.Action{ 412 // no update is performed... the custom namespace is preserved 413 }, 414 }, 415 416 "deleted secret without serviceaccount": { 417 DeletedSecret: serviceAccountTokenSecret(), 418 ExpectedActions: []core.Action{}, 419 }, 420 "deleted secret with serviceaccount with reference": { 421 ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences())}, 422 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 423 424 DeletedSecret: serviceAccountTokenSecret(), 425 ExpectedActions: []core.Action{ 426 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), 427 core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(emptySecretReferences())), 428 }, 429 }, 430 "deleted secret with serviceaccount without reference": { 431 ExistingServiceAccount: serviceAccount(emptySecretReferences()), 432 433 DeletedSecret: serviceAccountTokenSecret(), 434 ExpectedActions: []core.Action{ 435 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"), 436 }, 437 }, 438 } 439 440 for k, tc := range testcases { 441 t.Run(k, func(t *testing.T) { 442 _, ctx := ktesting.NewTestContext(t) 443 444 // Re-seed to reset name generation 445 utilrand.Seed(1) 446 447 generator := &testGenerator{Token: "ABC"} 448 449 client := fake.NewSimpleClientset(tc.ClientObjects...) 450 for _, reactor := range tc.Reactors { 451 client.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactor(t)) 452 } 453 informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) 454 secretInformer := informers.Core().V1().Secrets().Informer() 455 secrets := secretInformer.GetStore() 456 serviceAccounts := informers.Core().V1().ServiceAccounts().Informer().GetStore() 457 controller, err := NewTokensController(informers.Core().V1().ServiceAccounts(), informers.Core().V1().Secrets(), client, TokensControllerOptions{TokenGenerator: generator, RootCA: []byte("CA Data"), MaxRetries: tc.MaxRetries}) 458 if err != nil { 459 t.Fatalf("error creating Tokens controller: %v", err) 460 } 461 462 if tc.ExistingServiceAccount != nil { 463 serviceAccounts.Add(tc.ExistingServiceAccount) 464 } 465 for _, s := range tc.ExistingSecrets { 466 secrets.Add(s) 467 } 468 469 if tc.AddedServiceAccount != nil { 470 serviceAccounts.Add(tc.AddedServiceAccount) 471 controller.queueServiceAccountSync(tc.AddedServiceAccount) 472 } 473 if tc.UpdatedServiceAccount != nil { 474 serviceAccounts.Add(tc.UpdatedServiceAccount) 475 controller.queueServiceAccountUpdateSync(nil, tc.UpdatedServiceAccount) 476 } 477 if tc.DeletedServiceAccount != nil { 478 serviceAccounts.Delete(tc.DeletedServiceAccount) 479 controller.queueServiceAccountSync(tc.DeletedServiceAccount) 480 } 481 if tc.AddedSecret != nil { 482 secrets.Add(tc.AddedSecret) 483 controller.queueSecretSync(tc.AddedSecret) 484 } 485 if tc.AddedSecretLocal != nil { 486 controller.updatedSecrets.Mutation(tc.AddedSecretLocal) 487 } 488 if tc.UpdatedSecret != nil { 489 secrets.Add(tc.UpdatedSecret) 490 controller.queueSecretUpdateSync(nil, tc.UpdatedSecret) 491 } 492 if tc.DeletedSecret != nil { 493 secrets.Delete(tc.DeletedSecret) 494 controller.queueSecretSync(tc.DeletedSecret) 495 } 496 497 // This is the longest we'll wait for async tests 498 timeout := time.Now().Add(30 * time.Second) 499 waitedForAdditionalActions := false 500 501 for { 502 if controller.syncServiceAccountQueue.Len() > 0 { 503 controller.syncServiceAccount(ctx) 504 } 505 if controller.syncSecretQueue.Len() > 0 { 506 controller.syncSecret(ctx) 507 } 508 509 // The queues still have things to work on 510 if controller.syncServiceAccountQueue.Len() > 0 || controller.syncSecretQueue.Len() > 0 { 511 continue 512 } 513 514 // If we expect this test to work asynchronously... 515 if tc.IsAsync { 516 // if we're still missing expected actions within our test timeout 517 if len(client.Actions()) < len(tc.ExpectedActions) && time.Now().Before(timeout) { 518 // wait for the expected actions (without hotlooping) 519 time.Sleep(time.Millisecond) 520 continue 521 } 522 523 // if we exactly match our expected actions, wait a bit to make sure no other additional actions show up 524 if len(client.Actions()) == len(tc.ExpectedActions) && !waitedForAdditionalActions { 525 time.Sleep(time.Second) 526 waitedForAdditionalActions = true 527 continue 528 } 529 } 530 531 break 532 } 533 534 if controller.syncServiceAccountQueue.Len() > 0 { 535 t.Errorf("%s: unexpected items in service account queue: %d", k, controller.syncServiceAccountQueue.Len()) 536 } 537 if controller.syncSecretQueue.Len() > 0 { 538 t.Errorf("%s: unexpected items in secret queue: %d", k, controller.syncSecretQueue.Len()) 539 } 540 541 actions := client.Actions() 542 for i, action := range actions { 543 if len(tc.ExpectedActions) < i+1 { 544 t.Errorf("%s: %d unexpected actions: %+v", k, len(actions)-len(tc.ExpectedActions), actions[i:]) 545 break 546 } 547 548 expectedAction := tc.ExpectedActions[i] 549 if !reflect.DeepEqual(expectedAction, action) { 550 t.Errorf("%s:\nExpected:\n%s\ngot:\n%s", k, dump.Pretty(expectedAction), dump.Pretty(action)) 551 continue 552 } 553 } 554 555 if len(tc.ExpectedActions) > len(actions) { 556 t.Errorf("%s: %d additional expected actions", k, len(tc.ExpectedActions)-len(actions)) 557 for _, a := range tc.ExpectedActions[len(actions):] { 558 t.Logf(" %+v", a) 559 } 560 } 561 }) 562 } 563 }