From 7652f256864c134d2489a0d87804bc04198d1d79 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Wed, 26 Nov 2014 16:28:30 +0800 Subject: [PATCH] add vsphere driver support --- cmds.go | 3 +- driver/driver.go | 1 + dummy/machine.go | 5 + util.go | 2 +- virtualbox/machine.go | 5 + vsphere/README.md | 51 ++++ vsphere/errors/config_error.go | 18 ++ vsphere/errors/datastore_error.go | 22 ++ vsphere/errors/errors.go | 18 ++ vsphere/errors/govc_error.go | 18 ++ vsphere/errors/guest_error.go | 22 ++ vsphere/errors/login_error.go | 13 + vsphere/errors/state_error.go | 18 ++ vsphere/errors/vm_error.go | 22 ++ vsphere/govc.go | 55 ++++ vsphere/machine.go | 462 ++++++++++++++++++++++++++++++ vsphere/vcenter.go | 311 ++++++++++++++++++++ 17 files changed, 1044 insertions(+), 2 deletions(-) create mode 100644 vsphere/README.md create mode 100644 vsphere/errors/config_error.go create mode 100644 vsphere/errors/datastore_error.go create mode 100644 vsphere/errors/errors.go create mode 100644 vsphere/errors/govc_error.go create mode 100644 vsphere/errors/guest_error.go create mode 100644 vsphere/errors/login_error.go create mode 100644 vsphere/errors/state_error.go create mode 100644 vsphere/errors/vm_error.go create mode 100644 vsphere/govc.go create mode 100644 vsphere/machine.go create mode 100644 vsphere/vcenter.go diff --git a/cmds.go b/cmds.go index 743be1c..c3385a4 100644 --- a/cmds.go +++ b/cmds.go @@ -13,6 +13,7 @@ import ( _ "github.com/boot2docker/boot2docker-cli/dummy" _ "github.com/boot2docker/boot2docker-cli/virtualbox" + _ "github.com/boot2docker/boot2docker-cli/vsphere" "github.com/boot2docker/boot2docker-cli/driver" ) @@ -84,7 +85,7 @@ func cmdUp() error { fmt.Println("Waiting for VM and Docker daemon to start...") //give the VM a little time to start, so we don't kill the Serial Pipe/Socket time.Sleep(time.Duration(B2D.Waittime) * time.Millisecond) - natSSH := fmt.Sprintf("localhost:%d", m.GetSSHPort()) + natSSH := fmt.Sprintf("%s:%d", m.GetAddr(), m.GetSSHPort()) IP := "" for i := 1; i < B2D.Retries; i++ { print(".") diff --git a/driver/driver.go b/driver/driver.go index 525ee9b..56b38ce 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -45,6 +45,7 @@ type Machine interface { GetSerialFile() string GetDockerPort() uint GetSSHPort() uint + GetAddr() string } var ( diff --git a/dummy/machine.go b/dummy/machine.go index cfc3f1d..c591e31 100644 --- a/dummy/machine.go +++ b/dummy/machine.go @@ -122,6 +122,11 @@ func (m *Machine) GetName() string { return m.Name } +// Get machine address +func (m *Machine) GetAddr() string { + return "localhost" +} + // Get current state func (m *Machine) GetState() driver.MachineState { return m.State diff --git a/util.go b/util.go index fa072aa..aa5348c 100644 --- a/util.go +++ b/util.go @@ -176,7 +176,7 @@ func getSSHCommand(m driver.Machine, args ...string) *exec.Cmd { "-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts." "-p", fmt.Sprintf("%d", m.GetSSHPort()), "-i", B2D.SSHKey, - "docker@localhost", + fmt.Sprintf("docker@%s", m.GetAddr()), } sshArgs := append(DefaultSSHArgs, args...) diff --git a/virtualbox/machine.go b/virtualbox/machine.go index e275abb..ee0614b 100644 --- a/virtualbox/machine.go +++ b/virtualbox/machine.go @@ -308,6 +308,11 @@ func (m *Machine) GetName() string { return m.Name } +// Get machine address +func (m *Machine) GetAddr() string { + return "localhost" +} + // Get current state func (m *Machine) GetState() driver.MachineState { return m.State diff --git a/vsphere/README.md b/vsphere/README.md new file mode 100644 index 0000000..0886c1a --- /dev/null +++ b/vsphere/README.md @@ -0,0 +1,51 @@ +Boot2docker vSphere Driver +========================== + +The vSphere driver is to support running vSphere environment. + +vSphere Environment Requirement +--------------- + +The vSphere environment requires DHCP on the VM network the boot2docker VM is running on. + + +Configuration +--------------- + +The boot2docker reads the driver information from its profile, and a sample snippet configuration is provided below: + +```ini +# boot2docker profile filename: /Users/my/.boot2docker/profile +...... +Driver = "vsphere" +...... + +[DriverCfg.vsphere] +# path to the govc binary +Govc = "govc" + +# vCenter IP address +VcenterIp = "10.150.100.200" + +# vCenter Username (console should prompt for password) +VcenterUser = "root" + +# target datacenter to deploy the boot2docker virtual machine +VcenterDatacenter = "Datacenter" + +# target datastore to upload the boot2docker ISO and store the boot2docker virtual machine +VcenterDatastore = "datastore1" + +# target network to add the boot2docker virtual machine (requires DHCP) +VcenterNetwork = "VM Network" + +# (optional) required when user want to deploy to a specified host or multiple clusters/hosts exist in the environment +VcenterHostIp = "10.120.180.160" + +# (optional) required when user wants to deploy to a specified cluster or multiple clusters/hosts exist in this environment +VcenterPool = "cluster" + +# (optional) the default vm CPU number is 2 +VmCPU = 4 +``` + diff --git a/vsphere/errors/config_error.go b/vsphere/errors/config_error.go new file mode 100644 index 0000000..593fe1a --- /dev/null +++ b/vsphere/errors/config_error.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +type IncompleteVcConfigError struct { + component string +} + +func NewIncompleteVcConfigError(component string) error { + err := IncompleteVcConfigError{ + component: component, + } + return &err +} + +func (err *IncompleteVcConfigError) Error() string { + return fmt.Sprintf("Incomplete vCenter information: missing %s", err.component) +} diff --git a/vsphere/errors/datastore_error.go b/vsphere/errors/datastore_error.go new file mode 100644 index 0000000..fcf9cc4 --- /dev/null +++ b/vsphere/errors/datastore_error.go @@ -0,0 +1,22 @@ +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/vsphere/errors/errors.go b/vsphere/errors/errors.go new file mode 100644 index 0000000..bb7843a --- /dev/null +++ b/vsphere/errors/errors.go @@ -0,0 +1,18 @@ +package errors + +import ( + original "errors" + "fmt" +) + +func New(message string) error { + return original.New(message) +} + +func NewWithFmt(message string, args ...interface{}) error { + return original.New(fmt.Sprintf(message, args...)) +} + +func NewWithError(message string, err error) error { + return NewWithFmt("%s: %s", message, err.Error()) +} diff --git a/vsphere/errors/govc_error.go b/vsphere/errors/govc_error.go new file mode 100644 index 0000000..b036c13 --- /dev/null +++ b/vsphere/errors/govc_error.go @@ -0,0 +1,18 @@ +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/vsphere/errors/guest_error.go b/vsphere/errors/guest_error.go new file mode 100644 index 0000000..9c5df4a --- /dev/null +++ b/vsphere/errors/guest_error.go @@ -0,0 +1,22 @@ +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/vsphere/errors/login_error.go b/vsphere/errors/login_error.go new file mode 100644 index 0000000..23abc26 --- /dev/null +++ b/vsphere/errors/login_error.go @@ -0,0 +1,13 @@ +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/vsphere/errors/state_error.go b/vsphere/errors/state_error.go new file mode 100644 index 0000000..964b9bf --- /dev/null +++ b/vsphere/errors/state_error.go @@ -0,0 +1,18 @@ +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/vsphere/errors/vm_error.go b/vsphere/errors/vm_error.go new file mode 100644 index 0000000..7d12d16 --- /dev/null +++ b/vsphere/errors/vm_error.go @@ -0,0 +1,22 @@ +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/vsphere/govc.go b/vsphere/govc.go new file mode 100644 index 0000000..c6e24ec --- /dev/null +++ b/vsphere/govc.go @@ -0,0 +1,55 @@ +package vsphere + +import ( + "bytes" + "log" + "os" + "os/exec" + "strings" + + "github.com/boot2docker/boot2docker-cli/vsphere/errors" +) + +func init() { +} + +func govc(args ...string) error { + err := lookPath(cfg.Govc) + if err != nil { + return errors.NewGovcNotFoundError(cfg.Govc) + } + + cmd := exec.Command(cfg.Govc, args...) + if verbose { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + log.Printf("executing: %v %v", cfg.Govc, strings.Join(args, " ")) + } + if err := cmd.Run(); err != nil { + return err + } + return nil +} + +func govcOutErr(args ...string) (string, string, error) { + err := lookPath(cfg.Govc) + if err != nil { + return "", "", errors.NewGovcNotFoundError(cfg.Govc) + } + + cmd := exec.Command(cfg.Govc, args...) + if verbose { + log.Printf("executing: %v %v", cfg.Govc, 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 +} + +func lookPath(file string) error { + _, err := exec.LookPath(file) + return err +} diff --git a/vsphere/machine.go b/vsphere/machine.go new file mode 100644 index 0000000..13aec57 --- /dev/null +++ b/vsphere/machine.go @@ -0,0 +1,462 @@ +package vsphere + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/boot2docker/boot2docker-cli/driver" + "github.com/boot2docker/boot2docker-cli/vsphere/errors" + flag "github.com/ogier/pflag" +) + +type DriverCfg struct { + Govc string // Path to govc binary + VcenterIp string // vCenter URL + VcenterUser string // vCenter User + VcenterDC string // target vCenter Datacenter + VcenterDS string // target vCenter Datastore + VcenterNet string // vCenter VM Network + VcenterPool string // target vCenter Resource Pool + VcenterHostIp string // target vCenter Host Ip + Cpu string // CPU number of the virtual machine +} + +var ( + verbose bool // Verbose mode (Local copy of B2D.Verbose). + cfg DriverCfg +) + +const ( + DATASTORE_DIR = "boot2docker-iso" + DATASTORE_ISO_NAME = "boot2docker.iso" + DEFAULT_CPU_NUMBER = 2 +) + +func init() { + if err := driver.Register("vsphere", InitFunc); err != nil { + fmt.Fprintf(os.Stderr, "Failed to initialize driver. Error : %s", err.Error()) + os.Exit(1) + } + if err := driver.RegisterConfig("vsphere", ConfigFlags); err != nil { + fmt.Fprintf(os.Stderr, "Failed to initialize driver config. Error : %s", err.Error()) + os.Exit(1) + } +} + +// Initialize the Machine. +func InitFunc(mc *driver.MachineConfig) (driver.Machine, error) { + verbose = mc.Verbose + + m, err := GetMachine(mc) + if _, ok := err.(*errors.GovcNotFoundError); ok { + return nil, err + } + + if err != nil && mc.Init == true { + return CreateMachine(mc) + } + return m, err +} + +// Add cmdline params for this driver +func ConfigFlags(B2D *driver.MachineConfig, flags *flag.FlagSet) error { + flags.StringVar(&cfg.Govc, "govc", "govc", "Path to GOVC Binary") + flags.StringVar(&cfg.VcenterIp, "vcenter-ip", "", "vCenter URL") + flags.StringVar(&cfg.VcenterUser, "vcenter-user", "", "vCenter User") + flags.StringVar(&cfg.VcenterDC, "vcenter-datacenter", "", "vCenter Datacenter") + flags.StringVar(&cfg.VcenterDS, "vcenter-datastore", "", "vCenter Datastore") + flags.StringVar(&cfg.VcenterNet, "vcenter-vm-network", "", "vCenter VM Network") + flags.StringVar(&cfg.VcenterPool, "vcenter-pool", "", "vCenter Target Resource Pool") + flags.StringVar(&cfg.VcenterHostIp, "vcenter-host-ip", "", "vCenter Target Host IP") + + return nil +} + +// GetMachine fetches the machine information from a vCenter +func GetMachine(mc *driver.MachineConfig) (*Machine, error) { + err := GetDriverCfg(mc) + if err != nil { + return nil, err + } + + if mc.Init == false { + fmt.Fprintf(os.Stdout, "Connecting to vSphere environment %s...\n", cfg.VcenterIp) + } + + vcConn := NewVcConn(&cfg) + err = vcConn.Login() + if err != nil { + return nil, err + } + + stdout, err := vcConn.VmInfo(mc.VM) + if err != nil { + return nil, err + } + + m := &Machine{ + Name: mc.VM, + State: driver.Poweroff, + SshPubKey: mc.SSHKey + ".pub", + VcenterIp: cfg.VcenterIp, + VcenterUser: cfg.VcenterUser, + Datacenter: cfg.VcenterDC, + Network: cfg.VcenterNet, + } + + ParseVmProperty(stdout, m) + + return m, nil +} + +// create a new machine in vsphere includes the following steps: +// 1. create a directory in vsphere datastore to include the B2D ISO; +// 2. uploads the ISO to the corresponding datastore; +// 3. bootup the virtual machine with the ISO mounted; +func CreateMachine(mc *driver.MachineConfig) (*Machine, error) { + err := GetDriverCfg(mc) + if err != nil { + return nil, err + } + + vcConn := NewVcConn(&cfg) + err = vcConn.DatastoreMkdir(DATASTORE_DIR) + if err != nil { + return nil, err + } + + err = vcConn.DatastoreUpload(mc.ISO) + if err != nil { + return nil, err + } + + memory := strconv.Itoa(int(mc.Memory)) + isoPath := fmt.Sprintf("%s/%s", DATASTORE_DIR, DATASTORE_ISO_NAME) + err = vcConn.VmCreate(isoPath, memory, mc.VM) + if err != nil { + return nil, err + } + + fmt.Fprintf(os.Stdout, "Configuring the virtual machine %s... ", mc.VM) + diskSize := strconv.Itoa(int(mc.DiskSize)) + err = vcConn.VmDiskCreate(mc.VM, diskSize) + if err != nil { + fmt.Fprintf(os.Stderr, "failed!\n") + return nil, err + } + + err = vcConn.VmAttachNetwork(mc.VM) + if err != nil { + fmt.Fprintf(os.Stderr, "failed!\n") + return nil, err + } + + fmt.Fprintf(os.Stdout, "ok!\n") + cpu, err := strconv.ParseUint(cfg.Cpu, 10, 32) + if err != nil { + return nil, err + } + + m := &Machine{ + Name: mc.VM, + State: driver.Poweroff, + CPUs: uint(cpu), + Memory: mc.Memory, + VcenterIp: cfg.VcenterIp, + VcenterUser: cfg.VcenterUser, + Datacenter: cfg.VcenterDC, + Network: cfg.VcenterNet, + SshPubKey: mc.SSHKey + ".pub", + } + return m, nil +} + +func ParseVmProperty(stdout string, m *Machine) { + currentCpu := strings.Trim(strings.Split(strings.Split(stdout, "CPU:")[1], "vCPU")[0], " ") + if cpus, err := strconv.ParseUint(currentCpu, 10, 32); err == nil { + m.CPUs = uint(cpus) + } + currentMem := strings.Trim(strings.Split(strings.Split(stdout, "Memory:")[1], "MB")[0], " ") + if mem, err := strconv.ParseUint(currentMem, 10, 32); err == nil { + m.Memory = uint(mem) + } + if strings.Contains(stdout, "poweredOn") { + m.State = driver.Running + m.VmIp = strings.Trim(strings.Trim(strings.Split(stdout, "IP address:")[1], " "), "\n") + } +} + +func GetDriverCfg(mc *driver.MachineConfig) error { + vcenterIp := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterIp"] + if vcenterIp == nil { + if cfg.VcenterIp == "" { + return errors.NewIncompleteVcConfigError("vCenter IP") + } + } else { + cfg.VcenterIp = vcenterIp.(string) + } + vcenterUser := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterUser"] + if vcenterUser == nil { + if cfg.VcenterUser == "" { + return errors.NewIncompleteVcConfigError("vCenter User") + } + } else { + cfg.VcenterUser = vcenterUser.(string) + } + vcenterDC := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterDatacenter"] + if vcenterDC == nil { + if cfg.VcenterDC == "" { + return errors.NewIncompleteVcConfigError("vCenter Datacenter") + } + } else { + cfg.VcenterDC = vcenterDC.(string) + } + vcenterDS := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterDatastore"] + if vcenterDS == nil { + if cfg.VcenterDS == "" { + return errors.NewIncompleteVcConfigError("vCenter Datastore") + } + } else { + cfg.VcenterDS = vcenterDS.(string) + } + vcenterNet := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterNetwork"] + if vcenterNet == nil { + if cfg.VcenterNet == "" { + return errors.NewIncompleteVcConfigError("vCenter Network") + } + } else { + cfg.VcenterNet = vcenterNet.(string) + } + cpu := mc.DriverCfg["vsphere"].(map[string]interface{})["VmCPU"] + if cpu == nil { + if cfg.Cpu == "" { + cfg.Cpu = strconv.Itoa(DEFAULT_CPU_NUMBER) + } + } else { + cfg.Cpu = strconv.Itoa(int(cpu.(int64))) + } + + // govc path information are optional as user may want to use the default + govc := mc.DriverCfg["vsphere"].(map[string]interface{})["Govc"] + if govc != nil { + cfg.Govc = govc.(string) + } + + // vcenter resource pool and host ip are nullable configurations + pool := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterPool"] + if pool != nil { + cfg.VcenterPool = pool.(string) + } + hostIp := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterHostIp"] + if hostIp != nil { + cfg.VcenterHostIp = hostIp.(string) + } + return nil +} + +// Machine information. +type Machine struct { + Name string + State driver.MachineState + CPUs uint + Memory uint + VcenterIp string // the vcenter the machine belongs to + VcenterUser string // the vcenter user/admin to own the machine + Datacenter string // the datacenter the machine locates + Network string // the network the machine is using + VmIp string // the Ip address of the machine + SshPubKey string // pass SSH here so the vm knows the source of authorized_keys +} + +// Refresh reloads the machine information. +func (m *Machine) Refresh() error { + vcConn := NewVcConn(&cfg) + stdout, err := vcConn.VmInfo(m.Name) + if err != nil { + return err + } + ParseVmProperty(stdout, m) + return nil +} + +// Start starts the machine. +// for vSphere driver, the start process includes the following changes +// 1. start the docker virtual machine; +// 2. fetch the ip address from the virtual machine (with open-vmtools); +// 3. upload the ssh key to the virtual machine; +func (m *Machine) Start() error { + switch m.State { + case driver.Running: + msg := fmt.Sprintf("VM %s has already been started", m.Name) + fmt.Println(msg) + return nil + case driver.Poweroff: + // TODO add transactional or error handling in the following steps + vcConn := NewVcConn(&cfg) + err := vcConn.VmPowerOn(m.Name) + 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(m.Name) + if err != nil { + return err + } + + fmt.Fprintf(os.Stdout, "Configuring virtual machine %s... ", m.Name) + err = vcConn.GuestMkdir("docker", "tcuser", m.Name, "/home/docker/.ssh") + if err != nil { + fmt.Fprintf(os.Stdout, "failed!\n") + return err + } + err = vcConn.GuestUpload("docker", "tcuser", m.Name, m.SshPubKey, + "/home/docker/.ssh/authorized_keys") + if err != nil { + fmt.Fprintf(os.Stdout, "failed!\n") + return err + } + fmt.Fprintf(os.Stdout, "ok!\n") + } + return nil +} + +// Suspend suspends the machine and saves its state to disk. +func (m *Machine) Save() error { + return driver.ErrNotSupported +} + +// Pause pauses the execution of the machine. +func (m *Machine) Pause() error { + return driver.ErrNotSupported +} + +// Currently make stop equivalent to poweroff as there is no shutdown guestOS API +// yet with current open-vmtools and govc +func (m *Machine) Stop() error { + vcConn := NewVcConn(&cfg) + err := vcConn.VmPowerOff(m.Name) + if err != nil { + return err + } + m.State = driver.Poweroff + return err +} + +// Poweroff forcefully stops the machine. State is lost and might corrupt the disk image. +func (m *Machine) Poweroff() error { + vcConn := NewVcConn(&cfg) + err := vcConn.VmPowerOff(m.Name) + if err != nil { + return err + } + m.State = driver.Poweroff + return err +} + +// Restart gracefully restarts the machine. +func (m *Machine) Restart() error { + switch m.State { + case driver.Running: + if err := m.Stop(); err != nil { + return err + } + case driver.Poweroff: + fmt.Fprintf(os.Stdout, "Machine %s already stopped, starting it... \n", m.Name) + } + return m.Start() +} + +// Reset forcefully restarts the machine. State is lost and might corrupt the disk image. +func (m *Machine) Reset() error { + return m.Restart() +} + +// Get current name +func (m *Machine) GetName() string { + return m.Name +} + +// Get machine address +func (m *Machine) GetAddr() string { + return m.VmIp +} + +// Get current state +func (m *Machine) GetState() driver.MachineState { + return m.State +} + +// Get serial file +func (m *Machine) GetSerialFile() string { + return "" +} + +// Get Docker port +func (m *Machine) GetDockerPort() uint { + return 2375 +} + +// Get SSH port +func (m *Machine) GetSSHPort() uint { + return 22 +} + +// Delete deletes the machine and associated disk images. +func (m *Machine) Delete() error { + if m.State == driver.Running { + msg := fmt.Sprintf("Please poweroff machine %s before delete", m.Name) + fmt.Println(msg) + return errors.NewInvalidStateError(m.Name) + } + vcConn := NewVcConn(&cfg) + err := vcConn.VmDestroy(m.Name) + if err != nil { + return err + } + return nil +} + +// Modify changes the settings of the machine. +func (m *Machine) Modify() error { + fmt.Printf("Modify %s: %s\n", m.Name, m.State) + return m.Refresh() +} + +// AddNATPF adds a NAT port forarding rule to the n-th NIC with the given name. +func (m *Machine) AddNATPF(n int, name string, rule driver.PFRule) error { + fmt.Println("Add NAT PF") + return nil +} + +// DelNATPF deletes the NAT port forwarding rule with the given name from the n-th NIC. +func (m *Machine) DelNATPF(n int, name string) error { + fmt.Println("Del NAT PF") + return nil +} + +// SetNIC set the n-th NIC. +func (m *Machine) SetNIC(n int, nic driver.NIC) error { + fmt.Println("Set NIC") + return nil +} + +// AddStorageCtl adds a storage controller with the given name. +func (m *Machine) AddStorageCtl(name string, ctl driver.StorageController) error { + fmt.Println("Add storage ctl") + return nil +} + +// DelStorageCtl deletes the storage controller with the given name. +func (m *Machine) DelStorageCtl(name string) error { + fmt.Println("Del storage ctl") + return nil +} + +// AttachStorage attaches a storage medium to the named storage controller. +func (m *Machine) AttachStorage(ctlName string, medium driver.StorageMedium) error { + fmt.Println("Attach storage") + return nil +} diff --git a/vsphere/vcenter.go b/vsphere/vcenter.go new file mode 100644 index 0000000..eaec694 --- /dev/null +++ b/vsphere/vcenter.go @@ -0,0 +1,311 @@ +package vsphere + +import ( + "fmt" + "os" + "strings" + + "github.com/boot2docker/boot2docker-cli/vsphere/errors" + "github.com/howeyc/gopass" +) + +type VcConn struct { + cfg *DriverCfg + password string +} + +func NewVcConn(cfg *DriverCfg) VcConn { + return VcConn{ + cfg: cfg, + password: "", + } +} + +func (conn VcConn) Login() error { + err := conn.queryAboutInfo() + if err == nil { + return nil + } + if _, ok := err.(*errors.GovcNotFoundError); ok { + return err + } + + fmt.Fprintf(os.Stdout, "Enter vCenter Password: ") + password := gopass.GetPasswd() + conn.password = string(password[:]) + + err = conn.queryAboutInfo() + if err == nil { + return nil + } + return err +} + +func (conn VcConn) DatastoreLs(path string) (string, error) { + args := []string{"datastore.ls"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--ds=%s", conn.cfg.VcenterDS)) + args = append(args, path) + stdout, stderr, err := govcOutErr(args...) + if stderr == "" && err == nil { + return stdout, nil + } + return "", errors.NewDatastoreError(conn.cfg.VcenterDC, "ls", stderr) +} + +func (conn VcConn) DatastoreMkdir(dirName string) error { + _, err := conn.DatastoreLs(dirName) + if err == nil { + return nil + } + + fmt.Fprintf(os.Stdout, "Creating directory %s on datastore %s of vCenter %s... ", + dirName, conn.cfg.VcenterDS, conn.cfg.VcenterIp) + + args := []string{"datastore.mkdir"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--ds=%s", conn.cfg.VcenterDS)) + args = append(args, dirName) + _, stderr, err := govcOutErr(args...) + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewDatastoreError(conn.cfg.VcenterDS, "mkdir", stderr) + } +} + +func (conn VcConn) DatastoreUpload(localPath string) error { + stdout, err := conn.DatastoreLs(DATASTORE_DIR) + if err == nil && strings.Contains(stdout, DATASTORE_ISO_NAME) { + fmt.Fprintf(os.Stdout, "boot2docker ISO already uploaded, skipping upload... \n") + return nil + } + + fmt.Fprintf(os.Stdout, "Uploading %s to %s on datastore %s of vCenter %s... ", + localPath, DATASTORE_DIR, conn.cfg.VcenterDS, conn.cfg.VcenterIp) + + dsPath := fmt.Sprintf("%s/%s", DATASTORE_DIR, DATASTORE_ISO_NAME) + args := []string{"datastore.upload"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--ds=%s", conn.cfg.VcenterDS)) + args = append(args, localPath) + args = append(args, dsPath) + _, stderr, err := govcOutErr(args...) + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewDatastoreError(conn.cfg.VcenterDC, "upload", stderr) + } +} + +func (conn VcConn) VmInfo(vmName string) (string, error) { + args := []string{"vm.info"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--dc=%s", conn.cfg.VcenterDC)) + args = append(args, vmName) + + stdout, stderr, err := govcOutErr(args...) + if strings.Contains(stdout, "Name") && stderr == "" && err == nil { + return stdout, nil + } else { + return "", errors.NewVmError("find", vmName, "VM not found") + } +} + +func (conn VcConn) VmCreate(isoPath, memory, vmName string) error { + fmt.Fprintf(os.Stdout, "Creating virtual machine %s of vCenter %s... ", + vmName, conn.cfg.VcenterIp) + + args := []string{"vm.create"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--net=%s", conn.cfg.VcenterNet)) + args = append(args, fmt.Sprintf("--dc=%s", conn.cfg.VcenterDC)) + args = append(args, fmt.Sprintf("--ds=%s", conn.cfg.VcenterDS)) + args = append(args, fmt.Sprintf("--iso=%s", isoPath)) + args = append(args, fmt.Sprintf("--m=%s", memory)) + args = append(args, fmt.Sprintf("--c=%s", conn.cfg.Cpu)) + args = append(args, "--disk.controller=scsi") + args = append(args, "--on=false") + if conn.cfg.VcenterPool != "" { + args = append(args, fmt.Sprintf("--pool=%s", conn.cfg.VcenterPool)) + } + if conn.cfg.VcenterHostIp != "" { + args = append(args, fmt.Sprintf("--host.ip=%s", conn.cfg.VcenterHostIp)) + } + args = append(args, vmName) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewVmError("create", vmName, stderr) + } +} + +func (conn VcConn) VmPowerOn(vmName string) error { + fmt.Fprintf(os.Stdout, "Powering on virtual machine %s of vCenter %s... ", + vmName, conn.cfg.VcenterIp) + + args := []string{"vm.power"} + args = conn.AppendConnectionString(args) + args = append(args, "-on") + args = append(args, vmName) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewVmError("power on", vmName, stderr) + } +} + +func (conn VcConn) VmPowerOff(vmName string) error { + fmt.Fprintf(os.Stdout, "Powering off virtual machine %s of vCenter %s... ", + vmName, conn.cfg.VcenterIp) + + args := []string{"vm.power"} + args = conn.AppendConnectionString(args) + args = append(args, "-off") + args = append(args, vmName) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewVmError("power on", vmName, stderr) + } +} + +func (conn VcConn) VmDestroy(vmName string) error { + fmt.Fprintf(os.Stdout, "Deleting virtual machine %s of vCenter %s... ", + vmName, conn.cfg.VcenterIp) + + args := []string{"vm.destroy"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--dc=%s", conn.cfg.VcenterDC)) + args = append(args, vmName) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewVmError("delete", vmName, stderr) + } + +} + +func (conn VcConn) VmDiskCreate(vmName, diskSize string) error { + args := []string{"vm.disk.create"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--vm=%s", vmName)) + args = append(args, fmt.Sprintf("--ds=%s", conn.cfg.VcenterDS)) + args = append(args, fmt.Sprintf("--name=%s", vmName)) + args = append(args, fmt.Sprintf("--size=%sMiB", diskSize)) + + _, stderr, err := govcOutErr(args...) + if stderr == "" && err == nil { + return nil + } else { + return errors.NewVmError("add network", vmName, stderr) + } +} + +func (conn VcConn) VmAttachNetwork(vmName string) error { + args := []string{"vm.network.add"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--vm=%s", vmName)) + args = append(args, fmt.Sprintf("--net=%s", conn.cfg.VcenterNet)) + + _, stderr, err := govcOutErr(args...) + if stderr == "" && err == nil { + return nil + } else { + return errors.NewVmError("add network", vmName, stderr) + } +} + +func (conn VcConn) VmFetchIp(vmName string) (string, error) { + fmt.Fprintf(os.Stdout, "Fetching IP on virtual machine %s of vCenter %s... ", + vmName, conn.cfg.VcenterIp) + + args := []string{"vm.ip"} + args = conn.AppendConnectionString(args) + args = append(args, vmName) + stdout, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return stdout, nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return "", errors.NewVmError("fetching IP", vmName, stderr) + } +} + +func (conn VcConn) GuestMkdir(guestUser, guestPass, vmName, dirName string) error { + args := []string{"guest.mkdir"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--l=%s:%s", guestUser, guestPass)) + args = append(args, fmt.Sprintf("--vm=%s", vmName)) + args = append(args, "-p") + args = append(args, dirName) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + return nil + } else { + return errors.NewGuestError("mkdir", vmName, stderr) + } +} + +func (conn VcConn) GuestUpload(guestUser, guestPass, vmName, localPath, remotePath string) error { + args := []string{"guest.upload"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--l=%s:%s", guestUser, guestPass)) + args = append(args, fmt.Sprintf("--vm=%s", vmName)) + args = append(args, "-f") + args = append(args, localPath) + args = append(args, remotePath) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + return nil + } else { + return errors.NewGuestError("upload", vmName, stderr) + } +} + +func (conn VcConn) AppendConnectionString(args []string) []string { + if conn.password == "" { + args = append(args, fmt.Sprintf("--u=%s@%s", conn.cfg.VcenterUser, cfg.VcenterIp)) + } else { + args = append(args, fmt.Sprintf("--u=%s:%s@%s", conn.cfg.VcenterUser, conn.password, conn.cfg.VcenterIp)) + } + args = append(args, "--k=true") + return args +} + +func (conn VcConn) queryAboutInfo() error { + args := []string{"about"} + args = conn.AppendConnectionString(args) + stdout, _, err := govcOutErr(args...) + if strings.Contains(stdout, "Name") { + return nil + } + if _, ok := err.(*errors.GovcNotFoundError); ok { + return err + } + return errors.NewInvalidLoginError() +}