@@ -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})
0 commit comments