package iso import ( "context" "errors" "io/ioutil" "path" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/packer/builder/vsphere/common" "github.com/hashicorp/packer/builder/vsphere/driver" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer-plugin-sdk/multistep" ) func TestCreateConfig_Prepare(t *testing.T) { // Empty config - check defaults config := &CreateConfig{ // Storage is required StorageConfig: common.StorageConfig{ Storage: []common.DiskConfig{ { DiskSize: 32768, }, }, }, } if errs := config.Prepare(); len(errs) != 0 { t.Fatalf("Config preprare should not fail: %s", errs[0]) } if config.GuestOSType != "otherGuest" { t.Fatalf("GuestOSType should default to 'otherGuest'") } if len(config.StorageConfig.DiskControllerType) != 1 { t.Fatalf("DiskControllerType should have at least one element as default") } // Data validation tc := []struct { name string config *CreateConfig fail bool expectedErrMsg string }{ { name: "Storage validate disk_size", config: &CreateConfig{ StorageConfig: common.StorageConfig{ Storage: []common.DiskConfig{ { DiskSize: 0, DiskThinProvisioned: true, }, }, }, }, fail: true, expectedErrMsg: "storage[0].'disk_size' is required", }, { name: "Storage validate disk_controller_index", config: &CreateConfig{ StorageConfig: common.StorageConfig{ Storage: []common.DiskConfig{ { DiskSize: 32768, DiskControllerIndex: 3, }, }, }, }, fail: true, expectedErrMsg: "storage[0].'disk_controller_index' references an unknown disk controller", }, { name: "USBController validate 'usb' and 'xhci' can be set together", config: &CreateConfig{ USBController: []string{"usb", "xhci"}, StorageConfig: common.StorageConfig{ Storage: []common.DiskConfig{ { DiskSize: 32768, }, }, }, }, fail: false, }, { name: "USBController validate '1' and '0' can be set together", config: &CreateConfig{ USBController: []string{"1", "0"}, StorageConfig: common.StorageConfig{ Storage: []common.DiskConfig{ { DiskSize: 32768, }, }, }, }, fail: false, }, { name: "USBController validate 'true' and 'false' can be set together", config: &CreateConfig{ USBController: []string{"true", "false"}, StorageConfig: common.StorageConfig{ Storage: []common.DiskConfig{ { DiskSize: 32768, }, }, }, }, fail: false, }, { name: "USBController validate 'true' and 'usb' cannot be set together", config: &CreateConfig{ USBController: []string{"true", "usb"}, StorageConfig: common.StorageConfig{ Storage: []common.DiskConfig{ { DiskSize: 32768, }, }, }, }, fail: true, expectedErrMsg: "there can only be one usb controller and one xhci controller", }, { name: "USBController validate '1' and 'usb' cannot be set together", config: &CreateConfig{ USBController: []string{"1", "usb"}, StorageConfig: common.StorageConfig{ Storage: []common.DiskConfig{ { DiskSize: 32768, }, }, }, }, fail: true, expectedErrMsg: "there can only be one usb controller and one xhci controller", }, { name: "USBController validate 'xhci' cannot be set more that once", config: &CreateConfig{ USBController: []string{"xhci", "xhci"}, StorageConfig: common.StorageConfig{ Storage: []common.DiskConfig{ { DiskSize: 32768, }, }, }, }, fail: true, expectedErrMsg: "there can only be one usb controller and one xhci controller", }, { name: "USBController validate unknown value cannot be set", config: &CreateConfig{ USBController: []string{"unknown"}, StorageConfig: common.StorageConfig{ Storage: []common.DiskConfig{ { DiskSize: 32768, }, }, }, }, fail: true, expectedErrMsg: "usb_controller[0] references an unknown usb controller", }, } for _, c := range tc { errs := c.config.Prepare() if c.fail { if len(errs) == 0 { t.Fatalf("Config preprare should fail") } if errs[0].Error() != c.expectedErrMsg { t.Fatalf("Expected error message: %s but was '%s'", c.expectedErrMsg, errs[0].Error()) } } else { if len(errs) != 0 { t.Fatalf("Config preprare should not fail: %s", errs[0]) } } } } func TestStepCreateVM_Run(t *testing.T) { state := basicStateBag() driverMock := driver.NewDriverMock() state.Put("driver", driverMock) step := basicStepCreateVM() step.Force = true vmPath := path.Join(step.Location.Folder, step.Location.VMName) if action := step.Run(context.TODO(), state); action == multistep.ActionHalt { t.Fatalf("Should not halt.") } // Pre clean VM if !driverMock.PreCleanVMCalled { t.Fatalf("driver.PreCleanVM should be called.") } if driverMock.PreCleanForce != step.Force { t.Fatalf("Force PreCleanVM should be %t but was %t.", step.Force, driverMock.PreCleanForce) } if driverMock.PreCleanVMPath != vmPath { t.Fatalf("VM path expected to be %s but was %s", vmPath, driverMock.PreCleanVMPath) } if !driverMock.CreateVMCalled { t.Fatalf("driver.CreateVM should be called.") } if diff := cmp.Diff(driverMock.CreateConfig, driverCreateConfig(step.Config, step.Location)); diff != "" { t.Fatalf("wrong driver.CreateConfig: %s", diff) } vm, ok := state.GetOk("vm") if !ok { t.Fatal("state must contain the VM") } if vm != driverMock.VM { t.Fatalf("state doesn't contain the created VM.") } } func TestStepCreateVM_RunHalt(t *testing.T) { state := basicStateBag() step := basicStepCreateVM() // PreCleanVM fails driverMock := driver.NewDriverMock() driverMock.PreCleanShouldFail = true state.Put("driver", driverMock) if action := step.Run(context.TODO(), state); action != multistep.ActionHalt { t.Fatalf("Step should halt.") } if !driverMock.PreCleanVMCalled { t.Fatalf("driver.PreCleanVM should be called") } // CreateVM fails driverMock = driver.NewDriverMock() driverMock.CreateVMShouldFail = true state.Put("driver", driverMock) if action := step.Run(context.TODO(), state); action != multistep.ActionHalt { t.Fatalf("Step should halt.") } if !driverMock.PreCleanVMCalled { t.Fatalf("driver.PreCleanVM should be called") } if !driverMock.CreateVMCalled { t.Fatalf("driver.PreCleanVM should be called") } if _, ok := state.GetOk("vm"); ok { t.Fatal("state should not contain a VM") } } func TestStepCreateVM_Cleanup(t *testing.T) { state := basicStateBag() step := basicStepCreateVM() vm := new(driver.VirtualMachineMock) state.Put("vm", vm) // Clean up when state is cancelled state.Put(multistep.StateCancelled, true) step.Cleanup(state) if !vm.DestroyCalled { t.Fatalf("vm.Destroy should be called") } vm.DestroyCalled = false state.Remove(multistep.StateCancelled) // Clean up when state is halted state.Put(multistep.StateHalted, true) step.Cleanup(state) if !vm.DestroyCalled { t.Fatalf("vm.Destroy should be called") } vm.DestroyCalled = false state.Remove(multistep.StateHalted) // Clean up when state is destroy_vm is set state.Put("destroy_vm", true) step.Cleanup(state) if !vm.DestroyCalled { t.Fatalf("vm.Destroy should be called") } vm.DestroyCalled = false state.Remove("destroy_vm") // Don't clean up if state is not set with previous values step.Cleanup(state) if vm.DestroyCalled { t.Fatalf("vm.Destroy should not be called") } // Destroy fail errorBuffer := &strings.Builder{} ui := &packer.BasicUi{ Reader: strings.NewReader(""), Writer: ioutil.Discard, ErrorWriter: errorBuffer, } state.Put("ui", ui) state.Put(multistep.StateCancelled, true) vm.DestroyError = errors.New("destroy failed") step.Cleanup(state) if !vm.DestroyCalled { t.Fatalf("vm.Destroy should be called") } if !strings.Contains(errorBuffer.String(), vm.DestroyError.Error()) { t.Fatalf("Destroy should fail with error message '%s' but failed with '%s'", vm.DestroyError.Error(), errorBuffer.String()) } vm.DestroyCalled = false state.Remove(multistep.StateCancelled) // Should not destroy if VM is not set state.Remove("vm") state.Put(multistep.StateCancelled, true) step.Cleanup(state) if vm.DestroyCalled { t.Fatalf("vm.Destroy should not be called") } } func basicStepCreateVM() *StepCreateVM { step := &StepCreateVM{ Config: createConfig(), Location: basicLocationConfig(), } return step } func basicLocationConfig() *common.LocationConfig { return &common.LocationConfig{ VMName: "test-vm", Folder: "test-folder", Cluster: "test-cluster", Host: "test-host", ResourcePool: "test-resource-pool", Datastore: "test-datastore", } } func createConfig() *CreateConfig { return &CreateConfig{ Version: 1, GuestOSType: "ubuntu64Guest", StorageConfig: common.StorageConfig{ DiskControllerType: []string{"pvscsi"}, Storage: []common.DiskConfig{ { DiskSize: 32768, DiskThinProvisioned: true, }, }, }, NICs: []NIC{ { Network: "VM Network", NetworkCard: "vmxnet3", }, }, } } func driverCreateConfig(config *CreateConfig, location *common.LocationConfig) *driver.CreateConfig { var networkCards []driver.NIC for _, nic := range config.NICs { networkCards = append(networkCards, driver.NIC{ Network: nic.Network, NetworkCard: nic.NetworkCard, MacAddress: nic.MacAddress, Passthrough: nic.Passthrough, }) } var disks []driver.Disk for _, disk := range config.StorageConfig.Storage { disks = append(disks, driver.Disk{ DiskSize: disk.DiskSize, DiskEagerlyScrub: disk.DiskEagerlyScrub, DiskThinProvisioned: disk.DiskThinProvisioned, ControllerIndex: disk.DiskControllerIndex, }) } return &driver.CreateConfig{ StorageConfig: driver.StorageConfig{ DiskControllerType: config.StorageConfig.DiskControllerType, Storage: disks, }, Annotation: config.Notes, Name: location.VMName, Folder: location.Folder, Cluster: location.Cluster, Host: location.Host, ResourcePool: location.ResourcePool, Datastore: location.Datastore, GuestOS: config.GuestOSType, NICs: networkCards, USBController: config.USBController, Version: config.Version, } }