From e35b883fe101107297884e6737a8947f0d12e7a3 Mon Sep 17 00:00:00 2001 From: Camilo Aguilar Date: Thu, 8 Jan 2015 21:36:46 -0500 Subject: [PATCH] Work in progress --- osx-builder.go | 5 +- pkg/vmware/fusion7.go | 62 +++++++ pkg/vmware/vmware.go | 81 +++++++++ vms/error.go | 2 +- vms/image.go | 7 +- vms/vm.go | 389 ++++++++++++------------------------------ vms/web.go | 44 +---- 7 files changed, 264 insertions(+), 326 deletions(-) create mode 100644 pkg/vmware/fusion7.go create mode 100644 pkg/vmware/vmware.go diff --git a/osx-builder.go b/osx-builder.go index 0836ac1..1acca9a 100644 --- a/osx-builder.go +++ b/osx-builder.go @@ -37,8 +37,5 @@ func init() { func main() { address := ":" + config.Port - err := http.ListenAndServe(address, nil) - if err != nil { - log.Fatal("ListenAndServe: ", err) - } + log.Fatal(http.ListenAndServe(address, nil)) } diff --git a/pkg/vmware/fusion7.go b/pkg/vmware/fusion7.go new file mode 100644 index 0000000..5ee2add --- /dev/null +++ b/pkg/vmware/fusion7.go @@ -0,0 +1,62 @@ +package vmware + +import ( + "fmt" + "os" +) + +type Fusion7 struct{} + +func (v *Fusion7) vmrunPath() (string, error) { + vmrun := os.Getenv("VMWARE_VMRUN_PATH") + + if vmrun != "" { + return vmrun, nil + } + + // Guess it is in the default installation path + vmrun = "/Applications/VMware Fusion.app/Contents/Library/vmrun" + if _, err := os.Stat(vmrun); err != nil { + if os.IsNotExist(err) { + return "", fmt.Errorf("VMWare vmrun not found at path: %s", vmrun) + } + } + + return vmrun, nil +} + +func (v *Fusion7) Info(vmxfile string) (*VMInfo, error) { + return nil, nil +} + +func (v *Fusion7) SetInfo(info *VMInfo) error { + return nil +} + +func (v *Fusion7) Clone(vmxfile, dstfile string, ctype CloneType) error { + return nil +} + +func (v *Fusion7) Start(vmxfile string, gui bool) error { + return nil +} + +func (v *Fusion7) Stop(vmxfile string) error { + return nil +} + +func (v *Fusion7) Delete(vmxfile string) error { + return nil +} + +func (v *Fusion7) IsRunning(vmxfile string) (bool, error) { + return false, nil +} + +func (v *Fusion7) HasToolsInstalled(vmxfile string) (bool, error) { + return false, nil +} + +func (v *Fusion7) IPAddress(vmxfile string) (string, error) { + return "", nil +} diff --git a/pkg/vmware/vmware.go b/pkg/vmware/vmware.go new file mode 100644 index 0000000..7460c3c --- /dev/null +++ b/pkg/vmware/vmware.go @@ -0,0 +1,81 @@ +package vmware + +import ( + "bytes" + "fmt" + "log" + "os/exec" + "strings" +) + +type NetworkType string + +const ( + NetworkHostOnly NetworkType = "hostonly" + NetworkNAT NetworkType = "nat" + NetworkBridged NetworkType = "bridged" +) + +type CloneType string + +const ( + CloneFull CloneType = "full" + CloneLinked CloneType = "linked" +) + +type NetworkAdapter struct { + NetType NetworkType +} + +type VMInfo struct { + Name string + Annotation string + MemorySize int + CPUs int + NetworkAdapters []NetworkAdapter +} + +type VMManager interface { + vmrunPath() (string, error) + Info(vmxfile string) (*VMInfo, error) + SetInfo(info *VMInfo) error + Clone(vmxfile, dstfile string, ctype CloneType) error + Start(vmxfile string, gui bool) error + Stop(vmxfile string) error + Delete(vmxfile string) error + IsRunning(vmxfile string) (bool, error) + HasToolsInstalled(vmxfile string) (bool, error) + IPAddress(vmxfile string) (string, error) +} + +// Borrowed from https://github.com/mitchellh/packer/blob/master/builder/vmware/common/driver.go +func runAndLog(cmd *exec.Cmd) (string, string, error) { + var stdout, stderr bytes.Buffer + + log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:]) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + + stdoutString := strings.TrimSpace(stdout.String()) + stderrString := strings.TrimSpace(stderr.String()) + + if _, ok := err.(*exec.ExitError); ok { + message := stderrString + if message == "" { + message = stdoutString + } + + err = fmt.Errorf("VMware error: %s", message) + } + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + // Replace these for Windows, we only want to deal with Unix + // style line endings. + returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1) + returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1) + + return returnStdout, returnStderr, err +} diff --git a/vms/error.go b/vms/error.go index 7af95a6..1ea1fb7 100644 --- a/vms/error.go +++ b/vms/error.go @@ -3,7 +3,7 @@ package vms import ( "net/http" - "github.com/c4milo/go-osx-builder/apperror" + "github.com/c4milo/osx-builder/apperror" ) var ErrInternal = apperror.Error{ diff --git a/vms/image.go b/vms/image.go index b45f1f5..e3a4a50 100644 --- a/vms/image.go +++ b/vms/image.go @@ -6,6 +6,7 @@ import ( "crypto/sha256" "crypto/sha512" "crypto/tls" + "errors" "fmt" "hash" "io" @@ -37,15 +38,15 @@ type Image struct { // Downloads and a virtual machine image func (img *Image) Download(destPath string) error { if img.URL == "" { - panic("URL is required") + return errors.New("Image URL is required") } if img.Checksum == "" { - panic("Checksum is required") + return errors.New("Image checksum is required") } if img.ChecksumType == "" { - panic("Checksum type is required") + return errors.New("Image checksum type is required") } if destPath == "" { diff --git a/vms/vm.go b/vms/vm.go index 9b3f536..82a2df1 100644 --- a/vms/vm.go +++ b/vms/vm.go @@ -3,87 +3,73 @@ package vms import ( "encoding/base64" "encoding/json" + "errors" "fmt" "io/ioutil" "log" "os" "path/filepath" "runtime/debug" - "strings" - "time" "github.com/c4milo/osx-builder/config" "github.com/c4milo/osx-builder/pkg/unzipit" - "github.com/dustin/go-humanize" - govix "github.com/hooklift/govix" + "github.com/c4milo/osx-builder/pkg/vmware" ) -// Virtual machine configuration -type VM struct { - // VMX file path for Internal use only - VMXFile string `json:"-"` +type VMConfig struct { // ID of the virtual machine ID string `json:"id"` // Image to use during the creation of this virtual machine - Image Image `json:"image"` + OSImage Image `json:"image"` // Number of virtual cpus - CPUs uint `json:"cpus"` + CPUs int `json:"cpus"` // Memory size in megabytes. - Memory string `json:"memory"` - // Whether to upgrade the VM virtual hardware - UpgradeVHardware bool `json:"-"` - // The timeout to wait for VMware Tools to be initialized inside the VM - ToolsInitTimeout time.Duration `json:"tools_init_timeout"` - // Whether to launch the VM with graphical environment - LaunchGUI bool `json:"launch_gui"` + Memory int `json:"memory"` // Network adapters - VNetworkAdapters []*govix.NetworkAdapter `json:"-"` - // VM IP address as reported by VIX + Network vmware.NetworkType `json:"network_type"` + // Whether to launch the VM with graphical environment + GUI bool `json:"gui"` +} + +// Virtual machine configuration +type VM struct { + VMConfig + // Underlined virtual machine manager + manager vmware.VMManager + // Internal reference to the VM's vmx file + vmxfile string `json:"-"` + // VM IP address as reported by VMWare IPAddress string `json:"ip_address"` // Power status Status string `json:"status"` - // Guest OS - GuestOS string `json:"guest_os"` } -// Keeps a reference to a VMWare host connection -var VMwareClient *govix.Host +func NewVM(vmcfg VMConfig) *VM { + vmxfile := filepath.Join(config.VMSPath, vmcfg.ID, vmcfg.ID+".vmx") -func init() { - fmt.Printf("[INFO] Connecting to VMware...") - host, err := govix.Connect(govix.ConnectConfig{ - Provider: govix.VMWARE_WORKSTATION, - }) - - if err != nil { - log.Fatalln(err.Error()) + return &VM{ + VMConfig: vmcfg, + manager: new(vmware.Fusion7), + vmxfile: vmxfile, } - - fmt.Println(" OK") - - VMwareClient = host } // Sets default values for VM attributes -func (v *VM) SetDefaults() { +func (v *VM) setDefaults() { if v.CPUs <= 0 { v.CPUs = 2 } - if v.Memory == "" { - v.Memory = "512mib" - } - - if v.ToolsInitTimeout.Seconds() <= 0 { - v.ToolsInitTimeout = time.Duration(30) * time.Second + if v.Memory < 512 { + v.Memory = 512 } } // Downloads OS image, creates and launches a virtual machine. -func (v *VM) Create() (string, error) { - log.Printf("[DEBUG] Creating VM resource...") +func (v *VM) Create() error { + log.Printf("[DEBUG] Creating VM %s", v.ID) - image := v.Image + image := v.OSImage goldPath := filepath.Join(config.GoldImgsPath, image.Checksum) _, err := os.Stat(goldPath) @@ -95,85 +81,71 @@ func (v *VM) Create() (string, error) { imgPath := filepath.Join(config.ImagesPath, image.Checksum) if err = image.Download(imgPath); err != nil { - return "", err + return err } defer image.file.Close() // Makes sure file cursor is in the right position. _, err := image.file.Seek(0, 0) if err != nil { - return "", err + return err } - log.Printf("[DEBUG] Unpacking Gold virtual machine into %s\n", goldPath) + log.Printf("[DEBUG] Unpacking gold virtual machine into %s\n", goldPath) _, err = unzipit.Unpack(image.file, goldPath) if err != nil { debug.PrintStack() - log.Printf("[ERROR] Unpacking Gold image %s\n", image.file.Name()) - return "", err + log.Printf("[ERROR] Unpacking gold image %s\n", image.file.Name()) + return err } } pattern := filepath.Join(goldPath, "**.vmx") - log.Printf("[DEBUG] Finding Gold virtual machine vmx file in %s", pattern) + log.Printf("[DEBUG] Finding gold vmx file in %s", pattern) files, _ := filepath.Glob(pattern) if len(files) == 0 { - return "", fmt.Errorf("[ERROR] vmx file was not found: %s", pattern) + return fmt.Errorf("[ERROR] Gold vmx file was not found: %s", pattern) } - vmxFile := files[0] - log.Printf("[DEBUG] Gold virtual machine vmx file found %v", vmxFile) - - log.Printf("[INFO] Opening Gold virtual machine from %s", vmxFile) - vm, err := VMwareClient.OpenVM(vmxFile, v.Image.Password) - if err != nil { - return "", err - } + goldvmx := files[0] + log.Printf("[DEBUG] Gold vmx file found at %v", goldvmx) vmFolder := filepath.Join(config.VMSPath, v.ID) - newvmx := filepath.Join(config.VMSPath, v.ID, v.ID+".vmx") + clonevmx := filepath.Join(config.VMSPath, v.ID, v.ID+".vmx") - if _, err = os.Stat(newvmx); os.IsNotExist(err) { - log.Printf("[INFO] Virtual machine clone not found: %s, err: %+v", newvmx, err) + if _, err = os.Stat(clonevmx); os.IsNotExist(err) { + log.Printf("[INFO] Virtual machine clone not found: %s, err: %+v", clonevmx, err) // If there is not a VMX file, make sure nothing else is in there either. - // We were seeing VIX 13004 errors when only a nvram file existed. os.RemoveAll(vmFolder) - log.Printf("[INFO] Cloning Gold virtual machine into %s...", newvmx) - _, err := vm.Clone(govix.CLONETYPE_LINKED, newvmx) - - // If there is an error and the error is other than "The snapshot already exists" - // then return the error - if err != nil && err.(*govix.Error).Code != 13004 { - return "", err + log.Printf("[INFO] Cloning gold vmx into %s...", clonevmx) + err := v.manager.Clone(goldvmx, clonevmx, vmware.CloneLinked) + if err != nil { + return err } } else { - log.Printf("[INFO] Virtual Machine clone %s already exist, moving on.", newvmx) + log.Printf("[INFO] Clone %s already exist, moving on.", clonevmx) } - if err = v.Update(newvmx); err != nil { - return "", err + v.vmxfile = clonevmx + + if err = v.Update(); err != nil { + return err } - return newvmx, nil + return nil } -// Opens and updates virtual machine resource -func (v *VM) Update(vmxFile string) error { - // Sets default values if some attributes were not set or have - // invalid values - v.SetDefaults() - - log.Printf("[INFO] Opening virtual machine from %s", vmxFile) - - vm, err := VMwareClient.OpenVM(vmxFile, v.Image.Password) - if err != nil { - return err +// Updates virtual machine +func (v *VM) Update() error { + if v.vmxfile == "" { + return errors.New("Empty vmxfile. Nothing to update.") } + v.setDefaults() - running, err := vm.IsRunning() + running, err := v.manager.IsRunning(v.vmxfile) if err != nil { return err } @@ -181,169 +153,94 @@ func (v *VM) Update(vmxFile string) error { if running { log.Printf("[INFO] Virtual machine seems to be running, we need to " + "power it off in order to make changes.") - err = powerOff(vm) + err = v.manager.Stop(v.vmxfile) if err != nil { return err } } - memoryInMb, err := humanize.ParseBytes(v.Memory) - if err != nil { - log.Printf("[WARN] Unable to set memory size, defaulting to 512mib: %s", err) - memoryInMb = 512 - } else { - memoryInMb = (memoryInMb / 1024) / 1024 + info := &vmware.VMInfo{ + MemorySize: v.Memory, + CPUs: v.CPUs, + Name: v.ID, } - log.Printf("[DEBUG] Setting memory size to %d megabytes", memoryInMb) - vm.SetMemorySize(uint(memoryInMb)) - - log.Printf("[DEBUG] Setting vcpus to %d", v.CPUs) - vm.SetNumberVcpus(v.CPUs) - - log.Printf("[DEBUG] Setting ID to %s", v.ID) - vm.SetDisplayName(v.ID) - - imageJSON, err := json.Marshal(v.Image) + imageJSON, err := json.Marshal(v.OSImage) if err != nil { return err } // We need to encode the JSON data in base64 so that the VMX file is not // interpreted by VMWare as corrupted. - vm.SetAnnotation(base64.StdEncoding.EncodeToString(imageJSON)) + info.Annotation = base64.StdEncoding.EncodeToString(imageJSON) - if v.UpgradeVHardware { - log.Println("[INFO] Upgrading virtual hardware...") - err = vm.UpgradeVHardware() - if err != nil { - return err - } + log.Printf("[DEBUG] Adding network adapter...") + info.NetworkAdapters = []vmware.NetworkAdapter{ + vmware.NetworkAdapter{NetType: v.Network}, } - log.Printf("[DEBUG] Removing all network adapters from vmx file...") - err = vm.RemoveAllNetworkAdapters() + err = v.manager.SetInfo(info) if err != nil { return err } - log.Println("[INFO] Attaching virtual network adapters...") - for _, adapter := range v.VNetworkAdapters { - adapter.StartConnected = true - if adapter.ConnType == govix.NETWORK_BRIDGED { - adapter.LinkStatePropagation = true - } - - log.Printf("[DEBUG] Adapter: %+v", adapter) - err := vm.AddNetworkAdapter(adapter) - if err != nil { - return err - } - } - log.Println("[INFO] Powering virtual machine on...") - var options govix.VMPowerOption - - if v.LaunchGUI { - log.Println("[INFO] Preparing to launch GUI...") - options |= govix.VMPOWEROP_LAUNCH_GUI - } - - options |= govix.VMPOWEROP_NORMAL - - err = vm.PowerOn(options) + err = v.manager.Start(v.vmxfile, v.GUI) if err != nil { return err } - log.Printf("[INFO] Waiting %s for VMware Tools to initialize...\n", v.ToolsInitTimeout) - err = vm.WaitForToolsInGuest(v.ToolsInitTimeout) - if err != nil { - log.Println("[WARN] VMware Tools took too long to initialize or is not " + - "installed.") - } - - return nil -} - -// Powers off a virtual machine attempting a graceful shutdown. -func powerOff(vm *govix.VM) error { - tstate, err := vm.ToolsState() - if err != nil { - return err - } - - var powerOpts govix.VMPowerOption - log.Printf("Tools state %d", tstate) - - if (tstate & govix.TOOLSSTATE_RUNNING) != 0 { - log.Printf("[INFO] VMware Tools is running, attempting a graceful shutdown...") - // if VMware Tools is running, attempt a graceful shutdown. - powerOpts |= govix.VMPOWEROP_FROM_GUEST - } else { - log.Printf("[INFO] VMware Tools is NOT running, shutting down the " + - "machine abruptly...") - powerOpts |= govix.VMPOWEROP_NORMAL - } - - err = vm.PowerOff(powerOpts) - if err != nil { - return err - } - log.Printf("[DEBUG] Virtual machine is off.") - return nil } // Destroys a virtual machine resource -func (v *VM) Destroy(vmxFile string) error { - log.Printf("[DEBUG] Destroying VM resource %s...", vmxFile) - - vm, err := VMwareClient.OpenVM(vmxFile, v.Image.Password) - if err != nil { - return err +func (v *VM) Destroy() error { + if v.vmxfile == "" { + return errors.New("Empty vmxfile. Nothing to destroy.") } - running, err := vm.IsRunning() + running, err := v.manager.IsRunning(v.vmxfile) if err != nil { return err } if running { - if err = powerOff(vm); err != nil { + log.Printf("[DEBUG] Stopping %s...", v.vmxfile) + if err = v.manager.Stop(v.vmxfile); err != nil { return err } } - log.Println("[DEBUG] Asking VIX to delete the VM...") - err = vm.Delete(govix.VMDELETE_DISK_FILES | govix.VMDELETE_FORCE) + log.Printf("[DEBUG] Destroying %s...", v.vmxfile) + err = v.manager.Delete(v.vmxfile) if err != nil { return err } - // Just in case as we don't really know what VIX is doing under the hood + // Just in case if v.ID != "" { os.RemoveAll(filepath.Join(config.VMSPath, v.ID)) } - log.Printf("[DEBUG] VM %s Destroyed.\n", vmxFile) - + log.Printf("[DEBUG] VM %s Destroyed.", v.vmxfile) return nil } // Finds a virtual machine by ID func FindVM(id string) (*VM, error) { - vmxFile := filepath.Join(config.VMSPath, id, id+".vmx") + vmxfile := filepath.Join(config.VMSPath, id, id+".vmx") - _, err := os.Stat(vmxFile) + _, err := os.Stat(vmxfile) if os.IsNotExist(err) { return nil, nil } - vm := &VM{} + vm := NewVM(VMConfig{ + ID: id, + }) - log.Printf("Getting VM information from %s...\n", vmxFile) - err = vm.Refresh(vmxFile) + log.Printf("Getting VM information from %s...", vm.vmxfile) + err = vm.Refresh() if err != nil { return nil, err } @@ -352,48 +249,27 @@ func FindVM(id string) (*VM, error) { } // Refreshes state with VMware -func (v *VM) Refresh(vmxFile string) error { - log.Printf("[DEBUG] Syncing VM resource %s...", vmxFile) - - v.VMXFile = vmxFile - - log.Printf("[DEBUG] Opening VM %s...", vmxFile) - vm, err := VMwareClient.OpenVM(vmxFile, v.Image.Password) - if err != nil { - return err - } - - running, err := vm.IsRunning() - if !running { - return err +func (v *VM) Refresh() error { + if v.vmxfile == "" { + return errors.New("Empty vmxfile. Nothing to refresh.") } - vcpus, err := vm.Vcpus() + log.Printf("[DEBUG] Refreshing state with VMWare %s...", v.vmxfile) + info, err := v.manager.Info(v.vmxfile) if err != nil { return err } - memory, err := vm.MemorySize() - if err != nil { - return err - } + v.CPUs = info.CPUs + v.Memory = info.MemorySize + v.ID = info.Name - // We need to convert memory value to megabytes so humanize can interpret it - // properly. - memory = (memory * 1024) * 1024 - v.Memory = strings.ToLower(humanize.IBytes(uint64(memory))) - v.CPUs = uint(vcpus) - - v.ID, err = vm.DisplayName() - if err != nil { - return err - } - - imageJSONBase64, err := vm.Annotation() + v.IPAddress, err = v.manager.IPAddress(v.vmxfile) if err != nil { return err } + imageJSONBase64 := info.Annotation imageJSON, err := base64.StdEncoding.DecodeString(imageJSONBase64) if err != nil { return err @@ -404,74 +280,23 @@ func (v *VM) Refresh(vmxFile string) error { if err != nil { return err } - v.Image = image + v.OSImage = image - v.VNetworkAdapters, err = vm.NetworkAdapters() - if err != nil { - return err + if len(info.NetworkAdapters) > 0 { + v.Network = info.NetworkAdapters[0].NetType } - v.IPAddress, err = vm.IPAddress() + running, err := v.manager.IsRunning(v.vmxfile) if err != nil { return err } - powerState, err := vm.PowerState() - if err != nil { - return err - } - - v.Status = Status(powerState) - v.GuestOS, err = vm.GuestOS() - if err != nil { - return err + if running { + v.Status = "running" + } else { + v.Status = "stopped" } - log.Printf("[DEBUG] Finished syncing VM %s...", vmxFile) + log.Printf("[DEBUG] Finished refreshing state from %s...", v.vmxfile) return nil } - -// Resolves power state bitwise flags to more user friendly strings -func Status(s govix.VMPowerState) string { - blockedOnMsg := ",blocked" - toolsRunning := ",tools-running" - status := "unknown" - - if (s & govix.POWERSTATE_POWERING_OFF) != 0 { - status = "powering-off" - } - - if (s & govix.POWERSTATE_POWERED_OFF) != 0 { - status = "powered-off" - } - - if (s & govix.POWERSTATE_POWERING_ON) != 0 { - status = "powering-on" - } - - if (s & govix.POWERSTATE_POWERED_ON) != 0 { - status = "powered-on" - } - - if (s & govix.POWERSTATE_SUSPENDING) != 0 { - status = "suspending" - } - - if (s & govix.POWERSTATE_SUSPENDED) != 0 { - status = "suspended" - } - - if (s & govix.POWERSTATE_RESETTING) != 0 { - status = "resetting" - } - - if (s & govix.POWERSTATE_TOOLS_RUNNING) != 0 { - status += toolsRunning - } - - if (s & govix.POWERSTATE_BLOCKED_ON_MSG) != 0 { - status += blockedOnMsg - } - - return status -} diff --git a/vms/web.go b/vms/web.go index d3e4f9e..c08eb24 100644 --- a/vms/web.go +++ b/vms/web.go @@ -9,12 +9,9 @@ import ( "log" "net/http" "path" - "time" "github.com/c4milo/osx-builder/apperror" "github.com/c4milo/osx-builder/pkg/render" - - govix "github.com/hooklift/govix" ) var Handlers map[string]func(http.ResponseWriter, *http.Request) = map[string]func(http.ResponseWriter, *http.Request){ @@ -25,20 +22,9 @@ var Handlers map[string]func(http.ResponseWriter, *http.Request) = map[string]fu // Defines parameters supported by the CreateVM service type CreateVMParams struct { - // Number of virtual cpus to assign to the VM - CPUs uint `json:"cpus"` - // Memory for the virtual machine in IEC units. Ex: 1024mib, 1gib, 5120kib, - Memory string `json:"memory"` - // Network type, either "bridged", "nat" or "hostonly" - NetType govix.NetworkType `json:"network_type"` - // Guest OS image that is going to be used as Gold image for creating new VMs - OSImage Image `json:"image"` + VMConfig // Script to run inside the Guest OS upon first boot BootstrapScript string `json:"bootstrap_script"` - // Timeout value for waiting for VMWare Tools to initialize - ToolsInitTimeout time.Duration `json:"tools_init_timeout"` - // Whether or not to launch the user interface when creating the VM - LaunchGUI bool `json:"launch_gui"` // Callback URL to post results once the VM creation process finishes. It // must support POST requests and be ready to receive JSON in the body of // the request. @@ -109,26 +95,12 @@ func CreateVM(w http.ResponseWriter, req *http.Request) { } id := fmt.Sprintf("%x", b) + params.VMConfig.ID = id - vm := &VM{ - ID: id, - Image: params.OSImage, - CPUs: params.CPUs, - Memory: params.Memory, - UpgradeVHardware: false, - ToolsInitTimeout: params.ToolsInitTimeout, - LaunchGUI: params.LaunchGUI, - } - - nic := &govix.NetworkAdapter{ - ConnType: params.NetType, - } - - vm.VNetworkAdapters = make([]*govix.NetworkAdapter, 0, 1) - vm.VNetworkAdapters = append(vm.VNetworkAdapters, nic) + vm := NewVM(params.VMConfig) go func() { - vmxfile, err := vm.Create() + err := vm.Create() if err != nil { log.Printf(`[ERROR] msg="%s" value=%+v code=%s error="%s" stacktrace=%s\n`, ErrCreatingVM.Message, vm, ErrCreatingVM.Code, err.Error(), apperror.GetStacktrace()) @@ -137,8 +109,9 @@ func CreateVM(w http.ResponseWriter, req *http.Request) { return } + // One last effort to get an IP... if vm.IPAddress == "" { - vm.Refresh(vmxfile) + vm.Refresh() } sendResult(params.CallbackURL, vm) @@ -152,7 +125,7 @@ func CreateVM(w http.ResponseWriter, req *http.Request) { // Defines parameters supported by the DestroyVM service type DestroyVMParams struct { - // Virtual machine ID to destroy + // Virtual machine ID ID string } @@ -185,7 +158,7 @@ func DestroyVM(w http.ResponseWriter, req *http.Request) { return } - err = vm.Destroy(vm.VMXFile) + err = vm.Destroy() if err != nil { log.Printf(`[ERROR] msg="%s" code=%s error="%s" stacktrace=%s\n`, ErrInternal.Message, ErrInternal.Code, err.Error(), apperror.GetStacktrace()) @@ -214,7 +187,6 @@ func GetVM(w http.ResponseWriter, req *http.Request) { } vm, err := FindVM(params.ID) - if err != nil { log.Printf(`[ERROR] msg="%s" code=%s error="%s" stacktrace=%s\n`, ErrOpeningVM.Message, ErrOpeningVM.Code, err.Error(), apperror.GetStacktrace())