@@ -38,6 +38,7 @@ import (
3838 dwv2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
3939 controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
4040 "github.com/devfile/devworkspace-operator/pkg/conditions"
41+ "github.com/devfile/devworkspace-operator/pkg/constants"
4142)
4243
4344var _ = Describe ("BackupCronJobReconciler" , func () {
@@ -152,6 +153,39 @@ var _ = Describe("BackupCronJobReconciler", func() {
152153 Expect (reconciler .cron .Entries ()).To (HaveLen (1 ))
153154 })
154155
156+ It ("Should stop cron if cron is disabled" , func () {
157+ enabled := true
158+ schedule := "* * * * *"
159+ dwoc := & controllerv1alpha1.DevWorkspaceOperatorConfig {
160+ ObjectMeta : metav1.ObjectMeta {Name : nameNamespace .Name , Namespace : nameNamespace .Namespace },
161+ Config : & controllerv1alpha1.OperatorConfiguration {
162+ Workspace : & controllerv1alpha1.WorkspaceConfig {
163+ BackupCronJob : & controllerv1alpha1.BackupCronJobConfig {
164+ Enable : & enabled ,
165+ Schedule : schedule ,
166+ Registry : & controllerv1alpha1.RegistryConfig {
167+ Path : "fake-registry" ,
168+ },
169+ },
170+ },
171+ },
172+ }
173+ Expect (fakeClient .Create (ctx , dwoc )).To (Succeed ())
174+
175+ result , err := reconciler .Reconcile (ctx , ctrl.Request {NamespacedName : nameNamespace })
176+ Expect (err ).ToNot (HaveOccurred ())
177+ Expect (result ).To (Equal (ctrl.Result {}))
178+ Expect (reconciler .cron .Entries ()).To (HaveLen (1 ))
179+
180+ disabled := false
181+ dwoc .Config .Workspace .BackupCronJob .Enable = & disabled
182+ Expect (fakeClient .Update (ctx , dwoc )).To (Succeed ())
183+ result , err = reconciler .Reconcile (ctx , ctrl.Request {NamespacedName : nameNamespace })
184+ Expect (err ).ToNot (HaveOccurred ())
185+ Expect (result ).To (Equal (ctrl.Result {}))
186+ Expect (reconciler .cron .Entries ()).To (HaveLen (0 ))
187+ })
188+
155189 It ("Should update cron schedule if DevWorkspaceOperatorConfig is updated" , func () {
156190 enabled := true
157191 schedule1 := "* * * * *"
@@ -188,6 +222,32 @@ var _ = Describe("BackupCronJobReconciler", func() {
188222 Expect (reconciler .cron .Entries ()[0 ].ID ).NotTo (Equal (entryID ))
189223 })
190224
225+ It ("Should stop cron schedule if cron value is invalid" , func () {
226+ enabled := true
227+ schedule1 := "invalid schedule"
228+ dwoc := & controllerv1alpha1.DevWorkspaceOperatorConfig {
229+ ObjectMeta : metav1.ObjectMeta {Name : nameNamespace .Name , Namespace : nameNamespace .Namespace },
230+ Config : & controllerv1alpha1.OperatorConfiguration {
231+ Workspace : & controllerv1alpha1.WorkspaceConfig {
232+ BackupCronJob : & controllerv1alpha1.BackupCronJobConfig {
233+ Enable : & enabled ,
234+ Schedule : schedule1 ,
235+ Registry : & controllerv1alpha1.RegistryConfig {
236+ Path : "fake-registry" ,
237+ },
238+ },
239+ },
240+ },
241+ }
242+ Expect (fakeClient .Create (ctx , dwoc )).To (Succeed ())
243+
244+ result , err := reconciler .Reconcile (ctx , ctrl.Request {NamespacedName : nameNamespace })
245+ Expect (err ).ToNot (HaveOccurred ())
246+ Expect (result ).To (Equal (ctrl.Result {}))
247+ Expect (reconciler .cron .Entries ()).To (HaveLen (0 ))
248+
249+ })
250+
191251 It ("Should stop cron if DevWorkspaceOperatorConfig is deleted" , func () {
192252 enabled := true
193253 schedule := "* * * * *"
@@ -223,6 +283,32 @@ var _ = Describe("BackupCronJobReconciler", func() {
223283 })
224284
225285 Context ("executeBackupSync" , func () {
286+ It ("should fail if registry secret does not exist" , func () {
287+ enabled := true
288+ schedule := "* * * * *"
289+ dwoc := & controllerv1alpha1.DevWorkspaceOperatorConfig {
290+ ObjectMeta : metav1.ObjectMeta {Name : nameNamespace .Name , Namespace : nameNamespace .Namespace },
291+ Config : & controllerv1alpha1.OperatorConfiguration {
292+ Workspace : & controllerv1alpha1.WorkspaceConfig {
293+ BackupCronJob : & controllerv1alpha1.BackupCronJobConfig {
294+ Enable : & enabled ,
295+ Schedule : schedule ,
296+ Registry : & controllerv1alpha1.RegistryConfig {
297+ Path : "fake-registry" ,
298+ AuthSecret : "non-existent" ,
299+ },
300+ },
301+ },
302+ },
303+ }
304+ dw := createDevWorkspace ("dw-recent" , "ns-a" , false , metav1 .NewTime (time .Now ().Add (- 10 * time .Minute )))
305+ dw .Status .Phase = dwv2 .DevWorkspaceStatusStopped
306+ dw .Status .DevWorkspaceId = "id-recent"
307+ Expect (fakeClient .Create (ctx , dw )).To (Succeed ())
308+
309+ Expect (reconciler .executeBackupSync (ctx , dwoc , log )).To (HaveOccurred ())
310+ })
311+
226312 It ("creates a Job for a DevWorkspace stopped with no previous backup" , func () {
227313 enabled := true
228314 schedule := "* * * * *"
@@ -253,6 +339,25 @@ var _ = Describe("BackupCronJobReconciler", func() {
253339 jobList := & batchv1.JobList {}
254340 Expect (fakeClient .List (ctx , jobList , & client.ListOptions {Namespace : dw .Namespace })).To (Succeed ())
255341 Expect (jobList .Items ).To (HaveLen (1 ))
342+ job := jobList .Items [0 ]
343+ Expect (job .Labels [constants .DevWorkspaceIDLabel ]).To (Equal ("id-recent" ))
344+ Expect (job .Spec .Template .Spec .ServiceAccountName ).To (Equal ("devworkspace-job-runner-id-recent" ))
345+ container := job .Spec .Template .Spec .Containers [0 ]
346+ expectedEnvs := []corev1.EnvVar {
347+ {Name : "DEVWORKSPACE_NAME" , Value : "dw-recent" },
348+ {Name : "DEVWORKSPACE_NAMESPACE" , Value : "ns-a" },
349+ {Name : "WORKSPACE_ID" , Value : "id-recent" },
350+ {Name : "BACKUP_SOURCE_PATH" , Value : "/workspace/id-recent/projects" },
351+ {Name : "DEVWORKSPACE_BACKUP_REGISTRY" , Value : "fake-registry" },
352+ {Name : "PODMAN_PUSH_OPTIONS" , Value : "--tls-verify=false" },
353+ }
354+ Expect (container .Env ).Should (ContainElements (expectedEnvs ), "container env vars should include vars neeeded for backup" )
355+
356+ expectedVolumeMounts := []corev1.VolumeMount {
357+ {MountPath : "/workspace" , Name : "workspace-data" },
358+ {MountPath : "/var/lib/containers" , Name : "build-storage" },
359+ }
360+ Expect (container .VolumeMounts ).Should (ContainElements (expectedVolumeMounts ), "container volume mounts should include mounts needed for backup" )
256361 })
257362
258363 It ("does not create a Job when the DevWorkspace was stopped beyond time range" , func () {
@@ -357,6 +462,58 @@ var _ = Describe("BackupCronJobReconciler", func() {
357462 Expect (jobList .Items ).To (HaveLen (1 ))
358463 })
359464 })
465+ Context ("ensureJobRunnerRBAC" , func () {
466+ It ("creates ServiceAccount for Job runner" , func () {
467+ dw := createDevWorkspace ("dw-rbac" , "ns-rbac" , false , metav1 .NewTime (time .Now ().Add (- 10 * time .Minute )))
468+ dw .Status .DevWorkspaceId = "id-rbac"
469+ Expect (fakeClient .Create (ctx , dw )).To (Succeed ())
470+
471+ err := reconciler .ensureJobRunnerRBAC (ctx , dw )
472+ Expect (err ).ToNot (HaveOccurred ())
473+
474+ sa := & corev1.ServiceAccount {}
475+ err = fakeClient .Get (ctx , types.NamespacedName {
476+ Name : "devworkspace-job-runner-id-rbac" ,
477+ Namespace : dw .Namespace ,
478+ }, sa )
479+ Expect (err ).ToNot (HaveOccurred ())
480+ Expect (sa .Labels ).To (HaveKeyWithValue (constants .DevWorkspaceIDLabel , "id-rbac" ))
481+ Expect (sa .Labels ).To (HaveKeyWithValue (constants .DevWorkspaceWatchSecretLabel , "true" ))
482+
483+ // Calling again should be idempotent
484+ err = reconciler .ensureJobRunnerRBAC (ctx , dw )
485+ Expect (err ).ToNot (HaveOccurred ())
486+ })
487+ })
488+ Context ("wasStoppedSinceLastBackup" , func () {
489+ It ("returns true if DevWorkspace was stopped since last backup" , func () {
490+ lastBackupTime := metav1 .NewTime (time .Now ().Add (- 30 * time .Minute ))
491+ workspaceStoppedTime := metav1 .NewTime (time .Now ().Add (- 20 * time .Minute ))
492+ dw := createDevWorkspace ("dw-test" , "ns-test" , false , workspaceStoppedTime )
493+ result := reconciler .wasStoppedSinceLastBackup (dw , & lastBackupTime , log )
494+ Expect (result ).To (BeTrue ())
495+ })
496+
497+ It ("returns false if DevWorkspace was stopped before last backup" , func () {
498+ lastBackupTime := metav1 .NewTime (time .Now ().Add (- 5 * time .Minute ))
499+ workspaceStoppedTime := metav1 .NewTime (time .Now ().Add (- 10 * time .Minute ))
500+ dw := createDevWorkspace ("dw-test" , "ns-test" , false , workspaceStoppedTime )
501+ result := reconciler .wasStoppedSinceLastBackup (dw , & lastBackupTime , log )
502+ Expect (result ).To (BeFalse ())
503+ })
504+ It ("returns true if there is no last backup time" , func () {
505+ dw := createDevWorkspace ("dw-test" , "ns-test" , false , metav1 .NewTime (time .Now ().Add (- 10 * time .Minute )))
506+ result := reconciler .wasStoppedSinceLastBackup (dw , nil , log )
507+ Expect (result ).To (BeTrue ())
508+ })
509+ It ("returns false if DevWorkspace is running" , func () {
510+ lastBackupTime := metav1 .NewTime (time .Now ().Add (- 30 * time .Minute ))
511+ workspaceStoppedTime := metav1 .NewTime (time .Now ().Add (- 20 * time .Minute ))
512+ dw := createDevWorkspace ("dw-test" , "ns-test" , true , workspaceStoppedTime )
513+ result := reconciler .wasStoppedSinceLastBackup (dw , & lastBackupTime , log )
514+ Expect (result ).To (BeFalse ())
515+ })
516+ })
360517
361518})
362519
@@ -385,6 +542,7 @@ func createDevWorkspace(name, namespace string, started bool, lastTransitionTime
385542 }
386543 if ! started {
387544 condition .Status = corev1 .ConditionFalse
545+ dw .Status .Phase = dwv2 .DevWorkspaceStatusStopped
388546 }
389547 dw .Status .Conditions = append (dw .Status .Conditions , condition )
390548 }
0 commit comments