/* * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. */ package vmwarevsphere import ( "archive/tar" "fmt" "io/ioutil" "net" "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/mcnflag" "github.com/docker/machine/libmachine/mcnutils" "github.com/docker/machine/libmachine/ssh" "github.com/docker/machine/libmachine/state" ) const ( isoFilename = "boot2docker.iso" B2DUser = "docker" 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 ISO string } const ( defaultCpus = 2 defaultMemory = 2048 defaultDiskSize = 20000 ) // GetCreateFlags registers the flags this driver adds to // "docker hosts create" func (d *Driver) GetCreateFlags() []mcnflag.Flag { return []mcnflag.Flag{ mcnflag.IntFlag{ EnvVar: "VSPHERE_CPU_COUNT", Name: "vmwarevsphere-cpu-count", Usage: "vSphere CPU number for docker VM", Value: defaultCpus, }, mcnflag.IntFlag{ EnvVar: "VSPHERE_MEMORY_SIZE", Name: "vmwarevsphere-memory-size", Usage: "vSphere size of memory for docker VM (in MB)", Value: defaultMemory, }, mcnflag.IntFlag{ EnvVar: "VSPHERE_DISK_SIZE", Name: "vmwarevsphere-disk-size", Usage: "vSphere size of disk for docker VM (in MB)", Value: defaultDiskSize, }, mcnflag.StringFlag{ EnvVar: "VSPHERE_BOOT2DOCKER_URL", Name: "vmwarevsphere-boot2docker-url", Usage: "vSphere URL for boot2docker image", }, mcnflag.StringFlag{ EnvVar: "VSPHERE_VCENTER", Name: "vmwarevsphere-vcenter", Usage: "vSphere IP/hostname for vCenter", }, mcnflag.StringFlag{ EnvVar: "VSPHERE_USERNAME", Name: "vmwarevsphere-username", Usage: "vSphere username", }, mcnflag.StringFlag{ EnvVar: "VSPHERE_PASSWORD", Name: "vmwarevsphere-password", Usage: "vSphere password", }, mcnflag.StringFlag{ EnvVar: "VSPHERE_NETWORK", Name: "vmwarevsphere-network", Usage: "vSphere network where the docker VM will be attached", }, mcnflag.StringFlag{ EnvVar: "VSPHERE_DATASTORE", Name: "vmwarevsphere-datastore", Usage: "vSphere datastore for docker VM", }, mcnflag.StringFlag{ EnvVar: "VSPHERE_DATACENTER", Name: "vmwarevsphere-datacenter", Usage: "vSphere datacenter for docker VM", }, mcnflag.StringFlag{ EnvVar: "VSPHERE_POOL", Name: "vmwarevsphere-pool", 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", }, } } func NewDriver(hostName, storePath string) drivers.Driver { return &Driver{ CPU: defaultCpus, Memory: defaultMemory, DiskSize: defaultDiskSize, BaseDriver: &drivers.BaseDriver{ MachineName: hostName, StorePath: storePath, }, } } func (d *Driver) GetSSHHostname() (string, error) { return d.GetIP() } func (d *Driver) GetSSHUsername() string { if d.SSHUser == "" { d.SSHUser = "docker" } return d.SSHUser } // DriverName returns the name of the driver func (d *Driver) DriverName() string { return "vmwarevsphere" } func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { d.SSHUser = "docker" d.SSHPort = 22 d.CPU = flags.Int("vmwarevsphere-cpu-count") d.Memory = flags.Int("vmwarevsphere-memory-size") d.DiskSize = flags.Int("vmwarevsphere-disk-size") d.Boot2DockerURL = flags.String("vmwarevsphere-boot2docker-url") d.IP = flags.String("vmwarevsphere-vcenter") 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.SwarmMaster = flags.Bool("swarm-master") d.SwarmHost = flags.String("swarm-host") d.SwarmDiscovery = flags.String("swarm-discovery") d.ISO = d.ResolveStorePath(isoFilename) return nil } func (d *Driver) GetURL() (string, error) { ip, _ := d.GetIP() if ip == "" { return "", nil } return fmt.Sprintf("tcp://%s", net.JoinHostPort(ip, "2376")), nil } func (d *Driver) GetIP() (string, error) { status, err := d.GetState() if status != state.Running { return "", errors.NewInvalidStateError(d.MachineName) } vcConn := NewVcConn(d) rawIP, err := vcConn.VMFetchIP() 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() if err != nil { return state.None, err } if strings.Contains(stdout, "poweredOn") { return state.Running, nil } else if strings.Contains(stdout, "poweredOff") { return state.Stopped, nil } return state.None, nil } // PreCreateCheck checks that the machine creation process can be started safely. func (d *Driver) PreCreateCheck() error { if err := d.checkVsphereConfig(); 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 { return err } return nil } // Create has the following implementation: // 1. check whether the docker directory contains the boot2docker ISO // 2. generate an SSH keypair and bundle it in a tar. // 3. create a virtual machine with the boot2docker ISO mounted; // 4. reconfigure the virtual machine network and disk size; func (d *Driver) Create() error { b2dutils := mcnutils.NewB2dUtils(d.StorePath) if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } log.Infof("Generating SSH Keypair...") if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil { return err } vcConn := NewVcConn(d) log.Infof("Uploading Boot2docker ISO ...") if err := vcConn.DatastoreMkdir(d.MachineName); 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) } if err := vcConn.DatastoreUpload(d.ISO, d.MachineName); err != nil { return err } isoPath := fmt.Sprintf("%s/%s", d.MachineName, isoFilename) if err := vcConn.VMCreate(isoPath); err != nil { return err } log.Infof("Configuring the virtual machine %s... ", d.MachineName) if err := vcConn.VMDiskCreate(); err != nil { return err } if err := vcConn.VMAttachNetwork(); err != nil { return err } if err := d.Start(); err != nil { return err } // 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 { 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 { return err } return nil } func (d *Driver) Start() error { machineState, err := d.GetState() if err != nil { return err } switch machineState { case state.Running: log.Infof("VM %s has already been started", d.MachineName) 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() if err != nil { return err } d.IPAddress, err = d.GetIP() return err } return errors.NewInvalidStateError(d.MachineName) } func (d *Driver) Stop() error { vcConn := NewVcConn(d) if err := vcConn.VMShutdown(); err != nil { return err } d.IPAddress = "" return nil } func (d *Driver) Remove() error { machineState, err := d.GetState() if err != nil { return err } if machineState == state.Running { if err = d.Kill(); err != nil { return fmt.Errorf("can't stop VM: %s", err) } } vcConn := NewVcConn(d) if err = vcConn.VMDestroy(); err != nil { return err } return nil } func (d *Driver) Restart() error { if err := d.Stop(); err != nil { return err } // Check for 120 seconds for the machine to stop for i := 1; i <= 60; i++ { machineState, err := d.GetState() if err != nil { return err } if machineState == state.Running { log.Debugf("Not there yet %d/%d", i, 60) time.Sleep(2 * time.Second) continue } if machineState == state.Stopped { break } } machineState, err := d.GetState() // If the VM is still running after 120 seconds just kill it. if machineState == state.Running { if err = d.Kill(); err != nil { return fmt.Errorf("can't stop VM: %s", err) } } return d.Start() } func (d *Driver) Kill() error { vcConn := NewVcConn(d) if err := vcConn.VMPowerOff(); err != nil { return err } d.IPAddress = "" return nil } func (d *Driver) Upgrade() error { return fmt.Errorf("upgrade is not supported for vsphere driver at this moment") } 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...") magicString := "boot2docker, this is vmware speaking" tf, err := os.Create(d.ResolveStorePath("userdata.tar")) if err != nil { return err } defer tf.Close() var fileWriter = tf tw := tar.NewWriter(fileWriter) defer tw.Close() // magicString first so we can figure out who originally wrote the tar. file := &tar.Header{Name: magicString, Size: int64(len(magicString))} if err := tw.WriteHeader(file); err != nil { return err } if _, err := tw.Write([]byte(magicString)); err != nil { return err } // .ssh/key.pub => authorized_keys file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} if err := tw.WriteHeader(file); err != nil { return err } pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) if err != nil { return err } file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} if err := tw.WriteHeader(file); err != nil { return err } if _, err := tw.Write([]byte(pubKey)); err != nil { return err } file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} if err := tw.WriteHeader(file); err != nil { return err } if _, err := tw.Write([]byte(pubKey)); err != nil { return err } if err := tw.Close(); err != nil { return err } return nil } func (d *Driver) UpgradeISO() error { vcConn := NewVcConn(d) 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) } if err := vcConn.DatastoreUpload(d.ISO, d.MachineName); err != nil { return err } return nil }