Merge ~troyanov/maas:node-release-scriptset into maas:master
- Git
- lp:~troyanov/maas
- node-release-scriptset
- Merge into 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) |
Related bugs: |
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
Description of the change
To post a comment you must log in.
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
1 | diff --git a/src/maasserver/api/machines.py b/src/maasserver/api/machines.py | |||
2 | index f8cddf0..2c0a116 100644 | |||
3 | --- a/src/maasserver/api/machines.py | |||
4 | +++ b/src/maasserver/api/machines.py | |||
5 | @@ -973,12 +973,23 @@ class MachineHandler(NodeHandler, WorkloadAnnotationsMixin, PowerMixin): | |||
6 | 973 | # postcondition is achieved, so call this success. | 973 | # postcondition is achieved, so call this success. |
7 | 974 | pass | 974 | pass |
8 | 975 | elif machine.status in RELEASABLE_STATUSES: | 975 | elif machine.status in RELEASABLE_STATUSES: |
15 | 976 | machine.release_or_erase( | 976 | scripts = form.cleaned_data["scripts"] |
16 | 977 | request.user, | 977 | |
17 | 978 | form.cleaned_data["comment"], | 978 | params = {} |
18 | 979 | erase=form.cleaned_data["erase"], | 979 | if form.cleaned_data["erase"]: |
19 | 980 | secure_erase=form.cleaned_data["secure_erase"], | 980 | params["wipe-disks"] = {} |
20 | 981 | quick_erase=form.cleaned_data["quick_erase"], | 981 | params["wipe-disks"]["secure_erase"] = form.cleaned_data[ |
21 | 982 | "secure_erase" | ||
22 | 983 | ] | ||
23 | 984 | params["wipe-disks"]["quick_erase"] = form.cleaned_data[ | ||
24 | 985 | "quick_erase" | ||
25 | 986 | ] | ||
26 | 987 | params = params | form.get_script_param_dict(scripts) | ||
27 | 988 | machine.start_releasing( | ||
28 | 989 | user=request.user, | ||
29 | 990 | comment=form.cleaned_data["comment"], | ||
30 | 991 | scripts=scripts, | ||
31 | 992 | script_input=params, | ||
32 | 982 | force=form.cleaned_data["force"], | 993 | force=form.cleaned_data["force"], |
33 | 983 | ) | 994 | ) |
34 | 984 | else: | 995 | else: |
35 | @@ -2225,7 +2236,10 @@ class MachinesHandler(NodesHandler, PowersMixin): | |||
36 | 2225 | # Nothing to do. | 2236 | # Nothing to do. |
37 | 2226 | pass | 2237 | pass |
38 | 2227 | elif machine.status in RELEASABLE_STATUSES: | 2238 | elif machine.status in RELEASABLE_STATUSES: |
40 | 2228 | machine.release_or_erase(request.user, comment) | 2239 | machine.start_releasing( |
41 | 2240 | user=request.user, | ||
42 | 2241 | comment=comment, | ||
43 | 2242 | ) | ||
44 | 2229 | released_ids.append(machine.system_id) | 2243 | released_ids.append(machine.system_id) |
45 | 2230 | else: | 2244 | else: |
46 | 2231 | failed.append( | 2245 | failed.append( |
47 | diff --git a/src/maasserver/api/tests/test_machine.py b/src/maasserver/api/tests/test_machine.py | |||
48 | index f5a2b33..42b3511 100644 | |||
49 | --- a/src/maasserver/api/tests/test_machine.py | |||
50 | +++ b/src/maasserver/api/tests/test_machine.py | |||
51 | @@ -1683,17 +1683,16 @@ class TestMachineAPI(APITestCase.ForUser): | |||
52 | 1683 | ) | 1683 | ) |
53 | 1684 | self.become_admin() | 1684 | self.become_admin() |
54 | 1685 | comment = factory.make_name("comment") | 1685 | comment = factory.make_name("comment") |
56 | 1686 | machine_release = self.patch(node_module.Machine, "release_or_erase") | 1686 | machine_release = self.patch(node_module.Machine, "start_releasing") |
57 | 1687 | self.client.post( | 1687 | self.client.post( |
58 | 1688 | self.get_machine_uri(machine), | 1688 | self.get_machine_uri(machine), |
59 | 1689 | {"op": "release", "comment": comment}, | 1689 | {"op": "release", "comment": comment}, |
60 | 1690 | ) | 1690 | ) |
61 | 1691 | machine_release.assert_called_once_with( | 1691 | machine_release.assert_called_once_with( |
67 | 1692 | self.user, | 1692 | user=self.user, |
68 | 1693 | comment, | 1693 | comment=comment, |
69 | 1694 | erase=False, | 1694 | scripts=[], |
70 | 1695 | quick_erase=False, | 1695 | script_input={}, |
66 | 1696 | secure_erase=False, | ||
71 | 1697 | force=False, | 1696 | force=False, |
72 | 1698 | ) | 1697 | ) |
73 | 1699 | 1698 | ||
74 | @@ -1707,7 +1706,7 @@ class TestMachineAPI(APITestCase.ForUser): | |||
75 | 1707 | self.become_admin() | 1706 | self.become_admin() |
76 | 1708 | secure_erase = factory.pick_bool() | 1707 | secure_erase = factory.pick_bool() |
77 | 1709 | quick_erase = factory.pick_bool() | 1708 | quick_erase = factory.pick_bool() |
79 | 1710 | machine_release = self.patch(node_module.Machine, "release_or_erase") | 1709 | machine_release = self.patch(node_module.Machine, "start_releasing") |
80 | 1711 | self.client.post( | 1710 | self.client.post( |
81 | 1712 | self.get_machine_uri(machine), | 1711 | self.get_machine_uri(machine), |
82 | 1713 | { | 1712 | { |
83 | @@ -1718,11 +1717,15 @@ class TestMachineAPI(APITestCase.ForUser): | |||
84 | 1718 | }, | 1717 | }, |
85 | 1719 | ) | 1718 | ) |
86 | 1720 | machine_release.assert_called_once_with( | 1719 | machine_release.assert_called_once_with( |
92 | 1721 | self.user, | 1720 | user=self.user, |
93 | 1722 | "", | 1721 | comment="", |
94 | 1723 | erase=True, | 1722 | scripts=["wipe-disks"], |
95 | 1724 | quick_erase=quick_erase, | 1723 | script_input={ |
96 | 1725 | secure_erase=secure_erase, | 1724 | "wipe-disks": { |
97 | 1725 | "secure_erase": secure_erase, | ||
98 | 1726 | "quick_erase": quick_erase, | ||
99 | 1727 | } | ||
100 | 1728 | }, | ||
101 | 1726 | force=False, | 1729 | force=False, |
102 | 1727 | ) | 1730 | ) |
103 | 1728 | 1731 | ||
104 | @@ -1735,16 +1738,15 @@ class TestMachineAPI(APITestCase.ForUser): | |||
105 | 1735 | ) | 1738 | ) |
106 | 1736 | self.become_admin() | 1739 | self.become_admin() |
107 | 1737 | force = factory.pick_bool() | 1740 | force = factory.pick_bool() |
109 | 1738 | machine_release = self.patch(node_module.Machine, "release_or_erase") | 1741 | machine_release = self.patch(node_module.Machine, "start_releasing") |
110 | 1739 | self.client.post( | 1742 | self.client.post( |
111 | 1740 | self.get_machine_uri(machine), {"op": "release", "force": force} | 1743 | self.get_machine_uri(machine), {"op": "release", "force": force} |
112 | 1741 | ) | 1744 | ) |
113 | 1742 | machine_release.assert_called_once_with( | 1745 | machine_release.assert_called_once_with( |
119 | 1743 | self.user, | 1746 | user=self.user, |
120 | 1744 | "", | 1747 | comment="", |
121 | 1745 | erase=False, | 1748 | scripts=[], |
122 | 1746 | quick_erase=False, | 1749 | script_input={}, |
118 | 1747 | secure_erase=False, | ||
123 | 1748 | force=force, | 1750 | force=force, |
124 | 1749 | ) | 1751 | ) |
125 | 1750 | 1752 | ||
126 | @@ -1756,14 +1758,13 @@ class TestMachineAPI(APITestCase.ForUser): | |||
127 | 1756 | power_state=POWER_STATE.OFF, | 1758 | power_state=POWER_STATE.OFF, |
128 | 1757 | ) | 1759 | ) |
129 | 1758 | self.become_admin() | 1760 | self.become_admin() |
131 | 1759 | machine_release = self.patch(node_module.Machine, "release_or_erase") | 1761 | machine_release = self.patch(node_module.Machine, "start_releasing") |
132 | 1760 | self.client.post(self.get_machine_uri(machine), {"op": "release"}) | 1762 | self.client.post(self.get_machine_uri(machine), {"op": "release"}) |
133 | 1761 | machine_release.assert_called_once_with( | 1763 | machine_release.assert_called_once_with( |
139 | 1762 | self.user, | 1764 | user=self.user, |
140 | 1763 | "", | 1765 | comment="", |
141 | 1764 | erase=False, | 1766 | scripts=[], |
142 | 1765 | quick_erase=False, | 1767 | script_input={}, |
138 | 1766 | secure_erase=False, | ||
143 | 1767 | force=False, | 1768 | force=False, |
144 | 1768 | ) | 1769 | ) |
145 | 1769 | 1770 | ||
146 | diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py | |||
147 | index 0e66b44..e34f5d4 100644 | |||
148 | --- a/src/maasserver/models/node.py | |||
149 | +++ b/src/maasserver/models/node.py | |||
150 | @@ -19,6 +19,7 @@ from itertools import chain, count | |||
151 | 19 | import json | 19 | import json |
152 | 20 | import logging | 20 | import logging |
153 | 21 | from operator import attrgetter | 21 | from operator import attrgetter |
154 | 22 | import os | ||
155 | 22 | import random | 23 | import random |
156 | 23 | import re | 24 | import re |
157 | 24 | import socket | 25 | import socket |
158 | @@ -174,7 +175,11 @@ from metadataserver.enum import ( | |||
159 | 174 | SCRIPT_STATUS_FAILED, | 175 | SCRIPT_STATUS_FAILED, |
160 | 175 | SCRIPT_STATUS_RUNNING_OR_PENDING, | 176 | SCRIPT_STATUS_RUNNING_OR_PENDING, |
161 | 176 | ) | 177 | ) |
163 | 177 | from metadataserver.user_data import generate_user_data_for_status | 178 | from metadataserver.user_data import ( |
164 | 179 | generate_user_data, | ||
165 | 180 | generate_user_data_for_status, | ||
166 | 181 | ) | ||
167 | 182 | from metadataserver.user_data.snippets import get_userdata_template_dir | ||
168 | 178 | from provisioningserver.drivers.osystem import BOOT_IMAGE_PURPOSE | 183 | from provisioningserver.drivers.osystem import BOOT_IMAGE_PURPOSE |
169 | 179 | from provisioningserver.drivers.pod import Capabilities | 184 | from provisioningserver.drivers.pod import Capabilities |
170 | 180 | from provisioningserver.drivers.power.ipmi import IPMI_BOOT_TYPE | 185 | from provisioningserver.drivers.power.ipmi import IPMI_BOOT_TYPE |
171 | @@ -3623,6 +3628,144 @@ class Node(CleanSave, TimestampedModel): | |||
172 | 3623 | % (self.system_id, NODE_STATUS_CHOICES_DICT[self.status]) | 3628 | % (self.system_id, NODE_STATUS_CHOICES_DICT[self.status]) |
173 | 3624 | ) | 3629 | ) |
174 | 3625 | 3630 | ||
175 | 3631 | def start_releasing( | ||
176 | 3632 | self, user, comment=None, scripts=None, script_input=None, force=False | ||
177 | 3633 | ): | ||
178 | 3634 | """Start the release process for the machine.""" | ||
179 | 3635 | from maasserver.models import ScriptSet | ||
180 | 3636 | |||
181 | 3637 | if scripts is None: | ||
182 | 3638 | scripts = [] | ||
183 | 3639 | |||
184 | 3640 | self.maybe_delete_pods(not force) | ||
185 | 3641 | |||
186 | 3642 | config = Config.objects.get_configs( | ||
187 | 3643 | [ | ||
188 | 3644 | "commissioning_osystem", | ||
189 | 3645 | "commissioning_distro_series", | ||
190 | 3646 | "default_osystem", | ||
191 | 3647 | "default_distro_series", | ||
192 | 3648 | "disk_erase_with_secure_erase", | ||
193 | 3649 | "disk_erase_with_quick_erase", | ||
194 | 3650 | "enable_disk_erasing_on_release", | ||
195 | 3651 | ] | ||
196 | 3652 | ) | ||
197 | 3653 | |||
198 | 3654 | if config.get("enable_disk_erasing_on_release"): | ||
199 | 3655 | scripts.append("wipe-disks") | ||
200 | 3656 | |||
201 | 3657 | if not scripts: | ||
202 | 3658 | self.release(user, comment) | ||
203 | 3659 | return | ||
204 | 3660 | |||
205 | 3661 | self.current_release_script_set = ( | ||
206 | 3662 | ScriptSet.objects.create_release_script_set( | ||
207 | 3663 | self, scripts=scripts, script_input=script_input | ||
208 | 3664 | ) | ||
209 | 3665 | ) | ||
210 | 3666 | self.save() | ||
211 | 3667 | |||
212 | 3668 | template_file = os.path.join( | ||
213 | 3669 | get_userdata_template_dir(), "script_runner.template" | ||
214 | 3670 | ) | ||
215 | 3671 | user_data = generate_user_data(self, template_file) | ||
216 | 3672 | |||
217 | 3673 | failed_status = NODE_STATUS.FAILED_RELEASING | ||
218 | 3674 | # Before machine release scripts were introduced there was Erase Disk | ||
219 | 3675 | # functionality. We want to maintain backward compatible statuses | ||
220 | 3676 | # to be reported when only "wipe-disk" script is executed. | ||
221 | 3677 | if len(scripts) == 1 and "wipe-disks" in scripts: | ||
222 | 3678 | self._register_request_event( | ||
223 | 3679 | user, | ||
224 | 3680 | EVENT_TYPES.REQUEST_NODE_ERASE_DISK, | ||
225 | 3681 | action="start disk erasing", | ||
226 | 3682 | comment=comment, | ||
227 | 3683 | ) | ||
228 | 3684 | old_status = self.update_status(NODE_STATUS.DISK_ERASING) | ||
229 | 3685 | failed_status = NODE_STATUS.FAILED_DISK_ERASING | ||
230 | 3686 | else: | ||
231 | 3687 | self._register_request_event( | ||
232 | 3688 | user, | ||
233 | 3689 | EVENT_TYPES.REQUEST_NODE_RELEASE, | ||
234 | 3690 | action="start node releasing", | ||
235 | 3691 | comment=comment, | ||
236 | 3692 | ) | ||
237 | 3693 | |||
238 | 3694 | old_status = self.update_status(NODE_STATUS.RELEASING) | ||
239 | 3695 | |||
240 | 3696 | self.save() | ||
241 | 3697 | |||
242 | 3698 | try: | ||
243 | 3699 | # Node.start() has synchronous and asynchronous parts, so catch | ||
244 | 3700 | # exceptions arising synchronously, and chain callbacks to the | ||
245 | 3701 | # Deferred it returns for the asynchronous (post-commit) bits. | ||
246 | 3702 | starting = self._start( | ||
247 | 3703 | user, | ||
248 | 3704 | user_data, | ||
249 | 3705 | old_status, | ||
250 | 3706 | allow_power_cycle=True, | ||
251 | 3707 | config=config, | ||
252 | 3708 | ) | ||
253 | 3709 | except Exception as error: | ||
254 | 3710 | # We always mark the node as failed here, although we could | ||
255 | 3711 | # potentially move it back to the state it was in previously. For | ||
256 | 3712 | # now, though, this is safer, since it marks the node as needing | ||
257 | 3713 | # attention. | ||
258 | 3714 | self.update_status(failed_status) | ||
259 | 3715 | self.save() | ||
260 | 3716 | maaslog.error( | ||
261 | 3717 | f"{self.hostname}: Could not start node for release: {error}" | ||
262 | 3718 | ) | ||
263 | 3719 | # Let the exception bubble up, since the UI or API will have to | ||
264 | 3720 | # deal with it. | ||
265 | 3721 | raise | ||
266 | 3722 | else: | ||
267 | 3723 | # Don't permit naive mocking of start(); it causes too much | ||
268 | 3724 | # confusion when testing. Return a Deferred from side_effect. | ||
269 | 3725 | assert isinstance(starting, Deferred) or starting is None | ||
270 | 3726 | |||
271 | 3727 | if starting is None: | ||
272 | 3728 | starting = post_commit() | ||
273 | 3729 | # MAAS cannot start the node itself. | ||
274 | 3730 | is_starting = False | ||
275 | 3731 | else: | ||
276 | 3732 | # MAAS can direct the node to start. | ||
277 | 3733 | is_starting = True | ||
278 | 3734 | |||
279 | 3735 | @asynchronous | ||
280 | 3736 | def async_start(is_starting, hostname): | ||
281 | 3737 | if is_starting: | ||
282 | 3738 | maaslog.info(f"{hostname}: Release started") | ||
283 | 3739 | else: | ||
284 | 3740 | maaslog.warning( | ||
285 | 3741 | f"{hostname}: Could not start node for release; it " | ||
286 | 3742 | "must be started manually", | ||
287 | 3743 | ) | ||
288 | 3744 | |||
289 | 3745 | starting.addCallback( | ||
290 | 3746 | callOut, | ||
291 | 3747 | async_start, | ||
292 | 3748 | is_starting, | ||
293 | 3749 | self.hostname, | ||
294 | 3750 | ) | ||
295 | 3751 | |||
296 | 3752 | # If there's an error, reset the node's status. | ||
297 | 3753 | starting.addErrback( | ||
298 | 3754 | callOutToDatabase, | ||
299 | 3755 | Node._set_status, | ||
300 | 3756 | self.system_id, | ||
301 | 3757 | status=failed_status, | ||
302 | 3758 | ) | ||
303 | 3759 | |||
304 | 3760 | def eb_start(failure, hostname): | ||
305 | 3761 | maaslog.error( | ||
306 | 3762 | f"{hostname}: Could not start node for " | ||
307 | 3763 | f"releasing: {failure.getErrorMessage()}", | ||
308 | 3764 | ) | ||
309 | 3765 | return failure # Propagate. | ||
310 | 3766 | |||
311 | 3767 | return starting.addErrback(eb_start, self.hostname) | ||
312 | 3768 | |||
313 | 3626 | def release(self, user=None, comment=None): | 3769 | def release(self, user=None, comment=None): |
314 | 3627 | self._register_request_event( | 3770 | self._register_request_event( |
315 | 3628 | user, | 3771 | user, |
316 | @@ -3758,31 +3901,6 @@ class Node(CleanSave, TimestampedModel): | |||
317 | 3758 | # Remove all set owner data. | 3901 | # Remove all set owner data. |
318 | 3759 | OwnerData.objects.filter(node=self).delete() | 3902 | OwnerData.objects.filter(node=self).delete() |
319 | 3760 | 3903 | ||
320 | 3761 | def release_or_erase( | ||
321 | 3762 | self, | ||
322 | 3763 | user, | ||
323 | 3764 | comment=None, | ||
324 | 3765 | erase=False, | ||
325 | 3766 | secure_erase=None, | ||
326 | 3767 | quick_erase=None, | ||
327 | 3768 | force=False, | ||
328 | 3769 | ): | ||
329 | 3770 | """Either release the node or erase the node then release it, depending | ||
330 | 3771 | on settings and parameters.""" | ||
331 | 3772 | self.maybe_delete_pods(not force) | ||
332 | 3773 | erase_on_release = Config.objects.get_config( | ||
333 | 3774 | "enable_disk_erasing_on_release" | ||
334 | 3775 | ) | ||
335 | 3776 | if erase or erase_on_release: | ||
336 | 3777 | self.start_disk_erasing( | ||
337 | 3778 | user, | ||
338 | 3779 | comment, | ||
339 | 3780 | secure_erase=secure_erase, | ||
340 | 3781 | quick_erase=quick_erase, | ||
341 | 3782 | ) | ||
342 | 3783 | else: | ||
343 | 3784 | self.release(user, comment) | ||
344 | 3785 | |||
345 | 3786 | def maybe_delete_pods(self, dry_run: bool): | 3904 | def maybe_delete_pods(self, dry_run: bool): |
346 | 3787 | """Check if any pods are associated with this Node. | 3905 | """Check if any pods are associated with this Node. |
347 | 3788 | 3906 | ||
348 | diff --git a/src/maasserver/models/tests/test_node.py b/src/maasserver/models/tests/test_node.py | |||
349 | index 7706930..6195c28 100644 | |||
350 | --- a/src/maasserver/models/tests/test_node.py | |||
351 | +++ b/src/maasserver/models/tests/test_node.py | |||
352 | @@ -22,6 +22,7 @@ from fixtures import LoggerFixture | |||
353 | 22 | from netaddr import IPAddress, IPNetwork | 22 | from netaddr import IPAddress, IPNetwork |
354 | 23 | from testscenarios import multiply_scenarios | 23 | from testscenarios import multiply_scenarios |
355 | 24 | from twisted.internet import defer | 24 | from twisted.internet import defer |
356 | 25 | from twisted.internet.defer import succeed | ||
357 | 25 | from twisted.internet.error import ConnectionDone | 26 | from twisted.internet.error import ConnectionDone |
358 | 26 | import yaml | 27 | import yaml |
359 | 27 | 28 | ||
360 | @@ -7273,44 +7274,40 @@ class TestNodeManagerGetNodesRBAC(MAASServerTestCase): | |||
361 | 7273 | 7274 | ||
362 | 7274 | 7275 | ||
363 | 7275 | class TestNodeErase(MAASServerTestCase): | 7276 | class TestNodeErase(MAASServerTestCase): |
365 | 7276 | def test_release_or_erase_erases_when_enabled(self): | 7277 | def test_start_releasing_erases_when_enabled(self): |
366 | 7277 | owner = factory.make_User() | 7278 | owner = factory.make_User() |
367 | 7278 | node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner) | 7279 | node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner) |
368 | 7279 | Config.objects.set_config("enable_disk_erasing_on_release", True) | 7280 | Config.objects.set_config("enable_disk_erasing_on_release", True) |
369 | 7280 | erase_mock = self.patch_autospec(node, "start_disk_erasing") | ||
370 | 7281 | release_mock = self.patch_autospec(node, "release") | 7281 | release_mock = self.patch_autospec(node, "release") |
375 | 7282 | node.release_or_erase(owner) | 7282 | self.patch(node, "_start").return_value = succeed(None) |
376 | 7283 | erase_mock.assert_called_once_with( | 7283 | node.start_releasing(owner) |
373 | 7284 | owner, None, secure_erase=None, quick_erase=None | ||
374 | 7285 | ) | ||
377 | 7286 | release_mock.assert_not_called() | 7284 | release_mock.assert_not_called() |
378 | 7287 | 7285 | ||
380 | 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): |
381 | 7289 | owner = factory.make_User() | 7287 | owner = factory.make_User() |
382 | 7290 | node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner) | 7288 | node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner) |
383 | 7291 | Config.objects.set_config("enable_disk_erasing_on_release", False) | 7289 | Config.objects.set_config("enable_disk_erasing_on_release", False) |
384 | 7292 | erase_mock = self.patch_autospec(node, "start_disk_erasing") | ||
385 | 7293 | release_mock = self.patch_autospec(node, "release") | 7290 | release_mock = self.patch_autospec(node, "release") |
386 | 7291 | self.patch(node, "_start").return_value = succeed(None) | ||
387 | 7294 | secure_erase = factory.pick_bool() | 7292 | secure_erase = factory.pick_bool() |
388 | 7295 | quick_erase = factory.pick_bool() | 7293 | quick_erase = factory.pick_bool() |
397 | 7296 | node.release_or_erase( | 7294 | node.start_releasing( |
398 | 7297 | owner, | 7295 | user=owner, |
399 | 7298 | erase=True, | 7296 | scripts=["wipe-disks"], |
400 | 7299 | secure_erase=secure_erase, | 7297 | script_input={ |
401 | 7300 | quick_erase=quick_erase, | 7298 | "secure_erase": secure_erase, |
402 | 7301 | ) | 7299 | "quick_erase": quick_erase, |
403 | 7302 | erase_mock.assert_called_once_with( | 7300 | }, |
396 | 7303 | owner, None, secure_erase=secure_erase, quick_erase=quick_erase | ||
404 | 7304 | ) | 7301 | ) |
405 | 7305 | release_mock.assert_not_called() | 7302 | release_mock.assert_not_called() |
406 | 7306 | 7303 | ||
408 | 7307 | def test_release_or_erase_releases_when_disabled(self): | 7304 | def test_start_releasing_releases_when_disabled(self): |
409 | 7308 | owner = factory.make_User() | 7305 | owner = factory.make_User() |
410 | 7309 | node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner) | 7306 | node = factory.make_Node(status=NODE_STATUS.ALLOCATED, owner=owner) |
411 | 7310 | Config.objects.set_config("enable_disk_erasing_on_release", False) | 7307 | Config.objects.set_config("enable_disk_erasing_on_release", False) |
412 | 7311 | erase_mock = self.patch_autospec(node, "start_disk_erasing") | 7308 | erase_mock = self.patch_autospec(node, "start_disk_erasing") |
413 | 7312 | release_mock = self.patch_autospec(node, "release") | 7309 | release_mock = self.patch_autospec(node, "release") |
415 | 7313 | node.release_or_erase(owner) | 7310 | node.start_releasing(owner) |
416 | 7314 | release_mock.assert_called_once_with(owner, None) | 7311 | release_mock.assert_called_once_with(owner, None) |
417 | 7315 | erase_mock.assert_not_called() | 7312 | erase_mock.assert_not_called() |
418 | 7316 | 7313 | ||
419 | diff --git a/src/maasserver/node_action.py b/src/maasserver/node_action.py | |||
420 | index 0c19d9e..ec7a87d 100644 | |||
421 | --- a/src/maasserver/node_action.py | |||
422 | +++ b/src/maasserver/node_action.py | |||
423 | @@ -728,11 +728,22 @@ class Release(NodeAction): | |||
424 | 728 | if not form.is_valid(): | 728 | if not form.is_valid(): |
425 | 729 | raise NodeActionError(form.errors) | 729 | raise NodeActionError(form.errors) |
426 | 730 | try: | 730 | try: |
432 | 731 | self.node.release_or_erase( | 731 | scripts = [] |
433 | 732 | self.user, | 732 | params = {} |
434 | 733 | erase=form.cleaned_data["erase"], | 733 | if form.cleaned_data["erase"]: |
435 | 734 | secure_erase=form.cleaned_data["secure_erase"], | 734 | scripts.append("wipe-disks") |
436 | 735 | quick_erase=form.cleaned_data["quick_erase"], | 735 | params["wipe-disks"] = {} |
437 | 736 | params["wipe-disks"]["secure_erase"] = form.cleaned_data[ | ||
438 | 737 | "secure_erase" | ||
439 | 738 | ] | ||
440 | 739 | params["wipe-disks"]["quick_erase"] = form.cleaned_data[ | ||
441 | 740 | "quick_erase" | ||
442 | 741 | ] | ||
443 | 742 | params = params | form.get_script_param_dict(scripts) | ||
444 | 743 | self.node.start_releasing( | ||
445 | 744 | user=self.user, | ||
446 | 745 | scripts=scripts, | ||
447 | 746 | script_input=params, | ||
448 | 736 | ) | 747 | ) |
449 | 737 | except RPC_EXCEPTIONS + (ExternalProcessError,) as exception: | 748 | except RPC_EXCEPTIONS + (ExternalProcessError,) as exception: |
450 | 738 | raise NodeActionError(exception) | 749 | raise NodeActionError(exception) |
451 | diff --git a/src/maasserver/node_status.py b/src/maasserver/node_status.py | |||
452 | index 3a344ef..4ea19f1 100644 | |||
453 | --- a/src/maasserver/node_status.py | |||
454 | +++ b/src/maasserver/node_status.py | |||
455 | @@ -312,6 +312,7 @@ COMMISSIONING_LIKE_STATUSES = [ | |||
456 | 312 | NODE_STATUS.COMMISSIONING, | 312 | NODE_STATUS.COMMISSIONING, |
457 | 313 | NODE_STATUS.DISK_ERASING, | 313 | NODE_STATUS.DISK_ERASING, |
458 | 314 | NODE_STATUS.ENTERING_RESCUE_MODE, | 314 | NODE_STATUS.ENTERING_RESCUE_MODE, |
459 | 315 | NODE_STATUS.RELEASING, | ||
460 | 315 | NODE_STATUS.RESCUE_MODE, | 316 | NODE_STATUS.RESCUE_MODE, |
461 | 316 | NODE_STATUS.TESTING, | 317 | NODE_STATUS.TESTING, |
462 | 317 | ] | 318 | ] |
463 | diff --git a/src/maasserver/tests/test_node_action.py b/src/maasserver/tests/test_node_action.py | |||
464 | index 41e0a04..b04105e 100644 | |||
465 | --- a/src/maasserver/tests/test_node_action.py | |||
466 | +++ b/src/maasserver/tests/test_node_action.py | |||
467 | @@ -1555,15 +1555,19 @@ class TestReleaseAction(MAASServerTestCase): | |||
468 | 1555 | owner=user, | 1555 | owner=user, |
469 | 1556 | power_parameters=params, | 1556 | power_parameters=params, |
470 | 1557 | ) | 1557 | ) |
472 | 1558 | node_release_or_erase = self.patch_autospec(node, "release_or_erase") | 1558 | node_start_releasing = self.patch_autospec(node, "start_releasing") |
473 | 1559 | 1559 | ||
474 | 1560 | with post_commit_hooks: | 1560 | with post_commit_hooks: |
475 | 1561 | Release(node, user, request).execute( | 1561 | Release(node, user, request).execute( |
476 | 1562 | erase=True, secure_erase=True, quick_erase=True | 1562 | erase=True, secure_erase=True, quick_erase=True |
477 | 1563 | ) | 1563 | ) |
478 | 1564 | 1564 | ||
481 | 1565 | node_release_or_erase.assert_called_once_with( | 1565 | node_start_releasing.assert_called_once_with( |
482 | 1566 | user, erase=True, secure_erase=True, quick_erase=True | 1566 | user=user, |
483 | 1567 | scripts=["wipe-disks"], | ||
484 | 1568 | script_input={ | ||
485 | 1569 | "wipe-disks": {"secure_erase": True, "quick_erase": True} | ||
486 | 1570 | }, | ||
487 | 1567 | ) | 1571 | ) |
488 | 1568 | 1572 | ||
489 | 1569 | 1573 | ||
490 | diff --git a/src/metadataserver/api.py b/src/metadataserver/api.py | |||
491 | index d2394c2..35f9a9e 100644 | |||
492 | --- a/src/metadataserver/api.py | |||
493 | +++ b/src/metadataserver/api.py | |||
494 | @@ -519,6 +519,7 @@ class VersionIndexHandler(MetadataViewHandler): | |||
495 | 519 | NODE_STATUS.FAILED_DEPLOYMENT, | 519 | NODE_STATUS.FAILED_DEPLOYMENT, |
496 | 520 | NODE_STATUS.READY, | 520 | NODE_STATUS.READY, |
497 | 521 | NODE_STATUS.DISK_ERASING, | 521 | NODE_STATUS.DISK_ERASING, |
498 | 522 | NODE_STATUS.RELEASING, | ||
499 | 522 | NODE_STATUS.ENTERING_RESCUE_MODE, | 523 | NODE_STATUS.ENTERING_RESCUE_MODE, |
500 | 523 | NODE_STATUS.RESCUE_MODE, | 524 | NODE_STATUS.RESCUE_MODE, |
501 | 524 | NODE_STATUS.FAILED_ENTERING_RESCUE_MODE, | 525 | NODE_STATUS.FAILED_ENTERING_RESCUE_MODE, |
502 | @@ -531,6 +532,7 @@ class VersionIndexHandler(MetadataViewHandler): | |||
503 | 531 | NODE_STATUS.COMMISSIONING, | 532 | NODE_STATUS.COMMISSIONING, |
504 | 532 | NODE_STATUS.DEPLOYING, | 533 | NODE_STATUS.DEPLOYING, |
505 | 533 | NODE_STATUS.DISK_ERASING, | 534 | NODE_STATUS.DISK_ERASING, |
506 | 535 | NODE_STATUS.RELEASING, | ||
507 | 534 | NODE_STATUS.ENTERING_RESCUE_MODE, | 536 | NODE_STATUS.ENTERING_RESCUE_MODE, |
508 | 535 | NODE_STATUS.RESCUE_MODE, | 537 | NODE_STATUS.RESCUE_MODE, |
509 | 536 | NODE_STATUS.TESTING, | 538 | NODE_STATUS.TESTING, |
510 | @@ -789,6 +791,13 @@ class VersionIndexHandler(MetadataViewHandler): | |||
511 | 789 | self._store_results( | 791 | self._store_results( |
512 | 790 | node, node.current_release_script_set, request, status | 792 | node, node.current_release_script_set, request, status |
513 | 791 | ) | 793 | ) |
514 | 794 | |||
515 | 795 | if status == SIGNAL_STATUS.OK: | ||
516 | 796 | node.release() | ||
517 | 797 | |||
518 | 798 | if status == SIGNAL_STATUS.FAILED: | ||
519 | 799 | node.mark_failed(comment="Failed to release machine.") | ||
520 | 800 | |||
521 | 792 | return None | 801 | return None |
522 | 793 | 802 | ||
523 | 794 | @operation(idempotent=False) | 803 | @operation(idempotent=False) |
524 | diff --git a/src/metadataserver/api_twisted.py b/src/metadataserver/api_twisted.py | |||
525 | index ca432fa..a5c4f9e 100644 | |||
526 | --- a/src/metadataserver/api_twisted.py | |||
527 | +++ b/src/metadataserver/api_twisted.py | |||
528 | @@ -503,6 +503,12 @@ class StatusWorkerService(TimerService): | |||
529 | 503 | comment="Failed to erase disks.", commit=False | 503 | comment="Failed to erase disks.", commit=False |
530 | 504 | ) | 504 | ) |
531 | 505 | save_node = True | 505 | save_node = True |
532 | 506 | elif node.status == NODE_STATUS.RELEASING: | ||
533 | 507 | if failed: | ||
534 | 508 | node.mark_failed( | ||
535 | 509 | comment="Failed to release machine.", commit=False | ||
536 | 510 | ) | ||
537 | 511 | save_node = True | ||
538 | 506 | # Deallocate the node if we enter any terminal state. | 512 | # Deallocate the node if we enter any terminal state. |
539 | 507 | if node.node_type == NODE_TYPE.MACHINE and node.status in [ | 513 | if node.node_type == NODE_TYPE.MACHINE and node.status in [ |
540 | 508 | NODE_STATUS.READY, | 514 | NODE_STATUS.READY, |
UNIT TESTS scriptset lp:~troyanov/maas/+git/maas into -b master lp:~maas-committers/maas
-b node-release-
STATUS: SUCCESS de1ee90d7bcb88f cf8cf6e02d
COMMIT: 5672c8464f22aa4