@@ -277,6 +277,93 @@ var _ = Describe("DatasetReconciler (fake client)", func() {
277277 Expect (err ).To (HaveOccurred ())
278278 Expect (result ).To (Equal (ctrl.Result {}))
279279 })
280+
281+ It ("sets FailedDatasetPhase and stops requeue when dataset has multiple mounts and one has root path" , func () {
282+ ds := datav1alpha1.Dataset {
283+ ObjectMeta : metav1.ObjectMeta {
284+ Name : "multi-root" ,
285+ Namespace : "default" ,
286+ Finalizers : []string {finalizer },
287+ },
288+ Spec : datav1alpha1.DatasetSpec {
289+ Mounts : []datav1alpha1.Mount {
290+ {Name : "m1" , MountPoint : "local:///path1" , Path : "/" },
291+ {Name : "m2" , MountPoint : "local:///path2" , Path : "/path2" },
292+ },
293+ },
294+ Status : datav1alpha1.DatasetStatus {Phase : datav1alpha1 .NotBoundDatasetPhase },
295+ }
296+ r := newTestReconciler (& ds )
297+ ctx := makeReconcileCtx (r , ds )
298+
299+ result , err := r .reconcileDataset (ctx , false )
300+ // NoRequeue: no error, empty result
301+ Expect (err ).NotTo (HaveOccurred ())
302+ Expect (result ).To (Equal (ctrl.Result {}))
303+
304+ // Verify status phase is set to FailedDatasetPhase
305+ stored := & datav1alpha1.Dataset {}
306+ Expect (r .Get (ctx , types.NamespacedName {Namespace : "default" , Name : "multi-root" }, stored )).To (Succeed ())
307+ Expect (stored .Status .Phase ).To (Equal (datav1alpha1 .FailedDatasetPhase ))
308+ })
309+
310+ It ("catches implicit root path when mount.Path and mount.Name are both empty" , func () {
311+ ds := datav1alpha1.Dataset {
312+ ObjectMeta : metav1.ObjectMeta {
313+ Name : "implicit-root" ,
314+ Namespace : "default" ,
315+ Finalizers : []string {finalizer },
316+ },
317+ Spec : datav1alpha1.DatasetSpec {
318+ Mounts : []datav1alpha1.Mount {
319+ {MountPoint : "local:///path1" },
320+ {Name : "m2" , MountPoint : "local:///path2" , Path : "/path2" },
321+ },
322+ },
323+ Status : datav1alpha1.DatasetStatus {Phase : datav1alpha1 .NotBoundDatasetPhase },
324+ }
325+ r := newTestReconciler (& ds )
326+ ctx := makeReconcileCtx (r , ds )
327+
328+ result , err := r .reconcileDataset (ctx , false )
329+ Expect (err ).NotTo (HaveOccurred ())
330+ Expect (result ).To (Equal (ctrl.Result {}))
331+
332+ stored := & datav1alpha1.Dataset {}
333+ Expect (r .Get (ctx , types.NamespacedName {Namespace : "default" , Name : "implicit-root" }, stored )).To (Succeed ())
334+ Expect (stored .Status .Phase ).To (Equal (datav1alpha1 .FailedDatasetPhase ))
335+ })
336+
337+ It ("allows deletion of a dataset that was edited into an invalid multi-mount config" , func () {
338+ now := metav1 .Now ()
339+ ds := datav1alpha1.Dataset {
340+ ObjectMeta : metav1.ObjectMeta {
341+ Name : "del-invalid" ,
342+ Namespace : "default" ,
343+ Finalizers : []string {finalizer },
344+ DeletionTimestamp : & now ,
345+ },
346+ Spec : datav1alpha1.DatasetSpec {
347+ Mounts : []datav1alpha1.Mount {
348+ {Name : "m1" , MountPoint : "local:///path1" , Path : "/" },
349+ {Name : "m2" , MountPoint : "local:///path2" , Path : "/path2" },
350+ },
351+ },
352+ }
353+ r := newTestReconciler (& ds )
354+ ctx := makeReconcileCtx (r , ds )
355+
356+ result , err := r .reconcileDataset (ctx , false )
357+ // Should proceed to deletion, not block on validation
358+ Expect (err ).NotTo (HaveOccurred ())
359+ Expect (result ).To (Equal (ctrl.Result {}))
360+
361+ // Assert the finalizer is removed, demonstrating that the Terminating-stuck regression is fixed.
362+ // Once the finalizer is removed on an object with a deletion timestamp, the API server (or mock client) deletes it.
363+ stored := & datav1alpha1.Dataset {}
364+ getErr := r .Get (ctx , types.NamespacedName {Namespace : "default" , Name : "del-invalid" }, stored )
365+ Expect (apierrors .IsNotFound (getErr )).To (BeTrue ())
366+ })
280367 })
281368
282369 Describe ("reconcileDatasetDeletion" , func () {
0 commit comments