You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1104 lines
26 KiB
1104 lines
26 KiB
//This file was modified from the original work, https://github.com/xenserver/docker-machine-driver-xenserver
|
|
|
|
package xenserver
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
"path"
|
|
|
|
"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/nilshell/xmlrpc"
|
|
xsclient "github.com/xenserver/go-xenserver-client"
|
|
"golang.org/x/net/context"
|
|
|
|
diskfs "github.com/diskfs/go-diskfs"
|
|
"github.com/diskfs/go-diskfs/disk"
|
|
"github.com/diskfs/go-diskfs/filesystem"
|
|
"github.com/diskfs/go-diskfs/filesystem/iso9660"
|
|
)
|
|
|
|
const (
|
|
isoFilename = "boot2docker.iso"
|
|
tarFilename = "boot2docker.tar"
|
|
B2D_USER = "docker"
|
|
B2D_PASS = "tcuser"
|
|
)
|
|
|
|
type Driver struct {
|
|
*drivers.BaseDriver
|
|
|
|
Server string
|
|
Username string
|
|
Password string
|
|
Boot2DockerURL string
|
|
CPU uint
|
|
Memory uint
|
|
DiskSize uint
|
|
SR string
|
|
Network string
|
|
Host string
|
|
ISO string
|
|
TAR string
|
|
UploadTimeout uint
|
|
WaitTimeout uint
|
|
CaCertPath string
|
|
PrivateKeyPath string
|
|
osTemplateLabelName string
|
|
CoreosConfigDrive bool
|
|
|
|
xenAPIClient *XenAPIClient
|
|
}
|
|
|
|
// GetCreateFlags registers the flags this driver adds to
|
|
// "docker hosts create"
|
|
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
|
return []mcnflag.Flag{
|
|
mcnflag.StringFlag{
|
|
EnvVar: "XENSERVER_SERVER",
|
|
Name: "xenserver-server",
|
|
Usage: "XENSERVER server hostname/IP for docker VM",
|
|
},
|
|
mcnflag.StringFlag{
|
|
EnvVar: "XENSERVER_USERNAME",
|
|
Name: "xenserver-username",
|
|
Usage: "XENSERVER username",
|
|
},
|
|
mcnflag.StringFlag{
|
|
EnvVar: "XENSERVER_PASSWORD",
|
|
Name: "xenserver-password",
|
|
Usage: "XENSERVER password",
|
|
},
|
|
mcnflag.StringFlag{
|
|
EnvVar: "XENSERVER_BOOT2DOCKER_URL",
|
|
Name: "xenserver-boot2docker-url",
|
|
Usage: "XENSERVER URL for boot2docker image",
|
|
},
|
|
mcnflag.IntFlag{
|
|
EnvVar: "XENSERVER_VCPU_COUNT",
|
|
Name: "xenserver-vcpu-count",
|
|
Usage: "XENSERVER vCPU number for docker VM",
|
|
Value: 1,
|
|
},
|
|
mcnflag.IntFlag{
|
|
EnvVar: "XENSERVER_MEMORY_SIZE",
|
|
Name: "xenserver-memory-size",
|
|
Usage: "XENSERVER size of memory for docker VM (in MB)",
|
|
Value: 1024,
|
|
},
|
|
mcnflag.IntFlag{
|
|
EnvVar: "XENSERVER_DISK_SIZE",
|
|
Name: "xenserver-disk-size",
|
|
Usage: "XENSERVER size of disk for docker VM (in MB)",
|
|
Value: 5120,
|
|
},
|
|
mcnflag.StringFlag{
|
|
EnvVar: "XENSERVER_SR_LABEL",
|
|
Name: "xenserver-sr-label",
|
|
Usage: "XENSERVER SR label where the docker VM will be attached",
|
|
},
|
|
mcnflag.StringFlag{
|
|
EnvVar: "XENSERVER_NETWORK_LABEL",
|
|
Name: "xenserver-network-label",
|
|
Usage: "XENSERVER network label where the docker VM will be attached",
|
|
},
|
|
mcnflag.StringFlag{
|
|
EnvVar: "XENSERVER_HOST_LABEL",
|
|
Name: "xenserver-host-label",
|
|
Usage: "XENSERVER host label where the docker VM will be run",
|
|
},
|
|
mcnflag.IntFlag{
|
|
EnvVar: "XENSERVER_UPLOAD_TIMEOUT",
|
|
Name: "xenserver-upload-timeout",
|
|
Usage: "XENSERVER upload VDI timeout(seconds)",
|
|
Value: 5 * 60,
|
|
},
|
|
mcnflag.IntFlag{
|
|
EnvVar: "XENSERVER_WAIT_TIMEOUT",
|
|
Name: "xenserver-wait-timeout",
|
|
Usage: "XENSERVER wait VM start timeout(seconds)",
|
|
Value: 30 * 60,
|
|
},
|
|
mcnflag.StringFlag{
|
|
EnvVar: "XENSERVER_OS_TEMPLATE",
|
|
Name: "xenserver-os-template",
|
|
Usage: "XENSERVER OS Template Label Name",
|
|
Value: "Other install media",
|
|
},
|
|
mcnflag.StringFlag{
|
|
EnvVar: "XENSERVER_OS_USERNAME",
|
|
Name: "xenserver-os-username",
|
|
Usage: "XENSERVER Username used to SSH into guest OS",
|
|
Value: B2D_USER,
|
|
},
|
|
mcnflag.BoolFlag{
|
|
EnvVar: "XENSERVER_COREOS_CONFIGDRIVE",
|
|
Name: "xenserver-coreos-configdrive",
|
|
Usage: "XENSERVER Enable CoreOS specific ConfigDrive (requires xscontainer supplemental pack)",
|
|
},
|
|
}
|
|
}
|
|
|
|
func NewDriver() *Driver {
|
|
return &Driver{}
|
|
}
|
|
|
|
func (d *Driver) GetMachineName() string {
|
|
return d.MachineName
|
|
}
|
|
|
|
func (d *Driver) GetSSHHostname() (string, error) {
|
|
return d.GetIP()
|
|
}
|
|
|
|
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 = B2D_USER
|
|
}
|
|
|
|
return d.SSHUser
|
|
}
|
|
|
|
func (d *Driver) DriverName() string {
|
|
return "xenserver"
|
|
}
|
|
|
|
func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
|
d.SSHUser = flags.String("xenserver-os-username")
|
|
d.SSHPort = 22
|
|
d.Server = flags.String("xenserver-server")
|
|
d.Username = flags.String("xenserver-username")
|
|
d.Password = flags.String("xenserver-password")
|
|
d.Boot2DockerURL = flags.String("xenserver-boot2docker-url")
|
|
d.CPU = uint(flags.Int("xenserver-vcpu-count"))
|
|
d.Memory = uint(flags.Int("xenserver-memory-size"))
|
|
d.DiskSize = uint(flags.Int("xenserver-disk-size"))
|
|
d.SR = flags.String("xenserver-sr-label")
|
|
d.Network = flags.String("xenserver-network-label")
|
|
d.Host = flags.String("xenserver-host-label")
|
|
d.UploadTimeout = uint(flags.Int("xenserver-upload-timeout"))
|
|
d.WaitTimeout = uint(flags.Int("xenserver-wait-timeout"))
|
|
d.SwarmMaster = flags.Bool("swarm-master")
|
|
d.SwarmHost = flags.String("swarm-host")
|
|
d.SwarmDiscovery = flags.String("swarm-discovery")
|
|
d.ISO = d.ResolveStorePath(isoFilename)
|
|
d.TAR = d.ResolveStorePath(tarFilename)
|
|
d.osTemplateLabelName = flags.String("xenserver-os-template")
|
|
d.CoreosConfigDrive = flags.Bool("xenserver-coreos-configdrive")
|
|
|
|
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 err != nil {
|
|
return "", err
|
|
}
|
|
if status != state.Running {
|
|
return "", fmt.Errorf("Docker VM(%v) is not running", d.MachineName)
|
|
}
|
|
|
|
// Need Login first if it is a fresh session
|
|
c, err := d.GetXenAPIClient()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Get doker machine by label name
|
|
vm, err := c.GetUniqueVMByNameLabel(d.MachineName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Get docker machine VM metrics
|
|
metrics, err := vm.GetGuestMetrics()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Get networks metrics
|
|
networks, ok := metrics["networks"]
|
|
if !ok {
|
|
return "", fmt.Errorf("Docker VM(%v) get network metrics error: \"networks\" not presented", d.MachineName)
|
|
}
|
|
|
|
net, ok := networks.(xmlrpc.Struct)
|
|
if !ok {
|
|
return "", fmt.Errorf("Docker VM(%v) get network metrics error: \"networks\" is not a xmlrpc.Struct instance", d.MachineName)
|
|
}
|
|
|
|
for i := 0; i < len(net); i++ {
|
|
ip, ok := net[fmt.Sprintf("%d/ip", i)]
|
|
if ok && ip != "" {
|
|
return ip.(string), nil
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("Docker VM(%v) get ipaddr error", d.MachineName)
|
|
}
|
|
|
|
func (d *Driver) GetState() (state.State, error) {
|
|
var err error
|
|
|
|
// Need Login first if it is a fresh session
|
|
c, err := d.GetXenAPIClient()
|
|
if err != nil {
|
|
return state.None, err
|
|
}
|
|
|
|
// Get docker machine VM by label name
|
|
vm, err := c.GetUniqueVMByNameLabel(d.MachineName)
|
|
if err != nil {
|
|
return state.None, err
|
|
}
|
|
|
|
// Get doker machine VM power state
|
|
powerState, err := vm.GetPowerState()
|
|
if err != nil {
|
|
return state.None, err
|
|
}
|
|
|
|
// https://github.com/xapi-project/xen-api/blob/cacbb2d3c5996efbf4cf117ab862439271a8cecd/ocaml/client_records/record_util.ml#L20
|
|
switch strings.ToLower(powerState) {
|
|
case "halted":
|
|
return state.Stopped, nil
|
|
case "paused":
|
|
return state.Paused, nil
|
|
case "running":
|
|
return state.Running, nil
|
|
case "suspended":
|
|
return state.Paused, nil
|
|
case "shutting down":
|
|
return state.Stopping, nil
|
|
case "migrating":
|
|
return state.Running, nil
|
|
}
|
|
|
|
return state.None, nil
|
|
}
|
|
|
|
func (d *Driver) PreCreateCheck() error {
|
|
if d.osTemplateLabelName == "Other install media" {
|
|
// 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
|
|
}
|
|
|
|
func (d *Driver) Create() error {
|
|
var err error
|
|
|
|
d.setMachineNameIfNotSet()
|
|
|
|
if d.osTemplateLabelName == "Other install media" {
|
|
b2dutils := mcnutils.NewB2dUtils(d.StorePath)
|
|
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
log.Infof("Logging into XenServer %s...", d.Server)
|
|
c, err := d.GetXenAPIClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Generate SSH Keys
|
|
log.Infof("Creating SSH key...")
|
|
|
|
if err := ssh.GenerateSSHKey(d.sshKeyPath()); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Only upload ISO image if using Other install media, otherwise use existing VM template
|
|
var sr *xsclient.SR
|
|
var isoVdiUuid, diskVdiUuid string
|
|
|
|
// Get the SR
|
|
if d.SR == "" {
|
|
sr, err = c.GetDefaultSR()
|
|
if sr.Ref == "OpaqueRef:NULL" {
|
|
err := errors.New("No default SR found. Please configure a " +
|
|
"default or specify the SR explicitly using " +
|
|
"--xenserver-sr-label.")
|
|
log.Errorf("%v", err)
|
|
return err
|
|
}
|
|
} else {
|
|
sr, err = c.GetUniqueSRByNameLabel(d.SR)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.osTemplateLabelName == "Other install media" {
|
|
|
|
log.Infof("Creating ISO VDI...")
|
|
|
|
isoFileInfo, err := os.Stat(d.ISO)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create the VDI
|
|
isoVdi, err := sr.CreateVdi(isoFilename, isoFileInfo.Size())
|
|
if err != nil {
|
|
log.Errorf("Unable to create ISO VDI '%s': %v", isoFilename, err)
|
|
return err
|
|
}
|
|
|
|
// Import the VDI
|
|
if err = d.importVdi(isoVdi, d.ISO, time.Duration(d.UploadTimeout)*time.Second); err != nil {
|
|
return err
|
|
}
|
|
|
|
isoVdiUuid, err = isoVdi.GetUuid()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("Creating Disk VDI...")
|
|
err = d.generateDiskImage()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create the VDI
|
|
diskVdi, err := sr.CreateVdi("bootdocker disk", int64(d.DiskSize)*1024*1024)
|
|
if err != nil {
|
|
log.Errorf("Unable to create ISO VDI '%s': %v", "bootdocker disk", err)
|
|
return err
|
|
}
|
|
if err = d.importVdi(diskVdi, d.TAR, time.Duration(d.UploadTimeout)*time.Second); err != nil {
|
|
return err
|
|
}
|
|
|
|
diskVdiUuid, err = diskVdi.GetUuid()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
}
|
|
|
|
log.Infof("Creating VM...")
|
|
|
|
vm0, err := c.GetUniqueVMByNameLabel(d.osTemplateLabelName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Clone VM from VM template
|
|
vm, err := vm0.Clone(fmt.Sprintf("__gui__%s", d.MachineName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vmMacSeed, err := pseudoUuid()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hostname, err := os.Hostname()
|
|
if err != nil {
|
|
log.Errorf("Unable get local hostname")
|
|
}
|
|
|
|
otherConfig := map[string]string{
|
|
"base_template_name": d.osTemplateLabelName,
|
|
"install-methods": "cdrom,nfs,http,ftp",
|
|
"linux_template": "true",
|
|
"mac_seed": vmMacSeed,
|
|
"docker-machine-creator": hostname,
|
|
}
|
|
err = vm.SetOtherConfig(otherConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("Provision VM...")
|
|
err = vm.Provision()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set machine name
|
|
err = vm.SetNameLabel(d.MachineName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set vCPU number
|
|
err = vm.SetVCPUsMax(d.CPU)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = vm.SetVCPUsAtStartup(d.CPU)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
platform_params := map[string]string{
|
|
"acpi": "1",
|
|
"apic": "true",
|
|
"cores-per-socket": "1",
|
|
"device_id": "0001",
|
|
"nx": "true",
|
|
"pae": "true",
|
|
"vga": "std",
|
|
"videoram": "8",
|
|
"viridian": "false",
|
|
}
|
|
err = vm.SetPlatform(platform_params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set machine memory size
|
|
err = vm.SetStaticMemoryRange(uint64(d.Memory)*1024*1024, uint64(d.Memory)*1024*1024)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.osTemplateLabelName == "Other install media" {
|
|
log.Infof("Add ISO VDI to VM...")
|
|
diskVdi, err := c.GetVdiByUuid(isoVdiUuid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = vm.ConnectVdi(diskVdi, xsclient.Disk, "0")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("Add Disk VDI to VM...")
|
|
diskVdi, err = c.GetVdiByUuid(diskVdiUuid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = vm.ConnectVdi(diskVdi, xsclient.Disk, "1")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vmUuid, err := vm.GetUuid()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
srUuid, err := sr.GetUuid()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.CoreosConfigDrive {
|
|
err := d.createCoreOSConfigDrive(pubKey, vmUuid, srUuid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
log.Infof("Creating Config Drive...")
|
|
isoVdiUuid, err := d.createGenericConfigDrive(pubKey, vmUuid, sr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("Add ConfigDrive ISO VDI to VM...")
|
|
diskVdi, err := c.GetVdiByUuid(isoVdiUuid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = vm.ConnectVdi(diskVdi, xsclient.Disk, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Infof("Add Network to VM...")
|
|
var networks []*xsclient.Network
|
|
if d.Network == "" {
|
|
networks1, err := c.GetNetworks()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, network := range networks1 {
|
|
otherConfig, err := network.GetOtherConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
isInternal, ok := otherConfig["is_host_internal_management_network"]
|
|
if ok && isInternal == "true" {
|
|
continue
|
|
}
|
|
automaitc, ok := otherConfig["automatic"]
|
|
if ok && automaitc == "false" {
|
|
continue
|
|
}
|
|
networks = append(networks, network)
|
|
}
|
|
} else {
|
|
network, err := c.GetUniqueNetworkByNameLabel(d.Network)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
networks = append(networks, network)
|
|
}
|
|
if len(networks) == 0 {
|
|
return fmt.Errorf("Unable get available networks for %v", d.MachineName)
|
|
}
|
|
|
|
vifDevices, err := vm.GetAllowedVIFDevices()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(vifDevices) < len(networks) {
|
|
log.Warnf("VM(%s) networks number is limited to %d.", d.MachineName, len(vifDevices))
|
|
networks = networks[:len(vifDevices)]
|
|
}
|
|
|
|
for i, network := range networks {
|
|
_, err = vm.ConnectNetwork(network, vifDevices[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
log.Infof("Starting VM...")
|
|
if d.Host == "" {
|
|
if err = vm.Start(false, false); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
host, err := c.GetUniqueHostByNameLabel(d.Host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = vm.StartOn(host, false, false); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := d.wait(time.Duration(d.WaitTimeout) * time.Second); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("VM Created.")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) wait(timeout time.Duration) (err error) {
|
|
log.Infof("Waiting for VM to start...")
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
defer cancel()
|
|
|
|
out := make(chan error, 1)
|
|
go func(ctx context.Context, out chan<- error) {
|
|
var ip string
|
|
for {
|
|
ip, _ = d.GetIP()
|
|
if ip != "" {
|
|
break
|
|
}
|
|
if t, ok := ctx.Deadline(); ok && time.Now().After(t) {
|
|
out <- fmt.Errorf("Wait GetIP timed out")
|
|
return
|
|
}
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
|
|
port, err := d.GetSSHPort()
|
|
if err != nil {
|
|
out <- err
|
|
return
|
|
}
|
|
|
|
addr := fmt.Sprintf("%s:%d", ip, port)
|
|
d.IPAddress = ip
|
|
log.Infof("Got VM address(%v), Now waiting for SSH", addr)
|
|
out <- drivers.WaitForSSH(d)
|
|
}(ctx, out)
|
|
|
|
select {
|
|
case err := <-out:
|
|
return err
|
|
case <-ctx.Done():
|
|
return fmt.Errorf("Wait for VM to start timed out: %v", ctx.Err())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) Start() error {
|
|
var err error
|
|
|
|
c, err := d.GetXenAPIClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vm, err := c.GetUniqueVMByNameLabel(d.MachineName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = vm.Start(false, true); err != nil {
|
|
return err
|
|
}
|
|
if err = d.wait(time.Duration(d.WaitTimeout) * time.Second); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) Stop() error {
|
|
var err error
|
|
|
|
c, err := d.GetXenAPIClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vm, err := c.GetUniqueVMByNameLabel(d.MachineName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = vm.CleanShutdown(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) Remove() error {
|
|
var err error
|
|
|
|
c, err := d.GetXenAPIClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vm, err := c.GetUniqueVMByNameLabel(d.MachineName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vmState, err := d.GetState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch vmState {
|
|
case state.None:
|
|
return fmt.Errorf("Unable get VM(%v) state.", d.MachineName)
|
|
case state.Stopping:
|
|
return fmt.Errorf("VM(%v) state is stopping, please wait.", d.MachineName)
|
|
case state.Stopped:
|
|
break
|
|
case state.Paused:
|
|
if err = vm.HardShutdown(); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
if err = vm.CleanShutdown(); err != nil {
|
|
if err = vm.HardShutdown(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
disks, err := vm.GetDisks()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, disk := range disks {
|
|
if err = disk.Destroy(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err = vm.Destroy(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) Restart() error {
|
|
if err := d.Stop(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return d.Start()
|
|
}
|
|
|
|
func (d *Driver) Kill() error {
|
|
var err error
|
|
|
|
c, err := d.GetXenAPIClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vm, err := c.GetUniqueVMByNameLabel(d.MachineName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = vm.HardShutdown(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) setMachineNameIfNotSet() {
|
|
if d.MachineName == "" {
|
|
d.MachineName = fmt.Sprintf("docker-machine-unknown")
|
|
}
|
|
}
|
|
|
|
func (d *Driver) sshKeyPath() string {
|
|
return d.GetSSHKeyPath()
|
|
}
|
|
|
|
func (d *Driver) publicSSHKeyPath() string {
|
|
return d.sshKeyPath() + ".pub"
|
|
}
|
|
|
|
// Make a boot2docker VM disk image.
|
|
func (d *Driver) generateDiskImage() error {
|
|
magicString := "boot2docker, please format-me"
|
|
|
|
buf := new(bytes.Buffer)
|
|
tw := tar.NewWriter(buf)
|
|
|
|
log.Infof("Generate Disk Image...")
|
|
// magicString first so the automount script knows to format the disk
|
|
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
|
|
}
|
|
if err := ioutil.WriteFile(d.TAR, buf.Bytes(), 0644); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) GetXenAPIClient() (*XenAPIClient, error) {
|
|
if d.xenAPIClient == nil {
|
|
c := NewXenAPIClient(d.Server, d.Username, d.Password)
|
|
if err := c.Login(); err != nil {
|
|
return nil, err
|
|
}
|
|
d.xenAPIClient = &c
|
|
}
|
|
return d.xenAPIClient, nil
|
|
}
|
|
|
|
func (d *Driver) importVdi(vdi *xsclient.VDI, filename string, timeout time.Duration) error {
|
|
f, err := os.Open(filename)
|
|
if err != nil {
|
|
log.Errorf("Unable to open disk image '%s': %v", filename, err)
|
|
return err
|
|
}
|
|
|
|
// Get file length
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
log.Errorf("Unable to stat disk image '%s': %v", filename, err)
|
|
return err
|
|
}
|
|
|
|
task, err := vdi.Client.CreateTask()
|
|
if err != nil {
|
|
return fmt.Errorf("Unable to create task: %v", err)
|
|
}
|
|
urlStr := fmt.Sprintf("https://%s/import_raw_vdi?vdi=%s&session_id=%s&task_id=%s",
|
|
vdi.Client.Host, vdi.Ref, vdi.Client.Session.(string), task.Ref)
|
|
|
|
// Define a new http Transport which allows self-signed certs
|
|
tr := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
|
|
req, err := http.NewRequest("PUT", urlStr, f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.ContentLength = fi.Size()
|
|
|
|
resp, err := tr.RoundTrip(req)
|
|
if err != nil {
|
|
log.Errorf("Unable to upload VDI: %v", err)
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
msg, _ := ioutil.ReadAll(resp.Body)
|
|
err = fmt.Errorf("xenserver reply %s: %v", resp.Status, string(msg))
|
|
log.Errorf("Unable to upload VDI: %v", err)
|
|
return err
|
|
}
|
|
|
|
log.Infof("Waiting Upload VDI task(%v) to complete...", task.Ref)
|
|
if err = d.waitTask(task, timeout); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) waitTask(task *xsclient.Task, timeout time.Duration) error {
|
|
timeout_at := time.Now().Add(timeout)
|
|
|
|
for {
|
|
status, err := task.GetStatus()
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to get task status: %s", err.Error())
|
|
}
|
|
|
|
if status == xsclient.Success {
|
|
log.Infof("Upload VDI task(%s) completed", task.Ref)
|
|
break
|
|
}
|
|
|
|
switch status {
|
|
case xsclient.Pending:
|
|
progress, err := task.GetProgress()
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to get progress: %s", err.Error())
|
|
}
|
|
log.Debugf("Upload %.0f%% complete", progress*100)
|
|
log.Infof("Uploading...")
|
|
case xsclient.Failure:
|
|
errorInfo, err := task.GetErrorInfo()
|
|
if err != nil {
|
|
errorInfo = []string{fmt.Sprintf("furthermore, failed to get error info: %s", err.Error())}
|
|
}
|
|
return fmt.Errorf("Task failed: %s", errorInfo)
|
|
case xsclient.Cancelling, xsclient.Cancelled:
|
|
return fmt.Errorf("Task Cancelled")
|
|
default:
|
|
return fmt.Errorf("Unknown task status %v", status)
|
|
}
|
|
|
|
if time.Now().After(timeout_at) {
|
|
return fmt.Errorf("Upload VDI task(%s) timed out", task.Ref)
|
|
}
|
|
|
|
time.Sleep(5 * time.Second)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func pseudoUuid() (string, error) {
|
|
b := make([]byte, 16)
|
|
_, err := io.ReadFull(rand.Reader, b)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
uuid := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
|
return uuid, nil
|
|
}
|
|
|
|
// CoreOS only Config Drive
|
|
func (d *Driver) createCoreOSConfigDrive(pubKey []byte, vmuuid string, sruuid string) error {
|
|
userdatatext := `#cloud-config
|
|
hostname: %XSVMNAMETOHOSTNAME%
|
|
coreos:
|
|
units:
|
|
# XenServer Linux Guest Agent
|
|
- name: xe-linux-distribution.service
|
|
command: start
|
|
content: |
|
|
[Unit]
|
|
Description=XenServer Linux Guest Agent
|
|
After=docker.service
|
|
|
|
[Service]
|
|
ExecStartPre=/media/configdrive/agent/xe-linux-distribution /var/cache/xe-linux-distribution
|
|
Environment="XE_UPDATE_GUEST_ATTRS=/media/configdrive/agent/xe-update-guest-attrs"
|
|
ExecStart=/media/configdrive/agent/xe-daemon
|
|
ssh_authorized_keys:
|
|
# The following entry will automatically be replaced with a public key
|
|
# generated by XenServer's container management. The key-entry must exist,
|
|
# in order to enable container management for this VM.
|
|
- ssh-rsa %XSCONTAINERRSAPUB%
|
|
- ` + string(pubKey)
|
|
hosts, err := d.xenAPIClient.GetHosts()
|
|
if err != nil {
|
|
log.Errorf("Could not retrieve hosts in the pool: %s", err)
|
|
return err
|
|
}
|
|
ahost := hosts[0]
|
|
args := make(map[string]string)
|
|
args["vmuuid"] = vmuuid
|
|
args["sruuid"] = sruuid
|
|
args["configuration"] = userdatatext
|
|
ahost.CallPlugin("xscontainer", "create_config_drive", args)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Generic Config Drive for cloud-init enabled OS templates
|
|
func (d *Driver) createGenericConfigDrive(pubKey []byte, vmUuid string, sr *xsclient.SR) (string, error) {
|
|
userdatatext := `#cloud-config
|
|
ssh_authorized_keys:
|
|
- ` + string(pubKey)
|
|
|
|
metadatatext := `{ "uuid": "` + vmUuid + `"}`
|
|
|
|
//create ISO file
|
|
var diskSize int64
|
|
diskSize = 10 * 1024 * 1024 // 10 MB
|
|
var configIsoFilename = d.ResolveStorePath("configdrive.iso")
|
|
cdDisk, err := diskfs.Create(configIsoFilename, diskSize, diskfs.Raw)
|
|
if err != nil {
|
|
log.Errorf("Unable to create configdrive.iso: %v", err)
|
|
}
|
|
|
|
// the following line is required for an ISO, which may have logical block sizes
|
|
// only of 2048, 4096, 8192
|
|
cdDisk.LogicalBlocksize = 2048
|
|
fspec := disk.FilesystemSpec{Partition: 0, FSType: filesystem.TypeISO9660, VolumeLabel: "config-2"}
|
|
cdFS, err := cdDisk.CreateFilesystem(fspec)
|
|
if err != nil {
|
|
log.Errorf("Unable to create ConfigDrive filesystem: %v", err)
|
|
}
|
|
|
|
cloudInitPrefix := path.Join("/", "openstack", "latest")
|
|
|
|
// place down cloud-init info
|
|
err = cdFS.Mkdir(cloudInitPrefix)
|
|
if err != nil {
|
|
log.Errorf("Error creating cloud init directory structure: %v", err)
|
|
}
|
|
|
|
metadataPath := path.Join(cloudInitPrefix, "meta_data.json")
|
|
log.Infof("Opening %s", metadataPath)
|
|
metadataFile, err := cdFS.OpenFile(metadataPath, os.O_CREATE|os.O_RDWR)
|
|
if err != nil {
|
|
log.Errorf("Error opening meta data: %v", err)
|
|
}
|
|
|
|
log.Infof("Writing metadata contents")
|
|
_, err = metadataFile.Write([]byte(metadatatext))
|
|
if err != nil {
|
|
log.Errorf("Error writting meta data: %v", err)
|
|
}
|
|
|
|
userdataPath := path.Join(cloudInitPrefix, "user_data")
|
|
log.Infof("Opening %s", userdataPath)
|
|
userdataFile, err := cdFS.OpenFile(userdataPath, os.O_CREATE|os.O_RDWR)
|
|
if err != nil {
|
|
log.Errorf("Error opening user data: %v", err)
|
|
}
|
|
_, err = userdataFile.Write([]byte(userdatatext))
|
|
if err != nil {
|
|
log.Errorf("Error writting user data: %v", err)
|
|
}
|
|
|
|
//finalize ISO
|
|
iso, ok := cdFS.(*iso9660.FileSystem)
|
|
if !ok {
|
|
log.Errorf("not an iso9660 filesystem")
|
|
}
|
|
err = iso.Finalize(iso9660.FinalizeOptions{})
|
|
if err != nil {
|
|
log.Errorf("Error finalizing configdrive.iso: %v", err)
|
|
}
|
|
|
|
// Create the VDI
|
|
isoVdi, err := sr.CreateVdi(configIsoFilename, diskSize)
|
|
if err != nil {
|
|
log.Errorf("Unable to create ConfigDrive ISO VDI '%s': %v", configIsoFilename, err)
|
|
return "", err
|
|
}
|
|
|
|
// Import the VDI
|
|
log.Infof("Starting import of ConfigDrive VDI '%s'...", configIsoFilename)
|
|
if err = d.importVdi(isoVdi, configIsoFilename, time.Duration(d.UploadTimeout)*time.Second); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
isoVdiUuid, err := isoVdi.GetUuid()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return isoVdiUuid, nil
|
|
}
|
|
|