Merge lp:~blake-rouse/maas/fix-cluster-image-syncing 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: 2960
Proposed branch: lp:~blake-rouse/maas/fix-cluster-image-syncing
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 529 lines (+65/-217)
10 files modified
src/maasserver/rpc/bootsources.py (+0/-46)
src/maasserver/rpc/regionservice.py (+3/-7)
src/maasserver/rpc/tests/test_bootsources.py (+0/-42)
src/maasserver/rpc/tests/test_regionservice.py (+9/-49)
src/provisioningserver/import_images/boot_resources.py (+6/-8)
src/provisioningserver/import_images/tests/test_boot_resources.py (+1/-2)
src/provisioningserver/pserv_services/image_download_service.py (+7/-19)
src/provisioningserver/pserv_services/tests/test_image_download_service.py (+3/-40)
src/provisioningserver/rpc/boot_images.py (+6/-3)
src/provisioningserver/rpc/tests/test_boot_images.py (+30/-1)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-cluster-image-syncing
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+234192@code.launchpad.net

Commit message

Fix GetBootSources* RPC to return simplestreams endpoint on the region. Move the service_lock into rpc import images, so other RPC calls will block from running the import at the same time. Cleanup log messages from the import on the cluster side.

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

This is cool stuff :) It looks good, but I'm teetering on the edge of Needs Fixing for a couple of things, but Approve has it. However, please address (or put me right on) my comments before landing.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed file 'src/maasserver/rpc/bootsources.py'
--- src/maasserver/rpc/bootsources.py 2014-08-26 17:32:40 +0000
+++ src/maasserver/rpc/bootsources.py 1970-01-01 00:00:00 +0000
@@ -1,46 +0,0 @@
1# Copyright 2014 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""RPC helpers relating to boot sources."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = [
16 "get_boot_sources",
17]
18
19from maasserver.models import BootSource
20from maasserver.utils.async import transactional
21from provisioningserver.utils.twisted import synchronous
22
23
24@synchronous
25@transactional
26def get_boot_sources(uuid, remove_os=False):
27 """Obtain boot sources and selections from the database.
28
29 Returns them as a structure suitable for returning in the response
30 for :py:class:`~provisioningserver.rpc.region.GetBootSources`.
31
32 :param remove_os: Remove the os field from selections. This is
33 used for v1 of get_boot_sources, as it should not include the os field.
34 For v2 of get_boot_sources, the os field is included.
35 """
36 # No longer is uuid used for this, as its now global. The uuid is just
37 # ignored.
38 sources = [
39 source.to_dict()
40 for source in BootSource.objects.all()
41 ]
42 for source in sources:
43 if remove_os:
44 for selection in source['selections']:
45 del selection['os']
46 return sources
470
=== modified file 'src/maasserver/rpc/regionservice.py'
--- src/maasserver/rpc/regionservice.py 2014-09-05 08:10:06 +0000
+++ src/maasserver/rpc/regionservice.py 2014-09-11 13:00:18 +0000
@@ -30,8 +30,8 @@
30 eventloop,30 eventloop,
31 locks,31 locks,
32 )32 )
33from maasserver.bootresources import get_simplestream_endpoint
33from maasserver.rpc import (34from maasserver.rpc import (
34 bootsources,
35 configuration,35 configuration,
36 events,36 events,
37 leases,37 leases,
@@ -133,9 +133,7 @@
133 Implementation of133 Implementation of
134 :py:class:`~provisioningserver.rpc.region.GetBootSources`.134 :py:class:`~provisioningserver.rpc.region.GetBootSources`.
135 """135 """
136 d = deferToThread(bootsources.get_boot_sources, uuid, remove_os=True)136 return {b"sources": [get_simplestream_endpoint()]}
137 d.addCallback(lambda sources: {b"sources": sources})
138 return d
139137
140 @region.GetBootSourcesV2.responder138 @region.GetBootSourcesV2.responder
141 def get_boot_sources_v2(self, uuid):139 def get_boot_sources_v2(self, uuid):
@@ -144,9 +142,7 @@
144 Implementation of142 Implementation of
145 :py:class:`~provisioningserver.rpc.region.GetBootSources`.143 :py:class:`~provisioningserver.rpc.region.GetBootSources`.
146 """144 """
147 d = deferToThread(bootsources.get_boot_sources, uuid)145 return {b"sources": [get_simplestream_endpoint()]}
148 d.addCallback(lambda sources: {b"sources": sources})
149 return d
150146
151 @region.GetArchiveMirrors.responder147 @region.GetArchiveMirrors.responder
152 def get_archive_mirrors(self):148 def get_archive_mirrors(self):
153149
=== removed file 'src/maasserver/rpc/tests/test_bootsources.py'
--- src/maasserver/rpc/tests/test_bootsources.py 2014-09-05 16:38:32 +0000
+++ src/maasserver/rpc/tests/test_bootsources.py 1970-01-01 00:00:00 +0000
@@ -1,42 +0,0 @@
1# Copyright 2014 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for :py:module:`~maasserver.rpc.bootsources`."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = []
16
17from maasserver.rpc.bootsources import get_boot_sources
18from maasserver.testing.factory import factory
19from maasserver.testing.testcase import MAASServerTestCase
20
21
22class TestGetBootSources(MAASServerTestCase):
23
24 def test_returns_boot_sources_and_selections(self):
25 keyring = factory.make_bytes()
26 nodegroup = factory.make_NodeGroup()
27 source = factory.make_BootSource(keyring_data=keyring)
28 factory.make_BootSourceSelection(source)
29
30 expected = source.to_dict()
31 self.assertEqual([expected], get_boot_sources(nodegroup.uuid))
32
33 def test_removes_os_from_boot_source_selections(self):
34 keyring = factory.make_bytes()
35 nodegroup = factory.make_NodeGroup()
36 source = factory.make_BootSource(keyring_data=keyring)
37 factory.make_BootSourceSelection(source)
38
39 expected = source.to_dict()
40 del expected['selections'][0]['os']
41 self.assertEqual(
42 [expected], get_boot_sources(nodegroup.uuid, remove_os=True))
430
=== modified file 'src/maasserver/rpc/tests/test_regionservice.py'
--- src/maasserver/rpc/tests/test_regionservice.py 2014-09-08 20:07:07 +0000
+++ src/maasserver/rpc/tests/test_regionservice.py 2014-09-11 13:00:18 +0000
@@ -31,6 +31,7 @@
31 eventloop,31 eventloop,
32 locks,32 locks,
33 )33 )
34from maasserver.bootresources import get_simplestream_endpoint
34from maasserver.enum import (35from maasserver.enum import (
35 NODE_STATUS,36 NODE_STATUS,
36 POWER_STATE,37 POWER_STATE,
@@ -331,37 +332,18 @@
331 self.assertIsNot(responder, None)332 self.assertIsNot(responder, None)
332333
333 @wait_for_reactor334 @wait_for_reactor
334 def test_get_boot_sources_can_be_called(self):335 def test_get_boot_sources_returns_simplestreams_endpoint(self):
335 uuid = factory.make_name("uuid")336 uuid = factory.make_name("uuid")
336337
337 d = call_responder(Region(), GetBootSources, {b"uuid": uuid})338 d = call_responder(Region(), GetBootSources, {b"uuid": uuid})
338339
339 def check(response):340 def check(response):
340 self.assertEqual({b"sources": []}, response)341 self.assertEqual(
342 {b"sources": [get_simplestream_endpoint()]},
343 response)
341344
342 return d.addCallback(check)345 return d.addCallback(check)
343346
344 @transactional
345 def make_boot_source_selection(self, keyring):
346 nodegroup = factory.make_NodeGroup()
347 boot_source = factory.make_BootSource(keyring_data=keyring)
348 factory.make_BootSourceSelection(boot_source)
349 return nodegroup.uuid, boot_source.to_dict()
350
351 @wait_for_reactor
352 @inlineCallbacks
353 def test_get_boot_sources_with_real_cluster(self):
354 keyring = factory.make_bytes()
355
356 uuid, boot_source = yield deferToThread(
357 self.make_boot_source_selection, keyring)
358 del boot_source['selections'][0]['os']
359
360 response = yield call_responder(
361 Region(), GetBootSources, {b"uuid": uuid})
362
363 self.assertEqual({b"sources": [boot_source]}, response)
364
365347
366class TestRegionProtocol_GetBootSourcesV2(TransactionTestCase):348class TestRegionProtocol_GetBootSourcesV2(TransactionTestCase):
367349
@@ -371,40 +353,18 @@
371 self.assertIsNot(responder, None)353 self.assertIsNot(responder, None)
372354
373 @wait_for_reactor355 @wait_for_reactor
374 def test_get_boot_sources_v2_can_be_called(self):356 def test_get_boot_sources_v2_returns_simplestreams_endpoint(self):
375 uuid = factory.make_name("uuid")357 uuid = factory.make_name("uuid")
376358
377 d = call_responder(Region(), GetBootSourcesV2, {b"uuid": uuid})359 d = call_responder(Region(), GetBootSourcesV2, {b"uuid": uuid})
378360
379 def check(response):361 def check(response):
380 self.assertEqual({b"sources": []}, response)362 self.assertEqual(
363 {b"sources": [get_simplestream_endpoint()]},
364 response)
381365
382 return d.addCallback(check)366 return d.addCallback(check)
383367
384 @transactional
385 def make_boot_source_selection(self, keyring):
386 nodegroup = factory.make_NodeGroup()
387 boot_source = factory.make_BootSource(keyring_data=keyring)
388 factory.make_BootSourceSelection(boot_source)
389 return nodegroup.uuid, boot_source.to_dict()
390
391 @wait_for_reactor
392 @inlineCallbacks
393 def test_get_boot_sources_v2_with_real_cluster(self):
394 keyring = factory.make_bytes()
395
396 uuid, boot_source = yield deferToThread(
397 self.make_boot_source_selection, keyring)
398
399 # keyring_data contains the b64decoded representation since AMP
400 # is fine with bytes.
401 boot_source["keyring_data"] = keyring
402
403 response = yield call_responder(
404 Region(), GetBootSourcesV2, {b"uuid": uuid})
405
406 self.assertEqual({b"sources": [boot_source]}, response)
407
408368
409class TestRegionProtocol_GetArchiveMirrors(MAASTestCase):369class TestRegionProtocol_GetArchiveMirrors(MAASTestCase):
410370
411371
=== modified file 'src/provisioningserver/import_images/boot_resources.py'
--- src/provisioningserver/import_images/boot_resources.py 2014-09-08 06:37:02 +0000
+++ src/provisioningserver/import_images/boot_resources.py 2014-09-11 13:00:18 +0000
@@ -222,7 +222,7 @@
222 :param config: An iterable of dicts representing the sources from222 :param config: An iterable of dicts representing the sources from
223 which boot images will be downloaded.223 which boot images will be downloaded.
224 """224 """
225 maaslog.info("Importing boot resources.")225 maaslog.info("Started importing boot images.")
226 if len(sources) == 0:226 if len(sources) == 0:
227 maaslog.warn("Can't import: no Simplestreams sources selected.")227 maaslog.warn("Can't import: no Simplestreams sources selected.")
228 return228 return
@@ -236,15 +236,13 @@
236 image_descriptions = download_all_image_descriptions(sources)236 image_descriptions = download_all_image_descriptions(sources)
237 if image_descriptions.is_empty():237 if image_descriptions.is_empty():
238 maaslog.warn(238 maaslog.warn(
239 "No boot resources found. "239 "Finished importing boot images, no boot images available.")
240 "Check sources specification and connectivity.")
241 return240 return
242241
243 storage = provisioningserver.config.BOOT_RESOURCES_STORAGE242 storage = provisioningserver.config.BOOT_RESOURCES_STORAGE
244 meta_file_content = image_descriptions.dump_json()243 meta_file_content = image_descriptions.dump_json()
245 if meta_contains(storage, meta_file_content):244 if meta_contains(storage, meta_file_content):
246 # The current maas.meta already contains the new config. No need245 maaslog.info("Finished importing boot images, no new images.")
247 # to rewrite anything.
248 return246 return
249247
250 product_mapping = map_products(image_descriptions)248 product_mapping = map_products(image_descriptions)
@@ -252,7 +250,7 @@
252 snapshot_path = download_all_boot_resources(250 snapshot_path = download_all_boot_resources(
253 sources, storage, product_mapping)251 sources, storage, product_mapping)
254252
255 maaslog.info("Writing metadata and iSCSI targets.")253 maaslog.info("Writing boot image metadata and iSCSI targets.")
256 write_snapshot_metadata(snapshot_path, meta_file_content)254 write_snapshot_metadata(snapshot_path, meta_file_content)
257 write_targets_conf(snapshot_path)255 write_targets_conf(snapshot_path)
258256
@@ -261,9 +259,9 @@
261259
262 # If we got here, all went well. This is now truly the "current" snapshot.260 # If we got here, all went well. This is now truly the "current" snapshot.
263 update_current_symlink(storage, snapshot_path)261 update_current_symlink(storage, snapshot_path)
264 maaslog.info("Updating iSCSI targets.")262 maaslog.info("Updating boot image iSCSI targets.")
265 update_targets_conf(snapshot_path)263 update_targets_conf(snapshot_path)
266 maaslog.info("Import done.")264 maaslog.info("Finished importing boot images.")
267265
268266
269def main(args):267def main(args):
270268
=== modified file 'src/provisioningserver/import_images/tests/test_boot_resources.py'
--- src/provisioningserver/import_images/tests/test_boot_resources.py 2014-09-08 04:08:54 +0000
+++ src/provisioningserver/import_images/tests/test_boot_resources.py 2014-09-11 13:00:18 +0000
@@ -366,8 +366,7 @@
366 self.assertThat(366 self.assertThat(
367 boot_resources.maaslog.warn,367 boot_resources.maaslog.warn,
368 MockAnyCall(368 MockAnyCall(
369 "No boot resources found. "369 "Finished importing boot images, no boot images available."))
370 "Check sources specification and connectivity."))
371370
372 def test_raises_ioerror_when_no_sources_file_found(self):371 def test_raises_ioerror_when_no_sources_file_found(self):
373 self.patch_maaslog()372 self.patch_maaslog()
374373
=== modified file 'src/provisioningserver/pserv_services/image_download_service.py'
--- src/provisioningserver/pserv_services/image_download_service.py 2014-08-27 06:53:37 +0000
+++ src/provisioningserver/pserv_services/image_download_service.py 2014-09-11 13:00:18 +0000
@@ -33,7 +33,6 @@
33 )33 )
34from twisted.application.internet import TimerService34from twisted.application.internet import TimerService
35from twisted.internet.defer import (35from twisted.internet.defer import (
36 DeferredLock,
37 inlineCallbacks,36 inlineCallbacks,
38 returnValue,37 returnValue,
39 )38 )
@@ -42,7 +41,6 @@
4241
4342
44maaslog = get_maas_logger("boot_image_download_service")43maaslog = get_maas_logger("boot_image_download_service")
45service_lock = DeferredLock()
4644
4745
48class PeriodicImageDownloadService(TimerService, object):46class PeriodicImageDownloadService(TimerService, object):
@@ -117,21 +115,11 @@
117 """Check the time the last image refresh happened and initiate a new115 """Check the time the last image refresh happened and initiate a new
118 one if older than 15 minutes.116 one if older than 15 minutes.
119 """117 """
120 # Use a DeferredLock to prevent simultaneous downloads.118 last_modified = tftppath.maas_meta_last_modified()
121 if service_lock.locked:119 if last_modified is None:
122 # Don't want to block on lock release.120 yield self._start_download()
123 return121 return
124 yield service_lock.acquire()122
125 try:123 age_in_seconds = self.clock.seconds() - last_modified
126 last_modified = tftppath.maas_meta_last_modified()124 if age_in_seconds >= timedelta(minutes=15).total_seconds():
127 if last_modified is None:125 yield self._start_download()
128 # Don't auto-refresh if the user has never manually initiated
129 # a download.
130 return
131
132 age_in_seconds = self.clock.seconds() - last_modified
133 if age_in_seconds >= timedelta(minutes=15).total_seconds():
134 yield self._start_download()
135
136 finally:
137 service_lock.release()
138126
=== modified file 'src/provisioningserver/pserv_services/tests/test_image_download_service.py'
--- src/provisioningserver/pserv_services/tests/test_image_download_service.py 2014-09-01 08:40:13 +0000
+++ src/provisioningserver/pserv_services/tests/test_image_download_service.py 2014-09-11 13:00:18 +0000
@@ -34,7 +34,6 @@
34from provisioningserver.boot import tftppath34from provisioningserver.boot import tftppath
35from provisioningserver.pserv_services.image_download_service import (35from provisioningserver.pserv_services.image_download_service import (
36 PeriodicImageDownloadService,36 PeriodicImageDownloadService,
37 service_lock,
38 )37 )
39from provisioningserver.rpc import boot_images38from provisioningserver.rpc import boot_images
40from provisioningserver.rpc.boot_images import _run_import39from provisioningserver.rpc.boot_images import _run_import
@@ -45,7 +44,6 @@
45 )44 )
46from provisioningserver.rpc.testing import TwistedLoggerFixture45from provisioningserver.rpc.testing import TwistedLoggerFixture
47from provisioningserver.testing.testcase import PservTestCase46from provisioningserver.testing.testcase import PservTestCase
48from provisioningserver.utils.twisted import pause
49from testtools.deferredruntest import extract_result47from testtools.deferredruntest import extract_result
50from twisted.application.internet import TimerService48from twisted.application.internet import TimerService
51from twisted.internet import defer49from twisted.internet import defer
@@ -99,7 +97,7 @@
99 clock.advance(service.check_interval)97 clock.advance(service.check_interval)
100 self.assertEqual(3, len(get_mock_calls(maas_meta_last_modified)))98 self.assertEqual(3, len(get_mock_calls(maas_meta_last_modified)))
10199
102 def test_no_download_if_no_meta_file(self):100 def test_initiates_download_if_no_meta_file(self):
103 clock = Clock()101 clock = Clock()
104 service = PeriodicImageDownloadService(102 service = PeriodicImageDownloadService(
105 sentinel.service, clock, sentinel.uuid)103 sentinel.service, clock, sentinel.uuid)
@@ -108,7 +106,7 @@
108 tftppath,106 tftppath,
109 'maas_meta_last_modified').return_value = None107 'maas_meta_last_modified').return_value = None
110 service.startService()108 service.startService()
111 self.assertThat(_start_download, MockNotCalled())109 self.assertThat(_start_download, MockCalledOnceWith())
112110
113 def test_initiates_download_if_15_minutes_has_passed(self):111 def test_initiates_download_if_15_minutes_has_passed(self):
114 clock = Clock()112 clock = Clock()
@@ -165,7 +163,7 @@
165 def test_no_download_if_no_rpc_connections(self):163 def test_no_download_if_no_rpc_connections(self):
166 rpc_client = Mock()164 rpc_client = Mock()
167 failure = NoConnectionsAvailable()165 failure = NoConnectionsAvailable()
168 rpc_client.getClient.return_value.side_effect = failure166 rpc_client.getClient.side_effect = failure
169167
170 deferToThread = self.patch(boot_images, 'deferToThread')168 deferToThread = self.patch(boot_images, 'deferToThread')
171 service = PeriodicImageDownloadService(169 service = PeriodicImageDownloadService(
@@ -173,41 +171,6 @@
173 service.startService()171 service.startService()
174 self.assertThat(deferToThread, MockNotCalled())172 self.assertThat(deferToThread, MockNotCalled())
175173
176 @defer.inlineCallbacks
177 def test_does_not_run_if_lock_taken(self):
178 maas_meta_last_modified = self.patch(
179 tftppath, 'maas_meta_last_modified')
180 yield service_lock.acquire()
181 self.addCleanup(service_lock.release)
182 service = PeriodicImageDownloadService(
183 sentinel.rpc, Clock(), sentinel.uuid)
184 service.startService()
185 self.assertThat(maas_meta_last_modified, MockNotCalled())
186
187 def test_takes_lock_when_running(self):
188 clock = Clock()
189 service = PeriodicImageDownloadService(
190 sentinel.rpc, clock, sentinel.uuid)
191
192 # Patch the download func so it's just a Deferred that waits for
193 # one second.
194 _start_download = self.patch(service, '_start_download')
195 _start_download.return_value = pause(1, clock)
196
197 # Set conditions for a required download:
198 one_week_ago = clock.seconds() - timedelta(minutes=15).total_seconds()
199 self.patch(
200 tftppath,
201 'maas_meta_last_modified').return_value = one_week_ago
202
203 # Lock is acquired for the first download after startup.
204 service.startService()
205 self.assertTrue(service_lock.locked)
206
207 # Lock is released once the download is done.
208 clock.advance(1)
209 self.assertFalse(service_lock.locked)
210
211 def test_logs_other_errors(self):174 def test_logs_other_errors(self):
212 service = PeriodicImageDownloadService(175 service = PeriodicImageDownloadService(
213 sentinel.rpc, Clock(), sentinel.uuid)176 sentinel.rpc, Clock(), sentinel.uuid)
214177
=== modified file 'src/provisioningserver/rpc/boot_images.py'
--- src/provisioningserver/rpc/boot_images.py 2014-09-02 06:21:04 +0000
+++ src/provisioningserver/rpc/boot_images.py 2014-09-11 13:00:18 +0000
@@ -24,9 +24,12 @@
24from provisioningserver.import_images import boot_resources24from provisioningserver.import_images import boot_resources
25from provisioningserver.utils.env import environment_variables25from provisioningserver.utils.env import environment_variables
26from provisioningserver.utils.twisted import synchronous26from provisioningserver.utils.twisted import synchronous
27from twisted.internet.defer import inlineCallbacks27from twisted.internet.defer import DeferredLock
28from twisted.internet.threads import deferToThread28from twisted.internet.threads import deferToThread
2929
30# Lock is used so more than one import is not running at the same time.
31import_lock = DeferredLock()
32
3033
31def list_boot_images():34def list_boot_images():
32 """List the boot images that exist on the cluster."""35 """List the boot images that exist on the cluster."""
@@ -47,7 +50,7 @@
47 boot_resources.import_images(sources)50 boot_resources.import_images(sources)
4851
4952
50@inlineCallbacks
51def import_boot_images(sources):53def import_boot_images(sources):
52 """Imports the boot images from the given sources."""54 """Imports the boot images from the given sources."""
53 yield deferToThread(_run_import, sources)55 if not import_lock.locked:
56 return import_lock.run(deferToThread, _run_import, sources)
5457
=== modified file 'src/provisioningserver/rpc/tests/test_boot_images.py'
--- src/provisioningserver/rpc/tests/test_boot_images.py 2014-09-08 20:25:42 +0000
+++ src/provisioningserver/rpc/tests/test_boot_images.py 2014-09-11 13:00:18 +0000
@@ -17,7 +17,10 @@
17import os17import os
1818
19from maastesting.factory import factory19from maastesting.factory import factory
20from maastesting.matchers import MockCalledOnceWith20from maastesting.matchers import (
21 MockCalledOnceWith,
22 MockNotCalled,
23 )
21from maastesting.testcase import MAASTwistedRunTest24from maastesting.testcase import MAASTwistedRunTest
22from mock import sentinel25from mock import sentinel
23from provisioningserver.boot import tftppath26from provisioningserver.boot import tftppath
@@ -27,11 +30,14 @@
27from provisioningserver.rpc.boot_images import (30from provisioningserver.rpc.boot_images import (
28 _run_import,31 _run_import,
29 import_boot_images,32 import_boot_images,
33 import_lock,
30 list_boot_images,34 list_boot_images,
31 )35 )
32from provisioningserver.testing.config import BootSourcesFixture36from provisioningserver.testing.config import BootSourcesFixture
33from provisioningserver.testing.testcase import PservTestCase37from provisioningserver.testing.testcase import PservTestCase
38from provisioningserver.utils.twisted import pause
34from twisted.internet import defer39from twisted.internet import defer
40from twisted.internet.task import Clock
3541
3642
37class TestListBootImages(PservTestCase):43class TestListBootImages(PservTestCase):
@@ -113,6 +119,16 @@
113 run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)119 run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)
114120
115 @defer.inlineCallbacks121 @defer.inlineCallbacks
122 def test__does_not_run_if_lock_taken(self):
123 yield import_lock.acquire()
124 self.addCleanup(import_lock.release)
125 deferToThread = self.patch(boot_images, 'deferToThread')
126 deferToThread.return_value = defer.succeed(None)
127 yield import_boot_images(sentinel.sources)
128 self.assertThat(
129 deferToThread, MockNotCalled())
130
131 @defer.inlineCallbacks
116 def test__calls__run_import_using_deferToThread(self):132 def test__calls__run_import_using_deferToThread(self):
117 deferToThread = self.patch(boot_images, 'deferToThread')133 deferToThread = self.patch(boot_images, 'deferToThread')
118 deferToThread.return_value = defer.succeed(None)134 deferToThread.return_value = defer.succeed(None)
@@ -120,3 +136,16 @@
120 self.assertThat(136 self.assertThat(
121 deferToThread, MockCalledOnceWith(137 deferToThread, MockCalledOnceWith(
122 _run_import, sentinel.sources))138 _run_import, sentinel.sources))
139
140 def test__takes_lock_when_running(self):
141 clock = Clock()
142 deferToThread = self.patch(boot_images, 'deferToThread')
143 deferToThread.return_value = pause(1, clock)
144
145 # Lock is acquired when import is started.
146 import_boot_images(sentinel.sources)
147 self.assertTrue(import_lock.locked)
148
149 # Lock is released once the download is done.
150 clock.advance(1)
151 self.assertFalse(import_lock.locked)