Merge lp:~blake-rouse/maas/boot-images-rpc-v2 into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 4151
Proposed branch: lp:~blake-rouse/maas/boot-images-rpc-v2
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 442 lines (+222/-36)
8 files modified
src/maasserver/bootresources.py (+13/-2)
src/maasserver/clusterrpc/boot_images.py (+25/-5)
src/maasserver/clusterrpc/tests/test_boot_images.py (+75/-1)
src/maasserver/tests/test_bootresources.py (+65/-23)
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
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+266728@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.

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

Thanks for doing this. I have several suggestions. Please consider those before landing.

review: Approve
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Thanks for the quick review. Fixed all your points! Will backport this all the way to 1.7 so the previous backport wont affect others.

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

The attempt to merge lp:~blake-rouse/maas/boot-images-rpc-v2 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Hit http://security.ubuntu.com trusty-security Release.gpg
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://security.ubuntu.com trusty-security Release
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:1 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:2 http://nova.clouds.archive.ubuntu.com trusty-updates Release [63.5 kB]
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [227 kB]
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [130 kB]
Get:5 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [598 kB]
Get:6 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [301 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,322 kB in 3s (411 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 phantomjs postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage 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-mock python-netaddr python-netifaces python-nose python-oauth python-openssl python-paramiko python-pexpect python-pip pyt...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/bootresources.py'
--- src/maasserver/bootresources.py 2015-07-08 20:47:12 +0000
+++ src/maasserver/bootresources.py 2015-08-03 16:36:18 +0000
@@ -86,7 +86,10 @@
86from provisioningserver.import_images.keyrings import write_all_keyrings86from provisioningserver.import_images.keyrings import write_all_keyrings
87from provisioningserver.import_images.product_mapping import map_products87from provisioningserver.import_images.product_mapping import map_products
88from provisioningserver.logger import get_maas_logger88from provisioningserver.logger import get_maas_logger
89from provisioningserver.rpc.cluster import ListBootImages89from provisioningserver.rpc.cluster import (
90 ListBootImages,
91 ListBootImagesV2,
92)
90from provisioningserver.utils.fs import tempdir93from provisioningserver.utils.fs import tempdir
91from provisioningserver.utils.shell import ExternalProcessError94from provisioningserver.utils.shell import ExternalProcessError
92from provisioningserver.utils.twisted import (95from provisioningserver.utils.twisted import (
@@ -103,6 +106,7 @@
103from twisted.internet import reactor106from twisted.internet import reactor
104from twisted.internet.defer import DeferredList107from twisted.internet.defer import DeferredList
105from twisted.internet.threads import deferToThread108from twisted.internet.threads import deferToThread
109from twisted.protocols.amp import UnhandledCommand
106from twisted.python import log110from twisted.python import log
107111
108112
@@ -1122,7 +1126,14 @@
1122 clients = getAllClients()1126 clients = getAllClients()
11231127
1124 def get_images(client):1128 def get_images(client):
1125 return client(ListBootImages).addCallback(itemgetter("images"))1129 def fallback_v1(failure):
1130 failure.trap(UnhandledCommand)
1131 return client(ListBootImages)
1132
1133 d = client(ListBootImagesV2)
1134 d.addErrback(fallback_v1)
1135 d.addCallback(itemgetter("images"))
1136 return d
11261137
1127 d = DeferredList(imap(get_images, clients), consumeErrors=True)1138 d = DeferredList(imap(get_images, clients), consumeErrors=True)
11281139
11291140
=== modified file 'src/maasserver/clusterrpc/boot_images.py'
--- src/maasserver/clusterrpc/boot_images.py 2015-07-09 09:10:51 +0000
+++ src/maasserver/clusterrpc/boot_images.py 2015-08-03 16:36:18 +0000
@@ -32,8 +32,10 @@
32from provisioningserver.rpc.cluster import (32from provisioningserver.rpc.cluster import (
33 IsImportBootImagesRunning,33 IsImportBootImagesRunning,
34 ListBootImages,34 ListBootImages,
35 ListBootImagesV2,
35)36)
36from provisioningserver.utils.twisted import synchronous37from provisioningserver.utils.twisted import synchronous
38from twisted.protocols.amp import UnhandledCommand
37from twisted.python.failure import Failure39from twisted.python.failure import Failure
3840
3941
@@ -91,16 +93,34 @@
91 30 seconds.93 30 seconds.
92 """94 """
93 client = getClientFor(nodegroup.uuid, timeout=1)95 client = getClientFor(nodegroup.uuid, timeout=1)
94 call = client(ListBootImages)96 try:
95 return call.wait(30).get("images")97 call = client(ListBootImagesV2)
98 return call.wait(30).get("images")
99 except UnhandledCommand:
100 call = client(ListBootImages)
101 return call.wait(30).get("images")
96102
97103
98@synchronous104@synchronous
99def _get_available_boot_images():105def _get_available_boot_images():
100 """Obtain boot images available on connected clusters."""106 """Obtain boot images available on connected clusters."""
101 listimages = lambda client: partial(client, ListBootImages)107 listimages_v1 = lambda client: partial(client, ListBootImages)
102 responses = async.gather(imap(listimages, getAllClients()))108 listimages_v2 = lambda client: partial(client, ListBootImagesV2)
103 for response in suppress_failures(responses):109 clients_v2 = getAllClients()
110 responses_v2 = async.gather(imap(listimages_v2, clients_v2))
111 clients_v1 = []
112 for i, response in enumerate(responses_v2):
113 if (isinstance(response, Failure) and
114 response.check(UnhandledCommand) is not None):
115 clients_v1.append(clients_v2[i])
116 elif not isinstance(response, Failure):
117 # Convert each image to a frozenset of its items.
118 yield frozenset(
119 frozenset(image.viewitems())
120 for image in response["images"]
121 )
122 responses_v1 = async.gather(imap(listimages_v1, clients_v1))
123 for response in suppress_failures(responses_v1):
104 # Convert each image to a frozenset of its items.124 # Convert each image to a frozenset of its items.
105 yield frozenset(125 yield frozenset(
106 frozenset(image.viewitems())126 frozenset(image.viewitems())
107127
=== modified file 'src/maasserver/clusterrpc/tests/test_boot_images.py'
--- src/maasserver/clusterrpc/tests/test_boot_images.py 2015-07-09 09:16:20 +0000
+++ src/maasserver/clusterrpc/tests/test_boot_images.py 2015-08-03 16:36:18 +0000
@@ -16,6 +16,7 @@
1616
17import os17import os
1818
19from maasserver.clusterrpc import boot_images as boot_images_module
19from maasserver.clusterrpc.boot_images import (20from maasserver.clusterrpc.boot_images import (
20 get_all_available_boot_images,21 get_all_available_boot_images,
21 get_boot_images,22 get_boot_images,
@@ -30,9 +31,24 @@
30 NODEGROUP_STATUS,31 NODEGROUP_STATUS,
31)32)
32from maasserver.rpc import getAllClients33from maasserver.rpc import getAllClients
33from maasserver.rpc.testing.fixtures import RunningClusterRPCFixture34from maasserver.rpc.testing.fixtures import (
35 MockLiveRegionToClusterRPCFixture,
36 RunningClusterRPCFixture,
37)
38from maasserver.testing.eventloop import (
39 RegionEventLoopFixture,
40 RunningEventLoopFixture,
41)
34from maasserver.testing.factory import factory42from maasserver.testing.factory import factory
35from maasserver.testing.testcase import MAASServerTestCase43from maasserver.testing.testcase import MAASServerTestCase
44from maastesting.matchers import (
45 MockCalledOnceWith,
46 MockCallsMatch,
47)
48from mock import (
49 call,
50 MagicMock,
51)
36from provisioningserver.boot.tests import test_tftppath52from provisioningserver.boot.tests import test_tftppath
37from provisioningserver.boot.tftppath import (53from provisioningserver.boot.tftppath import (
38 compose_image_path,54 compose_image_path,
@@ -42,12 +58,17 @@
42 boot_images,58 boot_images,
43 clusterservice,59 clusterservice,
44)60)
61from provisioningserver.rpc.cluster import (
62 ListBootImages,
63 ListBootImagesV2,
64)
45from provisioningserver.testing.boot_images import (65from provisioningserver.testing.boot_images import (
46 make_boot_image_storage_params,66 make_boot_image_storage_params,
47 make_image,67 make_image,
48)68)
49from provisioningserver.testing.config import ClusterConfigurationFixture69from provisioningserver.testing.config import ClusterConfigurationFixture
50from twisted.internet.defer import succeed70from twisted.internet.defer import succeed
71from twisted.protocols.amp import UnhandledCommand
5172
5273
53def make_image_dir(image_params, tftp_root):74def make_image_dir(image_params, tftp_root):
@@ -171,6 +192,28 @@
171 ],192 ],
172 get_boot_images(nodegroup))193 get_boot_images(nodegroup))
173194
195 def test_calls_ListBootImagesV2_before_ListBootImages(self):
196 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
197 mock_client = MagicMock()
198 self.patch_autospec(
199 boot_images_module, "getClientFor").return_value = mock_client
200 get_boot_images(nodegroup)
201 self.assertThat(mock_client, MockCalledOnceWith(ListBootImagesV2))
202
203 def test_calls_ListBootImages_if_raised_UnhandledCommand(self):
204 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
205 mock_client = MagicMock()
206 self.patch_autospec(
207 boot_images_module, "getClientFor").return_value = mock_client
208 mock_client.return_value.wait.side_effect = [
209 UnhandledCommand(),
210 {"images": []},
211 ]
212 get_boot_images(nodegroup)
213 self.assertThat(mock_client, MockCallsMatch(
214 call(ListBootImagesV2),
215 call(ListBootImages)))
216
174217
175class TestGetAvailableBootImages(MAASServerTestCase):218class TestGetAvailableBootImages(MAASServerTestCase):
176 """Tests for `get_common_available_boot_images` and219 """Tests for `get_common_available_boot_images` and
@@ -251,6 +294,37 @@
251294
252 self.assertItemsEqual(images, self.get())295 self.assertItemsEqual(images, self.get())
253296
297 def test_fallback_to_ListBootImages_on_old_clusters(self):
298 nodegroup_1 = factory.make_NodeGroup()
299 nodegroup_1.accept()
300 nodegroup_2 = factory.make_NodeGroup()
301 nodegroup_2.accept()
302 nodegroup_3 = factory.make_NodeGroup()
303 nodegroup_3.accept()
304
305 images = [make_rpc_boot_image() for _ in range(3)]
306
307 # Limit the region's event loop to only the "rpc" service.
308 self.useFixture(RegionEventLoopFixture("rpc"))
309 # Now start the region's event loop.
310 self.useFixture(RunningEventLoopFixture())
311 # This fixture allows us to simulate mock clusters.
312 rpc = self.useFixture(MockLiveRegionToClusterRPCFixture())
313
314 # This simulates an older cluster, one without ListBootImagesV2.
315 cluster_1 = rpc.makeCluster(nodegroup_1, ListBootImages)
316 cluster_1.ListBootImages.return_value = succeed({'images': images})
317
318 # This simulates a newer cluster, one with ListBootImagesV2.
319 cluster_2 = rpc.makeCluster(nodegroup_2, ListBootImagesV2)
320 cluster_2.ListBootImagesV2.return_value = succeed({'images': images})
321
322 # This simulates a broken cluster.
323 cluster_3 = rpc.makeCluster(nodegroup_3, ListBootImagesV2)
324 cluster_3.ListBootImagesV2.side_effect = ZeroDivisionError
325
326 self.assertItemsEqual(images, self.get())
327
254 def test_returns_empty_list_when_all_clusters_fail(self):328 def test_returns_empty_list_when_all_clusters_fail(self):
255 factory.make_NodeGroup().accept()329 factory.make_NodeGroup().accept()
256 factory.make_NodeGroup().accept()330 factory.make_NodeGroup().accept()
257331
=== modified file 'src/maasserver/tests/test_bootresources.py'
--- src/maasserver/tests/test_bootresources.py 2015-07-10 10:55:53 +0000
+++ src/maasserver/tests/test_bootresources.py 2015-08-03 16:36:18 +0000
@@ -85,7 +85,10 @@
85)85)
86from provisioningserver.auth import get_maas_user_gpghome86from provisioningserver.auth import get_maas_user_gpghome
87from provisioningserver.import_images.product_mapping import ProductMapping87from provisioningserver.import_images.product_mapping import ProductMapping
88from provisioningserver.rpc.cluster import ListBootImages88from provisioningserver.rpc.cluster import (
89 ListBootImages,
90 ListBootImagesV2,
91)
89from provisioningserver.utils.text import normalise_whitespace92from provisioningserver.utils.text import normalise_whitespace
90from provisioningserver.utils.twisted import asynchronous93from provisioningserver.utils.twisted import asynchronous
91from testtools.deferredruntest import extract_result94from testtools.deferredruntest import extract_result
@@ -100,6 +103,7 @@
100 fail,103 fail,
101 succeed,104 succeed,
102)105)
106from twisted.protocols.amp import UnhandledCommand
103107
104108
105def make_boot_resource_file_with_stream():109def make_boot_resource_file_with_stream():
@@ -1460,28 +1464,66 @@
1460 factory.make_BootResource()1464 factory.make_BootResource()
1461 self.assertTrue(service.are_boot_images_available_in_the_region())1465 self.assertTrue(service.are_boot_images_available_in_the_region())
14621466
1463 def test__are_boot_images_available_in_any_cluster(self):1467 def test__are_boot_images_available_in_any_cluster_v2(self):
1464 # Import the websocket handlers now: merely defining DeviceHandler,1468 # Import the websocket handlers now: merely defining DeviceHandler,
1465 # e.g., causes a database access, which will crash if it happens1469 # e.g., causes a database access, which will crash if it happens
1466 # inside the reactor thread where database access is forbidden and1470 # inside the reactor thread where database access is forbidden and
1467 # prevented. My own opinion is that a class definition should not1471 # prevented. My own opinion is that a class definition should not
1468 # cause a database access and we ought to fix that.1472 # cause a database access and we ought to fix that.
1469 import maasserver.websockets.handlers # noqa1473 import maasserver.websockets.handlers # noqa
14701474
1471 cluster = factory.make_NodeGroup()1475 cluster = factory.make_NodeGroup()
1472 service = bootresources.ImportResourcesProgressService()1476 service = bootresources.ImportResourcesProgressService()
14731477
1474 self.useFixture(RegionEventLoopFixture("rpc"))1478 self.useFixture(RegionEventLoopFixture("rpc"))
1475 self.useFixture(RunningEventLoopFixture())1479 self.useFixture(RunningEventLoopFixture())
1476 region_rpc = MockLiveRegionToClusterRPCFixture()1480 region_rpc = MockLiveRegionToClusterRPCFixture()
1477 self.useFixture(region_rpc)1481 self.useFixture(region_rpc)
14781482
1479 # are_boot_images_available_in_the_region() returns False when there1483 # are_boot_images_available_in_the_region() returns False when there
1480 # are no clusters connected.1484 # are no clusters connected.
1481 self.assertFalse(service.are_boot_images_available_in_any_cluster())1485 self.assertFalse(service.are_boot_images_available_in_any_cluster())
14821486
1483 # Connect a cluster to the region via RPC.1487 # Connect a cluster to the region via RPC.
1484 cluster_rpc = region_rpc.makeCluster(cluster, ListBootImages)1488 cluster_rpc = region_rpc.makeCluster(cluster, ListBootImagesV2)
1489
1490 # are_boot_images_available_in_the_region() returns False when none of
1491 # the clusters have any images.
1492 cluster_rpc.ListBootImagesV2.return_value = succeed({"images": []})
1493 self.assertFalse(service.are_boot_images_available_in_any_cluster())
1494
1495 # are_boot_images_available_in_the_region() returns True when a
1496 # cluster has an imported boot image.
1497 response = {"images": [make_rpc_boot_image()]}
1498 cluster_rpc.ListBootImagesV2.return_value = succeed(response)
1499 self.assertTrue(service.are_boot_images_available_in_any_cluster())
1500
1501 def test__are_boot_images_available_in_any_cluster_v1(self):
1502 # Import the websocket handlers now: merely defining DeviceHandler,
1503 # e.g., causes a database access, which will crash if it happens
1504 # inside the reactor thread where database access is forbidden and
1505 # prevented. My own opinion is that a class definition should not
1506 # cause a database access and we ought to fix that.
1507 import maasserver.websockets.handlers # noqa
1508
1509 cluster = factory.make_NodeGroup()
1510 service = bootresources.ImportResourcesProgressService()
1511
1512 self.useFixture(RegionEventLoopFixture("rpc"))
1513 self.useFixture(RunningEventLoopFixture())
1514 region_rpc = MockLiveRegionToClusterRPCFixture()
1515 self.useFixture(region_rpc)
1516
1517 # are_boot_images_available_in_the_region() returns False when there
1518 # are no clusters connected.
1519 self.assertFalse(service.are_boot_images_available_in_any_cluster())
1520
1521 # Connect a cluster to the region via RPC.
1522 cluster_rpc = region_rpc.makeCluster(
1523 cluster, ListBootImagesV2, ListBootImages)
1524
1525 # All calls to ListBootImagesV2 raises a UnhandledCommand.
1526 cluster_rpc.ListBootImagesV2.side_effect = UnhandledCommand
14851527
1486 # are_boot_images_available_in_the_region() returns False when none of1528 # are_boot_images_available_in_the_region() returns False when none of
1487 # the clusters have any images.1529 # the clusters have any images.
14881530
=== modified file 'src/provisioningserver/rpc/cluster.py'
--- src/provisioningserver/rpc/cluster.py 2015-07-31 20:07:44 +0000
+++ src/provisioningserver/rpc/cluster.py 2015-08-03 16:36:18 +0000
@@ -59,6 +59,30 @@
5959
60 arguments = []60 arguments = []
61 response = [61 response = [
62 (b"images", amp.AmpList(
63 [(b"osystem", amp.Unicode()),
64 (b"architecture", amp.Unicode()),
65 (b"subarchitecture", amp.Unicode()),
66 (b"release", amp.Unicode()),
67 (b"label", amp.Unicode()),
68 (b"purpose", amp.Unicode()),
69 (b"xinstall_type", amp.Unicode()),
70 (b"xinstall_path", amp.Unicode())]))
71 ]
72 errors = []
73
74
75class ListBootImagesV2(amp.Command):
76 """List the boot images available on this cluster controller.
77
78 This command compresses the images list to allow more images in the
79 response and to remove the amp.TooLong error.
80
81 :since: 1.7.6
82 """
83
84 arguments = []
85 response = [
62 (b"images", CompressedAmpList(86 (b"images", CompressedAmpList(
63 [(b"osystem", amp.Unicode()),87 [(b"osystem", amp.Unicode()),
64 (b"architecture", amp.Unicode()),88 (b"architecture", amp.Unicode()),
6589
=== modified file 'src/provisioningserver/rpc/clusterservice.py'
--- src/provisioningserver/rpc/clusterservice.py 2015-06-25 22:33:37 +0000
+++ src/provisioningserver/rpc/clusterservice.py 2015-08-03 16:36:18 +0000
@@ -151,6 +151,15 @@
151 """151 """
152 return {"images": list_boot_images()}152 return {"images": list_boot_images()}
153153
154 @cluster.ListBootImagesV2.responder
155 def list_boot_images_v2(self):
156 """list_boot_images_v2()
157
158 Implementation of
159 :py:class:`~provisioningserver.rpc.cluster.ListBootImagesV2`.
160 """
161 return {"images": list_boot_images()}
162
154 @cluster.ImportBootImages.responder163 @cluster.ImportBootImages.responder
155 def import_boot_images(self, sources, http_proxy=None, https_proxy=None):164 def import_boot_images(self, sources, http_proxy=None, https_proxy=None):
156 """import_boot_images()165 """import_boot_images()
157166
=== modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py'
--- src/provisioningserver/rpc/tests/test_clusterservice.py 2015-06-26 15:36:26 +0000
+++ src/provisioningserver/rpc/tests/test_clusterservice.py 2015-08-03 16:36:18 +0000
@@ -215,14 +215,19 @@
215 return d.addCallback(check)215 return d.addCallback(check)
216216
217217
218class TestClusterProtocol_ListBootImages(MAASTestCase):218class TestClusterProtocol_ListBootImages_and_ListBootImagesV2(MAASTestCase):
219219
220 run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)220 run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)
221221
222 scenarios = (
223 ("ListBootImages", {"rpc_call": cluster.ListBootImages}),
224 ("ListBootImagesV2", {"rpc_call": cluster.ListBootImagesV2}),
225 )
226
222 def test_list_boot_images_is_registered(self):227 def test_list_boot_images_is_registered(self):
223 protocol = Cluster()228 protocol = Cluster()
224 responder = protocol.locateResponder(229 responder = protocol.locateResponder(
225 cluster.ListBootImages.commandName)230 self.rpc_call.commandName)
226 self.assertIsNotNone(responder)231 self.assertIsNotNone(responder)
227232
228 @inlineCallbacks233 @inlineCallbacks
@@ -232,7 +237,7 @@
232 list_boot_images = self.patch(tftppath, "list_boot_images")237 list_boot_images = self.patch(tftppath, "list_boot_images")
233 list_boot_images.return_value = []238 list_boot_images.return_value = []
234239
235 response = yield call_responder(Cluster(), cluster.ListBootImages, {})240 response = yield call_responder(Cluster(), self.rpc_call, {})
236241
237 self.assertEqual({"images": []}, response)242 self.assertEqual({"images": []}, response)
238243
@@ -282,7 +287,7 @@
282 expected_image['xinstall_path'] = ''287 expected_image['xinstall_path'] = ''
283 expected_image['xinstall_type'] = ''288 expected_image['xinstall_type'] = ''
284289
285 response = yield call_responder(Cluster(), cluster.ListBootImages, {})290 response = yield call_responder(Cluster(), self.rpc_call, {})
286291
287 self.assertThat(response, KeysEqual("images"))292 self.assertThat(response, KeysEqual("images"))
288 self.assertItemsEqual(expected_images, response["images"])293 self.assertItemsEqual(expected_images, response["images"])
289294
=== modified file 'src/provisioningserver/rpc/tests/test_docs.py'
--- src/provisioningserver/rpc/tests/test_docs.py 2015-05-07 18:14:38 +0000
+++ src/provisioningserver/rpc/tests/test_docs.py 2015-08-03 16:36:18 +0000
@@ -70,6 +70,7 @@
70 self.since_clause_missing_message, Contains(":since:"))70 self.since_clause_missing_message, Contains(":since:"))
71 since_clause_contains_version = Annotate(71 since_clause_contains_version = Annotate(
72 self.since_clause_version_not_recognised, MatchesRegex(72 self.since_clause_version_not_recognised, MatchesRegex(
73 ".*^:since: *[1-9][.][0-9]+$", re.DOTALL | re.MULTILINE))73 ".*^:since: *[1-9][.][0-9]+([.][0-9]+)?$",
74 re.DOTALL | re.MULTILINE))
74 self.assertThat(getdoc(self.command), MatchesAll(75 self.assertThat(getdoc(self.command), MatchesAll(
75 contains_since_clause, since_clause_contains_version))76 contains_since_clause, since_clause_contains_version))