Merge pull request #5985 from AndiDog/f-5979-vmware-fusion-guestip-lookup-multiple-devices

Handle multiple devices per VMware network type
This commit is contained in:
Megan Marsh 2018-03-21 17:12:36 -07:00 committed by GitHub
commit ef4f3f143b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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 // convert the stashed network to a device
network := state.Get("vmnetwork").(string) 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... // we were unable to find the device, maybe it's a custom one...
// so, check to see if it's in the .vmx configuration // 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 return "", err
} }
var device string
device, err = readCustomDeviceName(vmxData) device, err = readCustomDeviceName(vmxData)
devices = append(devices, device)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -338,64 +340,67 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) {
return "", err return "", err
} }
// figure out the correct dhcp leases for _, device := range devices {
dhcpLeasesPath := d.DhcpLeasesPath(device) // figure out the correct dhcp leases
log.Printf("DHCP leases path: %s", dhcpLeasesPath) dhcpLeasesPath := d.DhcpLeasesPath(device)
if dhcpLeasesPath == "" { log.Printf("Trying DHCP leases path: %s", dhcpLeasesPath)
return "", errors.New("no DHCP leases path found.") if dhcpLeasesPath == "" {
} return "", fmt.Errorf("no DHCP leases path found for device %s", device)
// 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
} }
matches = endTimeLineRe.FindStringSubmatch(line) // open up the lease and read its contents
if matches != nil { fh, err := os.Open(dhcpLeasesPath)
lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1]) if err != nil {
continue 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 // start grepping through the file looking for fields that we care about
// future than the last match we might have, then choose it. var lastIp string
matches = macLineRe.FindStringSubmatch(line) var lastLeaseEnd time.Time
if matches != nil && strings.EqualFold(matches[1], MACAddress) && curLeaseEnd.Before(lastLeaseEnd) {
curIp = lastIp var curIp string
curLeaseEnd = lastLeaseEnd 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 "", fmt.Errorf("None of the found device(s) %v has a DHCP lease for MAC %s", devices, MACAddress)
}
return curIp, nil
} }
func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) { 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 // convert network to name
network := state.Get("vmnetwork").(string) 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... // we were unable to find the device, maybe it's a custom one...
// so, check to see if it's in the .vmx configuration // 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 return "", err
} }
var device string
device, err = readCustomDeviceName(vmxData) device, err = readCustomDeviceName(vmxData)
devices = append(devices, device)
if err != nil { if err != nil {
return "", err return "", err
} }
} }
// parse dhcpd configuration var lastError error
pathDhcpConfig := d.DhcpConfPath(device) for _, device := range devices {
if _, err := os.Stat(pathDhcpConfig); err != nil { // parse dhcpd configuration
return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) 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 config, err := ReadDhcpConfig(pathDhcpConfig)
} if err != nil {
lastError = err
// find the entry configured in the dhcpd continue
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 {
// finally grab the hardware address lastError = err
address, err := interfaceConfig.Hardware() continue
if err == nil { }
return address.String(), nil
} // finally grab the hardware address
address, err := interfaceConfig.Hardware()
// we didn't find it, so search through our interfaces for the device name if err == nil {
interfaceList, err := net.Interfaces() return address.String(), nil
if err == nil { }
return "", err
} // we didn't find it, so search through our interfaces for the device name
interfaceList, err := net.Interfaces()
names := make([]string, 0) if err == nil {
for _, intf := range interfaceList { return "", err
if strings.HasSuffix(strings.ToLower(intf.Name), device) { }
return intf.HardwareAddr.String(), nil
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) { 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 // convert network to name
network := state.Get("vmnetwork").(string) 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... // we were unable to find the device, maybe it's a custom one...
// so, check to see if it's in the .vmx configuration // 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 return "", err
} }
var device string
device, err = readCustomDeviceName(vmxData) device, err = readCustomDeviceName(vmxData)
devices = append(devices, device)
if err != nil { if err != nil {
return "", err return "", err
} }
} }
// parse dhcpd configuration var lastError error
pathDhcpConfig := d.DhcpConfPath(device) for _, device := range devices {
if _, err := os.Stat(pathDhcpConfig); err != nil { // parse dhcpd configuration
return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) pathDhcpConfig := d.DhcpConfPath(device)
} if _, err := os.Stat(pathDhcpConfig); err != nil {
config, err := ReadDhcpConfig(pathDhcpConfig) return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig)
if err != nil { }
return "", err config, err := ReadDhcpConfig(pathDhcpConfig)
} if err != nil {
lastError = err
continue
}
// find the entry configured in the dhcpd // find the entry configured in the dhcpd
interfaceConfig, err := config.HostByName(device) interfaceConfig, err := config.HostByName(device)
if err != nil { if err != nil {
return "", err lastError = err
} continue
}
address, err := interfaceConfig.IP4() address, err := interfaceConfig.IP4()
if err != nil { if err != nil {
return "", err 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 DeviceIntoNameCalled int
} }
func (m NetworkMapperMock) NameIntoDevice(name string) (string, error) { func (m NetworkMapperMock) NameIntoDevices(name string) ([]string, error) {
m.NameIntoDeviceCalled += 1 m.NameIntoDeviceCalled += 1
return "", nil return make([]string, 0), nil
} }
func (m NetworkMapperMock) DeviceIntoName(device string) (string, error) { func (m NetworkMapperMock) DeviceIntoName(device string) (string, error) {
m.DeviceIntoNameCalled += 1 m.DeviceIntoNameCalled += 1

View File

@ -1065,7 +1065,7 @@ func (e *DhcpConfiguration) HostByName(host string) (configDeclaration, error) {
type NetworkMap []map[string]string type NetworkMap []map[string]string
type NetworkNameMapper interface { type NetworkNameMapper interface {
NameIntoDevice(string) (string, error) NameIntoDevices(string) ([]string, error)
DeviceIntoName(string) (string, error) DeviceIntoName(string) (string, error)
} }
@ -1082,13 +1082,17 @@ func ReadNetworkMap(fd *os.File) (NetworkMap, error) {
return result, nil 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 { for _, val := range e {
if strings.ToLower(val["name"]) == strings.ToLower(name) { 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) { func (e NetworkMap) DeviceIntoName(device string) (string, error) {
for _, val := range e { for _, val := range e {
@ -1905,21 +1909,26 @@ func networkingConfig_NamesToVmnet(config NetworkingConfig) map[NetworkingType][
const NetworkingInterfacePrefix = "vmnet" const NetworkingInterfacePrefix = "vmnet"
func (e NetworkingConfig) NameIntoDevice(name string) (string, error) { func (e NetworkingConfig) NameIntoDevices(name string) ([]string, error) {
netmapper := networkingConfig_NamesToVmnet(e) netmapper := networkingConfig_NamesToVmnet(e)
name = strings.ToLower(name) name = strings.ToLower(name)
var vmnet int var vmnets []string
var networkingType NetworkingType
if name == "hostonly" && len(netmapper[NetworkingType_HOSTONLY]) > 0 { 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 { } 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 { } else if name == "bridged" && len(netmapper[NetworkingType_BRIDGED]) > 0 {
vmnet = netmapper[NetworkingType_BRIDGED][0] networkingType = NetworkingType_BRIDGED
} else { } 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) { 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. // try and convert the specified network to a device.
device, err := netmap.NameIntoDevice(network) devices, err := netmap.NameIntoDevices(network)
if err == nil { if err == nil && len(devices) > 0 {
// success. so we know that it's an actual network type inside netmap.conf // 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_Type = network
templateData.Network_Device = device templateData.Network_Device = ""
} else { } 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_Type = "custom"
templateData.Network_Device = network templateData.Network_Device = network
} }