2 Copyright The Helm Authors.
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
8 http://www.apache.org/licenses/LICENSE-2.0
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.
17 package statuscheck // import "helm.sh/helm/v3/pkg/kube"
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"
32 const defaultNamespace = metav1.NamespaceDefault
34 func Test_ReadyChecker_deploymentReady(t *testing.T) {
37 dep *appsv1.Deployment
45 name: "deployment is ready",
47 rs: newReplicaSet("foo", 1, 1),
48 dep: newDeployment("foo", 1, 1, 0),
53 name: "deployment is not ready",
55 rs: newReplicaSet("foo", 0, 0),
56 dep: newDeployment("foo", 1, 1, 0),
61 name: "deployment is ready when maxUnavailable is set",
63 rs: newReplicaSet("foo", 2, 1),
64 dep: newDeployment("foo", 2, 1, 1),
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)
79 func Test_ReadyChecker_daemonSetReady(t *testing.T) {
89 name: "daemonset is ready",
91 ds: newDaemonSet("foo", 0, 1, 1, 1),
96 name: "daemonset is not ready",
98 ds: newDaemonSet("foo", 0, 0, 1, 1),
103 name: "daemonset pods have not been scheduled successfully",
105 ds: newDaemonSet("foo", 0, 0, 1, 0),
110 name: "daemonset is ready when maxUnavailable is set",
112 ds: newDaemonSet("foo", 1, 1, 2, 2),
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)
127 func Test_ReadyChecker_statefulSetReady(t *testing.T) {
129 sts *appsv1.StatefulSet
137 name: "statefulset is ready",
139 sts: newStatefulSet("foo", 1, 0, 1, 1),
144 name: "statefulset is not ready",
146 sts: newStatefulSet("foo", 1, 0, 0, 1),
151 name: "statefulset is ready when partition is specified",
153 sts: newStatefulSet("foo", 2, 1, 2, 1),
158 name: "statefulset is not ready when partition is set",
160 sts: newStatefulSet("foo", 1, 1, 1, 1),
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)
175 func Test_ReadyChecker_podsReadyForObject(t *testing.T) {
183 existPods []corev1.Pod
188 name: "pods ready for a replicaset",
190 namespace: defaultNamespace,
191 obj: newReplicaSet("foo", 1, 1),
193 existPods: []corev1.Pod{
194 *newPodWithCondition("foo", corev1.ConditionTrue),
200 name: "pods not ready for a replicaset",
202 namespace: defaultNamespace,
203 obj: newReplicaSet("foo", 1, 1),
205 existPods: []corev1.Pod{
206 *newPodWithCondition("foo", corev1.ConditionFalse),
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)
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)
227 t.Errorf("podsReadyForObject() got = %v, want %v", got, tt.want)
233 func Test_ReadyChecker_jobReady(t *testing.T) {
243 name: "job is completed",
244 args: args{job: newJob("foo", 1, 1, 1, 0)},
248 name: "job is incomplete",
249 args: args{job: newJob("foo", 1, 1, 0, 0)},
253 name: "job is failed",
254 args: args{job: newJob("foo", 1, 1, 0, 1)},
258 name: "job is completed with retry",
259 args: args{job: newJob("foo", 1, 1, 1, 1)},
263 name: "job is failed with retry",
264 args: args{job: newJob("foo", 1, 1, 0, 2)},
268 name: "job is completed single run",
269 args: args{job: newJob("foo", 0, 1, 1, 0)},
273 name: "job is failed single run",
274 args: args{job: newJob("foo", 0, 1, 0, 1)},
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)
288 func Test_ReadyChecker_volumeReady(t *testing.T) {
290 v *corev1.PersistentVolumeClaim
298 name: "pvc is bound",
300 v: newPersistentVolumeClaim("foo", corev1.ClaimBound),
305 name: "pvc is not ready",
307 v: newPersistentVolumeClaim("foo", corev1.ClaimPending),
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)
322 func newDaemonSet(name string, maxUnavailable, numberReady, desiredNumberScheduled, updatedNumberScheduled int) *appsv1.DaemonSet {
323 return &appsv1.DaemonSet{
324 ObjectMeta: metav1.ObjectMeta{
326 Namespace: defaultNamespace,
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 }(),
335 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}},
336 Template: corev1.PodTemplateSpec{
337 ObjectMeta: metav1.ObjectMeta{
339 Labels: map[string]string{"name": name},
341 Spec: corev1.PodSpec{
342 Containers: []corev1.Container{
350 Status: appsv1.DaemonSetStatus{
351 DesiredNumberScheduled: int32(desiredNumberScheduled),
352 NumberReady: int32(numberReady),
353 UpdatedNumberScheduled: int32(updatedNumberScheduled),
358 func newStatefulSet(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet {
359 return &appsv1.StatefulSet{
360 ObjectMeta: metav1.ObjectMeta{
362 Namespace: defaultNamespace,
364 Spec: appsv1.StatefulSetSpec{
365 UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
366 Type: appsv1.RollingUpdateStatefulSetStrategyType,
367 RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
368 Partition: intToInt32(partition),
371 Replicas: intToInt32(replicas),
372 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}},
373 Template: corev1.PodTemplateSpec{
374 ObjectMeta: metav1.ObjectMeta{
376 Labels: map[string]string{"name": name},
378 Spec: corev1.PodSpec{
379 Containers: []corev1.Container{
387 Status: appsv1.StatefulSetStatus{
388 UpdatedReplicas: int32(updatedReplicas),
389 ReadyReplicas: int32(readyReplicas),
394 func newDeployment(name string, replicas, maxSurge, maxUnavailable int) *appsv1.Deployment {
395 return &appsv1.Deployment{
396 ObjectMeta: metav1.ObjectMeta{
398 Namespace: defaultNamespace,
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 }(),
408 Replicas: intToInt32(replicas),
409 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}},
410 Template: corev1.PodTemplateSpec{
411 ObjectMeta: metav1.ObjectMeta{
413 Labels: map[string]string{"name": name},
415 Spec: corev1.PodSpec{
416 Containers: []corev1.Container{
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{
432 Namespace: defaultNamespace,
433 Labels: d.Spec.Selector.MatchLabels,
434 OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(d, d.GroupVersionKind())},
436 Spec: appsv1.ReplicaSetSpec{
437 Selector: d.Spec.Selector,
438 Replicas: intToInt32(replicas),
439 Template: d.Spec.Template,
441 Status: appsv1.ReplicaSetStatus{
442 ReadyReplicas: int32(readyReplicas),
447 func newPodWithCondition(name string, podReadyCondition corev1.ConditionStatus) *corev1.Pod {
449 ObjectMeta: metav1.ObjectMeta{
451 Namespace: defaultNamespace,
452 Labels: map[string]string{"name": name},
454 Spec: corev1.PodSpec{
455 Containers: []corev1.Container{
461 Status: corev1.PodStatus{
462 Conditions: []corev1.PodCondition{
464 Type: corev1.PodReady,
465 Status: podReadyCondition,
472 func newPersistentVolumeClaim(name string, phase corev1.PersistentVolumeClaimPhase) *corev1.PersistentVolumeClaim {
473 return &corev1.PersistentVolumeClaim{
474 ObjectMeta: metav1.ObjectMeta{
476 Namespace: defaultNamespace,
478 Status: corev1.PersistentVolumeClaimStatus{
484 func newJob(name string, backoffLimit, completions, succeeded, failed int) *batchv1.Job {
486 ObjectMeta: metav1.ObjectMeta{
488 Namespace: defaultNamespace,
490 Spec: batchv1.JobSpec{
491 BackoffLimit: intToInt32(backoffLimit),
492 Completions: intToInt32(completions),
493 Template: corev1.PodTemplateSpec{
494 ObjectMeta: metav1.ObjectMeta{
496 Labels: map[string]string{"name": name},
498 Spec: corev1.PodSpec{
499 Containers: []corev1.Container{
507 Status: batchv1.JobStatus{
508 Succeeded: int32(succeeded),
509 Failed: int32(failed),
514 func intToInt32(i int) *int32 {