Update status check endpoint 
[multicloud/k8s.git] / src / k8splugin / internal / statuscheck / ready_test.go
1 /*
2 Copyright The Helm Authors.
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8     http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 package statuscheck // import "helm.sh/helm/v3/pkg/kube"
18
19 import (
20         "context"
21         "testing"
22
23         appsv1 "k8s.io/api/apps/v1"
24         batchv1 "k8s.io/api/batch/v1"
25         corev1 "k8s.io/api/core/v1"
26         metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27         "k8s.io/apimachinery/pkg/runtime"
28         "k8s.io/apimachinery/pkg/util/intstr"
29         "k8s.io/client-go/kubernetes/fake"
30 )
31
32 const defaultNamespace = metav1.NamespaceDefault
33
34 func Test_ReadyChecker_deploymentReady(t *testing.T) {
35         type args struct {
36                 rs  *appsv1.ReplicaSet
37                 dep *appsv1.Deployment
38         }
39         tests := []struct {
40                 name string
41                 args args
42                 want bool
43         }{
44                 {
45                         name: "deployment is ready",
46                         args: args{
47                                 rs:  newReplicaSet("foo", 1, 1),
48                                 dep: newDeployment("foo", 1, 1, 0),
49                         },
50                         want: true,
51                 },
52                 {
53                         name: "deployment is not ready",
54                         args: args{
55                                 rs:  newReplicaSet("foo", 0, 0),
56                                 dep: newDeployment("foo", 1, 1, 0),
57                         },
58                         want: false,
59                 },
60                 {
61                         name: "deployment is ready when maxUnavailable is set",
62                         args: args{
63                                 rs:  newReplicaSet("foo", 2, 1),
64                                 dep: newDeployment("foo", 2, 1, 1),
65                         },
66                         want: true,
67                 },
68         }
69         for _, tt := range tests {
70                 t.Run(tt.name, func(t *testing.T) {
71                         c := NewReadyChecker(fake.NewSimpleClientset(), PausedAsReady(false), CheckJobs(false))
72                         if got := c.deploymentReady(tt.args.rs, tt.args.dep); got != tt.want {
73                                 t.Errorf("deploymentReady() = %v, want %v", got, tt.want)
74                         }
75                 })
76         }
77 }
78
79 func Test_ReadyChecker_daemonSetReady(t *testing.T) {
80         type args struct {
81                 ds *appsv1.DaemonSet
82         }
83         tests := []struct {
84                 name string
85                 args args
86                 want bool
87         }{
88                 {
89                         name: "daemonset is ready",
90                         args: args{
91                                 ds: newDaemonSet("foo", 0, 1, 1, 1),
92                         },
93                         want: true,
94                 },
95                 {
96                         name: "daemonset is not ready",
97                         args: args{
98                                 ds: newDaemonSet("foo", 0, 0, 1, 1),
99                         },
100                         want: false,
101                 },
102                 {
103                         name: "daemonset pods have not been scheduled successfully",
104                         args: args{
105                                 ds: newDaemonSet("foo", 0, 0, 1, 0),
106                         },
107                         want: false,
108                 },
109                 {
110                         name: "daemonset is ready when maxUnavailable is set",
111                         args: args{
112                                 ds: newDaemonSet("foo", 1, 1, 2, 2),
113                         },
114                         want: true,
115                 },
116         }
117         for _, tt := range tests {
118                 t.Run(tt.name, func(t *testing.T) {
119                         c := NewReadyChecker(fake.NewSimpleClientset(), PausedAsReady(false), CheckJobs(false))
120                         if got := c.daemonSetReady(tt.args.ds); got != tt.want {
121                                 t.Errorf("daemonSetReady() = %v, want %v", got, tt.want)
122                         }
123                 })
124         }
125 }
126
127 func Test_ReadyChecker_statefulSetReady(t *testing.T) {
128         type args struct {
129                 sts *appsv1.StatefulSet
130         }
131         tests := []struct {
132                 name string
133                 args args
134                 want bool
135         }{
136                 {
137                         name: "statefulset is ready",
138                         args: args{
139                                 sts: newStatefulSet("foo", 1, 0, 1, 1),
140                         },
141                         want: true,
142                 },
143                 {
144                         name: "statefulset is not ready",
145                         args: args{
146                                 sts: newStatefulSet("foo", 1, 0, 0, 1),
147                         },
148                         want: false,
149                 },
150                 {
151                         name: "statefulset is ready when partition is specified",
152                         args: args{
153                                 sts: newStatefulSet("foo", 2, 1, 2, 1),
154                         },
155                         want: true,
156                 },
157                 {
158                         name: "statefulset is not ready when partition is set",
159                         args: args{
160                                 sts: newStatefulSet("foo", 1, 1, 1, 1),
161                         },
162                         want: false,
163                 },
164         }
165         for _, tt := range tests {
166                 t.Run(tt.name, func(t *testing.T) {
167                         c := NewReadyChecker(fake.NewSimpleClientset(), PausedAsReady(false), CheckJobs(false))
168                         if got := c.statefulSetReady(tt.args.sts); got != tt.want {
169                                 t.Errorf("statefulSetReady() = %v, want %v", got, tt.want)
170                         }
171                 })
172         }
173 }
174
175 func Test_ReadyChecker_podsReadyForObject(t *testing.T) {
176         type args struct {
177                 namespace string
178                 obj       runtime.Object
179         }
180         tests := []struct {
181                 name      string
182                 args      args
183                 existPods []corev1.Pod
184                 want      bool
185                 wantErr   bool
186         }{
187                 {
188                         name: "pods ready for a replicaset",
189                         args: args{
190                                 namespace: defaultNamespace,
191                                 obj:       newReplicaSet("foo", 1, 1),
192                         },
193                         existPods: []corev1.Pod{
194                                 *newPodWithCondition("foo", corev1.ConditionTrue),
195                         },
196                         want:    true,
197                         wantErr: false,
198                 },
199                 {
200                         name: "pods not ready for a replicaset",
201                         args: args{
202                                 namespace: defaultNamespace,
203                                 obj:       newReplicaSet("foo", 1, 1),
204                         },
205                         existPods: []corev1.Pod{
206                                 *newPodWithCondition("foo", corev1.ConditionFalse),
207                         },
208                         want:    false,
209                         wantErr: false,
210                 },
211         }
212         for _, tt := range tests {
213                 t.Run(tt.name, func(t *testing.T) {
214                         c := NewReadyChecker(fake.NewSimpleClientset(), PausedAsReady(false), CheckJobs(false))
215                         for _, pod := range tt.existPods {
216                                 if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(context.TODO(), &pod, metav1.CreateOptions{}); err != nil {
217                                         t.Errorf("Failed to create Pod error: %v", err)
218                                         return
219                                 }
220                         }
221                         got, err := c.podsReadyForObject(context.TODO(), tt.args.namespace, tt.args.obj)
222                         if (err != nil) != tt.wantErr {
223                                 t.Errorf("podsReadyForObject() error = %v, wantErr %v", err, tt.wantErr)
224                                 return
225                         }
226                         if got != tt.want {
227                                 t.Errorf("podsReadyForObject() got = %v, want %v", got, tt.want)
228                         }
229                 })
230         }
231 }
232
233 func Test_ReadyChecker_jobReady(t *testing.T) {
234         type args struct {
235                 job *batchv1.Job
236         }
237         tests := []struct {
238                 name string
239                 args args
240                 want bool
241         }{
242                 {
243                         name: "job is completed",
244                         args: args{job: newJob("foo", 1, 1, 1, 0)},
245                         want: true,
246                 },
247                 {
248                         name: "job is incomplete",
249                         args: args{job: newJob("foo", 1, 1, 0, 0)},
250                         want: false,
251                 },
252                 {
253                         name: "job is failed",
254                         args: args{job: newJob("foo", 1, 1, 0, 1)},
255                         want: false,
256                 },
257                 {
258                         name: "job is completed with retry",
259                         args: args{job: newJob("foo", 1, 1, 1, 1)},
260                         want: true,
261                 },
262                 {
263                         name: "job is failed with retry",
264                         args: args{job: newJob("foo", 1, 1, 0, 2)},
265                         want: false,
266                 },
267                 {
268                         name: "job is completed single run",
269                         args: args{job: newJob("foo", 0, 1, 1, 0)},
270                         want: true,
271                 },
272                 {
273                         name: "job is failed single run",
274                         args: args{job: newJob("foo", 0, 1, 0, 1)},
275                         want: false,
276                 },
277         }
278         for _, tt := range tests {
279                 t.Run(tt.name, func(t *testing.T) {
280                         c := NewReadyChecker(fake.NewSimpleClientset(), PausedAsReady(false), CheckJobs(false))
281                         if got := c.jobReady(tt.args.job); got != tt.want {
282                                 t.Errorf("jobReady() = %v, want %v", got, tt.want)
283                         }
284                 })
285         }
286 }
287
288 func Test_ReadyChecker_volumeReady(t *testing.T) {
289         type args struct {
290                 v *corev1.PersistentVolumeClaim
291         }
292         tests := []struct {
293                 name string
294                 args args
295                 want bool
296         }{
297                 {
298                         name: "pvc is bound",
299                         args: args{
300                                 v: newPersistentVolumeClaim("foo", corev1.ClaimBound),
301                         },
302                         want: true,
303                 },
304                 {
305                         name: "pvc is not ready",
306                         args: args{
307                                 v: newPersistentVolumeClaim("foo", corev1.ClaimPending),
308                         },
309                         want: false,
310                 },
311         }
312         for _, tt := range tests {
313                 t.Run(tt.name, func(t *testing.T) {
314                         c := NewReadyChecker(fake.NewSimpleClientset(), PausedAsReady(false), CheckJobs(false))
315                         if got := c.volumeReady(tt.args.v); got != tt.want {
316                                 t.Errorf("volumeReady() = %v, want %v", got, tt.want)
317                         }
318                 })
319         }
320 }
321
322 func newDaemonSet(name string, maxUnavailable, numberReady, desiredNumberScheduled, updatedNumberScheduled int) *appsv1.DaemonSet {
323         return &appsv1.DaemonSet{
324                 ObjectMeta: metav1.ObjectMeta{
325                         Name:      name,
326                         Namespace: defaultNamespace,
327                 },
328                 Spec: appsv1.DaemonSetSpec{
329                         UpdateStrategy: appsv1.DaemonSetUpdateStrategy{
330                                 Type: appsv1.RollingUpdateDaemonSetStrategyType,
331                                 RollingUpdate: &appsv1.RollingUpdateDaemonSet{
332                                         MaxUnavailable: func() *intstr.IntOrString { i := intstr.FromInt(maxUnavailable); return &i }(),
333                                 },
334                         },
335                         Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}},
336                         Template: corev1.PodTemplateSpec{
337                                 ObjectMeta: metav1.ObjectMeta{
338                                         Name:   name,
339                                         Labels: map[string]string{"name": name},
340                                 },
341                                 Spec: corev1.PodSpec{
342                                         Containers: []corev1.Container{
343                                                 {
344                                                         Image: "nginx",
345                                                 },
346                                         },
347                                 },
348                         },
349                 },
350                 Status: appsv1.DaemonSetStatus{
351                         DesiredNumberScheduled: int32(desiredNumberScheduled),
352                         NumberReady:            int32(numberReady),
353                         UpdatedNumberScheduled: int32(updatedNumberScheduled),
354                 },
355         }
356 }
357
358 func newStatefulSet(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet {
359         return &appsv1.StatefulSet{
360                 ObjectMeta: metav1.ObjectMeta{
361                         Name:      name,
362                         Namespace: defaultNamespace,
363                 },
364                 Spec: appsv1.StatefulSetSpec{
365                         UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
366                                 Type: appsv1.RollingUpdateStatefulSetStrategyType,
367                                 RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
368                                         Partition: intToInt32(partition),
369                                 },
370                         },
371                         Replicas: intToInt32(replicas),
372                         Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}},
373                         Template: corev1.PodTemplateSpec{
374                                 ObjectMeta: metav1.ObjectMeta{
375                                         Name:   name,
376                                         Labels: map[string]string{"name": name},
377                                 },
378                                 Spec: corev1.PodSpec{
379                                         Containers: []corev1.Container{
380                                                 {
381                                                         Image: "nginx",
382                                                 },
383                                         },
384                                 },
385                         },
386                 },
387                 Status: appsv1.StatefulSetStatus{
388                         UpdatedReplicas: int32(updatedReplicas),
389                         ReadyReplicas:   int32(readyReplicas),
390                 },
391         }
392 }
393
394 func newDeployment(name string, replicas, maxSurge, maxUnavailable int) *appsv1.Deployment {
395         return &appsv1.Deployment{
396                 ObjectMeta: metav1.ObjectMeta{
397                         Name:      name,
398                         Namespace: defaultNamespace,
399                 },
400                 Spec: appsv1.DeploymentSpec{
401                         Strategy: appsv1.DeploymentStrategy{
402                                 Type: appsv1.RollingUpdateDeploymentStrategyType,
403                                 RollingUpdate: &appsv1.RollingUpdateDeployment{
404                                         MaxUnavailable: func() *intstr.IntOrString { i := intstr.FromInt(maxUnavailable); return &i }(),
405                                         MaxSurge:       func() *intstr.IntOrString { i := intstr.FromInt(maxSurge); return &i }(),
406                                 },
407                         },
408                         Replicas: intToInt32(replicas),
409                         Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}},
410                         Template: corev1.PodTemplateSpec{
411                                 ObjectMeta: metav1.ObjectMeta{
412                                         Name:   name,
413                                         Labels: map[string]string{"name": name},
414                                 },
415                                 Spec: corev1.PodSpec{
416                                         Containers: []corev1.Container{
417                                                 {
418                                                         Image: "nginx",
419                                                 },
420                                         },
421                                 },
422                         },
423                 },
424         }
425 }
426
427 func newReplicaSet(name string, replicas int, readyReplicas int) *appsv1.ReplicaSet {
428         d := newDeployment(name, replicas, 0, 0)
429         return &appsv1.ReplicaSet{
430                 ObjectMeta: metav1.ObjectMeta{
431                         Name:            name,
432                         Namespace:       defaultNamespace,
433                         Labels:          d.Spec.Selector.MatchLabels,
434                         OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(d, d.GroupVersionKind())},
435                 },
436                 Spec: appsv1.ReplicaSetSpec{
437                         Selector: d.Spec.Selector,
438                         Replicas: intToInt32(replicas),
439                         Template: d.Spec.Template,
440                 },
441                 Status: appsv1.ReplicaSetStatus{
442                         ReadyReplicas: int32(readyReplicas),
443                 },
444         }
445 }
446
447 func newPodWithCondition(name string, podReadyCondition corev1.ConditionStatus) *corev1.Pod {
448         return &corev1.Pod{
449                 ObjectMeta: metav1.ObjectMeta{
450                         Name:      name,
451                         Namespace: defaultNamespace,
452                         Labels:    map[string]string{"name": name},
453                 },
454                 Spec: corev1.PodSpec{
455                         Containers: []corev1.Container{
456                                 {
457                                         Image: "nginx",
458                                 },
459                         },
460                 },
461                 Status: corev1.PodStatus{
462                         Conditions: []corev1.PodCondition{
463                                 {
464                                         Type:   corev1.PodReady,
465                                         Status: podReadyCondition,
466                                 },
467                         },
468                 },
469         }
470 }
471
472 func newPersistentVolumeClaim(name string, phase corev1.PersistentVolumeClaimPhase) *corev1.PersistentVolumeClaim {
473         return &corev1.PersistentVolumeClaim{
474                 ObjectMeta: metav1.ObjectMeta{
475                         Name:      name,
476                         Namespace: defaultNamespace,
477                 },
478                 Status: corev1.PersistentVolumeClaimStatus{
479                         Phase: phase,
480                 },
481         }
482 }
483
484 func newJob(name string, backoffLimit, completions, succeeded, failed int) *batchv1.Job {
485         return &batchv1.Job{
486                 ObjectMeta: metav1.ObjectMeta{
487                         Name:      name,
488                         Namespace: defaultNamespace,
489                 },
490                 Spec: batchv1.JobSpec{
491                         BackoffLimit: intToInt32(backoffLimit),
492                         Completions:  intToInt32(completions),
493                         Template: corev1.PodTemplateSpec{
494                                 ObjectMeta: metav1.ObjectMeta{
495                                         Name:   name,
496                                         Labels: map[string]string{"name": name},
497                                 },
498                                 Spec: corev1.PodSpec{
499                                         Containers: []corev1.Container{
500                                                 {
501                                                         Image: "nginx",
502                                                 },
503                                         },
504                                 },
505                         },
506                 },
507                 Status: batchv1.JobStatus{
508                         Succeeded: int32(succeeded),
509                         Failed:    int32(failed),
510                 },
511         }
512 }
513
514 func intToInt32(i int) *int32 {
515         i32 := int32(i)
516         return &i32
517 }