Add basic functionality of creating VMs to the driver
This commit is contained in:
parent
e91ac3ef1b
commit
f909669670
|
@ -29,6 +29,17 @@ func (d *Driver) FindDatastore(name string) (*Datastore, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (d *Driver) FindDatastoreOrDefault(name string) (*Datastore, error) {
|
||||
ds, err := d.finder.DatastoreOrDefault(d.ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Datastore{
|
||||
ds: ds,
|
||||
driver: d,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) Info(params ...string) (*mo.Datastore, error) {
|
||||
var p []string
|
||||
if len(params) == 0 {
|
||||
|
@ -43,3 +54,16 @@ func (ds *Datastore) Info(params ...string) (*mo.Datastore, error) {
|
|||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) FileExists(path string) bool {
|
||||
_, err := ds.ds.Stat(ds.driver.ctx, path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) Name() string {
|
||||
return ds.ds.Name()
|
||||
}
|
||||
|
||||
func (ds *Datastore) ResolvePath(path string) string {
|
||||
return ds.ds.Path(path)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
// Defines whether acceptance tests should be run
|
||||
const TestEnvVar = "VSPHERE_DRIVER_ACC"
|
||||
const hostName = "esxi-1.vsphere65.test"
|
||||
const TestHostName = "esxi-1.vsphere65.test"
|
||||
|
||||
func newTestDriver(t *testing.T) *Driver {
|
||||
d, err := NewDriver(&ConnectConfig{
|
||||
|
|
|
@ -8,7 +8,7 @@ func TestHostAcc(t *testing.T) {
|
|||
initDriverAcceptanceTest(t)
|
||||
|
||||
d := newTestDriver(t)
|
||||
host, err := d.FindHost(hostName)
|
||||
host, err := d.FindHost(TestHostName)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot find the default host '%v': %v", "datastore1", err)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ func TestHostAcc(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Cannot read host properties: %v", err)
|
||||
}
|
||||
if info.Name != hostName {
|
||||
t.Errorf("Wrong host name: expected '%v', got: '%v'", hostName, info.Name)
|
||||
if info.Name != TestHostName {
|
||||
t.Errorf("Wrong host name: expected '%v', got: '%v'", TestHostName, info.Name)
|
||||
}
|
||||
}
|
||||
|
|
257
driver/vm.go
257
driver/vm.go
|
@ -34,6 +34,22 @@ type HardwareConfig struct {
|
|||
NestedHV bool
|
||||
}
|
||||
|
||||
type CreateConfig struct {
|
||||
HardwareConfig
|
||||
|
||||
DiskThinProvisioned bool
|
||||
DiskControllerType string // example: "scsi", "pvscsi"
|
||||
|
||||
Annotation string
|
||||
Name string
|
||||
Folder string
|
||||
Host string
|
||||
ResourcePool string
|
||||
Datastore string
|
||||
GuestOS string // example: otherGuest
|
||||
Overwrite bool
|
||||
}
|
||||
|
||||
func (d *Driver) NewVM(ref *types.ManagedObjectReference) *VirtualMachine {
|
||||
return &VirtualMachine{
|
||||
vm: object.NewVirtualMachine(d.client.Client, *ref),
|
||||
|
@ -52,6 +68,75 @@ func (d *Driver) FindVM(name string) (*VirtualMachine, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (d *Driver) CreateVM(config *CreateConfig) (*VirtualMachine, error) {
|
||||
createSpec := config.toConfigSpec()
|
||||
|
||||
folder, err := d.FindFolder(config.Folder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourcePool, err := d.FindResourcePool(config.Host, config.ResourcePool)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host, err := d.FindHost(config.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
datastore, err := d.FindDatastoreOrDefault(config.Datastore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !config.Overwrite {
|
||||
vmxPath := fmt.Sprintf("%s/%s.vmx", config.Name, config.Name)
|
||||
if datastore.FileExists(vmxPath) {
|
||||
dsPath := datastore.ResolvePath(vmxPath)
|
||||
return nil, fmt.Errorf("File '%s' already exists", dsPath)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.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 {
|
||||
|
@ -82,32 +167,12 @@ func (template *VirtualMachine) Clone(config *CloneConfig) (*VirtualMachine, err
|
|||
poolRef := pool.pool.Reference()
|
||||
relocateSpec.Pool = &poolRef
|
||||
|
||||
if config.Datastore == "" {
|
||||
host, err := template.driver.FindHost(config.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := host.Info("datastore")
|
||||
if err != nil {
|
||||
datastore, err := template.driver.FindDatastoreOrDefault(config.Datastore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(info.Datastore) > 1 {
|
||||
return nil, fmt.Errorf("Target host has several datastores. Specify 'datastore' parameter explicitly")
|
||||
}
|
||||
|
||||
ref := info.Datastore[0].Reference()
|
||||
relocateSpec.Datastore = &ref
|
||||
} else {
|
||||
ds, err := template.driver.FindDatastore(config.Datastore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref := ds.ds.Reference()
|
||||
relocateSpec.Datastore = &ref
|
||||
}
|
||||
datastoreRef := datastore.ds.Reference()
|
||||
relocateSpec.Datastore = &datastoreRef
|
||||
|
||||
var cloneSpec types.VirtualMachineCloneSpec
|
||||
cloneSpec.Location = relocateSpec
|
||||
|
@ -137,8 +202,8 @@ func (template *VirtualMachine) Clone(config *CloneConfig) (*VirtualMachine, err
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ref := info.Result.(types.ManagedObjectReference)
|
||||
vm := template.driver.NewVM(&ref)
|
||||
vmRef := info.Result.(types.ManagedObjectReference)
|
||||
vm := template.driver.NewVM(&vmRef)
|
||||
return vm, nil
|
||||
}
|
||||
|
||||
|
@ -152,21 +217,7 @@ func (vm *VirtualMachine) Destroy() error {
|
|||
}
|
||||
|
||||
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 := config.toConfigSpec()
|
||||
|
||||
if config.DiskSize > 0 {
|
||||
devices, err := vm.vm.Device(vm.driver.ctx)
|
||||
|
@ -179,7 +230,7 @@ func (vm *VirtualMachine) Configure(config *HardwareConfig) error {
|
|||
return err
|
||||
}
|
||||
|
||||
disk.CapacityInKB = config.DiskSize * 1024 * 1024 // Gb
|
||||
disk.CapacityInKB = convertGiBToKiB(config.DiskSize)
|
||||
|
||||
confSpec.DeviceChange = []types.BaseVirtualDeviceConfigSpec{
|
||||
&types.VirtualDeviceConfigSpec{
|
||||
|
@ -193,6 +244,7 @@ func (vm *VirtualMachine) Configure(config *HardwareConfig) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = task.WaitForResult(vm.driver.ctx, nil)
|
||||
return err
|
||||
}
|
||||
|
@ -285,3 +337,124 @@ func (vm *VirtualMachine) CreateSnapshot(name string) error {
|
|||
func (vm *VirtualMachine) ConvertToTemplate() error {
|
||||
return vm.vm.MarkAsTemplate(vm.driver.ctx)
|
||||
}
|
||||
|
||||
func (config HardwareConfig) toConfigSpec() types.VirtualMachineConfigSpec {
|
||||
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
|
||||
|
||||
return confSpec
|
||||
}
|
||||
|
||||
func (config CreateConfig) toConfigSpec() types.VirtualMachineConfigSpec {
|
||||
confSpec := config.HardwareConfig.toConfigSpec()
|
||||
confSpec.Name = config.Name
|
||||
confSpec.Annotation = config.Annotation
|
||||
confSpec.GuestId = config.GuestOS
|
||||
return confSpec
|
||||
}
|
||||
|
||||
func addDisk(d *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: convertGiBToKiB(config.DiskSize),
|
||||
}
|
||||
|
||||
devices.AssignController(disk, controller)
|
||||
devices = append(devices, disk)
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func addNetwork(d *Driver, devices object.VirtualDeviceList) (object.VirtualDeviceList, error) {
|
||||
network, err := d.finder.DefaultNetwork(d.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
backing, err := network.EthernetCardBackingInfo(d.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
device, err := object.EthernetCardTypes().CreateEthernetCard("", 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
|
||||
}
|
||||
ide, err := devices.FindIDEController("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cdrom, err := devices.CreateCdrom(ide)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isoPath != "" {
|
||||
cdrom = devices.InsertIso(cdrom, isoPath)
|
||||
}
|
||||
|
||||
newDevices := object.VirtualDeviceList{cdrom}
|
||||
confSpec := types.VirtualMachineConfigSpec{}
|
||||
confSpec.DeviceChange, err = newDevices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
|
||||
|
||||
task, err := vm.vm.Reconfigure(vm.driver.ctx, confSpec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = task.WaitForResult(vm.driver.ctx, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func convertGiBToKiB(gib int64) int64 {
|
||||
return gib * 1024 * 1024
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ func TestVMAcc_clone(t *testing.T) {
|
|||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.config.Host = hostName
|
||||
tc.config.Host = TestHostName
|
||||
tc.config.Name = newVMName()
|
||||
|
||||
templateName := "alpine"
|
||||
|
@ -45,17 +45,7 @@ func TestVMAcc_clone(t *testing.T) {
|
|||
t.Fatalf("Cannot clone vm '%v': %v", templateName, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
log.Printf("[DEBUG] Removing the clone")
|
||||
if err := vm.Destroy(); err != nil {
|
||||
t.Errorf("!!! ERROR REMOVING VM '%v': %v!!!", tc.config.Name, err)
|
||||
}
|
||||
|
||||
// Check that the clone is no longer exists
|
||||
if _, err := d.FindVM(tc.config.Name); err == nil {
|
||||
t.Errorf("!!! STILL CAN FIND VM '%v'. IT MIGHT NOT HAVE BEEN DELETED !!!", tc.config.Name)
|
||||
}
|
||||
}()
|
||||
defer destroyVM(t, vm, tc.config.Name)
|
||||
|
||||
log.Printf("[DEBUG] Running check function")
|
||||
tc.checkFunction(t, vm, tc.config)
|
||||
|
@ -94,8 +84,8 @@ func cloneDefaultCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) {
|
|||
if err != nil {
|
||||
t.Fatal("Cannot read host properties: ", err)
|
||||
}
|
||||
if hostInfo.Name != hostName {
|
||||
t.Errorf("Invalid host name: expected '%v', got '%v'", hostName, hostInfo.Name)
|
||||
if hostInfo.Name != TestHostName {
|
||||
t.Errorf("Invalid host name: expected '%v', got '%v'", TestHostName, hostInfo.Name)
|
||||
}
|
||||
|
||||
p := d.NewResourcePool(vmInfo.ResourcePool)
|
||||
|
@ -278,3 +268,15 @@ func startVM(t *testing.T, vm *VirtualMachine, vmName string) (stopper func()) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func destroyVM(t *testing.T, vm *VirtualMachine, vmName string) {
|
||||
log.Printf("[DEBUG] Deleting the VM")
|
||||
if err := vm.Destroy(); err != nil {
|
||||
t.Errorf("!!! ERROR DELETING VM '%v': %v!!!", vmName, err)
|
||||
}
|
||||
|
||||
// Check that the clone is no longer exists
|
||||
if _, err := vm.driver.FindVM(vmName); err == nil {
|
||||
t.Errorf("!!! STILL CAN FIND VM '%v'. IT MIGHT NOT HAVE BEEN DELETED !!!", vmName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package driver
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVMAcc_create(t *testing.T) {
|
||||
initDriverAcceptanceTest(t)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
config *CreateConfig
|
||||
checkFunction func(*testing.T, *VirtualMachine, *CreateConfig)
|
||||
}{
|
||||
{"MinimalConfiguration", &CreateConfig{}, createDefaultCheck},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.config.Host = TestHostName
|
||||
tc.config.Name = newVMName()
|
||||
|
||||
d := newTestDriver(t)
|
||||
|
||||
log.Printf("[DEBUG] Creating VM")
|
||||
vm, err := d.CreateVM(tc.config)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot create VM: %v", err)
|
||||
}
|
||||
|
||||
defer destroyVM(t, vm, tc.config.Name)
|
||||
|
||||
log.Printf("[DEBUG] Adding CDRom to the created VM")
|
||||
vm.AddCdrom("[datastore1] ISO/alpine-standard-3.6.2-x86_64.iso")
|
||||
|
||||
log.Printf("[DEBUG] Running check function")
|
||||
tc.checkFunction(t, vm, tc.config)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createDefaultCheck(t *testing.T, vm *VirtualMachine, config *CreateConfig) {
|
||||
d := vm.driver
|
||||
|
||||
// Check that the clone can be found by its name
|
||||
if _, err := d.FindVM(config.Name); err != nil {
|
||||
t.Errorf("Cannot find created vm '%v': %v", config.Name, err)
|
||||
}
|
||||
|
||||
vmInfo, err := vm.Info("name", "parent", "runtime.host", "resourcePool", "datastore", "layoutEx.disk")
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot read VM properties: %v", err)
|
||||
}
|
||||
|
||||
if vmInfo.Name != config.Name {
|
||||
t.Errorf("Invalid VM name: expected '%v', got '%v'", config.Name, vmInfo.Name)
|
||||
}
|
||||
|
||||
f := d.NewFolder(vmInfo.Parent)
|
||||
folderPath, err := f.Path()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot read folder name: %v", err)
|
||||
}
|
||||
if folderPath != "" {
|
||||
t.Errorf("Invalid folder: expected '/', got '%v'", folderPath)
|
||||
}
|
||||
|
||||
h := d.NewHost(vmInfo.Runtime.Host)
|
||||
hostInfo, err := h.Info("name")
|
||||
if err != nil {
|
||||
t.Fatal("Cannot read host properties: ", err)
|
||||
}
|
||||
if hostInfo.Name != TestHostName {
|
||||
t.Errorf("Invalid host name: expected '%v', got '%v'", TestHostName, hostInfo.Name)
|
||||
}
|
||||
|
||||
p := d.NewResourcePool(vmInfo.ResourcePool)
|
||||
poolPath, err := p.Path()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot read resource pool name: %v", err)
|
||||
}
|
||||
if poolPath != "" {
|
||||
t.Errorf("Invalid resource pool: expected '/', got '%v'", poolPath)
|
||||
}
|
||||
|
||||
dsr := vmInfo.Datastore[0].Reference()
|
||||
ds := d.NewDatastore(&dsr)
|
||||
dsInfo, err := ds.Info("name")
|
||||
if err != nil {
|
||||
t.Fatal("Cannot read datastore properties: ", err)
|
||||
}
|
||||
if dsInfo.Name != "datastore1" {
|
||||
t.Errorf("Invalid datastore name: expected 'datastore1', got '%v'", dsInfo.Name)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue