Merge lp:~rvb/maas/api-update-cluster into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 1469
Proposed branch: lp:~rvb/maas/api-update-cluster
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 286 lines (+103/-31)
4 files modified
src/maasserver/api.py (+31/-5)
src/maasserver/testing/factory.py (+19/-0)
src/maasserver/tests/test_api.py (+46/-0)
src/maasserver/tests/test_forms.py (+7/-26)
To merge this branch: bzr merge lp:~rvb/maas/api-update-cluster
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+160641@code.launchpad.net

Commit message

Add method to update a cluster using the API.

Description of the change

This has been discussed with Julian. We need this (in trunk and 1.2) because we need to improve the migration story from the version of MAAS that used cobbler and this will allow us to simplify the migration script.

The testing is pretty minimal because the form itself is already tested in src/maasserver/tests/test_forms.py:TestNodeGroupEdit.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Looks good.

[1]

+            raise PermissionDenied("That method is reserved to admin users.")

s/to/for/

[2]

+        Node groups can't be renamed while they are in an accepted state, have
+        DHCP and DNS management enabled, and have a node that is in allocated
+        state.

Why is this? Is the reason documented somewhere in the tree?

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

Thanks for the review.

> [1]
>
> +            raise PermissionDenied("That method is reserved to admin users.")
>
> s/to/for/

Done.

> [2]
>
> +        Node groups can't be renamed while they are in an accepted state,
> have
> +        DHCP and DNS management enabled, and have a node that is in allocated
> +        state.
>
> Why is this? Is the reason documented somewhere in the tree?

Because changing the name of the nodegroup changes the hostname of the nodes attached to it. You don't want to do this when you have node deployed with Juju. see src/maasserver/forms.py:NodeGroupEdit.clean_name()/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api.py'
2--- src/maasserver/api.py 2013-03-12 16:00:25 +0000
3+++ src/maasserver/api.py 2013-04-24 17:44:26 +0000
4@@ -67,6 +67,7 @@
5 "FilesHandler",
6 "get_oauth_token",
7 "MaasHandler",
8+ "NodeGroupHandler",
9 "NodeGroupsHandler",
10 "NodeGroupInterfaceHandler",
11 "NodeGroupInterfacesHandler",
12@@ -161,6 +162,7 @@
13 from maasserver.forms import (
14 get_node_create_form,
15 get_node_edit_form,
16+ NodeGroupEdit,
17 NodeGroupInterfaceForm,
18 NodeGroupWithInterfacesForm,
19 TagForm,
20@@ -216,6 +218,9 @@
21 )
22
23
24+METHOD_RESERVED_ADMIN = "That method is reserved for admin users."
25+
26+
27 def store_node_power_parameters(node, request):
28 """Store power parameters in request.
29
30@@ -1121,13 +1126,13 @@
31 nodegroup.accept()
32 return HttpResponse("Nodegroup(s) accepted.", status=httplib.OK)
33 else:
34- raise PermissionDenied("That method is reserved to admin users.")
35+ raise PermissionDenied(METHOD_RESERVED_ADMIN)
36
37 @operation(idempotent=False)
38 def import_boot_images(self, request):
39 """Import the boot images on all the accepted cluster controllers."""
40 if not request.user.is_superuser:
41- raise PermissionDenied("That method is reserved to admin users.")
42+ raise PermissionDenied(METHOD_RESERVED_ADMIN)
43 NodeGroup.objects.import_boot_images_accepted_clusters()
44 return HttpResponse(
45 "Import of boot images started on all cluster controllers",
46@@ -1149,7 +1154,7 @@
47 nodegroup.reject()
48 return HttpResponse("Nodegroup(s) rejected.", status=httplib.OK)
49 else:
50- raise PermissionDenied("That method is reserved to admin users.")
51+ raise PermissionDenied(METHOD_RESERVED_ADMIN)
52
53 @classmethod
54 def resource_uri(cls):
55@@ -1186,7 +1191,7 @@
56 Each NodeGroup has its own uuid.
57 """
58
59- create = update = delete = None
60+ create = delete = None
61 fields = DISPLAYED_NODEGROUP_FIELDS
62
63 def read(self, request, uuid):
64@@ -1201,6 +1206,27 @@
65 uuid = nodegroup.uuid
66 return ('nodegroup_handler', [uuid])
67
68+ def update(self, request, uuid):
69+ """Update a specific cluster.
70+
71+ :param name: The new DNS name for this cluster.
72+ :type name: basestring
73+ :param cluster_name: The new name for this cluster.
74+ :type cluster_name: basestring
75+ :param status: The new status for this cluster (see
76+ vocabulary `NODEGROUP_STATUS`).
77+ :type status: int
78+ """
79+ if not request.user.is_superuser:
80+ raise PermissionDenied(METHOD_RESERVED_ADMIN)
81+ nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
82+ data = get_overrided_query_dict(model_to_dict(nodegroup), request.data)
83+ form = NodeGroupEdit(instance=nodegroup, data=data)
84+ if form.is_valid():
85+ return form.save()
86+ else:
87+ raise ValidationError(form.errors)
88+
89 @operation(idempotent=False)
90 def update_leases(self, request, uuid):
91 """Submit latest state of DHCP leases within the cluster.
92@@ -1222,7 +1248,7 @@
93 def import_boot_images(self, request, uuid):
94 """Import the pxe files on this cluster controller."""
95 if not request.user.is_superuser:
96- raise PermissionDenied("That method is reserved to admin users.")
97+ raise PermissionDenied(METHOD_RESERVED_ADMIN)
98 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
99 nodegroup.import_boot_images()
100 return HttpResponse(
101
102=== modified file 'src/maasserver/testing/factory.py'
103--- src/maasserver/testing/factory.py 2013-03-07 14:41:14 +0000
104+++ src/maasserver/testing/factory.py 2013-04-24 17:44:26 +0000
105@@ -214,6 +214,25 @@
106 ng.save()
107 return ng
108
109+ def make_unrenamable_nodegroup_with_node(self):
110+ """Create a `NodeGroup` that can't be renamed, and `Node`.
111+
112+ Node groups can't be renamed while they are in an accepted state, have
113+ DHCP and DNS management enabled, and have a node that is in allocated
114+ state.
115+
116+ :return: tuple: (`NodeGroup`, `Node`).
117+ """
118+ name = self.make_name('original-name')
119+ nodegroup = self.make_node_group(
120+ name=name, status=NODEGROUP_STATUS.ACCEPTED)
121+ interface = nodegroup.get_managed_interface()
122+ interface.management = NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS
123+ interface.save()
124+ node = self.make_node(
125+ nodegroup=nodegroup, status=NODE_STATUS.ALLOCATED)
126+ return nodegroup, node
127+
128 def make_node_group_interface(self, nodegroup, ip=None,
129 router_ip=None, network=None,
130 subnet_mask=None, broadcast_ip=None,
131
132=== modified file 'src/maasserver/tests/test_api.py'
133--- src/maasserver/tests/test_api.py 2013-03-12 15:25:43 +0000
134+++ src/maasserver/tests/test_api.py 2013-04-24 17:44:26 +0000
135@@ -72,6 +72,7 @@
136 NODE_STATUS,
137 NODE_STATUS_CHOICES_DICT,
138 NODEGROUP_STATUS,
139+ NODEGROUP_STATUS_CHOICES,
140 NODEGROUPINTERFACE_MANAGEMENT,
141 )
142 from maasserver.exceptions import MAASAPIBadRequest
143@@ -4348,6 +4349,51 @@
144 self.get_uri('nodegroups/%s/' % factory.make_name('nodegroup')))
145 self.assertEqual(httplib.NOT_FOUND, response.status_code)
146
147+ def test_PUT_reserved_to_admin_users(self):
148+ nodegroup = factory.make_node_group()
149+ response = self.client.put(
150+ reverse('nodegroup_handler', args=[nodegroup.uuid]),
151+ {'name': factory.make_name("new-name")})
152+
153+ self.assertEqual(httplib.FORBIDDEN, response.status_code)
154+
155+ def test_PUT_updates_nodegroup(self):
156+ # The api allows the updating of a NodeGroup.
157+ nodegroup = factory.make_node_group()
158+ self.become_admin()
159+ new_name = factory.make_name("new-name")
160+ new_cluster_name = factory.make_name("new-cluster-name")
161+ new_status = factory.getRandomChoice(
162+ NODEGROUP_STATUS_CHOICES, but_not=[nodegroup.status])
163+ response = self.client.put(
164+ reverse('nodegroup_handler', args=[nodegroup.uuid]),
165+ {
166+ 'name': new_name,
167+ 'cluster_name': new_cluster_name,
168+ 'status': new_status,
169+ })
170+
171+ self.assertEqual(httplib.OK, response.status_code, response.content)
172+ nodegroup = reload_object(nodegroup)
173+ self.assertEqual(
174+ (new_name, new_cluster_name, new_status),
175+ (nodegroup.name, nodegroup.cluster_name, nodegroup.status))
176+
177+ def test_PUT_updates_nodegroup_validates_data(self):
178+ nodegroup, _ = factory.make_unrenamable_nodegroup_with_node()
179+ self.become_admin()
180+ new_name = factory.make_name("new-name")
181+ response = self.client.put(
182+ reverse('nodegroup_handler', args=[nodegroup.uuid]),
183+ {'name': new_name})
184+
185+ parsed_result = json.loads(response.content)
186+
187+ self.assertEqual(httplib.BAD_REQUEST, response.status_code)
188+ self.assertIn(
189+ "Can't rename DNS zone",
190+ parsed_result['name'][0])
191+
192 def test_update_leases_processes_empty_leases_dict(self):
193 nodegroup = factory.make_node_group()
194 factory.make_dhcp_lease(nodegroup=nodegroup)
195
196=== modified file 'src/maasserver/tests/test_forms.py'
197--- src/maasserver/tests/test_forms.py 2012-12-20 10:08:36 +0000
198+++ src/maasserver/tests/test_forms.py 2013-04-24 17:44:26 +0000
199@@ -842,25 +842,6 @@
200 ])
201
202
203-def make_unrenamable_nodegroup_with_node():
204- """Create a `NodeGroup` that can't be renamed, and `Node`.
205-
206- Node groups can't be renamed while they are in an accepted state, have
207- DHCP and DNS management enabled, and have a node that is in allocated
208- state.
209-
210- :return: tuple: (`NodeGroup`, `Node`).
211- """
212- name = factory.make_name('original-name')
213- nodegroup = factory.make_node_group(
214- name=name, status=NODEGROUP_STATUS.ACCEPTED)
215- interface = nodegroup.get_managed_interface()
216- interface.management = NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS
217- interface.save()
218- node = factory.make_node(nodegroup=nodegroup, status=NODE_STATUS.ALLOCATED)
219- return nodegroup, node
220-
221-
222 class TestNodeGroupEdit(TestCase):
223
224 def make_form_data(self, nodegroup):
225@@ -882,14 +863,14 @@
226 self.assertEqual(new_name, reload_object(nodegroup).name)
227
228 def test_refuses_name_change_if_dns_managed_and_nodes_in_use(self):
229- nodegroup, node = make_unrenamable_nodegroup_with_node()
230+ nodegroup, node = factory.make_unrenamable_nodegroup_with_node()
231 data = self.make_form_data(nodegroup)
232 data['name'] = factory.make_name('new-name')
233 form = NodeGroupEdit(instance=nodegroup, data=data)
234 self.assertFalse(form.is_valid())
235
236 def test_accepts_unchanged_name(self):
237- nodegroup, node = make_unrenamable_nodegroup_with_node()
238+ nodegroup, node = factory.make_unrenamable_nodegroup_with_node()
239 original_name = nodegroup.name
240 form = NodeGroupEdit(
241 instance=nodegroup, data=self.make_form_data(nodegroup))
242@@ -898,7 +879,7 @@
243 self.assertEqual(original_name, reload_object(nodegroup).name)
244
245 def test_accepts_omitted_name(self):
246- nodegroup, node = make_unrenamable_nodegroup_with_node()
247+ nodegroup, node = factory.make_unrenamable_nodegroup_with_node()
248 original_name = nodegroup.name
249 data = self.make_form_data(nodegroup)
250 del data['name']
251@@ -908,7 +889,7 @@
252 self.assertEqual(original_name, reload_object(nodegroup).name)
253
254 def test_accepts_name_change_if_nodegroup_not_accepted(self):
255- nodegroup, node = make_unrenamable_nodegroup_with_node()
256+ nodegroup, node = factory.make_unrenamable_nodegroup_with_node()
257 nodegroup.status = NODEGROUP_STATUS.PENDING
258 data = self.make_form_data(nodegroup)
259 data['name'] = factory.make_name('new-name')
260@@ -916,7 +897,7 @@
261 self.assertTrue(form.is_valid())
262
263 def test_accepts_name_change_if_dns_managed_but_no_nodes_in_use(self):
264- nodegroup, node = make_unrenamable_nodegroup_with_node()
265+ nodegroup, node = factory.make_unrenamable_nodegroup_with_node()
266 node.status = NODE_STATUS.READY
267 node.save()
268 data = self.make_form_data(nodegroup)
269@@ -927,7 +908,7 @@
270 self.assertEqual(data['name'], reload_object(nodegroup).name)
271
272 def test_accepts_name_change_if_nodes_in_use_but_dns_not_managed(self):
273- nodegroup, node = make_unrenamable_nodegroup_with_node()
274+ nodegroup, node = factory.make_unrenamable_nodegroup_with_node()
275 interface = nodegroup.get_managed_interface()
276 interface.management = NODEGROUPINTERFACE_MANAGEMENT.DHCP
277 interface.save()
278@@ -939,7 +920,7 @@
279 self.assertEqual(data['name'], reload_object(nodegroup).name)
280
281 def test_accepts_name_change_if_nodegroup_has_no_interface(self):
282- nodegroup, node = make_unrenamable_nodegroup_with_node()
283+ nodegroup, node = factory.make_unrenamable_nodegroup_with_node()
284 NodeGroupInterface.objects.filter(nodegroup=nodegroup).delete()
285 data = self.make_form_data(nodegroup)
286 data['name'] = factory.make_name('new-name')