Skip to content

Commit 93dfdc0

Browse files
authored
test(bdd): add integration test for pvc leak protection (#118)
Signed-off-by: Yashpal Choudhary <[email protected]>
1 parent cff665d commit 93dfdc0

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed

tests/provision_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ limitations under the License.
1717
package tests
1818

1919
import (
20+
"time"
21+
2022
. "github.com/onsi/ginkgo"
23+
"github.com/onsi/gomega"
2124
)
2225

2326
var _ = Describe("[lvmpv] TEST VOLUME PROVISIONING", func() {
@@ -91,8 +94,30 @@ func thinVolCreationTest() {
9194
By("Deleting thinProvision storage class", deleteStorageClass)
9295
}
9396

97+
func leakProtectionTest() {
98+
By("Creating default storage class", createStorageClass)
99+
ds := deleteNodeDaemonSet() // ensure that provisioning remains in pending state.
100+
101+
By("Creating PVC", createPVC)
102+
time.Sleep(30 * time.Second) // wait for external provisioner to pick up new pvc
103+
By("Verify pending lvm volume resource")
104+
verifyPendingLVMVolume(getGeneratedVolName(pvcObj))
105+
106+
existingSize := scaleControllerPlugin(0) // remove the external provisioner
107+
createNodeDaemonSet(ds) // provision the volume now by restoring node plugin
108+
By("Wait for lvm volume resource to become ready", WaitForLVMVolumeReady)
109+
110+
deleteAndVerifyLeakedPVC(pvcName)
111+
scaleControllerPlugin(existingSize)
112+
113+
gomega.Expect(IsPVCDeletedEventually(pvcName)).To(gomega.Equal(true),
114+
"failed to garbage collect leaked pvc")
115+
By("Deleting storage class", deleteStorageClass)
116+
}
117+
94118
func volumeCreationTest() {
95119
By("Running volume creation test", fsVolCreationTest)
96120
By("Running block volume creation test", blockVolCreationTest)
97121
By("Running thin volume creation test", thinVolCreationTest)
122+
By("Running leak protection test", leakProtectionTest)
98123
}

tests/suite_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
. "github.com/onsi/ginkgo"
2424
. "github.com/onsi/gomega"
25+
"github.com/openebs/lib-csi/pkg/common/kubernetes/client"
2526
"github.com/openebs/lvm-localpv/pkg/builder/volbuilder"
2627
"github.com/openebs/lvm-localpv/tests/deploy"
2728
"github.com/openebs/lvm-localpv/tests/pod"
@@ -31,6 +32,7 @@ import (
3132
appsv1 "k8s.io/api/apps/v1"
3233
corev1 "k8s.io/api/core/v1"
3334
storagev1 "k8s.io/api/storage/v1"
35+
"k8s.io/client-go/kubernetes"
3436
"k8s.io/klog"
3537

3638
// auth plugins
@@ -49,13 +51,19 @@ var (
4951
PVClient *pv.Kubeclient
5052
DeployClient *deploy.Kubeclient
5153
PodClient *pod.KubeClient
54+
55+
K8sClient *kubernetes.Clientset
56+
5257
nsName = "lvm"
5358
scName = "lvmpv-sc"
5459
LocalProvisioner = "local.csi.openebs.io"
5560
pvcName = "lvmpv-pvc"
5661
snapName = "lvmpv-snap"
5762
appName = "busybox-lvmpv"
5863

64+
nodeDaemonSet = "openebs-lvm-node"
65+
controllerStatefulSet = "openebs-lvm-controller"
66+
5967
nsObj *corev1.Namespace
6068
scObj *storagev1.StorageClass
6169
deployObj *appsv1.Deployment
@@ -74,6 +82,12 @@ func init() {
7482
if OpenEBSNamespace == "" {
7583
klog.Fatalf("LVM_NAMESPACE environment variable not set")
7684
}
85+
86+
var err error
87+
if K8sClient, err = client.Instance(client.WithKubeConfigPath(KubeConfigPath)).Clientset(); err != nil {
88+
klog.Fatalf("failed to init kubernetes client: %v", err)
89+
}
90+
7791
SCClient = sc.NewKubeClient(sc.WithKubeConfigPath(KubeConfigPath))
7892
PVCClient = pvc.NewKubeClient(pvc.WithKubeConfigPath(KubeConfigPath))
7993
PVClient = pv.NewKubeClient(pv.WithKubeConfigPath(KubeConfigPath))

tests/utils.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@ limitations under the License.
1717
package tests
1818

1919
import (
20+
"context"
21+
"fmt"
2022
"time"
2123

2224
"github.com/onsi/ginkgo"
2325
"github.com/onsi/gomega"
26+
"github.com/openebs/lib-csi/pkg/csipv"
2427

2528
"github.com/openebs/lvm-localpv/pkg/lvm"
2629
"github.com/openebs/lvm-localpv/tests/container"
@@ -30,6 +33,7 @@ import (
3033
"github.com/openebs/lvm-localpv/tests/pts"
3134
"github.com/openebs/lvm-localpv/tests/pvc"
3235
"github.com/openebs/lvm-localpv/tests/sc"
36+
appsv1 "k8s.io/api/apps/v1"
3337
corev1 "k8s.io/api/core/v1"
3438
k8serrors "k8s.io/apimachinery/pkg/api/errors"
3539
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -526,3 +530,178 @@ func IsPVDeletedEventually(shouldExist bool, pvName string) bool {
526530
120, 10).
527531
Should(shouldPVExist)
528532
}
533+
534+
func getGeneratedVolName(pvc *corev1.PersistentVolumeClaim) string {
535+
return fmt.Sprintf("pvc-%v", pvcObj.GetUID())
536+
}
537+
538+
func createPVC() {
539+
var (
540+
err error
541+
pvcName = "lvmpv-pvc"
542+
)
543+
ginkgo.By("building a pvc")
544+
pvcObj, err = pvc.NewBuilder().
545+
WithName(pvcName).
546+
WithNamespace(OpenEBSNamespace).
547+
WithStorageClass(scObj.Name).
548+
WithAccessModes(accessModes).
549+
WithCapacity(capacity).Build()
550+
gomega.Expect(err).ShouldNot(
551+
gomega.HaveOccurred(),
552+
"while building pvc {%s} in namespace {%s}",
553+
pvcName,
554+
OpenEBSNamespace,
555+
)
556+
557+
ginkgo.By("creating above pvc")
558+
pvcObj, err = PVCClient.WithNamespace(OpenEBSNamespace).Create(pvcObj)
559+
gomega.Expect(err).To(
560+
gomega.BeNil(),
561+
"while creating pvc {%s} in namespace {%s}",
562+
pvcName,
563+
OpenEBSNamespace,
564+
)
565+
}
566+
567+
func deleteAndVerifyLeakedPVC(pvcName string) {
568+
ginkgo.By("Deleting pending PVC")
569+
err := PVCClient.WithNamespace(OpenEBSNamespace).Delete(pvcName, &metav1.DeleteOptions{})
570+
gomega.Expect(err).To(
571+
gomega.BeNil(),
572+
"while deleting pvc {%s} in namespace {%s}",
573+
pvcName,
574+
OpenEBSNamespace,
575+
)
576+
ginkgo.By("Verify leaked pvc finalizer")
577+
578+
status := gomega.Eventually(func() bool {
579+
pvcRes, err := PVCClient.
580+
Get(pvcName, metav1.GetOptions{})
581+
gomega.Expect(err).To(
582+
gomega.BeNil(),
583+
"fetch pvc %v", pvcName)
584+
return len(pvcRes.GetFinalizers()) == 1 &&
585+
pvcRes.GetFinalizers()[0] == LocalProvisioner + "/" + csipv.LeakProtectionFinalizer
586+
}, 120, 10).Should(gomega.BeTrue())
587+
gomega.Expect(status).To(gomega.Equal(true), "expecting a leak protection finalizer")
588+
}
589+
590+
func verifyPendingLVMVolume(volName string) {
591+
ginkgo.By("fetching lvm volume")
592+
vol, err := LVMClient.WithNamespace(OpenEBSNamespace).
593+
Get(volName, metav1.GetOptions{})
594+
gomega.Expect(err).To(gomega.BeNil(), "while fetching the lvm volume {%s}", volName)
595+
596+
ginkgo.By("verifying lvm volume")
597+
gomega.Expect(scObj.Parameters["volgroup"]).To(gomega.MatchRegexp(vol.Spec.VgPattern),
598+
"while checking volume group of lvm volume", volName)
599+
}
600+
601+
// WaitForLVMVolumeReady verify the if lvm-volume is ready
602+
func WaitForLVMVolumeReady() {
603+
volName := getGeneratedVolName(pvcObj)
604+
status := gomega.Eventually(func() bool {
605+
vol, err := LVMClient.WithNamespace(OpenEBSNamespace).
606+
Get(volName, metav1.GetOptions{})
607+
gomega.Expect(err).To(gomega.BeNil(), "while fetching the lvm volume {%s}", volName)
608+
return vol.Status.State == "Ready"
609+
}, 120, 10).
610+
Should(gomega.BeTrue())
611+
gomega.Expect(status).To(gomega.Equal(true), "expecting a lvmvol resource to be ready")
612+
}
613+
614+
func scaleControllerPlugin(num int32) int32 {
615+
ginkgo.By(fmt.Sprintf( "scaling controller plugin statefulset %v to size %v", controllerStatefulSet, num))
616+
617+
scale, err := K8sClient.AppsV1().StatefulSets(metav1.NamespaceSystem).
618+
GetScale(context.Background(), controllerStatefulSet,metav1.GetOptions{})
619+
gomega.Expect(err).To(
620+
gomega.BeNil(),
621+
"fetch current replica of stateful set %v", controllerStatefulSet)
622+
existingReplicas := scale.Spec.Replicas
623+
624+
if scale.Spec.Replicas == num {
625+
return existingReplicas
626+
}
627+
scale.Spec.Replicas = num
628+
scale, err = K8sClient.AppsV1().StatefulSets(metav1.NamespaceSystem).
629+
UpdateScale(context.Background(), controllerStatefulSet, scale, metav1.UpdateOptions{})
630+
gomega.Expect(err).To(
631+
gomega.BeNil(),
632+
"update replicas of stateful set %v to %v", controllerStatefulSet, num)
633+
634+
scaled := gomega.Eventually(func() bool {
635+
scale, err = K8sClient.AppsV1().StatefulSets(metav1.NamespaceSystem).
636+
GetScale(context.Background(), controllerStatefulSet,metav1.GetOptions{})
637+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
638+
return scale.Spec.Replicas == num
639+
}, 120, 10).
640+
Should(gomega.BeTrue())
641+
gomega.Expect(scaled).To(gomega.BeTrue(),
642+
"failed to scale up stateful set %v to size %v", controllerStatefulSet, num)
643+
return existingReplicas
644+
}
645+
646+
func deleteNodeDaemonSet() *appsv1.DaemonSet {
647+
csiNodes, err := K8sClient.StorageV1().CSINodes().List(context.Background(), metav1.ListOptions{})
648+
gomega.Expect(err).To(
649+
gomega.BeNil(), "fetching csi node")
650+
if len(csiNodes.Items) == 0 {
651+
err = fmt.Errorf("expecting non-zero csi nodes in the cluster")
652+
gomega.Expect(err).To(gomega.BeNil())
653+
}
654+
csiNode := csiNodes.Items[0]
655+
656+
ginkgo.By("deleting node plugin daemonset " + nodeDaemonSet)
657+
ds, err := K8sClient.AppsV1().
658+
DaemonSets(metav1.NamespaceSystem).
659+
Get(context.Background(), nodeDaemonSet, metav1.GetOptions{})
660+
gomega.Expect(err).To(
661+
gomega.BeNil(),
662+
"fetching node plugin daemonset %v", nodeDaemonSet)
663+
policy := metav1.DeletePropagationForeground
664+
err = K8sClient.AppsV1().
665+
DaemonSets(metav1.NamespaceSystem).
666+
Delete(context.Background(), nodeDaemonSet, metav1.DeleteOptions{
667+
PropagationPolicy: &policy,
668+
})
669+
gomega.Expect(err).To(
670+
gomega.BeNil(),
671+
"deleting node plugin daemonset %v", nodeDaemonSet)
672+
673+
ginkgo.By("waiting for deletion of node plugin pods")
674+
status := gomega.Eventually(func() bool {
675+
_, err = K8sClient.AppsV1().
676+
DaemonSets(metav1.NamespaceSystem).
677+
Get(context.Background(), nodeDaemonSet, metav1.GetOptions{})
678+
return k8serrors.IsNotFound(err)
679+
}, 120, 10).Should(gomega.BeTrue())
680+
gomega.Expect(status).To(gomega.Equal(true),
681+
"waiting for deletion of node plugin daemonset")
682+
683+
684+
// update the underlying csi node resource to ensure pvc gets scheduled
685+
// by external provisioner.
686+
ginkgo.By("patching csinode resource")
687+
newCSINode, err := K8sClient.StorageV1().CSINodes().Get(context.Background(), csiNode.GetName(), metav1.GetOptions{})
688+
gomega.Expect(err).To(
689+
gomega.BeNil(), "fetching updated csi node")
690+
newCSINode.Spec.Drivers = csiNode.Spec.Drivers
691+
_, err = K8sClient.StorageV1().CSINodes().Update(context.Background(), newCSINode, metav1.UpdateOptions{})
692+
gomega.Expect(err).To(
693+
gomega.BeNil(), "updating csi node %v", csiNode.GetName())
694+
695+
return ds
696+
}
697+
698+
func createNodeDaemonSet(ds *appsv1.DaemonSet) {
699+
ds.SetResourceVersion("") // reset the resource version for creation.
700+
ginkgo.By("creating node plugin daemonset " + nodeDaemonSet)
701+
_, err := K8sClient.AppsV1().
702+
DaemonSets(metav1.NamespaceSystem).
703+
Create(context.Background(), ds, metav1.CreateOptions{})
704+
gomega.Expect(err).To(
705+
gomega.BeNil(),
706+
"creating node plugin daemonset %v", nodeDaemonSet)
707+
}

0 commit comments

Comments
 (0)