Add basic functionality of creating VMs to the driver

This commit is contained in:
Andrei Tonkikh 2018-01-24 16:35:04 +03:00
parent e91ac3ef1b
commit f909669670
6 changed files with 355 additions and 60 deletions

View File

@ -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)
}

View File

@ -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{

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}
}