Merge lp:~blake-rouse/maas/boot-images-rpc-v2-1.7 into lp:maas/1.7
- boot-images-rpc-v2-1.7
- Merge into 1.7
Status: | Merged |
---|---|
Approved by: | Blake Rouse |
Approved revision: | no longer in the source branch. |
Merged at revision: | 3380 |
Proposed branch: | lp:~blake-rouse/maas/boot-images-rpc-v2-1.7 |
Merge into: | lp:maas/1.7 |
Diff against target: |
615 lines (+162/-172) 7 files modified
src/maasserver/clusterrpc/boot_images.py (+43/-35) src/maasserver/clusterrpc/tests/test_boot_images.py (+75/-1) src/maasserver/utils/version.py (+0/-131) src/provisioningserver/rpc/cluster.py (+24/-0) src/provisioningserver/rpc/clusterservice.py (+9/-0) src/provisioningserver/rpc/tests/test_clusterservice.py (+9/-4) src/provisioningserver/rpc/tests/test_docs.py (+2/-1) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/boot-images-rpc-v2-1.7 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Review via email: mp+266772@code.launchpad.net |
Commit message
Add ListBootImagesV2 RPC command. Fallback to using ListBootImages RPC when the ListBootImagesV2 is not handled on the cluster.
The signature of the ListBootImages RPC call was changed to support more data in the response. This change will cause connected clusters that are not updated the same time as the region to stop communicating and error. This allows both methods to exist on the cluster where V2 will be called first then fallback to the older call in error.
Description of the change
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~blake-rouse/maas/boot-images-rpc-v2-1.7 into lp:maas/1.7 failed. Below is the output from the failed tests.
Ign http://
Ign http://
Hit http://
Ign http://
Hit http://
Hit http://
Get:1 http://
Hit http://
Get:2 http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Get:3 http://
Get:4 http://
Get:5 http://
Get:6 http://
Hit http://
Hit http://
Ign http://
Ign http://
Fetched 1,322 kB in 3s (413 kB/s)
Reading package lists...
sudo DEBIAN_
--
Preview Diff
1 | === modified file 'src/maasserver/clusterrpc/boot_images.py' |
2 | --- src/maasserver/clusterrpc/boot_images.py 2014-09-30 18:13:21 +0000 |
3 | +++ src/maasserver/clusterrpc/boot_images.py 2015-08-03 19:11:39 +0000 |
4 | @@ -21,6 +21,7 @@ |
5 | ] |
6 | |
7 | from functools import partial |
8 | +from itertools import imap |
9 | |
10 | from maasserver.rpc import ( |
11 | getAllClients, |
12 | @@ -30,8 +31,10 @@ |
13 | from provisioningserver.rpc.cluster import ( |
14 | IsImportBootImagesRunning, |
15 | ListBootImages, |
16 | + ListBootImagesV2, |
17 | ) |
18 | from provisioningserver.utils.twisted import synchronous |
19 | +from twisted.protocols.amp import UnhandledCommand |
20 | from twisted.python.failure import Failure |
21 | |
22 | |
23 | @@ -89,46 +92,51 @@ |
24 | 30 seconds. |
25 | """ |
26 | client = getClientFor(nodegroup.uuid, timeout=1) |
27 | - call = client(ListBootImages) |
28 | - return call.wait(30).get("images") |
29 | + try: |
30 | + call = client(ListBootImagesV2) |
31 | + return call.wait(30).get("images") |
32 | + except UnhandledCommand: |
33 | + call = client(ListBootImages) |
34 | + return call.wait(30).get("images") |
35 | + |
36 | + |
37 | +@synchronous |
38 | +def _get_available_boot_images(): |
39 | + """Obtain boot images available on connected clusters.""" |
40 | + listimages_v1 = lambda client: partial(client, ListBootImages) |
41 | + listimages_v2 = lambda client: partial(client, ListBootImagesV2) |
42 | + clients_v2 = getAllClients() |
43 | + responses_v2 = async.gather(imap(listimages_v2, clients_v2)) |
44 | + clients_v1 = [] |
45 | + for i, response in enumerate(responses_v2): |
46 | + if (isinstance(response, Failure) and |
47 | + response.check(UnhandledCommand) is not None): |
48 | + clients_v1.append(clients_v2[i]) |
49 | + elif not isinstance(response, Failure): |
50 | + # Convert each image to a frozenset of its items. |
51 | + yield frozenset( |
52 | + frozenset(image.viewitems()) |
53 | + for image in response["images"] |
54 | + ) |
55 | + responses_v1 = async.gather(imap(listimages_v1, clients_v1)) |
56 | + for response in suppress_failures(responses_v1): |
57 | + # Convert each image to a frozenset of its items. |
58 | + yield frozenset( |
59 | + frozenset(image.viewitems()) |
60 | + for image in response["images"] |
61 | + ) |
62 | |
63 | |
64 | @synchronous |
65 | def get_available_boot_images(): |
66 | """Obtain boot images that are available on all clusters.""" |
67 | - responses = async.gather( |
68 | - partial(client, ListBootImages) |
69 | - for client in getAllClients()) |
70 | - responses = [ |
71 | - response["images"] |
72 | - for response in suppress_failures(responses) |
73 | - ] |
74 | - if len(responses) == 0: |
75 | - return [] |
76 | - |
77 | - # Create the initial set of images from the first response. This will be |
78 | - # used to perform the intersection of all the other responses. |
79 | - images = responses.pop() |
80 | - images = { |
81 | - frozenset(image.items()) |
82 | - for image in images |
83 | - } |
84 | - |
85 | - # Intersect all of the remaining responses to get only the images that |
86 | - # exist on all clusters. |
87 | - for response in responses: |
88 | - response_images = { |
89 | - frozenset(image.items()) |
90 | - for image in response |
91 | - } |
92 | - images = images & response_images |
93 | - |
94 | - # Return only boot images on all cluster, in the same format as |
95 | - # get_boot_images. |
96 | - return [ |
97 | - dict(image) |
98 | - for image in list(images) |
99 | - ] |
100 | + image_sets = list(_get_available_boot_images()) |
101 | + if len(image_sets) > 0: |
102 | + images = frozenset.intersection(*image_sets) |
103 | + else: |
104 | + images = frozenset() |
105 | + # Return using the same format as get_boot_images. |
106 | + return list(dict(image) for image in images) |
107 | |
108 | |
109 | @synchronous |
110 | |
111 | === modified file 'src/maasserver/clusterrpc/tests/test_boot_images.py' |
112 | --- src/maasserver/clusterrpc/tests/test_boot_images.py 2014-12-16 19:29:45 +0000 |
113 | +++ src/maasserver/clusterrpc/tests/test_boot_images.py 2015-08-03 19:11:39 +0000 |
114 | @@ -16,6 +16,7 @@ |
115 | |
116 | import os |
117 | |
118 | +from maasserver.clusterrpc import boot_images as boot_images_module |
119 | from maasserver.clusterrpc.boot_images import ( |
120 | get_available_boot_images, |
121 | get_boot_images, |
122 | @@ -29,9 +30,24 @@ |
123 | NODEGROUP_STATUS, |
124 | ) |
125 | from maasserver.rpc import getAllClients |
126 | -from maasserver.rpc.testing.fixtures import RunningClusterRPCFixture |
127 | +from maasserver.rpc.testing.fixtures import ( |
128 | + MockLiveRegionToClusterRPCFixture, |
129 | + RunningClusterRPCFixture, |
130 | + ) |
131 | +from maasserver.testing.eventloop import ( |
132 | + RegionEventLoopFixture, |
133 | + RunningEventLoopFixture, |
134 | + ) |
135 | from maasserver.testing.factory import factory |
136 | from maasserver.testing.testcase import MAASServerTestCase |
137 | +from maastesting.matchers import ( |
138 | + MockCalledOnceWith, |
139 | + MockCallsMatch, |
140 | + ) |
141 | +from mock import ( |
142 | + call, |
143 | + MagicMock, |
144 | + ) |
145 | from provisioningserver.boot.tests import test_tftppath |
146 | from provisioningserver.boot.tftppath import ( |
147 | compose_image_path, |
148 | @@ -41,11 +57,16 @@ |
149 | boot_images, |
150 | clusterservice, |
151 | ) |
152 | +from provisioningserver.rpc.cluster import ( |
153 | + ListBootImages, |
154 | + ListBootImagesV2, |
155 | + ) |
156 | from provisioningserver.testing.boot_images import ( |
157 | make_boot_image_storage_params, |
158 | make_image, |
159 | ) |
160 | from twisted.internet.defer import succeed |
161 | +from twisted.protocols.amp import UnhandledCommand |
162 | |
163 | |
164 | def make_image_dir(image_params, tftproot): |
165 | @@ -164,6 +185,28 @@ |
166 | ], |
167 | get_boot_images(nodegroup)) |
168 | |
169 | + def test_calls_ListBootImagesV2_before_ListBootImages(self): |
170 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ACCEPTED) |
171 | + mock_client = MagicMock() |
172 | + self.patch_autospec( |
173 | + boot_images_module, "getClientFor").return_value = mock_client |
174 | + get_boot_images(nodegroup) |
175 | + self.assertThat(mock_client, MockCalledOnceWith(ListBootImagesV2)) |
176 | + |
177 | + def test_calls_ListBootImages_if_raised_UnhandledCommand(self): |
178 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ACCEPTED) |
179 | + mock_client = MagicMock() |
180 | + self.patch_autospec( |
181 | + boot_images_module, "getClientFor").return_value = mock_client |
182 | + mock_client.return_value.wait.side_effect = [ |
183 | + UnhandledCommand(), |
184 | + {"images": []}, |
185 | + ] |
186 | + get_boot_images(nodegroup) |
187 | + self.assertThat(mock_client, MockCallsMatch( |
188 | + call(ListBootImagesV2), |
189 | + call(ListBootImages))) |
190 | + |
191 | |
192 | class TestGetAvailableBootImages(MAASServerTestCase): |
193 | """Tests for `get_available_boot_images`.""" |
194 | @@ -239,6 +282,37 @@ |
195 | images, |
196 | get_available_boot_images()) |
197 | |
198 | + def test_fallback_to_ListBootImages_on_old_clusters(self): |
199 | + nodegroup_1 = factory.make_NodeGroup() |
200 | + nodegroup_1.accept() |
201 | + nodegroup_2 = factory.make_NodeGroup() |
202 | + nodegroup_2.accept() |
203 | + nodegroup_3 = factory.make_NodeGroup() |
204 | + nodegroup_3.accept() |
205 | + |
206 | + images = [make_rpc_boot_image() for _ in range(3)] |
207 | + |
208 | + # Limit the region's event loop to only the "rpc" service. |
209 | + self.useFixture(RegionEventLoopFixture("rpc")) |
210 | + # Now start the region's event loop. |
211 | + self.useFixture(RunningEventLoopFixture()) |
212 | + # This fixture allows us to simulate mock clusters. |
213 | + rpc = self.useFixture(MockLiveRegionToClusterRPCFixture()) |
214 | + |
215 | + # This simulates an older cluster, one without ListBootImagesV2. |
216 | + cluster_1 = rpc.makeCluster(nodegroup_1, ListBootImages) |
217 | + cluster_1.ListBootImages.return_value = succeed({'images': images}) |
218 | + |
219 | + # This simulates a newer cluster, one with ListBootImagesV2. |
220 | + cluster_2 = rpc.makeCluster(nodegroup_2, ListBootImagesV2) |
221 | + cluster_2.ListBootImagesV2.return_value = succeed({'images': images}) |
222 | + |
223 | + # This simulates a broken cluster. |
224 | + cluster_3 = rpc.makeCluster(nodegroup_3, ListBootImagesV2) |
225 | + cluster_3.ListBootImagesV2.side_effect = ZeroDivisionError |
226 | + |
227 | + self.assertItemsEqual(images, get_available_boot_images()) |
228 | + |
229 | def test_returns_empty_list_when_all_clusters_fail(self): |
230 | factory.make_NodeGroup().accept() |
231 | factory.make_NodeGroup().accept() |
232 | |
233 | === added file 'src/maasserver/utils/version.py' |
234 | --- src/maasserver/utils/version.py 1970-01-01 00:00:00 +0000 |
235 | +++ src/maasserver/utils/version.py 2015-08-03 19:11:39 +0000 |
236 | @@ -0,0 +1,131 @@ |
237 | +# Copyright 2015 Canonical Ltd. This software is licensed under the |
238 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
239 | + |
240 | +"""Version utilities.""" |
241 | + |
242 | +from __future__ import ( |
243 | + absolute_import, |
244 | + print_function, |
245 | + unicode_literals, |
246 | + ) |
247 | + |
248 | +str = None |
249 | + |
250 | +__metaclass__ = type |
251 | +__all__ = [ |
252 | + "get_maas_doc_version", |
253 | + "get_maas_version_subversion", |
254 | + "get_maas_version_ui", |
255 | + ] |
256 | + |
257 | +import apt_pkg |
258 | + |
259 | + |
260 | +try: |
261 | + from bzrlib.branch import Branch |
262 | + from bzrlib.errors import NotBranchError |
263 | +except ImportError: |
264 | + Branch = None |
265 | + |
266 | +# Initialize apt_pkg. |
267 | +apt_pkg.init() |
268 | + |
269 | +# Name of maas package to get version from. |
270 | +REGION_PACKAGE_NAME = "maas-region-controller-min" |
271 | + |
272 | + |
273 | +def get_version_from_apt(package): |
274 | + """Return the version output from `apt_pkg.Cache` for the given package.""" |
275 | + cache = apt_pkg.Cache(None) |
276 | + version = None |
277 | + if package in cache: |
278 | + apt_package = cache[package] |
279 | + version = apt_package.current_ver |
280 | + |
281 | + if version is not None: |
282 | + return version.ver_str |
283 | + else: |
284 | + return "" |
285 | + |
286 | + |
287 | +def extract_version_subversion(version): |
288 | + """Return a tuple (version, subversion) from the given apt version.""" |
289 | + if "~" in version: |
290 | + main_version, extra = version.split("~", 1) |
291 | + return main_version, extra.split("-", 1)[0] |
292 | + elif "+" in version: |
293 | + main_version, extra = version.split("+", 1) |
294 | + return main_version, "+" + extra.split("-", 1)[0] |
295 | + else: |
296 | + return version.split("-", 1)[0], '' |
297 | + |
298 | + |
299 | +def get_maas_branch(): |
300 | + """Return the `bzrlib.branch.Branch` for this running MAAS.""" |
301 | + if Branch is None: |
302 | + return None |
303 | + try: |
304 | + return Branch.open(".") |
305 | + except NotBranchError: |
306 | + return None |
307 | + |
308 | + |
309 | +_cache = {} |
310 | + |
311 | + |
312 | +# A very simply memoize function: when we switch to Django 1.7 we should use |
313 | +# Django's lru_cache method. |
314 | +def simple_cache(fun): |
315 | + def wrapped(*args, **kwargs): |
316 | + key = hash(repr(fun) + repr(args) + repr(kwargs)) |
317 | + if key not in _cache: |
318 | + _cache[key] = fun(*args, **kwargs) |
319 | + return _cache[key] |
320 | + |
321 | + wrapped.__doc__ = "%s %s" % (fun.__doc__, "(cached)") |
322 | + return wrapped |
323 | + |
324 | + |
325 | +@simple_cache |
326 | +def get_maas_package_version(): |
327 | + """Return the apt version for the main MAAS package.""" |
328 | + return get_version_from_apt(REGION_PACKAGE_NAME) |
329 | + |
330 | + |
331 | +@simple_cache |
332 | +def get_maas_version_subversion(): |
333 | + """Return a tuple with the MAAS version and the MAAS subversion.""" |
334 | + apt_version = get_maas_package_version() |
335 | + if apt_version: |
336 | + return extract_version_subversion(apt_version) |
337 | + else: |
338 | + # Get the branch information |
339 | + branch = get_maas_branch() |
340 | + if branch is None: |
341 | + # Not installed not in branch, then no way to identify. This should |
342 | + # not happen, but just in case. |
343 | + return "unknown", '' |
344 | + else: |
345 | + return "from source (+bzr%s)" % branch.revno(), '' |
346 | + |
347 | + |
348 | +@simple_cache |
349 | +def get_maas_version_ui(): |
350 | + """Return the version string for the running MAAS region. |
351 | + |
352 | + The returned string is suitable to display in the UI. |
353 | + """ |
354 | + version, subversion = get_maas_version_subversion() |
355 | + return "%s (%s)" % (version, subversion) if subversion else version |
356 | + |
357 | + |
358 | +@simple_cache |
359 | +def get_maas_doc_version(): |
360 | + """Return the doc version for the running MAAS region.""" |
361 | + doc_prefix = 'docs' |
362 | + apt_version = get_maas_package_version() |
363 | + if apt_version: |
364 | + version, _ = extract_version_subversion(apt_version) |
365 | + return doc_prefix + '.'.join(version.split('.')[:2]) |
366 | + else: |
367 | + return doc_prefix |
368 | |
369 | === removed file 'src/maasserver/utils/version.py' |
370 | --- src/maasserver/utils/version.py 2015-04-08 15:32:33 +0000 |
371 | +++ src/maasserver/utils/version.py 1970-01-01 00:00:00 +0000 |
372 | @@ -1,131 +0,0 @@ |
373 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
374 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
375 | - |
376 | -"""Version utilities.""" |
377 | - |
378 | -from __future__ import ( |
379 | - absolute_import, |
380 | - print_function, |
381 | - unicode_literals, |
382 | - ) |
383 | - |
384 | -str = None |
385 | - |
386 | -__metaclass__ = type |
387 | -__all__ = [ |
388 | - "get_maas_doc_version", |
389 | - "get_maas_version_subversion", |
390 | - "get_maas_version_ui", |
391 | - ] |
392 | - |
393 | -import apt_pkg |
394 | - |
395 | - |
396 | -try: |
397 | - from bzrlib.branch import Branch |
398 | - from bzrlib.errors import NotBranchError |
399 | -except ImportError: |
400 | - Branch = None |
401 | - |
402 | -# Initialize apt_pkg. |
403 | -apt_pkg.init() |
404 | - |
405 | -# Name of maas package to get version from. |
406 | -REGION_PACKAGE_NAME = "maas-region-controller-min" |
407 | - |
408 | - |
409 | -def get_version_from_apt(package): |
410 | - """Return the version output from `apt_pkg.Cache` for the given package.""" |
411 | - cache = apt_pkg.Cache(None) |
412 | - version = None |
413 | - if package in cache: |
414 | - apt_package = cache[package] |
415 | - version = apt_package.current_ver |
416 | - |
417 | - if version is not None: |
418 | - return version.ver_str |
419 | - else: |
420 | - return "" |
421 | - |
422 | - |
423 | -def extract_version_subversion(version): |
424 | - """Return a tuple (version, subversion) from the given apt version.""" |
425 | - if "~" in version: |
426 | - main_version, extra = version.split("~", 1) |
427 | - return main_version, extra.split("-", 1)[0] |
428 | - elif "+" in version: |
429 | - main_version, extra = version.split("+", 1) |
430 | - return main_version, "+" + extra.split("-", 1)[0] |
431 | - else: |
432 | - return version.split("-", 1)[0], '' |
433 | - |
434 | - |
435 | -def get_maas_branch(): |
436 | - """Return the `bzrlib.branch.Branch` for this running MAAS.""" |
437 | - if Branch is None: |
438 | - return None |
439 | - try: |
440 | - return Branch.open(".") |
441 | - except NotBranchError: |
442 | - return None |
443 | - |
444 | - |
445 | -_cache = {} |
446 | - |
447 | - |
448 | -# A very simply memoize function: when we switch to Django 1.7 we should use |
449 | -# Django's lru_cache method. |
450 | -def simple_cache(fun): |
451 | - def wrapped(*args, **kwargs): |
452 | - key = hash(repr(fun) + repr(args) + repr(kwargs)) |
453 | - if not key in _cache: |
454 | - _cache[key] = fun(*args, **kwargs) |
455 | - return _cache[key] |
456 | - |
457 | - wrapped.__doc__ = "%s %s" % (fun.__doc__, "(cached)") |
458 | - return wrapped |
459 | - |
460 | - |
461 | -@simple_cache |
462 | -def get_maas_package_version(): |
463 | - """Return the apt version for the main MAAS package.""" |
464 | - return get_version_from_apt(REGION_PACKAGE_NAME) |
465 | - |
466 | - |
467 | -@simple_cache |
468 | -def get_maas_version_subversion(): |
469 | - """Return a tuple with the MAAS version and the MAAS subversion.""" |
470 | - apt_version = get_maas_package_version() |
471 | - if apt_version: |
472 | - return extract_version_subversion(apt_version) |
473 | - else: |
474 | - # Get the branch information |
475 | - branch = get_maas_branch() |
476 | - if branch is None: |
477 | - # Not installed not in branch, then no way to identify. This should |
478 | - # not happen, but just in case. |
479 | - return "unknown", '' |
480 | - else: |
481 | - return "from source (+bzr%s)" % branch.revno(), '' |
482 | - |
483 | - |
484 | -@simple_cache |
485 | -def get_maas_version_ui(): |
486 | - """Return the version string for the running MAAS region. |
487 | - |
488 | - The returned string is suitable to display in the UI. |
489 | - """ |
490 | - version, subversion = get_maas_version_subversion() |
491 | - return "%s (%s)" % (version, subversion) if subversion else version |
492 | - |
493 | - |
494 | -@simple_cache |
495 | -def get_maas_doc_version(): |
496 | - """Return the doc version for the running MAAS region.""" |
497 | - doc_prefix = 'docs' |
498 | - apt_version = get_maas_package_version() |
499 | - if apt_version: |
500 | - version, _ = extract_version_subversion(apt_version) |
501 | - return doc_prefix + '.'.join(version.split('.')[:2]) |
502 | - else: |
503 | - return doc_prefix |
504 | |
505 | === modified file 'src/provisioningserver/rpc/cluster.py' |
506 | --- src/provisioningserver/rpc/cluster.py 2015-07-31 20:46:29 +0000 |
507 | +++ src/provisioningserver/rpc/cluster.py 2015-08-03 19:11:39 +0000 |
508 | @@ -59,6 +59,30 @@ |
509 | |
510 | arguments = [] |
511 | response = [ |
512 | + (b"images", amp.AmpList( |
513 | + [(b"osystem", amp.Unicode()), |
514 | + (b"architecture", amp.Unicode()), |
515 | + (b"subarchitecture", amp.Unicode()), |
516 | + (b"release", amp.Unicode()), |
517 | + (b"label", amp.Unicode()), |
518 | + (b"purpose", amp.Unicode()), |
519 | + (b"xinstall_type", amp.Unicode()), |
520 | + (b"xinstall_path", amp.Unicode())])) |
521 | + ] |
522 | + errors = [] |
523 | + |
524 | + |
525 | +class ListBootImagesV2(amp.Command): |
526 | + """List the boot images available on this cluster controller. |
527 | + |
528 | + This command compresses the images list to allow more images in the |
529 | + response and to remove the amp.TooLong error. |
530 | + |
531 | + :since: 1.7.6 |
532 | + """ |
533 | + |
534 | + arguments = [] |
535 | + response = [ |
536 | (b"images", CompressedAmpList( |
537 | [(b"osystem", amp.Unicode()), |
538 | (b"architecture", amp.Unicode()), |
539 | |
540 | === modified file 'src/provisioningserver/rpc/clusterservice.py' |
541 | --- src/provisioningserver/rpc/clusterservice.py 2014-12-18 13:53:48 +0000 |
542 | +++ src/provisioningserver/rpc/clusterservice.py 2015-08-03 19:11:39 +0000 |
543 | @@ -149,6 +149,15 @@ |
544 | """ |
545 | return {"images": list_boot_images()} |
546 | |
547 | + @cluster.ListBootImagesV2.responder |
548 | + def list_boot_images_v2(self): |
549 | + """list_boot_images_v2() |
550 | + |
551 | + Implementation of |
552 | + :py:class:`~provisioningserver.rpc.cluster.ListBootImagesV2`. |
553 | + """ |
554 | + return {"images": list_boot_images()} |
555 | + |
556 | @cluster.ImportBootImages.responder |
557 | def import_boot_images(self, sources, http_proxy=None, https_proxy=None): |
558 | """import_boot_images() |
559 | |
560 | === modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py' |
561 | --- src/provisioningserver/rpc/tests/test_clusterservice.py 2015-03-02 16:12:40 +0000 |
562 | +++ src/provisioningserver/rpc/tests/test_clusterservice.py 2015-08-03 19:11:39 +0000 |
563 | @@ -217,14 +217,19 @@ |
564 | return d.addCallback(check) |
565 | |
566 | |
567 | -class TestClusterProtocol_ListBootImages(MAASTestCase): |
568 | +class TestClusterProtocol_ListBootImages_and_ListBootImagesV2(MAASTestCase): |
569 | |
570 | run_tests_with = MAASTwistedRunTest.make_factory(timeout=5) |
571 | |
572 | + scenarios = ( |
573 | + ("ListBootImages", {"rpc_call": cluster.ListBootImages}), |
574 | + ("ListBootImagesV2", {"rpc_call": cluster.ListBootImagesV2}), |
575 | + ) |
576 | + |
577 | def test_list_boot_images_is_registered(self): |
578 | protocol = Cluster() |
579 | responder = protocol.locateResponder( |
580 | - cluster.ListBootImages.commandName) |
581 | + self.rpc_call.commandName) |
582 | self.assertIsNotNone(responder) |
583 | |
584 | @inlineCallbacks |
585 | @@ -233,7 +238,7 @@ |
586 | list_boot_images = self.patch(tftppath, "list_boot_images") |
587 | list_boot_images.return_value = [] |
588 | |
589 | - response = yield call_responder(Cluster(), cluster.ListBootImages, {}) |
590 | + response = yield call_responder(Cluster(), self.rpc_call, {}) |
591 | |
592 | self.assertEqual({"images": []}, response) |
593 | |
594 | @@ -281,7 +286,7 @@ |
595 | expected_image['xinstall_path'] = '' |
596 | expected_image['xinstall_type'] = '' |
597 | |
598 | - response = yield call_responder(Cluster(), cluster.ListBootImages, {}) |
599 | + response = yield call_responder(Cluster(), self.rpc_call, {}) |
600 | |
601 | self.assertThat(response, KeysEqual("images")) |
602 | self.assertItemsEqual(expected_images, response["images"]) |
603 | |
604 | === modified file 'src/provisioningserver/rpc/tests/test_docs.py' |
605 | --- src/provisioningserver/rpc/tests/test_docs.py 2014-06-25 11:26:56 +0000 |
606 | +++ src/provisioningserver/rpc/tests/test_docs.py 2015-08-03 19:11:39 +0000 |
607 | @@ -70,6 +70,7 @@ |
608 | self.since_clause_missing_message, Contains(":since:")) |
609 | since_clause_contains_version = Annotate( |
610 | self.since_clause_version_not_recognised, MatchesRegex( |
611 | - ".*^:since: *[1-9][.][0-9]+$", re.DOTALL | re.MULTILINE)) |
612 | + ".*^:since: *[1-9][.][0-9]+([.][0-9]+)?$", |
613 | + re.DOTALL | re.MULTILINE)) |
614 | self.assertThat(getdoc(self.command), MatchesAll( |
615 | contains_since_clause, since_clause_contains_version)) |
Self-approving backport.