diff --git a/docs/syntax.md b/docs/syntax.md index 5cc57f5..46a56b4 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -50,6 +50,7 @@ Below you will find the step syntax next to the name of the method it utilizes. - ` [the] deployment is running in namespace ` kdt.KubeClientSet.DeploymentIsRunning - ` [the] data in [the] ConfigMap "" in namespace "" has key "" with value ""` kdt.KubeClientSet.ConfigMapDataHasKeyAndValue - ` [the] persistentvolume exists with status (Available|Bound|Released|Failed|Pending)` kdt.KubeClientSet.PersistentVolExists +- ` [the] persistentvolumeclaim exists with status (Available|Bound|Released|Failed|Pending) in namespace ` kdt.KubeClientSet.PersistentVolClaimExists - ` [the] (clusterrole|clusterrolebinding) with name should be found` kdt.KubeClientSet.ClusterRbacIsFound - ` [the] ingress in [the] namespace [is] [available] on port and path ` kdt.KubeClientSet.IngressAvailable - ` [I] send tps to ingress in [the] namespace [available] on port and path for (minutes|seconds) expecting up to error[s]` kdt.KubeClientSet.SendTrafficToIngress diff --git a/kubedog.go b/kubedog.go index 82174af..0e3778c 100644 --- a/kubedog.go +++ b/kubedog.go @@ -80,6 +80,7 @@ func (kdt *Test) SetScenario(scenario *godog.ScenarioContext) { kdt.scenario.Step(`^(?:the )?deployment ([^"]*) is running in namespace ([^"]*)$`, kdt.KubeClientSet.DeploymentIsRunning) kdt.scenario.Step(`^(?:the )?data in (?:the )?ConfigMap "([^"]*)" in namespace "([^"]*)" has key "([^"]*)" with value "([^"]*)"$`, kdt.KubeClientSet.ConfigMapDataHasKeyAndValue) kdt.scenario.Step(`^(?:the )?persistentvolume ([^"]*) exists with status (Available|Bound|Released|Failed|Pending)$`, kdt.KubeClientSet.PersistentVolExists) + kdt.scenario.Step(`^(?:the )?persistentvolumeclaim ([^"]*) exists with status (Available|Bound|Released|Failed|Pending) in namespace ([^"]*)$`, kdt.KubeClientSet.PersistentVolClaimExists) kdt.scenario.Step(`^(?:the )?(clusterrole|clusterrolebinding) with name ([^"]*) should be found$`, kdt.KubeClientSet.ClusterRbacIsFound) kdt.scenario.Step(`^(?:the )?ingress (\S+) in (?:the )?namespace (\S+) (?:is )?(?:available )?on port (\d+) and path ([^"]*)$`, kdt.KubeClientSet.IngressAvailable) kdt.scenario.Step(`^(?:I )?send (\d+) tps to ingress (\S+) in (?:the )?namespace (\S+) (?:available )?on port (\d+) and path ([^"]*) for (\d+) (minutes|seconds) expecting up to (\d+) error(?:s)?$`, kdt.KubeClientSet.SendTrafficToIngress) diff --git a/pkg/kube/kube.go b/pkg/kube/kube.go index 932b937..8cf913a 100644 --- a/pkg/kube/kube.go +++ b/pkg/kube/kube.go @@ -320,6 +320,10 @@ func (kc *ClientSet) PersistentVolExists(name, expectedPhase string) error { return structured.PersistentVolExists(kc.KubeInterface, name, expectedPhase) } +func (kc *ClientSet) PersistentVolClaimExists(name, expectedPhase string, namespace string) error { + return structured.PersistentVolClaimExists(kc.KubeInterface, name, expectedPhase, namespace) +} + func (kc *ClientSet) ClusterRbacIsFound(resourceType, name string) error { return structured.ClusterRbacIsFound(kc.KubeInterface, resourceType, name) } diff --git a/pkg/kube/structured/structured.go b/pkg/kube/structured/structured.go index b0d469e..e83663c 100644 --- a/pkg/kube/structured/structured.go +++ b/pkg/kube/structured/structured.go @@ -20,6 +20,7 @@ import ( "net/http" "os" "strconv" + "strings" "time" log "github.com/sirupsen/logrus" @@ -235,6 +236,27 @@ func PersistentVolExists(kubeClientset kubernetes.Interface, name, expectedPhase return nil } +func PersistentVolClaimExists(kubeClientset kubernetes.Interface, name, expectedPhase string, namespace string) error { + _, err := util.RetryOnError( + &util.DefaultRetry, + func(err error) bool { + msg := "persistentvolumeclaim had unexpected phase" + return util.IsRetriable(err) || strings.Contains(err.Error(), msg) + }, + func() (interface{}, error) { + vol, err := GetPersistentVolumeClaim(kubeClientset, name, namespace) + if err != nil { + return nil, err + } + phase := string(vol.Status.Phase) + if phase != expectedPhase { + return nil, fmt.Errorf("persistentvolumeclaim had unexpected phase %v, expected phase %v", phase, expectedPhase) + } + return nil, nil + }) + return err +} + func ValidatePrometheusVolumeClaimTemplatesName(kubeClientset kubernetes.Interface, statefulsetName, namespace, volumeClaimTemplatesName string) error { // Prometheus StatefulSets deployed, then validate volumeClaimTemplate name. // Validation required: diff --git a/pkg/kube/structured/structured_helper.go b/pkg/kube/structured/structured_helper.go index 60a8665..37132a4 100644 --- a/pkg/kube/structured/structured_helper.go +++ b/pkg/kube/structured/structured_helper.go @@ -96,6 +96,20 @@ func GetPersistentVolume(kubeClientset kubernetes.Interface, name string) (*core return pvs.(*corev1.PersistentVolume), nil } +func GetPersistentVolumeClaim(kubeClientset kubernetes.Interface, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { + if err := common.ValidateClientset(kubeClientset); err != nil { + return nil, err + } + + pvc, err := util.RetryOnError(&util.DefaultRetry, util.IsRetriable, func() (interface{}, error) { + return kubeClientset.CoreV1().PersistentVolumeClaims(namespace).Get(context.Background(), name, metav1.GetOptions{}) + }) + if err != nil { + return nil, errors.Wrap(err, "failed to get persistentvolumeclaim") + } + return pvc.(*corev1.PersistentVolumeClaim), nil +} + func GetStatefulSetList(kubeClientset kubernetes.Interface, namespace string) (*appsv1.StatefulSetList, error) { if err := common.ValidateClientset(kubeClientset); err != nil { return nil, err diff --git a/pkg/kube/structured/structured_test.go b/pkg/kube/structured/structured_test.go index d4a8cdf..98adfe6 100644 --- a/pkg/kube/structured/structured_test.go +++ b/pkg/kube/structured/structured_test.go @@ -36,20 +36,21 @@ import ( ) const ( - configMapType = "configmap" - deploymentType = "deployment" - serviceType = "service" - hpaType = "horizontalpodautoscaler" - pdbType = "poddisruptionbudget" - saType = "serviceaccount" - clusterRoleType = "clusterrole" - clusterRoleBindingType = "clusterrolebinding" - nodeType = "node" - daemonSetType = "daemonset" - persistentVolumeType = "persistentvolume" - statefulSetType = "statefulset" - secretType = "secret" - ingressType = "ingress" + configMapType = "configmap" + deploymentType = "deployment" + serviceType = "service" + hpaType = "horizontalpodautoscaler" + pdbType = "poddisruptionbudget" + saType = "serviceaccount" + clusterRoleType = "clusterrole" + clusterRoleBindingType = "clusterrolebinding" + nodeType = "node" + daemonSetType = "daemonset" + persistentVolumeType = "persistentvolume" + persistentVolumeClaimType = "persistentVolumeClaim" + statefulSetType = "statefulset" + secretType = "secret" + ingressType = "ingress" ) func TestNodesWithSelectorShouldBe(t *testing.T) { @@ -480,6 +481,60 @@ func TestPersistentVolExists(t *testing.T) { } } +func TestPersistentVolClaimExists(t *testing.T) { + type args struct { + kubeClientset kubernetes.Interface + name string + namespace string + expectedPhase string + } + // expectedPhase: Available|Bound|Released|Failed|Pending + persistentvolumeClaimName := "persistentvolumeclaim1" + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "pvc found", + args: args{ + kubeClientset: fake.NewSimpleClientset(getResource(t, persistentVolumeClaimType, persistentvolumeClaimName)), + name: persistentvolumeClaimName, + namespace: "", + expectedPhase: "Bound", + }, + wantErr: false, + }, + { + name: "pvc found with wrong phase", + args: args{ + kubeClientset: fake.NewSimpleClientset(getResource(t, persistentVolumeClaimType, persistentvolumeClaimName)), + name: persistentvolumeClaimName, + namespace: "", + expectedPhase: "Released", + }, + wantErr: true, + }, + { + name: "pvc not found Test", + args: args{ + kubeClientset: fake.NewSimpleClientset(getResource(t, persistentVolumeClaimType, "testabc")), + name: persistentvolumeClaimName, + namespace: "", + expectedPhase: "", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := PersistentVolClaimExists(tt.args.kubeClientset, tt.args.name, tt.args.expectedPhase, tt.args.namespace); (err != nil) != tt.wantErr { + t.Errorf("PersistentVolClaimExists() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + func TestValidatePrometheusVolumeClaimTemplatesName(t *testing.T) { type args struct { kubeClientset kubernetes.Interface @@ -833,6 +888,17 @@ func getResourceWithAll(t *testing.T, resourceType, name, namespace, label strin Labels: labels, }, } + case persistentVolumeClaimType: + return &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: labels, + }, + Status: corev1.PersistentVolumeClaimStatus{ + Phase: corev1.ClaimBound, + }, + } case statefulSetType: return &appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{