Merge pull request #1292 from ehazlett/vbox-recreate-hostonlyif

virtualbox: more flexibility with networking
This commit is contained in:
Nathan LeClaire 2015-06-09 09:35:08 -07:00
commit 3387f988b8
3 changed files with 121 additions and 14 deletions

View File

@ -1464,6 +1464,7 @@ Options:
- `--virtualbox-disk-size`: Size of disk for the host in MB. - `--virtualbox-disk-size`: Size of disk for the host in MB.
- `--virtualbox-boot2docker-url`: The URL of the boot2docker image. Defaults to the latest available version. - `--virtualbox-boot2docker-url`: The URL of the boot2docker image. Defaults to the latest available version.
- `--virtualbox-import-boot2docker-vm`: The name of a Boot2Docker VM to import. - `--virtualbox-import-boot2docker-vm`: The name of a Boot2Docker VM to import.
- `--virtualbox-hostonly-cidr`: The CIDR of the host only adapter.
The `--virtualbox-boot2docker-url` flag takes a few different forms. By The `--virtualbox-boot2docker-url` flag takes a few different forms. By
default, if no value is specified for this flag, Machine will check locally for default, if no value is specified for this flag, Machine will check locally for
@ -1482,6 +1483,15 @@ file://$HOME/Downloads/rc.iso` to test out a release candidate ISO that you have
downloaded already. You could also just get an ISO straight from the Internet downloaded already. You could also just get an ISO straight from the Internet
using the `http://` form. using the `http://` form.
To customize the host only adapter, you can use the `--virtualbox-hostonly-cidr`
flag. This will specify the host IP and Machine will calculate the VirtualBox
DHCP server address (a random IP on the subnet between `.1` and `.25`) so
it does not clash with the specified host IP.
Machine will also specify the DHCP lower bound to `.100` and the upper bound
to `.254`. For example, a specified CIDR of `192.168.24.1/24` would have a
DHCP server between `192.168.24.2-25`, a lower bound of `192.168.24.100` and
upper bound of `192.168.24.254`.
Environment variables and default values: Environment variables and default values:
| CLI option | Environment variable | Default | | CLI option | Environment variable | Default |
@ -1491,6 +1501,7 @@ Environment variables and default values:
| `--virtualbox-disk-size` | `VIRTUALBOX_DISK_SIZE` | `20000` | | `--virtualbox-disk-size` | `VIRTUALBOX_DISK_SIZE` | `20000` |
| `--virtualbox-boot2docker-url` | `VIRTUALBOX_BOOT2DOCKER_URL` | *Latest boot2docker url* | | `--virtualbox-boot2docker-url` | `VIRTUALBOX_BOOT2DOCKER_URL` | *Latest boot2docker url* |
| `--virtualbox-import-boot2docker-vm` | - | `boot2docker-vm` | | `--virtualbox-import-boot2docker-vm` | - | `boot2docker-vm` |
| `--virtualbox-hostonly-cidr` | `VIRTUALBOX_HOSTONLY_CIDR` | `192.168.99.1/24` |
#### VMware Fusion #### VMware Fusion
Creates machines locally on [VMware Fusion](http://www.vmware.com/products/fusion). Requires VMware Fusion to be installed. Creates machines locally on [VMware Fusion](http://www.vmware.com/products/fusion). Requires VMware Fusion to be installed.

View File

@ -3,9 +3,11 @@ package virtualbox
import ( import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math/rand"
"net" "net"
"os" "os"
"os/exec" "os/exec"
@ -28,6 +30,10 @@ const (
isoFilename = "boot2docker.iso" isoFilename = "boot2docker.iso"
) )
var (
ErrUnableToGenerateRandomIP = errors.New("unable to generate random IP")
)
type Driver struct { type Driver struct {
IPAddress string IPAddress string
CPU int CPU int
@ -44,6 +50,7 @@ type Driver struct {
SwarmDiscovery string SwarmDiscovery string
storePath string storePath string
Boot2DockerImportVM string Boot2DockerImportVM string
HostOnlyCIDR string
} }
func init() { func init() {
@ -86,6 +93,12 @@ func GetCreateFlags() []cli.Flag {
Usage: "The name of a Boot2Docker VM to import", Usage: "The name of a Boot2Docker VM to import",
Value: "", Value: "",
}, },
cli.StringFlag{
Name: "virtualbox-hostonly-cidr",
Usage: "Specify the Host Only CIDR",
Value: "192.168.99.1/24",
EnvVar: "VIRTUALBOX_HOSTONLY_CIDR",
},
} }
} }
@ -150,6 +163,7 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
d.SwarmDiscovery = flags.String("swarm-discovery") d.SwarmDiscovery = flags.String("swarm-discovery")
d.SSHUser = "docker" d.SSHUser = "docker"
d.Boot2DockerImportVM = flags.String("virtualbox-import-boot2docker-vm") d.Boot2DockerImportVM = flags.String("virtualbox-import-boot2docker-vm")
d.HostOnlyCIDR = flags.String("virtualbox-hostonly-cidr")
return nil return nil
} }
@ -273,20 +287,7 @@ func (d *Driver) Create() error {
return err return err
} }
hostOnlyNetwork, err := getOrCreateHostOnlyNetwork( if err := d.setupHostOnlyNetwork(d.MachineName); err != nil {
net.ParseIP("192.168.99.1"),
net.IPv4Mask(255, 255, 255, 0),
net.ParseIP("192.168.99.2"),
net.ParseIP("192.168.99.100"),
net.ParseIP("192.168.99.254"))
if err != nil {
return err
}
if err := vbm("modifyvm", d.MachineName,
"--nic2", "hostonly",
"--nictype2", "82540EM",
"--hostonlyadapter2", hostOnlyNetwork.Name,
"--cableconnected2", "on"); err != nil {
return err return err
} }
@ -372,6 +373,11 @@ func (d *Driver) Start() error {
return err return err
} }
// check network to re-create if needed
if err := d.setupHostOnlyNetwork(d.MachineName); err != nil {
return err
}
switch s { switch s {
case state.Stopped, state.Saved: case state.Stopped, state.Saved:
d.SSHPort, err = setPortForwarding(d.MachineName, 1, "ssh", "tcp", 22, d.SSHPort) d.SSHPort, err = setPortForwarding(d.MachineName, 1, "ssh", "tcp", 22, d.SSHPort)
@ -572,6 +578,43 @@ func (d *Driver) generateDiskImage(size int) error {
return createDiskImage(d.diskPath(), size, raw) return createDiskImage(d.diskPath(), size, raw)
} }
func (d *Driver) setupHostOnlyNetwork(machineName string) error {
ip, network, err := net.ParseCIDR(d.HostOnlyCIDR)
nAddr := network.IP.To4()
dhcpAddr, err := getRandomIPinSubnet(network.IP)
if err != nil {
return err
}
lowerDHCPIP := net.IPv4(nAddr[0], nAddr[1], nAddr[2], byte(100))
upperDHCPIP := net.IPv4(nAddr[0], nAddr[1], nAddr[2], byte(254))
log.Debugf("using %s for dhcp address", dhcpAddr)
hostOnlyNetwork, err := getOrCreateHostOnlyNetwork(
ip,
network.Mask,
dhcpAddr,
lowerDHCPIP,
upperDHCPIP,
)
if err != nil {
return err
}
if err := vbm("modifyvm", machineName,
"--nic2", "hostonly",
"--nictype2", "82540EM",
"--hostonlyadapter2", hostOnlyNetwork.Name,
"--cableconnected2", "on"); err != nil {
return err
}
return nil
}
// createDiskImage makes a disk image at dest with the given size in MB. If r is // createDiskImage makes a disk image at dest with the given size in MB. If r is
// not nil, it will be read as a raw disk image to convert from. // not nil, it will be read as a raw disk image to convert from.
func createDiskImage(dest string, size int, r io.Reader) error { func createDiskImage(dest string, size int, r io.Reader) error {
@ -678,3 +721,26 @@ func setPortForwarding(machine string, interfaceNum int, mapName, protocol strin
} }
return actualHostPort, nil return actualHostPort, nil
} }
// getRandomIPinSubnet returns a pseudo-random net.IP in the same
// subnet as the IP passed
func getRandomIPinSubnet(baseIP net.IP) (net.IP, error) {
var dhcpAddr net.IP
nAddr := baseIP.To4()
// select pseudo-random DHCP addr; make sure not to clash with the host
// only try 5 times and bail if no random received
for i := 0; i < 5; i++ {
n := rand.Intn(25)
if byte(n) != nAddr[3] {
dhcpAddr = net.IPv4(nAddr[0], nAddr[1], nAddr[2], byte(1))
break
}
}
if dhcpAddr == nil {
return nil, ErrUnableToGenerateRandomIP
}
return dhcpAddr, nil
}

View File

@ -1 +1,31 @@
package virtualbox package virtualbox
import (
"net"
"testing"
)
func TestGetRandomIPinSubnet(t *testing.T) {
// test IP 1.2.3.4
testIP := net.IPv4(byte(1), byte(2), byte(3), byte(4))
newIP, err := getRandomIPinSubnet(testIP)
if err != nil {
t.Fatal(err)
}
if testIP.Equal(newIP) {
t.Fatalf("expected different IP (source %s); received %s", testIP.String(), newIP.String())
}
if newIP[0] != testIP[0] {
t.Fatalf("expected first octet of %d; received %d", testIP[0], newIP[0])
}
if newIP[1] != testIP[1] {
t.Fatalf("expected second octet of %d; received %d", testIP[1], newIP[1])
}
if newIP[2] != testIP[2] {
t.Fatalf("expected third octet of %d; received %d", testIP[2], newIP[2])
}
}