From 6c6ad79971c41e0b8f411ba7eeb06c82a3a8a72a Mon Sep 17 00:00:00 2001 From: Arunesh Pandey Date: Fri, 21 Nov 2025 14:15:20 -0800 Subject: [PATCH] Populate diskMode, provisioning mode and Scrubing of disks in VM status This change modifies VM status to include additional properties of a virtual disk such as: - Disk mode: this includes persistent, independent_persistent, independent_nonpersistent, nopersistent, undoable and append - Provisioning mode: Thin vs thick provisioned - Scrubing: Lazy vs eager zero-ing Diskmode is already available in the Status but not being populated. This change not sets that property. A new property called provisioningMode is introduced to handle zero-ing of disks. Testing Done: - Tested in a real env. Here's the status: ``` root@42177b6e043ae6c535c54e0d5c038e1f [ ~ ]# k get vm -n prod-vmsvc-reco-vds-1309 parunesh-vm -o=jsonpath={.status.volumes} | jq [ { "attached": true, "diskMode": "Persistent", "diskUUID": "6000C292-d460-6d94-62cf-2c4c9e7c7bfd", "limit": "10Mi", "name": "parunesh-vol", "provisioningMode": "Thin", "requested": "10Mi", "sharingMode": "None", "type": "Managed", "used": "939" }, { "attached": true, "diskMode": "Persistent", "diskUUID": "6000C293-fd2c-c9ba-14b7-ffc2f5ea2ea7", "limit": "10Gi", "name": "disk-ee05ebc8", "provisioningMode": "Thin", "requested": "10Gi", "sharingMode": "None", "type": "Classic", "used": "2948596348" } ] ``` --- api/v1alpha1/zz_generated.conversion.go | 1 + api/v1alpha2/zz_generated.conversion.go | 1 + api/v1alpha3/zz_generated.conversion.go | 1 + api/v1alpha4/zz_generated.conversion.go | 1 + api/v1alpha5/virtualmachine_storage_types.go | 7 + ...vmoperator.vmware.com_virtualmachines.yaml | 10 + .../volumebatch/volumebatch_controller.go | 2 + .../volumebatch_controller_intg_test.go | 52 ++--- .../vsphere/vmlifecycle/update_status.go | 92 ++++++++ .../vsphere/vmlifecycle/update_status_test.go | 221 ++++++++++++++++++ pkg/util/devices.go | 17 +- 11 files changed, 376 insertions(+), 29 deletions(-) diff --git a/api/v1alpha1/zz_generated.conversion.go b/api/v1alpha1/zz_generated.conversion.go index 14f67888e..9695d533b 100644 --- a/api/v1alpha1/zz_generated.conversion.go +++ b/api/v1alpha1/zz_generated.conversion.go @@ -2292,6 +2292,7 @@ func autoConvert_v1alpha5_VirtualMachineVolumeStatus_To_v1alpha1_VirtualMachineV // WARNING: in.Type requires manual conversion: does not exist in peer-type // WARNING: in.DiskMode requires manual conversion: does not exist in peer-type // WARNING: in.SharingMode requires manual conversion: does not exist in peer-type + // WARNING: in.ProvisioningMode requires manual conversion: does not exist in peer-type // WARNING: in.Crypto requires manual conversion: does not exist in peer-type // WARNING: in.Limit requires manual conversion: does not exist in peer-type // WARNING: in.Requested requires manual conversion: does not exist in peer-type diff --git a/api/v1alpha2/zz_generated.conversion.go b/api/v1alpha2/zz_generated.conversion.go index 95c8bfaf4..cbfd69367 100644 --- a/api/v1alpha2/zz_generated.conversion.go +++ b/api/v1alpha2/zz_generated.conversion.go @@ -4012,6 +4012,7 @@ func autoConvert_v1alpha5_VirtualMachineVolumeStatus_To_v1alpha2_VirtualMachineV // WARNING: in.Type requires manual conversion: does not exist in peer-type // WARNING: in.DiskMode requires manual conversion: does not exist in peer-type // WARNING: in.SharingMode requires manual conversion: does not exist in peer-type + // WARNING: in.ProvisioningMode requires manual conversion: does not exist in peer-type // WARNING: in.Crypto requires manual conversion: does not exist in peer-type // WARNING: in.Limit requires manual conversion: does not exist in peer-type // WARNING: in.Requested requires manual conversion: does not exist in peer-type diff --git a/api/v1alpha3/zz_generated.conversion.go b/api/v1alpha3/zz_generated.conversion.go index 531564e78..d35af8048 100644 --- a/api/v1alpha3/zz_generated.conversion.go +++ b/api/v1alpha3/zz_generated.conversion.go @@ -4673,6 +4673,7 @@ func autoConvert_v1alpha5_VirtualMachineVolumeStatus_To_v1alpha3_VirtualMachineV out.Type = VirtualMachineVolumeType(in.Type) // WARNING: in.DiskMode requires manual conversion: does not exist in peer-type // WARNING: in.SharingMode requires manual conversion: does not exist in peer-type + // WARNING: in.ProvisioningMode requires manual conversion: does not exist in peer-type out.Crypto = (*VirtualMachineVolumeCryptoStatus)(unsafe.Pointer(in.Crypto)) out.Limit = (*resource.Quantity)(unsafe.Pointer(in.Limit)) // WARNING: in.Requested requires manual conversion: does not exist in peer-type diff --git a/api/v1alpha4/zz_generated.conversion.go b/api/v1alpha4/zz_generated.conversion.go index 8c69df43f..955b1c861 100644 --- a/api/v1alpha4/zz_generated.conversion.go +++ b/api/v1alpha4/zz_generated.conversion.go @@ -4911,6 +4911,7 @@ func autoConvert_v1alpha5_VirtualMachineVolumeStatus_To_v1alpha4_VirtualMachineV out.Type = VirtualMachineVolumeType(in.Type) // WARNING: in.DiskMode requires manual conversion: does not exist in peer-type // WARNING: in.SharingMode requires manual conversion: does not exist in peer-type + // WARNING: in.ProvisioningMode requires manual conversion: does not exist in peer-type out.Crypto = (*VirtualMachineVolumeCryptoStatus)(unsafe.Pointer(in.Crypto)) out.Limit = (*resource.Quantity)(unsafe.Pointer(in.Limit)) out.Requested = (*resource.Quantity)(unsafe.Pointer(in.Requested)) diff --git a/api/v1alpha5/virtualmachine_storage_types.go b/api/v1alpha5/virtualmachine_storage_types.go index 4ce150a1e..477ed6f0b 100644 --- a/api/v1alpha5/virtualmachine_storage_types.go +++ b/api/v1alpha5/virtualmachine_storage_types.go @@ -295,6 +295,13 @@ type VirtualMachineVolumeStatus struct { // +optional + // ProvisioningMode describes the volume's observed provisioning mode. + // This indicates whether the disk is thin-provisioned, thick-provisioned, + // or thick-provisioned with eager zeroing. + ProvisioningMode VolumeProvisioningMode `json:"provisioningMode,omitempty"` + + // +optional + // Crypto describes the volume's encryption status. Crypto *VirtualMachineVolumeCryptoStatus `json:"crypto,omitempty"` diff --git a/config/crd/bases/vmoperator.vmware.com_virtualmachines.yaml b/config/crd/bases/vmoperator.vmware.com_virtualmachines.yaml index 8c688ef4e..dc075c003 100644 --- a/config/crd/bases/vmoperator.vmware.com_virtualmachines.yaml +++ b/config/crd/bases/vmoperator.vmware.com_virtualmachines.yaml @@ -13430,6 +13430,16 @@ spec: name: description: Name describes the name of the volume. type: string + provisioningMode: + description: |- + ProvisioningMode describes the volume's observed provisioning mode. + This indicates whether the disk is thin-provisioned, thick-provisioned, + or thick-provisioned with eager zeroing. + enum: + - Thin + - Thick + - ThickEagerZero + type: string requested: anyOf: - type: integer diff --git a/controllers/virtualmachine/volumebatch/volumebatch_controller.go b/controllers/virtualmachine/volumebatch/volumebatch_controller.go index df2d12e09..1f2fe8f4e 100644 --- a/controllers/virtualmachine/volumebatch/volumebatch_controller.go +++ b/controllers/virtualmachine/volumebatch/volumebatch_controller.go @@ -648,6 +648,7 @@ func (r *Reconciler) getVMVolStatusesFromBatchAttachment( vmVolStatus.UnitNumber = existingVol.UnitNumber vmVolStatus.DiskMode = existingVol.DiskMode vmVolStatus.SharingMode = existingVol.SharingMode + vmVolStatus.ProvisioningMode = existingVol.ProvisioningMode // Add PVC capacity information if err := r.updateVolumeStatusWithPVCInfo( @@ -1018,6 +1019,7 @@ func (r *Reconciler) getVMVolStatusesFromLegacyAttachments( vmVolStatus.UnitNumber = existingVol.UnitNumber vmVolStatus.DiskMode = existingVol.DiskMode vmVolStatus.SharingMode = existingVol.SharingMode + vmVolStatus.ProvisioningMode = existingVol.ProvisioningMode // Add PVC capacity information if err := r.updateVolumeStatusWithPVCInfo( diff --git a/controllers/virtualmachine/volumebatch/volumebatch_controller_intg_test.go b/controllers/virtualmachine/volumebatch/volumebatch_controller_intg_test.go index cc7cb8ac9..cfc06547e 100644 --- a/controllers/virtualmachine/volumebatch/volumebatch_controller_intg_test.go +++ b/controllers/virtualmachine/volumebatch/volumebatch_controller_intg_test.go @@ -61,21 +61,21 @@ func intgTestsReconcile() { dummyDiskUUID1 = uuid.New().String() dummyDiskUUID2 = uuid.New().String() - vmVolume1 = vmopv1.VirtualMachineVolume{ - Name: "cns-volume-1", - VirtualMachineVolumeSource: vmopv1.VirtualMachineVolumeSource{ - PersistentVolumeClaim: &vmopv1.PersistentVolumeClaimVolumeSource{ - PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "pvc-volume-1", + vmVolume1 = vmopv1.VirtualMachineVolume{ + Name: "cns-volume-1", + VirtualMachineVolumeSource: vmopv1.VirtualMachineVolumeSource{ + PersistentVolumeClaim: &vmopv1.PersistentVolumeClaimVolumeSource{ + PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "pvc-volume-1", + }, }, }, - }, - ControllerType: vmopv1.VirtualControllerTypeSCSI, - ControllerBusNumber: ptr.To(int32(0)), - UnitNumber: ptr.To(int32(0)), - DiskMode: vmopv1.VolumeDiskModePersistent, - SharingMode: vmopv1.VolumeSharingModeNone, - } + ControllerType: vmopv1.VirtualControllerTypeSCSI, + ControllerBusNumber: ptr.To(int32(0)), + UnitNumber: ptr.To(int32(0)), + DiskMode: vmopv1.VolumeDiskModePersistent, + SharingMode: vmopv1.VolumeSharingModeNone, + } pvc1 = &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ @@ -94,21 +94,21 @@ func intgTestsReconcile() { }, } - vmVolume2 = vmopv1.VirtualMachineVolume{ - Name: "cns-volume-2", - VirtualMachineVolumeSource: vmopv1.VirtualMachineVolumeSource{ - PersistentVolumeClaim: &vmopv1.PersistentVolumeClaimVolumeSource{ - PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: "pvc-volume-2", + vmVolume2 = vmopv1.VirtualMachineVolume{ + Name: "cns-volume-2", + VirtualMachineVolumeSource: vmopv1.VirtualMachineVolumeSource{ + PersistentVolumeClaim: &vmopv1.PersistentVolumeClaimVolumeSource{ + PersistentVolumeClaimVolumeSource: corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "pvc-volume-2", + }, }, }, - }, - ControllerType: vmopv1.VirtualControllerTypeSCSI, - ControllerBusNumber: ptr.To(int32(0)), - UnitNumber: ptr.To(int32(1)), - DiskMode: vmopv1.VolumeDiskModePersistent, - SharingMode: vmopv1.VolumeSharingModeNone, - } + ControllerType: vmopv1.VirtualControllerTypeSCSI, + ControllerBusNumber: ptr.To(int32(0)), + UnitNumber: ptr.To(int32(1)), + DiskMode: vmopv1.VolumeDiskModePersistent, + SharingMode: vmopv1.VolumeSharingModeNone, + } pvc2 = &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/providers/vsphere/vmlifecycle/update_status.go b/pkg/providers/vsphere/vmlifecycle/update_status.go index 3dce7b4a5..6afa3846f 100644 --- a/pkg/providers/vsphere/vmlifecycle/update_status.go +++ b/pkg/providers/vsphere/vmlifecycle/update_status.go @@ -1283,6 +1283,55 @@ func updateStorageUsage(vmCtx pkgctx.VirtualMachineContext) []error { return errs } +// convertDiskMode converts vSphere disk mode string to vmopv1.VolumeDiskMode. +func convertDiskMode(diskMode string) vmopv1.VolumeDiskMode { + switch diskMode { + case string(vimtypes.VirtualDiskModeIndependent_persistent): + return vmopv1.VolumeDiskModeIndependentPersistent + case string(vimtypes.VirtualDiskModeIndependent_nonpersistent): + return vmopv1.VolumeDiskModeIndependentNonPersistent + case string(vimtypes.VirtualDiskModeNonpersistent): + return vmopv1.VolumeDiskModeNonPersistent + case string(vimtypes.VirtualDiskModePersistent): + return vmopv1.VolumeDiskModePersistent + default: + // Default to persistent if unknown or empty. + return vmopv1.VolumeDiskModePersistent + } +} + +// convertSharingMode converts vSphere sharing mode to vmopv1.VolumeSharingMode. +func convertSharingMode(sharing vimtypes.VirtualDiskSharing) vmopv1.VolumeSharingMode { + switch sharing { + case vimtypes.VirtualDiskSharingSharingMultiWriter: + return vmopv1.VolumeSharingModeMultiWriter + case vimtypes.VirtualDiskSharingSharingNone: + return vmopv1.VolumeSharingModeNone + default: + // Default to None if unknown or empty. + return vmopv1.VolumeSharingModeNone + } +} + +// convertProvisioningMode determines the provisioning mode from ThinProvisioned +// and EagerlyScrub flags. +func convertProvisioningMode(thinProvisioned, eagerlyScrub *bool) vmopv1.VolumeProvisioningMode { + // If ThinProvisioned is true, it's thin provisioned. + if thinProvisioned != nil && *thinProvisioned { + return vmopv1.VolumeProvisioningModeThin + } + // If ThinProvisioned is false or nil, check EagerlyScrub for thick variants. + if eagerlyScrub != nil && *eagerlyScrub { + return vmopv1.VolumeProvisioningModeThickEagerZero + } + // Default to thick (lazy zeroed) if thinProvisioned is false. + if thinProvisioned != nil && !*thinProvisioned { + return vmopv1.VolumeProvisioningModeThick + } + // If both are nil, return empty string (unknown/unset). + return "" +} + func updateVolumeStatus(vmCtx pkgctx.VirtualMachineContext) { var ( moVM = vmCtx.MoVM @@ -1356,6 +1405,8 @@ func updateVolumeStatus(vmCtx pkgctx.VirtualMachineContext) { KeyID: ddi.CryptoKey.KeyID, } } + // Note: DiskMode, SharingMode, and ProvisioningMode are set later + // in a single pass for all volumes (both classic and managed). // This is for a rare case when VM is upgraded from v1alpha3 to // v1alpha4+. Since vm.status.volume.requested was introduced in // v1alpha4. So we need to patch it if it's missing from status for @@ -1420,6 +1471,8 @@ func updateVolumeStatus(vmCtx pkgctx.VirtualMachineContext) { KeyID: ddi.CryptoKey.KeyID, } } + // Note: DiskMode, SharingMode, and ProvisioningMode are set later + // in a single pass for all volumes (both classic and managed). vm.Status.Volumes = append(vm.Status.Volumes, volStatus) } } @@ -1437,6 +1490,45 @@ func updateVolumeStatus(vmCtx pkgctx.VirtualMachineContext) { } }) + // Update disk attachment properties for ALL volumes (classic and managed). + // This is done in a single pass at the end to ensure consistency. + // For managed volumes, the volume controllers populate status from CnsNodeVmAttachment, + // which doesn't include DiskMode, SharingMode, or ProvisioningMode. + // We populate these properties here by matching DiskUUID with vSphere disk info. + diskInfoByUUID := make(map[string]pkgvol.VirtualDiskInfo) + for _, di := range info.Disks { + if di.UUID != "" { + diskInfoByUUID[di.UUID] = di + } + } + + for i := range vm.Status.Volumes { + vol := &vm.Status.Volumes[i] + if vol.DiskUUID == "" { + continue + } + + di, ok := diskInfoByUUID[vol.DiskUUID] + if !ok { + vmCtx.Logger.V(4).Info("No disk info found for volume", + "volumeName", vol.Name, + "volumeType", vol.Type, + "diskUUID", vol.DiskUUID) + continue + } + + // Populate disk attachment properties from vSphere disk info. + if di.DiskMode != "" { + vol.DiskMode = convertDiskMode(di.DiskMode) + } + if di.Sharing != "" { + vol.SharingMode = convertSharingMode(di.Sharing) + } + if provMode := convertProvisioningMode(di.ThinProvisioned, di.EagerlyScrub); provMode != "" { + vol.ProvisioningMode = provMode + } + } + // This sort order is consistent with the logic from the volumes controller. vmopv1.SortVirtualMachineVolumeStatuses(vm.Status.Volumes) } diff --git a/pkg/providers/vsphere/vmlifecycle/update_status_test.go b/pkg/providers/vsphere/vmlifecycle/update_status_test.go index 3b677b742..10b3c737f 100644 --- a/pkg/providers/vsphere/vmlifecycle/update_status_test.go +++ b/pkg/providers/vsphere/vmlifecycle/update_status_test.go @@ -2162,6 +2162,227 @@ var _ = Describe("UpdateStatus", func() { })) }) }) + + Context("disk attachment properties (DiskMode, SharingMode, ProvisioningMode)", func() { + BeforeEach(func() { + // Set up disks with various backing types and attachment properties + vmCtx.MoVM.Config = &vimtypes.VirtualMachineConfigInfo{ + Hardware: vimtypes.VirtualHardware{ + Device: []vimtypes.BaseVirtualDevice{ + &vimtypes.ParaVirtualSCSIController{ + VirtualSCSIController: vimtypes.VirtualSCSIController{ + VirtualController: vimtypes.VirtualController{ + BusNumber: 0, + VirtualDevice: vimtypes.VirtualDevice{ + Key: 200, + }, + }, + }, + }, + // Classic disk with thin provisioning, persistent mode + &vimtypes.VirtualDisk{ + VirtualDevice: vimtypes.VirtualDevice{ + Backing: &vimtypes.VirtualDiskFlatVer2BackingInfo{ + VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ + FileName: "[datastore] vm/classic-thin.vmdk", + }, + DiskMode: string(vimtypes.VirtualDiskModePersistent), + Sharing: string(vimtypes.VirtualDiskSharingSharingNone), + ThinProvisioned: ptr.To(true), + EagerlyScrub: ptr.To(false), + Uuid: "200", + }, + Key: 200, + ControllerKey: 200, + UnitNumber: ptr.To[int32](0), + }, + CapacityInBytes: 10 * oneGiBInBytes, + }, + // Classic disk with thick eager zero, independent persistent mode + &vimtypes.VirtualDisk{ + VirtualDevice: vimtypes.VirtualDevice{ + Backing: &vimtypes.VirtualDiskFlatVer2BackingInfo{ + VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ + FileName: "[datastore] vm/classic-thick-eager.vmdk", + }, + DiskMode: string(vimtypes.VirtualDiskModeIndependent_persistent), + Sharing: string(vimtypes.VirtualDiskSharingSharingNone), + ThinProvisioned: ptr.To(false), + EagerlyScrub: ptr.To(true), + Uuid: "201", + }, + Key: 201, + ControllerKey: 200, + UnitNumber: ptr.To[int32](1), + }, + CapacityInBytes: 20 * oneGiBInBytes, + }, + // Classic disk with thick lazy zero, nonpersistent mode + &vimtypes.VirtualDisk{ + VirtualDevice: vimtypes.VirtualDevice{ + Backing: &vimtypes.VirtualDiskFlatVer2BackingInfo{ + VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ + FileName: "[datastore] vm/classic-thick-lazy.vmdk", + }, + DiskMode: string(vimtypes.VirtualDiskModeNonpersistent), + Sharing: string(vimtypes.VirtualDiskSharingSharingNone), + ThinProvisioned: ptr.To(false), + EagerlyScrub: ptr.To(false), + Uuid: "202", + }, + Key: 202, + ControllerKey: 200, + UnitNumber: ptr.To[int32](2), + }, + CapacityInBytes: 30 * oneGiBInBytes, + }, + // Managed disk (FCD) with multiwriter sharing and independent nonpersistent mode + &vimtypes.VirtualDisk{ + VirtualDevice: vimtypes.VirtualDevice{ + Backing: &vimtypes.VirtualDiskFlatVer2BackingInfo{ + VirtualDeviceFileBackingInfo: vimtypes.VirtualDeviceFileBackingInfo{ + FileName: "[datastore] fcd/managed-multiwriter.vmdk", + }, + DiskMode: string(vimtypes.VirtualDiskModeIndependent_nonpersistent), + Sharing: string(vimtypes.VirtualDiskSharingSharingMultiWriter), + ThinProvisioned: ptr.To(true), + Uuid: "300", + }, + Key: 300, + UnitNumber: ptr.To[int32](0), + }, + CapacityInBytes: 50 * oneGiBInBytes, + VDiskId: &vimtypes.ID{ + Id: "fcd-300", + }, + }, + }, + }, + } + vmCtx.MoVM.LayoutEx = &vimtypes.VirtualMachineFileLayoutEx{ + Disk: []vimtypes.VirtualMachineFileLayoutExDiskLayout{ + {Key: 200, Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{{FileKey: []int32{0, 10}}}}, + {Key: 201, Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{{FileKey: []int32{1, 11}}}}, + {Key: 202, Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{{FileKey: []int32{2, 12}}}}, + {Key: 300, Chain: []vimtypes.VirtualMachineFileLayoutExDiskUnit{{FileKey: []int32{3, 13}}}}, + }, + File: []vimtypes.VirtualMachineFileLayoutExFileInfo{ + {Key: 0, Size: 500, UniqueSize: 500}, + {Key: 10, Size: 10 * oneGiBInBytes, UniqueSize: 5 * oneGiBInBytes}, + {Key: 1, Size: 500, UniqueSize: 500}, + {Key: 11, Size: 20 * oneGiBInBytes, UniqueSize: 15 * oneGiBInBytes}, + {Key: 2, Size: 500, UniqueSize: 500}, + {Key: 12, Size: 30 * oneGiBInBytes, UniqueSize: 20 * oneGiBInBytes}, + {Key: 3, Size: 500, UniqueSize: 500}, + {Key: 13, Size: 50 * oneGiBInBytes, UniqueSize: 40 * oneGiBInBytes}, + }, + } + }) + + When("classic disks have various attachment properties", func() { + BeforeEach(func() { + vmCtx.VM.Status.Volumes = []vmopv1.VirtualMachineVolumeStatus{} + }) + + Specify("status.volumes includes disk attachment properties for classic disks", func() { + Expect(vmCtx.VM.Status.Volumes).To(HaveLen(3)) + + // Classic disk with thin provisioning, persistent mode + Expect(vmCtx.VM.Status.Volumes[0].Name).To(Equal(pkgutil.GeneratePVCName("disk", "200"))) + Expect(vmCtx.VM.Status.Volumes[0].DiskMode).To(Equal(vmopv1.VolumeDiskModePersistent)) + Expect(vmCtx.VM.Status.Volumes[0].SharingMode).To(Equal(vmopv1.VolumeSharingModeNone)) + Expect(vmCtx.VM.Status.Volumes[0].ProvisioningMode).To(Equal(vmopv1.VolumeProvisioningModeThin)) + + // Classic disk with thick eager zero, independent persistent mode + Expect(vmCtx.VM.Status.Volumes[1].Name).To(Equal(pkgutil.GeneratePVCName("disk", "201"))) + Expect(vmCtx.VM.Status.Volumes[1].DiskMode).To(Equal(vmopv1.VolumeDiskModeIndependentPersistent)) + Expect(vmCtx.VM.Status.Volumes[1].SharingMode).To(Equal(vmopv1.VolumeSharingModeNone)) + Expect(vmCtx.VM.Status.Volumes[1].ProvisioningMode).To(Equal(vmopv1.VolumeProvisioningModeThickEagerZero)) + + // Classic disk with thick lazy zero, nonpersistent mode + Expect(vmCtx.VM.Status.Volumes[2].Name).To(Equal(pkgutil.GeneratePVCName("disk", "202"))) + Expect(vmCtx.VM.Status.Volumes[2].DiskMode).To(Equal(vmopv1.VolumeDiskModeNonPersistent)) + Expect(vmCtx.VM.Status.Volumes[2].SharingMode).To(Equal(vmopv1.VolumeSharingModeNone)) + Expect(vmCtx.VM.Status.Volumes[2].ProvisioningMode).To(Equal(vmopv1.VolumeProvisioningModeThick)) + }) + }) + + When("managed disk (FCD) has attachment properties", func() { + BeforeEach(func() { + // Simulate volume controller creating managed volume status without disk properties + vmCtx.VM.Status.Volumes = []vmopv1.VirtualMachineVolumeStatus{ + { + Name: "my-managed-disk", + DiskUUID: "300", + Type: vmopv1.VolumeTypeManaged, + Attached: true, + Limit: kubeutil.BytesToResource(100 * oneGiBInBytes), + Requested: kubeutil.BytesToResource(50 * oneGiBInBytes), + // DiskMode, SharingMode, ProvisioningMode not set by volume controller + }, + } + }) + + Specify("status.volumes includes disk attachment properties for managed disk", func() { + Expect(vmCtx.VM.Status.Volumes).To(HaveLen(4)) // 3 classic + 1 managed + + // Find the managed disk in the status + var managedDisk *vmopv1.VirtualMachineVolumeStatus + for i := range vmCtx.VM.Status.Volumes { + if vmCtx.VM.Status.Volumes[i].Type == vmopv1.VolumeTypeManaged { + managedDisk = &vmCtx.VM.Status.Volumes[i] + break + } + } + + Expect(managedDisk).ToNot(BeNil()) + Expect(managedDisk.Name).To(Equal("my-managed-disk")) + Expect(managedDisk.DiskUUID).To(Equal("300")) + Expect(managedDisk.DiskMode).To(Equal(vmopv1.VolumeDiskModeIndependentNonPersistent)) + Expect(managedDisk.SharingMode).To(Equal(vmopv1.VolumeSharingModeMultiWriter)) + Expect(managedDisk.ProvisioningMode).To(Equal(vmopv1.VolumeProvisioningModeThin)) + Expect(managedDisk.Used).To(Equal(kubeutil.BytesToResource(500 + (40 * oneGiBInBytes)))) + }) + }) + + When("existing disk has properties updated", func() { + BeforeEach(func() { + // Existing disk with old/missing properties + vmCtx.VM.Status.Volumes = []vmopv1.VirtualMachineVolumeStatus{ + { + Name: pkgutil.GeneratePVCName("disk", "200"), + DiskUUID: "200", + Type: vmopv1.VolumeTypeClassic, + Attached: true, + Limit: kubeutil.BytesToResource(10 * oneGiBInBytes), + Requested: kubeutil.BytesToResource(10 * oneGiBInBytes), + Used: kubeutil.BytesToResource(5 * oneGiBInBytes), + DiskMode: "", // Empty/unset + SharingMode: "", // Empty/unset + ProvisioningMode: "", // Empty/unset + }, + } + }) + + Specify("status.volumes updates disk attachment properties for existing disk", func() { + Expect(vmCtx.VM.Status.Volumes).To(HaveLen(3)) // All classic disks + + // Find disk 200 and verify properties were updated + var disk200 *vmopv1.VirtualMachineVolumeStatus + for i := range vmCtx.VM.Status.Volumes { + if vmCtx.VM.Status.Volumes[i].DiskUUID == "200" { + disk200 = &vmCtx.VM.Status.Volumes[i] + break + } + } + + Expect(disk200).ToNot(BeNil()) + Expect(disk200.DiskMode).To(Equal(vmopv1.VolumeDiskModePersistent)) + Expect(disk200.SharingMode).To(Equal(vmopv1.VolumeSharingModeNone)) + Expect(disk200.ProvisioningMode).To(Equal(vmopv1.VolumeProvisioningModeThin)) + }) + }) + }) }) }) diff --git a/pkg/util/devices.go b/pkg/util/devices.go index 60e831930..9edb5fcb3 100644 --- a/pkg/util/devices.go +++ b/pkg/util/devices.go @@ -253,6 +253,9 @@ type VirtualDiskInfo struct { DeviceKey int32 CryptoKey *vimtypes.CryptoKeyId Sharing vimtypes.VirtualDiskSharing + DiskMode string + ThinProvisioned *bool + EagerlyScrub *bool HasParent bool ControllerKey int32 UnitNumber *int32 @@ -287,36 +290,44 @@ func GetVirtualDiskInfo( vdi.FileName = tb.FileName vdi.UUID = tb.Uuid vdi.CryptoKey = tb.KeyId + vdi.DiskMode = tb.DiskMode vdi.HasParent = tb.Parent != nil case *vimtypes.VirtualDiskSparseVer1BackingInfo: // No UUID or Crypto vdi.FileName = tb.FileName + vdi.DiskMode = tb.DiskMode vdi.HasParent = tb.Parent != nil case *vimtypes.VirtualDiskSparseVer2BackingInfo: vdi.FileName = tb.FileName vdi.UUID = tb.Uuid vdi.CryptoKey = tb.KeyId + vdi.DiskMode = tb.DiskMode vdi.HasParent = tb.Parent != nil case *vimtypes.VirtualDiskFlatVer1BackingInfo: // No UUID or Crypto vdi.FileName = tb.FileName + vdi.DiskMode = tb.DiskMode vdi.HasParent = tb.Parent != nil case *vimtypes.VirtualDiskFlatVer2BackingInfo: vdi.FileName = tb.FileName vdi.UUID = tb.Uuid vdi.CryptoKey = tb.KeyId vdi.Sharing = vimtypes.VirtualDiskSharing(tb.Sharing) + vdi.DiskMode = tb.DiskMode + vdi.ThinProvisioned = tb.ThinProvisioned + vdi.EagerlyScrub = tb.EagerlyScrub vdi.HasParent = tb.Parent != nil case *vimtypes.VirtualDiskLocalPMemBackingInfo: // No Crypto vdi.FileName = tb.FileName vdi.UUID = tb.Uuid - case *vimtypes.VirtualDiskRawDiskMappingVer1BackingInfo: // No Crypto + vdi.DiskMode = tb.DiskMode + case *vimtypes.VirtualDiskRawDiskMappingVer1BackingInfo: // No Crypto, No DiskMode vdi.FileName = tb.FileName vdi.UUID = tb.Uuid vdi.Sharing = vimtypes.VirtualDiskSharing(tb.Sharing) - case *vimtypes.VirtualDiskRawDiskVer2BackingInfo: // No Crypto + case *vimtypes.VirtualDiskRawDiskVer2BackingInfo: // No Crypto, No DiskMode vdi.FileName = tb.DescriptorFileName vdi.UUID = tb.Uuid vdi.Sharing = vimtypes.VirtualDiskSharing(tb.Sharing) - case *vimtypes.VirtualDiskPartitionedRawDiskVer2BackingInfo: // No Crypto + case *vimtypes.VirtualDiskPartitionedRawDiskVer2BackingInfo: // No Crypto, No DiskMode vdi.FileName = tb.DescriptorFileName vdi.UUID = tb.Uuid vdi.Sharing = vimtypes.VirtualDiskSharing(tb.Sharing)