Skip to content

Commit

Permalink
Merge branch 'vmware-iso-extraconfig' into release-0.10.1
Browse files Browse the repository at this point in the history
  • Loading branch information
arizvisa committed Aug 26, 2016
2 parents 4e5f651 + 5ed351c commit f9d33e1
Show file tree
Hide file tree
Showing 31 changed files with 2,200 additions and 426 deletions.
310 changes: 302 additions & 8 deletions builder/vmware/common/driver.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package common

import (
"errors"
"bytes"
"fmt"
"log"
"os"
"os/exec"
"io/ioutil"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"net"

"github.com/mitchellh/multistep"
)
Expand All @@ -29,16 +34,18 @@ type Driver interface {
// Checks if the VMX file at the given path is running.
IsRunning(string) (bool, error)

// CommHost returns the host address for the VM that is being
// managed by this driver.
CommHost(multistep.StateBag) (string, error)

// Start starts a VM specified by the path to the VMX given.
Start(string, bool) error

// Stop stops a VM specified by the path to the VMX given.
Stop(string) error

// Unregister unregisters a VM specified by the path to the VMX.
Unregister(string) error

// Destroy removes a VM specified by the path to the VMX.
Destroy(string) error

// SuppressMessages modifies the VMX or surrounding directory so that
// VMware doesn't show any annoying messages.
SuppressMessages(string) error
Expand All @@ -49,14 +56,32 @@ type Driver interface {
// Attach the VMware tools ISO
ToolsInstall() error

// Get the path to the DHCP leases file for the given device.
DhcpLeasesPath(string) string

// Verify checks to make sure that this driver should function
// properly. This should check that all the files it will use
// appear to exist and so on. If everything is okay, this doesn't
// return an error. Otherwise, this returns an error.
// return an error. Otherwise, this returns an error. Each vmware
// driver should assign the VmwareMachine callback functions for locating
// paths within this function.
Verify() error

/// This is to establish a connection to the guest
CommHost(multistep.StateBag) (string, error)

/// These methods are generally implemented by the VmwareDriver
/// structure within this file. A driver implementation can
/// reimplement these, though, if it wants.

// Get the guest hw address for the vm
GuestAddress(multistep.StateBag) (string, error)

// Get the guest ip address for the vm
GuestIP(multistep.StateBag) (string, error)

// Get the host hw address for the vm
HostAddress(multistep.StateBag) (string, error)

// Get the host ip address for the vm
HostIP(multistep.StateBag) (string, error)
}

// NewDriver returns a new driver implementation for this operating
Expand Down Expand Up @@ -189,3 +214,272 @@ func compareVersions(versionFound string, versionWanted string, product string)

return nil
}

/// helper functions that read configuration information from a file
// read the network<->device configuration out of the specified path
func readNetmapConfig(path string) (NetworkMap,error) {
fd,err := os.Open(path)
if err != nil { return nil, err }
defer fd.Close()
return ReadNetworkMap(fd)
}

// read the dhcp configuration out of the specified path
func readDhcpConfig(path string) (DhcpConfiguration,error) {
fd,err := os.Open(path)
if err != nil { return nil, err }
defer fd.Close()
return ReadDhcpConfiguration(fd)
}

// read the VMX configuration from the specified path
func readVMXConfig(path string) (map[string]string,error) {
f, err := os.Open(path)
if err != nil {
return map[string]string{}, err
}
defer f.Close()

vmxBytes, err := ioutil.ReadAll(f)
if err != nil {
return map[string]string{}, err
}
return ParseVMX(string(vmxBytes)), nil
}

// read the connection type out of a vmx configuration
func readCustomDeviceName(vmxData map[string]string) (string,error) {

connectionType, ok := vmxData["ethernet0.connectiontype"]
if !ok || connectionType != "custom" {
return "", fmt.Errorf("Unable to determine the device name for the connection type : %s", connectionType)
}

device, ok := vmxData["ethernet0.vnet"]
if !ok || device == "" {
return "", fmt.Errorf("Unable to determine the device name for the connection type \"%s\" : %s", connectionType, device)
}
return device, nil
}

// This VmwareDriver is a base class that contains default methods
// that a Driver can use or implement themselves.
type VmwareDriver struct {
/// These methods define paths that are utilized by the driver
/// A driver must overload these in order to point to the correct
/// files so that the address detection (ip and ethernet) machinery
/// works.
DhcpLeasesPath func(string) string
DhcpConfPath func(string) string
VmnetnatConfPath func(string) string
NetmapConfPath func() string
}

func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string,error) {
vmxPath := state.Get("vmx_path").(string)

log.Println("Lookup up IP information...")
vmxData, err := readVMXConfig(vmxPath)
if err != nil { return "", err }

var ok bool
macAddress := ""
if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" {
return "", errors.New("couldn't find MAC address in VMX")
}
}

res,err := net.ParseMAC(macAddress)
if err != nil { return "", err }

return res.String(),nil
}

func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string,error) {

// read netmap config
pathNetmap := d.NetmapConfPath()
if _, err := os.Stat(pathNetmap); err != nil {
return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap)
}
netmap,err := readNetmapConfig(pathNetmap)
if err != nil { return "",err }

// convert the stashed network to a device
network := state.Get("vmnetwork").(string)
device,err := netmap.NameIntoDevice(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
if err != nil || network == "custom" {
vmxPath := state.Get("vmx_path").(string)
vmxData, err := readVMXConfig(vmxPath)
if err != nil { return "", err }

device, err = readCustomDeviceName(vmxData)
if err != nil { return "", err }
}

// figure out our MAC address for looking up the guest address
MACAddress,err := d.GuestAddress(state)
if err != nil { 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
}

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 "", fmt.Errorf("IP not found for MAC %s in DHCP leases at %s", MACAddress, dhcpLeasesPath)
}
return curIp, nil
}

func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string,error) {

// parse network<->device mapping
pathNetmap := d.NetmapConfPath()
if _, err := os.Stat(pathNetmap); err != nil {
return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap)
}
netmap,err := readNetmapConfig(pathNetmap)
if err != nil { return "",err }

// convert network to name
network := state.Get("vmnetwork").(string)
device,err := netmap.NameIntoDevice(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
if err != nil || network == "custom" {
vmxPath := state.Get("vmx_path").(string)
vmxData, err := readVMXConfig(vmxPath)
if err != nil { return "", err }

device, err = readCustomDeviceName(vmxData)
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
}
names = append(names, intf.Name)
}
return "",fmt.Errorf("Unable to find device %s : %v", device, names)
}

func (d *VmwareDriver) HostIP(state multistep.StateBag) (string,error) {

// parse network<->device mapping
pathNetmap := d.NetmapConfPath()
if _, err := os.Stat(pathNetmap); err != nil {
return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap)
}
netmap,err := readNetmapConfig(pathNetmap)
if err != nil { return "",err }

// convert network to name
network := state.Get("vmnetwork").(string)
device,err := netmap.NameIntoDevice(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
if err != nil || network == "custom" {
vmxPath := state.Get("vmx_path").(string)
vmxData, err := readVMXConfig(vmxPath)
if err != nil { return "", err }

device, err = readCustomDeviceName(vmxData)
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 }

address,err := interfaceConfig.IP4()
if err != nil { return "", err }

return address.String(),nil
}
Loading

0 comments on commit f9d33e1

Please sign in to comment.