Merge lp:~blake-rouse/maas/backward-compatible-boot-source-api into lp:~maas-committers/maas/trunk
- backward-compatible-boot-source-api
- Merge into trunk
Proposed by
Blake Rouse
Status: | Merged |
---|---|
Approved by: | Blake Rouse |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2792 |
Proposed branch: | lp:~blake-rouse/maas/backward-compatible-boot-source-api |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
672 lines (+553/-1) 6 files modified
src/maasserver/api/boot_source_selections.py (+62/-0) src/maasserver/api/boot_sources.py (+60/-0) src/maasserver/api/doc.py (+4/-1) src/maasserver/api/tests/test_boot_source_selections.py (+207/-0) src/maasserver/api/tests/test_boot_sources.py (+196/-0) src/maasserver/urls_api.py (+24/-0) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/backward-compatible-boot-source-api |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | Approve | ||
Review via email: mp+231499@code.launchpad.net |
Commit message
Add backward compatible API for boot source on nodegroups.
Description of the change
Access to the boot sources using the api/1.0/
To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote : | # |
Thanks for the review.
.
Fixed the docstring.
.
It just to make very sure!!! :)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/api/boot_source_selections.py' |
2 | --- src/maasserver/api/boot_source_selections.py 2014-08-17 01:07:57 +0000 |
3 | +++ src/maasserver/api/boot_source_selections.py 2014-08-22 15:30:43 +0000 |
4 | @@ -94,6 +94,38 @@ |
5 | return ('boot_source_selection_handler', (boot_source_id, id)) |
6 | |
7 | |
8 | +class BootSourceSelectionBackwardHandler(BootSourceSelectionHandler): |
9 | + """Manage a boot source selection. |
10 | + |
11 | + It used to be that boot-sources could be set per cluster. Now it can only |
12 | + be set globally for the whole region and clusters. This api is now |
13 | + deprecated, and only exists for backwards compatibility. |
14 | + """ |
15 | + deprecated = True |
16 | + |
17 | + def read(self, request, uuid, boot_source_id, id): |
18 | + """Read a boot source selection.""" |
19 | + return super(BootSourceSelectionBackwardHandler, self).read( |
20 | + request, boot_source_id, id) |
21 | + |
22 | + def update(self, request, uuid, boot_source_id, id): |
23 | + """Update a specific boot source selection. |
24 | + |
25 | + :param release: The release for which to import resources. |
26 | + :param arches: The list of architectures for which to import resources. |
27 | + :param subarches: The list of subarchitectures for which to import |
28 | + resources. |
29 | + :param labels: The list of labels for which to import resources. |
30 | + """ |
31 | + return super(BootSourceSelectionBackwardHandler, self).update( |
32 | + request, boot_source_id, id) |
33 | + |
34 | + def delete(self, request, uuid, boot_source_id, id): |
35 | + """Delete a specific boot source.""" |
36 | + return super(BootSourceSelectionBackwardHandler, self).delete( |
37 | + request, boot_source_id, id) |
38 | + |
39 | + |
40 | class BootSourceSelectionsHandler(OperationsHandler): |
41 | """Manage the collection of boot source selections.""" |
42 | api_doc_section_name = "Boot source selections" |
43 | @@ -134,3 +166,33 @@ |
44 | return form.save() |
45 | else: |
46 | raise ValidationError(form.errors) |
47 | + |
48 | + |
49 | +class BootSourceSelectionsBackwardHandler(BootSourceSelectionsHandler): |
50 | + """Manage a boot source selection. |
51 | + |
52 | + It used to be that boot-sources could be set per cluster. Now it can only |
53 | + be set globally for the whole region and clusters. This api is now |
54 | + deprecated, and only exists for backwards compatibility. |
55 | + """ |
56 | + deprecated = True |
57 | + |
58 | + def read(self, request, uuid, boot_source_id): |
59 | + """List boot source selections. |
60 | + |
61 | + Get a listing of a boot source's selections. |
62 | + """ |
63 | + return super(BootSourceSelectionsBackwardHandler, self).read( |
64 | + request, boot_source_id) |
65 | + |
66 | + def create(self, request, uuid, boot_source_id): |
67 | + """Create a new boot source selection. |
68 | + |
69 | + :param release: The release for which to import resources. |
70 | + :param arches: The architecture list for which to import resources. |
71 | + :param subarches: The subarchitecture list for which to import |
72 | + resources. |
73 | + :param labels: The label lists for which to import resources. |
74 | + """ |
75 | + return super(BootSourceSelectionsBackwardHandler, self).create( |
76 | + request, boot_source_id) |
77 | |
78 | === modified file 'src/maasserver/api/boot_sources.py' |
79 | --- src/maasserver/api/boot_sources.py 2014-08-16 15:43:01 +0000 |
80 | +++ src/maasserver/api/boot_sources.py 2014-08-22 15:30:43 +0000 |
81 | @@ -112,6 +112,35 @@ |
82 | return ('boot_source_handler', (id, )) |
83 | |
84 | |
85 | +class BootSourceBackwardHandler(BootSourceHandler): |
86 | + """Manage a boot source. |
87 | + |
88 | + It used to be that boot-sources could be set per cluster. Now it can only |
89 | + be set globally for the whole region and clusters. This api is now |
90 | + deprecated, and only exists for backwards compatibility. |
91 | + """ |
92 | + deprecated = True |
93 | + |
94 | + def read(self, request, uuid, id): |
95 | + """Read a boot source.""" |
96 | + return super(BootSourceBackwardHandler, self).read(request, id) |
97 | + |
98 | + def update(self, request, uuid, id): |
99 | + """Update a specific boot source. |
100 | + |
101 | + :param url: The URL of the BootSource. |
102 | + :param keyring_filename: The path to the keyring file for this |
103 | + BootSource. |
104 | + :param keyring_filename: The GPG keyring for this BootSource, |
105 | + base64-encoded data. |
106 | + """ |
107 | + return super(BootSourceBackwardHandler, self).update(request, id) |
108 | + |
109 | + def delete(self, request, uuid, id): |
110 | + """Delete a specific boot source.""" |
111 | + return super(BootSourceBackwardHandler, self).delete(request, id) |
112 | + |
113 | + |
114 | class BootSourcesHandler(OperationsHandler): |
115 | """Manage the collection of boot sources.""" |
116 | api_doc_section_name = "Boot sources" |
117 | @@ -148,3 +177,34 @@ |
118 | status=httplib.CREATED) |
119 | else: |
120 | raise ValidationError(form.errors) |
121 | + |
122 | + |
123 | +class BootSourcesBackwardHandler(BootSourcesHandler): |
124 | + """Manage the collection of boot sources. |
125 | + |
126 | + It used to be that boot-sources could be set per cluster. Now it can only |
127 | + be set globally for the whole region and clusters. This api is now |
128 | + deprecated, and only exists for backwards compatibility. |
129 | + """ |
130 | + deprecated = True |
131 | + |
132 | + def read(self, request, uuid): |
133 | + """List boot sources. |
134 | + |
135 | + Get a listing of a cluster's boot sources. |
136 | + |
137 | + :param uuid: This is deprecated, only exists for backwards |
138 | + compatibility. Boot sources are now global for all of MAAS. |
139 | + """ |
140 | + return super(BootSourcesBackwardHandler, self).read(request) |
141 | + |
142 | + def create(self, request, uuid): |
143 | + """Create a new boot source. |
144 | + |
145 | + :param url: The URL of the BootSource. |
146 | + :param keyring_filename: The path to the keyring file for |
147 | + this BootSource. |
148 | + :param keyring_data: The GPG keyring for this BootSource, |
149 | + base64-encoded. |
150 | + """ |
151 | + return super(BootSourcesBackwardHandler, self).create(request) |
152 | |
153 | === modified file 'src/maasserver/api/doc.py' |
154 | --- src/maasserver/api/doc.py 2014-08-16 05:43:33 +0000 |
155 | +++ src/maasserver/api/doc.py 2014-08-22 15:30:43 +0000 |
156 | @@ -47,13 +47,16 @@ |
157 | """ |
158 | p_has_resource_uri = lambda resource: ( |
159 | getattr(resource.handler, "resource_uri", None) is not None) |
160 | + p_is_not_deprecated = lambda resource: ( |
161 | + getattr(resource.handler, "deprecated", False)) |
162 | for pattern in resolver.url_patterns: |
163 | if isinstance(pattern, RegexURLResolver): |
164 | accumulate_api_resources(pattern, accumulator) |
165 | elif isinstance(pattern, RegexURLPattern): |
166 | if isinstance(pattern.callback, Resource): |
167 | resource = pattern.callback |
168 | - if p_has_resource_uri(resource): |
169 | + if p_has_resource_uri(resource) and \ |
170 | + not p_is_not_deprecated(resource): |
171 | accumulator.add(resource) |
172 | else: |
173 | raise AssertionError( |
174 | |
175 | === modified file 'src/maasserver/api/tests/test_boot_source_selections.py' |
176 | --- src/maasserver/api/tests/test_boot_source_selections.py 2014-08-17 01:16:55 +0000 |
177 | +++ src/maasserver/api/tests/test_boot_source_selections.py 2014-08-22 15:30:43 +0000 |
178 | @@ -41,6 +41,22 @@ |
179 | ) |
180 | |
181 | |
182 | +def get_boot_source_selection_backward_uri( |
183 | + boot_source_selection, nodegroup=None): |
184 | + """Return a boot source's URI on the API.""" |
185 | + if nodegroup is None: |
186 | + nodegroup = factory.make_node_group() |
187 | + boot_source = boot_source_selection.boot_source |
188 | + return reverse( |
189 | + 'boot_source_selection_backward_handler', |
190 | + args=[ |
191 | + nodegroup.uuid, |
192 | + boot_source.id, |
193 | + boot_source_selection.id, |
194 | + ] |
195 | + ) |
196 | + |
197 | + |
198 | class TestBootSourceSelectionAPI(APITestCase): |
199 | |
200 | def test_handler_path(self): |
201 | @@ -125,6 +141,107 @@ |
202 | self.assertEqual(httplib.FORBIDDEN, response.status_code) |
203 | |
204 | |
205 | +class TestBootSourceSelectionBackwardAPI(APITestCase): |
206 | + |
207 | + def test_handler_path(self): |
208 | + self.assertEqual( |
209 | + '/api/1.0/nodegroups/uuid/boot-sources/3/selections/4/', |
210 | + reverse( |
211 | + 'boot_source_selection_backward_handler', |
212 | + args=['uuid', '3', '4'])) |
213 | + |
214 | + def test_GET_returns_boot_source(self): |
215 | + self.become_admin() |
216 | + boot_source_selection = factory.make_boot_source_selection() |
217 | + response = self.client.get( |
218 | + get_boot_source_selection_backward_uri(boot_source_selection)) |
219 | + self.assertEqual(httplib.OK, response.status_code) |
220 | + returned_boot_source_selection = json.loads(response.content) |
221 | + boot_source = boot_source_selection.boot_source |
222 | + # The returned object contains a 'resource_uri' field. |
223 | + self.assertEqual( |
224 | + reverse( |
225 | + 'boot_source_selection_handler', |
226 | + args=[ |
227 | + boot_source.id, |
228 | + boot_source_selection.id] |
229 | + ), |
230 | + returned_boot_source_selection['resource_uri']) |
231 | + # The other fields are the boot source selection's fields. |
232 | + del returned_boot_source_selection['resource_uri'] |
233 | + # All the fields are present. |
234 | + self.assertItemsEqual( |
235 | + DISPLAYED_BOOTSOURCESELECTION_FIELDS, |
236 | + returned_boot_source_selection.keys()) |
237 | + self.assertThat( |
238 | + boot_source_selection, |
239 | + MatchesStructure.byEquality(**returned_boot_source_selection)) |
240 | + |
241 | + def test_GET_returns_same_boot_source_for_different_node_groups(self): |
242 | + self.become_admin() |
243 | + boot_source_selection = factory.make_boot_source_selection() |
244 | + for _ in range(3): |
245 | + nodegroup = factory.make_node_group() |
246 | + response = self.client.get( |
247 | + get_boot_source_selection_backward_uri( |
248 | + boot_source_selection, nodegroup)) |
249 | + self.assertEqual(httplib.OK, response.status_code) |
250 | + returned_boot_source_selection = json.loads(response.content) |
251 | + del returned_boot_source_selection['resource_uri'] |
252 | + self.assertThat( |
253 | + boot_source_selection, |
254 | + MatchesStructure.byEquality(**returned_boot_source_selection)) |
255 | + |
256 | + def test_GET_requires_admin(self): |
257 | + boot_source_selection = factory.make_boot_source_selection() |
258 | + response = self.client.get( |
259 | + get_boot_source_selection_backward_uri(boot_source_selection)) |
260 | + self.assertEqual(httplib.FORBIDDEN, response.status_code) |
261 | + |
262 | + def test_DELETE_deletes_boot_source_selection(self): |
263 | + self.become_admin() |
264 | + boot_source_selection = factory.make_boot_source_selection() |
265 | + response = self.client.delete( |
266 | + get_boot_source_selection_backward_uri(boot_source_selection)) |
267 | + self.assertEqual(httplib.NO_CONTENT, response.status_code) |
268 | + self.assertIsNone(reload_object(boot_source_selection)) |
269 | + |
270 | + def test_DELETE_requires_admin(self): |
271 | + boot_source_selection = factory.make_boot_source_selection() |
272 | + response = self.client.delete( |
273 | + get_boot_source_selection_backward_uri(boot_source_selection)) |
274 | + self.assertEqual(httplib.FORBIDDEN, response.status_code) |
275 | + |
276 | + def test_PUT_updates_boot_source_selection(self): |
277 | + self.become_admin() |
278 | + boot_source_selection = factory.make_boot_source_selection() |
279 | + ubuntu_os = UbuntuOS() |
280 | + new_release = factory.pick_release(ubuntu_os) |
281 | + new_values = { |
282 | + 'release': new_release, |
283 | + 'arches': [factory.make_name('arch'), factory.make_name('arch')], |
284 | + 'subarches': [ |
285 | + factory.make_name('subarch'), factory.make_name('subarch')], |
286 | + 'labels': [factory.make_name('label')], |
287 | + } |
288 | + response = self.client_put( |
289 | + get_boot_source_selection_backward_uri( |
290 | + boot_source_selection), new_values) |
291 | + self.assertEqual(httplib.OK, response.status_code) |
292 | + boot_source_selection = reload_object(boot_source_selection) |
293 | + self.assertAttributes(boot_source_selection, new_values) |
294 | + |
295 | + def test_PUT_requires_admin(self): |
296 | + boot_source_selection = factory.make_boot_source_selection() |
297 | + new_values = { |
298 | + 'release': factory.make_name('release'), |
299 | + } |
300 | + response = self.client_put( |
301 | + get_boot_source_selection_backward_uri( |
302 | + boot_source_selection), new_values) |
303 | + self.assertEqual(httplib.FORBIDDEN, response.status_code) |
304 | + |
305 | + |
306 | class TestBootSourceSelectionsAPI(APITestCase): |
307 | """Test the the boot source selections API.""" |
308 | |
309 | @@ -198,3 +315,93 @@ |
310 | 'boot_source_selections_handler', |
311 | args=[boot_source.id]), params) |
312 | self.assertEqual(httplib.FORBIDDEN, response.status_code) |
313 | + |
314 | + |
315 | +class TestBootSourceSelectionsBackwardAPI(APITestCase): |
316 | + """Test the the boot source selections API.""" |
317 | + |
318 | + def get_uri(self, boot_source, nodegroup=None): |
319 | + if nodegroup is None: |
320 | + nodegroup = factory.make_node_group() |
321 | + return reverse( |
322 | + 'boot_source_selections_backward_handler', |
323 | + args=[nodegroup.uuid, boot_source.id]) |
324 | + |
325 | + def test_handler_path(self): |
326 | + self.assertEqual( |
327 | + '/api/1.0/nodegroups/uuid/boot-sources/3/selections/', |
328 | + reverse( |
329 | + 'boot_source_selections_backward_handler', |
330 | + args=['uuid', '3'])) |
331 | + |
332 | + def test_GET_returns_boot_source_selection_list(self): |
333 | + self.become_admin() |
334 | + boot_source = factory.make_boot_source() |
335 | + selections = [ |
336 | + factory.make_boot_source_selection(boot_source=boot_source) |
337 | + for _ in range(3)] |
338 | + # Create boot source selections in another boot source. |
339 | + [factory.make_boot_source_selection() for _ in range(3)] |
340 | + response = self.client.get(self.get_uri(boot_source)) |
341 | + self.assertEqual(httplib.OK, response.status_code, response.content) |
342 | + parsed_result = json.loads(response.content) |
343 | + self.assertItemsEqual( |
344 | + [selection.id for selection in selections], |
345 | + [selection.get('id') for selection in parsed_result]) |
346 | + |
347 | + def test_GET_returns_same_list_for_different_node_groups(self): |
348 | + self.become_admin() |
349 | + boot_source = factory.make_boot_source() |
350 | + selections = [ |
351 | + factory.make_boot_source_selection(boot_source=boot_source) |
352 | + for _ in range(3)] |
353 | + # Create boot source selections in another boot source. |
354 | + [factory.make_boot_source_selection() for _ in range(3)] |
355 | + for _ in range(3): |
356 | + nodegroup = factory.make_node_group() |
357 | + response = self.client.get(self.get_uri(boot_source, nodegroup)) |
358 | + self.assertEqual( |
359 | + httplib.OK, response.status_code, response.content) |
360 | + parsed_result = json.loads(response.content) |
361 | + self.assertItemsEqual( |
362 | + [selection.id for selection in selections], |
363 | + [selection.get('id') for selection in parsed_result]) |
364 | + |
365 | + def test_GET_requires_admin(self): |
366 | + boot_source = factory.make_boot_source() |
367 | + response = self.client.get(self.get_uri(boot_source)) |
368 | + self.assertEqual(httplib.FORBIDDEN, response.status_code) |
369 | + |
370 | + def test_POST_creates_boot_source_selection(self): |
371 | + self.become_admin() |
372 | + boot_source = factory.make_boot_source() |
373 | + ubuntu_os = UbuntuOS() |
374 | + new_release = factory.pick_release(ubuntu_os) |
375 | + params = { |
376 | + 'release': new_release, |
377 | + 'arches': [factory.make_name('arch'), factory.make_name('arch')], |
378 | + 'subarches': [ |
379 | + factory.make_name('subarch'), factory.make_name('subarch')], |
380 | + 'labels': [factory.make_name('label')], |
381 | + } |
382 | + response = self.client.post(self.get_uri(boot_source), params) |
383 | + self.assertEqual(httplib.OK, response.status_code) |
384 | + parsed_result = json.loads(response.content) |
385 | + |
386 | + boot_source_selection = BootSourceSelection.objects.get( |
387 | + id=parsed_result['id']) |
388 | + self.assertAttributes(boot_source_selection, params) |
389 | + |
390 | + def test_POST_requires_admin(self): |
391 | + boot_source = factory.make_boot_source() |
392 | + ubuntu_os = UbuntuOS() |
393 | + new_release = factory.pick_release(ubuntu_os) |
394 | + params = { |
395 | + 'release': new_release, |
396 | + 'arches': [factory.make_name('arch'), factory.make_name('arch')], |
397 | + 'subarches': [ |
398 | + factory.make_name('subarch'), factory.make_name('subarch')], |
399 | + 'labels': [factory.make_name('label')], |
400 | + } |
401 | + response = self.client.post(self.get_uri(boot_source), params) |
402 | + self.assertEqual(httplib.FORBIDDEN, response.status_code) |
403 | |
404 | === modified file 'src/maasserver/api/tests/test_boot_sources.py' |
405 | --- src/maasserver/api/tests/test_boot_sources.py 2014-08-16 15:43:01 +0000 |
406 | +++ src/maasserver/api/tests/test_boot_sources.py 2014-08-22 15:30:43 +0000 |
407 | @@ -34,6 +34,14 @@ |
408 | args=[boot_source.id]) |
409 | |
410 | |
411 | +def get_boot_source_backward_uri(boot_source, nodegroup=None): |
412 | + if nodegroup is None: |
413 | + nodegroup = factory.make_node_group() |
414 | + return reverse( |
415 | + 'boot_source_backward_handler', |
416 | + args=[nodegroup.uuid, boot_source.id]) |
417 | + |
418 | + |
419 | class TestBootSourceAPI(APITestCase): |
420 | |
421 | def test_handler_path(self): |
422 | @@ -104,6 +112,92 @@ |
423 | self.assertEqual(httplib.FORBIDDEN, response.status_code) |
424 | |
425 | |
426 | +class TestBootSourceBackwardAPI(APITestCase): |
427 | + |
428 | + def test_handler_path(self): |
429 | + self.assertEqual( |
430 | + '/api/1.0/nodegroups/uuid/boot-sources/3/', |
431 | + reverse('boot_source_backward_handler', args=['uuid', '3'])) |
432 | + |
433 | + def test_GET_returns_boot_source(self): |
434 | + self.become_admin() |
435 | + boot_source = factory.make_boot_source() |
436 | + response = self.client.get(get_boot_source_backward_uri(boot_source)) |
437 | + self.assertEqual(httplib.OK, response.status_code) |
438 | + returned_boot_source = json.loads(response.content) |
439 | + # The returned object contains a 'resource_uri' field. |
440 | + self.assertEqual( |
441 | + reverse( |
442 | + 'boot_source_handler', |
443 | + args=[boot_source.id] |
444 | + ), |
445 | + returned_boot_source['resource_uri']) |
446 | + # The other fields are the boot source's fields. |
447 | + del returned_boot_source['resource_uri'] |
448 | + # All the fields are present. |
449 | + self.assertItemsEqual( |
450 | + DISPLAYED_BOOTSOURCE_FIELDS, returned_boot_source.keys()) |
451 | + self.assertThat( |
452 | + boot_source, |
453 | + MatchesStructure.byEquality(**returned_boot_source)) |
454 | + |
455 | + def test_GET_returns_same_boot_source_no_matter_the_nodegroup(self): |
456 | + self.become_admin() |
457 | + boot_source = factory.make_boot_source() |
458 | + for _ in range(3): |
459 | + nodegroup = factory.make_node_group() |
460 | + response = self.client.get( |
461 | + get_boot_source_backward_uri(boot_source, nodegroup)) |
462 | + self.assertEqual(httplib.OK, response.status_code) |
463 | + returned_boot_source = json.loads(response.content) |
464 | + del returned_boot_source['resource_uri'] |
465 | + self.assertThat( |
466 | + boot_source, |
467 | + MatchesStructure.byEquality(**returned_boot_source)) |
468 | + |
469 | + def test_GET_requires_admin(self): |
470 | + boot_source = factory.make_boot_source() |
471 | + response = self.client.get(get_boot_source_backward_uri(boot_source)) |
472 | + self.assertEqual(httplib.FORBIDDEN, response.status_code) |
473 | + |
474 | + def test_DELETE_deletes_boot_source(self): |
475 | + self.become_admin() |
476 | + boot_source = factory.make_boot_source() |
477 | + response = self.client.delete( |
478 | + get_boot_source_backward_uri(boot_source)) |
479 | + self.assertEqual(httplib.NO_CONTENT, response.status_code) |
480 | + self.assertIsNone(reload_object(boot_source)) |
481 | + |
482 | + def test_DELETE_requires_admin(self): |
483 | + boot_source = factory.make_boot_source() |
484 | + response = self.client.delete( |
485 | + get_boot_source_backward_uri(boot_source)) |
486 | + self.assertEqual(httplib.FORBIDDEN, response.status_code) |
487 | + |
488 | + def test_PUT_updates_boot_source(self): |
489 | + self.become_admin() |
490 | + boot_source = factory.make_boot_source() |
491 | + new_values = { |
492 | + 'url': 'http://example.com/', |
493 | + 'keyring_filename': factory.make_name('filename'), |
494 | + } |
495 | + response = self.client_put( |
496 | + get_boot_source_backward_uri(boot_source), new_values) |
497 | + self.assertEqual(httplib.OK, response.status_code) |
498 | + boot_source = reload_object(boot_source) |
499 | + self.assertAttributes(boot_source, new_values) |
500 | + |
501 | + def test_PUT_requires_admin(self): |
502 | + boot_source = factory.make_boot_source() |
503 | + new_values = { |
504 | + 'url': 'http://example.com/', |
505 | + 'keyring_filename': factory.make_name('filename'), |
506 | + } |
507 | + response = self.client_put( |
508 | + get_boot_source_backward_uri(boot_source), new_values) |
509 | + self.assertEqual(httplib.FORBIDDEN, response.status_code) |
510 | + |
511 | + |
512 | class TestBootSourcesAPI(APITestCase): |
513 | """Test the the boot source API.""" |
514 | |
515 | @@ -190,3 +284,105 @@ |
516 | response = self.client.post( |
517 | reverse('boot_sources_handler'), params) |
518 | self.assertEqual(httplib.FORBIDDEN, response.status_code) |
519 | + |
520 | + |
521 | +class TestBootSourcesBackwardAPI(APITestCase): |
522 | + """Test the the boot source API.""" |
523 | + |
524 | + def get_uri(self, nodegroup=None): |
525 | + if nodegroup is None: |
526 | + nodegroup = factory.make_node_group() |
527 | + return reverse( |
528 | + 'boot_sources_backward_handler', args=[nodegroup.uuid]) |
529 | + |
530 | + def test_handler_path(self): |
531 | + self.assertEqual( |
532 | + '/api/1.0/nodegroups/uuid/boot-sources/', |
533 | + reverse('boot_sources_backward_handler', args=['uuid'])) |
534 | + |
535 | + def test_GET_returns_boot_source_list(self): |
536 | + self.become_admin() |
537 | + sources = [ |
538 | + factory.make_boot_source() for _ in range(3)] |
539 | + response = self.client.get(self.get_uri()) |
540 | + self.assertEqual(httplib.OK, response.status_code, response.content) |
541 | + parsed_result = json.loads(response.content) |
542 | + self.assertItemsEqual( |
543 | + [boot_source.id for boot_source in sources], |
544 | + [boot_source.get('id') for boot_source in parsed_result]) |
545 | + |
546 | + def test_GET_returns_same_list_for_different_node_groups(self): |
547 | + self.become_admin() |
548 | + sources = [ |
549 | + factory.make_boot_source() for _ in range(3)] |
550 | + for _ in range(3): |
551 | + nodegroup = factory.make_node_group() |
552 | + response = self.client.get(self.get_uri(nodegroup)) |
553 | + self.assertEqual( |
554 | + httplib.OK, response.status_code, response.content) |
555 | + parsed_result = json.loads(response.content) |
556 | + self.assertItemsEqual( |
557 | + [boot_source.id for boot_source in sources], |
558 | + [boot_source.get('id') for boot_source in parsed_result]) |
559 | + |
560 | + def test_GET_requires_admin(self): |
561 | + response = self.client.get(self.get_uri()) |
562 | + self.assertEqual(httplib.FORBIDDEN, response.status_code) |
563 | + |
564 | + def test_POST_creates_boot_source_with_keyring_filename(self): |
565 | + self.become_admin() |
566 | + |
567 | + params = { |
568 | + 'url': 'http://example.com/', |
569 | + 'keyring_filename': factory.make_name('filename'), |
570 | + 'keyring_data': '', |
571 | + } |
572 | + response = self.client.post(self.get_uri(), params) |
573 | + self.assertEqual(httplib.CREATED, response.status_code) |
574 | + parsed_result = json.loads(response.content) |
575 | + |
576 | + boot_source = BootSource.objects.get(id=parsed_result['id']) |
577 | + # boot_source.keyring_data is returned as a read-only buffer, test |
578 | + # it separately from the rest of the attributes. |
579 | + self.assertEqual('', bytes(boot_source.keyring_data)) |
580 | + del params['keyring_data'] |
581 | + self.assertAttributes(boot_source, params) |
582 | + |
583 | + def test_POST_creates_boot_source_with_keyring_data(self): |
584 | + self.become_admin() |
585 | + |
586 | + params = { |
587 | + 'url': 'http://example.com/', |
588 | + 'keyring_filename': '', |
589 | + 'keyring_data': ( |
590 | + factory.make_file_upload(content=sample_binary_data)), |
591 | + } |
592 | + response = self.client.post(self.get_uri(), params) |
593 | + self.assertEqual(httplib.CREATED, response.status_code) |
594 | + parsed_result = json.loads(response.content) |
595 | + |
596 | + boot_source = BootSource.objects.get(id=parsed_result['id']) |
597 | + # boot_source.keyring_data is returned as a read-only buffer, test |
598 | + # it separately from the rest of the attributes. |
599 | + self.assertEqual(sample_binary_data, bytes(boot_source.keyring_data)) |
600 | + del params['keyring_data'] |
601 | + self.assertAttributes(boot_source, params) |
602 | + |
603 | + def test_POST_validates_boot_source(self): |
604 | + self.become_admin() |
605 | + |
606 | + params = { |
607 | + 'url': 'http://example.com/', |
608 | + } |
609 | + response = self.client.post(self.get_uri(), params) |
610 | + self.assertEqual(httplib.BAD_REQUEST, response.status_code) |
611 | + |
612 | + def test_POST_requires_admin(self): |
613 | + params = { |
614 | + 'url': 'http://example.com/', |
615 | + 'keyring_filename': '', |
616 | + 'keyring_data': ( |
617 | + factory.make_file_upload(content=sample_binary_data)), |
618 | + } |
619 | + response = self.client.post(self.get_uri(), params) |
620 | + self.assertEqual(httplib.FORBIDDEN, response.status_code) |
621 | |
622 | === modified file 'src/maasserver/urls_api.py' |
623 | --- src/maasserver/urls_api.py 2014-08-21 19:20:31 +0000 |
624 | +++ src/maasserver/urls_api.py 2014-08-22 15:30:43 +0000 |
625 | @@ -29,11 +29,15 @@ |
626 | BootResourcesHandler, |
627 | ) |
628 | from maasserver.api.boot_source_selections import ( |
629 | + BootSourceSelectionBackwardHandler, |
630 | BootSourceSelectionHandler, |
631 | + BootSourceSelectionsBackwardHandler, |
632 | BootSourceSelectionsHandler, |
633 | ) |
634 | from maasserver.api.boot_sources import ( |
635 | + BootSourceBackwardHandler, |
636 | BootSourceHandler, |
637 | + BootSourcesBackwardHandler, |
638 | BootSourcesHandler, |
639 | ) |
640 | from maasserver.api.commissioning_scripts import ( |
641 | @@ -160,6 +164,14 @@ |
642 | BootSourceSelectionHandler, authentication=api_auth) |
643 | boot_source_selections_handler = AdminRestrictedResource( |
644 | BootSourceSelectionsHandler, authentication=api_auth) |
645 | +boot_source_backward_handler = AdminRestrictedResource( |
646 | + BootSourceBackwardHandler, authentication=api_auth) |
647 | +boot_sources_backward_handler = AdminRestrictedResource( |
648 | + BootSourcesBackwardHandler, authentication=api_auth) |
649 | +boot_source_selection_backward_handler = AdminRestrictedResource( |
650 | + BootSourceSelectionBackwardHandler, authentication=api_auth) |
651 | +boot_source_selections_backward_handler = AdminRestrictedResource( |
652 | + BootSourceSelectionsBackwardHandler, authentication=api_auth) |
653 | license_key_handler = AdminRestrictedResource( |
654 | LicenseKeyHandler, authentication=api_auth) |
655 | license_keys_handler = AdminRestrictedResource( |
656 | @@ -267,4 +279,16 @@ |
657 | url(r'^boot-sources/(?P<boot_source_id>[^/]+)/selections/(?P<id>[^/]+)/$', |
658 | boot_source_selection_handler, |
659 | name='boot_source_selection_handler'), |
660 | + url(r'^nodegroups/(?P<uuid>[^/]+)/boot-sources/$', |
661 | + boot_sources_backward_handler, name='boot_sources_backward_handler'), |
662 | + url(r'^nodegroups/(?P<uuid>[^/]+)/boot-sources/(?P<id>[^/]+)/$', |
663 | + boot_source_backward_handler, name='boot_source_backward_handler'), |
664 | + url(r'^nodegroups/(?P<uuid>[^/]+)/boot-sources/(?P<boot_source_id>[^/]+)/' |
665 | + 'selections/$', |
666 | + boot_source_selections_backward_handler, |
667 | + name='boot_source_selections_backward_handler'), |
668 | + url(r'^nodegroups/(?P<uuid>[^/]+)/boot-sources/(?P<boot_source_id>[^/]+)/' |
669 | + 'selections/(?P<id>[^/]+)/$', |
670 | + boot_source_selection_backward_handler, |
671 | + name='boot_source_selection_backward_handler'), |
672 | ) |
Excellent stuff. My eyes did glaze over a bit towards the end, but sometimes there's just no helping that with API tests.
I try always to find something to consider changing, just to prove that I'm really reviewing the branch, so here goes:
.
The docstring for BootSourcesBack wardHandler. read explains the uuid parameter, but maybe it should just say that it's a cluster uuid that is now ignored?
.
It looks like test_GET_ returns_ same_boot_ source_ for_different_ node_groups and the two instances of test_GET_ returns_ same_list_ for_different_ node_groups each test the same thing 3 times. Does doing it more than once really contribute anything?