From 2e90b7cb55d08d53eda1347ea1a75fa5afa4f9f1 Mon Sep 17 00:00:00 2001 From: Fabio Rapposelli Date: Wed, 8 Jul 2015 09:36:27 +0200 Subject: [PATCH] Update vSphere driver to work with govmomi instead of wrapping govc. Signed-off-by: Fabio Rapposelli --- drivers/vmwarevsphere/errors/config_error.go | 22 - .../vmwarevsphere/errors/datastore_error.go | 26 - drivers/vmwarevsphere/errors/govc_error.go | 22 - drivers/vmwarevsphere/errors/guest_error.go | 26 - drivers/vmwarevsphere/errors/login_error.go | 17 - drivers/vmwarevsphere/errors/state_error.go | 22 - drivers/vmwarevsphere/errors/vm_error.go | 26 - drivers/vmwarevsphere/govc.go | 41 -- drivers/vmwarevsphere/vc_conn.go | 313 --------- drivers/vmwarevsphere/vsphere.go | 641 +++++++++++++++--- 10 files changed, 541 insertions(+), 615 deletions(-) delete mode 100644 drivers/vmwarevsphere/errors/config_error.go delete mode 100644 drivers/vmwarevsphere/errors/datastore_error.go delete mode 100644 drivers/vmwarevsphere/errors/govc_error.go delete mode 100644 drivers/vmwarevsphere/errors/guest_error.go delete mode 100644 drivers/vmwarevsphere/errors/login_error.go delete mode 100644 drivers/vmwarevsphere/errors/state_error.go delete mode 100644 drivers/vmwarevsphere/errors/vm_error.go delete mode 100644 drivers/vmwarevsphere/govc.go delete mode 100644 drivers/vmwarevsphere/vc_conn.go diff --git a/drivers/vmwarevsphere/errors/config_error.go b/drivers/vmwarevsphere/errors/config_error.go deleted file mode 100644 index 23e0ab4b7b..0000000000 --- a/drivers/vmwarevsphere/errors/config_error.go +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. - */ - -package errors - -import "fmt" - -type IncompleteVsphereConfigError struct { - component string -} - -func NewIncompleteVsphereConfigError(component string) error { - err := IncompleteVsphereConfigError{ - component: component, - } - return &err -} - -func (err *IncompleteVsphereConfigError) Error() string { - return fmt.Sprintf("Incomplete vSphere information: missing %s", err.component) -} diff --git a/drivers/vmwarevsphere/errors/datastore_error.go b/drivers/vmwarevsphere/errors/datastore_error.go deleted file mode 100644 index 64439281e1..0000000000 --- a/drivers/vmwarevsphere/errors/datastore_error.go +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. - */ - -package errors - -import "fmt" - -type DatastoreError struct { - datastore string - operation string - reason string -} - -func NewDatastoreError(datastore, operation, reason string) error { - err := DatastoreError{ - datastore: datastore, - operation: operation, - reason: reason, - } - return &err -} - -func (err *DatastoreError) Error() string { - return fmt.Sprintf("Unable to %s on datastore %s due to %s", err.operation, err.datastore, err.reason) -} diff --git a/drivers/vmwarevsphere/errors/govc_error.go b/drivers/vmwarevsphere/errors/govc_error.go deleted file mode 100644 index 0cbf9841e1..0000000000 --- a/drivers/vmwarevsphere/errors/govc_error.go +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. - */ - -package errors - -import "fmt" - -type GovcNotFoundError struct { - path string -} - -func NewGovcNotFoundError(path string) error { - err := GovcNotFoundError{ - path: path, - } - return &err -} - -func (err *GovcNotFoundError) Error() string { - return fmt.Sprintf("govc not found: %s", err.path) -} diff --git a/drivers/vmwarevsphere/errors/guest_error.go b/drivers/vmwarevsphere/errors/guest_error.go deleted file mode 100644 index 4dffe821cb..0000000000 --- a/drivers/vmwarevsphere/errors/guest_error.go +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. - */ - -package errors - -import "fmt" - -type GuestError struct { - vm string - operation string - reason string -} - -func NewGuestError(vm, operation, reason string) error { - err := GuestError{ - vm: vm, - operation: operation, - reason: reason, - } - return &err -} - -func (err *GuestError) Error() string { - return fmt.Sprintf("Unable to %s on vm %s due to %s", err.operation, err.vm, err.reason) -} diff --git a/drivers/vmwarevsphere/errors/login_error.go b/drivers/vmwarevsphere/errors/login_error.go deleted file mode 100644 index b72569fc0d..0000000000 --- a/drivers/vmwarevsphere/errors/login_error.go +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. - */ - -package errors - -type InvalidLoginError struct { -} - -func NewInvalidLoginError() error { - err := InvalidLoginError{} - return &err -} - -func (err *InvalidLoginError) Error() string { - return "cannot complete operation due to incorrect vSphere username or password" -} diff --git a/drivers/vmwarevsphere/errors/state_error.go b/drivers/vmwarevsphere/errors/state_error.go deleted file mode 100644 index 43d92ad078..0000000000 --- a/drivers/vmwarevsphere/errors/state_error.go +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. - */ - -package errors - -import "fmt" - -type InvalidStateError struct { - vm string -} - -func NewInvalidStateError(vm string) error { - err := InvalidStateError{ - vm: vm, - } - return &err -} - -func (err *InvalidStateError) Error() string { - return fmt.Sprintf("Machine %s state invalid", err.vm) -} diff --git a/drivers/vmwarevsphere/errors/vm_error.go b/drivers/vmwarevsphere/errors/vm_error.go deleted file mode 100644 index f17757801a..0000000000 --- a/drivers/vmwarevsphere/errors/vm_error.go +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. - */ - -package errors - -import "fmt" - -type VMError struct { - operation string - vm string - reason string -} - -func NewVMError(operation, vm, reason string) error { - err := VMError{ - vm: vm, - operation: operation, - reason: reason, - } - return &err -} - -func (err *VMError) Error() string { - return fmt.Sprintf("Unable to %s docker host %s: %s", err.operation, err.vm, err.reason) -} diff --git a/drivers/vmwarevsphere/govc.go b/drivers/vmwarevsphere/govc.go deleted file mode 100644 index 6848e7793a..0000000000 --- a/drivers/vmwarevsphere/govc.go +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. - */ - -package vmwarevsphere - -import ( - "bytes" - "os/exec" - "strings" - - "github.com/docker/machine/libmachine/log" -) - -var ( - GovcCmd = "govc" -) - -func govc(args ...string) error { - cmd := exec.Command(GovcCmd, args...) - - log.Debugf("govc executing: %v %v", GovcCmd, strings.Join(args, " ")) - - if err := cmd.Run(); err != nil { - return err - } - return nil -} - -func govcOutErr(args ...string) (string, string, error) { - cmd := exec.Command(GovcCmd, args...) - - log.Debugf("govcOutErr executing: %v %v", GovcCmd, strings.Join(args, " ")) - - var stdout bytes.Buffer - var stderr bytes.Buffer - cmd.Stdout = &stdout - cmd.Stderr = &stderr - err := cmd.Run() - return stdout.String(), stderr.String(), err -} diff --git a/drivers/vmwarevsphere/vc_conn.go b/drivers/vmwarevsphere/vc_conn.go deleted file mode 100644 index 3377ee4037..0000000000 --- a/drivers/vmwarevsphere/vc_conn.go +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. - */ - -package vmwarevsphere - -import ( - "fmt" - "strconv" - "strings" - - "github.com/docker/machine/drivers/vmwarevsphere/errors" - "github.com/docker/machine/libmachine/log" -) - -type VcConn struct { - driver *Driver -} - -func NewVcConn(driver *Driver) VcConn { - return VcConn{driver: driver} -} - -func (conn VcConn) DatastoreLs(path string) (string, error) { - args := []string{"datastore.ls"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--ds=%s", conn.driver.Datastore)) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, path) - stdout, stderr, err := govcOutErr(args...) - if stderr == "" && err == nil { - return stdout, nil - } - return "", errors.NewDatastoreError(conn.driver.Datastore, "ls", stderr) -} - -func (conn VcConn) DatastoreMkdir(dirName string) error { - _, err := conn.DatastoreLs(dirName) - if err == nil { - return nil - } - - log.Infof("Creating directory %s on datastore %s of vCenter %s... ", - dirName, conn.driver.Datastore, conn.driver.IP) - - args := []string{"datastore.mkdir"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--ds=%s", conn.driver.Datastore)) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, dirName) - _, stderr, err := govcOutErr(args...) - - if stderr != "" { - return errors.NewDatastoreError(conn.driver.Datastore, "mkdir", stderr) - } - - if err != nil { - return err - } - - return nil -} - -func (conn VcConn) DatastoreUpload(localPath, destination string) error { - stdout, err := conn.DatastoreLs(destination) - if err == nil && strings.Contains(stdout, isoFilename) { - log.Infof("boot2docker ISO already uploaded, skipping upload... ") - return nil - } - - log.Infof("Uploading %s to %s on datastore %s of vCenter %s... ", - localPath, destination, conn.driver.Datastore, conn.driver.IP) - - dsPath := fmt.Sprintf("%s/%s", destination, isoFilename) - args := []string{"datastore.upload"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--ds=%s", conn.driver.Datastore)) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, localPath) - args = append(args, dsPath) - _, stderr, err := govcOutErr(args...) - if stderr == "" && err == nil { - return nil - } - return errors.NewDatastoreError(conn.driver.Datacenter, "upload", stderr) -} - -func (conn VcConn) VMInfo() (string, error) { - args := []string{"vm.info"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, conn.driver.MachineName) - - stdout, stderr, err := govcOutErr(args...) - if strings.Contains(stdout, "Name") && stderr == "" && err == nil { - return stdout, nil - } - return "", errors.NewVMError("find", conn.driver.MachineName, "VM not found") -} - -func (conn VcConn) VMCreate(isoPath string) error { - log.Infof("Creating virtual machine %s of vCenter %s... ", - conn.driver.MachineName, conn.driver.IP) - - args := []string{"vm.create"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--net=%s", conn.driver.Network)) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, fmt.Sprintf("--ds=%s", conn.driver.Datastore)) - args = append(args, fmt.Sprintf("--iso=%s", isoPath)) - memory := strconv.Itoa(conn.driver.Memory) - args = append(args, fmt.Sprintf("--m=%s", memory)) - cpu := strconv.Itoa(conn.driver.CPU) - args = append(args, fmt.Sprintf("--c=%s", cpu)) - args = append(args, "--disk.controller=pvscsi") - args = append(args, "--net.adapter=vmxnet3") - args = append(args, "--on=false") - if conn.driver.Pool != "" { - args = append(args, fmt.Sprintf("--pool=%s", conn.driver.Pool)) - } - if conn.driver.HostIP != "" { - args = append(args, fmt.Sprintf("--host.ip=%s", conn.driver.HostIP)) - } - args = append(args, conn.driver.MachineName) - _, stderr, err := govcOutErr(args...) - - if stderr == "" && err == nil { - return nil - } - return errors.NewVMError("create", conn.driver.MachineName, stderr) -} - -func (conn VcConn) VMPowerOn() error { - log.Infof("Powering on virtual machine %s of vCenter %s... ", - conn.driver.MachineName, conn.driver.IP) - - args := []string{"vm.power"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, "-on") - args = append(args, conn.driver.MachineName) - _, stderr, err := govcOutErr(args...) - - if stderr == "" && err == nil { - return nil - } - return errors.NewVMError("power on", conn.driver.MachineName, stderr) -} - -func (conn VcConn) VMPowerOff() error { - log.Infof("Powering off virtual machine %s of vCenter %s... ", - conn.driver.MachineName, conn.driver.IP) - - args := []string{"vm.power"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, "-off") - args = append(args, conn.driver.MachineName) - _, stderr, err := govcOutErr(args...) - - if stderr == "" && err == nil { - return nil - } - return errors.NewVMError("power on", conn.driver.MachineName, stderr) -} - -func (conn VcConn) VMShutdown() error { - log.Infof("Powering off virtual machine %s of vCenter %s... ", - conn.driver.MachineName, conn.driver.IP) - - args := []string{"vm.power"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, "-s") - args = append(args, conn.driver.MachineName) - _, stderr, err := govcOutErr(args...) - - if stderr == "" && err == nil { - return nil - } - return errors.NewVMError("power on", conn.driver.MachineName, stderr) -} - -func (conn VcConn) VMDestroy() error { - log.Infof("Deleting virtual machine %s of vCenter %s... ", - conn.driver.MachineName, conn.driver.IP) - - args := []string{"vm.destroy"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, conn.driver.MachineName) - _, stderr, err := govcOutErr(args...) - - if stderr == "" && err == nil { - return nil - } - return errors.NewVMError("delete", conn.driver.MachineName, stderr) -} - -func (conn VcConn) VMDiskCreate() error { - args := []string{"vm.disk.create"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, fmt.Sprintf("--vm=%s", conn.driver.MachineName)) - args = append(args, fmt.Sprintf("--ds=%s", conn.driver.Datastore)) - args = append(args, fmt.Sprintf("--name=%s/%s", conn.driver.MachineName, conn.driver.MachineName)) - diskSize := strconv.Itoa(conn.driver.DiskSize) - args = append(args, fmt.Sprintf("--size=%sMiB", diskSize)) - - _, stderr, err := govcOutErr(args...) - if stderr == "" && err == nil { - return nil - } - return errors.NewVMError("add network", conn.driver.MachineName, stderr) -} - -func (conn VcConn) VMAttachNetwork() error { - args := []string{"vm.network.add"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, fmt.Sprintf("--vm=%s", conn.driver.MachineName)) - args = append(args, fmt.Sprintf("--net=%s", conn.driver.Network)) - - _, stderr, err := govcOutErr(args...) - if stderr == "" && err == nil { - return nil - } - return errors.NewVMError("add network", conn.driver.MachineName, stderr) -} - -func (conn VcConn) VMFetchIP() (string, error) { - args := []string{"vm.ip"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, conn.driver.MachineName) - stdout, stderr, err := govcOutErr(args...) - - if stderr == "" && err == nil { - return stdout, nil - } - return "", errors.NewVMError("fetching IP", conn.driver.MachineName, stderr) -} - -func (conn VcConn) GuestMkdir(guestUser, guestPass, dirName string) error { - args := []string{"guest.mkdir"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, fmt.Sprintf("--l=%s:%s", guestUser, guestPass)) - args = append(args, fmt.Sprintf("--vm=%s", conn.driver.MachineName)) - args = append(args, "-p") - args = append(args, dirName) - _, stderr, err := govcOutErr(args...) - - if stderr == "" && err == nil { - return nil - } - return errors.NewGuestError("mkdir", conn.driver.MachineName, stderr) -} - -func (conn VcConn) GuestUpload(guestUser, guestPass, localPath, remotePath string) error { - args := []string{"guest.upload"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, fmt.Sprintf("--l=%s:%s", guestUser, guestPass)) - args = append(args, fmt.Sprintf("--vm=%s", conn.driver.MachineName)) - args = append(args, "-f") - args = append(args, localPath) - args = append(args, remotePath) - _, stderr, err := govcOutErr(args...) - - if stderr == "" && err == nil { - return nil - } - return errors.NewGuestError("upload", conn.driver.MachineName, stderr) -} - -func (conn VcConn) GuestStart(guestUser, guestPass, remoteBin, remoteArguments string) error { - args := []string{"guest.start"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, fmt.Sprintf("--l=%s:%s", guestUser, guestPass)) - args = append(args, fmt.Sprintf("--vm=%s", conn.driver.MachineName)) - args = append(args, remoteBin) - args = append(args, remoteArguments) - _, stderr, err := govcOutErr(args...) - - if stderr == "" && err == nil { - return nil - } - return errors.NewGuestError("start", conn.driver.MachineName, stderr) -} - -func (conn VcConn) GuestDownload(guestUser, guestPass, remotePath, localPath string) error { - args := []string{"guest.download"} - args = conn.AppendConnectionString(args) - args = append(args, fmt.Sprintf("--dc=%s", conn.driver.Datacenter)) - args = append(args, fmt.Sprintf("--l=%s:%s", guestUser, guestPass)) - args = append(args, fmt.Sprintf("--vm=%s", conn.driver.MachineName)) - args = append(args, remotePath) - args = append(args, localPath) - _, stderr, err := govcOutErr(args...) - - if stderr == "" && err == nil { - return nil - } - return errors.NewGuestError("download", conn.driver.MachineName, stderr) -} - -func (conn VcConn) AppendConnectionString(args []string) []string { - args = append(args, fmt.Sprintf("--u=%s:%s@%s", conn.driver.Username, conn.driver.Password, conn.driver.IP)) - args = append(args, "--k=true") - return args -} diff --git a/drivers/vmwarevsphere/vsphere.go b/drivers/vmwarevsphere/vsphere.go index 920afada89..83f36f34a8 100644 --- a/drivers/vmwarevsphere/vsphere.go +++ b/drivers/vmwarevsphere/vsphere.go @@ -9,51 +9,68 @@ import ( "fmt" "io/ioutil" "net" + "net/url" "os" "strings" "time" - "github.com/docker/machine/libmachine/log" - - "github.com/docker/machine/drivers/vmwarevsphere/errors" "github.com/docker/machine/libmachine/drivers" + "github.com/docker/machine/libmachine/log" "github.com/docker/machine/libmachine/mcnflag" "github.com/docker/machine/libmachine/mcnutils" "github.com/docker/machine/libmachine/ssh" "github.com/docker/machine/libmachine/state" + + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/guest" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/soap" + "github.com/vmware/govmomi/vim25/types" + "golang.org/x/net/context" ) const ( isoFilename = "boot2docker.iso" - B2DUser = "docker" - B2DPass = "tcuser" + // B2DUser is the guest User for tools login + B2DUser = "docker" + // B2DPass is the guest Pass for tools login + B2DPass = "tcuser" ) type Driver struct { *drivers.BaseDriver - CPU int Memory int DiskSize int - Boot2DockerURL string - IP string - Username string - Password string - Network string - Datastore string - Datacenter string - Pool string - HostIP string + CPU int ISO string + Boot2DockerURL string + CPUS int + + IP string + Port int + Username string + Password string + Network string + Datastore string + Datacenter string + Pool string + HostSystem string + + SSHPassword string } const ( + defaultSSHUser = B2DUser + defaultSSHPass = B2DPass defaultCpus = 2 defaultMemory = 2048 - defaultDiskSize = 20000 + defaultDiskSize = 20480 ) // GetCreateFlags registers the flags this driver adds to -// "docker hosts create" +// "docker-machine create" func (d *Driver) GetCreateFlags() []mcnflag.Flag { return []mcnflag.Flag{ mcnflag.IntFlag{ @@ -84,6 +101,12 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag { Name: "vmwarevsphere-vcenter", Usage: "vSphere IP/hostname for vCenter", }, + mcnflag.IntFlag{ + EnvVar: "VSPHERE_VCENTER_PORT", + Name: "vmwarevsphere-vcenter-port", + Usage: "vSphere Port for vCenter", + Value: 443, + }, mcnflag.StringFlag{ EnvVar: "VSPHERE_USERNAME", Name: "vmwarevsphere-username", @@ -115,19 +138,21 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag { Usage: "vSphere resource pool for docker VM", }, mcnflag.StringFlag{ - EnvVar: "VSPHERE_COMPUTE_IP", - Name: "vmwarevsphere-compute-ip", - Usage: "vSphere compute host IP where the docker VM will be instantiated", + EnvVar: "VSPHERE_HOSTSYSTEM", + Name: "vmwarevsphere-hostsystem", + Usage: "vSphere compute resource where the docker VM will be instantiated (use /* or / if using a cluster)", }, } } func NewDriver(hostName, storePath string) drivers.Driver { return &Driver{ - CPU: defaultCpus, - Memory: defaultMemory, - DiskSize: defaultDiskSize, + CPUS: defaultCpus, + Memory: defaultMemory, + DiskSize: defaultDiskSize, + SSHPassword: defaultSSHPass, BaseDriver: &drivers.BaseDriver{ + SSHUser: defaultSSHUser, MachineName: hostName, StorePath: storePath, }, @@ -159,13 +184,14 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { d.DiskSize = flags.Int("vmwarevsphere-disk-size") d.Boot2DockerURL = flags.String("vmwarevsphere-boot2docker-url") d.IP = flags.String("vmwarevsphere-vcenter") + d.Port = flags.Int("vmwarevsphere-vcenter-port") d.Username = flags.String("vmwarevsphere-username") d.Password = flags.String("vmwarevsphere-password") d.Network = flags.String("vmwarevsphere-network") d.Datastore = flags.String("vmwarevsphere-datastore") d.Datacenter = flags.String("vmwarevsphere-datacenter") d.Pool = flags.String("vmwarevsphere-pool") - d.HostIP = flags.String("vmwarevsphere-compute-ip") + d.HostSystem = flags.String("vmwarevsphere-hostsystem") d.SwarmMaster = flags.Bool("swarm-master") d.SwarmHost = flags.String("swarm-host") d.SwarmDiscovery = flags.String("swarm-discovery") @@ -176,7 +202,11 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { } func (d *Driver) GetURL() (string, error) { - ip, _ := d.GetIP() + + ip, err := d.GetIP() + if err != nil { + return "", err + } if ip == "" { return "", nil } @@ -186,27 +216,60 @@ func (d *Driver) GetURL() (string, error) { func (d *Driver) GetIP() (string, error) { status, err := d.GetState() if status != state.Running { - return "", errors.NewInvalidStateError(d.MachineName) + return "", drivers.ErrHostIsNotRunning } - vcConn := NewVcConn(d) - rawIP, err := vcConn.VMFetchIP() + + // Create context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := d.vsphereLogin(ctx) if err != nil { return "", err } + + vm, err := d.fetchVM(c, ctx, d.MachineName) + if err != nil { + return "", err + } + + rawIP, err := vm.WaitForIP(ctx) + if err != nil { + return "", err + } + ip := strings.Trim(strings.Split(rawIP, "\n")[0], " ") return ip, nil } func (d *Driver) GetState() (state.State, error) { - vcConn := NewVcConn(d) - stdout, err := vcConn.VMInfo() + + // Create context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := d.vsphereLogin(ctx) if err != nil { return state.None, err } - if strings.Contains(stdout, "poweredOn") { + vm, err := d.fetchVM(c, ctx, d.MachineName) + if err != nil { + return state.None, err + } + + var mvm mo.VirtualMachine + + err = c.RetrieveOne(ctx, vm.Reference(), nil, &mvm) + if err != nil { + return state.None, nil + } + + s := mvm.Summary + + if strings.Contains(string(s.Runtime.PowerState), "poweredOn") { return state.Running, nil - } else if strings.Contains(stdout, "poweredOff") { + } else if strings.Contains(string(s.Runtime.PowerState), "poweredOff") { return state.Stopped, nil } return state.None, nil @@ -214,17 +277,52 @@ func (d *Driver) GetState() (state.State, error) { // PreCreateCheck checks that the machine creation process can be started safely. func (d *Driver) PreCreateCheck() error { - if err := d.checkVsphereConfig(); err != nil { + log.Debug("Connecting to vSphere for pre-create checks...") + // Create context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := d.vsphereLogin(ctx) + if err != nil { return err } - // Downloading boot2docker to cache should be done here to make sure - // that a download failure will not leave a machine half created. - b2dutils := mcnutils.NewB2dUtils(d.StorePath) - if err := b2dutils.UpdateISOCache(d.Boot2DockerURL); err != nil { + // Create a new finder + f := find.NewFinder(c.Client, true) + + dc, err := d.getDatacenter(f, ctx) + if err != nil { return err } + f.SetDatacenter(dc) + + if _, err := d.getDatastore(f, ctx); err != nil { + return err + } + + if _, err := d.getNetwork(f, ctx); err != nil { + return err + } + + hs, err := d.getHostSystem(f, ctx) + if err != nil { + return err + } + + // ResourcePool + if d.Pool != "" { + // Find specified Resource Pool + if _, err := f.ResourcePool(ctx, d.Pool); err != nil { + return err + } + } else { + // Pick default Resource Pool for Host System + if _, err := hs.ResourcePool(ctx); err != nil { + return err + } + } + return nil } @@ -244,32 +342,140 @@ func (d *Driver) Create() error { return err } - vcConn := NewVcConn(d) + // Create context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := d.vsphereLogin(ctx) + if err != nil { + return err + } + // Create a new finder + f := find.NewFinder(c.Client, true) + + dc, err := d.getDatacenter(f, ctx) + if err != nil { + return err + } + + f.SetDatacenter(dc) + + dss, err := d.getDatastore(f, ctx) + if err != nil { + return err + } + + net, err := d.getNetwork(f, ctx) + if err != nil { + return err + } + + hs, err := d.getHostSystem(f, ctx) + if err != nil { + return err + } + + var rp *object.ResourcePool + if d.Pool != "" { + // Find specified Resource Pool + rp, err = f.ResourcePool(ctx, d.Pool) + if err != nil { + return err + } + } else { + // Pick default Resource Pool for Host System + rp, err = hs.ResourcePool(ctx) + if err != nil { + return err + } + } + + spec := types.VirtualMachineConfigSpec{ + Name: d.MachineName, + GuestId: "otherLinux64Guest", + Files: &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", dss.Name())}, + NumCPUs: d.CPU, + MemoryMB: int64(d.Memory), + } + + scsi, err := object.SCSIControllerTypes().CreateSCSIController("pvscsi") + if err != nil { + return err + } + + spec.DeviceChange = append(spec.DeviceChange, &types.VirtualDeviceConfigSpec{ + Operation: types.VirtualDeviceConfigSpecOperationAdd, + Device: scsi, + }) + + log.Infof("Creating VM...") + folders, err := dc.Folders(ctx) + task, err := folders.VmFolder.CreateVM(ctx, spec, rp, hs) + if err != nil { + return err + } + + info, err := task.WaitForResult(ctx, nil) + if err != nil { + return err + } + log.Infof("Uploading Boot2docker ISO ...") - if err := vcConn.DatastoreMkdir(d.MachineName); err != nil { + dsurl, err := dss.URL(ctx, dc, fmt.Sprintf("%s/%s", d.MachineName, isoFilename)) + if err != nil { + return err + } + p := soap.DefaultUpload + if err = c.Client.UploadFile(d.ISO, dsurl, &p); err != nil { return err } - if _, err := os.Stat(d.ISO); os.IsNotExist(err) { - log.Errorf("Unable to find boot2docker ISO at %s", d.ISO) - return errors.NewIncompleteVsphereConfigError(d.ISO) - } + // Retrieve the new VM + vm := object.NewVirtualMachine(c.Client, info.Result.(types.ManagedObjectReference)) - if err := vcConn.DatastoreUpload(d.ISO, d.MachineName); err != nil { + devices, err := vm.Device(ctx) + if err != nil { return err } - isoPath := fmt.Sprintf("%s/%s", d.MachineName, isoFilename) - if err := vcConn.VMCreate(isoPath); err != nil { + var add []types.BaseVirtualDevice + + controller, err := devices.FindDiskController("scsi") + if err != nil { return err } - log.Infof("Configuring the virtual machine %s... ", d.MachineName) - if err := vcConn.VMDiskCreate(); err != nil { + disk := devices.CreateDisk(controller, dss.Path(fmt.Sprintf("%s/%s.vmdk", d.MachineName, d.MachineName))) + + // Convert MB to KB + disk.CapacityInKB = int64(d.DiskSize) * 1024 + + add = append(add, disk) + ide, err := devices.FindIDEController("") + if err != nil { return err } - if err := vcConn.VMAttachNetwork(); err != nil { + cdrom, err := devices.CreateCdrom(ide) + if err != nil { + return err + } + + add = append(add, devices.InsertIso(cdrom, dss.Path(fmt.Sprintf("%s/%s", d.MachineName, isoFilename)))) + + backing, err := net.EthernetCardBackingInfo(ctx) + if err != nil { + return err + } + + netdev, err := object.EthernetCardTypes().CreateEthernetCard("vmxnet3", backing) + if err != nil { + return err + } + + log.Infof("Reconfiguring VM...") + add = append(add, netdev) + if vm.AddDevice(ctx, add...); err != nil { return err } @@ -277,18 +483,57 @@ func (d *Driver) Create() error { return err } + log.Infof("Provisioning certs and ssh keys...") // Generate a tar keys bundle if err := d.generateKeyBundle(); err != nil { return err } - // Copy SSH keys bundle - if err := vcConn.GuestUpload(B2DUser, B2DPass, d.ResolveStorePath("userdata.tar"), "/home/docker/userdata.tar"); err != nil { + opman := guest.NewOperationsManager(c.Client, vm.Reference()) + + fileman, err := opman.FileManager(ctx) + if err != nil { return err } - // Expand tar file. - if err := vcConn.GuestStart(B2DUser, B2DPass, "/usr/bin/sudo", "/bin/mv /home/docker/userdata.tar /var/lib/boot2docker/userdata.tar && /usr/bin/sudo tar xf /var/lib/boot2docker/userdata.tar -C /home/docker/ > /var/log/userdata.log 2>&1 && /usr/bin/sudo chown -R docker:staff /home/docker"); err != nil { + src := d.ResolveStorePath("userdata.tar") + s, err := os.Stat(src) + if err != nil { + return err + } + + auth := AuthFlag{} + flag := FileAttrFlag{} + auth.auth.Username = B2DUser + auth.auth.Password = B2DPass + flag.SetPerms(0, 0, 660) + url, err := fileman.InitiateFileTransferToGuest(ctx, auth.Auth(), "/home/docker/userdata.tar", flag.Attr(), s.Size(), true) + if err != nil { + return err + } + u, err := c.Client.ParseURL(url) + if err != nil { + return err + } + if err = c.Client.UploadFile(src, u, nil); err != nil { + return err + } + + procman, err := opman.ProcessManager(ctx) + if err != nil { + return err + } + + var env []string + guestspec := types.GuestProgramSpec{ + ProgramPath: "/usr/bin/sudo", + Arguments: "/bin/mv /home/docker/userdata.tar /var/lib/boot2docker/userdata.tar && /usr/bin/sudo tar xf /var/lib/boot2docker/userdata.tar -C /home/docker/ > /var/log/userdata.log 2>&1 && /usr/bin/sudo chown -R docker:staff /home/docker", + WorkingDirectory: "", + EnvVariables: env, + } + + _, err = procman.StartProgram(ctx, auth.Auth(), &guestspec) + if err != nil { return err } @@ -307,27 +552,53 @@ func (d *Driver) Start() error { return nil case state.Stopped: // TODO add transactional or error handling in the following steps - vcConn := NewVcConn(d) - err := vcConn.VMPowerOn() - if err != nil { - return err - } - // this step waits for the vm to start and fetch its ip address; - // this guarantees that the opem-vmtools has started working... - _, err = vcConn.VMFetchIP() + // Create context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := d.vsphereLogin(ctx) if err != nil { return err } - d.IPAddress, err = d.GetIP() - return err + vm, err := d.fetchVM(c, ctx, d.MachineName) + if err != nil { + return err + } + log.Infof("Powering on VM...") + task, err := vm.PowerOn(ctx) + if err != nil { + return err + } + + _, err = task.WaitForResult(ctx, nil) + if err != nil { + return err + } + + log.Infof("Waiting for VMware Tools to come online...") + if d.IPAddress, err = d.GetIP(); err != nil { + return err + } } - return errors.NewInvalidStateError(d.MachineName) + return nil } func (d *Driver) Stop() error { - vcConn := NewVcConn(d) - if err := vcConn.VMShutdown(); err != nil { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := d.vsphereLogin(ctx) + if err != nil { + return err + } + + vm, err := d.fetchVM(c, ctx, d.MachineName) + if err != nil { + return err + } + log.Infof("Powering off VM...") + if err := vm.ShutdownGuest(ctx); err != nil { return err } @@ -346,8 +617,56 @@ func (d *Driver) Remove() error { return fmt.Errorf("can't stop VM: %s", err) } } - vcConn := NewVcConn(d) - if err = vcConn.VMDestroy(); err != nil { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := d.vsphereLogin(ctx) + if err != nil { + return err + } + + // Create a new finder + f := find.NewFinder(c.Client, true) + + dc, err := d.getDatacenter(f, ctx) + if err != nil { + return err + } + + f.SetDatacenter(dc) + + dss, err := d.getDatastore(f, ctx) + if err != nil { + return err + } + + // Remove B2D Iso from VM folder + m := object.NewFileManager(c.Client) + task, err := m.DeleteDatastoreFile(ctx, dss.Path(fmt.Sprintf("%s/%s", d.MachineName, isoFilename)), dc) + if err != nil { + return err + } + + err = task.Wait(ctx) + if err != nil { + if types.IsFileNotFound(err) { + // Ignore error + return nil + } + } + + vm, err := d.fetchVM(c, ctx, d.MachineName) + if err != nil { + return err + } + + task, err = vm.Destroy(ctx) + if err != nil { + return err + } + + _, err = task.WaitForResult(ctx, nil) + if err != nil { return err } return nil @@ -385,8 +704,26 @@ func (d *Driver) Restart() error { } func (d *Driver) Kill() error { - vcConn := NewVcConn(d) - if err := vcConn.VMPowerOff(); err != nil { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c, err := d.vsphereLogin(ctx) + if err != nil { + return err + } + + vm, err := d.fetchVM(c, ctx, d.MachineName) + if err != nil { + return err + } + log.Infof("Powering off VM forcibly...") + task, err := vm.PowerOff(ctx) + if err != nil { + return err + } + + _, err = task.WaitForResult(ctx, nil) + if err != nil { return err } @@ -403,28 +740,6 @@ func (d *Driver) publicSSHKeyPath() string { return d.GetSSHKeyPath() + ".pub" } -func (d *Driver) checkVsphereConfig() error { - if d.IP == "" { - return errors.NewIncompleteVsphereConfigError("vSphere IP") - } - if d.Username == "" { - return errors.NewIncompleteVsphereConfigError("vSphere username") - } - if d.Password == "" { - return errors.NewIncompleteVsphereConfigError("vSphere password") - } - if d.Network == "" { - return errors.NewIncompleteVsphereConfigError("vSphere network") - } - if d.Datastore == "" { - return errors.NewIncompleteVsphereConfigError("vSphere datastore") - } - if d.Datacenter == "" { - return errors.NewIncompleteVsphereConfigError("vSphere datacenter") - } - return nil -} - // Make a boot2docker userdata.tar key bundle func (d *Driver) generateKeyBundle() error { log.Debugf("Creating Tar key bundle...") @@ -480,19 +795,145 @@ func (d *Driver) generateKeyBundle() error { } -func (d *Driver) UpgradeISO() error { +func (d *Driver) vsphereLogin(ctx context.Context) (*govmomi.Client, error) { - vcConn := NewVcConn(d) + // Parse URL from string + u, err := url.Parse(fmt.Sprintf("https://%s:%d/sdk", d.IP, d.Port)) + if err != nil { + return nil, err + } + // set username and password for the URL + u.User = url.UserPassword(d.Username, d.Password) - if _, err := os.Stat(d.ISO); os.IsNotExist(err) { - log.Errorf("Unable to find boot2docker ISO at %s", d.ISO) - return errors.NewIncompleteVsphereConfigError(d.ISO) + // Connect and log in to ESX or vCenter + c, err := govmomi.NewClient(ctx, u, true) + if err != nil { + return nil, err } - if err := vcConn.DatastoreUpload(d.ISO, d.MachineName); err != nil { - return err + return c, nil +} + +func (d *Driver) fetchVM(c *govmomi.Client, ctx context.Context, vmname string) (*object.VirtualMachine, error) { + + // Create a new finder + f := find.NewFinder(c.Client, true) + + var vm *object.VirtualMachine + var err error + + dc, err := d.getDatacenter(f, ctx) + if err != nil { + return vm, err } - return nil + f.SetDatacenter(dc) + + vm, err = f.VirtualMachine(ctx, vmname) + if err != nil { + return vm, err + } + return vm, nil +} + +type AuthFlag struct { + auth types.NamePasswordAuthentication +} + +func (f *AuthFlag) Auth() types.BaseGuestAuthentication { + return &f.auth +} + +type FileAttrFlag struct { + types.GuestPosixFileAttributes +} + +func (f *FileAttrFlag) SetPerms(owner, group, perms int) { + f.OwnerId = owner + f.GroupId = group + f.Permissions = int64(perms) +} + +func (f *FileAttrFlag) Attr() types.BaseGuestFileAttributes { + return &f.GuestPosixFileAttributes +} + +func (d *Driver) getDatacenter(f *find.Finder, ctx context.Context) (dc *object.Datacenter, err error) { + + // Datacenter + if d.Datacenter != "" { + // Find specified Datacenter + dc, err = f.Datacenter(ctx, d.Datacenter) + if err != nil { + return dc, err + } + } else { + // Use default Datacenter + dc, err = f.DefaultDatacenter(ctx) + if err != nil { + return dc, err + } + } + log.Debug("Datacenter found: ", dc) + return dc, nil +} + +func (d *Driver) getDatastore(f *find.Finder, ctx context.Context) (dss *object.Datastore, err error) { + + // Datastore + if d.Datastore != "" { + // Find specified Datastore + dss, err = f.Datastore(ctx, d.Datastore) + if err != nil { + return dss, err + } + } else { + // Find default Datastore + dss, err = f.DefaultDatastore(ctx) + if err != nil { + return dss, err + } + } + log.Debug("Datastore found: ", dss) + return dss, err +} + +func (d *Driver) getNetwork(f *find.Finder, ctx context.Context) (net object.NetworkReference, err error) { + + // Network + if d.Network != "" { + // Find specified Network + net, err = f.Network(ctx, d.Network) + if err != nil { + return net, err + } + } else { + // Find default Network + net, err = f.DefaultNetwork(ctx) + if err != nil { + return net, err + } + } + log.Debug("Network found: ", net) + return net, nil } + +func (d *Driver) getHostSystem(f *find.Finder, ctx context.Context) (hs *object.HostSystem, err error) { + // HostSystem + if d.HostSystem != "" { + // Find specified HostSystem + hs, err = f.HostSystem(ctx, d.HostSystem) + if err != nil { + return hs, err + } + } else { + // Find default HostSystem + hs, err = f.DefaultHostSystem(ctx) + if err != nil { + return hs, err + } + } + log.Debug("HostSystem found: ", hs) + return hs, nil +}