Handle multiple devices per VMware network type

Fixes #5979
This commit is contained in:
Andreas Sommer 2018-03-07 10:59:55 +01:00
parent 9eeb1c3932
commit ff92fb883d
4 changed files with 162 additions and 127 deletions

View File

@ -315,7 +315,7 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) {
// convert the stashed network to a device
network := state.Get("vmnetwork").(string)
device, err := netmap.NameIntoDevice(network)
devices, err := netmap.NameIntoDevices(network)
// we were unable to find the device, maybe it's a custom one...
// so, check to see if it's in the .vmx configuration
@ -326,7 +326,9 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) {
return "", err
}
var device string
device, err = readCustomDeviceName(vmxData)
devices = append(devices, device)
if err != nil {
return "", err
}
@ -338,64 +340,67 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) {
return "", err
}
// figure out the correct dhcp leases
dhcpLeasesPath := d.DhcpLeasesPath(device)
log.Printf("DHCP leases path: %s", dhcpLeasesPath)
if dhcpLeasesPath == "" {
return "", errors.New("no DHCP leases path found.")
}
// open up the lease and read its contents
fh, err := os.Open(dhcpLeasesPath)
if err != nil {
return "", err
}
defer fh.Close()
dhcpBytes, err := ioutil.ReadAll(fh)
if err != nil {
return "", err
}
// start grepping through the file looking for fields that we care about
var lastIp string
var lastLeaseEnd time.Time
var curIp string
var curLeaseEnd time.Time
ipLineRe := regexp.MustCompile(`^lease (.+?) {$`)
endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`)
macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`)
for _, line := range strings.Split(string(dhcpBytes), "\n") {
// Need to trim off CR character when running in windows
line = strings.TrimRight(line, "\r")
matches := ipLineRe.FindStringSubmatch(line)
if matches != nil {
lastIp = matches[1]
continue
for _, device := range devices {
// figure out the correct dhcp leases
dhcpLeasesPath := d.DhcpLeasesPath(device)
log.Printf("Trying DHCP leases path: %s", dhcpLeasesPath)
if dhcpLeasesPath == "" {
return "", fmt.Errorf("no DHCP leases path found for device %s", device)
}
matches = endTimeLineRe.FindStringSubmatch(line)
if matches != nil {
lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1])
continue
// open up the lease and read its contents
fh, err := os.Open(dhcpLeasesPath)
if err != nil {
return "", err
}
defer fh.Close()
dhcpBytes, err := ioutil.ReadAll(fh)
if err != nil {
return "", err
}
// If the mac address matches and this lease ends farther in the
// future than the last match we might have, then choose it.
matches = macLineRe.FindStringSubmatch(line)
if matches != nil && strings.EqualFold(matches[1], MACAddress) && curLeaseEnd.Before(lastLeaseEnd) {
curIp = lastIp
curLeaseEnd = lastLeaseEnd
// start grepping through the file looking for fields that we care about
var lastIp string
var lastLeaseEnd time.Time
var curIp string
var curLeaseEnd time.Time
ipLineRe := regexp.MustCompile(`^lease (.+?) {$`)
endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`)
macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`)
for _, line := range strings.Split(string(dhcpBytes), "\n") {
// Need to trim off CR character when running in windows
line = strings.TrimRight(line, "\r")
matches := ipLineRe.FindStringSubmatch(line)
if matches != nil {
lastIp = matches[1]
continue
}
matches = endTimeLineRe.FindStringSubmatch(line)
if matches != nil {
lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1])
continue
}
// If the mac address matches and this lease ends farther in the
// future than the last match we might have, then choose it.
matches = macLineRe.FindStringSubmatch(line)
if matches != nil && strings.EqualFold(matches[1], MACAddress) && curLeaseEnd.Before(lastLeaseEnd) {
curIp = lastIp
curLeaseEnd = lastLeaseEnd
}
}
if curIp != "" {
return curIp, nil
}
}
if curIp == "" {
return "", fmt.Errorf("IP not found for MAC %s in DHCP leases at %s", MACAddress, dhcpLeasesPath)
}
return curIp, nil
return "", fmt.Errorf("None of the found device(s) %v has a DHCP lease for MAC %s", devices, MACAddress)
}
func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) {
@ -408,7 +413,7 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) {
// convert network to name
network := state.Get("vmnetwork").(string)
device, err := netmap.NameIntoDevice(network)
devices, err := netmap.NameIntoDevices(network)
// we were unable to find the device, maybe it's a custom one...
// so, check to see if it's in the .vmx configuration
@ -419,49 +424,56 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) {
return "", err
}
var device string
device, err = readCustomDeviceName(vmxData)
devices = append(devices, device)
if err != nil {
return "", err
}
}
// parse dhcpd configuration
pathDhcpConfig := d.DhcpConfPath(device)
if _, err := os.Stat(pathDhcpConfig); err != nil {
return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig)
}
config, err := ReadDhcpConfig(pathDhcpConfig)
if err != nil {
return "", err
}
// find the entry configured in the dhcpd
interfaceConfig, err := config.HostByName(device)
if err != nil {
return "", err
}
// finally grab the hardware address
address, err := interfaceConfig.Hardware()
if err == nil {
return address.String(), nil
}
// we didn't find it, so search through our interfaces for the device name
interfaceList, err := net.Interfaces()
if err == nil {
return "", err
}
names := make([]string, 0)
for _, intf := range interfaceList {
if strings.HasSuffix(strings.ToLower(intf.Name), device) {
return intf.HardwareAddr.String(), nil
var lastError error
for _, device := range devices {
// parse dhcpd configuration
pathDhcpConfig := d.DhcpConfPath(device)
if _, err := os.Stat(pathDhcpConfig); err != nil {
return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig)
}
config, err := ReadDhcpConfig(pathDhcpConfig)
if err != nil {
lastError = err
continue
}
// find the entry configured in the dhcpd
interfaceConfig, err := config.HostByName(device)
if err != nil {
lastError = err
continue
}
// finally grab the hardware address
address, err := interfaceConfig.Hardware()
if err == nil {
return address.String(), nil
}
// we didn't find it, so search through our interfaces for the device name
interfaceList, err := net.Interfaces()
if err == nil {
return "", err
}
names := make([]string, 0)
for _, intf := range interfaceList {
if strings.HasSuffix(strings.ToLower(intf.Name), device) {
return intf.HardwareAddr.String(), nil
}
names = append(names, intf.Name)
}
names = append(names, intf.Name)
}
return "", fmt.Errorf("Unable to find device %s : %v", device, names)
return "", fmt.Errorf("Unable to find host address from devices %v, last error: %s", devices, lastError)
}
func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) {
@ -474,7 +486,7 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) {
// convert network to name
network := state.Get("vmnetwork").(string)
device, err := netmap.NameIntoDevice(network)
devices, err := netmap.NameIntoDevices(network)
// we were unable to find the device, maybe it's a custom one...
// so, check to see if it's in the .vmx configuration
@ -485,32 +497,41 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) {
return "", err
}
var device string
device, err = readCustomDeviceName(vmxData)
devices = append(devices, device)
if err != nil {
return "", err
}
}
// parse dhcpd configuration
pathDhcpConfig := d.DhcpConfPath(device)
if _, err := os.Stat(pathDhcpConfig); err != nil {
return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig)
}
config, err := ReadDhcpConfig(pathDhcpConfig)
if err != nil {
return "", err
}
var lastError error
for _, device := range devices {
// parse dhcpd configuration
pathDhcpConfig := d.DhcpConfPath(device)
if _, err := os.Stat(pathDhcpConfig); err != nil {
return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig)
}
config, err := ReadDhcpConfig(pathDhcpConfig)
if err != nil {
lastError = err
continue
}
// find the entry configured in the dhcpd
interfaceConfig, err := config.HostByName(device)
if err != nil {
return "", err
}
// find the entry configured in the dhcpd
interfaceConfig, err := config.HostByName(device)
if err != nil {
lastError = err
continue
}
address, err := interfaceConfig.IP4()
if err != nil {
return "", err
}
address, err := interfaceConfig.IP4()
if err != nil {
lastError = err
continue
}
return address.String(), nil
return address.String(), nil
}
return "", fmt.Errorf("Unable to find host IP from devices %v, last error: %s", devices, lastError)
}

View File

@ -98,9 +98,9 @@ type NetworkMapperMock struct {
DeviceIntoNameCalled int
}
func (m NetworkMapperMock) NameIntoDevice(name string) (string, error) {
func (m NetworkMapperMock) NameIntoDevices(name string) ([]string, error) {
m.NameIntoDeviceCalled += 1
return "", nil
return make([]string, 0), nil
}
func (m NetworkMapperMock) DeviceIntoName(device string) (string, error) {
m.DeviceIntoNameCalled += 1

View File

@ -1065,7 +1065,7 @@ func (e *DhcpConfiguration) HostByName(host string) (configDeclaration, error) {
type NetworkMap []map[string]string
type NetworkNameMapper interface {
NameIntoDevice(string) (string, error)
NameIntoDevices(string) ([]string, error)
DeviceIntoName(string) (string, error)
}
@ -1082,13 +1082,17 @@ func ReadNetworkMap(fd *os.File) (NetworkMap, error) {
return result, nil
}
func (e NetworkMap) NameIntoDevice(name string) (string, error) {
func (e NetworkMap) NameIntoDevices(name string) ([]string, error) {
var devices []string
for _, val := range e {
if strings.ToLower(val["name"]) == strings.ToLower(name) {
return val["device"], nil
devices = append(devices, val["device"])
}
}
return "", fmt.Errorf("Network name not found : %v", name)
if len(devices) > 0 {
return devices, nil
}
return make([]string, 0), fmt.Errorf("Network name not found : %v", name)
}
func (e NetworkMap) DeviceIntoName(device string) (string, error) {
for _, val := range e {
@ -1905,21 +1909,26 @@ func networkingConfig_NamesToVmnet(config NetworkingConfig) map[NetworkingType][
const NetworkingInterfacePrefix = "vmnet"
func (e NetworkingConfig) NameIntoDevice(name string) (string, error) {
func (e NetworkingConfig) NameIntoDevices(name string) ([]string, error) {
netmapper := networkingConfig_NamesToVmnet(e)
name = strings.ToLower(name)
var vmnet int
var vmnets []string
var networkingType NetworkingType
if name == "hostonly" && len(netmapper[NetworkingType_HOSTONLY]) > 0 {
vmnet = netmapper[NetworkingType_HOSTONLY][0]
networkingType = NetworkingType_HOSTONLY
} else if name == "nat" && len(netmapper[NetworkingType_NAT]) > 0 {
vmnet = netmapper[NetworkingType_NAT][0]
networkingType = NetworkingType_NAT
} else if name == "bridged" && len(netmapper[NetworkingType_BRIDGED]) > 0 {
vmnet = netmapper[NetworkingType_BRIDGED][0]
networkingType = NetworkingType_BRIDGED
} else {
return "", fmt.Errorf("Network name not found : %v", name)
return make([]string, 0), fmt.Errorf("Network name not found: %v", name)
}
return fmt.Sprintf("%s%d", NetworkingInterfacePrefix, vmnet), nil
for i := 0; i < len(netmapper[networkingType]); i++ {
vmnets = append(vmnets, fmt.Sprintf("%s%d", NetworkingInterfacePrefix, netmapper[networkingType][i]))
}
return vmnets, nil
}
func (e NetworkingConfig) DeviceIntoName(device string) (string, error) {

View File

@ -475,14 +475,19 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist
}
// try and convert the specified network to a device.
device, err := netmap.NameIntoDevice(network)
devices, err := netmap.NameIntoDevices(network)
if err == nil {
// success. so we know that it's an actual network type inside netmap.conf
if err == nil && len(devices) > 0 {
// If multiple devices exist, for example for network "nat", VMware chooses
// the actual device. Only type "custom" allows the exact choice of a
// specific virtual network (see below). We allow VMware to choose the device
// and for device-specific operations like GuestIP, try to go over all
// devices that match a name (e.g. "nat").
// https://pubs.vmware.com/workstation-9/index.jsp?topic=%2Fcom.vmware.ws.using.doc%2FGUID-3B504F2F-7A0B-415F-AE01-62363A95D052.html
templateData.Network_Type = network
templateData.Network_Device = device
templateData.Network_Device = ""
} else {
// otherwise, we were unable to find the type, so assume its a custom device.
// otherwise, we were unable to find the type, so assume it's a custom device
templateData.Network_Type = "custom"
templateData.Network_Device = network
}