mirror of https://github.com/docker/docs.git
448 lines
10 KiB
Go
448 lines
10 KiB
Go
/*
|
|
* Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
|
|
*/
|
|
|
|
package vmwarefusion
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
"github.com/codegangsta/cli"
|
|
"github.com/docker/machine/drivers"
|
|
"github.com/docker/machine/ssh"
|
|
"github.com/docker/machine/state"
|
|
)
|
|
|
|
const (
|
|
B2D_USER = "docker"
|
|
B2D_PASS = "tcuser"
|
|
dockerConfigDir = "/var/lib/boot2docker"
|
|
)
|
|
|
|
// Driver for VMware Fusion
|
|
type Driver struct {
|
|
MachineName string
|
|
Memory int
|
|
DiskSize int
|
|
ISO string
|
|
Boot2DockerURL string
|
|
CaCertPath string
|
|
PrivateKeyPath string
|
|
|
|
storePath string
|
|
}
|
|
|
|
type CreateFlags struct {
|
|
Boot2DockerURL *string
|
|
Memory *int
|
|
DiskSize *int
|
|
}
|
|
|
|
func init() {
|
|
drivers.Register("vmwarefusion", &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.StringFlag{
|
|
EnvVar: "FUSION_BOOT2DOCKER_URL",
|
|
Name: "vmwarefusion-boot2docker-url",
|
|
Usage: "Fusion URL for boot2docker image",
|
|
},
|
|
cli.IntFlag{
|
|
EnvVar: "FUSION_MEMORY_SIZE",
|
|
Name: "vmwarefusion-memory-size",
|
|
Usage: "Fusion size of memory for host VM (in MB)",
|
|
Value: 1024,
|
|
},
|
|
cli.IntFlag{
|
|
EnvVar: "FUSION_DISK_SIZE",
|
|
Name: "vmwarefusion-disk-size",
|
|
Usage: "Fusion size of disk for host VM (in MB)",
|
|
Value: 20000,
|
|
},
|
|
}
|
|
}
|
|
|
|
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) DriverName() string {
|
|
return "vmwarefusion"
|
|
}
|
|
|
|
func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
|
d.Memory = flags.Int("vmwarefusion-memory-size")
|
|
d.DiskSize = flags.Int("vmwarefusion-disk-size")
|
|
d.Boot2DockerURL = flags.String("vmwarefusion-boot2docker-url")
|
|
d.ISO = path.Join(d.storePath, "boot2docker.iso")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) GetURL() (string, error) {
|
|
ip, err := d.GetIP()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if ip == "" {
|
|
return "", nil
|
|
}
|
|
return fmt.Sprintf("tcp://%s:2376", ip), nil
|
|
}
|
|
|
|
func (d *Driver) GetIP() (string, error) {
|
|
ip, err := d.getIPfromDHCPLease()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return ip, nil
|
|
}
|
|
|
|
func (d *Driver) GetState() (state.State, error) {
|
|
// VMRUN only tells use if the vm is running or not
|
|
if stdout, _, _ := vmrun("list"); strings.Contains(stdout, d.vmxPath()) {
|
|
return state.Running, nil
|
|
}
|
|
return state.Stopped, nil
|
|
}
|
|
|
|
func (d *Driver) Create() error {
|
|
|
|
var isoURL string
|
|
if d.Boot2DockerURL != "" {
|
|
isoURL = d.Boot2DockerURL
|
|
} else {
|
|
// HACK: Docker 1.3 boot2docker image with identity auth and vmtoolsd
|
|
isoURL = "https://github.com/cloudnativeapps/boot2docker/releases/download/1.3.1_vmw-identity/boot2docker.iso"
|
|
}
|
|
log.Infof("Downloading boot2docker...")
|
|
if err := downloadISO(d.storePath, "boot2docker.iso", isoURL); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("Creating SSH key...")
|
|
if err := ssh.GenerateSSHKey(d.sshKeyPath()); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("Creating VM...")
|
|
if err := os.MkdirAll(d.storePath, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := os.Stat(d.vmxPath()); err == nil {
|
|
return ErrMachineExist
|
|
}
|
|
|
|
// Generate vmx config file from template
|
|
vmxt := template.Must(template.New("vmx").Parse(vmx))
|
|
vmxfile, err := os.Create(d.vmxPath())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vmxt.Execute(vmxfile, d)
|
|
|
|
// Generate vmdk file
|
|
diskImg := filepath.Join(d.storePath, fmt.Sprintf("%s.vmdk", d.MachineName))
|
|
if _, err := os.Stat(diskImg); err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
|
|
if err := vdiskmanager(diskImg, d.DiskSize); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := d.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
var ip string
|
|
|
|
log.Infof("Waiting for VM to come online...")
|
|
for i := 1; i <= 60; i++ {
|
|
ip, err = d.getIPfromDHCPLease()
|
|
if err != nil {
|
|
log.Debugf("Not there yet %d/%d, error: %s", i, 60, err)
|
|
time.Sleep(2 * time.Second)
|
|
continue
|
|
}
|
|
|
|
if ip != "" {
|
|
log.Debugf("Got an ip: %s", ip)
|
|
break
|
|
}
|
|
}
|
|
|
|
if ip == "" {
|
|
return fmt.Errorf("Machine didn't return an IP after 120 seconds, aborting")
|
|
}
|
|
|
|
// we got an IP, let's copy ssh keys over
|
|
// Create the dir
|
|
vmrun("-gu", B2D_USER, "-gp", B2D_PASS, "createDirectoryInGuest", d.vmxPath(), "/home/docker/.ssh")
|
|
|
|
// Copy SSH keys
|
|
vmrun("-gu", B2D_USER, "-gp", B2D_PASS, "CopyFileFromHostToGuest", d.vmxPath(), d.publicSSHKeyPath(), "/home/docker/.ssh/authorized_keys")
|
|
vmrun("-gu", B2D_USER, "-gp", B2D_PASS, "CopyFileFromHostToGuest", d.vmxPath(), d.publicSSHKeyPath(), "/home/docker/.ssh/authorized_keys2")
|
|
|
|
if err := drivers.AddPublicKeyToAuthorizedHosts(d, "/root/.docker/authorized-keys.d"); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("Setting hostname: %s", d.MachineName)
|
|
cmd, err := d.GetSSHCommand(fmt.Sprintf(
|
|
"echo \"127.0.0.1 %s\" | sudo tee -a /etc/hosts && sudo hostname %s && echo \"%s\" | sudo tee /etc/hostname",
|
|
d.MachineName,
|
|
d.MachineName,
|
|
d.MachineName,
|
|
))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd, err = d.GetSSHCommand("sudo /etc/init.d/docker restart; sleep 5")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
//cmd, err := d.GetSSHCommand("sudo /etc/init.d/docker restart; sleep 5")
|
|
//if err != nil {
|
|
// return err
|
|
//}
|
|
//if err := cmd.Run(); err != nil {
|
|
// return err
|
|
//}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) Start() error {
|
|
vmrun("start", d.vmxPath(), "nogui")
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) Stop() error {
|
|
vmrun("stop", d.vmxPath(), "nogui")
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) Remove() error {
|
|
|
|
s, _ := d.GetState()
|
|
if s == state.Running {
|
|
if err := d.Kill(); err != nil {
|
|
return fmt.Errorf("Error stopping VM before deletion")
|
|
}
|
|
}
|
|
|
|
vmrun("deleteVM", d.vmxPath(), "nogui")
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) Restart() error {
|
|
vmrun("reset", d.vmxPath(), "nogui")
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) Kill() error {
|
|
vmrun("stop", d.vmxPath(), "nogui")
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) StartDocker() error {
|
|
log.Debug("Starting Docker...")
|
|
|
|
cmd, err := d.GetSSHCommand("sudo /etc/init.d/docker start")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) StopDocker() error {
|
|
log.Debug("Stopping Docker...")
|
|
|
|
cmd, err := d.GetSSHCommand("sudo /etc/init.d/docker stop ; exit 0")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) GetDockerConfigDir() string {
|
|
return dockerConfigDir
|
|
}
|
|
|
|
func (d *Driver) Upgrade() error {
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) {
|
|
|
|
ip, err := d.GetIP()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ssh.GetSSHCommand(ip, 22, "docker", d.sshKeyPath(), args...), nil
|
|
}
|
|
|
|
func (d *Driver) vmxPath() string {
|
|
return path.Join(d.storePath, fmt.Sprintf("%s.vmx", d.MachineName))
|
|
}
|
|
|
|
func (d *Driver) vmdkPath() string {
|
|
return path.Join(d.storePath, fmt.Sprintf("%s.vmdk", d.MachineName))
|
|
}
|
|
|
|
// Download boot2docker ISO image for the given tag and save it at dest.
|
|
func downloadISO(dir, file, url string) error {
|
|
rsp, err := http.Get(url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rsp.Body.Close()
|
|
|
|
// Download to a temp file first then rename it to avoid partial download.
|
|
f, err := ioutil.TempFile(dir, file+".tmp")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.Remove(f.Name())
|
|
if _, err := io.Copy(f, rsp.Body); err != nil {
|
|
// TODO: display download progress?
|
|
return err
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
return err
|
|
}
|
|
if err := os.Rename(f.Name(), path.Join(dir, file)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) getIPfromDHCPLease() (string, error) {
|
|
var vmxfh *os.File
|
|
var dhcpfh *os.File
|
|
var vmxcontent []byte
|
|
var dhcpcontent []byte
|
|
var macaddr string
|
|
var err error
|
|
var lastipmatch string
|
|
var currentip string
|
|
var lastleaseendtime time.Time
|
|
var currentleadeendtime time.Time
|
|
|
|
// DHCP lease table for NAT vmnet interface
|
|
var dhcpfile = "/var/db/vmware/vmnet-dhcpd-vmnet8.leases"
|
|
|
|
if vmxfh, err = os.Open(d.vmxPath()); err != nil {
|
|
return "", err
|
|
}
|
|
defer vmxfh.Close()
|
|
|
|
if vmxcontent, err = ioutil.ReadAll(vmxfh); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Look for generatedAddress as we're passing a VMX with addressType = "generated".
|
|
vmxparse := regexp.MustCompile(`^ethernet0.generatedAddress\s*=\s*"(.*?)"\s*$`)
|
|
for _, line := range strings.Split(string(vmxcontent), "\n") {
|
|
if matches := vmxparse.FindStringSubmatch(line); matches == nil {
|
|
continue
|
|
} else {
|
|
macaddr = strings.ToLower(matches[1])
|
|
}
|
|
}
|
|
|
|
if macaddr == "" {
|
|
return "", fmt.Errorf("couldn't find MAC address in VMX file %s", d.vmxPath())
|
|
}
|
|
|
|
log.Debugf("MAC address in VMX: %s", macaddr)
|
|
if dhcpfh, err = os.Open(dhcpfile); err != nil {
|
|
return "", err
|
|
}
|
|
defer dhcpfh.Close()
|
|
|
|
if dhcpcontent, err = ioutil.ReadAll(dhcpfh); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Get the IP from the lease table.
|
|
leaseip := regexp.MustCompile(`^lease (.+?) {$`)
|
|
// Get the lease end date time.
|
|
leaseend := regexp.MustCompile(`^\s*ends \d (.+?);$`)
|
|
// Get the MAC address associated.
|
|
leasemac := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`)
|
|
|
|
for _, line := range strings.Split(string(dhcpcontent), "\n") {
|
|
|
|
if matches := leaseip.FindStringSubmatch(line); matches != nil {
|
|
lastipmatch = matches[1]
|
|
continue
|
|
}
|
|
|
|
if matches := leaseend.FindStringSubmatch(line); matches != nil {
|
|
lastleaseendtime, _ = time.Parse("2006/01/02 15:04:05", matches[1])
|
|
continue
|
|
}
|
|
|
|
if matches := leasemac.FindStringSubmatch(line); matches != nil && matches[1] == macaddr && currentleadeendtime.Before(lastleaseendtime) {
|
|
currentip = lastipmatch
|
|
currentleadeendtime = lastleaseendtime
|
|
}
|
|
}
|
|
|
|
if currentip == "" {
|
|
return "", fmt.Errorf("IP not found for MAC %s in DHCP leases", macaddr)
|
|
}
|
|
|
|
log.Debugf("IP found in DHCP lease table: %s", currentip)
|
|
return currentip, nil
|
|
|
|
}
|
|
|
|
func (d *Driver) sshKeyPath() string {
|
|
return path.Join(d.storePath, "id_rsa")
|
|
}
|
|
|
|
func (d *Driver) publicSSHKeyPath() string {
|
|
return d.sshKeyPath() + ".pub"
|
|
}
|