1
16
17 package polymorphichelpers
18
19 import (
20 "context"
21 "fmt"
22 "reflect"
23 "testing"
24
25 appsv1 "k8s.io/api/apps/v1"
26 corev1 "k8s.io/api/core/v1"
27 apiequality "k8s.io/apimachinery/pkg/api/equality"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/runtime"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 "k8s.io/apimachinery/pkg/types"
32 "k8s.io/apimachinery/pkg/util/json"
33 "k8s.io/client-go/kubernetes/fake"
34 )
35
36 var historytests = map[schema.GroupKind]reflect.Type{
37 {Group: "apps", Kind: "DaemonSet"}: reflect.TypeOf(&DaemonSetHistoryViewer{}),
38 {Group: "apps", Kind: "StatefulSet"}: reflect.TypeOf(&StatefulSetHistoryViewer{}),
39 {Group: "apps", Kind: "Deployment"}: reflect.TypeOf(&DeploymentHistoryViewer{}),
40 }
41
42 func TestHistoryViewerFor(t *testing.T) {
43 fakeClientset := &fake.Clientset{}
44
45 for kind, expectedType := range historytests {
46 result, err := HistoryViewerFor(kind, fakeClientset)
47 if err != nil {
48 t.Fatalf("error getting HistoryViewer for a %v: %v", kind.String(), err)
49 }
50
51 if reflect.TypeOf(result) != expectedType {
52 t.Fatalf("unexpected output type (%v was expected but got %v)", expectedType, reflect.TypeOf(result))
53 }
54 }
55 }
56
57 func TestViewDeploymentHistory(t *testing.T) {
58 trueVar := true
59 replicas := int32(1)
60
61 deployment := &appsv1.Deployment{
62 ObjectMeta: metav1.ObjectMeta{
63 Name: "moons",
64 Namespace: "default",
65 UID: "fc7e66ad-eacc-4413-8277-e22276eacce6",
66 Labels: map[string]string{"foo": "bar"},
67 },
68 Spec: appsv1.DeploymentSpec{
69 Selector: &metav1.LabelSelector{
70 MatchLabels: map[string]string{"foo": "bar"},
71 },
72 Replicas: &replicas,
73 Template: corev1.PodTemplateSpec{
74 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
75 Spec: corev1.PodSpec{
76 Containers: []corev1.Container{{
77 Name: "test",
78 Image: fmt.Sprintf("foo:1"),
79 }}},
80 },
81 },
82 }
83
84 fakeClientSet := fake.NewSimpleClientset(deployment)
85
86 replicaSets := map[int64]*appsv1.ReplicaSet{}
87 var i int64
88 for i = 1; i < 5; i++ {
89 rs := &appsv1.ReplicaSet{
90 ObjectMeta: metav1.ObjectMeta{
91 Name: fmt.Sprintf("moons-%d", i),
92 Namespace: "default",
93 UID: types.UID(fmt.Sprintf("00000000-0000-0000-0000-00000000000%d", i)),
94 Labels: map[string]string{"foo": "bar"},
95 OwnerReferences: []metav1.OwnerReference{{APIVersion: "apps/v1", Kind: "Deployment", Name: deployment.Name, UID: deployment.UID, Controller: &trueVar}},
96 Annotations: map[string]string{
97 "deployment.kubernetes.io/revision": fmt.Sprintf("%d", i),
98 },
99 },
100 Spec: appsv1.ReplicaSetSpec{
101 Selector: &metav1.LabelSelector{
102 MatchLabels: map[string]string{"foo": "bar"},
103 },
104 Replicas: &replicas,
105 Template: corev1.PodTemplateSpec{
106 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
107 Spec: corev1.PodSpec{
108 Containers: []corev1.Container{{
109 Name: "test",
110 Image: fmt.Sprintf("foo:%d", i),
111 }}},
112 },
113 },
114 }
115
116 if i == 3 {
117 rs.ObjectMeta.Annotations[ChangeCauseAnnotation] = "foo change cause"
118 } else if i == 4 {
119 rs.ObjectMeta.Annotations[ChangeCauseAnnotation] = "bar change cause"
120 }
121
122 fakeClientSet.AppsV1().ReplicaSets("default").Create(context.TODO(), rs, metav1.CreateOptions{})
123 replicaSets[i] = rs
124 }
125
126 viewer := DeploymentHistoryViewer{fakeClientSet}
127
128 t.Run("should show revisions list if the revision is not specified", func(t *testing.T) {
129 result, err := viewer.ViewHistory("default", "moons", 0)
130 if err != nil {
131 t.Fatalf("error getting history for Deployment moons: %v", err)
132 }
133
134 expected := `REVISION CHANGE-CAUSE
135 1 <none>
136 2 <none>
137 3 foo change cause
138 4 bar change cause
139 `
140 if result != expected {
141 t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
142 }
143 })
144
145 t.Run("should describe a single revision", func(t *testing.T) {
146 result, err := viewer.ViewHistory("default", "moons", 3)
147 if err != nil {
148 t.Fatalf("error getting history for Deployment moons: %v", err)
149 }
150
151 expected := `Pod Template:
152 Labels: foo=bar
153 Annotations: kubernetes.io/change-cause: foo change cause
154 Containers:
155 test:
156 Image: foo:3
157 Port: <none>
158 Host Port: <none>
159 Environment: <none>
160 Mounts: <none>
161 Volumes: <none>
162 Node-Selectors: <none>
163 Tolerations: <none>
164 `
165 if result != expected {
166 t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
167 }
168 })
169
170 t.Run("should get history", func(t *testing.T) {
171 result, err := viewer.GetHistory("default", "moons")
172 if err != nil {
173 t.Fatalf("error getting history for Deployment moons: %v", err)
174 }
175
176 if len(result) != 4 {
177 t.Fatalf("unexpected history length (expected 4, got %d", len(result))
178 }
179
180 for i = 1; i < 4; i++ {
181 actual, found := result[i]
182 if !found {
183 t.Fatalf("revision %d not found in history", i)
184 }
185 expected := replicaSets[i]
186 if !reflect.DeepEqual(expected, actual) {
187 t.Errorf("history does not match. expected %+v, got %+v", expected, actual)
188 }
189 }
190 })
191 }
192
193 func TestViewHistory(t *testing.T) {
194
195 t.Run("for statefulSet", func(t *testing.T) {
196 var (
197 trueVar = true
198 replicas = int32(1)
199
200 podStub = corev1.PodTemplateSpec{
201 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
202 Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "nginx"}}},
203 }
204
205 ssStub = &appsv1.StatefulSet{
206 ObjectMeta: metav1.ObjectMeta{
207 Name: "moons",
208 Namespace: "default",
209 UID: "1993",
210 Labels: map[string]string{"foo": "bar"},
211 },
212 Spec: appsv1.StatefulSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.ObjectMeta.Labels}, Replicas: &replicas, Template: podStub},
213 }
214 )
215 stsRawData, err := json.Marshal(ssStub)
216 if err != nil {
217 t.Fatalf("error creating sts raw data: %v", err)
218 }
219 ssStub1 := &appsv1.ControllerRevision{
220 ObjectMeta: metav1.ObjectMeta{
221 Name: "moons",
222 Namespace: "default",
223 Labels: map[string]string{"foo": "bar"},
224 OwnerReferences: []metav1.OwnerReference{{APIVersion: "apps/v1", Kind: "StatefulSet", Name: "moons", UID: "1993", Controller: &trueVar}},
225 },
226 Data: runtime.RawExtension{Raw: stsRawData},
227 TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: "apps/v1"},
228 Revision: 1,
229 }
230
231 fakeClientSet := fake.NewSimpleClientset(ssStub)
232 _, err = fakeClientSet.AppsV1().ControllerRevisions("default").Create(context.TODO(), ssStub1, metav1.CreateOptions{})
233 if err != nil {
234 t.Fatalf("create controllerRevisions error %v occurred ", err)
235 }
236
237 var sts = &StatefulSetHistoryViewer{
238 fakeClientSet,
239 }
240
241 t.Run("should show revisions list if the revision is not specified", func(t *testing.T) {
242 result, err := sts.ViewHistory("default", "moons", 0)
243 if err != nil {
244 t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err)
245 }
246
247 expected := `REVISION CHANGE-CAUSE
248 1 <none>
249 `
250
251 if result != expected {
252 t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
253 }
254 })
255
256 t.Run("should describe the revision if revision is specified", func(t *testing.T) {
257 result, err := sts.ViewHistory("default", "moons", 1)
258 if err != nil {
259 t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err)
260 }
261
262 expected := `Pod Template:
263 Labels: foo=bar
264 Containers:
265 test:
266 Image: nginx
267 Port: <none>
268 Host Port: <none>
269 Environment: <none>
270 Mounts: <none>
271 Volumes: <none>
272 Node-Selectors: <none>
273 Tolerations: <none>
274 `
275
276 if result != expected {
277 t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
278 }
279 })
280
281 t.Run("should get history", func(t *testing.T) {
282 result, err := sts.GetHistory("default", "moons")
283 if err != nil {
284 t.Fatalf("error getting history for StatefulSet moons: %v", err)
285 }
286
287 if len(result) != 1 {
288 t.Fatalf("unexpected history length (expected 1, got %d", len(result))
289 }
290
291 actual, found := result[1]
292 if !found {
293 t.Fatalf("revision 1 not found in history")
294 }
295 expected := ssStub
296 if !reflect.DeepEqual(expected, actual) {
297 t.Errorf("history does not match. expected %+v, got %+v", expected, actual)
298 }
299 })
300 })
301
302 t.Run("for daemonSet", func(t *testing.T) {
303 var (
304 trueVar = true
305 podStub = corev1.PodTemplateSpec{
306 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
307 Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "nginx"}}},
308 }
309
310 daemonSetStub = &appsv1.DaemonSet{
311 ObjectMeta: metav1.ObjectMeta{
312 Name: "moons",
313 Namespace: "default",
314 UID: "1993",
315 Labels: map[string]string{"foo": "bar"},
316 },
317 Spec: appsv1.DaemonSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.ObjectMeta.Labels}, Template: podStub},
318 }
319 )
320
321 daemonSetRaw, err := json.Marshal(daemonSetStub)
322 if err != nil {
323 t.Fatalf("error creating sts raw data: %v", err)
324 }
325 daemonSetControllerRevision := &appsv1.ControllerRevision{
326 ObjectMeta: metav1.ObjectMeta{
327 Name: "moons",
328 Namespace: "default",
329 Labels: map[string]string{"foo": "bar"},
330 OwnerReferences: []metav1.OwnerReference{{APIVersion: "apps/v1", Kind: "DaemonSet", Name: "moons", UID: "1993", Controller: &trueVar}},
331 },
332 Data: runtime.RawExtension{Raw: daemonSetRaw},
333 TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: "apps/v1"},
334 Revision: 1,
335 }
336
337 fakeClientSet := fake.NewSimpleClientset(daemonSetStub)
338 _, err = fakeClientSet.AppsV1().ControllerRevisions("default").Create(context.TODO(), daemonSetControllerRevision, metav1.CreateOptions{})
339 if err != nil {
340 t.Fatalf("create controllerRevisions error %v occurred ", err)
341 }
342
343 var daemonSetHistoryViewer = &DaemonSetHistoryViewer{
344 fakeClientSet,
345 }
346
347 t.Run("should show revisions list if the revision is not specified", func(t *testing.T) {
348 result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 0)
349 if err != nil {
350 t.Fatalf("error getting ViewHistory for DaemonSet moons: %v", err)
351 }
352
353 expected := `REVISION CHANGE-CAUSE
354 1 <none>
355 `
356
357 if result != expected {
358 t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
359 }
360 })
361
362 t.Run("should describe the revision if revision is specified", func(t *testing.T) {
363 result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 1)
364 if err != nil {
365 t.Fatalf("error getting ViewHistory for DaemonSet moons: %v", err)
366 }
367
368 expected := `Pod Template:
369 Labels: foo=bar
370 Containers:
371 test:
372 Image: nginx
373 Port: <none>
374 Host Port: <none>
375 Environment: <none>
376 Mounts: <none>
377 Volumes: <none>
378 Node-Selectors: <none>
379 Tolerations: <none>
380 `
381
382 if result != expected {
383 t.Fatalf("unexpected output (%v was expected but got %v)", expected, result)
384 }
385 })
386
387 t.Run("should get history", func(t *testing.T) {
388 result, err := daemonSetHistoryViewer.GetHistory("default", "moons")
389 if err != nil {
390 t.Fatalf("error getting history for DaemonSet moons: %v", err)
391 }
392
393 if len(result) != 1 {
394 t.Fatalf("unexpected history length (expected 1, got %d", len(result))
395 }
396
397 actual, found := result[1]
398 if !found {
399 t.Fatalf("revision 1 not found in history")
400 }
401 expected := daemonSetStub
402 if !reflect.DeepEqual(expected, actual) {
403 t.Errorf("history does not match. expected %+v, got %+v", expected, actual)
404 }
405 })
406 })
407 }
408
409 func TestApplyDaemonSetHistory(t *testing.T) {
410 tests := []struct {
411 name string
412 source *appsv1.DaemonSet
413 expected *appsv1.DaemonSet
414 }{
415 {
416 "test_less",
417 &appsv1.DaemonSet{
418 Spec: appsv1.DaemonSetSpec{
419 Template: corev1.PodTemplateSpec{
420 ObjectMeta: metav1.ObjectMeta{
421 Annotations: map[string]string{"version": "v3"},
422 },
423 Spec: corev1.PodSpec{
424 InitContainers: []corev1.Container{{Name: "i0"}},
425 Containers: []corev1.Container{{Name: "c0"}},
426 Volumes: []corev1.Volume{{Name: "v0"}},
427 NodeSelector: map[string]string{"1dsa": "n0"},
428 ImagePullSecrets: []corev1.LocalObjectReference{{Name: "ips0"}},
429 Tolerations: []corev1.Toleration{{Key: "t0"}},
430 HostAliases: []corev1.HostAlias{{IP: "h0"}},
431 ReadinessGates: []corev1.PodReadinessGate{{ConditionType: corev1.PodScheduled}},
432 },
433 },
434 },
435 },
436 &appsv1.DaemonSet{
437 Spec: appsv1.DaemonSetSpec{
438 Template: corev1.PodTemplateSpec{
439 Spec: corev1.PodSpec{
440 Containers: []corev1.Container{{Name: "c1"}},
441
442 InitContainers: []corev1.Container{},
443 },
444 },
445 },
446 },
447 },
448 {
449 "test_more",
450 &appsv1.DaemonSet{
451 Spec: appsv1.DaemonSetSpec{
452 Template: corev1.PodTemplateSpec{
453 Spec: corev1.PodSpec{
454 InitContainers: []corev1.Container{{Name: "i0"}},
455 },
456 },
457 },
458 },
459 &appsv1.DaemonSet{
460 Spec: appsv1.DaemonSetSpec{
461 Template: corev1.PodTemplateSpec{
462 ObjectMeta: metav1.ObjectMeta{
463 Annotations: map[string]string{"version": "v3"},
464 },
465 Spec: corev1.PodSpec{
466 InitContainers: []corev1.Container{{Name: "i1"}},
467 Containers: []corev1.Container{{Name: "c1"}},
468 Volumes: []corev1.Volume{{Name: "v1"}},
469 },
470 },
471 },
472 },
473 },
474 {
475 "test_equal",
476 &appsv1.DaemonSet{
477 Spec: appsv1.DaemonSetSpec{
478 Template: corev1.PodTemplateSpec{
479 ObjectMeta: metav1.ObjectMeta{
480 Annotations: map[string]string{"version": "v0"},
481 },
482 Spec: corev1.PodSpec{
483 Containers: []corev1.Container{{Name: "c1"}},
484 InitContainers: []corev1.Container{{Name: "i0"}},
485 Volumes: []corev1.Volume{{Name: "v0"}},
486 },
487 },
488 },
489 },
490 &appsv1.DaemonSet{
491 Spec: appsv1.DaemonSetSpec{
492 Template: corev1.PodTemplateSpec{
493 ObjectMeta: metav1.ObjectMeta{
494 Annotations: map[string]string{"version": "v1"},
495 },
496 Spec: corev1.PodSpec{
497 InitContainers: []corev1.Container{{Name: "i1"}},
498 Containers: []corev1.Container{{Name: "c1"}},
499 Volumes: []corev1.Volume{{Name: "v1"}},
500 },
501 },
502 },
503 },
504 },
505 }
506
507 for _, tt := range tests {
508 t.Run(tt.name, func(t *testing.T) {
509 patch, err := getDaemonSetPatch(tt.expected)
510 if err != nil {
511 t.Errorf("getDaemonSetPatch failed : %v", err)
512 }
513 cr := &appsv1.ControllerRevision{
514 Data: runtime.RawExtension{
515 Raw: patch,
516 },
517 }
518 tt.source, err = applyDaemonSetHistory(tt.source, cr)
519 if err != nil {
520 t.Errorf("applyDaemonSetHistory failed : %v", err)
521 }
522 if !apiequality.Semantic.DeepEqual(tt.source, tt.expected) {
523 t.Errorf("expected out [%v] but get [%v]", tt.expected, tt.source)
524 }
525 })
526 }
527 }
528
View as plain text