@@ -28,6 +28,8 @@ import (
2828 "github.com/devfile/devworkspace-operator/pkg/conditions"
2929 "github.com/devfile/devworkspace-operator/pkg/config"
3030 "github.com/devfile/devworkspace-operator/pkg/constants"
31+ "github.com/devfile/devworkspace-operator/pkg/library/projects"
32+ "github.com/devfile/devworkspace-operator/pkg/library/restore"
3133 . "github.com/onsi/ginkgo/v2"
3234 . "github.com/onsi/gomega"
3335 appsv1 "k8s.io/api/apps/v1"
@@ -36,6 +38,7 @@ import (
3638 rbacv1 "k8s.io/api/rbac/v1"
3739 k8sErrors "k8s.io/apimachinery/pkg/api/errors"
3840 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
41+ "k8s.io/utils/ptr"
3942 "sigs.k8s.io/controller-runtime/pkg/client"
4043 "sigs.k8s.io/yaml"
4144)
@@ -1024,6 +1027,155 @@ var _ = Describe("DevWorkspace Controller", func() {
10241027 })
10251028 })
10261029
1030+ Context ("Workspace Restore" , func () {
1031+ const testURL = "test-url"
1032+
1033+ BeforeEach (func () {
1034+ workspacecontroller .SetupHttpClientsForTesting (& http.Client {
1035+ Transport : & testutil.TestRoundTripper {
1036+ Data : map [string ]testutil.TestResponse {
1037+ fmt .Sprintf ("%s/healthz" , testURL ): {
1038+ StatusCode : http .StatusOK ,
1039+ },
1040+ },
1041+ },
1042+ })
1043+ })
1044+
1045+ AfterEach (func () {
1046+ deleteDevWorkspace (devWorkspaceName )
1047+ workspacecontroller .SetupHttpClientsForTesting (getBasicTestHttpClient ())
1048+ })
1049+
1050+ It ("Restores workspace from backup with common PVC" , func () {
1051+ config .SetGlobalConfigForTesting (& controllerv1alpha1.OperatorConfiguration {
1052+ Workspace : & controllerv1alpha1.WorkspaceConfig {
1053+ BackupCronJob : & controllerv1alpha1.BackupCronJobConfig {
1054+ Enable : ptr.To [bool ](true ),
1055+ Registry : & controllerv1alpha1.RegistryConfig {
1056+ Path : "localhost:5000" ,
1057+ },
1058+ },
1059+ },
1060+ })
1061+ defer config .SetGlobalConfigForTesting (nil )
1062+ By ("Reading DevWorkspace with restore configuration from testdata file" )
1063+ createDevWorkspace (devWorkspaceName , "restore-workspace-common.yaml" )
1064+ devworkspace := getExistingDevWorkspace (devWorkspaceName )
1065+ workspaceID := devworkspace .Status .DevWorkspaceId
1066+
1067+ By ("Waiting for DevWorkspaceRouting to be created" )
1068+ dwr := & controllerv1alpha1.DevWorkspaceRouting {}
1069+ dwrName := common .DevWorkspaceRoutingName (workspaceID )
1070+ Eventually (func () error {
1071+ return k8sClient .Get (ctx , namespacedName (dwrName , testNamespace ), dwr )
1072+ }, timeout , interval ).Should (Succeed (), "DevWorkspaceRouting should be created" )
1073+
1074+ By ("Manually making Routing ready to continue" )
1075+ markRoutingReady (testURL , common .DevWorkspaceRoutingName (workspaceID ))
1076+
1077+ By ("Setting the deployment to have 1 ready replica" )
1078+ markDeploymentReady (common .DeploymentName (devworkspace .Status .DevWorkspaceId ))
1079+
1080+ deployment := & appsv1.Deployment {}
1081+ err := k8sClient .Get (ctx , namespacedName (devworkspace .Status .DevWorkspaceId , devworkspace .Namespace ), deployment )
1082+ Expect (err ).ToNot (HaveOccurred (), "Failed to get DevWorkspace deployment" )
1083+
1084+ initContainers := deployment .Spec .Template .Spec .InitContainers
1085+ Expect (len (initContainers )).To (BeNumerically (">" , 0 ), "No initContainers found in deployment" )
1086+
1087+ var restoreInitContainer corev1.Container
1088+ var cloneInitContainer corev1.Container
1089+ for _ , container := range initContainers {
1090+ if container .Name == restore .WorkspaceRestoreContainerName {
1091+ restoreInitContainer = container
1092+ }
1093+ if container .Name == projects .ProjectClonerContainerName {
1094+ cloneInitContainer = container
1095+ }
1096+ }
1097+ // Expect(initContainers).To(BeEmpty(), "Init containers should be present in deployment")
1098+ Expect (cloneInitContainer .Name ).To (BeEmpty (), "Project clone init container should be omitted when restoring from backup" )
1099+ Expect (restoreInitContainer ).ToNot (BeNil (), "Workspace restore init container should not be nil" )
1100+ Expect (restoreInitContainer .Name ).To (Equal (restore .WorkspaceRestoreContainerName ), "Workspace restore init container should be present in deployment" )
1101+
1102+ Expect (restoreInitContainer .Command ).To (Equal ([]string {"/workspace-recovery.sh" }), "Restore init container should have correct command" )
1103+ Expect (restoreInitContainer .Args ).To (Equal ([]string {"--restore" }), "Restore init container should have correct args" )
1104+ Expect (restoreInitContainer .VolumeMounts ).To (ContainElement (corev1.VolumeMount {
1105+ Name : "claim-devworkspace" , // PVC name for common storage
1106+ MountPath : constants .DefaultProjectsSourcesRoot ,
1107+ ReadOnly : false ,
1108+ SubPath : workspaceID + "/projects" , // Dynamic workspace ID + projects
1109+ SubPathExpr : "" ,
1110+ }), "Restore init container should have workspace storage volume mounted at correct path" )
1111+
1112+ })
1113+ It ("Restores workspace from backup with per-workspace PVC" , func () {
1114+ config .SetGlobalConfigForTesting (& controllerv1alpha1.OperatorConfiguration {
1115+ Workspace : & controllerv1alpha1.WorkspaceConfig {
1116+ BackupCronJob : & controllerv1alpha1.BackupCronJobConfig {
1117+ Enable : ptr.To [bool ](true ),
1118+ Registry : & controllerv1alpha1.RegistryConfig {
1119+ Path : "localhost:5000" ,
1120+ },
1121+ },
1122+ },
1123+ })
1124+ defer config .SetGlobalConfigForTesting (nil )
1125+ By ("Reading DevWorkspace with restore configuration from testdata file" )
1126+ createDevWorkspace (devWorkspaceName , "restore-workspace-perworkspace.yaml" )
1127+ devworkspace := getExistingDevWorkspace (devWorkspaceName )
1128+ workspaceID := devworkspace .Status .DevWorkspaceId
1129+
1130+ By ("Waiting for DevWorkspaceRouting to be created" )
1131+ dwr := & controllerv1alpha1.DevWorkspaceRouting {}
1132+ dwrName := common .DevWorkspaceRoutingName (workspaceID )
1133+ Eventually (func () error {
1134+ return k8sClient .Get (ctx , namespacedName (dwrName , testNamespace ), dwr )
1135+ }, timeout , interval ).Should (Succeed (), "DevWorkspaceRouting should be created" )
1136+
1137+ By ("Manually making Routing ready to continue" )
1138+ markRoutingReady (testURL , common .DevWorkspaceRoutingName (workspaceID ))
1139+
1140+ By ("Setting the deployment to have 1 ready replica" )
1141+ markDeploymentReady (common .DeploymentName (devworkspace .Status .DevWorkspaceId ))
1142+
1143+ deployment := & appsv1.Deployment {}
1144+ err := k8sClient .Get (ctx , namespacedName (devworkspace .Status .DevWorkspaceId , devworkspace .Namespace ), deployment )
1145+ Expect (err ).ToNot (HaveOccurred (), "Failed to get DevWorkspace deployment" )
1146+
1147+ initContainers := deployment .Spec .Template .Spec .InitContainers
1148+ Expect (len (initContainers )).To (BeNumerically (">" , 0 ), "No initContainers found in deployment" )
1149+
1150+ var restoreInitContainer corev1.Container
1151+ var cloneInitContainer corev1.Container
1152+ for _ , container := range initContainers {
1153+ if container .Name == restore .WorkspaceRestoreContainerName {
1154+ restoreInitContainer = container
1155+ }
1156+ if container .Name == projects .ProjectClonerContainerName {
1157+ cloneInitContainer = container
1158+ }
1159+ }
1160+ // Expect(initContainers).To(BeEmpty(), "Init containers should be present in deployment")
1161+ Expect (cloneInitContainer .Name ).To (BeEmpty (), "Project clone init container should be omitted when restoring from backup" )
1162+ Expect (restoreInitContainer ).ToNot (BeNil (), "Workspace restore init container should not be nil" )
1163+ Expect (restoreInitContainer .Name ).To (Equal (restore .WorkspaceRestoreContainerName ), "Workspace restore init container should be present in deployment" )
1164+
1165+ Expect (restoreInitContainer .Command ).To (Equal ([]string {"/workspace-recovery.sh" }), "Restore init container should have correct command" )
1166+ Expect (restoreInitContainer .Args ).To (Equal ([]string {"--restore" }), "Restore init container should have correct args" )
1167+ Expect (restoreInitContainer .VolumeMounts ).To (ContainElement (corev1.VolumeMount {
1168+ Name : common .PerWorkspacePVCName (workspaceID ),
1169+ MountPath : constants .DefaultProjectsSourcesRoot ,
1170+ ReadOnly : false ,
1171+ SubPath : "projects" ,
1172+ SubPathExpr : "" ,
1173+ }), "Restore init container should have workspace storage volume mounted at correct path" )
1174+
1175+ })
1176+
1177+ })
1178+
10271179 Context ("Edge cases" , func () {
10281180
10291181 It ("Allows Kubernetes and Container components to share same target port on endpoint" , func () {
0 commit comments