diff --git a/api/Taskfile.dist.yaml b/api/Taskfile.dist.yaml index 685431549..606fe04e9 100644 --- a/api/Taskfile.dist.yaml +++ b/api/Taskfile.dist.yaml @@ -52,7 +52,7 @@ tasks: - | cd ../ && docker run --rm \ -v ./:/tmp/virt ghcr.io/deckhouse/virtualization/prettier:3.2.5 \ - sh -c "cd /tmp/virt ; prettier -w \"**/*.yaml\" \"**/*.yml\"" + sh -c "cd /tmp/virt ; prettier -w \"crds/*.yaml\"" _ci:verify-gen: desc: "Check generated files are up-to-date." @@ -87,4 +87,4 @@ tasks: - go install -mod=readonly sigs.k8s.io/controller-tools/cmd/controller-gen@v{{ .CONTROLLER_GEN_VERSION }} status: - | - ls $GOPATH/bin/controller-gen + $GOPATH/bin/controller-gen --version | grep -q "v{{ .CONTROLLER_GEN_VERSION }}" diff --git a/api/core/v1alpha2/virtual_machine_block_disk_attachment.go b/api/core/v1alpha2/virtual_machine_block_device_attachment.go similarity index 94% rename from api/core/v1alpha2/virtual_machine_block_disk_attachment.go rename to api/core/v1alpha2/virtual_machine_block_device_attachment.go index 287a0bb2e..3676f4571 100644 --- a/api/core/v1alpha2/virtual_machine_block_disk_attachment.go +++ b/api/core/v1alpha2/virtual_machine_block_device_attachment.go @@ -72,6 +72,7 @@ type VirtualMachineBlockDeviceAttachmentStatus struct { type VMBDAObjectRef struct { // The type of the block device. Options are: // * `VirtualDisk` — use `VirtualDisk` as the disk. This type is always mounted in RW mode. + // * `VirtualImage` — use `VirtualImage` as the disk. Only for type PersistentVolumeClaim. Kind VMBDAObjectRefKind `json:"kind,omitempty"` // The name of block device to attach. Name string `json:"name,omitempty"` @@ -79,11 +80,12 @@ type VMBDAObjectRef struct { // VMBDAObjectRefKind defines the type of the block device. // -// +kubebuilder:validation:Enum={VirtualDisk} +// +kubebuilder:validation:Enum={VirtualDisk,VirtualImage} type VMBDAObjectRefKind string const ( - VMBDAObjectRefKindVirtualDisk VMBDAObjectRefKind = "VirtualDisk" + VMBDAObjectRefKindVirtualDisk VMBDAObjectRefKind = "VirtualDisk" + VMBDAObjectRefKindVirtualImage VMBDAObjectRefKind = "VirtualImage" ) // BlockDeviceAttachmentPhase defines current status of resource: 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 d769f5487..1eb96cbf1 100644 --- a/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go +++ b/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go @@ -2072,7 +2072,7 @@ func schema_virtualization_api_core_v1alpha2_VMBDAObjectRef(ref common.Reference Properties: map[string]spec.Schema{ "kind": { SchemaProps: spec.SchemaProps{ - Description: "The type of the block device. Options are: * `VirtualDisk` — use `VirtualDisk` as the disk. This type is always mounted in RW mode.", + Description: "The type of the block device. Options are: * `VirtualDisk` — use `VirtualDisk` as the disk. This type is always mounted in RW mode. * `VirtualImage` — use `VirtualImage` as the disk. Only for type PersistentVolumeClaim.", Type: []string{"string"}, Format: "", }, diff --git a/crds/virtualmachineblockdeviceattachments.yaml b/crds/virtualmachineblockdeviceattachments.yaml index 953465bc1..742222f6f 100644 --- a/crds/virtualmachineblockdeviceattachments.yaml +++ b/crds/virtualmachineblockdeviceattachments.yaml @@ -71,8 +71,10 @@ spec: description: |- The type of the block device. Options are: * `VirtualDisk` — use `VirtualDisk` as the disk. This type is always mounted in RW mode. + * `VirtualImage` — use `VirtualImage` as the disk. Only for type PersistentVolumeClaim. enum: - VirtualDisk + - VirtualImage type: string name: description: The name of block device to attach. diff --git a/images/virtualization-artifact/pkg/controller/service/attachment_service.go b/images/virtualization-artifact/pkg/controller/service/attachment_service.go index b9627a273..da823e83c 100644 --- a/images/virtualization-artifact/pkg/controller/service/attachment_service.go +++ b/images/virtualization-artifact/pkg/controller/service/attachment_service.go @@ -48,14 +48,14 @@ func NewAttachmentService(client Client, controllerNamespace string) *Attachment var ( ErrVolumeStatusNotReady = errors.New("hotplug is not ready") - ErrDiskIsSpecAttached = errors.New("virtual disk is already attached to the virtual machine spec") + ErrBlockDeviceIsSpecAttached = errors.New("block device is already attached to the virtual machine spec") ErrHotPlugRequestAlreadySent = errors.New("attachment request is already sent") ErrVirtualMachineWaitsForRestartApproval = errors.New("virtual machine waits for restart approval") ) -func (s AttachmentService) IsHotPlugged(vd *virtv2.VirtualDisk, vm *virtv2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { - if vd == nil { - return false, errors.New("cannot check if a nil VirtualDisk is hot plugged") +func (s AttachmentService) IsHotPlugged(ad *AttachmentDisk, vm *virtv2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { + if ad == nil { + return false, errors.New("cannot check if a empty AttachmentDisk is hot plugged") } if vm == nil { @@ -67,7 +67,7 @@ func (s AttachmentService) IsHotPlugged(vd *virtv2.VirtualDisk, vm *virtv2.Virtu } for _, vs := range kvvmi.Status.VolumeStatus { - if vs.HotplugVolume != nil && vs.Name == kvbuilder.GenerateVMDDiskName(vd.Name) { + if vs.HotplugVolume != nil && vs.Name == ad.GenerateName { if vs.Phase == virtv1.VolumeReady { return true, nil } @@ -79,9 +79,9 @@ func (s AttachmentService) IsHotPlugged(vd *virtv2.VirtualDisk, vm *virtv2.Virtu return false, nil } -func (s AttachmentService) CanHotPlug(vd *virtv2.VirtualDisk, vm *virtv2.VirtualMachine, kvvm *virtv1.VirtualMachine) (bool, error) { - if vd == nil { - return false, errors.New("cannot hot plug a nil VirtualDisk") +func (s AttachmentService) CanHotPlug(ad *AttachmentDisk, vm *virtv2.VirtualMachine, kvvm *virtv1.VirtualMachine) (bool, error) { + if ad == nil { + return false, errors.New("cannot hot plug a nil AttachmentDisk") } if vm == nil { @@ -93,12 +93,12 @@ func (s AttachmentService) CanHotPlug(vd *virtv2.VirtualDisk, vm *virtv2.Virtual } for _, bdr := range vm.Spec.BlockDeviceRefs { - if bdr.Kind == virtv2.DiskDevice && bdr.Name == vd.Name { - return false, fmt.Errorf("%w: virtual machine has a virtual disk reference, but it is not a hot-plugged volume", ErrDiskIsSpecAttached) + if bdr.Kind == ad.Kind && bdr.Name == ad.Name { + return false, fmt.Errorf("%w: virtual machine has a block device reference, but it is not a hot-plugged volume", ErrBlockDeviceIsSpecAttached) } } - name := kvbuilder.GenerateVMDDiskName(vd.Name) + name := ad.GenerateName if kvvm.Spec.Template != nil { for _, vs := range kvvm.Spec.Template.Spec.Volumes { @@ -108,7 +108,7 @@ func (s AttachmentService) CanHotPlug(vd *virtv2.VirtualDisk, vm *virtv2.Virtual } if !vs.PersistentVolumeClaim.Hotpluggable { - return false, fmt.Errorf("%w: virtual machine has a virtual disk reference, but it is not a hot-plugged volume", ErrDiskIsSpecAttached) + return false, fmt.Errorf("%w: virtual machine has a block device reference, but it is not a hot-plugged volume", ErrBlockDeviceIsSpecAttached) } return false, ErrHotPlugRequestAlreadySent @@ -129,9 +129,9 @@ func (s AttachmentService) CanHotPlug(vd *virtv2.VirtualDisk, vm *virtv2.Virtual return true, nil } -func (s AttachmentService) HotPlugDisk(ctx context.Context, vd *virtv2.VirtualDisk, vm *virtv2.VirtualMachine, kvvm *virtv1.VirtualMachine) error { - if vd == nil { - return errors.New("cannot hot plug a nil VirtualDisk") +func (s AttachmentService) HotPlugDisk(ctx context.Context, ad *AttachmentDisk, vm *virtv2.VirtualMachine, kvvm *virtv1.VirtualMachine) error { + if ad == nil { + return errors.New("cannot hot plug a nil AttachmentDisk") } if vm == nil { @@ -142,23 +142,21 @@ func (s AttachmentService) HotPlugDisk(ctx context.Context, vd *virtv2.VirtualDi return errors.New("cannot hot plug a disk into a nil KVVM") } - name := kvbuilder.GenerateVMDDiskName(vd.Name) - hotplugRequest := virtv1.AddVolumeOptions{ - Name: name, + Name: ad.GenerateName, Disk: &virtv1.Disk{ - Name: name, + Name: ad.GenerateName, DiskDevice: virtv1.DiskDevice{ Disk: &virtv1.DiskTarget{ Bus: "scsi", }, }, - Serial: vd.Name, + Serial: ad.Name, }, VolumeSource: &virtv1.HotplugVolumeSource{ PersistentVolumeClaim: &virtv1.PersistentVolumeClaimVolumeSource{ PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: vd.Status.Target.PersistentVolumeClaim, + ClaimName: ad.PVCName, }, Hotpluggable: true, }, @@ -178,13 +176,13 @@ func (s AttachmentService) HotPlugDisk(ctx context.Context, vd *virtv2.VirtualDi return nil } -func (s AttachmentService) CanUnplug(vd *virtv2.VirtualDisk, kvvm *virtv1.VirtualMachine) bool { - if vd == nil || kvvm == nil || kvvm.Spec.Template == nil { +func (s AttachmentService) CanUnplug(ad *AttachmentDisk, kvvm *virtv1.VirtualMachine) bool { + if ad == nil || kvvm == nil || kvvm.Spec.Template == nil { return false } for _, volume := range kvvm.Spec.Template.Spec.Volumes { - if kvapi.VolumeExists(volume, kvbuilder.GenerateVMDDiskName(vd.Name)) { + if kvapi.VolumeExists(volume, ad.GenerateName) { return true } } @@ -192,13 +190,13 @@ func (s AttachmentService) CanUnplug(vd *virtv2.VirtualDisk, kvvm *virtv1.Virtua return false } -func (s AttachmentService) UnplugDisk(ctx context.Context, vd *virtv2.VirtualDisk, kvvm *virtv1.VirtualMachine) error { - if vd == nil || kvvm == nil { +func (s AttachmentService) UnplugDisk(ctx context.Context, ad *AttachmentDisk, kvvm *virtv1.VirtualMachine) error { + if ad == nil || kvvm == nil { return nil } unplugRequest := virtv1.RemoveVolumeOptions{ - Name: kvbuilder.GenerateVMDDiskName(vd.Name), + Name: ad.GenerateName, } kv, err := kubevirt.New(ctx, s.client, s.controllerNamespace) @@ -272,8 +270,12 @@ func (s AttachmentService) GetVirtualDisk(ctx context.Context, name, namespace s return object.FetchObject(ctx, types.NamespacedName{Namespace: namespace, Name: name}, s.client, &virtv2.VirtualDisk{}) } -func (s AttachmentService) GetPersistentVolumeClaim(ctx context.Context, vd *virtv2.VirtualDisk) (*corev1.PersistentVolumeClaim, error) { - return object.FetchObject(ctx, types.NamespacedName{Namespace: vd.Namespace, Name: vd.Status.Target.PersistentVolumeClaim}, s.client, &corev1.PersistentVolumeClaim{}) +func (s AttachmentService) GetVirtualImage(ctx context.Context, name, namespace string) (*virtv2.VirtualImage, error) { + return object.FetchObject(ctx, types.NamespacedName{Namespace: namespace, Name: name}, s.client, &virtv2.VirtualImage{}) +} + +func (s AttachmentService) GetPersistentVolumeClaim(ctx context.Context, ad *AttachmentDisk) (*corev1.PersistentVolumeClaim, error) { + return object.FetchObject(ctx, types.NamespacedName{Namespace: ad.Namespace, Name: ad.PVCName}, s.client, &corev1.PersistentVolumeClaim{}) } func (s AttachmentService) GetVirtualMachine(ctx context.Context, name, namespace string) (*virtv2.VirtualMachine, error) { @@ -291,3 +293,31 @@ func (s AttachmentService) GetKVVMI(ctx context.Context, vm *virtv2.VirtualMachi func isSameBlockDeviceRefs(a, b virtv2.VMBDAObjectRef) bool { return a.Kind == b.Kind && a.Name == b.Name } + +type AttachmentDisk struct { + Kind virtv2.BlockDeviceKind + Name string + Namespace string + GenerateName string + PVCName string +} + +func NewAttachmentDiskFromVirtualDisk(vd *virtv2.VirtualDisk) *AttachmentDisk { + return &AttachmentDisk{ + Kind: virtv2.DiskDevice, + Name: vd.GetName(), + Namespace: vd.GetNamespace(), + GenerateName: kvbuilder.GenerateVMDDiskName(vd.GetName()), + PVCName: vd.Status.Target.PersistentVolumeClaim, + } +} + +func NewAttachmentDiskFromVirtualImage(vi *virtv2.VirtualImage) *AttachmentDisk { + return &AttachmentDisk{ + Kind: virtv2.ImageDevice, + Name: vi.GetName(), + Namespace: vi.GetNamespace(), + GenerateName: kvbuilder.GenerateVMIDiskName(vi.GetName()), + PVCName: vi.Status.Target.PersistentVolumeClaim, + } +} diff --git a/images/virtualization-artifact/pkg/controller/vmbda/internal/block_device_ready.go b/images/virtualization-artifact/pkg/controller/vmbda/internal/block_device_ready.go index c32aaa808..d8f6c6ae5 100644 --- a/images/virtualization-artifact/pkg/controller/vmbda/internal/block_device_ready.go +++ b/images/virtualization-artifact/pkg/controller/vmbda/internal/block_device_ready.go @@ -110,7 +110,8 @@ func (h BlockDeviceReadyHandler) Handle(ctx context.Context, vmbda *virtv2.Virtu return reconcile.Result{}, nil } - pvc, err := h.attachment.GetPersistentVolumeClaim(ctx, vd) + ad := service.NewAttachmentDiskFromVirtualDisk(vd) + pvc, err := h.attachment.GetPersistentVolumeClaim(ctx, ad) if err != nil { return reconcile.Result{}, err } @@ -131,6 +132,71 @@ func (h BlockDeviceReadyHandler) Handle(ctx context.Context, vmbda *virtv2.Virtu return reconcile.Result{}, nil } + cb.Status(metav1.ConditionTrue).Reason(vmbdacondition.BlockDeviceReady) + return reconcile.Result{}, nil + case virtv2.VMBDAObjectRefKindVirtualImage: + viKey := types.NamespacedName{ + Name: vmbda.Spec.BlockDeviceRef.Name, + Namespace: vmbda.Namespace, + } + + vi, err := h.attachment.GetVirtualImage(ctx, viKey.Name, viKey.Namespace) + if err != nil { + return reconcile.Result{}, err + } + + if vi == nil { + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message(fmt.Sprintf("VirtualImage %q not found.", viKey.String())) + return reconcile.Result{}, nil + } + if vi.Generation != vi.Status.ObservedGeneration { + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message(fmt.Sprintf("Waiting for the VirtualImage %q to be observed in its latest state generation.", viKey.String())) + return reconcile.Result{}, nil + } + + if vi.Status.Phase != virtv2.ImageReady { + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message(fmt.Sprintf("VirtualImage %q is not ready to be attached to the virtual machine: waiting for the VirtualImage to be ready for attachment.", viKey.String())) + return reconcile.Result{}, nil + } + if vi.Status.Target.PersistentVolumeClaim == "" { + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message("Waiting until VirtualImage has associated PersistentVolumeClaim name.") + return reconcile.Result{}, nil + } + + ad := service.NewAttachmentDiskFromVirtualImage(vi) + pvc, err := h.attachment.GetPersistentVolumeClaim(ctx, ad) + if err != nil { + return reconcile.Result{}, err + } + + if pvc == nil { + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message(fmt.Sprintf("Underlying PersistentVolumeClaim %q not found.", vi.Status.Target.PersistentVolumeClaim)) + return reconcile.Result{}, nil + } + + if vi.Status.Phase == virtv2.ImageReady && pvc.Status.Phase != corev1.ClaimBound { + cb. + Status(metav1.ConditionFalse). + Reason(vmbdacondition.BlockDeviceNotReady). + Message(fmt.Sprintf("Underlying PersistentVolumeClaim %q not bound.", vi.Status.Target.PersistentVolumeClaim)) + return reconcile.Result{}, nil + } + cb.Status(metav1.ConditionTrue).Reason(vmbdacondition.BlockDeviceReady) return reconcile.Result{}, nil default: diff --git a/images/virtualization-artifact/pkg/controller/vmbda/internal/deletion.go b/images/virtualization-artifact/pkg/controller/vmbda/internal/deletion.go index fec026765..9a6e0477e 100644 --- a/images/virtualization-artifact/pkg/controller/vmbda/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/vmbda/internal/deletion.go @@ -34,14 +34,14 @@ func NewDeletionHandler() *DeletionHandler { return &DeletionHandler{} } -func (h DeletionHandler) Handle(ctx context.Context, vd *virtv2.VirtualMachineBlockDeviceAttachment) (reconcile.Result, error) { +func (h DeletionHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachineBlockDeviceAttachment) (reconcile.Result, error) { log := logger.FromContext(ctx).With(logger.SlogHandler(deletionHandlerName)) - if vd.DeletionTimestamp != nil { + if vmbda.DeletionTimestamp != nil { log.Info("Deletion observed: remove cleanup finalizer from VirtualMachineBlockDeviceAttachment") - controllerutil.RemoveFinalizer(vd, virtv2.FinalizerVMBDACleanup) + controllerutil.RemoveFinalizer(vmbda, virtv2.FinalizerVMBDACleanup) return reconcile.Result{}, nil } - controllerutil.AddFinalizer(vd, virtv2.FinalizerVMBDACleanup) + controllerutil.AddFinalizer(vmbda, virtv2.FinalizerVMBDACleanup) return reconcile.Result{}, nil } diff --git a/images/virtualization-artifact/pkg/controller/vmbda/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vmbda/internal/life_cycle.go index 7656b090b..451956dc5 100644 --- a/images/virtualization-artifact/pkg/controller/vmbda/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmbda/internal/life_cycle.go @@ -54,9 +54,24 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi cb.Status(metav1.ConditionUnknown).Reason(conditions.ReasonUnknown) } - vd, err := h.attacher.GetVirtualDisk(ctx, vmbda.Spec.BlockDeviceRef.Name, vmbda.Namespace) - if err != nil { - return reconcile.Result{}, err + var ad *service.AttachmentDisk + switch vmbda.Spec.BlockDeviceRef.Kind { + case virtv2.VMBDAObjectRefKindVirtualDisk: + vd, err := h.attacher.GetVirtualDisk(ctx, vmbda.Spec.BlockDeviceRef.Name, vmbda.Namespace) + if err != nil { + return reconcile.Result{}, err + } + if vd != nil { + ad = service.NewAttachmentDiskFromVirtualDisk(vd) + } + case virtv2.VMBDAObjectRefKindVirtualImage: + vi, err := h.attacher.GetVirtualImage(ctx, vmbda.Spec.BlockDeviceRef.Name, vmbda.Namespace) + if err != nil { + return reconcile.Result{}, err + } + if vi != nil { + ad = service.NewAttachmentDiskFromVirtualImage(vi) + } } vm, err := h.attacher.GetVirtualMachine(ctx, vmbda.Spec.VirtualMachineName, vmbda.Namespace) @@ -77,8 +92,8 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi case virtv2.BlockDeviceAttachmentPhasePending, virtv2.BlockDeviceAttachmentPhaseInProgress, virtv2.BlockDeviceAttachmentPhaseAttached: - if h.attacher.CanUnplug(vd, kvvm) { - err = h.attacher.UnplugDisk(ctx, vd, kvvm) + if h.attacher.CanUnplug(ad, kvvm) { + err = h.attacher.UnplugDisk(ctx, ad, kvvm) if err != nil { return reconcile.Result{}, err } @@ -138,12 +153,12 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi return reconcile.Result{}, nil } - if vd == nil { + if ad == nil { vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhasePending cb. Status(metav1.ConditionFalse). Reason(vmbdacondition.NotAttached). - Message(fmt.Sprintf("VirtualDisk %q not found.", vmbda.Spec.BlockDeviceRef.Name)) + Message(fmt.Sprintf("AttachmentDisk %q not found.", vmbda.Spec.BlockDeviceRef.Name)) return reconcile.Result{}, nil } @@ -179,10 +194,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi return reconcile.Result{}, nil } - log = log.With("vmName", vm.Name, "vdName", vd.Name) + log = log.With("vmName", vm.Name, "attachmentDiskName", ad.Name) log.Info("Check if hot plug is completed and disk is attached") - isHotPlugged, err := h.attacher.IsHotPlugged(vd, vm, kvvmi) + isHotPlugged, err := h.attacher.IsHotPlugged(ad, vm, kvvmi) if err != nil { if errors.Is(err, service.ErrVolumeStatusNotReady) { vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhaseInProgress @@ -207,14 +222,14 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi return reconcile.Result{}, nil } - _, err = h.attacher.CanHotPlug(vd, vm, kvvm) + _, err = h.attacher.CanHotPlug(ad, vm, kvvm) blockDeviceLimitCondition, _ := conditions.GetCondition(vmbdacondition.DiskAttachmentCapacityAvailableType, vmbda.Status.Conditions) switch { case err == nil && blockDeviceLimitCondition.Status == metav1.ConditionTrue: log.Info("Send attachment request") - err = h.attacher.HotPlugDisk(ctx, vd, vm, kvvm) + err = h.attacher.HotPlugDisk(ctx, ad, vm, kvvm) if err != nil { return reconcile.Result{}, err } @@ -225,7 +240,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmbda *virtv2.VirtualMachi Reason(vmbdacondition.AttachmentRequestSent). Message("Attachment request has sent: attachment is in progress.") return reconcile.Result{}, nil - case errors.Is(err, service.ErrDiskIsSpecAttached): + case errors.Is(err, service.ErrBlockDeviceIsSpecAttached): log.Info("VirtualDisk is already attached to the virtual machine spec") vmbda.Status.Phase = virtv2.BlockDeviceAttachmentPhaseFailed diff --git a/images/virtualization-artifact/pkg/controller/vmbda/internal/validators/image_validator.go b/images/virtualization-artifact/pkg/controller/vmbda/internal/validators/image_validator.go new file mode 100644 index 000000000..49d3a1d33 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmbda/internal/validators/image_validator.go @@ -0,0 +1,43 @@ +package validators + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type ImageValidator struct { + service *service.AttachmentService +} + +func NewImageValidator(service *service.AttachmentService) *ImageValidator { + return &ImageValidator{ + service: service, + } +} + +func (v *ImageValidator) ValidateCreate(ctx context.Context, vmbda *virtv2.VirtualMachineBlockDeviceAttachment) (admission.Warnings, error) { + return v.validateImage(ctx, vmbda) +} + +func (v *ImageValidator) ValidateUpdate(_ context.Context, _, _ *virtv2.VirtualMachineBlockDeviceAttachment) (admission.Warnings, error) { + return nil, nil +} + +func (v *ImageValidator) validateImage(ctx context.Context, vmbda *virtv2.VirtualMachineBlockDeviceAttachment) (admission.Warnings, error) { + vi, err := v.service.GetVirtualImage(ctx, vmbda.GetName(), vmbda.GetNamespace()) + if err != nil { + return nil, fmt.Errorf("failed to get virtual image: %w", err) + } + if vi == nil { + return admission.Warnings{"VirtualImage not found"}, nil + } + if vi.Spec.Storage != virtv2.StoragePersistentVolumeClaim { + return nil, fmt.Errorf("the virtual image type is not supported. The acceptable type is only %q", virtv2.StoragePersistentVolumeClaim) + } + return nil, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vmbda/vmbda_webhook.go b/images/virtualization-artifact/pkg/controller/vmbda/vmbda_webhook.go index 6f8432aa3..d92a6a46a 100644 --- a/images/virtualization-artifact/pkg/controller/vmbda/vmbda_webhook.go +++ b/images/virtualization-artifact/pkg/controller/vmbda/vmbda_webhook.go @@ -24,6 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/controller/vmbda/internal/validators" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -46,6 +47,7 @@ func NewValidator(attachmentService *service.AttachmentService, service *service validators.NewSpecMutateValidator(), validators.NewAttachmentConflictValidator(attachmentService, log), validators.NewVMConnectLimiterValidator(service, log), + validators.NewImageValidator(attachmentService), }, } }