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
=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py 2014-06-17 01:31:17 +0000
+++ src/maasserver/forms.py 2014-06-17 08:56:05 +0000
@@ -1484,8 +1484,12 @@
1484 if action_instance.is_permitted():1484 if action_instance.is_permitted():
1485 # Do not let execute() raise a redirect exception1485 # Do not let execute() raise a redirect exception
1486 # because this action is part of a bulk operation.1486 # because this action is part of a bulk operation.
1487 action_instance.execute(allow_redirect=False)1487 try:
1488 done += 11488 action_instance.execute(allow_redirect=False)
1489 except NodeActionError:
1490 not_actionable += 1
1491 else:
1492 done += 1
1489 else:1493 else:
1490 not_permitted += 11494 not_permitted += 1
1491 else:1495 else:
@@ -1521,6 +1525,12 @@
1521 transition was not allowed and the number of nodes for which the1525 transition was not allowed and the number of nodes for which the
1522 action could not be performed because the user does not have the1526 action could not be performed because the user does not have the
1523 required permission.1527 required permission.
1528
1529 Currently, in the event of a NodeActionError this is thrown into the
1530 "not actionable" bucket in lieu of an overhaul of this form to
1531 properly report errors for part-failing actions. In this case
1532 the transaction will still be valid for the actions that did complete
1533 successfully.
1524 """1534 """
1525 action_name = self.cleaned_data['action']1535 action_name = self.cleaned_data['action']
1526 system_ids = self.cleaned_data['system_id']1536 system_ids = self.cleaned_data['system_id']
15271537
=== modified file 'src/maasserver/tests/test_forms.py'
--- src/maasserver/tests/test_forms.py 2014-06-17 08:17:59 +0000
+++ src/maasserver/tests/test_forms.py 2014-06-17 08:56:05 +0000
@@ -1680,6 +1680,25 @@
1680 [[], node3.system_id],1680 [[], node3.system_id],
1681 [existing_nodes, node3_system_id])1681 [existing_nodes, node3_system_id])
16821682
1683 def test_perform_action_catches_start_action_errors(self):
1684 error_text = factory.getRandomString(prefix="NodeActionError")
1685 exc = NodeActionError(error_text)
1686 self.patch(StartNode, "execute").side_effect = exc
1687 user = factory.make_user()
1688 factory.make_sshkey(user)
1689 node = factory.make_node(status=NODE_STATUS.READY, owner=user)
1690 form = BulkNodeActionForm(
1691 user=user,
1692 data=dict(
1693 action=StartNode.name,
1694 system_id=[node.system_id]))
1695
1696 self.assertTrue(form.is_valid(), form._errors)
1697 done, not_actionable, not_permitted = form.save()
1698 self.assertEqual(
1699 [0, 1, 0],
1700 [done, not_actionable, not_permitted])
1701
1683 def test_first_action_is_empty(self):1702 def test_first_action_is_empty(self):
1684 form = BulkNodeActionForm(user=factory.make_admin())1703 form = BulkNodeActionForm(user=factory.make_admin())
1685 action = form.fields['action']1704 action = form.fields['action']