Skip to content

Commit fda3bc6

Browse files
committed
Add controller tests to cover deleting workspaces and finalizers
Signed-off-by: Angel Misevski <[email protected]>
1 parent aab9c87 commit fda3bc6

File tree

3 files changed

+184
-0
lines changed

3 files changed

+184
-0
lines changed

controllers/workspace/devworkspace_controller_test.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
. "github.com/onsi/ginkgo/v2"
3232
. "github.com/onsi/gomega"
3333
appsv1 "k8s.io/api/apps/v1"
34+
batchv1 "k8s.io/api/batch/v1"
3435
corev1 "k8s.io/api/core/v1"
3536
rbacv1 "k8s.io/api/rbac/v1"
3637
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
@@ -748,6 +749,144 @@ var _ = Describe("DevWorkspace Controller", func() {
748749
return currDW.Spec.Started, nil
749750
}, timeout, interval).Should(BeFalse(), "DevWorkspace should have spec.started = false")
750751
})
752+
})
753+
754+
Context("Deleting DevWorkspaces", func() {
755+
const testURL = "test-url"
756+
const altDevWorkspaceName = "test-devworkspace-2"
757+
758+
BeforeEach(func() {
759+
By("Setting up HTTP client")
760+
workspacecontroller.SetupHttpClientsForTesting(&http.Client{
761+
Transport: &testutil.TestRoundTripper{
762+
Data: map[string]testutil.TestResponse{
763+
fmt.Sprintf("%s/healthz", testURL): {
764+
StatusCode: http.StatusOK,
765+
},
766+
},
767+
},
768+
})
769+
})
770+
771+
AfterEach(func() {
772+
By("Deleting DevWorkspaces from test")
773+
deleteDevWorkspace(devWorkspaceName)
774+
deleteDevWorkspace(altDevWorkspaceName)
775+
cleanupPVC("claim-devworkspace")
776+
By("Resetting HTTP client")
777+
workspacecontroller.SetupHttpClientsForTesting(getBasicTestHttpClient())
778+
})
779+
780+
It("Cleans up workspace PVC storage when other workspaces exist", func() {
781+
By("Creating multiple DevWorkspaces")
782+
createStartedDevWorkspace(devWorkspaceName, "common-pvc-test-devworkspace.yaml")
783+
createStartedDevWorkspace(altDevWorkspaceName, "common-pvc-test-devworkspace.yaml")
784+
devworkspace := getExistingDevWorkspace(devWorkspaceName)
785+
Expect(devworkspace.Finalizers).Should(ContainElement(constants.StorageCleanupFinalizer), "DevWorkspace should get storage cleanup finalizer")
786+
787+
By("Deleting existing workspace")
788+
Expect(k8sClient.Delete(ctx, devworkspace)).Should(Succeed())
789+
790+
By("Check that cleanup job is created")
791+
cleanupJob := &batchv1.Job{}
792+
Eventually(func() error {
793+
return k8sClient.Get(ctx, namespacedName(common.PVCCleanupJobName(devworkspace.Status.DevWorkspaceId), testNamespace), cleanupJob)
794+
}, timeout, interval).Should(Succeed(), "Cleanup job should be created when workspace is deleted")
795+
expectedOwnerReference := devworkspaceOwnerRef(devworkspace)
796+
Expect(cleanupJob.OwnerReferences).Should(ContainElement(expectedOwnerReference), "Routing should be owned by DevWorkspace")
797+
Expect(cleanupJob.Labels[constants.DevWorkspaceIDLabel]).Should(Equal(devworkspace.Status.DevWorkspaceId), "Object should be labelled with DevWorkspace ID")
798+
799+
By("Marking Job as successfully completed")
800+
cleanupJob.Status.Succeeded = 1
801+
cleanupJob.Status.Conditions = []batchv1.JobCondition{
802+
{
803+
Type: batchv1.JobComplete,
804+
Status: corev1.ConditionTrue,
805+
},
806+
}
807+
Expect(k8sClient.Status().Update(ctx, cleanupJob)).Should(Succeed(), "Failed to update cleanup job")
808+
809+
By("Checking that workspace is deleted")
810+
currDW := &dw.DevWorkspace{}
811+
Eventually(func() (exists bool) {
812+
err := k8sClient.Get(ctx, namespacedName(devWorkspaceName, testNamespace), currDW)
813+
return err != nil && k8sErrors.IsNotFound(err)
814+
}, timeout, interval).Should(BeTrue(), "Finalizer should be cleared and workspace should be deleted")
815+
})
751816

817+
It("Marks workspace as Errored when cleanup Job fails", func() {
818+
By("Creating multiple DevWorkspaces")
819+
createStartedDevWorkspace(devWorkspaceName, "common-pvc-test-devworkspace.yaml")
820+
createStartedDevWorkspace(altDevWorkspaceName, "common-pvc-test-devworkspace.yaml")
821+
devworkspace := getExistingDevWorkspace(devWorkspaceName)
822+
Expect(devworkspace.Finalizers).Should(ContainElement(constants.StorageCleanupFinalizer), "DevWorkspace should get storage cleanup finalizer")
823+
824+
By("Deleting existing workspace")
825+
Expect(k8sClient.Delete(ctx, devworkspace)).Should(Succeed())
826+
827+
By("Check that cleanup job is created")
828+
cleanupJob := &batchv1.Job{}
829+
Eventually(func() error {
830+
return k8sClient.Get(ctx, namespacedName(common.PVCCleanupJobName(devworkspace.Status.DevWorkspaceId), testNamespace), cleanupJob)
831+
}, timeout, interval).Should(Succeed(), "Cleanup job should be created when workspace is deleted")
832+
expectedOwnerReference := devworkspaceOwnerRef(devworkspace)
833+
Expect(cleanupJob.OwnerReferences).Should(ContainElement(expectedOwnerReference), "Routing should be owned by DevWorkspace")
834+
Expect(cleanupJob.Labels[constants.DevWorkspaceIDLabel]).Should(Equal(devworkspace.Status.DevWorkspaceId), "Object should be labelled with DevWorkspace ID")
835+
836+
By("Marking Job as failed")
837+
cleanupJob.Status.Conditions = []batchv1.JobCondition{
838+
{
839+
Type: batchv1.JobFailed,
840+
Status: corev1.ConditionTrue,
841+
},
842+
}
843+
Expect(k8sClient.Status().Update(ctx, cleanupJob)).Should(Succeed(), "Failed to update cleanup job")
844+
845+
By("Checking that workspace is not deleted and ends up in error state")
846+
currDW := &dw.DevWorkspace{}
847+
Eventually(func() (dw.DevWorkspacePhase, error) {
848+
if err := k8sClient.Get(ctx, namespacedName(devWorkspaceName, testNamespace), currDW); err != nil {
849+
return "", err
850+
}
851+
return currDW.Status.Phase, nil
852+
}, timeout, interval).Should(Equal(dw.DevWorkspaceStatusError), "DevWorkspace should enter error phase")
853+
Expect(currDW.Finalizers).Should(ContainElement(constants.StorageCleanupFinalizer))
854+
})
855+
856+
It("Deletes shared PVC and clears finalizers when all workspaces are deleted", func() {
857+
By("Creating DevWorkspaces")
858+
createStartedDevWorkspace(devWorkspaceName, "common-pvc-test-devworkspace.yaml")
859+
devworkspace1 := getExistingDevWorkspace(devWorkspaceName)
860+
Expect(devworkspace1.Finalizers).Should(ContainElement(constants.StorageCleanupFinalizer), "DevWorkspace should get storage cleanup finalizer")
861+
862+
createStartedDevWorkspace(altDevWorkspaceName, "common-pvc-test-devworkspace.yaml")
863+
devworkspace2 := getExistingDevWorkspace(altDevWorkspaceName)
864+
Expect(devworkspace2.Finalizers).Should(ContainElement(constants.StorageCleanupFinalizer), "DevWorkspace should get storage cleanup finalizer")
865+
866+
By("Deleting existing workspaces")
867+
Expect(k8sClient.Delete(ctx, devworkspace1)).Should(Succeed())
868+
Expect(k8sClient.Delete(ctx, devworkspace2)).Should(Succeed())
869+
870+
pvc := &corev1.PersistentVolumeClaim{}
871+
Eventually(func() (deleted bool, err error) {
872+
if err := k8sClient.Get(ctx, namespacedName("claim-devworkspace", testNamespace), pvc); err != nil {
873+
return false, err
874+
}
875+
return pvc.DeletionTimestamp != nil, nil
876+
}, timeout, interval).Should(BeTrue(), "Shared PVC should be deleted")
877+
878+
By(fmt.Sprintf("Checking that devworkspace %s is deleted", devWorkspaceName))
879+
currDW := &dw.DevWorkspace{}
880+
Eventually(func() (exists bool) {
881+
err := k8sClient.Get(ctx, namespacedName(devWorkspaceName, testNamespace), currDW)
882+
return err != nil && k8sErrors.IsNotFound(err)
883+
}, timeout, interval).Should(BeTrue(), "Finalizer should be cleared and workspace should be deleted")
884+
885+
By(fmt.Sprintf("Checking that devworkspace %s is deleted", altDevWorkspaceName))
886+
Eventually(func() (exists bool) {
887+
err := k8sClient.Get(ctx, namespacedName(altDevWorkspaceName, testNamespace), currDW)
888+
return err != nil && k8sErrors.IsNotFound(err)
889+
}, timeout, interval).Should(BeTrue(), "Finalizer should be cleared and workspace should be deleted")
890+
})
752891
})
753892
})
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
kind: DevWorkspace
2+
apiVersion: workspace.devfile.io/v1alpha2
3+
metadata:
4+
labels:
5+
controller.devfile.io/creator: ""
6+
spec:
7+
started: true
8+
routingClass: 'basic'
9+
template:
10+
attributes:
11+
controller.devfile.io/storage-type: common
12+
projects:
13+
- name: web-nodejs-sample
14+
git:
15+
remotes:
16+
origin: "https://github.com/che-samples/web-nodejs-sample.git"
17+
components:
18+
- name: web-terminal
19+
container:
20+
image: quay.io/wto/web-terminal-tooling:latest
21+
memoryLimit: 512Mi
22+
mountSources: true
23+
command:
24+
- "tail"
25+
- "-f"
26+
- "/dev/null"

controllers/workspace/util_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,25 @@ func deleteDevWorkspace(name string) {
133133
}, 10*time.Second, 250*time.Millisecond).Should(BeTrue(), "DevWorkspace not deleted after timeout")
134134
}
135135

136+
func cleanupPVC(name string) {
137+
By("Cleaning up shared PVC")
138+
pvc := &corev1.PersistentVolumeClaim{}
139+
pvcNN := namespacedName(name, testNamespace)
140+
Eventually(func() error {
141+
err := k8sClient.Get(ctx, pvcNN, pvc)
142+
if k8sErrors.IsNotFound(err) {
143+
return nil
144+
}
145+
// Need to clear finalizers to allow PVC to be cleaned up
146+
pvc.Finalizers = nil
147+
err = k8sClient.Update(ctx, pvc)
148+
if err == nil {
149+
return err
150+
}
151+
return fmt.Errorf("PVC not deleted yet")
152+
}, timeout, interval).Should(Succeed(), "PVC should be deleted")
153+
}
154+
136155
func createObject(obj crclient.Object) {
137156
Expect(k8sClient.Create(ctx, obj)).Should(Succeed())
138157
Eventually(func() error {

0 commit comments

Comments
 (0)