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 }