diff --git a/src/bosh-virtualbox-cpi/vm/host.go b/src/bosh-virtualbox-cpi/vm/host.go index dd096079..48cec60d 100644 --- a/src/bosh-virtualbox-cpi/vm/host.go +++ b/src/bosh-virtualbox-cpi/vm/host.go @@ -30,6 +30,11 @@ func (h Host) FindNetwork(net Network) (bnet.Network, error) { } } +// Enable the network that the VM is to be attached to, and create that +// network if necessary. +// +// In the VM creation workflow, the network is enabled _before_ being looked +// up with (vm.Host).FindNetworks(Network). func (h Host) EnableNetworks(nets Networks) error { for _, net := range nets { switch net.CloudPropertyType() { @@ -86,6 +91,8 @@ func (n *hostNetwork) Find() (bnet.Network, error) { return nil, fmt.Errorf("Expected to find network '%s'", n.net.CloudPropertyName()) } +// Enable the network, and try to create it if it does not exist and if is not +// already been tried. func (n *hostNetwork) Enable() error { actualNets, err := n.adapter.List() if err != nil { @@ -189,13 +196,7 @@ func (n hostOnlysAdapter) List() ([]bnet.Network, error) { } func (n hostOnlysAdapter) Create(net Network) error { - canCreate, err := n.AddHostOnly(net.CloudPropertyName(), net.Gateway(), net.Netmask()) - if err != nil { - return err - } else if !canCreate { - return fmt.Errorf("Expected to find Host-only network '%s'", net.CloudPropertyName()) - } - return nil + return n.AddHostOnly(net.CloudPropertyName(), net.Gateway(), net.Netmask()) } func (n hostOnlysAdapter) Matches(net Network, actualNet bnet.Network) bool { diff --git a/src/bosh-virtualbox-cpi/vm/network/add_host_only.go b/src/bosh-virtualbox-cpi/vm/network/add_host_only.go index 6f8833c6..2f523efd 100644 --- a/src/bosh-virtualbox-cpi/vm/network/add_host_only.go +++ b/src/bosh-virtualbox-cpi/vm/network/add_host_only.go @@ -10,121 +10,99 @@ import ( ) var ( - createdHostOnlyMatch = regexp.MustCompile(`Interface '(.+)' was successfully created`) - createdHostOnlyNetMatch = regexp.MustCompile(`Name: vboxnet0`) + createdHostOnlyMatch = regexp.MustCompile(`Interface '(.+)' was successfully created`) ) -func (n Networks) AddHostOnly(name, gateway, netmask string) (bool, error) { - // VB does not allow naming host-only networks inside version <= 6 , exit if it's not the first one - if len(name) > 0 && name != "vboxnet0" { - return false, nil - } - - createdName, err := n.createHostOnly(gateway, netmask) - +func (n Networks) AddHostOnly(expectedName, gateway, netmask string) error { + systemInfo, err := n.NewSystemInfo() if err != nil { - return true, err + return err } - if len(name) > 0 && createdName != name { - n.cleanUpPartialHostOnlyCreate(createdName) - return true, fmt.Errorf("expected created host-only network '%s' to have name '%s'", createdName, name) + var createdName string + if systemInfo.IsMacOSVboxV7OrLater() { + if err := n.createHostOnlyNet(expectedName, gateway, netmask); err != nil { + return err + } + createdName = expectedName + } else { + // Virtualox v6 or earlier choses itself the name of newly created + // host-only networks + createdName, err := n.createLegacyHostOnly() + if err != nil { + return err + } + if len(expectedName) > 0 && createdName != expectedName { + n.cleanUpPartialHostOnlyCreate(createdName) + return fmt.Errorf("expected created host-only network '%s' to have name '%s'. Have you made an incorrect guess?", createdName, expectedName) + } } err = n.configureHostOnly(createdName, gateway, netmask) if err != nil { n.cleanUpPartialHostOnlyCreate(createdName) - return true, err + return err } - return true, nil + return nil } -func (n Networks) createHostOnly(gateway, netmask string) (string, error) { +func (n Networks) createHostOnlyNet(name, gateway, netmask string) error { systemInfo, err := n.NewSystemInfo() if err != nil { - return "", err + return err + } + maskIP := net.ParseIP(netmask).To4() + if maskIP == nil { + return bosherr.Errorf("expected netmask to be valid IP v4 (got '%s')", netmask) + } + gwIP := net.ParseIP(gateway) + if gwIP == nil { + return bosherr.Errorf("expected gateway to be valid IP v4 (got '%s')", gateway) } - var matches []string - var errorMessage string - var expectedMatchesLen int - - if systemInfo.IsMacOSVboxV7OrLater() { - maskIP := net.ParseIP(netmask).To4() - if maskIP == nil { - return "", bosherr.Errorf("expected netmask to be valid IP v4 (got '%s')", netmask) - } - gwIP := net.ParseIP(gateway) - if gwIP == nil { - return "", bosherr.Errorf("expected gateway to be valid IP v4 (got '%s')", gateway) - } - - mask := net.IPv4Mask(maskIP[0], maskIP[1], maskIP[2], maskIP[3]) - subnetFirstIP := &net.IPNet{ - IP: gwIP, - Mask: mask, - } - maskLength, _ := mask.Size() - _, subnet, _ := net.ParseCIDR(fmt.Sprintf("%s/%v", gateway, maskLength)) - - lowerIp, err := systemInfo.GetFirstIP(subnetFirstIP) - if err != nil { - return "", err - } - upperIp, err := systemInfo.GetLastIP(subnet) - if err != nil { - return "", err - } - - args := []string{"hostonlynet", - "add", fmt.Sprintf("--name=%s", "vboxnet0"), - fmt.Sprintf("--netmask=%s", netmask), fmt.Sprintf("--lower-ip=%s", lowerIp.String()), - fmt.Sprintf("--upper-ip=%s", upperIp.String()), "--disable"} + mask := net.IPv4Mask(maskIP[0], maskIP[1], maskIP[2], maskIP[3]) + subnetFirstIP := &net.IPNet{ + IP: gwIP, + Mask: mask, + } + maskLength, _ := mask.Size() + _, subnet, _ := net.ParseCIDR(fmt.Sprintf("%s/%v", gateway, maskLength)) - // The output of the hostonlynet interface creation is empty. We need another solution to handle and verify the - // VboxManage creation. - _, err = n.driver.ExecuteComplex(args, driver.ExecuteOpts{}) - if err != nil { - return "", err - } + var lowerIP, upperIP net.IP + if lowerIP, err = systemInfo.GetFirstIP(subnetFirstIP); err != nil { + return err + } + if upperIP, err = systemInfo.GetLastIP(subnet); err != nil { + return err + } - args = []string{"list", "hostonlynets"} - output, err := n.driver.ExecuteComplex(args, driver.ExecuteOpts{}) - if err != nil { - return "", err - } + args := []string{"hostonlynet", + "add", fmt.Sprintf("--name=%s", name), + fmt.Sprintf("--netmask=%s", netmask), fmt.Sprintf("--lower-ip=%s", lowerIP.String()), + fmt.Sprintf("--upper-ip=%s", upperIP.String()), "--disable"} - matches = createdHostOnlyNetMatch.FindStringSubmatch(output) - //Define the return value of the created Host only Adapter. We're only creating one adapter, - //so we can also define the used name hard coded. - if len(matches) == 1 { - matches[0] = "vboxnet0" - } + // The output of the hostonlynet interface creation is empty. We need another solution to handle and verify the + // VboxManage creation. + _, err = n.driver.ExecuteComplex(args, driver.ExecuteOpts{}) + if err != nil { + return err + } + return nil +} - errorMessage = fmt.Sprintf( - "Internal inconsistency: Expected len(%s matches) == 1:", - createdHostOnlyNetMatch, - ) - expectedMatchesLen = 1 - } else { - output, err := n.driver.Execute("hostonlyif", "create") - if err != nil { - return "", err - } - matches = createdHostOnlyMatch.FindStringSubmatch(output) - errorMessage = fmt.Sprintf( - "Internal inconsistency: Expected len(%s matches) == 2:", - createdHostOnlyMatch, - ) - expectedMatchesLen = 2 +func (n Networks) createLegacyHostOnly() (string, error) { + output, err := n.driver.Execute("hostonlyif", "create") + if err != nil { + return "", err } - if len(matches) != expectedMatchesLen { - panic(errorMessage) + matches := createdHostOnlyMatch.FindStringSubmatch(output) + if len(matches) != 2 { + panic(fmt.Sprintf("Internal inconsistency: Expected len(%s matches) == 2:", createdHostOnlyMatch)) } - return matches[expectedMatchesLen-1], nil + return matches[1], nil } func (n Networks) configureHostOnly(name, gateway, netmask string) error { @@ -157,17 +135,11 @@ func (n Networks) cleanUpPartialHostOnlyCreate(name string) { "Failed to get the SystemInfo: %s", err) } - args := []string{ - "hostonlyif", - "remove", - name, - } - if systemInfo.IsMacOSVbox7() { - args = []string{ - "hostonlynet", - "remove", - fmt.Sprintf("--name=%s", name), - } + args := []string{"hostonlyif", "remove"} + if systemInfo.IsMacOSVboxV7OrLater() { + args = append(args, fmt.Sprintf("--name=%s", name)) + } else { + args = append(args, name) } _, err = n.driver.ExecuteComplex(args, driver.ExecuteOpts{}) diff --git a/src/bosh-virtualbox-cpi/vm/network/system_info.go b/src/bosh-virtualbox-cpi/vm/network/system_info.go index d71bf695..b2fee92d 100644 --- a/src/bosh-virtualbox-cpi/vm/network/system_info.go +++ b/src/bosh-virtualbox-cpi/vm/network/system_info.go @@ -6,6 +6,8 @@ import ( "net" "runtime" "strings" + + bosherr "github.com/cloudfoundry/bosh-utils/errors" ) type SystemInfo struct { @@ -57,7 +59,7 @@ func (n Networks) getVboxVersion() (string, string, error) { matches := strings.Split(output, ".") if len(matches) > 3 { - panic(fmt.Sprintf("Internal inconsistency: Expected len(%s matches) >= 3:", createdHostOnlyMatch)) + return "", "", bosherr.Errorf("Expected VirtualBox version to have 3 dot-separated parts (got '%s')", output) } return matches[0], matches[1], nil