Merge lp:~rbanffy/maas/bug-1337874 into lp:~maas-committers/maas/trunk

Proposed by Ricardo Bánffy
Status: Merged
Approved by: Ricardo Bánffy
Approved revision: no longer in the source branch.
Merged at revision: 3759
Proposed branch: lp:~rbanffy/maas/bug-1337874
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 216 lines (+158/-0)
3 files modified
src/metadataserver/models/commissioningscript.py (+48/-0)
src/metadataserver/models/tests/ip_link_results.txt (+18/-0)
src/metadataserver/models/tests/test_noderesults.py (+92/-0)
To merge this branch: bzr merge lp:~rbanffy/maas/bug-1337874
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+254779@code.launchpad.net

Commit message

Update node network interface information during commissioning.

Description of the change

Added a new step in the commissioning process that uses `ip link` to gather the MAC addresses of interfaces currently connected to the node and update the node information according to that new information.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks close. Just a few comments that should be addressed.

review: Needs Fixing
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Little better. Still wonder why my suggestion was not fully implemented.

review: Needs Information
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. This will be so nice being fixed, always been a bug that people wanted fixed.

review: Approve

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 2015-03-25 15:33:23 +0000
3+++ src/metadataserver/models/commissioningscript.py 2015-04-01 22:34:36 +0000
4@@ -43,6 +43,7 @@
5 )
6 from lxml import etree
7 from maasserver.fields import MAC
8+from maasserver.models.macaddress import MACAddress
9 from maasserver.models.physicalblockdevice import PhysicalBlockDevice
10 from maasserver.models.tag import Tag
11 from metadataserver import DefaultMeta
12@@ -118,6 +119,11 @@
13 lshw -xml
14 """)
15
16+# Built-in script to run `ip link`
17+IPLINK_SCRIPT = dedent("""\
18+ #!/bin/sh
19+ ip link
20+ """)
21
22 # Count the processors which do not declare their number of 'threads'
23 # as 1 processor.
24@@ -140,6 +146,44 @@
25 """
26
27
28+def update_node_network_information(node, output, exit_status):
29+ """Updates the network interfaces from the results of `IPLINK_SCRIPT`.
30+
31+ Creates and deletes MACAddresses according to what we currently know about
32+ this node's hardware.
33+
34+ If `exit_status` is non-zero, this function returns without doing
35+ anything.
36+
37+ """
38+ assert isinstance(output, bytes)
39+ if exit_status != 0:
40+ return
41+
42+ # Get the MAC addresses of all connected interfaces.
43+ hw_macaddresses = {
44+ MAC(line.split()[1]) for line in output.splitlines()
45+ if line.strip().startswith('link/ether')
46+ }
47+
48+ # MAC addresses found in the db but not on the hardware node will be
49+ # deleted.
50+ for mac_address in node.macaddress_set.all():
51+ if mac_address not in hw_macaddresses:
52+ mac_address.delete()
53+
54+ # MAC addresses found in the hardware node but not on the db will be
55+ # created or reassigned.
56+ for address in hw_macaddresses:
57+ if address not in node.macaddress_set.all():
58+ try:
59+ mac_address = MACAddress.objects.get(mac_address=address)
60+ mac_address.node = node
61+ mac_address.save()
62+ except MACAddress.DoesNotExist:
63+ MACAddress(mac_address=address, node=node).save()
64+
65+
66 def update_hardware_details(node, output, exit_status):
67 """Process the results of `LSHW_SCRIPT`.
68
69@@ -547,6 +591,10 @@
70 'content': make_function_call_script(gather_physical_block_devices),
71 'hook': update_node_physical_block_devices,
72 },
73+ '00-maas-07-network-interfaces.out': {
74+ 'content': IPLINK_SCRIPT.encode('ascii'),
75+ 'hook': update_node_network_information,
76+ },
77 '99-maas-01-wait-for-lldpd.out': {
78 'content': make_function_call_script(
79 lldpd_wait, "/var/run/lldpd.socket", time_delay=60),
80
81=== added file 'src/metadataserver/models/tests/ip_link_results.txt'
82--- src/metadataserver/models/tests/ip_link_results.txt 1970-01-01 00:00:00 +0000
83+++ src/metadataserver/models/tests/ip_link_results.txt 2015-04-01 22:34:36 +0000
84@@ -0,0 +1,18 @@
85+1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default
86+ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
87+2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
88+ link/ether ec:f4:bb:f9:17:8e brd ff:ff:ff:ff:ff:ff
89+3: wlan0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN mode DORMANT group default qlen 1000
90+ link/ether 38:b1:db:cd:f0:ab brd ff:ff:ff:ff:ff:ff
91+5: virbr2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
92+ link/ether 52:54:00:03:53:aa brd ff:ff:ff:ff:ff:ff
93+6: virbr2-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master virbr2 state DOWN mode DEFAULT group default qlen 500
94+ link/ether 52:54:00:03:53:aa brd ff:ff:ff:ff:ff:ff
95+7: virbr1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
96+ link/ether 52:54:00:67:f5:3a brd ff:ff:ff:ff:ff:ff
97+8: virbr1-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master virbr1 state DOWN mode DEFAULT group default qlen 500
98+ link/ether 52:54:00:67:f5:3a brd ff:ff:ff:ff:ff:ff
99+9: vnet0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master virbr2 state UNKNOWN mode DEFAULT group default qlen 500
100+ link/ether fe:54:00:b4:f1:61 brd ff:ff:ff:ff:ff:ff
101+10: vnet1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master virbr2 state UNKNOWN mode DEFAULT group default qlen 500
102+ link/ether fe:54:00:02:36:19 brd ff:ff:ff:ff:ff:ff
103
104=== modified file 'src/metadataserver/models/tests/test_noderesults.py'
105--- src/metadataserver/models/tests/test_noderesults.py 2015-03-25 15:33:23 +0000
106+++ src/metadataserver/models/tests/test_noderesults.py 2015-04-01 22:34:36 +0000
107@@ -38,6 +38,7 @@
108
109 from fixtures import FakeLogger
110 from maasserver.fields import MAC
111+from maasserver.models.macaddress import MACAddress
112 from maasserver.models.physicalblockdevice import PhysicalBlockDevice
113 from maasserver.models.tag import Tag
114 from maasserver.testing.factory import factory
115@@ -69,6 +70,7 @@
116 set_node_routers,
117 set_virtual_tag,
118 update_hardware_details,
119+ update_node_network_information,
120 update_node_physical_block_devices,
121 )
122 from metadataserver.models.noderesult import NodeResult
123@@ -1109,3 +1111,93 @@
124 self.assertThat(
125 PhysicalBlockDevice.objects.filter(node=node).first().tags,
126 Contains('sata'))
127+
128+
129+class TestUpdateNodeNetworkInformation(MAASServerTestCase):
130+ """Tests the update_node_network_information function using data from the
131+ ip_link_results.txt file to simulate `ip link`'s output.
132+
133+ The file records 6 different MAC addresses coming from different kinds of
134+ interfaces:
135+
136+ 38:b1:db:cd:f0:ab
137+ 52:54:00:03:53:aa
138+ 52:54:00:67:f5:3a
139+ ec:f4:bb:f9:17:8e
140+ fe:54:00:02:36:19
141+ fe:54:00:b4:f1:61
142+ """
143+
144+ def test__add_all_interfaces(self):
145+ """Test a node that has no previously known interfaces on which we
146+ need to add a series of interfaces.
147+ """
148+ node = factory.make_Node()
149+ # Delete all MAC addresses eventually created by factory attached to
150+ # this node.
151+ MACAddress.objects.filter(node_id=node.id).delete()
152+
153+ output = open(
154+ os.path.dirname(__file__) + '/ip_link_results.txt').read()
155+ update_node_network_information(node, output, 0)
156+
157+ # Makes sure all the test dataset MAC addresses were added to the node.
158+ node_macaddresses = [m.mac_address for m in node.macaddress_set.all()]
159+ self.assertIn(MAC("38:b1:db:cd:f0:ab"), node_macaddresses)
160+ self.assertIn(MAC("52:54:00:03:53:aa"), node_macaddresses)
161+ self.assertIn(MAC("52:54:00:67:f5:3a"), node_macaddresses)
162+ self.assertIn(MAC("ec:f4:bb:f9:17:8e"), node_macaddresses)
163+ self.assertIn(MAC("fe:54:00:02:36:19"), node_macaddresses)
164+ self.assertIn(MAC("fe:54:00:b4:f1:61"), node_macaddresses)
165+
166+ def test__one_mac_missing(self):
167+ """Test whether we correcly detach a NIC that no longer appears to be
168+ connected to the node.
169+ """
170+ node = factory.make_Node()
171+
172+ # Create a MAC address that we know is not in the test dataset.
173+ mac_to_be_detached = factory.make_MACAddress(node=node)
174+ mac_to_be_detached.mac_address = "01:23:45:67:89:ab"
175+ mac_to_be_detached.save()
176+
177+ output = open(
178+ os.path.dirname(__file__) + '/ip_link_results.txt').read()
179+ update_node_network_information(node, output, 0)
180+ db_macaddresses = [m.mac_address for m in node.macaddress_set.all()]
181+
182+ # These should have been added to the node.
183+ self.assertIn(MAC("38:b1:db:cd:f0:ab"), db_macaddresses)
184+ self.assertIn(MAC("52:54:00:03:53:aa"), db_macaddresses)
185+ self.assertIn(MAC("52:54:00:67:f5:3a"), db_macaddresses)
186+ self.assertIn(MAC("ec:f4:bb:f9:17:8e"), db_macaddresses)
187+ self.assertIn(MAC("fe:54:00:02:36:19"), db_macaddresses)
188+ self.assertIn(MAC("fe:54:00:b4:f1:61"), db_macaddresses)
189+
190+ # This one should have been removed because it no longer shows on the
191+ # `ip link` output.
192+ self.assertNotIn(MAC('01:23:45:67:89:ab'), db_macaddresses)
193+
194+ def test__reassign_mac(self):
195+ """Test whether we can assign a MAC address previously connected to a
196+ different node to the current one"""
197+ node1 = factory.make_Node()
198+
199+ # Create a MAC address that we know IS in the test dataset.
200+ mac_to_be_reassigned = factory.make_MACAddress(node=node1)
201+ mac_to_be_reassigned.mac_address = MAC('38:b1:db:cd:f0:ab')
202+ mac_to_be_reassigned.save()
203+
204+ node2 = factory.make_Node()
205+ output = open(
206+ os.path.dirname(__file__) + '/ip_link_results.txt').read()
207+ update_node_network_information(node2, output, 0)
208+
209+ node2_db_macaddresses = [m.mac_address
210+ for m in node2.macaddress_set.all()]
211+ self.assertIn(MAC("38:b1:db:cd:f0:ab"), node2_db_macaddresses)
212+ self.assertIn(MAC("52:54:00:03:53:aa"), node2_db_macaddresses)
213+ self.assertIn(MAC("52:54:00:67:f5:3a"), node2_db_macaddresses)
214+ self.assertIn(MAC("ec:f4:bb:f9:17:8e"), node2_db_macaddresses)
215+ self.assertIn(MAC("fe:54:00:02:36:19"), node2_db_macaddresses)
216+ self.assertIn(MAC("fe:54:00:b4:f1:61"), node2_db_macaddresses)