Merge ~ltrager/maas:release_auto_ip_on_power_off into maas:master

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: de74f32fff29c1d05368ef99806464956346ea8a
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:release_auto_ip_on_power_off
Merge into: maas:master
Diff against target: 192 lines (+134/-4)
3 files modified
src/maasserver/models/signals/nodes.py (+29/-2)
src/maasserver/models/signals/tests/test_nodes.py (+101/-1)
src/maasserver/models/tests/test_node.py (+4/-1)
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
MAAS Lander Needs Fixing
Review via email: mp+371380@code.launchpad.net

Commit message

Release AUTOIP addresses when powered off in a non-use status

Commissioning and testing now require AUTOIP addresses to be assigned to
allow for network validation testing. The signal to end these statuses
comes before the machine is actually turned off. Further users may keep
the machine on after commissioning/testing to debug script issues. To
ensure that AUTOIPs are released MAAS will now release any assigned
AUTOIP if the machine isn't currently in a status that needs an IP and
is off.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b release_auto_ip_on_power_off lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/6186/console
COMMIT: 979449786d721e16c0e607e6744933a63efcae87

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

Looks good.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
de74f32... by Lee Trager

Fix broken test

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/models/signals/nodes.py b/src/maasserver/models/signals/nodes.py
index c6b4a33..a1d2f06 100644
--- a/src/maasserver/models/signals/nodes.py
+++ b/src/maasserver/models/signals/nodes.py
@@ -1,4 +1,4 @@
1# Copyright 2015-2017 Canonical Ltd. This software is licensed under the1# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Respond to node changes."""4"""Respond to node changes."""
@@ -13,7 +13,10 @@ from django.db.models.signals import (
13 pre_delete,13 pre_delete,
14 pre_save,14 pre_save,
15)15)
16from maasserver.enum import NODE_STATUS16from maasserver.enum import (
17 NODE_STATUS,
18 POWER_STATE,
19)
17from maasserver.models import (20from maasserver.models import (
18 Controller,21 Controller,
19 Device,22 Device,
@@ -136,5 +139,29 @@ for klass in NODE_CLASSES:
136 sender=klass)139 sender=klass)
137140
138141
142def release_auto_ips(node, old_values, deleted=False):
143 """Release auto assigned IPs once the machine is off and ready."""
144 # Only machines use AUTO_IPs.
145 if not node.is_machine:
146 return
147 # Commissioning and testing may acquire an AUTO_IP for network testing.
148 # Users may keep the machine on after commissioning/testing to debug
149 # issues where the assigned IP is still in use. Wait till the machine
150 # is off and not in a status which will have an IP in use.
151 if (node.power_state == POWER_STATE.OFF and node.status not in (
152 NODE_STATUS.COMMISSIONING,
153 NODE_STATUS.DEPLOYED,
154 NODE_STATUS.DEPLOYING,
155 NODE_STATUS.DISK_ERASING,
156 NODE_STATUS.RESCUE_MODE,
157 NODE_STATUS.ENTERING_RESCUE_MODE,
158 NODE_STATUS.TESTING)):
159 node.release_interface_config()
160
161
162for klass in [Node, Machine]:
163 signals.watch_fields(release_auto_ips, klass, ['power_state'])
164
165
139# Enable all signals by default.166# Enable all signals by default.
140signals.enable()167signals.enable()
diff --git a/src/maasserver/models/signals/tests/test_nodes.py b/src/maasserver/models/signals/tests/test_nodes.py
index 5e8ab15..d909b4a 100644
--- a/src/maasserver/models/signals/tests/test_nodes.py
+++ b/src/maasserver/models/signals/tests/test_nodes.py
@@ -1,4 +1,4 @@
1# Copyright 2015-2017 Canonical Ltd. This software is licensed under the1# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test the behaviour of node signals."""4"""Test the behaviour of node signals."""
@@ -8,9 +8,17 @@ __all__ = []
8import random8import random
99
10from maasserver.enum import (10from maasserver.enum import (
11 IPADDRESS_TYPE,
11 NODE_STATUS,12 NODE_STATUS,
12 NODE_STATUS_CHOICES,13 NODE_STATUS_CHOICES,
13 NODE_TYPE,14 NODE_TYPE,
15 NODE_TYPE_CHOICES,
16 POWER_STATE,
17 POWER_STATE_CHOICES,
18)
19from maasserver.models import (
20 Node,
21 StaticIPAddress,
14)22)
15from maasserver.models.service import (23from maasserver.models.service import (
16 RACK_SERVICES,24 RACK_SERVICES,
@@ -212,3 +220,95 @@ class TestNodeCreateServices(MAASServerTestCase):
212 self.assertThat(220 self.assertThat(
213 {service.name for service in services},221 {service.name for service in services},
214 Equals(REGION_SERVICES))222 Equals(REGION_SERVICES))
223
224
225class TestNodeReleasesAutoIPs(MAASServerTestCase):
226 """Test that auto ips are released when node power is off."""
227
228 def __init__(self, *args, **kwargs):
229 self.reserved_statuses = [
230 NODE_STATUS.COMMISSIONING,
231 NODE_STATUS.DEPLOYED,
232 NODE_STATUS.DEPLOYING,
233 NODE_STATUS.DISK_ERASING,
234 NODE_STATUS.RESCUE_MODE,
235 NODE_STATUS.ENTERING_RESCUE_MODE,
236 NODE_STATUS.TESTING,
237 ]
238 self.scenarios = [
239 (status_label, {'status': status})
240 for status, status_label in NODE_STATUS_CHOICES
241 if status not in self.reserved_statuses
242 ]
243 super().__init__(*args, **kwargs)
244
245 def test__releases_interface_config_when_turned_off(self):
246 machine = factory.make_Machine_with_Interface_on_Subnet(
247 status=random.choice(self.reserved_statuses),
248 power_state=POWER_STATE.ON)
249 for interface in machine.interface_set.all():
250 interface.claim_auto_ips()
251
252 # Hack to get around node transition model
253 Node.objects.filter(id=machine.id).update(status=self.status)
254 machine = reload_object(machine)
255 machine.power_state = POWER_STATE.OFF
256 machine.save()
257
258 for ip in StaticIPAddress.objects.filter(
259 interface__node=machine, alloc_type=IPADDRESS_TYPE.AUTO):
260 self.assertIsNone(ip.ip)
261
262 def test__does_nothing_if_not_off(self):
263 machine = factory.make_Machine_with_Interface_on_Subnet(
264 status=random.choice(self.reserved_statuses),
265 power_state=POWER_STATE.ON)
266 for interface in machine.interface_set.all():
267 interface.claim_auto_ips()
268
269 # Hack to get around node transition model
270 Node.objects.filter(id=machine.id).update(status=self.status)
271 machine = reload_object(machine)
272 machine.power_state = factory.pick_choice(
273 POWER_STATE_CHOICES, but_not=[POWER_STATE.OFF])
274 machine.save()
275
276 for ip in StaticIPAddress.objects.filter(
277 interface__node=machine, alloc_type=IPADDRESS_TYPE.AUTO):
278 self.assertIsNotNone(ip.ip)
279
280 def test__does_nothing_if_reserved_status(self):
281 machine = factory.make_Machine_with_Interface_on_Subnet(
282 status=self.status, power_state=POWER_STATE.ON)
283 for interface in machine.interface_set.all():
284 interface.claim_auto_ips()
285
286 # Hack to get around node transition model
287 Node.objects.filter(id=machine.id).update(
288 status=random.choice(self.reserved_statuses))
289 machine = reload_object(machine)
290 machine.power_state = POWER_STATE.OFF
291 machine.save()
292
293 for ip in StaticIPAddress.objects.filter(
294 interface__node=machine, alloc_type=IPADDRESS_TYPE.AUTO):
295 self.assertIsNotNone(ip.ip)
296
297 def test__does_nothing_if_not_machine(self):
298 node = factory.make_Node_with_Interface_on_Subnet(
299 node_type=factory.pick_choice(
300 NODE_TYPE_CHOICES, but_not=[NODE_TYPE.MACHINE]),
301 status=random.choice(self.reserved_statuses),
302 power_state=POWER_STATE.ON)
303 for interface in node.interface_set.all():
304 interface.claim_auto_ips()
305
306 # Hack to get around node transition model
307 Node.objects.filter(id=node.id).update(status=self.status)
308 node = reload_object(node)
309 node.power_state = POWER_STATE.OFF
310 node.save()
311
312 for ip in StaticIPAddress.objects.filter(
313 interface__node=node, alloc_type=IPADDRESS_TYPE.AUTO):
314 self.assertIsNotNone(ip.ip)
diff --git a/src/maasserver/models/tests/test_node.py b/src/maasserver/models/tests/test_node.py
index f428cc4..92d391f 100644
--- a/src/maasserver/models/tests/test_node.py
+++ b/src/maasserver/models/tests/test_node.py
@@ -3868,7 +3868,10 @@ class TestNode(MAASServerTestCase):
3868 release = self.patch_autospec(node, 'release_interface_config')3868 release = self.patch_autospec(node, 'release_interface_config')
3869 self.patch(Node, '_clear_status_expires')3869 self.patch(Node, '_clear_status_expires')
3870 node.update_power_state(POWER_STATE.OFF)3870 node.update_power_state(POWER_STATE.OFF)
3871 self.assertThat(release, MockCalledOnceWith())3871 # release_interface_config() is called once by update_power_state and
3872 # a second time by the release_auto_ips() signal. Whichever runs
3873 # second is a noop but is needed for commissioning/testing.
3874 self.assertThat(release, MockCallsMatch(call(), call()))
38723875
3873 def test_update_power_state_doesnt_release_interface_config_if_on(self):3876 def test_update_power_state_doesnt_release_interface_config_if_on(self):
3874 node = factory.make_Node(3877 node = factory.make_Node(

Subscribers

People subscribed via source and target branches