Unused PVC: Check for unused claims in the cluster.

varsha/versions
Varsha Varadarajan 2019-06-27 10:05:59 -04:00
parent f1a6a18208
commit 05502e3b32
3 changed files with 183 additions and 0 deletions

View File

@ -265,3 +265,17 @@ How to fix:
```bash
kubectl delete pv <unused pv>
```
###### Unused Persistent Volume Claims
Name: `unused-pvc`
Group: `basic`
Description: This check reports all the persistent volume claims in the cluster that are not referenced by pods in the respective namespaces. The cluster can be cleaned up based on this information and there will be fewer objects to manage.
How to fix:
```bash
kubectl delete pvc <unused pvc>
```

View File

@ -0,0 +1,64 @@
package basic
import (
"github.com/digitalocean/clusterlint/checks"
"github.com/digitalocean/clusterlint/kube"
)
func init() {
checks.Register(&unusedClaimCheck{})
}
type unusedClaimCheck struct{}
// Name returns a unique name for this check.
func (c *unusedClaimCheck) Name() string {
return "unused-pvc"
}
// Groups returns a list of group names this check should be part of.
func (c *unusedClaimCheck) Groups() []string {
return []string{"basic"}
}
// Description returns a detailed human-readable description of what this check
// does.
func (c *unusedClaimCheck) Description() string {
return "Check if there are unused persistent volume claims in the cluster"
}
type identifier struct {
Name string
Namespace string
}
// 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 (c *unusedClaimCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
var diagnostics []checks.Diagnostic
used := make(map[identifier]bool)
for _, pod := range objects.Pods.Items {
for _, volume := range pod.Spec.Volumes {
claim := volume.VolumeSource.PersistentVolumeClaim
if claim != nil {
used[identifier{Name: claim.ClaimName, Namespace: pod.GetNamespace()}] = true
}
}
}
for _, claim := range objects.PersistentVolumeClaims.Items {
if _, ok := used[identifier{Name: claim.GetName(), Namespace: claim.GetNamespace()}]; !ok {
d := checks.Diagnostic{
Severity: checks.Warning,
Message: "Unused persistent volume claim",
Kind: checks.PersistentVolumeClaim,
Object: &claim.ObjectMeta,
Owners: claim.ObjectMeta.GetOwnerReferences(),
}
diagnostics = append(diagnostics, d)
}
}
return diagnostics, nil
}

View File

@ -0,0 +1,105 @@
package basic
import (
"testing"
"github.com/digitalocean/clusterlint/checks"
"github.com/digitalocean/clusterlint/kube"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestUnusedPVCCheckMeta(t *testing.T) {
unusedClaimCheck := unusedClaimCheck{}
assert.Equal(t, "unused-pvc", unusedClaimCheck.Name())
assert.Equal(t, []string{"basic"}, unusedClaimCheck.Groups())
assert.NotEmpty(t, unusedClaimCheck.Description())
}
func TestUnusedPVCCheckRegistration(t *testing.T) {
unusedClaimCheck := &unusedClaimCheck{}
check, err := checks.Get("unused-pvc")
assert.NoError(t, err)
assert.Equal(t, check, unusedClaimCheck)
}
func TestUnusedPVCWarning(t *testing.T) {
tests := []struct {
name string
objs *kube.Objects
expected []checks.Diagnostic
}{
{
name: "no pvcs",
objs: &kube.Objects{Pods: &corev1.PodList{}, PersistentVolumeClaims: &corev1.PersistentVolumeClaimList{}},
expected: nil,
},
{
name: "pod with pvc",
objs: boundPVC(corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: "pvc_foo",
},
}),
expected: nil,
},
{
name: "unused pvc",
objs: initPVC(),
expected: []checks.Diagnostic{
{
Severity: checks.Warning,
Message: "Unused persistent volume claim",
Kind: checks.PersistentVolumeClaim,
Object: &metav1.ObjectMeta{Name: "pvc_foo", Namespace: "k8s"},
Owners: GetOwners(),
},
},
},
}
unusedClaimCheck := unusedClaimCheck{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d, err := unusedClaimCheck.Run(test.objs)
assert.NoError(t, err)
assert.ElementsMatch(t, test.expected, d)
})
}
}
func initPVC() *kube.Objects {
objs := &kube.Objects{
Pods: &corev1.PodList{
Items: []corev1.Pod{
{
TypeMeta: metav1.TypeMeta{Kind: "Pod", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Name: "pod_foo", Namespace: "k8s"},
},
},
},
PersistentVolumeClaims: &corev1.PersistentVolumeClaimList{
Items: []corev1.PersistentVolumeClaim{
{
TypeMeta: metav1.TypeMeta{Kind: "PersistentVolumeClaim", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Name: "pvc_foo", Namespace: "k8s"},
},
},
},
}
return objs
}
func boundPVC(volumeSrc corev1.VolumeSource) *kube.Objects {
objs := initPVC()
objs.Pods.Items[0].Spec = corev1.PodSpec{
Volumes: []corev1.Volume{
{
Name: "bar",
VolumeSource: volumeSrc,
}},
}
return objs
}