570 lines
13 KiB
Go
570 lines
13 KiB
Go
package driver
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/vmware/govmomi/object"
|
|
"github.com/vmware/govmomi/vim25/mo"
|
|
"github.com/vmware/govmomi/vim25/types"
|
|
"time"
|
|
"strings"
|
|
"context"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
type HardwareConfig struct {
|
|
CPUs int32
|
|
CPUReservation int64
|
|
CPULimit int64
|
|
RAM int64
|
|
RAMReservation int64
|
|
RAMReserveAll bool
|
|
NestedHV bool
|
|
CpuHotAddEnabled bool
|
|
MemoryHotAddEnabled bool
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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.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 = addIDE(devices)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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
|
|
}
|
|
|
|
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.MemoryMB = config.RAM
|
|
|
|
var cpuSpec types.ResourceAllocationInfo
|
|
cpuSpec.Reservation = &config.CPUReservation
|
|
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
|
|
|
|
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) {
|
|
network, err := d.finder.NetworkOrDefault(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(isoPath string) error {
|
|
devices, err := vm.vm.Device(vm.driver.ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sata, err := vm.FindSATAController()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cdrom, err := vm.CreateCdrom(sata)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if isoPath != "" {
|
|
cdrom = 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
|
|
}
|