diff --git a/api/core/v1alpha2/cvicondition/condition.go b/api/core/v1alpha2/cvicondition/condition.go index 529db762d..079a20a11 100644 --- a/api/core/v1alpha2/cvicondition/condition.go +++ b/api/core/v1alpha2/cvicondition/condition.go @@ -44,6 +44,8 @@ const ( ClusterImageNotReady DatasourceReadyReason = "ClusterImageNotReady" // VirtualDiskNotReady indicates that the `VirtualDisk` datasource is not ready, which prevents the import process from starting. VirtualDiskNotReady DatasourceReadyReason = "VirtualDiskNotReady" + // VirtualDiskInUseInRunningVirtualMachine indicates that the `VirtualDisk` attached to running `VirtualMachine` + VirtualDiskInUseInRunningVirtualMachine DatasourceReadyReason = "VirtualDiskInUseInRunningVirtualMachine" // WaitForUserUpload indicates that the `ClusterVirtualImage` is waiting for the user to upload a datasource for the import process to continue. WaitForUserUpload ReadyReason = "WaitForUserUpload" diff --git a/api/core/v1alpha2/vdcondition/condition.go b/api/core/v1alpha2/vdcondition/condition.go index 622e2e1d7..b1ec61927 100644 --- a/api/core/v1alpha2/vdcondition/condition.go +++ b/api/core/v1alpha2/vdcondition/condition.go @@ -28,6 +28,8 @@ const ( ResizedType Type = "Resized" // SnapshottingType indicates whether the disk snapshotting operation is in progress. SnapshottingType Type = "Snapshotting" + // InUseType indicates whether the VirtualDisk is attached to a running VirtualMachine or is being used in a process of a VirtualImage creation + InUseType Type = "InUse" ) type ( @@ -39,6 +41,8 @@ type ( ResizedReason = string // SnapshottingReason represents the various reasons for the Snapshotting condition type. SnapshottingReason = string + // InUseReason represents the various reasons for the InUse condition type. + InUseReason = string ) const ( @@ -83,4 +87,11 @@ const ( Snapshotting SnapshottingReason = "Snapshotting" // SnapshottingNotAvailable indicates that the snapshotting operation is not available for now. SnapshottingNotAvailable SnapshottingReason = "NotAvailable" + + // InUseInRunningVirtualMachine indicates that this `VirtualDisk` attached to running `VirtualMachine` + InUseInRunningVirtualMachine InUseReason = "InUseInRunningVirtualMachine" + // InUseForCreateImage indicates running a process to create a `VirtualImage` or `ClusterVirtualImage` from this `VirtualDisk` + InUseForCreateImage InUseReason = "InUseForCreateImage" + // NotInUse indicates that this `VirtualDisk` is available. + NotInUse InUseReason = "NotInUse" ) diff --git a/api/core/v1alpha2/vicondition/condition.go b/api/core/v1alpha2/vicondition/condition.go index 0b3031a68..274c8f9e1 100644 --- a/api/core/v1alpha2/vicondition/condition.go +++ b/api/core/v1alpha2/vicondition/condition.go @@ -44,6 +44,8 @@ const ( ClusterImageNotReady DatasourceReadyReason = "ClusterImageNotReady" // VirtualDiskNotReady indicates that the `VirtualDisk` datasource is not ready, which prevents the import process from starting. VirtualDiskNotReady DatasourceReadyReason = "VirtualDiskNotReady" + // VirtualDiskInUseInRunningVirtualMachine indicates that the `VirtualDisk` attached to running `VirtualMachine` + VirtualDiskInUseInRunningVirtualMachine DatasourceReadyReason = "VirtualDiskInUseInRunningVirtualMachine" // WaitForUserUpload indicates that the `VirtualImage` is waiting for the user to upload a datasource for the import process to continue. WaitForUserUpload ReadyReason = "WaitForUserUpload" diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go index 3e3f4ffae..22af02074 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_reconciler.go @@ -38,6 +38,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/watchers" "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) type Handler interface { @@ -165,7 +166,14 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return false } - return oldVD.Status.Phase != newVD.Status.Phase + oldInUseCondition, _ := service.GetCondition(vdcondition.InUseType, oldVD.Status.Conditions) + newInUseCondition, _ := service.GetCondition(vdcondition.InUseType, newVD.Status.Conditions) + + if oldVD.Status.Phase != newVD.Status.Phase || len(oldVD.Status.AttachedToVirtualMachines) != len(newVD.Status.AttachedToVirtualMachines) || oldInUseCondition.Status != newInUseCondition.Status { + return true + } + + return false }, }, ); err != nil { diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/datasource_ready.go b/images/virtualization-artifact/pkg/controller/cvi/internal/datasource_ready.go index 444758a02..d3fb50b77 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/datasource_ready.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/datasource_ready.go @@ -89,9 +89,9 @@ func (h DatasourceReadyHandler) Handle(ctx context.Context, cvi *virtv2.ClusterV condition.Reason = cvicondition.VirtualDiskNotReady condition.Message = service.CapitalizeFirstLetter(err.Error()) return reconcile.Result{}, nil - case errors.As(err, &source.VirtualDiskAttachedToRunningVMError{}): + case errors.As(err, &source.VirtualDiskInUseError{}): condition.Status = metav1.ConditionFalse - condition.Reason = cvicondition.VirtualDiskNotReady + condition.Reason = cvicondition.VirtualDiskInUseInRunningVirtualMachine condition.Message = service.CapitalizeFirstLetter(err.Error()) return reconcile.Result{}, nil default: diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/errors.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/errors.go index f8daee718..bb615ac87 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/errors.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/source/errors.go @@ -65,18 +65,16 @@ func NewVirtualDiskNotReadyError(name string) error { } } -type VirtualDiskAttachedToRunningVMError struct { - name string - vmName string +type VirtualDiskInUseError struct { + name string } -func (e VirtualDiskAttachedToRunningVMError) Error() string { - return fmt.Sprintf("VirtualDisk %q attached to running VirtualMachine %q", e.name, e.vmName) +func (e VirtualDiskInUseError) Error() string { + return fmt.Sprintf("VirtualDisk %q attached to running `VirtualMachine`", e.name) } -func NewVirtualDiskAttachedToRunningVMError(name, vmName string) error { - return VirtualDiskAttachedToRunningVMError{ - name: name, - vmName: vmName, +func NewVirtualDiskInUseError(name string) error { + return VirtualDiskInUseError{ + name: name, } } diff --git a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vd.go b/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vd.go index a92662033..b94a54448 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vd.go +++ b/images/virtualization-artifact/pkg/controller/cvi/internal/source/object_ref_vd.go @@ -34,6 +34,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/logger" "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) @@ -207,16 +208,10 @@ func (ds ObjectRefVirtualDisk) Validate(ctx context.Context, cvi *virtv2.Cluster return NewVirtualDiskNotReadyError(cvi.Spec.DataSource.ObjectRef.Name) } - if len(vd.Status.AttachedToVirtualMachines) != 0 { - vmName := vd.Status.AttachedToVirtualMachines[0] - - vm, err := helper.FetchObject(ctx, types.NamespacedName{Name: vmName.Name, Namespace: vd.Namespace}, ds.client, &virtv2.VirtualMachine{}) - if err != nil { - return err - } - - if vm.Status.Phase != virtv2.MachineStopped { - return NewVirtualDiskAttachedToRunningVMError(vd.Name, vmName.Name) + inUseCondition, _ := service.GetCondition(vdcondition.InUseType, vd.Status.Conditions) + if inUseCondition.Status != metav1.ConditionFalse { + if inUseCondition.Reason == vdcondition.InUseInRunningVirtualMachine { + return NewVirtualDiskInUseError(vd.Name) } } diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/inuse.go b/images/virtualization-artifact/pkg/controller/vd/internal/inuse.go new file mode 100644 index 000000000..8c47bb1ac --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vd/internal/inuse.go @@ -0,0 +1,153 @@ +/* +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" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + virtv1 "kubevirt.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" +) + +type InUseHandler struct { + client client.Client +} + +func NewInUseHandler(client client.Client) *InUseHandler { + return &InUseHandler{ + client: client, + } +} + +func (h InUseHandler) Handle(ctx context.Context, vd *virtv2.VirtualDisk) (reconcile.Result, error) { + inUseCondition, ok := service.GetCondition(vdcondition.InUseType, vd.Status.Conditions) + if !ok { + inUseCondition = metav1.Condition{ + Type: vdcondition.InUseType, + Status: metav1.ConditionUnknown, + } + + service.SetCondition(inUseCondition, &vd.Status.Conditions) + } + + var inUseForCreateImage, inUseInRunningVirtualMachine bool + + var vms virtv2.VirtualMachineList + err := h.client.List(ctx, &vms, &client.ListOptions{ + Namespace: vd.GetNamespace(), + }) + if err != nil { + return reconcile.Result{}, fmt.Errorf("error getting virtual machines: %w", err) + } + + for _, vm := range vms.Items { + for _, bda := range vm.Status.BlockDeviceRefs { + if bda.Kind == virtv2.DiskDevice && bda.Name == vd.GetName() { + runningCondition, _ := service.GetCondition(vmcondition.TypeRunning.String(), vm.Status.Conditions) + if runningCondition.Status == metav1.ConditionTrue || vm.Status.Phase == virtv2.MachineStarting { + inUseInRunningVirtualMachine = true + } else { + kvvm, err := helper.FetchObject(ctx, types.NamespacedName{Namespace: vm.GetNamespace(), Name: vm.GetName()}, h.client, &virtv1.VirtualMachine{}) + if err != nil { + return reconcile.Result{}, fmt.Errorf("error getting kubevirt virtual machine: %w", err) + } + if kvvm != nil { + if kvvm.Status.StateChangeRequests != nil { + inUseInRunningVirtualMachine = true + } + } + } + } + } + } + + var viList virtv2.VirtualImageList + err = h.client.List(ctx, &viList, &client.ListOptions{ + Namespace: vd.GetNamespace(), + }) + if err != nil { + return reconcile.Result{}, fmt.Errorf("error getting virtual images: %w", err) + } + + 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 != vd.GetName() { + continue + } + + readyCondition, _ := service.GetCondition(vicondition.ReadyType, vi.Status.Conditions) + if readyCondition.Status != metav1.ConditionTrue { + inUseForCreateImage = true + } + } + + var cviList virtv2.ClusterVirtualImageList + err = h.client.List(ctx, &cviList, &client.ListOptions{}) + if err != nil { + return reconcile.Result{}, fmt.Errorf("error getting cluster virtual images: %w", err) + } + + 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 != vd.GetName() && cvi.Spec.DataSource.ObjectRef.Namespace != vd.GetNamespace() { + continue + } + + readyCondition, _ := service.GetCondition(cvicondition.ReadyType, cvi.Status.Conditions) + if readyCondition.Status != metav1.ConditionTrue { + inUseForCreateImage = true + } + } + + if inUseInRunningVirtualMachine && !inUseForCreateImage { + inUseCondition.Status = metav1.ConditionTrue + inUseCondition.Reason = vdcondition.InUseInRunningVirtualMachine + } + + if inUseForCreateImage && !inUseInRunningVirtualMachine { + inUseCondition.Status = metav1.ConditionTrue + inUseCondition.Reason = vdcondition.InUseForCreateImage + } + + if !inUseInRunningVirtualMachine && !inUseForCreateImage { + inUseCondition.Status = metav1.ConditionFalse + inUseCondition.Reason = vdcondition.NotInUse + } + + inUseCondition.Message = "" + service.SetCondition(inUseCondition, &vd.Status.Conditions) + + return reconcile.Result{}, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vd/vd_controller.go b/images/virtualization-artifact/pkg/controller/vd/vd_controller.go index 0737e7192..cf793066d 100644 --- a/images/virtualization-artifact/pkg/controller/vd/vd_controller.go +++ b/images/virtualization-artifact/pkg/controller/vd/vd_controller.go @@ -82,6 +82,7 @@ func NewController( internal.NewDeletionHandler(sources), internal.NewAttacheeHandler(mgr.GetClient()), internal.NewStatsHandler(stat, importer, uploader), + internal.NewInUseHandler(mgr.GetClient()), ) vdController, err := controller.New(ControllerName, mgr, controller.Options{ diff --git a/images/virtualization-artifact/pkg/controller/vd/vd_reconciler.go b/images/virtualization-artifact/pkg/controller/vd/vd_reconciler.go index 0b0e7cace..7bd4482e7 100644 --- a/images/virtualization-artifact/pkg/controller/vd/vd_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vd/vd_reconciler.go @@ -232,8 +232,8 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return fmt.Errorf("error setting watch on CVIs: %w", err) } - w := watcher.NewVirtualDiskSnapshotWatcher(mgr.GetClient()) - if err := w.Watch(mgr, ctr); err != nil { + snapshotWatcher := watcher.NewVirtualDiskSnapshotWatcher(mgr.GetClient()) + if err := snapshotWatcher.Watch(mgr, ctr); err != nil { return fmt.Errorf("error setting watch on VDSnapshots: %w", err) } diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/datasource_ready.go b/images/virtualization-artifact/pkg/controller/vi/internal/datasource_ready.go index 9d11ed922..876f40e5e 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/datasource_ready.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/datasource_ready.go @@ -27,7 +27,6 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/controller/vi/internal/source" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" - "github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition" "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) @@ -90,9 +89,9 @@ func (h DatasourceReadyHandler) Handle(ctx context.Context, vi *virtv2.VirtualIm condition.Reason = vicondition.VirtualDiskNotReady condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") return reconcile.Result{}, nil - case errors.As(err, &source.VirtualDiskAttachedToRunningVMError{}): + case errors.As(err, &source.VirtualDiskInUseError{}): condition.Status = metav1.ConditionFalse - condition.Reason = cvicondition.VirtualDiskNotReady + condition.Reason = vicondition.VirtualDiskInUseInRunningVirtualMachine condition.Message = service.CapitalizeFirstLetter(err.Error() + ".") return reconcile.Result{}, nil default: diff --git a/images/virtualization-artifact/pkg/controller/vi/internal/source/errors.go b/images/virtualization-artifact/pkg/controller/vi/internal/source/errors.go index f8daee718..bb615ac87 100644 --- a/images/virtualization-artifact/pkg/controller/vi/internal/source/errors.go +++ b/images/virtualization-artifact/pkg/controller/vi/internal/source/errors.go @@ -65,18 +65,16 @@ func NewVirtualDiskNotReadyError(name string) error { } } -type VirtualDiskAttachedToRunningVMError struct { - name string - vmName string +type VirtualDiskInUseError struct { + name string } -func (e VirtualDiskAttachedToRunningVMError) Error() string { - return fmt.Sprintf("VirtualDisk %q attached to running VirtualMachine %q", e.name, e.vmName) +func (e VirtualDiskInUseError) Error() string { + return fmt.Sprintf("VirtualDisk %q attached to running `VirtualMachine`", e.name) } -func NewVirtualDiskAttachedToRunningVMError(name, vmName string) error { - return VirtualDiskAttachedToRunningVMError{ - name: name, - vmName: vmName, +func NewVirtualDiskInUseError(name string) error { + return VirtualDiskInUseError{ + name: name, } } 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 445764bdf..4150a68c2 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 @@ -39,6 +39,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" "github.com/deckhouse/virtualization-controller/pkg/util" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" "github.com/deckhouse/virtualization/api/core/v1alpha2/vicondition" ) @@ -332,7 +333,7 @@ func (ds ObjectRefVirtualDisk) getEnvSettings(vi *virtv2.VirtualImage, sup *supp func (ds ObjectRefVirtualDisk) Validate(ctx context.Context, vi *virtv2.VirtualImage) error { if vi.Spec.DataSource.ObjectRef == nil || vi.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualImageObjectRefKindVirtualDisk { - return fmt.Errorf("not a %s data source", virtv2.ClusterVirtualImageObjectRefKindVirtualDisk) + return fmt.Errorf("not a %s data source", virtv2.VirtualImageObjectRefKindVirtualDisk) } vd, err := helper.FetchObject(ctx, types.NamespacedName{Name: vi.Spec.DataSource.ObjectRef.Name, Namespace: vi.Namespace}, ds.client, &virtv2.VirtualDisk{}) @@ -344,15 +345,10 @@ func (ds ObjectRefVirtualDisk) Validate(ctx context.Context, vi *virtv2.VirtualI return NewVirtualDiskNotReadyError(vi.Spec.DataSource.ObjectRef.Name) } - if len(vd.Status.AttachedToVirtualMachines) > 0 { - vmName := vd.Status.AttachedToVirtualMachines[0] - vm, err := helper.FetchObject(ctx, types.NamespacedName{Name: vmName.Name, Namespace: vd.Namespace}, ds.client, &virtv2.VirtualMachine{}) - if err != nil { - return err - } - - if vm.Status.Phase != virtv2.MachineStopped { - return NewVirtualDiskAttachedToRunningVMError(vd.Name, vmName.Name) + inUseCondition, _ := service.GetCondition(vdcondition.InUseType, vd.Status.Conditions) + if inUseCondition.Status == metav1.ConditionTrue { + if inUseCondition.Reason != vdcondition.InUseForCreateImage { + return NewVirtualDiskInUseError(vd.Name) } } diff --git a/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go b/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go index b34fa5f88..0a5e2f6ba 100644 --- a/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vi/vi_reconciler.go @@ -39,6 +39,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/watchers" "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) type Handler interface { @@ -219,7 +220,14 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return false } - return oldVD.Status.Phase != newVD.Status.Phase + oldInUseCondition, _ := service.GetCondition(vdcondition.InUseType, oldVD.Status.Conditions) + newInUseCondition, _ := service.GetCondition(vdcondition.InUseType, newVD.Status.Conditions) + + if oldVD.Status.Phase != newVD.Status.Phase || len(oldVD.Status.AttachedToVirtualMachines) != len(newVD.Status.AttachedToVirtualMachines) || oldInUseCondition.Status != newInUseCondition.Status { + return true + } + + return false }, }, ); err != nil { diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/block_device.go b/images/virtualization-artifact/pkg/controller/vm/internal/block_device.go index 4b1dab110..33fb97b90 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/block_device.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/block_device.go @@ -37,6 +37,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/vm/internal/state" "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/vmcondition" ) @@ -299,9 +300,16 @@ func (h *BlockDeviceHandler) countReadyBlockDevices(vm *virtv2.VirtualMachine, s canStartKVVM = false continue } - - if vd.Status.Phase == virtv2.DiskReady { - ready++ + readyCondition, _ := service.GetCondition(vdcondition.ReadyType, vd.Status.Conditions) + if readyCondition.Status == metav1.ConditionTrue { + inUseCondition, _ := service.GetCondition(vdcondition.InUseType, vd.Status.Conditions) + if inUseCondition.Status != metav1.ConditionTrue { + ready++ + } else { + msg := fmt.Sprintf("The virtual disk %s is being used to create a virtual image or a cluster virtual image.", vd.Name) + warnings = append(warnings, msg) + canStartKVVM = false + } } else { msg := fmt.Sprintf("virtual disk %s is waiting for the it's pvc to be bound", vd.Name) warnings = append(warnings, msg) diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/block_devices_test.go b/images/virtualization-artifact/pkg/controller/vm/internal/block_devices_test.go index 6c88718ee..ef0238c95 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/block_devices_test.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/block_devices_test.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) func TestBlockDeviceHandler(t *testing.T) { @@ -72,6 +73,18 @@ var _ = Describe("BlockDeviceHandler", func() { Status: virtv2.VirtualDiskStatus{ Phase: virtv2.DiskReady, Target: virtv2.DiskTarget{PersistentVolumeClaim: "pvc-foo"}, + Conditions: []metav1.Condition{ + { + Type: vdcondition.InUseType, + Reason: vdcondition.NotInUse, + Status: metav1.ConditionFalse, + }, + { + Type: vdcondition.ReadyType, + Reason: vdcondition.Ready, + Status: metav1.ConditionTrue, + }, + }, }, } vdBar = &virtv2.VirtualDisk{ @@ -79,6 +92,18 @@ var _ = Describe("BlockDeviceHandler", func() { Status: virtv2.VirtualDiskStatus{ Phase: virtv2.DiskReady, Target: virtv2.DiskTarget{PersistentVolumeClaim: "pvc-bar"}, + Conditions: []metav1.Condition{ + { + Type: vdcondition.InUseType, + Reason: vdcondition.NotInUse, + Status: metav1.ConditionFalse, + }, + { + Type: vdcondition.ReadyType, + Reason: vdcondition.Ready, + Status: metav1.ConditionTrue, + }, + }, }, } vm = &virtv2.VirtualMachine{ @@ -170,6 +195,18 @@ var _ = Describe("BlockDeviceHandler", func() { It("VirtualDisk's target pvc is created", func() { vdFoo.Status.Phase = virtv2.DiskProvisioning + vdFoo.Status.Conditions = []metav1.Condition{ + { + Type: vdcondition.InUseType, + Reason: vdcondition.NotInUse, + Status: metav1.ConditionFalse, + }, + { + Type: vdcondition.ReadyType, + Reason: vdcondition.Provisioning, + Status: metav1.ConditionFalse, + }, + } state := getBlockDevicesState(vi, cvi, vdFoo, vdBar) ready, canStart, warnings := h.countReadyBlockDevices(vm, state, logger) Expect(ready).To(Equal(3)) diff --git a/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go b/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go index 5654dce2f..14c8ea8ac 100644 --- a/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go @@ -40,6 +40,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/vm/internal/watcher" "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) type Handler interface { @@ -216,7 +217,15 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr if !oldOk || !newOk { return false } - return oldVd.Status.Phase != newVd.Status.Phase + + oldInUseCondition, _ := service.GetCondition(vdcondition.InUseType, oldVd.Status.Conditions) + newInUseCondition, _ := service.GetCondition(vdcondition.InUseType, newVd.Status.Conditions) + + if oldVd.Status.Phase != newVd.Status.Phase || oldInUseCondition.Status != newInUseCondition.Status { + return true + } + + return false }, }, ); err != nil { diff --git a/images/virtualization-artifact/pkg/controller/watchers/object_ref_watcher.go b/images/virtualization-artifact/pkg/controller/watchers/object_ref_watcher.go index 2f6fa9d54..3aece3437 100644 --- a/images/virtualization-artifact/pkg/controller/watchers/object_ref_watcher.go +++ b/images/virtualization-artifact/pkg/controller/watchers/object_ref_watcher.go @@ -61,8 +61,8 @@ func (w ObjectRefWatcher) Run(mgr manager.Manager, ctr controller.Controller) er source.Kind(mgr.GetCache(), w.enqueuer.GetEnqueueFrom()), handler.EnqueueRequestsFromMapFunc(w.enqueuer.EnqueueRequests), predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { return false }, - DeleteFunc: func(e event.DeleteEvent) bool { return false }, + CreateFunc: func(e event.CreateEvent) bool { return true }, + DeleteFunc: func(e event.DeleteEvent) bool { return true }, UpdateFunc: w.filter.FilterUpdateEvents, }, ) diff --git a/images/virtualization-artifact/pkg/controller/watchers/vd_enqueuer.go b/images/virtualization-artifact/pkg/controller/watchers/vd_enqueuer.go index 95bf14482..ee55d2a5a 100644 --- a/images/virtualization-artifact/pkg/controller/watchers/vd_enqueuer.go +++ b/images/virtualization-artifact/pkg/controller/watchers/vd_enqueuer.go @@ -52,6 +52,38 @@ func (w VirtualDiskRequestEnqueuer) GetEnqueueFrom() client.Object { } func (w VirtualDiskRequestEnqueuer) EnqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { + if w.enqueueFromKind == virtv2.VirtualDiskObjectRefKindVirtualImage { + 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 && vi.Spec.DataSource.ObjectRef != nil && vi.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualDiskKind { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vi.Spec.DataSource.ObjectRef.Name, + Namespace: vi.Namespace, + }, + }) + } + } else if w.enqueueFromKind == virtv2.VirtualDiskObjectRefKindClusterVirtualImage { + 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 && cvi.Spec.DataSource.ObjectRef != nil && cvi.Spec.DataSource.ObjectRef.Kind == virtv2.VirtualDiskKind { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: cvi.Spec.DataSource.ObjectRef.Name, + Namespace: cvi.Spec.DataSource.ObjectRef.Namespace, + }, + }) + } + } + var vds virtv2.VirtualDiskList err := w.client.List(ctx, &vds) if err != nil {