Jon Ludlam
9 years ago
2 changed files with 1049 additions and 0 deletions
@ -0,0 +1,123 @@ |
|||||
|
package xenserver |
||||
|
|
||||
|
import ( |
||||
|
"crypto/tls" |
||||
|
"fmt" |
||||
|
"net/http" |
||||
|
|
||||
|
"github.com/nilshell/xmlrpc" |
||||
|
xsclient "github.com/xenserver/go-xenserver-client" |
||||
|
) |
||||
|
|
||||
|
type XenAPIClient struct { |
||||
|
xsclient.XenAPIClient |
||||
|
} |
||||
|
|
||||
|
func assertUnique(objs []interface{}, obj_label, name_label string) (obj interface{}, err error) { |
||||
|
switch len(objs) { |
||||
|
case 1: |
||||
|
return objs[0], nil |
||||
|
case 0: |
||||
|
return nil, fmt.Errorf("Unable to get a %s named %v", obj_label, name_label) |
||||
|
default: |
||||
|
return nil, fmt.Errorf("Too many %ss returned for name %v", obj_label, name_label) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Get Unique VM By NameLabel
|
||||
|
func (c *XenAPIClient) GetUniqueVMByNameLabel(name_label string) (vm *xsclient.VM, err error) { |
||||
|
vms, err := c.GetVMByNameLabel(name_label) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
var objs []interface{} |
||||
|
for _, v := range vms { |
||||
|
objs = append(objs, v) |
||||
|
} |
||||
|
|
||||
|
obj, err := assertUnique(objs, "VM", name_label) |
||||
|
|
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return obj.(*xsclient.VM), nil |
||||
|
} |
||||
|
|
||||
|
// Get Unique SR By NameLabel
|
||||
|
func (c *XenAPIClient) GetUniqueSRByNameLabel(name_label string) (sr *xsclient.SR, err error) { |
||||
|
srs, err := c.GetSRByNameLabel(name_label) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
var objs []interface{} |
||||
|
for _, v := range srs { |
||||
|
objs = append(objs, v) |
||||
|
} |
||||
|
|
||||
|
obj, err := assertUnique(objs, "SR", name_label) |
||||
|
|
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return obj.(*xsclient.SR), nil |
||||
|
} |
||||
|
|
||||
|
// Get Unique Host By NameLabel
|
||||
|
func (c *XenAPIClient) GetUniqueHostByNameLabel(name_label string) (host *xsclient.Host, err error) { |
||||
|
hosts, err := c.GetHostByNameLabel(name_label) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
var objs []interface{} |
||||
|
for _, v := range hosts { |
||||
|
objs = append(objs, v) |
||||
|
} |
||||
|
|
||||
|
obj, err := assertUnique(objs, "Host", name_label) |
||||
|
|
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return obj.(*xsclient.Host), nil |
||||
|
} |
||||
|
|
||||
|
// Get Unique Network By NameLabel
|
||||
|
func (c *XenAPIClient) GetUniqueNetworkByNameLabel(name_label string) (network *xsclient.Network, err error) { |
||||
|
networks, err := c.GetNetworkByNameLabel(name_label) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
var objs []interface{} |
||||
|
for _, v := range networks { |
||||
|
objs = append(objs, v) |
||||
|
} |
||||
|
|
||||
|
obj, err := assertUnique(objs, "Network", name_label) |
||||
|
|
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return obj.(*xsclient.Network), nil |
||||
|
} |
||||
|
|
||||
|
func NewXenAPIClient(host, username, password string) (c XenAPIClient) { |
||||
|
c.Host = host |
||||
|
c.Url = "https://" + host |
||||
|
c.Username = username |
||||
|
c.Password = password |
||||
|
tr := &http.Transport{ |
||||
|
TLSClientConfig: &tls.Config{ |
||||
|
InsecureSkipVerify: true, |
||||
|
}, |
||||
|
} |
||||
|
c.RPC, _ = xmlrpc.NewClient(c.Url, tr) |
||||
|
return |
||||
|
} |
@ -0,0 +1,926 @@ |
|||||
|
package xenserver |
||||
|
|
||||
|
import ( |
||||
|
"archive/tar" |
||||
|
"bytes" |
||||
|
"crypto/rand" |
||||
|
"crypto/tls" |
||||
|
"fmt" |
||||
|
"io" |
||||
|
"io/ioutil" |
||||
|
"net/http" |
||||
|
"os" |
||||
|
"path/filepath" |
||||
|
"strings" |
||||
|
"time" |
||||
|
|
||||
|
"github.com/codegangsta/cli" |
||||
|
"github.com/docker/machine/drivers" |
||||
|
"github.com/docker/machine/log" |
||||
|
"github.com/docker/machine/ssh" |
||||
|
"github.com/docker/machine/state" |
||||
|
"github.com/docker/machine/utils" |
||||
|
"github.com/nilshell/xmlrpc" |
||||
|
"golang.org/x/net/context" |
||||
|
|
||||
|
xsclient "github.com/xenserver/go-xenserver-client" |
||||
|
) |
||||
|
|
||||
|
const ( |
||||
|
isoOnlineURL = "https://github.com/xenserver/boot2docker/releases/download/v1.7.0-xentools/boot2docker-v1.7.0-xentools.iso" |
||||
|
isoFilename = "boot2docker.iso" |
||||
|
tarFilename = "boot2docker.tar" |
||||
|
osTemplateLabelName = "Other install media" |
||||
|
B2D_USER = "docker" |
||||
|
B2D_PASS = "tcuser" |
||||
|
) |
||||
|
|
||||
|
type Driver struct { |
||||
|
MachineName string |
||||
|
SSHUser string |
||||
|
SSHPort int |
||||
|
Server string |
||||
|
Username string |
||||
|
Password string |
||||
|
Boot2DockerURL string |
||||
|
CPU uint |
||||
|
Memory uint |
||||
|
DiskSize uint |
||||
|
SR string |
||||
|
Network string |
||||
|
Host string |
||||
|
StorePath string |
||||
|
ISO string |
||||
|
TAR string |
||||
|
UploadTimeout uint |
||||
|
WaitTimeout uint |
||||
|
CaCertPath string |
||||
|
PrivateKeyPath string |
||||
|
SwarmMaster bool |
||||
|
SwarmHost string |
||||
|
SwarmDiscovery string |
||||
|
|
||||
|
xenAPIClient *XenAPIClient |
||||
|
} |
||||
|
|
||||
|
func init() { |
||||
|
drivers.Register("xenserver", &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: "XENSERVER_SERVER", |
||||
|
Name: "xenserver-server", |
||||
|
Usage: "XenServer server hostname/IP for docker VM", |
||||
|
}, |
||||
|
cli.StringFlag{ |
||||
|
EnvVar: "XENSERVER_USERNAME", |
||||
|
Name: "xenserver-username", |
||||
|
Usage: "XenServer username", |
||||
|
}, |
||||
|
cli.StringFlag{ |
||||
|
EnvVar: "XENSERVER_PASSWORD", |
||||
|
Name: "xenserver-password", |
||||
|
Usage: "XenServer password", |
||||
|
}, |
||||
|
cli.StringFlag{ |
||||
|
EnvVar: "XENSERVER_BOOT2DOCKER_URL", |
||||
|
Name: "xenserver-boot2docker-url", |
||||
|
Usage: "XenServer URL for boot2docker image", |
||||
|
Value: isoOnlineURL, |
||||
|
}, |
||||
|
cli.IntFlag{ |
||||
|
EnvVar: "XENSERVER_VCPU_COUNT", |
||||
|
Name: "xenserver-vcpu-count", |
||||
|
Usage: "XenServer vCPU number for docker VM", |
||||
|
Value: 1, |
||||
|
}, |
||||
|
cli.IntFlag{ |
||||
|
EnvVar: "XENSERVER_MEMORY_SIZE", |
||||
|
Name: "xenserver-memory-size", |
||||
|
Usage: "XenServer size of memory for docker VM (in MB)", |
||||
|
Value: 1024, |
||||
|
}, |
||||
|
cli.IntFlag{ |
||||
|
EnvVar: "XENSERVER_DISK_SIZE", |
||||
|
Name: "xenserver-disk-size", |
||||
|
Usage: "XenServer size of disk for docker VM (in MB)", |
||||
|
Value: 5120, |
||||
|
}, |
||||
|
cli.StringFlag{ |
||||
|
EnvVar: "XENSERVER_SR_LABEL", |
||||
|
Name: "xenserver-sr-label", |
||||
|
Usage: "XenServer SR label where the docker VM will be attached", |
||||
|
}, |
||||
|
cli.StringFlag{ |
||||
|
EnvVar: "XENSERVER_NETWORK_LABEL", |
||||
|
Name: "xenserver-network-label", |
||||
|
Usage: "XenServer network label where the docker VM will be attached", |
||||
|
}, |
||||
|
cli.StringFlag{ |
||||
|
EnvVar: "XENSERVER_HOST_LABEL", |
||||
|
Name: "xenserver-host-label", |
||||
|
Usage: "XenServer host label where the docker VM will be run", |
||||
|
}, |
||||
|
cli.IntFlag{ |
||||
|
EnvVar: "XENSERVER_UPLOAD_TIMEOUT", |
||||
|
Name: "xenserver-upload-timeout", |
||||
|
Usage: "XenServer upload VDI timeout(seconds)", |
||||
|
Value: 5 * 60, |
||||
|
}, |
||||
|
cli.IntFlag{ |
||||
|
EnvVar: "XENSERVER_WAIT_TIMEOUT", |
||||
|
Name: "xenserver-wait-timeout", |
||||
|
Usage: "XenServer wait VM start timeout(seconds)", |
||||
|
Value: 30 * 60, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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 = B2D_USER |
||||
|
} |
||||
|
|
||||
|
return d.SSHUser |
||||
|
} |
||||
|
|
||||
|
func (d *Driver) DriverName() string { |
||||
|
return "xenserver" |
||||
|
} |
||||
|
|
||||
|
func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { |
||||
|
d.SSHUser = B2D_USER |
||||
|
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 = filepath.Join(d.StorePath, isoFilename) |
||||
|
d.TAR = filepath.Join(d.StorePath, tarFilename) |
||||
|
|
||||
|
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 { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (d *Driver) Create() error { |
||||
|
var err error |
||||
|
|
||||
|
d.setMachineNameIfNotSet() |
||||
|
|
||||
|
// Download boot2docker ISO from Internet
|
||||
|
var isoURL string |
||||
|
b2dutils := utils.NewB2dUtils("", "") |
||||
|
|
||||
|
if d.Boot2DockerURL != "" { |
||||
|
isoURL = d.Boot2DockerURL |
||||
|
} else { |
||||
|
isoURL, err = b2dutils.GetLatestBoot2DockerReleaseURL() |
||||
|
if err != nil { |
||||
|
log.Errorf("Unable to check for the latest release: %s", err) |
||||
|
return err |
||||
|
} |
||||
|
} |
||||
|
log.Infof("Downloading %s from %s...", isoFilename, isoURL) |
||||
|
if err := b2dutils.DownloadISO(d.StorePath, isoFilename, isoURL); 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 |
||||
|
} |
||||
|
|
||||
|
log.Infof("Creating ISO VDI...") |
||||
|
|
||||
|
// Get the SR
|
||||
|
var sr *xsclient.SR |
||||
|
if d.SR == "" { |
||||
|
sr, err = c.GetDefaultSR() |
||||
|
} else { |
||||
|
sr, err = c.GetUniqueSRByNameLabel(d.SR) |
||||
|
} |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
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(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": 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 |
||||
|
} |
||||
|
|
||||
|
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 |
||||
|
} |
||||
|
|
||||
|
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) |
||||
|
log.Infof("Got VM address(%v), Now waiting for SSH", addr) |
||||
|
out <- ssh.WaitForTCP(addr) |
||||
|
}(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 filepath.Join(d.StorePath, "id_rsa") |
||||
|
} |
||||
|
|
||||
|
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 |
||||
|
} |
Loading…
Reference in new issue