From 01c08d08a3eda2e399ae896372515a564acc5c6f Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Wed, 15 Apr 2026 13:04:05 +0200 Subject: [PATCH 1/3] fix: auto-delete unassigned missing node usb devices Signed-off-by: Daniil Antoshin --- .../nodeusbdevice/nodeusbdevice_reconciler.go | 28 ++++++ .../nodeusbdevice_reconciler_test.go | 95 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler_test.go diff --git a/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler.go b/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler.go index a283879b2c..7950eeb75f 100644 --- a/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler.go @@ -21,6 +21,9 @@ import ( "fmt" "reflect" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -33,6 +36,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" "github.com/deckhouse/virtualization-controller/pkg/logger" "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/nodeusbdevicecondition" ) type Watcher interface { @@ -104,6 +108,13 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco changed := nodeUSBDevice.Changed() changed.Status.ObservedGeneration = changed.Generation + if shouldAutoDeleteNodeUSBDevice(changed) { + if err := r.client.Delete(ctx, changed); err != nil && !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to delete NodeUSBDevice: %w", err) + } + return nil + } + return nodeUSBDevice.Update(ctx) }) @@ -117,3 +128,20 @@ func (r *Reconciler) factory() *v1alpha2.NodeUSBDevice { func (r *Reconciler) statusGetter(obj *v1alpha2.NodeUSBDevice) v1alpha2.NodeUSBDeviceStatus { return obj.Status } + +func shouldAutoDeleteNodeUSBDevice(nodeUSBDevice *v1alpha2.NodeUSBDevice) bool { + if nodeUSBDevice == nil || nodeUSBDevice.GetDeletionTimestamp() != nil { + return false + } + + if nodeUSBDevice.Spec.AssignedNamespace != "" { + return false + } + + readyCondition := meta.FindStatusCondition(nodeUSBDevice.Status.Conditions, string(nodeusbdevicecondition.ReadyType)) + if readyCondition == nil { + return false + } + + return readyCondition.Status == metav1.ConditionFalse && readyCondition.Reason == string(nodeusbdevicecondition.NotFound) +} diff --git a/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler_test.go b/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler_test.go new file mode 100644 index 0000000000..2ac45b7591 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler_test.go @@ -0,0 +1,95 @@ +/* +Copyright 2026 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package nodeusbdevice + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/nodeusbdevicecondition" +) + +func TestShouldAutoDeleteNodeUSBDevice(t *testing.T) { + tests := []struct { + name string + nodeUSBDevice *v1alpha2.NodeUSBDevice + expectDelete bool + }{ + { + name: "delete unassigned not found device", + nodeUSBDevice: &v1alpha2.NodeUSBDevice{ + Spec: v1alpha2.NodeUSBDeviceSpec{AssignedNamespace: ""}, + Status: v1alpha2.NodeUSBDeviceStatus{Conditions: []metav1.Condition{{ + Type: string(nodeusbdevicecondition.ReadyType), + Status: metav1.ConditionFalse, + Reason: string(nodeusbdevicecondition.NotFound), + }}}, + }, + expectDelete: true, + }, + { + name: "keep assigned not found device", + nodeUSBDevice: &v1alpha2.NodeUSBDevice{ + Spec: v1alpha2.NodeUSBDeviceSpec{AssignedNamespace: "test-ns"}, + Status: v1alpha2.NodeUSBDeviceStatus{Conditions: []metav1.Condition{{ + Type: string(nodeusbdevicecondition.ReadyType), + Status: metav1.ConditionFalse, + Reason: string(nodeusbdevicecondition.NotFound), + }}}, + }, + expectDelete: false, + }, + { + name: "keep unassigned ready device", + nodeUSBDevice: &v1alpha2.NodeUSBDevice{ + Spec: v1alpha2.NodeUSBDeviceSpec{AssignedNamespace: ""}, + Status: v1alpha2.NodeUSBDeviceStatus{Conditions: []metav1.Condition{{ + Type: string(nodeusbdevicecondition.ReadyType), + Status: metav1.ConditionTrue, + Reason: string(nodeusbdevicecondition.Ready), + }}}, + }, + expectDelete: false, + }, + { + name: "keep device already deleting", + nodeUSBDevice: func() *v1alpha2.NodeUSBDevice { + now := metav1.Now() + return &v1alpha2.NodeUSBDevice{ + ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &now}, + Spec: v1alpha2.NodeUSBDeviceSpec{AssignedNamespace: ""}, + Status: v1alpha2.NodeUSBDeviceStatus{Conditions: []metav1.Condition{{ + Type: string(nodeusbdevicecondition.ReadyType), + Status: metav1.ConditionFalse, + Reason: string(nodeusbdevicecondition.NotFound), + }}}, + } + }(), + expectDelete: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if actual := shouldAutoDeleteNodeUSBDevice(tt.nodeUSBDevice); actual != tt.expectDelete { + t.Fatalf("expected delete=%v, got %v", tt.expectDelete, actual) + } + }) + } +} From 4c3ed1d8c1ab8abb7e92155e6a1394d7c13b7810 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Wed, 15 Apr 2026 13:13:15 +0200 Subject: [PATCH 2/3] fix: cleanup usb devices before auto-deleting nodeusbdevice Signed-off-by: Daniil Antoshin --- .../internal/handler/deletion.go | 30 ++++++ .../internal/handler/deletion_test.go | 39 ++++++-- .../nodeusbdevice/nodeusbdevice_reconciler.go | 28 ------ .../nodeusbdevice_reconciler_test.go | 95 ------------------- 4 files changed, 62 insertions(+), 130 deletions(-) delete mode 100644 images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler_test.go diff --git a/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion.go b/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion.go index e8138f422a..b650320705 100644 --- a/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion.go +++ b/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion.go @@ -21,13 +21,16 @@ import ( "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/controller/nodeusbdevice/internal/state" + "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/nodeusbdevicecondition" ) const ( @@ -56,6 +59,16 @@ func (h *DeletionHandler) Handle(ctx context.Context, s state.NodeUSBDeviceState switch { case current.GetDeletionTimestamp().IsZero(): + if shouldAutoDeleteNodeUSBDevice(current) { + if err := h.cleanupOwnedUSBDevices(ctx, current); err != nil { + return reconcile.Result{}, err + } + if err := h.client.Delete(ctx, current); err != nil && !apierrors.IsNotFound(err) { + return reconcile.Result{}, fmt.Errorf("failed to delete NodeUSBDevice: %w", err) + } + return reconcile.Result{}, reconciler.ErrStopHandlerChain + } + controllerutil.AddFinalizer(changed, v1alpha2.FinalizerNodeUSBDeviceCleanup) return reconcile.Result{}, nil @@ -92,3 +105,20 @@ func (h *DeletionHandler) cleanupOwnedUSBDevices(ctx context.Context, owner *v1a func (h *DeletionHandler) Name() string { return nameDeletionHandler } + +func shouldAutoDeleteNodeUSBDevice(nodeUSBDevice *v1alpha2.NodeUSBDevice) bool { + if nodeUSBDevice == nil || nodeUSBDevice.GetDeletionTimestamp() != nil { + return false + } + + if nodeUSBDevice.Spec.AssignedNamespace != "" { + return false + } + + readyCondition := meta.FindStatusCondition(nodeUSBDevice.Status.Conditions, string(nodeusbdevicecondition.ReadyType)) + if readyCondition == nil { + return false + } + + return readyCondition.Status == metav1.ConditionFalse && readyCondition.Reason == string(nodeusbdevicecondition.NotFound) +} diff --git a/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion_test.go b/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion_test.go index ec39f47379..103f286a3a 100644 --- a/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion_test.go +++ b/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion_test.go @@ -33,6 +33,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" "github.com/deckhouse/virtualization-controller/pkg/logger" "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/nodeusbdevicecondition" ) var _ = Describe("DeletionHandler", func() { @@ -43,11 +44,21 @@ var _ = Describe("DeletionHandler", func() { }) DescribeTable("Handle", - func(deleting, withOwnedUSB bool, usbNamespace string, expectFinalizerPresent, expectOwnedUSBDeleted bool) { + func(deleting, autoDelete, withOwnedUSB bool, assignedNamespace, usbNamespace string, expectFinalizerPresent, expectOwnedUSBDeleted, expectNodeDeleted bool) { scheme := apiruntime.NewScheme() Expect(v1alpha2.AddToScheme(scheme)).To(Succeed()) - node := &v1alpha2.NodeUSBDevice{ObjectMeta: metav1.ObjectMeta{Name: "usb-device-1", UID: "node-usb-uid"}} + node := &v1alpha2.NodeUSBDevice{ + ObjectMeta: metav1.ObjectMeta{Name: "usb-device-1", UID: "node-usb-uid"}, + Spec: v1alpha2.NodeUSBDeviceSpec{AssignedNamespace: assignedNamespace}, + } + if autoDelete { + node.Status.Conditions = []metav1.Condition{{ + Type: string(nodeusbdevicecondition.ReadyType), + Status: metav1.ConditionFalse, + Reason: string(nodeusbdevicecondition.NotFound), + }} + } if deleting { now := metav1.Now() node.DeletionTimestamp = &now @@ -83,7 +94,11 @@ var _ = Describe("DeletionHandler", func() { h := NewDeletionHandler(cl) st := state.New(cl, res) _, err := h.Handle(ctx, st) - Expect(err).NotTo(HaveOccurred()) + if expectNodeDeleted { + Expect(err).To(MatchError(reconciler.ErrStopHandlerChain)) + } else { + Expect(err).NotTo(HaveOccurred()) + } if expectFinalizerPresent { Expect(res.Changed().GetFinalizers()).To(ContainElement(v1alpha2.FinalizerNodeUSBDeviceCleanup)) @@ -100,10 +115,20 @@ var _ = Describe("DeletionHandler", func() { Expect(err).NotTo(HaveOccurred()) } } + + deletedNode := &v1alpha2.NodeUSBDevice{} + err = cl.Get(ctx, types.NamespacedName{Name: node.Name}, deletedNode) + if expectNodeDeleted { + Expect(err).To(HaveOccurred()) + } else { + Expect(err).NotTo(HaveOccurred()) + } }, - Entry("not deleting adds finalizer", false, false, "", true, false), - Entry("deleting removes finalizer and owned USB", true, true, "test-namespace", false, true), - Entry("deleting removes finalizer even without owned USB", true, false, "", false, false), - Entry("deleting removes owned USB in different namespace", true, true, "previous-namespace", false, true), + Entry("not deleting adds finalizer", false, false, false, "", "", true, false, false), + Entry("auto-delete cleans owned USB before deleting node object", false, true, true, "", "test-namespace", false, true, true), + Entry("assigned not found device is not auto-deleted", false, true, false, "test-namespace", "", true, false, false), + Entry("deleting removes finalizer and owned USB", true, false, true, "", "test-namespace", false, true, false), + Entry("deleting removes finalizer even without owned USB", true, false, false, "", "", false, false, false), + Entry("deleting removes owned USB in different namespace", true, false, true, "", "previous-namespace", false, true, false), ) }) diff --git a/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler.go b/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler.go index 7950eeb75f..a283879b2c 100644 --- a/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler.go @@ -21,9 +21,6 @@ import ( "fmt" "reflect" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -36,7 +33,6 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" "github.com/deckhouse/virtualization-controller/pkg/logger" "github.com/deckhouse/virtualization/api/core/v1alpha2" - "github.com/deckhouse/virtualization/api/core/v1alpha2/nodeusbdevicecondition" ) type Watcher interface { @@ -108,13 +104,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reco changed := nodeUSBDevice.Changed() changed.Status.ObservedGeneration = changed.Generation - if shouldAutoDeleteNodeUSBDevice(changed) { - if err := r.client.Delete(ctx, changed); err != nil && !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to delete NodeUSBDevice: %w", err) - } - return nil - } - return nodeUSBDevice.Update(ctx) }) @@ -128,20 +117,3 @@ func (r *Reconciler) factory() *v1alpha2.NodeUSBDevice { func (r *Reconciler) statusGetter(obj *v1alpha2.NodeUSBDevice) v1alpha2.NodeUSBDeviceStatus { return obj.Status } - -func shouldAutoDeleteNodeUSBDevice(nodeUSBDevice *v1alpha2.NodeUSBDevice) bool { - if nodeUSBDevice == nil || nodeUSBDevice.GetDeletionTimestamp() != nil { - return false - } - - if nodeUSBDevice.Spec.AssignedNamespace != "" { - return false - } - - readyCondition := meta.FindStatusCondition(nodeUSBDevice.Status.Conditions, string(nodeusbdevicecondition.ReadyType)) - if readyCondition == nil { - return false - } - - return readyCondition.Status == metav1.ConditionFalse && readyCondition.Reason == string(nodeusbdevicecondition.NotFound) -} diff --git a/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler_test.go b/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler_test.go deleted file mode 100644 index 2ac45b7591..0000000000 --- a/images/virtualization-artifact/pkg/controller/nodeusbdevice/nodeusbdevice_reconciler_test.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright 2026 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package nodeusbdevice - -import ( - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/deckhouse/virtualization/api/core/v1alpha2" - "github.com/deckhouse/virtualization/api/core/v1alpha2/nodeusbdevicecondition" -) - -func TestShouldAutoDeleteNodeUSBDevice(t *testing.T) { - tests := []struct { - name string - nodeUSBDevice *v1alpha2.NodeUSBDevice - expectDelete bool - }{ - { - name: "delete unassigned not found device", - nodeUSBDevice: &v1alpha2.NodeUSBDevice{ - Spec: v1alpha2.NodeUSBDeviceSpec{AssignedNamespace: ""}, - Status: v1alpha2.NodeUSBDeviceStatus{Conditions: []metav1.Condition{{ - Type: string(nodeusbdevicecondition.ReadyType), - Status: metav1.ConditionFalse, - Reason: string(nodeusbdevicecondition.NotFound), - }}}, - }, - expectDelete: true, - }, - { - name: "keep assigned not found device", - nodeUSBDevice: &v1alpha2.NodeUSBDevice{ - Spec: v1alpha2.NodeUSBDeviceSpec{AssignedNamespace: "test-ns"}, - Status: v1alpha2.NodeUSBDeviceStatus{Conditions: []metav1.Condition{{ - Type: string(nodeusbdevicecondition.ReadyType), - Status: metav1.ConditionFalse, - Reason: string(nodeusbdevicecondition.NotFound), - }}}, - }, - expectDelete: false, - }, - { - name: "keep unassigned ready device", - nodeUSBDevice: &v1alpha2.NodeUSBDevice{ - Spec: v1alpha2.NodeUSBDeviceSpec{AssignedNamespace: ""}, - Status: v1alpha2.NodeUSBDeviceStatus{Conditions: []metav1.Condition{{ - Type: string(nodeusbdevicecondition.ReadyType), - Status: metav1.ConditionTrue, - Reason: string(nodeusbdevicecondition.Ready), - }}}, - }, - expectDelete: false, - }, - { - name: "keep device already deleting", - nodeUSBDevice: func() *v1alpha2.NodeUSBDevice { - now := metav1.Now() - return &v1alpha2.NodeUSBDevice{ - ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &now}, - Spec: v1alpha2.NodeUSBDeviceSpec{AssignedNamespace: ""}, - Status: v1alpha2.NodeUSBDeviceStatus{Conditions: []metav1.Condition{{ - Type: string(nodeusbdevicecondition.ReadyType), - Status: metav1.ConditionFalse, - Reason: string(nodeusbdevicecondition.NotFound), - }}}, - } - }(), - expectDelete: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if actual := shouldAutoDeleteNodeUSBDevice(tt.nodeUSBDevice); actual != tt.expectDelete { - t.Fatalf("expected delete=%v, got %v", tt.expectDelete, actual) - } - }) - } -} From c6f44b53c4a3b9aba77965a5dd99630b9672ffe1 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Wed, 15 Apr 2026 13:16:12 +0200 Subject: [PATCH 3/3] fix: defer nodeusbdevice cleanup to deletion flow Signed-off-by: Daniil Antoshin --- .../internal/handler/deletion.go | 9 ++++---- .../internal/handler/deletion_test.go | 21 +++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion.go b/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion.go index b650320705..89808a41ed 100644 --- a/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion.go +++ b/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion.go @@ -59,17 +59,18 @@ func (h *DeletionHandler) Handle(ctx context.Context, s state.NodeUSBDeviceState switch { case current.GetDeletionTimestamp().IsZero(): + if !controllerutil.ContainsFinalizer(current, v1alpha2.FinalizerNodeUSBDeviceCleanup) { + controllerutil.AddFinalizer(changed, v1alpha2.FinalizerNodeUSBDeviceCleanup) + return reconcile.Result{}, nil + } + if shouldAutoDeleteNodeUSBDevice(current) { - if err := h.cleanupOwnedUSBDevices(ctx, current); err != nil { - return reconcile.Result{}, err - } if err := h.client.Delete(ctx, current); err != nil && !apierrors.IsNotFound(err) { return reconcile.Result{}, fmt.Errorf("failed to delete NodeUSBDevice: %w", err) } return reconcile.Result{}, reconciler.ErrStopHandlerChain } - controllerutil.AddFinalizer(changed, v1alpha2.FinalizerNodeUSBDeviceCleanup) return reconcile.Result{}, nil default: diff --git a/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion_test.go b/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion_test.go index 103f286a3a..5c83fb0009 100644 --- a/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion_test.go +++ b/images/virtualization-artifact/pkg/controller/nodeusbdevice/internal/handler/deletion_test.go @@ -44,7 +44,7 @@ var _ = Describe("DeletionHandler", func() { }) DescribeTable("Handle", - func(deleting, autoDelete, withOwnedUSB bool, assignedNamespace, usbNamespace string, expectFinalizerPresent, expectOwnedUSBDeleted, expectNodeDeleted bool) { + func(deleting, autoDelete, withOwnedUSB, withFinalizer bool, assignedNamespace, usbNamespace string, expectFinalizerPresent, expectOwnedUSBDeleted, expectNodeDeleted, expectStopChain bool) { scheme := apiruntime.NewScheme() Expect(v1alpha2.AddToScheme(scheme)).To(Succeed()) @@ -59,10 +59,12 @@ var _ = Describe("DeletionHandler", func() { Reason: string(nodeusbdevicecondition.NotFound), }} } + if withFinalizer { + node.Finalizers = []string{v1alpha2.FinalizerNodeUSBDeviceCleanup} + } if deleting { now := metav1.Now() node.DeletionTimestamp = &now - node.Finalizers = []string{v1alpha2.FinalizerNodeUSBDeviceCleanup} } objects := []client.Object{node} @@ -94,7 +96,7 @@ var _ = Describe("DeletionHandler", func() { h := NewDeletionHandler(cl) st := state.New(cl, res) _, err := h.Handle(ctx, st) - if expectNodeDeleted { + if expectStopChain { Expect(err).To(MatchError(reconciler.ErrStopHandlerChain)) } else { Expect(err).NotTo(HaveOccurred()) @@ -124,11 +126,12 @@ var _ = Describe("DeletionHandler", func() { Expect(err).NotTo(HaveOccurred()) } }, - Entry("not deleting adds finalizer", false, false, false, "", "", true, false, false), - Entry("auto-delete cleans owned USB before deleting node object", false, true, true, "", "test-namespace", false, true, true), - Entry("assigned not found device is not auto-deleted", false, true, false, "test-namespace", "", true, false, false), - Entry("deleting removes finalizer and owned USB", true, false, true, "", "test-namespace", false, true, false), - Entry("deleting removes finalizer even without owned USB", true, false, false, "", "", false, false, false), - Entry("deleting removes owned USB in different namespace", true, false, true, "", "previous-namespace", false, true, false), + Entry("not deleting adds finalizer", false, false, false, false, "", "", true, false, false, false), + Entry("auto-delete first adds finalizer without deleting node object", false, true, true, false, "", "test-namespace", true, false, false, false), + Entry("auto-delete marks node object for deletion when finalizer is already present", false, true, true, true, "", "test-namespace", true, false, false, true), + Entry("assigned not found device is not auto-deleted", false, true, false, true, "test-namespace", "", true, false, false, false), + Entry("deleting removes finalizer and owned USB", true, false, true, true, "", "test-namespace", false, true, false, false), + Entry("deleting removes finalizer even without owned USB", true, false, false, true, "", "", false, false, false, false), + Entry("deleting removes owned USB in different namespace", true, false, true, true, "", "previous-namespace", false, true, false, false), ) })