diff --git a/checks/basic/pod_status.go b/checks/basic/pod_status.go new file mode 100644 index 0000000..260e4ee --- /dev/null +++ b/checks/basic/pod_status.go @@ -0,0 +1,46 @@ +package basic + +import ( + "fmt" + + "github.com/digitalocean/clusterlint/checks" + "github.com/digitalocean/clusterlint/kube" + corev1 "k8s.io/api/core/v1" +) + +func init() { + checks.Register(&podStatusCheck{}) +} + +type podStatusCheck struct{} + +// Name returns a unique name for this check. +func (p *podStatusCheck) Name() string { + return "pod-state" +} + +// Groups returns a list of group names this check should be part of. +func (p *podStatusCheck) Groups() []string { + return []string{"workload-health"} +} + +// Description returns a detailed human-readable description of what this check +// does. +func (p *podStatusCheck) Description() string { + return "Check if there are unhealthy pods in the cluster" +} + +// Run runs this check on a set of Kubernetes objects. It can return warnings +// (low-priority problems) and errors (high-priority problems) as well as an +// error value indicating that the check failed to run. +func (p *podStatusCheck) Run(objects *kube.Objects) (warnings []error, errors []error, err error) { + var e []error + + for _, pod := range objects.Pods.Items { + if corev1.PodFailed == pod.Status.Phase || corev1.PodUnknown == pod.Status.Phase { + e = append(e, fmt.Errorf("Pod '%s' in namespace '%s' has state: %s. Pod state should be `Running`, `Pending` or `Succeeded`.", pod.GetName(), pod.GetNamespace(), pod.Status.Phase)) + } + } + + return nil, e, nil +} diff --git a/checks/basic/pod_status_test.go b/checks/basic/pod_status_test.go new file mode 100644 index 0000000..dc4a0c5 --- /dev/null +++ b/checks/basic/pod_status_test.go @@ -0,0 +1,79 @@ +package basic + +import ( + "fmt" + "testing" + + "github.com/digitalocean/clusterlint/kube" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" +) + +func TestMeta(t *testing.T) { + podStatusCheck := podStatusCheck{} + assert.Equal(t, "pod-state", podStatusCheck.Name()) + assert.Equal(t, "Check if there are unhealthy pods in the cluster", podStatusCheck.Description()) + assert.Equal(t, []string{"workload-health"}, podStatusCheck.Groups()) +} + +func TestPodStateError(t *testing.T) { + scenarios := []struct { + name string + arg *kube.Objects + expected []error + }{ + { + name: "no pods", + arg: initPod(), + expected: nil, + }, + { + name: "pod with running status", + arg: status(corev1.PodRunning), + expected: nil, + }, + { + name: "pod with pending status", + arg: status(corev1.PodPending), + expected: nil, + }, + { + name: "pod with succeeded status", + arg: status(corev1.PodSucceeded), + expected: nil, + }, + { + name: "pod with failed status", + arg: status(corev1.PodFailed), + expected: []error{ + fmt.Errorf("Pod 'pod_foo' in namespace 'k8s' has state: Failed. Pod state should be `Running`, `Pending` or `Succeeded`."), + }, + }, + { + name: "pod with unknown status", + arg: status(corev1.PodUnknown), + expected: []error{ + fmt.Errorf("Pod 'pod_foo' in namespace 'k8s' has state: Unknown. Pod state should be `Running`, `Pending` or `Succeeded`."), + }, + }, + } + + podStatusCheck := podStatusCheck{} + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + w, e, err := podStatusCheck.Run(scenario.arg) + assert.ElementsMatch(t, scenario.expected, e) + assert.Empty(t, w) + assert.Nil(t, err) + }) + } +} + +func status(status corev1.PodPhase) *kube.Objects { + objs := initPod() + objs.Pods.Items[0].Status = corev1.PodStatus{ + Phase: status, + } + return objs +}