Merge lp:~bjornt/maas/reset-network-config into lp:~maas-committers/maas/trunk

Proposed by Björn Tillenius
Status: Merged
Approved by: Björn Tillenius
Approved revision: no longer in the source branch.
Merged at revision: 6054
Proposed branch: lp:~bjornt/maas/reset-network-config
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 343 lines (+115/-17)
7 files modified
src/maasserver/api/machines.py (+2/-0)
src/maasserver/api/tests/test_machine.py (+6/-0)
src/maasserver/models/node.py (+14/-0)
src/maasserver/models/tests/test_node.py (+73/-1)
src/metadataserver/builtin_scripts/hooks.py (+8/-3)
src/metadataserver/builtin_scripts/tests/test_hooks.py (+7/-12)
src/provisioningserver/refresh/node_info_scripts.py (+5/-1)
To merge this branch: bzr merge lp:~bjornt/maas/reset-network-config
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+324173@code.launchpad.net

Commit message

When resetting the network configuration over the API, remove all
network interfaces that weren't detected during commissioning.

Description of the change

When resetting the network configuration over the API, remove all
network interfaces that weren't detected during commissioning.

I did it by re-running the hooks for the commissioning script that
detects the network interfaces. It was mentiond that maasserver
shouldn't import from metadata server, but I did that for now, since we
seem to have quite a few such imports already. But I'd be happy to move
code before merging, if you tell me where it should live.

I also fixed a bug where set_initial_networking_configuration would add
an additional IP to the boot interface every time it was called,
resulting in a growing list of alias interfaces when the network
configuration was reset.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good in general. I've got a couple minor issues below, but I'll trust you to resolve them before you land this.

review: Approve
Revision history for this message
Björn Tillenius (bjornt) :
Revision history for this message
MAAS Lander (maas-lander) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Revision history for this message
MAAS Lander (maas-lander) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/machines.py'
2--- src/maasserver/api/machines.py 2017-05-05 14:10:46 +0000
3+++ src/maasserver/api/machines.py 2017-05-18 09:01:59 +0000
4@@ -730,6 +730,7 @@
5 raise NodeStateViolation(
6 "Machine must be in a ready state to restore networking "
7 "configuration")
8+ machine.restore_network_interfaces()
9 machine.set_initial_networking_configuration()
10 return reload_object(machine)
11
12@@ -765,6 +766,7 @@
13 "Machine must be in a ready state to restore default "
14 "networking and storage configuration.")
15 machine.set_default_storage_layout()
16+ machine.restore_network_interfaces()
17 machine.set_initial_networking_configuration()
18 return reload_object(machine)
19
20
21=== modified file 'src/maasserver/api/tests/test_machine.py'
22--- src/maasserver/api/tests/test_machine.py 2017-04-21 16:47:02 +0000
23+++ src/maasserver/api/tests/test_machine.py 2017-05-18 09:01:59 +0000
24@@ -2128,6 +2128,8 @@
25 def test_restore_networking_configuration(self):
26 self.become_admin()
27 machine = factory.make_Machine(status=NODE_STATUS.READY)
28+ mock_restore_network_interfaces = self.patch(
29+ node_module.Machine, 'restore_network_interfaces')
30 mock_set_initial_networking_config = self.patch(
31 node_module.Machine, 'set_initial_networking_configuration')
32 response = self.client.post(
33@@ -2137,6 +2139,7 @@
34 self.assertEqual(
35 machine.system_id, json_load_bytes(response.content)['system_id'])
36 self.assertThat(mock_set_initial_networking_config, MockCalledOnce())
37+ self.assertThat(mock_restore_network_interfaces, MockCalledOnce())
38
39 def test_restore_networking_configuration_requires_admin(self):
40 machine = factory.make_Machine()
41@@ -2212,6 +2215,8 @@
42 machine = factory.make_Machine(status=NODE_STATUS.READY)
43 mock_set_default_storage_layout = self.patch(
44 node_module.Machine, 'set_default_storage_layout')
45+ mock_restore_network_interfaces = self.patch(
46+ node_module.Machine, 'restore_network_interfaces')
47 mock_set_initial_networking_config = self.patch(
48 node_module.Machine, 'set_initial_networking_configuration')
49 response = self.client.post(
50@@ -2221,6 +2226,7 @@
51 self.assertEqual(
52 machine.system_id, json_load_bytes(response.content)['system_id'])
53 self.assertThat(mock_set_default_storage_layout, MockCalledOnce())
54+ self.assertThat(mock_restore_network_interfaces, MockCalledOnce())
55 self.assertThat(mock_set_initial_networking_config, MockCalledOnce())
56
57 def test_restore_default_configuration_requires_admin(self):
58
59=== modified file 'src/maasserver/models/node.py'
60--- src/maasserver/models/node.py 2017-05-06 02:05:56 +0000
61+++ src/maasserver/models/node.py 2017-05-18 09:01:59 +0000
62@@ -176,6 +176,7 @@
63 get_sys_info,
64 refresh,
65 )
66+from provisioningserver.refresh.node_info_scripts import IPADDR_OUTPUT_NAME
67 from provisioningserver.rpc.cluster import (
68 AddChassis,
69 DisableAndShutoffRackd,
70@@ -3097,6 +3098,16 @@
71 for interface in interfaces:
72 interface.clear_all_links(clearing_config=True)
73
74+ def restore_network_interfaces(self):
75+ """Restore the network interface to their commissioned state."""
76+ # Local import to avoid circular import problems.
77+ from metadataserver.builtin_scripts.hooks import (
78+ update_node_network_information)
79+ script = self.current_commissioning_script_set.find_script_result(
80+ script_name=IPADDR_OUTPUT_NAME)
81+ update_node_network_information(
82+ self, script.output, script.exit_status)
83+
84 def set_initial_networking_configuration(self):
85 """Set the networking configuration to the default for this node.
86
87@@ -3119,6 +3130,9 @@
88 self.status not in [NODE_STATUS.DEPLOYING, NODE_STATUS.DEPLOYED], \
89 'Node cannot be in a deploying state when configuring network'
90
91+ # Clear the configuration, so that we can call this method
92+ # multiple times.
93+ self._clear_networking_configuration()
94 # Set AUTO mode on the boot interface.
95 auto_set = False
96 discovered_addresses = boot_interface.ip_addresses.filter(
97
98=== modified file 'src/maasserver/models/tests/test_node.py'
99--- src/maasserver/models/tests/test_node.py 2017-05-06 02:05:56 +0000
100+++ src/maasserver/models/tests/test_node.py 2017-05-18 09:01:59 +0000
101@@ -8,6 +8,7 @@
102 import base64
103 from datetime import datetime
104 import email
105+import os
106 import random
107 import re
108 from unittest.mock import (
109@@ -153,6 +154,7 @@
110 MockCallsMatch,
111 MockNotCalled,
112 )
113+from metadataserver.builtin_scripts.tests import test_hooks
114 from metadataserver.enum import (
115 SCRIPT_STATUS,
116 SCRIPT_TYPE,
117@@ -176,7 +178,10 @@
118 EVENT_DETAILS,
119 EVENT_TYPES,
120 )
121-from provisioningserver.refresh.node_info_scripts import NODE_INFO_SCRIPTS
122+from provisioningserver.refresh.node_info_scripts import (
123+ IPADDR_OUTPUT_NAME,
124+ NODE_INFO_SCRIPTS,
125+)
126 from provisioningserver.rpc.cluster import (
127 AddChassis,
128 DecomposeMachine,
129@@ -5185,6 +5190,73 @@
130 )
131 self.assertItemsEqual(enabled_interfaces, observed_interfaces)
132
133+ def test_set_initial_networking_configuration_no_multiple_auto_ips(self):
134+ node = factory.make_Node_with_Interface_on_Subnet()
135+ boot_interface = node.get_boot_interface()
136+ subnet = boot_interface.ip_addresses.filter(
137+ alloc_type=IPADDRESS_TYPE.DISCOVERED).first().subnet
138+ boot_interface.link_subnet(INTERFACE_LINK_TYPE.AUTO, subnet)
139+ node.set_initial_networking_configuration()
140+ boot_interface = reload_object(boot_interface)
141+ auto_ips = boot_interface.ip_addresses.filter(
142+ alloc_type=IPADDRESS_TYPE.AUTO)
143+ self.assertEqual(1, auto_ips.count())
144+
145+ def test_restore_commisioned_network_interfaces(self):
146+ node = factory.make_Node()
147+ IP_ADDR_OUTPUT_FILE = os.path.join(
148+ os.path.dirname(test_hooks.__file__), 'ip_addr_results_xenial.txt')
149+ with open(IP_ADDR_OUTPUT_FILE, "rb") as fd:
150+ IP_ADDR_OUTPUT_XENIAL = fd.read()
151+ script = factory.make_Script(
152+ name=IPADDR_OUTPUT_NAME, script_type=SCRIPT_TYPE.COMMISSIONING)
153+ commissioning_script_set = (
154+ ScriptSet.objects.create_commissioning_script_set(
155+ node, scripts=[script.name]))
156+ node.current_commissioning_script_set = commissioning_script_set
157+ factory.make_ScriptResult(
158+ script_set=commissioning_script_set, script=script, exit_status=0,
159+ output=IP_ADDR_OUTPUT_XENIAL)
160+
161+ # restore_network_interfaces() will set up the network intefaces
162+ # specified in ip_addr_results_xenial.txt.
163+ node.restore_network_interfaces()
164+ self.assertEqual(
165+ ["ens10", "ens11", "ens12", "ens3"],
166+ sorted(interface.name for interface in node.interface_set.all()))
167+
168+ def test_restore_network_interfaces_extra(self):
169+ node = factory.make_Node()
170+ IP_ADDR_OUTPUT_FILE = os.path.join(
171+ os.path.dirname(test_hooks.__file__), 'ip_addr_results_xenial.txt')
172+ with open(IP_ADDR_OUTPUT_FILE, "rb") as fd:
173+ IP_ADDR_OUTPUT_XENIAL = fd.read()
174+ script = factory.make_Script(
175+ name=IPADDR_OUTPUT_NAME, script_type=SCRIPT_TYPE.COMMISSIONING)
176+ commissioning_script_set = (
177+ ScriptSet.objects.create_commissioning_script_set(
178+ node, scripts=[script.name]))
179+ node.current_commissioning_script_set = commissioning_script_set
180+ factory.make_ScriptResult(
181+ script_set=commissioning_script_set, script=script, exit_status=0,
182+ output=IP_ADDR_OUTPUT_XENIAL)
183+ node.restore_network_interfaces()
184+
185+ # If extra interfaces are added, they will be removed when
186+ # calling restore_network_interfaces().
187+ factory.make_Interface(
188+ INTERFACE_TYPE.BOND, node=node,
189+ parents=node.interface_set.all()[:2])
190+ factory.make_Interface(
191+ INTERFACE_TYPE.VLAN, node=node,
192+ parents=[node.interface_set.all()[3]])
193+ factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
194+ factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
195+ node.restore_network_interfaces()
196+ self.assertEqual(
197+ ["ens10", "ens11", "ens12", "ens3"],
198+ sorted(interface.name for interface in node.interface_set.all()))
199+
200
201 class TestGetBestGuessForDefaultGateways(MAASServerTestCase):
202 """Tests for `Node.get_best_guess_for_default_gateways`."""
203
204=== modified file 'src/metadataserver/builtin_scripts/hooks.py'
205--- src/metadataserver/builtin_scripts/hooks.py 2017-03-30 23:28:49 +0000
206+++ src/metadataserver/builtin_scripts/hooks.py 2017-05-18 09:01:59 +0000
207@@ -5,6 +5,7 @@
208
209 __all__ = [
210 'NODE_INFO_SCRIPTS',
211+ 'update_node_network_information',
212 ]
213
214 import fnmatch
215@@ -16,11 +17,15 @@
216 from lxml import etree
217 from maasserver.models import Fabric
218 from maasserver.models.blockdevice import MIN_BLOCK_DEVICE_SIZE
219-from maasserver.models.interface import PhysicalInterface
220+from maasserver.models.interface import (
221+ Interface,
222+ PhysicalInterface,
223+)
224 from maasserver.models.physicalblockdevice import PhysicalBlockDevice
225 from maasserver.models.tag import Tag
226 from maasserver.utils.orm import get_one
227 from provisioningserver.refresh.node_info_scripts import (
228+ IPADDR_OUTPUT_NAME,
229 LIST_MODALIASES_OUTPUT_NAME,
230 LSHW_OUTPUT_NAME,
231 NODE_INFO_SCRIPTS,
232@@ -154,7 +159,7 @@
233 interface.vlan = None
234 interface.save(update_fields=['vlan', 'updated'])
235
236- for iface in PhysicalInterface.objects.filter(node=node):
237+ for iface in Interface.objects.filter(node=node):
238 if iface not in current_interfaces:
239 iface.delete()
240
241@@ -647,7 +652,7 @@
242 NODE_INFO_SCRIPTS['00-maas-02-virtuality']['hook'] = set_virtual_tag
243 NODE_INFO_SCRIPTS['00-maas-07-block-devices']['hook'] = (
244 update_node_physical_block_devices)
245-NODE_INFO_SCRIPTS['99-maas-03-network-interfaces']['hook'] = (
246+NODE_INFO_SCRIPTS[IPADDR_OUTPUT_NAME]['hook'] = (
247 update_node_network_information)
248 NODE_INFO_SCRIPTS['99-maas-04-network-interfaces-with-sriov']['hook'] = (
249 update_node_network_interface_tags)
250
251=== modified file 'src/metadataserver/builtin_scripts/tests/test_hooks.py'
252--- src/metadataserver/builtin_scripts/tests/test_hooks.py 2017-03-30 23:41:35 +0000
253+++ src/metadataserver/builtin_scripts/tests/test_hooks.py 2017-05-18 09:01:59 +0000
254@@ -1087,7 +1087,7 @@
255 # ... and ensure that the interface was deleted.
256 self.assertItemsEqual([], Interface.objects.filter(id=interface_id))
257
258- def test__does_not_delete_virtual_interfaces_with_shared_mac(self):
259+ def test__deletes_virtual_interfaces_with_shared_mac(self):
260 # Note: since this VLANInterface will be linked to the default VLAN
261 # ("vid 0", which is actually invalid) the VLANInterface will
262 # automatically get the name "vlan0".
263@@ -1101,7 +1101,7 @@
264 eth1 = factory.make_Interface(
265 name="eth1", mac_address=ETH1_MAC, node=node)
266
267- vlanif = factory.make_Interface(
268+ factory.make_Interface(
269 INTERFACE_TYPE.VLAN, mac_address=ETH0_MAC, parents=[eth0],
270 node=node)
271 factory.make_Interface(
272@@ -1110,8 +1110,7 @@
273
274 update_node_network_information(node, self.IP_ADDR_OUTPUT, 0)
275 node_interfaces = Interface.objects.filter(node=node)
276- self.assert_expected_interfaces_and_macs_exist(
277- node_interfaces, {vlanif.name: ETH0_MAC, BOND_NAME: ETH1_MAC})
278+ self.assert_expected_interfaces_and_macs_exist(node_interfaces)
279
280 def test__interface_names_changed(self):
281 # Note: the MACs here are swapped compared to their expected values.
282@@ -1182,26 +1181,22 @@
283 # Make sure the interface that no longer exists has been removed.
284 self.assertIsNone(reload_object(eth3))
285
286- def test__does_not_delete_virtual_interfaces_with_unique_mac(self):
287+ def test__deletes_virtual_interfaces_with_unique_mac(self):
288 ETH0_MAC = self.EXPECTED_INTERFACES['eth0'].get_raw()
289 ETH1_MAC = self.EXPECTED_INTERFACES['eth1'].get_raw()
290 BOND_MAC = '00:00:00:00:01:02'
291 node = factory.make_Node()
292 eth0 = factory.make_Interface(mac_address=ETH0_MAC, node=node)
293 eth1 = factory.make_Interface(mac_address=ETH1_MAC, node=node)
294- vlan = factory.make_Interface(
295+ factory.make_Interface(
296 INTERFACE_TYPE.VLAN, node=node, parents=[eth0])
297- bond = factory.make_Interface(
298+ factory.make_Interface(
299 INTERFACE_TYPE.BOND, mac_address=BOND_MAC, node=node,
300 parents=[eth1])
301
302 update_node_network_information(node, self.IP_ADDR_OUTPUT, 0)
303- # Freshen the other objects, since they may have changed names.
304- vlan = reload_object(vlan)
305- bond = reload_object(bond)
306 node_interfaces = Interface.objects.filter(node=node)
307- self.assert_expected_interfaces_and_macs_exist(
308- node_interfaces, {vlan.name: ETH0_MAC, bond.name: BOND_MAC})
309+ self.assert_expected_interfaces_and_macs_exist(node_interfaces)
310
311 def test__deletes_virtual_interfaces_linked_to_removed_macs(self):
312 VLAN_MAC = '00:00:00:00:01:01'
313
314=== modified file 'src/provisioningserver/refresh/node_info_scripts.py'
315--- src/provisioningserver/refresh/node_info_scripts.py 2017-04-22 02:54:57 +0000
316+++ src/provisioningserver/refresh/node_info_scripts.py 2017-05-18 09:01:59 +0000
317@@ -4,6 +4,7 @@
318 """Builtin node info scripts."""
319
320 __all__ = [
321+ 'IPADDR_OUTPUT_NAME',
322 'NODE_INFO_SCRIPTS',
323 'LIST_MODALIASES_OUTPUT_NAME',
324 'LLDP_OUTPUT_NAME',
325@@ -23,6 +24,9 @@
326 # Name of the file where the node info scripts store LLDP output.
327 LLDP_OUTPUT_NAME = '99-maas-02-capture-lldp'
328
329+# Name of the file where the node info scripts store ip addr output.
330+IPADDR_OUTPUT_NAME = '99-maas-03-network-interfaces'
331+
332
333 def make_function_call_script(function, *args, **kwargs):
334 """Compose a Python script that calls the given function.
335@@ -642,7 +646,7 @@
336 'timeout': timedelta(minutes=3),
337 'run_on_controller': False,
338 }),
339- ('99-maas-03-network-interfaces', {
340+ (IPADDR_OUTPUT_NAME, {
341 'content': IPADDR_SCRIPT.encode('ascii'),
342 'hook': null_hook,
343 'timeout': timedelta(seconds=10),