github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/client/fingerprint/network.go (about)

     1  package fingerprint
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"net"
     9  	"os/exec"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/hashicorp/nomad/client/config"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  )
    17  
    18  // NetworkFingerprint is used to fingerprint the Network capabilities of a node
    19  type NetworkFingerprint struct {
    20  	StaticFingerprinter
    21  	logger            *log.Logger
    22  	interfaceDetector NetworkInterfaceDetector
    23  }
    24  
    25  // An interface to isolate calls to various api in net package
    26  // This facilitates testing where we can implement
    27  // fake interfaces and addresses to test varios code paths
    28  type NetworkInterfaceDetector interface {
    29  	Interfaces() ([]net.Interface, error)
    30  	InterfaceByName(name string) (*net.Interface, error)
    31  	Addrs(intf *net.Interface) ([]net.Addr, error)
    32  }
    33  
    34  // Implements the interface detector which calls net directly
    35  type DefaultNetworkInterfaceDetector struct {
    36  }
    37  
    38  func (b *DefaultNetworkInterfaceDetector) Interfaces() ([]net.Interface, error) {
    39  	return net.Interfaces()
    40  }
    41  
    42  func (b *DefaultNetworkInterfaceDetector) InterfaceByName(name string) (*net.Interface, error) {
    43  	return net.InterfaceByName(name)
    44  }
    45  
    46  func (b *DefaultNetworkInterfaceDetector) Addrs(intf *net.Interface) ([]net.Addr, error) {
    47  	return intf.Addrs()
    48  }
    49  
    50  // NewNetworkFingerprinter returns a new NetworkFingerprinter with the given
    51  // logger
    52  func NewNetworkFingerprinter(logger *log.Logger) Fingerprint {
    53  	f := &NetworkFingerprint{logger: logger, interfaceDetector: &DefaultNetworkInterfaceDetector{}}
    54  	return f
    55  }
    56  
    57  func (f *NetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
    58  	// newNetwork is populated and addded to the Nodes resources
    59  	newNetwork := &structs.NetworkResource{}
    60  	var ip string
    61  
    62  	intf, err := f.findInterface(cfg.NetworkInterface)
    63  	if err != nil {
    64  		return false, fmt.Errorf("Error while detecting network interface during fingerprinting: %v", err)
    65  	}
    66  
    67  	// No interface could be found
    68  	if intf == nil {
    69  		return false, nil
    70  	}
    71  
    72  	if ip, err = f.ipAddress(intf); err != nil {
    73  		return false, fmt.Errorf("Unable to find IP address of interface: %s, err: %v", intf.Name, err)
    74  	}
    75  
    76  	newNetwork.Device = intf.Name
    77  	node.Attributes["unique.network.ip-address"] = ip
    78  	newNetwork.IP = ip
    79  	newNetwork.CIDR = newNetwork.IP + "/32"
    80  
    81  	f.logger.Printf("[DEBUG] fingerprint.network: Detected interface %v  with IP %v during fingerprinting", intf.Name, ip)
    82  
    83  	if throughput := f.linkSpeed(intf.Name); throughput > 0 {
    84  		newNetwork.MBits = throughput
    85  	} else {
    86  		f.logger.Printf("[DEBUG] fingerprint.network: Unable to read link speed; setting to default %v", cfg.NetworkSpeed)
    87  		newNetwork.MBits = cfg.NetworkSpeed
    88  	}
    89  
    90  	if node.Resources == nil {
    91  		node.Resources = &structs.Resources{}
    92  	}
    93  
    94  	node.Resources.Networks = append(node.Resources.Networks, newNetwork)
    95  
    96  	// return true, because we have a network connection
    97  	return true, nil
    98  }
    99  
   100  // linkSpeed returns link speed in Mb/s, or 0 when unable to determine it.
   101  func (f *NetworkFingerprint) linkSpeed(device string) int {
   102  	// Use LookPath to find the ethtool in the systems $PATH
   103  	// If it's not found or otherwise errors, LookPath returns and empty string
   104  	// and an error we can ignore for our purposes
   105  	ethtoolPath, _ := exec.LookPath("ethtool")
   106  	if ethtoolPath != "" {
   107  		if speed := f.linkSpeedEthtool(ethtoolPath, device); speed > 0 {
   108  			return speed
   109  		}
   110  	}
   111  
   112  	// Fall back on checking a system file for link speed.
   113  	return f.linkSpeedSys(device)
   114  }
   115  
   116  // linkSpeedSys parses link speed in Mb/s from /sys.
   117  func (f *NetworkFingerprint) linkSpeedSys(device string) int {
   118  	path := fmt.Sprintf("/sys/class/net/%s/speed", device)
   119  
   120  	// Read contents of the device/speed file
   121  	content, err := ioutil.ReadFile(path)
   122  	if err != nil {
   123  		f.logger.Printf("[WARN] fingerprint.network: Unable to read link speed from %s", path)
   124  		return 0
   125  	}
   126  
   127  	lines := strings.Split(string(content), "\n")
   128  	mbs, err := strconv.Atoi(lines[0])
   129  	if err != nil || mbs <= 0 {
   130  		f.logger.Printf("[WARN] fingerprint.network: Unable to parse link speed from %s", path)
   131  		return 0
   132  	}
   133  
   134  	return mbs
   135  }
   136  
   137  // linkSpeedEthtool determines link speed in Mb/s with 'ethtool'.
   138  func (f *NetworkFingerprint) linkSpeedEthtool(path, device string) int {
   139  	outBytes, err := exec.Command(path, device).Output()
   140  	if err != nil {
   141  		f.logger.Printf("[WARN] fingerprint.network: Error calling ethtool (%s %s): %v", path, device, err)
   142  		return 0
   143  	}
   144  
   145  	output := strings.TrimSpace(string(outBytes))
   146  	re := regexp.MustCompile("Speed: [0-9]+[a-zA-Z]+/s")
   147  	m := re.FindString(output)
   148  	if m == "" {
   149  		// no matches found, output may be in a different format
   150  		f.logger.Printf("[WARN] fingerprint.network: Unable to parse Speed in output of '%s %s'", path, device)
   151  		return 0
   152  	}
   153  
   154  	// Split and trim the Mb/s unit from the string output
   155  	args := strings.Split(m, ": ")
   156  	raw := strings.TrimSuffix(args[1], "Mb/s")
   157  
   158  	// convert to Mb/s
   159  	mbs, err := strconv.Atoi(raw)
   160  	if err != nil || mbs <= 0 {
   161  		f.logger.Printf("[WARN] fingerprint.network: Unable to parse Mb/s in output of '%s %s'", path, device)
   162  		return 0
   163  	}
   164  
   165  	return mbs
   166  }
   167  
   168  // Gets the ipv4 addr for a network interface
   169  func (f *NetworkFingerprint) ipAddress(intf *net.Interface) (string, error) {
   170  	var addrs []net.Addr
   171  	var err error
   172  
   173  	if addrs, err = f.interfaceDetector.Addrs(intf); err != nil {
   174  		return "", err
   175  	}
   176  
   177  	if len(addrs) == 0 {
   178  		return "", errors.New(fmt.Sprintf("Interface %s has no IP address", intf.Name))
   179  	}
   180  	for _, addr := range addrs {
   181  		var ip net.IP
   182  		switch v := (addr).(type) {
   183  		case *net.IPNet:
   184  			ip = v.IP
   185  		case *net.IPAddr:
   186  			ip = v.IP
   187  		}
   188  		if ip.To4() != nil {
   189  			return ip.String(), nil
   190  		}
   191  	}
   192  
   193  	return "", fmt.Errorf("Couldn't parse IP address for interface %s", intf.Name)
   194  
   195  }
   196  
   197  // Checks if the device is marked UP by the operator
   198  func (f *NetworkFingerprint) isDeviceEnabled(intf *net.Interface) bool {
   199  	return intf.Flags&net.FlagUp != 0
   200  }
   201  
   202  // Checks if the device has any IP address configured
   203  func (f *NetworkFingerprint) deviceHasIpAddress(intf *net.Interface) bool {
   204  	_, err := f.ipAddress(intf)
   205  	return err == nil
   206  }
   207  
   208  func (n *NetworkFingerprint) isDeviceLoopBackOrPointToPoint(intf *net.Interface) bool {
   209  	return intf.Flags&(net.FlagLoopback|net.FlagPointToPoint) != 0
   210  }
   211  
   212  // Returns the interface with the name passed by user
   213  // If the name is blank then it iterates through all the devices
   214  // and finds one which is routable and marked as UP
   215  // It excludes PPP and lo devices unless they are specifically asked
   216  func (f *NetworkFingerprint) findInterface(deviceName string) (*net.Interface, error) {
   217  	var interfaces []net.Interface
   218  	var err error
   219  
   220  	if deviceName != "" {
   221  		return f.interfaceDetector.InterfaceByName(deviceName)
   222  	}
   223  
   224  	var intfs []net.Interface
   225  
   226  	if intfs, err = f.interfaceDetector.Interfaces(); err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	for _, intf := range intfs {
   231  		if f.isDeviceEnabled(&intf) && !f.isDeviceLoopBackOrPointToPoint(&intf) && f.deviceHasIpAddress(&intf) {
   232  			interfaces = append(interfaces, intf)
   233  		}
   234  	}
   235  
   236  	if len(interfaces) == 0 {
   237  		return nil, nil
   238  	}
   239  	return &interfaces[0], nil
   240  }