Skip to content

Commit

Permalink
feat(vd): add binding mode
Browse files Browse the repository at this point in the history
Add binding mode with WaitForFirstConsumer and Immediate options for virtual disks.

New reason for VM Running condition: VirtualMachineInternalError
New reason for VM PodStarted condition: PodNotStarted
New reason for VM BlockDevicesReady condition: BlockDevicesWaitingForProvisioning
New reason for VD Ready condition: WaitForFirstConsumer

BlockDevicesReady: false no longer blocks VM creation (if the issue is with disks in Provisioning or WaitForFirstConsumer, the VM is created regardless).

WaitForFirstConsumer disk creates RWO pvc to prevent virtual machine migrations from failing.
New condition: Migratable (if the VM cannot be migrated, this will be indicated here)

Signed-off-by: Isteb4k <[email protected]>
  • Loading branch information
Isteb4k authored Aug 8, 2024
1 parent 7f62061 commit da65e56
Show file tree
Hide file tree
Showing 30 changed files with 497 additions and 250 deletions.
2 changes: 0 additions & 2 deletions api/core/v1alpha2/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ const (
// ReasonErrVmNotSynced is event reason that vm is not synced.
ReasonErrVmNotSynced = "VirtualMachineNotSynced"

// ReasonVMDegraded is event reason that vm is degraded.
ReasonVMDegraded = "VirtualmachineDegraded"
// ReasonErrVMOPNotPermitted is event reason that vmop is not permitted.
ReasonErrVMOPNotPermitted = "VirtualMachineOperationNotPermitted"

Expand Down
2 changes: 2 additions & 0 deletions api/core/v1alpha2/vdcondition/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ const (
Provisioning ReadyReason = "Provisioning"
// ProvisioningNotStarted indicates that the provisioning process has not started yet.
ProvisioningNotStarted ReadyReason = "ProvisioningNotStarted"
// WaitForFirstConsumer indicates that the provisioning has been suspended: a created and scheduled virtual machine is awaited.
WaitForFirstConsumer ReadyReason = "WaitForFirstConsumer"
// ProvisioningFailed indicates that the provisioning process has failed.
ProvisioningFailed ReadyReason = "ProvisioningFailed"
// Ready indicates that the import process is complete and the `VirtualDisk` is ready for use.
Expand Down
25 changes: 17 additions & 8 deletions api/core/v1alpha2/virtual_disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,18 @@ type VirtualDisk struct {
}

type VirtualDiskSpec struct {
BindingMode *VirtualDiskBindingMode `json:"bindingMode,omitempty"`
DataSource *VirtualDiskDataSource `json:"dataSource,omitempty"`
PersistentVolumeClaim VirtualDiskPersistentVolumeClaim `json:"persistentVolumeClaim"`
}

type VirtualDiskBindingMode string

const (
VirtualDiskBindingModeWaitForFirstConsumer VirtualDiskBindingMode = "WaitForFirstConsumer"
VirtualDiskBindingModeImmediate VirtualDiskBindingMode = "Immediate"
)

type VirtualDiskStatus struct {
DownloadSpeed *StatusSpeed `json:"downloadSpeed,omitempty"`
Capacity string `json:"capacity,omitempty"`
Expand Down Expand Up @@ -110,12 +118,13 @@ type VirtualDiskList struct {
type DiskPhase string

const (
DiskPending DiskPhase = "Pending"
DiskWaitForUserUpload DiskPhase = "WaitForUserUpload"
DiskProvisioning DiskPhase = "Provisioning"
DiskFailed DiskPhase = "Failed"
DiskLost DiskPhase = "Lost"
DiskReady DiskPhase = "Ready"
DiskResizing DiskPhase = "Resizing"
DiskTerminating DiskPhase = "Terminating"
DiskPending DiskPhase = "Pending"
DiskWaitForUserUpload DiskPhase = "WaitForUserUpload"
DiskWaitForFirstConsumer DiskPhase = "WaitForFirstConsumer"
DiskProvisioning DiskPhase = "Provisioning"
DiskFailed DiskPhase = "Failed"
DiskLost DiskPhase = "Lost"
DiskReady DiskPhase = "Ready"
DiskResizing DiskPhase = "Resizing"
DiskTerminating DiskPhase = "Terminating"
)
1 change: 0 additions & 1 deletion api/core/v1alpha2/virtual_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ const (
MachineStarting MachinePhase = "Starting"
MachineMigrating MachinePhase = "Migrating"
MachinePause MachinePhase = "Pause"
MachineDegraded MachinePhase = "Degraded"
)

// VirtualMachineList contains a list of VirtualMachine
Expand Down
24 changes: 16 additions & 8 deletions api/core/v1alpha2/vmcondition/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
TypeBlockDevicesReady Type = "BlockDevicesReady"
TypeRunning Type = "Running"
TypeMigrating Type = "Migrating"
TypeMigratable Type = "Migratable"
TypePodStarted Type = "PodStarted"
TypeProvisioningReady Type = "ProvisioningReady"
TypeAgentReady Type = "AgentReady"
Expand All @@ -53,8 +54,9 @@ const (
ReasonIPAddressNotAssigned Reason = "VirtualMachineIPAddressNotAssigned"
ReasonIPAddressNotAvailable Reason = "VirtualMachineIPAddressNotAvailable"

ReasonBlockDevicesReady Reason = "BlockDevicesReady"
ReasonBlockDevicesNotReady Reason = "BlockDevicesNotReady"
ReasonBlockDevicesReady Reason = "BlockDevicesReady"
ReasonBlockDevicesWaitingForProvisioning Reason = "BlockDevicesWaitingForProvisioning"
ReasonBlockDevicesNotReady Reason = "BlockDevicesNotReady"

ReasonProvisioningReady Reason = "ProvisioningReady"
ReasonProvisioningNotReady Reason = "ProvisioningNotReady"
Expand All @@ -65,10 +67,16 @@ const (
ReasonRestartAwaitingChangesNotExist Reason = "RestartAwaitingChangesNotExist"
ReasonRestartNoNeed Reason = "NoNeedRestart"

ReasonPodNodFound Reason = "PodNotFound"
ReasonPodStarted Reason = "PodStarted"
ReasonVmIsMigrating Reason = "VirtualMachineMigrating"
ReasonVmIsNotMigrating Reason = "VirtualMachineNotMigrating"
ReasonVmIsNotRunning Reason = "VirtualMachineNotRunning"
ReasonVmIsRunning Reason = "VirtualMachineRunning"
ReasonPodStarted Reason = "PodStarted"
ReasonPodNotFound Reason = "PodNotFound"
ReasonPodNotStarted Reason = "PodNotStarted"

ReasonMigratable Reason = "VirtualMachineMigratable"
ReasonNotMigratable Reason = "VirtualMachineNotMigratable"

ReasonVmIsMigrating Reason = "VirtualMachineMigrating"
ReasonVmIsNotMigrating Reason = "VirtualMachineNotMigrating"
ReasonVmIsNotRunning Reason = "VirtualMachineNotRunning"
ReasonVmIsRunning Reason = "VirtualMachineRunning"
ReasonInternalVirtualMachineError Reason = "InternalVirtualMachineError"
)
5 changes: 5 additions & 0 deletions api/core/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions crds/doc-ru-virtualdisk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ spec:
properties:
spec:
properties:
bindingMode:
description: |
Типы режимов привязки диска:
* `WaitForFirstConsumer` — отложить создание диска до тех пор, пока использующая этот диск виртуальная машина не будет назначена на узел.
* `Immediate` — начать создание диска, не дожидаясь создания виртуальной машины.
dataSource:
description: |
Тип источника, из которого будет создан диск. Если источник (.spec.dataSource) отсутствует, то будет создан пустой диск.
Expand Down Expand Up @@ -156,6 +162,7 @@ spec:
* Pending — ресурс был создан и находится в очереди ожидания.
* Provisioning — идет процесс создания ресурса (копирование/загрузка/создание диска).
* WaitForUserUpload — ожидание загрузки образа пользователем. Путь для загрузки образа указывается в `.status.uploadCommand`.
* WaitForFirstConsumer - ожидание пока использующая этот диск виртуальная машина не будет назначена на узел.
* Ready — ресурс создан и готов к использованию.
* Resizing — идет процесс увеличения размера диска.
* Failed — при создании ресурса возникла проблема.
Expand Down
1 change: 0 additions & 1 deletion crds/doc-ru-virtualmachine.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,6 @@ spec:
* `Scheduling` — ВМ запланирована для размещения на узлах кластера.
* `Pending` — осуществляется процесс запуска ВМ.
* `Running` — ВМ запущенна.
* `Degraded` — произошла ошибка в процессе запуска или работы ВМ.
* `Terminating` — в настоящий момент ВМ завершает свою работу.
* `Stopped` — ВМ остановлена.
restartAwaitingChanges:
Expand Down
12 changes: 12 additions & 0 deletions crds/virtualdisk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ spec:
required:
- persistentVolumeClaim
properties:
bindingMode:
type: string
enum:
- "WaitForFirstConsumer"
- "Immediate"
description: |
The types of disk binding modes are:
* `WaitForFirstConsumer` — delay the provisioning of a disk until a `VirtualMachine` that uses the disk is scheduled.
* `Immediate` — start creating the disk without waiting for the scheduling of the virtual machine.
persistentVolumeClaim:
type: object
description: |
Expand Down Expand Up @@ -264,6 +274,7 @@ spec:
* Pending - The resource has been created and is on a waiting queue.
* Provisioning - The process of resource creation (copying/downloading/filling the PVC with data/extending PVC) is in progress.
* WaitForUserUpload - Waiting for the user to upload the image. The endpoint to upload the image is specified in `.status.uploadCommand`.
* WaitForFirstConsumer - Waiting for the virtual machine that uses the disk is scheduled.
* Ready - The resource is created and ready to use.
* Resizing — The process of resource resizing is in progress.
* Failed - There was a problem when creating a resource.
Expand All @@ -274,6 +285,7 @@ spec:
"Pending",
"Provisioning",
"WaitForUserUpload",
"WaitForFirstConsumer",
"Ready",
"Resizing",
"Failed",
Expand Down
3 changes: 0 additions & 3 deletions crds/virtualmachine.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -936,13 +936,11 @@ spec:
The current phase of the virtual machine:
* `Pending` - The process of starting the VM is in progress.
* `Running` - VM is running.
* `Degraded` - An error occurred during the startup process or while the VM is running.
* `Terminating` - The VM is currently in the process of shutting down.
* `Stopped` - The VM is stopped.
enum:
- "Pending"
- "Running"
- "Degraded"
- "Terminating"
- "Stopped"
- "Stopping"
Expand Down Expand Up @@ -1057,7 +1055,6 @@ spec:
enum:
- "Pending"
- "Running"
- "Degraded"
- "Terminating"
- "Stopped"
- "Stopping"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
diff --git a/pkg/controller/util.go b/pkg/controller/util.go
index 6828590fc..7c022d7e0 100644
--- a/pkg/controller/util.go
+++ b/pkg/controller/util.go
@@ -36,6 +36,7 @@ import (
"k8s.io/klog/v2"

cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
+
"kubevirt.io/containerized-data-importer/pkg/common"

cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
@@ -196,28 +197,12 @@ func GetFilesystemOverhead(ctx context.Context, client client.Client, pvc *v1.Pe
return cc.GetFilesystemOverheadForStorageClass(ctx, client, pvc.Spec.StorageClassName)
}

-// GetScratchPvcStorageClass tries to determine which storage class to use for use with a scratch persistent
-// volume claim. The order of preference is the following:
-// 1. Defined value in CDI Config field scratchSpaceStorageClass.
-// 2. If 1 is not available, use the storage class name of the original pvc that will own the scratch pvc.
-// 3. If none of those are available, return blank.
-func GetScratchPvcStorageClass(client client.Client, pvc *v1.PersistentVolumeClaim) string {
- config := &cdiv1.CDIConfig{}
- if err := client.Get(context.TODO(), types.NamespacedName{Name: common.ConfigName}, config); err != nil {
- return ""
- }
- storageClassName := config.Status.ScratchSpaceStorageClass
- if storageClassName == "" {
- // Unable to determine scratch storage class, attempt to read the storage class from the pvc.
- if pvc.Spec.StorageClassName != nil {
- storageClassName = *pvc.Spec.StorageClassName
- if storageClassName != "" {
- return storageClassName
- }
- }
- } else {
- return storageClassName
+// GetScratchPvcStorageClass returns the storage class name for the scratch pvc from the original pvc that will own the scratch pvc, or set it to an empty value if not available.
+func GetScratchPvcStorageClass(_ client.Client, pvc *v1.PersistentVolumeClaim) string {
+ if pvc.Spec.StorageClassName != nil {
+ return *pvc.Spec.StorageClassName
}
+
return ""
}

diff --git a/pkg/controller/util_test.go b/pkg/controller/util_test.go
index 8423107b8..87898b890 100644
--- a/pkg/controller/util_test.go
+++ b/pkg/controller/util_test.go
@@ -16,6 +16,7 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log"

cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
+
"kubevirt.io/containerized-data-importer/pkg/common"
. "kubevirt.io/containerized-data-importer/pkg/controller/common"
"kubevirt.io/containerized-data-importer/pkg/util/cert"
@@ -60,38 +61,15 @@ var _ = Describe("CheckIfLabelExists", func() {
})

var _ = Describe("GetScratchPVCStorageClass", func() {
- It("Should return default storage class from status in CDIConfig", func() {
- storageClassName := "test3"
- client := CreateClient(CreateStorageClass("test1", nil), CreateStorageClass("test2", nil), CreateStorageClass("test3", map[string]string{
- AnnDefaultStorageClass: "true",
- }), createCDIConfigWithStorageClass(common.ConfigName, storageClassName))
- pvc := CreatePvc("test", "test", nil, nil)
- Expect(GetScratchPvcStorageClass(client, pvc)).To(Equal(storageClassName))
- })
-
- It("Should return default storage class from status in CDIConfig", func() {
- storageClassName := "test1"
- config := createCDIConfigWithStorageClass(common.ConfigName, storageClassName)
- config.Spec.ScratchSpaceStorageClass = &storageClassName
- client := CreateClient(CreateStorageClass("test1", nil), CreateStorageClass("test2", nil), CreateStorageClass("test3", map[string]string{
- AnnDefaultStorageClass: "true",
- }), config)
- pvc := CreatePvc("test", "test", nil, nil)
- Expect(GetScratchPvcStorageClass(client, pvc)).To(Equal(storageClassName))
- })
-
It("Should return storage class from pvc", func() {
storageClassName := "storageClass"
- client := CreateClient(createCDIConfigWithStorageClass(common.ConfigName, ""))
pvc := CreatePvcInStorageClass("test", "test", &storageClassName, nil, nil, v1.ClaimBound)
- Expect(GetScratchPvcStorageClass(client, pvc)).To(Equal(storageClassName))
+ Expect(GetScratchPvcStorageClass(nil, pvc)).To(Equal(storageClassName))
})

It("Should return blank if CDIConfig not there", func() {
- storageClassName := "storageClass"
- client := CreateClient()
- pvc := CreatePvcInStorageClass("test", "test", &storageClassName, nil, nil, v1.ClaimBound)
- Expect(GetScratchPvcStorageClass(client, pvc)).To(Equal(""))
+ pvc := CreatePvcInStorageClass("test", "test", nil, nil, nil, v1.ClaimBound)
+ Expect(GetScratchPvcStorageClass(nil, pvc)).To(Equal(""))
})
})

4 changes: 4 additions & 0 deletions images/cdi-artifact/patches/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ by DVP, but conflicts with original CDI.
#### `010-stop-managing-datavolume-crd.patch`

Do not manage DataVolume CRD with cdi-operator. Module will install this CRD using Helm.

#### `011-change-storage-class-for-scratch-pvc.patch`

Set the storage class name for the scratch pvc from the original pvc that will own the scratch pvc, or set it to an empty value if not available.
14 changes: 12 additions & 2 deletions images/virtualization-artifact/pkg/controller/kvbuilder/dv.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package kvbuilder

import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -44,8 +45,7 @@ func NewDV(name types.NamespacedName) *DV {
Namespace: name.Namespace,
Name: name.Name,
Annotations: map[string]string{
"cdi.kubevirt.io/storage.deleteAfterCompletion": "false",
"cdi.kubevirt.io/storage.bind.immediate.requested": "true",
"cdi.kubevirt.io/storage.deleteAfterCompletion": "false",
},
},
Spec: cdiv1.DataVolumeSpec{
Expand All @@ -56,6 +56,16 @@ func NewDV(name types.NamespacedName) *DV {
}
}

func (b *DV) SetAccessMode(accessMode corev1.PersistentVolumeAccessMode) {
b.Resource.Spec.PVC.AccessModes = []corev1.PersistentVolumeAccessMode{accessMode}
}

func (b *DV) SetBindingMode(wffc bool) {
if !wffc {
b.AddAnnotation("cdi.kubevirt.io/storage.bind.immediate.requested", "true")
}
}

func (b *DV) SetPVC(storageClassName *string, size resource.Quantity) {
b.Resource.Spec.PVC = pvc.CreateSpecForDataVolume(storageClassName, size)
}
Expand Down
Loading

0 comments on commit da65e56

Please sign in to comment.