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-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-hostonly-cidr`: The CIDR of the host only adapter.
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
@ -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
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:
| CLI option | Environment variable | Default |
@ -1491,6 +1501,7 @@ Environment variables and default values:
| `--virtualbox-disk-size` | `VIRTUALBOX_DISK_SIZE` | `20000` |
| `--virtualbox-boot2docker-url` | `VIRTUALBOX_BOOT2DOCKER_URL` | *Latest boot2docker url* |
| `--virtualbox-import-boot2docker-vm` | - | `boot2docker-vm` |
| `--virtualbox-hostonly-cidr` | `VIRTUALBOX_HOSTONLY_CIDR` | `192.168.99.1/24` |
#### VMware Fusion
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 (
"archive/tar"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net"
"os"
"os/exec"
@ -28,6 +30,10 @@ const (
isoFilename = "boot2docker.iso"
)
var (
ErrUnableToGenerateRandomIP = errors.New("unable to generate random IP")
)
type Driver struct {
IPAddress string
CPU int
@ -44,6 +50,7 @@ type Driver struct {
SwarmDiscovery string
storePath string
Boot2DockerImportVM string
HostOnlyCIDR string
}
func init() {
@ -86,6 +93,12 @@ func GetCreateFlags() []cli.Flag {
Usage: "The name of a Boot2Docker VM to import",
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.SSHUser = "docker"
d.Boot2DockerImportVM = flags.String("virtualbox-import-boot2docker-vm")
d.HostOnlyCIDR = flags.String("virtualbox-hostonly-cidr")
return nil
}
@ -273,20 +287,7 @@ func (d *Driver) Create() error {
return err
}
hostOnlyNetwork, err := getOrCreateHostOnlyNetwork(
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 {
if err := d.setupHostOnlyNetwork(d.MachineName); err != nil {
return err
}
@ -372,6 +373,11 @@ func (d *Driver) Start() error {
return err
}
// check network to re-create if needed
if err := d.setupHostOnlyNetwork(d.MachineName); err != nil {
return err
}
switch s {
case state.Stopped, state.Saved:
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)
}
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
// not nil, it will be read as a raw disk image to convert from.
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
}
// 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
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])
}
}