Merge lp:~julian-edwards/maas/bulk_action_error_catch into lp:~maas-committers/maas/trunk

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: 2428
Proposed branch: lp:~julian-edwards/maas/bulk_action_error_catch
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 61 lines (+31/-2)
2 files modified
src/maasserver/forms.py (+12/-2)
src/maasserver/tests/test_forms.py (+19/-0)
To merge this branch: bzr merge lp:~julian-edwards/maas/bulk_action_error_catch
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+223345@code.launchpad.net

Commit message

Ensure that BulkActionForm catches the new NodeActionError raised inside StartNode. Previously it resulted in an internal server error traceback.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

This is stretching the definition of "not actionable." You'll want to update the docstring for BulkNodeActionForm.save: it still says that this outcome is for disallowed state transitions.

Exception safety w.r.t. NodeActionError is important here. You assume that the transaction will still be valid when this happens, which is not what you'd expect for your average exception coming out of the action. You're making this part of the contract for NodeAction.execute, so document it in the docstring.

review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :

Thank you for the review.

Yes it is indeed stretching it. I was looking for a quick fix, otherwise I'd be doing a large internal API change here which I really didn't fancy.

WRT to valid transactions; I did file another bug today about this mess. The notion of what is and isn't atomic is a little frustrating. Things that are not atomic (like this) simply fail to report any useful info, and *that* is the part of the API that needs to change.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/forms.py'
2--- src/maasserver/forms.py 2014-06-17 01:31:17 +0000
3+++ src/maasserver/forms.py 2014-06-17 08:56:05 +0000
4@@ -1484,8 +1484,12 @@
5 if action_instance.is_permitted():
6 # Do not let execute() raise a redirect exception
7 # because this action is part of a bulk operation.
8- action_instance.execute(allow_redirect=False)
9- done += 1
10+ try:
11+ action_instance.execute(allow_redirect=False)
12+ except NodeActionError:
13+ not_actionable += 1
14+ else:
15+ done += 1
16 else:
17 not_permitted += 1
18 else:
19@@ -1521,6 +1525,12 @@
20 transition was not allowed and the number of nodes for which the
21 action could not be performed because the user does not have the
22 required permission.
23+
24+ Currently, in the event of a NodeActionError this is thrown into the
25+ "not actionable" bucket in lieu of an overhaul of this form to
26+ properly report errors for part-failing actions. In this case
27+ the transaction will still be valid for the actions that did complete
28+ successfully.
29 """
30 action_name = self.cleaned_data['action']
31 system_ids = self.cleaned_data['system_id']
32
33=== modified file 'src/maasserver/tests/test_forms.py'
34--- src/maasserver/tests/test_forms.py 2014-06-17 08:17:59 +0000
35+++ src/maasserver/tests/test_forms.py 2014-06-17 08:56:05 +0000
36@@ -1680,6 +1680,25 @@
37 [[], node3.system_id],
38 [existing_nodes, node3_system_id])
39
40+ def test_perform_action_catches_start_action_errors(self):
41+ error_text = factory.getRandomString(prefix="NodeActionError")
42+ exc = NodeActionError(error_text)
43+ self.patch(StartNode, "execute").side_effect = exc
44+ user = factory.make_user()
45+ factory.make_sshkey(user)
46+ node = factory.make_node(status=NODE_STATUS.READY, owner=user)
47+ form = BulkNodeActionForm(
48+ user=user,
49+ data=dict(
50+ action=StartNode.name,
51+ system_id=[node.system_id]))
52+
53+ self.assertTrue(form.is_valid(), form._errors)
54+ done, not_actionable, not_permitted = form.save()
55+ self.assertEqual(
56+ [0, 1, 0],
57+ [done, not_actionable, not_permitted])
58+
59 def test_first_action_is_empty(self):
60 form = BulkNodeActionForm(user=factory.make_admin())
61 action = form.fields['action']