1
16
17 package manager
18
19 import (
20 "context"
21 "fmt"
22 "reflect"
23 "strings"
24 "sync"
25 "testing"
26 "time"
27
28 v1 "k8s.io/api/core/v1"
29
30 apierrors "k8s.io/apimachinery/pkg/api/errors"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/apimachinery/pkg/runtime"
33 "k8s.io/apimachinery/pkg/types"
34 "k8s.io/apimachinery/pkg/util/sets"
35
36 clientset "k8s.io/client-go/kubernetes"
37 "k8s.io/client-go/kubernetes/fake"
38 core "k8s.io/client-go/testing"
39 podutil "k8s.io/kubernetes/pkg/api/v1/pod"
40 "k8s.io/utils/clock"
41 testingclock "k8s.io/utils/clock/testing"
42
43 "github.com/stretchr/testify/assert"
44 )
45
46 func checkObject(t *testing.T, store *objectStore, ns, name string, shouldExist bool) {
47 _, err := store.Get(ns, name)
48 if shouldExist && err != nil {
49 t.Errorf("unexpected actions: %#v", err)
50 }
51 if !shouldExist && (err == nil || !strings.Contains(err.Error(), fmt.Sprintf("object %q/%q not registered", ns, name))) {
52 t.Errorf("unexpected actions: %#v", err)
53 }
54 }
55
56 func noObjectTTL() (time.Duration, bool) {
57 return time.Duration(0), false
58 }
59
60 func getSecret(fakeClient clientset.Interface) GetObjectFunc {
61 return func(namespace, name string, opts metav1.GetOptions) (runtime.Object, error) {
62 return fakeClient.CoreV1().Secrets(namespace).Get(context.TODO(), name, opts)
63 }
64 }
65
66 func newSecretStore(fakeClient clientset.Interface, clock clock.Clock, getTTL GetObjectTTLFunc, ttl time.Duration) *objectStore {
67 return &objectStore{
68 getObject: getSecret(fakeClient),
69 clock: clock,
70 items: make(map[objectKey]*objectStoreItem),
71 defaultTTL: ttl,
72 getTTL: getTTL,
73 }
74 }
75
76 func getSecretNames(pod *v1.Pod) sets.String {
77 result := sets.NewString()
78 podutil.VisitPodSecretNames(pod, func(name string) bool {
79 result.Insert(name)
80 return true
81 })
82 return result
83 }
84
85 func newCacheBasedSecretManager(store Store) Manager {
86 return NewCacheBasedManager(store, getSecretNames)
87 }
88
89 func TestSecretStore(t *testing.T) {
90 fakeClient := &fake.Clientset{}
91 store := newSecretStore(fakeClient, clock.RealClock{}, noObjectTTL, 0)
92 store.AddReference("ns1", "name1", "pod1")
93 store.AddReference("ns2", "name2", "pod2")
94 store.AddReference("ns1", "name1", "pod3")
95 store.AddReference("ns1", "name1", "pod4")
96 store.DeleteReference("ns1", "name1", "pod1")
97 store.DeleteReference("ns2", "name2", "pod2")
98 store.AddReference("ns3", "name3", "pod5")
99
100
101 actions := fakeClient.Actions()
102 assert.Equal(t, 0, len(actions), "unexpected actions: %#v", actions)
103
104 store.Get("ns1", "name1")
105
106 store.Get("ns2", "name2")
107
108 store.Get("ns3", "name3")
109
110 actions = fakeClient.Actions()
111 assert.Equal(t, 2, len(actions), "unexpected actions: %#v", actions)
112
113 for _, a := range actions {
114 assert.True(t, a.Matches("get", "secrets"), "unexpected actions: %#v", a)
115 }
116
117 checkObject(t, store, "ns1", "name1", true)
118 checkObject(t, store, "ns2", "name2", false)
119 checkObject(t, store, "ns3", "name3", true)
120 checkObject(t, store, "ns4", "name4", false)
121 }
122
123 func TestSecretStoreDeletingSecret(t *testing.T) {
124 fakeClient := &fake.Clientset{}
125 store := newSecretStore(fakeClient, clock.RealClock{}, noObjectTTL, 0)
126 store.AddReference("ns", "name", "pod")
127
128 result := &v1.Secret{ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "name", ResourceVersion: "10"}}
129 fakeClient.AddReactor("get", "secrets", func(action core.Action) (bool, runtime.Object, error) {
130 return true, result, nil
131 })
132 secret, err := store.Get("ns", "name")
133 if err != nil {
134 t.Errorf("Unexpected error: %v", err)
135 }
136 if !reflect.DeepEqual(secret, result) {
137 t.Errorf("Unexpected secret: %v", secret)
138 }
139
140 fakeClient.PrependReactor("get", "secrets", func(action core.Action) (bool, runtime.Object, error) {
141 return true, &v1.Secret{}, apierrors.NewNotFound(v1.Resource("secret"), "name")
142 })
143 secret, err = store.Get("ns", "name")
144 if err == nil || !apierrors.IsNotFound(err) {
145 t.Errorf("Unexpected error: %v", err)
146 }
147 if !reflect.DeepEqual(secret, &v1.Secret{}) {
148 t.Errorf("Unexpected secret: %v", secret)
149 }
150 }
151
152 func TestSecretStoreGetAlwaysRefresh(t *testing.T) {
153 fakeClient := &fake.Clientset{}
154 fakeClock := testingclock.NewFakeClock(time.Now())
155 store := newSecretStore(fakeClient, fakeClock, noObjectTTL, 0)
156
157 for i := 0; i < 10; i++ {
158 store.AddReference(fmt.Sprintf("ns-%d", i), fmt.Sprintf("name-%d", i), types.UID(fmt.Sprintf("pod-%d", i)))
159 }
160 fakeClient.ClearActions()
161
162 wg := sync.WaitGroup{}
163 wg.Add(100)
164 for i := 0; i < 100; i++ {
165 go func(i int) {
166 store.Get(fmt.Sprintf("ns-%d", i%10), fmt.Sprintf("name-%d", i%10))
167 wg.Done()
168 }(i)
169 }
170 wg.Wait()
171 actions := fakeClient.Actions()
172 assert.Equal(t, 100, len(actions), "unexpected actions: %#v", actions)
173
174 for _, a := range actions {
175 assert.True(t, a.Matches("get", "secrets"), "unexpected actions: %#v", a)
176 }
177 }
178
179 func TestSecretStoreGetNeverRefresh(t *testing.T) {
180 fakeClient := &fake.Clientset{}
181 fakeClock := testingclock.NewFakeClock(time.Now())
182 store := newSecretStore(fakeClient, fakeClock, noObjectTTL, time.Minute)
183
184 for i := 0; i < 10; i++ {
185 store.AddReference(fmt.Sprintf("ns-%d", i), fmt.Sprintf("name-%d", i), types.UID(fmt.Sprintf("pod-%d", i)))
186 }
187 fakeClient.ClearActions()
188
189 wg := sync.WaitGroup{}
190 wg.Add(100)
191 for i := 0; i < 100; i++ {
192 go func(i int) {
193 store.Get(fmt.Sprintf("ns-%d", i%10), fmt.Sprintf("name-%d", i%10))
194 wg.Done()
195 }(i)
196 }
197 wg.Wait()
198 actions := fakeClient.Actions()
199
200 assert.Equal(t, 10, len(actions), "unexpected actions: %#v", actions)
201 }
202
203 func TestCustomTTL(t *testing.T) {
204 ttl := time.Duration(0)
205 ttlExists := false
206 customTTL := func() (time.Duration, bool) {
207 return ttl, ttlExists
208 }
209
210 fakeClient := &fake.Clientset{}
211 fakeClock := testingclock.NewFakeClock(time.Time{})
212 store := newSecretStore(fakeClient, fakeClock, customTTL, time.Minute)
213
214 store.AddReference("ns", "name", "pod")
215 store.Get("ns", "name")
216 fakeClient.ClearActions()
217
218
219 ttl = time.Duration(0)
220 ttlExists = true
221 store.Get("ns", "name")
222 actions := fakeClient.Actions()
223 assert.Equal(t, 1, len(actions), "unexpected actions: %#v", actions)
224 fakeClient.ClearActions()
225
226
227 ttl = time.Duration(5) * time.Minute
228 store.Get("ns", "name")
229 actions = fakeClient.Actions()
230 assert.Equal(t, 0, len(actions), "unexpected actions: %#v", actions)
231
232 fakeClock.Step(4 * time.Minute)
233 store.Get("ns", "name")
234 actions = fakeClient.Actions()
235 assert.Equal(t, 0, len(actions), "unexpected actions: %#v", actions)
236
237 fakeClock.Step(time.Minute)
238 store.Get("ns", "name")
239 actions = fakeClient.Actions()
240 assert.Equal(t, 1, len(actions), "unexpected actions: %#v", actions)
241 fakeClient.ClearActions()
242
243
244 ttlExists = false
245 fakeClock.Step(55 * time.Second)
246 store.Get("ns", "name")
247 actions = fakeClient.Actions()
248 assert.Equal(t, 0, len(actions), "unexpected actions: %#v", actions)
249
250 fakeClock.Step(5 * time.Second)
251 store.Get("ns", "name")
252 actions = fakeClient.Actions()
253 assert.Equal(t, 1, len(actions), "unexpected actions: %#v", actions)
254 }
255
256 func TestParseNodeAnnotation(t *testing.T) {
257 testCases := []struct {
258 node *v1.Node
259 err error
260 exists bool
261 ttl time.Duration
262 }{
263 {
264 node: nil,
265 err: fmt.Errorf("error"),
266 exists: false,
267 },
268 {
269 node: &v1.Node{
270 ObjectMeta: metav1.ObjectMeta{
271 Name: "node",
272 },
273 },
274 exists: false,
275 },
276 {
277 node: &v1.Node{
278 ObjectMeta: metav1.ObjectMeta{
279 Name: "node",
280 Annotations: map[string]string{},
281 },
282 },
283 exists: false,
284 },
285 {
286 node: &v1.Node{
287 ObjectMeta: metav1.ObjectMeta{
288 Name: "node",
289 Annotations: map[string]string{v1.ObjectTTLAnnotationKey: "bad"},
290 },
291 },
292 exists: false,
293 },
294 {
295 node: &v1.Node{
296 ObjectMeta: metav1.ObjectMeta{
297 Name: "node",
298 Annotations: map[string]string{v1.ObjectTTLAnnotationKey: "0"},
299 },
300 },
301 exists: true,
302 ttl: time.Duration(0),
303 },
304 {
305 node: &v1.Node{
306 ObjectMeta: metav1.ObjectMeta{
307 Name: "node",
308 Annotations: map[string]string{v1.ObjectTTLAnnotationKey: "60"},
309 },
310 },
311 exists: true,
312 ttl: time.Minute,
313 },
314 }
315 for i, testCase := range testCases {
316 getNode := func() (*v1.Node, error) { return testCase.node, testCase.err }
317 ttl, exists := GetObjectTTLFromNodeFunc(getNode)()
318 if exists != testCase.exists {
319 t.Errorf("%d: incorrect parsing: %t", i, exists)
320 continue
321 }
322 if exists && ttl != testCase.ttl {
323 t.Errorf("%d: incorrect ttl: %v", i, ttl)
324 }
325 }
326 }
327
328 type envSecrets struct {
329 envVarNames []string
330 envFromNames []string
331 }
332
333 type secretsToAttach struct {
334 imagePullSecretNames []string
335 containerEnvSecrets []envSecrets
336 }
337
338 func podWithSecrets(ns, podName string, toAttach secretsToAttach) *v1.Pod {
339 return podWithSecretsAndUID(ns, podName, fmt.Sprintf("%s/%s", ns, podName), toAttach)
340 }
341
342 func podWithSecretsAndUID(ns, podName, podUID string, toAttach secretsToAttach) *v1.Pod {
343 pod := &v1.Pod{
344 ObjectMeta: metav1.ObjectMeta{
345 Namespace: ns,
346 Name: podName,
347 UID: types.UID(podUID),
348 },
349 Spec: v1.PodSpec{},
350 }
351 for _, name := range toAttach.imagePullSecretNames {
352 pod.Spec.ImagePullSecrets = append(
353 pod.Spec.ImagePullSecrets, v1.LocalObjectReference{Name: name})
354 }
355 for i, secrets := range toAttach.containerEnvSecrets {
356 container := v1.Container{
357 Name: fmt.Sprintf("container-%d", i),
358 }
359 for _, name := range secrets.envFromNames {
360 envFrom := v1.EnvFromSource{
361 SecretRef: &v1.SecretEnvSource{
362 LocalObjectReference: v1.LocalObjectReference{
363 Name: name,
364 },
365 },
366 }
367 container.EnvFrom = append(container.EnvFrom, envFrom)
368 }
369
370 for _, name := range secrets.envVarNames {
371 envSource := &v1.EnvVarSource{
372 SecretKeyRef: &v1.SecretKeySelector{
373 LocalObjectReference: v1.LocalObjectReference{
374 Name: name,
375 },
376 },
377 }
378 container.Env = append(container.Env, v1.EnvVar{ValueFrom: envSource})
379 }
380 pod.Spec.Containers = append(pod.Spec.Containers, container)
381 }
382 return pod
383 }
384
385 func TestCacheInvalidation(t *testing.T) {
386 fakeClient := &fake.Clientset{}
387 fakeClock := testingclock.NewFakeClock(time.Now())
388 store := newSecretStore(fakeClient, fakeClock, noObjectTTL, time.Minute)
389 manager := newCacheBasedSecretManager(store)
390
391
392 s1 := secretsToAttach{
393 imagePullSecretNames: []string{"s1"},
394 containerEnvSecrets: []envSecrets{
395 {envVarNames: []string{"s1"}, envFromNames: []string{"s10"}},
396 {envVarNames: []string{"s2"}},
397 },
398 }
399 manager.RegisterPod(podWithSecrets("ns1", "name1", s1))
400
401 store.Get("ns1", "s1")
402 store.Get("ns1", "s10")
403 store.Get("ns1", "s2")
404 actions := fakeClient.Actions()
405 assert.Equal(t, 3, len(actions), "unexpected actions: %#v", actions)
406 fakeClient.ClearActions()
407
408
409 s2 := secretsToAttach{
410 imagePullSecretNames: []string{"s1"},
411 containerEnvSecrets: []envSecrets{
412 {envVarNames: []string{"s1"}},
413 {envVarNames: []string{"s2"}, envFromNames: []string{"s20"}},
414 {envVarNames: []string{"s3"}},
415 },
416 }
417 manager.RegisterPod(podWithSecrets("ns1", "name1", s2))
418
419 store.Get("ns1", "s1")
420 store.Get("ns1", "s2")
421 store.Get("ns1", "s20")
422 store.Get("ns1", "s3")
423 actions = fakeClient.Actions()
424 assert.Equal(t, 2, len(actions), "unexpected actions: %#v", actions)
425 fakeClient.ClearActions()
426
427
428
429 manager.RegisterPod(podWithSecrets("ns1", "name2", s1))
430 store.Get("ns1", "s1")
431 store.Get("ns1", "s10")
432 store.Get("ns1", "s2")
433 store.Get("ns1", "s20")
434 store.Get("ns1", "s3")
435 actions = fakeClient.Actions()
436 assert.Equal(t, 3, len(actions), "unexpected actions: %#v", actions)
437 fakeClient.ClearActions()
438 }
439
440 func TestRegisterIdempotence(t *testing.T) {
441 fakeClient := &fake.Clientset{}
442 fakeClock := testingclock.NewFakeClock(time.Now())
443 store := newSecretStore(fakeClient, fakeClock, noObjectTTL, time.Minute)
444 manager := newCacheBasedSecretManager(store)
445
446 s1 := secretsToAttach{
447 imagePullSecretNames: []string{"s1"},
448 }
449
450 refs := func(ns, name string) int {
451 store.lock.Lock()
452 defer store.lock.Unlock()
453 item, ok := store.items[objectKey{namespace: ns, name: name}]
454 if !ok {
455 return 0
456 }
457 return item.refCount
458 }
459
460 manager.RegisterPod(podWithSecrets("ns1", "name1", s1))
461 assert.Equal(t, 1, refs("ns1", "s1"))
462 manager.RegisterPod(podWithSecrets("ns1", "name1", s1))
463 assert.Equal(t, 1, refs("ns1", "s1"))
464 manager.RegisterPod(podWithSecrets("ns1", "name2", s1))
465 assert.Equal(t, 2, refs("ns1", "s1"))
466
467 manager.UnregisterPod(podWithSecrets("ns1", "name1", s1))
468 assert.Equal(t, 1, refs("ns1", "s1"))
469 manager.UnregisterPod(podWithSecrets("ns1", "name1", s1))
470 assert.Equal(t, 1, refs("ns1", "s1"))
471 manager.UnregisterPod(podWithSecrets("ns1", "name2", s1))
472 assert.Equal(t, 0, refs("ns1", "s1"))
473 }
474
475 func TestCacheRefcounts(t *testing.T) {
476 fakeClient := &fake.Clientset{}
477 fakeClock := testingclock.NewFakeClock(time.Now())
478 store := newSecretStore(fakeClient, fakeClock, noObjectTTL, time.Minute)
479 manager := newCacheBasedSecretManager(store)
480
481 s1 := secretsToAttach{
482 imagePullSecretNames: []string{"s1"},
483 containerEnvSecrets: []envSecrets{
484 {envVarNames: []string{"s1"}, envFromNames: []string{"s10"}},
485 {envVarNames: []string{"s2"}},
486 {envVarNames: []string{"s3"}},
487 },
488 }
489 manager.RegisterPod(podWithSecrets("ns1", "name1", s1))
490 manager.RegisterPod(podWithSecrets("ns1", "name2", s1))
491 s2 := secretsToAttach{
492 imagePullSecretNames: []string{"s2"},
493 containerEnvSecrets: []envSecrets{
494 {envVarNames: []string{"s4"}},
495 {envVarNames: []string{"s5"}, envFromNames: []string{"s50"}},
496 },
497 }
498 manager.RegisterPod(podWithSecrets("ns1", "name2", s2))
499 manager.RegisterPod(podWithSecrets("ns1", "name3", s2))
500 manager.RegisterPod(podWithSecrets("ns1", "name4", s2))
501 manager.UnregisterPod(podWithSecrets("ns1", "name3", s2))
502 s3 := secretsToAttach{
503 imagePullSecretNames: []string{"s1"},
504 containerEnvSecrets: []envSecrets{
505 {envVarNames: []string{"s3"}, envFromNames: []string{"s30"}},
506 {envVarNames: []string{"s5"}},
507 },
508 }
509 manager.RegisterPod(podWithSecrets("ns1", "name5", s3))
510 manager.RegisterPod(podWithSecrets("ns1", "name6", s3))
511 s4 := secretsToAttach{
512 imagePullSecretNames: []string{"s3"},
513 containerEnvSecrets: []envSecrets{
514 {envVarNames: []string{"s6"}},
515 {envFromNames: []string{"s60"}},
516 },
517 }
518 manager.RegisterPod(podWithSecrets("ns1", "name7", s4))
519 manager.UnregisterPod(podWithSecrets("ns1", "name7", s4))
520
521
522 manager.RegisterPod(podWithSecrets("ns1", "other-name", s1))
523 manager.RegisterPod(podWithSecrets("ns1", "other-name", s2))
524 manager.UnregisterPod(podWithSecrets("ns1", "other-name", s2))
525
526 s5 := secretsToAttach{
527 containerEnvSecrets: []envSecrets{
528 {envVarNames: []string{"s7"}},
529 {envFromNames: []string{"s70"}},
530 },
531 }
532
533 manager.RegisterPod(podWithSecrets("ns1", "noop-pod", s5))
534 manager.RegisterPod(podWithSecrets("ns1", "noop-pod", s5))
535
536
537 refs := func(ns, name string) int {
538 store.lock.Lock()
539 defer store.lock.Unlock()
540 item, ok := store.items[objectKey{namespace: ns, name: name}]
541 if !ok {
542 return 0
543 }
544 return item.refCount
545 }
546 assert.Equal(t, 3, refs("ns1", "s1"))
547 assert.Equal(t, 1, refs("ns1", "s10"))
548 assert.Equal(t, 3, refs("ns1", "s2"))
549 assert.Equal(t, 3, refs("ns1", "s3"))
550 assert.Equal(t, 2, refs("ns1", "s30"))
551 assert.Equal(t, 2, refs("ns1", "s4"))
552 assert.Equal(t, 4, refs("ns1", "s5"))
553 assert.Equal(t, 2, refs("ns1", "s50"))
554 assert.Equal(t, 0, refs("ns1", "s6"))
555 assert.Equal(t, 0, refs("ns1", "s60"))
556 assert.Equal(t, 1, refs("ns1", "s7"))
557 assert.Equal(t, 1, refs("ns1", "s70"))
558
559
560 secret1 := secretsToAttach{
561 containerEnvSecrets: []envSecrets{
562 {envVarNames: []string{"secret1"}},
563 },
564 }
565 secret2 := secretsToAttach{
566 containerEnvSecrets: []envSecrets{
567 {envVarNames: []string{"secret2"}},
568 },
569 }
570
571
572 assert.Equal(t, 0, refs("nsinterleaved", "secret1"))
573 assert.Equal(t, 0, refs("nsinterleaved", "secret2"))
574
575
576 manager.RegisterPod(podWithSecretsAndUID("nsinterleaved", "pod", "poduid1", secret1))
577 assert.Equal(t, 1, refs("nsinterleaved", "secret1"))
578 assert.Equal(t, 0, refs("nsinterleaved", "secret2"))
579
580
581 manager.RegisterPod(podWithSecretsAndUID("nsinterleaved", "pod", "poduid2", secret2))
582 assert.Equal(t, 1, refs("nsinterleaved", "secret1"))
583 assert.Equal(t, 1, refs("nsinterleaved", "secret2"))
584
585
586 manager.UnregisterPod(podWithSecretsAndUID("nsinterleaved", "pod", "poduid1", secretsToAttach{}))
587 assert.Equal(t, 0, refs("nsinterleaved", "secret1"))
588 assert.Equal(t, 1, refs("nsinterleaved", "secret2"))
589
590
591 manager.UnregisterPod(podWithSecretsAndUID("nsinterleaved", "pod", "poduid2", secretsToAttach{}))
592 assert.Equal(t, 0, refs("nsinterleaved", "secret1"))
593 assert.Equal(t, 0, refs("nsinterleaved", "secret2"))
594 }
595
596 func TestCacheBasedSecretManager(t *testing.T) {
597 fakeClient := &fake.Clientset{}
598 store := newSecretStore(fakeClient, clock.RealClock{}, noObjectTTL, 0)
599 manager := newCacheBasedSecretManager(store)
600
601
602 s1 := secretsToAttach{
603 imagePullSecretNames: []string{"s1"},
604 containerEnvSecrets: []envSecrets{
605 {envVarNames: []string{"s1"}},
606 {envVarNames: []string{"s2"}},
607 {envFromNames: []string{"s20"}},
608 },
609 }
610 manager.RegisterPod(podWithSecrets("ns1", "name1", s1))
611
612 s2 := secretsToAttach{
613 imagePullSecretNames: []string{"s1"},
614 containerEnvSecrets: []envSecrets{
615 {envVarNames: []string{"s3"}},
616 {envVarNames: []string{"s4"}},
617 {envFromNames: []string{"s40"}},
618 },
619 }
620 manager.RegisterPod(podWithSecrets("ns1", "name1", s2))
621
622 manager.RegisterPod(podWithSecrets("ns2", "name2", s2))
623
624 s3 := secretsToAttach{
625 imagePullSecretNames: []string{"s5"},
626 containerEnvSecrets: []envSecrets{
627 {envVarNames: []string{"s6"}},
628 {envFromNames: []string{"s60"}},
629 },
630 }
631 manager.RegisterPod(podWithSecrets("ns3", "name", s3))
632 manager.UnregisterPod(podWithSecrets("ns3", "name", s3))
633
634
635 for _, ns := range []string{"ns1", "ns2", "ns3"} {
636 for _, secret := range []string{"s1", "s2", "s3", "s4", "s5", "s6", "s20", "s40", "s50"} {
637 shouldExist :=
638 (secret == "s1" || secret == "s3" || secret == "s4" || secret == "s40") && (ns == "ns1" || ns == "ns2")
639 checkObject(t, store, ns, secret, shouldExist)
640 }
641 }
642 }
643
View as plain text