Skip to content

Commit

Permalink
Added multi disk support
Browse files Browse the repository at this point in the history
  • Loading branch information
vr4manta committed Oct 29, 2024
1 parent df3d0b2 commit 78a0873
Show file tree
Hide file tree
Showing 15 changed files with 469 additions and 20 deletions.
1 change: 1 addition & 0 deletions apis/v1alpha3/vspheremachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func (src *VSphereMachine) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Network.Devices[i].DHCP6Overrides = restored.Spec.Network.Devices[i].DHCP6Overrides
dst.Spec.Network.Devices[i].SkipIPAllocation = restored.Spec.Network.Devices[i].SkipIPAllocation
}
dst.Spec.DataDisks = restored.Spec.DataDisks

return nil
}
Expand Down
1 change: 1 addition & 0 deletions apis/v1alpha3/vspheremachinetemplate_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func (src *VSphereMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Template.Spec.Network.Devices[i].DHCP6Overrides = restored.Spec.Template.Spec.Network.Devices[i].DHCP6Overrides
dst.Spec.Template.Spec.Network.Devices[i].SkipIPAllocation = restored.Spec.Template.Spec.Network.Devices[i].SkipIPAllocation
}
dst.Spec.Template.Spec.DataDisks = restored.Spec.Template.Spec.DataDisks

return nil
}
Expand Down
1 change: 1 addition & 0 deletions apis/v1alpha3/vspherevm_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func (src *VSphereVM) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Network.Devices[i].DHCP6Overrides = restored.Spec.Network.Devices[i].DHCP6Overrides
dst.Spec.Network.Devices[i].SkipIPAllocation = restored.Spec.Network.Devices[i].SkipIPAllocation
}
dst.Spec.DataDisks = restored.Spec.DataDisks

return nil
}
Expand Down
1 change: 1 addition & 0 deletions apis/v1alpha3/zz_generated.conversion.go

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

1 change: 1 addition & 0 deletions apis/v1alpha4/vspheremachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func (src *VSphereMachine) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Network.Devices[i].DHCP6Overrides = restored.Spec.Network.Devices[i].DHCP6Overrides
dst.Spec.Network.Devices[i].SkipIPAllocation = restored.Spec.Network.Devices[i].SkipIPAllocation
}
dst.Spec.DataDisks = restored.Spec.DataDisks

return nil
}
Expand Down
1 change: 1 addition & 0 deletions apis/v1alpha4/vspheremachinetemplate_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func (src *VSphereMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Template.Spec.Network.Devices[i].DHCP6Overrides = restored.Spec.Template.Spec.Network.Devices[i].DHCP6Overrides
dst.Spec.Template.Spec.Network.Devices[i].SkipIPAllocation = restored.Spec.Template.Spec.Network.Devices[i].SkipIPAllocation
}
dst.Spec.Template.Spec.DataDisks = restored.Spec.Template.Spec.DataDisks

return nil
}
Expand Down
1 change: 1 addition & 0 deletions apis/v1alpha4/vspherevm_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func (src *VSphereVM) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Network.Devices[i].DHCP6Overrides = restored.Spec.Network.Devices[i].DHCP6Overrides
dst.Spec.Network.Devices[i].SkipIPAllocation = restored.Spec.Network.Devices[i].SkipIPAllocation
}
dst.Spec.DataDisks = restored.Spec.DataDisks

return nil
}
Expand Down
1 change: 1 addition & 0 deletions apis/v1alpha4/zz_generated.conversion.go

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

15 changes: 15 additions & 0 deletions apis/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,21 @@ type VirtualMachineCloneSpec struct {
// Check the compatibility with the ESXi version before setting the value.
// +optional
HardwareVersion string `json:"hardwareVersion,omitempty"`
// DataDisks are additional disks to add to the VM that are not part of the VM's OVA template.
// +optional
DataDisks []VSphereDisk `json:"dataDisks,omitempty"`
}

// VSphereDisk is an additional disk to add to the VM that is not part of the VM OVA template.
type VSphereDisk struct {
// Name is used to identify the disk definition. If Name is not specified, the disk will still be created.
// The Name should be unique so that it can be used to clearly identify purpose of the disk, but is not
// required to be unique.
// +optional
Name string `json:"name,omitempty"`
// SizeGiB is the size of the disk in GiB.
// +kubebuilder:validation:Required
SizeGiB int32 `json:"sizeGiB"`
}

// VSphereMachineTemplateResource describes the data needed to create a VSphereMachine from a template.
Expand Down
20 changes: 20 additions & 0 deletions apis/v1beta1/zz_generated.deepcopy.go

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

Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,27 @@ spec:
CustomVMXKeys is a dictionary of advanced VMX options that can be set on VM
Defaults to empty map
type: object
dataDisks:
description: DataDisks are additional disks to add to the VM that
are not part of the VM's OVA template.
items:
description: VSphereDisk is an additional disk to add to the VM
that is not part of the VM OVA template.
properties:
name:
description: |-
Name is used to identify the disk definition. If Name is not specified, the disk will still be created.
The Name should be unique so that it can be used to clearly identify purpose of the disk, but is not
required to be unique.
type: string
sizeGiB:
description: SizeGiB is the size of the disk in GiB.
format: int32
type: integer
required:
- sizeGiB
type: object
type: array
datacenter:
description: |-
Datacenter is the name or inventory path of the datacenter in which the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,27 @@ spec:
CustomVMXKeys is a dictionary of advanced VMX options that can be set on VM
Defaults to empty map
type: object
dataDisks:
description: DataDisks are additional disks to add to the
VM that are not part of the VM's OVA template.
items:
description: VSphereDisk is an additional disk to add to
the VM that is not part of the VM OVA template.
properties:
name:
description: |-
Name is used to identify the disk definition. If Name is not specified, the disk will still be created.
The Name should be unique so that it can be used to clearly identify purpose of the disk, but is not
required to be unique.
type: string
sizeGiB:
description: SizeGiB is the size of the disk in GiB.
format: int32
type: integer
required:
- sizeGiB
type: object
type: array
datacenter:
description: |-
Datacenter is the name or inventory path of the datacenter in which the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,27 @@ spec:
CustomVMXKeys is a dictionary of advanced VMX options that can be set on VM
Defaults to empty map
type: object
dataDisks:
description: DataDisks are additional disks to add to the VM that
are not part of the VM's OVA template.
items:
description: VSphereDisk is an additional disk to add to the VM
that is not part of the VM OVA template.
properties:
name:
description: |-
Name is used to identify the disk definition. If Name is not specified, the disk will still be created.
The Name should be unique so that it can be used to clearly identify purpose of the disk, but is not
required to be unique.
type: string
sizeGiB:
description: SizeGiB is the size of the disk in GiB.
format: int32
type: integer
required:
- sizeGiB
type: object
type: array
datacenter:
description: |-
Datacenter is the name or inventory path of the datacenter in which the
Expand Down
135 changes: 132 additions & 3 deletions pkg/services/govmomi/vcenter/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ import (
const (
fullCloneDiskMoveType = types.VirtualMachineRelocateDiskMoveOptionsMoveAllDiskBackingsAndConsolidate
linkCloneDiskMoveType = types.VirtualMachineRelocateDiskMoveOptionsCreateNewChildDiskBacking

// maxUnitNumber constant used to define they most devices that can be assigned to a controller. Not all controllers support up to 30, but max is 30.
// Documentation on limits / behavior: https://docs.vmware.com/en/VMware-vSphere/8.0/vsphere-vm-administration/GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362.html#:~:text=If%20you%20add%20a%20hard,values%20from%200%20to%2014.
maxUnitNumber = 30
)

// Clone kicks off a clone operation on vCenter to create a new virtual machine. This function does not wait for
Expand Down Expand Up @@ -138,10 +142,11 @@ func Clone(ctx context.Context, vmCtx *capvcontext.VMContext, bootstrapData []by

// Only non-linked clones may expand the size of the template's disk.
if snapshotRef == nil {
diskSpecs, err := getDiskSpec(vmCtx, devices)
diskSpecs, err := getDiskSpec(ctx, vmCtx, devices)
if err != nil {
return errors.Wrapf(err, "error getting disk spec for %q", ctx)
}
log.Info("Got the following disks", "disk", diskSpecs)
deviceSpecs = append(deviceSpecs, diskSpecs...)
}

Expand Down Expand Up @@ -334,14 +339,16 @@ func getDiskLocators(disks object.VirtualDeviceList, datastoreRef types.ManagedO
return diskLocators
}

func getDiskSpec(vmCtx *capvcontext.VMContext, devices object.VirtualDeviceList) ([]types.BaseVirtualDeviceConfigSpec, error) {
func getDiskSpec(ctx context.Context, vmCtx *capvcontext.VMContext, devices object.VirtualDeviceList) ([]types.BaseVirtualDeviceConfigSpec, error) {
log := ctrl.LoggerFrom(ctx)

disks := devices.SelectByType((*types.VirtualDisk)(nil))
if len(disks) == 0 {
return nil, errors.Errorf("Invalid disk count: %d", len(disks))
}

// There is at least one disk
var diskSpecs []types.BaseVirtualDeviceConfigSpec
diskSpecs := []types.BaseVirtualDeviceConfigSpec{}
primaryDisk := disks[0].(*types.VirtualDisk)
primaryCloneCapacityKB := int64(vmCtx.VSphereVM.Spec.DiskGiB) * 1024 * 1024
primaryDiskConfigSpec, err := getDiskConfigSpec(primaryDisk, primaryCloneCapacityKB)
Expand Down Expand Up @@ -369,6 +376,17 @@ func getDiskSpec(vmCtx *capvcontext.VMContext, devices object.VirtualDeviceList)
diskSpecs = append(diskSpecs, additionalDiskConfigSpec)
}
}

// Process all DataDisks definitions to dynamically create and add disks to the VM
if len(vmCtx.VSphereVM.Spec.DataDisks) > 0 {
additionalDisks, err := createDataDisks(ctx, vmCtx.VSphereVM.Spec.DataDisks, primaryDisk, devices)
if err != nil {
log.Error(err, "Unable to add additional disks.")
return nil, err
}
diskSpecs = append(diskSpecs, additionalDisks...)
}

return diskSpecs, nil
}

Expand All @@ -390,6 +408,117 @@ func getDiskConfigSpec(disk *types.VirtualDisk, diskCloneCapacityKB int64) (type
}, nil
}

// createDataDisks parses through the list of VSphereDisk objects and generates the VirtualDeviceConfigSpec for each one.
func createDataDisks(ctx context.Context, disks []infrav1.VSphereDisk, primaryDisk *types.VirtualDisk, devices object.VirtualDeviceList) ([]types.BaseVirtualDeviceConfigSpec, error) {
log := ctrl.LoggerFrom(ctx)
additionalDisks := []types.BaseVirtualDeviceConfigSpec{}
unit := int32(1)

for i, dataDisk := range disks {
log.Info("Adding disk", "name", dataDisk.Name, "spec", dataDisk)

// Get the controller of the primary disk.
controller, ok := devices.FindByKey(primaryDisk.ControllerKey).(types.BaseVirtualController)
if !ok {
return nil, errors.Errorf("unable to find controller with key=%v", primaryDisk.ControllerKey)
}

dev := &types.VirtualDisk{
VirtualDevice: types.VirtualDevice{
Key: devices.NewKey() - int32(i),
Backing: &types.VirtualDiskFlatVer2BackingInfo{
DiskMode: string(types.VirtualDiskModePersistent),
ThinProvisioned: types.NewBool(true),
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
FileName: "",
},
},
ControllerKey: controller.GetVirtualController().Key,
},
CapacityInKB: int64(dataDisk.SizeGiB) * 1024 * 1024,
}

// Assign unit number to the next slot on the controller.
if err := assignUnitNumber(ctx, dev, devices, additionalDisks, controller, unit); err != nil {
log.Error(err, "unable to assign unit number")
return nil, err
}
unit = *dev.UnitNumber

diskConfigSpec := types.VirtualDeviceConfigSpec{
Device: dev,
Operation: types.VirtualDeviceConfigSpecOperationAdd,
FileOperation: types.VirtualDeviceConfigSpecFileOperationCreate,
}

log.Info("Generated device", "dev", dev)

additionalDisks = append(additionalDisks, &diskConfigSpec)
}

return additionalDisks, nil
}

// assignUnitNumber assigns a controller unit number to a device.
func assignUnitNumber(ctx context.Context, device types.BaseVirtualDevice, existingDevices object.VirtualDeviceList, newDevices []types.BaseVirtualDeviceConfigSpec, controller types.BaseVirtualController, offset int32) error {
if offset > maxUnitNumber {
return errors.Errorf("unable to assign unit number due to offset %v exceeding max allowed of %v", offset, maxUnitNumber)
}

if controller == nil {
return errors.Errorf("unable to assign unit number due to controller parameter being nil")
}

log := ctrl.LoggerFrom(ctx)
vd := device.GetVirtualDevice()
vd.ControllerKey = controller.GetVirtualController().Key
vd.UnitNumber = &offset

units := make([]bool, maxUnitNumber)

for i := 0; i < int(offset); i++ {
units[i] = true
}

sc, ok := controller.(types.BaseVirtualSCSIController)
if ok {
// The SCSI controller sits on its own bus
log.V(4).Info(fmt.Sprintf("Marking SCSI Controller's unit number: %d", sc.GetVirtualSCSIController().ScsiCtlrUnitNumber))
units[sc.GetVirtualSCSIController().ScsiCtlrUnitNumber] = true
}

key := controller.GetVirtualController().Key

// Check all existing devices
for _, device := range existingDevices {
d := device.GetVirtualDevice()
if d.ControllerKey == key && d.UnitNumber != nil {
units[int(*d.UnitNumber)] = true
}
}

// Check new devices
for _, device := range newDevices {
d := device.GetVirtualDeviceConfigSpec().Device.GetVirtualDevice()
if d.ControllerKey == key && d.UnitNumber != nil {
units[int(*d.UnitNumber)] = true
}
}

// Assign first unused unit number
for unit, used := range units {
if !used {
unit32 := int32(unit)
vd.UnitNumber = &unit32
log.V(4).Info(fmt.Sprintf("Determined next available unit number: %d", unit32))
return nil
}
}

// If we are here, we did not find a unit number. Return error.
return errors.Errorf("unable to find available unit number")
}

const ethCardType = "vmxnet3"

func getNetworkSpecs(ctx context.Context, vmCtx *capvcontext.VMContext, devices object.VirtualDeviceList) ([]types.BaseVirtualDeviceConfigSpec, error) {
Expand Down
Loading

0 comments on commit 78a0873

Please sign in to comment.