Merge ~andreserl/maas:lp1785721_2.4 into maas:2.4

Proposed by Andres Rodriguez
Status: Merged
Approved by: Andres Rodriguez
Approved revision: 87dc33a05f379011f4c2b61e254ae106678e2dfa
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~andreserl/maas:lp1785721_2.4
Merge into: maas:2.4
Diff against target: 281 lines (+152/-10)
5 files modified
src/maasserver/api/resourcepools.py (+29/-3)
src/maasserver/api/tests/test_resourcepool.py (+23/-4)
src/maasserver/models/__init__.py (+1/-0)
src/maasserver/models/resourcepool.py (+43/-2)
src/maasserver/models/tests/test_resourcepool.py (+56/-1)
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+352635@code.launchpad.net

Commit message

Backport 87dc33a - LP: #1785721 - read/update/delete resource pool by name

Allows the API to read/update/delete resource pool by name. This is a backport to maintain backwards compatibility, given that resource pools is introduced in 2.4.1.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

selfie!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/api/resourcepools.py b/src/maasserver/api/resourcepools.py
2index cb232d9..495b640 100644
3--- a/src/maasserver/api/resourcepools.py
4+++ b/src/maasserver/api/resourcepools.py
5@@ -13,8 +13,11 @@ from maasserver.api.support import (
6 ModelCollectionOperationsHandler,
7 ModelOperationsHandler,
8 )
9+from maasserver.enum import NODE_PERMISSION
10+from maasserver.exceptions import MAASAPIValidationError
11 from maasserver.forms import ResourcePoolForm
12 from maasserver.models import ResourcePool
13+from piston3.utils import rc
14
15
16 DISPLAYED_RESOURCEPOOL_FIELDS = (
17@@ -45,14 +48,34 @@ class ResourcePoolHandler(ModelOperationsHandler):
18
19 Returns 404 if the resource pool is not found.
20 """
21- return super().read(request, id=id)
22+ return ResourcePool.objects.get_resource_pool_or_404(
23+ id, request.user, NODE_PERMISSION.VIEW)
24
25 def update(self, request, id):
26 """PUT request. Update resource pool.
27
28+ Please see the documentation for the 'create' operation for detailed
29+ descriptions of each parameter.
30+
31+ Optional parameters
32+ -------------------
33+
34+ name
35+ Name of the resource pool.
36+
37+ description
38+ Description of the resource pool.
39+
40 Returns 404 if the resource pool is not found.
41 """
42- return super().update(request, id=id)
43+
44+ pool = ResourcePool.objects.get_resource_pool_or_404(
45+ id, request.user, NODE_PERMISSION.ADMIN)
46+ form = ResourcePoolForm(instance=pool, data=request.data)
47+ if form.is_valid():
48+ return form.save()
49+ else:
50+ raise MAASAPIValidationError(form.errors)
51
52 def delete(self, request, id):
53 """DELETE request. Delete resource pool.
54@@ -60,7 +83,10 @@ class ResourcePoolHandler(ModelOperationsHandler):
55 Returns 404 if the resource pool is not found.
56 Returns 204 if the resource pool is successfully deleted.
57 """
58- return super().delete(request, id=id)
59+ pool = ResourcePool.objects.get_resource_pool_or_404(
60+ id, request.user, NODE_PERMISSION.ADMIN)
61+ pool.delete()
62+ return rc.DELETED
63
64
65 class ResourcePoolsHandler(ModelCollectionOperationsHandler):
66diff --git a/src/maasserver/api/tests/test_resourcepool.py b/src/maasserver/api/tests/test_resourcepool.py
67index 7e93e43..4e3d2df 100644
68--- a/src/maasserver/api/tests/test_resourcepool.py
69+++ b/src/maasserver/api/tests/test_resourcepool.py
70@@ -57,6 +57,19 @@ class TestResourcePoolAPI(APITestCase.ForUser):
71 self.assertEqual(pool.name, new_name)
72 self.assertEqual(pool.description, new_description)
73
74+ def test_PUT_updates_pool_by_name(self):
75+ self.become_admin()
76+ pool = factory.make_ResourcePool()
77+ new_name = factory.make_name('name')
78+ new_description = factory.make_name('description')
79+ response = self.client.put(
80+ reverse('resourcepool_handler', args=[pool.name]),
81+ {'name': new_name, 'description': new_description})
82+ self.assertEqual(response.status_code, http.client.OK)
83+ pool = reload_object(pool)
84+ self.assertEqual(pool.name, new_name)
85+ self.assertEqual(pool.description, new_description)
86+
87 def test_PUT_missing(self):
88 self.become_admin()
89 description = factory.make_string()
90@@ -72,7 +85,7 @@ class TestResourcePoolAPI(APITestCase.ForUser):
91 {'description': factory.make_string()})
92 self.assertEqual(response.status_code, http.client.FORBIDDEN)
93
94- def test_DELETE_removes_pool(self):
95+ def test_DELETE_removes_pool_by_id(self):
96 self.become_admin()
97 pool = factory.make_ResourcePool()
98 response = self.client.delete(
99@@ -80,6 +93,14 @@ class TestResourcePoolAPI(APITestCase.ForUser):
100 self.assertEqual(response.status_code, http.client.NO_CONTENT)
101 self.assertIsNone(reload_object(pool))
102
103+ def test_DELETE_removes_pool_by_name(self):
104+ self.become_admin()
105+ pool = factory.make_ResourcePool()
106+ response = self.client.delete(
107+ reverse('resourcepool_handler', args=[pool.name]), {})
108+ self.assertEqual(response.status_code, http.client.NO_CONTENT)
109+ self.assertIsNone(reload_object(pool))
110+
111 def test_DELETE_default_pool_denied(self):
112 self.become_admin()
113 response = self.client.delete(
114@@ -100,6 +121,4 @@ class TestResourcePoolAPI(APITestCase.ForUser):
115 response = self.client.delete(
116 reverse('resourcepool_handler', args=[pool.id]))
117 self.assertEqual(response.status_code, http.client.NO_CONTENT)
118- response = self.client.delete(
119- reverse('resourcepool_handler', args=[pool.id]))
120- self.assertEqual(response.status_code, http.client.NO_CONTENT)
121+ self.assertIsNone(reload_object(pool))
122diff --git a/src/maasserver/models/__init__.py b/src/maasserver/models/__init__.py
123index 2904e6b..e4a73b4 100644
124--- a/src/maasserver/models/__init__.py
125+++ b/src/maasserver/models/__init__.py
126@@ -278,6 +278,7 @@ UNRESTRICTED_READ_MODELS = (
127 Domain,
128 Fabric,
129 FanNetwork,
130+ ResourcePool,
131 Space,
132 Subnet,
133 StaticRoute,
134diff --git a/src/maasserver/models/resourcepool.py b/src/maasserver/models/resourcepool.py
135index 377db23..d1651c3 100644
136--- a/src/maasserver/models/resourcepool.py
137+++ b/src/maasserver/models/resourcepool.py
138@@ -11,7 +11,10 @@ __all__ = [
139
140 from datetime import datetime
141
142-from django.core.exceptions import ValidationError
143+from django.core.exceptions import (
144+ PermissionDenied,
145+ ValidationError,
146+)
147 from django.db.models import (
148 CharField,
149 Manager,
150@@ -21,13 +24,27 @@ from maasserver import DefaultMeta
151 from maasserver.fields import MODEL_NAME_VALIDATOR
152 from maasserver.models.cleansave import CleanSave
153 from maasserver.models.timestampedmodel import TimestampedModel
154+from maasserver.utils.orm import MAASQueriesMixin
155
156
157 DEFAULT_RESOURCEPOOL_NAME = 'default'
158 DEFAULT_RESOURCEPOOL_DESCRIPTION = 'Default pool'
159
160
161-class ResourcePoolManager(Manager):
162+class ResourcePoolQueriesMixin(MAASQueriesMixin):
163+
164+ def get_specifiers_q(self, specifiers, separator=':', **kwargs):
165+ specifier_types = {
166+ None: self._add_default_query,
167+ 'name': "__name",
168+ 'id': "__id",
169+ }
170+ return super(ResourcePoolQueriesMixin, self).get_specifiers_q(
171+ specifiers, specifier_types=specifier_types, separator=separator,
172+ **kwargs)
173+
174+
175+class ResourcePoolManager(Manager, ResourcePoolQueriesMixin):
176 """Manager for the :class:`ResourcePool` model."""
177
178 def get_default_resource_pool(self):
179@@ -43,6 +60,30 @@ class ResourcePoolManager(Manager):
180 'updated': now})
181 return pool
182
183+ def get_resource_pool_or_404(self, specifiers, user, perm):
184+ """Fetch a `ResourcePool` by its id. Raise exceptions if no
185+ `ResourcePool` with this id exists or if the provided user has not
186+ the required permission to access this `ResourcePool`.
187+
188+ :param specifiers: A specifier to uniquely locate the ResourcePool.
189+ :type specifiers: unicode
190+ :param user: The user that should be used in the permission check.
191+ :type user: django.contrib.auth.models.User
192+ :param perm: The permission to assert that the user has on the node.
193+ :type perm: unicode
194+ :raises: django.http.Http404_,
195+ :class:`maasserver.exceptions.PermissionDenied`.
196+
197+ .. _django.http.Http404: https://
198+ docs.djangoproject.com/en/dev/topics/http/views/
199+ #the-http404-exception
200+ """
201+ pool = self.get_object_by_specifiers_or_raise(specifiers)
202+ if user.has_perm(perm, pool):
203+ return pool
204+ else:
205+ raise PermissionDenied()
206+
207
208 class ResourcePool(CleanSave, TimestampedModel):
209 """A resource pool."""
210diff --git a/src/maasserver/models/tests/test_resourcepool.py b/src/maasserver/models/tests/test_resourcepool.py
211index 8984826..9875084 100644
212--- a/src/maasserver/models/tests/test_resourcepool.py
213+++ b/src/maasserver/models/tests/test_resourcepool.py
214@@ -3,7 +3,11 @@
215
216 """Test ResourcePool objects."""
217
218-from django.core.exceptions import ValidationError
219+from django.core.exceptions import (
220+ PermissionDenied,
221+ ValidationError,
222+)
223+from maasserver.enum import NODE_PERMISSION
224 from maasserver.models.resourcepool import (
225 DEFAULT_RESOURCEPOOL_DESCRIPTION,
226 DEFAULT_RESOURCEPOOL_NAME,
227@@ -63,3 +67,54 @@ class TestResourcePool(MAASServerTestCase):
228 pool = ResourcePool.objects.get_default_resource_pool()
229 factory.make_Node(pool=pool)
230 self.assertRaises(ValidationError, pool.delete)
231+
232+
233+class TestResourcePoolManagerGetResourcePoolOr404(MAASServerTestCase):
234+
235+ def test__user_view_returns_resource_pool(self):
236+ user = factory.make_User()
237+ pool = factory.make_ResourcePool()
238+ self.assertEqual(
239+ pool,
240+ ResourcePool.objects.get_resource_pool_or_404(
241+ pool.id, user, NODE_PERMISSION.VIEW))
242+
243+ def test__user_edit_raises_PermissionError(self):
244+ user = factory.make_User()
245+ pool = factory.make_ResourcePool()
246+ self.assertRaises(
247+ PermissionDenied,
248+ ResourcePool.objects.get_resource_pool_or_404,
249+ pool.id, user, NODE_PERMISSION.EDIT)
250+
251+ def test__user_admin_raises_PermissionError(self):
252+ user = factory.make_User()
253+ pool = factory.make_ResourcePool()
254+ self.assertRaises(
255+ PermissionDenied,
256+ ResourcePool.objects.get_resource_pool_or_404,
257+ pool.id, user, NODE_PERMISSION.ADMIN)
258+
259+ def test__admin_view_returns_resource_pool(self):
260+ admin = factory.make_admin()
261+ pool = factory.make_ResourcePool()
262+ self.assertEqual(
263+ pool,
264+ ResourcePool.objects.get_resource_pool_or_404(
265+ pool.id, admin, NODE_PERMISSION.VIEW))
266+
267+ def test__admin_edit_returns_resource_pool(self):
268+ admin = factory.make_admin()
269+ pool = factory.make_ResourcePool()
270+ self.assertEqual(
271+ pool,
272+ ResourcePool.objects.get_resource_pool_or_404(
273+ pool.id, admin, NODE_PERMISSION.EDIT))
274+
275+ def test__admin_admin_returns_pool(self):
276+ admin = factory.make_admin()
277+ pool = factory.make_ResourcePool()
278+ self.assertEqual(
279+ pool,
280+ ResourcePool.objects.get_resource_pool_or_404(
281+ pool.id, admin, NODE_PERMISSION.ADMIN))

Subscribers

People subscribed via source and target branches