package driver import ( "context" "errors" "fmt" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" "strings" "time" ) type VirtualMachine struct { vm *object.VirtualMachine driver *Driver } type CloneConfig struct { Name string Folder string Cluster string Host string ResourcePool string Datastore string LinkedClone bool Annotation string } type HardwareConfig struct { CPUs int32 CpuCores int32 CPUReservation int64 CPULimit int64 RAM int64 RAMReservation int64 RAMReserveAll bool NestedHV bool CpuHotAddEnabled bool MemoryHotAddEnabled bool VideoRAM int64 } type CreateConfig struct { DiskThinProvisioned bool DiskControllerType string // example: "scsi", "pvscsi" DiskSize int64 Annotation string Name string Folder string Cluster string Host string ResourcePool string Datastore string GuestOS string // example: otherGuest Network string // "" for default network NetworkCard string // example: vmxnet3 USBController bool Version uint // example: 10 Firmware string // efi or bios } func (d *Driver) NewVM(ref *types.ManagedObjectReference) *VirtualMachine { return &VirtualMachine{ vm: object.NewVirtualMachine(d.client.Client, *ref), driver: d, } } func (d *Driver) FindVM(name string) (*VirtualMachine, error) { vm, err := d.finder.VirtualMachine(d.ctx, name) if err != nil { return nil, err } return &VirtualMachine{ vm: vm, driver: d, }, nil } func (d *Driver) CreateVM(config *CreateConfig) (*VirtualMachine, error) { createSpec := types.VirtualMachineConfigSpec{ Name: config.Name, Annotation: config.Annotation, GuestId: config.GuestOS, } if config.Version != 0 { createSpec.Version = fmt.Sprintf("%s%d", "vmx-", config.Version) } if config.Firmware != "" { createSpec.Firmware = config.Firmware } folder, err := d.FindFolder(config.Folder) if err != nil { return nil, err } resourcePool, err := d.FindResourcePool(config.Cluster, config.Host, config.ResourcePool) if err != nil { return nil, err } var host *object.HostSystem if config.Cluster != "" && config.Host != "" { h, err := d.FindHost(config.Host) if err != nil { return nil, err } host = h.host } datastore, err := d.FindDatastore(config.Datastore, config.Host) if err != nil { return nil, err } devices := object.VirtualDeviceList{} devices, err = addDisk(d, devices, config) if err != nil { return nil, err } devices, err = addNetwork(d, devices, config) if err != nil { return nil, err } if config.USBController { t := true usb := &types.VirtualUSBController{ EhciEnabled: &t, } devices = append(devices, usb) } createSpec.DeviceChange, err = devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd) if err != nil { return nil, err } createSpec.Files = &types.VirtualMachineFileInfo{ VmPathName: fmt.Sprintf("[%s]", datastore.Name()), } task, err := folder.folder.CreateVM(d.ctx, createSpec, resourcePool.pool, host) if err != nil { return nil, err } taskInfo, err := task.WaitForResult(d.ctx, nil) if err != nil { return nil, err } vmRef := taskInfo.Result.(types.ManagedObjectReference) return d.NewVM(&vmRef), nil } func (vm *VirtualMachine) Info(params ...string) (*mo.VirtualMachine, error) { var p []string if len(params) == 0 { p = []string{"*"} } else { p = params } var info mo.VirtualMachine err := vm.vm.Properties(vm.driver.ctx, vm.vm.Reference(), p, &info) if err != nil { return nil, err } return &info, nil } func (vm *VirtualMachine) Devices() (object.VirtualDeviceList, error) { vmInfo, err := vm.Info("config.hardware.device") if err != nil { return nil, err } return vmInfo.Config.Hardware.Device, nil } func (template *VirtualMachine) Clone(ctx context.Context, config *CloneConfig) (*VirtualMachine, error) { folder, err := template.driver.FindFolder(config.Folder) if err != nil { return nil, err } var relocateSpec types.VirtualMachineRelocateSpec pool, err := template.driver.FindResourcePool(config.Cluster, config.Host, config.ResourcePool) if err != nil { return nil, err } poolRef := pool.pool.Reference() relocateSpec.Pool = &poolRef datastore, err := template.driver.FindDatastore(config.Datastore, config.Host) if err != nil { return nil, err } datastoreRef := datastore.ds.Reference() relocateSpec.Datastore = &datastoreRef var cloneSpec types.VirtualMachineCloneSpec cloneSpec.Location = relocateSpec cloneSpec.PowerOn = false if config.LinkedClone == true { cloneSpec.Location.DiskMoveType = "createNewChildDiskBacking" tpl, err := template.Info("snapshot") if err != nil { return nil, err } if tpl.Snapshot == nil { err = errors.New("`linked_clone=true`, but template has no snapshots") return nil, err } cloneSpec.Snapshot = tpl.Snapshot.CurrentSnapshot } if config.Annotation != "" { var configSpec types.VirtualMachineConfigSpec configSpec.Annotation = config.Annotation cloneSpec.Config = &configSpec } task, err := template.vm.Clone(template.driver.ctx, folder.folder, config.Name, cloneSpec) if err != nil { return nil, err } info, err := task.WaitForResult(ctx, nil) if err != nil { if ctx.Err() == context.Canceled { err = task.Cancel(context.TODO()) return nil, err } return nil, err } vmRef := info.Result.(types.ManagedObjectReference) vm := template.driver.NewVM(&vmRef) return vm, nil } func (vm *VirtualMachine) Destroy() error { task, err := vm.vm.Destroy(vm.driver.ctx) if err != nil { return err } _, err = task.WaitForResult(vm.driver.ctx, nil) return err } func (vm *VirtualMachine) Configure(config *HardwareConfig) error { var confSpec types.VirtualMachineConfigSpec confSpec.NumCPUs = config.CPUs confSpec.NumCoresPerSocket = config.CpuCores confSpec.MemoryMB = config.RAM var cpuSpec types.ResourceAllocationInfo cpuSpec.Reservation = &config.CPUReservation if config.CPULimit != 0 { cpuSpec.Limit = &config.CPULimit } confSpec.CpuAllocation = &cpuSpec var ramSpec types.ResourceAllocationInfo ramSpec.Reservation = &config.RAMReservation confSpec.MemoryAllocation = &ramSpec confSpec.MemoryReservationLockedToMax = &config.RAMReserveAll confSpec.NestedHVEnabled = &config.NestedHV confSpec.CpuHotAddEnabled = &config.CpuHotAddEnabled confSpec.MemoryHotAddEnabled = &config.MemoryHotAddEnabled if config.VideoRAM != 0 { devices, err := vm.vm.Device(vm.driver.ctx) if err != nil { return err } l := devices.SelectByType((*types.VirtualMachineVideoCard)(nil)) if len(l) != 1 { return err } card := l[0].(*types.VirtualMachineVideoCard) card.VideoRamSizeInKB = config.VideoRAM spec := &types.VirtualDeviceConfigSpec{ Device: card, Operation: types.VirtualDeviceConfigSpecOperationEdit, } confSpec.DeviceChange = append(confSpec.DeviceChange, spec) } task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec) if err != nil { return err } _, err = task.WaitForResult(vm.driver.ctx, nil) return err } func (vm *VirtualMachine) ResizeDisk(diskSize int64) error { var confSpec types.VirtualMachineConfigSpec devices, err := vm.vm.Device(vm.driver.ctx) if err != nil { return err } disk, err := findDisk(devices) if err != nil { return err } disk.CapacityInKB = diskSize * 1024 confSpec.DeviceChange = []types.BaseVirtualDeviceConfigSpec{ &types.VirtualDeviceConfigSpec{ Device: disk, Operation: types.VirtualDeviceConfigSpecOperationEdit, }, } task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec) if err != nil { return err } _, err = task.WaitForResult(vm.driver.ctx, nil) return err } func findDisk(devices object.VirtualDeviceList) (*types.VirtualDisk, error) { var disks []*types.VirtualDisk for _, device := range devices { switch d := device.(type) { case *types.VirtualDisk: disks = append(disks, d) } } switch len(disks) { case 0: return nil, errors.New("VM has no disks") case 1: return disks[0], nil } return nil, errors.New("VM has multiple disks") } func (vm *VirtualMachine) PowerOn() error { task, err := vm.vm.PowerOn(vm.driver.ctx) if err != nil { return err } _, err = task.WaitForResult(vm.driver.ctx, nil) return err } func (vm *VirtualMachine) WaitForIP(ctx context.Context) (string, error) { return vm.vm.WaitForIP(ctx) } func (vm *VirtualMachine) PowerOff() error { state, err := vm.vm.PowerState(vm.driver.ctx) if err != nil { return err } if state == types.VirtualMachinePowerStatePoweredOff { return nil } task, err := vm.vm.PowerOff(vm.driver.ctx) if err != nil { return err } _, err = task.WaitForResult(vm.driver.ctx, nil) return err } func (vm *VirtualMachine) StartShutdown() error { err := vm.vm.ShutdownGuest(vm.driver.ctx) return err } func (vm *VirtualMachine) WaitForShutdown(ctx context.Context, timeout time.Duration) error { shutdownTimer := time.After(timeout) for { powerState, err := vm.vm.PowerState(vm.driver.ctx) if err != nil { return err } if powerState == "poweredOff" { break } select { case <-shutdownTimer: err := errors.New("Timeout while waiting for machine to shut down.") return err case <-ctx.Done(): return nil default: time.Sleep(1 * time.Second) } } return nil } func (vm *VirtualMachine) CreateSnapshot(name string) error { task, err := vm.vm.CreateSnapshot(vm.driver.ctx, name, "", false, false) if err != nil { return err } _, err = task.WaitForResult(vm.driver.ctx, nil) return err } func (vm *VirtualMachine) ConvertToTemplate() error { return vm.vm.MarkAsTemplate(vm.driver.ctx) } func (vm *VirtualMachine) GetDir() (string, error) { vmInfo, err := vm.Info("name", "layoutEx.file") if err != nil { return "", err } vmxName := fmt.Sprintf("/%s.vmx", vmInfo.Name) for _, file := range vmInfo.LayoutEx.File { if strings.HasSuffix(file.Name, vmxName) { return RemoveDatastorePrefix(file.Name[:len(file.Name)-len(vmxName)]), nil } } return "", fmt.Errorf("cannot find '%s'", vmxName) } func addDisk(_ *Driver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) { device, err := devices.CreateSCSIController(config.DiskControllerType) if err != nil { return nil, err } devices = append(devices, device) controller, err := devices.FindDiskController(devices.Name(device)) if err != nil { return nil, err } disk := &types.VirtualDisk{ VirtualDevice: types.VirtualDevice{ Key: devices.NewKey(), Backing: &types.VirtualDiskFlatVer2BackingInfo{ DiskMode: string(types.VirtualDiskModePersistent), ThinProvisioned: types.NewBool(config.DiskThinProvisioned), }, }, CapacityInKB: config.DiskSize * 1024, } devices.AssignController(disk, controller) devices = append(devices, disk) return devices, nil } func addNetwork(d *Driver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) { var network object.NetworkReference if config.Network == "" { h, err := d.FindHost(config.Host) if err != nil { return nil, err } i, err := h.Info("network") if err != nil { return nil, err } if len(i.Network) > 1 { return nil, fmt.Errorf("Host has multiple networks. Specify it explicitly") } network = object.NewNetwork(d.client.Client, i.Network[0]) } else { var err error network, err = d.finder.Network(d.ctx, config.Network) if err != nil { return nil, err } } backing, err := network.EthernetCardBackingInfo(d.ctx) if err != nil { return nil, err } device, err := object.EthernetCardTypes().CreateEthernetCard(config.NetworkCard, backing) if err != nil { return nil, err } return append(devices, device), nil } func addIDE(devices object.VirtualDeviceList) (object.VirtualDeviceList, error) { ideDevice, err := devices.CreateIDEController() if err != nil { return nil, err } devices = append(devices, ideDevice) return devices, nil } func (vm *VirtualMachine) AddCdrom(controllerType string, isoPath string) error { devices, err := vm.vm.Device(vm.driver.ctx) if err != nil { return err } var controller *types.VirtualController if controllerType == "sata" { c, err := vm.FindSATAController() if err != nil { return err } controller = c.GetVirtualController() } else { c, err := devices.FindIDEController("") if err != nil { return err } controller = c.GetVirtualController() } cdrom, err := vm.CreateCdrom(controller) if err != nil { return err } if isoPath != "" { devices.InsertIso(cdrom, isoPath) } return vm.addDevice(cdrom) } func (vm *VirtualMachine) AddFloppy(imgPath string) error { devices, err := vm.vm.Device(vm.driver.ctx) if err != nil { return err } floppy, err := devices.CreateFloppy() if err != nil { return err } if imgPath != "" { floppy = devices.InsertImg(floppy, imgPath) } return vm.addDevice(floppy) } func (vm *VirtualMachine) SetBootOrder(order []string) error { devices, err := vm.vm.Device(vm.driver.ctx) if err != nil { return err } bootOptions := types.VirtualMachineBootOptions{ BootOrder: devices.BootOrder(order), } return vm.vm.SetBootOptions(vm.driver.ctx, &bootOptions) } func (vm *VirtualMachine) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error { return vm.vm.RemoveDevice(vm.driver.ctx, keepFiles, device...) } func (vm *VirtualMachine) addDevice(device types.BaseVirtualDevice) error { newDevices := object.VirtualDeviceList{device} confSpec := types.VirtualMachineConfigSpec{} var err error confSpec.DeviceChange, err = newDevices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd) if err != nil { return err } task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec) if err != nil { return err } _, err = task.WaitForResult(vm.driver.ctx, nil) return err } func (vm *VirtualMachine) AddConfigParams(params map[string]string) error { var confSpec types.VirtualMachineConfigSpec var ov []types.BaseOptionValue for k, v := range params { o := types.OptionValue{ Key: k, Value: v, } ov = append(ov, &o) } confSpec.ExtraConfig = ov task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec) if err != nil { return err } _, err = task.WaitForResult(vm.driver.ctx, nil) return err }