Merge lp:~rvb/maas/prevent-deletion into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Rejected
Rejected by: Raphaël Badin
Proposed branch: lp:~rvb/maas/prevent-deletion
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 142 lines (+26/-16)
7 files modified
src/maasserver/api/tests/test_node.py (+6/-6)
src/maasserver/models/node.py (+3/-3)
src/maasserver/models/tests/test_node.py (+2/-2)
src/maasserver/node_action.py (+5/-2)
src/maasserver/support/pertenant/tests/test_migration.py (+2/-0)
src/maasserver/support/pertenant/tests/test_utils.py (+2/-0)
src/maasserver/tests/test_node_action.py (+6/-3)
To merge this branch: bzr merge lp:~rvb/maas/prevent-deletion
Reviewer Review Type Date Requested Status
Jason Hobbs (community) Disapprove
Graham Binns (community) Approve
Review via email: mp+240700@code.launchpad.net

Commit message

Do not allow the deletion of a node if it's owned by a user.

Description of the change

This is something we missed when refactoring the node lifecycle: now we have more than one state that means that a node is "in use". In this branch I changed the check that MAAS performs before allowing a node to be deleted: the check is now that it's not owned by someone. This is meant to force an admin (only an admin can delete a node) to consciously release an "in use" node before he's allowed to remove it.

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

Two questions though:

1. Should an admin perhaps be allowed to delete a node that they own, so a user can't grab it at just the wrong moment?

2. Where the error message interpolates the owner, does it get the owner's name, or some other representation? AFAICT the test just uses the same interpolation, so it doesn't actually prove that the message is sensible.

Revision history for this message
Raphaël Badin (rvb) wrote :

> Two questions though:
>
> 1. Should an admin perhaps be allowed to delete a node that they own, so a
> user can't grab it at just the wrong moment?

I don't think that's worth adding: an admin can always mark a node as 'broken' to make sure nobody will grab it.

> 2. Where the error message interpolates the owner, does it get the owner's
> name, or some other representation? AFAICT the test just uses the same
> interpolation, so it doesn't actually prove that the message is sensible.

Good point, I've fixed this by using the username explicitly.

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (21.4 KiB)

The attempt to merge lp:~rvb/maas/prevent-deletion into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Get:2 http://security.ubuntu.com trusty-security Release [62.0 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [62.0 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [49.5 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [11.2 kB]
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [153 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [51.2 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [136 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [89.3 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [356 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [217 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 1,188 kB in 2s (420 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools gjs ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make pep8 postgresql pyflakes python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-mimeparse python-mock python-netaddr python-netifaces python-nose python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twisted python-oops-wsgi python-openssl py...

Revision history for this message
Jason Hobbs (jason-hobbs) wrote :

The ability to deleted an owned node is used by at least one customer - I don't think we should land this.

Their workflow is

1) Deploy a node with Juju
2) Delete the node from MAAS
3) Run the node offline

If they have to release the node first, it will shut the node down when that's not what they want.

review: Disapprove
lp:~rvb/maas/prevent-deletion updated
3336. By Raphaël Badin

Fix test failures.

Revision history for this message
Graham Binns (gmb) wrote :

On 5 November 2014 13:39, Jason Hobbs <email address hidden> wrote:
> The ability to deleted an owned node is used by at least one customer - I don't think we should land this.

!!!

I'd like to understand the use-case for this — other than "this is
what they do," because ISTM this isn't a workflow that we really want
to support.

Revision history for this message
Raphaël Badin (rvb) wrote :

> I'd like to understand the use-case for this — other than "this is
> what they do," because ISTM this isn't a workflow that we really want
> to support.

I see two valid workflows here:
- you might want to deploy nodes with a MAAS server and then use these nodes outside of MAAS (for instance because these nodes need to belong to a different service that is not comfortable letting others keep the credentials to the BMC of their nodes).
- if something goes wrong with a node (let say the BMC dies), it's awkward to force the admins to 'release' a node (because releasing will fail) before deleting the node.

Ultimately, the main thing that I was trying to fix here is the inconsistency; as long as deleting nodes is reserved to the admins, I'm fine with the new solution.

Unmerged revisions

3336. By Raphaël Badin

Fix test failures.

3335. By Raphaël Badin

Use username instead of str(user).

3334. By Raphaël Badin

Do not allow the deletion of a node if it's owned by a user.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/tests/test_node.py'
2--- src/maasserver/api/tests/test_node.py 2014-11-05 10:41:37 +0000
3+++ src/maasserver/api/tests/test_node.py 2014-11-05 13:41:36 +0000
4@@ -27,7 +27,6 @@
5 IPADDRESS_TYPE,
6 NODE_STATUS,
7 NODE_STATUS_CHOICES,
8- NODE_STATUS_CHOICES_DICT,
9 )
10 from maasserver.fields import (
11 MAC,
12@@ -925,24 +924,25 @@
13 def test_DELETE_deletes_node(self):
14 # The api allows to delete a Node.
15 self.become_admin()
16- node = factory.make_Node(owner=self.logged_in_user)
17+ node = factory.make_Node(owner=None)
18 system_id = node.system_id
19 response = self.client.delete(self.get_node_uri(node))
20
21 self.assertEqual(204, response.status_code)
22 self.assertItemsEqual([], Node.objects.filter(system_id=system_id))
23
24- def test_DELETE_cannot_delete_allocated_node(self):
25+ def test_DELETE_cannot_delete_owned_node(self):
26 # The api allows to delete a Node.
27 self.become_admin()
28- node = factory.make_Node(status=NODE_STATUS.ALLOCATED)
29+ user = factory.make_User()
30+ node = factory.make_Node(owner=user)
31 response = self.client.delete(self.get_node_uri(node))
32
33 self.assertEqual(
34 (httplib.CONFLICT,
35- "Cannot delete node %s: node is in state %s." % (
36+ "Cannot delete node %s: node is owned by user %s." % (
37 node.system_id,
38- NODE_STATUS_CHOICES_DICT[NODE_STATUS.ALLOCATED])),
39+ user.username)),
40 (response.status_code, response.content))
41
42 def test_DELETE_deletes_node_fails_if_not_admin(self):
43
44=== modified file 'src/maasserver/models/node.py'
45--- src/maasserver/models/node.py 2014-11-05 13:27:50 +0000
46+++ src/maasserver/models/node.py 2014-11-05 13:41:36 +0000
47@@ -881,10 +881,10 @@
48 def delete(self):
49 """Delete this node."""
50 # Allocated nodes can't be deleted.
51- if self.status == NODE_STATUS.ALLOCATED:
52+ if self.owner is not None:
53 raise NodeStateViolation(
54- "Cannot delete node %s: node is in state %s."
55- % (self.system_id, NODE_STATUS_CHOICES_DICT[self.status]))
56+ "Cannot delete node %s: node is owned by user %s."
57+ % (self.system_id, self.owner.username))
58
59 maaslog.info("%s: Deleting node", self.hostname)
60
61
62=== modified file 'src/maasserver/models/tests/test_node.py'
63--- src/maasserver/models/tests/test_node.py 2014-11-05 13:27:50 +0000
64+++ src/maasserver/models/tests/test_node.py 2014-11-05 13:41:36 +0000
65@@ -383,8 +383,8 @@
66 self.assertRaises(
67 MACAddress.DoesNotExist, MACAddress.objects.get, id=mac.id)
68
69- def test_cannot_delete_allocated_node(self):
70- node = factory.make_Node(status=NODE_STATUS.ALLOCATED)
71+ def test_cannot_delete_owned_node(self):
72+ node = factory.make_Node(owner=factory.make_User())
73 self.assertRaises(NodeStateViolation, node.delete)
74
75 def test_delete_node_also_deletes_related_static_IPs(self):
76
77=== modified file 'src/maasserver/node_action.py'
78--- src/maasserver/node_action.py 2014-11-03 17:18:09 +0000
79+++ src/maasserver/node_action.py 2014-11-05 13:41:36 +0000
80@@ -170,8 +170,11 @@
81 permission = NODE_PERMISSION.ADMIN
82
83 def inhibit(self):
84- if self.node.status == NODE_STATUS.ALLOCATED:
85- return "You cannot delete this node because it's in use."
86+ if self.node.owner is not None:
87+ return (
88+ "You cannot delete this node because it's being used "
89+ "by user %s." % self.node.owner.username
90+ )
91 return None
92
93 def execute(self, allow_redirect=True):
94
95=== modified file 'src/maasserver/support/pertenant/tests/test_migration.py'
96--- src/maasserver/support/pertenant/tests/test_migration.py 2014-09-10 16:20:31 +0000
97+++ src/maasserver/support/pertenant/tests/test_migration.py 2014-11-05 13:41:36 +0000
98@@ -167,6 +167,8 @@
99 factory.make_User()
100 node = factory.make_Node(owner=user)
101 make_provider_state_file(node)
102+ node.owner = None
103+ node.save()
104 node.delete() # Orphan the state.
105 self.assertEqual(get_legacy_user(), get_destination_user())
106
107
108=== modified file 'src/maasserver/support/pertenant/tests/test_utils.py'
109--- src/maasserver/support/pertenant/tests/test_utils.py 2014-09-10 16:20:31 +0000
110+++ src/maasserver/support/pertenant/tests/test_utils.py 2014-11-05 13:41:36 +0000
111@@ -72,6 +72,8 @@
112 def test_returns_None_if_node_does_not_exist(self):
113 node = factory.make_Node(owner=factory.make_User())
114 make_provider_state_file(node=node)
115+ node.owner = None
116+ node.save()
117 node.delete()
118 self.assertIsNone(get_bootstrap_node_owner())
119
120
121=== modified file 'src/maasserver/tests/test_node_action.py'
122--- src/maasserver/tests/test_node_action.py 2014-11-05 04:40:21 +0000
123+++ src/maasserver/tests/test_node_action.py 2014-11-05 13:41:36 +0000
124@@ -195,12 +195,15 @@
125
126 class TestDeleteNodeAction(MAASServerTestCase):
127
128- def test_Delete_inhibit_when_node_is_allocated(self):
129- node = factory.make_Node(status=NODE_STATUS.ALLOCATED)
130+ def test_Delete_inhibit_when_node_is_owned(self):
131+ user = factory.make_User()
132+ node = factory.make_Node(owner=user)
133 action = Delete(node, factory.make_admin())
134 inhibition = action.inhibit()
135 self.assertEqual(
136- "You cannot delete this node because it's in use.", inhibition)
137+ "You cannot delete this node because it's being used "
138+ "by user %s." % user.username,
139+ inhibition)
140
141 def test_Delete_does_not_inhibit_otherwise(self):
142 node = factory.make_Node(status=NODE_STATUS.FAILED_COMMISSIONING)