1
2
3
4 package status
5
6 import (
7 "fmt"
8 "math"
9 "strings"
10 "time"
11
12 corev1 "k8s.io/api/core/v1"
13 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14 )
15
16
17
18 type GetConditionsFn func(*unstructured.Unstructured) (*Result, error)
19
20
21
22 var legacyTypes = map[string]GetConditionsFn{
23 "Service": serviceConditions,
24 "Pod": podConditions,
25 "Secret": alwaysReady,
26 "PersistentVolumeClaim": pvcConditions,
27 "apps/StatefulSet": stsConditions,
28 "apps/DaemonSet": daemonsetConditions,
29 "extensions/DaemonSet": daemonsetConditions,
30 "apps/Deployment": deploymentConditions,
31 "extensions/Deployment": deploymentConditions,
32 "apps/ReplicaSet": replicasetConditions,
33 "extensions/ReplicaSet": replicasetConditions,
34 "policy/PodDisruptionBudget": pdbConditions,
35 "batch/CronJob": alwaysReady,
36 "ConfigMap": alwaysReady,
37 "batch/Job": jobConditions,
38 "apiextensions.k8s.io/CustomResourceDefinition": crdConditions,
39 }
40
41 const (
42 tooFewReady = "LessReady"
43 tooFewAvailable = "LessAvailable"
44 tooFewUpdated = "LessUpdated"
45 tooFewReplicas = "LessReplicas"
46 extraPods = "ExtraPods"
47
48 onDeleteUpdateStrategy = "OnDelete"
49
50
51
52 ScheduleWindow = 15 * time.Second
53 )
54
55
56
57 func GetLegacyConditionsFn(u *unstructured.Unstructured) GetConditionsFn {
58 gvk := u.GroupVersionKind()
59 g := gvk.Group
60 k := gvk.Kind
61 key := g + "/" + k
62 if g == "" {
63 key = k
64 }
65 return legacyTypes[key]
66 }
67
68
69 func alwaysReady(u *unstructured.Unstructured) (*Result, error) {
70 return &Result{
71 Status: CurrentStatus,
72 Message: "Resource is always ready",
73 Conditions: []Condition{},
74 }, nil
75 }
76
77
78
79
80
81
82
83 func stsConditions(u *unstructured.Unstructured) (*Result, error) {
84 obj := u.UnstructuredContent()
85
86
87 updateStrategy := GetStringField(obj, ".spec.updateStrategy.type", "")
88 if updateStrategy == onDeleteUpdateStrategy {
89 return &Result{
90 Status: CurrentStatus,
91 Message: "StatefulSet is using the ondelete update strategy",
92 Conditions: []Condition{},
93 }, nil
94 }
95
96
97 specReplicas := GetIntField(obj, ".spec.replicas", 1)
98 readyReplicas := GetIntField(obj, ".status.readyReplicas", 0)
99 currentReplicas := GetIntField(obj, ".status.currentReplicas", 0)
100 updatedReplicas := GetIntField(obj, ".status.updatedReplicas", 0)
101 statusReplicas := GetIntField(obj, ".status.replicas", 0)
102 partition := GetIntField(obj, ".spec.updateStrategy.rollingUpdate.partition", -1)
103
104 if specReplicas > statusReplicas {
105 message := fmt.Sprintf("Replicas: %d/%d", statusReplicas, specReplicas)
106 return newInProgressStatus(tooFewReplicas, message), nil
107 }
108
109 if specReplicas > readyReplicas {
110 message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas)
111 return newInProgressStatus(tooFewReady, message), nil
112 }
113
114 if statusReplicas > specReplicas {
115 message := fmt.Sprintf("Pending termination: %d", statusReplicas-specReplicas)
116 return newInProgressStatus(extraPods, message), nil
117 }
118
119
120 if partition != -1 {
121 if updatedReplicas < (specReplicas - partition) {
122 message := fmt.Sprintf("updated: %d/%d", updatedReplicas, specReplicas-partition)
123 return newInProgressStatus("PartitionRollout", message), nil
124 }
125
126 return &Result{
127 Status: CurrentStatus,
128 Message: fmt.Sprintf("Partition rollout complete. updated: %d", updatedReplicas),
129 Conditions: []Condition{},
130 }, nil
131 }
132
133 if specReplicas > currentReplicas {
134 message := fmt.Sprintf("current: %d/%d", currentReplicas, specReplicas)
135 return newInProgressStatus("LessCurrent", message), nil
136 }
137
138
139 currentRevision := GetStringField(obj, ".status.currentRevision", "")
140 updatedRevision := GetStringField(obj, ".status.updateRevision", "")
141 if currentRevision != updatedRevision {
142 message := "Waiting for updated revision to match current"
143 return newInProgressStatus("RevisionMismatch", message), nil
144 }
145
146
147 return &Result{
148 Status: CurrentStatus,
149 Message: fmt.Sprintf("All replicas scheduled as expected. Replicas: %d", statusReplicas),
150 Conditions: []Condition{},
151 }, nil
152 }
153
154
155
156
157
158 func deploymentConditions(u *unstructured.Unstructured) (*Result, error) {
159 obj := u.UnstructuredContent()
160
161 progressing := false
162
163
164
165
166
167
168 progressDeadline := GetIntField(obj, ".spec.progressDeadlineSeconds", math.MaxInt32)
169 if progressDeadline == math.MaxInt32 {
170 progressing = true
171 }
172
173 available := false
174
175 objc, err := GetObjectWithConditions(obj)
176 if err != nil {
177 return nil, err
178 }
179
180 for _, c := range objc.Status.Conditions {
181 switch c.Type {
182 case "Progressing":
183
184 if c.Reason == "ProgressDeadlineExceeded" {
185 return &Result{
186 Status: FailedStatus,
187 Message: "Progress deadline exceeded",
188 Conditions: []Condition{{ConditionStalled, corev1.ConditionTrue, c.Reason, c.Message}},
189 }, nil
190 }
191 if c.Status == corev1.ConditionTrue && c.Reason == "NewReplicaSetAvailable" {
192 progressing = true
193 }
194 case "Available":
195 if c.Status == corev1.ConditionTrue {
196 available = true
197 }
198 }
199 }
200
201
202 specReplicas := GetIntField(obj, ".spec.replicas", 1)
203 statusReplicas := GetIntField(obj, ".status.replicas", 0)
204 updatedReplicas := GetIntField(obj, ".status.updatedReplicas", 0)
205 readyReplicas := GetIntField(obj, ".status.readyReplicas", 0)
206 availableReplicas := GetIntField(obj, ".status.availableReplicas", 0)
207
208
209
210 if specReplicas > statusReplicas {
211 message := fmt.Sprintf("Replicas: %d/%d", statusReplicas, specReplicas)
212 return newInProgressStatus(tooFewReplicas, message), nil
213 }
214
215 if specReplicas > updatedReplicas {
216 message := fmt.Sprintf("Updated: %d/%d", updatedReplicas, specReplicas)
217 return newInProgressStatus(tooFewUpdated, message), nil
218 }
219
220 if statusReplicas > specReplicas {
221 message := fmt.Sprintf("Pending termination: %d", statusReplicas-specReplicas)
222 return newInProgressStatus(extraPods, message), nil
223 }
224
225 if updatedReplicas > availableReplicas {
226 message := fmt.Sprintf("Available: %d/%d", availableReplicas, updatedReplicas)
227 return newInProgressStatus(tooFewAvailable, message), nil
228 }
229
230 if specReplicas > readyReplicas {
231 message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas)
232 return newInProgressStatus(tooFewReady, message), nil
233 }
234
235
236 if !progressing {
237 message := "ReplicaSet not Available"
238 return newInProgressStatus("ReplicaSetNotAvailable", message), nil
239 }
240 if !available {
241 message := "Deployment not Available"
242 return newInProgressStatus("DeploymentNotAvailable", message), nil
243 }
244
245 return &Result{
246 Status: CurrentStatus,
247 Message: fmt.Sprintf("Deployment is available. Replicas: %d", statusReplicas),
248 Conditions: []Condition{},
249 }, nil
250 }
251
252
253 func replicasetConditions(u *unstructured.Unstructured) (*Result, error) {
254 obj := u.UnstructuredContent()
255
256
257 objc, err := GetObjectWithConditions(obj)
258 if err != nil {
259 return nil, err
260 }
261
262 for _, c := range objc.Status.Conditions {
263
264 if c.Type == "ReplicaFailure" && c.Status == corev1.ConditionTrue {
265 message := "Replica Failure condition. Check Pods"
266 return newInProgressStatus("ReplicaFailure", message), nil
267 }
268 }
269
270
271 specReplicas := GetIntField(obj, ".spec.replicas", 1)
272 statusReplicas := GetIntField(obj, ".status.replicas", 0)
273 readyReplicas := GetIntField(obj, ".status.readyReplicas", 0)
274 availableReplicas := GetIntField(obj, ".status.availableReplicas", 0)
275 fullyLabelledReplicas := GetIntField(obj, ".status.fullyLabeledReplicas", 0)
276
277 if specReplicas > fullyLabelledReplicas {
278 message := fmt.Sprintf("Labelled: %d/%d", fullyLabelledReplicas, specReplicas)
279 return newInProgressStatus("LessLabelled", message), nil
280 }
281
282 if specReplicas > availableReplicas {
283 message := fmt.Sprintf("Available: %d/%d", availableReplicas, specReplicas)
284 return newInProgressStatus(tooFewAvailable, message), nil
285 }
286
287 if specReplicas > readyReplicas {
288 message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas)
289 return newInProgressStatus(tooFewReady, message), nil
290 }
291
292 if statusReplicas > specReplicas {
293 message := fmt.Sprintf("Pending termination: %d", statusReplicas-specReplicas)
294 return newInProgressStatus(extraPods, message), nil
295 }
296
297 return &Result{
298 Status: CurrentStatus,
299 Message: fmt.Sprintf("ReplicaSet is available. Replicas: %d", statusReplicas),
300 Conditions: []Condition{},
301 }, nil
302 }
303
304
305 func daemonsetConditions(u *unstructured.Unstructured) (*Result, error) {
306
307
308
309
310
311 res, err := checkGenerationSet(u)
312 if err != nil || res != nil {
313 return res, err
314 }
315
316 obj := u.UnstructuredContent()
317
318
319 desiredNumberScheduled := GetIntField(obj, ".status.desiredNumberScheduled", -1)
320 currentNumberScheduled := GetIntField(obj, ".status.currentNumberScheduled", 0)
321 updatedNumberScheduled := GetIntField(obj, ".status.updatedNumberScheduled", 0)
322 numberAvailable := GetIntField(obj, ".status.numberAvailable", 0)
323 numberReady := GetIntField(obj, ".status.numberReady", 0)
324
325 if desiredNumberScheduled == -1 {
326 message := "Missing .status.desiredNumberScheduled"
327 return newInProgressStatus("NoDesiredNumber", message), nil
328 }
329
330 if desiredNumberScheduled > currentNumberScheduled {
331 message := fmt.Sprintf("Current: %d/%d", currentNumberScheduled, desiredNumberScheduled)
332 return newInProgressStatus("LessCurrent", message), nil
333 }
334
335 if desiredNumberScheduled > updatedNumberScheduled {
336 message := fmt.Sprintf("Updated: %d/%d", updatedNumberScheduled, desiredNumberScheduled)
337 return newInProgressStatus(tooFewUpdated, message), nil
338 }
339
340 if desiredNumberScheduled > numberAvailable {
341 message := fmt.Sprintf("Available: %d/%d", numberAvailable, desiredNumberScheduled)
342 return newInProgressStatus(tooFewAvailable, message), nil
343 }
344
345 if desiredNumberScheduled > numberReady {
346 message := fmt.Sprintf("Ready: %d/%d", numberReady, desiredNumberScheduled)
347 return newInProgressStatus(tooFewReady, message), nil
348 }
349
350
351 return &Result{
352 Status: CurrentStatus,
353 Message: fmt.Sprintf("All replicas scheduled as expected. Replicas: %d", desiredNumberScheduled),
354 Conditions: []Condition{},
355 }, nil
356 }
357
358
359
360 func checkGenerationSet(u *unstructured.Unstructured) (*Result, error) {
361 _, found, err := unstructured.NestedInt64(u.Object, "metadata", "generation")
362 if err != nil {
363 return nil, fmt.Errorf("looking up metadata.generation from resource: %w", err)
364 }
365 if !found {
366 message := fmt.Sprintf("%s metadata.generation not found", u.GetKind())
367 return &Result{
368 Status: InProgressStatus,
369 Message: message,
370 Conditions: []Condition{newReconcilingCondition("NoGeneration", message)},
371 }, nil
372 }
373
374 _, found, err = unstructured.NestedInt64(u.Object, "status", "observedGeneration")
375 if err != nil {
376 return nil, fmt.Errorf("looking up status.observedGeneration from resource: %w", err)
377 }
378 if !found {
379 message := fmt.Sprintf("%s status.observedGeneration not found", u.GetKind())
380 return &Result{
381 Status: InProgressStatus,
382 Message: message,
383 Conditions: []Condition{newReconcilingCondition("NoObservedGeneration", message)},
384 }, nil
385 }
386
387 return nil, nil
388 }
389
390
391 func pvcConditions(u *unstructured.Unstructured) (*Result, error) {
392 obj := u.UnstructuredContent()
393
394 phase := GetStringField(obj, ".status.phase", "unknown")
395 if phase != "Bound" {
396 message := fmt.Sprintf("PVC is not Bound. phase: %s", phase)
397 return newInProgressStatus("NotBound", message), nil
398 }
399
400 return &Result{
401 Status: CurrentStatus,
402 Message: "PVC is Bound",
403 Conditions: []Condition{},
404 }, nil
405 }
406
407
408 func podConditions(u *unstructured.Unstructured) (*Result, error) {
409 obj := u.UnstructuredContent()
410 objc, err := GetObjectWithConditions(obj)
411 if err != nil {
412 return nil, err
413 }
414 phase := GetStringField(obj, ".status.phase", "")
415
416 switch phase {
417 case "Succeeded":
418 return &Result{
419 Status: CurrentStatus,
420 Message: "Pod has completed successfully",
421 Conditions: []Condition{},
422 }, nil
423 case "Failed":
424 return &Result{
425 Status: CurrentStatus,
426 Message: "Pod has completed, but not successfully",
427 Conditions: []Condition{},
428 }, nil
429 case "Running":
430 if hasConditionWithStatus(objc.Status.Conditions, "Ready", corev1.ConditionTrue) {
431 return &Result{
432 Status: CurrentStatus,
433 Message: "Pod is Ready",
434 Conditions: []Condition{},
435 }, nil
436 }
437
438 containerNames, isCrashLooping, err := getCrashLoopingContainers(obj)
439 if err != nil {
440 return nil, err
441 }
442 if isCrashLooping {
443 return newFailedStatus("ContainerCrashLooping",
444 fmt.Sprintf("Containers in CrashLoop state: %s", strings.Join(containerNames, ","))), nil
445 }
446
447 return newInProgressStatus("PodRunningNotReady", "Pod is running but is not Ready"), nil
448 case "Pending":
449 c, found := getConditionWithStatus(objc.Status.Conditions, "PodScheduled", corev1.ConditionFalse)
450 if found && c.Reason == "Unschedulable" {
451 if time.Now().Add(-ScheduleWindow).Before(u.GetCreationTimestamp().Time) {
452
453
454 return newInProgressStatus("PodNotScheduled", "Pod has not been scheduled"), nil
455 }
456 return newFailedStatus("PodUnschedulable", "Pod could not be scheduled"), nil
457 }
458 return newInProgressStatus("PodPending", "Pod is in the Pending phase"), nil
459 default:
460
461
462 if phase == "" {
463 return newInProgressStatus("PodNotObserved", "Pod phase not available"), nil
464 }
465 return nil, fmt.Errorf("unknown phase %s", phase)
466 }
467 }
468
469 func getCrashLoopingContainers(obj map[string]interface{}) ([]string, bool, error) {
470 var containerNames []string
471 css, found, err := unstructured.NestedSlice(obj, "status", "containerStatuses")
472 if !found || err != nil {
473 return containerNames, found, err
474 }
475 for _, item := range css {
476 cs := item.(map[string]interface{})
477 n, found := cs["name"]
478 if !found {
479 continue
480 }
481 name := n.(string)
482 s, found := cs["state"]
483 if !found {
484 continue
485 }
486 state := s.(map[string]interface{})
487
488 ws, found := state["waiting"]
489 if !found {
490 continue
491 }
492 waitingState := ws.(map[string]interface{})
493
494 r, found := waitingState["reason"]
495 if !found {
496 continue
497 }
498 reason := r.(string)
499 if reason == "CrashLoopBackOff" {
500 containerNames = append(containerNames, name)
501 }
502 }
503 if len(containerNames) > 0 {
504 return containerNames, true, nil
505 }
506 return containerNames, false, nil
507 }
508
509
510
511
512
513
514
515
516
517
518
519 func pdbConditions(_ *unstructured.Unstructured) (*Result, error) {
520
521 return &Result{
522 Status: CurrentStatus,
523 Message: "AllowedDisruptions has been computed.",
524 Conditions: []Condition{},
525 }, nil
526 }
527
528
529
530
531
532
533 func jobConditions(u *unstructured.Unstructured) (*Result, error) {
534 obj := u.UnstructuredContent()
535
536 parallelism := GetIntField(obj, ".spec.parallelism", 1)
537 completions := GetIntField(obj, ".spec.completions", parallelism)
538 succeeded := GetIntField(obj, ".status.succeeded", 0)
539 active := GetIntField(obj, ".status.active", 0)
540 failed := GetIntField(obj, ".status.failed", 0)
541 starttime := GetStringField(obj, ".status.startTime", "")
542
543
544
545 objc, err := GetObjectWithConditions(obj)
546 if err != nil {
547 return nil, err
548 }
549 for _, c := range objc.Status.Conditions {
550 switch c.Type {
551 case "Complete":
552 if c.Status == corev1.ConditionTrue {
553 message := fmt.Sprintf("Job Completed. succeeded: %d/%d", succeeded, completions)
554 return &Result{
555 Status: CurrentStatus,
556 Message: message,
557 Conditions: []Condition{},
558 }, nil
559 }
560 case "Failed":
561 if c.Status == corev1.ConditionTrue {
562 return newFailedStatus("JobFailed",
563 fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions)), nil
564 }
565 }
566 }
567
568
569 if starttime == "" {
570 message := "Job not started"
571 return newInProgressStatus("JobNotStarted", message), nil
572 }
573 return &Result{
574 Status: CurrentStatus,
575 Message: fmt.Sprintf("Job in progress. success:%d, active: %d, failed: %d", succeeded, active, failed),
576 Conditions: []Condition{},
577 }, nil
578 }
579
580
581 func serviceConditions(u *unstructured.Unstructured) (*Result, error) {
582 obj := u.UnstructuredContent()
583
584 specType := GetStringField(obj, ".spec.type", "ClusterIP")
585 specClusterIP := GetStringField(obj, ".spec.clusterIP", "")
586
587 if specType == "LoadBalancer" {
588 if specClusterIP == "" {
589 message := "ClusterIP not set. Service type: LoadBalancer"
590 return newInProgressStatus("NoIPAssigned", message), nil
591 }
592 }
593
594 return &Result{
595 Status: CurrentStatus,
596 Message: "Service is ready",
597 Conditions: []Condition{},
598 }, nil
599 }
600
601 func crdConditions(u *unstructured.Unstructured) (*Result, error) {
602 obj := u.UnstructuredContent()
603
604 objc, err := GetObjectWithConditions(obj)
605 if err != nil {
606 return nil, err
607 }
608
609 for _, c := range objc.Status.Conditions {
610 if c.Type == "NamesAccepted" && c.Status == corev1.ConditionFalse {
611 return newFailedStatus(c.Reason, c.Message), nil
612 }
613 if c.Type == "Established" {
614 if c.Status == corev1.ConditionFalse && c.Reason != "Installing" {
615 return newFailedStatus(c.Reason, c.Message), nil
616 }
617 if c.Status == corev1.ConditionTrue {
618 return &Result{
619 Status: CurrentStatus,
620 Message: "CRD is established",
621 Conditions: []Condition{},
622 }, nil
623 }
624 }
625 }
626 return newInProgressStatus("Installing", "Install in progress"), nil
627 }
628
View as plain text