Merge lp:~blake-rouse/maas/fix-1387133-1.7 into lp:maas/1.7

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 3293
Proposed branch: lp:~blake-rouse/maas/fix-1387133-1.7
Merge into: lp:maas/1.7
Diff against target: 307 lines (+115/-25)
5 files modified
src/maas/__init__.py (+20/-0)
src/maas/demo.py (+4/-0)
src/maas/settings.py (+7/-1)
src/maasserver/bootresources.py (+47/-12)
src/maasserver/tests/test_bootresources.py (+37/-12)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-1387133-1.7
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+240154@code.launchpad.net

This proposal supersedes a proposal from 2014-10-30.

Commit message

Handle wierd race that will delete a synced boot images. Add more logging to the importing of images on the region. Enable simplestreams to log to maas-django.log, without modifing the maas_local_settings.py file.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote : Posted in a previous version of this proposal

Self-approve.

Already approved for 1.7 landing here: https://code.launchpad.net/~blake-rouse/maas/fix-1387133/+merge/240132

review: Approve
Revision history for this message
Blake Rouse (blake-rouse) :
review: Approve
Revision history for this message
Andres Rodriguez (andreserl) wrote : Posted in a previous version of this proposal

This is a huge diff and I see merge conflcist. Can you check that this is
the correct branch?
On Oct 30, 2014 2:50 PM, "Blake Rouse" <email address hidden> wrote:

> Review: Approve
>
> Self-approve.
>
> Already approved for 1.7 landing here:
> https://code.launchpad.net/~blake-rouse/maas/fix-1387133/+merge/240132
> --
> https://code.launchpad.net/~blake-rouse/maas/fix-1387133-1.7/+merge/240153
> You are subscribed to branch lp:maas.
>

Revision history for this message
Blake Rouse (blake-rouse) wrote : Posted in a previous version of this proposal

Yeah it was incorrect, I fixed the MP.

On Thu, Oct 30, 2014 at 3:42 PM, Andres Rodriguez <email address hidden>
wrote:

> This is a huge diff and I see merge conflcist. Can you check that this is
> the correct branch?
> On Oct 30, 2014 2:50 PM, "Blake Rouse" <email address hidden> wrote:
>
>> Review: Approve
>>
>> Self-approve.
>>
>> Already approved for 1.7 landing here:
>> https://code.launchpad.net/~blake-rouse/maas/fix-1387133/+merge/240132
>> --
>> https://code.launchpad.net/~blake-rouse/maas/fix-1387133-1.7/+merge/240153
>> You are subscribed to branch lp:maas.
>>
>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maas/__init__.py'
2--- src/maas/__init__.py 2014-08-13 21:49:35 +0000
3+++ src/maas/__init__.py 2014-10-30 18:49:24 +0000
4@@ -15,6 +15,7 @@
5 __all__ = [
6 "import_settings",
7 "import_local_settings",
8+ "log_sstreams",
9 ]
10
11 import sys
12@@ -59,6 +60,25 @@
13 target.update(source)
14
15
16+def log_sstreams(LOGGING):
17+ """Turn on simplestreams logging.
18+
19+ Copies the exact logging configuration for maasserver into sstreams,
20+ unless a sstreams config already exists.
21+ """
22+ if 'loggers' not in LOGGING:
23+ # No loggers in LOGGING, can't do anthing.
24+ return
25+ if 'sstreams' in LOGGING['loggers']:
26+ # Already have a simplestreams config for logging.
27+ return
28+ if 'maasserver' not in LOGGING['loggers']:
29+ # No maasserver logger present to copy, no way of know what it
30+ # should be set to.
31+ return
32+ LOGGING['loggers']['sstreams'] = LOGGING['loggers']['maasserver']
33+
34+
35 try:
36 import maasfascist
37 maasfascist # Silence lint.
38
39=== modified file 'src/maas/demo.py'
40--- src/maas/demo.py 2014-09-24 14:54:56 +0000
41+++ src/maas/demo.py 2014-10-30 18:49:24 +0000
42@@ -73,6 +73,10 @@
43 'twisted': {
44 'handlers': ['console'],
45 'propagate': True,
46+ },
47+ 'sstreams': {
48+ 'handlers': ['console'],
49+ 'propagate': True,
50 }
51 },
52 }
53
54=== modified file 'src/maas/settings.py'
55--- src/maas/settings.py 2014-10-15 20:39:07 +0000
56+++ src/maas/settings.py 2014-10-30 18:49:24 +0000
57@@ -19,7 +19,10 @@
58 # Use new style url tag:
59 # https://docs.djangoproject.com/en/dev/releases/1.3/#changes-to-url-and-ssi
60 import django.template
61-from maas import import_local_settings
62+from maas import (
63+ import_local_settings,
64+ log_sstreams,
65+ )
66 from maas.monkey import patch_get_script_prefix
67 from metadataserver.address import guess_server_host
68 from provisioningserver.utils import compose_URL
69@@ -318,3 +321,6 @@
70
71 # Patch the get_script_prefix method to allow twisted to work with django.
72 patch_get_script_prefix()
73+
74+# Enable the logging of simplestreams
75+log_sstreams(LOGGING)
76
77=== modified file 'src/maasserver/bootresources.py'
78--- src/maasserver/bootresources.py 2014-10-28 14:17:28 +0000
79+++ src/maasserver/bootresources.py 2014-10-30 18:49:24 +0000
80@@ -395,6 +395,11 @@
81 rtype=BOOT_RESOURCE_TYPE.SYNCED)
82 }
83
84+ # XXX blake_r 2014-10-30 bug=1387133: We store a copy of the resources
85+ # to delete, so we can check if all the same resources will be delete
86+ # in the finalize call.
87+ self._init_resources_to_delete = set(self._resources_to_delete)
88+
89 def prevent_resource_deletion(self, resource):
90 """Remove the `BootResource` from the resources to delete list.
91
92@@ -605,7 +610,7 @@
93 else:
94 ident = self.get_resource_file_log_identifier(
95 rfile, resource_set, resource)
96- maaslog.debug('Boot resource already up-to-date %s.' % ident)
97+ maaslog.debug('Boot image already up-to-date %s.', ident)
98
99 def write_content(self, rfile, reader):
100 """Writes the data from the given reader, into the object storage
101@@ -613,7 +618,7 @@
102 ident = self.get_resource_file_log_identifier(rfile)
103 cksummer = sutil.checksummer(
104 {'sha256': rfile.largefile.sha256})
105- maaslog.debug("Syncing boot resource %s." % ident)
106+ maaslog.debug("Finalizing boot image %s.", ident)
107
108 # Write the contents into the database, while calculating the sha256
109 # hash for the read data.
110@@ -630,14 +635,13 @@
111 # simplestreams is telling us it should be. This resource file
112 # will be deleted since it is corrupt.
113 maaslog.error(
114- "Failed to sync boot resource %s. Unexpected "
115- "checksum '%s' (found: %s expected: %s)" % (
116- ident, cksummer.algorithm,
117- cksummer.hexdigest(), cksummer.expected,
118- ))
119+ "Failed to finalize boot image %s. Unexpected "
120+ "checksum '%s' (found: %s expected: %s)",
121+ ident, cksummer.algorithm,
122+ cksummer.hexdigest(), cksummer.expected)
123 rfile.delete()
124 else:
125- maaslog.debug('Finished syncing boot resource %s.' % ident)
126+ maaslog.debug('Finalized boot image %s.', ident)
127
128 @transactional
129 def write_content_thread(self, rid, reader):
130@@ -689,6 +693,9 @@
131 rtype=BOOT_RESOURCE_TYPE.SYNCED,
132 name=name, architecture=architecture))
133 if delete_resource is not None:
134+ maaslog.debug(
135+ "Deleting boot image %s.",
136+ self.get_resource_identity(delete_resource))
137 delete_resource.delete()
138
139 @transactional
140@@ -727,6 +734,25 @@
141 This will remove the un-needed `BootResource`'s and write the
142 file data into the large object store.
143 """
144+ # XXX blake_r 2014-10-30 bug=1387133: A scenario can occur where insert
145+ # never gets called by the writer, causing this method to delete all
146+ # of the synced resources. The actual cause of this issue is unknown,
147+ # but we want to handle the case or all the images will be deleted and
148+ # no nodes will be able to be provisioned.
149+ maaslog.debug(
150+ "Finalize will delete %d images(s).",
151+ len(self._resources_to_delete))
152+ maaslog.debug(
153+ "Finalize will save %d new images(s).",
154+ len(self._content_to_finalize))
155+ if (self._resources_to_delete == self._init_resources_to_delete and
156+ len(self._content_to_finalize) == 0):
157+ maaslog.error(
158+ "Finalization of imported images skipped, "
159+ "or all %s synced images would be deleted.",
160+ self._resources_to_delete)
161+ close_old_connections()
162+ return
163 self.resource_cleaner()
164 self.perform_write()
165 self.resource_set_cleaner()
166@@ -799,13 +825,16 @@
167 :param product_mapping: A `ProductMapping` describing the resources to be
168 downloaded.
169 """
170+ maaslog.debug("Initializing BootResourceStore.")
171 if store is None:
172 store = BootResourceStore()
173 assert isinstance(store, BootResourceStore)
174 for source in sources:
175+ maaslog.info("Importing images from source: %s", source['url'])
176 download_boot_resources(
177 source['url'], store, product_mapping,
178 keyring_file=source.get('keyring'))
179+ maaslog.debug("Finalizing BootResourceStore.")
180 store.finalize()
181
182
183@@ -851,6 +880,7 @@
184 """
185 # If the lock is already held, then import is already running.
186 if locks.import_images.is_locked():
187+ maaslog.debug("Skipping import as another import is already running.")
188 return
189
190 # If we're not being forced, don't sync unless we've already done it once
191@@ -878,7 +908,8 @@
192 # Timeout occurred, kill the thread and exit.
193 kill_event.set()
194 lock_thread.join()
195- maaslog.error("Another import task is already running.")
196+ maaslog.debug(
197+ "Unable to grab import lock, another import is already running.")
198 return
199
200 try:
201@@ -890,20 +921,24 @@
202 env['http_proxy'] = http_proxy
203 env['https_proxy'] = http_proxy
204 with environment_variables(env), tempdir('keyrings') as keyrings_path:
205- maaslog.info("Started importing of boot resources.")
206 sources = get_boot_sources()
207 sources = write_all_keyrings(keyrings_path, sources)
208+ maaslog.info(
209+ "Started importing of boot images from %d source(s).",
210+ len(sources))
211
212 image_descriptions = download_all_image_descriptions(sources)
213 if image_descriptions.is_empty():
214 maaslog.warn(
215- "Unable to import boot resources, no image "
216+ "Unable to import boot images, no image "
217 "descriptions avaliable.")
218 return
219 product_mapping = map_products(image_descriptions)
220
221 download_all_boot_resources(sources, product_mapping)
222- maaslog.info("Finished importing of boot resources.")
223+ maaslog.info(
224+ "Finished importing of boot images from %d source(s).",
225+ len(sources))
226
227 # Tell the clusters to download the data from the region.
228 NodeGroup.objects.import_boot_images_on_accepted_clusters()
229
230=== modified file 'src/maasserver/tests/test_bootresources.py'
231--- src/maasserver/tests/test_bootresources.py 2014-10-28 14:17:28 +0000
232+++ src/maasserver/tests/test_bootresources.py 2014-10-30 18:49:24 +0000
233@@ -757,15 +757,40 @@
234 store.write_content(rfile, reader)
235 self.assertFalse(BootResourceFile.objects.filter(id=rfile.id).exists())
236
237- def test_finalize_calls_methods(self):
238- store = BootResourceStore()
239- mock_resource_cleaner = self.patch(store, 'resource_cleaner')
240- mock_perform_write = self.patch(store, 'perform_write')
241- mock_resource_set_cleaner = self.patch(store, 'resource_set_cleaner')
242- store.finalize()
243- self.assertTrue(mock_resource_cleaner, MockCalledOnceWith())
244- self.assertTrue(mock_perform_write, MockCalledOnceWith())
245- self.assertTrue(mock_resource_set_cleaner, MockCalledOnceWith())
246+ def test_finalize_does_nothing_if_resources_to_delete_hasnt_changed(self):
247+ factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.SYNCED)
248+ store = BootResourceStore()
249+ mock_resource_cleaner = self.patch(store, 'resource_cleaner')
250+ mock_perform_write = self.patch(store, 'perform_write')
251+ mock_resource_set_cleaner = self.patch(store, 'resource_set_cleaner')
252+ store.finalize()
253+ self.expectThat(mock_resource_cleaner, MockNotCalled())
254+ self.expectThat(mock_perform_write, MockNotCalled())
255+ self.expectThat(mock_resource_set_cleaner, MockNotCalled())
256+
257+ def test_finalize_calls_methods_if_new_resources_need_to_be_saved(self):
258+ factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.SYNCED)
259+ store = BootResourceStore()
260+ store._content_to_finalize = [sentinel.content]
261+ mock_resource_cleaner = self.patch(store, 'resource_cleaner')
262+ mock_perform_write = self.patch(store, 'perform_write')
263+ mock_resource_set_cleaner = self.patch(store, 'resource_set_cleaner')
264+ store.finalize()
265+ self.expectThat(mock_resource_cleaner, MockCalledOnceWith())
266+ self.expectThat(mock_perform_write, MockCalledOnceWith())
267+ self.expectThat(mock_resource_set_cleaner, MockCalledOnceWith())
268+
269+ def test_finalize_calls_methods_if_resources_to_delete_has_changed(self):
270+ factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.SYNCED)
271+ store = BootResourceStore()
272+ store._resources_to_delete = set()
273+ mock_resource_cleaner = self.patch(store, 'resource_cleaner')
274+ mock_perform_write = self.patch(store, 'perform_write')
275+ mock_resource_set_cleaner = self.patch(store, 'resource_set_cleaner')
276+ store.finalize()
277+ self.expectThat(mock_resource_cleaner, MockCalledOnceWith())
278+ self.expectThat(mock_perform_write, MockCalledOnceWith())
279+ self.expectThat(mock_resource_set_cleaner, MockCalledOnceWith())
280
281
282 class TestBootResourceTransactional(TransactionTestCase):
283@@ -1036,7 +1061,7 @@
284 def test__import_resources_calls_functions_with_correct_parameters(self):
285 fake_write_all_keyrings = self.patch(
286 bootresources, 'write_all_keyrings')
287- fake_write_all_keyrings.return_value = sentinel.sources
288+ fake_write_all_keyrings.return_value = [sentinel.source]
289 fake_image_descriptions = self.patch(
290 bootresources, 'download_all_image_descriptions')
291 descriptions = Mock()
292@@ -1055,13 +1080,13 @@
293 MockCalledOnceWith(ANY, []))
294 self.assertThat(
295 fake_image_descriptions,
296- MockCalledOnceWith(sentinel.sources))
297+ MockCalledOnceWith([sentinel.source]))
298 self.assertThat(
299 fake_map_products,
300 MockCalledOnceWith(descriptions))
301 self.assertThat(
302 fake_download_all_boot_resources,
303- MockCalledOnceWith(sentinel.sources, sentinel.mapping))
304+ MockCalledOnceWith([sentinel.source], sentinel.mapping))
305
306 def test__import_resources_has_env_GNUPGHOME_set(self):
307 fake_image_descriptions = self.patch(

Subscribers

People subscribed via source and target branches

to all changes: