From 957152dccfc58194c128a44916613f1417066fc0 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 24 Mar 2025 11:06:23 +0300 Subject: [PATCH 01/27] feat Signed-off-by: Valeriy Khorunzhin --- api/core/v1alpha2/cluster_virtual_image.go | 7 +- api/core/v1alpha2/cvicondition/condition.go | 10 + api/core/v1alpha2/vicondition/condition.go | 12 +- api/core/v1alpha2/zz_generated.deepcopy.go | 5 + .../generated/openapi/zz_generated.openapi.go | 14 + crds/clustervirtualimages.yaml | 4 + crds/doc-ru-clustervirtualimages.yaml | 3 + .../pkg/controller/cvi/cvi_controller.go | 1 + .../pkg/controller/cvi/cvi_reconciler.go | 17 +- .../pkg/controller/cvi/internal/deletion.go | 11 +- .../controller/cvi/internal/handler_test.go | 29 ++ .../pkg/controller/cvi/internal/inuse.go | 233 ++++++++++ .../pkg/controller/cvi/internal/inuse_test.go | 358 +++++++++++++++ .../pkg/controller/cvi/internal/life_cycle.go | 8 + .../cvi/internal/source/object_ref.go | 5 +- .../cvi/internal/watchers/cvi_watcher.go | 100 ++++ .../cvi/internal/watchers/vd_watcher.go | 101 ++++ .../cvi/internal/watchers/vi_watcher.go | 100 ++++ .../pkg/controller/dvcr_data_source.go | 27 +- .../pkg/controller/indexer/cvi_indexer.go | 68 +++ .../pkg/controller/indexer/indexer.go | 14 + .../pkg/controller/indexer/vd_indexer.go | 50 ++ .../pkg/controller/indexer/vi_indexer.go | 42 ++ .../pkg/controller/vd/internal/life_cycle.go | 55 ++- .../vd/internal/source/object_ref_cvi.go | 10 +- .../vd/internal/source/object_ref_vi_dvcr.go | 9 +- .../vd/internal/source/object_ref_vi_pvc.go | 9 +- .../pkg/controller/vi/internal/deletion.go | 9 + .../pkg/controller/vi/internal/inuse.go | 205 +++++++++ .../pkg/controller/vi/internal/inuse_test.go | 433 ++++++++++++++++++ .../pkg/controller/vi/internal/life_cycle.go | 52 ++- .../vi/internal/source/object_ref.go | 4 +- .../vi/internal/watcher/cvi_watcher.go | 100 ++++ .../vi/internal/watcher/vd_watcher.go | 102 +++++ .../vi/internal/watcher/vi_watcher.go | 100 ++++ .../pkg/controller/vi/vi_controller.go | 1 + .../pkg/controller/vi/vi_reconciler.go | 3 + 37 files changed, 2278 insertions(+), 33 deletions(-) create mode 100644 images/virtualization-artifact/pkg/controller/cvi/internal/handler_test.go create mode 100644 images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go create mode 100644 images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go create mode 100644 images/virtualization-artifact/pkg/controller/cvi/internal/watchers/cvi_watcher.go create mode 100644 images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vd_watcher.go create mode 100644 images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vi_watcher.go create mode 100644 images/virtualization-artifact/pkg/controller/vi/internal/inuse.go create mode 100644 images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go create mode 100644 images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go create mode 100644 images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go create mode 100644 images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go diff --git a/api/core/v1alpha2/cluster_virtual_image.go b/api/core/v1alpha2/cluster_virtual_image.go index 79e5ce9907..953a1b7fb8 100644 --- a/api/core/v1alpha2/cluster_virtual_image.go +++ b/api/core/v1alpha2/cluster_virtual_image.go @@ -145,9 +145,10 @@ type ClusterVirtualImageStatus struct { // Resource generation last processed by the controller. ObservedGeneration int64 `json:"observedGeneration,omitempty"` // Deprecated. Use `imageUploadURLs` instead. - UploadCommand string `json:"uploadCommand,omitempty"` - ImageUploadURLs *ImageUploadURLs `json:"imageUploadURLs,omitempty"` - Target ClusterVirtualImageStatusTarget `json:"target,omitempty"` + UploadCommand string `json:"uploadCommand,omitempty"` + ImageUploadURLs *ImageUploadURLs `json:"imageUploadURLs,omitempty"` + Target ClusterVirtualImageStatusTarget `json:"target,omitempty"` + UsedInNamespaces []string `json:"usedInNamespaces,omitempty"` } type ClusterVirtualImageStatusTarget struct { diff --git a/api/core/v1alpha2/cvicondition/condition.go b/api/core/v1alpha2/cvicondition/condition.go index 1d55753286..7adc905ba6 100644 --- a/api/core/v1alpha2/cvicondition/condition.go +++ b/api/core/v1alpha2/cvicondition/condition.go @@ -28,6 +28,8 @@ const ( DatasourceReadyType Type = "DatasourceReady" // ReadyType indicates whether the import process succeeded and the `ClusterVirtualImage` is ready for use. ReadyType Type = "Ready" + // InUse indicates that the `ClusterVirtualImage` is used by other resources and cannot be deleted now. + InUseType Type = "InUse" ) type ( @@ -35,6 +37,8 @@ type ( DatasourceReadyReason string // ReadyReason represents the various reasons for the Ready condition type. ReadyReason string + // InUseReason represents the various reasons for the InUseType condition type. + InUseReason string ) func (s DatasourceReadyReason) String() string { @@ -45,6 +49,10 @@ func (s ReadyReason) String() string { return string(s) } +func (s InUseReason) String() string { + return string(s) +} + const ( // DatasourceReady indicates that the datasource is ready for use, allowing the import process to start. DatasourceReady DatasourceReadyReason = "DatasourceReady" @@ -73,4 +81,6 @@ const ( ProvisioningFailed ReadyReason = "ProvisioningFailed" // Ready indicates that the import process is complete and the `ClusterVirtualImage` is ready for use. Ready ReadyReason = "Ready" + // InUse indicates that the `ClusterVirtualImage` is used by other resources and cannot be deleted now. + InUse InUseReason = "InUse" ) diff --git a/api/core/v1alpha2/vicondition/condition.go b/api/core/v1alpha2/vicondition/condition.go index 72bb5f9424..7d0006633f 100644 --- a/api/core/v1alpha2/vicondition/condition.go +++ b/api/core/v1alpha2/vicondition/condition.go @@ -28,8 +28,10 @@ const ( DatasourceReadyType Type = "DatasourceReady" // ReadyType indicates whether the import process succeeded and the `VirtualImage` is ready for use. ReadyType Type = "Ready" - // StorageClassReadyType indicates whether the storageClass ready + // StorageClassReadyType indicates whether the storageClass ready. StorageClassReadyType Type = "StorageClassReady" + // InUse indicates that the `VirtualImage` is used by other resources and cannot be deleted now. + InUseType Type = "InUse" ) type ( @@ -39,6 +41,8 @@ type ( ReadyReason string // StorageClassReadyReason represents the various reasons for the StorageClassReady condition type. StorageClassReadyReason string + // InUseReason represents the various reasons for the InUseType condition type. + InUseReason string ) func (s DatasourceReadyReason) String() string { @@ -53,6 +57,10 @@ func (s StorageClassReadyReason) String() string { return string(s) } +func (s InUseReason) String() string { + return string(s) +} + const ( // DatasourceReady indicates that the datasource is ready for use, allowing the import process to start. DatasourceReady DatasourceReadyReason = "DatasourceReady" @@ -99,4 +107,6 @@ const ( StorageClassNotFound StorageClassReadyReason = "StorageClassNotFound" // DVCRTypeUsed indicates that the DVCR provisioning chosen. DVCRTypeUsed StorageClassReadyReason = "DVCRTypeUsed" + // InUse indicates that the `VirtualImage` is used by other resources and cannot be deleted now. + InUse InUseReason = "InUse" ) diff --git a/api/core/v1alpha2/zz_generated.deepcopy.go b/api/core/v1alpha2/zz_generated.deepcopy.go index 1245cd6aff..263c7f83f5 100644 --- a/api/core/v1alpha2/zz_generated.deepcopy.go +++ b/api/core/v1alpha2/zz_generated.deepcopy.go @@ -324,6 +324,11 @@ func (in *ClusterVirtualImageStatus) DeepCopyInto(out *ClusterVirtualImageStatus **out = **in } out.Target = in.Target + if in.UsedInNamespaces != nil { + in, out := &in.UsedInNamespaces, &out.UsedInNamespaces + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go b/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go index 38efc2e08c..b7fcf1b74f 100644 --- a/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go +++ b/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go @@ -1271,6 +1271,20 @@ func schema_virtualization_api_core_v1alpha2_ClusterVirtualImageStatus(ref commo Ref: ref("github.com/deckhouse/virtualization/api/core/v1alpha2.ClusterVirtualImageStatusTarget"), }, }, + "usedInNamespaces": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, }, }, }, diff --git a/crds/clustervirtualimages.yaml b/crds/clustervirtualimages.yaml index 9c6de96471..68798fcd03 100644 --- a/crds/clustervirtualimages.yaml +++ b/crds/clustervirtualimages.yaml @@ -407,6 +407,10 @@ spec: uploadCommand: description: Deprecated. Use `imageUploadURLs` instead. type: string + usedInNamespaces: + items: + type: string + type: array type: object required: - spec diff --git a/crds/doc-ru-clustervirtualimages.yaml b/crds/doc-ru-clustervirtualimages.yaml index 92517f6bed..75afa619f0 100644 --- a/crds/doc-ru-clustervirtualimages.yaml +++ b/crds/doc-ru-clustervirtualimages.yaml @@ -172,6 +172,9 @@ spec: uploadCommand: description: | Устаревшее поле. Используйте `imageUploadURLs`. + usedInNamespaces: + description: | + Поле, отображающее неймспейсы в которых используется образ, при удалении образа. imageUploadURLs: properties: external: diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go index 5da3b27699..78a089413d 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go @@ -74,6 +74,7 @@ func NewController( mgr.GetClient(), internal.NewDatasourceReadyHandler(sources), internal.NewLifeCycleHandler(sources, mgr.GetClient()), + internal.NewInUseHandler(mgr.GetClient()), internal.NewDeletionHandler(sources), internal.NewAttacheeHandler(mgr.GetClient()), ) diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go index d840e1d935..1dbfb73f20 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go @@ -32,7 +32,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" - "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/watcher" + watcher "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/watchers" "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" "github.com/deckhouse/virtualization-controller/pkg/controller/watchers" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -43,6 +43,10 @@ type Watcher interface { Watch(mgr manager.Manager, ctr controller.Controller) error } +type Watcher interface { + Watch(mgr manager.Manager, ctr controller.Controller) error +} + type Handler interface { Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) } @@ -150,6 +154,17 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return fmt.Errorf("error setting watch on VDs: %w", err) } + for _, w := range []Watcher{ + watcher.NewClusterVirtualImageWatcher(mgr.GetClient()), + watcher.NewVirtualImageWatcher(mgr.GetClient()), + watcher.NewVirtualDiskWatcher(mgr.GetClient()), + } { + err := w.Watch(mgr, ctr) + if err != nil { + return fmt.Errorf("error setting watcher: %w", err) + } + } + cviFromVIEnqueuer := watchers.NewClusterVirtualImageRequestEnqueuer(mgr.GetClient(), &virtv2.VirtualImage{}, virtv2.ClusterVirtualImageObjectRefKindVirtualImage) viWatcher := watchers.NewObjectRefWatcher(watchers.NewVirtualImageFilter(), cviFromVIEnqueuer) if err := viWatcher.Run(mgr, ctr); err != nil { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go b/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go index a3f90a5303..39821bc7b1 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go @@ -20,12 +20,15 @@ import ( "context" "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/source" "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" ) const deletionHandlerName = "DeletionHandler" @@ -44,7 +47,13 @@ func (h DeletionHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualI log := logger.FromContext(ctx).With(logger.SlogHandler(deletionHandlerName)) if cvi.DeletionTimestamp != nil { - requeue, err := h.sources.CleanUp(ctx, cvi) + inUseCondition, _ := conditions.GetCondition(cvicondition.InUseType, cvi.Status.Conditions) + if inUseCondition.Status == metav1.ConditionTrue && conditions.IsLastUpdated(inUseCondition, cvi) { + controllerutil.AddFinalizer(cvi, virtv2.FinalizerCVICleanup) + return reconcile.Result{}, nil + } + + result, err := h.sources.CleanUp(ctx, cvi) if err != nil { return reconcile.Result{}, err } diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/handler_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/handler_test.go new file mode 100644 index 0000000000..68c60dea80 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/handler_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2024 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 internal + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestHandlers(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Handlers") +} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go new file mode 100644 index 0000000000..af14d38fef --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go @@ -0,0 +1,233 @@ +/* +Copyright 2024 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 internal + +import ( + "context" + "fmt" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" +) + +const inUseHandlerName = "InUseHandler" + +type InUseHandler struct { + client client.Client +} + +func NewInUseHandler(client client.Client) *InUseHandler { + return &InUseHandler{ + client: client, + } +} + +func (h InUseHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) { + if cvi.DeletionTimestamp == nil { + conditions.RemoveCondition(cvicondition.InUse, &cvi.Status.Conditions) + return reconcile.Result{}, nil + } + + readyCondition, _ := conditions.GetCondition(cvicondition.ReadyType, cvi.Status.Conditions) + if readyCondition.Status != metav1.ConditionTrue || !conditions.IsLastUpdated(readyCondition, cvi) { + conditions.RemoveCondition(cvicondition.InUse, &cvi.Status.Conditions) + return reconcile.Result{}, nil + } + + cb := conditions.NewConditionBuilder(cvicondition.InUse).Generation(cvi.Generation) + + namespacesMap := make(map[string]struct{}) + + var vms virtv2.VirtualMachineList + err := h.client.List(ctx, &vms) + if err != nil { + return reconcile.Result{}, err + } + + var vmUsedImage []*virtv2.VirtualMachine + for _, vm := range vms.Items { + for _, bd := range vm.Status.BlockDeviceRefs { + if bd.Kind == virtv2.ClusterVirtualImageKind && bd.Name == cvi.Name { + vmUsedImage = append(vmUsedImage, &vm) + namespacesMap[vm.Namespace] = struct{}{} + } + } + } + + var vds virtv2.VirtualDiskList + err = h.client.List(ctx, &vds, client.MatchingFields{ + indexer.IndexFieldVDByCVIDataSourceNotReady: cvi.GetName(), + }) + if err != nil { + return reconcile.Result{}, err + } + for _, vd := range vds.Items { + namespacesMap[vd.Namespace] = struct{}{} + } + + var vis virtv2.VirtualImageList + err = h.client.List(ctx, &vis, client.MatchingFields{ + indexer.IndexFieldVIByCVIDataSourceNotReady: cvi.GetName(), + }) + if err != nil { + return reconcile.Result{}, err + } + for _, vi := range vis.Items { + namespacesMap[vi.Namespace] = struct{}{} + } + + var cvis virtv2.ClusterVirtualImageList + err = h.client.List(ctx, &cvis, client.MatchingFields{ + indexer.IndexFieldCVIByCVIDataSourceNotReady: cvi.GetName(), + }) + if err != nil { + return reconcile.Result{}, err + } + for _, cv := range cvis.Items { + namespacesMap[cv.Namespace] = struct{}{} + } + + consumerCount := len(vmUsedImage) + len(vds.Items) + len(vis.Items) + len(cvis.Items) + + if consumerCount > 0 { + var messageBuilder strings.Builder + var needComma bool + if len(vmUsedImage) > 0 { + needComma = true + switch len(vmUsedImage) { + case 1: + messageBuilder.WriteString(fmt.Sprintf("The ClusterVirtualImage is currently attached to the VirtualMachine %s/%s", vmUsedImage[0].Namespace, vmUsedImage[0].Name)) + case 2, 3: + var vmNamespacedNames []string + for _, vm := range vmUsedImage { + vmNamespacedNames = append(vmNamespacedNames, fmt.Sprintf("%s/%s", vm.Namespace, vm.Name)) + } + messageBuilder.WriteString(fmt.Sprintf("The ClusterVirtualImage is currently attached to the VirtualMachines: %s", strings.Join(vmNamespacedNames, ", "))) + default: + messageBuilder.WriteString(fmt.Sprintf("%d VirtualMachines are using the ClusterVirtualImage", len(vmUsedImage))) + } + } + + if len(vds.Items) > 0 { + if needComma { + messageBuilder.WriteString(", ") + } + needComma = true + + switch len(vds.Items) { + case 1: + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the VirtualDisk %s/%s", vds.Items[0].Namespace, vds.Items[0].Name)) + case 2, 3: + var vdNamespacedNames []string + for _, vd := range vds.Items { + vdNamespacedNames = append(vdNamespacedNames, fmt.Sprintf("%s/%s", vd.Namespace, vd.Name)) + } + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the VirtualDisks: %s", strings.Join(vdNamespacedNames, ", "))) + default: + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is used to create %d VirtualDisks", len(vds.Items))) + } + } + + if len(vis.Items) > 0 { + if needComma { + messageBuilder.WriteString(", ") + } + needComma = true + + switch len(vis.Items) { + case 1: + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the VirtualImage %s/%s", vis.Items[0].Namespace, vis.Items[0].Name)) + case 2, 3: + var viNamespacedNames []string + for _, vi := range vis.Items { + viNamespacedNames = append(viNamespacedNames, fmt.Sprintf("%s/%s", vi.Namespace, vi.Name)) + } + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the VirtualImages: %s", strings.Join(viNamespacedNames, ", "))) + default: + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is used to create %d VirtualImages", len(vis.Items))) + } + } + + if len(cvis.Items) > 0 { + if needComma { + messageBuilder.WriteString(", ") + } + needComma = true + + switch len(cvis.Items) { + case 1: + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the ClusterVirtualImage %s", cvis.Items[0].Name)) + case 2, 3: + var cviNames []string + for _, cvi := range cvis.Items { + cviNames = append(cviNames, cvi.Name) + } + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the ClusterVirtualImages: %s", strings.Join(cviNames, ", "))) + default: + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is used to create %d ClusterVirtualImages", len(cvis.Items))) + } + } + + var namespaces []string + for namespace := range namespacesMap { + namespaces = append(namespaces, namespace) + } + + if len(namespaces) > 0 { + if needComma { + messageBuilder.WriteString(", ") + } + + switch len(namespaces) { + case 1: + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently using in Namespace %s", namespaces[0])) + case 2, 3: + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is using in Namespaces: %s", strings.Join(namespaces, ", "))) + default: + messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is using in %d Namespaces", len(namespaces))) + } + } + + cvi.Status.UsedInNamespaces = []string{} + for namespace := range namespacesMap { + cvi.Status.UsedInNamespaces = append(cvi.Status.UsedInNamespaces, namespace) + } + + messageBuilder.WriteString(".") + cb. + Status(metav1.ConditionTrue). + Reason(cvicondition.InUse). + Message(service.CapitalizeFirstLetter(messageBuilder.String())) + conditions.SetCondition(cb, &cvi.Status.Conditions) + } else { + conditions.RemoveCondition(cvicondition.InUse, &cvi.Status.Conditions) + } + + return reconcile.Result{}, nil +} + +func (h InUseHandler) Name() string { + return inUseHandlerName +} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go new file mode 100644 index 0000000000..27ced12556 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go @@ -0,0 +1,358 @@ +/* +Copyright 2024 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 internal + +import ( + "context" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apiruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" + virtv1 "kubevirt.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" +) + +var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { + scheme := apiruntime.NewScheme() + for _, f := range []func(*apiruntime.Scheme) error{ + virtv2.AddToScheme, + virtv1.AddToScheme, + corev1.AddToScheme, + } { + err := f(scheme) + Expect(err).NotTo(HaveOccurred(), "failed to add scheme: %s", err) + } + + cvi := &virtv2.ClusterVirtualImage{ + ObjectMeta: metav1.ObjectMeta{ + Name: args.CVIName, + DeletionTimestamp: args.DeletionTimestamp, + }, + Status: virtv2.ClusterVirtualImageStatus{ + Conditions: []metav1.Condition{ + { + Type: cvicondition.ReadyType.String(), + Reason: cvicondition.Ready.String(), + Status: metav1.ConditionTrue, + }, + }, + }, + } + + var objects []client.Object + for _, vm := range args.VMs { + objects = append(objects, &vm) + } + + for _, vd := range args.VDs { + objects = append(objects, &vd) + } + + for _, vi := range args.VIs { + objects = append(objects, &vi) + } + + for _, cvi := range args.CVIs { + objects = append(objects, &cvi) + } + + fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).WithIndex( + &virtv2.VirtualDisk{}, + indexer.IndexFieldVDByCVIDataSourceNotReady, + indexer.IndexVDByVIDataSourceIndexerFunc, + ).WithIndex( + &virtv2.VirtualImage{}, + indexer.IndexFieldVIByCVIDataSourceNotReady, + indexer.IndexVIByVIDataSourceIndexerFunc, + ).WithIndex( + &virtv2.ClusterVirtualImage{}, + indexer.IndexFieldCVIByCVIDataSourceNotReady, + indexer.IndexCVIByVIDataSourceIndexerFunc, + ).Build() + handler := NewInUseHandler(fakeClient) + + result, err := handler.Handle(context.Background(), cvi) + Expect(err).To(BeNil()) + Expect(result).To(Equal(reconcile.Result{})) + inUseCondition, ok := conditions.GetCondition(cvicondition.InUseType, cvi.Status.Conditions) + if args.ExpectedConditionExists { + Expect(ok).To(BeTrue()) + Expect(inUseCondition.Status).To(Equal(args.ExpectedConditionStatus)) + Expect(inUseCondition.Reason).To(Equal(args.ExpectedConditionReason)) + Expect(inUseCondition.Message).To(Equal(args.ExpectedConditionMessage)) + } else { + Expect(ok).To(BeFalse()) + } +}, + Entry("deletionTimestamp not exists", inUseHandlerTestArgs{ + VMs: []virtv2.VirtualMachine{ + generateVMForInUseTest("name", "ns", []virtv2.BlockDeviceStatusRef{}), + }, + CVIName: "test", + ExpectedConditionExists: false, + }), + Entry("has VirtualMachine but with no deleted CVI", inUseHandlerTestArgs{ + CVIName: "test", + DeletionTimestamp: ptr.To(metav1.Time{Time: time.Now()}), + VMs: []virtv2.VirtualMachine{ + generateVMForInUseTest("name", "ns2", []virtv2.BlockDeviceStatusRef{}), + }, + ExpectedConditionExists: false, + }), + Entry("has 1 VirtualMachine with connected terminating CVI", inUseHandlerTestArgs{ + CVIName: "test", + DeletionTimestamp: ptr.To(metav1.Time{Time: time.Now()}), + VMs: []virtv2.VirtualMachine{ + generateVMForInUseTest("name", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + }, + ExpectedConditionExists: true, + ExpectedConditionStatus: metav1.ConditionTrue, + ExpectedConditionReason: cvicondition.InUse.String(), + ExpectedConditionMessage: "The ClusterVirtualImage is currently attached to the VirtualMachine ns/name, ClusterVirtualImage is currently using in Namespace ns.", + }), + Entry("has 5 VirtualMachines with connected terminating CVI", inUseHandlerTestArgs{ + CVIName: "test", + DeletionTimestamp: ptr.To(metav1.Time{Time: time.Now()}), + VMs: []virtv2.VirtualMachine{ + generateVMForInUseTest("name", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("name2", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("name3", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("name4", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("name5", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + }, + ExpectedConditionExists: true, + ExpectedConditionStatus: metav1.ConditionTrue, + ExpectedConditionReason: cvicondition.InUse.String(), + ExpectedConditionMessage: "5 VirtualMachines are using the ClusterVirtualImage, ClusterVirtualImage is currently using in Namespace ns.", + }), + Entry("has 5 VirtualMachines with connected terminating CVI, 4 VD, 2 VI, 1 CVI", inUseHandlerTestArgs{ + CVIName: "test", + DeletionTimestamp: ptr.To(metav1.Time{Time: time.Now()}), + VMs: []virtv2.VirtualMachine{ + generateVMForInUseTest("name", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("name2", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("name3", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("name4", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("name5", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + }, + VDs: []virtv2.VirtualDisk{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "ns1", + }, + Spec: virtv2.VirtualDiskSpec{ + DataSource: &virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test2", + Namespace: "ns2", + }, + Spec: virtv2.VirtualDiskSpec{ + DataSource: &virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test3", + Namespace: "ns3", + }, + Spec: virtv2.VirtualDiskSpec{ + DataSource: &virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test4", + Namespace: "ns4", + }, + Spec: virtv2.VirtualDiskSpec{ + DataSource: &virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }, + }, + }, + }, + VIs: []virtv2.VirtualImage{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "ns", + }, + Spec: virtv2.VirtualImageSpec{ + DataSource: virtv2.VirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualImageObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test2", + Namespace: "ns5", + }, + Spec: virtv2.VirtualImageSpec{ + DataSource: virtv2.VirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualImageObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }, + }, + }, + }, + CVIs: []virtv2.ClusterVirtualImage{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test2", + }, + Spec: virtv2.ClusterVirtualImageSpec{ + DataSource: virtv2.ClusterVirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }, + }, + }, + }, + ExpectedConditionExists: true, + ExpectedConditionStatus: metav1.ConditionTrue, + ExpectedConditionReason: cvicondition.InUse.String(), + ExpectedConditionMessage: "5 VirtualMachines are using the ClusterVirtualImage, ClusterVirtualImage is currently using in Namespace ns.", + }), +) + +type inUseHandlerTestArgs struct { + CVIName string + DeletionTimestamp *metav1.Time + VMs []virtv2.VirtualMachine + VDs []virtv2.VirtualDisk + VIs []virtv2.VirtualImage + CVIs []virtv2.ClusterVirtualImage + ExpectedConditionExists bool + ExpectedConditionReason string + ExpectedConditionMessage string + ExpectedConditionStatus metav1.ConditionStatus +} + +func generateVMForInUseTest(name, namespace string, blockDeviceRefs []virtv2.BlockDeviceStatusRef) virtv2.VirtualMachine { + return virtv2.VirtualMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Status: virtv2.VirtualMachineStatus{ + BlockDeviceRefs: blockDeviceRefs, + }, + } +} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go index d3c281ef5d..d63cbd06e3 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go @@ -55,6 +55,14 @@ func (h LifeCycleHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtual } if cvi.DeletionTimestamp != nil { + if readyCondition.Status == metav1.ConditionTrue { + cb := conditions.NewConditionBuilder(cvicondition.ReadyType).Generation(cvi.Generation). + Status(metav1.ConditionTrue). + Reason(cvicondition.Ready). + Message("") + conditions.SetCondition(cb, &cvi.Status.Conditions) + } + cvi.Status.Phase = virtv2.ImageTerminating return reconcile.Result{}, nil } diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref.go index 8d3bfb8929..5ae22250fe 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref.go @@ -42,6 +42,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) type ObjectRefDataSource struct { @@ -332,7 +333,9 @@ func (ds ObjectRefDataSource) Validate(ctx context.Context, cvi *virtv2.ClusterV } if vi.Spec.Storage == virtv2.StorageKubernetes || vi.Spec.Storage == virtv2.StoragePersistentVolumeClaim { - if vi.Status.Phase != virtv2.ImageReady { + readyCondition, _ := conditions.GetCondition(vicondition.ReadyType, vi.Status.Conditions) + + if readyCondition.Status != metav1.ConditionTrue || !conditions.IsLastUpdated(readyCondition, vi) { return NewImageNotReadyError(cvi.Spec.DataSource.ObjectRef.Name) } return nil diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/cvi_watcher.go new file mode 100644 index 0000000000..045530d913 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/cvi_watcher.go @@ -0,0 +1,100 @@ +/* +Copyright 2025 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 watcher + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/types" + "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/deckhouse/pkg/log" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type ClusterVirtualImageWatcher struct { + logger *log.Logger + client client.Client +} + +func NewClusterVirtualImageWatcher(client client.Client) *ClusterVirtualImageWatcher { + return &ClusterVirtualImageWatcher{ + logger: log.Default().With("watcher", "cvi"), + client: client, + } +} + +func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.ClusterVirtualImage{}), + handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return w.isDataSourceCVI(e.Object) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return w.isDataSourceCVI(e.Object) + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return w.isDataSourceCVI(e.ObjectOld) || w.isDataSourceCVI(e.ObjectNew) + }, + }, + ) +} + +func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { + cvi, ok := obj.(*virtv2.ClusterVirtualImage) + if !ok { + w.logger.Error(fmt.Sprintf("expected a ClusterVirtualImage but got a %T", obj)) + return + } + + if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { + return + } + + if cvi.Spec.DataSource.ObjectRef == nil || cvi.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind { + return + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: cvi.Spec.DataSource.ObjectRef.Name, + Namespace: cvi.Spec.DataSource.ObjectRef.Namespace, + }, + }) + + return +} + +func (w ClusterVirtualImageWatcher) isDataSourceCVI(obj client.Object) bool { + cvi, ok := obj.(*virtv2.ClusterVirtualImage) + if !ok { + w.logger.Error(fmt.Sprintf("expected a ClusterVirtualImage but got a %T", obj)) + return false + } + + return cvi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && cvi.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vd_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vd_watcher.go new file mode 100644 index 0000000000..53f9256617 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vd_watcher.go @@ -0,0 +1,101 @@ +/* +Copyright 2025 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 watcher + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/types" + "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/deckhouse/pkg/log" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VirtualDiskWatcher struct { + logger *log.Logger + client client.Client +} + +func NewVirtualDiskWatcher(client client.Client) *VirtualDiskWatcher { + return &VirtualDiskWatcher{ + logger: log.Default().With("watcher", "vd"), + client: client, + } +} + +func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualDisk{}), + handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return w.isDataSourceCVI(e.Object) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return w.isDataSourceCVI(e.Object) + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return w.isDataSourceCVI(e.ObjectOld) || w.isDataSourceCVI(e.ObjectNew) + }, + }, + ) +} + +func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { + vd, ok := obj.(*virtv2.VirtualDisk) + if !ok { + w.logger.Error(fmt.Sprintf("expected a VirtualDisk but got a %T", obj)) + return + } + + if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { + return + } + + if vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind { + return + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vd.Spec.DataSource.ObjectRef.Name, + }, + }) + + return +} + +func (w VirtualDiskWatcher) isDataSourceCVI(obj client.Object) bool { + vd, ok := obj.(*virtv2.VirtualDisk) + if !ok { + w.logger.Error(fmt.Sprintf("expected a VirtualDisk but got a %T", obj)) + return false + } + + return vd.Spec.DataSource != nil && + vd.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && + vd.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vi_watcher.go new file mode 100644 index 0000000000..ef81a8ad18 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vi_watcher.go @@ -0,0 +1,100 @@ +/* +Copyright 2025 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 watcher + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/types" + "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/deckhouse/pkg/log" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VirtualImageWatcher struct { + logger *log.Logger + client client.Client +} + +func NewVirtualImageWatcher(client client.Client) *VirtualImageWatcher { + return &VirtualImageWatcher{ + logger: log.Default().With("watcher", "vi"), + client: client, + } +} + +func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualImage{}), + handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return w.isDataSourceCVI(e.Object) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return w.isDataSourceCVI(e.Object) + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return w.isDataSourceCVI(e.ObjectOld) || w.isDataSourceCVI(e.ObjectNew) + }, + }, + ) +} + +func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { + vi, ok := obj.(*virtv2.VirtualImage) + if !ok { + w.logger.Error(fmt.Sprintf("expected a VirtualImage but got a %T", obj)) + return + } + + if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { + return + } + + if vi.Spec.DataSource.ObjectRef == nil || vi.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind { + return + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vi.Spec.DataSource.ObjectRef.Name, + Namespace: vi.Namespace, + }, + }) + + return +} + +func (w VirtualImageWatcher) isDataSourceCVI(obj client.Object) bool { + vi, ok := obj.(*virtv2.VirtualImage) + if !ok { + w.logger.Error(fmt.Sprintf("expected a VirtualImage but got a %T", obj)) + return false + } + + return vi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && vi.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/dvcr_data_source.go b/images/virtualization-artifact/pkg/controller/dvcr_data_source.go index d74970833f..c613ba2b74 100644 --- a/images/virtualization-artifact/pkg/controller/dvcr_data_source.go +++ b/images/virtualization-artifact/pkg/controller/dvcr_data_source.go @@ -26,7 +26,10 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/common/imageformat" "github.com/deckhouse/virtualization-controller/pkg/common/object" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) type DVCRDataSource struct { @@ -56,11 +59,13 @@ func NewDVCRDataSourcesForCVMI(ctx context.Context, ds virtv2.ClusterVirtualImag } if vmi != nil { + readyCondition, _ := conditions.GetCondition(vicondition.ReadyType, vmi.Status.Conditions) + dsDVCR.uid = vmi.UID dsDVCR.size = vmi.Status.Size dsDVCR.format = vmi.Status.Format dsDVCR.meta = vmi.GetObjectMeta() - dsDVCR.isReady = vmi.Status.Phase == virtv2.ImageReady + dsDVCR.isReady = readyCondition.Status == metav1.ConditionTrue && conditions.IsLastUpdated(readyCondition, vmi) dsDVCR.target = vmi.Status.Target.RegistryURL } } @@ -73,11 +78,13 @@ func NewDVCRDataSourcesForCVMI(ctx context.Context, ds virtv2.ClusterVirtualImag } if cvmi != nil { + readyCondition, _ := conditions.GetCondition(cvicondition.ReadyType, cvmi.Status.Conditions) + dsDVCR.uid = cvmi.UID dsDVCR.size = cvmi.Status.Size dsDVCR.meta = cvmi.GetObjectMeta() dsDVCR.format = cvmi.Status.Format - dsDVCR.isReady = cvmi.Status.Phase == virtv2.ImageReady + dsDVCR.isReady = readyCondition.Status == metav1.ConditionTrue && conditions.IsLastUpdated(readyCondition, cvmi) dsDVCR.target = cvmi.Status.Target.RegistryURL } } @@ -108,11 +115,13 @@ func NewDVCRDataSourcesForVMI(ctx context.Context, ds virtv2.VirtualImageDataSou return DVCRDataSource{}, fmt.Errorf("the DVCR not used for virtual images with storage type '%s'", vmi.Spec.Storage) } + readyCondition, _ := conditions.GetCondition(vicondition.ReadyType, vmi.Status.Conditions) + dsDVCR.uid = vmi.UID dsDVCR.size = vmi.Status.Size dsDVCR.format = vmi.Status.Format dsDVCR.meta = vmi.GetObjectMeta() - dsDVCR.isReady = vmi.Status.Phase == virtv2.ImageReady + dsDVCR.isReady = readyCondition.Status == metav1.ConditionTrue && conditions.IsLastUpdated(readyCondition, vmi) dsDVCR.target = vmi.Status.Target.RegistryURL } } @@ -125,11 +134,13 @@ func NewDVCRDataSourcesForVMI(ctx context.Context, ds virtv2.VirtualImageDataSou } if cvmi != nil { + readyCondition, _ := conditions.GetCondition(vicondition.ReadyType, cvmi.Status.Conditions) + dsDVCR.uid = cvmi.UID dsDVCR.size = cvmi.Status.Size dsDVCR.meta = cvmi.GetObjectMeta() dsDVCR.format = cvmi.Status.Format - dsDVCR.isReady = cvmi.Status.Phase == virtv2.ImageReady + dsDVCR.isReady = readyCondition.Status == metav1.ConditionTrue && conditions.IsLastUpdated(readyCondition, cvmi) dsDVCR.target = cvmi.Status.Target.RegistryURL } } @@ -156,11 +167,13 @@ func NewDVCRDataSourcesForVMD(ctx context.Context, ds *virtv2.VirtualDiskDataSou } if vmi != nil { + readyCondition, _ := conditions.GetCondition(vicondition.ReadyType, vmi.Status.Conditions) + dsDVCR.uid = vmi.UID dsDVCR.size = vmi.Status.Size dsDVCR.format = vmi.Status.Format dsDVCR.meta = vmi.GetObjectMeta() - dsDVCR.isReady = vmi.Status.Phase == virtv2.ImageReady + dsDVCR.isReady = readyCondition.Status == metav1.ConditionTrue && conditions.IsLastUpdated(readyCondition, vmi) dsDVCR.target = vmi.Status.Target.RegistryURL } } @@ -173,11 +186,13 @@ func NewDVCRDataSourcesForVMD(ctx context.Context, ds *virtv2.VirtualDiskDataSou } if cvmi != nil { + readyCondition, _ := conditions.GetCondition(vicondition.ReadyType, cvmi.Status.Conditions) + dsDVCR.uid = cvmi.UID dsDVCR.size = cvmi.Status.Size dsDVCR.meta = cvmi.GetObjectMeta() dsDVCR.format = cvmi.Status.Format - dsDVCR.isReady = cvmi.Status.Phase == virtv2.ImageReady + dsDVCR.isReady = readyCondition.Status == metav1.ConditionTrue && conditions.IsLastUpdated(readyCondition, cvmi) dsDVCR.target = cvmi.Status.Target.RegistryURL } } diff --git a/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go index d0c764fce3..8679673362 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go @@ -1,4 +1,72 @@ /* +Copyright 2024 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 indexer + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func IndexCVIByCVIDataSource(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.ClusterVirtualImage{}, IndexFieldCVIByCVIDataSourceNotReady, IndexCVIByCVIDataSourceIndexerFunc) +} + +func IndexCVIByCVIDataSourceIndexerFunc(object client.Object) []string { + cvi, ok := object.(*virtv2.ClusterVirtualImage) + if !ok || cvi == nil { + return nil + } + + if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || cvi.Status.Phase == virtv2.ImageReady { + return nil + } + + if cvi.Spec.DataSource.ObjectRef == nil || cvi.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind { + return nil + } + + return []string{cvi.Spec.DataSource.ObjectRef.Name} +} + +func IndexCVIByVIDataSource(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.ClusterVirtualImage{}, IndexFieldCVIByVIDataSourceNotReady, IndexCVIByVIDataSourceIndexerFunc) +} + +func IndexCVIByVIDataSourceIndexerFunc(object client.Object) []string { + cvi, ok := object.(*virtv2.ClusterVirtualImage) + if !ok || cvi == nil { + return nil + } + + if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || cvi.Status.Phase == virtv2.ImageReady { + return nil + } + + if cvi.Spec.DataSource.ObjectRef == nil || cvi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind { + return nil + } + + return []string{cvi.Spec.DataSource.ObjectRef.Name} +} +/* Copyright 2025 Flant JSC Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/images/virtualization-artifact/pkg/controller/indexer/indexer.go b/images/virtualization-artifact/pkg/controller/indexer/indexer.go index 7122f105a1..2cf484015c 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/indexer.go @@ -54,6 +54,14 @@ const ( IndexFieldVMIPByAddress = "spec.staticIP|status.address" IndexFieldVMBDAByVM = "spec.virtualMachineName" + + IndexFieldVDByCVIDataSourceNotReady = "vd,spec.DataSource.ObjectRef.Name,.Kind=ClusterVirtualImage,.Phase!=Ready" + IndexFieldVIByCVIDataSourceNotReady = "vi,spec.DataSource.ObjectRef.Name,.Kind=ClusterVirtualImage,.Phase!=Ready" + IndexFieldCVIByCVIDataSourceNotReady = "cvi,spec.DataSource.ObjectRef.Name,.Kind=ClusterVirtualImage,.Phase!=Ready" + + IndexFieldVDByVIDataSourceNotReady = "vd,spec.DataSource.ObjectRef.Name,.Kind=VirtualImage,.Phase!=Ready" + IndexFieldVIByVIDataSourceNotReady = "vi,spec.DataSource.ObjectRef.Name,.Kind=VirtualImage,.Phase!=Ready" + IndexFieldCVIByVIDataSourceNotReady = "cvi,spec.DataSource.ObjectRef.Name,.Kind=VirtualImage,.Phase!=Ready" ) var IndexGetters = []IndexGetter{ @@ -74,6 +82,12 @@ var IndexGetters = []IndexGetter{ IndexCVIByVDSnapshot, IndexVMIPByAddress, IndexVMBDAByVM, + IndexVDByCVIDataSource, + IndexVIByCVIDataSource, + IndexCVIByCVIDataSource, + IndexVDByVIDataSource, + IndexVIByVIDataSource, + IndexCVIByVIDataSource, } type IndexGetter func() (obj client.Object, field string, extractValue client.IndexerFunc) diff --git a/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go index 0a4e96d6e5..118bfebe95 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go @@ -58,3 +58,53 @@ func IndexVDByStorageClass() (obj client.Object, field string, extractValue clie } } } + +func IndexVDByCVIDataSource(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualDisk{}, IndexFieldVDByCVIDataSourceNotReady, IndexVDByCVIDataSourceIndexerFunc) +} + +func IndexVDByCVIDataSourceIndexerFunc(object client.Object) []string { + vd, ok := object.(*virtv2.VirtualDisk) + if !ok || vd == nil { + return nil + } + + if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { + return nil + } + + if vd.Status.Phase == virtv2.DiskReady { + return nil + } + + if vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind { + return nil + } + + return []string{vd.Spec.DataSource.ObjectRef.Name} +} + +func IndexVDByVIDataSource(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualDisk{}, IndexFieldVDByVIDataSourceNotReady, IndexVDByVIDataSourceIndexerFunc) +} + +func IndexVDByVIDataSourceIndexerFunc(object client.Object) []string { + vd, ok := object.(*virtv2.VirtualDisk) + if !ok || vd == nil { + return nil + } + + if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { + return nil + } + + if vd.Status.Phase == virtv2.DiskReady { + return nil + } + + if vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind { + return nil + } + + return []string{vd.Spec.DataSource.ObjectRef.Name} +} diff --git a/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go index a00f0c3ed2..bf056bd632 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go @@ -58,3 +58,45 @@ func IndexVIByStorageClass() (obj client.Object, field string, extractValue clie } } } + +func IndexVIByCVIDataSource(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualImage{}, IndexFieldVIByCVIDataSourceNotReady, IndexVIByCVIDataSourceIndexerFunc) +} + +func IndexVIByCVIDataSourceIndexerFunc(object client.Object) []string { + vi, ok := object.(*virtv2.VirtualImage) + if !ok || vi == nil { + return nil + } + + if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || vi.Status.Phase == virtv2.ImageReady { + return nil + } + + if vi.Spec.DataSource.ObjectRef == nil || vi.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind { + return nil + } + + return []string{vi.Spec.DataSource.ObjectRef.Name} +} + +func IndexVIByVIDataSource(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualImage{}, IndexFieldVIByVIDataSourceNotReady, IndexVIByVIDataSourceIndexerFunc) +} + +func IndexVIByVIDataSourceIndexerFunc(object client.Object) []string { + vi, ok := object.(*virtv2.VirtualImage) + if !ok || vi == nil { + return nil + } + + if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || vi.Status.Phase == virtv2.ImageReady { + return nil + } + + if vi.Spec.DataSource.ObjectRef == nil || vi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind { + return nil + } + + return []string{vi.Spec.DataSource.ObjectRef.Name} +} diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go index 70fb324ffa..1feded323f 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go @@ -25,7 +25,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/deckhouse/virtualization-controller/pkg/common/annotations" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/supplements" "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/source" "github.com/deckhouse/virtualization-controller/pkg/eventrecord" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -33,18 +36,20 @@ import ( ) type LifeCycleHandler struct { - client client.Client - blank source.Handler - sources Sources - recorder eventrecord.EventRecorderLogger + client client.Client + blank source.Handler + sources Sources + recorder eventrecord.EventRecorderLogger + diskService *service.DiskService } func NewLifeCycleHandler(recorder eventrecord.EventRecorderLogger, blank source.Handler, sources Sources, client client.Client) *LifeCycleHandler { return &LifeCycleHandler{ - client: client, - blank: blank, - sources: sources, - recorder: recorder, + client: client, + blank: blank, + sources: sources, + recorder: recorder, + diskService: service.NewDiskService(client, nil, nil, "vd"), } } @@ -61,8 +66,40 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vd *virtv2.VirtualDisk) (r } if vd.DeletionTimestamp != nil { + needRequeue := false vd.Status.Phase = virtv2.DiskTerminating - return reconcile.Result{}, nil + if readyCondition.Status == metav1.ConditionTrue { + cb := conditions.NewConditionBuilder(vdcondition.ReadyType).Generation(vd.Generation) + + supgen := supplements.NewGenerator(annotations.VDShortName, vd.Name, vd.Namespace, vd.UID) + pvc, err := h.diskService.GetPersistentVolumeClaim(ctx, supgen) + if err != nil { + return reconcile.Result{}, err + } + + switch { + case pvc == nil: + cb. + Status(metav1.ConditionFalse). + Reason(vdcondition.Lost). + Message(fmt.Sprintf("PVC %s not found.", supgen.PersistentVolumeClaim().String())) + needRequeue = true + case pvc.Status.Phase == corev1.ClaimLost: + cb. + Status(metav1.ConditionFalse). + Reason(vdcondition.Lost). + Message(fmt.Sprintf("PV %s not found.", pvc.Spec.VolumeName)) + needRequeue = true + default: + cb. + Status(metav1.ConditionTrue). + Reason(vdcondition.Ready). + Message("") + } + + conditions.SetCondition(cb, &vd.Status.Conditions) + } + return reconcile.Result{Requeue: needRequeue}, nil } if vd.Status.Phase == "" { diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_cvi.go b/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_cvi.go index f7795a652a..8712af4428 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_cvi.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_cvi.go @@ -42,6 +42,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/eventrecord" "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) @@ -258,7 +259,14 @@ func (ds ObjectRefClusterVirtualImage) Validate(ctx context.Context, vd *virtv2. if err != nil { return err } - if cvi == nil || cvi.Status.Phase != virtv2.ImageReady || cvi.Status.Target.RegistryURL == "" { + + var readyCondition metav1.Condition + var ok bool + if cvi != nil { + readyCondition, ok = conditions.GetCondition(cvicondition.ReadyType, cvi.Status.Conditions) + } + + if cvi == nil || !ok || readyCondition.Status != metav1.ConditionTrue || !conditions.IsLastUpdated(readyCondition, cvi) || cvi.Status.Target.RegistryURL == "" { return NewClusterImageNotReadyError(vd.Spec.DataSource.ObjectRef.Name) } diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vi_dvcr.go b/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vi_dvcr.go index abbf119909..022be705f5 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vi_dvcr.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vi_dvcr.go @@ -43,6 +43,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) type ObjectRefVirtualImageDVCR struct { @@ -259,7 +260,13 @@ func (ds ObjectRefVirtualImageDVCR) Validate(ctx context.Context, vd *virtv2.Vir return err } - if vi == nil || vi.Status.Phase != virtv2.ImageReady || vi.Status.Target.RegistryURL == "" { + var readyCondition metav1.Condition + var ok bool + if vi != nil { + readyCondition, ok = conditions.GetCondition(vicondition.ReadyType, vi.Status.Conditions) + } + + if vi == nil || !ok || readyCondition.Status != metav1.ConditionTrue || !conditions.IsLastUpdated(readyCondition, vi) || vi.Status.Target.RegistryURL == "" { return NewImageNotReadyError(vd.Spec.DataSource.ObjectRef.Name) } diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vi_pvc.go b/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vi_pvc.go index 9bd482c439..cbf311d9aa 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vi_pvc.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vi_pvc.go @@ -42,6 +42,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) type ObjectRefVirtualImagePVC struct { @@ -259,7 +260,13 @@ func (ds ObjectRefVirtualImagePVC) Validate(ctx context.Context, vd *virtv2.Virt return fmt.Errorf("unable to get VI: %w", err) } - if vi == nil || vi.Status.Phase != virtv2.ImageReady || vi.Status.Target.PersistentVolumeClaim == "" { + var readyCondition metav1.Condition + var ok bool + if vi != nil { + readyCondition, ok = conditions.GetCondition(vicondition.ReadyType, vi.Status.Conditions) + } + + if vi == nil || !ok || readyCondition.Status != metav1.ConditionTrue || !conditions.IsLastUpdated(readyCondition, vi) || vi.Status.Target.PersistentVolumeClaim == "" { return NewImageNotReadyError(vd.Spec.DataSource.ObjectRef.Name) } diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/deletion.go b/images/virtualization-artifact/pkg/controller/vi/internal/deletion.go index 030e94d986..c6a0743179 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/deletion.go @@ -20,12 +20,15 @@ import ( "context" "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/vi/internal/source" "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) const deletionHandlerName = "DeletionHandler" @@ -44,6 +47,12 @@ func (h DeletionHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) (r log := logger.FromContext(ctx).With(logger.SlogHandler(deletionHandlerName)) if vi.DeletionTimestamp != nil { + inUseCondition, _ := conditions.GetCondition(vicondition.InUseType, vi.Status.Conditions) + if inUseCondition.Status == metav1.ConditionTrue && conditions.IsLastUpdated(inUseCondition, vi) { + controllerutil.AddFinalizer(vi, virtv2.FinalizerVICleanup) + return reconcile.Result{}, nil + } + requeue, err := h.sources.CleanUp(ctx, vi) if err != nil { return reconcile.Result{}, err diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/inuse.go b/images/virtualization-artifact/pkg/controller/vi/internal/inuse.go new file mode 100644 index 0000000000..5e279118f0 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vi/internal/inuse.go @@ -0,0 +1,205 @@ +/* +Copyright 2024 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 internal + +import ( + "context" + "fmt" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" +) + +const inUseHandlerName = "InUseHandler" + +type InUseHandler struct { + client client.Client +} + +func NewInUseHandler(client client.Client) *InUseHandler { + return &InUseHandler{ + client: client, + } +} + +func (h InUseHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) (reconcile.Result, error) { + if vi.DeletionTimestamp == nil { + conditions.RemoveCondition(vicondition.InUse, &vi.Status.Conditions) + return reconcile.Result{}, nil + } + + readyCondition, _ := conditions.GetCondition(vicondition.ReadyType, vi.Status.Conditions) + if readyCondition.Status != metav1.ConditionTrue || !conditions.IsLastUpdated(readyCondition, vi) { + conditions.RemoveCondition(vicondition.InUse, &vi.Status.Conditions) + return reconcile.Result{}, nil + } + + cb := conditions.NewConditionBuilder(vicondition.InUse).Generation(vi.Generation) + + var vms virtv2.VirtualMachineList + err := h.client.List(ctx, &vms, client.InNamespace(vi.GetNamespace())) + if err != nil { + return reconcile.Result{}, err + } + + var vmUsedImage []*virtv2.VirtualMachine + for _, vm := range vms.Items { + for _, bd := range vm.Status.BlockDeviceRefs { + if bd.Kind == virtv2.VirtualImageKind && bd.Name == vi.Name { + vmUsedImage = append(vmUsedImage, &vm) + } + } + } + + var vds virtv2.VirtualDiskList + err = h.client.List(ctx, &vds, client.InNamespace(vi.GetNamespace()), client.MatchingFields{ + indexer.IndexFieldVDByVIDataSourceNotReady: vi.GetName(), + }) + if err != nil { + return reconcile.Result{}, err + } + + var vis virtv2.VirtualImageList + err = h.client.List(ctx, &vis, client.InNamespace(vi.GetNamespace()), client.MatchingFields{ + indexer.IndexFieldVIByVIDataSourceNotReady: vi.GetName(), + }) + if err != nil { + return reconcile.Result{}, err + } + + var cvis virtv2.ClusterVirtualImageList + err = h.client.List(ctx, &cvis, client.MatchingFields{ + indexer.IndexFieldCVIByVIDataSourceNotReady: vi.GetName(), + }) + if err != nil { + return reconcile.Result{}, err + } + + var cvisFiltered []*virtv2.ClusterVirtualImage + for _, cvi := range cvis.Items { + if cvi.Spec.DataSource.ObjectRef == nil { + continue + } + if cvi.Spec.DataSource.ObjectRef.Namespace == vi.GetNamespace() { + cvisFiltered = append(cvisFiltered, &cvi) + } + } + + consumerCount := len(vmUsedImage) + len(vds.Items) + len(vis.Items) + len(cvisFiltered) + + if consumerCount > 0 { + var messageBuilder strings.Builder + var needComma bool + if len(vmUsedImage) > 0 { + needComma = true + switch len(vmUsedImage) { + case 1: + messageBuilder.WriteString(fmt.Sprintf("The VirtualImage is currently attached to the VirtualMachine %s", vmUsedImage[0].Name)) + case 2, 3: + var vmNames []string + for _, vm := range vmUsedImage { + vmNames = append(vmNames, vm.GetName()) + } + messageBuilder.WriteString(fmt.Sprintf("The VirtualImage is currently attached to the VirtualMachines: %s", strings.Join(vmNames, ", "))) + default: + messageBuilder.WriteString(fmt.Sprintf("%d VirtualMachines are using the VirtualImage", len(vmUsedImage))) + } + } + + if len(vds.Items) > 0 { + if needComma { + messageBuilder.WriteString(", ") + } + needComma = true + + switch len(vds.Items) { + case 1: + messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the VirtualDisk %s", vds.Items[0].Name)) + case 2, 3: + var vdNames []string + for _, vd := range vds.Items { + vdNames = append(vdNames, vd.GetName()) + } + messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the VirtualDisks: %s", strings.Join(vdNames, ", "))) + default: + messageBuilder.WriteString(fmt.Sprintf("VirtualImage is used to create %d VirtualDisks", len(vds.Items))) + } + } + + if len(vis.Items) > 0 { + if needComma { + messageBuilder.WriteString(", ") + } + needComma = true + + switch len(vis.Items) { + case 1: + messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the VirtualImage %s", vis.Items[0].Name)) + case 2, 3: + var viNames []string + for _, vi := range vis.Items { + viNames = append(viNames, vi.Name) + } + messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the VirtualImages: %s", strings.Join(viNames, ", "))) + default: + messageBuilder.WriteString(fmt.Sprintf("VirtualImage is used to create %d VirtualImages", len(vis.Items))) + } + } + + if len(cvisFiltered) > 0 { + if needComma { + messageBuilder.WriteString(", ") + } + + switch len(cvisFiltered) { + case 1: + messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the ClusterVirtualImage %s", cvisFiltered[0].Name)) + case 2, 3: + var cviNames []string + for _, cvi := range cvisFiltered { + cviNames = append(cviNames, cvi.Name) + } + messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the ClusterVirtualImages: %s", strings.Join(cviNames, ", "))) + default: + messageBuilder.WriteString(fmt.Sprintf("VirtualImage is used to create %d ClusterVirtualImages", len(cvisFiltered))) + } + } + + messageBuilder.WriteString(".") + cb. + Status(metav1.ConditionTrue). + Reason(vicondition.InUse). + Message(service.CapitalizeFirstLetter(messageBuilder.String())) + conditions.SetCondition(cb, &vi.Status.Conditions) + } else { + conditions.RemoveCondition(vicondition.InUse, &vi.Status.Conditions) + } + + return reconcile.Result{}, nil +} + +func (h InUseHandler) Name() string { + return inUseHandlerName +} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go new file mode 100644 index 0000000000..38d0dca530 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go @@ -0,0 +1,433 @@ +/* +Copyright 2024 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 internal + +import ( + "context" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apiruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" + virtv1 "kubevirt.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" +) + +var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { + scheme := apiruntime.NewScheme() + for _, f := range []func(*apiruntime.Scheme) error{ + virtv2.AddToScheme, + virtv1.AddToScheme, + corev1.AddToScheme, + } { + err := f(scheme) + Expect(err).NotTo(HaveOccurred(), "failed to add scheme: %s", err) + } + + vi := &virtv2.VirtualImage{ + ObjectMeta: metav1.ObjectMeta{ + Name: args.VINamespacedName.Name, + Namespace: args.VINamespacedName.Namespace, + DeletionTimestamp: args.DeletionTimestamp, + }, + Status: virtv2.VirtualImageStatus{ + Conditions: []metav1.Condition{ + { + Type: vicondition.ReadyType.String(), + Reason: vicondition.Ready.String(), + Status: metav1.ConditionTrue, + }, + }, + }, + } + + var objects []client.Object + for _, vm := range args.VMs { + objects = append(objects, &vm) + } + + for _, vd := range args.VDs { + objects = append(objects, &vd) + } + + for _, vi := range args.VIs { + objects = append(objects, &vi) + } + + for _, cvi := range args.CVIs { + objects = append(objects, &cvi) + } + + fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).WithIndex( + &virtv2.VirtualDisk{}, + indexer.IndexFieldVDByVIDataSourceNotReady, + indexer.IndexVDByVIDataSourceIndexerFunc, + ).WithIndex( + &virtv2.VirtualImage{}, + indexer.IndexFieldVIByVIDataSourceNotReady, + indexer.IndexVIByVIDataSourceIndexerFunc, + ).WithIndex( + &virtv2.ClusterVirtualImage{}, + indexer.IndexFieldCVIByVIDataSourceNotReady, + indexer.IndexCVIByVIDataSourceIndexerFunc, + ).Build() + handler := NewInUseHandler(fakeClient) + + result, err := handler.Handle(context.Background(), vi) + Expect(err).To(BeNil()) + Expect(result).To(Equal(reconcile.Result{})) + inUseCondition, ok := conditions.GetCondition(vicondition.InUseType, vi.Status.Conditions) + if args.ExpectedConditionExists { + Expect(ok).To(BeTrue()) + Expect(inUseCondition.Status).To(Equal(args.ExpectedConditionStatus)) + Expect(inUseCondition.Reason).To(Equal(args.ExpectedConditionReason)) + Expect(inUseCondition.Message).To(Equal(args.ExpectedConditionMessage)) + } else { + Expect(ok).To(BeFalse()) + } +}, + Entry("deletionTimestamp not exists", inUseHandlerTestArgs{ + VMs: []virtv2.VirtualMachine{}, + VINamespacedName: types.NamespacedName{ + Name: "test", + Namespace: "ns", + }, + ExpectedConditionExists: false, + }), + Entry("deletionTimestamp exists but no one uses VI", inUseHandlerTestArgs{ + VMs: []virtv2.VirtualMachine{}, + VINamespacedName: types.NamespacedName{ + Name: "test", + Namespace: "ns", + }, + DeletionTimestamp: ptr.To(metav1.Time{Time: time.Now()}), + ExpectedConditionExists: false, + }), + Entry("has VirtualMachine but with no deleted VI", inUseHandlerTestArgs{ + VINamespacedName: types.NamespacedName{ + Name: "test", + Namespace: "ns", + }, + VMs: []virtv2.VirtualMachine{ + generateVMForInUseTest("test-vm", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test123", + }, + }), + }, + ExpectedConditionExists: false, + }), + Entry("has 1 VirtualMachine with connected terminating VI", inUseHandlerTestArgs{ + VINamespacedName: types.NamespacedName{ + Name: "test", + Namespace: "ns", + }, + DeletionTimestamp: ptr.To(metav1.Time{Time: time.Now()}), + VMs: []virtv2.VirtualMachine{ + generateVMForInUseTest("test-vm", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + }, + ExpectedConditionExists: true, + ExpectedConditionStatus: metav1.ConditionTrue, + ExpectedConditionReason: vicondition.InUse.String(), + ExpectedConditionMessage: "The VirtualImage is currently attached to the VirtualMachine test-vm.", + }), + Entry("has 2 VirtualMachines with connected terminating VI", inUseHandlerTestArgs{ + VINamespacedName: types.NamespacedName{ + Name: "test", + Namespace: "ns", + }, + DeletionTimestamp: ptr.To(metav1.Time{Time: time.Now()}), + VMs: []virtv2.VirtualMachine{ + generateVMForInUseTest("test-vm", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("test-vm2", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + }, + ExpectedConditionExists: true, + ExpectedConditionStatus: metav1.ConditionTrue, + ExpectedConditionReason: vicondition.InUse.String(), + ExpectedConditionMessage: "The VirtualImage is currently attached to the VirtualMachines: test-vm, test-vm2.", + }), + Entry("has 5 VirtualMachines with connected terminating VI", inUseHandlerTestArgs{ + VINamespacedName: types.NamespacedName{ + Name: "test", + Namespace: "ns", + }, + DeletionTimestamp: ptr.To(metav1.Time{Time: time.Now()}), + VMs: []virtv2.VirtualMachine{ + generateVMForInUseTest("test-vm", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("test-vm2", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("test-vm3", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("test-vm4", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("test-vm5", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("test-vm-imposter", "ns-imposter", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + }, + ExpectedConditionExists: true, + ExpectedConditionStatus: metav1.ConditionTrue, + ExpectedConditionReason: vicondition.InUse.String(), + ExpectedConditionMessage: "5 VirtualMachines are using the VirtualImage.", + }), + Entry("has 5 VM with connected terminating VI, 4 VD, 2 CVI, 1 VI", inUseHandlerTestArgs{ + VINamespacedName: types.NamespacedName{ + Name: "test", + Namespace: "ns", + }, + DeletionTimestamp: ptr.To(metav1.Time{Time: time.Now()}), + VMs: []virtv2.VirtualMachine{ + generateVMForInUseTest("test-vm", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("test-vm2", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("test-vm3", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("test-vm4", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + generateVMForInUseTest("test-vm5", "ns", []virtv2.BlockDeviceStatusRef{ + { + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }), + }, + VDs: []virtv2.VirtualDisk{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + Namespace: "ns", + }, + Spec: virtv2.VirtualDiskSpec{ + DataSource: &virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test2", + Namespace: "ns", + }, + Spec: virtv2.VirtualDiskSpec{ + DataSource: &virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test3", + Namespace: "ns", + }, + Spec: virtv2.VirtualDiskSpec{ + DataSource: &virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test4", + Namespace: "ns", + }, + Spec: virtv2.VirtualDiskSpec{ + DataSource: &virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }, + }, + }, + }, + VIs: []virtv2.VirtualImage{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + Namespace: "ns", + }, + Spec: virtv2.VirtualImageSpec{ + DataSource: virtv2.VirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualImageObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + }, + }, + }, + }, + }, + CVIs: []virtv2.ClusterVirtualImage{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + }, + Spec: virtv2.ClusterVirtualImageSpec{ + DataSource: virtv2.ClusterVirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + Namespace: "ns", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test2", + }, + Spec: virtv2.ClusterVirtualImageSpec{ + DataSource: virtv2.ClusterVirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + Namespace: "ns", + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test3", + }, + Spec: virtv2.ClusterVirtualImageSpec{ + DataSource: virtv2.ClusterVirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + Namespace: "test", + }, + }, + }, + }, + }, + ExpectedConditionExists: true, + ExpectedConditionStatus: metav1.ConditionTrue, + ExpectedConditionReason: vicondition.InUse.String(), + ExpectedConditionMessage: "5 VirtualMachines are using the VirtualImage, VirtualImage is used to create 4 VirtualDisks, VirtualImage is currently being used to create the VirtualImage test1, VirtualImage is currently being used to create the ClusterVirtualImages: test1, test2.", + }), +) + +type inUseHandlerTestArgs struct { + VINamespacedName types.NamespacedName + DeletionTimestamp *metav1.Time + VMs []virtv2.VirtualMachine + VDs []virtv2.VirtualDisk + VIs []virtv2.VirtualImage + CVIs []virtv2.ClusterVirtualImage + ExpectedConditionExists bool + ExpectedConditionReason string + ExpectedConditionMessage string + ExpectedConditionStatus metav1.ConditionStatus +} + +func generateVMForInUseTest(name, namespace string, blockDeviceRefs []virtv2.BlockDeviceStatusRef) virtv2.VirtualMachine { + return virtv2.VirtualMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Status: virtv2.VirtualMachineStatus{ + BlockDeviceRefs: blockDeviceRefs, + }, + } +} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go index 8048ef0338..30d2eea6fd 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go @@ -25,7 +25,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/deckhouse/virtualization-controller/pkg/common/annotations" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/supplements" "github.com/deckhouse/virtualization-controller/pkg/controller/vi/internal/source" "github.com/deckhouse/virtualization-controller/pkg/eventrecord" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -33,16 +36,16 @@ import ( ) type LifeCycleHandler struct { - client client.Client - sources Sources - recorder eventrecord.EventRecorderLogger + client client.Client + sources Sources + diskService *service.DiskService + recorder eventrecord.EventRecorderLogger } func NewLifeCycleHandler(recorder eventrecord.EventRecorderLogger, sources Sources, client client.Client) *LifeCycleHandler { return &LifeCycleHandler{ - recorder: recorder, - client: client, - sources: sources, + client: client, + sources: sources, } } @@ -59,8 +62,43 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) ( } if vi.DeletionTimestamp != nil { + needRequeue := false + + if readyCondition.Status == metav1.ConditionTrue { + cb := conditions.NewConditionBuilder(vicondition.ReadyType).Generation(vi.Generation) + + if vi.Spec.Storage == virtv2.StorageContainerRegistry { + cb. + Status(metav1.ConditionTrue). + Reason(vicondition.Ready). + Message("") + } else { + supgen := supplements.NewGenerator(annotations.VIShortName, vi.Name, vi.Namespace, vi.UID) + pvc, err := h.diskService.GetPersistentVolumeClaim(ctx, supgen) + if err != nil { + return reconcile.Result{}, err + } + + switch { + case pvc == nil: + cb. + Status(metav1.ConditionFalse). + Reason(vicondition.Lost). + Message(fmt.Sprintf("PVC %s not found.", supgen.PersistentVolumeClaim().String())) + needRequeue = true + default: + cb. + Status(metav1.ConditionTrue). + Reason(vicondition.Ready). + Message("") + } + } + + conditions.SetCondition(cb, &vi.Status.Conditions) + } + vi.Status.Phase = virtv2.ImageTerminating - return reconcile.Result{}, nil + return reconcile.Result{Requeue: needRequeue}, nil } if vi.Status.Phase == "" { diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go index 00feaafc55..07603a6e8e 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go @@ -504,7 +504,9 @@ func (ds ObjectRefDataSource) Validate(ctx context.Context, vi *virtv2.VirtualIm } if viRef.Spec.Storage == virtv2.StorageKubernetes || viRef.Spec.Storage == virtv2.StoragePersistentVolumeClaim { - if viRef.Status.Phase != virtv2.ImageReady { + readyCondition, _ := conditions.GetCondition(vicondition.ReadyType, viRef.Status.Conditions) + + if readyCondition.Status != metav1.ConditionTrue { return NewImageNotReadyError(vi.Spec.DataSource.ObjectRef.Name) } return nil diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go new file mode 100644 index 0000000000..ae93c6ba28 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go @@ -0,0 +1,100 @@ +/* +Copyright 2025 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 watcher + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/types" + "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/deckhouse/pkg/log" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type ClusterVirtualImageWatcher struct { + logger *log.Logger + client client.Client +} + +func NewClusterVirtualImageWatcher(client client.Client) *ClusterVirtualImageWatcher { + return &ClusterVirtualImageWatcher{ + logger: log.Default().With("watcher", "cvi"), + client: client, + } +} + +func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.ClusterVirtualImage{}), + handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return w.isDataSourceVI(e.Object) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return w.isDataSourceVI(e.Object) + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return w.isDataSourceVI(e.ObjectOld) || w.isDataSourceVI(e.ObjectNew) + }, + }, + ) +} + +func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { + cvi, ok := obj.(*virtv2.ClusterVirtualImage) + if !ok { + w.logger.Error(fmt.Sprintf("expected a ClusterVirtualImage but got a %T", obj)) + return + } + + if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { + return + } + + if cvi.Spec.DataSource.ObjectRef == nil || cvi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind { + return + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: cvi.Spec.DataSource.ObjectRef.Name, + Namespace: cvi.Spec.DataSource.ObjectRef.Namespace, + }, + }) + + return +} + +func (w ClusterVirtualImageWatcher) isDataSourceVI(obj client.Object) bool { + cvi, ok := obj.(*virtv2.ClusterVirtualImage) + if !ok { + w.logger.Error(fmt.Sprintf("expected a ClusterVirtualImage but got a %T", obj)) + return false + } + + return cvi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && cvi.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go new file mode 100644 index 0000000000..4d9967451e --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go @@ -0,0 +1,102 @@ +/* +Copyright 2025 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 watcher + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/types" + "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/deckhouse/pkg/log" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VirtualDiskWatcher struct { + logger *log.Logger + client client.Client +} + +func NewVirtualDiskWatcher(client client.Client) *VirtualDiskWatcher { + return &VirtualDiskWatcher{ + logger: log.Default().With("watcher", "vd"), + client: client, + } +} + +func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualDisk{}), + handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return w.isDataSourceVI(e.Object) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return w.isDataSourceVI(e.Object) + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return w.isDataSourceVI(e.ObjectOld) || w.isDataSourceVI(e.ObjectNew) + }, + }, + ) +} + +func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { + vd, ok := obj.(*virtv2.VirtualDisk) + if !ok { + w.logger.Error(fmt.Sprintf("expected a VirtualDisk but got a %T", obj)) + return + } + + if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { + return + } + + if vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind { + return + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vd.Spec.DataSource.ObjectRef.Name, + Namespace: vd.Namespace, + }, + }) + + return +} + +func (w VirtualDiskWatcher) isDataSourceVI(obj client.Object) bool { + vd, ok := obj.(*virtv2.VirtualDisk) + if !ok { + w.logger.Error(fmt.Sprintf("expected a VirtualDisk but got a %T", obj)) + return false + } + + return vd.Spec.DataSource != nil && + vd.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && + vd.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go new file mode 100644 index 0000000000..8106d9872c --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go @@ -0,0 +1,100 @@ +/* +Copyright 2025 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 watcher + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/types" + "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/deckhouse/pkg/log" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VirtualImageWatcher struct { + logger *log.Logger + client client.Client +} + +func NewVirtualImageWatcher(client client.Client) *VirtualImageWatcher { + return &VirtualImageWatcher{ + logger: log.Default().With("watcher", "vi"), + client: client, + } +} + +func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualImage{}), + handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return w.isDataSourceVI(e.Object) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return w.isDataSourceVI(e.Object) + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return w.isDataSourceVI(e.ObjectOld) || w.isDataSourceVI(e.ObjectNew) + }, + }, + ) +} + +func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { + vi, ok := obj.(*virtv2.VirtualImage) + if !ok { + w.logger.Error(fmt.Sprintf("expected a VirtualImage but got a %T", obj)) + return + } + + if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { + return + } + + if vi.Spec.DataSource.ObjectRef == nil || vi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind { + return + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vi.Spec.DataSource.ObjectRef.Name, + Namespace: vi.Namespace, + }, + }) + + return +} + +func (w VirtualImageWatcher) isDataSourceVI(obj client.Object) bool { + vi, ok := obj.(*virtv2.VirtualImage) + if !ok { + w.logger.Error(fmt.Sprintf("expected a VirtualImage but got a %T", obj)) + return false + } + + return vi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && vi.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/vi/vi_controller.go b/images/virtualization-artifact/pkg/controller/vi/vi_controller.go index 3a02fbc9c9..b70837b083 100644 --- a/images/virtualization-artifact/pkg/controller/vi/vi_controller.go +++ b/images/virtualization-artifact/pkg/controller/vi/vi_controller.go @@ -81,6 +81,7 @@ func NewController( internal.NewStorageClassReadyHandler(recorder, scService), internal.NewDatasourceReadyHandler(sources), internal.NewLifeCycleHandler(recorder, sources, mgr.GetClient()), + internal.NewInUseHandler(mgr.GetClient()), internal.NewDeletionHandler(sources), internal.NewAttacheeHandler(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 7623d3ba56..f70c56befc 100644 --- a/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go @@ -225,6 +225,9 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr watcher.NewStorageClassWatcher(mgr.GetClient()), watcher.NewVirtualMachineWatcher(mgr.GetClient()), watcher.NewVirtualDiskSnapshotWatcher(mgr.GetClient()), + watcher.NewVirtualDiskWatcher(mgr.GetClient()), + watcher.NewClusterVirtualImageWatcher(mgr.GetClient()), + watcher.NewVirtualImageWatcher(mgr.GetClient()), } { err := w.Watch(mgr, ctr) if err != nil { From 440d23e77430c0f9484eb8039a3dbdf664872c51 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 14 Apr 2025 11:34:35 +0300 Subject: [PATCH 02/27] fix merge conflicts Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/cvi_reconciler.go | 13 --------- .../pkg/controller/cvi/internal/deletion.go | 2 +- .../pkg/controller/indexer/cvi_indexer.go | 27 ++----------------- 3 files changed, 3 insertions(+), 39 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go index 1dbfb73f20..edc73140b3 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go @@ -43,10 +43,6 @@ type Watcher interface { Watch(mgr manager.Manager, ctr controller.Controller) error } -type Watcher interface { - Watch(mgr manager.Manager, ctr controller.Controller) error -} - type Handler interface { Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImage) (reconcile.Result, error) } @@ -177,15 +173,6 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return fmt.Errorf("error setting watch on CVIs: %w", err) } - for _, w := range []Watcher{ - watcher.NewVirtualDiskSnapshotWatcher(mgr.GetClient()), - } { - err := w.Watch(mgr, ctr) - if err != nil { - return fmt.Errorf("error setting watcher: %w", err) - } - } - return nil } diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go b/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go index 39821bc7b1..6498732d1a 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go @@ -53,7 +53,7 @@ func (h DeletionHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualI return reconcile.Result{}, nil } - result, err := h.sources.CleanUp(ctx, cvi) + requeue, err := h.sources.CleanUp(ctx, cvi) if err != nil { return reconcile.Result{}, err } diff --git a/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go index 8679673362..afc4c9c84d 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go @@ -1,5 +1,5 @@ /* -Copyright 2024 Flant JSC +Copyright 2025 Flant JSC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package indexer import ( "context" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -66,30 +67,6 @@ func IndexCVIByVIDataSourceIndexerFunc(object client.Object) []string { return []string{cvi.Spec.DataSource.ObjectRef.Name} } -/* -Copyright 2025 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 indexer - -import ( - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - - virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" -) func IndexCVIByVDSnapshot() (obj client.Object, field string, extractValue client.IndexerFunc) { return &virtv2.ClusterVirtualImage{}, IndexFieldCVIByVDSnapshot, func(object client.Object) []string { From 7928982f3d9834aacfbd264db8248dc87c07932c Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 14 Apr 2025 15:33:48 +0300 Subject: [PATCH 03/27] fix Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/cvi_reconciler.go | 3 ++- .../cvi/internal/{watchers => watcher}/cvi_watcher.go | 0 .../cvi/internal/{watchers => watcher}/vd_watcher.go | 0 .../cvi/internal/{watchers => watcher}/vi_watcher.go | 0 4 files changed, 2 insertions(+), 1 deletion(-) rename images/virtualization-artifact/pkg/controller/cvi/internal/{watchers => watcher}/cvi_watcher.go (100%) rename images/virtualization-artifact/pkg/controller/cvi/internal/{watchers => watcher}/vd_watcher.go (100%) rename images/virtualization-artifact/pkg/controller/cvi/internal/{watchers => watcher}/vi_watcher.go (100%) diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go index edc73140b3..54c0e45d2a 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go @@ -32,7 +32,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" - watcher "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/watchers" + watcher "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/watcher" "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" "github.com/deckhouse/virtualization-controller/pkg/controller/watchers" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -154,6 +154,7 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr watcher.NewClusterVirtualImageWatcher(mgr.GetClient()), watcher.NewVirtualImageWatcher(mgr.GetClient()), watcher.NewVirtualDiskWatcher(mgr.GetClient()), + watcher.NewVirtualDiskSnapshotWatcher(mgr.GetClient()), } { err := w.Watch(mgr, ctr) if err != nil { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go similarity index 100% rename from images/virtualization-artifact/pkg/controller/cvi/internal/watchers/cvi_watcher.go rename to images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vd_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go similarity index 100% rename from images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vd_watcher.go rename to images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go similarity index 100% rename from images/virtualization-artifact/pkg/controller/cvi/internal/watchers/vi_watcher.go rename to images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go From 35ca77f821ac343c15b51cacec5f9b75f8766082 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 14 Apr 2025 15:58:45 +0300 Subject: [PATCH 04/27] refactoring Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/cvi_reconciler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go index 54c0e45d2a..cc2171e9ae 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go @@ -32,7 +32,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" - watcher "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/watcher" + "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/watcher" "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" "github.com/deckhouse/virtualization-controller/pkg/controller/watchers" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" From 10e0e42293580faf9e06e38b4e8fa7b6c698f95f Mon Sep 17 00:00:00 2001 From: mortelumina98 Date: Wed, 16 Apr 2025 16:51:11 +0300 Subject: [PATCH 05/27] crds review Signed-off-by: mortelumina98 --- crds/clustervirtualimages.yaml | 2 ++ crds/doc-ru-clustervirtualimages.yaml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crds/clustervirtualimages.yaml b/crds/clustervirtualimages.yaml index 68798fcd03..f2994b95bb 100644 --- a/crds/clustervirtualimages.yaml +++ b/crds/clustervirtualimages.yaml @@ -408,6 +408,8 @@ spec: description: Deprecated. Use `imageUploadURLs` instead. type: string usedInNamespaces: + description: | + Displays the list of namespaces where the image is currently used when attempting to delete it. items: type: string type: array diff --git a/crds/doc-ru-clustervirtualimages.yaml b/crds/doc-ru-clustervirtualimages.yaml index 75afa619f0..3b87795cb1 100644 --- a/crds/doc-ru-clustervirtualimages.yaml +++ b/crds/doc-ru-clustervirtualimages.yaml @@ -174,7 +174,7 @@ spec: Устаревшее поле. Используйте `imageUploadURLs`. usedInNamespaces: description: | - Поле, отображающее неймспейсы в которых используется образ, при удалении образа. + Список пространств имён, в которых используется образ, отображается при его удалении. imageUploadURLs: properties: external: From 716ae4ffb71d85f0c91204b352fee89953ceb5c6 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 21 Apr 2025 11:45:06 +0300 Subject: [PATCH 06/27] remove adding finalizers in deleteion handlers Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/internal/deletion.go | 1 - .../pkg/controller/vi/internal/deletion.go | 1 - 2 files changed, 2 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go b/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go index 6498732d1a..108af81332 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/deletion.go @@ -49,7 +49,6 @@ func (h DeletionHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualI if cvi.DeletionTimestamp != nil { inUseCondition, _ := conditions.GetCondition(cvicondition.InUseType, cvi.Status.Conditions) if inUseCondition.Status == metav1.ConditionTrue && conditions.IsLastUpdated(inUseCondition, cvi) { - controllerutil.AddFinalizer(cvi, virtv2.FinalizerCVICleanup) return reconcile.Result{}, nil } diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/deletion.go b/images/virtualization-artifact/pkg/controller/vi/internal/deletion.go index c6a0743179..6659ea2688 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/deletion.go @@ -49,7 +49,6 @@ func (h DeletionHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) (r if vi.DeletionTimestamp != nil { inUseCondition, _ := conditions.GetCondition(vicondition.InUseType, vi.Status.Conditions) if inUseCondition.Status == metav1.ConditionTrue && conditions.IsLastUpdated(inUseCondition, vi) { - controllerutil.AddFinalizer(vi, virtv2.FinalizerVICleanup) return reconcile.Result{}, nil } From 8082cc359936bf22d897b18f3a0999de336052ec Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 21 Apr 2025 11:46:40 +0300 Subject: [PATCH 07/27] suite_test Signed-off-by: Valeriy Khorunzhin --- .../controller/cvi/internal/{handler_test.go => suite_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename images/virtualization-artifact/pkg/controller/cvi/internal/{handler_test.go => suite_test.go} (100%) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/handler_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/suite_test.go similarity index 100% rename from images/virtualization-artifact/pkg/controller/cvi/internal/handler_test.go rename to images/virtualization-artifact/pkg/controller/cvi/internal/suite_test.go From 833787d81590e852baa62fc1f1f5b00d76e9431b Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 21 Apr 2025 18:30:52 +0300 Subject: [PATCH 08/27] refactoring tests Signed-off-by: Valeriy Khorunzhin --- .../pkg/builder/cvi/cvi.go | 50 +++++ .../pkg/builder/cvi/option.go | 38 ++++ .../pkg/builder/vd/option.go | 39 ++++ .../pkg/builder/vd/vd.go | 51 +++++ .../pkg/builder/vi/option.go | 39 ++++ .../pkg/builder/vi/vi.go | 51 +++++ .../pkg/common/testutil/testutil.go | 16 +- .../pkg/controller/cvi/internal/inuse_test.go | 184 ++++++---------- .../pkg/controller/vi/internal/inuse_test.go | 208 +++++++----------- 9 files changed, 430 insertions(+), 246 deletions(-) create mode 100644 images/virtualization-artifact/pkg/builder/cvi/cvi.go create mode 100644 images/virtualization-artifact/pkg/builder/cvi/option.go create mode 100644 images/virtualization-artifact/pkg/builder/vd/option.go create mode 100644 images/virtualization-artifact/pkg/builder/vd/vd.go create mode 100644 images/virtualization-artifact/pkg/builder/vi/option.go create mode 100644 images/virtualization-artifact/pkg/builder/vi/vi.go diff --git a/images/virtualization-artifact/pkg/builder/cvi/cvi.go b/images/virtualization-artifact/pkg/builder/cvi/cvi.go new file mode 100644 index 0000000000..ab68362378 --- /dev/null +++ b/images/virtualization-artifact/pkg/builder/cvi/cvi.go @@ -0,0 +1,50 @@ +/* +Copyright 2025 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 cvi + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func New(options ...Option) *v1alpha2.ClusterVirtualImage { + cvi := NewEmpty("") + ApplyOptions(cvi, options) + return cvi +} + +func ApplyOptions(cvi *v1alpha2.ClusterVirtualImage, opts []Option) { + if cvi == nil { + return + } + for _, opt := range opts { + opt(cvi) + } +} + +func NewEmpty(name string) *v1alpha2.ClusterVirtualImage { + return &v1alpha2.ClusterVirtualImage{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha2.SchemeGroupVersion.String(), + Kind: v1alpha2.VirtualDiskKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } +} diff --git a/images/virtualization-artifact/pkg/builder/cvi/option.go b/images/virtualization-artifact/pkg/builder/cvi/option.go new file mode 100644 index 0000000000..ee576afaa3 --- /dev/null +++ b/images/virtualization-artifact/pkg/builder/cvi/option.go @@ -0,0 +1,38 @@ +/* +Copyright 2025 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 cvi + +import ( + "github.com/deckhouse/virtualization-controller/pkg/builder/meta" + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type Option func(cvi *v1alpha2.ClusterVirtualImage) + +var ( + WithName = meta.WithName[*v1alpha2.ClusterVirtualImage] + WithLabel = meta.WithLabel[*v1alpha2.ClusterVirtualImage] + WithLabels = meta.WithLabels[*v1alpha2.ClusterVirtualImage] + WithAnnotation = meta.WithAnnotation[*v1alpha2.ClusterVirtualImage] + WithAnnotations = meta.WithAnnotations[*v1alpha2.ClusterVirtualImage] +) + +func WithDatasource(datasource v1alpha2.ClusterVirtualImageDataSource) func(cvi *v1alpha2.ClusterVirtualImage) { + return func(cvi *v1alpha2.ClusterVirtualImage) { + cvi.Spec.DataSource = datasource + } +} diff --git a/images/virtualization-artifact/pkg/builder/vd/option.go b/images/virtualization-artifact/pkg/builder/vd/option.go new file mode 100644 index 0000000000..3ed62f1e65 --- /dev/null +++ b/images/virtualization-artifact/pkg/builder/vd/option.go @@ -0,0 +1,39 @@ +/* +Copyright 2025 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 vd + +import ( + "github.com/deckhouse/virtualization-controller/pkg/builder/meta" + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type Option func(vd *v1alpha2.VirtualDisk) + +var ( + WithName = meta.WithName[*v1alpha2.VirtualDisk] + WithNamespace = meta.WithNamespace[*v1alpha2.VirtualDisk] + WithLabel = meta.WithLabel[*v1alpha2.VirtualDisk] + WithLabels = meta.WithLabels[*v1alpha2.VirtualDisk] + WithAnnotation = meta.WithAnnotation[*v1alpha2.VirtualDisk] + WithAnnotations = meta.WithAnnotations[*v1alpha2.VirtualDisk] +) + +func WithDatasource(datasource *v1alpha2.VirtualDiskDataSource) func(vd *v1alpha2.VirtualDisk) { + return func(vd *v1alpha2.VirtualDisk) { + vd.Spec.DataSource = datasource + } +} diff --git a/images/virtualization-artifact/pkg/builder/vd/vd.go b/images/virtualization-artifact/pkg/builder/vd/vd.go new file mode 100644 index 0000000000..c959acfdec --- /dev/null +++ b/images/virtualization-artifact/pkg/builder/vd/vd.go @@ -0,0 +1,51 @@ +/* +Copyright 2025 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 vd + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func New(options ...Option) *v1alpha2.VirtualDisk { + vd := NewEmpty("", "") + ApplyOptions(vd, options) + return vd +} + +func ApplyOptions(vd *v1alpha2.VirtualDisk, opts []Option) { + if vd == nil { + return + } + for _, opt := range opts { + opt(vd) + } +} + +func NewEmpty(name, namespace string) *v1alpha2.VirtualDisk { + return &v1alpha2.VirtualDisk{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha2.SchemeGroupVersion.String(), + Kind: v1alpha2.VirtualDiskKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } +} diff --git a/images/virtualization-artifact/pkg/builder/vi/option.go b/images/virtualization-artifact/pkg/builder/vi/option.go new file mode 100644 index 0000000000..a01d30587a --- /dev/null +++ b/images/virtualization-artifact/pkg/builder/vi/option.go @@ -0,0 +1,39 @@ +/* +Copyright 2025 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 vi + +import ( + "github.com/deckhouse/virtualization-controller/pkg/builder/meta" + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type Option func(vi *v1alpha2.VirtualImage) + +var ( + WithName = meta.WithName[*v1alpha2.VirtualImage] + WithNamespace = meta.WithNamespace[*v1alpha2.VirtualImage] + WithLabel = meta.WithLabel[*v1alpha2.VirtualImage] + WithLabels = meta.WithLabels[*v1alpha2.VirtualImage] + WithAnnotation = meta.WithAnnotation[*v1alpha2.VirtualImage] + WithAnnotations = meta.WithAnnotations[*v1alpha2.VirtualImage] +) + +func WithDatasource(datasource v1alpha2.VirtualImageDataSource) func(vi *v1alpha2.VirtualImage) { + return func(vi *v1alpha2.VirtualImage) { + vi.Spec.DataSource = datasource + } +} diff --git a/images/virtualization-artifact/pkg/builder/vi/vi.go b/images/virtualization-artifact/pkg/builder/vi/vi.go new file mode 100644 index 0000000000..f0daf9e756 --- /dev/null +++ b/images/virtualization-artifact/pkg/builder/vi/vi.go @@ -0,0 +1,51 @@ +/* +Copyright 2025 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 vi + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func New(options ...Option) *v1alpha2.VirtualImage { + vi := NewEmpty("", "") + ApplyOptions(vi, options) + return vi +} + +func ApplyOptions(vi *v1alpha2.VirtualImage, opts []Option) { + if vi == nil { + return + } + for _, opt := range opts { + opt(vi) + } +} + +func NewEmpty(name, namespace string) *v1alpha2.VirtualImage { + return &v1alpha2.VirtualImage{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha2.SchemeGroupVersion.String(), + Kind: v1alpha2.VirtualDiskKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } +} diff --git a/images/virtualization-artifact/pkg/common/testutil/testutil.go b/images/virtualization-artifact/pkg/common/testutil/testutil.go index 842bebe2af..49c387fd65 100644 --- a/images/virtualization-artifact/pkg/common/testutil/testutil.go +++ b/images/virtualization-artifact/pkg/common/testutil/testutil.go @@ -30,11 +30,18 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) func NewFakeClientWithObjects(objs ...client.Object) (client.WithWatch, error) { + builder, err := NewFakeClientBuilderWithObjects(objs...) + if err != nil { + return nil, err + } + return builder.Build(), nil +} + +func NewFakeClientBuilderWithObjects(objs ...client.Object) (*fake.ClientBuilder, error) { scheme := apiruntime.NewScheme() for _, f := range []func(*apiruntime.Scheme) error{ virtv2.AddToScheme, @@ -54,12 +61,7 @@ func NewFakeClientWithObjects(objs ...client.Object) (client.WithWatch, error) { } newObjs = append(newObjs, obj) } - b := fake.NewClientBuilder().WithScheme(scheme).WithObjects(newObjs...).WithStatusSubresource(newObjs...) - for _, fn := range indexer.IndexGetters { - b.WithIndex(fn()) - } - - return b.Build(), nil + return fake.NewClientBuilder().WithScheme(scheme).WithObjects(newObjs...).WithStatusSubresource(newObjs...), nil } func NewNoOpLogger() *log.Logger { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go index 27ced12556..52172e2e20 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go @@ -22,15 +22,15 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - apiruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/utils/ptr" - virtv1 "kubevirt.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" + cvibuilder "github.com/deckhouse/virtualization-controller/pkg/builder/cvi" + vdbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vd" + vibuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vi" + "github.com/deckhouse/virtualization-controller/pkg/common/testutil" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -38,16 +38,6 @@ import ( ) var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { - scheme := apiruntime.NewScheme() - for _, f := range []func(*apiruntime.Scheme) error{ - virtv2.AddToScheme, - virtv1.AddToScheme, - corev1.AddToScheme, - } { - err := f(scheme) - Expect(err).NotTo(HaveOccurred(), "failed to add scheme: %s", err) - } - cvi := &virtv2.ClusterVirtualImage{ ObjectMeta: metav1.ObjectMeta{ Name: args.CVIName, @@ -81,7 +71,9 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { objects = append(objects, &cvi) } - fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).WithIndex( + fakeClientBuilder, err := testutil.NewFakeClientBuilderWithObjects(objects...) + Expect(err).ShouldNot(HaveOccurred()) + fakeClient := fakeClientBuilder.WithIndex( &virtv2.VirtualDisk{}, indexer.IndexFieldVDByCVIDataSourceNotReady, indexer.IndexVDByVIDataSourceIndexerFunc, @@ -216,114 +208,59 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { }), }, VDs: []virtv2.VirtualDisk{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "ns1", - }, - Spec: virtv2.VirtualDiskSpec{ - DataSource: &virtv2.VirtualDiskDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.VirtualDiskObjectRef{ - Kind: virtv2.ClusterVirtualImageKind, - Name: "test", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test2", - Namespace: "ns2", - }, - Spec: virtv2.VirtualDiskSpec{ - DataSource: &virtv2.VirtualDiskDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.VirtualDiskObjectRef{ - Kind: virtv2.ClusterVirtualImageKind, - Name: "test", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test3", - Namespace: "ns3", + generateVDForInUseTest("test", "ns1", virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", }, - Spec: virtv2.VirtualDiskSpec{ - DataSource: &virtv2.VirtualDiskDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.VirtualDiskObjectRef{ - Kind: virtv2.ClusterVirtualImageKind, - Name: "test", - }, - }, + }), + generateVDForInUseTest("test2", "ns2", virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test4", - Namespace: "ns4", + }), + generateVDForInUseTest("test3", "ns3", virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", }, - Spec: virtv2.VirtualDiskSpec{ - DataSource: &virtv2.VirtualDiskDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.VirtualDiskObjectRef{ - Kind: virtv2.ClusterVirtualImageKind, - Name: "test", - }, - }, + }), + generateVDForInUseTest("test4", "ns4", virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", }, - }, + }), }, VIs: []virtv2.VirtualImage{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "ns", - }, - Spec: virtv2.VirtualImageSpec{ - DataSource: virtv2.VirtualImageDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.VirtualImageObjectRef{ - Kind: virtv2.ClusterVirtualImageKind, - Name: "test", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test2", - Namespace: "ns5", + generateVIForInUseTest("test", "ns", virtv2.VirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualImageObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", }, - Spec: virtv2.VirtualImageSpec{ - DataSource: virtv2.VirtualImageDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.VirtualImageObjectRef{ - Kind: virtv2.ClusterVirtualImageKind, - Name: "test", - }, - }, + }), + generateVIForInUseTest("test2", "ns5", virtv2.VirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualImageObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", }, - }, + }), }, CVIs: []virtv2.ClusterVirtualImage{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test2", - }, - Spec: virtv2.ClusterVirtualImageSpec{ - DataSource: virtv2.ClusterVirtualImageDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ - Kind: virtv2.ClusterVirtualImageKind, - Name: "test", - }, - }, + generateCVIForInUseTest("test2", virtv2.ClusterVirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", }, - }, + }), }, ExpectedConditionExists: true, ExpectedConditionStatus: metav1.ConditionTrue, @@ -356,3 +293,26 @@ func generateVMForInUseTest(name, namespace string, blockDeviceRefs []virtv2.Blo }, } } + +func generateVIForInUseTest(name, namespace string, datasource virtv2.VirtualImageDataSource) virtv2.VirtualImage { + return *vibuilder.New( + vibuilder.WithName(name), + vibuilder.WithNamespace(namespace), + vibuilder.WithDatasource(datasource), + ) +} + +func generateCVIForInUseTest(name string, datasource virtv2.ClusterVirtualImageDataSource) virtv2.ClusterVirtualImage { + return *cvibuilder.New( + cvibuilder.WithName(name), + cvibuilder.WithDatasource(datasource), + ) +} + +func generateVDForInUseTest(name, namespace string, datasource virtv2.VirtualDiskDataSource) virtv2.VirtualDisk { + return *vdbuilder.New( + vdbuilder.WithName(name), + vdbuilder.WithNamespace(namespace), + vdbuilder.WithDatasource(&datasource), + ) +} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go index 38d0dca530..3f85ff2d92 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go @@ -22,16 +22,16 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - apiruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" - virtv1 "kubevirt.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" + cvibuilder "github.com/deckhouse/virtualization-controller/pkg/builder/cvi" + vdbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vd" + vibuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vi" + "github.com/deckhouse/virtualization-controller/pkg/common/testutil" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -39,16 +39,6 @@ import ( ) var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { - scheme := apiruntime.NewScheme() - for _, f := range []func(*apiruntime.Scheme) error{ - virtv2.AddToScheme, - virtv1.AddToScheme, - corev1.AddToScheme, - } { - err := f(scheme) - Expect(err).NotTo(HaveOccurred(), "failed to add scheme: %s", err) - } - vi := &virtv2.VirtualImage{ ObjectMeta: metav1.ObjectMeta{ Name: args.VINamespacedName.Name, @@ -83,7 +73,9 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { objects = append(objects, &cvi) } - fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).WithIndex( + fakeClientBuilder, err := testutil.NewFakeClientBuilderWithObjects(objects...) + Expect(err).ShouldNot(HaveOccurred()) + fakeClient := fakeClientBuilder.WithIndex( &virtv2.VirtualDisk{}, indexer.IndexFieldVDByVIDataSourceNotReady, indexer.IndexVDByVIDataSourceIndexerFunc, @@ -275,130 +267,69 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { }), }, VDs: []virtv2.VirtualDisk{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test1", - Namespace: "ns", - }, - Spec: virtv2.VirtualDiskSpec{ - DataSource: &virtv2.VirtualDiskDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.VirtualDiskObjectRef{ - Kind: virtv2.VirtualImageKind, - Name: "test", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test2", - Namespace: "ns", - }, - Spec: virtv2.VirtualDiskSpec{ - DataSource: &virtv2.VirtualDiskDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.VirtualDiskObjectRef{ - Kind: virtv2.VirtualImageKind, - Name: "test", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test3", - Namespace: "ns", + generateVDForInUseTest("test1", "ns", virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", }, - Spec: virtv2.VirtualDiskSpec{ - DataSource: &virtv2.VirtualDiskDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.VirtualDiskObjectRef{ - Kind: virtv2.VirtualImageKind, - Name: "test", - }, - }, + }), + generateVDForInUseTest("test2", "ns", virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test4", - Namespace: "ns", + }), + generateVDForInUseTest("test3", "ns", virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", }, - Spec: virtv2.VirtualDiskSpec{ - DataSource: &virtv2.VirtualDiskDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.VirtualDiskObjectRef{ - Kind: virtv2.VirtualImageKind, - Name: "test", - }, - }, + }), + generateVDForInUseTest("test4", "ns", virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", }, - }, + }), }, VIs: []virtv2.VirtualImage{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test1", - Namespace: "ns", - }, - Spec: virtv2.VirtualImageSpec{ - DataSource: virtv2.VirtualImageDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.VirtualImageObjectRef{ - Kind: virtv2.VirtualImageKind, - Name: "test", - }, - }, + generateVIForInUseTest("test1", "ns", virtv2.VirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualImageObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", }, - }, + }), }, CVIs: []virtv2.ClusterVirtualImage{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test1", - }, - Spec: virtv2.ClusterVirtualImageSpec{ - DataSource: virtv2.ClusterVirtualImageDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ - Kind: virtv2.VirtualImageKind, - Name: "test", - Namespace: "ns", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test2", - }, - Spec: virtv2.ClusterVirtualImageSpec{ - DataSource: virtv2.ClusterVirtualImageDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ - Kind: virtv2.VirtualImageKind, - Name: "test", - Namespace: "ns", - }, - }, + generateCVIForInUseTest("test1", virtv2.ClusterVirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + Namespace: "ns", }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "test3", + }), + generateCVIForInUseTest("test2", virtv2.ClusterVirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + Namespace: "ns", }, - Spec: virtv2.ClusterVirtualImageSpec{ - DataSource: virtv2.ClusterVirtualImageDataSource{ - Type: virtv2.DataSourceTypeObjectRef, - ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ - Kind: virtv2.VirtualImageKind, - Name: "test", - Namespace: "test", - }, - }, + }), + generateCVIForInUseTest("test3", virtv2.ClusterVirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + Namespace: "test", }, - }, + }), }, ExpectedConditionExists: true, ExpectedConditionStatus: metav1.ConditionTrue, @@ -431,3 +362,26 @@ func generateVMForInUseTest(name, namespace string, blockDeviceRefs []virtv2.Blo }, } } + +func generateVIForInUseTest(name, namespace string, datasource virtv2.VirtualImageDataSource) virtv2.VirtualImage { + return *vibuilder.New( + vibuilder.WithName(name), + vibuilder.WithNamespace(namespace), + vibuilder.WithDatasource(datasource), + ) +} + +func generateCVIForInUseTest(name string, datasource virtv2.ClusterVirtualImageDataSource) virtv2.ClusterVirtualImage { + return *cvibuilder.New( + cvibuilder.WithName(name), + cvibuilder.WithDatasource(datasource), + ) +} + +func generateVDForInUseTest(name, namespace string, datasource virtv2.VirtualDiskDataSource) virtv2.VirtualDisk { + return *vdbuilder.New( + vdbuilder.WithName(name), + vdbuilder.WithNamespace(namespace), + vdbuilder.WithDatasource(&datasource), + ) +} From 3ebf06ee6dc10bf355e3366b1e6707fdd477fb3e Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Tue, 22 Apr 2025 16:25:30 +0300 Subject: [PATCH 09/27] use test context Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/internal/inuse_test.go | 3 +-- .../pkg/controller/vi/internal/inuse_test.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go index 52172e2e20..2e71192108 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go @@ -17,7 +17,6 @@ limitations under the License. package internal import ( - "context" "time" . "github.com/onsi/ginkgo/v2" @@ -88,7 +87,7 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { ).Build() handler := NewInUseHandler(fakeClient) - result, err := handler.Handle(context.Background(), cvi) + result, err := handler.Handle(testutil.ContextBackgroundWithNoOpLogger(), cvi) Expect(err).To(BeNil()) Expect(result).To(Equal(reconcile.Result{})) inUseCondition, ok := conditions.GetCondition(cvicondition.InUseType, cvi.Status.Conditions) diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go index 3f85ff2d92..639de11a20 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go @@ -17,7 +17,6 @@ limitations under the License. package internal import ( - "context" "time" . "github.com/onsi/ginkgo/v2" @@ -90,7 +89,7 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { ).Build() handler := NewInUseHandler(fakeClient) - result, err := handler.Handle(context.Background(), vi) + result, err := handler.Handle(testutil.ContextBackgroundWithNoOpLogger(), vi) Expect(err).To(BeNil()) Expect(result).To(Equal(reconcile.Result{})) inUseCondition, ok := conditions.GetCondition(vicondition.InUseType, vi.Status.Conditions) From c51e540b8e66a72e052dc1afda9638cc96f94c34 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 23 Apr 2025 15:03:26 +0300 Subject: [PATCH 10/27] refactoring Signed-off-by: Valeriy Khorunzhin --- .../pkg/builder/cvi/cvi.go | 2 +- .../pkg/builder/cvi/option.go | 13 ++ .../pkg/builder/vi/vi.go | 2 +- .../pkg/controller/cvi/internal/inuse.go | 196 +++++++++--------- .../pkg/controller/cvi/internal/inuse_test.go | 37 +++- .../pkg/controller/indexer/cvi_indexer.go | 8 +- .../pkg/controller/indexer/indexer.go | 12 +- .../pkg/controller/indexer/vd_indexer.go | 12 +- .../pkg/controller/indexer/vi_indexer.go | 8 +- .../pkg/controller/vi/internal/inuse.go | 133 +++++------- .../pkg/controller/vi/internal/inuse_test.go | 32 ++- 11 files changed, 242 insertions(+), 213 deletions(-) diff --git a/images/virtualization-artifact/pkg/builder/cvi/cvi.go b/images/virtualization-artifact/pkg/builder/cvi/cvi.go index ab68362378..ef209ea8ab 100644 --- a/images/virtualization-artifact/pkg/builder/cvi/cvi.go +++ b/images/virtualization-artifact/pkg/builder/cvi/cvi.go @@ -41,7 +41,7 @@ func NewEmpty(name string) *v1alpha2.ClusterVirtualImage { return &v1alpha2.ClusterVirtualImage{ TypeMeta: metav1.TypeMeta{ APIVersion: v1alpha2.SchemeGroupVersion.String(), - Kind: v1alpha2.VirtualDiskKind, + Kind: v1alpha2.ClusterVirtualImageKind, }, ObjectMeta: metav1.ObjectMeta{ Name: name, diff --git a/images/virtualization-artifact/pkg/builder/cvi/option.go b/images/virtualization-artifact/pkg/builder/cvi/option.go index ee576afaa3..6bd08a78b8 100644 --- a/images/virtualization-artifact/pkg/builder/cvi/option.go +++ b/images/virtualization-artifact/pkg/builder/cvi/option.go @@ -19,6 +19,7 @@ package cvi import ( "github.com/deckhouse/virtualization-controller/pkg/builder/meta" "github.com/deckhouse/virtualization/api/core/v1alpha2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type Option func(cvi *v1alpha2.ClusterVirtualImage) @@ -36,3 +37,15 @@ func WithDatasource(datasource v1alpha2.ClusterVirtualImageDataSource) func(cvi cvi.Spec.DataSource = datasource } } + +func WithPhase(phase v1alpha2.ImagePhase) func(cvi *v1alpha2.ClusterVirtualImage) { + return func(cvi *v1alpha2.ClusterVirtualImage) { + cvi.Status.Phase = phase + } +} + +func WithCondition(condition metav1.Condition) func(cvi *v1alpha2.ClusterVirtualImage) { + return func(cvi *v1alpha2.ClusterVirtualImage) { + cvi.Status.Conditions = append(cvi.Status.Conditions, condition) + } +} diff --git a/images/virtualization-artifact/pkg/builder/vi/vi.go b/images/virtualization-artifact/pkg/builder/vi/vi.go index f0daf9e756..731dd527d8 100644 --- a/images/virtualization-artifact/pkg/builder/vi/vi.go +++ b/images/virtualization-artifact/pkg/builder/vi/vi.go @@ -41,7 +41,7 @@ func NewEmpty(name, namespace string) *v1alpha2.VirtualImage { return &v1alpha2.VirtualImage{ TypeMeta: metav1.TypeMeta{ APIVersion: v1alpha2.SchemeGroupVersion.String(), - Kind: v1alpha2.VirtualDiskKind, + Kind: v1alpha2.VirtualImageKind, }, ObjectMeta: metav1.ObjectMeta{ Name: name, diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go index af14d38fef..c64d3893a4 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go @@ -19,6 +19,7 @@ package internal import ( "context" "fmt" + "k8s.io/apimachinery/pkg/types" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -66,7 +67,7 @@ func (h InUseHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImag return reconcile.Result{}, err } - var vmUsedImage []*virtv2.VirtualMachine + var vmUsedImage []client.Object for _, vm := range vms.Items { for _, bd := range vm.Status.BlockDeviceRefs { if bd.Kind == virtv2.ClusterVirtualImageKind && bd.Name == cvi.Name { @@ -78,116 +79,76 @@ func (h InUseHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImag var vds virtv2.VirtualDiskList err = h.client.List(ctx, &vds, client.MatchingFields{ - indexer.IndexFieldVDByCVIDataSourceNotReady: cvi.GetName(), + indexer.IndexFieldVDByCVIDataSource: cvi.GetName(), }) if err != nil { return reconcile.Result{}, err } + var vdsNotReady []client.Object for _, vd := range vds.Items { - namespacesMap[vd.Namespace] = struct{}{} + if vd.Status.Phase != virtv2.DiskReady { + vdsNotReady = append(vdsNotReady, &vd) + } + } + + for _, vd := range vdsNotReady { + namespacesMap[vd.GetNamespace()] = struct{}{} } var vis virtv2.VirtualImageList err = h.client.List(ctx, &vis, client.MatchingFields{ - indexer.IndexFieldVIByCVIDataSourceNotReady: cvi.GetName(), + indexer.IndexFieldVIByCVIDataSource: cvi.GetName(), }) if err != nil { return reconcile.Result{}, err } + var visNotReady []client.Object for _, vi := range vis.Items { - namespacesMap[vi.Namespace] = struct{}{} + if vi.Status.Phase != virtv2.ImageReady { + visNotReady = append(visNotReady, &vi) + } + } + + for _, vi := range visNotReady { + namespacesMap[vi.GetNamespace()] = struct{}{} } var cvis virtv2.ClusterVirtualImageList err = h.client.List(ctx, &cvis, client.MatchingFields{ - indexer.IndexFieldCVIByCVIDataSourceNotReady: cvi.GetName(), + indexer.IndexFieldCVIByCVIDataSource: cvi.GetName(), }) if err != nil { return reconcile.Result{}, err } - for _, cv := range cvis.Items { - namespacesMap[cv.Namespace] = struct{}{} + var cvisNotReady []client.Object + for _, cvi := range cvis.Items { + if cvi.Status.Phase != virtv2.ImageReady { + cvisNotReady = append(cvisNotReady, &cvi) + } } - consumerCount := len(vmUsedImage) + len(vds.Items) + len(vis.Items) + len(cvis.Items) + for _, cv := range cvisNotReady { + namespacesMap[cv.GetNamespace()] = struct{}{} + } + + consumerCount := len(vmUsedImage) + len(vdsNotReady) + len(visNotReady) + len(cvisNotReady) if consumerCount > 0 { - var messageBuilder strings.Builder - var needComma bool + var msgs []string if len(vmUsedImage) > 0 { - needComma = true - switch len(vmUsedImage) { - case 1: - messageBuilder.WriteString(fmt.Sprintf("The ClusterVirtualImage is currently attached to the VirtualMachine %s/%s", vmUsedImage[0].Namespace, vmUsedImage[0].Name)) - case 2, 3: - var vmNamespacedNames []string - for _, vm := range vmUsedImage { - vmNamespacedNames = append(vmNamespacedNames, fmt.Sprintf("%s/%s", vm.Namespace, vm.Name)) - } - messageBuilder.WriteString(fmt.Sprintf("The ClusterVirtualImage is currently attached to the VirtualMachines: %s", strings.Join(vmNamespacedNames, ", "))) - default: - messageBuilder.WriteString(fmt.Sprintf("%d VirtualMachines are using the ClusterVirtualImage", len(vmUsedImage))) - } + msgs = append(msgs, getTerminationMessage(virtv2.VirtualMachineKind, vmUsedImage...)) } - if len(vds.Items) > 0 { - if needComma { - messageBuilder.WriteString(", ") - } - needComma = true - - switch len(vds.Items) { - case 1: - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the VirtualDisk %s/%s", vds.Items[0].Namespace, vds.Items[0].Name)) - case 2, 3: - var vdNamespacedNames []string - for _, vd := range vds.Items { - vdNamespacedNames = append(vdNamespacedNames, fmt.Sprintf("%s/%s", vd.Namespace, vd.Name)) - } - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the VirtualDisks: %s", strings.Join(vdNamespacedNames, ", "))) - default: - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is used to create %d VirtualDisks", len(vds.Items))) - } + if len(vdsNotReady) > 0 { + msgs = append(msgs, getTerminationMessage(virtv2.VirtualDiskKind, vdsNotReady...)) } - if len(vis.Items) > 0 { - if needComma { - messageBuilder.WriteString(", ") - } - needComma = true - - switch len(vis.Items) { - case 1: - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the VirtualImage %s/%s", vis.Items[0].Namespace, vis.Items[0].Name)) - case 2, 3: - var viNamespacedNames []string - for _, vi := range vis.Items { - viNamespacedNames = append(viNamespacedNames, fmt.Sprintf("%s/%s", vi.Namespace, vi.Name)) - } - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the VirtualImages: %s", strings.Join(viNamespacedNames, ", "))) - default: - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is used to create %d VirtualImages", len(vis.Items))) - } + if len(visNotReady) > 0 { + msgs = append(msgs, getTerminationMessage(virtv2.VirtualImageKind, visNotReady...)) } - if len(cvis.Items) > 0 { - if needComma { - messageBuilder.WriteString(", ") - } - needComma = true - - switch len(cvis.Items) { - case 1: - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the ClusterVirtualImage %s", cvis.Items[0].Name)) - case 2, 3: - var cviNames []string - for _, cvi := range cvis.Items { - cviNames = append(cviNames, cvi.Name) - } - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently being used to create the ClusterVirtualImages: %s", strings.Join(cviNames, ", "))) - default: - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is used to create %d ClusterVirtualImages", len(cvis.Items))) - } + if len(cvisNotReady) > 0 { + msgs = append(msgs, getTerminationMessage(virtv2.ClusterVirtualImageKind, cvisNotReady...)) } var namespaces []string @@ -196,30 +157,15 @@ func (h InUseHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImag } if len(namespaces) > 0 { - if needComma { - messageBuilder.WriteString(", ") - } - - switch len(namespaces) { - case 1: - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is currently using in Namespace %s", namespaces[0])) - case 2, 3: - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is using in Namespaces: %s", strings.Join(namespaces, ", "))) - default: - messageBuilder.WriteString(fmt.Sprintf("ClusterVirtualImage is using in %d Namespaces", len(namespaces))) - } + msgs = append(msgs, getNamespacesTerminationMessage(namespaces)) } - cvi.Status.UsedInNamespaces = []string{} - for namespace := range namespacesMap { - cvi.Status.UsedInNamespaces = append(cvi.Status.UsedInNamespaces, namespace) - } + cvi.Status.UsedInNamespaces = namespaces - messageBuilder.WriteString(".") cb. Status(metav1.ConditionTrue). Reason(cvicondition.InUse). - Message(service.CapitalizeFirstLetter(messageBuilder.String())) + Message(service.CapitalizeFirstLetter(fmt.Sprintf("%s.", strings.Join(msgs, ", ")))) conditions.SetCondition(cb, &cvi.Status.Conditions) } else { conditions.RemoveCondition(cvicondition.InUse, &cvi.Status.Conditions) @@ -231,3 +177,61 @@ func (h InUseHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImag func (h InUseHandler) Name() string { return inUseHandlerName } + +func getTerminationMessage(objectKind string, objects ...client.Object) string { + var objectFilteredNamespacedNames []types.NamespacedName + for _, obj := range objects { + if obj.GetObjectKind().GroupVersionKind().Kind == objectKind { + objectFilteredNamespacedNames = append(objectFilteredNamespacedNames, types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}) + } + } + + if objectKind == virtv2.VirtualMachineKind { + switch len(objectFilteredNamespacedNames) { + case 1: + return fmt.Sprintf("the ClusterVirtualImage is currently attached to the VirtualMachine %s/%s", objectFilteredNamespacedNames[0].Namespace, objectFilteredNamespacedNames[0].Name) + case 2, 3: + return fmt.Sprintf("the ClusterVirtualImage is currently attached to the VirtualMachines: %s", strings.Join(nameSpacedNamesToStringSlice(objectFilteredNamespacedNames), ", ")) + default: + return fmt.Sprintf("%d VirtualMachines are using the ClusterVirtualImage", len(objectFilteredNamespacedNames)) + } + } else { + switch len(objectFilteredNamespacedNames) { + case 1: + if objectFilteredNamespacedNames[0].Namespace != "" { + return fmt.Sprintf("the ClusterVirtualImage is currently being used to create the %s %s/%s", objectKind, objectFilteredNamespacedNames[0].Namespace, objectFilteredNamespacedNames[0].Name) + } else { + return fmt.Sprintf("the ClusterVirtualImage is currently being used to create the %s %s", objectKind, objectFilteredNamespacedNames[0].Name) + } + case 2, 3: + return fmt.Sprintf("the ClusterVirtualImage is currently being used to create the %ss: %s", objectKind, strings.Join(nameSpacedNamesToStringSlice(objectFilteredNamespacedNames), ", ")) + default: + return fmt.Sprintf("the ClusterVirtualImage is currently used to create %d %ss", len(objectFilteredNamespacedNames), objectKind) + } + } +} + +func nameSpacedNamesToStringSlice(namespacedNames []types.NamespacedName) []string { + var result []string + + for _, namespacedName := range namespacedNames { + if namespacedName.Namespace != "" { + result = append(result, fmt.Sprintf("%s/%s", namespacedName.Namespace, namespacedName.Name)) + } else { + result = append(result, namespacedName.Name) + } + } + + return result +} + +func getNamespacesTerminationMessage(namespaces []string) string { + switch len(namespaces) { + case 1: + return fmt.Sprintf("the ClusterVirtualImage is currently using in Namespace %s", namespaces[0]) + case 2, 3: + return fmt.Sprintf("the ClusterVirtualImage is currently using in Namespaces: %s", strings.Join(namespaces, ", ")) + default: + return fmt.Sprintf("the ClusterVirtualImage is currently using in %d Namespaces", len(namespaces)) + } +} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go index 2e71192108..53af4d758f 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go @@ -74,16 +74,16 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { Expect(err).ShouldNot(HaveOccurred()) fakeClient := fakeClientBuilder.WithIndex( &virtv2.VirtualDisk{}, - indexer.IndexFieldVDByCVIDataSourceNotReady, - indexer.IndexVDByVIDataSourceIndexerFunc, + indexer.IndexFieldVDByCVIDataSource, + indexer.IndexVDByCVIDataSourceIndexerFunc, ).WithIndex( &virtv2.VirtualImage{}, - indexer.IndexFieldVIByCVIDataSourceNotReady, - indexer.IndexVIByVIDataSourceIndexerFunc, + indexer.IndexFieldVIByCVIDataSource, + indexer.IndexVIByCVIDataSourceIndexerFunc, ).WithIndex( &virtv2.ClusterVirtualImage{}, - indexer.IndexFieldCVIByCVIDataSourceNotReady, - indexer.IndexCVIByVIDataSourceIndexerFunc, + indexer.IndexFieldCVIByCVIDataSource, + indexer.IndexCVIByCVIDataSourceIndexerFunc, ).Build() handler := NewInUseHandler(fakeClient) @@ -129,7 +129,7 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { ExpectedConditionExists: true, ExpectedConditionStatus: metav1.ConditionTrue, ExpectedConditionReason: cvicondition.InUse.String(), - ExpectedConditionMessage: "The ClusterVirtualImage is currently attached to the VirtualMachine ns/name, ClusterVirtualImage is currently using in Namespace ns.", + ExpectedConditionMessage: "The ClusterVirtualImage is currently attached to the VirtualMachine ns/name, the ClusterVirtualImage is currently using in Namespace ns.", }), Entry("has 5 VirtualMachines with connected terminating CVI", inUseHandlerTestArgs{ CVIName: "test", @@ -169,7 +169,7 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { ExpectedConditionExists: true, ExpectedConditionStatus: metav1.ConditionTrue, ExpectedConditionReason: cvicondition.InUse.String(), - ExpectedConditionMessage: "5 VirtualMachines are using the ClusterVirtualImage, ClusterVirtualImage is currently using in Namespace ns.", + ExpectedConditionMessage: "5 VirtualMachines are using the ClusterVirtualImage, the ClusterVirtualImage is currently using in Namespace ns.", }), Entry("has 5 VirtualMachines with connected terminating CVI, 4 VD, 2 VI, 1 CVI", inUseHandlerTestArgs{ CVIName: "test", @@ -260,11 +260,26 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { Name: "test", }, }), + *cvibuilder.New( + cvibuilder.WithName("test322"), + cvibuilder.WithPhase(virtv2.ImageReady), + cvibuilder.WithCondition(metav1.Condition{ + Status: metav1.ConditionTrue, + Reason: cvicondition.ReadyType.String(), + }), + cvibuilder.WithDatasource(virtv2.ClusterVirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + ), }, ExpectedConditionExists: true, ExpectedConditionStatus: metav1.ConditionTrue, ExpectedConditionReason: cvicondition.InUse.String(), - ExpectedConditionMessage: "5 VirtualMachines are using the ClusterVirtualImage, ClusterVirtualImage is currently using in Namespace ns.", + ExpectedConditionMessage: "5 VirtualMachines are using the ClusterVirtualImage, the ClusterVirtualImage is currently used to create 4 VirtualDisks, the ClusterVirtualImage is currently being used to create the VirtualImages: ns/test, ns5/test2, the ClusterVirtualImage is currently being used to create the ClusterVirtualImage test2, the ClusterVirtualImage is currently using in 7 Namespaces.", }), ) @@ -287,6 +302,10 @@ func generateVMForInUseTest(name, namespace string, blockDeviceRefs []virtv2.Blo Name: name, Namespace: namespace, }, + TypeMeta: metav1.TypeMeta{ + APIVersion: virtv2.SchemeGroupVersion.String(), + Kind: "VirtualMachine", + }, Status: virtv2.VirtualMachineStatus{ BlockDeviceRefs: blockDeviceRefs, }, diff --git a/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go index afc4c9c84d..69f4ea2705 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go @@ -27,7 +27,7 @@ import ( ) func IndexCVIByCVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.ClusterVirtualImage{}, IndexFieldCVIByCVIDataSourceNotReady, IndexCVIByCVIDataSourceIndexerFunc) + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.ClusterVirtualImage{}, IndexFieldCVIByCVIDataSource, IndexCVIByCVIDataSourceIndexerFunc) } func IndexCVIByCVIDataSourceIndexerFunc(object client.Object) []string { @@ -36,7 +36,7 @@ func IndexCVIByCVIDataSourceIndexerFunc(object client.Object) []string { return nil } - if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || cvi.Status.Phase == virtv2.ImageReady { + if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { return nil } @@ -48,7 +48,7 @@ func IndexCVIByCVIDataSourceIndexerFunc(object client.Object) []string { } func IndexCVIByVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.ClusterVirtualImage{}, IndexFieldCVIByVIDataSourceNotReady, IndexCVIByVIDataSourceIndexerFunc) + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.ClusterVirtualImage{}, IndexFieldCVIByVIDataSource, IndexCVIByVIDataSourceIndexerFunc) } func IndexCVIByVIDataSourceIndexerFunc(object client.Object) []string { @@ -57,7 +57,7 @@ func IndexCVIByVIDataSourceIndexerFunc(object client.Object) []string { return nil } - if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || cvi.Status.Phase == virtv2.ImageReady { + if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { return nil } diff --git a/images/virtualization-artifact/pkg/controller/indexer/indexer.go b/images/virtualization-artifact/pkg/controller/indexer/indexer.go index 2cf484015c..aff3fe0cab 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/indexer.go @@ -55,13 +55,13 @@ const ( IndexFieldVMBDAByVM = "spec.virtualMachineName" - IndexFieldVDByCVIDataSourceNotReady = "vd,spec.DataSource.ObjectRef.Name,.Kind=ClusterVirtualImage,.Phase!=Ready" - IndexFieldVIByCVIDataSourceNotReady = "vi,spec.DataSource.ObjectRef.Name,.Kind=ClusterVirtualImage,.Phase!=Ready" - IndexFieldCVIByCVIDataSourceNotReady = "cvi,spec.DataSource.ObjectRef.Name,.Kind=ClusterVirtualImage,.Phase!=Ready" + IndexFieldVDByCVIDataSource = "vd,spec.DataSource.ObjectRef.Name,.Kind=ClusterVirtualImage" + IndexFieldVIByCVIDataSource = "vi,spec.DataSource.ObjectRef.Name,.Kind=ClusterVirtualImage" + IndexFieldCVIByCVIDataSource = "cvi,spec.DataSource.ObjectRef.Name,.Kind=ClusterVirtualImage" - IndexFieldVDByVIDataSourceNotReady = "vd,spec.DataSource.ObjectRef.Name,.Kind=VirtualImage,.Phase!=Ready" - IndexFieldVIByVIDataSourceNotReady = "vi,spec.DataSource.ObjectRef.Name,.Kind=VirtualImage,.Phase!=Ready" - IndexFieldCVIByVIDataSourceNotReady = "cvi,spec.DataSource.ObjectRef.Name,.Kind=VirtualImage,.Phase!=Ready" + IndexFieldVDByVIDataSource = "vd,spec.DataSource.ObjectRef.Name,.Kind=VirtualImage" + IndexFieldVIByVIDataSource = "vi,spec.DataSource.ObjectRef.Name,.Kind=VirtualImage" + IndexFieldCVIByVIDataSource = "cvi,spec.DataSource.ObjectRef.Name,.Kind=VirtualImage" ) var IndexGetters = []IndexGetter{ diff --git a/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go index 118bfebe95..087ca2d10b 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go @@ -60,7 +60,7 @@ func IndexVDByStorageClass() (obj client.Object, field string, extractValue clie } func IndexVDByCVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualDisk{}, IndexFieldVDByCVIDataSourceNotReady, IndexVDByCVIDataSourceIndexerFunc) + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualDisk{}, IndexFieldVDByCVIDataSource, IndexVDByCVIDataSourceIndexerFunc) } func IndexVDByCVIDataSourceIndexerFunc(object client.Object) []string { @@ -73,10 +73,6 @@ func IndexVDByCVIDataSourceIndexerFunc(object client.Object) []string { return nil } - if vd.Status.Phase == virtv2.DiskReady { - return nil - } - if vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind { return nil } @@ -85,7 +81,7 @@ func IndexVDByCVIDataSourceIndexerFunc(object client.Object) []string { } func IndexVDByVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualDisk{}, IndexFieldVDByVIDataSourceNotReady, IndexVDByVIDataSourceIndexerFunc) + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualDisk{}, IndexFieldVDByVIDataSource, IndexVDByVIDataSourceIndexerFunc) } func IndexVDByVIDataSourceIndexerFunc(object client.Object) []string { @@ -98,10 +94,6 @@ func IndexVDByVIDataSourceIndexerFunc(object client.Object) []string { return nil } - if vd.Status.Phase == virtv2.DiskReady { - return nil - } - if vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind { return nil } diff --git a/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go index bf056bd632..f04f374788 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go @@ -60,7 +60,7 @@ func IndexVIByStorageClass() (obj client.Object, field string, extractValue clie } func IndexVIByCVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualImage{}, IndexFieldVIByCVIDataSourceNotReady, IndexVIByCVIDataSourceIndexerFunc) + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualImage{}, IndexFieldVIByCVIDataSource, IndexVIByCVIDataSourceIndexerFunc) } func IndexVIByCVIDataSourceIndexerFunc(object client.Object) []string { @@ -69,7 +69,7 @@ func IndexVIByCVIDataSourceIndexerFunc(object client.Object) []string { return nil } - if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || vi.Status.Phase == virtv2.ImageReady { + if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { return nil } @@ -81,7 +81,7 @@ func IndexVIByCVIDataSourceIndexerFunc(object client.Object) []string { } func IndexVIByVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualImage{}, IndexFieldVIByVIDataSourceNotReady, IndexVIByVIDataSourceIndexerFunc) + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualImage{}, IndexFieldVIByVIDataSource, IndexVIByVIDataSourceIndexerFunc) } func IndexVIByVIDataSourceIndexerFunc(object client.Object) []string { @@ -90,7 +90,7 @@ func IndexVIByVIDataSourceIndexerFunc(object client.Object) []string { return nil } - if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || vi.Status.Phase == virtv2.ImageReady { + if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { return nil } diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/inuse.go b/images/virtualization-artifact/pkg/controller/vi/internal/inuse.go index 5e279118f0..b7a7b7782d 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/inuse.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/inuse.go @@ -64,42 +64,55 @@ func (h InUseHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) (reco return reconcile.Result{}, err } - var vmUsedImage []*virtv2.VirtualMachine + var vmUsedImage []client.Object for _, vm := range vms.Items { for _, bd := range vm.Status.BlockDeviceRefs { if bd.Kind == virtv2.VirtualImageKind && bd.Name == vi.Name { vmUsedImage = append(vmUsedImage, &vm) + break } } } var vds virtv2.VirtualDiskList err = h.client.List(ctx, &vds, client.InNamespace(vi.GetNamespace()), client.MatchingFields{ - indexer.IndexFieldVDByVIDataSourceNotReady: vi.GetName(), + indexer.IndexFieldVDByVIDataSource: vi.GetName(), }) if err != nil { return reconcile.Result{}, err } + var vdsNotReady []client.Object + for _, vd := range vds.Items { + if vd.Status.Phase != virtv2.DiskReady { + vdsNotReady = append(vdsNotReady, &vd) + } + } var vis virtv2.VirtualImageList err = h.client.List(ctx, &vis, client.InNamespace(vi.GetNamespace()), client.MatchingFields{ - indexer.IndexFieldVIByVIDataSourceNotReady: vi.GetName(), + indexer.IndexFieldVIByVIDataSource: vi.GetName(), }) if err != nil { return reconcile.Result{}, err } + var visNotReady []client.Object + for _, vi := range vis.Items { + if vi.Status.Phase != virtv2.ImageReady { + visNotReady = append(visNotReady, &vi) + } + } var cvis virtv2.ClusterVirtualImageList err = h.client.List(ctx, &cvis, client.MatchingFields{ - indexer.IndexFieldCVIByVIDataSourceNotReady: vi.GetName(), + indexer.IndexFieldCVIByVIDataSource: vi.GetName(), }) if err != nil { return reconcile.Result{}, err } - var cvisFiltered []*virtv2.ClusterVirtualImage + var cvisFiltered []client.Object for _, cvi := range cvis.Items { - if cvi.Spec.DataSource.ObjectRef == nil { + if cvi.Spec.DataSource.ObjectRef == nil || cvi.Status.Phase == virtv2.ImageReady { continue } if cvi.Spec.DataSource.ObjectRef.Namespace == vi.GetNamespace() { @@ -107,91 +120,30 @@ func (h InUseHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) (reco } } - consumerCount := len(vmUsedImage) + len(vds.Items) + len(vis.Items) + len(cvisFiltered) + consumerCount := len(vmUsedImage) + len(vdsNotReady) + len(visNotReady) + len(cvisFiltered) if consumerCount > 0 { - var messageBuilder strings.Builder - var needComma bool + var msgs []string if len(vmUsedImage) > 0 { - needComma = true - switch len(vmUsedImage) { - case 1: - messageBuilder.WriteString(fmt.Sprintf("The VirtualImage is currently attached to the VirtualMachine %s", vmUsedImage[0].Name)) - case 2, 3: - var vmNames []string - for _, vm := range vmUsedImage { - vmNames = append(vmNames, vm.GetName()) - } - messageBuilder.WriteString(fmt.Sprintf("The VirtualImage is currently attached to the VirtualMachines: %s", strings.Join(vmNames, ", "))) - default: - messageBuilder.WriteString(fmt.Sprintf("%d VirtualMachines are using the VirtualImage", len(vmUsedImage))) - } + msgs = append(msgs, getTerminationMessage(virtv2.VirtualMachineKind, vmUsedImage...)) } - if len(vds.Items) > 0 { - if needComma { - messageBuilder.WriteString(", ") - } - needComma = true - - switch len(vds.Items) { - case 1: - messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the VirtualDisk %s", vds.Items[0].Name)) - case 2, 3: - var vdNames []string - for _, vd := range vds.Items { - vdNames = append(vdNames, vd.GetName()) - } - messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the VirtualDisks: %s", strings.Join(vdNames, ", "))) - default: - messageBuilder.WriteString(fmt.Sprintf("VirtualImage is used to create %d VirtualDisks", len(vds.Items))) - } + if len(vdsNotReady) > 0 { + msgs = append(msgs, getTerminationMessage(virtv2.VirtualDiskKind, vdsNotReady...)) } - if len(vis.Items) > 0 { - if needComma { - messageBuilder.WriteString(", ") - } - needComma = true - - switch len(vis.Items) { - case 1: - messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the VirtualImage %s", vis.Items[0].Name)) - case 2, 3: - var viNames []string - for _, vi := range vis.Items { - viNames = append(viNames, vi.Name) - } - messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the VirtualImages: %s", strings.Join(viNames, ", "))) - default: - messageBuilder.WriteString(fmt.Sprintf("VirtualImage is used to create %d VirtualImages", len(vis.Items))) - } + if len(visNotReady) > 0 { + msgs = append(msgs, getTerminationMessage(virtv2.VirtualImageKind, visNotReady...)) } if len(cvisFiltered) > 0 { - if needComma { - messageBuilder.WriteString(", ") - } - - switch len(cvisFiltered) { - case 1: - messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the ClusterVirtualImage %s", cvisFiltered[0].Name)) - case 2, 3: - var cviNames []string - for _, cvi := range cvisFiltered { - cviNames = append(cviNames, cvi.Name) - } - messageBuilder.WriteString(fmt.Sprintf("VirtualImage is currently being used to create the ClusterVirtualImages: %s", strings.Join(cviNames, ", "))) - default: - messageBuilder.WriteString(fmt.Sprintf("VirtualImage is used to create %d ClusterVirtualImages", len(cvisFiltered))) - } + msgs = append(msgs, getTerminationMessage(virtv2.ClusterVirtualImageKind, cvisFiltered...)) } - messageBuilder.WriteString(".") cb. Status(metav1.ConditionTrue). Reason(vicondition.InUse). - Message(service.CapitalizeFirstLetter(messageBuilder.String())) + Message(service.CapitalizeFirstLetter(fmt.Sprintf("%s.", strings.Join(msgs, ", ")))) conditions.SetCondition(cb, &vi.Status.Conditions) } else { conditions.RemoveCondition(vicondition.InUse, &vi.Status.Conditions) @@ -203,3 +155,32 @@ func (h InUseHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) (reco func (h InUseHandler) Name() string { return inUseHandlerName } + +func getTerminationMessage(objectKind string, objects ...client.Object) string { + var objectFilteredNames []string + for _, obj := range objects { + if obj.GetObjectKind().GroupVersionKind().Kind == objectKind { + objectFilteredNames = append(objectFilteredNames, obj.GetName()) + } + } + + if objectKind == virtv2.VirtualMachineKind { + switch len(objectFilteredNames) { + case 1: + return fmt.Sprintf("the VirtualImage is currently attached to the VirtualMachine %s", objectFilteredNames[0]) + case 2, 3: + return fmt.Sprintf("the VirtualImage is currently attached to the VirtualMachines: %s", strings.Join(objectFilteredNames, ", ")) + default: + return fmt.Sprintf("%d VirtualMachines are using the VirtualImage", len(objectFilteredNames)) + } + } else { + switch len(objectFilteredNames) { + case 1: + return fmt.Sprintf("the VirtualImage is currently being used to create the %s %s", objectKind, objectFilteredNames[0]) + case 2, 3: + return fmt.Sprintf("the VirtualImage is currently being used to create the %ss: %s", objectKind, strings.Join(objectFilteredNames, ", ")) + default: + return fmt.Sprintf("the VirtualImage is currently used to create %d %ss", len(objectFilteredNames), objectKind) + } + } +} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go index 639de11a20..2c89a4bc2d 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go @@ -17,8 +17,7 @@ limitations under the License. package internal import ( - "time" - + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -26,6 +25,7 @@ import ( "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "time" cvibuilder "github.com/deckhouse/virtualization-controller/pkg/builder/cvi" vdbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vd" @@ -76,15 +76,15 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { Expect(err).ShouldNot(HaveOccurred()) fakeClient := fakeClientBuilder.WithIndex( &virtv2.VirtualDisk{}, - indexer.IndexFieldVDByVIDataSourceNotReady, + indexer.IndexFieldVDByVIDataSource, indexer.IndexVDByVIDataSourceIndexerFunc, ).WithIndex( &virtv2.VirtualImage{}, - indexer.IndexFieldVIByVIDataSourceNotReady, + indexer.IndexFieldVIByVIDataSource, indexer.IndexVIByVIDataSourceIndexerFunc, ).WithIndex( &virtv2.ClusterVirtualImage{}, - indexer.IndexFieldCVIByVIDataSourceNotReady, + indexer.IndexFieldCVIByVIDataSource, indexer.IndexCVIByVIDataSourceIndexerFunc, ).Build() handler := NewInUseHandler(fakeClient) @@ -102,6 +102,7 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { Expect(ok).To(BeFalse()) } }, + Entry("deletionTimestamp not exists", inUseHandlerTestArgs{ VMs: []virtv2.VirtualMachine{}, VINamespacedName: types.NamespacedName{ @@ -329,11 +330,27 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { Namespace: "test", }, }), + *cvibuilder.New( + cvibuilder.WithName("test322"), + cvibuilder.WithPhase(virtv2.ImageReady), + cvibuilder.WithCondition(metav1.Condition{ + Status: metav1.ConditionTrue, + Reason: cvicondition.ReadyType.String(), + }), + cvibuilder.WithDatasource(virtv2.ClusterVirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ + Kind: virtv2.VirtualImageKind, + Name: "test", + Namespace: "ns", + }, + }), + ), }, ExpectedConditionExists: true, ExpectedConditionStatus: metav1.ConditionTrue, ExpectedConditionReason: vicondition.InUse.String(), - ExpectedConditionMessage: "5 VirtualMachines are using the VirtualImage, VirtualImage is used to create 4 VirtualDisks, VirtualImage is currently being used to create the VirtualImage test1, VirtualImage is currently being used to create the ClusterVirtualImages: test1, test2.", + ExpectedConditionMessage: "5 VirtualMachines are using the VirtualImage, the VirtualImage is currently used to create 4 VirtualDisks, the VirtualImage is currently being used to create the VirtualImage test1, the VirtualImage is currently being used to create the ClusterVirtualImages: test1, test2.", }), ) @@ -356,6 +373,9 @@ func generateVMForInUseTest(name, namespace string, blockDeviceRefs []virtv2.Blo Name: name, Namespace: namespace, }, + TypeMeta: metav1.TypeMeta{ + Kind: virtv2.VirtualMachineKind, + }, Status: virtv2.VirtualMachineStatus{ BlockDeviceRefs: blockDeviceRefs, }, From 9849745a4165bd4db5107da558771fef60fe6529 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 23 Apr 2025 15:26:37 +0300 Subject: [PATCH 11/27] resolve Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/indexer/cvi_indexer.go | 11 ++++------- .../pkg/controller/indexer/vd_indexer.go | 8 ++++---- .../pkg/controller/indexer/vi_indexer.go | 8 ++++---- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go index 69f4ea2705..603158761f 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/cvi_indexer.go @@ -17,17 +17,14 @@ limitations under the License. package indexer import ( - "context" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/manager" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) -func IndexCVIByCVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.ClusterVirtualImage{}, IndexFieldCVIByCVIDataSource, IndexCVIByCVIDataSourceIndexerFunc) +func IndexCVIByCVIDataSource() (obj client.Object, field string, extractValue client.IndexerFunc) { + return &virtv2.ClusterVirtualImage{}, IndexFieldCVIByCVIDataSource, IndexCVIByCVIDataSourceIndexerFunc } func IndexCVIByCVIDataSourceIndexerFunc(object client.Object) []string { @@ -47,8 +44,8 @@ func IndexCVIByCVIDataSourceIndexerFunc(object client.Object) []string { return []string{cvi.Spec.DataSource.ObjectRef.Name} } -func IndexCVIByVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.ClusterVirtualImage{}, IndexFieldCVIByVIDataSource, IndexCVIByVIDataSourceIndexerFunc) +func IndexCVIByVIDataSource() (obj client.Object, field string, extractValue client.IndexerFunc) { + return &virtv2.ClusterVirtualImage{}, IndexFieldCVIByVIDataSource, IndexCVIByVIDataSourceIndexerFunc } func IndexCVIByVIDataSourceIndexerFunc(object client.Object) []string { diff --git a/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go index 087ca2d10b..80362fb9ed 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/vd_indexer.go @@ -59,8 +59,8 @@ func IndexVDByStorageClass() (obj client.Object, field string, extractValue clie } } -func IndexVDByCVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualDisk{}, IndexFieldVDByCVIDataSource, IndexVDByCVIDataSourceIndexerFunc) +func IndexVDByCVIDataSource() (obj client.Object, field string, extractValue client.IndexerFunc) { + return &virtv2.VirtualDisk{}, IndexFieldVDByCVIDataSource, IndexVDByCVIDataSourceIndexerFunc } func IndexVDByCVIDataSourceIndexerFunc(object client.Object) []string { @@ -80,8 +80,8 @@ func IndexVDByCVIDataSourceIndexerFunc(object client.Object) []string { return []string{vd.Spec.DataSource.ObjectRef.Name} } -func IndexVDByVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualDisk{}, IndexFieldVDByVIDataSource, IndexVDByVIDataSourceIndexerFunc) +func IndexVDByVIDataSource() (obj client.Object, field string, extractValue client.IndexerFunc) { + return &virtv2.VirtualDisk{}, IndexFieldVDByVIDataSource, IndexVDByVIDataSourceIndexerFunc } func IndexVDByVIDataSourceIndexerFunc(object client.Object) []string { diff --git a/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go index f04f374788..9e3d719be0 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/vi_indexer.go @@ -59,8 +59,8 @@ func IndexVIByStorageClass() (obj client.Object, field string, extractValue clie } } -func IndexVIByCVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualImage{}, IndexFieldVIByCVIDataSource, IndexVIByCVIDataSourceIndexerFunc) +func IndexVIByCVIDataSource() (obj client.Object, field string, extractValue client.IndexerFunc) { + return &virtv2.VirtualImage{}, IndexFieldVIByCVIDataSource, IndexVIByCVIDataSourceIndexerFunc } func IndexVIByCVIDataSourceIndexerFunc(object client.Object) []string { @@ -80,8 +80,8 @@ func IndexVIByCVIDataSourceIndexerFunc(object client.Object) []string { return []string{vi.Spec.DataSource.ObjectRef.Name} } -func IndexVIByVIDataSource(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualImage{}, IndexFieldVIByVIDataSource, IndexVIByVIDataSourceIndexerFunc) +func IndexVIByVIDataSource() (obj client.Object, field string, extractValue client.IndexerFunc) { + return &virtv2.VirtualImage{}, IndexFieldVIByVIDataSource, IndexVIByVIDataSourceIndexerFunc } func IndexVIByVIDataSourceIndexerFunc(object client.Object) []string { From 8b756657756d64e897a1c5e520bdcc901f630fe4 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 23 Apr 2025 15:31:23 +0300 Subject: [PATCH 12/27] remove useless Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/internal/watcher/cvi_watcher.go | 7 ------- .../pkg/controller/cvi/internal/watcher/vd_watcher.go | 7 ------- .../pkg/controller/cvi/internal/watcher/vi_watcher.go | 7 ------- .../pkg/controller/vi/internal/watcher/cvi_watcher.go | 7 ------- .../pkg/controller/vi/internal/watcher/vd_watcher.go | 7 ------- .../pkg/controller/vi/internal/watcher/vi_watcher.go | 7 ------- 6 files changed, 42 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go index 045530d913..01a060f263 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go @@ -18,8 +18,6 @@ package watcher import ( "context" - "fmt" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -30,18 +28,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - "github.com/deckhouse/deckhouse/pkg/log" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) type ClusterVirtualImageWatcher struct { - logger *log.Logger client client.Client } func NewClusterVirtualImageWatcher(client client.Client) *ClusterVirtualImageWatcher { return &ClusterVirtualImageWatcher{ - logger: log.Default().With("watcher", "cvi"), client: client, } } @@ -67,7 +62,6 @@ func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Co func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { cvi, ok := obj.(*virtv2.ClusterVirtualImage) if !ok { - w.logger.Error(fmt.Sprintf("expected a ClusterVirtualImage but got a %T", obj)) return } @@ -92,7 +86,6 @@ func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj clien func (w ClusterVirtualImageWatcher) isDataSourceCVI(obj client.Object) bool { cvi, ok := obj.(*virtv2.ClusterVirtualImage) if !ok { - w.logger.Error(fmt.Sprintf("expected a ClusterVirtualImage but got a %T", obj)) return false } diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go index 53f9256617..3fddac7764 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go @@ -18,8 +18,6 @@ package watcher import ( "context" - "fmt" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -30,18 +28,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - "github.com/deckhouse/deckhouse/pkg/log" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) type VirtualDiskWatcher struct { - logger *log.Logger client client.Client } func NewVirtualDiskWatcher(client client.Client) *VirtualDiskWatcher { return &VirtualDiskWatcher{ - logger: log.Default().With("watcher", "vd"), client: client, } } @@ -67,7 +62,6 @@ func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { vd, ok := obj.(*virtv2.VirtualDisk) if !ok { - w.logger.Error(fmt.Sprintf("expected a VirtualDisk but got a %T", obj)) return } @@ -91,7 +85,6 @@ func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object func (w VirtualDiskWatcher) isDataSourceCVI(obj client.Object) bool { vd, ok := obj.(*virtv2.VirtualDisk) if !ok { - w.logger.Error(fmt.Sprintf("expected a VirtualDisk but got a %T", obj)) return false } diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go index ef81a8ad18..8aa8b7cbba 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go @@ -18,8 +18,6 @@ package watcher import ( "context" - "fmt" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -30,18 +28,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - "github.com/deckhouse/deckhouse/pkg/log" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) type VirtualImageWatcher struct { - logger *log.Logger client client.Client } func NewVirtualImageWatcher(client client.Client) *VirtualImageWatcher { return &VirtualImageWatcher{ - logger: log.Default().With("watcher", "vi"), client: client, } } @@ -67,7 +62,6 @@ func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controlle func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { vi, ok := obj.(*virtv2.VirtualImage) if !ok { - w.logger.Error(fmt.Sprintf("expected a VirtualImage but got a %T", obj)) return } @@ -92,7 +86,6 @@ func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Objec func (w VirtualImageWatcher) isDataSourceCVI(obj client.Object) bool { vi, ok := obj.(*virtv2.VirtualImage) if !ok { - w.logger.Error(fmt.Sprintf("expected a VirtualImage but got a %T", obj)) return false } diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go index ae93c6ba28..6c85a75b21 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go @@ -18,8 +18,6 @@ package watcher import ( "context" - "fmt" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -30,18 +28,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - "github.com/deckhouse/deckhouse/pkg/log" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) type ClusterVirtualImageWatcher struct { - logger *log.Logger client client.Client } func NewClusterVirtualImageWatcher(client client.Client) *ClusterVirtualImageWatcher { return &ClusterVirtualImageWatcher{ - logger: log.Default().With("watcher", "cvi"), client: client, } } @@ -67,7 +62,6 @@ func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Co func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { cvi, ok := obj.(*virtv2.ClusterVirtualImage) if !ok { - w.logger.Error(fmt.Sprintf("expected a ClusterVirtualImage but got a %T", obj)) return } @@ -92,7 +86,6 @@ func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj clien func (w ClusterVirtualImageWatcher) isDataSourceVI(obj client.Object) bool { cvi, ok := obj.(*virtv2.ClusterVirtualImage) if !ok { - w.logger.Error(fmt.Sprintf("expected a ClusterVirtualImage but got a %T", obj)) return false } diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go index 4d9967451e..52c8fefa20 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go @@ -18,8 +18,6 @@ package watcher import ( "context" - "fmt" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -30,18 +28,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - "github.com/deckhouse/deckhouse/pkg/log" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) type VirtualDiskWatcher struct { - logger *log.Logger client client.Client } func NewVirtualDiskWatcher(client client.Client) *VirtualDiskWatcher { return &VirtualDiskWatcher{ - logger: log.Default().With("watcher", "vd"), client: client, } } @@ -67,7 +62,6 @@ func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { vd, ok := obj.(*virtv2.VirtualDisk) if !ok { - w.logger.Error(fmt.Sprintf("expected a VirtualDisk but got a %T", obj)) return } @@ -92,7 +86,6 @@ func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object func (w VirtualDiskWatcher) isDataSourceVI(obj client.Object) bool { vd, ok := obj.(*virtv2.VirtualDisk) if !ok { - w.logger.Error(fmt.Sprintf("expected a VirtualDisk but got a %T", obj)) return false } diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go index 8106d9872c..b8f00cacf5 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go @@ -18,8 +18,6 @@ package watcher import ( "context" - "fmt" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -30,18 +28,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - "github.com/deckhouse/deckhouse/pkg/log" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) type VirtualImageWatcher struct { - logger *log.Logger client client.Client } func NewVirtualImageWatcher(client client.Client) *VirtualImageWatcher { return &VirtualImageWatcher{ - logger: log.Default().With("watcher", "vi"), client: client, } } @@ -67,7 +62,6 @@ func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controlle func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { vi, ok := obj.(*virtv2.VirtualImage) if !ok { - w.logger.Error(fmt.Sprintf("expected a VirtualImage but got a %T", obj)) return } @@ -92,7 +86,6 @@ func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Objec func (w VirtualImageWatcher) isDataSourceVI(obj client.Object) bool { vi, ok := obj.(*virtv2.VirtualImage) if !ok { - w.logger.Error(fmt.Sprintf("expected a VirtualImage but got a %T", obj)) return false } From a027d4563ca32623e0bedbc13bc5df6b53738b83 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 23 Apr 2025 15:45:31 +0300 Subject: [PATCH 13/27] fix vi lifecycle Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/vi/internal/life_cycle.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go index 30d2eea6fd..bbb3a3cfb4 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go @@ -19,8 +19,8 @@ package internal import ( "context" "fmt" - 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/reconcile" @@ -44,8 +44,10 @@ type LifeCycleHandler struct { func NewLifeCycleHandler(recorder eventrecord.EventRecorderLogger, sources Sources, client client.Client) *LifeCycleHandler { return &LifeCycleHandler{ - client: client, - sources: sources, + recorder: recorder, + client: client, + sources: sources, + diskService: service.NewDiskService(client, nil, nil, "vi"), } } From d3213f5680f8c955bcf7b1efef17f58d2df0e129 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 23 Apr 2025 16:02:55 +0300 Subject: [PATCH 14/27] fix tests Signed-off-by: Valeriy Khorunzhin --- .../pkg/common/testutil/testutil.go | 24 ++++++++----------- .../pkg/controller/cvi/internal/inuse_test.go | 16 +------------ .../pkg/controller/vi/internal/inuse_test.go | 16 +------------ 3 files changed, 12 insertions(+), 44 deletions(-) diff --git a/images/virtualization-artifact/pkg/common/testutil/testutil.go b/images/virtualization-artifact/pkg/common/testutil/testutil.go index 49c387fd65..ce71a41f11 100644 --- a/images/virtualization-artifact/pkg/common/testutil/testutil.go +++ b/images/virtualization-artifact/pkg/common/testutil/testutil.go @@ -18,30 +18,21 @@ package testutil import ( "context" - "log/slog" - "reflect" - "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/go-logr/logr" apiruntime "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" virtv1 "kubevirt.io/api/core/v1" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" + "log/slog" + "reflect" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) func NewFakeClientWithObjects(objs ...client.Object) (client.WithWatch, error) { - builder, err := NewFakeClientBuilderWithObjects(objs...) - if err != nil { - return nil, err - } - return builder.Build(), nil -} - -func NewFakeClientBuilderWithObjects(objs ...client.Object) (*fake.ClientBuilder, error) { scheme := apiruntime.NewScheme() for _, f := range []func(*apiruntime.Scheme) error{ virtv2.AddToScheme, @@ -61,7 +52,12 @@ func NewFakeClientBuilderWithObjects(objs ...client.Object) (*fake.ClientBuilder } newObjs = append(newObjs, obj) } - return fake.NewClientBuilder().WithScheme(scheme).WithObjects(newObjs...).WithStatusSubresource(newObjs...), nil + b := fake.NewClientBuilder().WithScheme(scheme).WithObjects(newObjs...).WithStatusSubresource(newObjs...) + for _, fn := range indexer.IndexGetters { + b.WithIndex(fn()) + } + + return b.Build(), nil } func NewNoOpLogger() *log.Logger { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go index 53af4d758f..248f15a24f 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go @@ -31,7 +31,6 @@ import ( vibuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vi" "github.com/deckhouse/virtualization-controller/pkg/common/testutil" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" - "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" ) @@ -70,21 +69,8 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { objects = append(objects, &cvi) } - fakeClientBuilder, err := testutil.NewFakeClientBuilderWithObjects(objects...) + fakeClient, err := testutil.NewFakeClientWithObjects(objects...) Expect(err).ShouldNot(HaveOccurred()) - fakeClient := fakeClientBuilder.WithIndex( - &virtv2.VirtualDisk{}, - indexer.IndexFieldVDByCVIDataSource, - indexer.IndexVDByCVIDataSourceIndexerFunc, - ).WithIndex( - &virtv2.VirtualImage{}, - indexer.IndexFieldVIByCVIDataSource, - indexer.IndexVIByCVIDataSourceIndexerFunc, - ).WithIndex( - &virtv2.ClusterVirtualImage{}, - indexer.IndexFieldCVIByCVIDataSource, - indexer.IndexCVIByCVIDataSourceIndexerFunc, - ).Build() handler := NewInUseHandler(fakeClient) result, err := handler.Handle(testutil.ContextBackgroundWithNoOpLogger(), cvi) diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go index 2c89a4bc2d..3ef7efbb0b 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go @@ -32,7 +32,6 @@ import ( vibuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vi" "github.com/deckhouse/virtualization-controller/pkg/common/testutil" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" - "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) @@ -72,21 +71,8 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { objects = append(objects, &cvi) } - fakeClientBuilder, err := testutil.NewFakeClientBuilderWithObjects(objects...) + fakeClient, err := testutil.NewFakeClientWithObjects(objects...) Expect(err).ShouldNot(HaveOccurred()) - fakeClient := fakeClientBuilder.WithIndex( - &virtv2.VirtualDisk{}, - indexer.IndexFieldVDByVIDataSource, - indexer.IndexVDByVIDataSourceIndexerFunc, - ).WithIndex( - &virtv2.VirtualImage{}, - indexer.IndexFieldVIByVIDataSource, - indexer.IndexVIByVIDataSourceIndexerFunc, - ).WithIndex( - &virtv2.ClusterVirtualImage{}, - indexer.IndexFieldCVIByVIDataSource, - indexer.IndexCVIByVIDataSourceIndexerFunc, - ).Build() handler := NewInUseHandler(fakeClient) result, err := handler.Handle(testutil.ContextBackgroundWithNoOpLogger(), vi) From 1a8cf15b0b24894957f360a08bcc8f7104581a19 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 23 Apr 2025 16:15:57 +0300 Subject: [PATCH 15/27] refactoring Signed-off-by: Valeriy Khorunzhin --- .../pkg/common/testutil/testutil.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/images/virtualization-artifact/pkg/common/testutil/testutil.go b/images/virtualization-artifact/pkg/common/testutil/testutil.go index ce71a41f11..842bebe2af 100644 --- a/images/virtualization-artifact/pkg/common/testutil/testutil.go +++ b/images/virtualization-artifact/pkg/common/testutil/testutil.go @@ -18,18 +18,20 @@ package testutil import ( "context" + "log/slog" + "reflect" + "github.com/deckhouse/deckhouse/pkg/log" - "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" - virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/go-logr/logr" apiruntime "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" virtv1 "kubevirt.io/api/core/v1" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" - "log/slog" - "reflect" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) func NewFakeClientWithObjects(objs ...client.Object) (client.WithWatch, error) { From 81bf512105670470239dd3496a36b595d38286f5 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 23 Apr 2025 16:30:48 +0300 Subject: [PATCH 16/27] refactoring Signed-off-by: Valeriy Khorunzhin --- images/virtualization-artifact/pkg/common/testutil/testutil.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/virtualization-artifact/pkg/common/testutil/testutil.go b/images/virtualization-artifact/pkg/common/testutil/testutil.go index 842bebe2af..1785bf86e4 100644 --- a/images/virtualization-artifact/pkg/common/testutil/testutil.go +++ b/images/virtualization-artifact/pkg/common/testutil/testutil.go @@ -21,7 +21,6 @@ import ( "log/slog" "reflect" - "github.com/deckhouse/deckhouse/pkg/log" "github.com/go-logr/logr" apiruntime "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -30,6 +29,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "github.com/deckhouse/deckhouse/pkg/log" "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) From 132b8b37bdd4f0b29b4699c932e630dee153ee8f Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Tue, 29 Apr 2025 17:29:21 +0300 Subject: [PATCH 17/27] refactoring Signed-off-by: Valeriy Khorunzhin --- .../cvi/internal/watcher/cvi_watcher.go | 22 +++--------- .../controller/cvi/internal/watcher/util.go | 34 +++++++++++++++++++ .../cvi/internal/watcher/vd_watcher.go | 24 +++---------- .../cvi/internal/watcher/vi_watcher.go | 22 +++--------- .../vi/internal/watcher/cvi_watcher.go | 22 +++--------- .../controller/vi/internal/watcher/util.go | 34 +++++++++++++++++++ .../vi/internal/watcher/vd_watcher.go | 24 +++---------- .../vi/internal/watcher/vi_watcher.go | 22 +++--------- 8 files changed, 98 insertions(+), 106 deletions(-) create mode 100644 images/virtualization-artifact/pkg/controller/cvi/internal/watcher/util.go create mode 100644 images/virtualization-artifact/pkg/controller/vi/internal/watcher/util.go diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go index 01a060f263..e624d8c7ef 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go @@ -18,6 +18,7 @@ package watcher import ( "context" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -47,13 +48,13 @@ func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Co handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceCVI(e.Object) + return isDataSourceCVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceCVI(e.Object) + return isDataSourceCVI(e.Object) }, UpdateFunc: func(e event.UpdateEvent) bool { - return w.isDataSourceCVI(e.ObjectOld) || w.isDataSourceCVI(e.ObjectNew) + return isDataSourceCVI(e.ObjectOld) || isDataSourceCVI(e.ObjectNew) }, }, ) @@ -65,11 +66,7 @@ func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj clien return } - if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { - return - } - - if cvi.Spec.DataSource.ObjectRef == nil || cvi.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind { + if !isDataSourceCVI(cvi) { return } @@ -82,12 +79,3 @@ func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj clien return } - -func (w ClusterVirtualImageWatcher) isDataSourceCVI(obj client.Object) bool { - cvi, ok := obj.(*virtv2.ClusterVirtualImage) - if !ok { - return false - } - - return cvi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && cvi.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/util.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/util.go new file mode 100644 index 0000000000..73bb2ea8eb --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/util.go @@ -0,0 +1,34 @@ +/* +Copyright 2025 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 watcher + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func isDataSourceCVI(obj client.Object) bool { + vi, ok := obj.(*virtv2.VirtualImage) + if !ok { + return false + } + + return vi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && + vi.Spec.DataSource.ObjectRef != nil && + vi.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go index 3fddac7764..55096153ce 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go @@ -18,6 +18,7 @@ package watcher import ( "context" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -47,13 +48,13 @@ func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceCVI(e.Object) + return isDataSourceCVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceCVI(e.Object) + return isDataSourceCVI(e.Object) }, UpdateFunc: func(e event.UpdateEvent) bool { - return w.isDataSourceCVI(e.ObjectOld) || w.isDataSourceCVI(e.ObjectNew) + return isDataSourceCVI(e.ObjectOld) || isDataSourceCVI(e.ObjectNew) }, }, ) @@ -65,11 +66,7 @@ func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object return } - if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { - return - } - - if vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind { + if !isDataSourceCVI(vd) { return } @@ -81,14 +78,3 @@ func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object return } - -func (w VirtualDiskWatcher) isDataSourceCVI(obj client.Object) bool { - vd, ok := obj.(*virtv2.VirtualDisk) - if !ok { - return false - } - - return vd.Spec.DataSource != nil && - vd.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && - vd.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go index 8aa8b7cbba..cdbebecfdf 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go @@ -18,6 +18,7 @@ package watcher import ( "context" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -47,13 +48,13 @@ func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controlle handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceCVI(e.Object) + return isDataSourceCVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceCVI(e.Object) + return isDataSourceCVI(e.Object) }, UpdateFunc: func(e event.UpdateEvent) bool { - return w.isDataSourceCVI(e.ObjectOld) || w.isDataSourceCVI(e.ObjectNew) + return isDataSourceCVI(e.ObjectOld) || isDataSourceCVI(e.ObjectNew) }, }, ) @@ -65,11 +66,7 @@ func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Objec return } - if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { - return - } - - if vi.Spec.DataSource.ObjectRef == nil || vi.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind { + if !isDataSourceCVI(vi) { return } @@ -82,12 +79,3 @@ func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Objec return } - -func (w VirtualImageWatcher) isDataSourceCVI(obj client.Object) bool { - vi, ok := obj.(*virtv2.VirtualImage) - if !ok { - return false - } - - return vi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && vi.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go index 6c85a75b21..ff5e5b4047 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go @@ -18,6 +18,7 @@ package watcher import ( "context" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -47,13 +48,13 @@ func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Co handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceVI(e.Object) + return isDataSourceVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceVI(e.Object) + return isDataSourceVI(e.Object) }, UpdateFunc: func(e event.UpdateEvent) bool { - return w.isDataSourceVI(e.ObjectOld) || w.isDataSourceVI(e.ObjectNew) + return isDataSourceVI(e.ObjectOld) || isDataSourceVI(e.ObjectNew) }, }, ) @@ -65,11 +66,7 @@ func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj clien return } - if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { - return - } - - if cvi.Spec.DataSource.ObjectRef == nil || cvi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind { + if !isDataSourceVI(cvi) { return } @@ -82,12 +79,3 @@ func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj clien return } - -func (w ClusterVirtualImageWatcher) isDataSourceVI(obj client.Object) bool { - cvi, ok := obj.(*virtv2.ClusterVirtualImage) - if !ok { - return false - } - - return cvi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && cvi.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/util.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/util.go new file mode 100644 index 0000000000..068b16261f --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/util.go @@ -0,0 +1,34 @@ +/* +Copyright 2025 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 watcher + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func isDataSourceVI(obj client.Object) bool { + vd, ok := obj.(*virtv2.VirtualDisk) + if !ok { + return false + } + + return vd.Spec.DataSource != nil && + vd.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && + vd.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go index 52c8fefa20..69f1663261 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go @@ -18,6 +18,7 @@ package watcher import ( "context" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -47,13 +48,13 @@ func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceVI(e.Object) + return isDataSourceVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceVI(e.Object) + return isDataSourceVI(e.Object) }, UpdateFunc: func(e event.UpdateEvent) bool { - return w.isDataSourceVI(e.ObjectOld) || w.isDataSourceVI(e.ObjectNew) + return isDataSourceVI(e.ObjectOld) || isDataSourceVI(e.ObjectNew) }, }, ) @@ -65,11 +66,7 @@ func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object return } - if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { - return - } - - if vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind { + if !isDataSourceVI(vd) { return } @@ -82,14 +79,3 @@ func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object return } - -func (w VirtualDiskWatcher) isDataSourceVI(obj client.Object) bool { - vd, ok := obj.(*virtv2.VirtualDisk) - if !ok { - return false - } - - return vd.Spec.DataSource != nil && - vd.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && - vd.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go index b8f00cacf5..17e5ccb1e9 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go @@ -18,6 +18,7 @@ package watcher import ( "context" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -47,13 +48,13 @@ func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controlle handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceVI(e.Object) + return isDataSourceVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceVI(e.Object) + return isDataSourceVI(e.Object) }, UpdateFunc: func(e event.UpdateEvent) bool { - return w.isDataSourceVI(e.ObjectOld) || w.isDataSourceVI(e.ObjectNew) + return isDataSourceVI(e.ObjectOld) || isDataSourceVI(e.ObjectNew) }, }, ) @@ -65,11 +66,7 @@ func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Objec return } - if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef { - return - } - - if vi.Spec.DataSource.ObjectRef == nil || vi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind { + if !isDataSourceVI(vi) { return } @@ -82,12 +79,3 @@ func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Objec return } - -func (w VirtualImageWatcher) isDataSourceVI(obj client.Object) bool { - vi, ok := obj.(*virtv2.VirtualImage) - if !ok { - return false - } - - return vi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && vi.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind -} From 7ba89abeab43422d0215bad0dc296cb019c7b55a Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Tue, 29 Apr 2025 17:54:13 +0300 Subject: [PATCH 18/27] fmt Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/vi/internal/inuse_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go index 3ef7efbb0b..33cf77ea73 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/inuse_test.go @@ -17,7 +17,8 @@ limitations under the License. package internal import ( - "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" + "time" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -25,7 +26,6 @@ import ( "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "time" cvibuilder "github.com/deckhouse/virtualization-controller/pkg/builder/cvi" vdbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vd" @@ -33,6 +33,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/common/testutil" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) From f85b72d24a77e2be10f724a0def1a729ccbc2a23 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 30 Apr 2025 10:02:25 +0300 Subject: [PATCH 19/27] clear legacy watchers Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/cvi_reconciler.go | 13 ------------- .../pkg/controller/vi/vi_reconciler.go | 13 ------------- 2 files changed, 26 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go index cc2171e9ae..926f3c31b4 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go @@ -34,7 +34,6 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/watcher" "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" - "github.com/deckhouse/virtualization-controller/pkg/controller/watchers" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) @@ -162,18 +161,6 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr } } - cviFromVIEnqueuer := watchers.NewClusterVirtualImageRequestEnqueuer(mgr.GetClient(), &virtv2.VirtualImage{}, virtv2.ClusterVirtualImageObjectRefKindVirtualImage) - viWatcher := watchers.NewObjectRefWatcher(watchers.NewVirtualImageFilter(), cviFromVIEnqueuer) - if err := viWatcher.Run(mgr, ctr); err != nil { - return fmt.Errorf("error setting watch on VIs: %w", err) - } - - cviFromCVIEnqueuer := watchers.NewClusterVirtualImageRequestEnqueuer(mgr.GetClient(), &virtv2.ClusterVirtualImage{}, virtv2.ClusterVirtualImageObjectRefKindClusterVirtualImage) - cviWatcher := watchers.NewObjectRefWatcher(watchers.NewClusterVirtualImageFilter(), cviFromCVIEnqueuer) - if err := cviWatcher.Run(mgr, ctr); err != nil { - return fmt.Errorf("error setting watch on CVIs: %w", err) - } - return nil } diff --git a/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go b/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go index f70c56befc..35726f5c4c 100644 --- a/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go @@ -37,7 +37,6 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/controller/vi/internal/watcher" - "github.com/deckhouse/virtualization-controller/pkg/controller/watchers" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) @@ -208,18 +207,6 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return fmt.Errorf("error setting watch on VDs: %w", err) } - viFromVIEnqueuer := watchers.NewVirtualImageRequestEnqueuer(mgr.GetClient(), &virtv2.VirtualImage{}, virtv2.VirtualImageObjectRefKindVirtualImage) - viWatcher := watchers.NewObjectRefWatcher(watchers.NewVirtualImageFilter(), viFromVIEnqueuer) - if err := viWatcher.Run(mgr, ctr); err != nil { - return fmt.Errorf("error setting watch on VIs: %w", err) - } - - viFromCVIEnqueuer := watchers.NewVirtualImageRequestEnqueuer(mgr.GetClient(), &virtv2.ClusterVirtualImage{}, virtv2.VirtualImageObjectRefKindClusterVirtualImage) - cviWatcher := watchers.NewObjectRefWatcher(watchers.NewClusterVirtualImageFilter(), viFromCVIEnqueuer) - if err := cviWatcher.Run(mgr, ctr); err != nil { - return fmt.Errorf("error setting watch on CVIs: %w", err) - } - for _, w := range []Watcher{ watcher.NewPodWatcher(mgr.GetClient()), watcher.NewStorageClassWatcher(mgr.GetClient()), From c22ba0880e31f475bf69d55ce2da96dbec8b7da0 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 30 Apr 2025 10:48:00 +0300 Subject: [PATCH 20/27] refactoring Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/internal/life_cycle.go | 1 + .../pkg/controller/vi/internal/life_cycle.go | 18 +++++------------- .../pkg/controller/vi/internal/source/http.go | 2 +- .../vi/internal/source/object_ref.go | 2 +- .../vi/internal/source/object_ref_vd.go | 2 +- .../vi/internal/source/object_ref_vi_on_pvc.go | 2 +- .../controller/vi/internal/source/registry.go | 2 +- .../controller/vi/internal/source/sources.go | 2 +- .../controller/vi/internal/source/upload.go | 2 +- 9 files changed, 13 insertions(+), 20 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go index d63cbd06e3..72d0a0a854 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/life_cycle.go @@ -55,6 +55,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtual } if cvi.DeletionTimestamp != nil { + // It is necessary to update this condition in order to use this image as a data source. if readyCondition.Status == metav1.ConditionTrue { cb := conditions.NewConditionBuilder(cvicondition.ReadyType).Generation(cvi.Generation). Status(metav1.ConditionTrue). diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go index bbb3a3cfb4..e50c20e474 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/life_cycle.go @@ -19,8 +19,8 @@ package internal import ( "context" "fmt" - corev1 "k8s.io/api/core/v1" + 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/reconcile" @@ -66,6 +66,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) ( if vi.DeletionTimestamp != nil { needRequeue := false + // It is necessary to update this condition in order to use this image as a datasource. if readyCondition.Status == metav1.ConditionTrue { cb := conditions.NewConditionBuilder(vicondition.ReadyType).Generation(vi.Generation) @@ -81,18 +82,9 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) ( return reconcile.Result{}, err } - switch { - case pvc == nil: - cb. - Status(metav1.ConditionFalse). - Reason(vicondition.Lost). - Message(fmt.Sprintf("PVC %s not found.", supgen.PersistentVolumeClaim().String())) - needRequeue = true - default: - cb. - Status(metav1.ConditionTrue). - Reason(vicondition.Ready). - Message("") + source.SetPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) + if cb.Condition().Status != metav1.ConditionTrue { + needRequeue = true // If a PVC is lost, we need to recheck InUseCondition status. } } diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/http.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/http.go index cc291461b9..42887a972d 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/http.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/http.go @@ -219,7 +219,7 @@ func (ds HTTPDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualImage case IsImageProvisioningFinished(condition): log.Info("Image provisioning finished: clean up") - setPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) + SetPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go index 07603a6e8e..fc76569334 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref.go @@ -150,7 +150,7 @@ func (ds ObjectRefDataSource) StoreToPVC(ctx context.Context, vi *virtv2.Virtual case IsImageProvisioningFinished(condition): log.Info("Disk provisioning finished: clean up") - setPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) + SetPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vd.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vd.go index 609bd5bb62..3bde462130 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vd.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vd.go @@ -238,7 +238,7 @@ func (ds ObjectRefVirtualDisk) StoreToPVC(ctx context.Context, vi *virtv2.Virtua case IsImageProvisioningFinished(condition): log.Info("Disk provisioning finished: clean up") - setPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) + SetPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vi_on_pvc.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vi_on_pvc.go index 80b376ee43..97ee4ada76 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vi_on_pvc.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/object_ref_vi_on_pvc.go @@ -215,7 +215,7 @@ func (ds ObjectRefDataVirtualImageOnPVC) StoreToPVC(ctx context.Context, vi, viR case IsImageProvisioningFinished(condition): log.Info("Disk provisioning finished: clean up") - setPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) + SetPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/registry.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/registry.go index 00be0c90ab..be38036405 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/registry.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/registry.go @@ -107,7 +107,7 @@ func (ds RegistryDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualI case IsImageProvisioningFinished(condition): log.Info("Disk provisioning finished: clean up") - setPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) + SetPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/sources.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/sources.go index 72674d6b00..567e90554d 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/sources.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/sources.go @@ -109,7 +109,7 @@ type CheckImportProcess interface { CheckImportProcess(ctx context.Context, dv *cdiv1.DataVolume, pvc *corev1.PersistentVolumeClaim) error } -func setPhaseConditionForFinishedImage( +func SetPhaseConditionForFinishedImage( pvc *corev1.PersistentVolumeClaim, cb *conditions.ConditionBuilder, phase *virtv2.ImagePhase, diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/upload.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/upload.go index 7ae7c2aa39..92e6bde452 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/upload.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/upload.go @@ -110,7 +110,7 @@ func (ds UploadDataSource) StoreToPVC(ctx context.Context, vi *virtv2.VirtualIma case IsImageProvisioningFinished(condition): log.Info("Disk provisioning finished: clean up") - setPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) + SetPhaseConditionForFinishedImage(pvc, cb, &vi.Status.Phase, supgen) // Protect Ready Disk and underlying PVC. err = ds.diskService.Protect(ctx, vi, nil, pvc) From 023ac8a1620fc20270342d6d5f22d3cef7e512c0 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 30 Apr 2025 16:21:58 +0300 Subject: [PATCH 21/27] fix empty ns Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/internal/inuse.go | 4 ---- .../pkg/controller/cvi/internal/inuse_test.go | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go index c64d3893a4..3f6963f644 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go @@ -127,10 +127,6 @@ func (h InUseHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImag } } - for _, cv := range cvisNotReady { - namespacesMap[cv.GetNamespace()] = struct{}{} - } - consumerCount := len(vmUsedImage) + len(vdsNotReady) + len(visNotReady) + len(cvisNotReady) if consumerCount > 0 { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go index 248f15a24f..5419eedf51 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse_test.go @@ -265,7 +265,24 @@ var _ = DescribeTable("InUseHandler Handle", func(args inUseHandlerTestArgs) { ExpectedConditionExists: true, ExpectedConditionStatus: metav1.ConditionTrue, ExpectedConditionReason: cvicondition.InUse.String(), - ExpectedConditionMessage: "5 VirtualMachines are using the ClusterVirtualImage, the ClusterVirtualImage is currently used to create 4 VirtualDisks, the ClusterVirtualImage is currently being used to create the VirtualImages: ns/test, ns5/test2, the ClusterVirtualImage is currently being used to create the ClusterVirtualImage test2, the ClusterVirtualImage is currently using in 7 Namespaces.", + ExpectedConditionMessage: "5 VirtualMachines are using the ClusterVirtualImage, the ClusterVirtualImage is currently used to create 4 VirtualDisks, the ClusterVirtualImage is currently being used to create the VirtualImages: ns/test, ns5/test2, the ClusterVirtualImage is currently being used to create the ClusterVirtualImage test2, the ClusterVirtualImage is currently using in 6 Namespaces.", + }), + Entry("has 1 CVI", inUseHandlerTestArgs{ + CVIName: "test", + DeletionTimestamp: ptr.To(metav1.Time{Time: time.Now()}), + CVIs: []virtv2.ClusterVirtualImage{ + generateCVIForInUseTest("test2", virtv2.ClusterVirtualImageDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.ClusterVirtualImageObjectRef{ + Kind: virtv2.ClusterVirtualImageKind, + Name: "test", + }, + }), + }, + ExpectedConditionExists: true, + ExpectedConditionStatus: metav1.ConditionTrue, + ExpectedConditionReason: cvicondition.InUse.String(), + ExpectedConditionMessage: "The ClusterVirtualImage is currently being used to create the ClusterVirtualImage test2.", }), ) From 5752dc492e0f3dd304cd985178be6f5c6565747d Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Sun, 4 May 2025 13:57:43 +0300 Subject: [PATCH 22/27] vi watchers reshuffle Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/cvi_reconciler.go | 13 +++ .../controller/cvi/internal/watcher/util.go | 34 ------- .../vi/internal/watcher/cvi_watcher.go | 76 +++++++++++++-- .../controller/vi/internal/watcher/util.go | 34 ------- .../vi/internal/watcher/vd_watcher.go | 95 ++++++++++++++++--- .../vi/internal/watcher/vi_watcher.go | 78 +++++++++++++-- .../pkg/controller/vi/vi_reconciler.go | 67 ------------- 7 files changed, 233 insertions(+), 164 deletions(-) delete mode 100644 images/virtualization-artifact/pkg/controller/cvi/internal/watcher/util.go delete mode 100644 images/virtualization-artifact/pkg/controller/vi/internal/watcher/util.go diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go index 926f3c31b4..a1e2c5a5c2 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go @@ -34,6 +34,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/watcher" "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" + "github.com/deckhouse/virtualization-controller/pkg/controller/watchers" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) @@ -149,6 +150,18 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return fmt.Errorf("error setting watch on VDs: %w", err) } + cviFromVIEnqueuer := watchers.NewClusterVirtualImageRequestEnqueuer(mgr.GetClient(), &virtv2.VirtualImage{}, virtv2.ClusterVirtualImageObjectRefKindVirtualImage) + viWatcher := watchers.NewObjectRefWatcher(watchers.NewVirtualImageFilter(), cviFromVIEnqueuer) + if err := viWatcher.Run(mgr, ctr); err != nil { + return fmt.Errorf("error setting watch on VIs: %w", err) + } + + cviFromCVIEnqueuer := watchers.NewClusterVirtualImageRequestEnqueuer(mgr.GetClient(), &virtv2.ClusterVirtualImage{}, virtv2.ClusterVirtualImageObjectRefKindClusterVirtualImage) + cviWatcher := watchers.NewObjectRefWatcher(watchers.NewClusterVirtualImageFilter(), cviFromCVIEnqueuer) + if err := cviWatcher.Run(mgr, ctr); err != nil { + return fmt.Errorf("error setting watch on CVIs: %w", err) + } + for _, w := range []Watcher{ watcher.NewClusterVirtualImageWatcher(mgr.GetClient()), watcher.NewVirtualImageWatcher(mgr.GetClient()), diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/util.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/util.go deleted file mode 100644 index 73bb2ea8eb..0000000000 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/util.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright 2025 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 watcher - -import ( - "sigs.k8s.io/controller-runtime/pkg/client" - - virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" -) - -func isDataSourceCVI(obj client.Object) bool { - vi, ok := obj.(*virtv2.VirtualImage) - if !ok { - return false - } - - return vi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && - vi.Spec.DataSource.ObjectRef != nil && - vi.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go index ff5e5b4047..c4e11cd429 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go @@ -18,6 +18,7 @@ package watcher import ( "context" + "fmt" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,7 +30,10 @@ import ( "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/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" ) type ClusterVirtualImageWatcher struct { @@ -48,25 +52,23 @@ func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Co handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return isDataSourceVI(e.Object) + return w.isDataSourceVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return isDataSourceVI(e.Object) - }, - UpdateFunc: func(e event.UpdateEvent) bool { - return isDataSourceVI(e.ObjectOld) || isDataSourceVI(e.ObjectNew) + return w.isDataSourceVI(e.Object) }, + UpdateFunc: w.filterUpdateEvents, }, ) } -func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { +func (w ClusterVirtualImageWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { cvi, ok := obj.(*virtv2.ClusterVirtualImage) if !ok { return } - if !isDataSourceVI(cvi) { + if !w.isDataSourceVI(cvi) { return } @@ -77,5 +79,65 @@ func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj clien }, }) + var viList virtv2.VirtualImageList + err := w.client.List(ctx, &viList) + if err != nil { + logger.FromContext(ctx).Error(fmt.Sprintf("failed to list vi: %s", err)) + return + } + + for _, vi := range viList.Items { + if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || vi.Spec.DataSource.ObjectRef == nil { + continue + } + + if vi.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind || vi.Spec.DataSource.ObjectRef.Name != obj.GetName() { + continue + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vi.Name, + Namespace: vi.Namespace, + }, + }) + } + return } + +func (w ClusterVirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool { + if !w.isDataSourceVI(e.ObjectOld) && !w.isDataSourceVI(e.ObjectNew) { + return false + } + + oldCVI, ok := e.ObjectOld.(*virtv2.ClusterVirtualImage) + if !ok { + return false + } + + newCVI, ok := e.ObjectNew.(*virtv2.ClusterVirtualImage) + if !ok { + return false + } + + oldReadyCondition, _ := conditions.GetCondition(cvicondition.ReadyType, oldCVI.Status.Conditions) + newReadyCondition, _ := conditions.GetCondition(cvicondition.ReadyType, newCVI.Status.Conditions) + + if oldCVI.Status.Phase != newCVI.Status.Phase || oldReadyCondition.Status != newReadyCondition.Status { + return true + } + + return false +} + +func (w ClusterVirtualImageWatcher) isDataSourceVI(obj client.Object) bool { + cvi, ok := obj.(*virtv2.ClusterVirtualImage) + if !ok { + return false + } + + return cvi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && + cvi.Spec.DataSource.ObjectRef != nil && + cvi.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/util.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/util.go deleted file mode 100644 index 068b16261f..0000000000 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/util.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright 2025 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 watcher - -import ( - "sigs.k8s.io/controller-runtime/pkg/client" - - virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" -) - -func isDataSourceVI(obj client.Object) bool { - vd, ok := obj.(*virtv2.VirtualDisk) - if !ok { - return false - } - - return vd.Spec.DataSource != nil && - vd.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && - vd.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go index 69f1663261..244fb64565 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go @@ -9,7 +9,7 @@ You may obtain a copy of the License at 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. +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.VirtualImage See the License for the specific language governing permissions and limitations under the License. */ @@ -18,6 +18,7 @@ package watcher import ( "context" + "fmt" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,7 +30,10 @@ import ( "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/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) type VirtualDiskWatcher struct { @@ -48,34 +52,95 @@ func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return isDataSourceVI(e.Object) + return w.isDataSourceVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return isDataSourceVI(e.Object) - }, - UpdateFunc: func(e event.UpdateEvent) bool { - return isDataSourceVI(e.ObjectOld) || isDataSourceVI(e.ObjectNew) + return w.isDataSourceVI(e.Object) }, + UpdateFunc: w.filterUpdateEvents, }, ) } -func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { - vd, ok := obj.(*virtv2.VirtualDisk) - if !ok { - return - } - - if !isDataSourceVI(vd) { +func (w VirtualDiskWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { + if !w.isDataSourceVI(obj) { return } requests = append(requests, reconcile.Request{ NamespacedName: types.NamespacedName{ - Name: vd.Spec.DataSource.ObjectRef.Name, - Namespace: vd.Namespace, + Name: obj.GetName(), + Namespace: obj.GetNamespace(), }, }) + var viList virtv2.VirtualImageList + err := w.client.List(ctx, &viList, &client.ListOptions{ + Namespace: obj.GetNamespace(), + }) + if err != nil { + logger.FromContext(ctx).Error(fmt.Sprintf("failed to list vi: %s", err)) + return + } + + for _, vi := range viList.Items { + if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || vi.Spec.DataSource.ObjectRef == nil { + continue + } + + if vi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualDiskKind || vi.Spec.DataSource.ObjectRef.Name != obj.GetName() { + continue + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vi.Name, + Namespace: vi.Namespace, + }, + }) + } + return } + +func (w VirtualDiskWatcher) filterUpdateEvents(e event.UpdateEvent) bool { + if !w.isDataSourceVI(e.ObjectOld) && !w.isDataSourceVI(e.ObjectNew) { + return false + } + + oldVD, ok := e.ObjectOld.(*virtv2.VirtualDisk) + if !ok { + return false + } + + newVD, ok := e.ObjectNew.(*virtv2.VirtualDisk) + if !ok { + return false + } + + oldInUseCondition, _ := conditions.GetCondition(vdcondition.InUseType, oldVD.Status.Conditions) + newInUseCondition, _ := conditions.GetCondition(vdcondition.InUseType, newVD.Status.Conditions) + + oldReadyCondition, _ := conditions.GetCondition(vdcondition.ReadyType, oldVD.Status.Conditions) + newReadyCondition, _ := conditions.GetCondition(vdcondition.ReadyType, newVD.Status.Conditions) + + if oldVD.Status.Phase != newVD.Status.Phase || + len(oldVD.Status.AttachedToVirtualMachines) != len(newVD.Status.AttachedToVirtualMachines) || + oldInUseCondition.Status != newInUseCondition.Status || + oldReadyCondition.Status != newReadyCondition.Status { + return true + } + + return false +} + +func (w VirtualDiskWatcher) isDataSourceVI(obj client.Object) bool { + vd, ok := obj.(*virtv2.VirtualDisk) + if !ok { + return false + } + + return vd.Spec.DataSource != nil && + vd.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && + vd.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go index 17e5ccb1e9..c1d0e5acc4 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go @@ -18,6 +18,7 @@ package watcher import ( "context" + "fmt" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,7 +30,10 @@ import ( "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/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) type VirtualImageWatcher struct { @@ -48,25 +52,23 @@ func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controlle handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return isDataSourceVI(e.Object) + return w.isDataSourceVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return isDataSourceVI(e.Object) - }, - UpdateFunc: func(e event.UpdateEvent) bool { - return isDataSourceVI(e.ObjectOld) || isDataSourceVI(e.ObjectNew) + return w.isDataSourceVI(e.Object) }, + UpdateFunc: w.filterUpdateEvents, }, ) } -func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { +func (w VirtualImageWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { vi, ok := obj.(*virtv2.VirtualImage) if !ok { return } - if !isDataSourceVI(vi) { + if !w.isDataSourceVI(vi) { return } @@ -77,5 +79,67 @@ func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Objec }, }) + var viList virtv2.VirtualImageList + err := w.client.List(ctx, &viList, &client.ListOptions{ + Namespace: obj.GetNamespace(), + }) + if err != nil { + logger.FromContext(ctx).Error(fmt.Sprintf("failed to list vi: %s", err)) + return + } + + for _, vi := range viList.Items { + if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || vi.Spec.DataSource.ObjectRef == nil { + continue + } + + if vi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind || vi.Spec.DataSource.ObjectRef.Name != obj.GetName() { + continue + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vi.Name, + Namespace: vi.Namespace, + }, + }) + } + return } + +func (w VirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool { + if !w.isDataSourceVI(e.ObjectOld) && !w.isDataSourceVI(e.ObjectNew) { + return false + } + + oldVI, ok := e.ObjectOld.(*virtv2.VirtualImage) + if !ok { + return false + } + + newVI, ok := e.ObjectNew.(*virtv2.VirtualImage) + if !ok { + return false + } + + oldReadyCondition, _ := conditions.GetCondition(vicondition.ReadyType, oldVI.Status.Conditions) + newReadyCondition, _ := conditions.GetCondition(vicondition.ReadyType, newVI.Status.Conditions) + + if oldVI.Status.Phase != newVI.Status.Phase || oldReadyCondition.Status != newReadyCondition.Status { + return true + } + + return false +} + +func (w VirtualImageWatcher) isDataSourceVI(obj client.Object) bool { + vi, ok := obj.(*virtv2.VirtualImage) + if !ok { + return false + } + + return vi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && + vi.Spec.DataSource.ObjectRef != nil && + vi.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go b/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go index 35726f5c4c..7919178281 100644 --- a/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go @@ -19,10 +19,8 @@ package vi import ( "context" "fmt" - "log/slog" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -33,12 +31,10 @@ import ( "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/reconciler" "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/controller/vi/internal/watcher" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" - "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) type Watcher interface { @@ -174,39 +170,6 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return fmt.Errorf("error setting watch on PVC: %w", err) } - if err := ctr.Watch( - source.Kind(mgr.GetCache(), &virtv2.VirtualDisk{}), - handler.EnqueueRequestsFromMapFunc(r.enqueueRequestsFromVDs), - predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { return true }, - DeleteFunc: func(e event.DeleteEvent) bool { return true }, - UpdateFunc: func(e event.UpdateEvent) bool { - oldVD, ok := e.ObjectOld.(*virtv2.VirtualDisk) - if !ok { - slog.Default().Error(fmt.Sprintf("expected an old VirtualDisk but got a %T", e.ObjectOld)) - return false - } - - newVD, ok := e.ObjectNew.(*virtv2.VirtualDisk) - if !ok { - slog.Default().Error(fmt.Sprintf("expected a new VirtualDisk but got a %T", e.ObjectNew)) - return false - } - - oldInUseCondition, _ := conditions.GetCondition(vdcondition.InUseType, oldVD.Status.Conditions) - newInUseCondition, _ := conditions.GetCondition(vdcondition.InUseType, newVD.Status.Conditions) - - if oldVD.Status.Phase != newVD.Status.Phase || len(oldVD.Status.AttachedToVirtualMachines) != len(newVD.Status.AttachedToVirtualMachines) || oldInUseCondition != newInUseCondition { - return true - } - - return false - }, - }, - ); err != nil { - return fmt.Errorf("error setting watch on VDs: %w", err) - } - for _, w := range []Watcher{ watcher.NewPodWatcher(mgr.GetClient()), watcher.NewStorageClassWatcher(mgr.GetClient()), @@ -225,36 +188,6 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return nil } -func (r *Reconciler) enqueueRequestsFromVDs(ctx context.Context, obj client.Object) (requests []reconcile.Request) { - var viList virtv2.VirtualImageList - err := r.client.List(ctx, &viList, &client.ListOptions{ - Namespace: obj.GetNamespace(), - }) - if err != nil { - slog.Default().Error(fmt.Sprintf("failed to list vi: %s", err)) - return - } - - for _, vi := range viList.Items { - if vi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || vi.Spec.DataSource.ObjectRef == nil { - continue - } - - if vi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualDiskKind || vi.Spec.DataSource.ObjectRef.Name != obj.GetName() { - continue - } - - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: vi.Name, - Namespace: vi.Namespace, - }, - }) - } - - return -} - func (r *Reconciler) factory() *virtv2.VirtualImage { return &virtv2.VirtualImage{} } From 6f2bbcc3300cd339b9a4a83889b9da2c2f1427d8 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Sun, 4 May 2025 14:49:20 +0300 Subject: [PATCH 23/27] refactorig cvi watchers Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/cvi_reconciler.go | 13 --- .../cvi/internal/watcher/cvi_watcher.go | 78 ++++++++++++++-- .../cvi/internal/watcher/vd_watcher.go | 89 ++++++++++++++++--- .../cvi/internal/watcher/vi_watcher.go | 75 ++++++++++++++-- 4 files changed, 213 insertions(+), 42 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go index a1e2c5a5c2..926f3c31b4 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go @@ -34,7 +34,6 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/cvi/internal/watcher" "github.com/deckhouse/virtualization-controller/pkg/controller/reconciler" - "github.com/deckhouse/virtualization-controller/pkg/controller/watchers" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) @@ -150,18 +149,6 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return fmt.Errorf("error setting watch on VDs: %w", err) } - cviFromVIEnqueuer := watchers.NewClusterVirtualImageRequestEnqueuer(mgr.GetClient(), &virtv2.VirtualImage{}, virtv2.ClusterVirtualImageObjectRefKindVirtualImage) - viWatcher := watchers.NewObjectRefWatcher(watchers.NewVirtualImageFilter(), cviFromVIEnqueuer) - if err := viWatcher.Run(mgr, ctr); err != nil { - return fmt.Errorf("error setting watch on VIs: %w", err) - } - - cviFromCVIEnqueuer := watchers.NewClusterVirtualImageRequestEnqueuer(mgr.GetClient(), &virtv2.ClusterVirtualImage{}, virtv2.ClusterVirtualImageObjectRefKindClusterVirtualImage) - cviWatcher := watchers.NewObjectRefWatcher(watchers.NewClusterVirtualImageFilter(), cviFromCVIEnqueuer) - if err := cviWatcher.Run(mgr, ctr); err != nil { - return fmt.Errorf("error setting watch on CVIs: %w", err) - } - for _, w := range []Watcher{ watcher.NewClusterVirtualImageWatcher(mgr.GetClient()), watcher.NewVirtualImageWatcher(mgr.GetClient()), diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go index e624d8c7ef..f50053740a 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go @@ -18,6 +18,7 @@ package watcher import ( "context" + "fmt" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,7 +30,10 @@ import ( "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/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" ) type ClusterVirtualImageWatcher struct { @@ -48,34 +52,90 @@ func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Co handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return isDataSourceCVI(e.Object) + return w.isDataSourceCVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return isDataSourceCVI(e.Object) - }, - UpdateFunc: func(e event.UpdateEvent) bool { - return isDataSourceCVI(e.ObjectOld) || isDataSourceCVI(e.ObjectNew) + return w.isDataSourceCVI(e.Object) }, + UpdateFunc: w.filterUpdateEvents, }, ) } -func (w ClusterVirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { +func (w ClusterVirtualImageWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { cvi, ok := obj.(*virtv2.ClusterVirtualImage) if !ok { return } - if !isDataSourceCVI(cvi) { + if !w.isDataSourceCVI(cvi) { return } requests = append(requests, reconcile.Request{ NamespacedName: types.NamespacedName{ - Name: cvi.Spec.DataSource.ObjectRef.Name, - Namespace: cvi.Spec.DataSource.ObjectRef.Namespace, + Name: cvi.Spec.DataSource.ObjectRef.Name, }, }) + var cviList virtv2.ClusterVirtualImageList + err := w.client.List(ctx, &cviList) + if err != nil { + logger.FromContext(ctx).Error(fmt.Sprintf("failed to list cvi: %s", err)) + return + } + + for _, cvi := range cviList.Items { + if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || cvi.Spec.DataSource.ObjectRef == nil { + continue + } + + if cvi.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind || cvi.Spec.DataSource.ObjectRef.Name != obj.GetName() { + continue + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: cvi.Name, + }, + }) + } + return } + +func (w ClusterVirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool { + if !w.isDataSourceCVI(e.ObjectOld) && !w.isDataSourceCVI(e.ObjectNew) { + return false + } + + oldCVI, ok := e.ObjectOld.(*virtv2.ClusterVirtualImage) + if !ok { + return false + } + + newCVI, ok := e.ObjectNew.(*virtv2.ClusterVirtualImage) + if !ok { + return false + } + + oldReadyCondition, _ := conditions.GetCondition(cvicondition.ReadyType, oldCVI.Status.Conditions) + newReadyCondition, _ := conditions.GetCondition(cvicondition.ReadyType, newCVI.Status.Conditions) + + if oldCVI.Status.Phase != newCVI.Status.Phase || oldReadyCondition.Status != newReadyCondition.Status { + return true + } + + return false +} + +func (w ClusterVirtualImageWatcher) isDataSourceCVI(obj client.Object) bool { + cvi, ok := obj.(*virtv2.ClusterVirtualImage) + if !ok { + return false + } + + return cvi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && + cvi.Spec.DataSource.ObjectRef != nil && + cvi.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go index 55096153ce..04ceb2e099 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go @@ -18,6 +18,7 @@ package watcher import ( "context" + "fmt" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,7 +30,10 @@ import ( "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/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) type VirtualDiskWatcher struct { @@ -48,33 +52,92 @@ func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return isDataSourceCVI(e.Object) + return w.isDataSourceCVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return isDataSourceCVI(e.Object) - }, - UpdateFunc: func(e event.UpdateEvent) bool { - return isDataSourceCVI(e.ObjectOld) || isDataSourceCVI(e.ObjectNew) + return w.isDataSourceCVI(e.Object) }, + UpdateFunc: w.filterUpdateEvents, }, ) } -func (w VirtualDiskWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { - vd, ok := obj.(*virtv2.VirtualDisk) - if !ok { - return - } - - if !isDataSourceCVI(vd) { +func (w VirtualDiskWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { + if !w.isDataSourceCVI(obj) { return } requests = append(requests, reconcile.Request{ NamespacedName: types.NamespacedName{ - Name: vd.Spec.DataSource.ObjectRef.Name, + Name: obj.GetName(), + Namespace: obj.GetNamespace(), }, }) + var cviList virtv2.ClusterVirtualImageList + err := w.client.List(ctx, &cviList) + if err != nil { + logger.FromContext(ctx).Error(fmt.Sprintf("failed to list cvi: %s", err)) + return + } + + for _, cvi := range cviList.Items { + if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || cvi.Spec.DataSource.ObjectRef == nil { + continue + } + + if cvi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualDiskKind || cvi.Spec.DataSource.ObjectRef.Name != obj.GetName() { + continue + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: cvi.Name, + }, + }) + } + return } + +func (w VirtualDiskWatcher) filterUpdateEvents(e event.UpdateEvent) bool { + if !w.isDataSourceCVI(e.ObjectOld) && !w.isDataSourceCVI(e.ObjectNew) { + return false + } + + oldVD, ok := e.ObjectOld.(*virtv2.VirtualDisk) + if !ok { + return false + } + + newVD, ok := e.ObjectNew.(*virtv2.VirtualDisk) + if !ok { + return false + } + + oldInUseCondition, _ := conditions.GetCondition(vdcondition.InUseType, oldVD.Status.Conditions) + newInUseCondition, _ := conditions.GetCondition(vdcondition.InUseType, newVD.Status.Conditions) + + oldReadyCondition, _ := conditions.GetCondition(vdcondition.ReadyType, oldVD.Status.Conditions) + newReadyCondition, _ := conditions.GetCondition(vdcondition.ReadyType, newVD.Status.Conditions) + + if oldVD.Status.Phase != newVD.Status.Phase || + len(oldVD.Status.AttachedToVirtualMachines) != len(newVD.Status.AttachedToVirtualMachines) || + oldInUseCondition.Status != newInUseCondition.Status || + oldReadyCondition.Status != newReadyCondition.Status { + return true + } + + return false +} + +func (w VirtualDiskWatcher) isDataSourceCVI(obj client.Object) bool { + vd, ok := obj.(*virtv2.VirtualDisk) + if !ok { + return false + } + + return vd.Spec.DataSource != nil && + vd.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && + vd.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind +} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go index cdbebecfdf..f8cdc44858 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go @@ -18,6 +18,7 @@ package watcher import ( "context" + "fmt" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,7 +30,10 @@ import ( "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/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) type VirtualImageWatcher struct { @@ -48,25 +52,23 @@ func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controlle handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return isDataSourceCVI(e.Object) + return w.isDataSourceCVI(e.Object) }, DeleteFunc: func(e event.DeleteEvent) bool { - return isDataSourceCVI(e.Object) - }, - UpdateFunc: func(e event.UpdateEvent) bool { - return isDataSourceCVI(e.ObjectOld) || isDataSourceCVI(e.ObjectNew) + return w.isDataSourceCVI(e.Object) }, + UpdateFunc: w.filterUpdateEvents, }, ) } -func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { +func (w VirtualImageWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { vi, ok := obj.(*virtv2.VirtualImage) if !ok { return } - if !isDataSourceCVI(vi) { + if !w.isDataSourceCVI(vi) { return } @@ -77,5 +79,64 @@ func (w VirtualImageWatcher) enqueueRequests(_ context.Context, obj client.Objec }, }) + var cviList virtv2.ClusterVirtualImageList + err := w.client.List(ctx, &cviList) + if err != nil { + logger.FromContext(ctx).Error(fmt.Sprintf("failed to list cvi: %s", err)) + return + } + + for _, cvi := range cviList.Items { + if cvi.Spec.DataSource.Type != virtv2.DataSourceTypeObjectRef || cvi.Spec.DataSource.ObjectRef == nil { + continue + } + + if cvi.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind || cvi.Spec.DataSource.ObjectRef.Name != obj.GetName() { + continue + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: cvi.Name, + }, + }) + } + return } + +func (w VirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool { + if !w.isDataSourceCVI(e.ObjectOld) && !w.isDataSourceCVI(e.ObjectNew) { + return false + } + + oldVI, ok := e.ObjectOld.(*virtv2.VirtualImage) + if !ok { + return false + } + + newVI, ok := e.ObjectNew.(*virtv2.VirtualImage) + if !ok { + return false + } + + oldReadyCondition, _ := conditions.GetCondition(vicondition.ReadyType, oldVI.Status.Conditions) + newReadyCondition, _ := conditions.GetCondition(vicondition.ReadyType, newVI.Status.Conditions) + + if oldVI.Status.Phase != newVI.Status.Phase || oldReadyCondition.Status != newReadyCondition.Status { + return true + } + + return false +} + +func (w VirtualImageWatcher) isDataSourceCVI(obj client.Object) bool { + vi, ok := obj.(*virtv2.VirtualImage) + if !ok { + return false + } + + return vi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && + vi.Spec.DataSource.ObjectRef != nil && + vi.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind +} From 66782a68297ff44ecad61ed58af56db336a7a03e Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Sun, 4 May 2025 18:47:28 +0300 Subject: [PATCH 24/27] fix Signed-off-by: Valeriy Khorunzhin --- .../cvi/internal/watcher/cvi_watcher.go | 15 --------------- .../cvi/internal/watcher/vd_watcher.go | 11 ----------- .../cvi/internal/watcher/vi_watcher.go | 16 ---------------- .../vi/internal/watcher/cvi_watcher.go | 16 ---------------- .../controller/vi/internal/watcher/vd_watcher.go | 11 ----------- .../controller/vi/internal/watcher/vi_watcher.go | 16 ---------------- 6 files changed, 85 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go index f50053740a..8922813b5b 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go @@ -63,21 +63,6 @@ func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Co } func (w ClusterVirtualImageWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { - cvi, ok := obj.(*virtv2.ClusterVirtualImage) - if !ok { - return - } - - if !w.isDataSourceCVI(cvi) { - return - } - - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: cvi.Spec.DataSource.ObjectRef.Name, - }, - }) - var cviList virtv2.ClusterVirtualImageList err := w.client.List(ctx, &cviList) if err != nil { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go index 04ceb2e099..01d53f6589 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go @@ -63,17 +63,6 @@ func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller } func (w VirtualDiskWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { - if !w.isDataSourceCVI(obj) { - return - } - - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }, - }) - var cviList virtv2.ClusterVirtualImageList err := w.client.List(ctx, &cviList) if err != nil { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go index f8cdc44858..ffe7741ce7 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go @@ -63,22 +63,6 @@ func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controlle } func (w VirtualImageWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { - vi, ok := obj.(*virtv2.VirtualImage) - if !ok { - return - } - - if !w.isDataSourceCVI(vi) { - return - } - - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: vi.Spec.DataSource.ObjectRef.Name, - Namespace: vi.Namespace, - }, - }) - var cviList virtv2.ClusterVirtualImageList err := w.client.List(ctx, &cviList) if err != nil { diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go index c4e11cd429..482ff8b79d 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go @@ -63,22 +63,6 @@ func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Co } func (w ClusterVirtualImageWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { - cvi, ok := obj.(*virtv2.ClusterVirtualImage) - if !ok { - return - } - - if !w.isDataSourceVI(cvi) { - return - } - - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: cvi.Spec.DataSource.ObjectRef.Name, - Namespace: cvi.Spec.DataSource.ObjectRef.Namespace, - }, - }) - var viList virtv2.VirtualImageList err := w.client.List(ctx, &viList) if err != nil { diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go index 244fb64565..223ac2c29f 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go @@ -63,17 +63,6 @@ func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller } func (w VirtualDiskWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { - if !w.isDataSourceVI(obj) { - return - } - - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - }, - }) - var viList virtv2.VirtualImageList err := w.client.List(ctx, &viList, &client.ListOptions{ Namespace: obj.GetNamespace(), diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go index c1d0e5acc4..817f08e62e 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go @@ -63,22 +63,6 @@ func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controlle } func (w VirtualImageWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { - vi, ok := obj.(*virtv2.VirtualImage) - if !ok { - return - } - - if !w.isDataSourceVI(vi) { - return - } - - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: vi.Spec.DataSource.ObjectRef.Name, - Namespace: vi.Namespace, - }, - }) - var viList virtv2.VirtualImageList err := w.client.List(ctx, &viList, &client.ListOptions{ Namespace: obj.GetNamespace(), From 77e69255fd0fcc34597c207a4e1d54842c36835f Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 5 May 2025 00:50:19 +0300 Subject: [PATCH 25/27] oops Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/vi/internal/watcher/vd_watcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go index 223ac2c29f..ee49ab33ed 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go @@ -9,7 +9,7 @@ You may obtain a copy of the License at 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.VirtualImage +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. */ From 35ac8f065ef96ae33c3d8c1dafad0d6d386a1e4c Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 5 May 2025 08:23:54 +0300 Subject: [PATCH 26/27] fix Signed-off-by: Valeriy Khorunzhin --- .../cvi/internal/watcher/cvi_watcher.go | 19 ++--------------- .../cvi/internal/watcher/vd_watcher.go | 19 ++--------------- .../cvi/internal/watcher/vi_watcher.go | 21 +++---------------- .../vi/internal/watcher/cvi_watcher.go | 19 ++--------------- .../vi/internal/watcher/vd_watcher.go | 19 ++--------------- .../vi/internal/watcher/vi_watcher.go | 19 ++--------------- 6 files changed, 13 insertions(+), 103 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go index 8922813b5b..4a47658c13 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/cvi_watcher.go @@ -52,10 +52,10 @@ func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Co handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceCVI(e.Object) + return true }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceCVI(e.Object) + return true }, UpdateFunc: w.filterUpdateEvents, }, @@ -90,10 +90,6 @@ func (w ClusterVirtualImageWatcher) enqueueRequests(ctx context.Context, obj cli } func (w ClusterVirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool { - if !w.isDataSourceCVI(e.ObjectOld) && !w.isDataSourceCVI(e.ObjectNew) { - return false - } - oldCVI, ok := e.ObjectOld.(*virtv2.ClusterVirtualImage) if !ok { return false @@ -113,14 +109,3 @@ func (w ClusterVirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool return false } - -func (w ClusterVirtualImageWatcher) isDataSourceCVI(obj client.Object) bool { - cvi, ok := obj.(*virtv2.ClusterVirtualImage) - if !ok { - return false - } - - return cvi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && - cvi.Spec.DataSource.ObjectRef != nil && - cvi.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go index 01d53f6589..283c1128b3 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vd_watcher.go @@ -52,10 +52,10 @@ func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceCVI(e.Object) + return true }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceCVI(e.Object) + return true }, UpdateFunc: w.filterUpdateEvents, }, @@ -90,10 +90,6 @@ func (w VirtualDiskWatcher) enqueueRequests(ctx context.Context, obj client.Obje } func (w VirtualDiskWatcher) filterUpdateEvents(e event.UpdateEvent) bool { - if !w.isDataSourceCVI(e.ObjectOld) && !w.isDataSourceCVI(e.ObjectNew) { - return false - } - oldVD, ok := e.ObjectOld.(*virtv2.VirtualDisk) if !ok { return false @@ -119,14 +115,3 @@ func (w VirtualDiskWatcher) filterUpdateEvents(e event.UpdateEvent) bool { return false } - -func (w VirtualDiskWatcher) isDataSourceCVI(obj client.Object) bool { - vd, ok := obj.(*virtv2.VirtualDisk) - if !ok { - return false - } - - return vd.Spec.DataSource != nil && - vd.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && - vd.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go index ffe7741ce7..7dba4fa2dc 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/watcher/vi_watcher.go @@ -52,10 +52,10 @@ func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controlle handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceCVI(e.Object) + return true }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceCVI(e.Object) + return true }, UpdateFunc: w.filterUpdateEvents, }, @@ -75,7 +75,7 @@ func (w VirtualImageWatcher) enqueueRequests(ctx context.Context, obj client.Obj continue } - if cvi.Spec.DataSource.ObjectRef.Kind != virtv2.ClusterVirtualImageKind || cvi.Spec.DataSource.ObjectRef.Name != obj.GetName() { + if cvi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageKind || cvi.Spec.DataSource.ObjectRef.Name != obj.GetName() { continue } @@ -90,10 +90,6 @@ func (w VirtualImageWatcher) enqueueRequests(ctx context.Context, obj client.Obj } func (w VirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool { - if !w.isDataSourceCVI(e.ObjectOld) && !w.isDataSourceCVI(e.ObjectNew) { - return false - } - oldVI, ok := e.ObjectOld.(*virtv2.VirtualImage) if !ok { return false @@ -113,14 +109,3 @@ func (w VirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool { return false } - -func (w VirtualImageWatcher) isDataSourceCVI(obj client.Object) bool { - vi, ok := obj.(*virtv2.VirtualImage) - if !ok { - return false - } - - return vi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && - vi.Spec.DataSource.ObjectRef != nil && - vi.Spec.DataSource.ObjectRef.Kind == virtv2.ClusterVirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go index 482ff8b79d..ae3de0ee02 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/cvi_watcher.go @@ -52,10 +52,10 @@ func (w ClusterVirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Co handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceVI(e.Object) + return true }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceVI(e.Object) + return true }, UpdateFunc: w.filterUpdateEvents, }, @@ -91,10 +91,6 @@ func (w ClusterVirtualImageWatcher) enqueueRequests(ctx context.Context, obj cli } func (w ClusterVirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool { - if !w.isDataSourceVI(e.ObjectOld) && !w.isDataSourceVI(e.ObjectNew) { - return false - } - oldCVI, ok := e.ObjectOld.(*virtv2.ClusterVirtualImage) if !ok { return false @@ -114,14 +110,3 @@ func (w ClusterVirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool return false } - -func (w ClusterVirtualImageWatcher) isDataSourceVI(obj client.Object) bool { - cvi, ok := obj.(*virtv2.ClusterVirtualImage) - if !ok { - return false - } - - return cvi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && - cvi.Spec.DataSource.ObjectRef != nil && - cvi.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go index ee49ab33ed..0827eb2205 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vd_watcher.go @@ -52,10 +52,10 @@ func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceVI(e.Object) + return true }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceVI(e.Object) + return true }, UpdateFunc: w.filterUpdateEvents, }, @@ -93,10 +93,6 @@ func (w VirtualDiskWatcher) enqueueRequests(ctx context.Context, obj client.Obje } func (w VirtualDiskWatcher) filterUpdateEvents(e event.UpdateEvent) bool { - if !w.isDataSourceVI(e.ObjectOld) && !w.isDataSourceVI(e.ObjectNew) { - return false - } - oldVD, ok := e.ObjectOld.(*virtv2.VirtualDisk) if !ok { return false @@ -122,14 +118,3 @@ func (w VirtualDiskWatcher) filterUpdateEvents(e event.UpdateEvent) bool { return false } - -func (w VirtualDiskWatcher) isDataSourceVI(obj client.Object) bool { - vd, ok := obj.(*virtv2.VirtualDisk) - if !ok { - return false - } - - return vd.Spec.DataSource != nil && - vd.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && - vd.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind -} diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go index 817f08e62e..ef74a89541 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/watcher/vi_watcher.go @@ -52,10 +52,10 @@ func (w VirtualImageWatcher) Watch(mgr manager.Manager, ctr controller.Controlle handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { - return w.isDataSourceVI(e.Object) + return true }, DeleteFunc: func(e event.DeleteEvent) bool { - return w.isDataSourceVI(e.Object) + return true }, UpdateFunc: w.filterUpdateEvents, }, @@ -93,10 +93,6 @@ func (w VirtualImageWatcher) enqueueRequests(ctx context.Context, obj client.Obj } func (w VirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool { - if !w.isDataSourceVI(e.ObjectOld) && !w.isDataSourceVI(e.ObjectNew) { - return false - } - oldVI, ok := e.ObjectOld.(*virtv2.VirtualImage) if !ok { return false @@ -116,14 +112,3 @@ func (w VirtualImageWatcher) filterUpdateEvents(e event.UpdateEvent) bool { return false } - -func (w VirtualImageWatcher) isDataSourceVI(obj client.Object) bool { - vi, ok := obj.(*virtv2.VirtualImage) - if !ok { - return false - } - - return vi.Spec.DataSource.Type == virtv2.DataSourceTypeObjectRef && - vi.Spec.DataSource.ObjectRef != nil && - vi.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualImageKind -} From 583eca1cf03fdfeb0872845501d7b9229842d9d9 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 5 May 2025 09:09:29 +0300 Subject: [PATCH 27/27] fix phase check Signed-off-by: Valeriy Khorunzhin --- .../pkg/controller/cvi/internal/inuse.go | 9 +++++---- .../pkg/controller/vi/internal/inuse.go | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go index 3f6963f644..c13b3d7175 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/inuse.go @@ -19,9 +19,10 @@ package internal import ( "context" "fmt" - "k8s.io/apimachinery/pkg/types" "strings" + "k8s.io/apimachinery/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -86,7 +87,7 @@ func (h InUseHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImag } var vdsNotReady []client.Object for _, vd := range vds.Items { - if vd.Status.Phase != virtv2.DiskReady { + if vd.Status.Phase != virtv2.DiskReady && vd.Status.Phase != virtv2.DiskTerminating { vdsNotReady = append(vdsNotReady, &vd) } } @@ -104,7 +105,7 @@ func (h InUseHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImag } var visNotReady []client.Object for _, vi := range vis.Items { - if vi.Status.Phase != virtv2.ImageReady { + if vi.Status.Phase != virtv2.ImageReady && vi.Status.Phase != virtv2.ImageTerminating { visNotReady = append(visNotReady, &vi) } } @@ -122,7 +123,7 @@ func (h InUseHandler) Handle(ctx context.Context, cvi *virtv2.ClusterVirtualImag } var cvisNotReady []client.Object for _, cvi := range cvis.Items { - if cvi.Status.Phase != virtv2.ImageReady { + if cvi.Status.Phase != virtv2.ImageReady && cvi.Status.Phase != virtv2.ImageTerminating { cvisNotReady = append(cvisNotReady, &cvi) } } diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/inuse.go b/images/virtualization-artifact/pkg/controller/vi/internal/inuse.go index b7a7b7782d..d8bf4a6e32 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/inuse.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/inuse.go @@ -83,7 +83,7 @@ func (h InUseHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) (reco } var vdsNotReady []client.Object for _, vd := range vds.Items { - if vd.Status.Phase != virtv2.DiskReady { + if vd.Status.Phase != virtv2.DiskReady && vd.Status.Phase != virtv2.DiskTerminating { vdsNotReady = append(vdsNotReady, &vd) } } @@ -97,7 +97,7 @@ func (h InUseHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) (reco } var visNotReady []client.Object for _, vi := range vis.Items { - if vi.Status.Phase != virtv2.ImageReady { + if vi.Status.Phase != virtv2.ImageReady && vi.Status.Phase != virtv2.ImageTerminating { visNotReady = append(visNotReady, &vi) } } @@ -112,7 +112,7 @@ func (h InUseHandler) Handle(ctx context.Context, vi *virtv2.VirtualImage) (reco var cvisFiltered []client.Object for _, cvi := range cvis.Items { - if cvi.Spec.DataSource.ObjectRef == nil || cvi.Status.Phase == virtv2.ImageReady { + if cvi.Spec.DataSource.ObjectRef == nil || cvi.Status.Phase == virtv2.ImageReady || cvi.Status.Phase == virtv2.ImageTerminating { continue } if cvi.Spec.DataSource.ObjectRef.Namespace == vi.GetNamespace() {