diff --git a/commands/commands.go b/commands/commands.go index 9f85f051d..fdbf44076 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -22,6 +22,7 @@ import ( _ "github.com/docker/machine/drivers/hyperv" _ "github.com/docker/machine/drivers/none" _ "github.com/docker/machine/drivers/openstack" + _ "github.com/docker/machine/drivers/parallels" _ "github.com/docker/machine/drivers/rackspace" _ "github.com/docker/machine/drivers/softlayer" _ "github.com/docker/machine/drivers/virtualbox" diff --git a/docs/drivers/index.md b/docs/drivers/index.md index 657c2f09c..61a519daa 100644 --- a/docs/drivers/index.md +++ b/docs/drivers/index.md @@ -19,6 +19,7 @@ identifier="smn_machine_drivers" * [Generic](generic.md) * [Microsoft Hyper-V](hyper-v.md) * [OpenStack](openstack.md) +* [Parallels](parallels.md) * [Rackspace](rackspace.md) * [IBM Softlayer](soft-layer.md) * [Oracle VirtualBox](virtualbox.md) diff --git a/docs/drivers/parallels.md b/docs/drivers/parallels.md new file mode 100644 index 000000000..5b47384a9 --- /dev/null +++ b/docs/drivers/parallels.md @@ -0,0 +1,43 @@ + + +#### Parallels +Creates machines locally on [Parallels Desktop for Mac](http://www.parallels.com/products/desktop/). +Requires _Parallels Desktop for Mac_ version 11 or higher to be installed. + + $ docker-machine create --driver=parallels prl-test + +Options: + + - `--parallels-boot2docker-url`: The URL of the boot2docker image. + - `--parallels-disk-size`: Size of disk for the host VM (in MB). + - `--parallels-memory`: Size of memory for the host VM (in MB). + - `--parallels-cpu-count`: Number of CPUs to use to create the VM (-1 to use the number of CPUs available). + +The `--parallels-boot2docker-url` flag takes a few different forms. By +default, if no value is specified for this flag, Machine will check locally for +a boot2docker ISO. If one is found, that will be used as the ISO for the +created machine. If one is not found, the latest ISO release available on +[boot2docker/boot2docker](https://github.com/boot2docker/boot2docker) will be +downloaded and stored locally for future use. Note that this means you must run +`docker-machine upgrade` deliberately on a machine if you wish to update the "cached" +boot2docker ISO. + +This is the default behavior (when `--parallels-boot2docker-url=""`), but the +option also supports specifying ISOs by the `http://` and `file://` protocols. + +Environment variables and default values: + +| CLI option | Environment variable | Default | +|-------------------------------|-----------------------------|--------------------------| +| `--parallels-boot2docker-url` | `PARALLELS_BOOT2DOCKER_URL` | *Latest boot2docker url* | +| `--parallels-cpu-count` | `PARALLELS_CPU_COUNT` | `1` | +| `--parallels-disk-size` | `PARALLELS_DISK_SIZE` | `20000` | +| `--parallels-memory` | `PARALLELS_MEMORY_SIZE` | `1024` | diff --git a/drivers/parallels/parallels.go b/drivers/parallels/parallels.go new file mode 100644 index 000000000..2d345161f --- /dev/null +++ b/drivers/parallels/parallels.go @@ -0,0 +1,573 @@ +package parallels + +import ( + "archive/tar" + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "runtime" + "strconv" + "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" +) + +const ( + isoFilename = "boot2docker.iso" + shareFolderName = "Users" + shareFolderPath = "/Users" + minDiskSize = 32 +) + +type Driver struct { + *drivers.BaseDriver + CPU int + Memory int + DiskSize int + Boot2DockerURL string +} + +func init() { + drivers.Register("parallels", &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: "PARALLELS_MEMORY_SIZE", + Name: "parallels-memory", + Usage: "Size of memory for host in MB", + Value: 1024, + }, + cli.IntFlag{ + EnvVar: "PARALLELS_CPU_COUNT", + Name: "parallels-cpu-count", + Usage: "number of CPUs for the machine (-1 to use the number of CPUs available)", + Value: 1, + }, + cli.IntFlag{ + EnvVar: "PARALLELS_DISK_SIZE", + Name: "parallels-disk-size", + Usage: "Size of disk for host in MB", + Value: 20000, + }, + cli.StringFlag{ + EnvVar: "PARALLELS_BOOT2DOCKER_URL", + Name: "parallels-boot2docker-url", + Usage: "The URL of the boot2docker image. Defaults to the latest available version", + Value: "", + }, + } +} + +func NewDriver(machineName string, storePath string, caCert string, privateKey string) (drivers.Driver, error) { + inner := drivers.NewBaseDriver(machineName, storePath, caCert, privateKey) + return &Driver{BaseDriver: inner}, nil +} + +func (d *Driver) GetSSHHostname() (string, error) { + return d.GetIP() +} + +func (d *Driver) GetSSHUsername() string { + if d.SSHUser == "" { + d.SSHUser = "docker" + } + + return d.SSHUser +} + +func (d *Driver) DriverName() string { + return "parallels" +} + +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) SetConfigFromFlags(flags drivers.DriverOptions) error { + d.CPU = flags.Int("parallels-cpu-count") + d.Memory = flags.Int("parallels-memory") + d.DiskSize = flags.Int("parallels-disk-size") + d.Boot2DockerURL = flags.String("parallels-boot2docker-url") + d.SwarmMaster = flags.Bool("swarm-master") + d.SwarmHost = flags.String("swarm-host") + d.SwarmDiscovery = flags.String("swarm-discovery") + d.SSHUser = "docker" + d.SSHPort = 22 + + return nil +} + +func (d *Driver) PreCreateCheck() error { + // Check platform type + if runtime.GOOS != "darwin" { + return fmt.Errorf("Driver \"parallels\" works only on OS X!") + } + + // Check prlctl binary is available + stdout, stderr, err := prlctlOutErr("--version") + if err != nil { + if err == ErrPrlctlNotFound { + return err + } + return fmt.Errorf(string(stderr)) + } + + // Check Parallels Desktop version + res := reMajorVersion.FindStringSubmatch(string(stdout)) + if res == nil { + return fmt.Errorf("Parallels Desktop version could not be parsed: %s", stdout) + } + + ver, err := strconv.Atoi(res[1]) + if err != nil { + return err + } + + if ver < 11 { + return fmt.Errorf("Driver \"parallels\" supports only Parallels Desktop 11 and higher. You use: Paralells Desktop %d.", ver) + } + + return nil +} + +func (d *Driver) Create() error { + var ( + err error + ) + + b2dutils := utils.NewB2dUtils("", "") + if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { + return err + } + + log.Infof("Creating SSH key...") + if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil { + return err + } + + log.Infof("Creating Parallels Desktop VM...") + + if err := prlctl("create", d.MachineName, + "--distribution", "linux-2.6", + "--dst", d.ResolveStorePath("."), + "--no-hdd"); err != nil { + return err + } + + cpus := d.CPU + if cpus < 1 { + cpus = int(runtime.NumCPU()) + } + if cpus > 32 { + cpus = 32 + } + + if err := prlctl("set", d.MachineName, + "--select-boot-device", "off", + "--cpus", fmt.Sprintf("%d", cpus), + "--memsize", fmt.Sprintf("%d", d.Memory), + "--cpu-hotplug", "off", + "--nested-virt", "on", + "--pmu-virt", "on", + "--on-window-close", "keep-running", + "--longer-battery-life", "on", + "--3d-accelerate", "off", + "--device-bootorder", "cdrom0"); err != nil { + return err + } + + if err := prlctl("set", d.MachineName, + "--device-set", "cdrom0", + "--iface", "sata", + "--position", "0", + "--image", d.ResolveStorePath(isoFilename)); err != nil { + return err + } + + // Create a small plain disk. It will be converted and expanded later + if err := prlctl("set", d.MachineName, + "--device-add", "hdd", + "--iface", "sata", + "--position", "1", + "--image", d.diskPath(), + "--type", "plain", + "--size", fmt.Sprintf("%d", minDiskSize)); err != nil { + return err + } + + if err := d.generateDiskImage(d.DiskSize); err != nil { + return err + } + + // Disable Time Sync feature because it has an issue with timezones. + // TODO: Turn it back as soon as Time Sync is fixed in Parallels Tools + if err := prlctl("set", d.MachineName, "--time-sync", "off"); err != nil { + return err + } + + // Enable Shared Folders + if err := prlctl("set", d.MachineName, "--shf-host", "on"); err != nil { + return err + } + + if err := prlctl("set", d.MachineName, + "--shf-host-add", shareFolderName, + "--path", shareFolderPath); err != nil { + return err + } + + log.Infof("Starting Parallels Desktop VM...") + + // Don't use Start() since it expects to have a dhcp lease already + if err := prlctl("start", d.MachineName); 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") + } + + d.IPAddress = ip + + if err := d.Start(); err != nil { + return err + } + + return nil +} + +func (d *Driver) Start() error { + s, err := d.GetState() + if err != nil { + return err + } + + switch s { + case state.Stopped, state.Saved, state.Paused: + if err := prlctl("start", d.MachineName); err != nil { + return err + } + log.Infof("Waiting for VM to start...") + case state.Running: + break + default: + log.Infof("VM not in restartable state") + } + + if err := drivers.WaitForSSH(d); err != nil { + return err + } + + d.IPAddress, err = d.GetIP() + if err != nil { + return err + } + + // Mount Share Folder + if err := d.mountShareFolder(shareFolderName, shareFolderPath); err != nil { + return err + } + + return nil +} + +func (d *Driver) Stop() error { + if err := prlctl("stop", d.MachineName); err != nil { + return err + } + for { + s, err := d.GetState() + if err != nil { + return err + } + if s == state.Running { + time.Sleep(1 * time.Second) + } else { + break + } + } + return nil +} + +func (d *Driver) Remove() error { + s, err := d.GetState() + if err != nil { + if err == ErrMachineNotExist { + log.Infof("machine does not exist, assuming it has been removed already") + return nil + } + return err + } + if s == state.Running { + if err := d.Kill(); err != nil { + return err + } + } + return prlctl("delete", d.MachineName) +} + +func (d *Driver) Restart() error { + if err := d.Stop(); err != nil { + return err + } + return d.Start() +} + +func (d *Driver) Kill() error { + return prlctl("stop", d.MachineName, "--kill") +} + +func (d *Driver) GetState() (state.State, error) { + stdout, stderr, err := prlctlOutErr("list", d.MachineName, "--output", "status", "--no-header") + if err != nil { + if reMachineNotFound.FindString(stderr) != "" { + return state.Error, ErrMachineNotExist + } + return state.Error, err + } + + switch stdout { + // TODO: state.Starting ?! + case "running\n": + return state.Running, nil + case "paused\n": + return state.Paused, nil + case "suspended\n": + return state.Saved, nil + case "stopping\n": + return state.Stopping, nil + case "stopped\n": + return state.Stopped, nil + } + return state.None, nil +} + +func (d *Driver) GetIP() (string, error) { + // Assume that Parallels Desktop hosts don't have IPs unless they are running + s, err := d.GetState() + if err != nil { + return "", err + } + if s != state.Running { + return "", drivers.ErrHostIsNotRunning + } + + ip, err := d.getIPfromDHCPLease() + if err != nil { + return "", err + } + + return ip, nil +} + +func (d *Driver) getIPfromDHCPLease() (string, error) { + + dhcp_lease_file := "/Library/Preferences/Parallels/parallels_dhcp_leases" + + stdout, err := prlctlOut("list", "-i", d.MachineName) + macRe := regexp.MustCompile("net0.* mac=([0-9A-F]{12}) card=.*") + macMatch := macRe.FindAllStringSubmatch(stdout, 1) + + if len(macMatch) != 1 { + return "", fmt.Errorf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", d.MachineName) + } + mac := macMatch[0][1] + + if len(mac) != 12 { + return "", fmt.Errorf("Not a valid MAC address: %s. It should be exactly 12 digits.", mac) + } + + leases, err := ioutil.ReadFile(dhcp_lease_file) + if err != nil { + return "", err + } + + ipRe := regexp.MustCompile("(.*)=\"(.*),(.*)," + strings.ToLower(mac) + ",.*\"") + mostRecentIp := "" + mostRecentLease := uint64(0) + for _, l := range ipRe.FindAllStringSubmatch(string(leases), -1) { + ip := l[1] + expiry, _ := strconv.ParseUint(l[2], 10, 64) + leaseTime, _ := strconv.ParseUint(l[3], 10, 32) + log.Debugf("Found lease: %s for MAC: %s, expiring at %d, leased for %d s.\n", ip, mac, expiry, leaseTime) + if mostRecentLease <= expiry-leaseTime { + mostRecentIp = ip + mostRecentLease = expiry - leaseTime + } + } + + if len(mostRecentIp) == 0 { + return "", fmt.Errorf("IP lease not found for MAC address %s in: %s\n", mac, dhcp_lease_file) + } + log.Debugf("Found IP lease: %s for MAC address %s\n", mostRecentIp, mac) + + return mostRecentIp, nil +} + +func (d *Driver) publicSSHKeyPath() string { + return d.GetSSHKeyPath() + ".pub" +} + +func (d *Driver) diskPath() string { + return d.ResolveStorePath("disk.hdd") +} + +func (d *Driver) mountShareFolder(shareName string, mountPoint string) error { + cmd := "sudo mkdir -p " + mountPoint + " && sudo mount -t prl_fs " + shareName + " " + mountPoint + + if _, err := os.Stat(mountPoint); err != nil { + if !os.IsNotExist(err) { + return err + } else { + log.Infof("Host path '%s' does not exist. Skipping mount to VM...", mountPoint) + } + } else { + if _, err := drivers.RunSSHCommandFromDriver(d, cmd); err != nil { + return fmt.Errorf("Error mounting shared folder: %s", err) + } + } + + return nil +} + +// Make a boot2docker VM disk image. +func (d *Driver) generateDiskImage(size int) error { + tarBuf, err := d.generateTar() + if err != nil { + return err + } + + minSizeBytes := int64(minDiskSize) << 20 // usually won't fit in 32-bit int (max 2GB) + + //Expand the initial image if needed + if bufLen := int64(tarBuf.Len()); bufLen > minSizeBytes { + bufLenMBytes := bufLen>>20 + 1 + if err := prldisktool("resize", + "--hdd", d.diskPath(), + "--size", fmt.Sprintf("%d", bufLenMBytes)); err != nil { + return err + } + } + + // Find hds file + hdsList, err := filepath.Glob(d.diskPath() + "/*.hds") + if err != nil { + return err + } + if len(hdsList) == 0 { + return fmt.Errorf("Could not find *.hds image in %s", d.diskPath()) + } + hdsPath := hdsList[0] + log.Debugf("HDS image path: %s", hdsPath) + + // Write tar to the hds file + hds, err := os.OpenFile(hdsPath, os.O_WRONLY, 0644) + if err != nil { + return err + } + defer hds.Close() + hds.Seek(0, os.SEEK_SET) + _, err = hds.Write(tarBuf.Bytes()) + if err != nil { + return err + } + hds.Close() + + // Convert image to expanding type and resize it + if err := prldisktool("convert", "--expanding", + "--hdd", d.diskPath()); err != nil { + return err + } + + if err := prldisktool("resize", + "--hdd", d.diskPath(), + "--size", fmt.Sprintf("%d", size)); err != nil { + return err + } + + return nil +} + +// See https://github.com/boot2docker/boot2docker/blob/master/rootfs/rootfs/etc/rc.d/automount +func (d *Driver) generateTar() (*bytes.Buffer, error) { + magicString := "boot2docker, please format-me" + + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + + // 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 nil, err + } + if _, err := tw.Write([]byte(magicString)); err != nil { + return nil, err + } + // .ssh/key.pub => authorized_keys + file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} + if err := tw.WriteHeader(file); err != nil { + return nil, err + } + pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) + if err != nil { + return nil, err + } + file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return nil, err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return nil, err + } + file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return nil, err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return nil, err + } + if err := tw.Close(); err != nil { + return nil, err + } + return buf, nil +} diff --git a/drivers/parallels/parallels_test.go b/drivers/parallels/parallels_test.go new file mode 100644 index 000000000..b1e9db43e --- /dev/null +++ b/drivers/parallels/parallels_test.go @@ -0,0 +1 @@ +package parallels diff --git a/drivers/parallels/prlctl.go b/drivers/parallels/prlctl.go new file mode 100644 index 000000000..ec7474114 --- /dev/null +++ b/drivers/parallels/prlctl.go @@ -0,0 +1,94 @@ +package parallels + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "regexp" + "strings" + + "github.com/docker/machine/log" +) + +// TODO check these +var ( + reVMNameUUID = regexp.MustCompile(`"(.+)" {([0-9a-f-]+)}`) + reVMInfoLine = regexp.MustCompile(`(?:"(.+)"|(.+))=(?:"(.*)"|(.*))`) + reColonLine = regexp.MustCompile(`(.+):\s+(.*)`) + reMachineNotFound = regexp.MustCompile(`Failed to get VM config: The virtual machine could not be found..*`) + reMajorVersion = regexp.MustCompile(`prlctl version (\d+)\.\d+\.\d+.*`) +) + +var ( + ErrMachineExist = errors.New("machine already exists") + ErrMachineNotExist = errors.New("machine does not exist") + ErrPrlctlNotFound = errors.New("prlctl not found") + prlctlCmd = "prlctl" + prldisktoolCmd = "prl_disk_tool" +) + +func prlctl(args ...string) error { + cmd := exec.Command(prlctlCmd, args...) + if os.Getenv("DEBUG") != "" { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + log.Debugf("executing: %v %v", prlctlCmd, strings.Join(args, " ")) + if err := cmd.Run(); err != nil { + if ee, ok := err.(*exec.Error); ok && ee == exec.ErrNotFound { + return ErrPrlctlNotFound + } + return fmt.Errorf("%v %v failed: %v", prlctlCmd, strings.Join(args, " "), err) + } + return nil +} + +func prlctlOut(args ...string) (string, error) { + cmd := exec.Command(prlctlCmd, args...) + if os.Getenv("DEBUG") != "" { + cmd.Stderr = os.Stderr + } + log.Debugf("executing: %v %v", prlctlCmd, strings.Join(args, " ")) + + b, err := cmd.Output() + if err != nil { + if ee, ok := err.(*exec.Error); ok && ee == exec.ErrNotFound { + err = ErrPrlctlNotFound + } + } + return string(b), err +} + +func prlctlOutErr(args ...string) (string, string, error) { + cmd := exec.Command(prlctlCmd, args...) + log.Debugf("executing: %v %v", prlctlCmd, strings.Join(args, " ")) + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + if ee, ok := err.(*exec.Error); ok && ee == exec.ErrNotFound { + err = ErrPrlctlNotFound + } + } + return stdout.String(), stderr.String(), err +} + +func prldisktool(args ...string) error { + cmd := exec.Command(prldisktoolCmd, args...) + if os.Getenv("DEBUG") != "" { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + log.Debugf("executing: %v %v", prldisktoolCmd, strings.Join(args, " ")) + if err := cmd.Run(); err != nil { + if ee, ok := err.(*exec.Error); ok && ee == exec.ErrNotFound { + return ErrPrlctlNotFound + } + return fmt.Errorf("%v %v failed: %v", prldisktoolCmd, strings.Join(args, " "), err) + } + return nil +} diff --git a/test/integration/drivers/parallels/bad-create-iso.bats b/test/integration/drivers/parallels/bad-create-iso.bats new file mode 100644 index 000000000..b15e8e3b4 --- /dev/null +++ b/test/integration/drivers/parallels/bad-create-iso.bats @@ -0,0 +1,12 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +force_env DRIVER parallels + +export BAD_URL="http://dev.null:9111/bad.iso" + +@test "$DRIVER: Should not allow machine creation with bad ISO" { + run machine create -d parallels --parallels-boot2docker-url $BAD_URL $NAME + [[ ${status} -eq 1 ]] +} diff --git a/test/integration/drivers/parallels/certs-checksum.bats b/test/integration/drivers/parallels/certs-checksum.bats new file mode 100644 index 000000000..cf79bff2c --- /dev/null +++ b/test/integration/drivers/parallels/certs-checksum.bats @@ -0,0 +1,26 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +force_env DRIVER parallels + +@test "$DRIVER: create" { + run machine create -d $DRIVER $NAME +} + +@test "$DRIVER: verify that server cert checksum matches local checksum" { + # TODO: This test is tightly coupled to VirtualBox right now, but should be + # available for all providers ideally. + # + # TODO: Does this test work OK on Linux? cc @ehazlett + # + # Have to create this directory and file or else the OpenSSL checksum will barf. + machine ssh $NAME -- sudo mkdir -p /usr/local/ssl + machine ssh $NAME -- sudo touch /usr/local/ssl/openssl.cnf + + SERVER_CHECKSUM=$(machine ssh $NAME -- openssl dgst -sha256 /var/lib/boot2docker/ca.pem | awk '{ print $2 }') + LOCAL_CHECKSUM=$(openssl dgst -sha256 $MACHINE_STORAGE_PATH/certs/ca.pem | awk '{ print $2 }') + echo ${SERVER_CHECKSUM} + echo ${LOCAL_CHECKSUM} + [[ ${SERVER_CHECKSUM} == ${LOCAL_CHECKSUM} ]] +} diff --git a/test/integration/drivers/parallels/custom-mem-disk.bats b/test/integration/drivers/parallels/custom-mem-disk.bats new file mode 100644 index 000000000..3c2cd4776 --- /dev/null +++ b/test/integration/drivers/parallels/custom-mem-disk.bats @@ -0,0 +1,51 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +force_env DRIVER parallels + +# Default memsize is 1024MB and disksize is 20000MB +# These values are defined in drivers/parallels/parallels.go +export DEFAULT_MEMSIZE=1024 +export DEFAULT_DISKSIZE=20000 +export CUSTOM_MEMSIZE=1536 +export CUSTOM_DISKSIZE=10000 +export CUSTOM_CPUCOUNT=1 + +function findDiskSize() { + run bash -c "prlctl list -i $NAME | grep 'hdd0.*sata' | grep -o '\d*Mb' | awk -F 'Mb' '{print $1}'" +} + +function findMemorySize() { + run bash -c "prlctl list -i $NAME | grep 'memory ' | grep -o '[0-9]\+'" +} + +function findCPUCount() { + run bash -c "prlctl list -i $NAME | grep -o 'cpus=\d*' | cut -d'=' -f2" +} + +@test "$DRIVER: create with custom disk, cpu count and memory size flags" { + run machine create -d $DRIVER --parallels-cpu-count $CUSTOM_CPUCOUNT --parallels-disk-size $CUSTOM_DISKSIZE --parallels-memory $CUSTOM_MEMSIZE $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: check custom machine memory size" { + findMemorySize + [[ ${output} == "$CUSTOM_MEMSIZE" ]] +} + +@test "$DRIVER: check custom machine disksize" { + findDiskSize + [[ ${output} == *"$CUSTOM_DISKSIZE"* ]] +} + +@test "$DRIVER: check custom machine cpucount" { + findCPUCount + [[ ${output} == "$CUSTOM_CPUCOUNT" ]] +} + +@test "$DRIVER: machine should show running after create" { + run machine ls + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Running"* ]] +} diff --git a/test/integration/drivers/parallels/pause-save-start.bats b/test/integration/drivers/parallels/pause-save-start.bats new file mode 100644 index 000000000..0b7be945c --- /dev/null +++ b/test/integration/drivers/parallels/pause-save-start.bats @@ -0,0 +1,55 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +force_env DRIVER parallels + +@test "$DRIVER: create" { + run machine create -d $DRIVER $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: prlctl pause" { + run prlctl pause $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show paused after 'prlctl pause'" { + run machine ls + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Paused"* ]] +} + +@test "$DRIVER: start after paused" { + run machine start $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show running after start" { + run machine ls + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Running"* ]] +} + +@test "$DRIVER: prlctl suspend" { + run prlctl suspend $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show saved after 'prlctl suspend'" { + run machine ls + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"$NAME"* ]] + [[ ${lines[1]} == *"Saved"* ]] +} + +@test "$DRIVER: start after saved" { + run machine start $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show running after start" { + run machine ls + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Running"* ]] +} diff --git a/test/integration/drivers/parallels/upgrade.bats b/test/integration/drivers/parallels/upgrade.bats new file mode 100644 index 000000000..3fa8b9cb7 --- /dev/null +++ b/test/integration/drivers/parallels/upgrade.bats @@ -0,0 +1,28 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +force_env DRIVER parallels + +export OLD_ISO_URL="https://github.com/Parallels/boot2docker/releases/download/v1.7.0-prl-tools/boot2docker.iso" + +@test "$DRIVER: create for upgrade" { + run machine create -d parallels --parallels-boot2docker-url $OLD_ISO_URL $NAME +} + +@test "$DRIVER: verify that docker version is old" { + # Have to run this over SSH due to client/server mismatch restriction + SERVER_VERSION=$(machine ssh $NAME docker version | grep 'Server version' | awk '{ print $3; }') + [[ "$SERVER_VERSION" == "1.7.0" ]] +} + +@test "$DRIVER: upgrade" { + run machine upgrade $NAME + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: upgrade is correct version" { + SERVER_VERSION=$(docker $(machine config $NAME) version | grep 'Server version' | awk '{ print $3; }') + [[ "$SERVER_VERSION" != "1.7.0" ]] +}