Merge ~troyanov/maas:node-release-scriptset into maas:master

Proposed by Anton Troyanov
Status: Merged
Approved by: Anton Troyanov
Approved revision: 5672c8464f22aa4de1ee90d7bcb88fcf8cf6e02d
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~troyanov/maas:node-release-scriptset
Merge into: maas:master
Diff against target: 540 lines (+244/-83)
9 files modified
src/maasserver/api/machines.py (+21/-7)
src/maasserver/api/tests/test_machine.py (+25/-24)
src/maasserver/models/node.py (+144/-26)
src/maasserver/models/tests/test_node.py (+15/-18)
src/maasserver/node_action.py (+16/-5)
src/maasserver/node_status.py (+1/-0)
src/maasserver/tests/test_node_action.py (+7/-3)
src/metadataserver/api.py (+9/-0)
src/metadataserver/api_twisted.py (+6/-0)
Reviewer Review Type Date Requested Status
Christian Grabowski Approve
MAAS Lander Approve
Review via email: mp+461347@code.launchpad.net

Commit message

feat: machine release scripts

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

UNIT TESTS
-b node-release-scriptset lp:~troyanov/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 5672c8464f22aa4de1ee90d7bcb88fcf8cf6e02d

review: Approve
Revision history for this message
Christian Grabowski (cgrabowski) wrote :

One minor nit inline, otherwise looks good, +1

review: Approve

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 f8cddf0..2c0a116 100644
--- a/src/maasserver/api/machines.py
+++ b/src/maasserver/api/machines.py
@@ -973,12 +973,23 @@ class MachineHandler(NodeHandler, WorkloadAnnotationsMixin, PowerMixin):
973 # postcondition is achieved, so call this success.973 # postcondition is achieved, so call this success.
974 pass974 pass
975 elif machine.status in RELEASABLE_STATUSES:975 elif machine.status in RELEASABLE_STATUSES:
976 machine.release_or_erase(976 scripts = form.cleaned_data["scripts"]
977 request.user,977
978 form.cleaned_data["comment"],978 params = {}
979 erase=form.cleaned_data["erase"],979 if form.cleaned_data["erase"]:
980 secure_erase=form.cleaned_data["secure_erase"],980 params["wipe-disks"] = {}
981 quick_erase=form.cleaned_data["quick_erase"],981 params["wipe-disks"]["secure_erase"] = form.cleaned_data[
982 "secure_erase"
983 ]
984 params["wipe-disks"]["quick_erase"] = form.cleaned_data[
985 "quick_erase"
986 ]
987 params = params | form.get_script_param_dict(scripts)
988 machine.start_releasing(
989 user=request.user,
990 comment=form.cleaned_data["comment"],
991 scripts=scripts,
992 script_input=params,
982 force=form.cleaned_data["force"],993 force=form.cleaned_data["force"],
983 )994 )
984 else:995 else:
@@ -2225,7 +2236,10 @@ class MachinesHandler(NodesHandler, PowersMixin):
2225 # Nothing to do.2236 # Nothing to do.
2226 pass2237 pass
2227 elif machine.status in RELEASABLE_STATUSES:2238 elif machine.status in RELEASABLE_STATUSES:
2228 machine.release_or_erase(request.user, comment)2239 machine.start_releasing(
2240 user=request.user,
2241 comment=comment,
2242 )
2229 released_ids.append(machine.system_id)2243 released_ids.append(machine.system_id)
2230 else:2244 else:
2231 failed.append(2245 failed.append(
diff --git a/src/maasserver/api/tests/test_machine.py b/src/maasserver/api/tests/test_machine.py
index f5a2b33..42b3511 100644
--- a/src/maasserver/api/tests/test_machine.py
+++ b/src/maasserver/api/tests/test_machine.py
@@ -1683,17 +1683,16 @@ class TestMachineAPI(APITestCase.ForUser):
1683 )1683 )
1684 self.become_admin()1684 self.become_admin()
1685 comment = factory.make_name("comment")1685 comment = factory.make_name("comment")
1686 machine_release = self.patch(node_module.Machine, "release_or_erase")1686 machine_release = self.patch(node_module.Machine, "start_releasing")
1687 self.client.post(1687 self.client.post(
1688 self.get_machine_uri(machine),1688 self.get_machine_uri(machine),
1689 {"op": "release", "comment": comment},1689 {"op": "release", "comment": comment},
1690 )1690 )
1691 machine_release.assert_called_once_with(1691 machine_release.assert_called_once_with(
1692 self.user,1692 user=self.user,
1693 comment,1693 comment=comment,
1694 erase=False,1694 scripts=[],
1695 quick_erase=False,1695 script_input={},
1696 secure_erase=False,
1697 force=False,1696 force=False,
1698 )1697 )
16991698
@@ -1707,7 +1706,7 @@ class TestMachineAPI(APITestCase.ForUser):
1707 self.become_admin()1706 self.become_admin()
1708 secure_erase = factory.pick_bool()1707 secure_erase = factory.pick_bool()
1709 quick_erase = factory.pick_bool()1708 quick_erase = factory.pick_bool()
1710 machine_release = self.patch(node_module.Machine, "release_or_erase")1709 machine_release = self.patch(node_module.Machine, "start_releasing")
1711 self.client.post(1710 self.client.post(
1712 self.get_machine_uri(machine),1711 self.get_machine_uri(machine),
1713 {1712 {
@@ -1718,11 +1717,15 @@ class TestMachineAPI(APITestCase.ForUser):
1718 },1717 },
1719 )1718 )
1720 machine_release.assert_called_once_with(1719 machine_release.assert_called_once_with(
1721 self.user,1720 user=self.user,
1722 "",1721 comment="",
1723 erase=True,1722 scripts=["wipe-disks"],
1724 quick_erase=quick_erase,1723 script_input={
1725 secure_erase=secure_erase,1724 "wipe-disks": {
1725 "secure_erase": secure_erase,
1726 "quick_erase": quick_erase,
1727 }
1728 },
1726 force=False,1729 force=False,
1727 )1730 )
17281731
@@ -1735,16 +1738,15 @@ class TestMachineAPI(APITestCase.ForUser):
1735 )1738 )
1736 self.become_admin()1739 self.become_admin()
1737 force = factory.pick_bool()1740 force = factory.pick_bool()
1738 machine_release = self.patch(node_module.Machine, "release_or_erase")1741 machine_release = self.patch(node_module.Machine, "start_releasing")
1739 self.client.post(1742 self.client.post(
1740 self.get_machine_uri(machine), {"op": "release", "force": force}1743 self.get_machine_uri(machine), {"op": "release", "force": force}
1741 )1744 )
1742 machine_release.assert_called_once_with(1745 machine_release.assert_called_once_with(
1743 self.user,1746 user=self.user,
1744 "",1747 comment="",
1745 erase=False,1748 scripts=[],
1746 quick_erase=False,1749 script_input={},
1747 secure_erase=False,
1748 force=force,1750 force=force,
1749 )1751 )
17501752
@@ -1756,14 +1758,13 @@ class TestMachineAPI(APITestCase.ForUser):
1756 power_state=POWER_STATE.OFF,1758 power_state=POWER_STATE.OFF,
1757 )1759 )
1758 self.become_admin()1760 self.become_admin()
1759 machine_release = self.patch(node_module.Machine, "release_or_erase")1761 machine_release = self.patch(node_module.Machine, "start_releasing")
1760 self.client.post(self.get_machine_uri(machine), {"op": "release"})1762 self.client.post(self.get_machine_uri(machine), {"op": "release"})
1761 machine_release.assert_called_once_with(1763 machine_release.assert_called_once_with(
1762 self.user,1764 user=self.user,
1763 "",1765 comment="",
1764 erase=False,1766 scripts=[],
1765 quick_erase=False,1767 script_input={},
1766 secure_erase=False,
1767 force=False,1768 force=False,
1768 )1769 )
17691770
diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py
index 0e66b44..e34f5d4 100644
--- a/src/maasserver/models/node.py
+++ b/src/maasserver/models/node.py
@@ -19,6 +19,7 @@ from itertools import chain, count
19import json19import json
20import logging20import logging
21from operator import attrgetter21from operator import attrgetter
22import os
22import random23import random
23import re24import re
24import socket25import socket
@@ -174,7 +175,11 @@ from metadataserver.enum import (
174 SCRIPT_STATUS_FAILED,175 SCRIPT_STATUS_FAILED,
175 SCRIPT_STATUS_RUNNING_OR_PENDING,176 SCRIPT_STATUS_RUNNING_OR_PENDING,
176)177)
177from metadataserver.user_data import generate_user_data_for_status178from metadataserver.user_data import (
179 generate_user_data,
180 generate_user_data_for_status,
181)
182from metadataserver.user_data.snippets import get_userdata_template_dir
178from provisioningserver.drivers.osystem import BOOT_IMAGE_PURPOSE183from provisioningserver.drivers.osystem import BOOT_IMAGE_PURPOSE
179from provisioningserver.drivers.pod import Capabilities184from provisioningserver.drivers.pod import Capabilities
180from provisioningserver.drivers.power.ipmi import IPMI_BOOT_TYPE185from provisioningserver.drivers.power.ipmi import IPMI_BOOT_TYPE
@@ -3623,6 +3628,144 @@ class Node(CleanSave, TimestampedModel):
3623 % (self.system_id, NODE_STATUS_CHOICES_DICT[self.status])3628 % (self.system_id, NODE_STATUS_CHOICES_DICT[self.status])
3624 )3629 )
36253630
3631 def start_releasing(
3632 self, user, comment=None, scripts=None, script_input=None, force=False
3633 ):
3634 """Start the release process for the machine."""
3635 from maasserver.models import ScriptSet
3636
3637 if scripts is None:
3638 scripts = []
3639
3640 self.maybe_delete_pods(not force)
3641
3642 config = Config.objects.get_configs(
3643 [
3644 "commissioning_osystem",
3645 "commissioning_distro_series",
3646 "default_osystem",
3647 "default_distro_series",
3648 "disk_erase_with_secure_erase",
3649 "disk_erase_with_quick_erase",
3650 "enable_disk_erasing_on_release",
3651 ]
3652 )
3653
3654 if config.get("enable_disk_erasing_on_release"):
3655 scripts.append("wipe-disks")
3656
3657 if not scripts:
3658 self.release(user, comment)
3659 return
3660
3661 self.current_release_script_set = (
3662 ScriptSet.objects.create_release_script_set(
3663 self, scripts=scripts, script_input=script_input
3664 )
3665 )
3666 self.save()
3667
3668 template_file = os.path.join(
3669 get_userdata_template_dir(), "script_runner.template"
3670 )
3671 user_data = generate_user_data(self, template_file)
3672
3673 failed_status = NODE_STATUS.FAILED_RELEASING
3674 # Before machine release scripts were introduced there was Erase Disk
3675 # functionality. We want to maintain backward compatible statuses
3676 # to be reported when only "wipe-disk" script is executed.
3677 if len(scripts) == 1 and "wipe-disks" in scripts:
3678 self._register_request_event(
3679 user,
3680 EVENT_TYPES.REQUEST_NODE_ERASE_DISK,
3681 action="start disk erasing",
3682 comment=comment,
3683 )
3684 old_status = self.update_status(NODE_STATUS.DISK_ERASING)
3685 failed_status = NODE_STATUS.FAILED_DISK_ERASING
3686 else:
3687 self._register_request_event(
3688 user,
3689 EVENT_TYPES.REQUEST_NODE_RELEASE,
3690 action="start node releasing",
3691 comment=comment,
3692 )
3693
3694 old_status = self.update_status(NODE_STATUS.RELEASING)
3695
3696 self.save()
3697
3698 try:
3699 # Node.start() has synchronous and asynchronous parts, so catch
3700 # exceptions arising synchronously, and chain callbacks to the
3701 # Deferred it returns for the asynchronous (post-commit) bits.
3702 starting = self._start(
3703 user,
3704 user_data,
3705 old_status,
3706 allow_power_cycle=True,
3707 config=config,
3708 )
3709 except Exception as error:
3710 # We always mark the node as failed here, although we could
3711 # potentially move it back to the state it was in previously. For
3712 # now, though, this is safer, since it marks the node as needing
3713 # attention.
3714 self.update_status(failed_status)
3715 self.save()
3716 maaslog.error(
3717 f"{self.hostname}: Could not start node for release: {error}"
3718 )
3719 # Let the exception bubble up, since the UI or API will have to
3720 # deal with it.
3721 raise
3722 else:
3723 # Don't permit naive mocking of start(); it causes too much
3724 # confusion when testing. Return a Deferred from side_effect.
3725 assert isinstance(starting, Deferred) or starting is None
3726
3727 if starting is None:
3728 starting = post_commit()
3729 # MAAS cannot start the node itself.
3730 is_starting = False
3731 else:
3732 # MAAS can direct the node to start.
3733 is_starting = True
3734
3735 @asynchronous
3736 def async_start(is_starting, hostname):
3737 if is_starting:
3738 maaslog.info(f"{hostname}: Release started")
3739 else:
3740 maaslog.warning(
3741 f"{hostname}: Could not start node for release; it "
3742 "must be started manually",
3743 )
3744
3745 starting.addCallback(
3746 callOut,
3747 async_start,
3748 is_starting,
3749 self.hostname,
3750 )
3751
3752 # If there's an error, reset the node's status.
3753 starting.addErrback(
3754 callOutToDatabase,
3755 Node._set_status,
3756 self.system_id,
3757 status=failed_status,
3758 )
3759
3760 def eb_start(failure, hostname):
3761 maaslog.error(
3762 f"{hostname}: Could not start node for "
3763 f"releasing: {failure.getErrorMessage()}",
3764 )
3765 return failure # Propagate.
3766
3767 return starting.addErrback(eb_start, self.hostname)
3768
3626 def release(self, user=None, comment=None):3769 def release(self, user=None, comment=None):
3627 self._register_request_event(3770 self._register_request_event(
3628 user,3771 user,
@@ -3758,31 +3901,6 @@ class Node(CleanSave, TimestampedModel):
3758 # Remove all set owner data.3901 # Remove all set owner data.
3759 OwnerData.objects.filter(node=self).delete()3902 OwnerData.objects.filter(node=self).delete()
37603903
3761 def release_or_erase(
3762 self,
3763 user,
3764 comment=None,
3765 erase=False,
3766 secure_erase=None,
3767 quick_erase=None,
3768 force=False,
3769 ):
3770 """Either release the node or erase the node then release it, depending
3771 on settings and parameters."""
3772 self.maybe_delete_pods(not force)
3773 erase_on_release = Config.objects.get_config(
3774 "enable_disk_erasing_on_release"
3775 )
3776 if erase or erase_on_release:
3777 self.start_disk_erasing(
3778 user,
3779 comment,
3780 secure_erase=secure_erase,
3781 quick_erase=quick_erase,
3782 )
3783 else:
3784 self.release(user, comment)
3785
3786 def maybe_delete_pods(self, dry_run: bool):3904 def maybe_delete_pods(self, dry_run: bool):
3787 """Check if any pods are associated with this Node.3905 """Check if any pods are associated with this Node.
37883906
diff --git a/src/maasserver/models/tests/test_node.py b/src/maasserver/models/tests/test_node.py
index 7706930..6195c28 100644
--- a/src/maasserver/models/tests/test_node.py
+++ b/src/maasserver/models/tests/test_node.py
@@ -22,6 +22,7 @@ from fixtures import LoggerFixture
22from netaddr import IPAddress, IPNetwork22from netaddr import IPAddress, IPNetwork
23from testscenarios import multiply_scenarios23from testscenarios import multiply_scenarios
24from twisted.internet import defer24from twisted.internet import defer
25from twisted.internet.defer import succeed
25from twisted.internet.error import ConnectionDone26from twisted.internet.error import ConnectionDone
26import yaml27import yaml
2728
@@ -7273,44 +7274,40 @@ class TestNodeManagerGetNodesRBAC(MAASServerTestCase):
72737274
72747275
7275class TestNodeErase(MAASServerTestCase):7276class TestNodeErase(MAASServerTestCase):
7276 def test_release_or_erase_erases_when_enabled(self):7277 def test_start_releasing_erases_when_enabled(self):
7277 owner = factory.make_User()7278 owner = factory.make_User()
7278 node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner)7279 node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner)
7279 Config.objects.set_config("enable_disk_erasing_on_release", True)7280 Config.objects.set_config("enable_disk_erasing_on_release", True)
7280 erase_mock = self.patch_autospec(node, "start_disk_erasing")
7281 release_mock = self.patch_autospec(node, "release")7281 release_mock = self.patch_autospec(node, "release")
7282 node.release_or_erase(owner)7282 self.patch(node, "_start").return_value = succeed(None)
7283 erase_mock.assert_called_once_with(7283 node.start_releasing(owner)
7284 owner, None, secure_erase=None, quick_erase=None
7285 )
7286 release_mock.assert_not_called()7284 release_mock.assert_not_called()
72877285
7288 def test_release_or_erase_erases_when_disabled_and_erase_param(self):7286 def test_start_releasing_erases_when_disabled_and_erase_param(self):
7289 owner = factory.make_User()7287 owner = factory.make_User()
7290 node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner)7288 node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner)
7291 Config.objects.set_config("enable_disk_erasing_on_release", False)7289 Config.objects.set_config("enable_disk_erasing_on_release", False)
7292 erase_mock = self.patch_autospec(node, "start_disk_erasing")
7293 release_mock = self.patch_autospec(node, "release")7290 release_mock = self.patch_autospec(node, "release")
7291 self.patch(node, "_start").return_value = succeed(None)
7294 secure_erase = factory.pick_bool()7292 secure_erase = factory.pick_bool()
7295 quick_erase = factory.pick_bool()7293 quick_erase = factory.pick_bool()
7296 node.release_or_erase(7294 node.start_releasing(
7297 owner,7295 user=owner,
7298 erase=True,7296 scripts=["wipe-disks"],
7299 secure_erase=secure_erase,7297 script_input={
7300 quick_erase=quick_erase,7298 "secure_erase": secure_erase,
7301 )7299 "quick_erase": quick_erase,
7302 erase_mock.assert_called_once_with(7300 },
7303 owner, None, secure_erase=secure_erase, quick_erase=quick_erase
7304 )7301 )
7305 release_mock.assert_not_called()7302 release_mock.assert_not_called()
73067303
7307 def test_release_or_erase_releases_when_disabled(self):7304 def test_start_releasing_releases_when_disabled(self):
7308 owner = factory.make_User()7305 owner = factory.make_User()
7309 node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner)7306 node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner)
7310 Config.objects.set_config("enable_disk_erasing_on_release", False)7307 Config.objects.set_config("enable_disk_erasing_on_release", False)
7311 erase_mock = self.patch_autospec(node, "start_disk_erasing")7308 erase_mock = self.patch_autospec(node, "start_disk_erasing")
7312 release_mock = self.patch_autospec(node, "release")7309 release_mock = self.patch_autospec(node, "release")
7313 node.release_or_erase(owner)7310 node.start_releasing(owner)
7314 release_mock.assert_called_once_with(owner, None)7311 release_mock.assert_called_once_with(owner, None)
7315 erase_mock.assert_not_called()7312 erase_mock.assert_not_called()
73167313
diff --git a/src/maasserver/node_action.py b/src/maasserver/node_action.py
index 0c19d9e..ec7a87d 100644
--- a/src/maasserver/node_action.py
+++ b/src/maasserver/node_action.py
@@ -728,11 +728,22 @@ class Release(NodeAction):
728 if not form.is_valid():728 if not form.is_valid():
729 raise NodeActionError(form.errors)729 raise NodeActionError(form.errors)
730 try:730 try:
731 self.node.release_or_erase(731 scripts = []
732 self.user,732 params = {}
733 erase=form.cleaned_data["erase"],733 if form.cleaned_data["erase"]:
734 secure_erase=form.cleaned_data["secure_erase"],734 scripts.append("wipe-disks")
735 quick_erase=form.cleaned_data["quick_erase"],735 params["wipe-disks"] = {}
736 params["wipe-disks"]["secure_erase"] = form.cleaned_data[
737 "secure_erase"
738 ]
739 params["wipe-disks"]["quick_erase"] = form.cleaned_data[
740 "quick_erase"
741 ]
742 params = params | form.get_script_param_dict(scripts)
743 self.node.start_releasing(
744 user=self.user,
745 scripts=scripts,
746 script_input=params,
736 )747 )
737 except RPC_EXCEPTIONS + (ExternalProcessError,) as exception:748 except RPC_EXCEPTIONS + (ExternalProcessError,) as exception:
738 raise NodeActionError(exception)749 raise NodeActionError(exception)
diff --git a/src/maasserver/node_status.py b/src/maasserver/node_status.py
index 3a344ef..4ea19f1 100644
--- a/src/maasserver/node_status.py
+++ b/src/maasserver/node_status.py
@@ -312,6 +312,7 @@ COMMISSIONING_LIKE_STATUSES = [
312 NODE_STATUS.COMMISSIONING,312 NODE_STATUS.COMMISSIONING,
313 NODE_STATUS.DISK_ERASING,313 NODE_STATUS.DISK_ERASING,
314 NODE_STATUS.ENTERING_RESCUE_MODE,314 NODE_STATUS.ENTERING_RESCUE_MODE,
315 NODE_STATUS.RELEASING,
315 NODE_STATUS.RESCUE_MODE,316 NODE_STATUS.RESCUE_MODE,
316 NODE_STATUS.TESTING,317 NODE_STATUS.TESTING,
317]318]
diff --git a/src/maasserver/tests/test_node_action.py b/src/maasserver/tests/test_node_action.py
index 41e0a04..b04105e 100644
--- a/src/maasserver/tests/test_node_action.py
+++ b/src/maasserver/tests/test_node_action.py
@@ -1555,15 +1555,19 @@ class TestReleaseAction(MAASServerTestCase):
1555 owner=user,1555 owner=user,
1556 power_parameters=params,1556 power_parameters=params,
1557 )1557 )
1558 node_release_or_erase = self.patch_autospec(node, "release_or_erase")1558 node_start_releasing = self.patch_autospec(node, "start_releasing")
15591559
1560 with post_commit_hooks:1560 with post_commit_hooks:
1561 Release(node, user, request).execute(1561 Release(node, user, request).execute(
1562 erase=True, secure_erase=True, quick_erase=True1562 erase=True, secure_erase=True, quick_erase=True
1563 )1563 )
15641564
1565 node_release_or_erase.assert_called_once_with(1565 node_start_releasing.assert_called_once_with(
1566 user, erase=True, secure_erase=True, quick_erase=True1566 user=user,
1567 scripts=["wipe-disks"],
1568 script_input={
1569 "wipe-disks": {"secure_erase": True, "quick_erase": True}
1570 },
1567 )1571 )
15681572
15691573
diff --git a/src/metadataserver/api.py b/src/metadataserver/api.py
index d2394c2..35f9a9e 100644
--- a/src/metadataserver/api.py
+++ b/src/metadataserver/api.py
@@ -519,6 +519,7 @@ class VersionIndexHandler(MetadataViewHandler):
519 NODE_STATUS.FAILED_DEPLOYMENT,519 NODE_STATUS.FAILED_DEPLOYMENT,
520 NODE_STATUS.READY,520 NODE_STATUS.READY,
521 NODE_STATUS.DISK_ERASING,521 NODE_STATUS.DISK_ERASING,
522 NODE_STATUS.RELEASING,
522 NODE_STATUS.ENTERING_RESCUE_MODE,523 NODE_STATUS.ENTERING_RESCUE_MODE,
523 NODE_STATUS.RESCUE_MODE,524 NODE_STATUS.RESCUE_MODE,
524 NODE_STATUS.FAILED_ENTERING_RESCUE_MODE,525 NODE_STATUS.FAILED_ENTERING_RESCUE_MODE,
@@ -531,6 +532,7 @@ class VersionIndexHandler(MetadataViewHandler):
531 NODE_STATUS.COMMISSIONING,532 NODE_STATUS.COMMISSIONING,
532 NODE_STATUS.DEPLOYING,533 NODE_STATUS.DEPLOYING,
533 NODE_STATUS.DISK_ERASING,534 NODE_STATUS.DISK_ERASING,
535 NODE_STATUS.RELEASING,
534 NODE_STATUS.ENTERING_RESCUE_MODE,536 NODE_STATUS.ENTERING_RESCUE_MODE,
535 NODE_STATUS.RESCUE_MODE,537 NODE_STATUS.RESCUE_MODE,
536 NODE_STATUS.TESTING,538 NODE_STATUS.TESTING,
@@ -789,6 +791,13 @@ class VersionIndexHandler(MetadataViewHandler):
789 self._store_results(791 self._store_results(
790 node, node.current_release_script_set, request, status792 node, node.current_release_script_set, request, status
791 )793 )
794
795 if status == SIGNAL_STATUS.OK:
796 node.release()
797
798 if status == SIGNAL_STATUS.FAILED:
799 node.mark_failed(comment="Failed to release machine.")
800
792 return None801 return None
793802
794 @operation(idempotent=False)803 @operation(idempotent=False)
diff --git a/src/metadataserver/api_twisted.py b/src/metadataserver/api_twisted.py
index ca432fa..a5c4f9e 100644
--- a/src/metadataserver/api_twisted.py
+++ b/src/metadataserver/api_twisted.py
@@ -503,6 +503,12 @@ class StatusWorkerService(TimerService):
503 comment="Failed to erase disks.", commit=False503 comment="Failed to erase disks.", commit=False
504 )504 )
505 save_node = True505 save_node = True
506 elif node.status == NODE_STATUS.RELEASING:
507 if failed:
508 node.mark_failed(
509 comment="Failed to release machine.", commit=False
510 )
511 save_node = True
506 # Deallocate the node if we enter any terminal state.512 # Deallocate the node if we enter any terminal state.
507 if node.node_type == NODE_TYPE.MACHINE and node.status in [513 if node.node_type == NODE_TYPE.MACHINE and node.status in [
508 NODE_STATUS.READY,514 NODE_STATUS.READY,

Subscribers

People subscribed via source and target branches