Merge ~ltrager/maas:config_network_failed_testing into maas:master

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: 2ef17cc18e3985070288bec89a9fe5da0fc6385b
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:config_network_failed_testing
Merge into: maas:master
Diff against target: 721 lines (+126/-111)
8 files modified
src/maasserver/api/interfaces.py (+4/-1)
src/maasserver/api/machines.py (+12/-9)
src/maasserver/api/tests/test_interfaces.py (+69/-87)
src/maasserver/api/tests/test_machine.py (+15/-6)
src/maasserver/forms/clone.py (+2/-1)
src/maasserver/forms/tests/test_clone.py (+19/-6)
src/maasserver/static/js/angular/controllers/node_details_networking.js (+3/-1)
src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js (+2/-0)
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
MAAS Lander Needs Fixing
Review via email: mp+371552@code.launchpad.net

Commit message

Allow network configuration to be changed when in FAILED_TESTING.

When network-validation fails the machine will be put into FAILED_TESTING.
The failure may be due to user configuration so allow users to change
configuration without overriding failed tests.

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

UNIT TESTS
-b config_network_failed_testing 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/6222/console
COMMIT: 3b28fb91bd6c0ee09783e0f96e6bc2b9bea1dff0

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

That's a lot of tests for such a simple change... I hate that!

review: Approve
Revision history for this message
Adam Collard (adam-collard) :
517f9d5... by Lee Trager

Merge branch 'master' into config_network_failed_testing

2ef17cc... by Lee Trager

adam-collard fixes

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/api/interfaces.py b/src/maasserver/api/interfaces.py
2index 0232d08..099b50a 100644
3--- a/src/maasserver/api/interfaces.py
4+++ b/src/maasserver/api/interfaces.py
5@@ -98,7 +98,10 @@ INTERFACES_PREFETCH = [
6 'children_relationships__child__vlan'),
7 ]
8
9-ALLOWED_STATES = (NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN)
10+ALLOWED_STATES = (
11+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING,
12+ NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN,
13+)
14
15
16 def raise_error_for_invalid_state_on_allocated_operations(
17diff --git a/src/maasserver/api/machines.py b/src/maasserver/api/machines.py
18index bb50d67..b9e7d9f 100644
19--- a/src/maasserver/api/machines.py
20+++ b/src/maasserver/api/machines.py
21@@ -1226,10 +1226,11 @@ class MachineHandler(NodeHandler, OwnerDataMixin, PowerMixin):
22 machine = self.model.objects.get_node_or_404(
23 system_id=system_id, user=request.user,
24 perm=NodePermission.admin)
25- if machine.status != NODE_STATUS.READY:
26+ if machine.status not in {
27+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING}:
28 raise NodeStateViolation(
29- "Machine must be in a ready state to restore networking "
30- "configuration")
31+ "Machine must be in a ready or failed testing state to "
32+ "restore networking configuration")
33 machine.restore_network_interfaces()
34 machine.set_initial_networking_configuration()
35 return reload_object(machine)
36@@ -1260,10 +1261,11 @@ class MachineHandler(NodeHandler, OwnerDataMixin, PowerMixin):
37 machine = self.model.objects.get_node_or_404(
38 system_id=system_id, user=request.user,
39 perm=NodePermission.admin)
40- if machine.status != NODE_STATUS.READY:
41+ if machine.status not in {
42+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING}:
43 raise NodeStateViolation(
44- "Machine must be in a ready state to restore storage "
45- "configuration.")
46+ "Machine must be in a ready or failed testing state to "
47+ "restore storage configuration.")
48 machine.set_default_storage_layout()
49 return reload_object(machine)
50
51@@ -1293,10 +1295,11 @@ class MachineHandler(NodeHandler, OwnerDataMixin, PowerMixin):
52 machine = self.model.objects.get_node_or_404(
53 system_id=system_id, user=request.user,
54 perm=NodePermission.admin)
55- if machine.status != NODE_STATUS.READY:
56+ if machine.status not in {
57+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING}:
58 raise NodeStateViolation(
59- "Machine must be in a ready state to restore default "
60- "networking and storage configuration.")
61+ "Machine must be in a ready or failed testing state to "
62+ "restore default networking and storage configuration.")
63 machine.set_default_storage_layout()
64 machine.restore_network_interfaces()
65 machine.set_initial_networking_configuration()
66diff --git a/src/maasserver/api/tests/test_interfaces.py b/src/maasserver/api/tests/test_interfaces.py
67index 3e40bd7..fc27dad 100644
68--- a/src/maasserver/api/tests/test_interfaces.py
69+++ b/src/maasserver/api/tests/test_interfaces.py
70@@ -1,4 +1,4 @@
71-# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
72+# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
73 # GNU Affero General Public License version 3 (see the file LICENSE).
74
75 """Tests for NodeInterfaces API."""
76@@ -14,6 +14,7 @@ from maasserver.enum import (
77 INTERFACE_TYPE,
78 IPADDRESS_TYPE,
79 NODE_STATUS,
80+ NODE_STATUS_CHOICES,
81 NODE_TYPE,
82 )
83 from maasserver.models import Interface
84@@ -37,25 +38,14 @@ from testtools.matchers import (
85 )
86
87
88-STATUSES = (
89- NODE_STATUS.NEW,
90- NODE_STATUS.COMMISSIONING,
91- NODE_STATUS.FAILED_COMMISSIONING,
92- NODE_STATUS.MISSING,
93- NODE_STATUS.RESERVED,
94- NODE_STATUS.DEPLOYING,
95- NODE_STATUS.DEPLOYED,
96- NODE_STATUS.RETIRED,
97- NODE_STATUS.FAILED_DEPLOYMENT,
98- NODE_STATUS.RELEASING,
99- NODE_STATUS.FAILED_RELEASING,
100- NODE_STATUS.DISK_ERASING,
101- NODE_STATUS.FAILED_DISK_ERASING,
102- NODE_STATUS.ENTERING_RESCUE_MODE,
103- NODE_STATUS.FAILED_ENTERING_RESCUE_MODE,
104- NODE_STATUS.RESCUE_MODE,
105- NODE_STATUS.EXITING_RESCUE_MODE,
106- NODE_STATUS.FAILED_EXITING_RESCUE_MODE,
107+EDITABLE_STATUSES = (
108+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING,
109+ NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN,
110+)
111+
112+BLOCKED_STATUSES = (
113+ status for status, _ in NODE_STATUS_CHOICES
114+ if status not in EDITABLE_STATUSES
115 )
116
117
118@@ -174,7 +164,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
119
120 def test_create_physical(self):
121 self.become_admin()
122- for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):
123+ for status in EDITABLE_STATUSES:
124 node = factory.make_Node(status=status)
125 mac = factory.make_mac_address()
126 name = factory.make_name("eth")
127@@ -240,8 +230,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
128
129 def test_create_physical_disabled(self):
130 self.become_admin()
131- for status in (
132- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
133+ for status in EDITABLE_STATUSES:
134 node = factory.make_Node(status=status)
135 mac = factory.make_mac_address()
136 name = factory.make_name("eth")
137@@ -288,9 +277,9 @@ class TestInterfacesAPI(APITestCase.ForUser):
138 self.assertEqual(
139 http.client.FORBIDDEN, response.status_code, response.content)
140
141- def test_create_physical_409_when_not_ready_or_broken(self):
142+ def test_create_physical_409_when_invalid_status(self):
143 self.become_admin()
144- for status in STATUSES:
145+ for status in BLOCKED_STATUSES:
146 node = factory.make_Node(status=status)
147 mac = factory.make_mac_address()
148 name = factory.make_name("eth")
149@@ -307,7 +296,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
150
151 def test_create_physical_requires_mac(self):
152 self.become_admin()
153- node = factory.make_Node(status=NODE_STATUS.READY)
154+ node = factory.make_Node(status=random.choice(EDITABLE_STATUSES))
155 uri = get_interfaces_uri(node)
156 response = self.client.post(uri, {
157 "op": "create_physical",
158@@ -320,7 +309,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
159
160 def test_create_physical_doesnt_allow_mac_already_register(self):
161 self.become_admin()
162- node = factory.make_Node(status=NODE_STATUS.READY)
163+ node = factory.make_Node(status=random.choice(EDITABLE_STATUSES))
164 interface_on_other_node = factory.make_Interface(
165 INTERFACE_TYPE.PHYSICAL)
166 name = factory.make_name("eth")
167@@ -342,8 +331,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
168
169 def test_create_bond(self):
170 self.become_admin()
171- for status in (
172- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
173+ for status in EDITABLE_STATUSES:
174 node = factory.make_Node(status=status)
175 vlan = factory.make_VLAN()
176 parent_1_iface = factory.make_Interface(
177@@ -413,9 +401,9 @@ class TestInterfacesAPI(APITestCase.ForUser):
178 self.assertEqual(
179 http.client.FORBIDDEN, response.status_code, response.content)
180
181- def test_create_bond_409_when_not_ready_or_broken(self):
182+ def test_create_bond_409_when_invalid_status(self):
183 self.become_admin()
184- for status in STATUSES:
185+ for status in BLOCKED_STATUSES:
186 node = factory.make_Node(status=status)
187 vlan = factory.make_VLAN()
188 parent_1_iface = factory.make_Interface(
189@@ -436,7 +424,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
190
191 def test_create_bond_requires_name_vlan_and_parents(self):
192 self.become_admin()
193- node = factory.make_Node(status=NODE_STATUS.READY)
194+ node = factory.make_Node(status=random.choice(EDITABLE_STATUSES))
195 uri = get_interfaces_uri(node)
196 response = self.client.post(uri, {
197 "op": "create_bond",
198@@ -452,7 +440,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
199
200 def test_create_vlan(self):
201 self.become_admin()
202- node = factory.make_Node(status=NODE_STATUS.READY)
203+ node = factory.make_Node(status=random.choice(EDITABLE_STATUSES))
204 untagged_vlan = factory.make_VLAN()
205 parent_iface = factory.make_Interface(
206 INTERFACE_TYPE.PHYSICAL, vlan=untagged_vlan, node=node)
207@@ -495,7 +483,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
208 http.client.NOT_FOUND, response.status_code, response.content)
209
210 def test_create_vlan_requires_admin(self):
211- node = factory.make_Node(status=NODE_STATUS.READY)
212+ node = factory.make_Node(status=random.choice(EDITABLE_STATUSES))
213 untagged_vlan = factory.make_VLAN()
214 parent_iface = factory.make_Interface(
215 INTERFACE_TYPE.PHYSICAL, vlan=untagged_vlan, node=node)
216@@ -511,7 +499,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
217
218 def test_create_vlan_requires_vlan_and_parent(self):
219 self.become_admin()
220- node = factory.make_Node(status=NODE_STATUS.READY)
221+ node = factory.make_Node(status=random.choice(EDITABLE_STATUSES))
222 uri = get_interfaces_uri(node)
223 response = self.client.post(uri, {
224 "op": "create_vlan",
225@@ -527,7 +515,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
226 def test_create_bridge(self):
227 self.become_admin()
228 name = factory.make_name("br")
229- node = factory.make_Node(status=NODE_STATUS.READY)
230+ node = factory.make_Node(status=random.choice(EDITABLE_STATUSES))
231 untagged_vlan = factory.make_VLAN()
232 parent_iface = factory.make_Interface(
233 INTERFACE_TYPE.PHYSICAL, vlan=untagged_vlan, node=node)
234@@ -571,7 +559,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
235 http.client.NOT_FOUND, response.status_code, response.content)
236
237 def test_create_bridge_requires_admin(self):
238- node = factory.make_Node(status=NODE_STATUS.READY)
239+ node = factory.make_Node(status=random.choice(EDITABLE_STATUSES))
240 untagged_vlan = factory.make_VLAN()
241 parent_iface = factory.make_Interface(
242 INTERFACE_TYPE.PHYSICAL, vlan=untagged_vlan, node=node)
243@@ -586,7 +574,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
244
245 def test_create_bridge_requires_name_and_parent(self):
246 self.become_admin()
247- node = factory.make_Node(status=NODE_STATUS.READY)
248+ node = factory.make_Node(status=random.choice(EDITABLE_STATUSES))
249 uri = get_interfaces_uri(node)
250 response = self.client.post(uri, {
251 "op": "create_bridge",
252@@ -603,8 +591,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
253 def test_create_acquired_bridge(self):
254 self.become_admin()
255 name = factory.make_name("br")
256- node = factory.make_Node(
257- status=NODE_STATUS.ALLOCATED, owner=self.user)
258+ node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=self.user)
259 parent_fabric = factory.make_Fabric()
260 parent_vlan = parent_fabric.get_default_vlan()
261 parent_iface = factory.make_Interface(
262@@ -651,7 +638,7 @@ class TestInterfacesAPI(APITestCase.ForUser):
263
264 def test_create_acquired_bridge_not_allowed_in_ready(self):
265 name = factory.make_name("br")
266- node = factory.make_Node(status=NODE_STATUS.READY)
267+ node = factory.make_Node(status=random.choice(EDITABLE_STATUSES))
268 parent_fabric = factory.make_Fabric()
269 parent_vlan = parent_fabric.get_default_vlan()
270 parent_iface = factory.make_Interface(
271@@ -933,8 +920,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
272
273 def test_update_physical_interface(self):
274 self.become_admin()
275- for status in (
276- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
277+ for status in EDITABLE_STATUSES:
278 node = factory.make_Node(status=status)
279 interface = factory.make_Interface(
280 INTERFACE_TYPE.PHYSICAL, node=node)
281@@ -984,8 +970,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
282
283 def test_update_bond_interface(self):
284 self.become_admin()
285- for status in (
286- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
287+ for status in EDITABLE_STATUSES:
288 node = factory.make_Node(status=status)
289 bond, [nic_0, nic_1], [vlan_10, vlan_11] = make_complex_interface(
290 node)
291@@ -1000,8 +985,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
292
293 def test_update_vlan_interface(self):
294 self.become_admin()
295- for status in (
296- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
297+ for status in EDITABLE_STATUSES:
298 node = factory.make_Node(status=status)
299 bond, [nic_0, nic_1], [vlan_10, vlan_11] = make_complex_interface(
300 node)
301@@ -1030,11 +1014,11 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
302
303 def test_update_409_when_not_ready_broken_or_deployed(self):
304 self.become_admin()
305- statuses = list(STATUSES)
306- # Update is the only call that a deployed node can have called on
307- # its interface.
308- statuses.remove(NODE_STATUS.DEPLOYED)
309- for status in statuses:
310+ for status in BLOCKED_STATUSES:
311+ # Update is the only call that a deployed node can have called on
312+ # its interface.
313+ if status == NODE_STATUS.DEPLOYED:
314+ continue
315 node = factory.make_Node(interface=True, status=status)
316 interface = factory.make_Interface(
317 INTERFACE_TYPE.PHYSICAL, node=node)
318@@ -1048,8 +1032,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
319
320 def test_delete_deletes_interface(self):
321 self.become_admin()
322- for status in (
323- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
324+ for status in EDITABLE_STATUSES:
325 node = factory.make_Node(interface=True, status=status)
326 interface = node.get_boot_interface()
327 uri = get_interface_uri(interface)
328@@ -1089,9 +1072,9 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
329 self.assertEqual(
330 http.client.NOT_FOUND, response.status_code, response.content)
331
332- def test_delete_409_when_not_ready_or_broken(self):
333+ def test_delete_409_when_invalid_status(self):
334 self.become_admin()
335- for status in STATUSES:
336+ for status in BLOCKED_STATUSES:
337 node = factory.make_Node(interface=True, status=status)
338 interface = node.get_boot_interface()
339 uri = get_interface_uri(interface)
340@@ -1104,8 +1087,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
341 # This just tests that the form is saved and the updated interface
342 # is returned.
343 self.become_admin()
344- for status in (
345- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
346+ for status in EDITABLE_STATUSES:
347 node = factory.make_Node(interface=True, status=status)
348 interface = node.get_boot_interface()
349 uri = get_interface_uri(interface)
350@@ -1144,7 +1126,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
351
352 def test_link_subnet_allows_subnet_with_link_up(self):
353 self.become_admin()
354- node = factory.make_Node(owner=self.user, status=NODE_STATUS.READY)
355+ node = factory.make_Node(
356+ owner=self.user, status=random.choice(EDITABLE_STATUSES))
357 interface = factory.make_Interface(
358 INTERFACE_TYPE.PHYSICAL, node=node)
359 subnet = factory.make_Subnet(vlan=interface.vlan)
360@@ -1166,7 +1149,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
361
362 def test_link_subnet_allows_link_up_subnet_to_be_cleared(self):
363 self.become_admin()
364- node = factory.make_Node(owner=self.user, status=NODE_STATUS.READY)
365+ node = factory.make_Node(
366+ owner=self.user, status=random.choice(EDITABLE_STATUSES))
367 interface = factory.make_Interface(
368 INTERFACE_TYPE.PHYSICAL, node=node)
369 subnet = factory.make_Subnet(vlan=interface.vlan)
370@@ -1191,7 +1175,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
371
372 def test_link_subnet_allows_link_up_subnet_to_be_changed(self):
373 self.become_admin()
374- node = factory.make_Node(owner=self.user, status=NODE_STATUS.READY)
375+ node = factory.make_Node(
376+ owner=self.user, status=random.choice(EDITABLE_STATUSES))
377 interface = factory.make_Interface(
378 INTERFACE_TYPE.PHYSICAL, node=node)
379 subnet = factory.make_Subnet(vlan=interface.vlan)
380@@ -1219,7 +1204,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
381
382 def test_link_subnet_disallows_subnets_on_another_vlan(self):
383 self.become_admin()
384- node = factory.make_Node(owner=self.user, status=NODE_STATUS.READY)
385+ node = factory.make_Node(
386+ owner=self.user, status=random.choice(EDITABLE_STATUSES))
387 interface = factory.make_Interface(
388 INTERFACE_TYPE.PHYSICAL, node=node)
389 vlan2 = factory.make_VLAN()
390@@ -1241,7 +1227,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
391
392 def test_link_subnet_allows_link_up_subnet_to_be_forcibly_changed(self):
393 self.become_admin()
394- node = factory.make_Node(owner=self.user, status=NODE_STATUS.READY)
395+ node = factory.make_Node(
396+ owner=self.user, status=random.choice(EDITABLE_STATUSES))
397 interface = factory.make_Interface(
398 INTERFACE_TYPE.PHYSICAL, node=node)
399 vlan2 = factory.make_VLAN()
400@@ -1271,7 +1258,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
401
402 def test_link_subnet_force_link_up_deletes_existing_links(self):
403 self.become_admin()
404- node = factory.make_Node(owner=self.user, status=NODE_STATUS.READY)
405+ node = factory.make_Node(
406+ owner=self.user, status=random.choice(EDITABLE_STATUSES))
407 interface = factory.make_Interface(
408 INTERFACE_TYPE.PHYSICAL, node=node)
409 vlan2 = factory.make_VLAN()
410@@ -1301,7 +1289,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
411
412 def test_link_subnet_without_force_link_up_returns_bad_request(self):
413 self.become_admin()
414- node = factory.make_Node(owner=self.user, status=NODE_STATUS.READY)
415+ node = factory.make_Node(
416+ owner=self.user, status=random.choice(EDITABLE_STATUSES))
417 interface = factory.make_Interface(
418 INTERFACE_TYPE.PHYSICAL, node=node)
419 vlan2 = factory.make_VLAN()
420@@ -1343,8 +1332,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
421
422 def test_link_subnet_raises_error(self):
423 self.become_admin()
424- for status in (
425- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
426+ for status in EDITABLE_STATUSES:
427 node = factory.make_Node(interface=True, status=status)
428 interface = node.get_boot_interface()
429 uri = get_interface_uri(interface)
430@@ -1368,9 +1356,9 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
431 self.assertEqual(
432 http.client.FORBIDDEN, response.status_code, response.content)
433
434- def test_link_subnet_409_when_not_ready_or_broken(self):
435+ def test_link_subnet_409_when_invalid_status(self):
436 self.become_admin()
437- for status in STATUSES:
438+ for status in BLOCKED_STATUSES:
439 node = factory.make_Node(interface=True, status=status)
440 interface = node.get_boot_interface()
441 uri = get_interface_uri(interface)
442@@ -1386,8 +1374,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
443 # This just tests that the form is saved and the updated interface
444 # is returned.
445 self.become_admin()
446- for status in (
447- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
448+ for status in EDITABLE_STATUSES:
449 node = factory.make_Node(interface=True, status=status)
450 interface = node.get_boot_interface()
451 subnet = factory.make_Subnet()
452@@ -1424,8 +1411,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
453
454 def test_unlink_subnet_raises_error(self):
455 self.become_admin()
456- for status in (
457- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
458+ for status in EDITABLE_STATUSES:
459 node = factory.make_Node(interface=True, status=status)
460 interface = node.get_boot_interface()
461 uri = get_interface_uri(interface)
462@@ -1449,9 +1435,9 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
463 self.assertEqual(
464 http.client.FORBIDDEN, response.status_code, response.content)
465
466- def test_unlink_subnet_409_when_not_ready_or_broken(self):
467+ def test_unlink_subnet_409_when_invalid_status(self):
468 self.become_admin()
469- for status in STATUSES:
470+ for status in BLOCKED_STATUSES:
471 node = factory.make_Node(interface=True, status=status)
472 interface = node.get_boot_interface()
473 uri = get_interface_uri(interface)
474@@ -1466,8 +1452,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
475 # This just tests that the form is saved and the updated interface
476 # is returned.
477 self.become_admin()
478- for status in (
479- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
480+ for status in EDITABLE_STATUSES:
481 node = factory.make_Node(interface=True, status=status)
482 interface = node.get_boot_interface()
483 subnet = factory.make_Subnet()
484@@ -1493,9 +1478,9 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
485 self.assertEqual(
486 http.client.FORBIDDEN, response.status_code, response.content)
487
488- def test_disconnect_409_when_not_ready_or_broken(self):
489+ def test_disconnect_409_when_invalid_status(self):
490 self.become_admin()
491- for status in STATUSES:
492+ for status in BLOCKED_STATUSES:
493 node = factory.make_Node(interface=True, status=status)
494 interface = node.get_boot_interface()
495 uri = get_interface_uri(interface)
496@@ -1509,8 +1494,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
497 # The form that is used is fully tested in test_forms_interface_link.
498 # This just tests that the form is saved and the node link is created.
499 self.become_admin()
500- for status in (
501- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
502+ for status in EDITABLE_STATUSES:
503 node = factory.make_Node(interface=True, status=status)
504 interface = node.get_boot_interface()
505 network = factory.make_ipv4_network()
506@@ -1532,8 +1516,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
507 # The form that is used is fully tested in test_forms_interface_link.
508 # This just tests that the form is saved and the node link is created.
509 self.become_admin()
510- for status in (
511- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
512+ for status in EDITABLE_STATUSES:
513 node = factory.make_Node(interface=True, status=status)
514 interface = node.get_boot_interface()
515 network = factory.make_ipv6_network()
516@@ -1553,8 +1536,7 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
517
518 def test_set_default_gateway_raises_error(self):
519 self.become_admin()
520- for status in (
521- NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
522+ for status in EDITABLE_STATUSES:
523 node = factory.make_Node(interface=True, status=status)
524 interface = node.get_boot_interface()
525 uri = get_interface_uri(interface)
526@@ -1578,9 +1560,9 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
527 self.assertEqual(
528 http.client.FORBIDDEN, response.status_code, response.content)
529
530- def test_set_default_gateway_409_when_not_ready_or_broken(self):
531+ def test_set_default_gateway_409_when_invalid_status(self):
532 self.become_admin()
533- for status in STATUSES:
534+ for status in BLOCKED_STATUSES:
535 node = factory.make_Node(interface=True, status=status)
536 interface = node.get_boot_interface()
537 uri = get_interface_uri(interface)
538diff --git a/src/maasserver/api/tests/test_machine.py b/src/maasserver/api/tests/test_machine.py
539index 196b8b2..cc2c8a6 100644
540--- a/src/maasserver/api/tests/test_machine.py
541+++ b/src/maasserver/api/tests/test_machine.py
542@@ -2675,7 +2675,8 @@ class TestRestoreNetworkingConfiguration(APITestCase.ForUser):
543
544 def test_restore_networking_configuration(self):
545 self.become_admin()
546- machine = factory.make_Machine(status=NODE_STATUS.READY)
547+ machine = factory.make_Machine(status=choice([
548+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]))
549 mock_restore_network_interfaces = self.patch(
550 node_module.Machine, 'restore_network_interfaces')
551 mock_set_initial_networking_config = self.patch(
552@@ -2699,7 +2700,9 @@ class TestRestoreNetworkingConfiguration(APITestCase.ForUser):
553
554 def test_restore_networking_configuration_checks_machine_status(self):
555 self.become_admin()
556- machine = factory.make_Machine(status=NODE_STATUS.DEPLOYED)
557+ machine = factory.make_Machine(status=factory.pick_choice(
558+ NODE_STATUS_CHOICES,
559+ but_not=[NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]))
560 mock_set_initial_networking_config = self.patch(
561 node_module.Machine, 'set_initial_networking_configuration')
562 response = self.client.post(
563@@ -2719,7 +2722,8 @@ class TestRestoreStorageConfiguration(APITestCase.ForUser):
564
565 def test_restore_storage_configuration(self):
566 self.become_admin()
567- machine = factory.make_Machine(status=NODE_STATUS.READY)
568+ machine = factory.make_Machine(status=choice([
569+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]))
570 mock_set_default_storage_layout = self.patch(
571 node_module.Machine, 'set_default_storage_layout')
572 response = self.client.post(
573@@ -2740,7 +2744,9 @@ class TestRestoreStorageConfiguration(APITestCase.ForUser):
574
575 def test_restore_storage_configuration_checks_machine_status(self):
576 self.become_admin()
577- machine = factory.make_Machine(status=NODE_STATUS.DEPLOYED)
578+ machine = factory.make_Machine(status=factory.pick_choice(
579+ NODE_STATUS_CHOICES,
580+ but_not=[NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]))
581 mock_set_default_storage_layout = self.patch(
582 node_module.Machine, 'set_default_storage_layout')
583 response = self.client.post(
584@@ -2760,7 +2766,8 @@ class TestRestoreDefaultConfiguration(APITestCase.ForUser):
585
586 def test_restore_default_configuration(self):
587 self.become_admin()
588- machine = factory.make_Machine(status=NODE_STATUS.READY)
589+ machine = factory.make_Machine(status=choice([
590+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]))
591 mock_set_default_storage_layout = self.patch(
592 node_module.Machine, 'set_default_storage_layout')
593 mock_restore_network_interfaces = self.patch(
594@@ -2787,7 +2794,9 @@ class TestRestoreDefaultConfiguration(APITestCase.ForUser):
595
596 def test_restore_default_configuration_checks_machine_status(self):
597 self.become_admin()
598- machine = factory.make_Machine(status=NODE_STATUS.DEPLOYED)
599+ machine = factory.make_Machine(status=factory.pick_choice(
600+ NODE_STATUS_CHOICES,
601+ but_not=[NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]))
602 mock_restore_default_configuration = self.patch(
603 node_module.Machine, 'restore_default_configuration')
604 response = self.client.post(
605diff --git a/src/maasserver/forms/clone.py b/src/maasserver/forms/clone.py
606index 6999193..5d59af3 100644
607--- a/src/maasserver/forms/clone.py
608+++ b/src/maasserver/forms/clone.py
609@@ -47,7 +47,8 @@ class CloneForm(forms.Form):
610 self.fields['destinations'].base_field.queryset = (
611 Machine.objects.get_nodes(
612 self.user, NodePermission.admin,
613- from_nodes=Machine.objects.filter(status=NODE_STATUS.READY)))
614+ from_nodes=Machine.objects.filter(status__in={
615+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING})))
616
617 def clean(self):
618 """Validate that the form is valid and that the destinations can accept
619diff --git a/src/maasserver/forms/tests/test_clone.py b/src/maasserver/forms/tests/test_clone.py
620index aa8738a..6db8e2d 100644
621--- a/src/maasserver/forms/tests/test_clone.py
622+++ b/src/maasserver/forms/tests/test_clone.py
623@@ -5,6 +5,8 @@
624
625 __all__ = []
626
627+import random
628+
629 from maasserver.enum import NODE_STATUS
630 from maasserver.forms.clone import CloneForm
631 from maasserver.testing.factory import factory
632@@ -25,7 +27,8 @@ class TestCloneForm(MAASServerTestCase):
633
634 def test__source_destination_match_error(self):
635 user = factory.make_admin()
636- source = factory.make_Machine(status=NODE_STATUS.READY)
637+ source = factory.make_Machine(status=random.choice([
638+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]))
639 form = CloneForm(user, data={
640 'source': source.system_id,
641 'destinations': [source.system_id],
642@@ -44,7 +47,9 @@ class TestCloneForm(MAASServerTestCase):
643 factory.make_PhysicalBlockDevice(
644 node=source, size=8 * 1024 ** 3, name="sda")
645 destination = factory.make_Machine(
646- status=NODE_STATUS.READY, with_boot_disk=False)
647+ status=random.choice([
648+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]),
649+ with_boot_disk=False)
650 factory.make_PhysicalBlockDevice(
651 node=destination, size=4 * 1024 ** 3, name="sda")
652 form = CloneForm(user, data={
653@@ -65,7 +70,9 @@ class TestCloneForm(MAASServerTestCase):
654 source = factory.make_Machine(with_boot_disk=False)
655 factory.make_Interface(node=source, name='eth0')
656 destination = factory.make_Machine(
657- status=NODE_STATUS.READY, with_boot_disk=False)
658+ status=random.choice([
659+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]),
660+ with_boot_disk=False)
661 factory.make_Interface(node=destination, name='eth1')
662 form = CloneForm(user, data={
663 'source': source.system_id,
664@@ -87,7 +94,9 @@ class TestCloneForm(MAASServerTestCase):
665 node=source, size=8 * 1024 ** 3, name="sda")
666 factory.make_Interface(node=source, name='eth0')
667 destination = factory.make_Machine(
668- status=NODE_STATUS.READY, with_boot_disk=False)
669+ status=random.choice([
670+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]),
671+ with_boot_disk=False)
672 factory.make_PhysicalBlockDevice(
673 node=destination, size=8 * 1024 ** 3, name="sda")
674 factory.make_Interface(node=destination, name='eth0')
675@@ -112,12 +121,16 @@ class TestCloneForm(MAASServerTestCase):
676 node=source, size=8 * 1024 ** 3, name="sda")
677 factory.make_Interface(node=source, name='eth0')
678 destination1 = factory.make_Machine(
679- status=NODE_STATUS.READY, with_boot_disk=False)
680+ status=random.choice([
681+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]),
682+ with_boot_disk=False)
683 factory.make_PhysicalBlockDevice(
684 node=destination1, size=8 * 1024 ** 3, name="sda")
685 factory.make_Interface(node=destination1, name='eth0')
686 destination2 = factory.make_Machine(
687- status=NODE_STATUS.READY, with_boot_disk=False)
688+ status=random.choice([
689+ NODE_STATUS.READY, NODE_STATUS.FAILED_TESTING]),
690+ with_boot_disk=False)
691 factory.make_PhysicalBlockDevice(
692 node=destination2, size=8 * 1024 ** 3, name="sda")
693 factory.make_Interface(node=destination2, name='eth0')
694diff --git a/src/maasserver/static/js/angular/controllers/node_details_networking.js b/src/maasserver/static/js/angular/controllers/node_details_networking.js
695index d0a80ec..4fda858 100644
696--- a/src/maasserver/static/js/angular/controllers/node_details_networking.js
697+++ b/src/maasserver/static/js/angular/controllers/node_details_networking.js
698@@ -727,7 +727,9 @@ export function NodeNetworkingController(
699 }
700 if (
701 angular.isObject($scope.node) &&
702- ["New", "Ready", "Allocated", "Broken"].indexOf($scope.node.status) === -1
703+ ["New", "Ready", "Failed testing", "Allocated", "Broken"].indexOf(
704+ $scope.node.status
705+ ) === -1
706 ) {
707 // If a non-controller node is not ready allocated, or broken,
708 // disable networking panel.
709diff --git a/src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js b/src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js
710index c99f740..2764a15 100644
711--- a/src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js
712+++ b/src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js
713@@ -3412,6 +3412,8 @@ describe("NodeNetworkingController", function() {
714 expect($scope.isAllNetworkingDisabled()).toBe(false);
715 $scope.node = { status: "Ready" };
716 expect($scope.isAllNetworkingDisabled()).toBe(false);
717+ $scope.node = { status: "Failed testing" };
718+ expect($scope.isAllNetworkingDisabled()).toBe(false);
719 $scope.node = { status: "Allocated" };
720 expect($scope.isAllNetworkingDisabled()).toBe(false);
721 $scope.node = { status: "Broken" };

Subscribers

People subscribed via source and target branches