From f6d5dc486776019e0799f95f8d1982be8fba4da5 Mon Sep 17 00:00:00 2001
From: Joshua Harlow <jxharlow@godaddy.com>
Date: Thu, 15 Sep 2016 14:46:14 -0700
Subject: Replace usage of sys_netdev_info with read_sys_net

I've seen cases of unable to read from files as
well as the existing os errors so catch io error
and skip by using the smarter read_sys_net instead.

LP: #1625766
---
 cloudinit/net/__init__.py | 166 +++++++++++++++++++++++-----------------------
 cloudinit/net/cmdline.py  |   9 ++-
 2 files changed, 90 insertions(+), 85 deletions(-)
 mode change 100644 => 100755 cloudinit/net/__init__.py
 mode change 100644 => 100755 cloudinit/net/cmdline.py

(limited to 'cloudinit/net')

diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
old mode 100644
new mode 100755
index 7e58bfea..465c2a01
--- a/cloudinit/net/__init__.py
+++ b/cloudinit/net/__init__.py
@@ -32,25 +32,53 @@ def sys_dev_path(devname, path=""):
     return SYS_CLASS_NET + devname + "/" + path
 
 
-def read_sys_net(devname, path, translate=None, enoent=None, keyerror=None):
+def read_sys_net(devname, path, translate=None,
+                 on_enoent=None, on_keyerror=None,
+                 on_einval=None):
+    dev_path = sys_dev_path(devname, path)
     try:
-        contents = util.load_file(sys_dev_path(devname, path))
+        contents = util.load_file(dev_path)
     except (OSError, IOError) as e:
-        if getattr(e, 'errno', None) in (errno.ENOENT, errno.ENOTDIR):
-            if enoent is not None:
-                return enoent
+        e_errno = getattr(e, 'errno', None)
+        if e_errno in (errno.ENOENT, errno.ENOTDIR):
+            if on_enoent is not None:
+                return on_enoent(e)
+        if e_errno in (errno.EINVAL,):
+            if on_einval is not None:
+                return on_einval(e)
         raise
     contents = contents.strip()
     if translate is None:
         return contents
     try:
-        return translate.get(contents)
-    except KeyError:
-        LOG.debug("found unexpected value '%s' in '%s/%s'", contents,
-                  devname, path)
-        if keyerror is not None:
-            return keyerror
-        raise
+        return translate[contents]
+    except KeyError as e:
+        if on_keyerror is not None:
+            return on_keyerror(e)
+        else:
+            LOG.debug("Found unexpected (not translatable) value"
+                      " '%s' in '%s", contents, dev_path)
+            raise
+
+
+def read_sys_net_safe(iface, field, translate=None):
+    def on_excp_false(e):
+        return False
+    return read_sys_net(iface, field,
+                        on_keyerror=on_excp_false,
+                        on_enoent=on_excp_false,
+                        on_einval=on_excp_false,
+                        translate=translate)
+
+
+def read_sys_net_int(iface, field):
+    val = read_sys_net_safe(iface, field)
+    if val is False:
+        return None
+    try:
+        return int(val)
+    except TypeError:
+        return None
 
 
 def is_up(devname):
@@ -58,8 +86,7 @@ def is_up(devname):
     # operstate as up for the purposes of network configuration. See
     # Documentation/networking/operstates.txt in the kernel source.
     translate = {'up': True, 'unknown': True, 'down': False}
-    return read_sys_net(devname, "operstate", enoent=False, keyerror=False,
-                        translate=translate)
+    return read_sys_net_safe(devname, "operstate", translate=translate)
 
 
 def is_wireless(devname):
@@ -70,21 +97,14 @@ def is_connected(devname):
     # is_connected isn't really as simple as that.  2 is
     # 'physically connected'. 3 is 'not connected'. but a wlan interface will
     # always show 3.
-    try:
-        iflink = read_sys_net(devname, "iflink", enoent=False)
-        if iflink == "2":
-            return True
-        if not is_wireless(devname):
-            return False
-        LOG.debug("'%s' is wireless, basing 'connected' on carrier", devname)
-
-        return read_sys_net(devname, "carrier", enoent=False, keyerror=False,
-                            translate={'0': False, '1': True})
-
-    except IOError as e:
-        if e.errno == errno.EINVAL:
-            return False
-        raise
+    iflink = read_sys_net_safe(devname, "iflink")
+    if iflink == "2":
+        return True
+    if not is_wireless(devname):
+        return False
+    LOG.debug("'%s' is wireless, basing 'connected' on carrier", devname)
+    return read_sys_net_safe(devname, "carrier",
+                             translate={'0': False, '1': True})
 
 
 def is_physical(devname):
@@ -109,25 +129,9 @@ def is_disabled_cfg(cfg):
     return cfg.get('config') == "disabled"
 
 
-def sys_netdev_info(name, field):
-    if not os.path.exists(os.path.join(SYS_CLASS_NET, name)):
-        raise OSError("%s: interface does not exist in %s" %
-                      (name, SYS_CLASS_NET))
-    fname = os.path.join(SYS_CLASS_NET, name, field)
-    if not os.path.exists(fname):
-        raise OSError("%s: could not find sysfs entry: %s" % (name, fname))
-    data = util.load_file(fname)
-    if data[-1] == '\n':
-        data = data[:-1]
-    return data
-
-
 def generate_fallback_config():
     """Determine which attached net dev is most likely to have a connection and
        generate network state to run dhcp on that interface"""
-    # by default use eth0 as primary interface
-    nconf = {'config': [], 'version': 1}
-
     # get list of interfaces that could have connections
     invalid_interfaces = set(['lo'])
     potential_interfaces = set(get_devicelist())
@@ -142,30 +146,21 @@ def generate_fallback_config():
         if os.path.exists(sys_dev_path(interface, "bridge")):
             # skip any bridges
             continue
-        try:
-            carrier = int(sys_netdev_info(interface, 'carrier'))
-            if carrier:
-                connected.append(interface)
-                continue
-        except OSError:
-            pass
+        carrier = read_sys_net_int(interface, 'carrier')
+        if carrier:
+            connected.append(interface)
+            continue
         # check if nic is dormant or down, as this may make a nick appear to
         # not have a carrier even though it could acquire one when brought
         # online by dhclient
-        try:
-            dormant = int(sys_netdev_info(interface, 'dormant'))
-            if dormant:
-                possibly_connected.append(interface)
-                continue
-        except OSError:
-            pass
-        try:
-            operstate = sys_netdev_info(interface, 'operstate')
-            if operstate in ['dormant', 'down', 'lowerlayerdown', 'unknown']:
-                possibly_connected.append(interface)
-                continue
-        except OSError:
-            pass
+        dormant = read_sys_net_int(interface, 'dormant')
+        if dormant:
+            possibly_connected.append(interface)
+            continue
+        operstate = read_sys_net_safe(interface, 'operstate')
+        if operstate in ['dormant', 'down', 'lowerlayerdown', 'unknown']:
+            possibly_connected.append(interface)
+            continue
 
     # don't bother with interfaces that might not be connected if there are
     # some that definitely are
@@ -173,23 +168,30 @@ def generate_fallback_config():
         potential_interfaces = connected
     else:
         potential_interfaces = possibly_connected
-    # if there are no interfaces, give up
-    if not potential_interfaces:
-        return
+
     # if eth0 exists use it above anything else, otherwise get the interface
-    # that looks 'first'
-    if DEFAULT_PRIMARY_INTERFACE in potential_interfaces:
-        name = DEFAULT_PRIMARY_INTERFACE
+    # that we can read 'first' (using the sorted defintion of first).
+    names = list(sorted(potential_interfaces))
+    if DEFAULT_PRIMARY_INTERFACE in names:
+        names.remove(DEFAULT_PRIMARY_INTERFACE)
+        names.insert(0, DEFAULT_PRIMARY_INTERFACE)
+    target_name = None
+    target_mac = None
+    for name in names:
+        mac = read_sys_net_safe(name, 'address')
+        if mac:
+            target_name = name
+            target_mac = mac
+            break
+    if target_mac and target_name:
+        nconf = {'config': [], 'version': 1}
+        nconf['config'].append(
+            {'type': 'physical', 'name': target_name,
+             'mac_address': target_mac, 'subnets': [{'type': 'dhcp'}]})
+        return nconf
     else:
-        name = sorted(potential_interfaces)[0]
-
-    mac = sys_netdev_info(name, 'address')
-    target_name = name
-
-    nconf['config'].append(
-        {'type': 'physical', 'name': target_name,
-         'mac_address': mac, 'subnets': [{'type': 'dhcp'}]})
-    return nconf
+        # can't read any interfaces addresses (or there are none); give up
+        return None
 
 
 def apply_network_config_names(netcfg, strict_present=True, strict_busy=True):
@@ -352,7 +354,7 @@ def get_interface_mac(ifname):
         # for a bond slave, get the nic's hwaddress, not the address it
         # is using because its part of a bond.
         path = "bonding_slave/perm_hwaddr"
-    return read_sys_net(ifname, path, enoent=False)
+    return read_sys_net_safe(ifname, path)
 
 
 def get_interfaces_by_mac(devs=None):
diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py
old mode 100644
new mode 100755
index cbed908d..a8bf73fb
--- a/cloudinit/net/cmdline.py
+++ b/cloudinit/net/cmdline.py
@@ -26,7 +26,7 @@ import sys
 import six
 
 from . import get_devicelist
-from . import sys_netdev_info
+from . import read_sys_net_safe
 
 from cloudinit import util
 
@@ -210,7 +210,10 @@ def read_kernel_cmdline_config(files=None, mac_addrs=None, cmdline=None):
         return None
 
     if mac_addrs is None:
-        mac_addrs = dict((k, sys_netdev_info(k, 'address'))
-                         for k in get_devicelist())
+        mac_addrs = {}
+        for k in get_devicelist():
+            mac_addr = read_sys_net_safe(k, 'address')
+            if mac_addr:
+                mac_addrs[k] = mac_addr
 
     return config_from_klibc_net_cfg(files=files, mac_addrs=mac_addrs)
-- 
cgit v1.2.3