From e2c535c225508b7d20ea9889194dc60112638582 Mon Sep 17 00:00:00 2001 From: Ivan Mikheykin Date: Wed, 8 Apr 2026 15:46:57 +0300 Subject: [PATCH] fix(dvcr): resume postponed imports after garbage collection These changes should fix problem when VI/CVI/VD stuck in "Postponed" state. Add a DVCR garbage collection secret watcher for VI, CVI, and VD controllers to requeue postponed resources when garbage collection is over. Add a timeout for the DVCR garbage collection in dvcr-gc controller. It should auto-heal Deploy/dvcr and postponed VI/CVI/VD in case dvcr-cleaner is failed. Minor: - Handle nil garbage collection secret in dvcr-cleaner when adding cleanup completion annotation. - Refactor dvcr gc secret watcher: filter events for secret early in predicate functions. Signed-off-by: Ivan Mikheykin --- .../dvcr-artifact/cmd/dvcr-cleaner/cmd/gc.go | 4 + .../pkg/controller/cvi/cvi_controller.go | 4 +- .../pkg/controller/cvi/cvi_reconciler.go | 2 + .../internal/life_cycle.go | 15 +- .../watcher/garbage_collection_secret.go | 22 ++- .../postpone_handler.go} | 12 +- .../postponeimporter/secret_watcher.go | 137 ++++++++++++++++++ .../controller/vd/internal/postpone_filter.go | 6 +- .../pkg/controller/vd/vd_controller.go | 4 +- .../pkg/controller/vd/vd_reconciler.go | 2 + .../pkg/controller/vi/vi_controller.go | 4 +- .../pkg/controller/vi/vi_reconciler.go | 2 + 12 files changed, 194 insertions(+), 20 deletions(-) rename images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/{postponehandler/postpone.go => postponeimporter/postpone_handler.go} (92%) create mode 100644 images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/postponeimporter/secret_watcher.go diff --git a/images/dvcr-artifact/cmd/dvcr-cleaner/cmd/gc.go b/images/dvcr-artifact/cmd/dvcr-cleaner/cmd/gc.go index b827981abb..3176d37ca3 100644 --- a/images/dvcr-artifact/cmd/dvcr-cleaner/cmd/gc.go +++ b/images/dvcr-artifact/cmd/dvcr-cleaner/cmd/gc.go @@ -380,6 +380,10 @@ func annotateGarbageCollectionSecretOnCleanupDone(ctx context.Context, result ma return err } + if secret == nil { + return fmt.Errorf("garbage collection secret not found") + } + if secret.Annotations == nil { secret.Annotations = make(map[string]string) } diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go index 0bbb1b28fd..310e263eb3 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go @@ -33,7 +33,7 @@ import ( "github.com/deckhouse/deckhouse/pkg/log" "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal" "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/source" - "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/postponehandler" + "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/postponeimporter" "github.com/deckhouse/virtualization-controller/pkg/controller/gc" "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" "github.com/deckhouse/virtualization-controller/pkg/controller/service" @@ -81,7 +81,7 @@ func NewController( reconciler := NewReconciler( mgr.GetClient(), - postponehandler.New[*v1alpha2.ClusterVirtualImage](dvcrService, recorder), + postponeimporter.NewHandler[*v1alpha2.ClusterVirtualImage](dvcrService, recorder), internal.NewDatasourceReadyHandler(sources), internal.NewLifeCycleHandler(sources, mgr.GetClient()), internal.NewImagePresenceHandler(dvcr.NewImageChecker(mgr.GetClient(), dvcrSettings)), diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go index a568441588..cb5cff00f2 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go @@ -31,6 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/watcher" + "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/postponeimporter" "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" "github.com/deckhouse/virtualization-controller/pkg/controller/watchers" "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -102,6 +103,7 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr watcher.NewVirtualMachineWatcher(), watcher.NewVirtualDiskWatcher(mgrClient), watcher.NewVirtualDiskSnapshotWatcher(mgrClient), + postponeimporter.NewWatcher[*v1alpha2.ClusterVirtualImage](mgrClient), } { err := w.Watch(mgr, ctr) if err != nil { diff --git a/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/internal/life_cycle.go index 6f5a428616..7a0dd49be9 100644 --- a/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/internal/life_cycle.go @@ -92,12 +92,25 @@ func (h LifeCycleHandler) Handle(ctx context.Context, req reconcile.Request, dep } if h.dvcrService.IsGarbageCollectionStarted(secret) { + hasCreationTimestamp := !secret.GetCreationTimestamp().Time.IsZero() + waitDuration := time.Since(secret.GetCreationTimestamp().Time) + if hasCreationTimestamp && waitDuration > dvcrtypes.WaitProvisionersTimeout { + dvcrcondition.UpdateGarbageCollectionCondition(deploy, + dvcrdeploymentcondition.Error, + "Wait for garbage collection more than %s timeout: %s elapsed, garbage collection canceled", + dvcrtypes.WaitProvisionersTimeout.String(), + waitDuration.String(), + ) + annotations.AddAnnotation(deploy, annotations.AnnDVCRGarbageCollectionResult, "") + return reconcile.Result{}, h.dvcrService.DeleteGarbageCollectionSecret(ctx) + } + dvcrcondition.UpdateGarbageCollectionCondition(deploy, dvcrdeploymentcondition.InProgress, "Wait for garbage collection to finish.", ) // Wait for done annotation appears on secret. - return reconcile.Result{}, nil + return reconcile.Result{RequeueAfter: time.Second * 20}, nil } // No special annotation, check for provisioners to finish. diff --git a/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/internal/watcher/garbage_collection_secret.go b/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/internal/watcher/garbage_collection_secret.go index 5d8c6738e2..3bfcfd1f2f 100644 --- a/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/internal/watcher/garbage_collection_secret.go +++ b/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/internal/watcher/garbage_collection_secret.go @@ -21,6 +21,7 @@ import ( "reflect" corev1 "k8s.io/api/core/v1" + 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/event" @@ -50,16 +51,29 @@ func (w *DVCRGarbageCollectionSecretWatcher) Watch(mgr manager.Manager, ctr cont mgr.GetCache(), &corev1.Secret{}, handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, secret *corev1.Secret) []reconcile.Request { - if secret.GetNamespace() == dvcrtypes.ModuleNamespace && secret.GetName() == dvcrtypes.DVCRGarbageCollectionSecretName { - return []reconcile.Request{{NamespacedName: client.ObjectKeyFromObject(secret)}} - } - return nil + return []reconcile.Request{{NamespacedName: client.ObjectKeyFromObject(secret)}} }), predicate.TypedFuncs[*corev1.Secret]{ + CreateFunc: func(e event.TypedCreateEvent[*corev1.Secret]) bool { + return IsDVCRGarbageCollectionSecret(e.Object) + }, UpdateFunc: func(e event.TypedUpdateEvent[*corev1.Secret]) bool { + if !IsDVCRGarbageCollectionSecret(e.ObjectNew) { + return false + } return !reflect.DeepEqual(e.ObjectNew.GetAnnotations(), e.ObjectOld.GetAnnotations()) }, + DeleteFunc: func(e event.TypedDeleteEvent[*corev1.Secret]) bool { + return IsDVCRGarbageCollectionSecret(e.Object) + }, }, ), ) } + +func IsDVCRGarbageCollectionSecret(secret metav1.Object) bool { + if secret == nil { + return false + } + return secret.GetNamespace() == dvcrtypes.ModuleNamespace && secret.GetName() == dvcrtypes.DVCRGarbageCollectionSecretName +} diff --git a/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/postponehandler/postpone.go b/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/postponeimporter/postpone_handler.go similarity index 92% rename from images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/postponehandler/postpone.go rename to images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/postponeimporter/postpone_handler.go index 2ac267de01..006faaca5a 100644 --- a/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/postponehandler/postpone.go +++ b/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/postponeimporter/postpone_handler.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package postponehandler +package postponeimporter import ( "context" @@ -42,13 +42,13 @@ type DVCRService interface { var PostponePeriod = time.Second * 15 -type Postpone[object client.Object] struct { +type PostponeHandler[object client.Object] struct { dvcrService DVCRService recorder eventrecord.EventRecorderLogger } -func New[T client.Object](dvcrService DVCRService, recorder eventrecord.EventRecorderLogger) *Postpone[T] { - return &Postpone[T]{ +func NewHandler[T client.Object](dvcrService DVCRService, recorder eventrecord.EventRecorderLogger) *PostponeHandler[T] { + return &PostponeHandler[T]{ dvcrService: dvcrService, recorder: recorder, } @@ -57,7 +57,7 @@ func New[T client.Object](dvcrService DVCRService, recorder eventrecord.EventRec // Handle sets Ready condition to Provisioning for newly created resources // if dvcr is in the garbage collection mode. // Applicable for ClusterVirtualImage, VirtualImage, and VirtualDisk. -func (p *Postpone[T]) Handle(ctx context.Context, obj T) (reconcile.Result, error) { +func (p *PostponeHandler[T]) Handle(ctx context.Context, obj T) (reconcile.Result, error) { conditionsPtr := conditions.NewConditionsAccessor(obj).Conditions() readyCondition, readyConditionPresent := conditions.GetCondition(getReadyType(obj), *conditionsPtr) @@ -109,7 +109,7 @@ func (p *Postpone[T]) Handle(ctx context.Context, obj T) (reconcile.Result, erro return reconcile.Result{RequeueAfter: PostponePeriod}, reconciler.ErrStopHandlerChain } -func (p *Postpone[T]) Name() string { +func (p *PostponeHandler[T]) Name() string { return "postpone-on-dvcr-garbage-collection-handler" } diff --git a/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/postponeimporter/secret_watcher.go b/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/postponeimporter/secret_watcher.go new file mode 100644 index 0000000000..c968719fe6 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/dvcr-garbage-collection/postponeimporter/secret_watcher.go @@ -0,0 +1,137 @@ +/* +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 postponeimporter + +import ( + "context" + "fmt" + "reflect" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/internal/watcher" + "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" +) + +type DVCRGarbageCollectionSecretWatcher[object client.Object] struct { + client client.Client +} + +func NewWatcher[T client.Object](client client.Client) *DVCRGarbageCollectionSecretWatcher[T] { + return &DVCRGarbageCollectionSecretWatcher[T]{ + client: client, + } +} + +func (w *DVCRGarbageCollectionSecretWatcher[T]) Watch(mgr manager.Manager, ctr controller.Controller) error { + if err := ctr.Watch( + source.Kind( + mgr.GetCache(), + &corev1.Secret{}, + handler.TypedEnqueueRequestsFromMapFunc(w.enqueuePostponedResources), + predicate.TypedFuncs[*corev1.Secret]{ + CreateFunc: func(e event.TypedCreateEvent[*corev1.Secret]) bool { + return watcher.IsDVCRGarbageCollectionSecret(e.Object) + }, + UpdateFunc: func(e event.TypedUpdateEvent[*corev1.Secret]) bool { + if !watcher.IsDVCRGarbageCollectionSecret(e.ObjectNew) { + return false + } + return !reflect.DeepEqual(e.ObjectNew.GetAnnotations(), e.ObjectOld.GetAnnotations()) + }, + DeleteFunc: func(e event.TypedDeleteEvent[*corev1.Secret]) bool { + return watcher.IsDVCRGarbageCollectionSecret(e.Object) + }, + }, + ), + ); err != nil { + return fmt.Errorf("error setting watch on DVCR garbage collection Secret: %w", err) + } + return nil +} + +func (w *DVCRGarbageCollectionSecretWatcher[T]) enqueuePostponedResources(ctx context.Context, _ *corev1.Secret) []reconcile.Request { + var resList client.ObjectList + + // Type switch for T should be done on an empty value for T. + var obj T + switch any(obj).(type) { + case *v1alpha2.ClusterVirtualImage: + resList = &v1alpha2.ClusterVirtualImageList{} + case *v1alpha2.VirtualImage: + resList = &v1alpha2.VirtualImageList{} + case *v1alpha2.VirtualDisk: + resList = &v1alpha2.VirtualDiskList{} + default: + return nil + } + + if err := w.client.List(ctx, resList, &client.ListOptions{}); err != nil { + return nil + } + + requests := make([]reconcile.Request, 0) + switch obj := resList.(type) { + case *v1alpha2.ClusterVirtualImageList: + for _, item := range obj.Items { + if isClusterVirtualImagePostponed(&item) { + requests = append(requests, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&item)}) + } + } + case *v1alpha2.VirtualDiskList: + for _, item := range obj.Items { + if isVirtualDiskPostponed(&item) { + requests = append(requests, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&item)}) + } + } + case *v1alpha2.VirtualImageList: + for _, item := range obj.Items { + if isVirtualImagePostponed(&item) { + requests = append(requests, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&item)}) + } + } + } + + return requests +} + +func isClusterVirtualImagePostponed(cvi *v1alpha2.ClusterVirtualImage) bool { + cond, ok := conditions.GetCondition(cvicondition.ReadyType, cvi.Status.Conditions) + return ok && cond.Reason == ProvisioningPostponedReason.String() +} + +func isVirtualImagePostponed(vi *v1alpha2.VirtualImage) bool { + cond, ok := conditions.GetCondition(vicondition.ReadyType, vi.Status.Conditions) + return ok && cond.Reason == ProvisioningPostponedReason.String() +} + +func isVirtualDiskPostponed(vd *v1alpha2.VirtualDisk) bool { + cond, ok := conditions.GetCondition(vdcondition.ReadyType, vd.Status.Conditions) + return ok && cond.Reason == ProvisioningPostponedReason.String() +} diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/postpone_filter.go b/images/virtualization-artifact/pkg/controller/vd/internal/postpone_filter.go index 8117d1c587..1dcc100aa6 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/postpone_filter.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/postpone_filter.go @@ -22,7 +22,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" - "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/postponehandler" + "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/postponeimporter" "github.com/deckhouse/virtualization-controller/pkg/logger" "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -30,12 +30,12 @@ import ( const postponeFilterHandlerPrefix = "vd-" type PostponeHandlerPreFilter struct { - postponeHandler *postponehandler.Postpone[*v1alpha2.VirtualDisk] + postponeHandler *postponeimporter.PostponeHandler[*v1alpha2.VirtualDisk] } // NewPostponeHandlerPreFilter runs postpone handler only if VirtualDisk is required to import/upload // to DVCR first. -func NewPostponeHandlerPreFilter(postponeHandler *postponehandler.Postpone[*v1alpha2.VirtualDisk]) *PostponeHandlerPreFilter { +func NewPostponeHandlerPreFilter(postponeHandler *postponeimporter.PostponeHandler[*v1alpha2.VirtualDisk]) *PostponeHandlerPreFilter { return &PostponeHandlerPreFilter{ postponeHandler: postponeHandler, } diff --git a/images/virtualization-artifact/pkg/controller/vd/vd_controller.go b/images/virtualization-artifact/pkg/controller/vd/vd_controller.go index c887446849..85744f89af 100644 --- a/images/virtualization-artifact/pkg/controller/vd/vd_controller.go +++ b/images/virtualization-artifact/pkg/controller/vd/vd_controller.go @@ -29,7 +29,7 @@ import ( "github.com/deckhouse/deckhouse/pkg/log" "github.com/deckhouse/virtualization-controller/pkg/config" - "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/postponehandler" + "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/postponeimporter" "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal" intsvc "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/service" @@ -82,7 +82,7 @@ func NewController( reconciler := NewReconciler( mgr.GetClient(), - internal.NewPostponeHandlerPreFilter(postponehandler.New[*v1alpha2.VirtualDisk](dvcrService, recorder)), + internal.NewPostponeHandlerPreFilter(postponeimporter.NewHandler[*v1alpha2.VirtualDisk](dvcrService, recorder)), internal.NewInitHandler(), internal.NewStorageClassReadyHandler(scService), internal.NewDatasourceReadyHandler(recorder, blank, sources), diff --git a/images/virtualization-artifact/pkg/controller/vd/vd_reconciler.go b/images/virtualization-artifact/pkg/controller/vd/vd_reconciler.go index c71f4565c5..6cd379b90d 100644 --- a/images/virtualization-artifact/pkg/controller/vd/vd_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vd/vd_reconciler.go @@ -26,6 +26,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/postponeimporter" "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" vdsupplements "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/supplements" "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/watcher" @@ -106,6 +107,7 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr watcher.NewDataVolumeWatcher(), watcher.NewVirtualMachineWatcher(), watcher.NewResourceQuotaWatcher(mgrClient), + postponeimporter.NewWatcher[*v1alpha2.VirtualDisk](mgrClient), } { err := w.Watch(mgr, ctr) if err != nil { diff --git a/images/virtualization-artifact/pkg/controller/vi/vi_controller.go b/images/virtualization-artifact/pkg/controller/vi/vi_controller.go index 02c74e3f81..a70302aa7e 100644 --- a/images/virtualization-artifact/pkg/controller/vi/vi_controller.go +++ b/images/virtualization-artifact/pkg/controller/vi/vi_controller.go @@ -32,7 +32,7 @@ import ( "github.com/deckhouse/deckhouse/pkg/log" "github.com/deckhouse/virtualization-controller/pkg/config" - "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/postponehandler" + "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/postponeimporter" "github.com/deckhouse/virtualization-controller/pkg/controller/gc" "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" "github.com/deckhouse/virtualization-controller/pkg/controller/service" @@ -86,7 +86,7 @@ func NewController( reconciler := NewReconciler( mgr.GetClient(), - postponehandler.New[*v1alpha2.VirtualImage](dvcrService, recorder), + postponeimporter.NewHandler[*v1alpha2.VirtualImage](dvcrService, recorder), internal.NewStorageClassReadyHandler(recorder, scService), internal.NewDatasourceReadyHandler(sources), internal.NewLifeCycleHandler(recorder, sources, mgr.GetClient()), diff --git a/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go b/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go index ed4ae610e4..55672470fc 100644 --- a/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go @@ -30,6 +30,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" + "github.com/deckhouse/virtualization-controller/pkg/controller/dvcr-garbage-collection/postponeimporter" "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" "github.com/deckhouse/virtualization-controller/pkg/controller/vi/internal/watcher" "github.com/deckhouse/virtualization-controller/pkg/controller/watchers" @@ -117,6 +118,7 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr watcher.NewDataVolumeWatcher(), watcher.NewPersistentVolumeClaimWatcher(), watcher.NewVirtualDiskWatcher(mgrClient), + postponeimporter.NewWatcher[*v1alpha2.VirtualImage](mgrClient), } { err := w.Watch(mgr, ctr) if err != nil {