1103 lines
29 KiB
Go
1103 lines
29 KiB
Go
package driver
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
"github.com/vmware/govmomi/find"
|
|
"github.com/vmware/govmomi/nfc"
|
|
"github.com/vmware/govmomi/object"
|
|
"github.com/vmware/govmomi/ovf"
|
|
"github.com/vmware/govmomi/property"
|
|
"github.com/vmware/govmomi/vapi/vcenter"
|
|
"github.com/vmware/govmomi/vim25/mo"
|
|
"github.com/vmware/govmomi/vim25/types"
|
|
)
|
|
|
|
type VirtualMachine interface {
|
|
Info(params ...string) (*mo.VirtualMachine, error)
|
|
Devices() (object.VirtualDeviceList, error)
|
|
FloppyDevices() (object.VirtualDeviceList, error)
|
|
Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error)
|
|
updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error)
|
|
AddPublicKeys(ctx context.Context, publicKeys string) error
|
|
Properties(ctx context.Context) (*mo.VirtualMachine, error)
|
|
Destroy() error
|
|
Configure(config *HardwareConfig) error
|
|
Customize(spec types.CustomizationSpec) error
|
|
ResizeDisk(diskSize int64) error
|
|
WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error)
|
|
PowerOn() error
|
|
PowerOff() error
|
|
IsPoweredOff() (bool, error)
|
|
StartShutdown() error
|
|
WaitForShutdown(ctx context.Context, timeout time.Duration) error
|
|
CreateSnapshot(name string) error
|
|
ConvertToTemplate() error
|
|
ImportOvfToContentLibrary(ovf vcenter.OVF) error
|
|
ImportToContentLibrary(template vcenter.Template) error
|
|
GetDir() (string, error)
|
|
AddFloppy(imgPath string) error
|
|
SetBootOrder(order []string) error
|
|
RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error
|
|
addDevice(device types.BaseVirtualDevice) error
|
|
AddConfigParams(params map[string]string, info *types.ToolsConfigInfo) error
|
|
Export() (*nfc.Lease, error)
|
|
CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error)
|
|
NewOvfManager() *ovf.Manager
|
|
GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error)
|
|
|
|
AddCdrom(controllerType string, datastoreIsoPath string) error
|
|
CreateCdrom(c *types.VirtualController) (*types.VirtualCdrom, error)
|
|
RemoveCdroms() error
|
|
EjectCdroms() error
|
|
AddSATAController() error
|
|
FindSATAController() (*types.VirtualAHCIController, error)
|
|
}
|
|
|
|
type VirtualMachineDriver struct {
|
|
vm *object.VirtualMachine
|
|
driver *VCenterDriver
|
|
}
|
|
|
|
type CloneConfig struct {
|
|
Name string
|
|
Folder string
|
|
Cluster string
|
|
Host string
|
|
ResourcePool string
|
|
Datastore string
|
|
LinkedClone bool
|
|
Network string
|
|
MacAddress string
|
|
Annotation string
|
|
VAppProperties map[string]string
|
|
StorageConfig StorageConfig
|
|
}
|
|
|
|
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
|
|
VGPUProfile string
|
|
Firmware string
|
|
ForceBIOSSetup bool
|
|
}
|
|
|
|
type NIC struct {
|
|
Network string // "" for default network
|
|
NetworkCard string // example: vmxnet3
|
|
MacAddress string // set mac if want specific address
|
|
Passthrough *bool // direct path i/o
|
|
}
|
|
|
|
type CreateConfig struct {
|
|
Annotation string
|
|
Name string
|
|
Folder string
|
|
Cluster string
|
|
Host string
|
|
ResourcePool string
|
|
Datastore string
|
|
GuestOS string // example: otherGuest
|
|
NICs []NIC
|
|
USBController []string
|
|
Version uint // example: 10
|
|
StorageConfig StorageConfig
|
|
}
|
|
|
|
func (d *VCenterDriver) NewVM(ref *types.ManagedObjectReference) VirtualMachine {
|
|
return &VirtualMachineDriver{
|
|
vm: object.NewVirtualMachine(d.client.Client, *ref),
|
|
driver: d,
|
|
}
|
|
}
|
|
|
|
func (d *VCenterDriver) FindVM(name string) (VirtualMachine, error) {
|
|
vm, err := d.finder.VirtualMachine(d.ctx, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &VirtualMachineDriver{
|
|
vm: vm,
|
|
driver: d,
|
|
}, nil
|
|
}
|
|
|
|
func (d *VCenterDriver) PreCleanVM(ui packersdk.Ui, vmPath string, force bool) error {
|
|
vm, err := d.FindVM(vmPath)
|
|
if err != nil {
|
|
if _, ok := err.(*find.NotFoundError); !ok {
|
|
return fmt.Errorf("error looking up old vm: %v", err)
|
|
}
|
|
}
|
|
if force && vm != nil {
|
|
ui.Say(fmt.Sprintf("the vm/template %s already exists, but deleting it due to -force flag", vmPath))
|
|
|
|
// power off just in case it is still on
|
|
vm.PowerOff()
|
|
|
|
err := vm.Destroy()
|
|
if err != nil {
|
|
return fmt.Errorf("error destroying %s: %v", vmPath, err)
|
|
}
|
|
}
|
|
if !force && vm != nil {
|
|
return fmt.Errorf("%s already exists, you can use -force flag to destroy it: %v", vmPath, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *VCenterDriver) 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.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{}
|
|
storageConfigSpec, err := config.StorageConfig.AddStorageDevices(devices)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
createSpec.DeviceChange = append(createSpec.DeviceChange, storageConfigSpec...)
|
|
|
|
devices, err = addNetwork(d, devices, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
t := true
|
|
for _, usbType := range config.USBController {
|
|
var usb types.BaseVirtualDevice
|
|
switch usbType {
|
|
// handle "true" and "1" for backwards compatibility
|
|
case "usb", "true", "1":
|
|
usb = &types.VirtualUSBController{
|
|
EhciEnabled: &t,
|
|
}
|
|
case "xhci":
|
|
usb = new(types.VirtualUSBXHCIController)
|
|
default:
|
|
continue
|
|
}
|
|
|
|
devices = append(devices, usb)
|
|
}
|
|
|
|
devicesConfigSpec, err := devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
createSpec.DeviceChange = append(createSpec.DeviceChange, devicesConfigSpec...)
|
|
|
|
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, ok := taskInfo.Result.(types.ManagedObjectReference)
|
|
if !ok {
|
|
return nil, fmt.Errorf("something went wrong when creating the VM")
|
|
}
|
|
|
|
return d.NewVM(&vmRef), nil
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) 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 *VirtualMachineDriver) Devices() (object.VirtualDeviceList, error) {
|
|
vmInfo, err := vm.Info("config.hardware.device")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return vmInfo.Config.Hardware.Device, nil
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) FloppyDevices() (object.VirtualDeviceList, error) {
|
|
device, err := vm.Devices()
|
|
if err != nil {
|
|
return device, err
|
|
}
|
|
floppies := device.SelectByType((*types.VirtualFloppy)(nil))
|
|
return floppies, nil
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig) (VirtualMachine, error) {
|
|
folder, err := vm.driver.FindFolder(config.Folder)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error finding filder: %s", err)
|
|
}
|
|
|
|
var relocateSpec types.VirtualMachineRelocateSpec
|
|
|
|
pool, err := vm.driver.FindResourcePool(config.Cluster, config.Host, config.ResourcePool)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error finding resource pool: %s", err)
|
|
}
|
|
poolRef := pool.pool.Reference()
|
|
relocateSpec.Pool = &poolRef
|
|
|
|
datastore, err := vm.driver.FindDatastore(config.Datastore, config.Host)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error finding datastore: %s", err)
|
|
}
|
|
datastoreRef := datastore.Reference()
|
|
relocateSpec.Datastore = &datastoreRef
|
|
|
|
var cloneSpec types.VirtualMachineCloneSpec
|
|
cloneSpec.Location = relocateSpec
|
|
cloneSpec.PowerOn = false
|
|
|
|
if config.LinkedClone == true {
|
|
cloneSpec.Location.DiskMoveType = "createNewChildDiskBacking"
|
|
|
|
tpl, err := vm.Info("snapshot")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error getting snapshot info for vm: %s", err)
|
|
}
|
|
if tpl.Snapshot == nil {
|
|
err = errors.New("`linked_clone=true`, but template has no snapshots")
|
|
return nil, err
|
|
}
|
|
cloneSpec.Snapshot = tpl.Snapshot.CurrentSnapshot
|
|
}
|
|
|
|
var configSpec types.VirtualMachineConfigSpec
|
|
cloneSpec.Config = &configSpec
|
|
|
|
if config.Annotation != "" {
|
|
configSpec.Annotation = config.Annotation
|
|
}
|
|
|
|
devices, err := vm.vm.Device(vm.driver.ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
virtualDisks := devices.SelectByType((*types.VirtualDisk)(nil))
|
|
virtualControllers := devices.SelectByType((*types.VirtualController)(nil))
|
|
|
|
// Use existing devices to avoid overlapping configuration
|
|
existingDevices := object.VirtualDeviceList{}
|
|
existingDevices = append(existingDevices, virtualDisks...)
|
|
existingDevices = append(existingDevices, virtualControllers...)
|
|
|
|
storageConfigSpec, err := config.StorageConfig.AddStorageDevices(existingDevices)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
configSpec.DeviceChange = append(configSpec.DeviceChange, storageConfigSpec...)
|
|
|
|
if config.Network != "" {
|
|
net, err := vm.driver.FindNetwork(config.Network)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error finding network: %s", err)
|
|
}
|
|
backing, err := net.network.EthernetCardBackingInfo(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error finding ethernet card backing info: %s", err)
|
|
}
|
|
|
|
devices, err := vm.vm.Device(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error finding vm devices: %s", err)
|
|
}
|
|
|
|
adapter, err := findNetworkAdapter(devices)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error finding network adapter: %s", err)
|
|
}
|
|
|
|
current := adapter.GetVirtualEthernetCard()
|
|
current.Backing = backing
|
|
current.MacAddress = config.MacAddress
|
|
|
|
config := &types.VirtualDeviceConfigSpec{
|
|
Device: adapter.(types.BaseVirtualDevice),
|
|
Operation: types.VirtualDeviceConfigSpecOperationEdit,
|
|
}
|
|
|
|
configSpec.DeviceChange = append(configSpec.DeviceChange, config)
|
|
}
|
|
|
|
vAppConfig, err := vm.updateVAppConfig(ctx, config.VAppProperties)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error updating VAppConfig: %s", err)
|
|
}
|
|
configSpec.VAppConfig = vAppConfig
|
|
|
|
task, err := vm.vm.Clone(vm.driver.ctx, folder.folder, config.Name, cloneSpec)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error calling vm.vm.Clone task: %s", 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, fmt.Errorf("Error waiting for vm Clone to complete: %s", err)
|
|
}
|
|
|
|
vmRef, ok := info.Result.(types.ManagedObjectReference)
|
|
if !ok {
|
|
return nil, fmt.Errorf("something went wrong when cloning the VM")
|
|
}
|
|
|
|
created := vm.driver.NewVM(&vmRef)
|
|
return created, nil
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) updateVAppConfig(ctx context.Context, newProps map[string]string) (*types.VmConfigSpec, error) {
|
|
if len(newProps) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
vProps, _ := vm.Properties(ctx)
|
|
if vProps.Config.VAppConfig == nil {
|
|
return nil, fmt.Errorf("this VM lacks a vApp configuration and cannot have vApp properties set on it")
|
|
}
|
|
|
|
allProperties := vProps.Config.VAppConfig.GetVmConfigInfo().Property
|
|
|
|
var props []types.VAppPropertySpec
|
|
for _, p := range allProperties {
|
|
userValue, setByUser := newProps[p.Id]
|
|
if !setByUser {
|
|
continue
|
|
}
|
|
|
|
if *p.UserConfigurable == false {
|
|
return nil, fmt.Errorf("vApp property with userConfigurable=false specified in vapp.properties: %+v", reflect.ValueOf(newProps).MapKeys())
|
|
}
|
|
|
|
prop := types.VAppPropertySpec{
|
|
ArrayUpdateSpec: types.ArrayUpdateSpec{
|
|
Operation: types.ArrayUpdateOperationEdit,
|
|
},
|
|
Info: &types.VAppPropertyInfo{
|
|
Key: p.Key,
|
|
Id: p.Id,
|
|
Value: userValue,
|
|
UserConfigurable: p.UserConfigurable,
|
|
},
|
|
}
|
|
props = append(props, prop)
|
|
|
|
delete(newProps, p.Id)
|
|
}
|
|
|
|
if len(newProps) > 0 {
|
|
return nil, fmt.Errorf("unsupported vApp properties in vapp.properties: %+v", reflect.ValueOf(newProps).MapKeys())
|
|
}
|
|
|
|
return &types.VmConfigSpec{
|
|
Property: props,
|
|
}, nil
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) AddPublicKeys(ctx context.Context, publicKeys string) error {
|
|
newProps := map[string]string{"public-keys": publicKeys}
|
|
config, err := vm.updateVAppConfig(ctx, newProps)
|
|
if err != nil {
|
|
return fmt.Errorf("not possible to save temporary public key: %s", err.Error())
|
|
}
|
|
|
|
confSpec := types.VirtualMachineConfigSpec{VAppConfig: config}
|
|
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 *VirtualMachineDriver) Properties(ctx context.Context) (*mo.VirtualMachine, error) {
|
|
log.Printf("fetching properties for VM %q", vm.vm.InventoryPath)
|
|
var props mo.VirtualMachine
|
|
if err := vm.vm.Properties(ctx, vm.vm.Reference(), nil, &props); err != nil {
|
|
return nil, err
|
|
}
|
|
return &props, nil
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) 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 *VirtualMachineDriver) 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)
|
|
}
|
|
if config.VGPUProfile != "" {
|
|
devices, err := vm.vm.Device(vm.driver.ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pciDevices := devices.SelectByType((*types.VirtualPCIPassthrough)(nil))
|
|
vGPUDevices := pciDevices.SelectByBackingInfo((*types.VirtualPCIPassthroughVmiopBackingInfo)(nil))
|
|
var operation types.VirtualDeviceConfigSpecOperation
|
|
if len(vGPUDevices) > 1 {
|
|
return err
|
|
} else if len(pciDevices) == 1 {
|
|
operation = types.VirtualDeviceConfigSpecOperationEdit
|
|
} else if len(pciDevices) == 0 {
|
|
operation = types.VirtualDeviceConfigSpecOperationAdd
|
|
}
|
|
|
|
vGPUProfile := newVGPUProfile(config.VGPUProfile)
|
|
spec := &types.VirtualDeviceConfigSpec{
|
|
Device: &vGPUProfile,
|
|
Operation: operation,
|
|
}
|
|
log.Printf("Adding vGPU device with profile '%s'", config.VGPUProfile)
|
|
confSpec.DeviceChange = append(confSpec.DeviceChange, spec)
|
|
}
|
|
|
|
efiSecureBootEnabled := false
|
|
firmware := config.Firmware
|
|
|
|
if firmware == "efi-secure" {
|
|
firmware = "efi"
|
|
efiSecureBootEnabled = true
|
|
}
|
|
|
|
confSpec.Firmware = firmware
|
|
confSpec.BootOptions = &types.VirtualMachineBootOptions{
|
|
EnterBIOSSetup: types.NewBool(config.ForceBIOSSetup),
|
|
EfiSecureBootEnabled: types.NewBool(efiSecureBootEnabled),
|
|
}
|
|
|
|
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 *VirtualMachineDriver) Customize(spec types.CustomizationSpec) error {
|
|
task, err := vm.vm.Customize(vm.driver.ctx, spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return task.Wait(vm.driver.ctx)
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) 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 (vm *VirtualMachineDriver) 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 *VirtualMachineDriver) WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error) {
|
|
netIP, err := vm.vm.WaitForNetIP(ctx, false)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for _, ips := range netIP {
|
|
for _, ip := range ips {
|
|
parseIP := net.ParseIP(ip)
|
|
if ipNet != nil && !ipNet.Contains(parseIP) {
|
|
// ip address is not in range
|
|
continue
|
|
}
|
|
// default to an ipv4 addresses if no ipNet is defined
|
|
if ipNet == nil && parseIP.To4() == nil {
|
|
continue
|
|
}
|
|
return ip, nil
|
|
}
|
|
}
|
|
|
|
// unable to find an IP
|
|
return "", nil
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) 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 *VirtualMachineDriver) IsPoweredOff() (bool, error) {
|
|
state, err := vm.vm.PowerState(vm.driver.ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return state == types.VirtualMachinePowerStatePoweredOff, nil
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) StartShutdown() error {
|
|
err := vm.vm.ShutdownGuest(vm.driver.ctx)
|
|
return err
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) WaitForShutdown(ctx context.Context, timeout time.Duration) error {
|
|
shutdownTimer := time.After(timeout)
|
|
for {
|
|
off, err := vm.IsPoweredOff()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if off {
|
|
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 *VirtualMachineDriver) 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 *VirtualMachineDriver) ConvertToTemplate() error {
|
|
return vm.vm.MarkAsTemplate(vm.driver.ctx)
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) ImportOvfToContentLibrary(ovf vcenter.OVF) error {
|
|
err := vm.driver.restClient.Login(vm.driver.ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
l, err := vm.driver.FindContentLibraryByName(ovf.Target.LibraryID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if l.library.Type != "LOCAL" {
|
|
return fmt.Errorf("can not deploy a VM to the content library %s of type %s; "+
|
|
"the content library must be of type LOCAL", ovf.Target.LibraryID, l.library.Type)
|
|
}
|
|
|
|
item, err := vm.driver.FindContentLibraryItem(l.library.ID, ovf.Spec.Name)
|
|
if err == nil {
|
|
// Updates existing library item
|
|
ovf.Target.LibraryItemID = item.ID
|
|
}
|
|
|
|
ovf.Target.LibraryID = l.library.ID
|
|
ovf.Source.Value = vm.vm.Reference().Value
|
|
ovf.Source.Type = "VirtualMachine"
|
|
|
|
vcm := vcenter.NewManager(vm.driver.restClient.client)
|
|
_, err = vcm.CreateOVF(vm.driver.ctx, ovf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return vm.driver.restClient.Logout(vm.driver.ctx)
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) ImportToContentLibrary(template vcenter.Template) error {
|
|
err := vm.driver.restClient.Login(vm.driver.ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
l, err := vm.driver.FindContentLibraryByName(template.Library)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if l.library.Type != "LOCAL" {
|
|
return fmt.Errorf("can not deploy a VM to the content library %s of type %s; "+
|
|
"the content library must be of type LOCAL", template.Library, l.library.Type)
|
|
}
|
|
|
|
template.Library = l.library.ID
|
|
template.SourceVM = vm.vm.Reference().Value
|
|
|
|
if template.Placement.Cluster != "" {
|
|
c, err := vm.driver.FindCluster(template.Placement.Cluster)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
template.Placement.Cluster = c.cluster.Reference().Value
|
|
}
|
|
if template.Placement.Folder != "" {
|
|
f, err := vm.driver.FindFolder(template.Placement.Folder)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
template.Placement.Folder = f.folder.Reference().Value
|
|
}
|
|
if template.Placement.Host != "" {
|
|
h, err := vm.driver.FindHost(template.Placement.Host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
template.Placement.Host = h.host.Reference().Value
|
|
}
|
|
if template.Placement.ResourcePool != "" {
|
|
rp, err := vm.driver.FindResourcePool(template.Placement.Cluster, template.Placement.Host, template.Placement.ResourcePool)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
template.Placement.ResourcePool = rp.pool.Reference().Value
|
|
}
|
|
|
|
if template.VMHomeStorage != nil {
|
|
d, err := vm.driver.FindDatastore(template.VMHomeStorage.Datastore, template.Placement.Host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
template.VMHomeStorage.Datastore = d.Reference().Value
|
|
}
|
|
|
|
vcm := vcenter.NewManager(vm.driver.restClient.client)
|
|
_, err = vcm.CreateTemplate(vm.driver.ctx, template)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return vm.driver.restClient.Logout(vm.driver.ctx)
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) 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.Contains(file.Name, vmInfo.Name) {
|
|
return RemoveDatastorePrefix(file.Name[:len(file.Name)-len(vmxName)]), nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("cannot find '%s'", vmxName)
|
|
}
|
|
|
|
func addNetwork(d *VCenterDriver, devices object.VirtualDeviceList, config *CreateConfig) (object.VirtualDeviceList, error) {
|
|
if len(config.NICs) == 0 {
|
|
return nil, errors.New("no network adapters have been defined")
|
|
}
|
|
|
|
for _, nic := range config.NICs {
|
|
network, err := findNetwork(nic.Network, config.Host, d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
backing, err := network.EthernetCardBackingInfo(d.ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
device, err := object.EthernetCardTypes().CreateEthernetCard(nic.NetworkCard, backing)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
card := device.(types.BaseVirtualEthernetCard).GetVirtualEthernetCard()
|
|
if nic.MacAddress != "" {
|
|
card.AddressType = string(types.VirtualEthernetCardMacTypeManual)
|
|
card.MacAddress = nic.MacAddress
|
|
}
|
|
card.UptCompatibilityEnabled = nic.Passthrough
|
|
|
|
devices = append(devices, device)
|
|
}
|
|
return devices, nil
|
|
}
|
|
|
|
func findNetwork(network string, host string, d *VCenterDriver) (object.NetworkReference, error) {
|
|
if network != "" {
|
|
var err error
|
|
networks, err := d.FindNetworks(network)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(networks) == 1 {
|
|
return networks[0].network, nil
|
|
}
|
|
|
|
// If there are multiple networks then try to match the host
|
|
if host != "" {
|
|
h, err := d.FindHost(host)
|
|
if err != nil {
|
|
return nil, &MultipleNetworkFoundError{network, fmt.Sprintf("unable to match a network to the host %s: %s", host, err.Error())}
|
|
}
|
|
for _, n := range networks {
|
|
info, err := n.Info("host")
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for _, host := range info.Host {
|
|
if h.host.Reference().Value == host.Reference().Value {
|
|
return n.network, nil
|
|
}
|
|
}
|
|
}
|
|
return nil, &MultipleNetworkFoundError{network, fmt.Sprintf("unable to match a network to the host %s", host)}
|
|
}
|
|
|
|
return nil, &MultipleNetworkFoundError{network, "please provide a host to match or the network full path"}
|
|
}
|
|
|
|
if host != "" {
|
|
h, err := d.FindHost(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")
|
|
}
|
|
|
|
return object.NewNetwork(d.client.Client, i.Network[0]), nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("Couldn't find network; 'host' and 'network' not specified. At least one of the two must be specified.")
|
|
}
|
|
|
|
func newVGPUProfile(vGPUProfile string) types.VirtualPCIPassthrough {
|
|
return types.VirtualPCIPassthrough{
|
|
VirtualDevice: types.VirtualDevice{
|
|
DeviceInfo: &types.Description{
|
|
Summary: "",
|
|
Label: fmt.Sprintf("New vGPU %v PCI device", vGPUProfile),
|
|
},
|
|
Backing: &types.VirtualPCIPassthroughVmiopBackingInfo{
|
|
Vgpu: vGPUProfile,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) AddCdrom(controllerType string, datastoreIsoPath 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 datastoreIsoPath != "" {
|
|
ds := &DatastoreIsoPath{path: datastoreIsoPath}
|
|
if !ds.Validate() {
|
|
return fmt.Errorf("%s is not a valid iso path", datastoreIsoPath)
|
|
}
|
|
if libPath, err := vm.driver.FindContentLibraryFileDatastorePath(ds.GetFilePath()); err == nil {
|
|
datastoreIsoPath = libPath
|
|
} else {
|
|
log.Printf("Using %s as the datastore path", datastoreIsoPath)
|
|
}
|
|
|
|
devices.InsertIso(cdrom, datastoreIsoPath)
|
|
}
|
|
|
|
log.Printf("Creating CD-ROM on controller '%v' with iso '%v'", controller, datastoreIsoPath)
|
|
return vm.addDevice(cdrom)
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) 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 *VirtualMachineDriver) 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 *VirtualMachineDriver) RemoveDevice(keepFiles bool, device ...types.BaseVirtualDevice) error {
|
|
return vm.vm.RemoveDevice(vm.driver.ctx, keepFiles, device...)
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) 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 *VirtualMachineDriver) AddConfigParams(params map[string]string, info *types.ToolsConfigInfo) 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
|
|
|
|
confSpec.Tools = info
|
|
|
|
if len(confSpec.ExtraConfig) > 0 || confSpec.Tools != nil {
|
|
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = task.WaitForResult(vm.driver.ctx, nil)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) Export() (*nfc.Lease, error) {
|
|
return vm.vm.Export(vm.driver.ctx)
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) {
|
|
return m.CreateDescriptor(vm.driver.ctx, vm.vm, cdp)
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) NewOvfManager() *ovf.Manager {
|
|
return ovf.NewManager(vm.vm.Client())
|
|
}
|
|
|
|
func (vm *VirtualMachineDriver) GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error) {
|
|
var mgr mo.OvfManager
|
|
err := property.DefaultCollector(vm.vm.Client()).RetrieveOne(vm.driver.ctx, m.Reference(), nil, &mgr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return mgr.OvfExportOption, nil
|
|
}
|
|
|
|
func findNetworkAdapter(l object.VirtualDeviceList) (types.BaseVirtualEthernetCard, error) {
|
|
c := l.SelectByType((*types.VirtualEthernetCard)(nil))
|
|
if len(c) == 0 {
|
|
return nil, errors.New("no network adapter device found")
|
|
}
|
|
|
|
return c[0].(types.BaseVirtualEthernetCard), nil
|
|
}
|