1
16
17 package describe
18
19 import (
20 "bytes"
21 "fmt"
22 "reflect"
23 "strings"
24 "testing"
25 "time"
26
27 "github.com/google/go-cmp/cmp"
28 appsv1 "k8s.io/api/apps/v1"
29 autoscalingv1 "k8s.io/api/autoscaling/v1"
30 autoscalingv2 "k8s.io/api/autoscaling/v2"
31 batchv1 "k8s.io/api/batch/v1"
32 coordinationv1 "k8s.io/api/coordination/v1"
33 corev1 "k8s.io/api/core/v1"
34 discoveryv1 "k8s.io/api/discovery/v1"
35 discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
36 networkingv1 "k8s.io/api/networking/v1"
37 networkingv1alpha1 "k8s.io/api/networking/v1alpha1"
38 networkingv1beta1 "k8s.io/api/networking/v1beta1"
39 policyv1 "k8s.io/api/policy/v1"
40 policyv1beta1 "k8s.io/api/policy/v1beta1"
41 schedulingv1 "k8s.io/api/scheduling/v1"
42 storagev1 "k8s.io/api/storage/v1"
43 storagev1alpha1 "k8s.io/api/storage/v1alpha1"
44 apiequality "k8s.io/apimachinery/pkg/api/equality"
45 "k8s.io/apimachinery/pkg/api/resource"
46 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
47 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
48 "k8s.io/apimachinery/pkg/runtime"
49 "k8s.io/apimachinery/pkg/util/intstr"
50 "k8s.io/client-go/kubernetes"
51 "k8s.io/client-go/kubernetes/fake"
52 utilpointer "k8s.io/utils/pointer"
53 "k8s.io/utils/ptr"
54 )
55
56 type describeClient struct {
57 T *testing.T
58 Namespace string
59 Err error
60 kubernetes.Interface
61 }
62
63 func TestDescribePod(t *testing.T) {
64 deletionTimestamp := metav1.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}
65 gracePeriod := int64(1234)
66 condition1 := corev1.PodConditionType("condition1")
67 condition2 := corev1.PodConditionType("condition2")
68 fake := fake.NewSimpleClientset(&corev1.Pod{
69 ObjectMeta: metav1.ObjectMeta{
70 Name: "bar",
71 Namespace: "foo",
72 DeletionTimestamp: &deletionTimestamp,
73 DeletionGracePeriodSeconds: &gracePeriod,
74 },
75 Spec: corev1.PodSpec{
76 ReadinessGates: []corev1.PodReadinessGate{
77 {
78 ConditionType: condition1,
79 },
80 {
81 ConditionType: condition2,
82 },
83 },
84 },
85 Status: corev1.PodStatus{
86 Conditions: []corev1.PodCondition{
87 {
88 Type: condition1,
89 Status: corev1.ConditionTrue,
90 },
91 },
92 },
93 })
94 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
95 d := PodDescriber{c}
96 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
97 if err != nil {
98 t.Errorf("unexpected error: %v", err)
99 }
100 if !strings.Contains(out, "bar") || !strings.Contains(out, "Status:") {
101 t.Errorf("unexpected out: %s", out)
102 }
103 if !strings.Contains(out, "Terminating (lasts 10y)") || !strings.Contains(out, "1234s") {
104 t.Errorf("unexpected out: %s", out)
105 }
106 }
107
108 func TestDescribePodServiceAccount(t *testing.T) {
109 fake := fake.NewSimpleClientset(&corev1.Pod{
110 ObjectMeta: metav1.ObjectMeta{
111 Name: "bar",
112 Namespace: "foo",
113 },
114 Spec: corev1.PodSpec{
115 ServiceAccountName: "fooaccount",
116 },
117 })
118 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
119 d := PodDescriber{c}
120 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
121 if err != nil {
122 t.Errorf("unexpected error: %v", err)
123 }
124 if !strings.Contains(out, "Service Account:") {
125 t.Errorf("unexpected out: %s", out)
126 }
127 if !strings.Contains(out, "fooaccount") {
128 t.Errorf("unexpected out: %s", out)
129 }
130 }
131
132 func TestDescribePodEphemeralContainers(t *testing.T) {
133 fake := fake.NewSimpleClientset(&corev1.Pod{
134 ObjectMeta: metav1.ObjectMeta{
135 Name: "bar",
136 Namespace: "foo",
137 },
138 Spec: corev1.PodSpec{
139 EphemeralContainers: []corev1.EphemeralContainer{
140 {
141 EphemeralContainerCommon: corev1.EphemeralContainerCommon{
142 Name: "debugger",
143 Image: "busybox",
144 },
145 },
146 },
147 },
148 Status: corev1.PodStatus{
149 EphemeralContainerStatuses: []corev1.ContainerStatus{
150 {
151 Name: "debugger",
152 State: corev1.ContainerState{
153 Running: &corev1.ContainerStateRunning{
154 StartedAt: metav1.NewTime(time.Now()),
155 },
156 },
157 Ready: false,
158 RestartCount: 0,
159 },
160 },
161 },
162 })
163 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
164 d := PodDescriber{c}
165 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
166 if err != nil {
167 t.Errorf("unexpected error: %v", err)
168 }
169 if !strings.Contains(out, "debugger:") {
170 t.Errorf("unexpected out: %s", out)
171 }
172 if !strings.Contains(out, "busybox") {
173 t.Errorf("unexpected out: %s", out)
174 }
175 }
176
177 func TestDescribePodNode(t *testing.T) {
178 fake := fake.NewSimpleClientset(&corev1.Pod{
179 ObjectMeta: metav1.ObjectMeta{
180 Name: "bar",
181 Namespace: "foo",
182 },
183 Spec: corev1.PodSpec{
184 NodeName: "all-in-one",
185 },
186 Status: corev1.PodStatus{
187 HostIP: "127.0.0.1",
188 NominatedNodeName: "nodeA",
189 },
190 })
191 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
192 d := PodDescriber{c}
193 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
194 if err != nil {
195 t.Errorf("unexpected error: %v", err)
196 }
197 if !strings.Contains(out, "all-in-one/127.0.0.1") {
198 t.Errorf("unexpected out: %s", out)
199 }
200 if !strings.Contains(out, "nodeA") {
201 t.Errorf("unexpected out: %s", out)
202 }
203 }
204
205 func TestDescribePodTolerations(t *testing.T) {
206 fake := fake.NewSimpleClientset(&corev1.Pod{
207 ObjectMeta: metav1.ObjectMeta{
208 Name: "bar",
209 Namespace: "foo",
210 },
211 Spec: corev1.PodSpec{
212 Tolerations: []corev1.Toleration{
213 {Operator: corev1.TolerationOpExists},
214 {Effect: corev1.TaintEffectNoSchedule, Operator: corev1.TolerationOpExists},
215 {Key: "key0", Operator: corev1.TolerationOpExists},
216 {Key: "key1", Value: "value1"},
217 {Key: "key2", Operator: corev1.TolerationOpEqual, Value: "value2", Effect: corev1.TaintEffectNoSchedule},
218 {Key: "key3", Value: "value3", Effect: corev1.TaintEffectNoExecute, TolerationSeconds: &[]int64{300}[0]},
219 {Key: "key4", Effect: corev1.TaintEffectNoExecute, TolerationSeconds: &[]int64{60}[0]},
220 },
221 },
222 })
223 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
224 d := PodDescriber{c}
225 out, err := d.Describe("foo", "bar", DescriberSettings{})
226 if err != nil {
227 t.Errorf("unexpected error: %v", err)
228 }
229 if !strings.Contains(out, " op=Exists\n") ||
230 !strings.Contains(out, ":NoSchedule op=Exists\n") ||
231 !strings.Contains(out, "key0 op=Exists\n") ||
232 !strings.Contains(out, "key1=value1\n") ||
233 !strings.Contains(out, "key2=value2:NoSchedule\n") ||
234 !strings.Contains(out, "key3=value3:NoExecute for 300s\n") ||
235 !strings.Contains(out, "key4:NoExecute for 60s\n") ||
236 !strings.Contains(out, "Tolerations:") {
237 t.Errorf("unexpected out:\n%s", out)
238 }
239 }
240
241 func TestDescribeTopologySpreadConstraints(t *testing.T) {
242 fake := fake.NewSimpleClientset(&corev1.Pod{
243 ObjectMeta: metav1.ObjectMeta{
244 Name: "bar",
245 Namespace: "foo",
246 },
247 Spec: corev1.PodSpec{
248 TopologySpreadConstraints: []corev1.TopologySpreadConstraint{
249 {
250 MaxSkew: 3,
251 TopologyKey: "topology.kubernetes.io/test1",
252 WhenUnsatisfiable: "DoNotSchedule",
253 LabelSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"key1": "val1", "key2": "val2"}},
254 },
255 {
256 MaxSkew: 1,
257 TopologyKey: "topology.kubernetes.io/test2",
258 WhenUnsatisfiable: "ScheduleAnyway",
259 },
260 },
261 },
262 })
263 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
264 d := PodDescriber{c}
265 out, err := d.Describe("foo", "bar", DescriberSettings{})
266 if err != nil {
267 t.Errorf("unexpected error: %v", err)
268 }
269 if !strings.Contains(out, "topology.kubernetes.io/test1:DoNotSchedule when max skew 3 is exceeded for selector key1=val1,key2=val2\n") ||
270 !strings.Contains(out, "topology.kubernetes.io/test2:ScheduleAnyway when max skew 1 is exceeded\n") {
271 t.Errorf("unexpected out:\n%s", out)
272 }
273 }
274
275 func TestDescribeSecret(t *testing.T) {
276 fake := fake.NewSimpleClientset(&corev1.Secret{
277 ObjectMeta: metav1.ObjectMeta{
278 Name: "bar",
279 Namespace: "foo",
280 },
281 Data: map[string][]byte{
282 "username": []byte("YWRtaW4="),
283 "password": []byte("MWYyZDFlMmU2N2Rm"),
284 },
285 })
286 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
287 d := SecretDescriber{c}
288 out, err := d.Describe("foo", "bar", DescriberSettings{})
289 if err != nil {
290 t.Errorf("unexpected error: %v", err)
291 }
292 if !strings.Contains(out, "bar") || !strings.Contains(out, "foo") || !strings.Contains(out, "username") || !strings.Contains(out, "8 bytes") || !strings.Contains(out, "password") || !strings.Contains(out, "16 bytes") {
293 t.Errorf("unexpected out: %s", out)
294 }
295 if strings.Contains(out, "YWRtaW4=") || strings.Contains(out, "MWYyZDFlMmU2N2Rm") {
296 t.Errorf("sensitive data should not be shown, unexpected out: %s", out)
297 }
298 }
299
300 func TestDescribeNamespace(t *testing.T) {
301 exampleNamespaceName := "example"
302
303 testCases := []struct {
304 name string
305 namespace *corev1.Namespace
306 expect []string
307 }{
308 {
309 name: "no quotas or limit ranges",
310 namespace: &corev1.Namespace{
311 ObjectMeta: metav1.ObjectMeta{
312 Name: exampleNamespaceName,
313 },
314 Status: corev1.NamespaceStatus{
315 Phase: corev1.NamespaceActive,
316 },
317 },
318 expect: []string{
319 "Name",
320 exampleNamespaceName,
321 "Status",
322 string(corev1.NamespaceActive),
323 "No resource quota",
324 "No LimitRange resource.",
325 },
326 },
327 {
328 name: "has conditions",
329 namespace: &corev1.Namespace{
330 ObjectMeta: metav1.ObjectMeta{
331 Name: exampleNamespaceName,
332 },
333 Status: corev1.NamespaceStatus{
334 Phase: corev1.NamespaceTerminating,
335 Conditions: []corev1.NamespaceCondition{
336 {
337 LastTransitionTime: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
338 Message: "example message",
339 Reason: "example reason",
340 Status: corev1.ConditionTrue,
341 Type: corev1.NamespaceDeletionContentFailure,
342 },
343 },
344 },
345 },
346 expect: []string{
347 "Name",
348 exampleNamespaceName,
349 "Status",
350 string(corev1.NamespaceTerminating),
351 "Conditions",
352 "Type",
353 string(corev1.NamespaceDeletionContentFailure),
354 "Status",
355 string(corev1.ConditionTrue),
356 "Reason",
357 "example reason",
358 "Message",
359 "example message",
360 "No resource quota",
361 "No LimitRange resource.",
362 },
363 },
364 }
365
366 for _, testCase := range testCases {
367 t.Run(testCase.name, func(t *testing.T) {
368 fake := fake.NewSimpleClientset(testCase.namespace)
369 c := &describeClient{T: t, Namespace: "", Interface: fake}
370 d := NamespaceDescriber{c}
371
372 out, err := d.Describe("", testCase.namespace.Name, DescriberSettings{ShowEvents: true})
373 if err != nil {
374 t.Errorf("unexpected error: %v", err)
375 }
376
377 for _, expected := range testCase.expect {
378 if !strings.Contains(out, expected) {
379 t.Errorf("expected to find %q in output: %q", expected, out)
380 }
381 }
382 })
383 }
384 }
385
386 func TestDescribePodPriority(t *testing.T) {
387 priority := int32(1000)
388 fake := fake.NewSimpleClientset(&corev1.Pod{
389 ObjectMeta: metav1.ObjectMeta{
390 Name: "bar",
391 },
392 Spec: corev1.PodSpec{
393 PriorityClassName: "high-priority",
394 Priority: &priority,
395 },
396 })
397 c := &describeClient{T: t, Namespace: "", Interface: fake}
398 d := PodDescriber{c}
399 out, err := d.Describe("", "bar", DescriberSettings{ShowEvents: true})
400 if err != nil {
401 t.Errorf("unexpected error: %v", err)
402 }
403 if !strings.Contains(out, "high-priority") || !strings.Contains(out, "1000") {
404 t.Errorf("unexpected out: %s", out)
405 }
406 }
407
408 func TestDescribePodRuntimeClass(t *testing.T) {
409 runtimeClassNames := []string{"test1", ""}
410 testCases := []struct {
411 name string
412 pod *corev1.Pod
413 expect []string
414 unexpect []string
415 }{
416 {
417 name: "test1",
418 pod: &corev1.Pod{
419 ObjectMeta: metav1.ObjectMeta{
420 Name: "bar",
421 },
422 Spec: corev1.PodSpec{
423 RuntimeClassName: &runtimeClassNames[0],
424 },
425 },
426 expect: []string{
427 "Name", "bar",
428 "Runtime Class Name", "test1",
429 },
430 unexpect: []string{},
431 },
432 {
433 name: "test2",
434 pod: &corev1.Pod{
435 ObjectMeta: metav1.ObjectMeta{
436 Name: "bar",
437 },
438 Spec: corev1.PodSpec{
439 RuntimeClassName: &runtimeClassNames[1],
440 },
441 },
442 expect: []string{
443 "Name", "bar",
444 },
445 unexpect: []string{
446 "Runtime Class Name",
447 },
448 },
449 {
450 name: "test3",
451 pod: &corev1.Pod{
452 ObjectMeta: metav1.ObjectMeta{
453 Name: "bar",
454 },
455 Spec: corev1.PodSpec{},
456 },
457 expect: []string{
458 "Name", "bar",
459 },
460 unexpect: []string{
461 "Runtime Class Name",
462 },
463 },
464 }
465 for _, testCase := range testCases {
466 t.Run(testCase.name, func(t *testing.T) {
467 fake := fake.NewSimpleClientset(testCase.pod)
468 c := &describeClient{T: t, Interface: fake}
469 d := PodDescriber{c}
470 out, err := d.Describe("", "bar", DescriberSettings{ShowEvents: true})
471 if err != nil {
472 t.Errorf("unexpected error: %v", err)
473 }
474 for _, expected := range testCase.expect {
475 if !strings.Contains(out, expected) {
476 t.Errorf("expected to find %q in output: %q", expected, out)
477 }
478 }
479 for _, unexpected := range testCase.unexpect {
480 if strings.Contains(out, unexpected) {
481 t.Errorf("unexpected to find %q in output: %q", unexpected, out)
482 }
483 }
484 })
485 }
486 }
487
488 func TestDescribePriorityClass(t *testing.T) {
489 preemptLowerPriority := corev1.PreemptLowerPriority
490 preemptNever := corev1.PreemptNever
491
492 testCases := []struct {
493 name string
494 priorityClass *schedulingv1.PriorityClass
495 expect []string
496 }{
497 {
498 name: "test1",
499 priorityClass: &schedulingv1.PriorityClass{
500 ObjectMeta: metav1.ObjectMeta{
501 Name: "bar",
502 },
503 Value: 10,
504 GlobalDefault: false,
505 PreemptionPolicy: &preemptLowerPriority,
506 Description: "test1",
507 },
508 expect: []string{
509 "Name", "bar",
510 "Value", "10",
511 "GlobalDefault", "false",
512 "PreemptionPolicy", "PreemptLowerPriority",
513 "Description", "test1",
514 "Annotations", "",
515 },
516 },
517 {
518 name: "test2",
519 priorityClass: &schedulingv1.PriorityClass{
520 ObjectMeta: metav1.ObjectMeta{
521 Name: "bar",
522 },
523 Value: 100,
524 GlobalDefault: true,
525 PreemptionPolicy: &preemptNever,
526 Description: "test2",
527 },
528 expect: []string{
529 "Name", "bar",
530 "Value", "100",
531 "GlobalDefault", "true",
532 "PreemptionPolicy", "Never",
533 "Description", "test2",
534 "Annotations", "",
535 },
536 },
537 }
538 for _, testCase := range testCases {
539 t.Run(testCase.name, func(t *testing.T) {
540 fake := fake.NewSimpleClientset(testCase.priorityClass)
541 c := &describeClient{T: t, Interface: fake}
542 d := PriorityClassDescriber{c}
543 out, err := d.Describe("", "bar", DescriberSettings{ShowEvents: true})
544 if err != nil {
545 t.Errorf("unexpected error: %v", err)
546 }
547 for _, expected := range testCase.expect {
548 if !strings.Contains(out, expected) {
549 t.Errorf("expected to find %q in output: %q", expected, out)
550 }
551 }
552 })
553 }
554 }
555
556 func TestDescribeConfigMap(t *testing.T) {
557 fake := fake.NewSimpleClientset(&corev1.ConfigMap{
558 ObjectMeta: metav1.ObjectMeta{
559 Name: "mycm",
560 Namespace: "foo",
561 },
562 Data: map[string]string{
563 "key1": "value1",
564 "key2": "value2",
565 },
566 BinaryData: map[string][]byte{
567 "binarykey1": {0xFF, 0xFE, 0xFD, 0xFC, 0xFB},
568 "binarykey2": {0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA},
569 },
570 })
571 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
572 d := ConfigMapDescriber{c}
573 out, err := d.Describe("foo", "mycm", DescriberSettings{ShowEvents: true})
574 if err != nil {
575 t.Errorf("unexpected error: %v", err)
576 }
577 if !strings.Contains(out, "foo") || !strings.Contains(out, "mycm") {
578 t.Errorf("unexpected out: %s", out)
579 }
580 if !strings.Contains(out, "key1") || !strings.Contains(out, "value1") || !strings.Contains(out, "key2") || !strings.Contains(out, "value2") {
581 t.Errorf("unexpected out: %s", out)
582 }
583 if !strings.Contains(out, "binarykey1") || !strings.Contains(out, "5 bytes") || !strings.Contains(out, "binarykey2") || !strings.Contains(out, "6 bytes") {
584 t.Errorf("unexpected out: %s", out)
585 }
586 }
587
588 func TestDescribeLimitRange(t *testing.T) {
589 fake := fake.NewSimpleClientset(&corev1.LimitRange{
590 ObjectMeta: metav1.ObjectMeta{
591 Name: "mylr",
592 Namespace: "foo",
593 },
594 Spec: corev1.LimitRangeSpec{
595 Limits: []corev1.LimitRangeItem{
596 {
597 Type: corev1.LimitTypePod,
598 Max: getResourceList("100m", "10000Mi"),
599 Min: getResourceList("5m", "100Mi"),
600 MaxLimitRequestRatio: getResourceList("10", ""),
601 },
602 {
603 Type: corev1.LimitTypeContainer,
604 Max: getResourceList("100m", "10000Mi"),
605 Min: getResourceList("5m", "100Mi"),
606 Default: getResourceList("50m", "500Mi"),
607 DefaultRequest: getResourceList("10m", "200Mi"),
608 MaxLimitRequestRatio: getResourceList("10", ""),
609 },
610 {
611 Type: corev1.LimitTypePersistentVolumeClaim,
612 Max: getStorageResourceList("10Gi"),
613 Min: getStorageResourceList("5Gi"),
614 },
615 },
616 },
617 })
618 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
619 d := LimitRangeDescriber{c}
620 out, err := d.Describe("foo", "mylr", DescriberSettings{ShowEvents: true})
621 if err != nil {
622 t.Errorf("unexpected error: %v", err)
623 }
624
625 checks := []string{"foo", "mylr", "Pod", "cpu", "5m", "100m", "memory", "100Mi", "10000Mi", "10", "Container", "cpu", "10m", "50m", "200Mi", "500Mi", "PersistentVolumeClaim", "storage", "5Gi", "10Gi"}
626 for _, check := range checks {
627 if !strings.Contains(out, check) {
628 t.Errorf("unexpected out: %s", out)
629 }
630 }
631 }
632
633 func getStorageResourceList(storage string) corev1.ResourceList {
634 res := corev1.ResourceList{}
635 if storage != "" {
636 res[corev1.ResourceStorage] = resource.MustParse(storage)
637 }
638 return res
639 }
640
641 func getResourceList(cpu, memory string) corev1.ResourceList {
642 res := corev1.ResourceList{}
643 if cpu != "" {
644 res[corev1.ResourceCPU] = resource.MustParse(cpu)
645 }
646 if memory != "" {
647 res[corev1.ResourceMemory] = resource.MustParse(memory)
648 }
649 return res
650 }
651
652 func TestDescribeService(t *testing.T) {
653 singleStack := corev1.IPFamilyPolicySingleStack
654 testCases := []struct {
655 name string
656 service *corev1.Service
657 expect []string
658 }{
659 {
660 name: "test1",
661 service: &corev1.Service{
662 ObjectMeta: metav1.ObjectMeta{
663 Name: "bar",
664 Namespace: "foo",
665 },
666 Spec: corev1.ServiceSpec{
667 Type: corev1.ServiceTypeLoadBalancer,
668 Ports: []corev1.ServicePort{{
669 Name: "port-tcp",
670 Port: 8080,
671 Protocol: corev1.ProtocolTCP,
672 TargetPort: intstr.FromInt32(9527),
673 NodePort: 31111,
674 }},
675 Selector: map[string]string{"blah": "heh"},
676 ClusterIP: "1.2.3.4",
677 IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
678 LoadBalancerIP: "5.6.7.8",
679 SessionAffinity: "None",
680 ExternalTrafficPolicy: "Local",
681 HealthCheckNodePort: 32222,
682 },
683 },
684 expect: []string{
685 "Name", "bar",
686 "Namespace", "foo",
687 "Selector", "blah=heh",
688 "Type", "LoadBalancer",
689 "IP", "1.2.3.4",
690 "Port", "port-tcp", "8080/TCP",
691 "TargetPort", "9527/TCP",
692 "NodePort", "port-tcp", "31111/TCP",
693 "Session Affinity", "None",
694 "External Traffic Policy", "Local",
695 "HealthCheck NodePort", "32222",
696 },
697 },
698 {
699 name: "test2",
700 service: &corev1.Service{
701 ObjectMeta: metav1.ObjectMeta{
702 Name: "bar",
703 Namespace: "foo",
704 },
705 Spec: corev1.ServiceSpec{
706 Type: corev1.ServiceTypeLoadBalancer,
707 Ports: []corev1.ServicePort{{
708 Name: "port-tcp",
709 Port: 8080,
710 Protocol: corev1.ProtocolTCP,
711 TargetPort: intstr.FromString("targetPort"),
712 NodePort: 31111,
713 }},
714 Selector: map[string]string{"blah": "heh"},
715 ClusterIP: "1.2.3.4",
716 IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
717 LoadBalancerIP: "5.6.7.8",
718 SessionAffinity: "None",
719 ExternalTrafficPolicy: "Local",
720 HealthCheckNodePort: 32222,
721 },
722 },
723 expect: []string{
724 "Name", "bar",
725 "Namespace", "foo",
726 "Selector", "blah=heh",
727 "Type", "LoadBalancer",
728 "IP", "1.2.3.4",
729 "Port", "port-tcp", "8080/TCP",
730 "TargetPort", "targetPort/TCP",
731 "NodePort", "port-tcp", "31111/TCP",
732 "Session Affinity", "None",
733 "External Traffic Policy", "Local",
734 "HealthCheck NodePort", "32222",
735 },
736 },
737 {
738 name: "test-ServiceIPFamily",
739 service: &corev1.Service{
740 ObjectMeta: metav1.ObjectMeta{
741 Name: "bar",
742 Namespace: "foo",
743 },
744 Spec: corev1.ServiceSpec{
745 Type: corev1.ServiceTypeLoadBalancer,
746 Ports: []corev1.ServicePort{{
747 Name: "port-tcp",
748 Port: 8080,
749 Protocol: corev1.ProtocolTCP,
750 TargetPort: intstr.FromString("targetPort"),
751 NodePort: 31111,
752 }},
753 Selector: map[string]string{"blah": "heh"},
754 ClusterIP: "1.2.3.4",
755 IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
756 LoadBalancerIP: "5.6.7.8",
757 SessionAffinity: "None",
758 ExternalTrafficPolicy: "Local",
759 HealthCheckNodePort: 32222,
760 },
761 },
762 expect: []string{
763 "Name", "bar",
764 "Namespace", "foo",
765 "Selector", "blah=heh",
766 "Type", "LoadBalancer",
767 "IP", "1.2.3.4",
768 "IP Families", "IPv4",
769 "Port", "port-tcp", "8080/TCP",
770 "TargetPort", "targetPort/TCP",
771 "NodePort", "port-tcp", "31111/TCP",
772 "Session Affinity", "None",
773 "External Traffic Policy", "Local",
774 "HealthCheck NodePort", "32222",
775 },
776 },
777 {
778 name: "test-ServiceIPFamilyPolicy+ClusterIPs",
779 service: &corev1.Service{
780 ObjectMeta: metav1.ObjectMeta{
781 Name: "bar",
782 Namespace: "foo",
783 },
784 Spec: corev1.ServiceSpec{
785 Type: corev1.ServiceTypeLoadBalancer,
786 Ports: []corev1.ServicePort{{
787 Name: "port-tcp",
788 Port: 8080,
789 Protocol: corev1.ProtocolTCP,
790 TargetPort: intstr.FromString("targetPort"),
791 NodePort: 31111,
792 }},
793 Selector: map[string]string{"blah": "heh"},
794 ClusterIP: "1.2.3.4",
795 IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
796 IPFamilyPolicy: &singleStack,
797 ClusterIPs: []string{"1.2.3.4"},
798 LoadBalancerIP: "5.6.7.8",
799 SessionAffinity: "None",
800 ExternalTrafficPolicy: "Local",
801 HealthCheckNodePort: 32222,
802 },
803 },
804 expect: []string{
805 "Name", "bar",
806 "Namespace", "foo",
807 "Selector", "blah=heh",
808 "Type", "LoadBalancer",
809 "IP", "1.2.3.4",
810 "IP Families", "IPv4",
811 "IP Family Policy", "SingleStack",
812 "IPs", "1.2.3.4",
813 "Port", "port-tcp", "8080/TCP",
814 "TargetPort", "targetPort/TCP",
815 "NodePort", "port-tcp", "31111/TCP",
816 "Session Affinity", "None",
817 "External Traffic Policy", "Local",
818 "HealthCheck NodePort", "32222",
819 },
820 },
821 }
822 for _, testCase := range testCases {
823 t.Run(testCase.name, func(t *testing.T) {
824 fake := fake.NewSimpleClientset(testCase.service)
825 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
826 d := ServiceDescriber{c}
827 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
828 if err != nil {
829 t.Errorf("unexpected error: %v", err)
830 }
831 for _, expected := range testCase.expect {
832 if !strings.Contains(out, expected) {
833 t.Errorf("expected to find %q in output: %q", expected, out)
834 }
835 }
836 })
837 }
838 }
839
840 func TestPodDescribeResultsSorted(t *testing.T) {
841
842 fake := fake.NewSimpleClientset(
843 &corev1.EventList{
844 Items: []corev1.Event{
845 {
846 ObjectMeta: metav1.ObjectMeta{Name: "one"},
847 Source: corev1.EventSource{Component: "kubelet"},
848 Message: "Item 1",
849 FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
850 LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
851 Count: 1,
852 Type: corev1.EventTypeNormal,
853 },
854 {
855 ObjectMeta: metav1.ObjectMeta{Name: "two"},
856 Source: corev1.EventSource{Component: "scheduler"},
857 Message: "Item 2",
858 FirstTimestamp: metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)),
859 LastTimestamp: metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)),
860 Count: 1,
861 Type: corev1.EventTypeNormal,
862 },
863 {
864 ObjectMeta: metav1.ObjectMeta{Name: "three"},
865 Source: corev1.EventSource{Component: "kubelet"},
866 Message: "Item 3",
867 FirstTimestamp: metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)),
868 LastTimestamp: metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)),
869 Count: 1,
870 Type: corev1.EventTypeNormal,
871 },
872 },
873 },
874 &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}},
875 )
876 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
877 d := PodDescriber{c}
878
879
880 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
881
882
883 if err != nil {
884 t.Errorf("unexpected error: %v", err)
885 }
886 VerifyDatesInOrder(out, "\n" , "\t" , t)
887 }
888
889
890
891 func VerifyDatesInOrder(
892 resultToTest, rowDelimiter, columnDelimiter string, t *testing.T) {
893 lines := strings.Split(resultToTest, rowDelimiter)
894 var previousTime time.Time
895 for _, str := range lines {
896 columns := strings.Split(str, columnDelimiter)
897 if len(columns) > 0 {
898 currentTime, err := time.Parse(time.RFC1123Z, columns[0])
899 if err == nil {
900 if previousTime.After(currentTime) {
901 t.Errorf(
902 "Output is not sorted by time. %s should be listed after %s. Complete output: %s",
903 previousTime.Format(time.RFC1123Z),
904 currentTime.Format(time.RFC1123Z),
905 resultToTest)
906 }
907 previousTime = currentTime
908 }
909 }
910 }
911 }
912
913 func TestDescribeContainers(t *testing.T) {
914 trueVal := true
915 testCases := []struct {
916 container corev1.Container
917 status corev1.ContainerStatus
918 expectedElements []string
919 }{
920
921 {
922 container: corev1.Container{Name: "test", Image: "image"},
923 status: corev1.ContainerStatus{
924 Name: "test",
925 State: corev1.ContainerState{
926 Running: &corev1.ContainerStateRunning{
927 StartedAt: metav1.NewTime(time.Now()),
928 },
929 },
930 Ready: true,
931 RestartCount: 7,
932 },
933 expectedElements: []string{"test", "State", "Running", "Ready", "True", "Restart Count", "7", "Image", "image", "Started"},
934 },
935
936 {
937 container: corev1.Container{Name: "test", Image: "image"},
938 status: corev1.ContainerStatus{
939 Name: "test",
940 State: corev1.ContainerState{
941 Waiting: &corev1.ContainerStateWaiting{
942 Reason: "potato",
943 },
944 },
945 Ready: true,
946 RestartCount: 7,
947 },
948 expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "Reason", "potato"},
949 },
950
951 {
952 container: corev1.Container{Name: "test", Image: "image"},
953 status: corev1.ContainerStatus{
954 Name: "test",
955 State: corev1.ContainerState{
956 Terminated: &corev1.ContainerStateTerminated{
957 StartedAt: metav1.NewTime(time.Now()),
958 FinishedAt: metav1.NewTime(time.Now()),
959 Reason: "potato",
960 ExitCode: 2,
961 },
962 },
963 Ready: true,
964 RestartCount: 7,
965 },
966 expectedElements: []string{"test", "State", "Terminated", "Ready", "True", "Restart Count", "7", "Image", "image", "Reason", "potato", "Started", "Finished", "Exit Code", "2"},
967 },
968
969 {
970 container: corev1.Container{Name: "test", Image: "image"},
971 status: corev1.ContainerStatus{
972 Name: "test",
973 State: corev1.ContainerState{
974 Running: &corev1.ContainerStateRunning{
975 StartedAt: metav1.NewTime(time.Now()),
976 },
977 },
978 LastTerminationState: corev1.ContainerState{
979 Terminated: &corev1.ContainerStateTerminated{
980 StartedAt: metav1.NewTime(time.Now().Add(time.Second * 3)),
981 FinishedAt: metav1.NewTime(time.Now()),
982 Reason: "crashing",
983 ExitCode: 3,
984 },
985 },
986 Ready: true,
987 RestartCount: 7,
988 },
989 expectedElements: []string{"test", "State", "Terminated", "Ready", "True", "Restart Count", "7", "Image", "image", "Started", "Finished", "Exit Code", "2", "crashing", "3"},
990 },
991
992 {
993 container: corev1.Container{Name: "test", Image: "image"},
994 status: corev1.ContainerStatus{
995 Name: "test",
996 Ready: true,
997 RestartCount: 7,
998 },
999 expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image"},
1000 },
1001
1002 {
1003 container: corev1.Container{Name: "test", Image: "image", Env: []corev1.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []corev1.EnvFromSource{{ConfigMapRef: &corev1.ConfigMapEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "a123"}}}}},
1004 status: corev1.ContainerStatus{
1005 Name: "test",
1006 Ready: true,
1007 RestartCount: 7,
1008 },
1009 expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap\tOptional: false"},
1010 },
1011 {
1012 container: corev1.Container{Name: "test", Image: "image", Env: []corev1.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []corev1.EnvFromSource{{Prefix: "p_", ConfigMapRef: &corev1.ConfigMapEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "a123"}}}}},
1013 status: corev1.ContainerStatus{
1014 Name: "test",
1015 Ready: true,
1016 RestartCount: 7,
1017 },
1018 expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap with prefix 'p_'\tOptional: false"},
1019 },
1020 {
1021 container: corev1.Container{Name: "test", Image: "image", Env: []corev1.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []corev1.EnvFromSource{{ConfigMapRef: &corev1.ConfigMapEnvSource{Optional: &trueVal, LocalObjectReference: corev1.LocalObjectReference{Name: "a123"}}}}},
1022 status: corev1.ContainerStatus{
1023 Name: "test",
1024 Ready: true,
1025 RestartCount: 7,
1026 },
1027 expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap\tOptional: true"},
1028 },
1029 {
1030 container: corev1.Container{Name: "test", Image: "image", Env: []corev1.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []corev1.EnvFromSource{{SecretRef: &corev1.SecretEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "a123"}, Optional: &trueVal}}}},
1031 status: corev1.ContainerStatus{
1032 Name: "test",
1033 Ready: true,
1034 RestartCount: 7,
1035 },
1036 expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tSecret\tOptional: true"},
1037 },
1038 {
1039 container: corev1.Container{Name: "test", Image: "image", Env: []corev1.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []corev1.EnvFromSource{{Prefix: "p_", SecretRef: &corev1.SecretEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "a123"}}}}},
1040 status: corev1.ContainerStatus{
1041 Name: "test",
1042 Ready: true,
1043 RestartCount: 7,
1044 },
1045 expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tSecret with prefix 'p_'\tOptional: false"},
1046 },
1047
1048 {
1049 container: corev1.Container{Name: "test", Image: "image", Command: []string{"sleep", "1000"}},
1050 status: corev1.ContainerStatus{
1051 Name: "test",
1052 Ready: true,
1053 RestartCount: 7,
1054 },
1055 expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "sleep", "1000"},
1056 },
1057
1058 {
1059 container: corev1.Container{Name: "test", Image: "image", Command: []string{"sleep", "1000\n2000"}},
1060 status: corev1.ContainerStatus{
1061 Name: "test",
1062 Ready: true,
1063 RestartCount: 7,
1064 },
1065 expectedElements: []string{"1000\n 2000"},
1066 },
1067
1068 {
1069 container: corev1.Container{Name: "test", Image: "image", Args: []string{"time", "1000"}},
1070 status: corev1.ContainerStatus{
1071 Name: "test",
1072 Ready: true,
1073 RestartCount: 7,
1074 },
1075 expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "time", "1000"},
1076 },
1077
1078 {
1079 container: corev1.Container{Name: "test", Image: "image", Args: []string{"time", "1000\n2000"}},
1080 status: corev1.ContainerStatus{
1081 Name: "test",
1082 Ready: true,
1083 RestartCount: 7,
1084 },
1085 expectedElements: []string{"1000\n 2000"},
1086 },
1087
1088 {
1089 container: corev1.Container{
1090 Name: "test",
1091 Image: "image",
1092 Resources: corev1.ResourceRequirements{
1093 Limits: corev1.ResourceList{
1094 corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("1000"),
1095 corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("4G"),
1096 corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("20G"),
1097 },
1098 },
1099 },
1100 status: corev1.ContainerStatus{
1101 Name: "test",
1102 Ready: true,
1103 RestartCount: 7,
1104 },
1105 expectedElements: []string{"cpu", "1k", "memory", "4G", "storage", "20G"},
1106 },
1107
1108 {
1109 container: corev1.Container{
1110 Name: "test",
1111 Image: "image",
1112 Resources: corev1.ResourceRequirements{
1113 Requests: corev1.ResourceList{
1114 corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("1000"),
1115 corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("4G"),
1116 corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("20G"),
1117 },
1118 },
1119 },
1120 expectedElements: []string{"cpu", "1k", "memory", "4G", "storage", "20G"},
1121 },
1122
1123 {
1124 container: corev1.Container{
1125 Name: "test",
1126 Image: "image",
1127 VolumeMounts: []corev1.VolumeMount{
1128 {
1129 Name: "mounted-volume",
1130 MountPath: "/opt/",
1131 },
1132 },
1133 },
1134 expectedElements: []string{"mounted-volume", "/opt/", "(rw)"},
1135 },
1136
1137 {
1138 container: corev1.Container{
1139 Name: "test",
1140 Image: "image",
1141 VolumeMounts: []corev1.VolumeMount{
1142 {
1143 Name: "mounted-volume",
1144 MountPath: "/opt/",
1145 ReadOnly: true,
1146 },
1147 },
1148 },
1149 expectedElements: []string{"Mounts", "mounted-volume", "/opt/", "(ro)"},
1150 },
1151
1152
1153 {
1154 container: corev1.Container{
1155 Name: "test",
1156 Image: "image",
1157 VolumeMounts: []corev1.VolumeMount{
1158 {
1159 Name: "mounted-volume",
1160 MountPath: "/opt/",
1161 SubPath: "foo",
1162 },
1163 },
1164 },
1165 expectedElements: []string{"Mounts", "mounted-volume", "/opt/", "(rw,path=\"foo\")"},
1166 },
1167
1168
1169 {
1170 container: corev1.Container{
1171 Name: "test",
1172 Image: "image",
1173 VolumeDevices: []corev1.VolumeDevice{
1174 {
1175 Name: "volume-device",
1176 DevicePath: "/dev/xvda",
1177 },
1178 },
1179 },
1180 expectedElements: []string{"Devices", "volume-device", "/dev/xvda"},
1181 },
1182 }
1183
1184 for i, testCase := range testCases {
1185 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
1186 out := new(bytes.Buffer)
1187 pod := corev1.Pod{
1188 Spec: corev1.PodSpec{
1189 Containers: []corev1.Container{testCase.container},
1190 },
1191 Status: corev1.PodStatus{
1192 ContainerStatuses: []corev1.ContainerStatus{testCase.status},
1193 },
1194 }
1195 writer := NewPrefixWriter(out)
1196 describeContainers("Containers", pod.Spec.Containers, pod.Status.ContainerStatuses, EnvValueRetriever(&pod), writer, "")
1197 output := out.String()
1198 for _, expected := range testCase.expectedElements {
1199 if !strings.Contains(output, expected) {
1200 t.Errorf("Test case %d: expected to find %q in output: %q", i, expected, output)
1201 }
1202 }
1203 })
1204 }
1205 }
1206
1207 func TestDescribers(t *testing.T) {
1208 first := &corev1.Event{}
1209 second := &corev1.Pod{}
1210 var third *corev1.Pod
1211 testErr := fmt.Errorf("test")
1212 d := Describers{}
1213 d.Add(
1214 func(e *corev1.Event, p *corev1.Pod) (string, error) {
1215 if e != first {
1216 t.Errorf("first argument not equal: %#v", e)
1217 }
1218 if p != second {
1219 t.Errorf("second argument not equal: %#v", p)
1220 }
1221 return "test", testErr
1222 },
1223 )
1224 if out, err := d.DescribeObject(first, second); out != "test" || err != testErr {
1225 t.Errorf("unexpected result: %s %v", out, err)
1226 }
1227
1228 if out, err := d.DescribeObject(first, second, third); out != "" || err == nil {
1229 t.Errorf("unexpected result: %s %v", out, err)
1230 } else {
1231 if noDescriber, ok := err.(ErrNoDescriber); ok {
1232 if !reflect.DeepEqual(noDescriber.Types, []string{"*v1.Event", "*v1.Pod", "*v1.Pod"}) {
1233 t.Errorf("unexpected describer: %v", err)
1234 }
1235 } else {
1236 t.Errorf("unexpected error type: %v", err)
1237 }
1238 }
1239
1240 d.Add(
1241 func(e *corev1.Event) (string, error) {
1242 if e != first {
1243 t.Errorf("first argument not equal: %#v", e)
1244 }
1245 return "simpler", testErr
1246 },
1247 )
1248 if out, err := d.DescribeObject(first); out != "simpler" || err != testErr {
1249 t.Errorf("unexpected result: %s %v", out, err)
1250 }
1251 }
1252
1253 func TestDefaultDescribers(t *testing.T) {
1254 out, err := DefaultObjectDescriber.DescribeObject(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
1255 if err != nil {
1256 t.Fatalf("unexpected error: %v", err)
1257 }
1258 if !strings.Contains(out, "foo") {
1259 t.Errorf("missing Pod `foo` in output: %s", out)
1260 }
1261
1262 out, err = DefaultObjectDescriber.DescribeObject(&corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
1263 if err != nil {
1264 t.Fatalf("unexpected error: %v", err)
1265 }
1266 if !strings.Contains(out, "foo") {
1267 t.Errorf("missing Service `foo` in output: %s", out)
1268 }
1269
1270 out, err = DefaultObjectDescriber.DescribeObject(&corev1.ReplicationController{
1271 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
1272 Spec: corev1.ReplicationControllerSpec{Replicas: utilpointer.Int32(1)},
1273 })
1274 if err != nil {
1275 t.Fatalf("unexpected error: %v", err)
1276 }
1277 if !strings.Contains(out, "foo") {
1278 t.Errorf("missing Replication Controller `foo` in output: %s", out)
1279 }
1280
1281 out, err = DefaultObjectDescriber.DescribeObject(&corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
1282 if err != nil {
1283 t.Fatalf("unexpected error: %v", err)
1284 }
1285 if !strings.Contains(out, "foo") {
1286 t.Errorf("missing Node `foo` output: %s", out)
1287 }
1288
1289 out, err = DefaultObjectDescriber.DescribeObject(&appsv1.StatefulSet{
1290 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
1291 Spec: appsv1.StatefulSetSpec{Replicas: utilpointer.Int32(1)},
1292 })
1293 if err != nil {
1294 t.Fatalf("unexpected error: %v", err)
1295 }
1296 if !strings.Contains(out, "foo") {
1297 t.Errorf("missing StatefulSet `foo` in output: %s", out)
1298 }
1299 }
1300
1301 func TestGetPodsTotalRequests(t *testing.T) {
1302 testCases := []struct {
1303 name string
1304 pods *corev1.PodList
1305 expectedReqs map[corev1.ResourceName]resource.Quantity
1306 }{
1307 {
1308 name: "test1",
1309 pods: &corev1.PodList{
1310 Items: []corev1.Pod{
1311 {
1312 Spec: corev1.PodSpec{
1313 Containers: []corev1.Container{
1314 {
1315 Resources: corev1.ResourceRequirements{
1316 Requests: corev1.ResourceList{
1317 corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("1"),
1318 corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("300Mi"),
1319 corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("1G"),
1320 },
1321 },
1322 },
1323 {
1324 Resources: corev1.ResourceRequirements{
1325 Requests: corev1.ResourceList{
1326 corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("90m"),
1327 corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("120Mi"),
1328 corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("200M"),
1329 },
1330 },
1331 },
1332 },
1333 },
1334 },
1335 {
1336 Spec: corev1.PodSpec{
1337 Containers: []corev1.Container{
1338 {
1339 Resources: corev1.ResourceRequirements{
1340 Requests: corev1.ResourceList{
1341 corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("60m"),
1342 corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("43Mi"),
1343 corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("500M"),
1344 },
1345 },
1346 },
1347 {
1348 Resources: corev1.ResourceRequirements{
1349 Requests: corev1.ResourceList{
1350 corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("34m"),
1351 corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("83Mi"),
1352 corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("700M"),
1353 },
1354 },
1355 },
1356 },
1357 },
1358 },
1359 },
1360 },
1361 expectedReqs: map[corev1.ResourceName]resource.Quantity{
1362 corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("1.184"),
1363 corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("546Mi"),
1364 corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("2.4G"),
1365 },
1366 },
1367 }
1368
1369 for _, testCase := range testCases {
1370 t.Run(testCase.name, func(t *testing.T) {
1371 reqs, _ := getPodsTotalRequestsAndLimits(testCase.pods)
1372 if !apiequality.Semantic.DeepEqual(reqs, testCase.expectedReqs) {
1373 t.Errorf("Expected %v, got %v", testCase.expectedReqs, reqs)
1374 }
1375 })
1376 }
1377 }
1378
1379 func TestPersistentVolumeDescriber(t *testing.T) {
1380 block := corev1.PersistentVolumeBlock
1381 file := corev1.PersistentVolumeFilesystem
1382 foo := "glusterfsendpointname"
1383 deletionTimestamp := metav1.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}
1384 testCases := []struct {
1385 name string
1386 plugin string
1387 pv *corev1.PersistentVolume
1388 expectedElements []string
1389 unexpectedElements []string
1390 }{
1391 {
1392 name: "test0",
1393 plugin: "hostpath",
1394 pv: &corev1.PersistentVolume{
1395 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1396 Spec: corev1.PersistentVolumeSpec{
1397 PersistentVolumeSource: corev1.PersistentVolumeSource{
1398 HostPath: &corev1.HostPathVolumeSource{Type: new(corev1.HostPathType)},
1399 },
1400 },
1401 },
1402 unexpectedElements: []string{"VolumeMode", "Filesystem"},
1403 },
1404 {
1405 name: "test1",
1406 plugin: "gce",
1407 pv: &corev1.PersistentVolume{
1408 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1409 Spec: corev1.PersistentVolumeSpec{
1410 PersistentVolumeSource: corev1.PersistentVolumeSource{
1411 GCEPersistentDisk: &corev1.GCEPersistentDiskVolumeSource{},
1412 },
1413 VolumeMode: &file,
1414 },
1415 },
1416 expectedElements: []string{"VolumeMode", "Filesystem"},
1417 },
1418 {
1419 name: "test2",
1420 plugin: "ebs",
1421 pv: &corev1.PersistentVolume{
1422 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1423 Spec: corev1.PersistentVolumeSpec{
1424 PersistentVolumeSource: corev1.PersistentVolumeSource{
1425 AWSElasticBlockStore: &corev1.AWSElasticBlockStoreVolumeSource{},
1426 },
1427 },
1428 },
1429 unexpectedElements: []string{"VolumeMode", "Filesystem"},
1430 },
1431 {
1432 name: "test3",
1433 plugin: "nfs",
1434 pv: &corev1.PersistentVolume{
1435 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1436 Spec: corev1.PersistentVolumeSpec{
1437 PersistentVolumeSource: corev1.PersistentVolumeSource{
1438 NFS: &corev1.NFSVolumeSource{},
1439 },
1440 },
1441 },
1442 unexpectedElements: []string{"VolumeMode", "Filesystem"},
1443 },
1444 {
1445 name: "test4",
1446 plugin: "iscsi",
1447 pv: &corev1.PersistentVolume{
1448 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1449 Spec: corev1.PersistentVolumeSpec{
1450 PersistentVolumeSource: corev1.PersistentVolumeSource{
1451 ISCSI: &corev1.ISCSIPersistentVolumeSource{},
1452 },
1453 VolumeMode: &block,
1454 },
1455 },
1456 expectedElements: []string{"VolumeMode", "Block"},
1457 },
1458 {
1459 name: "test5",
1460 plugin: "gluster",
1461 pv: &corev1.PersistentVolume{
1462 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1463 Spec: corev1.PersistentVolumeSpec{
1464 PersistentVolumeSource: corev1.PersistentVolumeSource{
1465 Glusterfs: &corev1.GlusterfsPersistentVolumeSource{},
1466 },
1467 },
1468 },
1469 expectedElements: []string{"EndpointsNamespace", "<unset>"},
1470 unexpectedElements: []string{"VolumeMode", "Filesystem"},
1471 },
1472 {
1473 name: "test6",
1474 plugin: "rbd",
1475 pv: &corev1.PersistentVolume{
1476 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1477 Spec: corev1.PersistentVolumeSpec{
1478 PersistentVolumeSource: corev1.PersistentVolumeSource{
1479 RBD: &corev1.RBDPersistentVolumeSource{},
1480 },
1481 },
1482 },
1483 unexpectedElements: []string{"VolumeMode", "Filesystem"},
1484 },
1485 {
1486 name: "test7",
1487 plugin: "quobyte",
1488 pv: &corev1.PersistentVolume{
1489 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1490 Spec: corev1.PersistentVolumeSpec{
1491 PersistentVolumeSource: corev1.PersistentVolumeSource{
1492 Quobyte: &corev1.QuobyteVolumeSource{},
1493 },
1494 },
1495 },
1496 unexpectedElements: []string{"VolumeMode", "Filesystem"},
1497 },
1498 {
1499 name: "test8",
1500 plugin: "cinder",
1501 pv: &corev1.PersistentVolume{
1502 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1503 Spec: corev1.PersistentVolumeSpec{
1504 PersistentVolumeSource: corev1.PersistentVolumeSource{
1505 Cinder: &corev1.CinderPersistentVolumeSource{},
1506 },
1507 },
1508 },
1509 unexpectedElements: []string{"VolumeMode", "Filesystem"},
1510 },
1511 {
1512 name: "test9",
1513 plugin: "fc",
1514 pv: &corev1.PersistentVolume{
1515 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1516 Spec: corev1.PersistentVolumeSpec{
1517 PersistentVolumeSource: corev1.PersistentVolumeSource{
1518 FC: &corev1.FCVolumeSource{},
1519 },
1520 VolumeMode: &block,
1521 },
1522 },
1523 expectedElements: []string{"VolumeMode", "Block"},
1524 },
1525 {
1526 name: "test10",
1527 plugin: "local",
1528 pv: &corev1.PersistentVolume{
1529 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1530 Spec: corev1.PersistentVolumeSpec{
1531 PersistentVolumeSource: corev1.PersistentVolumeSource{
1532 Local: &corev1.LocalVolumeSource{},
1533 },
1534 },
1535 },
1536 expectedElements: []string{"Node Affinity: <none>"},
1537 unexpectedElements: []string{"Required Terms", "Term "},
1538 },
1539 {
1540 name: "test11",
1541 plugin: "local",
1542 pv: &corev1.PersistentVolume{
1543 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1544 Spec: corev1.PersistentVolumeSpec{
1545 PersistentVolumeSource: corev1.PersistentVolumeSource{
1546 Local: &corev1.LocalVolumeSource{},
1547 },
1548 NodeAffinity: &corev1.VolumeNodeAffinity{},
1549 },
1550 },
1551 expectedElements: []string{"Node Affinity: <none>"},
1552 unexpectedElements: []string{"Required Terms", "Term "},
1553 },
1554 {
1555 name: "test12",
1556 plugin: "local",
1557 pv: &corev1.PersistentVolume{
1558 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1559 Spec: corev1.PersistentVolumeSpec{
1560 PersistentVolumeSource: corev1.PersistentVolumeSource{
1561 Local: &corev1.LocalVolumeSource{},
1562 },
1563 NodeAffinity: &corev1.VolumeNodeAffinity{
1564 Required: &corev1.NodeSelector{},
1565 },
1566 },
1567 },
1568 expectedElements: []string{"Node Affinity", "Required Terms: <none>"},
1569 unexpectedElements: []string{"Term "},
1570 },
1571 {
1572 name: "test13",
1573 plugin: "local",
1574 pv: &corev1.PersistentVolume{
1575 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1576 Spec: corev1.PersistentVolumeSpec{
1577 PersistentVolumeSource: corev1.PersistentVolumeSource{
1578 Local: &corev1.LocalVolumeSource{},
1579 },
1580 NodeAffinity: &corev1.VolumeNodeAffinity{
1581 Required: &corev1.NodeSelector{
1582 NodeSelectorTerms: []corev1.NodeSelectorTerm{
1583 {
1584 MatchExpressions: []corev1.NodeSelectorRequirement{},
1585 },
1586 {
1587 MatchExpressions: []corev1.NodeSelectorRequirement{},
1588 },
1589 },
1590 },
1591 },
1592 },
1593 },
1594 expectedElements: []string{"Node Affinity", "Required Terms", "Term 0", "Term 1"},
1595 },
1596 {
1597 name: "test14",
1598 plugin: "local",
1599 pv: &corev1.PersistentVolume{
1600 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1601 Spec: corev1.PersistentVolumeSpec{
1602 PersistentVolumeSource: corev1.PersistentVolumeSource{
1603 Local: &corev1.LocalVolumeSource{},
1604 },
1605 NodeAffinity: &corev1.VolumeNodeAffinity{
1606 Required: &corev1.NodeSelector{
1607 NodeSelectorTerms: []corev1.NodeSelectorTerm{
1608 {
1609 MatchExpressions: []corev1.NodeSelectorRequirement{
1610 {
1611 Key: "foo",
1612 Operator: "In",
1613 Values: []string{"val1", "val2"},
1614 },
1615 {
1616 Key: "foo",
1617 Operator: "Exists",
1618 },
1619 },
1620 },
1621 },
1622 },
1623 },
1624 },
1625 },
1626 expectedElements: []string{"Node Affinity", "Required Terms", "Term 0",
1627 "foo in [val1, val2]",
1628 "foo exists"},
1629 },
1630 {
1631 name: "test15",
1632 plugin: "local",
1633 pv: &corev1.PersistentVolume{
1634 ObjectMeta: metav1.ObjectMeta{
1635 Name: "bar",
1636 DeletionTimestamp: &deletionTimestamp,
1637 },
1638 Spec: corev1.PersistentVolumeSpec{
1639 PersistentVolumeSource: corev1.PersistentVolumeSource{
1640 Local: &corev1.LocalVolumeSource{},
1641 },
1642 },
1643 },
1644 expectedElements: []string{"Terminating (lasts 10y)"},
1645 },
1646 {
1647 name: "test16",
1648 plugin: "local",
1649 pv: &corev1.PersistentVolume{
1650 ObjectMeta: metav1.ObjectMeta{
1651 Name: "bar",
1652 GenerateName: "test-GenerateName",
1653 UID: "test-UID",
1654 CreationTimestamp: metav1.Time{Time: time.Now()},
1655 DeletionTimestamp: &metav1.Time{Time: time.Now()},
1656 DeletionGracePeriodSeconds: new(int64),
1657 Labels: map[string]string{"label1": "label1", "label2": "label2", "label3": "label3"},
1658 Annotations: map[string]string{"annotation1": "annotation1", "annotation2": "annotation2", "annotation3": "annotation3"},
1659 },
1660 Spec: corev1.PersistentVolumeSpec{
1661 PersistentVolumeSource: corev1.PersistentVolumeSource{
1662 Local: &corev1.LocalVolumeSource{},
1663 },
1664 NodeAffinity: &corev1.VolumeNodeAffinity{
1665 Required: &corev1.NodeSelector{
1666 NodeSelectorTerms: []corev1.NodeSelectorTerm{
1667 {
1668 MatchExpressions: []corev1.NodeSelectorRequirement{
1669 {
1670 Key: "foo",
1671 Operator: "In",
1672 Values: []string{"val1", "val2"},
1673 },
1674 {
1675 Key: "foo",
1676 Operator: "Exists",
1677 },
1678 },
1679 },
1680 },
1681 },
1682 },
1683 },
1684 },
1685 expectedElements: []string{"Node Affinity", "Required Terms", "Term 0",
1686 "foo in [val1, val2]",
1687 "foo exists"},
1688 },
1689 {
1690 name: "test17",
1691 plugin: "local",
1692 pv: &corev1.PersistentVolume{
1693 ObjectMeta: metav1.ObjectMeta{
1694 Name: "bar",
1695 GenerateName: "test-GenerateName",
1696 UID: "test-UID",
1697 CreationTimestamp: metav1.Time{Time: time.Now()},
1698 DeletionTimestamp: &metav1.Time{Time: time.Now()},
1699 DeletionGracePeriodSeconds: new(int64),
1700 Labels: map[string]string{"label1": "label1", "label2": "label2", "label3": "label3"},
1701 Annotations: map[string]string{"annotation1": "annotation1", "annotation2": "annotation2", "annotation3": "annotation3"},
1702 },
1703 Spec: corev1.PersistentVolumeSpec{
1704 PersistentVolumeSource: corev1.PersistentVolumeSource{
1705 CSI: &corev1.CSIPersistentVolumeSource{
1706 Driver: "drive",
1707 VolumeHandle: "handler",
1708 ReadOnly: true,
1709 VolumeAttributes: map[string]string{
1710 "Attribute1": "Value1",
1711 "Attribute2": "Value2",
1712 "Attribute3": "Value3",
1713 },
1714 },
1715 },
1716 },
1717 },
1718 expectedElements: []string{"Driver", "VolumeHandle", "ReadOnly", "VolumeAttributes"},
1719 },
1720 {
1721 name: "test19",
1722 plugin: "gluster",
1723 pv: &corev1.PersistentVolume{
1724 ObjectMeta: metav1.ObjectMeta{Name: "bar"},
1725 Spec: corev1.PersistentVolumeSpec{
1726 PersistentVolumeSource: corev1.PersistentVolumeSource{
1727 Glusterfs: &corev1.GlusterfsPersistentVolumeSource{
1728 EndpointsNamespace: &foo,
1729 },
1730 },
1731 },
1732 },
1733 expectedElements: []string{"EndpointsNamespace", "glusterfsendpointname"},
1734 unexpectedElements: []string{"VolumeMode", "Filesystem"},
1735 },
1736 }
1737
1738 for _, test := range testCases {
1739 t.Run(test.name, func(t *testing.T) {
1740 fake := fake.NewSimpleClientset(test.pv)
1741 c := PersistentVolumeDescriber{fake}
1742 str, err := c.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
1743 if err != nil {
1744 t.Errorf("Unexpected error for test %s: %v", test.plugin, err)
1745 }
1746 if str == "" {
1747 t.Errorf("Unexpected empty string for test %s. Expected PV Describer output", test.plugin)
1748 }
1749 for _, expected := range test.expectedElements {
1750 if !strings.Contains(str, expected) {
1751 t.Errorf("expected to find %q in output: %q", expected, str)
1752 }
1753 }
1754 for _, unexpected := range test.unexpectedElements {
1755 if strings.Contains(str, unexpected) {
1756 t.Errorf("unexpected to find %q in output: %q", unexpected, str)
1757 }
1758 }
1759 })
1760 }
1761 }
1762
1763 func TestPersistentVolumeClaimDescriber(t *testing.T) {
1764 block := corev1.PersistentVolumeBlock
1765 file := corev1.PersistentVolumeFilesystem
1766 goldClassName := "gold"
1767 now := time.Now()
1768 deletionTimestamp := metav1.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}
1769 snapshotAPIGroup := "snapshot.storage.k8s.io"
1770 defaultDescriberSettings := &DescriberSettings{ShowEvents: true}
1771 testCases := []struct {
1772 name string
1773 pvc *corev1.PersistentVolumeClaim
1774 describerSettings *DescriberSettings
1775 expectedElements []string
1776 unexpectedElements []string
1777 }{
1778 {
1779 name: "default",
1780 pvc: &corev1.PersistentVolumeClaim{
1781 ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
1782 Spec: corev1.PersistentVolumeClaimSpec{
1783 VolumeName: "volume1",
1784 StorageClassName: &goldClassName,
1785 },
1786 Status: corev1.PersistentVolumeClaimStatus{
1787 Phase: corev1.ClaimBound,
1788 },
1789 },
1790 expectedElements: []string{"Events"},
1791 unexpectedElements: []string{"VolumeMode", "Filesystem"},
1792 },
1793 {
1794 name: "filesystem",
1795 pvc: &corev1.PersistentVolumeClaim{
1796 ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
1797 Spec: corev1.PersistentVolumeClaimSpec{
1798 VolumeName: "volume2",
1799 StorageClassName: &goldClassName,
1800 VolumeMode: &file,
1801 },
1802 Status: corev1.PersistentVolumeClaimStatus{
1803 Phase: corev1.ClaimBound,
1804 },
1805 },
1806 expectedElements: []string{"VolumeMode", "Filesystem"},
1807 },
1808 {
1809 name: "block",
1810 pvc: &corev1.PersistentVolumeClaim{
1811 ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
1812 Spec: corev1.PersistentVolumeClaimSpec{
1813 VolumeName: "volume3",
1814 StorageClassName: &goldClassName,
1815 VolumeMode: &block,
1816 },
1817 Status: corev1.PersistentVolumeClaimStatus{
1818 Phase: corev1.ClaimBound,
1819 },
1820 },
1821 expectedElements: []string{"VolumeMode", "Block"},
1822 },
1823
1824 {
1825 name: "condition-type",
1826 pvc: &corev1.PersistentVolumeClaim{
1827 ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
1828 Spec: corev1.PersistentVolumeClaimSpec{
1829 VolumeName: "volume4",
1830 StorageClassName: &goldClassName,
1831 },
1832 Status: corev1.PersistentVolumeClaimStatus{
1833 Conditions: []corev1.PersistentVolumeClaimCondition{
1834 {Type: corev1.PersistentVolumeClaimResizing},
1835 },
1836 },
1837 },
1838 expectedElements: []string{"Conditions", "Type", "Resizing"},
1839 },
1840 {
1841 name: "condition-status",
1842 pvc: &corev1.PersistentVolumeClaim{
1843 ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
1844 Spec: corev1.PersistentVolumeClaimSpec{
1845 VolumeName: "volume5",
1846 StorageClassName: &goldClassName,
1847 },
1848 Status: corev1.PersistentVolumeClaimStatus{
1849 Conditions: []corev1.PersistentVolumeClaimCondition{
1850 {Status: corev1.ConditionTrue},
1851 },
1852 },
1853 },
1854 expectedElements: []string{"Conditions", "Status", "True"},
1855 },
1856 {
1857 name: "condition-last-probe-time",
1858 pvc: &corev1.PersistentVolumeClaim{
1859 ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
1860 Spec: corev1.PersistentVolumeClaimSpec{
1861 VolumeName: "volume6",
1862 StorageClassName: &goldClassName,
1863 },
1864 Status: corev1.PersistentVolumeClaimStatus{
1865 Conditions: []corev1.PersistentVolumeClaimCondition{
1866 {LastProbeTime: metav1.Time{Time: now}},
1867 },
1868 },
1869 },
1870 expectedElements: []string{"Conditions", "LastProbeTime", now.Format(time.RFC1123Z)},
1871 },
1872 {
1873 name: "condition-last-transition-time",
1874 pvc: &corev1.PersistentVolumeClaim{
1875 ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
1876 Spec: corev1.PersistentVolumeClaimSpec{
1877 VolumeName: "volume7",
1878 StorageClassName: &goldClassName,
1879 },
1880 Status: corev1.PersistentVolumeClaimStatus{
1881 Conditions: []corev1.PersistentVolumeClaimCondition{
1882 {LastTransitionTime: metav1.Time{Time: now}},
1883 },
1884 },
1885 },
1886 expectedElements: []string{"Conditions", "LastTransitionTime", now.Format(time.RFC1123Z)},
1887 },
1888 {
1889 name: "condition-reason",
1890 pvc: &corev1.PersistentVolumeClaim{
1891 ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
1892 Spec: corev1.PersistentVolumeClaimSpec{
1893 VolumeName: "volume8",
1894 StorageClassName: &goldClassName,
1895 },
1896 Status: corev1.PersistentVolumeClaimStatus{
1897 Conditions: []corev1.PersistentVolumeClaimCondition{
1898 {Reason: "OfflineResize"},
1899 },
1900 },
1901 },
1902 expectedElements: []string{"Conditions", "Reason", "OfflineResize"},
1903 },
1904 {
1905 name: "condition-message",
1906 pvc: &corev1.PersistentVolumeClaim{
1907 ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
1908 Spec: corev1.PersistentVolumeClaimSpec{
1909 VolumeName: "volume9",
1910 StorageClassName: &goldClassName,
1911 },
1912 Status: corev1.PersistentVolumeClaimStatus{
1913 Conditions: []corev1.PersistentVolumeClaimCondition{
1914 {Message: "User request resize"},
1915 },
1916 },
1917 },
1918 expectedElements: []string{"Conditions", "Message", "User request resize"},
1919 },
1920 {
1921 name: "deletion-timestamp",
1922 pvc: &corev1.PersistentVolumeClaim{
1923 ObjectMeta: metav1.ObjectMeta{
1924 Namespace: "foo",
1925 Name: "bar",
1926 DeletionTimestamp: &deletionTimestamp,
1927 },
1928 Spec: corev1.PersistentVolumeClaimSpec{
1929 VolumeName: "volume10",
1930 StorageClassName: &goldClassName,
1931 },
1932 Status: corev1.PersistentVolumeClaimStatus{},
1933 },
1934 expectedElements: []string{"Terminating (lasts 10y)"},
1935 },
1936 {
1937 name: "pvc-datasource",
1938 pvc: &corev1.PersistentVolumeClaim{
1939 ObjectMeta: metav1.ObjectMeta{
1940 Namespace: "foo",
1941 Name: "bar",
1942 },
1943 Spec: corev1.PersistentVolumeClaimSpec{
1944 VolumeName: "volume10",
1945 StorageClassName: &goldClassName,
1946 DataSource: &corev1.TypedLocalObjectReference{
1947 Name: "srcpvc",
1948 Kind: "PersistentVolumeClaim",
1949 },
1950 },
1951 Status: corev1.PersistentVolumeClaimStatus{},
1952 },
1953 expectedElements: []string{"\nDataSource:\n Kind: PersistentVolumeClaim\n Name: srcpvc"},
1954 },
1955 {
1956 name: "snapshot-datasource",
1957 pvc: &corev1.PersistentVolumeClaim{
1958 ObjectMeta: metav1.ObjectMeta{
1959 Namespace: "foo",
1960 Name: "bar",
1961 },
1962 Spec: corev1.PersistentVolumeClaimSpec{
1963 VolumeName: "volume10",
1964 StorageClassName: &goldClassName,
1965 DataSource: &corev1.TypedLocalObjectReference{
1966 Name: "src-snapshot",
1967 Kind: "VolumeSnapshot",
1968 APIGroup: &snapshotAPIGroup,
1969 },
1970 },
1971 Status: corev1.PersistentVolumeClaimStatus{},
1972 },
1973 expectedElements: []string{"DataSource:\n APIGroup: snapshot.storage.k8s.io\n Kind: VolumeSnapshot\n Name: src-snapshot\n"},
1974 },
1975 {
1976 name: "no-show-events",
1977 pvc: &corev1.PersistentVolumeClaim{
1978 ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
1979 Spec: corev1.PersistentVolumeClaimSpec{
1980 VolumeName: "volume1",
1981 StorageClassName: &goldClassName,
1982 },
1983 Status: corev1.PersistentVolumeClaimStatus{
1984 Phase: corev1.ClaimBound,
1985 },
1986 },
1987 unexpectedElements: []string{"Events"},
1988 describerSettings: &DescriberSettings{ShowEvents: false},
1989 },
1990 }
1991
1992 for _, test := range testCases {
1993 t.Run(test.name, func(t *testing.T) {
1994 fake := fake.NewSimpleClientset(test.pvc)
1995 c := PersistentVolumeClaimDescriber{fake}
1996
1997 var describerSettings DescriberSettings
1998 if test.describerSettings != nil {
1999 describerSettings = *test.describerSettings
2000 } else {
2001 describerSettings = *defaultDescriberSettings
2002 }
2003
2004 str, err := c.Describe("foo", "bar", describerSettings)
2005 if err != nil {
2006 t.Errorf("Unexpected error for test %s: %v", test.name, err)
2007 }
2008 if str == "" {
2009 t.Errorf("Unexpected empty string for test %s. Expected PVC Describer output", test.name)
2010 }
2011 for _, expected := range test.expectedElements {
2012 if !strings.Contains(str, expected) {
2013 t.Errorf("expected to find %q in output: %q", expected, str)
2014 }
2015 }
2016 for _, unexpected := range test.unexpectedElements {
2017 if strings.Contains(str, unexpected) {
2018 t.Errorf("unexpected to find %q in output: %q", unexpected, str)
2019 }
2020 }
2021 })
2022 }
2023 }
2024
2025 func TestGetPodsForPVC(t *testing.T) {
2026 goldClassName := "gold"
2027 testCases := []struct {
2028 name string
2029 pvc *corev1.PersistentVolumeClaim
2030 requiredObjects []runtime.Object
2031 expectedPods []string
2032 }{
2033 {
2034 name: "pvc-unused",
2035 pvc: &corev1.PersistentVolumeClaim{
2036 ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pvc-name"},
2037 Spec: corev1.PersistentVolumeClaimSpec{
2038 VolumeName: "volume1",
2039 StorageClassName: &goldClassName,
2040 },
2041 Status: corev1.PersistentVolumeClaimStatus{
2042 Phase: corev1.ClaimBound,
2043 },
2044 },
2045 expectedPods: []string{},
2046 },
2047 {
2048 name: "pvc-in-pods-volumes-list",
2049 pvc: &corev1.PersistentVolumeClaim{
2050 ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pvc-name"},
2051 Spec: corev1.PersistentVolumeClaimSpec{
2052 VolumeName: "volume1",
2053 StorageClassName: &goldClassName,
2054 },
2055 Status: corev1.PersistentVolumeClaimStatus{
2056 Phase: corev1.ClaimBound,
2057 },
2058 },
2059 requiredObjects: []runtime.Object{
2060 &corev1.Pod{
2061 ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod-name"},
2062 Spec: corev1.PodSpec{
2063 Volumes: []corev1.Volume{
2064 {
2065 Name: "volume",
2066 VolumeSource: corev1.VolumeSource{
2067 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
2068 ClaimName: "pvc-name",
2069 },
2070 },
2071 },
2072 },
2073 },
2074 },
2075 },
2076 expectedPods: []string{"pod-name"},
2077 },
2078 {
2079 name: "pvc-owned-by-pod",
2080 pvc: &corev1.PersistentVolumeClaim{
2081 ObjectMeta: metav1.ObjectMeta{
2082 Namespace: "ns",
2083 Name: "pvc-name",
2084 OwnerReferences: []metav1.OwnerReference{
2085 {
2086 Kind: "Pod",
2087 Name: "pod-name",
2088 UID: "pod-uid",
2089 },
2090 },
2091 },
2092 Spec: corev1.PersistentVolumeClaimSpec{
2093 VolumeName: "volume1",
2094 StorageClassName: &goldClassName,
2095 },
2096 Status: corev1.PersistentVolumeClaimStatus{
2097 Phase: corev1.ClaimBound,
2098 },
2099 },
2100 requiredObjects: []runtime.Object{
2101 &corev1.Pod{
2102 ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "pod-name", UID: "pod-uid"},
2103 },
2104 },
2105 expectedPods: []string{"pod-name"},
2106 },
2107 }
2108
2109 for _, test := range testCases {
2110 t.Run(test.name, func(t *testing.T) {
2111 var objects []runtime.Object
2112 objects = append(objects, test.requiredObjects...)
2113 objects = append(objects, test.pvc)
2114 fake := fake.NewSimpleClientset(objects...)
2115
2116 pods, err := getPodsForPVC(fake.CoreV1().Pods(test.pvc.ObjectMeta.Namespace), test.pvc, DescriberSettings{})
2117 if err != nil {
2118 t.Errorf("Unexpected error for test %s: %v", test.name, err)
2119 }
2120
2121 for _, expectedPod := range test.expectedPods {
2122 foundPod := false
2123 for _, pod := range pods {
2124 if pod.Name == expectedPod {
2125 foundPod = true
2126 break
2127 }
2128 }
2129
2130 if !foundPod {
2131 t.Errorf("Expected pod %s, but it was not returned: %v", expectedPod, pods)
2132 }
2133 }
2134
2135 if len(test.expectedPods) != len(pods) {
2136 t.Errorf("Expected %d pods, but got %d pods", len(test.expectedPods), len(pods))
2137 }
2138 })
2139 }
2140 }
2141
2142 func TestDescribeDeployment(t *testing.T) {
2143 labels := map[string]string{"k8s-app": "bar"}
2144 testCases := []struct {
2145 name string
2146 objects []runtime.Object
2147 expects []string
2148 }{
2149 {
2150 name: "deployment with two mounted volumes",
2151 objects: []runtime.Object{
2152 &appsv1.Deployment{
2153 ObjectMeta: metav1.ObjectMeta{
2154 Name: "bar",
2155 Namespace: "foo",
2156 Labels: labels,
2157 UID: "00000000-0000-0000-0000-000000000001",
2158 CreationTimestamp: metav1.NewTime(time.Date(2021, time.Month(1), 1, 0, 0, 0, 0, time.UTC)),
2159 },
2160 Spec: appsv1.DeploymentSpec{
2161 Replicas: utilpointer.Int32Ptr(1),
2162 Selector: &metav1.LabelSelector{
2163 MatchLabels: labels,
2164 },
2165 Template: corev1.PodTemplateSpec{
2166 ObjectMeta: metav1.ObjectMeta{
2167 Name: "bar",
2168 Namespace: "foo",
2169 Labels: labels,
2170 },
2171 Spec: corev1.PodSpec{
2172 Containers: []corev1.Container{
2173 {
2174 Image: "mytest-image:latest",
2175 VolumeMounts: []corev1.VolumeMount{
2176 {
2177 Name: "vol-foo",
2178 MountPath: "/tmp/vol-foo",
2179 }, {
2180 Name: "vol-bar",
2181 MountPath: "/tmp/vol-bar",
2182 },
2183 },
2184 },
2185 },
2186 Volumes: []corev1.Volume{
2187 {
2188 Name: "vol-foo",
2189 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2190 },
2191 {
2192 Name: "vol-bar",
2193 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2194 },
2195 },
2196 },
2197 },
2198 },
2199 }, &appsv1.ReplicaSet{
2200 ObjectMeta: metav1.ObjectMeta{
2201 Name: "bar-001",
2202 Namespace: "foo",
2203 Labels: labels,
2204 OwnerReferences: []metav1.OwnerReference{
2205 {
2206 Controller: utilpointer.BoolPtr(true),
2207 UID: "00000000-0000-0000-0000-000000000001",
2208 },
2209 },
2210 },
2211 Spec: appsv1.ReplicaSetSpec{
2212 Replicas: utilpointer.Int32Ptr(1),
2213 Selector: &metav1.LabelSelector{
2214 MatchLabels: labels,
2215 },
2216 Template: corev1.PodTemplateSpec{
2217 ObjectMeta: metav1.ObjectMeta{
2218 Name: "bar",
2219 Namespace: "foo",
2220 Labels: labels,
2221 },
2222 Spec: corev1.PodSpec{
2223 Containers: []corev1.Container{
2224 {
2225 Image: "mytest-image:latest",
2226 VolumeMounts: []corev1.VolumeMount{
2227 {
2228 Name: "vol-foo",
2229 MountPath: "/tmp/vol-foo",
2230 }, {
2231 Name: "vol-bar",
2232 MountPath: "/tmp/vol-bar",
2233 },
2234 },
2235 },
2236 },
2237 Volumes: []corev1.Volume{
2238 {
2239 Name: "vol-foo",
2240 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2241 },
2242 {
2243 Name: "vol-bar",
2244 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2245 },
2246 },
2247 },
2248 },
2249 },
2250 Status: appsv1.ReplicaSetStatus{
2251 Replicas: 1,
2252 ReadyReplicas: 1,
2253 AvailableReplicas: 1,
2254 },
2255 },
2256 },
2257 expects: []string{
2258 "Name: bar\nNamespace: foo",
2259 "CreationTimestamp: Fri, 01 Jan 2021 00:00:00 +0000",
2260 "Labels: k8s-app=bar",
2261 "Selector: k8s-app=bar",
2262 "Replicas: 1 desired | 0 updated | 0 total | 0 available | 0 unavailable",
2263 "Image: mytest-image:latest",
2264 "Mounts:\n /tmp/vol-bar from vol-bar (rw)\n /tmp/vol-foo from vol-foo (rw)",
2265 "OldReplicaSets: <none>",
2266 "NewReplicaSet: bar-001 (1/1 replicas created)",
2267 "Events: <none>",
2268 "Node-Selectors: <none>",
2269 "Tolerations: <none>",
2270 },
2271 },
2272 {
2273 name: "deployment during the process of rolling out",
2274 objects: []runtime.Object{
2275 &appsv1.Deployment{
2276 ObjectMeta: metav1.ObjectMeta{
2277 Name: "bar",
2278 Namespace: "foo",
2279 Labels: labels,
2280 UID: "00000000-0000-0000-0000-000000000001",
2281 CreationTimestamp: metav1.NewTime(time.Date(2021, time.Month(1), 1, 0, 0, 0, 0, time.UTC)),
2282 },
2283 Spec: appsv1.DeploymentSpec{
2284 Replicas: utilpointer.Int32Ptr(2),
2285 Selector: &metav1.LabelSelector{
2286 MatchLabels: labels,
2287 },
2288 Template: corev1.PodTemplateSpec{
2289 ObjectMeta: metav1.ObjectMeta{
2290 Name: "bar",
2291 Namespace: "foo",
2292 Labels: labels,
2293 },
2294 Spec: corev1.PodSpec{
2295 Containers: []corev1.Container{
2296 {
2297 Image: "mytest-image:v2.0",
2298 VolumeMounts: []corev1.VolumeMount{
2299 {
2300 Name: "vol-foo",
2301 MountPath: "/tmp/vol-foo",
2302 }, {
2303 Name: "vol-bar",
2304 MountPath: "/tmp/vol-bar",
2305 },
2306 },
2307 },
2308 },
2309 Volumes: []corev1.Volume{
2310 {
2311 Name: "vol-foo",
2312 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2313 },
2314 {
2315 Name: "vol-bar",
2316 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2317 },
2318 },
2319 },
2320 },
2321 },
2322 Status: appsv1.DeploymentStatus{
2323 Replicas: 3,
2324 UpdatedReplicas: 1,
2325 AvailableReplicas: 2,
2326 UnavailableReplicas: 1,
2327 },
2328 }, &appsv1.ReplicaSet{
2329 ObjectMeta: metav1.ObjectMeta{
2330 Name: "bar-001",
2331 Namespace: "foo",
2332 Labels: labels,
2333 UID: "00000000-0000-0000-0000-000000000001",
2334 OwnerReferences: []metav1.OwnerReference{
2335 {
2336 Controller: utilpointer.BoolPtr(true),
2337 UID: "00000000-0000-0000-0000-000000000001",
2338 },
2339 },
2340 },
2341 Spec: appsv1.ReplicaSetSpec{
2342 Replicas: utilpointer.Int32Ptr(2),
2343 Selector: &metav1.LabelSelector{
2344 MatchLabels: labels,
2345 },
2346 Template: corev1.PodTemplateSpec{
2347 ObjectMeta: metav1.ObjectMeta{
2348 Name: "bar",
2349 Namespace: "foo",
2350 Labels: labels,
2351 },
2352 Spec: corev1.PodSpec{
2353 Containers: []corev1.Container{
2354 {
2355 Image: "mytest-image:v1.0",
2356 VolumeMounts: []corev1.VolumeMount{
2357 {
2358 Name: "vol-foo",
2359 MountPath: "/tmp/vol-foo",
2360 }, {
2361 Name: "vol-bar",
2362 MountPath: "/tmp/vol-bar",
2363 },
2364 },
2365 },
2366 },
2367 Volumes: []corev1.Volume{
2368 {
2369 Name: "vol-foo",
2370 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2371 },
2372 {
2373 Name: "vol-bar",
2374 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2375 },
2376 },
2377 },
2378 },
2379 },
2380 Status: appsv1.ReplicaSetStatus{
2381 Replicas: 2,
2382 ReadyReplicas: 2,
2383 AvailableReplicas: 2,
2384 },
2385 }, &appsv1.ReplicaSet{
2386 ObjectMeta: metav1.ObjectMeta{
2387 Name: "bar-002",
2388 Namespace: "foo",
2389 Labels: labels,
2390 UID: "00000000-0000-0000-0000-000000000002",
2391 OwnerReferences: []metav1.OwnerReference{
2392 {
2393 Controller: utilpointer.BoolPtr(true),
2394 UID: "00000000-0000-0000-0000-000000000001",
2395 },
2396 },
2397 },
2398 Spec: appsv1.ReplicaSetSpec{
2399 Replicas: utilpointer.Int32Ptr(1),
2400 Selector: &metav1.LabelSelector{
2401 MatchLabels: labels,
2402 },
2403 Template: corev1.PodTemplateSpec{
2404 ObjectMeta: metav1.ObjectMeta{
2405 Name: "bar",
2406 Namespace: "foo",
2407 Labels: labels,
2408 },
2409 Spec: corev1.PodSpec{
2410 Containers: []corev1.Container{
2411 {
2412 Image: "mytest-image:v2.0",
2413 VolumeMounts: []corev1.VolumeMount{
2414 {
2415 Name: "vol-foo",
2416 MountPath: "/tmp/vol-foo",
2417 }, {
2418 Name: "vol-bar",
2419 MountPath: "/tmp/vol-bar",
2420 },
2421 },
2422 },
2423 },
2424 Volumes: []corev1.Volume{
2425 {
2426 Name: "vol-foo",
2427 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2428 },
2429 {
2430 Name: "vol-bar",
2431 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2432 },
2433 },
2434 },
2435 },
2436 },
2437 Status: appsv1.ReplicaSetStatus{
2438 Replicas: 1,
2439 ReadyReplicas: 0,
2440 AvailableReplicas: 1,
2441 },
2442 }, &corev1.Event{
2443 ObjectMeta: metav1.ObjectMeta{
2444 Name: "bar-000",
2445 Namespace: "foo",
2446 },
2447 InvolvedObject: corev1.ObjectReference{
2448 APIVersion: "apps/v1",
2449 Kind: "Deployment",
2450 Name: "bar",
2451 Namespace: "foo",
2452 UID: "00000000-0000-0000-0000-000000000001",
2453 },
2454 Type: corev1.EventTypeNormal,
2455 Reason: "ScalingReplicaSet",
2456 Message: "Scaled up replica set bar-002 to 1",
2457 ReportingController: "deployment-controller",
2458 EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
2459 Series: &corev1.EventSeries{
2460 Count: 3,
2461 LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)),
2462 },
2463 }, &corev1.Event{
2464 ObjectMeta: metav1.ObjectMeta{
2465 Name: "bar-001",
2466 Namespace: "foo",
2467 },
2468 InvolvedObject: corev1.ObjectReference{
2469 APIVersion: "apps/v1",
2470 Kind: "Deployment",
2471 Name: "bar",
2472 Namespace: "foo",
2473 UID: "00000000-0000-0000-0000-000000000001",
2474 },
2475 Type: corev1.EventTypeNormal,
2476 Reason: "ScalingReplicaSet",
2477 Message: "Scaled up replica set bar-001 to 2",
2478 Source: corev1.EventSource{
2479 Component: "deployment-controller",
2480 },
2481 FirstTimestamp: metav1.NewTime(time.Now().Add(-10 * time.Minute)),
2482 }, &corev1.Event{
2483 ObjectMeta: metav1.ObjectMeta{
2484 Name: "bar-002",
2485 Namespace: "foo",
2486 },
2487 InvolvedObject: corev1.ObjectReference{
2488 APIVersion: "apps/v1",
2489 Kind: "Deployment",
2490 Name: "bar",
2491 Namespace: "foo",
2492 UID: "00000000-0000-0000-0000-000000000001",
2493 },
2494 Type: corev1.EventTypeNormal,
2495 Reason: "ScalingReplicaSet",
2496 Message: "Scaled up replica set bar-002 to 1",
2497 Source: corev1.EventSource{
2498 Component: "deployment-controller",
2499 },
2500 FirstTimestamp: metav1.NewTime(time.Now().Add(-2 * time.Minute)),
2501 }, &corev1.Event{
2502 ObjectMeta: metav1.ObjectMeta{
2503 Name: "bar-003",
2504 Namespace: "foo",
2505 },
2506 InvolvedObject: corev1.ObjectReference{
2507 APIVersion: "apps/v1",
2508 Kind: "Deployment",
2509 Name: "bar",
2510 Namespace: "foo",
2511 UID: "00000000-0000-0000-0000-000000000001",
2512 },
2513 Type: corev1.EventTypeNormal,
2514 Reason: "ScalingReplicaSet",
2515 Message: "Scaled down replica set bar-002 to 1",
2516 ReportingController: "deployment-controller",
2517 EventTime: metav1.NewMicroTime(time.Now().Add(-1 * time.Minute)),
2518 },
2519 },
2520 expects: []string{
2521 "Replicas: 2 desired | 1 updated | 3 total | 2 available | 1 unavailable",
2522 "Image: mytest-image:v2.0",
2523 "OldReplicaSets: bar-001 (2/2 replicas created)",
2524 "NewReplicaSet: bar-002 (1/1 replicas created)",
2525 "Events:\n",
2526 "Normal ScalingReplicaSet 12m (x3 over 20m) deployment-controller Scaled up replica set bar-002 to 1",
2527 "Normal ScalingReplicaSet 10m deployment-controller Scaled up replica set bar-001 to 2",
2528 "Normal ScalingReplicaSet 2m deployment-controller Scaled up replica set bar-002 to 1",
2529 "Normal ScalingReplicaSet 60s deployment-controller Scaled down replica set bar-002 to 1",
2530 },
2531 },
2532 {
2533 name: "deployment after successful rollout",
2534 objects: []runtime.Object{
2535 &appsv1.Deployment{
2536 ObjectMeta: metav1.ObjectMeta{
2537 Name: "bar",
2538 Namespace: "foo",
2539 Labels: labels,
2540 UID: "00000000-0000-0000-0000-000000000001",
2541 CreationTimestamp: metav1.NewTime(time.Date(2021, time.Month(1), 1, 0, 0, 0, 0, time.UTC)),
2542 },
2543 Spec: appsv1.DeploymentSpec{
2544 Replicas: utilpointer.Int32Ptr(2),
2545 Selector: &metav1.LabelSelector{
2546 MatchLabels: labels,
2547 },
2548 Template: corev1.PodTemplateSpec{
2549 ObjectMeta: metav1.ObjectMeta{
2550 Name: "bar",
2551 Namespace: "foo",
2552 Labels: labels,
2553 },
2554 Spec: corev1.PodSpec{
2555 Containers: []corev1.Container{
2556 {
2557 Image: "mytest-image:v2.0",
2558 VolumeMounts: []corev1.VolumeMount{
2559 {
2560 Name: "vol-foo",
2561 MountPath: "/tmp/vol-foo",
2562 }, {
2563 Name: "vol-bar",
2564 MountPath: "/tmp/vol-bar",
2565 },
2566 },
2567 },
2568 },
2569 Volumes: []corev1.Volume{
2570 {
2571 Name: "vol-foo",
2572 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2573 },
2574 {
2575 Name: "vol-bar",
2576 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2577 },
2578 },
2579 },
2580 },
2581 },
2582 Status: appsv1.DeploymentStatus{
2583 Replicas: 2,
2584 UpdatedReplicas: 2,
2585 AvailableReplicas: 2,
2586 UnavailableReplicas: 0,
2587 },
2588 }, &appsv1.ReplicaSet{
2589 ObjectMeta: metav1.ObjectMeta{
2590 Name: "bar-001",
2591 Namespace: "foo",
2592 Labels: labels,
2593 UID: "00000000-0000-0000-0000-000000000001",
2594 OwnerReferences: []metav1.OwnerReference{
2595 {
2596 Controller: utilpointer.BoolPtr(true),
2597 UID: "00000000-0000-0000-0000-000000000001",
2598 },
2599 },
2600 },
2601 Spec: appsv1.ReplicaSetSpec{
2602 Replicas: utilpointer.Int32Ptr(0),
2603 Selector: &metav1.LabelSelector{
2604 MatchLabels: labels,
2605 },
2606 Template: corev1.PodTemplateSpec{
2607 ObjectMeta: metav1.ObjectMeta{
2608 Name: "bar",
2609 Namespace: "foo",
2610 Labels: labels,
2611 },
2612 Spec: corev1.PodSpec{
2613 Containers: []corev1.Container{
2614 {
2615 Image: "mytest-image:v1.0",
2616 VolumeMounts: []corev1.VolumeMount{
2617 {
2618 Name: "vol-foo",
2619 MountPath: "/tmp/vol-foo",
2620 }, {
2621 Name: "vol-bar",
2622 MountPath: "/tmp/vol-bar",
2623 },
2624 },
2625 },
2626 },
2627 Volumes: []corev1.Volume{
2628 {
2629 Name: "vol-foo",
2630 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2631 },
2632 {
2633 Name: "vol-bar",
2634 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2635 },
2636 },
2637 },
2638 },
2639 },
2640 Status: appsv1.ReplicaSetStatus{
2641 Replicas: 0,
2642 ReadyReplicas: 0,
2643 AvailableReplicas: 0,
2644 },
2645 }, &appsv1.ReplicaSet{
2646 ObjectMeta: metav1.ObjectMeta{
2647 Name: "bar-002",
2648 Namespace: "foo",
2649 Labels: labels,
2650 UID: "00000000-0000-0000-0000-000000000002",
2651 OwnerReferences: []metav1.OwnerReference{
2652 {
2653 Controller: utilpointer.BoolPtr(true),
2654 UID: "00000000-0000-0000-0000-000000000001",
2655 },
2656 },
2657 },
2658 Spec: appsv1.ReplicaSetSpec{
2659 Replicas: utilpointer.Int32Ptr(2),
2660 Selector: &metav1.LabelSelector{
2661 MatchLabels: labels,
2662 },
2663 Template: corev1.PodTemplateSpec{
2664 ObjectMeta: metav1.ObjectMeta{
2665 Name: "bar",
2666 Namespace: "foo",
2667 Labels: labels,
2668 },
2669 Spec: corev1.PodSpec{
2670 Containers: []corev1.Container{
2671 {
2672 Image: "mytest-image:v2.0",
2673 VolumeMounts: []corev1.VolumeMount{
2674 {
2675 Name: "vol-foo",
2676 MountPath: "/tmp/vol-foo",
2677 }, {
2678 Name: "vol-bar",
2679 MountPath: "/tmp/vol-bar",
2680 },
2681 },
2682 },
2683 },
2684 Volumes: []corev1.Volume{
2685 {
2686 Name: "vol-foo",
2687 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2688 },
2689 {
2690 Name: "vol-bar",
2691 VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}},
2692 },
2693 },
2694 },
2695 },
2696 },
2697 Status: appsv1.ReplicaSetStatus{
2698 Replicas: 2,
2699 ReadyReplicas: 2,
2700 AvailableReplicas: 2,
2701 },
2702 }, &corev1.Event{
2703 ObjectMeta: metav1.ObjectMeta{
2704 Name: "bar-000",
2705 Namespace: "foo",
2706 },
2707 InvolvedObject: corev1.ObjectReference{
2708 APIVersion: "apps/v1",
2709 Kind: "Deployment",
2710 Name: "bar",
2711 Namespace: "foo",
2712 UID: "00000000-0000-0000-0000-000000000001",
2713 },
2714 Type: corev1.EventTypeNormal,
2715 Reason: "ScalingReplicaSet",
2716 Message: "Scaled up replica set bar-002 to 1",
2717 ReportingController: "deployment-controller",
2718 EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
2719 Series: &corev1.EventSeries{
2720 Count: 3,
2721 LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)),
2722 },
2723 }, &corev1.Event{
2724 ObjectMeta: metav1.ObjectMeta{
2725 Name: "bar-001",
2726 Namespace: "foo",
2727 },
2728 InvolvedObject: corev1.ObjectReference{
2729 APIVersion: "apps/v1",
2730 Kind: "Deployment",
2731 Name: "bar",
2732 Namespace: "foo",
2733 UID: "00000000-0000-0000-0000-000000000001",
2734 },
2735 Type: corev1.EventTypeNormal,
2736 Reason: "ScalingReplicaSet",
2737 Message: "Scaled up replica set bar-001 to 2",
2738 Source: corev1.EventSource{
2739 Component: "deployment-controller",
2740 },
2741 FirstTimestamp: metav1.NewTime(time.Now().Add(-10 * time.Minute)),
2742 }, &corev1.Event{
2743 ObjectMeta: metav1.ObjectMeta{
2744 Name: "bar-002",
2745 Namespace: "foo",
2746 },
2747 InvolvedObject: corev1.ObjectReference{
2748 APIVersion: "apps/v1",
2749 Kind: "Deployment",
2750 Name: "bar",
2751 Namespace: "foo",
2752 UID: "00000000-0000-0000-0000-000000000001",
2753 },
2754 Type: corev1.EventTypeNormal,
2755 Reason: "ScalingReplicaSet",
2756 Message: "Scaled up replica set bar-002 to 1",
2757 Source: corev1.EventSource{
2758 Component: "deployment-controller",
2759 },
2760 FirstTimestamp: metav1.NewTime(time.Now().Add(-2 * time.Minute)),
2761 }, &corev1.Event{
2762 ObjectMeta: metav1.ObjectMeta{
2763 Name: "bar-003",
2764 Namespace: "foo",
2765 },
2766 InvolvedObject: corev1.ObjectReference{
2767 APIVersion: "apps/v1",
2768 Kind: "Deployment",
2769 Name: "bar",
2770 Namespace: "foo",
2771 UID: "00000000-0000-0000-0000-000000000001",
2772 },
2773 Type: corev1.EventTypeNormal,
2774 Reason: "ScalingReplicaSet",
2775 Message: "Scaled down replica set bar-002 to 1",
2776 ReportingController: "deployment-controller",
2777 EventTime: metav1.NewMicroTime(time.Now().Add(-1 * time.Minute)),
2778 }, &corev1.Event{
2779 ObjectMeta: metav1.ObjectMeta{
2780 Name: "bar-004",
2781 Namespace: "foo",
2782 },
2783 InvolvedObject: corev1.ObjectReference{
2784 APIVersion: "apps/v1",
2785 Kind: "Deployment",
2786 Name: "bar",
2787 Namespace: "foo",
2788 UID: "00000000-0000-0000-0000-000000000001",
2789 },
2790 Type: corev1.EventTypeNormal,
2791 Reason: "ScalingReplicaSet",
2792 Message: "Scaled up replica set bar-002 to 2",
2793 ReportingController: "deployment-controller",
2794 EventTime: metav1.NewMicroTime(time.Now().Add(-15 * time.Second)),
2795 }, &corev1.Event{
2796 ObjectMeta: metav1.ObjectMeta{
2797 Name: "bar-005",
2798 Namespace: "foo",
2799 },
2800 InvolvedObject: corev1.ObjectReference{
2801 APIVersion: "apps/v1",
2802 Kind: "Deployment",
2803 Name: "bar",
2804 Namespace: "foo",
2805 UID: "00000000-0000-0000-0000-000000000001",
2806 },
2807 Type: corev1.EventTypeNormal,
2808 Reason: "ScalingReplicaSet",
2809 Message: "Scaled down replica set bar-001 to 0",
2810 ReportingController: "deployment-controller",
2811 EventTime: metav1.NewMicroTime(time.Now().Add(-3 * time.Second)),
2812 },
2813 },
2814 expects: []string{
2815 "Replicas: 2 desired | 2 updated | 2 total | 2 available | 0 unavailable",
2816 "Image: mytest-image:v2.0",
2817 "OldReplicaSets: bar-001 (0/0 replicas created)",
2818 "NewReplicaSet: bar-002 (2/2 replicas created)",
2819 "Events:\n",
2820 "Normal ScalingReplicaSet 12m (x3 over 20m) deployment-controller Scaled up replica set bar-002 to 1",
2821 "Normal ScalingReplicaSet 10m deployment-controller Scaled up replica set bar-001 to 2",
2822 "Normal ScalingReplicaSet 2m deployment-controller Scaled up replica set bar-002 to 1",
2823 "Normal ScalingReplicaSet 60s deployment-controller Scaled down replica set bar-002 to 1",
2824 "Normal ScalingReplicaSet 15s deployment-controller Scaled up replica set bar-002 to 2",
2825 "Normal ScalingReplicaSet 3s deployment-controller Scaled down replica set bar-001 to 0",
2826 },
2827 },
2828 }
2829 for _, testCase := range testCases {
2830 t.Run(testCase.name, func(t *testing.T) {
2831 fakeClient := fake.NewSimpleClientset(testCase.objects...)
2832 d := DeploymentDescriber{fakeClient}
2833 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
2834 if err != nil {
2835 t.Errorf("unexpected error: %v", err)
2836 }
2837
2838 for _, expect := range testCase.expects {
2839 if !strings.Contains(out, expect) {
2840 t.Errorf("expected to find \"%s\" in:\n %s", expect, out)
2841 }
2842 }
2843
2844 })
2845 }
2846 }
2847
2848 func TestDescribeJob(t *testing.T) {
2849 indexedCompletion := batchv1.IndexedCompletion
2850 cases := map[string]struct {
2851 job *batchv1.Job
2852 wantElements []string
2853 dontWantElements []string
2854 }{
2855 "empty job": {
2856 job: &batchv1.Job{
2857 ObjectMeta: metav1.ObjectMeta{
2858 Name: "bar",
2859 Namespace: "foo",
2860 },
2861 Spec: batchv1.JobSpec{},
2862 },
2863 dontWantElements: []string{"Completed Indexes:", "Suspend:", "Backoff Limit:", "TTL Seconds After Finished:"},
2864 },
2865 "no completed indexes": {
2866 job: &batchv1.Job{
2867 ObjectMeta: metav1.ObjectMeta{
2868 Name: "bar",
2869 Namespace: "foo",
2870 },
2871 Spec: batchv1.JobSpec{
2872 CompletionMode: &indexedCompletion,
2873 },
2874 },
2875 wantElements: []string{"Completed Indexes: <none>"},
2876 },
2877 "few completed indexes": {
2878 job: &batchv1.Job{
2879 ObjectMeta: metav1.ObjectMeta{
2880 Name: "bar",
2881 Namespace: "foo",
2882 },
2883 Spec: batchv1.JobSpec{
2884 CompletionMode: &indexedCompletion,
2885 },
2886 Status: batchv1.JobStatus{
2887 CompletedIndexes: "0-5,7,9,10,12,13,15,16,18,20,21,23,24,26,27,29,30,32",
2888 },
2889 },
2890 wantElements: []string{"Completed Indexes: 0-5,7,9,10,12,13,15,16,18,20,21,23,24,26,27,29,30,32"},
2891 },
2892 "too many completed indexes": {
2893 job: &batchv1.Job{
2894 ObjectMeta: metav1.ObjectMeta{
2895 Name: "bar",
2896 Namespace: "foo",
2897 },
2898 Spec: batchv1.JobSpec{
2899 CompletionMode: &indexedCompletion,
2900 },
2901 Status: batchv1.JobStatus{
2902 CompletedIndexes: "0-5,7,9,10,12,13,15,16,18,20,21,23,24,26,27,29,30,32-34,36,37",
2903 },
2904 },
2905 wantElements: []string{"Completed Indexes: 0-5,7,9,10,12,13,15,16,18,20,21,23,24,26,27,29,30,32-34,..."},
2906 },
2907 "suspend set to true": {
2908 job: &batchv1.Job{
2909 ObjectMeta: metav1.ObjectMeta{
2910 Name: "bar",
2911 Namespace: "foo",
2912 },
2913 Spec: batchv1.JobSpec{
2914 Suspend: ptr.To(true),
2915 TTLSecondsAfterFinished: ptr.To(int32(123)),
2916 BackoffLimit: ptr.To(int32(1)),
2917 },
2918 },
2919 wantElements: []string{
2920 "Suspend: true",
2921 "TTL Seconds After Finished: 123",
2922 "Backoff Limit: 1",
2923 },
2924 },
2925 "suspend set to false": {
2926 job: &batchv1.Job{
2927 ObjectMeta: metav1.ObjectMeta{
2928 Name: "bar",
2929 Namespace: "foo",
2930 },
2931 Spec: batchv1.JobSpec{
2932 Suspend: ptr.To(false),
2933 },
2934 },
2935 wantElements: []string{"Suspend: false"},
2936 },
2937 }
2938 for name, tc := range cases {
2939 t.Run(name, func(t *testing.T) {
2940 client := &describeClient{
2941 T: t,
2942 Namespace: tc.job.Namespace,
2943 Interface: fake.NewSimpleClientset(tc.job),
2944 }
2945 describer := JobDescriber{Interface: client}
2946 out, err := describer.Describe(tc.job.Namespace, tc.job.Name, DescriberSettings{ShowEvents: true})
2947 if err != nil {
2948 t.Fatalf("unexpected error describing object: %v", err)
2949 }
2950
2951 for _, expected := range tc.wantElements {
2952 if !strings.Contains(out, expected) {
2953 t.Errorf("expected to find %q in output:\n %s", expected, out)
2954 }
2955 }
2956
2957 for _, unexpected := range tc.dontWantElements {
2958 if strings.Contains(out, unexpected) {
2959 t.Errorf("unexpected to find %q in output:\n %s", unexpected, out)
2960 }
2961 }
2962 })
2963 }
2964 }
2965
2966 func TestDescribeIngress(t *testing.T) {
2967 ingresClassName := "test"
2968 backendV1beta1 := networkingv1beta1.IngressBackend{
2969 ServiceName: "default-backend",
2970 ServicePort: intstr.FromInt32(80),
2971 }
2972 v1beta1 := fake.NewSimpleClientset(&networkingv1beta1.Ingress{
2973 ObjectMeta: metav1.ObjectMeta{
2974 Name: "bar",
2975 Labels: map[string]string{
2976 "id1": "app1",
2977 "id2": "app2",
2978 },
2979 Namespace: "foo",
2980 },
2981 Spec: networkingv1beta1.IngressSpec{
2982 IngressClassName: &ingresClassName,
2983 Rules: []networkingv1beta1.IngressRule{
2984 {
2985 Host: "foo.bar.com",
2986 IngressRuleValue: networkingv1beta1.IngressRuleValue{
2987 HTTP: &networkingv1beta1.HTTPIngressRuleValue{
2988 Paths: []networkingv1beta1.HTTPIngressPath{
2989 {
2990 Path: "/foo",
2991 Backend: backendV1beta1,
2992 },
2993 },
2994 },
2995 },
2996 },
2997 },
2998 },
2999 })
3000 backendV1 := networkingv1.IngressBackend{
3001 Service: &networkingv1.IngressServiceBackend{
3002 Name: "default-backend",
3003 Port: networkingv1.ServiceBackendPort{
3004 Number: 80,
3005 },
3006 },
3007 }
3008
3009 netv1 := fake.NewSimpleClientset(&networkingv1.Ingress{
3010 ObjectMeta: metav1.ObjectMeta{
3011 Name: "bar",
3012 Namespace: "foo",
3013 },
3014 Spec: networkingv1.IngressSpec{
3015 IngressClassName: &ingresClassName,
3016 Rules: []networkingv1.IngressRule{
3017 {
3018 Host: "foo.bar.com",
3019 IngressRuleValue: networkingv1.IngressRuleValue{
3020 HTTP: &networkingv1.HTTPIngressRuleValue{
3021 Paths: []networkingv1.HTTPIngressPath{
3022 {
3023 Path: "/foo",
3024 Backend: backendV1,
3025 },
3026 },
3027 },
3028 },
3029 },
3030 },
3031 },
3032 })
3033
3034 backendResource := networkingv1.IngressBackend{
3035 Resource: &corev1.TypedLocalObjectReference{
3036 APIGroup: utilpointer.StringPtr("example.com"),
3037 Kind: "foo",
3038 Name: "bar",
3039 },
3040 }
3041 backendResourceNoAPIGroup := networkingv1.IngressBackend{
3042 Resource: &corev1.TypedLocalObjectReference{
3043 Kind: "foo",
3044 Name: "bar",
3045 },
3046 }
3047
3048 tests := map[string]struct {
3049 input *fake.Clientset
3050 output string
3051 }{
3052 "IngressRule.HTTP.Paths.Backend.Service v1beta1": {
3053 input: v1beta1,
3054 output: `Name: bar
3055 Labels: id1=app1
3056 id2=app2
3057 Namespace: foo
3058 Address:
3059 Ingress Class: test
3060 Default backend: <default>
3061 Rules:
3062 Host Path Backends
3063 ---- ---- --------
3064 foo.bar.com
3065 /foo default-backend:80 (<error: endpoints "default-backend" not found>)
3066 Annotations: <none>
3067 Events: <none>` + "\n",
3068 },
3069 "IngressRule.HTTP.Paths.Backend.Service v1": {
3070 input: netv1,
3071 output: `Name: bar
3072 Labels: <none>
3073 Namespace: foo
3074 Address:
3075 Ingress Class: test
3076 Default backend: <default>
3077 Rules:
3078 Host Path Backends
3079 ---- ---- --------
3080 foo.bar.com
3081 /foo default-backend:80 (<error: endpoints "default-backend" not found>)
3082 Annotations: <none>
3083 Events: <none>` + "\n",
3084 },
3085 "IngressRule.HTTP.Paths.Backend.Resource v1": {
3086 input: fake.NewSimpleClientset(&networkingv1.Ingress{
3087 ObjectMeta: metav1.ObjectMeta{
3088 Name: "bar",
3089 Namespace: "foo",
3090 },
3091 Spec: networkingv1.IngressSpec{
3092 IngressClassName: &ingresClassName,
3093 Rules: []networkingv1.IngressRule{
3094 {
3095 Host: "foo.bar.com",
3096 IngressRuleValue: networkingv1.IngressRuleValue{
3097 HTTP: &networkingv1.HTTPIngressRuleValue{
3098 Paths: []networkingv1.HTTPIngressPath{
3099 {
3100 Path: "/foo",
3101 Backend: backendResource,
3102 },
3103 },
3104 },
3105 },
3106 },
3107 },
3108 },
3109 }),
3110 output: `Name: bar
3111 Labels: <none>
3112 Namespace: foo
3113 Address:
3114 Ingress Class: test
3115 Default backend: <default>
3116 Rules:
3117 Host Path Backends
3118 ---- ---- --------
3119 foo.bar.com
3120 /foo APIGroup: example.com, Kind: foo, Name: bar
3121 Annotations: <none>
3122 Events: <none>` + "\n",
3123 },
3124 "IngressRule.HTTP.Paths.Backend.Resource v1 Without APIGroup": {
3125 input: fake.NewSimpleClientset(&networkingv1.Ingress{
3126 ObjectMeta: metav1.ObjectMeta{
3127 Name: "bar",
3128 Namespace: "foo",
3129 },
3130 Spec: networkingv1.IngressSpec{
3131 IngressClassName: &ingresClassName,
3132 Rules: []networkingv1.IngressRule{
3133 {
3134 Host: "foo.bar.com",
3135 IngressRuleValue: networkingv1.IngressRuleValue{
3136 HTTP: &networkingv1.HTTPIngressRuleValue{
3137 Paths: []networkingv1.HTTPIngressPath{
3138 {
3139 Path: "/foo",
3140 Backend: backendResourceNoAPIGroup,
3141 },
3142 },
3143 },
3144 },
3145 },
3146 },
3147 },
3148 }),
3149 output: `Name: bar
3150 Labels: <none>
3151 Namespace: foo
3152 Address:
3153 Ingress Class: test
3154 Default backend: <default>
3155 Rules:
3156 Host Path Backends
3157 ---- ---- --------
3158 foo.bar.com
3159 /foo APIGroup: <none>, Kind: foo, Name: bar
3160 Annotations: <none>
3161 Events: <none>` + "\n",
3162 },
3163 "Spec.DefaultBackend.Service & IngressRule.HTTP.Paths.Backend.Service v1": {
3164 input: fake.NewSimpleClientset(&networkingv1.Ingress{
3165 ObjectMeta: metav1.ObjectMeta{
3166 Name: "bar",
3167 Namespace: "foo",
3168 },
3169 Spec: networkingv1.IngressSpec{
3170 DefaultBackend: &backendV1,
3171 IngressClassName: &ingresClassName,
3172 Rules: []networkingv1.IngressRule{
3173 {
3174 Host: "foo.bar.com",
3175 IngressRuleValue: networkingv1.IngressRuleValue{
3176 HTTP: &networkingv1.HTTPIngressRuleValue{
3177 Paths: []networkingv1.HTTPIngressPath{
3178 {
3179 Path: "/foo",
3180 Backend: backendV1,
3181 },
3182 },
3183 },
3184 },
3185 },
3186 },
3187 },
3188 }),
3189 output: `Name: bar
3190 Labels: <none>
3191 Namespace: foo
3192 Address:
3193 Ingress Class: test
3194 Default backend: default-backend:80 (<error: endpoints "default-backend" not found>)
3195 Rules:
3196 Host Path Backends
3197 ---- ---- --------
3198 foo.bar.com
3199 /foo default-backend:80 (<error: endpoints "default-backend" not found>)
3200 Annotations: <none>
3201 Events: <none>` + "\n",
3202 },
3203 "Spec.DefaultBackend.Resource & IngressRule.HTTP.Paths.Backend.Resource v1": {
3204 input: fake.NewSimpleClientset(&networkingv1.Ingress{
3205 ObjectMeta: metav1.ObjectMeta{
3206 Name: "bar",
3207 Namespace: "foo",
3208 },
3209 Spec: networkingv1.IngressSpec{
3210 DefaultBackend: &backendResource,
3211 IngressClassName: &ingresClassName,
3212 Rules: []networkingv1.IngressRule{
3213 {
3214 Host: "foo.bar.com",
3215 IngressRuleValue: networkingv1.IngressRuleValue{
3216 HTTP: &networkingv1.HTTPIngressRuleValue{
3217 Paths: []networkingv1.HTTPIngressPath{
3218 {
3219 Path: "/foo",
3220 Backend: backendResource,
3221 },
3222 },
3223 },
3224 },
3225 },
3226 },
3227 },
3228 }),
3229 output: `Name: bar
3230 Labels: <none>
3231 Namespace: foo
3232 Address:
3233 Ingress Class: test
3234 Default backend: APIGroup: example.com, Kind: foo, Name: bar
3235 Rules:
3236 Host Path Backends
3237 ---- ---- --------
3238 foo.bar.com
3239 /foo APIGroup: example.com, Kind: foo, Name: bar
3240 Annotations: <none>
3241 Events: <none>` + "\n",
3242 },
3243 "Spec.DefaultBackend.Resource & IngressRule.HTTP.Paths.Backend.Service v1": {
3244 input: fake.NewSimpleClientset(&networkingv1.Ingress{
3245 ObjectMeta: metav1.ObjectMeta{
3246 Name: "bar",
3247 Namespace: "foo",
3248 },
3249 Spec: networkingv1.IngressSpec{
3250 DefaultBackend: &backendResource,
3251 IngressClassName: &ingresClassName,
3252 Rules: []networkingv1.IngressRule{
3253 {
3254 Host: "foo.bar.com",
3255 IngressRuleValue: networkingv1.IngressRuleValue{
3256 HTTP: &networkingv1.HTTPIngressRuleValue{
3257 Paths: []networkingv1.HTTPIngressPath{
3258 {
3259 Path: "/foo",
3260 Backend: backendV1,
3261 },
3262 },
3263 },
3264 },
3265 },
3266 },
3267 },
3268 }),
3269 output: `Name: bar
3270 Labels: <none>
3271 Namespace: foo
3272 Address:
3273 Ingress Class: test
3274 Default backend: APIGroup: example.com, Kind: foo, Name: bar
3275 Rules:
3276 Host Path Backends
3277 ---- ---- --------
3278 foo.bar.com
3279 /foo default-backend:80 (<error: endpoints "default-backend" not found>)
3280 Annotations: <none>
3281 Events: <none>` + "\n",
3282 },
3283 "DefaultBackend": {
3284 input: fake.NewSimpleClientset(&networkingv1.Ingress{
3285 ObjectMeta: metav1.ObjectMeta{
3286 Name: "bar",
3287 Namespace: "foo",
3288 },
3289 Spec: networkingv1.IngressSpec{
3290 DefaultBackend: &backendV1,
3291 IngressClassName: &ingresClassName,
3292 },
3293 }),
3294 output: `Name: bar
3295 Labels: <none>
3296 Namespace: foo
3297 Address:
3298 Ingress Class: test
3299 Default backend: default-backend:80 (<error: endpoints "default-backend" not found>)
3300 Rules:
3301 Host Path Backends
3302 ---- ---- --------
3303 * * default-backend:80 (<error: endpoints "default-backend" not found>)
3304 Annotations: <none>
3305 Events: <none>
3306 `,
3307 },
3308 "EmptyBackend": {
3309 input: fake.NewSimpleClientset(&networkingv1.Ingress{
3310 ObjectMeta: metav1.ObjectMeta{
3311 Name: "bar",
3312 Namespace: "foo",
3313 },
3314 Spec: networkingv1.IngressSpec{
3315 IngressClassName: &ingresClassName,
3316 },
3317 }),
3318 output: `Name: bar
3319 Labels: <none>
3320 Namespace: foo
3321 Address:
3322 Ingress Class: test
3323 Default backend: <default>
3324 Rules:
3325 Host Path Backends
3326 ---- ---- --------
3327 * * <default>
3328 Annotations: <none>
3329 Events: <none>
3330 `,
3331 },
3332 "EmptyIngressClassName": {
3333 input: fake.NewSimpleClientset(&networkingv1.Ingress{
3334 ObjectMeta: metav1.ObjectMeta{
3335 Name: "bar",
3336 Namespace: "foo",
3337 },
3338 Spec: networkingv1.IngressSpec{
3339 DefaultBackend: &backendV1,
3340 },
3341 }),
3342 output: `Name: bar
3343 Labels: <none>
3344 Namespace: foo
3345 Address:
3346 Ingress Class: <none>
3347 Default backend: default-backend:80 (<error: endpoints "default-backend" not found>)
3348 Rules:
3349 Host Path Backends
3350 ---- ---- --------
3351 * * default-backend:80 (<error: endpoints "default-backend" not found>)
3352 Annotations: <none>
3353 Events: <none>
3354 `,
3355 },
3356 }
3357
3358 for name, test := range tests {
3359 t.Run(name, func(t *testing.T) {
3360 c := &describeClient{T: t, Namespace: "foo", Interface: test.input}
3361 i := IngressDescriber{c}
3362 out, err := i.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
3363 if err != nil {
3364 t.Errorf("unexpected error: %v", err)
3365 }
3366 if out != test.output {
3367 t.Logf(out)
3368 t.Logf(test.output)
3369 t.Errorf("expected: \n%q\n but got output: \n%q\n", test.output, out)
3370 }
3371 })
3372 }
3373 }
3374
3375 func TestDescribeIngressV1(t *testing.T) {
3376 ingresClassName := "test"
3377 defaultBackend := networkingv1.IngressBackend{
3378 Service: &networkingv1.IngressServiceBackend{
3379 Name: "default-backend",
3380 Port: networkingv1.ServiceBackendPort{
3381 Number: 80,
3382 },
3383 },
3384 }
3385
3386 fakeClient := fake.NewSimpleClientset(&networkingv1.Ingress{
3387 ObjectMeta: metav1.ObjectMeta{
3388 Name: "bar",
3389 Labels: map[string]string{
3390 "id1": "app1",
3391 "id2": "app2",
3392 },
3393 Namespace: "foo",
3394 },
3395 Spec: networkingv1.IngressSpec{
3396 IngressClassName: &ingresClassName,
3397 Rules: []networkingv1.IngressRule{
3398 {
3399 Host: "foo.bar.com",
3400 IngressRuleValue: networkingv1.IngressRuleValue{
3401 HTTP: &networkingv1.HTTPIngressRuleValue{
3402 Paths: []networkingv1.HTTPIngressPath{
3403 {
3404 Path: "/foo",
3405 Backend: defaultBackend,
3406 },
3407 },
3408 },
3409 },
3410 },
3411 },
3412 },
3413 })
3414 i := IngressDescriber{fakeClient}
3415 out, err := i.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
3416 if err != nil {
3417 t.Errorf("unexpected error: %v", err)
3418 }
3419 if !strings.Contains(out, "bar") ||
3420 !strings.Contains(out, "foo") ||
3421 !strings.Contains(out, "foo.bar.com") ||
3422 !strings.Contains(out, "/foo") ||
3423 !strings.Contains(out, "app1") ||
3424 !strings.Contains(out, "app2") {
3425 t.Errorf("unexpected out: %s", out)
3426 }
3427 }
3428
3429 func TestDescribeStorageClass(t *testing.T) {
3430 reclaimPolicy := corev1.PersistentVolumeReclaimRetain
3431 bindingMode := storagev1.VolumeBindingMode("bindingmode")
3432 f := fake.NewSimpleClientset(&storagev1.StorageClass{
3433 ObjectMeta: metav1.ObjectMeta{
3434 Name: "foo",
3435 ResourceVersion: "4",
3436 Annotations: map[string]string{
3437 "name": "foo",
3438 },
3439 },
3440 Provisioner: "my-provisioner",
3441 Parameters: map[string]string{
3442 "param1": "value1",
3443 "param2": "value2",
3444 },
3445 ReclaimPolicy: &reclaimPolicy,
3446 VolumeBindingMode: &bindingMode,
3447 AllowedTopologies: []corev1.TopologySelectorTerm{
3448 {
3449 MatchLabelExpressions: []corev1.TopologySelectorLabelRequirement{
3450 {
3451 Key: "failure-domain.beta.kubernetes.io/zone",
3452 Values: []string{"zone1"},
3453 },
3454 {
3455 Key: "kubernetes.io/hostname",
3456 Values: []string{"node1"},
3457 },
3458 },
3459 },
3460 {
3461 MatchLabelExpressions: []corev1.TopologySelectorLabelRequirement{
3462 {
3463 Key: "failure-domain.beta.kubernetes.io/zone",
3464 Values: []string{"zone2"},
3465 },
3466 {
3467 Key: "kubernetes.io/hostname",
3468 Values: []string{"node2"},
3469 },
3470 },
3471 },
3472 },
3473 })
3474 s := StorageClassDescriber{f}
3475 out, err := s.Describe("", "foo", DescriberSettings{ShowEvents: true})
3476 if err != nil {
3477 t.Errorf("unexpected error: %v", err)
3478 }
3479 if !strings.Contains(out, "foo") ||
3480 !strings.Contains(out, "my-provisioner") ||
3481 !strings.Contains(out, "param1") ||
3482 !strings.Contains(out, "param2") ||
3483 !strings.Contains(out, "value1") ||
3484 !strings.Contains(out, "value2") ||
3485 !strings.Contains(out, "Retain") ||
3486 !strings.Contains(out, "bindingmode") ||
3487 !strings.Contains(out, "failure-domain.beta.kubernetes.io/zone") ||
3488 !strings.Contains(out, "zone1") ||
3489 !strings.Contains(out, "kubernetes.io/hostname") ||
3490 !strings.Contains(out, "node1") ||
3491 !strings.Contains(out, "zone2") ||
3492 !strings.Contains(out, "node2") {
3493 t.Errorf("unexpected out: %s", out)
3494 }
3495 }
3496
3497 func TestDescribeVolumeAttributesClass(t *testing.T) {
3498 expectedOut := `Name: foo
3499 Annotations: name=bar
3500 DriverName: my-driver
3501 Parameters: param1=value1,param2=value2
3502 Events: <none>
3503 `
3504
3505 f := fake.NewSimpleClientset(&storagev1alpha1.VolumeAttributesClass{
3506 ObjectMeta: metav1.ObjectMeta{
3507 Name: "foo",
3508 ResourceVersion: "4",
3509 Annotations: map[string]string{
3510 "name": "bar",
3511 },
3512 },
3513 DriverName: "my-driver",
3514 Parameters: map[string]string{
3515 "param1": "value1",
3516 "param2": "value2",
3517 },
3518 })
3519 s := VolumeAttributesClassDescriber{f}
3520 out, err := s.Describe("", "foo", DescriberSettings{ShowEvents: true})
3521 if err != nil {
3522 t.Errorf("unexpected error: %v", err)
3523 }
3524 if out != expectedOut {
3525 t.Errorf("expected:\n %s\n but got output:\n %s diff:\n%s", expectedOut, out, cmp.Diff(out, expectedOut))
3526 }
3527 }
3528
3529 func TestDescribeCSINode(t *testing.T) {
3530 limit := utilpointer.Int32Ptr(int32(2))
3531 f := fake.NewSimpleClientset(&storagev1.CSINode{
3532 ObjectMeta: metav1.ObjectMeta{Name: "foo"},
3533 Spec: storagev1.CSINodeSpec{
3534 Drivers: []storagev1.CSINodeDriver{
3535 {
3536 Name: "driver1",
3537 NodeID: "node1",
3538 },
3539 {
3540 Name: "driver2",
3541 NodeID: "node2",
3542 Allocatable: &storagev1.VolumeNodeResources{Count: limit},
3543 },
3544 },
3545 },
3546 })
3547 s := CSINodeDescriber{f}
3548 out, err := s.Describe("", "foo", DescriberSettings{ShowEvents: true})
3549 if err != nil {
3550 t.Errorf("unexpected error: %v", err)
3551 }
3552 if !strings.Contains(out, "foo") ||
3553 !strings.Contains(out, "driver1") ||
3554 !strings.Contains(out, "node1") ||
3555 !strings.Contains(out, "driver2") ||
3556 !strings.Contains(out, "node2") {
3557 t.Errorf("unexpected out: %s", out)
3558 }
3559 }
3560
3561 func TestDescribePodDisruptionBudgetV1beta1(t *testing.T) {
3562 minAvailable := intstr.FromInt32(22)
3563 f := fake.NewSimpleClientset(&policyv1beta1.PodDisruptionBudget{
3564 ObjectMeta: metav1.ObjectMeta{
3565 Namespace: "ns1",
3566 Name: "pdb1",
3567 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
3568 },
3569 Spec: policyv1beta1.PodDisruptionBudgetSpec{
3570 MinAvailable: &minAvailable,
3571 },
3572 Status: policyv1beta1.PodDisruptionBudgetStatus{
3573 DisruptionsAllowed: 5,
3574 },
3575 })
3576 s := PodDisruptionBudgetDescriber{f}
3577 out, err := s.Describe("ns1", "pdb1", DescriberSettings{ShowEvents: true})
3578 if err != nil {
3579 t.Errorf("unexpected error: %v", err)
3580 }
3581 if !strings.Contains(out, "pdb1") ||
3582 !strings.Contains(out, "ns1") ||
3583 !strings.Contains(out, "22") ||
3584 !strings.Contains(out, "5") {
3585 t.Errorf("unexpected out: %s", out)
3586 }
3587 }
3588
3589 func TestDescribePodDisruptionBudgetV1(t *testing.T) {
3590 minAvailable := intstr.FromInt32(22)
3591 f := fake.NewSimpleClientset(&policyv1.PodDisruptionBudget{
3592 ObjectMeta: metav1.ObjectMeta{
3593 Namespace: "ns1",
3594 Name: "pdb1",
3595 CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
3596 },
3597 Spec: policyv1.PodDisruptionBudgetSpec{
3598 MinAvailable: &minAvailable,
3599 },
3600 Status: policyv1.PodDisruptionBudgetStatus{
3601 DisruptionsAllowed: 5,
3602 },
3603 })
3604 s := PodDisruptionBudgetDescriber{f}
3605 out, err := s.Describe("ns1", "pdb1", DescriberSettings{ShowEvents: true})
3606 if err != nil {
3607 t.Errorf("unexpected error: %v", err)
3608 }
3609 if !strings.Contains(out, "pdb1") ||
3610 !strings.Contains(out, "ns1") ||
3611 !strings.Contains(out, "22") ||
3612 !strings.Contains(out, "5") {
3613 t.Errorf("unexpected out: %s", out)
3614 }
3615 }
3616
3617 func TestDescribeHorizontalPodAutoscaler(t *testing.T) {
3618 minReplicasVal := int32(2)
3619 targetUtilizationVal := int32(80)
3620 currentUtilizationVal := int32(50)
3621 maxSelectPolicy := autoscalingv2.MaxChangePolicySelect
3622 metricLabelSelector, err := metav1.ParseToLabelSelector("label=value")
3623 if err != nil {
3624 t.Errorf("unable to parse label selector: %v", err)
3625 }
3626 testsv2 := []struct {
3627 name string
3628 hpa autoscalingv2.HorizontalPodAutoscaler
3629 }{
3630 {
3631 "minReplicas unset",
3632 autoscalingv2.HorizontalPodAutoscaler{
3633 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
3634 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
3635 Name: "some-rc",
3636 Kind: "ReplicationController",
3637 },
3638 MaxReplicas: 10,
3639 },
3640 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
3641 CurrentReplicas: 4,
3642 DesiredReplicas: 5,
3643 },
3644 },
3645 },
3646 {
3647 "external source type, target average value (no current)",
3648 autoscalingv2.HorizontalPodAutoscaler{
3649 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
3650 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
3651 Name: "some-rc",
3652 Kind: "ReplicationController",
3653 },
3654 MinReplicas: &minReplicasVal,
3655 MaxReplicas: 10,
3656 Metrics: []autoscalingv2.MetricSpec{
3657 {
3658 Type: autoscalingv2.ExternalMetricSourceType,
3659 External: &autoscalingv2.ExternalMetricSource{
3660 Metric: autoscalingv2.MetricIdentifier{
3661 Name: "some-external-metric",
3662 Selector: metricLabelSelector,
3663 },
3664 Target: autoscalingv2.MetricTarget{
3665 Type: autoscalingv2.AverageValueMetricType,
3666 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
3667 },
3668 },
3669 },
3670 },
3671 },
3672 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
3673 CurrentReplicas: 4,
3674 DesiredReplicas: 5,
3675 },
3676 },
3677 },
3678 {
3679 "external source type, target average value (with current)",
3680 autoscalingv2.HorizontalPodAutoscaler{
3681 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
3682 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
3683 Name: "some-rc",
3684 Kind: "ReplicationController",
3685 },
3686 MinReplicas: &minReplicasVal,
3687 MaxReplicas: 10,
3688 Metrics: []autoscalingv2.MetricSpec{
3689 {
3690 Type: autoscalingv2.ExternalMetricSourceType,
3691 External: &autoscalingv2.ExternalMetricSource{
3692 Metric: autoscalingv2.MetricIdentifier{
3693 Name: "some-external-metric",
3694 Selector: metricLabelSelector,
3695 },
3696 Target: autoscalingv2.MetricTarget{
3697 Type: autoscalingv2.AverageValueMetricType,
3698 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
3699 },
3700 },
3701 },
3702 },
3703 },
3704 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
3705 CurrentReplicas: 4,
3706 DesiredReplicas: 5,
3707 CurrentMetrics: []autoscalingv2.MetricStatus{
3708 {
3709 Type: autoscalingv2.ExternalMetricSourceType,
3710 External: &autoscalingv2.ExternalMetricStatus{
3711 Metric: autoscalingv2.MetricIdentifier{
3712 Name: "some-external-metric",
3713 Selector: metricLabelSelector,
3714 },
3715 Current: autoscalingv2.MetricValueStatus{
3716 AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
3717 },
3718 },
3719 },
3720 },
3721 },
3722 },
3723 },
3724 {
3725 "external source type, target value (no current)",
3726 autoscalingv2.HorizontalPodAutoscaler{
3727 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
3728 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
3729 Name: "some-rc",
3730 Kind: "ReplicationController",
3731 },
3732 MinReplicas: &minReplicasVal,
3733 MaxReplicas: 10,
3734 Metrics: []autoscalingv2.MetricSpec{
3735 {
3736 Type: autoscalingv2.ExternalMetricSourceType,
3737 External: &autoscalingv2.ExternalMetricSource{
3738 Metric: autoscalingv2.MetricIdentifier{
3739 Name: "some-external-metric",
3740 Selector: metricLabelSelector,
3741 },
3742 Target: autoscalingv2.MetricTarget{
3743 Type: autoscalingv2.ValueMetricType,
3744 Value: resource.NewMilliQuantity(100, resource.DecimalSI),
3745 },
3746 },
3747 },
3748 },
3749 },
3750 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
3751 CurrentReplicas: 4,
3752 DesiredReplicas: 5,
3753 },
3754 },
3755 },
3756 {
3757 "external source type, target value (with current)",
3758 autoscalingv2.HorizontalPodAutoscaler{
3759 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
3760 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
3761 Name: "some-rc",
3762 Kind: "ReplicationController",
3763 },
3764 MinReplicas: &minReplicasVal,
3765 MaxReplicas: 10,
3766 Metrics: []autoscalingv2.MetricSpec{
3767 {
3768 Type: autoscalingv2.ExternalMetricSourceType,
3769 External: &autoscalingv2.ExternalMetricSource{
3770 Metric: autoscalingv2.MetricIdentifier{
3771 Name: "some-external-metric",
3772 Selector: metricLabelSelector,
3773 },
3774 Target: autoscalingv2.MetricTarget{
3775 Type: autoscalingv2.ValueMetricType,
3776 Value: resource.NewMilliQuantity(100, resource.DecimalSI),
3777 },
3778 },
3779 },
3780 },
3781 },
3782 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
3783 CurrentReplicas: 4,
3784 DesiredReplicas: 5,
3785 CurrentMetrics: []autoscalingv2.MetricStatus{
3786 {
3787 Type: autoscalingv2.ExternalMetricSourceType,
3788 External: &autoscalingv2.ExternalMetricStatus{
3789 Metric: autoscalingv2.MetricIdentifier{
3790 Name: "some-external-metric",
3791 Selector: metricLabelSelector,
3792 },
3793 Current: autoscalingv2.MetricValueStatus{
3794 Value: resource.NewMilliQuantity(50, resource.DecimalSI),
3795 },
3796 },
3797 },
3798 },
3799 },
3800 },
3801 },
3802 {
3803 "pods source type (no current)",
3804 autoscalingv2.HorizontalPodAutoscaler{
3805 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
3806 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
3807 Name: "some-rc",
3808 Kind: "ReplicationController",
3809 },
3810 MinReplicas: &minReplicasVal,
3811 MaxReplicas: 10,
3812 Metrics: []autoscalingv2.MetricSpec{
3813 {
3814 Type: autoscalingv2.PodsMetricSourceType,
3815 Pods: &autoscalingv2.PodsMetricSource{
3816 Metric: autoscalingv2.MetricIdentifier{
3817 Name: "some-pods-metric",
3818 },
3819 Target: autoscalingv2.MetricTarget{
3820 Type: autoscalingv2.AverageValueMetricType,
3821 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
3822 },
3823 },
3824 },
3825 },
3826 },
3827 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
3828 CurrentReplicas: 4,
3829 DesiredReplicas: 5,
3830 },
3831 },
3832 },
3833 {
3834 "pods source type (with current)",
3835 autoscalingv2.HorizontalPodAutoscaler{
3836 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
3837 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
3838 Name: "some-rc",
3839 Kind: "ReplicationController",
3840 },
3841 MinReplicas: &minReplicasVal,
3842 MaxReplicas: 10,
3843 Metrics: []autoscalingv2.MetricSpec{
3844 {
3845 Type: autoscalingv2.PodsMetricSourceType,
3846 Pods: &autoscalingv2.PodsMetricSource{
3847 Metric: autoscalingv2.MetricIdentifier{
3848 Name: "some-pods-metric",
3849 },
3850 Target: autoscalingv2.MetricTarget{
3851 Type: autoscalingv2.AverageValueMetricType,
3852 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
3853 },
3854 },
3855 },
3856 },
3857 },
3858 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
3859 CurrentReplicas: 4,
3860 DesiredReplicas: 5,
3861 CurrentMetrics: []autoscalingv2.MetricStatus{
3862 {
3863 Type: autoscalingv2.PodsMetricSourceType,
3864 Pods: &autoscalingv2.PodsMetricStatus{
3865 Metric: autoscalingv2.MetricIdentifier{
3866 Name: "some-pods-metric",
3867 },
3868 Current: autoscalingv2.MetricValueStatus{
3869 AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
3870 },
3871 },
3872 },
3873 },
3874 },
3875 },
3876 },
3877 {
3878 "object source type target average value (no current)",
3879 autoscalingv2.HorizontalPodAutoscaler{
3880 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
3881 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
3882 Name: "some-rc",
3883 Kind: "ReplicationController",
3884 },
3885 MinReplicas: &minReplicasVal,
3886 MaxReplicas: 10,
3887 Metrics: []autoscalingv2.MetricSpec{
3888 {
3889 Type: autoscalingv2.ObjectMetricSourceType,
3890 Object: &autoscalingv2.ObjectMetricSource{
3891 DescribedObject: autoscalingv2.CrossVersionObjectReference{
3892 Name: "some-service",
3893 Kind: "Service",
3894 },
3895 Metric: autoscalingv2.MetricIdentifier{
3896 Name: "some-service-metric",
3897 },
3898 Target: autoscalingv2.MetricTarget{
3899 Type: autoscalingv2.AverageValueMetricType,
3900 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
3901 },
3902 },
3903 },
3904 },
3905 },
3906 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
3907 CurrentReplicas: 4,
3908 DesiredReplicas: 5,
3909 },
3910 },
3911 },
3912 {
3913 "object source type target average value (with current)",
3914 autoscalingv2.HorizontalPodAutoscaler{
3915 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
3916 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
3917 Name: "some-rc",
3918 Kind: "ReplicationController",
3919 },
3920 MinReplicas: &minReplicasVal,
3921 MaxReplicas: 10,
3922 Metrics: []autoscalingv2.MetricSpec{
3923 {
3924 Type: autoscalingv2.ObjectMetricSourceType,
3925 Object: &autoscalingv2.ObjectMetricSource{
3926 DescribedObject: autoscalingv2.CrossVersionObjectReference{
3927 Name: "some-service",
3928 Kind: "Service",
3929 },
3930 Metric: autoscalingv2.MetricIdentifier{
3931 Name: "some-service-metric",
3932 },
3933 Target: autoscalingv2.MetricTarget{
3934 Type: autoscalingv2.AverageValueMetricType,
3935 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
3936 },
3937 },
3938 },
3939 },
3940 },
3941 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
3942 CurrentReplicas: 4,
3943 DesiredReplicas: 5,
3944 CurrentMetrics: []autoscalingv2.MetricStatus{
3945 {
3946 Type: autoscalingv2.ObjectMetricSourceType,
3947 Object: &autoscalingv2.ObjectMetricStatus{
3948 DescribedObject: autoscalingv2.CrossVersionObjectReference{
3949 Name: "some-service",
3950 Kind: "Service",
3951 },
3952 Metric: autoscalingv2.MetricIdentifier{
3953 Name: "some-service-metric",
3954 },
3955 Current: autoscalingv2.MetricValueStatus{
3956 AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
3957 },
3958 },
3959 },
3960 },
3961 },
3962 },
3963 },
3964 {
3965 "object source type target value (no current)",
3966 autoscalingv2.HorizontalPodAutoscaler{
3967 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
3968 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
3969 Name: "some-rc",
3970 Kind: "ReplicationController",
3971 },
3972 MinReplicas: &minReplicasVal,
3973 MaxReplicas: 10,
3974 Metrics: []autoscalingv2.MetricSpec{
3975 {
3976 Type: autoscalingv2.ObjectMetricSourceType,
3977 Object: &autoscalingv2.ObjectMetricSource{
3978 DescribedObject: autoscalingv2.CrossVersionObjectReference{
3979 Name: "some-service",
3980 Kind: "Service",
3981 },
3982 Metric: autoscalingv2.MetricIdentifier{
3983 Name: "some-service-metric",
3984 },
3985 Target: autoscalingv2.MetricTarget{
3986 Type: autoscalingv2.ValueMetricType,
3987 Value: resource.NewMilliQuantity(100, resource.DecimalSI),
3988 },
3989 },
3990 },
3991 },
3992 },
3993 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
3994 CurrentReplicas: 4,
3995 DesiredReplicas: 5,
3996 },
3997 },
3998 },
3999 {
4000 "object source type target value (with current)",
4001 autoscalingv2.HorizontalPodAutoscaler{
4002 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4003 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4004 Name: "some-rc",
4005 Kind: "ReplicationController",
4006 },
4007 MinReplicas: &minReplicasVal,
4008 MaxReplicas: 10,
4009 Metrics: []autoscalingv2.MetricSpec{
4010 {
4011 Type: autoscalingv2.ObjectMetricSourceType,
4012 Object: &autoscalingv2.ObjectMetricSource{
4013 DescribedObject: autoscalingv2.CrossVersionObjectReference{
4014 Name: "some-service",
4015 Kind: "Service",
4016 },
4017 Metric: autoscalingv2.MetricIdentifier{
4018 Name: "some-service-metric",
4019 },
4020 Target: autoscalingv2.MetricTarget{
4021 Type: autoscalingv2.ValueMetricType,
4022 Value: resource.NewMilliQuantity(100, resource.DecimalSI),
4023 },
4024 },
4025 },
4026 },
4027 },
4028 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
4029 CurrentReplicas: 4,
4030 DesiredReplicas: 5,
4031 CurrentMetrics: []autoscalingv2.MetricStatus{
4032 {
4033 Type: autoscalingv2.ObjectMetricSourceType,
4034 Object: &autoscalingv2.ObjectMetricStatus{
4035 DescribedObject: autoscalingv2.CrossVersionObjectReference{
4036 Name: "some-service",
4037 Kind: "Service",
4038 },
4039 Metric: autoscalingv2.MetricIdentifier{
4040 Name: "some-service-metric",
4041 },
4042 Current: autoscalingv2.MetricValueStatus{
4043 Value: resource.NewMilliQuantity(50, resource.DecimalSI),
4044 },
4045 },
4046 },
4047 },
4048 },
4049 },
4050 },
4051 {
4052 "resource source type, target average value (no current)",
4053 autoscalingv2.HorizontalPodAutoscaler{
4054 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4055 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4056 Name: "some-rc",
4057 Kind: "ReplicationController",
4058 },
4059 MinReplicas: &minReplicasVal,
4060 MaxReplicas: 10,
4061 Metrics: []autoscalingv2.MetricSpec{
4062 {
4063 Type: autoscalingv2.ResourceMetricSourceType,
4064 Resource: &autoscalingv2.ResourceMetricSource{
4065 Name: corev1.ResourceCPU,
4066 Target: autoscalingv2.MetricTarget{
4067 Type: autoscalingv2.AverageValueMetricType,
4068 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
4069 },
4070 },
4071 },
4072 },
4073 },
4074 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
4075 CurrentReplicas: 4,
4076 DesiredReplicas: 5,
4077 },
4078 },
4079 },
4080 {
4081 "resource source type, target average value (with current)",
4082 autoscalingv2.HorizontalPodAutoscaler{
4083 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4084 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4085 Name: "some-rc",
4086 Kind: "ReplicationController",
4087 },
4088 MinReplicas: &minReplicasVal,
4089 MaxReplicas: 10,
4090 Metrics: []autoscalingv2.MetricSpec{
4091 {
4092 Type: autoscalingv2.ResourceMetricSourceType,
4093 Resource: &autoscalingv2.ResourceMetricSource{
4094 Name: corev1.ResourceCPU,
4095 Target: autoscalingv2.MetricTarget{
4096 Type: autoscalingv2.AverageValueMetricType,
4097 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
4098 },
4099 },
4100 },
4101 },
4102 },
4103 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
4104 CurrentReplicas: 4,
4105 DesiredReplicas: 5,
4106 CurrentMetrics: []autoscalingv2.MetricStatus{
4107 {
4108 Type: autoscalingv2.ResourceMetricSourceType,
4109 Resource: &autoscalingv2.ResourceMetricStatus{
4110 Name: corev1.ResourceCPU,
4111 Current: autoscalingv2.MetricValueStatus{
4112 AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
4113 },
4114 },
4115 },
4116 },
4117 },
4118 },
4119 },
4120 {
4121 "resource source type, target utilization (no current)",
4122 autoscalingv2.HorizontalPodAutoscaler{
4123 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4124 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4125 Name: "some-rc",
4126 Kind: "ReplicationController",
4127 },
4128 MinReplicas: &minReplicasVal,
4129 MaxReplicas: 10,
4130 Metrics: []autoscalingv2.MetricSpec{
4131 {
4132 Type: autoscalingv2.ResourceMetricSourceType,
4133 Resource: &autoscalingv2.ResourceMetricSource{
4134 Name: corev1.ResourceCPU,
4135 Target: autoscalingv2.MetricTarget{
4136 Type: autoscalingv2.UtilizationMetricType,
4137 AverageUtilization: &targetUtilizationVal,
4138 },
4139 },
4140 },
4141 },
4142 },
4143 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
4144 CurrentReplicas: 4,
4145 DesiredReplicas: 5,
4146 },
4147 },
4148 },
4149 {
4150 "resource source type, target utilization (with current)",
4151 autoscalingv2.HorizontalPodAutoscaler{
4152 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4153 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4154 Name: "some-rc",
4155 Kind: "ReplicationController",
4156 },
4157 MinReplicas: &minReplicasVal,
4158 MaxReplicas: 10,
4159 Metrics: []autoscalingv2.MetricSpec{
4160 {
4161 Type: autoscalingv2.ResourceMetricSourceType,
4162 Resource: &autoscalingv2.ResourceMetricSource{
4163 Name: corev1.ResourceCPU,
4164 Target: autoscalingv2.MetricTarget{
4165 Type: autoscalingv2.UtilizationMetricType,
4166 AverageUtilization: &targetUtilizationVal,
4167 },
4168 },
4169 },
4170 },
4171 },
4172 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
4173 CurrentReplicas: 4,
4174 DesiredReplicas: 5,
4175 CurrentMetrics: []autoscalingv2.MetricStatus{
4176 {
4177 Type: autoscalingv2.ResourceMetricSourceType,
4178 Resource: &autoscalingv2.ResourceMetricStatus{
4179 Name: corev1.ResourceCPU,
4180 Current: autoscalingv2.MetricValueStatus{
4181 AverageUtilization: ¤tUtilizationVal,
4182 AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI),
4183 },
4184 },
4185 },
4186 },
4187 },
4188 },
4189 },
4190 {
4191 "container resource source type, target average value (no current)",
4192 autoscalingv2.HorizontalPodAutoscaler{
4193 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4194 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4195 Name: "some-rc",
4196 Kind: "ReplicationController",
4197 },
4198 MinReplicas: &minReplicasVal,
4199 MaxReplicas: 10,
4200 Metrics: []autoscalingv2.MetricSpec{
4201 {
4202 Type: autoscalingv2.ContainerResourceMetricSourceType,
4203 ContainerResource: &autoscalingv2.ContainerResourceMetricSource{
4204 Name: corev1.ResourceCPU,
4205 Container: "application",
4206 Target: autoscalingv2.MetricTarget{
4207 Type: autoscalingv2.AverageValueMetricType,
4208 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
4209 },
4210 },
4211 },
4212 },
4213 },
4214 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
4215 CurrentReplicas: 4,
4216 DesiredReplicas: 5,
4217 },
4218 },
4219 },
4220 {
4221 "container resource source type, target average value (with current)",
4222 autoscalingv2.HorizontalPodAutoscaler{
4223 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4224 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4225 Name: "some-rc",
4226 Kind: "ReplicationController",
4227 },
4228 MinReplicas: &minReplicasVal,
4229 MaxReplicas: 10,
4230 Metrics: []autoscalingv2.MetricSpec{
4231 {
4232 Type: autoscalingv2.ContainerResourceMetricSourceType,
4233 ContainerResource: &autoscalingv2.ContainerResourceMetricSource{
4234 Name: corev1.ResourceCPU,
4235 Container: "application",
4236 Target: autoscalingv2.MetricTarget{
4237 Type: autoscalingv2.AverageValueMetricType,
4238 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
4239 },
4240 },
4241 },
4242 },
4243 },
4244 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
4245 CurrentReplicas: 4,
4246 DesiredReplicas: 5,
4247 CurrentMetrics: []autoscalingv2.MetricStatus{
4248 {
4249 Type: autoscalingv2.ContainerResourceMetricSourceType,
4250 ContainerResource: &autoscalingv2.ContainerResourceMetricStatus{
4251 Name: corev1.ResourceCPU,
4252 Container: "application",
4253 Current: autoscalingv2.MetricValueStatus{
4254 AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
4255 },
4256 },
4257 },
4258 },
4259 },
4260 },
4261 },
4262 {
4263 "container resource source type, target utilization (no current)",
4264 autoscalingv2.HorizontalPodAutoscaler{
4265 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4266 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4267 Name: "some-rc",
4268 Kind: "ReplicationController",
4269 },
4270 MinReplicas: &minReplicasVal,
4271 MaxReplicas: 10,
4272 Metrics: []autoscalingv2.MetricSpec{
4273 {
4274 Type: autoscalingv2.ContainerResourceMetricSourceType,
4275 ContainerResource: &autoscalingv2.ContainerResourceMetricSource{
4276 Name: corev1.ResourceCPU,
4277 Container: "application",
4278 Target: autoscalingv2.MetricTarget{
4279 Type: autoscalingv2.UtilizationMetricType,
4280 AverageUtilization: &targetUtilizationVal,
4281 },
4282 },
4283 },
4284 },
4285 },
4286 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
4287 CurrentReplicas: 4,
4288 DesiredReplicas: 5,
4289 },
4290 },
4291 },
4292 {
4293 "container resource source type, target utilization (with current)",
4294 autoscalingv2.HorizontalPodAutoscaler{
4295 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4296 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4297 Name: "some-rc",
4298 Kind: "ReplicationController",
4299 },
4300 MinReplicas: &minReplicasVal,
4301 MaxReplicas: 10,
4302 Metrics: []autoscalingv2.MetricSpec{
4303 {
4304 Type: autoscalingv2.ContainerResourceMetricSourceType,
4305 ContainerResource: &autoscalingv2.ContainerResourceMetricSource{
4306 Name: corev1.ResourceCPU,
4307 Container: "application",
4308 Target: autoscalingv2.MetricTarget{
4309 Type: autoscalingv2.UtilizationMetricType,
4310 AverageUtilization: &targetUtilizationVal,
4311 },
4312 },
4313 },
4314 },
4315 },
4316 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
4317 CurrentReplicas: 4,
4318 DesiredReplicas: 5,
4319 CurrentMetrics: []autoscalingv2.MetricStatus{
4320 {
4321 Type: autoscalingv2.ContainerResourceMetricSourceType,
4322 ContainerResource: &autoscalingv2.ContainerResourceMetricStatus{
4323 Name: corev1.ResourceCPU,
4324 Container: "application",
4325 Current: autoscalingv2.MetricValueStatus{
4326 AverageUtilization: ¤tUtilizationVal,
4327 AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI),
4328 },
4329 },
4330 },
4331 },
4332 },
4333 },
4334 },
4335
4336 {
4337 "multiple metrics",
4338 autoscalingv2.HorizontalPodAutoscaler{
4339 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4340 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4341 Name: "some-rc",
4342 Kind: "ReplicationController",
4343 },
4344 MinReplicas: &minReplicasVal,
4345 MaxReplicas: 10,
4346 Metrics: []autoscalingv2.MetricSpec{
4347 {
4348 Type: autoscalingv2.PodsMetricSourceType,
4349 Pods: &autoscalingv2.PodsMetricSource{
4350 Metric: autoscalingv2.MetricIdentifier{
4351 Name: "some-pods-metric",
4352 },
4353 Target: autoscalingv2.MetricTarget{
4354 Type: autoscalingv2.AverageValueMetricType,
4355 AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
4356 },
4357 },
4358 },
4359 {
4360 Type: autoscalingv2.ResourceMetricSourceType,
4361 Resource: &autoscalingv2.ResourceMetricSource{
4362 Name: corev1.ResourceCPU,
4363 Target: autoscalingv2.MetricTarget{
4364 Type: autoscalingv2.UtilizationMetricType,
4365 AverageUtilization: &targetUtilizationVal,
4366 },
4367 },
4368 },
4369 {
4370 Type: autoscalingv2.PodsMetricSourceType,
4371 Pods: &autoscalingv2.PodsMetricSource{
4372 Metric: autoscalingv2.MetricIdentifier{
4373 Name: "other-pods-metric",
4374 },
4375 Target: autoscalingv2.MetricTarget{
4376 Type: autoscalingv2.AverageValueMetricType,
4377 AverageValue: resource.NewMilliQuantity(400, resource.DecimalSI),
4378 },
4379 },
4380 },
4381 },
4382 },
4383 Status: autoscalingv2.HorizontalPodAutoscalerStatus{
4384 CurrentReplicas: 4,
4385 DesiredReplicas: 5,
4386 CurrentMetrics: []autoscalingv2.MetricStatus{
4387 {
4388 Type: autoscalingv2.PodsMetricSourceType,
4389 Pods: &autoscalingv2.PodsMetricStatus{
4390 Metric: autoscalingv2.MetricIdentifier{
4391 Name: "some-pods-metric",
4392 },
4393 Current: autoscalingv2.MetricValueStatus{
4394 AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
4395 },
4396 },
4397 },
4398 {
4399 Type: autoscalingv2.ResourceMetricSourceType,
4400 Resource: &autoscalingv2.ResourceMetricStatus{
4401 Name: corev1.ResourceCPU,
4402 Current: autoscalingv2.MetricValueStatus{
4403 AverageUtilization: ¤tUtilizationVal,
4404 AverageValue: resource.NewMilliQuantity(40, resource.DecimalSI),
4405 },
4406 },
4407 },
4408 },
4409 },
4410 },
4411 },
4412 {
4413 "scale up behavior specified",
4414 autoscalingv2.HorizontalPodAutoscaler{
4415 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4416 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4417 Name: "behavior-target",
4418 Kind: "Deployment",
4419 },
4420 MinReplicas: &minReplicasVal,
4421 MaxReplicas: 10,
4422 Metrics: []autoscalingv2.MetricSpec{
4423 {
4424 Type: autoscalingv2.ResourceMetricSourceType,
4425 Resource: &autoscalingv2.ResourceMetricSource{
4426 Name: corev1.ResourceCPU,
4427 Target: autoscalingv2.MetricTarget{
4428 Type: autoscalingv2.UtilizationMetricType,
4429 AverageUtilization: &targetUtilizationVal,
4430 },
4431 },
4432 },
4433 },
4434 Behavior: &autoscalingv2.HorizontalPodAutoscalerBehavior{
4435 ScaleUp: &autoscalingv2.HPAScalingRules{
4436 StabilizationWindowSeconds: utilpointer.Int32Ptr(30),
4437 SelectPolicy: &maxSelectPolicy,
4438 Policies: []autoscalingv2.HPAScalingPolicy{
4439 {Type: autoscalingv2.PodsScalingPolicy, Value: 10, PeriodSeconds: 10},
4440 {Type: autoscalingv2.PercentScalingPolicy, Value: 10, PeriodSeconds: 10},
4441 },
4442 },
4443 },
4444 },
4445 },
4446 },
4447 {
4448 "scale down behavior specified",
4449 autoscalingv2.HorizontalPodAutoscaler{
4450 Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
4451 ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
4452 Name: "behavior-target",
4453 Kind: "Deployment",
4454 },
4455 MinReplicas: &minReplicasVal,
4456 MaxReplicas: 10,
4457 Metrics: []autoscalingv2.MetricSpec{
4458 {
4459 Type: autoscalingv2.ResourceMetricSourceType,
4460 Resource: &autoscalingv2.ResourceMetricSource{
4461 Name: corev1.ResourceCPU,
4462 Target: autoscalingv2.MetricTarget{
4463 Type: autoscalingv2.UtilizationMetricType,
4464 AverageUtilization: &targetUtilizationVal,
4465 },
4466 },
4467 },
4468 },
4469 Behavior: &autoscalingv2.HorizontalPodAutoscalerBehavior{
4470 ScaleDown: &autoscalingv2.HPAScalingRules{
4471 StabilizationWindowSeconds: utilpointer.Int32Ptr(30),
4472 Policies: []autoscalingv2.HPAScalingPolicy{
4473 {Type: autoscalingv2.PodsScalingPolicy, Value: 10, PeriodSeconds: 10},
4474 {Type: autoscalingv2.PercentScalingPolicy, Value: 10, PeriodSeconds: 10},
4475 },
4476 },
4477 },
4478 },
4479 },
4480 },
4481 }
4482
4483 for _, test := range testsv2 {
4484 t.Run(test.name, func(t *testing.T) {
4485 test.hpa.ObjectMeta = metav1.ObjectMeta{
4486 Name: "bar",
4487 Namespace: "foo",
4488 }
4489 fake := fake.NewSimpleClientset(&test.hpa)
4490 desc := HorizontalPodAutoscalerDescriber{fake}
4491 str, err := desc.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
4492 if err != nil {
4493 t.Errorf("Unexpected error for test %s: %v", test.name, err)
4494 }
4495 if str == "" {
4496 t.Errorf("Unexpected empty string for test %s. Expected HPA Describer output", test.name)
4497 }
4498 t.Logf("Description for %q:\n%s", test.name, str)
4499 })
4500 }
4501
4502 testsV1 := []struct {
4503 name string
4504 hpa autoscalingv1.HorizontalPodAutoscaler
4505 }{
4506 {
4507 "minReplicas unset",
4508 autoscalingv1.HorizontalPodAutoscaler{
4509 Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
4510 ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
4511 Name: "some-rc",
4512 Kind: "ReplicationController",
4513 },
4514 MaxReplicas: 10,
4515 },
4516 Status: autoscalingv1.HorizontalPodAutoscalerStatus{
4517 CurrentReplicas: 4,
4518 DesiredReplicas: 5,
4519 },
4520 },
4521 },
4522 {
4523 "minReplicas set",
4524 autoscalingv1.HorizontalPodAutoscaler{
4525 Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
4526 ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
4527 Name: "some-rc",
4528 Kind: "ReplicationController",
4529 },
4530 MinReplicas: &minReplicasVal,
4531 MaxReplicas: 10,
4532 },
4533 Status: autoscalingv1.HorizontalPodAutoscalerStatus{
4534 CurrentReplicas: 4,
4535 DesiredReplicas: 5,
4536 },
4537 },
4538 },
4539 {
4540 "with target no current",
4541 autoscalingv1.HorizontalPodAutoscaler{
4542 Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
4543 ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
4544 Name: "some-rc",
4545 Kind: "ReplicationController",
4546 },
4547 MinReplicas: &minReplicasVal,
4548 MaxReplicas: 10,
4549 TargetCPUUtilizationPercentage: &targetUtilizationVal,
4550 },
4551 Status: autoscalingv1.HorizontalPodAutoscalerStatus{
4552 CurrentReplicas: 4,
4553 DesiredReplicas: 5,
4554 },
4555 },
4556 },
4557 {
4558 "with target and current",
4559 autoscalingv1.HorizontalPodAutoscaler{
4560 Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
4561 ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
4562 Name: "some-rc",
4563 Kind: "ReplicationController",
4564 },
4565 MinReplicas: &minReplicasVal,
4566 MaxReplicas: 10,
4567 TargetCPUUtilizationPercentage: &targetUtilizationVal,
4568 },
4569 Status: autoscalingv1.HorizontalPodAutoscalerStatus{
4570 CurrentReplicas: 4,
4571 DesiredReplicas: 5,
4572 CurrentCPUUtilizationPercentage: ¤tUtilizationVal,
4573 },
4574 },
4575 },
4576 }
4577
4578 for _, test := range testsV1 {
4579 t.Run(test.name, func(t *testing.T) {
4580 test.hpa.ObjectMeta = metav1.ObjectMeta{
4581 Name: "bar",
4582 Namespace: "foo",
4583 }
4584 fake := fake.NewSimpleClientset(&test.hpa)
4585 desc := HorizontalPodAutoscalerDescriber{fake}
4586 str, err := desc.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
4587 if err != nil {
4588 t.Errorf("Unexpected error for test %s: %v", test.name, err)
4589 }
4590 if str == "" {
4591 t.Errorf("Unexpected empty string for test %s. Expected HPA Describer output", test.name)
4592 }
4593 t.Logf("Description for %q:\n%s", test.name, str)
4594 })
4595 }
4596 }
4597
4598 func TestDescribeEvents(t *testing.T) {
4599
4600 events := &corev1.EventList{
4601 Items: []corev1.Event{
4602 {
4603 ObjectMeta: metav1.ObjectMeta{
4604 Name: "event-1",
4605 Namespace: "foo",
4606 },
4607 Source: corev1.EventSource{Component: "kubelet"},
4608 Message: "Item 1",
4609 FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
4610 LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
4611 Count: 1,
4612 Type: corev1.EventTypeNormal,
4613 },
4614 {
4615 ObjectMeta: metav1.ObjectMeta{
4616 Name: "event-2",
4617 Namespace: "foo",
4618 },
4619 Source: corev1.EventSource{Component: "kubelet"},
4620 Message: "Item 1",
4621 EventTime: metav1.NewMicroTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
4622 Series: &corev1.EventSeries{
4623 Count: 1,
4624 LastObservedTime: metav1.NewMicroTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
4625 },
4626 Type: corev1.EventTypeNormal,
4627 },
4628 },
4629 }
4630
4631 m := map[string]ResourceDescriber{
4632 "DaemonSetDescriber": &DaemonSetDescriber{
4633 fake.NewSimpleClientset(&appsv1.DaemonSet{
4634 ObjectMeta: metav1.ObjectMeta{
4635 Name: "bar",
4636 Namespace: "foo",
4637 },
4638 }, events),
4639 },
4640 "DeploymentDescriber": &DeploymentDescriber{
4641 fake.NewSimpleClientset(&appsv1.Deployment{
4642 ObjectMeta: metav1.ObjectMeta{
4643 Name: "bar",
4644 Namespace: "foo",
4645 },
4646 Spec: appsv1.DeploymentSpec{
4647 Replicas: utilpointer.Int32Ptr(1),
4648 Selector: &metav1.LabelSelector{},
4649 },
4650 }, events),
4651 },
4652 "EndpointsDescriber": &EndpointsDescriber{
4653 fake.NewSimpleClientset(&corev1.Endpoints{
4654 ObjectMeta: metav1.ObjectMeta{
4655 Name: "bar",
4656 Namespace: "foo",
4657 },
4658 }, events),
4659 },
4660 "EndpointSliceDescriber": &EndpointSliceDescriber{
4661 fake.NewSimpleClientset(&discoveryv1beta1.EndpointSlice{
4662 ObjectMeta: metav1.ObjectMeta{
4663 Name: "bar",
4664 Namespace: "foo",
4665 },
4666 }, events),
4667 },
4668 "JobDescriber": &JobDescriber{
4669 fake.NewSimpleClientset(&batchv1.Job{
4670 ObjectMeta: metav1.ObjectMeta{
4671 Name: "bar",
4672 Namespace: "foo",
4673 },
4674 }, events),
4675 },
4676 "IngressDescriber": &IngressDescriber{
4677 fake.NewSimpleClientset(&networkingv1beta1.Ingress{
4678 ObjectMeta: metav1.ObjectMeta{
4679 Name: "bar",
4680 Namespace: "foo",
4681 },
4682 }, events),
4683 },
4684 "NodeDescriber": &NodeDescriber{
4685 fake.NewSimpleClientset(&corev1.Node{
4686 ObjectMeta: metav1.ObjectMeta{
4687 Name: "bar",
4688 },
4689 }, events),
4690 },
4691 "PersistentVolumeDescriber": &PersistentVolumeDescriber{
4692 fake.NewSimpleClientset(&corev1.PersistentVolume{
4693 ObjectMeta: metav1.ObjectMeta{
4694 Name: "bar",
4695 },
4696 }, events),
4697 },
4698 "PodDescriber": &PodDescriber{
4699 fake.NewSimpleClientset(&corev1.Pod{
4700 ObjectMeta: metav1.ObjectMeta{
4701 Name: "bar",
4702 Namespace: "foo",
4703 },
4704 }, events),
4705 },
4706 "ReplicaSetDescriber": &ReplicaSetDescriber{
4707 fake.NewSimpleClientset(&appsv1.ReplicaSet{
4708 ObjectMeta: metav1.ObjectMeta{
4709 Name: "bar",
4710 Namespace: "foo",
4711 },
4712 Spec: appsv1.ReplicaSetSpec{
4713 Replicas: utilpointer.Int32Ptr(1),
4714 },
4715 }, events),
4716 },
4717 "ReplicationControllerDescriber": &ReplicationControllerDescriber{
4718 fake.NewSimpleClientset(&corev1.ReplicationController{
4719 ObjectMeta: metav1.ObjectMeta{
4720 Name: "bar",
4721 Namespace: "foo",
4722 },
4723 Spec: corev1.ReplicationControllerSpec{
4724 Replicas: utilpointer.Int32Ptr(1),
4725 },
4726 }, events),
4727 },
4728 "Service": &ServiceDescriber{
4729 fake.NewSimpleClientset(&corev1.Service{
4730 ObjectMeta: metav1.ObjectMeta{
4731 Name: "bar",
4732 Namespace: "foo",
4733 },
4734 }, events),
4735 },
4736 "StorageClass": &StorageClassDescriber{
4737 fake.NewSimpleClientset(&storagev1.StorageClass{
4738 ObjectMeta: metav1.ObjectMeta{
4739 Name: "bar",
4740 },
4741 }, events),
4742 },
4743 "HorizontalPodAutoscaler": &HorizontalPodAutoscalerDescriber{
4744 fake.NewSimpleClientset(&autoscalingv2.HorizontalPodAutoscaler{
4745 ObjectMeta: metav1.ObjectMeta{
4746 Name: "bar",
4747 Namespace: "foo",
4748 },
4749 }, events),
4750 },
4751 "ConfigMap": &ConfigMapDescriber{
4752 fake.NewSimpleClientset(&corev1.ConfigMap{
4753 ObjectMeta: metav1.ObjectMeta{
4754 Name: "bar",
4755 Namespace: "foo",
4756 },
4757 }, events),
4758 },
4759 }
4760
4761 for name, d := range m {
4762 t.Run(name, func(t *testing.T) {
4763 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
4764 if err != nil {
4765 t.Errorf("unexpected error for %q: %v", name, err)
4766 }
4767 if !strings.Contains(out, "bar") {
4768 t.Errorf("unexpected out for %q: %s", name, out)
4769 }
4770 if !strings.Contains(out, "Events:") {
4771 t.Errorf("events not found for %q when ShowEvents=true: %s", name, out)
4772 }
4773
4774 out, err = d.Describe("foo", "bar", DescriberSettings{ShowEvents: false})
4775 if err != nil {
4776 t.Errorf("unexpected error for %q: %s", name, err)
4777 }
4778 if !strings.Contains(out, "bar") {
4779 t.Errorf("unexpected out for %q: %s", name, out)
4780 }
4781 if strings.Contains(out, "Events:") {
4782 t.Errorf("events found for %q when ShowEvents=false: %s", name, out)
4783 }
4784 })
4785 }
4786 }
4787
4788 func TestPrintLabelsMultiline(t *testing.T) {
4789 key := "MaxLenAnnotation"
4790 value := strings.Repeat("a", maxAnnotationLen-len(key)-2)
4791 testCases := []struct {
4792 annotations map[string]string
4793 expectPrint string
4794 }{
4795 {
4796 annotations: map[string]string{"col1": "asd", "COL2": "zxc"},
4797 expectPrint: "Annotations:\tCOL2: zxc\n\tcol1: asd\n",
4798 },
4799 {
4800 annotations: map[string]string{"MaxLenAnnotation": value},
4801 expectPrint: fmt.Sprintf("Annotations:\t%s: %s\n", key, value),
4802 },
4803 {
4804 annotations: map[string]string{"MaxLenAnnotation": value + "1"},
4805 expectPrint: fmt.Sprintf("Annotations:\t%s:\n\t %s\n", key, value+"1"),
4806 },
4807 {
4808 annotations: map[string]string{"MaxLenAnnotation": value + value},
4809 expectPrint: fmt.Sprintf("Annotations:\t%s:\n\t %s\n", key, strings.Repeat("a", maxAnnotationLen-2)+"..."),
4810 },
4811 {
4812 annotations: map[string]string{"key": "value\nwith\nnewlines\n"},
4813 expectPrint: "Annotations:\tkey:\n\t value\n\t with\n\t newlines\n",
4814 },
4815 {
4816 annotations: map[string]string{},
4817 expectPrint: "Annotations:\t<none>\n",
4818 },
4819 }
4820 for i, testCase := range testCases {
4821 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
4822 out := new(bytes.Buffer)
4823 writer := NewPrefixWriter(out)
4824 printAnnotationsMultiline(writer, "Annotations", testCase.annotations)
4825 output := out.String()
4826 if output != testCase.expectPrint {
4827 t.Errorf("Test case %d: expected to match:\n%q\nin output:\n%q", i, testCase.expectPrint, output)
4828 }
4829 })
4830 }
4831 }
4832
4833 func TestDescribeUnstructuredContent(t *testing.T) {
4834 testCases := []struct {
4835 expected string
4836 unexpected string
4837 }{
4838 {
4839 expected: `API Version: v1
4840 Dummy - Dummy: present
4841 dummy-dummy@dummy: present
4842 dummy/dummy: present
4843 dummy2: present
4844 Dummy Dummy: present
4845 Items:
4846 Item Bool: true
4847 Item Int: 42
4848 Kind: Test
4849 Metadata:
4850 Creation Timestamp: 2017-04-01T00:00:00Z
4851 Name: MyName
4852 Namespace: MyNamespace
4853 Resource Version: 123
4854 UID: 00000000-0000-0000-0000-000000000001
4855 Status: ok
4856 URL: http://localhost
4857 `,
4858 },
4859 {
4860 unexpected: "\nDummy 1:\tpresent\n",
4861 },
4862 {
4863 unexpected: "Dummy 1",
4864 },
4865 {
4866 unexpected: "Dummy 3",
4867 },
4868 {
4869 unexpected: "Dummy3",
4870 },
4871 {
4872 unexpected: "dummy3",
4873 },
4874 {
4875 unexpected: "dummy 3",
4876 },
4877 }
4878 out := new(bytes.Buffer)
4879 w := NewPrefixWriter(out)
4880 obj := &unstructured.Unstructured{
4881 Object: map[string]interface{}{
4882 "apiVersion": "v1",
4883 "kind": "Test",
4884 "dummyDummy": "present",
4885 "dummy/dummy": "present",
4886 "dummy-dummy@dummy": "present",
4887 "dummy-dummy": "present",
4888 "dummy1": "present",
4889 "dummy2": "present",
4890 "metadata": map[string]interface{}{
4891 "name": "MyName",
4892 "namespace": "MyNamespace",
4893 "creationTimestamp": "2017-04-01T00:00:00Z",
4894 "resourceVersion": 123,
4895 "uid": "00000000-0000-0000-0000-000000000001",
4896 "dummy3": "present",
4897 },
4898 "items": []interface{}{
4899 map[string]interface{}{
4900 "itemBool": true,
4901 "itemInt": 42,
4902 },
4903 },
4904 "url": "http://localhost",
4905 "status": "ok",
4906 },
4907 }
4908 printUnstructuredContent(w, LEVEL_0, obj.UnstructuredContent(), "", ".dummy1", ".metadata.dummy3")
4909 output := out.String()
4910
4911 for _, test := range testCases {
4912 if len(test.expected) > 0 {
4913 if !strings.Contains(output, test.expected) {
4914 t.Errorf("Expected to find %q in: %q", test.expected, output)
4915 }
4916 }
4917 if len(test.unexpected) > 0 {
4918 if strings.Contains(output, test.unexpected) {
4919 t.Errorf("Didn't expect to find %q in: %q", test.unexpected, output)
4920 }
4921 }
4922 }
4923 }
4924
4925 func TestDescribeResourceQuota(t *testing.T) {
4926 fake := fake.NewSimpleClientset(&corev1.ResourceQuota{
4927 ObjectMeta: metav1.ObjectMeta{
4928 Name: "bar",
4929 Namespace: "foo",
4930 },
4931 Status: corev1.ResourceQuotaStatus{
4932 Hard: corev1.ResourceList{
4933 corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("1"),
4934 corev1.ResourceName(corev1.ResourceLimitsCPU): resource.MustParse("2"),
4935 corev1.ResourceName(corev1.ResourceLimitsMemory): resource.MustParse("2G"),
4936 corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("1G"),
4937 corev1.ResourceName(corev1.ResourceRequestsCPU): resource.MustParse("1"),
4938 corev1.ResourceName(corev1.ResourceRequestsMemory): resource.MustParse("1G"),
4939 },
4940 Used: corev1.ResourceList{
4941 corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("0"),
4942 corev1.ResourceName(corev1.ResourceLimitsCPU): resource.MustParse("0"),
4943 corev1.ResourceName(corev1.ResourceLimitsMemory): resource.MustParse("0G"),
4944 corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("0G"),
4945 corev1.ResourceName(corev1.ResourceRequestsCPU): resource.MustParse("0"),
4946 corev1.ResourceName(corev1.ResourceRequestsMemory): resource.MustParse("1000Ki"),
4947 },
4948 },
4949 })
4950 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
4951 d := ResourceQuotaDescriber{c}
4952 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
4953 if err != nil {
4954 t.Errorf("unexpected error: %v", err)
4955 }
4956 expectedOut := []string{"bar", "foo", "limits.cpu", "2", "limits.memory", "2G", "requests.cpu", "1", "requests.memory", "1024k", "1G"}
4957 for _, expected := range expectedOut {
4958 if !strings.Contains(out, expected) {
4959 t.Errorf("expected to find %q in output: %q", expected, out)
4960 }
4961 }
4962 }
4963
4964 func TestDescribeIngressClass(t *testing.T) {
4965 expectedOut := `Name: example-class
4966 Labels: <none>
4967 Annotations: <none>
4968 Controller: example.com/controller
4969 Parameters:
4970 APIGroup: v1
4971 Kind: ConfigMap
4972 Name: example-parameters` + "\n"
4973
4974 tests := map[string]struct {
4975 input *fake.Clientset
4976 output string
4977 }{
4978 "basic IngressClass (v1beta1)": {
4979 input: fake.NewSimpleClientset(&networkingv1beta1.IngressClass{
4980 ObjectMeta: metav1.ObjectMeta{
4981 Name: "example-class",
4982 },
4983 Spec: networkingv1beta1.IngressClassSpec{
4984 Controller: "example.com/controller",
4985 Parameters: &networkingv1beta1.IngressClassParametersReference{
4986 APIGroup: utilpointer.StringPtr("v1"),
4987 Kind: "ConfigMap",
4988 Name: "example-parameters",
4989 },
4990 },
4991 }),
4992 output: expectedOut,
4993 },
4994 "basic IngressClass (v1)": {
4995 input: fake.NewSimpleClientset(&networkingv1.IngressClass{
4996 ObjectMeta: metav1.ObjectMeta{
4997 Name: "example-class",
4998 },
4999 Spec: networkingv1.IngressClassSpec{
5000 Controller: "example.com/controller",
5001 Parameters: &networkingv1.IngressClassParametersReference{
5002 APIGroup: utilpointer.StringPtr("v1"),
5003 Kind: "ConfigMap",
5004 Name: "example-parameters",
5005 },
5006 },
5007 }),
5008 output: expectedOut,
5009 },
5010 }
5011
5012 for name, test := range tests {
5013 t.Run(name, func(t *testing.T) {
5014 c := &describeClient{T: t, Namespace: "foo", Interface: test.input}
5015 d := IngressClassDescriber{c}
5016 out, err := d.Describe("", "example-class", DescriberSettings{})
5017 if err != nil {
5018 t.Errorf("unexpected error: %v", err)
5019 }
5020 if out != expectedOut {
5021 t.Logf(out)
5022 t.Errorf("expected : %q\n but got output:\n %q", test.output, out)
5023 }
5024 })
5025 }
5026 }
5027
5028 func TestDescribeNetworkPolicies(t *testing.T) {
5029 expectedTime, err := time.Parse("2006-01-02 15:04:05 Z0700 MST", "2017-06-04 21:45:56 -0700 PDT")
5030 if err != nil {
5031 t.Errorf("unable to parse time %q error: %s", "2017-06-04 21:45:56 -0700 PDT", err)
5032 }
5033 expectedOut := `Name: network-policy-1
5034 Namespace: default
5035 Created on: 2017-06-04 21:45:56 -0700 PDT
5036 Labels: <none>
5037 Annotations: <none>
5038 Spec:
5039 PodSelector: foo in (bar1,bar2),foo2 notin (bar1,bar2),id1=app1,id2=app2
5040 Allowing ingress traffic:
5041 To Port: 80/TCP
5042 To Port: 82/TCP
5043 From:
5044 NamespaceSelector: id=ns1,id2=ns2
5045 PodSelector: id=pod1,id2=pod2
5046 From:
5047 PodSelector: id=app2,id2=app3
5048 From:
5049 NamespaceSelector: id=app2,id2=app3
5050 From:
5051 NamespaceSelector: foo in (bar1,bar2),id=app2,id2=app3
5052 From:
5053 IPBlock:
5054 CIDR: 192.168.0.0/16
5055 Except: 192.168.3.0/24, 192.168.4.0/24
5056 ----------
5057 To Port: <any> (traffic allowed to all ports)
5058 From: <any> (traffic not restricted by source)
5059 Allowing egress traffic:
5060 To Port: 80/TCP
5061 To Port: 82/TCP
5062 To:
5063 NamespaceSelector: id=ns1,id2=ns2
5064 PodSelector: id=pod1,id2=pod2
5065 To:
5066 PodSelector: id=app2,id2=app3
5067 To:
5068 NamespaceSelector: id=app2,id2=app3
5069 To:
5070 NamespaceSelector: foo in (bar1,bar2),id=app2,id2=app3
5071 To:
5072 IPBlock:
5073 CIDR: 192.168.0.0/16
5074 Except: 192.168.3.0/24, 192.168.4.0/24
5075 ----------
5076 To Port: <any> (traffic allowed to all ports)
5077 To: <any> (traffic not restricted by destination)
5078 Policy Types: Ingress, Egress
5079 `
5080
5081 port80 := intstr.FromInt32(80)
5082 port82 := intstr.FromInt32(82)
5083 protoTCP := corev1.ProtocolTCP
5084
5085 versionedFake := fake.NewSimpleClientset(&networkingv1.NetworkPolicy{
5086 ObjectMeta: metav1.ObjectMeta{
5087 Name: "network-policy-1",
5088 Namespace: "default",
5089 CreationTimestamp: metav1.NewTime(expectedTime),
5090 },
5091 Spec: networkingv1.NetworkPolicySpec{
5092 PodSelector: metav1.LabelSelector{
5093 MatchLabels: map[string]string{
5094 "id1": "app1",
5095 "id2": "app2",
5096 },
5097 MatchExpressions: []metav1.LabelSelectorRequirement{
5098 {Key: "foo", Operator: "In", Values: []string{"bar1", "bar2"}},
5099 {Key: "foo2", Operator: "NotIn", Values: []string{"bar1", "bar2"}},
5100 },
5101 },
5102 Ingress: []networkingv1.NetworkPolicyIngressRule{
5103 {
5104 Ports: []networkingv1.NetworkPolicyPort{
5105 {Port: &port80},
5106 {Port: &port82, Protocol: &protoTCP},
5107 },
5108 From: []networkingv1.NetworkPolicyPeer{
5109 {
5110 PodSelector: &metav1.LabelSelector{
5111 MatchLabels: map[string]string{
5112 "id": "pod1",
5113 "id2": "pod2",
5114 },
5115 },
5116 NamespaceSelector: &metav1.LabelSelector{
5117 MatchLabels: map[string]string{
5118 "id": "ns1",
5119 "id2": "ns2",
5120 },
5121 },
5122 },
5123 {
5124 PodSelector: &metav1.LabelSelector{
5125 MatchLabels: map[string]string{
5126 "id": "app2",
5127 "id2": "app3",
5128 },
5129 },
5130 },
5131 {
5132 NamespaceSelector: &metav1.LabelSelector{
5133 MatchLabels: map[string]string{
5134 "id": "app2",
5135 "id2": "app3",
5136 },
5137 },
5138 },
5139 {
5140 NamespaceSelector: &metav1.LabelSelector{
5141 MatchLabels: map[string]string{
5142 "id": "app2",
5143 "id2": "app3",
5144 },
5145 MatchExpressions: []metav1.LabelSelectorRequirement{
5146 {Key: "foo", Operator: "In", Values: []string{"bar1", "bar2"}},
5147 },
5148 },
5149 },
5150 {
5151 IPBlock: &networkingv1.IPBlock{
5152 CIDR: "192.168.0.0/16",
5153 Except: []string{"192.168.3.0/24", "192.168.4.0/24"},
5154 },
5155 },
5156 },
5157 },
5158 {},
5159 },
5160 Egress: []networkingv1.NetworkPolicyEgressRule{
5161 {
5162 Ports: []networkingv1.NetworkPolicyPort{
5163 {Port: &port80},
5164 {Port: &port82, Protocol: &protoTCP},
5165 },
5166 To: []networkingv1.NetworkPolicyPeer{
5167 {
5168 PodSelector: &metav1.LabelSelector{
5169 MatchLabels: map[string]string{
5170 "id": "pod1",
5171 "id2": "pod2",
5172 },
5173 },
5174 NamespaceSelector: &metav1.LabelSelector{
5175 MatchLabels: map[string]string{
5176 "id": "ns1",
5177 "id2": "ns2",
5178 },
5179 },
5180 },
5181 {
5182 PodSelector: &metav1.LabelSelector{
5183 MatchLabels: map[string]string{
5184 "id": "app2",
5185 "id2": "app3",
5186 },
5187 },
5188 },
5189 {
5190 NamespaceSelector: &metav1.LabelSelector{
5191 MatchLabels: map[string]string{
5192 "id": "app2",
5193 "id2": "app3",
5194 },
5195 },
5196 },
5197 {
5198 NamespaceSelector: &metav1.LabelSelector{
5199 MatchLabels: map[string]string{
5200 "id": "app2",
5201 "id2": "app3",
5202 },
5203 MatchExpressions: []metav1.LabelSelectorRequirement{
5204 {Key: "foo", Operator: "In", Values: []string{"bar1", "bar2"}},
5205 },
5206 },
5207 },
5208 {
5209 IPBlock: &networkingv1.IPBlock{
5210 CIDR: "192.168.0.0/16",
5211 Except: []string{"192.168.3.0/24", "192.168.4.0/24"},
5212 },
5213 },
5214 },
5215 },
5216 {},
5217 },
5218 PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress, networkingv1.PolicyTypeEgress},
5219 },
5220 })
5221 d := NetworkPolicyDescriber{versionedFake}
5222 out, err := d.Describe("default", "network-policy-1", DescriberSettings{})
5223 if err != nil {
5224 t.Errorf("unexpected error: %s", err)
5225 }
5226 if out != expectedOut {
5227 t.Errorf("want:\n%s\ngot:\n%s", expectedOut, out)
5228 }
5229 }
5230
5231 func TestDescribeIngressNetworkPolicies(t *testing.T) {
5232 expectedTime, err := time.Parse("2006-01-02 15:04:05 Z0700 MST", "2017-06-04 21:45:56 -0700 PDT")
5233 if err != nil {
5234 t.Errorf("unable to parse time %q error: %s", "2017-06-04 21:45:56 -0700 PDT", err)
5235 }
5236 expectedOut := `Name: network-policy-1
5237 Namespace: default
5238 Created on: 2017-06-04 21:45:56 -0700 PDT
5239 Labels: <none>
5240 Annotations: <none>
5241 Spec:
5242 PodSelector: foo in (bar1,bar2),foo2 notin (bar1,bar2),id1=app1,id2=app2
5243 Allowing ingress traffic:
5244 To Port: 80/TCP
5245 To Port: 82/TCP
5246 From:
5247 NamespaceSelector: id=ns1,id2=ns2
5248 PodSelector: id=pod1,id2=pod2
5249 From:
5250 PodSelector: id=app2,id2=app3
5251 From:
5252 NamespaceSelector: id=app2,id2=app3
5253 From:
5254 NamespaceSelector: foo in (bar1,bar2),id=app2,id2=app3
5255 From:
5256 IPBlock:
5257 CIDR: 192.168.0.0/16
5258 Except: 192.168.3.0/24, 192.168.4.0/24
5259 ----------
5260 To Port: <any> (traffic allowed to all ports)
5261 From: <any> (traffic not restricted by source)
5262 Not affecting egress traffic
5263 Policy Types: Ingress
5264 `
5265
5266 port80 := intstr.FromInt32(80)
5267 port82 := intstr.FromInt32(82)
5268 protoTCP := corev1.ProtocolTCP
5269
5270 versionedFake := fake.NewSimpleClientset(&networkingv1.NetworkPolicy{
5271 ObjectMeta: metav1.ObjectMeta{
5272 Name: "network-policy-1",
5273 Namespace: "default",
5274 CreationTimestamp: metav1.NewTime(expectedTime),
5275 },
5276 Spec: networkingv1.NetworkPolicySpec{
5277 PodSelector: metav1.LabelSelector{
5278 MatchLabels: map[string]string{
5279 "id1": "app1",
5280 "id2": "app2",
5281 },
5282 MatchExpressions: []metav1.LabelSelectorRequirement{
5283 {Key: "foo", Operator: "In", Values: []string{"bar1", "bar2"}},
5284 {Key: "foo2", Operator: "NotIn", Values: []string{"bar1", "bar2"}},
5285 },
5286 },
5287 Ingress: []networkingv1.NetworkPolicyIngressRule{
5288 {
5289 Ports: []networkingv1.NetworkPolicyPort{
5290 {Port: &port80},
5291 {Port: &port82, Protocol: &protoTCP},
5292 },
5293 From: []networkingv1.NetworkPolicyPeer{
5294 {
5295 PodSelector: &metav1.LabelSelector{
5296 MatchLabels: map[string]string{
5297 "id": "pod1",
5298 "id2": "pod2",
5299 },
5300 },
5301 NamespaceSelector: &metav1.LabelSelector{
5302 MatchLabels: map[string]string{
5303 "id": "ns1",
5304 "id2": "ns2",
5305 },
5306 },
5307 },
5308 {
5309 PodSelector: &metav1.LabelSelector{
5310 MatchLabels: map[string]string{
5311 "id": "app2",
5312 "id2": "app3",
5313 },
5314 },
5315 },
5316 {
5317 NamespaceSelector: &metav1.LabelSelector{
5318 MatchLabels: map[string]string{
5319 "id": "app2",
5320 "id2": "app3",
5321 },
5322 },
5323 },
5324 {
5325 NamespaceSelector: &metav1.LabelSelector{
5326 MatchLabels: map[string]string{
5327 "id": "app2",
5328 "id2": "app3",
5329 },
5330 MatchExpressions: []metav1.LabelSelectorRequirement{
5331 {Key: "foo", Operator: "In", Values: []string{"bar1", "bar2"}},
5332 },
5333 },
5334 },
5335 {
5336 IPBlock: &networkingv1.IPBlock{
5337 CIDR: "192.168.0.0/16",
5338 Except: []string{"192.168.3.0/24", "192.168.4.0/24"},
5339 },
5340 },
5341 },
5342 },
5343 {},
5344 },
5345 PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress},
5346 },
5347 })
5348 d := NetworkPolicyDescriber{versionedFake}
5349 out, err := d.Describe("default", "network-policy-1", DescriberSettings{})
5350 if err != nil {
5351 t.Errorf("unexpected error: %s", err)
5352 }
5353 if out != expectedOut {
5354 t.Errorf("want:\n%s\ngot:\n%s", expectedOut, out)
5355 }
5356 }
5357
5358 func TestDescribeIsolatedEgressNetworkPolicies(t *testing.T) {
5359 expectedTime, err := time.Parse("2006-01-02 15:04:05 Z0700 MST", "2017-06-04 21:45:56 -0700 PDT")
5360 if err != nil {
5361 t.Errorf("unable to parse time %q error: %s", "2017-06-04 21:45:56 -0700 PDT", err)
5362 }
5363 expectedOut := `Name: network-policy-1
5364 Namespace: default
5365 Created on: 2017-06-04 21:45:56 -0700 PDT
5366 Labels: <none>
5367 Annotations: <none>
5368 Spec:
5369 PodSelector: foo in (bar1,bar2),foo2 notin (bar1,bar2),id1=app1,id2=app2
5370 Allowing ingress traffic:
5371 To Port: 80/TCP
5372 To Port: 82/TCP
5373 From:
5374 NamespaceSelector: id=ns1,id2=ns2
5375 PodSelector: id=pod1,id2=pod2
5376 From:
5377 PodSelector: id=app2,id2=app3
5378 From:
5379 NamespaceSelector: id=app2,id2=app3
5380 From:
5381 NamespaceSelector: foo in (bar1,bar2),id=app2,id2=app3
5382 From:
5383 IPBlock:
5384 CIDR: 192.168.0.0/16
5385 Except: 192.168.3.0/24, 192.168.4.0/24
5386 ----------
5387 To Port: <any> (traffic allowed to all ports)
5388 From: <any> (traffic not restricted by source)
5389 Allowing egress traffic:
5390 <none> (Selected pods are isolated for egress connectivity)
5391 Policy Types: Ingress, Egress
5392 `
5393
5394 port80 := intstr.FromInt32(80)
5395 port82 := intstr.FromInt32(82)
5396 protoTCP := corev1.ProtocolTCP
5397
5398 versionedFake := fake.NewSimpleClientset(&networkingv1.NetworkPolicy{
5399 ObjectMeta: metav1.ObjectMeta{
5400 Name: "network-policy-1",
5401 Namespace: "default",
5402 CreationTimestamp: metav1.NewTime(expectedTime),
5403 },
5404 Spec: networkingv1.NetworkPolicySpec{
5405 PodSelector: metav1.LabelSelector{
5406 MatchLabels: map[string]string{
5407 "id1": "app1",
5408 "id2": "app2",
5409 },
5410 MatchExpressions: []metav1.LabelSelectorRequirement{
5411 {Key: "foo", Operator: "In", Values: []string{"bar1", "bar2"}},
5412 {Key: "foo2", Operator: "NotIn", Values: []string{"bar1", "bar2"}},
5413 },
5414 },
5415 Ingress: []networkingv1.NetworkPolicyIngressRule{
5416 {
5417 Ports: []networkingv1.NetworkPolicyPort{
5418 {Port: &port80},
5419 {Port: &port82, Protocol: &protoTCP},
5420 },
5421 From: []networkingv1.NetworkPolicyPeer{
5422 {
5423 PodSelector: &metav1.LabelSelector{
5424 MatchLabels: map[string]string{
5425 "id": "pod1",
5426 "id2": "pod2",
5427 },
5428 },
5429 NamespaceSelector: &metav1.LabelSelector{
5430 MatchLabels: map[string]string{
5431 "id": "ns1",
5432 "id2": "ns2",
5433 },
5434 },
5435 },
5436 {
5437 PodSelector: &metav1.LabelSelector{
5438 MatchLabels: map[string]string{
5439 "id": "app2",
5440 "id2": "app3",
5441 },
5442 },
5443 },
5444 {
5445 NamespaceSelector: &metav1.LabelSelector{
5446 MatchLabels: map[string]string{
5447 "id": "app2",
5448 "id2": "app3",
5449 },
5450 },
5451 },
5452 {
5453 NamespaceSelector: &metav1.LabelSelector{
5454 MatchLabels: map[string]string{
5455 "id": "app2",
5456 "id2": "app3",
5457 },
5458 MatchExpressions: []metav1.LabelSelectorRequirement{
5459 {Key: "foo", Operator: "In", Values: []string{"bar1", "bar2"}},
5460 },
5461 },
5462 },
5463 {
5464 IPBlock: &networkingv1.IPBlock{
5465 CIDR: "192.168.0.0/16",
5466 Except: []string{"192.168.3.0/24", "192.168.4.0/24"},
5467 },
5468 },
5469 },
5470 },
5471 {},
5472 },
5473 PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress, networkingv1.PolicyTypeEgress},
5474 },
5475 })
5476 d := NetworkPolicyDescriber{versionedFake}
5477 out, err := d.Describe("default", "network-policy-1", DescriberSettings{})
5478 if err != nil {
5479 t.Errorf("unexpected error: %s", err)
5480 }
5481 if out != expectedOut {
5482 t.Errorf("want:\n%s\ngot:\n%s", expectedOut, out)
5483 }
5484 }
5485
5486 func TestDescribeServiceAccount(t *testing.T) {
5487 fake := fake.NewSimpleClientset(&corev1.ServiceAccount{
5488 ObjectMeta: metav1.ObjectMeta{
5489 Name: "bar",
5490 Namespace: "foo",
5491 },
5492 Secrets: []corev1.ObjectReference{
5493 {
5494 Name: "test-objectref",
5495 },
5496 },
5497 ImagePullSecrets: []corev1.LocalObjectReference{
5498 {
5499 Name: "test-local-ref",
5500 },
5501 },
5502 })
5503 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
5504 d := ServiceAccountDescriber{c}
5505 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
5506 if err != nil {
5507 t.Errorf("unexpected error: %v", err)
5508 }
5509 expectedOut := `Name: bar
5510 Namespace: foo
5511 Labels: <none>
5512 Annotations: <none>
5513 Image pull secrets: test-local-ref (not found)
5514 Mountable secrets: test-objectref (not found)
5515 Tokens: <none>
5516 Events: <none>` + "\n"
5517 if out != expectedOut {
5518 t.Errorf("expected : %q\n but got output:\n %q", expectedOut, out)
5519 }
5520
5521 }
5522 func getHugePageResourceList(pageSize, value string) corev1.ResourceList {
5523 res := corev1.ResourceList{}
5524 if pageSize != "" && value != "" {
5525 res[corev1.ResourceName(corev1.ResourceHugePagesPrefix+pageSize)] = resource.MustParse(value)
5526 }
5527 return res
5528 }
5529
5530
5531
5532 func mergeResourceLists(resourceLists ...corev1.ResourceList) corev1.ResourceList {
5533 result := corev1.ResourceList{}
5534 for _, rl := range resourceLists {
5535 for resource, quantity := range rl {
5536 result[resource] = quantity
5537 }
5538 }
5539 return result
5540 }
5541
5542 func TestDescribeNode(t *testing.T) {
5543 holderIdentity := "holder"
5544 nodeCapacity := mergeResourceLists(
5545 getHugePageResourceList("2Mi", "4Gi"),
5546 getResourceList("8", "24Gi"),
5547 getHugePageResourceList("1Gi", "0"),
5548 )
5549 nodeAllocatable := mergeResourceLists(
5550 getHugePageResourceList("2Mi", "2Gi"),
5551 getResourceList("4", "12Gi"),
5552 getHugePageResourceList("1Gi", "0"),
5553 )
5554
5555 fake := fake.NewSimpleClientset(
5556 &corev1.Node{
5557 ObjectMeta: metav1.ObjectMeta{
5558 Name: "bar",
5559 UID: "uid",
5560 },
5561 Spec: corev1.NodeSpec{
5562 Unschedulable: true,
5563 },
5564 Status: corev1.NodeStatus{
5565 Capacity: nodeCapacity,
5566 Allocatable: nodeAllocatable,
5567 },
5568 },
5569 &coordinationv1.Lease{
5570 ObjectMeta: metav1.ObjectMeta{
5571 Name: "bar",
5572 Namespace: corev1.NamespaceNodeLease,
5573 },
5574 Spec: coordinationv1.LeaseSpec{
5575 HolderIdentity: &holderIdentity,
5576 AcquireTime: &metav1.MicroTime{Time: time.Now().Add(-time.Hour)},
5577 RenewTime: &metav1.MicroTime{Time: time.Now()},
5578 },
5579 },
5580 &corev1.Pod{
5581 ObjectMeta: metav1.ObjectMeta{
5582 Name: "pod-with-resources",
5583 Namespace: "foo",
5584 },
5585 TypeMeta: metav1.TypeMeta{
5586 Kind: "Pod",
5587 },
5588 Spec: corev1.PodSpec{
5589 Containers: []corev1.Container{
5590 {
5591 Name: "cpu-mem",
5592 Image: "image:latest",
5593 Resources: corev1.ResourceRequirements{
5594 Requests: getResourceList("1", "1Gi"),
5595 Limits: getResourceList("2", "2Gi"),
5596 },
5597 },
5598 {
5599 Name: "hugepages",
5600 Image: "image:latest",
5601 Resources: corev1.ResourceRequirements{
5602 Requests: getHugePageResourceList("2Mi", "512Mi"),
5603 Limits: getHugePageResourceList("2Mi", "512Mi"),
5604 },
5605 },
5606 },
5607 },
5608 Status: corev1.PodStatus{
5609 Phase: corev1.PodRunning,
5610 },
5611 },
5612 &corev1.EventList{
5613 Items: []corev1.Event{
5614 {
5615 ObjectMeta: metav1.ObjectMeta{
5616 Name: "event-1",
5617 Namespace: "default",
5618 },
5619 InvolvedObject: corev1.ObjectReference{
5620 Kind: "Node",
5621 Name: "bar",
5622 UID: "bar",
5623 },
5624 Message: "Node bar status is now: NodeHasNoDiskPressure",
5625 FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
5626 LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
5627 Count: 1,
5628 Type: corev1.EventTypeNormal,
5629 },
5630 {
5631 ObjectMeta: metav1.ObjectMeta{
5632 Name: "event-2",
5633 Namespace: "default",
5634 },
5635 InvolvedObject: corev1.ObjectReference{
5636 Kind: "Node",
5637 Name: "bar",
5638 UID: "0ceac5fb-a393-49d7-b04f-9ea5f18de5e9",
5639 },
5640 Message: "Node bar status is now: NodeReady",
5641 FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
5642 LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
5643 Count: 2,
5644 Type: corev1.EventTypeNormal,
5645 },
5646 },
5647 },
5648 )
5649 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
5650 d := NodeDescriber{c}
5651 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
5652 if err != nil {
5653 t.Errorf("unexpected error: %v", err)
5654 }
5655
5656 expectedOut := []string{"Unschedulable", "true", "holder",
5657 `Allocated resources:
5658 (Total limits may be over 100 percent, i.e., overcommitted.)
5659 Resource Requests Limits
5660 -------- -------- ------
5661 cpu 1 (25%) 2 (50%)
5662 memory 1Gi (8%) 2Gi (16%)
5663 ephemeral-storage 0 (0%) 0 (0%)
5664 hugepages-1Gi 0 (0%) 0 (0%)
5665 hugepages-2Mi 512Mi (25%) 512Mi (25%)`,
5666 `Node bar status is now: NodeHasNoDiskPressure`,
5667 `Node bar status is now: NodeReady`}
5668 for _, expected := range expectedOut {
5669 if !strings.Contains(out, expected) {
5670 t.Errorf("expected to find %q in output: %q", expected, out)
5671 }
5672 }
5673 }
5674
5675 func TestDescribeNodeWithSidecar(t *testing.T) {
5676 holderIdentity := "holder"
5677 nodeCapacity := mergeResourceLists(
5678 getHugePageResourceList("2Mi", "4Gi"),
5679 getResourceList("8", "24Gi"),
5680 getHugePageResourceList("1Gi", "0"),
5681 )
5682 nodeAllocatable := mergeResourceLists(
5683 getHugePageResourceList("2Mi", "2Gi"),
5684 getResourceList("4", "12Gi"),
5685 getHugePageResourceList("1Gi", "0"),
5686 )
5687
5688 restartPolicy := corev1.ContainerRestartPolicyAlways
5689 fake := fake.NewSimpleClientset(
5690 &corev1.Node{
5691 ObjectMeta: metav1.ObjectMeta{
5692 Name: "bar",
5693 UID: "uid",
5694 },
5695 Spec: corev1.NodeSpec{
5696 Unschedulable: true,
5697 },
5698 Status: corev1.NodeStatus{
5699 Capacity: nodeCapacity,
5700 Allocatable: nodeAllocatable,
5701 },
5702 },
5703 &coordinationv1.Lease{
5704 ObjectMeta: metav1.ObjectMeta{
5705 Name: "bar",
5706 Namespace: corev1.NamespaceNodeLease,
5707 },
5708 Spec: coordinationv1.LeaseSpec{
5709 HolderIdentity: &holderIdentity,
5710 AcquireTime: &metav1.MicroTime{Time: time.Now().Add(-time.Hour)},
5711 RenewTime: &metav1.MicroTime{Time: time.Now()},
5712 },
5713 },
5714 &corev1.Pod{
5715 ObjectMeta: metav1.ObjectMeta{
5716 Name: "pod-with-resources",
5717 Namespace: "foo",
5718 },
5719 TypeMeta: metav1.TypeMeta{
5720 Kind: "Pod",
5721 },
5722 Spec: corev1.PodSpec{
5723 InitContainers: []corev1.Container{
5724
5725 {
5726 Name: "init-container-1",
5727 RestartPolicy: &restartPolicy,
5728 Resources: corev1.ResourceRequirements{
5729 Requests: getResourceList("1", "1Gi"),
5730 },
5731 },
5732
5733 {
5734 Name: "init-container-2",
5735 Resources: corev1.ResourceRequirements{
5736 Requests: getResourceList("1", "1Gi"),
5737 },
5738 },
5739 },
5740 Containers: []corev1.Container{
5741 {
5742 Name: "cpu-mem",
5743 Image: "image:latest",
5744 Resources: corev1.ResourceRequirements{
5745 Requests: getResourceList("1", "1Gi"),
5746 Limits: getResourceList("2", "2Gi"),
5747 },
5748 },
5749 {
5750 Name: "hugepages",
5751 Image: "image:latest",
5752 Resources: corev1.ResourceRequirements{
5753 Requests: getHugePageResourceList("2Mi", "512Mi"),
5754 Limits: getHugePageResourceList("2Mi", "512Mi"),
5755 },
5756 },
5757 },
5758 },
5759 Status: corev1.PodStatus{
5760 Phase: corev1.PodRunning,
5761 },
5762 },
5763 &corev1.EventList{
5764 Items: []corev1.Event{
5765 {
5766 ObjectMeta: metav1.ObjectMeta{
5767 Name: "event-1",
5768 Namespace: "default",
5769 },
5770 InvolvedObject: corev1.ObjectReference{
5771 Kind: "Node",
5772 Name: "bar",
5773 UID: "bar",
5774 },
5775 Message: "Node bar status is now: NodeHasNoDiskPressure",
5776 FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
5777 LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
5778 Count: 1,
5779 Type: corev1.EventTypeNormal,
5780 },
5781 {
5782 ObjectMeta: metav1.ObjectMeta{
5783 Name: "event-2",
5784 Namespace: "default",
5785 },
5786 InvolvedObject: corev1.ObjectReference{
5787 Kind: "Node",
5788 Name: "bar",
5789 UID: "0ceac5fb-a393-49d7-b04f-9ea5f18de5e9",
5790 },
5791 Message: "Node bar status is now: NodeReady",
5792 FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
5793 LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
5794 Count: 2,
5795 Type: corev1.EventTypeNormal,
5796 },
5797 },
5798 },
5799 )
5800 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
5801 d := NodeDescriber{c}
5802 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
5803 if err != nil {
5804 t.Errorf("unexpected error: %v", err)
5805 }
5806
5807 expectedOut := []string{"Unschedulable", "true", "holder",
5808 `Allocated resources:
5809 (Total limits may be over 100 percent, i.e., overcommitted.)
5810 Resource Requests Limits
5811 -------- -------- ------
5812 cpu 2 (50%) 2 (50%)
5813 memory 2Gi (16%) 2Gi (16%)
5814 ephemeral-storage 0 (0%) 0 (0%)
5815 hugepages-1Gi 0 (0%) 0 (0%)
5816 hugepages-2Mi 512Mi (25%) 512Mi (25%)`,
5817 `Node bar status is now: NodeHasNoDiskPressure`,
5818 `Node bar status is now: NodeReady`}
5819 for _, expected := range expectedOut {
5820 if !strings.Contains(out, expected) {
5821 t.Errorf("expected to find %s in output: %s", expected, out)
5822 }
5823 }
5824 }
5825 func TestDescribeStatefulSet(t *testing.T) {
5826 var partition int32 = 2672
5827 var replicas int32 = 1
5828 fake := fake.NewSimpleClientset(&appsv1.StatefulSet{
5829 ObjectMeta: metav1.ObjectMeta{
5830 Name: "bar",
5831 Namespace: "foo",
5832 },
5833 Spec: appsv1.StatefulSetSpec{
5834 Replicas: &replicas,
5835 Selector: &metav1.LabelSelector{},
5836 Template: corev1.PodTemplateSpec{
5837 Spec: corev1.PodSpec{
5838 Containers: []corev1.Container{
5839 {Image: "mytest-image:latest"},
5840 },
5841 },
5842 },
5843 UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
5844 Type: appsv1.RollingUpdateStatefulSetStrategyType,
5845 RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
5846 Partition: &partition,
5847 },
5848 },
5849 },
5850 })
5851 d := StatefulSetDescriber{fake}
5852 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: true})
5853 if err != nil {
5854 t.Errorf("unexpected error: %v", err)
5855 }
5856 expectedOutputs := []string{
5857 "bar", "foo", "Containers:", "mytest-image:latest", "Update Strategy", "RollingUpdate", "Partition", "2672",
5858 }
5859 for _, o := range expectedOutputs {
5860 if !strings.Contains(out, o) {
5861 t.Errorf("unexpected out: %s", out)
5862 break
5863 }
5864 }
5865 }
5866
5867 func TestDescribeEndpointSlice(t *testing.T) {
5868 protocolTCP := corev1.ProtocolTCP
5869 port80 := int32(80)
5870
5871 testcases := map[string]struct {
5872 input *fake.Clientset
5873 output string
5874 }{
5875 "EndpointSlices v1beta1": {
5876 input: fake.NewSimpleClientset(&discoveryv1beta1.EndpointSlice{
5877 ObjectMeta: metav1.ObjectMeta{
5878 Name: "foo.123",
5879 Namespace: "bar",
5880 },
5881 AddressType: discoveryv1beta1.AddressTypeIPv4,
5882 Endpoints: []discoveryv1beta1.Endpoint{
5883 {
5884 Addresses: []string{"1.2.3.4", "1.2.3.5"},
5885 Conditions: discoveryv1beta1.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
5886 TargetRef: &corev1.ObjectReference{Kind: "Pod", Name: "test-123"},
5887 Topology: map[string]string{
5888 "topology.kubernetes.io/zone": "us-central1-a",
5889 "topology.kubernetes.io/region": "us-central1",
5890 },
5891 }, {
5892 Addresses: []string{"1.2.3.6", "1.2.3.7"},
5893 Conditions: discoveryv1beta1.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
5894 TargetRef: &corev1.ObjectReference{Kind: "Pod", Name: "test-124"},
5895 Topology: map[string]string{
5896 "topology.kubernetes.io/zone": "us-central1-b",
5897 "topology.kubernetes.io/region": "us-central1",
5898 },
5899 },
5900 },
5901 Ports: []discoveryv1beta1.EndpointPort{
5902 {
5903 Protocol: &protocolTCP,
5904 Port: &port80,
5905 },
5906 },
5907 }),
5908
5909 output: `Name: foo.123
5910 Namespace: bar
5911 Labels: <none>
5912 Annotations: <none>
5913 AddressType: IPv4
5914 Ports:
5915 Name Port Protocol
5916 ---- ---- --------
5917 <unset> 80 TCP
5918 Endpoints:
5919 - Addresses: 1.2.3.4,1.2.3.5
5920 Conditions:
5921 Ready: true
5922 Hostname: <unset>
5923 TargetRef: Pod/test-123
5924 Topology: topology.kubernetes.io/region=us-central1
5925 topology.kubernetes.io/zone=us-central1-a
5926 - Addresses: 1.2.3.6,1.2.3.7
5927 Conditions:
5928 Ready: true
5929 Hostname: <unset>
5930 TargetRef: Pod/test-124
5931 Topology: topology.kubernetes.io/region=us-central1
5932 topology.kubernetes.io/zone=us-central1-b
5933 Events: <none>` + "\n",
5934 },
5935 "EndpointSlices v1": {
5936 input: fake.NewSimpleClientset(&discoveryv1.EndpointSlice{
5937 ObjectMeta: metav1.ObjectMeta{
5938 Name: "foo.123",
5939 Namespace: "bar",
5940 },
5941 AddressType: discoveryv1.AddressTypeIPv4,
5942 Endpoints: []discoveryv1.Endpoint{
5943 {
5944 Addresses: []string{"1.2.3.4", "1.2.3.5"},
5945 Conditions: discoveryv1.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
5946 TargetRef: &corev1.ObjectReference{Kind: "Pod", Name: "test-123"},
5947 Zone: utilpointer.StringPtr("us-central1-a"),
5948 NodeName: utilpointer.StringPtr("node-1"),
5949 }, {
5950 Addresses: []string{"1.2.3.6", "1.2.3.7"},
5951 Conditions: discoveryv1.EndpointConditions{Ready: utilpointer.BoolPtr(true)},
5952 TargetRef: &corev1.ObjectReference{Kind: "Pod", Name: "test-124"},
5953 NodeName: utilpointer.StringPtr("node-2"),
5954 },
5955 },
5956 Ports: []discoveryv1.EndpointPort{
5957 {
5958 Protocol: &protocolTCP,
5959 Port: &port80,
5960 },
5961 },
5962 }),
5963
5964 output: `Name: foo.123
5965 Namespace: bar
5966 Labels: <none>
5967 Annotations: <none>
5968 AddressType: IPv4
5969 Ports:
5970 Name Port Protocol
5971 ---- ---- --------
5972 <unset> 80 TCP
5973 Endpoints:
5974 - Addresses: 1.2.3.4, 1.2.3.5
5975 Conditions:
5976 Ready: true
5977 Hostname: <unset>
5978 TargetRef: Pod/test-123
5979 NodeName: node-1
5980 Zone: us-central1-a
5981 - Addresses: 1.2.3.6, 1.2.3.7
5982 Conditions:
5983 Ready: true
5984 Hostname: <unset>
5985 TargetRef: Pod/test-124
5986 NodeName: node-2
5987 Zone: <unset>
5988 Events: <none>` + "\n",
5989 },
5990 }
5991
5992 for name, tc := range testcases {
5993 t.Run(name, func(t *testing.T) {
5994 c := &describeClient{T: t, Namespace: "foo", Interface: tc.input}
5995 d := EndpointSliceDescriber{c}
5996 out, err := d.Describe("bar", "foo.123", DescriberSettings{ShowEvents: true})
5997 if err != nil {
5998 t.Errorf("unexpected error: %v", err)
5999 }
6000 if out != tc.output {
6001 t.Logf(out)
6002 t.Errorf("expected :\n%s\nbut got output:\n%s", tc.output, out)
6003 }
6004 })
6005 }
6006 }
6007
6008 func TestDescribeServiceCIDR(t *testing.T) {
6009
6010 testcases := map[string]struct {
6011 input *fake.Clientset
6012 output string
6013 }{
6014 "ServiceCIDR v1alpha1": {
6015 input: fake.NewSimpleClientset(&networkingv1alpha1.ServiceCIDR{
6016 ObjectMeta: metav1.ObjectMeta{
6017 Name: "foo.123",
6018 },
6019 Spec: networkingv1alpha1.ServiceCIDRSpec{
6020 CIDRs: []string{"10.1.0.0/16", "fd00:1:1::/64"},
6021 },
6022 }),
6023
6024 output: `Name: foo.123
6025 Labels: <none>
6026 Annotations: <none>
6027 CIDRs: 10.1.0.0/16, fd00:1:1::/64
6028 Events: <none>` + "\n",
6029 },
6030 "ServiceCIDR v1alpha1 IPv4": {
6031 input: fake.NewSimpleClientset(&networkingv1alpha1.ServiceCIDR{
6032 ObjectMeta: metav1.ObjectMeta{
6033 Name: "foo.123",
6034 },
6035 Spec: networkingv1alpha1.ServiceCIDRSpec{
6036 CIDRs: []string{"10.1.0.0/16"},
6037 },
6038 }),
6039
6040 output: `Name: foo.123
6041 Labels: <none>
6042 Annotations: <none>
6043 CIDRs: 10.1.0.0/16
6044 Events: <none>` + "\n",
6045 },
6046 "ServiceCIDR v1alpha1 IPv6": {
6047 input: fake.NewSimpleClientset(&networkingv1alpha1.ServiceCIDR{
6048 ObjectMeta: metav1.ObjectMeta{
6049 Name: "foo.123",
6050 },
6051 Spec: networkingv1alpha1.ServiceCIDRSpec{
6052 CIDRs: []string{"fd00:1:1::/64"},
6053 },
6054 }),
6055
6056 output: `Name: foo.123
6057 Labels: <none>
6058 Annotations: <none>
6059 CIDRs: fd00:1:1::/64
6060 Events: <none>` + "\n",
6061 },
6062 }
6063
6064 for name, tc := range testcases {
6065 t.Run(name, func(t *testing.T) {
6066 c := &describeClient{T: t, Namespace: "foo", Interface: tc.input}
6067 d := ServiceCIDRDescriber{c}
6068 out, err := d.Describe("bar", "foo.123", DescriberSettings{ShowEvents: true})
6069 if err != nil {
6070 t.Errorf("unexpected error: %v", err)
6071 }
6072 if out != tc.output {
6073 t.Errorf("expected :\n%s\nbut got output:\n%s diff:\n%s", tc.output, out, cmp.Diff(tc.output, out))
6074 }
6075 })
6076 }
6077 }
6078
6079 func TestDescribeIPAddress(t *testing.T) {
6080
6081 testcases := map[string]struct {
6082 input *fake.Clientset
6083 output string
6084 }{
6085 "IPAddress v1alpha1": {
6086 input: fake.NewSimpleClientset(&networkingv1alpha1.IPAddress{
6087 ObjectMeta: metav1.ObjectMeta{
6088 Name: "foo.123",
6089 },
6090 Spec: networkingv1alpha1.IPAddressSpec{
6091 ParentRef: &networkingv1alpha1.ParentReference{
6092 Group: "mygroup",
6093 Resource: "myresource",
6094 Namespace: "mynamespace",
6095 Name: "myname",
6096 },
6097 },
6098 }),
6099
6100 output: `Name: foo.123
6101 Labels: <none>
6102 Annotations: <none>
6103 Parent Reference:
6104 Group: mygroup
6105 Resource: myresource
6106 Namespace: mynamespace
6107 Name: myname
6108 Events: <none>` + "\n",
6109 },
6110 }
6111
6112 for name, tc := range testcases {
6113 t.Run(name, func(t *testing.T) {
6114 c := &describeClient{T: t, Namespace: "foo", Interface: tc.input}
6115 d := IPAddressDescriber{c}
6116 out, err := d.Describe("bar", "foo.123", DescriberSettings{ShowEvents: true})
6117 if err != nil {
6118 t.Errorf("unexpected error: %v", err)
6119 }
6120 if out != tc.output {
6121 t.Errorf("expected :\n%s\nbut got output:\n%s diff:\n%s", tc.output, out, cmp.Diff(tc.output, out))
6122 }
6123 })
6124 }
6125 }
6126
6127 func TestControllerRef(t *testing.T) {
6128 var replicas int32 = 1
6129 f := fake.NewSimpleClientset(
6130 &corev1.ReplicationController{
6131 ObjectMeta: metav1.ObjectMeta{
6132 Name: "bar",
6133 Namespace: "foo",
6134 UID: "123456",
6135 },
6136 TypeMeta: metav1.TypeMeta{
6137 Kind: "ReplicationController",
6138 },
6139 Spec: corev1.ReplicationControllerSpec{
6140 Replicas: &replicas,
6141 Selector: map[string]string{"abc": "xyz"},
6142 Template: &corev1.PodTemplateSpec{
6143 Spec: corev1.PodSpec{
6144 Containers: []corev1.Container{
6145 {Image: "mytest-image:latest"},
6146 },
6147 },
6148 },
6149 },
6150 },
6151 &corev1.Pod{
6152 ObjectMeta: metav1.ObjectMeta{
6153 Name: "barpod",
6154 Namespace: "foo",
6155 Labels: map[string]string{"abc": "xyz"},
6156 OwnerReferences: []metav1.OwnerReference{{Name: "bar", UID: "123456", Controller: utilpointer.BoolPtr(true)}},
6157 },
6158 TypeMeta: metav1.TypeMeta{
6159 Kind: "Pod",
6160 },
6161 Spec: corev1.PodSpec{
6162 Containers: []corev1.Container{
6163 {Image: "mytest-image:latest"},
6164 },
6165 },
6166 Status: corev1.PodStatus{
6167 Phase: corev1.PodRunning,
6168 },
6169 },
6170 &corev1.Pod{
6171 ObjectMeta: metav1.ObjectMeta{
6172 Name: "orphan",
6173 Namespace: "foo",
6174 Labels: map[string]string{"abc": "xyz"},
6175 },
6176 TypeMeta: metav1.TypeMeta{
6177 Kind: "Pod",
6178 },
6179 Spec: corev1.PodSpec{
6180 Containers: []corev1.Container{
6181 {Image: "mytest-image:latest"},
6182 },
6183 },
6184 Status: corev1.PodStatus{
6185 Phase: corev1.PodRunning,
6186 },
6187 },
6188 &corev1.Pod{
6189 ObjectMeta: metav1.ObjectMeta{
6190 Name: "buzpod",
6191 Namespace: "foo",
6192 Labels: map[string]string{"abc": "xyz"},
6193 OwnerReferences: []metav1.OwnerReference{{Name: "buz", UID: "654321", Controller: utilpointer.BoolPtr(true)}},
6194 },
6195 TypeMeta: metav1.TypeMeta{
6196 Kind: "Pod",
6197 },
6198 Spec: corev1.PodSpec{
6199 Containers: []corev1.Container{
6200 {Image: "mytest-image:latest"},
6201 },
6202 },
6203 Status: corev1.PodStatus{
6204 Phase: corev1.PodRunning,
6205 },
6206 })
6207 d := ReplicationControllerDescriber{f}
6208 out, err := d.Describe("foo", "bar", DescriberSettings{ShowEvents: false})
6209 if err != nil {
6210 t.Errorf("unexpected error: %v", err)
6211 }
6212 if !strings.Contains(out, "1 Running") {
6213 t.Errorf("unexpected out: %s", out)
6214 }
6215 }
6216
6217 func TestDescribeTerminalEscape(t *testing.T) {
6218 fake := fake.NewSimpleClientset(&corev1.ConfigMap{
6219 ObjectMeta: metav1.ObjectMeta{
6220 Name: "mycm",
6221 Namespace: "foo",
6222 Annotations: map[string]string{"annotation1": "terminal escape: \x1b"},
6223 },
6224 })
6225 c := &describeClient{T: t, Namespace: "foo", Interface: fake}
6226 d := ConfigMapDescriber{c}
6227 out, err := d.Describe("foo", "mycm", DescriberSettings{ShowEvents: true})
6228 if err != nil {
6229 t.Errorf("unexpected error: %v", err)
6230 }
6231 if strings.Contains(out, "\x1b") || !strings.Contains(out, "^[") {
6232 t.Errorf("unexpected out: %s", out)
6233 }
6234 }
6235
6236 func TestDescribeSeccompProfile(t *testing.T) {
6237 testLocalhostProfiles := []string{"lauseafoodpod", "tikkamasalaconatiner", "dropshotephemeral"}
6238
6239 testCases := []struct {
6240 name string
6241 pod *corev1.Pod
6242 expect []string
6243 }{
6244 {
6245 name: "podLocalhostSeccomp",
6246 pod: &corev1.Pod{
6247 Spec: corev1.PodSpec{
6248 SecurityContext: &corev1.PodSecurityContext{
6249 SeccompProfile: &corev1.SeccompProfile{
6250 Type: corev1.SeccompProfileTypeLocalhost,
6251 LocalhostProfile: &testLocalhostProfiles[0],
6252 },
6253 },
6254 },
6255 },
6256 expect: []string{
6257 "SeccompProfile", "Localhost",
6258 "LocalhostProfile", testLocalhostProfiles[0],
6259 },
6260 },
6261 {
6262 name: "podOther",
6263 pod: &corev1.Pod{
6264 Spec: corev1.PodSpec{
6265 SecurityContext: &corev1.PodSecurityContext{
6266 SeccompProfile: &corev1.SeccompProfile{
6267 Type: corev1.SeccompProfileTypeRuntimeDefault,
6268 },
6269 },
6270 },
6271 },
6272 expect: []string{
6273 "SeccompProfile", "RuntimeDefault",
6274 },
6275 },
6276 {
6277 name: "containerLocalhostSeccomp",
6278 pod: &corev1.Pod{
6279 Spec: corev1.PodSpec{
6280 Containers: []corev1.Container{
6281 {
6282 SecurityContext: &corev1.SecurityContext{
6283 SeccompProfile: &corev1.SeccompProfile{
6284 Type: corev1.SeccompProfileTypeLocalhost,
6285 LocalhostProfile: &testLocalhostProfiles[1],
6286 },
6287 },
6288 },
6289 },
6290 },
6291 },
6292 expect: []string{
6293 "SeccompProfile", "Localhost",
6294 "LocalhostProfile", testLocalhostProfiles[1],
6295 },
6296 },
6297 {
6298 name: "containerOther",
6299 pod: &corev1.Pod{
6300 Spec: corev1.PodSpec{
6301 Containers: []corev1.Container{
6302 {
6303 SecurityContext: &corev1.SecurityContext{
6304 SeccompProfile: &corev1.SeccompProfile{
6305 Type: corev1.SeccompProfileTypeUnconfined,
6306 },
6307 },
6308 },
6309 },
6310 },
6311 },
6312 expect: []string{
6313 "SeccompProfile", "Unconfined",
6314 },
6315 },
6316 {
6317 name: "ephemeralLocalhostSeccomp",
6318 pod: &corev1.Pod{
6319 Spec: corev1.PodSpec{
6320 EphemeralContainers: []corev1.EphemeralContainer{
6321 {
6322 EphemeralContainerCommon: corev1.EphemeralContainerCommon{
6323 SecurityContext: &corev1.SecurityContext{
6324 SeccompProfile: &corev1.SeccompProfile{
6325 Type: corev1.SeccompProfileTypeLocalhost,
6326 LocalhostProfile: &testLocalhostProfiles[2],
6327 },
6328 },
6329 },
6330 },
6331 },
6332 },
6333 },
6334 expect: []string{
6335 "SeccompProfile", "Localhost",
6336 "LocalhostProfile", testLocalhostProfiles[2],
6337 },
6338 },
6339 {
6340 name: "ephemeralOther",
6341 pod: &corev1.Pod{
6342 Spec: corev1.PodSpec{
6343 Containers: []corev1.Container{
6344 {
6345 SecurityContext: &corev1.SecurityContext{
6346 SeccompProfile: &corev1.SeccompProfile{
6347 Type: corev1.SeccompProfileTypeUnconfined,
6348 },
6349 },
6350 },
6351 },
6352 },
6353 },
6354 expect: []string{
6355 "SeccompProfile", "Unconfined",
6356 },
6357 },
6358 }
6359 for _, testCase := range testCases {
6360 t.Run(testCase.name, func(t *testing.T) {
6361 fake := fake.NewSimpleClientset(testCase.pod)
6362 c := &describeClient{T: t, Interface: fake}
6363 d := PodDescriber{c}
6364 out, err := d.Describe("", "", DescriberSettings{ShowEvents: true})
6365 if err != nil {
6366 t.Errorf("unexpected error: %v", err)
6367 }
6368 for _, expected := range testCase.expect {
6369 if !strings.Contains(out, expected) {
6370 t.Errorf("expected to find %q in output: %q", expected, out)
6371 }
6372 }
6373 })
6374 }
6375 }
6376
View as plain text