Skip to content
This repository has been archived by the owner on Sep 26, 2021. It is now read-only.

VirtualBox: enable ability to import b2d instances #979

Merged
merged 7 commits into from
Apr 23, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/boot2docker_migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
This guide explains migrating from the Boot2Docker CLI to Docker Machine.

This guide assumes basic knowledge of the Boot2Docker CLI and Docker Machine. If you are not familiar, please review those docs prior to migrating.

There are a few differences between the Boot2Docker CLI commands and Machine. Please review the table below for the Boot2Docker command and the corresponding Machine command. You can also see details on Machine commands in the official [Docker Machine Docs](http://docs.docker.com/machine/#subcommands).

# Migrating

In order to migrate a Boot2Docker VM to Docker Machine, you must have Docker Machine installed. If you do not have Docker Machine, please see the [install docs](http://docs.docker.com/machine/#installation) before proceeding.

> Note: when migrating to Docker Machine, this will also update Docker to the latest stable version

To migrate a Boot2Docker VM, run the following command where `<boot2docker-vm-name>` is the name of your Boot2Docker VM and `<new-machine-name>` is the name of the new Machine (i.e. `dev`):

> To get the name of your Boot2Docker VM, use the `boot2docker config` command. Default: `boot2docker-vm`.

```
docker-machine create -d virtualbox --virtualbox-import-boot2docker-vm <boot2docker-vm-name> <new-machine-name>
```

> Note: this will stop the Boot2Docker VM in order to safely copy the virtual disk

You should see output similar to the following:

```
$> docker-machine create -d virtualbox --virtualbox-import-boot2docker-vm boot2docker-vm dev
INFO[0000] Creating VirtualBox VM...
INFO[0001] Starting VirtualBox VM...
INFO[0001] Waiting for VM to start...
INFO[0035] "dev" has been created and is now the active machine.
INFO[0035] To point your Docker client at it, run this in your shell: eval "$(docker-machine env dev)"
```

You now should have a Machine that contains all of the Docker data from the Boot2Docker VM. See the Docker Machine [usage docs](http://docs.docker.com/machine/#getting-started-with-docker-machine-using-a-local-vm) for details on working with Machine.

# Cleanup
When migrating a Boot2Docker VM to Docker Machine the Boot2Docker VM is left intact. Once you have verified that all of your Docker data (containers, images, etc) are in the new Machine, you can remove the Boot2Docker VM using `boot2docker delete`.

# Command Comparison

| boot2docker cli | machine | machine description |
|----|----|----|
| init | create | creates a new docker host |
| up | start | starts a stopped machine |
| ssh | ssh | runs a command or interactive ssh session on the machine |
| save | - | n/a |
| down | stop | stops a running machine |
| poweroff | stop | stops a running machine |
| reset | restart | restarts a running machine |
| config | inspect (*) | shows details about machine |
| status | ls (**) | shows a list of all machines |
| info | inspect (*) | shows details about machine |
| ip | url (***) | shows the Docker URL for the machine |
| shellinit | env | shows the environment configuration needed to configure the Docker CLI for the machine |
| delete | rm | removes a machine |
| download | - | |
| upgrade | upgrade | upgrades Docker on the machine to the latest stable release |


\* provides similar functionality but not exact

** `ls` will show all machines including their status

** the `url` command reports the entire Docker URL including the IP / Hostname:
```
```
```
```
47 changes: 47 additions & 0 deletions drivers/virtualbox/disk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package virtualbox

import (
"bufio"
"io"
"strings"
)

type VirtualDisk struct {
UUID string
Path string
}

func parseDiskInfo(r io.Reader) (*VirtualDisk, error) {
s := bufio.NewScanner(r)
disk := &VirtualDisk{}
for s.Scan() {
line := s.Text()
if line == "" {
continue
}
res := reEqualQuoteLine.FindStringSubmatch(line)
if res == nil {
continue
}
key, val := res[1], res[2]
switch key {
case "SATA-1-0":
disk.Path = val
case "SATA-ImageUUID-1-0":
disk.UUID = val
}
}
if err := s.Err(); err != nil {
return nil, err
}
return disk, nil
}

func getVMDiskInfo(name string) (*VirtualDisk, error) {
out, err := vbmOut("showvminfo", name, "--machinereadable")
if err != nil {
return nil, err
}
r := strings.NewReader(out)
return parseDiskInfo(r)
}
36 changes: 36 additions & 0 deletions drivers/virtualbox/disk_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package virtualbox

import (
"strings"
"testing"
)

var (
testDiskInfoText = `
storagecontrollerbootable0="on"
"SATA-0-0"="/home/ehazlett/.boot2docker/boot2docker.iso"
"SATA-IsEjected"="off"
"SATA-1-0"="/home/ehazlett/vm/test/disk.vmdk"
"SATA-ImageUUID-1-0"="12345-abcdefg"
"SATA-2-0"="none"
nic1="nat"
`
)

func TestVMDiskInfo(t *testing.T) {
r := strings.NewReader(testDiskInfoText)
disk, err := parseDiskInfo(r)
if err != nil {
t.Fatal(err)
}

diskPath := "/home/ehazlett/vm/test/disk.vmdk"
diskUUID := "12345-abcdefg"
if disk.Path != diskPath {
t.Fatalf("expected disk path %s", diskPath)
}

if disk.UUID != diskUUID {
t.Fatalf("expected disk uuid %s", diskUUID)
}
}
2 changes: 2 additions & 0 deletions drivers/virtualbox/vbm.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ var (
reVMNameUUID = regexp.MustCompile(`"(.+)" {([0-9a-f-]+)}`)
reVMInfoLine = regexp.MustCompile(`(?:"(.+)"|(.+))=(?:"(.*)"|(.*))`)
reColonLine = regexp.MustCompile(`(.+):\s+(.*)`)
reEqualLine = regexp.MustCompile(`(.+)=(.*)`)
reEqualQuoteLine = regexp.MustCompile(`"(.+)"="(.*)"`)
reMachineNotFound = regexp.MustCompile(`Could not find a registered machine named '(.+)'`)
)

Expand Down
88 changes: 67 additions & 21 deletions drivers/virtualbox/virtualbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,20 @@ const (
)

type Driver struct {
CPU int
MachineName string
SSHUser string
SSHPort int
Memory int
DiskSize int
Boot2DockerURL string
CaCertPath string
PrivateKeyPath string
SwarmMaster bool
SwarmHost string
SwarmDiscovery string
storePath string
CPU int
MachineName string
SSHUser string
SSHPort int
Memory int
DiskSize int
Boot2DockerURL string
CaCertPath string
PrivateKeyPath string
SwarmMaster bool
SwarmHost string
SwarmDiscovery string
storePath string
Boot2DockerImportVM string
}

func init() {
Expand Down Expand Up @@ -80,6 +81,11 @@ func GetCreateFlags() []cli.Flag {
Usage: "The URL of the boot2docker image. Defaults to the latest available version",
Value: "",
},
cli.StringFlag{
Name: "virtualbox-import-boot2docker-vm",
Usage: "The name of a Boot2Docker VM to import",
Value: "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I keep thinking that maybe we should default this value to boot2docker-vm, since that's by far the most common name and I have a feeling users will run this flag without args expecting it to "just work".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, I'd certainly expect that myself -- I don't know that's it's very common for users to actually name their boot2docker VMs (even though it's supported to do so)

},
}
}

Expand Down Expand Up @@ -147,6 +153,7 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
d.SwarmHost = flags.String("swarm-host")
d.SwarmDiscovery = flags.String("swarm-discovery")
d.SSHUser = "docker"
d.Boot2DockerImportVM = flags.String("virtualbox-import-boot2docker-vm")

return nil
}
Expand All @@ -170,21 +177,57 @@ func (d *Driver) Create() error {
return err
}

log.Infof("Creating SSH key...")

b2dutils := utils.NewB2dUtils("", "")
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
return err
}

if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
return err
}

log.Infof("Creating VirtualBox VM...")

if err := d.generateDiskImage(d.DiskSize); err != nil {
return err
// import b2d VM if requested
if d.Boot2DockerImportVM != "" {
name := d.Boot2DockerImportVM

// make sure vm is stopped
_ = vbm("controlvm", name, "poweroff")

diskInfo, err := getVMDiskInfo(name)
if err != nil {
return err
}

if _, err := os.Stat(diskInfo.Path); err != nil {
return err
}

if err := vbm("clonehd", diskInfo.Path, d.diskPath()); err != nil {
return err
}

log.Debugf("Importing VM settings...")
vmInfo, err := getVMInfo(name)
if err != nil {
return err
}

d.CPU = vmInfo.CPUs
d.Memory = vmInfo.Memory

log.Debugf("Importing SSH key...")
keyPath := filepath.Join(utils.GetHomeDir(), ".ssh", "id_boot2docker")
if err := utils.CopyFile(keyPath, d.GetSSHKeyPath()); err != nil {
return err
}
} else {
log.Infof("Creating SSH key...")
if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
return err
}

log.Debugf("Creating disk image...")
if err := d.generateDiskImage(d.DiskSize); err != nil {
return err
}
}

if err := vbm("createvm",
Expand All @@ -194,6 +237,9 @@ func (d *Driver) Create() error {
return err
}

log.Debugf("VM CPUS: %d", d.CPU)
log.Debugf("VM Memory: %d", d.Memory)

cpus := d.CPU
if cpus < 1 {
cpus = int(runtime.NumCPU())
Expand Down
55 changes: 55 additions & 0 deletions drivers/virtualbox/vm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package virtualbox

import (
"bufio"
"io"
"strconv"
"strings"
)

type VirtualBoxVM struct {
CPUs int
Memory int
}

func parseVMInfo(r io.Reader) (*VirtualBoxVM, error) {
s := bufio.NewScanner(r)
vm := &VirtualBoxVM{}
for s.Scan() {
line := s.Text()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move this little block into its own function please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean here. The scanning?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bit after line := s.Text(). Should work fine to just pass line into it right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but then we would have to pass the ref to VirtualBoxVm{} to set the fields or make it generic to return the val and then parse. I think it's concise the way it is and it's tested as well.

if line == "" {
continue
}
res := reEqualLine.FindStringSubmatch(line)
if res == nil {
continue
}
switch key, val := res[1], res[2]; key {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I think I'd prefer separate lines for the assignment and the switch here for clarity (switch key) -- do you agree?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok works for me. We should change it in the original too -- that's how it is to find the network details too.

case "cpus":
v, err := strconv.Atoi(val)
if err != nil {
return nil, err
}
vm.CPUs = v
case "memory":
v, err := strconv.Atoi(val)
if err != nil {
return nil, err
}
vm.Memory = v
}
}
if err := s.Err(); err != nil {
return nil, err
}
return vm, nil
}

func getVMInfo(name string) (*VirtualBoxVM, error) {
out, err := vbmOut("showvminfo", name, "--machinereadable")
if err != nil {
return nil, err
}
r := strings.NewReader(out)
return parseVMInfo(r)
}
38 changes: 38 additions & 0 deletions drivers/virtualbox/vm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package virtualbox

import (
"strings"
"testing"
)

var (
testVMInfoText = `
storagecontrollerbootable0="on"
memory=1024
cpus=2
"SATA-0-0"="/home/ehazlett/.boot2docker/boot2docker.iso"
"SATA-IsEjected"="off"
"SATA-1-0"="/home/ehazlett/vm/test/disk.vmdk"
"SATA-ImageUUID-1-0"="12345-abcdefg"
"SATA-2-0"="none"
nic1="nat"
`
)

func TestVMInfo(t *testing.T) {
r := strings.NewReader(testVMInfoText)
vm, err := parseVMInfo(r)
if err != nil {
t.Fatal(err)
}

vmCPUs := 2
vmMemory := 1024
if vm.CPUs != vmCPUs {
t.Fatalf("expected %d cpus; received %d", vmCPUs, vm.CPUs)
}

if vm.Memory != vmMemory {
t.Fatalf("expected memory %d; received %d", vmMemory, vm.Memory)
}
}
Loading