Merge ~james-hogarth/cloud-init:net-tools-deprecation into cloud-init:master

Proposed by Scott Moser on 2017-11-14
Status: Needs review
Proposed branch: ~james-hogarth/cloud-init:net-tools-deprecation
Merge into: cloud-init:master
Diff against target: 632 lines (+378/-124)
2 files modified
cloudinit/netinfo.py (+215/-64)
cloudinit/tests/test_netinfo.py (+163/-60)
Reviewer Review Type Date Requested Status
Chad Smith 2017-11-14 Needs Fixing on 2017-12-19
Server Team CI bot continuous-integration Needs Fixing on 2017-11-14
Review via email: mp+333657@code.launchpad.net
To post a comment you must log in.

FAILED: Continuous integration, rev:13f71fbb9b090fc90e295e84eed98719aa2252c8
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~james-hogarth/cloud-init/+git/cloud-init/+merge/333657/+edit-commit-message

https://jenkins.ubuntu.com/server/job/cloud-init-ci/489/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    FAILED: Ubuntu LTS: Integration

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/489/rebuild

review: Needs Fixing (continuous-integration)
ca9c666... by James Hogarth on 2017-11-14

ifconfig changed it's output, check both versions for compatibility reasons

Ryan Harper (raharper) wrote :

There's quite a bit of work to make the tables look the same.
Is that required or can we just dump a different format?

Please make sure the subp command used long options:

netstat --route --numeric --extended
ip --oneline ..

etc.

James Hogarth (james-hogarth) wrote :
Download full text (7.4 KiB)

I assumed we want to have the same output regardless of the tool... at
least initially.

It's the best way of ensuring both code paths are accurate at this time -
especially since new ifconfig syntax and route information is basically
completely broken.

The existing netstat and ifconfig used short options which is why I
continued that practice... switching to long is no big deal.

On 14 Nov 2017 20:08, "Ryan Harper" <email address hidden> wrote:

> There's quite a bit of work to make the tables look the same.
> Is that required or can we just dump a different format?
>
> Please make sure the subp command used long options:
>
> netstat --route --numeric --extended
> ip --oneline ..
>
> etc.
>
>
> Diff comments:
>
> > diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py
> > index 993b26c..0b76301 100644
> > --- a/cloudinit/netinfo.py
> > +++ b/cloudinit/netinfo.py
> > @@ -74,6 +127,20 @@ def netdev_info(empty=""):
> > pass
> > elif toks[i].startswith("%s" % origfield):
> > devs[curdev][target] = toks[i][len(field) + 1:]
> > + return devs
> > +
> > +
> > +def netdev_info(empty=""):
> > + devs = {}
> > + try:
> > + # Try iproute first of all
>
> iproute2
>
> > + (ipaddr_out, _err) = util.subp(["ip", "-o", "addr", "list"])
> > + (iplink_out, _err) = util.subp(["ip", "-o", "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)
> >
> > if empty != "":
> > for (_devname, dev) in devs.items():
> > @@ -84,14 +151,73 @@ def netdev_info(empty=""):
> > return devs
> >
> >
> > -def route_info():
> > - (route_out, _err) = util.subp(["netstat", "-rn"], rcs=[0, 1])
> > +def netdev_route_info_iproute(iproute_data):
> > + routes = {}
> > + routes['ipv4'] = []
> > + routes['ipv6'] = []
> > + entries = iproute_data.splitlines()
> > + for line in entries:
> > + entry = {}
> > + if not line:
> > + continue
> > + toks = line.split()
> > + if toks[0] == "default":
> > + entry['destination'] = "0.0.0.0"
> > + entry['genmask'] = "0.0.0.0"
> > + entry['flags'] = "UG"
> > + else:
> > + (addr, cidr) = toks[0].split("/")
> > + entry['destination'] = addr
> > + entry['genmask'] = netdev_cidr_to_mask(cidr)
> > + entry['gateway'] = "0.0.0.0"
> > + entry['flags'] = "U"
> > + for i in range(len(toks)):
> > + if toks[i] == "via":
> > + entry['gateway'] = toks[i + 1]
> > + if toks[i] == "dev":
> > + entry["iface"] = toks[i + 1]
> > + if toks[i] == "metric":
> > + entry['metric'] = toks[i + 1]
> > + routes['ipv4'].append(entry)
> > + try:
>
> it does seem odd to subp this here rather then above where you subp other
> ip commands
>
> > + (iproute_data6, _e...

Read more...

James Hogarth (james-hogarth) wrote :

I did say that this wasn't ready for merge yet ;)

Once we have suitable test coverage in this area and actually have valid output, then I figure the time for tidiness will be valid (long opts rather than short, etc) for a final cleanup.

There will certainly be a lot of refactoring possible that I can already see which will cut the complexity ... but that has to come over tests are valid and passing.

I can already see a bunch of cleanup incoming in the process to refactor to handle multiple addresses.

Ryan Harper (raharper) wrote :
Download full text (8.3 KiB)

> I assumed we want to have the same output regardless of the tool... at
> least initially.

Right; I'm just asking the question (to other cloud-init devs as well as you)
if it's worth keeping the same format?

While I understand there may be tools that scrape the cloud-init log for
those values; I'd prefer to save execution time by dumping the iproute2
output values sort of as-is.

>
> It's the best way of ensuring both code paths are accurate at this time -
> especially since new ifconfig syntax and route information is basically
> completely broken.

For sure; and I think the unittests comparing both make sense.

That said, I do think we could save quite a bit of time and code
by dropping it.

>
> The existing netstat and ifconfig used short options which is why I
> continued that practice... switching to long is no big deal.

Yeah, makes sense; Since we're touching it, it's worth updating for
readers.

>
> On 14 Nov 2017 20:08, "Ryan Harper" <email address hidden> wrote:
>
> > There's quite a bit of work to make the tables look the same.
> > Is that required or can we just dump a different format?
> >
> > Please make sure the subp command used long options:
> >
> > netstat --route --numeric --extended
> > ip --oneline ..
> >
> > etc.
> >
> >
> > Diff comments:
> >
> > > diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py
> > > index 993b26c..0b76301 100644
> > > --- a/cloudinit/netinfo.py
> > > +++ b/cloudinit/netinfo.py
> > > @@ -74,6 +127,20 @@ def netdev_info(empty=""):
> > > pass
> > > elif toks[i].startswith("%s" % origfield):
> > > devs[curdev][target] = toks[i][len(field) + 1:]
> > > + return devs
> > > +
> > > +
> > > +def netdev_info(empty=""):
> > > + devs = {}
> > > + try:
> > > + # Try iproute first of all
> >
> > iproute2
> >
> > > + (ipaddr_out, _err) = util.subp(["ip", "-o", "addr", "list"])
> > > + (iplink_out, _err) = util.subp(["ip", "-o", "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)
> > >
> > > if empty != "":
> > > for (_devname, dev) in devs.items():
> > > @@ -84,14 +151,73 @@ def netdev_info(empty=""):
> > > return devs
> > >
> > >
> > > -def route_info():
> > > - (route_out, _err) = util.subp(["netstat", "-rn"], rcs=[0, 1])
> > > +def netdev_route_info_iproute(iproute_data):
> > > + routes = {}
> > > + routes['ipv4'] = []
> > > + routes['ipv6'] = []
> > > + entries = iproute_data.splitlines()
> > > + for line in entries:
> > > + entry = {}
> > > + if not line:
> > > + continue
> > > + toks = line.split()
> > > + if toks[0] == "default":
> > > + entry['destination'] = "0.0.0.0"
> > > + entry['genmask'] = "0.0.0.0"
> > > + entry['flags'] = "UG"
> > > + else:
> > > + (addr, cidr) = toks[0].split("/")
> > > + ...

Read more...

James Hogarth (james-hogarth) wrote :

Is there anything else you're aware of that may call netdev_info() or route_info() directly?

If so we still need some gymnastics to ensure that the object returned is as expected, even if pformat isn't being called (which frankly is the simple bit).

A grep didn't find anything in the codebase, but I'm not familiar with cloud-init to know if it might be considered an interface or not... after all getgateway() isn't found via grep either.

You might want to wait for my refactor that handles new ifconfig output cleanly (nearly done with that) as it does clean up a lot of the ... uh ... awkward logic in parsing the ifconfig output.

f87375c... by James Hogarth on 2017-11-14

handle new and old net-tools ifconfig output

this simplifies the parsing of the ifconfig output and
fixes issues with gathering the ipv6 information when
relying on the net-tools ifconfig command

FAILED: Continuous integration, rev:f87375c2ea20c45921cf621b6da57539ced75826
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~james-hogarth/cloud-init/+git/cloud-init/+merge/333657/+edit-commit-message

https://jenkins.ubuntu.com/server/job/cloud-init-ci/493/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    FAILED: Ubuntu LTS: Integration

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/493/rebuild

review: Needs Fixing (continuous-integration)
Chad Smith (chad.smith) wrote :

Thanks for this branch James, I've got a first round of comments here and have a minor fixup needed for running in an lxc that I'll post tomorrrow when I get to the bottom of what's happening.

but the following breaks on lxcs. The way routes are listed breaks your existing logic a bit:

it seems to fallover parsing the second line (probably because the route has an alias name @if64 in it and doesn't match the original key.

It might be as simple as truncating any aliases in the interface name after @.....

 ip -o link list
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
63: eth0@if64: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000\ link/ether 00:16:3e:ee:c3:6b brd ff:ff:ff:ff:ff:ff link-netnsid 0

make deb
lxc launch ubuntu-daily:xenial x1
lxc file push cloud-init_17.2.*deb x1/cloud-init.deb
lxc exec x1 dpkg -i /cloud-init.deb

Chad Smith (chad.smith) :
review: Needs Information
Chad Smith (chad.smith) wrote :

Here's the collection of review comments plus fix for lxc to truncate the ethX@interface# aliases
http://paste.ubuntu.com/26215869/

review: Needs Fixing
Chad Smith (chad.smith) wrote :

Just saw it also lacks handling of host route targets routes and host gateway routes

On Azure cloud:
# old route format
default via 10.0.0.1 dev eth0 proto dhcp src 10.0.0.4 metric 100
default via 10.0.0.1 dev eth0 proto dhcp metric 100
10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.4
10.0.0.1 dev eth0 proto dhcp scope link src 10.0.0.4 metric 100
168.63.129.16 via 10.0.0.1 dev eth0 proto dhcp metric 100
169.254.169.254 via 10.0.0.1 dev eth0 proto dhcp metric 100

# ip -o route list
default via 10.0.0.1 dev eth0 proto dhcp src 10.0.0.4 metric 100
default via 10.0.0.1 dev eth0 proto dhcp metric 100
10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.4
10.0.0.1 dev eth0 proto dhcp scope link src 10.0.0.4 metric 100
168.63.129.16 via 10.0.0.1 dev eth0 proto dhcp metric 100
169.254.169.254 via 10.0.0.1 dev eth0 proto dhcp metric 100

The last 3 host routes cause tracebacksTraceback (most recent call last):
  File "/usr/lib/python3/dist-packages/cloudinit/netinfo.py", line 339, in route_pformat
    routes = route_info()
  File "/usr/lib/python3/dist-packages/cloudinit/netinfo.py", line 295, in route_info
    routes = netdev_route_info_iproute(iproute_out)
  File "/usr/lib/python3/dist-packages/cloudinit/netinfo.py", line 172, in netdev_route_info_iproute
    (addr, cidr) = toks[0].split("/")

James Hogarth (james-hogarth) wrote :

Yes as I mentioned it was a WIP not ready for merging, but given it's been
pending years and I'm on a drive to remove the net-tools dependency on
fedora I've picked it up.

If you can provide the ip route and ip addr output from your LXC container
that would be useful to add to the tests as I don't have that to hand.

Been busy with actual job and family past few weeks. Hopefully in the quiet
of Christmas I'll get this finished off.

On 20 Dec 2017 12:19, "Chad Smith" <email address hidden> wrote:

Just saw it also lacks handling of host route targets routes and host
gateway routes

On Azure cloud:
# old route format
default via 10.0.0.1 dev eth0 proto dhcp src 10.0.0.4 metric 100
default via 10.0.0.1 dev eth0 proto dhcp metric 100
10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.4
10.0.0.1 dev eth0 proto dhcp scope link src 10.0.0.4 metric 100
168.63.129.16 via 10.0.0.1 dev eth0 proto dhcp metric 100
169.254.169.254 via 10.0.0.1 dev eth0 proto dhcp metric 100

# ip -o route list
default via 10.0.0.1 dev eth0 proto dhcp src 10.0.0.4 metric 100
default via 10.0.0.1 dev eth0 proto dhcp metric 100
10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.4
10.0.0.1 dev eth0 proto dhcp scope link src 10.0.0.4 metric 100
168.63.129.16 via 10.0.0.1 dev eth0 proto dhcp metric 100
169.254.169.254 via 10.0.0.1 dev eth0 proto dhcp metric 100

The last 3 host routes cause tracebacksTraceback (most recent call last):
  File "/usr/lib/python3/dist-packages/cloudinit/netinfo.py", line 339, in
route_pformat
    routes = route_info()
  File "/usr/lib/python3/dist-packages/cloudinit/netinfo.py", line 295, in
route_info
    routes = netdev_route_info_iproute(iproute_out)
  File "/usr/lib/python3/dist-packages/cloudinit/netinfo.py", line 172, in
netdev_route_info_iproute
    (addr, cidr) = toks[0].split("/")

--
https://code.launchpad.net/~james-hogarth/cloud-init/+git/
cloud-init/+merge/333657
You are the owner of ~james-hogarth/cloud-init:net-tools-deprecation.

Chad Smith (chad.smith) wrote :

Expected cloud init ouput would have been
ci-info: ++++++++++++++++++++++++++++++Route IPv4 info+++++++++++++++++++++++++++++++
ci-info: +-------+-----------------+----------+-----------------+-----------+-------+
ci-info: | Route | Destination | Gateway | Genmask | Interface | Flags |
ci-info: +-------+-----------------+----------+-----------------+-----------+-------+
ci-info: | 0 | 0.0.0.0 | 10.0.0.1 | 0.0.0.0 | eth0 | UG |
ci-info: | 1 | 0.0.0.0 | 10.0.0.1 | 0.0.0.0 | eth0 | UG |
ci-info: | 2 | 10.0.0.0 | 0.0.0.0 | 255.255.255.0 | eth0 | U |
ci-info: | 3 | 10.0.0.1 | 0.0.0.0 | 255.255.255.255 | eth0 | UH |
ci-info: | 4 | 168.63.129.16 | 10.0.0.1 | 255.255.255.255 | eth0 | UGH |
ci-info: | 5 | 169.254.169.254 | 10.0.0.1 | 255.255.255.255 | eth0 | UGH |
ci-info: +-------+-----------------+----------+-----------------+-----------+-------+

Instead we got no route ci-info:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!Route info failed!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
info.

here is the additional tweak needed to handlehost target routes
http://paste.ubuntu.com/26221017/

Chad Smith (chad.smith) wrote :

Unmerged commits

f87375c... by James Hogarth on 2017-11-14

handle new and old net-tools ifconfig output

this simplifies the parsing of the ifconfig output and
fixes issues with gathering the ipv6 information when
relying on the net-tools ifconfig command

ca9c666... by James Hogarth on 2017-11-14

ifconfig changed it's output, check both versions for compatibility reasons

13f71fb... by James Hogarth on 2017-11-14

add the iproute based route information gathering

f5c9f20... by James Hogarth on 2017-11-14

bring the iproute mock data inline with the nettools mock data

6412ac5... by James Hogarth on 2017-11-13

fix flake8 failures

2dcfbde... by James Hogarth on 2017-11-13

fix nettools route information gathering

27df90a... by James Hogarth on 2017-11-13

provide correct route output for v6 mock data

ee51394... by James Hogarth on 2017-11-13

make the mock data more readable to ease testing

07be9d4... by James Hogarth on 2017-11-13

Use side_effect to handle netstat versus ip for route determination

db48caf... by James Hogarth on 2017-11-08

handle flake8 and pylint testing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py
2index 993b26c..3743b91 100644
3--- a/cloudinit/netinfo.py
4+++ b/cloudinit/netinfo.py
5@@ -9,6 +9,8 @@
6 # This file is part of cloud-init. See LICENSE file for license information.
7
8 import re
9+import socket
10+import struct
11
12 from cloudinit import log as logging
13 from cloudinit import util
14@@ -18,18 +20,72 @@ from cloudinit.simpletable import SimpleTable
15 LOG = logging.getLogger()
16
17
18-def netdev_info(empty=""):
19- fields = ("hwaddr", "addr", "bcast", "mask")
20- (ifcfg_out, _err) = util.subp(["ifconfig", "-a"], rcs=[0, 1])
21+def netdev_cidr_to_mask(cidr):
22+ mask = socket.inet_ntoa(
23+ struct.pack(">I", (0xffffffff << (32 - int(cidr)) & 0xffffffff)))
24+ return mask
25+
26+
27+def netdev_info_iproute(ipaddr_data, iplink_data):
28+ # fields that need to be returned in devs for each dev
29+ fields = ("hwaddr", "up", "addr", "bcast", "mask", "scope")
30+ devs = {}
31+ for line in str(ipaddr_data).splitlines():
32+ details = line.lower().strip().split()
33+ curdev = details[1]
34+ fieldpost = ""
35+ if curdev not in devs:
36+ devs[curdev] = {"up": False}
37+ if details[2] == "inet6":
38+ fieldpost = "6"
39+ for i in range(len(details)):
40+ if details[i] == "inet" or details[i] == "inet6":
41+ addr = ""
42+ if fieldpost == "":
43+ (addr, cidr) = details[i + 1].split("/")
44+ devs[curdev]["mask"] = netdev_cidr_to_mask(cidr)
45+ else:
46+ addr = details[i + 1]
47+ devs[curdev]["addr" + fieldpost] = addr
48+ if details[i] == "scope":
49+ devs[curdev]["scope" + fieldpost] = details[i + 1]
50+ if details[i] == "brd":
51+ devs[curdev]["bcast" + fieldpost] = details[i + 1]
52+
53+ for line in str(iplink_data).splitlines():
54+ details = line.lower().strip().split()
55+ curdev = details[1][:-1]
56+ for i in range(len(details)):
57+ if details[i] == curdev + ":":
58+ flags = details[i + 1].strip("<>").split(",")
59+ if "lower_up" in flags and "up" in flags:
60+ devs[curdev]["up"] = True
61+ if details[i] == "link/ether":
62+ devs[curdev]["hwaddr"] = details[i + 1]
63+
64+ # Run through all devs and ensure all fields are defined
65+ for dev in devs:
66+ for field in fields:
67+ if field not in devs[dev]:
68+ devs[dev][field] = ""
69+
70+ return devs
71+
72+
73+def netdev_info_ifconfig(ifconfig_data):
74+ # fields that need to be returned in devs for each dev
75+ fields = ("hwaddr", "up", "addr", "bcast", "mask", "scope")
76 devs = {}
77- for line in str(ifcfg_out).splitlines():
78+ for line in str(ifconfig_data).splitlines():
79 if len(line) == 0:
80 continue
81 if line[0] not in ("\t", " "):
82 curdev = line.split()[0]
83- devs[curdev] = {"up": False}
84- for field in fields:
85- devs[curdev][field] = ""
86+ # current ifconfig pops a ':' on the end of the device
87+ if curdev.endswith(':'):
88+ curdev = curdev[:-1]
89+ if curdev not in devs:
90+ devs[curdev] = {"up": False}
91 toks = line.lower().strip().split()
92 if toks[0] == "up":
93 devs[curdev]['up'] = True
94@@ -39,41 +95,57 @@ def netdev_info(empty=""):
95 if re.search(r"flags=\d+<up,", toks[1]):
96 devs[curdev]['up'] = True
97
98- fieldpost = ""
99- if toks[0] == "inet6":
100- fieldpost = "6"
101-
102 for i in range(len(toks)):
103- # older net-tools (ubuntu) show 'inet addr:xx.yy',
104- # newer (freebsd and fedora) show 'inet xx.yy'
105- # just skip this 'inet' entry. (LP: #1285185)
106- try:
107- if ((toks[i] in ("inet", "inet6") and
108- toks[i + 1].startswith("addr:"))):
109- continue
110- except IndexError:
111- pass
112-
113- # Couple the different items we're interested in with the correct
114- # field since FreeBSD/CentOS/Fedora differ in the output.
115- ifconfigfields = {
116- "addr:": "addr", "inet": "addr",
117- "bcast:": "bcast", "broadcast": "bcast",
118- "mask:": "mask", "netmask": "mask",
119- "hwaddr": "hwaddr", "ether": "hwaddr",
120- "scope": "scope",
121- }
122- for origfield, field in ifconfigfields.items():
123- target = "%s%s" % (field, fieldpost)
124- if devs[curdev].get(target, ""):
125- continue
126- if toks[i] == "%s" % origfield:
127- try:
128- devs[curdev][target] = toks[i + 1]
129- except IndexError:
130- pass
131- elif toks[i].startswith("%s" % origfield):
132- devs[curdev][target] = toks[i][len(field) + 1:]
133+ if toks[i] == "inet":
134+ if toks[i + 1].startswith("addr:"):
135+ devs[curdev]['addr'] = toks[i + 1].lstrip("addr:")
136+ else:
137+ devs[curdev]['addr'] = toks[i + 1]
138+ elif toks[i].startswith("bcast:"):
139+ devs[curdev]['bcast'] = toks[i].lstrip("bcast:")
140+ elif toks[i] == "broadcast":
141+ devs[curdev]['bcast'] = toks[i + 1]
142+ elif toks[i].startswith("mask:"):
143+ devs[curdev]['mask'] = toks[i].lstrip("mask:")
144+ elif toks[i] == "netmask":
145+ devs[curdev]['mask'] = toks[i + 1]
146+ elif toks[i] == "hwaddr" or toks[i] == "ether":
147+ devs[curdev]['hwaddr'] = toks[i + 1]
148+ elif toks[i] == "inet6":
149+ if toks[i + 1] == "addr:":
150+ devs[curdev]['addr6'] = toks[i + 2]
151+ else:
152+ devs[curdev]['addr6'] = toks[i + 1]
153+ elif toks[i] == "prefixlen":
154+ addr6 = devs[curdev]['addr6'] + "/" + toks[i + 1]
155+ devs[curdev]['addr6'] = addr6
156+ elif toks[i].startswith("scope:"):
157+ devs[curdev]['scope6'] = toks[i].lstrip("scope:")
158+ elif toks[i] == "scopeid":
159+ res = re.match(".*<(\S+)>", toks[i + 1])
160+ if res:
161+ devs[curdev]['scope6'] = res.group(1)
162+
163+ # Run through all devs and ensure all fields are defined
164+ for dev in devs:
165+ for field in fields:
166+ if field not in devs[dev]:
167+ devs[dev][field] = ""
168+
169+ return devs
170+
171+
172+def netdev_info(empty=""):
173+ devs = {}
174+ try:
175+ # Try iproute first of all
176+ (ipaddr_out, _err) = util.subp(["ip", "-o", "addr", "list"])
177+ (iplink_out, _err) = util.subp(["ip", "-o", "link", "list"])
178+ devs = netdev_info_iproute(ipaddr_out, iplink_out)
179+ except util.ProcessExecutionError:
180+ # Fall back to net-tools if iproute2 is not present
181+ (ifcfg_out, _err) = util.subp(["ifconfig", "-a"], rcs=[0, 1])
182+ devs = netdev_info_ifconfig(ifcfg_out)
183
184 if empty != "":
185 for (_devname, dev) in devs.items():
186@@ -84,14 +156,73 @@ def netdev_info(empty=""):
187 return devs
188
189
190-def route_info():
191- (route_out, _err) = util.subp(["netstat", "-rn"], rcs=[0, 1])
192+def netdev_route_info_iproute(iproute_data):
193+ routes = {}
194+ routes['ipv4'] = []
195+ routes['ipv6'] = []
196+ entries = iproute_data.splitlines()
197+ for line in entries:
198+ entry = {}
199+ if not line:
200+ continue
201+ toks = line.split()
202+ if toks[0] == "default":
203+ entry['destination'] = "0.0.0.0"
204+ entry['genmask'] = "0.0.0.0"
205+ entry['flags'] = "UG"
206+ else:
207+ (addr, cidr) = toks[0].split("/")
208+ entry['destination'] = addr
209+ entry['genmask'] = netdev_cidr_to_mask(cidr)
210+ entry['gateway'] = "0.0.0.0"
211+ entry['flags'] = "U"
212+ for i in range(len(toks)):
213+ if toks[i] == "via":
214+ entry['gateway'] = toks[i + 1]
215+ if toks[i] == "dev":
216+ entry["iface"] = toks[i + 1]
217+ if toks[i] == "metric":
218+ entry['metric'] = toks[i + 1]
219+ routes['ipv4'].append(entry)
220+ try:
221+ (iproute_data6, _err6) = util.subp(["ip", "-o", "-6", "route", "list"],
222+ rcs=[0, 1])
223+ except util.ProcessExecutionError:
224+ pass
225+ else:
226+ entries6 = iproute_data6.splitlines()
227+ for line in entries6:
228+ entry = {}
229+ if not line:
230+ continue
231+ toks = line.split()
232+ if toks[0] == "default":
233+ entry['destination'] = "::/0"
234+ entry['flags'] = "UG"
235+ else:
236+ entry['destination'] = toks[0]
237+ entry['gateway'] = "::"
238+ entry['flags'] = "U"
239+ for i in range(len(toks)):
240+ if toks[i] == "via":
241+ entry['gateway'] = toks[i + 1]
242+ entry['flags'] = "UG"
243+ if toks[i] == "dev":
244+ entry["iface"] = toks[i + 1]
245+ if toks[i] == "metric":
246+ entry['metric'] = toks[i + 1]
247+ if toks[i] == "expires":
248+ entry['flags'] = entry['flags'] + 'e'
249+ routes['ipv6'].append(entry)
250+ return routes
251+
252
253+def netdev_route_info_netstat(route_data):
254 routes = {}
255 routes['ipv4'] = []
256 routes['ipv6'] = []
257
258- entries = route_out.splitlines()[1:]
259+ entries = route_data.splitlines()
260 for line in entries:
261 if not line:
262 continue
263@@ -101,8 +232,8 @@ def route_info():
264 # default 10.65.0.1 UGS 0 34920 vtnet0
265 #
266 # Linux netstat shows 2 more:
267- # Destination Gateway Genmask Flags MSS Window irtt Iface
268- # 0.0.0.0 10.65.0.1 0.0.0.0 UG 0 0 0 eth0
269+ # Destination Gateway Genmask Flags Metric Ref Use Iface
270+ # 0.0.0.0 10.65.0.1 0.0.0.0 UG 0 0 0 eth0
271 if (len(toks) < 6 or toks[0] == "Kernel" or
272 toks[0] == "Destination" or toks[0] == "Internet" or
273 toks[0] == "Internet6" or toks[0] == "Routing"):
274@@ -125,31 +256,52 @@ def route_info():
275 routes['ipv4'].append(entry)
276
277 try:
278- (route_out6, _err6) = util.subp(["netstat", "-A", "inet6", "-n"],
279- rcs=[0, 1])
280+ (route_data6, _err6) = util.subp(["netstat", "-A", "inet6", "-rn"],
281+ rcs=[0, 1])
282 except util.ProcessExecutionError:
283 pass
284 else:
285- entries6 = route_out6.splitlines()[1:]
286+ entries6 = route_data6.splitlines()
287 for line in entries6:
288 if not line:
289 continue
290 toks = line.split()
291- if (len(toks) < 6 or toks[0] == "Kernel" or
292+ if (len(toks) < 7 or toks[0] == "Kernel" or
293+ toks[0] == "Destination" or toks[0] == "Internet" or
294 toks[0] == "Proto" or toks[0] == "Active"):
295 continue
296 entry = {
297- 'proto': toks[0],
298- 'recv-q': toks[1],
299- 'send-q': toks[2],
300- 'local address': toks[3],
301- 'foreign address': toks[4],
302- 'state': toks[5],
303+ 'destination': toks[0],
304+ 'gateway': toks[1],
305+ 'flags': toks[2],
306+ 'metric': toks[3],
307+ 'ref': toks[4],
308+ 'use': toks[5],
309+ 'iface': toks[6],
310 }
311+ # skip lo interface on ipv6
312+ if entry['iface'] == "lo":
313+ continue
314+ # strip /128 from address if it's included
315+ if entry['destination'].endswith('/128'):
316+ entry['destination'] = entry['destination'][:-4]
317 routes['ipv6'].append(entry)
318 return routes
319
320
321+def route_info():
322+ routes = {}
323+ try:
324+ # Try iproute first of all
325+ (iproute_out, _err) = util.subp(["ip", "-o", "route", "list"])
326+ routes = netdev_route_info_iproute(iproute_out)
327+ except util.ProcessExecutionError:
328+ # Fall back to net-tools if iproute2 is not present
329+ (route_out, _err) = util.subp(["netstat", "-rne"], rcs=[0, 1])
330+ routes = netdev_route_info_netstat(route_out)
331+ return routes
332+
333+
334 def getgateway():
335 try:
336 routes = route_info()
337@@ -193,27 +345,26 @@ def route_pformat():
338 else:
339 if routes.get('ipv4'):
340 fields_v4 = ['Route', 'Destination', 'Gateway',
341- 'Genmask', 'Interface', 'Flags']
342+ 'Genmask', 'Interface', 'Flags', 'Metric']
343 tbl_v4 = SimpleTable(fields_v4)
344 for (n, r) in enumerate(routes.get('ipv4')):
345 route_id = str(n)
346 tbl_v4.add_row([route_id, r['destination'],
347 r['gateway'], r['genmask'],
348- r['iface'], r['flags']])
349+ r['iface'], r['flags'], r['metric']])
350 route_s = tbl_v4.get_string()
351 max_len = len(max(route_s.splitlines(), key=len))
352 header = util.center("Route IPv4 info", "+", max_len)
353 lines.extend([header, route_s])
354 if routes.get('ipv6'):
355- fields_v6 = ['Route', 'Proto', 'Recv-Q', 'Send-Q',
356- 'Local Address', 'Foreign Address', 'State']
357+ fields_v6 = ['Route', 'Destination', 'Gateway', 'Interface',
358+ 'Flags', 'Metric']
359 tbl_v6 = SimpleTable(fields_v6)
360 for (n, r) in enumerate(routes.get('ipv6')):
361 route_id = str(n)
362- tbl_v6.add_row([route_id, r['proto'],
363- r['recv-q'], r['send-q'],
364- r['local address'], r['foreign address'],
365- r['state']])
366+ tbl_v6.add_row([route_id, r['destination'],
367+ r['gateway'], r['iface'],
368+ r['flags'], r['metric']])
369 route_s = tbl_v6.get_string()
370 max_len = len(max(route_s.splitlines(), key=len))
371 header = util.center("Route IPv6 info", "+", max_len)
372diff --git a/cloudinit/tests/test_netinfo.py b/cloudinit/tests/test_netinfo.py
373index 7dea2e4..fe48acf 100644
374--- a/cloudinit/tests/test_netinfo.py
375+++ b/cloudinit/tests/test_netinfo.py
376@@ -2,12 +2,13 @@
377
378 """Tests netinfo module functions and classes."""
379
380+from cloudinit import util
381 from cloudinit.netinfo import netdev_pformat, route_pformat
382 from cloudinit.tests.helpers import CiTestCase, mock
383
384
385 # Example ifconfig and route output
386-SAMPLE_IFCONFIG_OUT = """\
387+SAMPLE_OLD_IFCONFIG_OUT = """\
388 enp0s25 Link encap:Ethernet HWaddr 50:7b:9d:2c:af:91
389 inet addr:192.168.2.18 Bcast:192.168.2.255 Mask:255.255.255.0
390 inet6 addr: fe80::8107:2b92:867e:f8a6/64 Scope:Link
391@@ -27,80 +28,182 @@ lo Link encap:Local Loopback
392 collisions:0 txqueuelen:1
393 """
394
395-SAMPLE_ROUTE_OUT = '\n'.join([
396- '0.0.0.0 192.168.2.1 0.0.0.0 UG 0 0 0'
397- ' enp0s25',
398- '0.0.0.0 192.168.2.1 0.0.0.0 UG 0 0 0'
399- ' wlp3s0',
400- '192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0'
401- ' enp0s25'])
402+SAMPLE_NEW_IFCONFIG_OUT = """\
403+enp0s25: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
404+ inet 192.168.2.18 netmask 255.255.255.0 broadcast 192.168.2.255
405+ inet6 fe80::8107:2b92:867e:f8a6 prefixlen 64 scopeid 0x20<link>
406+ ether 50:7b:9d:2c:af:91 txqueuelen 1000 (Ethernet)
407+ RX packets 3017 bytes 10601563 (10.1 MiB)
408+ RX errors 0 dropped 39 overruns 0 frame 0
409+ TX packets 2627 bytes 196976 (192.3 KiB)
410+ TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
411
412+lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
413+ inet 127.0.0.1 netmask 255.0.0.0
414+ inet6 ::1 prefixlen 128 scopeid 0x10<host>
415+ loop txqueuelen 1 (Local Loopback)
416+ RX packets 0 bytes 0 (0.0 B)
417+ RX errors 0 dropped 0 overruns 0 frame 0
418+ TX packets 0 bytes 0 (0.0 B)
419+ TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
420+"""
421+
422+# Intentionally disabling line length check on mock data for clarity
423+SAMPLE_IPADDR_OUT = '\n'.join([
424+ '1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever', # noqa: E501
425+ '1: lo inet6 ::1/128 scope host \ valid_lft forever preferred_lft forever', # noqa: E501
426+ '2: enp0s25 inet 192.168.2.18/24 brd 192.168.2.255 scope global dynamic enp0s25\ valid_lft 84174sec preferred_lft 84174sec', # noqa: E501
427+ '2: enp0s25 inet6 fe80::8107:2b92:867e:f8a6/64 scope link \ valid_lft forever preferred_lft forever']) # noqa: E501
428+
429+SAMPLE_IPLINK_OUT = '\n'.join([
430+ '1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00', # noqa: E501
431+ '2: enp0s25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000\ link/ether 50:7b:9d:2c:af:91 brd ff:ff:ff:ff:ff:ff']) # noqa: E501
432+
433+SAMPLE_ROUTE_OUT_V4 = '\n'.join([
434+ 'Kernel IP routing table', # noqa: E501
435+ 'Destination Gateway Genmask Flags Metric Ref Use Iface', # noqa: E501
436+ '0.0.0.0 192.168.2.1 0.0.0.0 UG 100 0 0 enp0s25', # noqa: E501
437+ '0.0.0.0 192.168.2.1 0.0.0.0 UG 150 0 0 wlp3s0', # noqa: E501
438+ '192.168.2.0 0.0.0.0 255.255.255.0 U 100 0 0 enp0s25']) # noqa: E501
439+
440+SAMPLE_ROUTE_OUT_V6 = '\n'.join([
441+ 'Kernel IPv6 routing table', # noqa: E501
442+ 'Destination Next Hop Flag Met Re Use If', # noqa: E501
443+ '2a00:abcd:82ae:cd33::657/128 :: Ue 256 1 0 enp0s25', # noqa: E501
444+ '2a00:abcd:82ae:cd33::/64 :: U 100 1 0 enp0s25', # noqa: E501
445+ '2a00:abcd:82ae:cd33::/56 fe80::32ee:54de:cd43:b4e1 UG 100 1 0 enp0s25', # noqa: E501
446+ 'fd81:123f:654::657/128 :: U 256 1 0 enp0s25', # noqa: E501
447+ 'fd81:123f:654::/64 :: U 100 1 0 enp0s25', # noqa: E501
448+ 'fd81:123f:654::/48 fe80::32ee:54de:cd43:b4e1 UG 100 1 0 enp0s25', # noqa: E501
449+ 'fe80::abcd:ef12:bc34:da21/128 :: U 100 1 2 enp0s25', # noqa: E501
450+ 'fe80::/64 :: U 256 1 16880 enp0s25', # noqa: E501
451+ '::/0 fe80::32ee:54de:cd43:b4e1 UG 100 1 0 enp0s25', # noqa: E501
452+ '::/0 :: !n -1 1424956 lo', # noqa: E501
453+ '::1/128 :: Un 0 4 26289 lo']) # noqa: E501
454+
455+SAMPLE_IPROUTE_OUT_V4 = '\n'.join([
456+ 'default via 192.168.2.1 dev enp0s25 proto static metric 100', # noqa: E501
457+ 'default via 192.168.2.1 dev wlp3s0 proto static metric 150', # noqa: E501
458+ '192.168.2.0/24 dev enp0s25 proto kernel scope link src 192.168.2.18 metric 100']) # noqa: E501
459+
460+SAMPLE_IPROUTE_OUT_V6 = '\n'.join([
461+ '2a00:abcd:82ae:cd33::657 dev enp0s25 proto kernel metric 256 expires 2334sec pref medium', # noqa: E501
462+ '2a00:abcd:82ae:cd33::/64 dev enp0s25 proto ra metric 100 pref medium', # noqa: E501
463+ '2a00:abcd:82ae:cd33::/56 via fe80::32ee:54de:cd43:b4e1 dev enp0s25 proto ra metric 100 pref medium', # noqa: E501
464+ 'fd81:123f:654::657 dev enp0s25 proto kernel metric 256 pref medium', # noqa: E501
465+ 'fd81:123f:654::/64 dev enp0s25 proto ra metric 100 pref medium', # noqa: E501
466+ 'fd81:123f:654::/48 via fe80::32ee:54de:cd43:b4e1 dev enp0s25 proto ra metric 100 pref medium', # noqa: E501
467+ 'fe80::abcd:ef12:bc34:da21 dev enp0s25 proto static metric 100 pref medium', # noqa: E501
468+ 'fe80::/64 dev enp0s25 proto kernel metric 256 pref medium', # noqa: E501
469+ 'default via fe80::32ee:54de:cd43:b4e1 dev enp0s25 proto static metric 100 pref medium']) # noqa: E501
470
471 NETDEV_FORMATTED_OUT = '\n'.join([
472- '+++++++++++++++++++++++++++++++++++++++Net device info+++++++++++++++++++'
473- '++++++++++++++++++++',
474- '+---------+------+------------------------------+---------------+-------+'
475- '-------------------+',
476- '| Device | Up | Address | Mask | Scope |'
477- ' Hw-Address |',
478- '+---------+------+------------------------------+---------------+-------+'
479- '-------------------+',
480- '| enp0s25 | True | 192.168.2.18 | 255.255.255.0 | . |'
481- ' 50:7b:9d:2c:af:91 |',
482- '| enp0s25 | True | fe80::8107:2b92:867e:f8a6/64 | . | link |'
483- ' 50:7b:9d:2c:af:91 |',
484- '| lo | True | 127.0.0.1 | 255.0.0.0 | . |'
485- ' . |',
486- '| lo | True | ::1/128 | . | host |'
487- ' . |',
488- '+---------+------+------------------------------+---------------+-------+'
489- '-------------------+'])
490+ '+++++++++++++++++++++++++++++++++++++++Net device info+++++++++++++++++++++++++++++++++++++++', # noqa: E501
491+ '+---------+------+------------------------------+---------------+-------+-------------------+', # noqa: E501
492+ '| Device | Up | Address | Mask | Scope | Hw-Address |', # noqa: E501
493+ '+---------+------+------------------------------+---------------+-------+-------------------+', # noqa: E501
494+ '| enp0s25 | True | 192.168.2.18 | 255.255.255.0 | . | 50:7b:9d:2c:af:91 |', # noqa: E501
495+ '| enp0s25 | True | fe80::8107:2b92:867e:f8a6/64 | . | link | 50:7b:9d:2c:af:91 |', # noqa: E501
496+ '| lo | True | 127.0.0.1 | 255.0.0.0 | . | . |', # noqa: E501
497+ '| lo | True | ::1/128 | . | host | . |', # noqa: E501
498+ '+---------+------+------------------------------+---------------+-------+-------------------+']) # noqa: E501
499
500 ROUTE_FORMATTED_OUT = '\n'.join([
501- '+++++++++++++++++++++++++++++Route IPv4 info++++++++++++++++++++++++++'
502- '+++',
503- '+-------+-------------+-------------+---------------+-----------+-----'
504- '--+',
505- '| Route | Destination | Gateway | Genmask | Interface | Flags'
506- ' |',
507- '+-------+-------------+-------------+---------------+-----------+'
508- '-------+',
509- '| 0 | 0.0.0.0 | 192.168.2.1 | 0.0.0.0 | wlp3s0 |'
510- ' UG |',
511- '| 1 | 192.168.2.0 | 0.0.0.0 | 255.255.255.0 | enp0s25 |'
512- ' U |',
513- '+-------+-------------+-------------+---------------+-----------+'
514- '-------+',
515- '++++++++++++++++++++++++++++++++++++++++Route IPv6 info++++++++++'
516- '++++++++++++++++++++++++++++++',
517- '+-------+-------------+-------------+---------------+---------------+'
518- '-----------------+-------+',
519- '| Route | Proto | Recv-Q | Send-Q | Local Address |'
520- ' Foreign Address | State |',
521- '+-------+-------------+-------------+---------------+---------------+'
522- '-----------------+-------+',
523- '| 0 | 0.0.0.0 | 192.168.2.1 | 0.0.0.0 | UG |'
524- ' 0 | 0 |',
525- '| 1 | 192.168.2.0 | 0.0.0.0 | 255.255.255.0 | U |'
526- ' 0 | 0 |',
527- '+-------+-------------+-------------+---------------+---------------+'
528- '-----------------+-------+'])
529+ '+++++++++++++++++++++++++++++++++Route IPv4 info++++++++++++++++++++++++++++++++++', # noqa: E501
530+ '+-------+-------------+-------------+---------------+-----------+-------+--------+', # noqa: E501
531+ '| Route | Destination | Gateway | Genmask | Interface | Flags | Metric |', # noqa: E501
532+ '+-------+-------------+-------------+---------------+-----------+-------+--------+', # noqa: E501
533+ '| 0 | 0.0.0.0 | 192.168.2.1 | 0.0.0.0 | enp0s25 | UG | 100 |', # noqa: E501
534+ '| 1 | 0.0.0.0 | 192.168.2.1 | 0.0.0.0 | wlp3s0 | UG | 150 |', # noqa: E501
535+ '| 2 | 192.168.2.0 | 0.0.0.0 | 255.255.255.0 | enp0s25 | U | 100 |', # noqa: E501
536+ '+-------+-------------+-------------+---------------+-----------+-------+--------+', # noqa: E501
537+ '+++++++++++++++++++++++++++++++++++++++Route IPv6 info++++++++++++++++++++++++++++++++++++++++', # noqa: E501
538+ '+-------+---------------------------+---------------------------+-----------+-------+--------+', # noqa: E501
539+ '| Route | Destination | Gateway | Interface | Flags | Metric |', # noqa: E501
540+ '+-------+---------------------------+---------------------------+-----------+-------+--------+', # noqa: E501
541+ '| 0 | 2a00:abcd:82ae:cd33::657 | :: | enp0s25 | Ue | 256 |', # noqa: E501
542+ '| 1 | 2a00:abcd:82ae:cd33::/64 | :: | enp0s25 | U | 100 |', # noqa: E501
543+ '| 2 | 2a00:abcd:82ae:cd33::/56 | fe80::32ee:54de:cd43:b4e1 | enp0s25 | UG | 100 |', # noqa: E501
544+ '| 3 | fd81:123f:654::657 | :: | enp0s25 | U | 256 |', # noqa: E501
545+ '| 4 | fd81:123f:654::/64 | :: | enp0s25 | U | 100 |', # noqa: E501
546+ '| 5 | fd81:123f:654::/48 | fe80::32ee:54de:cd43:b4e1 | enp0s25 | UG | 100 |', # noqa: E501
547+ '| 6 | fe80::abcd:ef12:bc34:da21 | :: | enp0s25 | U | 100 |', # noqa: E501
548+ '| 7 | fe80::/64 | :: | enp0s25 | U | 256 |', # noqa: E501
549+ '| 8 | ::/0 | fe80::32ee:54de:cd43:b4e1 | enp0s25 | UG | 100 |', # noqa: E501
550+ '+-------+---------------------------+---------------------------+-----------+-------+--------+']) # noqa: E501
551
552
553 class TestNetInfo(CiTestCase):
554
555 maxDiff = None
556
557- @mock.patch('cloudinit.netinfo.util.subp')
558- def test_netdev_pformat(self, m_subp):
559+ # older ifconfig syntax from around 2012
560+ def netdev_old_nettools_selector(*args, **kwargs):
561+ # pylint:disable=no-method-argument
562+ if 'ip' in args[0]:
563+ raise util.ProcessExecutionError
564+ if 'ifconfig' in args[0]:
565+ return (SAMPLE_OLD_IFCONFIG_OUT, '')
566+
567+ # current ifconfig syntax from present snapshot builds
568+ def netdev_new_nettools_selector(*args, **kwargs):
569+ # pylint:disable=no-method-argument
570+ if 'ip' in args[0]:
571+ raise util.ProcessExecutionError
572+ if 'ifconfig' in args[0]:
573+ return (SAMPLE_NEW_IFCONFIG_OUT, '')
574+
575+ def netdev_nettools_route_selector(*args, **kwargs):
576+ # pylint:disable=no-method-argument
577+ if 'ip' in args[0]:
578+ raise util.ProcessExecutionError
579+ if 'netstat' in args[0] and 'inet6' not in args[0]:
580+ return (SAMPLE_ROUTE_OUT_V4, '')
581+ if 'netstat' in args[0] and 'inet6' in args[0]:
582+ return (SAMPLE_ROUTE_OUT_V6, '')
583+
584+ def netdev_iproute_selector(*args, **kwargs):
585+ # pylint:disable=no-method-argument
586+ if 'ip' in args[0] and 'addr' in args[0]:
587+ return (SAMPLE_IPADDR_OUT, '')
588+ if 'ip' in args[0] and 'link' in args[0]:
589+ return (SAMPLE_IPLINK_OUT, '')
590+ if 'ip' in args[0] and 'route' in args[0] and '-6' not in args[0]:
591+ return (SAMPLE_IPROUTE_OUT_V4, '')
592+ if 'ip' in args[0] and 'route' in args[0] and '-6' in args[0]:
593+ return (SAMPLE_IPROUTE_OUT_V6, '')
594+
595+ @mock.patch('cloudinit.netinfo.util.subp',
596+ side_effect=netdev_old_nettools_selector)
597+ def test_netdev_old_nettools_pformat(self, m_subp):
598 """netdev_pformat properly rendering network device information."""
599- m_subp.return_value = (SAMPLE_IFCONFIG_OUT, '')
600 content = netdev_pformat()
601 self.assertEqual(NETDEV_FORMATTED_OUT, content)
602
603- @mock.patch('cloudinit.netinfo.util.subp')
604- def test_route_pformat(self, m_subp):
605+ @mock.patch('cloudinit.netinfo.util.subp',
606+ side_effect=netdev_new_nettools_selector)
607+ def test_netdev_new_nettools_pformat(self, m_subp):
608+ """netdev_pformat properly rendering network device information."""
609+ content = netdev_pformat()
610+ self.assertEqual(NETDEV_FORMATTED_OUT, content)
611+
612+ @mock.patch('cloudinit.netinfo.util.subp',
613+ side_effect=netdev_iproute_selector)
614+ def test_netdev_iproute_pformat(self, m_subp):
615+ """netdev_pformat properly rendering network device information."""
616+ content = netdev_pformat()
617+ self.assertEqual(NETDEV_FORMATTED_OUT, content)
618+
619+ @mock.patch('cloudinit.netinfo.util.subp',
620+ side_effect=netdev_nettools_route_selector)
621+ def test_route_nettools_pformat(self, m_subp):
622+ """netdev_pformat properly rendering network device information."""
623+ content = route_pformat()
624+ self.assertEqual(ROUTE_FORMATTED_OUT, content)
625+
626+ @mock.patch('cloudinit.netinfo.util.subp',
627+ side_effect=netdev_iproute_selector)
628+ def test_route_iproute_pformat(self, m_subp):
629 """netdev_pformat properly rendering network device information."""
630- m_subp.return_value = (SAMPLE_ROUTE_OUT, '')
631 content = route_pformat()
632 self.assertEqual(ROUTE_FORMATTED_OUT, content)

Subscribers

People subscribed via source and target branches