docs/drivers/vmwarevsphere/vsphere.go

522 lines
12 KiB
Go

/*
* Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/
package vmwarevsphere
import (
"archive/tar"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/docker/machine/log"
"github.com/codegangsta/cli"
"github.com/docker/machine/drivers"
"github.com/docker/machine/drivers/vmwarevsphere/errors"
"github.com/docker/machine/ssh"
"github.com/docker/machine/state"
"github.com/docker/machine/utils"
)
const (
isoFilename = "boot2docker.iso"
B2DISOName = isoFilename
DefaultCPUNumber = 2
B2DUser = "docker"
B2DPass = "tcuser"
)
type Driver struct {
IPAddress string
MachineName string
SSHUser string
SSHPort int
CPU int
Memory int
DiskSize int
Boot2DockerURL string
IP string
Username string
Password string
Network string
Datastore string
Datacenter string
Pool string
HostIP string
storePath string
ISO string
CaCertPath string
PrivateKeyPath string
SwarmMaster bool
SwarmHost string
SwarmDiscovery string
}
func init() {
drivers.Register("vmwarevsphere", &drivers.RegisteredDriver{
New: NewDriver,
GetCreateFlags: GetCreateFlags,
})
}
// GetCreateFlags registers the flags this driver adds to
// "docker hosts create"
func GetCreateFlags() []cli.Flag {
return []cli.Flag{
cli.IntFlag{
EnvVar: "VSPHERE_CPU_COUNT",
Name: "vmwarevsphere-cpu-count",
Usage: "vSphere CPU number for docker VM",
Value: 2,
},
cli.IntFlag{
EnvVar: "VSPHERE_MEMORY_SIZE",
Name: "vmwarevsphere-memory-size",
Usage: "vSphere size of memory for docker VM (in MB)",
Value: 2048,
},
cli.IntFlag{
EnvVar: "VSPHERE_DISK_SIZE",
Name: "vmwarevsphere-disk-size",
Usage: "vSphere size of disk for docker VM (in MB)",
Value: 20000,
},
cli.StringFlag{
EnvVar: "VSPHERE_BOOT2DOCKER_URL",
Name: "vmwarevsphere-boot2docker-url",
Usage: "vSphere URL for boot2docker image",
},
cli.StringFlag{
EnvVar: "VSPHERE_VCENTER",
Name: "vmwarevsphere-vcenter",
Usage: "vSphere IP/hostname for vCenter",
},
cli.StringFlag{
EnvVar: "VSPHERE_USERNAME",
Name: "vmwarevsphere-username",
Usage: "vSphere username",
},
cli.StringFlag{
EnvVar: "VSPHERE_PASSWORD",
Name: "vmwarevsphere-password",
Usage: "vSphere password",
},
cli.StringFlag{
EnvVar: "VSPHERE_NETWORK",
Name: "vmwarevsphere-network",
Usage: "vSphere network where the docker VM will be attached",
},
cli.StringFlag{
EnvVar: "VSPHERE_DATASTORE",
Name: "vmwarevsphere-datastore",
Usage: "vSphere datastore for docker VM",
},
cli.StringFlag{
EnvVar: "VSPHERE_DATACENTER",
Name: "vmwarevsphere-datacenter",
Usage: "vSphere datacenter for docker VM",
},
cli.StringFlag{
EnvVar: "VSPHERE_POOL",
Name: "vmwarevsphere-pool",
Usage: "vSphere resource pool for docker VM",
},
cli.StringFlag{
EnvVar: "VSPHERE_COMPUTE_IP",
Name: "vmwarevsphere-compute-ip",
Usage: "vSphere compute host IP where the docker VM will be instantiated",
},
}
}
func NewDriver(machineName string, storePath string, caCert string, privateKey string) (drivers.Driver, error) {
return &Driver{MachineName: machineName, storePath: storePath, CaCertPath: caCert, PrivateKeyPath: privateKey}, nil
}
func (d *Driver) AuthorizePort(ports []*drivers.Port) error {
return nil
}
func (d *Driver) DeauthorizePort(ports []*drivers.Port) error {
return nil
}
func (d *Driver) GetMachineName() string {
return d.MachineName
}
func (d *Driver) GetSSHHostname() (string, error) {
return d.GetIP()
}
func (d *Driver) GetSSHKeyPath() string {
return filepath.Join(d.storePath, "id_rsa")
}
func (d *Driver) GetSSHPort() (int, error) {
if d.SSHPort == 0 {
d.SSHPort = 22
}
return d.SSHPort, nil
}
func (d *Driver) GetSSHUsername() string {
if d.SSHUser == "" {
d.SSHUser = "docker"
}
return d.SSHUser
}
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")
imgPath := utils.GetMachineCacheDir()
commonIsoPath := filepath.Join(imgPath, isoFilename)
d.ISO = path.Join(commonIsoPath)
return nil
}
func (d *Driver) GetURL() (string, error) {
ip, _ := d.GetIP()
if ip == "" {
return "", nil
}
return fmt.Sprintf("tcp://%s:2376", ip), 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
}
func (d *Driver) PreCreateCheck() error {
return nil
}
// the current implementation does the following:
// 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 {
if err := d.checkVsphereConfig(); err != nil {
return err
}
b2dutils := utils.NewB2dUtils("", "")
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, path.Join(d.storePath, "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(path.Join(d.storePath, "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
}