All comments addressed. Please glance over the changes if you get a chance.

Diff comments:

> diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py
> index 993b26c..081f7b4 100644
> --- a/cloudinit/netinfo.py
> +++ b/cloudinit/netinfo.py
> @@ -18,18 +21,81 @@ from cloudinit.simpletable import SimpleTable
>  LOG = logging.getLogger()
>  
>  
> -def netdev_info(empty=""):
> -    fields = ("hwaddr", "addr", "bcast", "mask")
> -    (ifcfg_out, _err) = util.subp(["ifconfig", "-a"], rcs=[0, 1])
> +DEFAULT_NETDEV_INFO = {
> +    "addr": "",
> +    "bcast": "",
> +    "hwaddr": "",
> +    "mask": "",
> +    "scope": "",
> +    "up": False
> +}
> +
> +
> +def netdev_cidr_to_mask(cidr):
> +    mask = socket.inet_ntoa(
> +        struct.pack(">I", (0xffffffff << (32 - int(cidr)) & 0xffffffff)))
> +    return mask
> +
> +
> +def netdev_info_iproute(ipaddr_data, iplink_data):
> +    """
> +    Get network device dicts from ip route and ip link info.
> +
> +    @param ipaddr_data: Output string from ip address command.
> +    @param iplink_data: Output string from ip link command.
> +
> +    @returns: A dict of device info keyed by network device name containing
> +              device configuration values.
> +    """
>      devs = {}

We won't blowup on empty data, we return devs = {} in that case, which feels 
appropriate if no routes are listed in output.

> -    for line in str(ifcfg_out).splitlines():
> +    for line in str(ipaddr_data).splitlines():

Yeah, I should've looked more at some of this code when I took over the branch. 
The util.subp function should take care of this data decoding for us if 
necessary, I'd expect it to be handling in a common layer instead of making all 
call-sites type-check and decode. This str might have been inherited from when 
subp didn't return the right string type.

> +        details = line.lower().strip().split()
> +        curdev = details[1]
> +        fieldpost = ""

Good though, changed to ip_version_suffix and added a comment about its use.

> +        if curdev not in devs:
> +            devs[curdev] = copy(DEFAULT_NETDEV_INFO)
> +        for i in range(len(details)):
> +            if details[i] == 'inet':
> +                (addr, cidr) = details[i + 1].split("/")

I used partition('/') and set the cidr to '24' if not present. We can look at 
consolidating in a separate branch.

> +                devs[curdev]["mask"] = netdev_cidr_to_mask(cidr)

Moved it. good thought. I missed the duplication.

> +                devs[curdev]["addr"] = addr
> +                fieldpost = ""
> +            elif details[i] == 'inet6':
> +                addr = details[i + 1]
> +                devs[curdev]["addr6"] = addr
> +                fieldpost = "6"
> +            elif details[i] == "scope":
> +                devs[curdev]["scope" + fieldpost] = details[i + 1]
> +            elif details[i] == "brd":
> +                devs[curdev]["bcast" + fieldpost] = details[i + 1]

Took both of your comments. I simplified netdev_info_iproute to parse all of 
'ip addr show' in one go. I also tweaked the logic to use regex matches instead 
of walking through individual tokens. It's only about the same performance as 
far timeit is concerned, bit the regex gives us a better view of the expected 
format of the output.

> +
> +    for line in str(iplink_data).splitlines():

dropped the str throughout.

> +        details = line.lower().strip().split()
> +        # Strip trailing ':' and truncate any interface alias '@if34'
> +        curdev = details[1].rstrip(':').split('@')[0]
> +        for i in range(len(details)):
> +            if details[i] == curdev + ":":
> +                flags = details[i + 1].strip("<>").split(",")
> +                if "lower_up" in flags and "up" in flags:
> +                    devs[curdev]["up"] = True
> +            elif details[i] == "link/ether":
> +                devs[curdev]["hwaddr"] = details[i + 1]
> +    return devs
> +
> +
> +def netdev_info_ifconfig(ifconfig_data):
> +    # fields that need to be returned in devs for each dev
> +    devs = {}
> +    for line in str(ifconfig_data).splitlines():

dropped the str throughout.

>          if len(line) == 0:
>              continue
>          if line[0] not in ("\t", " "):
>              curdev = line.split()[0]
> -            devs[curdev] = {"up": False}
> -            for field in fields:
> -                devs[curdev][field] = ""
> +            # current ifconfig pops a ':' on the end of the device
> +            if curdev.endswith(':'):
> +                curdev = curdev[:-1]
> +            if curdev not in devs:
> +                devs[curdev] = copy(DEFAULT_NETDEV_INFO)
>          toks = line.lower().strip().split()
>          if toks[0] == "up":
>              devs[curdev]['up'] = True
> @@ -39,41 +105,50 @@ def netdev_info(empty=""):
>              if re.search(r"flags=\d+<up,", toks[1]):
>                  devs[curdev]['up'] = True
>  
> -        fieldpost = ""
> -        if toks[0] == "inet6":
> -            fieldpost = "6"
> -
>          for i in range(len(toks)):
> -            # older net-tools (ubuntu) show 'inet addr:xx.yy',
> -            # newer (freebsd and fedora) show 'inet xx.yy'
> -            # just skip this 'inet' entry. (LP: #1285185)
> -            try:
> -                if ((toks[i] in ("inet", "inet6") and
> -                     toks[i + 1].startswith("addr:"))):
> -                    continue
> -            except IndexError:
> -                pass
> -
> -            # Couple the different items we're interested in with the correct
> -            # field since FreeBSD/CentOS/Fedora differ in the output.
> -            ifconfigfields = {
> -                "addr:": "addr", "inet": "addr",
> -                "bcast:": "bcast", "broadcast": "bcast",
> -                "mask:": "mask", "netmask": "mask",
> -                "hwaddr": "hwaddr", "ether": "hwaddr",
> -                "scope": "scope",
> -            }
> -            for origfield, field in ifconfigfields.items():
> -                target = "%s%s" % (field, fieldpost)
> -                if devs[curdev].get(target, ""):
> -                    continue
> -                if toks[i] == "%s" % origfield:
> -                    try:
> -                        devs[curdev][target] = toks[i + 1]
> -                    except IndexError:
> -                        pass
> -                elif toks[i].startswith("%s" % origfield):
> -                    devs[curdev][target] = toks[i][len(field) + 1:]
> +            if toks[i] == "inet":
> +                if toks[i + 1].startswith("addr:"):
> +                    devs[curdev]['addr'] = toks[i + 1].lstrip("addr:")
> +                else:
> +                    devs[curdev]['addr'] = toks[i + 1]
> +            elif toks[i].startswith("bcast:"):
> +                devs[curdev]['bcast'] = toks[i].lstrip("bcast:")
> +            elif toks[i] == "broadcast":
> +                devs[curdev]['bcast'] = toks[i + 1]
> +            elif toks[i].startswith("mask:"):
> +                devs[curdev]['mask'] = toks[i].lstrip("mask:")
> +            elif toks[i] == "netmask":
> +                devs[curdev]['mask'] = toks[i + 1]
> +            elif toks[i] == "hwaddr" or toks[i] == "ether":
> +                devs[curdev]['hwaddr'] = toks[i + 1]
> +            elif toks[i] == "inet6":
> +                if toks[i + 1] == "addr:":
> +                    devs[curdev]['addr6'] = toks[i + 2]
> +                else:
> +                    devs[curdev]['addr6'] = toks[i + 1]
> +            elif toks[i] == "prefixlen":
> +                addr6 = devs[curdev]['addr6'] + "/" + toks[i + 1]
> +                devs[curdev]['addr6'] = addr6
> +            elif toks[i].startswith("scope:"):
> +                devs[curdev]['scope6'] = toks[i].lstrip("scope:")
> +            elif toks[i] == "scopeid":
> +                res = re.match(".*<(\S+)>", toks[i + 1])
> +                if res:
> +                    devs[curdev]['scope6'] = res.group(1)
> +    return devs
> +
> +
> +def netdev_info(empty=""):
> +    devs = {}
> +    try:
> +        # Try iproute first of all
> +        (ipaddr_out, _err) = util.subp(["ip", "--oneline", "addr", "list"])
> +        (iplink_out, _err) = util.subp(["ip", "--oneline", "link", "list"])
> +        devs = netdev_info_iproute(ipaddr_out, iplink_out)
> +    except util.ProcessExecutionError:
> +        # Fall back to net-tools if iproute2 is not present
> +        (ifcfg_out, _err) = util.subp(["ifconfig", "-a"], rcs=[0, 1])
> +        devs = netdev_info_ifconfig(ifcfg_out)

+1 great point. fixed.

>  
>      if empty != "":
>          for (_devname, dev) in devs.items():
> @@ -125,31 +279,53 @@ def route_info():
>          routes['ipv4'].append(entry)
>  
>      try:
> -        (route_out6, _err6) = util.subp(["netstat", "-A", "inet6", "-n"],
> -                                        rcs=[0, 1])
> +        (route_data6, _err6) = util.subp(
> +            ["netstat", "-A", "inet6", "--route", "--numeric"], rcs=[0, 1])
>      except util.ProcessExecutionError:
>          pass
>      else:
> -        entries6 = route_out6.splitlines()[1:]
> +        entries6 = route_data6.splitlines()
>          for line in entries6:
>              if not line:
>                  continue
>              toks = line.split()
> -            if (len(toks) < 6 or toks[0] == "Kernel" or
> +            if (len(toks) < 7 or toks[0] == "Kernel" or
> +                    toks[0] == "Destination" or toks[0] == "Internet" or
>                      toks[0] == "Proto" or toks[0] == "Active"):
>                  continue
>              entry = {
> -                'proto': toks[0],
> -                'recv-q': toks[1],
> -                'send-q': toks[2],
> -                'local address': toks[3],
> -                'foreign address': toks[4],
> -                'state': toks[5],
> +                'destination': toks[0],
> +                'gateway': toks[1],
> +                'flags': toks[2],
> +                'metric': toks[3],
> +                'ref': toks[4],
> +                'use': toks[5],
> +                'iface': toks[6],
>              }
> +            # skip lo interface on ipv6
> +            if entry['iface'] == "lo":
> +                continue
> +            # strip /128 from address if it's included
> +            if entry['destination'].endswith('/128'):
> +                entry['destination'] = entry['destination'][:-4]

Good suggestion, though I went with re.sub(r'\/128$', '', ...) here just to be 
explicit.

>              routes['ipv6'].append(entry)
>      return routes
>  
>  
> +def route_info():
> +    routes = {}
> +    try:
> +        # Try iproute first of all
> +        (iproute_out, _err) = util.subp(["ip", "-o", "route", "list"])
> +        routes = netdev_route_info_iproute(iproute_out)
> +    except util.ProcessExecutionError:
> +        # Fall back to net-tools if iproute2 is not present
> +        (route_out, _err) = util.subp(
> +            ["netstat", "--route", "--numeric", "--extend"], rcs=[0, 1])
> +        routes = netdev_route_info_netstat(route_out)
> +    return routes
> +
> +
>  def getgateway():
>      try:
>          routes = route_info()


-- 
https://code.launchpad.net/~chad.smith/cloud-init/+git/cloud-init/+merge/342428
Your team cloud-init commiters is requested to review the proposed merge of 
~chad.smith/cloud-init:net-tools-deprecation-plus-review-comments into 
cloud-init:master.

_______________________________________________
Mailing list: https://launchpad.net/~cloud-init-dev
Post to     : cloud-init-dev@lists.launchpad.net
Unsubscribe : https://launchpad.net/~cloud-init-dev
More help   : https://help.launchpad.net/ListHelp

Reply via email to