Skip to content

Commit

Permalink
feat: Add cloud-init support for scratch typed VirtualMachines
Browse files Browse the repository at this point in the history
  • Loading branch information
alperencelik committed Nov 25, 2024
1 parent 27ff006 commit 43bb49c
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 23 deletions.
3 changes: 3 additions & 0 deletions api/proxmox/v1alpha1/virtualmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ type VirtualMachineSpec struct {
// If not set, it defaults to true.
// +kubebuilder:default:=true
EnableAutoStart bool `json:"enableAutoStart,omitempty"`
// // AdditionalConfig is the additional configuration of the VM
// // +kubebuilder:validation:Optional
// AdditionalConfig map[string]string `json:"additionalConfig,omitempty"`
}

type NewVMSpec struct {
Expand Down
54 changes: 54 additions & 0 deletions internal/controller/proxmox/virtualmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ func (r *VirtualMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reque
logger.Error(err, "Error updating VirtualMachine")
return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err)
}
err = r.handleCloudInitOperations(ctx, vm)
if err != nil {
logger.Error(err, "Error handling cloud-init operations")
return ctrl.Result{Requeue: true}, client.IgnoreNotFound(err)
}
}
logger.Info(fmt.Sprintf("VirtualMachine %s already exists", vmName))

Expand Down Expand Up @@ -225,6 +230,12 @@ func (r *VirtualMachineReconciler) CreateVirtualMachine(ctx context.Context, vm
case "scratch":
r.Recorder.Event(vm, "Normal", "Creating", fmt.Sprintf("VirtualMachine %s is being created", vmName))
proxmox.CreateVMFromScratch(vm)
r.Recorder.Event(vm, "Normal", "Created", fmt.Sprintf("VirtualMachine %s has been created", vmName))
err := r.handleCloudInitOperations(ctx, vm)
if err != nil {
return err
}

startResult, err := proxmox.StartVM(vmName, nodeName)
if err != nil {
return err
Expand Down Expand Up @@ -332,6 +343,49 @@ func (r *VirtualMachineReconciler) handleFinalizer(ctx context.Context, vm *prox
return nil
}

func (r *VirtualMachineReconciler) handleCloudInitOperations(ctx context.Context,
vm *proxmoxv1alpha1.VirtualMachine) error {
if proxmox.CheckVMType(vm) == proxmox.VirtualMachineTemplateType {
return nil
}
logger := log.FromContext(ctx)

if vm.Spec.VMSpec.CloudInitConfig == nil {
return nil
}

vmName := vm.Spec.Name
nodeName := vm.Spec.NodeName

// 1. Add cloud Init CD-ROM drive
err := proxmox.AddCloudInitDrive(vmName, nodeName)
if err != nil {
logger.Error(err, "Failed to add cloud-init drive to VM")
return err
}
// 2. Set cloud-init configuration
err = proxmox.SetCloudInitConfig(vmName, nodeName, vm.Spec.VMSpec.CloudInitConfig)
if err != nil {
logger.Error(err, "Failed to set cloud-init configuration")
return err
}
// If the machine is running, reboot it to apply the cloud-init configuration
vmState := proxmox.GetVMState(vmName, nodeName)
if vmState == proxmox.VirtualMachineRunningState {
err = proxmox.RebootVM(vmName, nodeName)
if err != nil {
logger.Error(err, "Failed to reboot VM")
}
}

return nil
}

// func (r *VirtualMachineReconciler) handleAdditionalConfig(ctx context.Context, vm *proxmoxv1alpha1.VirtualMachine) error {
// // TODO: Implement additional configuration handling
// return nil
// }

func (r *VirtualMachineReconciler) handleWatcher(ctx context.Context, req ctrl.Request, vm *proxmoxv1alpha1.VirtualMachine) {
r.Watchers.HandleWatcher(ctx, req, func(ctx context.Context, stopChan chan struct{}) (ctrl.Result, error) {
return proxmox.StartWatcher(ctx, vm, stopChan, r.fetchResource, r.updateStatus,
Expand Down
90 changes: 67 additions & 23 deletions pkg/proxmox/virtualmachine.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ const (
// The tag that will be added to VMs in Proxmox cluster
VirtualMachineRunningState = "running"
VirtualMachineStoppedState = "stopped"
virtualMachineTemplateType = "template"
virtualMachineScratchType = "scratch"
VirtualMachineTemplateType = "template"
VirtualMachineScratchType = "scratch"
virtualMachineCPUOption = "cores"
virtualMachineSocketOption = "sockets"
virtualMachineMemoryOption = "memory"
Expand Down Expand Up @@ -383,15 +383,16 @@ func CreateVMFromScratch(vm *proxmoxv1alpha1.VirtualMachine) {
})
}
}
if len(*virtualMachineSpec.Network) != 0 {
if virtualMachineSpec.Network != nil {
for i, network := range *virtualMachineSpec.Network {
VMOptions = append(VMOptions, proxmox.VirtualMachineOption{
Name: "net" + strconv.Itoa(i),
Value: network.Model + ",bridge=" + network.Bridge,
})
}
}

// Make sure that not two VMs are created at the exact time
mutex.Lock()
// Get next VMID
vmID, err := getNextVMID(Client)
if err != nil {
Expand All @@ -402,6 +403,7 @@ func CreateVMFromScratch(vm *proxmoxv1alpha1.VirtualMachine) {
if err != nil {
panic(err)
}
mutex.Unlock()
_, taskCompleted, taskErr := task.WaitForCompleteStatus(ctx, 10, 10)
switch taskCompleted {
case false:
Expand All @@ -416,7 +418,7 @@ func CreateVMFromScratch(vm *proxmoxv1alpha1.VirtualMachine) {
panic(err)
}
addTagTask, err := VirtualMachine.AddTag(ctx, virtualMachineTag)
_, taskCompleted, taskErr = addTagTask.WaitForCompleteStatus(ctx, 1, 10)
_, taskCompleted, taskErr = addTagTask.WaitForCompleteStatus(ctx, 3, 10)
if !taskCompleted {
log.Log.Error(taskErr, "Can't add tag to VM")
}
Expand All @@ -429,9 +431,9 @@ func CheckVMType(vm *proxmoxv1alpha1.VirtualMachine) string {
var VMType string
switch {
case !reflect.ValueOf(vm.Spec.Template).IsZero():
VMType = virtualMachineTemplateType
VMType = VirtualMachineTemplateType
case !reflect.ValueOf(vm.Spec.VMSpec).IsZero():
VMType = virtualMachineScratchType
VMType = VirtualMachineScratchType
case !reflect.ValueOf(vm.Spec.Template).IsZero() && !reflect.ValueOf(vm.Spec.VMSpec).IsZero():
VMType = "faulty"
default:
Expand Down Expand Up @@ -530,12 +532,12 @@ func UpdateVM(vm *proxmoxv1alpha1.VirtualMachine) bool {
cpuOption.Name = virtualMachineCPUOption
memoryOption.Name = virtualMachineMemoryOption
switch CheckVMType(vm) {
case virtualMachineTemplateType:
case VirtualMachineTemplateType:
cpuOption.Value = vm.Spec.Template.Cores
memoryOption.Value = uint64(vm.Spec.Template.Memory)
metrics.SetVirtualMachineCPUCores(vmName, vm.Namespace, float64(vm.Spec.Template.Cores))
metrics.SetVirtualMachineMemory(vmName, vm.Namespace, float64(vm.Spec.Template.Memory))
case virtualMachineScratchType:
case VirtualMachineScratchType:
cpuOption.Value = vm.Spec.VMSpec.Cores
memoryOption.Value = uint64(vm.Spec.VMSpec.Memory)
metrics.SetVirtualMachineCPUCores(vmName, vm.Namespace, float64(vm.Spec.VMSpec.Cores))
Expand Down Expand Up @@ -1200,14 +1202,7 @@ func getVirtualMachine(vmName, nodeName string) (*proxmox.VirtualMachine, error)

func configureVirtualMachinePCI(vm *proxmoxv1alpha1.VirtualMachine) error {
// Get VM PCI spec and actual PCI configuration
vmType := CheckVMType(vm)
var PCIs []proxmoxv1alpha1.PciDevice
if vmType == virtualMachineTemplateType {
PCIs = vm.Spec.Template.PciDevices
}
if vmType == virtualMachineScratchType {
PCIs = vm.Spec.VMSpec.PciDevices
}
PCIs := getPciDevices(vm)
virtualMachinePCIs, err := GetPCIConfiguration(vm.Name, vm.Spec.NodeName)
if err != nil {
return err
Expand Down Expand Up @@ -1301,7 +1296,7 @@ func updatePCIConfig(ctx context.Context, vm *proxmoxv1alpha1.VirtualMachine,
// If the type is "mapped" then the value should be "mapped=deviceID"
var pciID string
if pci.Type == "mapped" {
pciID = "mapped=" + pci.DeviceID
pciID = "mapping=" + pci.DeviceID
} else {
pciID = pci.DeviceID
}
Expand Down Expand Up @@ -1332,6 +1327,7 @@ func updatePCIConfig(ctx context.Context, vm *proxmoxv1alpha1.VirtualMachine,
})
if err != nil {
log.Log.Error(err, "Error updating PCI configuration for VirtualMachine")
return err
}
_, taskCompleted, taskErr := taskID.WaitForCompleteStatus(ctx, 5, 3)
if !taskCompleted {
Expand Down Expand Up @@ -1402,39 +1398,87 @@ func revertVirtualMachineOption(vmName, nodeName, value string) error {
return taskErr
}

func RebootVM(vmName, nodeName string) error {
virtualMachine, err := getVirtualMachine(vmName, nodeName)
if err != nil {
log.Log.Error(err, "Error getting VM for rebooting")
}
// Reboot VM
task, err := virtualMachine.Reboot(ctx)
if err != nil {
log.Log.Error(err, "Error rebooting VirtualMachine %s", vmName)
}
_, taskCompleted, taskErr := task.WaitForCompleteStatus(ctx, 5, 3)
if !taskCompleted {
log.Log.Error(taskErr, "Error rebooting VirtualMachine %s", vmName)
}
return err
}

// TODO: Implement the function
// func ApplyAdditionalConfiguration(vm *proxmoxv1alpha1.VirtualMachine) error {
// // Get VirtualMachine
// VirtualMachine, err := getVirtualMachine(vm.Name, vm.Spec.NodeName)
// if err != nil {
// log.Log.Error(err, "Error getting VM for applying additional configuration")
// }
// // Apply additional configuration
// for key, value := range vm.Spec.AdditionalConfig {
// task, err := VirtualMachine.Config(ctx, proxmox.VirtualMachineOption{
// Name: key,
// Value: value,
// })
// if err != nil {
// log.Log.Error(err, "Error applying additional configuration")
// }
// _, taskCompleted, taskErr := task.WaitForCompleteStatus(ctx, 5, 3)
// if !taskCompleted {
// log.Log.Error(taskErr, "Error applying additional configuration")
// }
// }
// return nil
// }

// Helper functions

func getCores(vm *proxmoxv1alpha1.VirtualMachine) int {
if CheckVMType(vm) == virtualMachineTemplateType {
if CheckVMType(vm) == VirtualMachineTemplateType {
return vm.Spec.Template.Cores
}
return vm.Spec.VMSpec.Cores
}

func getMemory(vm *proxmoxv1alpha1.VirtualMachine) int {
if CheckVMType(vm) == virtualMachineTemplateType {
if CheckVMType(vm) == VirtualMachineTemplateType {
return vm.Spec.Template.Memory
}
return vm.Spec.VMSpec.Memory
}

func getSockets(vm *proxmoxv1alpha1.VirtualMachine) int {
if CheckVMType(vm) == virtualMachineTemplateType {
if CheckVMType(vm) == VirtualMachineTemplateType {
return vm.Spec.Template.Socket
}
return vm.Spec.VMSpec.Socket
}

func getDisks(vm *proxmoxv1alpha1.VirtualMachine) []proxmoxv1alpha1.VirtualMachineDisk {
if CheckVMType(vm) == virtualMachineTemplateType {
if CheckVMType(vm) == VirtualMachineTemplateType {
return vm.Spec.Template.Disk
}
return vm.Spec.VMSpec.Disk
}

func getNetworks(vm *proxmoxv1alpha1.VirtualMachine) *[]proxmoxv1alpha1.VirtualMachineNetwork {
if CheckVMType(vm) == virtualMachineTemplateType {
if CheckVMType(vm) == VirtualMachineTemplateType {
return vm.Spec.Template.Network
}
return vm.Spec.VMSpec.Network
}

func getPciDevices(vm *proxmoxv1alpha1.VirtualMachine) []proxmoxv1alpha1.PciDevice {
if CheckVMType(vm) == VirtualMachineTemplateType {
return vm.Spec.Template.PciDevices
}
return vm.Spec.VMSpec.PciDevices
}

0 comments on commit 43bb49c

Please sign in to comment.