diff --git a/builder_acc_test.go b/builder_acc_test.go index faa660ade..43b496da8 100644 --- a/builder_acc_test.go +++ b/builder_acc_test.go @@ -1,13 +1,13 @@ package main import ( - "testing" - builderT "github.com/hashicorp/packer/helper/builder/testing" + "encoding/json" "fmt" + builderT "github.com/hashicorp/packer/helper/builder/testing" "github.com/hashicorp/packer/packer" - "encoding/json" - "math/rand" "github.com/jetbrains-infra/packer-builder-vsphere/driver" + "math/rand" + "testing" ) func TestBuilderAcc_default(t *testing.T) { @@ -74,7 +74,7 @@ func checkDefault(t *testing.T, name string, host string, datastore string) buil t.Fatalf("Cannot read resource pool name: %v", err) } if poolPath != "" { - t.Error("Invalid resource pool: expected '/', got '%v'", poolPath) + t.Errorf("Invalid resource pool: expected '/', got '%v'", poolPath) } dsr := vmInfo.Datastore[0].Reference() diff --git a/driver/datastore_acc_test.go b/driver/datastore_acc_test.go new file mode 100644 index 000000000..290ae3342 --- /dev/null +++ b/driver/datastore_acc_test.go @@ -0,0 +1,22 @@ +package driver + +import ( + "testing" +) + +func TestDatastoreAcc(t *testing.T) { + initDriverAcceptanceTest(t) + + d := newTestDriver(t) + ds, err := d.FindDatastore("datastore1") + if err != nil { + t.Fatalf("Cannot find the default datastore '%v': %v", "datastore1", err) + } + info, err := ds.Info("name") + if err != nil { + t.Fatalf("Cannot read datastore properties: %v", err) + } + if info.Name != "datastore1" { + t.Errorf("Wrong datastore. expected: 'datastore1', got: '%v'", info.Name) + } +} diff --git a/driver/driver_test.go b/driver/driver_test.go new file mode 100644 index 000000000..e7e347e68 --- /dev/null +++ b/driver/driver_test.go @@ -0,0 +1,46 @@ +package driver + +import ( + "os" + "fmt" + "testing" + "time" + "math/rand" +) + +// Defines whether acceptance tests should be run +const TestEnvVar = "VSPHERE_DRIVER_ACC" +const hostName = "esxi-1.vsphere55.test" + +func newTestDriver(t *testing.T) *Driver { + d, err := NewDriver(&ConnectConfig{ + VCenterServer: "vcenter.vsphere55.test", + Username: "root", + Password: "jetbrains", + InsecureConnection: true, + }) + if err != nil { + t.Fatalf("Cannot connect: %v", err) + } + return d +} + +func newVMName() string { + rand.Seed(time.Now().UTC().UnixNano()) + return fmt.Sprintf("test-%v", rand.Intn(1000)) +} + +func initDriverAcceptanceTest(t *testing.T) { + // We only run acceptance tests if an env var is set because they're + // slow and require outside configuration. + if os.Getenv(TestEnvVar) == "" { + t.Skip(fmt.Sprintf( + "Acceptance tests skipped unless env '%s' set", + TestEnvVar)) + } + + // We require verbose mode so that the user knows what is going on. + if !testing.Verbose() { + t.Fatal("Acceptance tests must be run with the -v flag on tests") + } +} diff --git a/driver/folder_acc_test.go b/driver/folder_acc_test.go new file mode 100644 index 000000000..220f1195e --- /dev/null +++ b/driver/folder_acc_test.go @@ -0,0 +1,20 @@ +package driver + +import "testing" + +func TestFolderAcc(t *testing.T) { + initDriverAcceptanceTest(t) + + d := newTestDriver(t) + f, err := d.FindFolder("folder1/folder2") + if err != nil { + t.Fatalf("Cannot find the default folder '%v': %v", "folder1/folder2", err) + } + path, err := f.Path() + if err != nil { + t.Fatalf("Cannot read folder name: %v", err) + } + if path != "folder1/folder2" { + t.Errorf("Wrong folder. expected: 'folder1/folder2', got: '%v'", path) + } +} diff --git a/driver/host_acc_test.go b/driver/host_acc_test.go new file mode 100644 index 000000000..bba4da7cf --- /dev/null +++ b/driver/host_acc_test.go @@ -0,0 +1,23 @@ +package driver + +import ( + "testing" +) + +func TestHostAcc(t *testing.T) { + initDriverAcceptanceTest(t) + + d := newTestDriver(t) + host, err := d.FindHost(hostName) + if err != nil { + t.Fatalf("Cannot find the default host '%v': %v", "datastore1", err) + } + + info, err := host.Info("name") + 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) + } +} diff --git a/driver/resource_pool_acc_test.go b/driver/resource_pool_acc_test.go new file mode 100644 index 000000000..a1f2b47bd --- /dev/null +++ b/driver/resource_pool_acc_test.go @@ -0,0 +1,21 @@ +package driver + +import "testing" + +func TestResourcePoolAcc(t *testing.T) { + initDriverAcceptanceTest(t) + + d := newTestDriver(t) + p, err := d.FindResourcePool("esxi-1.vsphere55.test", "pool1/pool2") + if err != nil { + t.Fatalf("Cannot find the default resource pool '%v': %v", "pool1/pool2", err) + } + + path, err := p.Path() + if err != nil { + t.Fatalf("Cannot read resource pool name: %v", err) + } + if path != "pool1/pool2" { + t.Errorf("Wrong folder. expected: 'pool1/pool2', got: '%v'", path) + } +} diff --git a/driver/test.sh b/driver/test.sh new file mode 100755 index 000000000..14607e82e --- /dev/null +++ b/driver/test.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +export VSPHERE_DRIVER_ACC=1 +go test -v "$@" diff --git a/driver/vm.go b/driver/vm.go index 4c23904b0..a4d6734cf 100644 --- a/driver/vm.go +++ b/driver/vm.go @@ -1,12 +1,12 @@ package driver import ( + "errors" + "fmt" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" - "errors" "time" - "fmt" ) type VirtualMachine struct { @@ -90,7 +90,7 @@ func (template *VirtualMachine) Clone(config *CloneConfig) (*VirtualMachine, err info, err := host.Info("datastore") if err != nil { - return nil, err + return nil, err } if len(info.Datastore) > 1 { @@ -225,11 +225,7 @@ func (vm *VirtualMachine) PowerOn() error { } func (vm *VirtualMachine) WaitForIP() (string, error) { - ip, err := vm.vm.WaitForIP(vm.driver.ctx) - if err != nil { - return "", err - } - return ip, nil + return vm.vm.WaitForIP(vm.driver.ctx) } func (vm *VirtualMachine) PowerOff() error { @@ -287,6 +283,5 @@ func (vm *VirtualMachine) CreateSnapshot(name string) error { } func (vm *VirtualMachine) ConvertToTemplate() error { - err := vm.vm.MarkAsTemplate(vm.driver.ctx) - return err + return vm.vm.MarkAsTemplate(vm.driver.ctx) } diff --git a/driver/vm_acc_test.go b/driver/vm_acc_test.go new file mode 100644 index 000000000..ccde75b47 --- /dev/null +++ b/driver/vm_acc_test.go @@ -0,0 +1,280 @@ +package driver + +import ( + "log" + "net" + "testing" + "time" +) + +func TestVMAcc_clone(t *testing.T) { + initDriverAcceptanceTest(t) + + testCases := []struct { + name string + config *CloneConfig + checkFunction func(*testing.T, *VirtualMachine, *CloneConfig) + }{ + {"Default", &CloneConfig{}, cloneDefaultCheck}, + {"LinkedClone", &CloneConfig{LinkedClone: true}, cloneLinkedCloneCheck}, + {"Folder", &CloneConfig{LinkedClone: true, Folder: "folder1/folder2"}, cloneFolderCheck}, + {"ResourcePool", &CloneConfig{LinkedClone: true, ResourcePool: "pool1/pool2"}, cloneResourcePoolCheck}, + {"Configure", &CloneConfig{LinkedClone: true}, configureCheck}, + {"Configure_RAMReserveAll", &CloneConfig{LinkedClone: true}, configureRAMReserveAllCheck}, + {"StartAndStop", &CloneConfig{LinkedClone: true}, startAndStopCheck}, + {"Template", &CloneConfig{LinkedClone: true}, templateCheck}, + {"Snapshot", &CloneConfig{}, snapshotCheck}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tc.config.Host = hostName + tc.config.Name = newVMName() + + templateName := "alpine" + d := newTestDriver(t) + + template, err := d.FindVM(templateName) // Don't destroy this VM! + if err != nil { + t.Fatalf("Cannot find template vm '%v': %v", templateName, err) + } + + log.Printf("[DEBUG] Clonning VM") + vm, err := template.Clone(tc.config) + if err != nil { + 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) + } + }() + + log.Printf("[DEBUG] Running check function") + tc.checkFunction(t, vm, tc.config) + }) + } +} + +func cloneDefaultCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { + 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 != hostName { + t.Errorf("Invalid host name: expected '%v', got '%v'", hostName, 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 '%v', got '%v'", "datastore1", dsInfo.Name) + } + + if len(vmInfo.LayoutEx.Disk[0].Chain) != 1 { + t.Error("Not a full clone") + } +} + +func configureCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) { + log.Printf("[DEBUG] Configuring the vm") + hwConfig := &HardwareConfig{ + CPUs: 2, + CPUReservation: 1000, + CPULimit: 1500, + RAM: 2048, + RAMReservation: 1024, + } + vm.Configure(hwConfig) + + log.Printf("[DEBUG] Running checks") + vmInfo, err := vm.Info("config") + if err != nil { + t.Fatalf("Cannot read VM properties: %v", err) + } + + cpuSockets := vmInfo.Config.Hardware.NumCPU + if cpuSockets != hwConfig.CPUs { + t.Errorf("VM should have %v CPU sockets, got %v", hwConfig.CPUs, cpuSockets) + } + + cpuReservation := vmInfo.Config.CpuAllocation.GetResourceAllocationInfo().Reservation + if cpuReservation != hwConfig.CPUReservation { + t.Errorf("VM should have CPU reservation for %v Mhz, got %v", hwConfig.CPUReservation, cpuReservation) + } + + cpuLimit := vmInfo.Config.CpuAllocation.GetResourceAllocationInfo().Limit + if cpuLimit != hwConfig.CPULimit { + t.Errorf("VM should have CPU reservation for %v Mhz, got %v", hwConfig.CPULimit, cpuLimit) + } + + ram := vmInfo.Config.Hardware.MemoryMB + if int64(ram) != hwConfig.RAM { + t.Errorf("VM should have %v MB of RAM, got %v", hwConfig.RAM, ram) + } + + ramReservation := vmInfo.Config.MemoryAllocation.GetResourceAllocationInfo().Reservation + if ramReservation != hwConfig.RAMReservation { + t.Errorf("VM should have RAM reservation for %v MB, got %v", hwConfig.RAMReservation, ramReservation) + } +} + +func configureRAMReserveAllCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) { + log.Printf("[DEBUG] Configuring the vm") + vm.Configure(&HardwareConfig{ RAMReserveAll: true }) + + log.Printf("[DEBUG] Running checks") + vmInfo, err := vm.Info("config") + if err != nil { + t.Fatalf("Cannot read VM properties: %v", err) + } + + if *vmInfo.Config.MemoryReservationLockedToMax != true { + t.Errorf("VM should have all RAM reserved") + } +} + +func cloneLinkedCloneCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) { + vmInfo, err := vm.Info("layoutEx.disk") + if err != nil { + t.Fatalf("Cannot read VM properties: %v", err) + } + + if len(vmInfo.LayoutEx.Disk[0].Chain) != 2 { + t.Error("Not a linked clone") + } +} + +func cloneFolderCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { + vmInfo, err := vm.Info("parent") + if err != nil { + t.Fatalf("Cannot read VM properties: %v", err) + } + + f := vm.driver.NewFolder(vmInfo.Parent) + path, err := f.Path() + if err != nil { + t.Fatalf("Cannot read folder name: %v", err) + } + if path != config.Folder { + t.Errorf("Wrong folder. expected: %v, got: %v", config.Folder, path) + } +} + +func cloneResourcePoolCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { + vmInfo, err := vm.Info("resourcePool") + if err != nil { + t.Fatalf("Cannot read VM properties: %v", err) + } + + p := vm.driver.NewResourcePool(vmInfo.ResourcePool) + path, err := p.Path() + if err != nil { + t.Fatalf("Cannot read resource pool name: %v", err) + } + if path != config.ResourcePool { + t.Errorf("Wrong folder. expected: %v, got: %v", config.ResourcePool, path) + } +} + +func startAndStopCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { + stopper := startVM(t, vm, config.Name) + defer stopper() + + switch ip, err := vm.WaitForIP(); { + case err != nil: + t.Errorf("Cannot obtain IP address from created vm '%v': %v", config.Name, err) + case net.ParseIP(ip) == nil: + t.Errorf("'%v' is not a valid ip address", ip) + } + + vm.StartShutdown() + log.Printf("[DEBUG] Waiting max 1m0s for shutdown to complete") + vm.WaitForShutdown(1 * time.Minute) +} + +func snapshotCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { + stopper := startVM(t, vm, config.Name) + defer stopper() + + vm.CreateSnapshot("test-snapshot") + + vmInfo, err := vm.Info("layoutEx.disk") + if err != nil { + t.Fatalf("Cannot read VM properties: %v", err) + } + + layers := len(vmInfo.LayoutEx.Disk[0].Chain) + if layers != 2 { + t.Errorf("VM should have a single snapshot. expected 2 disk layers, got %v", layers) + } +} + +func templateCheck(t *testing.T, vm *VirtualMachine, _ *CloneConfig) { + vm.ConvertToTemplate() + vmInfo, err := vm.Info("config.template") + if err != nil { + t.Errorf("Cannot read VM properties: %v", err) + } else if !vmInfo.Config.Template { + t.Error("Not a template") + } +} + +func startVM(t *testing.T, vm *VirtualMachine, vmName string) (stopper func()) { + log.Printf("[DEBUG] Starting the vm") + if err := vm.PowerOn(); err != nil { + t.Fatalf("Cannot start vm '%v': %v", vmName, err) + } + return func() { + log.Printf("[DEBUG] Powering off the vm") + if err := vm.PowerOff(); err != nil { + t.Errorf("Cannot power off started vm '%v': %v", vmName, err) + } + } +} diff --git a/test.sh b/test.sh index 0c7683736..dd91fee3a 100755 --- a/test.sh +++ b/test.sh @@ -1,4 +1,4 @@ #!/bin/sh export PACKER_ACC=1 -go test -v +go test -v "$@"