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 Dec 10, 2024
1 parent 5cde143 commit 4af6c63
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 0 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.

17 changes: 17 additions & 0 deletions apis/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,23 @@ 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
// +listType=map
// +listMapKey=name
// +kubebuilder:validation:MaxItems=29
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. Name is required and needs to be unique so that it can be used to
// clearly identify purpose of the disk.
// +kubebuilder:validation:Required
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,31 @@ 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. Name is required and needs to be unique so that it can be used to
clearly identify purpose of the disk.
type: string
sizeGiB:
description: SizeGiB is the size of the disk in GiB.
format: int32
type: integer
required:
- name
- sizeGiB
type: object
maxItems: 29
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
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,31 @@ 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. Name is required and needs to be unique so that it can be used to
clearly identify purpose of the disk.
type: string
sizeGiB:
description: SizeGiB is the size of the disk in GiB.
format: int32
type: integer
required:
- name
- sizeGiB
type: object
maxItems: 29
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
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,31 @@ 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. Name is required and needs to be unique so that it can be used to
clearly identify purpose of the disk.
type: string
sizeGiB:
description: SizeGiB is the size of the disk in GiB.
format: int32
type: integer
required:
- name
- sizeGiB
type: object
maxItems: 29
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
datacenter:
description: |-
Datacenter is the name or inventory path of the datacenter in which the
Expand Down
126 changes: 126 additions & 0 deletions pkg/services/govmomi/vcenter/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ import (
const (
fullCloneDiskMoveType = types.VirtualMachineRelocateDiskMoveOptionsMoveAllDiskBackingsAndConsolidate
linkCloneDiskMoveType = types.VirtualMachineRelocateDiskMoveOptionsCreateNewChildDiskBacking

// maxUnitNumber constant is used to define the maximum number of devices that can be assigned to a virtual machine's controller.
// Not all controllers support up to 30, but the maximum is 30.
// xref: 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 @@ -145,6 +150,16 @@ func Clone(ctx context.Context, vmCtx *capvcontext.VMContext, bootstrapData []by
deviceSpecs = append(deviceSpecs, diskSpecs...)
}

// Process all DataDisks definitions to dynamically create and add disks to the VM
if len(vmCtx.VSphereVM.Spec.DataDisks) > 0 {
dataDisks, err := createDataDisks(ctx, vmCtx.VSphereVM.Spec.DataDisks, devices)
if err != nil {
return errors.Wrapf(err, "error getting data disks")
}
log.V(4).Info("Adding the following data disks", "disks", dataDisks)
deviceSpecs = append(deviceSpecs, dataDisks...)
}

networkSpecs, err := getNetworkSpecs(ctx, vmCtx, devices)
if err != nil {
return errors.Wrapf(err, "error getting network specs for %q", ctx)
Expand Down Expand Up @@ -390,6 +405,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, dataDiskDefs []infrav1.VSphereDisk, devices object.VirtualDeviceList) ([]types.BaseVirtualDeviceConfigSpec, error) {
log := ctrl.LoggerFrom(ctx)
additionalDisks := []types.BaseVirtualDeviceConfigSpec{}

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
primaryDisk := disks[0].(*types.VirtualDisk)

// 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)
}

controllerKey := controller.GetVirtualController().Key
unitNumberAssigner, err := newUnitNumberAssigner(controller, devices)
if err != nil {
return nil, err
}

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

dev := &types.VirtualDisk{
VirtualDevice: types.VirtualDevice{
// Key needs to be unique and cannot match another new disk being added. So we'll use the index as an
// input to NewKey. NewKey() will always return same value since our new devices are not part of devices yet.
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,
}

vd := dev.GetVirtualDevice()
vd.ControllerKey = controllerKey

// Assign unit number to the new disk. Should be next available slot on the controller.
unitNumber, err := unitNumberAssigner.assign()
if err != nil {
return nil, err
}
vd.UnitNumber = &unitNumber

log.V(4).Info("Created device for data disk device", "name", dataDisk.Name, "spec", dataDisk, "device", dev)

additionalDisks = append(additionalDisks, &types.VirtualDeviceConfigSpec{
Device: dev,
Operation: types.VirtualDeviceConfigSpecOperationAdd,
FileOperation: types.VirtualDeviceConfigSpecFileOperationCreate,
})
}

return additionalDisks, nil
}

type unitNumberAssigner struct {
used []bool
offset int32
}

func newUnitNumberAssigner(controller types.BaseVirtualController, existingDevices object.VirtualDeviceList) (*unitNumberAssigner, error) {
if controller == nil {
return nil, errors.New("controller parameter cannot be nil")
}
used := make([]bool, maxUnitNumber)

// SCSIControllers also use a unit.
if scsiController, ok := controller.(types.BaseVirtualSCSIController); ok {
used[scsiController.GetVirtualSCSIController().ScsiCtlrUnitNumber] = true
}
controllerKey := controller.GetVirtualController().Key

// Mark all unit numbers of existing devices as used
for _, device := range existingDevices {
d := device.GetVirtualDevice()
if d.ControllerKey == controllerKey && d.UnitNumber != nil {
used[*d.UnitNumber] = true
}
}

// Set offset to 0, it will auto-increment on the first assignment.
return &unitNumberAssigner{used: used, offset: 0}, nil
}

func (a *unitNumberAssigner) assign() (int32, error) {
if int(a.offset) > len(a.used) {
return -1, fmt.Errorf("all unit numbers are already in-use")
}
for i, isInUse := range a.used[a.offset:] {
unit := int32(i) + a.offset
if !isInUse {
a.used[unit] = true
a.offset++
return unit, nil
}
}
return -1, fmt.Errorf("all unit numbers are already in-use")
}

const ethCardType = "vmxnet3"

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

0 comments on commit 4af6c63

Please sign in to comment.