Merge ~newell-jensen/maas:lp1548402 into maas:master

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: 9a16d511e3f3ed9b56fd2890e85fceed1c711b61
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~newell-jensen/maas:lp1548402
Merge into: maas:master
Diff against target: 352 lines (+108/-11)
5 files modified
src/maasserver/api/machines.py (+7/-0)
src/maasserver/api/tests/test_machine.py (+34/-1)
src/maasserver/node_action.py (+7/-0)
src/maasserver/tests/test_node_action.py (+58/-10)
src/maasserver/websockets/handlers/tests/test_machine.py (+2/-0)
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+327766@code.launchpad.net

Commit message

LP: #1548402 - Handle errors in preseeds

Ensure that MAAS prevents starting the deployment of a machine if the preseeds fail to render.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

lgtm!

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
~newell-jensen/maas:lp1548402 updated
9a16d51... by Newell Jensen

Add fix for failing test in websocket handler machine_test

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/api/machines.py b/src/maasserver/api/machines.py
index 58d9dfd..dfca107 100644
--- a/src/maasserver/api/machines.py
+++ b/src/maasserver/api/machines.py
@@ -443,6 +443,13 @@ class MachineHandler(NodeHandler, OwnerDataMixin, PowerMixin):
443 form.save()443 form.save()
444 else:444 else:
445 raise MAASAPIValidationError(form.errors)445 raise MAASAPIValidationError(form.errors)
446 # Check that the curtin preseeds renders correctly.
447 try:
448 get_curtin_merged_config(machine)
449 except Exception as e:
450 raise MAASAPIBadRequest(
451 "Failed to render preseed: %s" % e)
452
446 return self.power_on(request, system_id)453 return self.power_on(request, system_id)
447454
448 @operation(idempotent=False)455 @operation(idempotent=False)
diff --git a/src/maasserver/api/tests/test_machine.py b/src/maasserver/api/tests/test_machine.py
index 4f9fa5d..9d4abf0 100644
--- a/src/maasserver/api/tests/test_machine.py
+++ b/src/maasserver/api/tests/test_machine.py
@@ -1,4 +1,4 @@
1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the1# Copyright 2015-2017 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"""Tests for the Machine API."""4"""Tests for the Machine API."""
@@ -326,6 +326,7 @@ class TestMachineAPI(APITestCase.ForUser):
326326
327 def test_POST_deploy_sets_osystem_and_distro_series(self):327 def test_POST_deploy_sets_osystem_and_distro_series(self):
328 self.patch(node_module.Node, "_start")328 self.patch(node_module.Node, "_start")
329 self.patch(machines_module, "get_curtin_merged_config")
329 machine = factory.make_Node(330 machine = factory.make_Node(
330 owner=self.user, interface=True,331 owner=self.user, interface=True,
331 power_type='manual',332 power_type='manual',
@@ -367,6 +368,7 @@ class TestMachineAPI(APITestCase.ForUser):
367368
368 def test_POST_deploy_sets_license_key(self):369 def test_POST_deploy_sets_license_key(self):
369 self.patch(node_module.Node, "_start")370 self.patch(node_module.Node, "_start")
371 self.patch(machines_module, "get_curtin_merged_config")
370 machine = factory.make_Node(372 machine = factory.make_Node(
371 owner=self.user, interface=True,373 owner=self.user, interface=True,
372 power_type='manual',374 power_type='manual',
@@ -415,6 +417,7 @@ class TestMachineAPI(APITestCase.ForUser):
415417
416 def test_POST_deploy_sets_default_distro_series(self):418 def test_POST_deploy_sets_default_distro_series(self):
417 self.patch(node_module.Node, "_start")419 self.patch(node_module.Node, "_start")
420 self.patch(machines_module, "get_curtin_merged_config")
418 machine = factory.make_Node(421 machine = factory.make_Node(
419 owner=self.user, interface=True,422 owner=self.user, interface=True,
420 power_type='manual',423 power_type='manual',
@@ -432,6 +435,7 @@ class TestMachineAPI(APITestCase.ForUser):
432435
433 def test_POST_deploy_works_if_series_already_set(self):436 def test_POST_deploy_works_if_series_already_set(self):
434 self.patch(node_module.Node, "_start")437 self.patch(node_module.Node, "_start")
438 self.patch(machines_module, "get_curtin_merged_config")
435 osystem = Config.objects.get_config('default_osystem')439 osystem = Config.objects.get_config('default_osystem')
436 distro_series = Config.objects.get_config('default_distro_series')440 distro_series = Config.objects.get_config('default_distro_series')
437 make_usable_osystem(441 make_usable_osystem(
@@ -467,6 +471,26 @@ class TestMachineAPI(APITestCase.ForUser):
467 ),471 ),
468 (response.status_code, json_load_bytes(response.content)))472 (response.status_code, json_load_bytes(response.content)))
469473
474 def test_POST_deploy_fails_when_preseed_not_rendered(self):
475 mock_get_curtin_merged_config = self.patch(
476 machines_module, "get_curtin_merged_config")
477 mock_get_curtin_merged_config.side_effect = Exception('error')
478 osystem = Config.objects.get_config('default_osystem')
479 distro_series = Config.objects.get_config('default_distro_series')
480 make_usable_osystem(
481 self, osystem_name=osystem, releases=[distro_series])
482 machine = factory.make_Node(
483 owner=self.user, interface=True,
484 status=NODE_STATUS.ALLOCATED,
485 power_type='manual',
486 distro_series=distro_series,
487 osystem=osystem,
488 architecture=make_usable_architecture(self))
489 response = self.client.post(
490 self.get_machine_uri(machine), {'op': 'deploy'})
491 self.assertEqual(http.client.BAD_REQUEST, response.status_code)
492 self.assertEqual(b"Failed to render preseed: error", response.content)
493
470 def test_POST_deploy_validates_hwe_kernel_with_default_distro_series(self):494 def test_POST_deploy_validates_hwe_kernel_with_default_distro_series(self):
471 architecture = make_usable_architecture(self, subarch_name="generic")495 architecture = make_usable_architecture(self, subarch_name="generic")
472 machine = factory.make_Node(496 machine = factory.make_Node(
@@ -495,6 +519,7 @@ class TestMachineAPI(APITestCase.ForUser):
495519
496 def test_POST_deploy_may_be_repeated(self):520 def test_POST_deploy_may_be_repeated(self):
497 self.patch(node_module.Node, "_start")521 self.patch(node_module.Node, "_start")
522 self.patch(machines_module, "get_curtin_merged_config")
498 machine = factory.make_Node(523 machine = factory.make_Node(
499 owner=self.user, interface=True,524 owner=self.user, interface=True,
500 power_type='manual',525 power_type='manual',
@@ -515,6 +540,7 @@ class TestMachineAPI(APITestCase.ForUser):
515 node_module.RackControllerManager, "filter_by_url_accessible"540 node_module.RackControllerManager, "filter_by_url_accessible"
516 ).return_value = [rack_controller]541 ).return_value = [rack_controller]
517 self.patch(node_module.Node, "_power_control_node")542 self.patch(node_module.Node, "_power_control_node")
543 self.patch(machines_module, "get_curtin_merged_config")
518 machine = factory.make_Node(544 machine = factory.make_Node(
519 owner=self.user, interface=True,545 owner=self.user, interface=True,
520 power_type='virsh', architecture=make_usable_architecture(self),546 power_type='virsh', architecture=make_usable_architecture(self),
@@ -536,6 +562,7 @@ class TestMachineAPI(APITestCase.ForUser):
536562
537 def test_POST_deploy_passes_comment(self):563 def test_POST_deploy_passes_comment(self):
538 self.patch(node_module.Node, "_start")564 self.patch(node_module.Node, "_start")
565 self.patch(machines_module, "get_curtin_merged_config")
539 rack_controller = factory.make_RackController()566 rack_controller = factory.make_RackController()
540 machine = factory.make_Node(567 machine = factory.make_Node(
541 owner=self.user, interface=True,568 owner=self.user, interface=True,
@@ -565,6 +592,7 @@ class TestMachineAPI(APITestCase.ForUser):
565 osystem = make_usable_osystem(self)592 osystem = make_usable_osystem(self)
566 distro_series = osystem['default_release']593 distro_series = osystem['default_release']
567 machine_start = self.patch(node_module.Machine, 'start')594 machine_start = self.patch(node_module.Machine, 'start')
595 self.patch(machines_module, "get_curtin_merged_config")
568 machine_start.return_value = False596 machine_start.return_value = False
569 self.client.post(597 self.client.post(
570 self.get_machine_uri(machine), {598 self.get_machine_uri(machine), {
@@ -578,6 +606,7 @@ class TestMachineAPI(APITestCase.ForUser):
578 def test_POST_deploy_doesnt_reset_power_options_bug_1569102(self):606 def test_POST_deploy_doesnt_reset_power_options_bug_1569102(self):
579 self.become_admin()607 self.become_admin()
580 self.patch(node_module.Node, "_start")608 self.patch(node_module.Node, "_start")
609 self.patch(machines_module, "get_curtin_merged_config")
581 rack_controller = factory.make_RackController()610 rack_controller = factory.make_RackController()
582 machine = factory.make_Node(611 machine = factory.make_Node(
583 owner=self.user, interface=True,612 owner=self.user, interface=True,
@@ -600,6 +629,7 @@ class TestMachineAPI(APITestCase.ForUser):
600629
601 def test_POST_deploy_allocates_ready_machines(self):630 def test_POST_deploy_allocates_ready_machines(self):
602 self.patch(node_module.Node, "_start")631 self.patch(node_module.Node, "_start")
632 self.patch(machines_module, "get_curtin_merged_config")
603 machine = factory.make_Node(633 machine = factory.make_Node(
604 status=NODE_STATUS.READY, interface=True,634 status=NODE_STATUS.READY, interface=True,
605 power_type='manual',635 power_type='manual',
@@ -631,6 +661,7 @@ class TestMachineAPI(APITestCase.ForUser):
631661
632 def test_POST_deploy_passes_agent_name(self):662 def test_POST_deploy_passes_agent_name(self):
633 self.patch(node_module.Node, "_start")663 self.patch(node_module.Node, "_start")
664 self.patch(machines_module, "get_curtin_merged_config")
634 machine = factory.make_Node(665 machine = factory.make_Node(
635 status=NODE_STATUS.READY, interface=True,666 status=NODE_STATUS.READY, interface=True,
636 power_type='manual',667 power_type='manual',
@@ -650,6 +681,7 @@ class TestMachineAPI(APITestCase.ForUser):
650681
651 def test_POST_deploy_passes_comment_on_acquire(self):682 def test_POST_deploy_passes_comment_on_acquire(self):
652 self.patch(node_module.Node, "_start")683 self.patch(node_module.Node, "_start")
684 self.patch(machines_module, "get_curtin_merged_config")
653 machine_method = self.patch(node_module.Machine, 'acquire')685 machine_method = self.patch(node_module.Machine, 'acquire')
654 machine = factory.make_Node(686 machine = factory.make_Node(
655 status=NODE_STATUS.READY, owner=self.user, interface=True,687 status=NODE_STATUS.READY, owner=self.user, interface=True,
@@ -673,6 +705,7 @@ class TestMachineAPI(APITestCase.ForUser):
673705
674 def test_POST_deploy_passes_bridge_settings(self):706 def test_POST_deploy_passes_bridge_settings(self):
675 self.patch(node_module.Node, "_start")707 self.patch(node_module.Node, "_start")
708 self.patch(machines_module, "get_curtin_merged_config")
676 machine_method = self.patch(node_module.Machine, 'acquire')709 machine_method = self.patch(node_module.Machine, 'acquire')
677 machine = factory.make_Node(710 machine = factory.make_Node(
678 status=NODE_STATUS.READY, owner=self.user, interface=True,711 status=NODE_STATUS.READY, owner=self.user, interface=True,
diff --git a/src/maasserver/node_action.py b/src/maasserver/node_action.py
index fc48bb0..564abd5 100644
--- a/src/maasserver/node_action.py
+++ b/src/maasserver/node_action.py
@@ -44,6 +44,7 @@ from maasserver.node_status import (
44 is_failed_status,44 is_failed_status,
45 NON_MONITORED_STATUSES,45 NON_MONITORED_STATUSES,
46)46)
47from maasserver.preseed import get_curtin_config
47from maasserver.utils.orm import post_commit_do48from maasserver.utils.orm import post_commit_do
48from maasserver.utils.osystems import (49from maasserver.utils.osystems import (
49 validate_hwe_kernel,50 validate_hwe_kernel,
@@ -346,6 +347,12 @@ class Deploy(NodeAction):
346 raise NodeActionError(e)347 raise NodeActionError(e)
347348
348 try:349 try:
350 get_curtin_config(self.node)
351 except Exception as e:
352 raise NodeActionError(
353 "Failed to retrieve curtin config: %s" % e)
354
355 try:
349 self.node.start(self.user)356 self.node.start(self.user)
350 except StaticIPAddressExhaustion:357 except StaticIPAddressExhaustion:
351 raise NodeActionError(358 raise NodeActionError(
diff --git a/src/maasserver/tests/test_node_action.py b/src/maasserver/tests/test_node_action.py
index 4f0c0bd..bad38a2 100644
--- a/src/maasserver/tests/test_node_action.py
+++ b/src/maasserver/tests/test_node_action.py
@@ -49,6 +49,7 @@ from maasserver.node_action import (
49 SetZone,49 SetZone,
50 Test,50 Test,
51)51)
52import maasserver.node_action as node_action_module
52from maasserver.node_status import (53from maasserver.node_status import (
53 MONITORED_STATUSES,54 MONITORED_STATUSES,
54 NODE_TESTING_RESET_READY_TRANSITIONS,55 NODE_TESTING_RESET_READY_TRANSITIONS,
@@ -499,10 +500,27 @@ class TestDeployAction(MAASServerTestCase):
499 node = factory.make_Node(500 node = factory.make_Node(
500 interface=True, status=NODE_STATUS.ALLOCATED,501 interface=True, status=NODE_STATUS.ALLOCATED,
501 power_type='manual', owner=user)502 power_type='manual', owner=user)
502 node_start = self.patch(node, 'start')503 mock_get_curtin_config = self.patch(
504 node_action_module, 'get_curtin_config')
505 mock_node_start = self.patch(node, 'start')
503 Deploy(node, user).execute()506 Deploy(node, user).execute()
504 self.assertThat(507 self.expectThat(
505 node_start, MockCalledOnceWith(user))508 mock_get_curtin_config, MockCalledOnceWith(node))
509 self.expectThat(
510 mock_node_start, MockCalledOnceWith(user))
511
512 def test_Deploy_raises_NodeActionError_for_no_curtin_config(self):
513 user = factory.make_User()
514 node = factory.make_Node(
515 interface=True, status=NODE_STATUS.ALLOCATED,
516 power_type='manual', owner=user)
517 mock_get_curtin_config = self.patch(
518 node_action_module, 'get_curtin_config')
519 mock_get_curtin_config.side_effect = NodeActionError('error')
520 error = self.assertRaises(
521 NodeActionError, Deploy(node, user).execute)
522 self.assertEqual(
523 "Failed to retrieve curtin config: error", str(error))
506524
507 def test_Deploy_raises_NodeActionError_for_invalid_os(self):525 def test_Deploy_raises_NodeActionError_for_invalid_os(self):
508 user = factory.make_User()526 user = factory.make_User()
@@ -527,7 +545,9 @@ class TestDeployAction(MAASServerTestCase):
527 node = factory.make_Node(545 node = factory.make_Node(
528 interface=True, status=NODE_STATUS.ALLOCATED,546 interface=True, status=NODE_STATUS.ALLOCATED,
529 power_type='manual', owner=user)547 power_type='manual', owner=user)
530 self.patch(node, 'start')548 mock_get_curtin_config = self.patch(
549 node_action_module, 'get_curtin_config')
550 mock_node_start = self.patch(node, 'start')
531 osystem = make_usable_osystem(self)551 osystem = make_usable_osystem(self)
532 os_name = osystem["name"]552 os_name = osystem["name"]
533 release_name = osystem["releases"][0]["name"]553 release_name = osystem["releases"][0]["name"]
@@ -536,6 +556,10 @@ class TestDeployAction(MAASServerTestCase):
536 "distro_series": release_name556 "distro_series": release_name
537 }557 }
538 Deploy(node, user).execute(**extra)558 Deploy(node, user).execute(**extra)
559 self.expectThat(
560 mock_get_curtin_config, MockCalledOnceWith(node))
561 self.expectThat(
562 mock_node_start, MockCalledOnceWith(user))
539 self.expectThat(node.osystem, Equals(os_name))563 self.expectThat(node.osystem, Equals(os_name))
540 self.expectThat(564 self.expectThat(
541 node.distro_series, Equals(release_name))565 node.distro_series, Equals(release_name))
@@ -545,7 +569,9 @@ class TestDeployAction(MAASServerTestCase):
545 node = factory.make_Node(569 node = factory.make_Node(
546 interface=True, status=NODE_STATUS.ALLOCATED,570 interface=True, status=NODE_STATUS.ALLOCATED,
547 power_type='manual', owner=user)571 power_type='manual', owner=user)
548 self.patch(node, 'start')572 mock_get_curtin_config = self.patch(
573 node_action_module, 'get_curtin_config')
574 mock_node_start = self.patch(node, 'start')
549 osystem = make_usable_osystem(self)575 osystem = make_usable_osystem(self)
550 os_name = osystem["name"]576 os_name = osystem["name"]
551 release_name = osystem["releases"][0]["name"]577 release_name = osystem["releases"][0]["name"]
@@ -554,6 +580,10 @@ class TestDeployAction(MAASServerTestCase):
554 "distro_series": release_name + '*'580 "distro_series": release_name + '*'
555 }581 }
556 Deploy(node, user).execute(**extra)582 Deploy(node, user).execute(**extra)
583 self.expectThat(
584 mock_get_curtin_config, MockCalledOnceWith(node))
585 self.expectThat(
586 mock_node_start, MockCalledOnceWith(user))
557 self.expectThat(node.osystem, Equals(os_name))587 self.expectThat(node.osystem, Equals(os_name))
558 self.expectThat(588 self.expectThat(
559 node.distro_series, Equals(release_name))589 node.distro_series, Equals(release_name))
@@ -563,12 +593,18 @@ class TestDeployAction(MAASServerTestCase):
563 node = factory.make_Node(593 node = factory.make_Node(
564 interface=True, status=NODE_STATUS.ALLOCATED,594 interface=True, status=NODE_STATUS.ALLOCATED,
565 power_type='manual', owner=user)595 power_type='manual', owner=user)
566 self.patch(node, 'start')596 mock_get_curtin_config = self.patch(
597 node_action_module, 'get_curtin_config')
598 mock_node_start = self.patch(node, 'start')
567 osystem = make_osystem_with_releases(self)599 osystem = make_osystem_with_releases(self)
568 extra = {600 extra = {
569 "distro_series": osystem["releases"][0]["name"],601 "distro_series": osystem["releases"][0]["name"],
570 }602 }
571 Deploy(node, user).execute(**extra)603 Deploy(node, user).execute(**extra)
604 self.expectThat(
605 mock_get_curtin_config, MockCalledOnceWith(node))
606 self.expectThat(
607 mock_node_start, MockCalledOnceWith(user))
572 self.expectThat(node.osystem, Equals(""))608 self.expectThat(node.osystem, Equals(""))
573 self.expectThat(node.distro_series, Equals(""))609 self.expectThat(node.distro_series, Equals(""))
574610
@@ -577,24 +613,36 @@ class TestDeployAction(MAASServerTestCase):
577 node = factory.make_Node(613 node = factory.make_Node(
578 interface=True, status=NODE_STATUS.ALLOCATED,614 interface=True, status=NODE_STATUS.ALLOCATED,
579 power_type='manual', owner=user)615 power_type='manual', owner=user)
580 self.patch(node, 'start')616 mock_get_curtin_config = self.patch(
617 node_action_module, 'get_curtin_config')
618 mock_node_start = self.patch(node, 'start')
581 osystem = make_osystem_with_releases(self)619 osystem = make_osystem_with_releases(self)
582 extra = {620 extra = {
583 "osystem": osystem["name"],621 "osystem": osystem["name"],
584 }622 }
585 Deploy(node, user).execute(**extra)623 Deploy(node, user).execute(**extra)
624 self.expectThat(
625 mock_get_curtin_config, MockCalledOnceWith(node))
626 self.expectThat(
627 mock_node_start, MockCalledOnceWith(user))
586 self.expectThat(node.osystem, Equals(""))628 self.expectThat(node.osystem, Equals(""))
587 self.expectThat(node.distro_series, Equals(""))629 self.expectThat(node.distro_series, Equals(""))
588630
589 def test_Deploy_allocates_node_if_node_not_already_allocated(self):631 def test_Deploy_allocates_node_if_node_not_already_allocated(self):
590 user = factory.make_User()632 user = factory.make_User()
591 node = factory.make_Node(status=NODE_STATUS.READY, with_boot_disk=True)633 node = factory.make_Node(status=NODE_STATUS.READY, with_boot_disk=True)
592 self.patch(node, 'start')634 mock_get_curtin_config = self.patch(
635 node_action_module, 'get_curtin_config')
636 mock_node_start = self.patch(node, 'start')
593 action = Deploy(node, user)637 action = Deploy(node, user)
594 action.execute()638 action.execute()
595639
596 self.assertEqual(user, node.owner)640 self.expectThat(
597 self.assertEqual(NODE_STATUS.ALLOCATED, node.status)641 mock_get_curtin_config, MockCalledOnceWith(node))
642 self.expectThat(
643 mock_node_start, MockCalledOnceWith(user))
644 self.expectThat(user, Equals(node.owner))
645 self.expectThat(NODE_STATUS.ALLOCATED, Equals(node.status))
598646
599647
600class TestDeployActionTransactional(MAASTransactionServerTestCase):648class TestDeployActionTransactional(MAASTransactionServerTestCase):
diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py
index 80ab253..0732ba1 100644
--- a/src/maasserver/websockets/handlers/tests/test_machine.py
+++ b/src/maasserver/websockets/handlers/tests/test_machine.py
@@ -56,6 +56,7 @@ from maasserver.models.partition import (
56 PARTITION_ALIGNMENT_SIZE,56 PARTITION_ALIGNMENT_SIZE,
57)57)
58from maasserver.node_action import compile_node_actions58from maasserver.node_action import compile_node_actions
59import maasserver.node_action as node_action_module
59from maasserver.testing.architecture import make_usable_architecture60from maasserver.testing.architecture import make_usable_architecture
60from maasserver.testing.factory import factory61from maasserver.testing.factory import factory
61from maasserver.testing.osystems import make_usable_osystem62from maasserver.testing.osystems import make_usable_osystem
@@ -2059,6 +2060,7 @@ class TestMachineHandler(MAASServerTestCase):
2059 self.patch(Machine, 'on_network').return_value = True2060 self.patch(Machine, 'on_network').return_value = True
2060 node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=user)2061 node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=user)
2061 self.patch(Machine, "_start").return_value = None2062 self.patch(Machine, "_start").return_value = None
2063 self.patch(node_action_module, 'get_curtin_config')
2062 osystem = make_usable_osystem(self)2064 osystem = make_usable_osystem(self)
2063 handler = MachineHandler(user, {})2065 handler = MachineHandler(user, {})
2064 handler.action({2066 handler.action({

Subscribers

People subscribed via source and target branches