Merge lp:~rvb/maas/explore-intf-bug-1375360 into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 3244
Proposed branch: lp:~rvb/maas/explore-intf-bug-1375360
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 121 lines (+85/-1)
2 files modified
src/metadataserver/models/commissioningscript.py (+27/-0)
src/metadataserver/models/tests/test_noderesults.py (+58/-1)
To merge this branch: bzr merge lp:~rvb/maas/explore-intf-bug-1375360
Reviewer Review Type Date Requested Status
Julian Edwards (community) Needs Information
Gavin Panella (community) Approve
Graham Binns (community) Approve
Review via email: mp+238139@code.launchpad.net

Commit message

Run dhclient on all the unconfigured interfaces during commissioning to create links between the NICs attached to unconfigured interfaces and the networks MAAS knows about.

Description of the change

I've successfully tested this on my NUCs (I used an Ethernet Dongle to get an additional NIC).

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) :
review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

Is there really no sensible scripting language available in the ephemeral environment?

Revision history for this message
Raphaël Badin (rvb) wrote :

> Is there really no sensible scripting language available in the ephemeral
> environment?

Well, I could have used Python but I think this script is so simple that a Shell script is all we need.

Revision history for this message
Gavin Panella (allenap) :
Revision history for this message
Gavin Panella (allenap) wrote :

> Is there really no sensible scripting language available in the
> ephemeral environment?

Python is available. See lldpd_install, lldpd_wait, and lldpd_capture. I
strongly suggest rewriting this in Python so that it can be unit-tested,
and so it doesn't completely suck :)

review: Needs Fixing
Revision history for this message
Gavin Panella (allenap) wrote :

That's great!

review: Approve
Revision history for this message
Raphaël Badin (rvb) wrote :

> That's great!

Thanks for not letting me be lazy about this ;).

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Monday 13 Oct 2014 12:18:47 you wrote:
> Diff comments:
> > === modified file 'src/metadataserver/models/commissioningscript.py'
> > --- src/metadataserver/models/commissioningscript.py 2014-08-13 21:49:35
> > +0000 +++ src/metadataserver/models/commissioningscript.py 2014-10-13
> > 11:44:38 +0000 @@ -214,6 +214,31 @@
> >
> > node.system_id)
> >
> > +# Run `dhclient` on all the unconfigured interfaces.
> > +# This is done to create records in the leases file for the
> > +# NICs attached to unconfigured interfaces. This way the leases
> > +# parser will be able to connect these NICs and the networks
> > +# MAAS knows about.
> > +DHCP_EXPLORE_SCRIPT = dedent("""\
> > + #!/bin/sh
> > +
> > + # Compute the list of interfaces.
> > + all_ifaces=`ifconfig -s -a | cut -d' ' -f1 | tail -n +2 | sort`
> > + echo "All interfaces:" $all_ifaces
> > + configured_ifaces=`ifconfig -s | cut -d' ' -f1 | tail -n +2 | sort`
> > + echo "Configured interfaces:" $configured_ifaces
> > +
> > + # Run dhclient on each of these interfaces.
> > + for iface in $all_ifaces; do
> > + echo $configured_ifaces | grep -q -v $iface
>
> This won't work if there are 10 or more NICs, because 'eth1' will match
> 'eth10'. There are probably other edge-cases like that.
>
> Also, use fgrep here to avoid treating $iface as a regex. That won't fix the
> edge-cases above though.

There will be some interfaces on which you don't want to run a dhclient, such
as lo. I would grep out anything starting 'lo' and 'virbr' for now.

I'm also concerned about the difference between ifconfig with the -a and
without. What is going to cause an interface to be unconfigured in the
ephemeral environment?

Revision history for this message
Julian Edwards (julian-edwards) wrote :

For example, I have eth0 on my laptop which appears with and without -a, but it is not "configured" as it doesn't have any IP (it's not plugged in).

review: Needs Information
Revision history for this message
Raphaël Badin (rvb) wrote :

[...]

Note that after the discussion with Gavin, I've rewritten the script in
Python.

> There will be some interfaces on which you don't want to run a dhclient, such
> as lo. I would grep out anything starting 'lo' and 'virbr' for now.

lo is always configured and we only run dhclient on unconfigured interfaces.

This is all happening during commissioning and thus in an environment we
control. Although a commissioning script could configure an
unconfigured virtual bridge, this is not done by default.
Additionally, running dhclient on a virbr will simply timeout.

> I'm also concerned about the difference between ifconfig with the -a and
> without. What is going to cause an interface to be unconfigured in the
> ephemeral environment?

I'm not sure how cloud-init decides which interfaces should be brought
up in the presence of multiple interfaces but this doesn't really matter
here, this code is there to "explore" the interfaces that have not been
already configured so that the leases parser will pick up these interfaces.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

For the benefit of others reading, we had a call and discussed this. It is still possible for an interface to be unconfigured, but UP.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/metadataserver/models/commissioningscript.py'
2--- src/metadataserver/models/commissioningscript.py 2014-08-13 21:49:35 +0000
3+++ src/metadataserver/models/commissioningscript.py 2014-10-13 13:00:53 +0000
4@@ -214,6 +214,29 @@
5 node.system_id)
6
7
8+# Run `dhclient` on all the unconfigured interfaces.
9+# This is done to create records in the leases file for the
10+# NICs attached to unconfigured interfaces. This way the leases
11+# parser will be able to connect these NICs and the networks
12+# MAAS knows about.
13+def dhcp_explore():
14+ def get_iface_list(ifconfig_output):
15+ return [
16+ line.split()[0]
17+ for line in ifconfig_output.splitlines()[1:]]
18+
19+ from subprocess import check_output, CalledProcessError, check_call
20+ all_ifaces = get_iface_list(check_output(("ifconfig", "-s", "-a")))
21+ configured_ifaces = get_iface_list(check_output(("ifconfig", "-s")))
22+ unconfigured_ifaces = set(all_ifaces) - set(configured_ifaces)
23+ for iface in sorted(unconfigured_ifaces):
24+ try:
25+ check_call(("sudo", "dhclient", "-w", iface))
26+ except CalledProcessError:
27+ # Continue trying to run dhcplient on the other interfaces.
28+ pass
29+
30+
31 # This function must be entirely self-contained. It must not use
32 # variables or imports from the surrounding scope.
33 def lldpd_install(config_file):
34@@ -369,6 +392,10 @@
35 'content': LIST_MODALIASES_SCRIPT.encode('ascii'),
36 'hook': null_hook,
37 },
38+ '00-maas-05-dhcp-unconfigured-ifaces': {
39+ 'content': make_function_call_script(dhcp_explore),
40+ 'hook': null_hook,
41+ },
42 '99-maas-01-wait-for-lldpd.out': {
43 'content': make_function_call_script(
44 lldpd_wait, "/var/run/lldpd.socket", time_delay=60),
45
46=== modified file 'src/metadataserver/models/tests/test_noderesults.py'
47--- src/metadataserver/models/tests/test_noderesults.py 2014-09-10 16:20:31 +0000
48+++ src/metadataserver/models/tests/test_noderesults.py 2014-10-13 13:00:53 +0000
49@@ -42,7 +42,10 @@
50 MAASServerTestCase,
51 TestWithoutCrochetMixin,
52 )
53-from maastesting.matchers import MockCalledOnceWith
54+from maastesting.matchers import (
55+ MockCalledOnceWith,
56+ MockCallsMatch,
57+ )
58 from maastesting.utils import sample_binary_data
59 from metadataserver.enum import RESULT_TYPE
60 from metadataserver.fields import Bin
61@@ -316,6 +319,60 @@
62 return bytes(script)
63
64
65+# The two following example outputs differ because eth2 and eth1 are not
66+# configured and thus 'ifconfig -s -a' returns a list with both 'eth1'
67+# and 'eth2' while 'ifconfig -s' does not contain them.
68+
69+# Example output of 'ifconfig -s -a':
70+ifconfig_all = """
71+Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP
72+eth2 1500 0 0 0 0 0 0 0
73+eth1 1500 0 0 0 0 0 0 0
74+eth0 1500 0 1366127 0 0 0 831110 0
75+lo 65536 0 38075 0 0 0 38075 0
76+virbr0 1500 0 0 0 0 0 0 0
77+wlan0 1500 0 2304695 0 0 0 1436049 0
78+"""
79+
80+# Example output of 'ifconfig -s':
81+ifconfig_config = """
82+Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP
83+eth0 1500 0 1366127 0 0 0 831110 0
84+lo 65536 0 38115 0 0 0 38115 0
85+virbr0 1500 0 0 0 0 0 0 0
86+wlan0 1500 0 2304961 0 0 0 1436319 0
87+"""
88+
89+
90+class TestDHCPExplore(MAASServerTestCase):
91+
92+ def test_calls_dhclient_on_unconfigured_interfaces(self):
93+ check_output = self.patch(subprocess, "check_output")
94+ check_output.side_effect = [ifconfig_all, ifconfig_config]
95+ check_call = self.patch(subprocess, "check_call")
96+ dhcp_explore = isolate_function(cs_module.dhcp_explore)
97+ dhcp_explore()
98+ self.assertThat(
99+ check_call,
100+ MockCallsMatch(
101+ call(("sudo", "dhclient", "-w", 'eth1')),
102+ call(("sudo", "dhclient", "-w", 'eth2'))))
103+
104+ def test_swallows_errors(self):
105+ check_output = self.patch(subprocess, "check_output")
106+ check_output.side_effect = [ifconfig_all, ifconfig_config]
107+ check_call = self.patch(subprocess, "check_call")
108+ # Simulate a failure for the first call to dhclient.
109+ check_call.side_effect = [CalledProcessError('boom', 2), '']
110+ dhcp_explore = isolate_function(cs_module.dhcp_explore)
111+ dhcp_explore()
112+ self.assertThat(
113+ check_call,
114+ MockCallsMatch(
115+ call(("sudo", "dhclient", "-w", 'eth1')),
116+ call(("sudo", "dhclient", "-w", 'eth2'))))
117+
118+
119 class TestExtractRouters(MAASServerTestCase):
120
121 def test_extract_router_mac_addresses_returns_None_when_empty_input(self):