Skip to content

Commit 214b762

Browse files
committed
test: Add integration tests for restore feature
A new tests that verifies the workspace is created from a backup. It checks if a deployment is ready and if it contains a new restore init container with proper configuration. There are 2 tests - one focused on common pvc and other that have per-workspace storage. Signed-off-by: Ales Raszka <araszka@redhat.com>
1 parent 9a876c1 commit 214b762

5 files changed

Lines changed: 213 additions & 4 deletions

File tree

controllers/workspace/devworkspace_controller_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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() {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
controller.devfile.io/restore-workspace: 'true'
13+
projects:
14+
- name: web-nodejs-sample
15+
git:
16+
remotes:
17+
origin: "https://github.com/che-samples/web-nodejs-sample.git"
18+
components:
19+
- name: web-terminal
20+
container:
21+
image: quay.io/wto/web-terminal-tooling:latest
22+
memoryLimit: 512Mi
23+
mountSources: true
24+
command:
25+
- "tail"
26+
- "-f"
27+
- "/dev/null"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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: per-workspace
12+
controller.devfile.io/restore-workspace: 'true'
13+
projects:
14+
- name: web-nodejs-sample
15+
git:
16+
remotes:
17+
origin: "https://github.com/che-samples/web-nodejs-sample.git"
18+
components:
19+
- volume:
20+
size: 1Gi
21+
name: projects
22+
- name: web-terminal
23+
container:
24+
image: quay.io/wto/web-terminal-tooling:latest
25+
memoryLimit: 512Mi
26+
mountSources: true
27+
command:
28+
- "tail"
29+
- "-f"
30+
- "/dev/null"

pkg/library/projects/clone.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import (
3131
)
3232

3333
const (
34-
projectClonerContainerName = "project-clone"
34+
ProjectClonerContainerName = "project-clone"
3535
)
3636

3737
type Options struct {
@@ -118,7 +118,7 @@ func GetProjectCloneInitContainer(workspace *dw.DevWorkspaceTemplateSpec, option
118118
}
119119

120120
return &corev1.Container{
121-
Name: projectClonerContainerName,
121+
Name: ProjectClonerContainerName,
122122
Image: cloneImage,
123123
Env: options.Env,
124124
Resources: *resources,

pkg/library/restore/restore.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import (
3333
)
3434

3535
const (
36-
workspaceRestoreContainerName = "workspace-restore"
36+
WorkspaceRestoreContainerName = "workspace-restore"
3737
)
3838

3939
type Options struct {
@@ -103,7 +103,7 @@ func GetWorkspaceRestoreInitContainer(
103103
}...)
104104

105105
return &corev1.Container{
106-
Name: workspaceRestoreContainerName,
106+
Name: WorkspaceRestoreContainerName,
107107
Image: restoreImage,
108108
Command: []string{"/workspace-recovery.sh"},
109109
Args: []string{"--restore"},

0 commit comments

Comments
 (0)