Merge lp:~blake-rouse/maas/fix-1378527-1.7 into lp:maas/1.7
- fix-1378527-1.7
- Merge into 1.7
Proposed by
Blake Rouse
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Raphaël Badin | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 3263 | ||||
Proposed branch: | lp:~blake-rouse/maas/fix-1378527-1.7 | ||||
Merge into: | lp:maas/1.7 | ||||
Diff against target: |
530 lines (+386/-24) 7 files modified
src/maasserver/models/bootresource.py (+21/-12) src/maasserver/models/bootresourceset.py (+1/-1) src/maasserver/models/tests/test_bootresource.py (+28/-0) src/maasserver/models/tests/test_bootresourceset.py (+3/-3) src/maasserver/views/images.py (+139/-3) src/maasserver/views/tests/test_images.py (+191/-0) src/provisioningserver/import_images/download_descriptions.py (+3/-5) |
||||
To merge this branch: | bzr merge lp:~blake-rouse/maas/fix-1378527-1.7 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Christian Reis (community) | Needs Information | ||
Gavin Panella (community) | Abstain | ||
Andres Rodriguez (community) | Approve | ||
Raphaël Badin (community) | Approve | ||
Review via email: mp+238641@code.launchpad.net |
Commit message
Fixes the images page to not show multiple resources for HWE enablement resources. Fixes the incorrect progress of downloading resources.
Description of the change
To post a comment you must log in.
Revision history for this message
Christian Reis (kiko) wrote : | # |
Revision history for this message
Raphaël Badin (rvb) wrote : | # |
Not done with the review yet but here is a first set of comments…
Revision history for this message
Raphaël Badin (rvb) : | # |
review:
Approve
Revision history for this message
Christian Reis (kiko) wrote : | # |
Can someone independently verify trunk before we land this and/or cut the RC for Utopic?
Revision history for this message
Andres Rodriguez (andreserl) wrote : | # |
Tested... no regressions. Seems to fix the issues.
review:
Approve
Revision history for this message
Gavin Panella (allenap) wrote : | # |
I've skimmed over this, so no vote. I don't think I can add anything to what Raphaël has written.
review:
Abstain
Revision history for this message
Christian Reis (kiko) wrote : | # |
How can this have not regressed locally and then died when on the branch?
review:
Needs Information
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/models/bootresource.py' |
2 | --- src/maasserver/models/bootresource.py 2014-10-10 10:24:21 +0000 |
3 | +++ src/maasserver/models/bootresource.py 2014-10-17 01:59:55 +0000 |
4 | @@ -186,19 +186,28 @@ |
5 | else: |
6 | rtypes = [BOOT_RESOURCE_TYPE.UPLOADED] |
7 | name = image['release'] |
8 | - resource = resources.filter( |
9 | + matching_resources = resources.filter( |
10 | rtype__in=rtypes, name=name, |
11 | - architecture__startswith=image['architecture']).first() |
12 | - if resource is None: |
13 | - continue |
14 | - if not resource.supports_subarch(image['subarchitecture']): |
15 | - continue |
16 | - resource_set = resource.get_latest_complete_set() |
17 | - if resource_set is None: |
18 | - continue |
19 | - if resource_set.label != image['label']: |
20 | - continue |
21 | - matched_resources.add(resource) |
22 | + architecture__startswith=image['architecture']) |
23 | + for resource in matching_resources: |
24 | + if resource is None: |
25 | + # This shouldn't happen at all, but just to be sure. |
26 | + continue |
27 | + if not resource.supports_subarch(image['subarchitecture']): |
28 | + # This matching resource doesn't support the images |
29 | + # subarchitecture, so its not a matching resource. |
30 | + continue |
31 | + resource_set = resource.get_latest_complete_set() |
32 | + if resource_set is None: |
33 | + # Possible that the import just started, and there is no |
34 | + # set. Making it not a matching resource, as it cannot |
35 | + # exist on the cluster unless it has a set. |
36 | + continue |
37 | + if resource_set.label != image['label']: |
38 | + # The label is different so the cluster has a different |
39 | + # version of this set. |
40 | + continue |
41 | + matched_resources.add(resource) |
42 | return list(matched_resources) |
43 | |
44 | def boot_images_are_in_sync(self, images): |
45 | |
46 | === modified file 'src/maasserver/models/bootresourceset.py' |
47 | --- src/maasserver/models/bootresourceset.py 2014-08-31 02:47:48 +0000 |
48 | +++ src/maasserver/models/bootresourceset.py 2014-10-17 01:59:55 +0000 |
49 | @@ -130,7 +130,7 @@ |
50 | if size <= 0: |
51 | # Handle division by zero |
52 | return 0 |
53 | - return self.total_size / float(size) |
54 | + return 100.0 * size / float(self.total_size) |
55 | |
56 | @property |
57 | def complete(self): |
58 | |
59 | === modified file 'src/maasserver/models/tests/test_bootresource.py' |
60 | --- src/maasserver/models/tests/test_bootresource.py 2014-10-10 10:59:28 +0000 |
61 | +++ src/maasserver/models/tests/test_bootresource.py 2014-10-17 01:59:55 +0000 |
62 | @@ -363,6 +363,34 @@ |
63 | [resource], |
64 | BootResource.objects.get_resources_matching_boot_images(images)) |
65 | |
66 | + def test__returns_multiple_resource_for_hwe_resources(self): |
67 | + os = factory.make_name('os') |
68 | + series = factory.make_name('series') |
69 | + name = '%s/%s' % (os, series) |
70 | + arch = factory.make_name('arch') |
71 | + subarches = [factory.make_name('hwe') for _ in range(3)] |
72 | + resources = [ |
73 | + factory.make_usable_boot_resource( |
74 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
75 | + name=name, architecture='%s/%s' % (arch, subarch)) |
76 | + for subarch in subarches |
77 | + ] |
78 | + images = [] |
79 | + for resource in resources: |
80 | + label = resource.get_latest_complete_set().label |
81 | + purposes = [factory.make_name('purpose') for _ in range(3)] |
82 | + arch, subarch = resource.split_arch() |
83 | + images.extend([ |
84 | + make_rpc_boot_image( |
85 | + osystem=os, release=series, |
86 | + architecture=arch, subarchitecture=subarch, |
87 | + label=label, purpose=purpose) |
88 | + for purpose in purposes |
89 | + ]) |
90 | + self.assertItemsEqual( |
91 | + resources, |
92 | + BootResource.objects.get_resources_matching_boot_images(images)) |
93 | + |
94 | def test__returns_resource_for_generated_resource(self): |
95 | resource = factory.make_usable_boot_resource( |
96 | rtype=BOOT_RESOURCE_TYPE.GENERATED) |
97 | |
98 | === modified file 'src/maasserver/models/tests/test_bootresourceset.py' |
99 | --- src/maasserver/models/tests/test_bootresourceset.py 2014-09-10 16:20:31 +0000 |
100 | +++ src/maasserver/models/tests/test_bootresourceset.py 2014-10-17 01:59:55 +0000 |
101 | @@ -132,7 +132,7 @@ |
102 | resource_set, largefile, filename=filetype, filetype=filetype) |
103 | self.assertEqual(0, resource_set.progress) |
104 | |
105 | - def test_progress_increases_from_0_to_1(self): |
106 | + def test_progress_increases_from_0_to_100(self): |
107 | resource = factory.make_BootResource() |
108 | resource_set = factory.make_BootResourceSet(resource) |
109 | filetype = BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE |
110 | @@ -149,7 +149,7 @@ |
111 | stream.write(b"a") |
112 | current_size += 1 |
113 | self.assertAlmostEqual( |
114 | - total_size / float(current_size), |
115 | + 100.0 * current_size / float(total_size), |
116 | resource_set.progress) |
117 | |
118 | def test_progress_accumulates_all_files(self): |
119 | @@ -174,7 +174,7 @@ |
120 | content=content, size=total_size) |
121 | factory.make_BootResourceFile( |
122 | resource_set, largefile, filename=filetype, filetype=filetype) |
123 | - progress = final_total_size / float(final_size) |
124 | + progress = 100.0 * final_size / float(final_total_size) |
125 | self.assertAlmostEqual(progress, resource_set.progress) |
126 | |
127 | def test_complete_returns_false_for_no_files(self): |
128 | |
129 | === modified file 'src/maasserver/views/images.py' |
130 | --- src/maasserver/views/images.py 2014-10-02 18:55:37 +0000 |
131 | +++ src/maasserver/views/images.py 2014-10-17 01:59:55 +0000 |
132 | @@ -53,6 +53,7 @@ |
133 | BootSourceCache, |
134 | BootSourceSelection, |
135 | Config, |
136 | + LargeFile, |
137 | Node, |
138 | ) |
139 | from maasserver.views import HelpfulDeleteView |
140 | @@ -460,14 +461,149 @@ |
141 | self.add_resource_template_attributes(resource) |
142 | return resources |
143 | |
144 | + def is_hwe_resource(self, resource): |
145 | + """Return True if the resource is an Ubuntu HWE resource.""" |
146 | + if resource.rtype != BOOT_RESOURCE_TYPE.SYNCED: |
147 | + return False |
148 | + if not resource.name.startswith('ubuntu/'): |
149 | + return False |
150 | + arch, subarch = resource.split_arch() |
151 | + return subarch.startswith('hwe-') |
152 | + |
153 | + def pick_latest_datetime(self, time, other_time): |
154 | + """Return the datetime that is the latest.""" |
155 | + if time is None: |
156 | + return other_time |
157 | + return max([time, other_time]) |
158 | + |
159 | + def calculate_unique_size_for_resources(self, resources): |
160 | + """Return size of all unique largefiles for the given resources.""" |
161 | + shas = set() |
162 | + size = 0 |
163 | + for resource in resources: |
164 | + resource_set = resource.get_latest_set() |
165 | + if resource_set is None: |
166 | + continue |
167 | + for rfile in resource_set.files.all(): |
168 | + try: |
169 | + largefile = rfile.largefile |
170 | + except LargeFile.DoesNotExist: |
171 | + continue |
172 | + if largefile.sha256 not in shas: |
173 | + size += largefile.total_size |
174 | + shas.add(largefile.sha256) |
175 | + return size |
176 | + |
177 | + def are_all_resources_complete(self, resources): |
178 | + """Return the complete status for all the given resources.""" |
179 | + for resource in resources: |
180 | + resource_set = resource.get_latest_set() |
181 | + if resource_set is None: |
182 | + return False |
183 | + if not resource_set.complete: |
184 | + return False |
185 | + return True |
186 | + |
187 | + def get_last_update_for_resources(self, resources): |
188 | + """Return the latest updated time for all resources.""" |
189 | + last_update = None |
190 | + for resource in resources: |
191 | + last_update = self.pick_latest_datetime( |
192 | + last_update, resource.updated) |
193 | + resource_set = resource.get_latest_set() |
194 | + if resource_set is not None: |
195 | + last_update = self.pick_latest_datetime( |
196 | + last_update, resource_set.updated) |
197 | + return last_update |
198 | + |
199 | + def get_number_of_nodes_for_resources(self, resources): |
200 | + """Return the number of nodes used by all resources.""" |
201 | + return sum([ |
202 | + self.get_number_of_nodes_deployed_for(resource) |
203 | + for resource in resources]) |
204 | + |
205 | + def get_progress_for_resources(self, resources): |
206 | + """Return the overall progress for all resources.""" |
207 | + size = 0 |
208 | + total_size = 0 |
209 | + for resource in resources: |
210 | + resource_set = resource.get_latest_set() |
211 | + if resource_set is not None: |
212 | + size += resource_set.size |
213 | + total_size += resource_set.total_size |
214 | + if size <= 0: |
215 | + # Handle division by zero |
216 | + return 0 |
217 | + return 100.0 * (size / float(total_size)) |
218 | + |
219 | + def hwes_to_resource(self, hwes): |
220 | + """Convert the list of hwes into one resource to be used in the UI.""" |
221 | + # Calculate all of the values using all of the hwe resources for |
222 | + # this combination of resources. |
223 | + last_update = self.get_last_update_for_resources(hwes) |
224 | + unique_size = self.calculate_unique_size_for_resources(hwes) |
225 | + number_of_nodes = self.get_number_of_nodes_for_resources(hwes) |
226 | + complete = self.are_all_resources_complete(hwes) |
227 | + progress = self.get_progress_for_resources(hwes) |
228 | + |
229 | + # Set the computed attributes on the first resource as that will |
230 | + # be the only one returned to the UI. |
231 | + resource = hwes[0] |
232 | + resource.arch, resource.subarch = resource.split_arch() |
233 | + resource.title = self.get_resource_title(resource) |
234 | + resource.complete = complete |
235 | + resource.size = format_size(unique_size) |
236 | + resource.last_update = last_update |
237 | + resource.number_of_nodes = number_of_nodes |
238 | + resource.complete = complete |
239 | + if not complete: |
240 | + if progress > 0: |
241 | + resource.status = "Downloading %3.0f%%" % progress |
242 | + resource.downloading = True |
243 | + else: |
244 | + resource.status = "Queued for download" |
245 | + resource.downloading = False |
246 | + else: |
247 | + # See if all the hwe resources exist on all the clusters. |
248 | + cluster_has_hwes = any( |
249 | + hwe in hwes for hwe in self.cluster_resources) |
250 | + if cluster_has_hwes: |
251 | + resource.status = "Complete" |
252 | + resource.downloading = False |
253 | + else: |
254 | + resource.complete = False |
255 | + if self.clusters_syncing: |
256 | + resource.status = "Syncing to clusters" |
257 | + resource.downloading = True |
258 | + else: |
259 | + resource.status = "Waiting for clusters to sync" |
260 | + resource.downloading = False |
261 | + return resource |
262 | + |
263 | + def combine_hwe_resources(self, resources): |
264 | + """Return a list of resources removing the duplicate hwe resources.""" |
265 | + none_hwe_resources = [] |
266 | + hwe_resources = defaultdict(list) |
267 | + for resource in resources: |
268 | + if not self.is_hwe_resource(resource): |
269 | + self.add_resource_template_attributes(resource) |
270 | + none_hwe_resources.append(resource) |
271 | + else: |
272 | + arch = resource.split_arch()[0] |
273 | + key = '%s/%s' % (resource.name, arch) |
274 | + hwe_resources[key].append(resource) |
275 | + combined_hwes = [ |
276 | + self.hwes_to_resource(hwes) |
277 | + for _, hwes in hwe_resources.items() |
278 | + ] |
279 | + return none_hwe_resources + combined_hwes |
280 | + |
281 | def ajax(self, request, *args, **kwargs): |
282 | """Return all resources in a json object. |
283 | |
284 | This is used by the image model list on the client side to update |
285 | the status of images.""" |
286 | - resources = list(BootResource.objects.all()) |
287 | - for resource in resources: |
288 | - self.add_resource_template_attributes(resource) |
289 | + resources = self.combine_hwe_resources(BootResource.objects.all()) |
290 | json_resources = [ |
291 | dict( |
292 | id=resource.id, |
293 | |
294 | === modified file 'src/maasserver/views/tests/test_images.py' |
295 | --- src/maasserver/views/tests/test_images.py 2014-10-02 18:55:37 +0000 |
296 | +++ src/maasserver/views/tests/test_images.py 2014-10-17 01:59:55 +0000 |
297 | @@ -25,6 +25,7 @@ |
298 | NODE_STATUS, |
299 | ) |
300 | from maasserver.models import ( |
301 | + BootResource, |
302 | BootSourceCache, |
303 | BootSourceSelection, |
304 | Config, |
305 | @@ -37,6 +38,7 @@ |
306 | ) |
307 | from maasserver.testing.testcase import MAASServerTestCase |
308 | from maasserver.views import images as images_view |
309 | +from maasserver.views.images import format_size |
310 | from maastesting.matchers import ( |
311 | MockCalledOnceWith, |
312 | MockCalledWith, |
313 | @@ -688,6 +690,195 @@ |
314 | json_resource = json_obj['resources'][0] |
315 | self.assertEqual(number_of_nodes, json_resource['numberOfNodes']) |
316 | |
317 | + def test_combines_hwe_resources_into_one_resource(self): |
318 | + self.client_log_in() |
319 | + name = 'ubuntu/%s' % factory.make_name('series') |
320 | + arch = factory.make_name('arch') |
321 | + subarches = [factory.make_name('hwe') for _ in range(3)] |
322 | + for subarch in subarches: |
323 | + factory.make_usable_boot_resource( |
324 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
325 | + name=name, architecture='%s/%s' % (arch, subarch)) |
326 | + response = self.get_images_ajax() |
327 | + json_obj = json.loads(response.content) |
328 | + self.assertEqual( |
329 | + 1, len(json_obj['resources']), |
330 | + 'More than one resource was returned.') |
331 | + |
332 | + def test_combined_hwe_resource_calculates_unique_size(self): |
333 | + self.client_log_in() |
334 | + name = 'ubuntu/%s' % factory.make_name('series') |
335 | + arch = factory.make_name('arch') |
336 | + subarches = [factory.make_name('hwe') for _ in range(3)] |
337 | + largefile = factory.make_LargeFile() |
338 | + for subarch in subarches: |
339 | + resource = factory.make_BootResource( |
340 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
341 | + name=name, architecture='%s/%s' % (arch, subarch)) |
342 | + resource_set = factory.make_BootResourceSet(resource) |
343 | + factory.make_BootResourceFile(resource_set, largefile) |
344 | + response = self.get_images_ajax() |
345 | + json_obj = json.loads(response.content) |
346 | + json_resource = json_obj['resources'][0] |
347 | + self.assertEqual( |
348 | + format_size(largefile.total_size), json_resource['size']) |
349 | + |
350 | + def test_combined_hwe_resource_calculates_number_of_nodes_deployed(self): |
351 | + self.client_log_in() |
352 | + osystem = 'ubuntu' |
353 | + series = factory.make_name('series') |
354 | + name = '%s/%s' % (osystem, series) |
355 | + arch = factory.make_name('arch') |
356 | + subarches = [factory.make_name('hwe') for _ in range(3)] |
357 | + for subarch in subarches: |
358 | + factory.make_usable_boot_resource( |
359 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
360 | + name=name, architecture='%s/%s' % (arch, subarch)) |
361 | + |
362 | + number_of_nodes = random.randint(1, 4) |
363 | + for _ in range(number_of_nodes): |
364 | + subarch = random.choice(subarches) |
365 | + node_architecture = '%s/%s' % (arch, subarch) |
366 | + factory.make_Node( |
367 | + status=NODE_STATUS.DEPLOYED, |
368 | + osystem=osystem, distro_series=series, |
369 | + architecture=node_architecture) |
370 | + |
371 | + response = self.get_images_ajax() |
372 | + json_obj = json.loads(response.content) |
373 | + json_resource = json_obj['resources'][0] |
374 | + self.assertEqual(number_of_nodes, json_resource['numberOfNodes']) |
375 | + |
376 | + def test_combined_hwe_resource_calculates_complete_True(self): |
377 | + self.client_log_in() |
378 | + name = 'ubuntu/%s' % factory.make_name('series') |
379 | + arch = factory.make_name('arch') |
380 | + subarches = [factory.make_name('hwe') for _ in range(3)] |
381 | + resources = [ |
382 | + factory.make_usable_boot_resource( |
383 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
384 | + name=name, architecture='%s/%s' % (arch, subarch)) |
385 | + for subarch in subarches |
386 | + ] |
387 | + self.patch( |
388 | + BootResource.objects, |
389 | + 'get_resources_matching_boot_images').return_value = resources |
390 | + response = self.get_images_ajax() |
391 | + json_obj = json.loads(response.content) |
392 | + json_resource = json_obj['resources'][0] |
393 | + self.assertTrue(json_resource['complete']) |
394 | + |
395 | + def test_combined_hwe_resource_calculates_complete_False(self): |
396 | + self.client_log_in() |
397 | + name = 'ubuntu/%s' % factory.make_name('series') |
398 | + arch = factory.make_name('arch') |
399 | + subarches = [factory.make_name('hwe') for _ in range(3)] |
400 | + incomplete_subarch = subarches.pop() |
401 | + factory.make_BootResource( |
402 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
403 | + name=name, architecture='%s/%s' % (arch, incomplete_subarch)) |
404 | + for subarch in subarches: |
405 | + factory.make_usable_boot_resource( |
406 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
407 | + name=name, architecture='%s/%s' % (arch, subarch)) |
408 | + response = self.get_images_ajax() |
409 | + json_obj = json.loads(response.content) |
410 | + json_resource = json_obj['resources'][0] |
411 | + self.assertFalse(json_resource['complete']) |
412 | + |
413 | + def test_combined_hwe_resource_calculates_progress(self): |
414 | + self.client_log_in() |
415 | + name = 'ubuntu/%s' % factory.make_name('series') |
416 | + arch = factory.make_name('arch') |
417 | + subarches = [factory.make_name('hwe') for _ in range(3)] |
418 | + largefile = factory.make_LargeFile() |
419 | + largefile.total_size = largefile.total_size * 2 |
420 | + largefile.save() |
421 | + for subarch in subarches: |
422 | + resource = factory.make_BootResource( |
423 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
424 | + name=name, architecture='%s/%s' % (arch, subarch)) |
425 | + resource_set = factory.make_BootResourceSet(resource) |
426 | + factory.make_BootResourceFile(resource_set, largefile) |
427 | + response = self.get_images_ajax() |
428 | + json_obj = json.loads(response.content) |
429 | + json_resource = json_obj['resources'][0] |
430 | + self.assertEqual("Downloading 50%", json_resource['status']) |
431 | + |
432 | + def test_combined_hwe_resource_shows_queued_if_no_progress(self): |
433 | + self.client_log_in() |
434 | + name = 'ubuntu/%s' % factory.make_name('series') |
435 | + arch = factory.make_name('arch') |
436 | + subarches = [factory.make_name('hwe') for _ in range(3)] |
437 | + largefile = factory.make_LargeFile(content="") |
438 | + for subarch in subarches: |
439 | + resource = factory.make_BootResource( |
440 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
441 | + name=name, architecture='%s/%s' % (arch, subarch)) |
442 | + resource_set = factory.make_BootResourceSet(resource) |
443 | + factory.make_BootResourceFile(resource_set, largefile) |
444 | + response = self.get_images_ajax() |
445 | + json_obj = json.loads(response.content) |
446 | + json_resource = json_obj['resources'][0] |
447 | + self.assertEqual("Queued for download", json_resource['status']) |
448 | + |
449 | + def test_combined_hwe_resource_shows_complete_status(self): |
450 | + self.client_log_in() |
451 | + name = 'ubuntu/%s' % factory.make_name('series') |
452 | + arch = factory.make_name('arch') |
453 | + subarches = [factory.make_name('hwe') for _ in range(3)] |
454 | + resources = [ |
455 | + factory.make_usable_boot_resource( |
456 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
457 | + name=name, architecture='%s/%s' % (arch, subarch)) |
458 | + for subarch in subarches |
459 | + ] |
460 | + self.patch( |
461 | + BootResource.objects, |
462 | + 'get_resources_matching_boot_images').return_value = resources |
463 | + response = self.get_images_ajax() |
464 | + json_obj = json.loads(response.content) |
465 | + json_resource = json_obj['resources'][0] |
466 | + self.assertEqual("Complete", json_resource['status']) |
467 | + |
468 | + def test_combined_hwe_resource_shows_waiting_for_cluster_to_sync(self): |
469 | + self.client_log_in() |
470 | + name = 'ubuntu/%s' % factory.make_name('series') |
471 | + arch = factory.make_name('arch') |
472 | + subarches = [factory.make_name('hwe') for _ in range(3)] |
473 | + for subarch in subarches: |
474 | + factory.make_usable_boot_resource( |
475 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
476 | + name=name, architecture='%s/%s' % (arch, subarch)) |
477 | + self.patch( |
478 | + BootResource.objects, |
479 | + 'get_resources_matching_boot_images').return_value = [] |
480 | + response = self.get_images_ajax() |
481 | + json_obj = json.loads(response.content) |
482 | + json_resource = json_obj['resources'][0] |
483 | + self.assertEqual( |
484 | + "Waiting for clusters to sync", json_resource['status']) |
485 | + |
486 | + def test_combined_hwe_resource_shows_clusters_syncing(self): |
487 | + self.client_log_in() |
488 | + name = 'ubuntu/%s' % factory.make_name('series') |
489 | + arch = factory.make_name('arch') |
490 | + subarches = [factory.make_name('hwe') for _ in range(3)] |
491 | + for subarch in subarches: |
492 | + factory.make_usable_boot_resource( |
493 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
494 | + name=name, architecture='%s/%s' % (arch, subarch)) |
495 | + self.patch( |
496 | + BootResource.objects, |
497 | + 'get_resources_matching_boot_images').return_value = [] |
498 | + self.patch( |
499 | + images_view, 'is_import_boot_images_running').return_value = True |
500 | + response = self.get_images_ajax() |
501 | + json_obj = json.loads(response.content) |
502 | + json_resource = json_obj['resources'][0] |
503 | + self.assertEqual( |
504 | + "Syncing to clusters", json_resource['status']) |
505 | + |
506 | |
507 | class TestImageDelete(MAASServerTestCase): |
508 | |
509 | |
510 | === modified file 'src/provisioningserver/import_images/download_descriptions.py' |
511 | --- src/provisioningserver/import_images/download_descriptions.py 2014-09-30 20:54:32 +0000 |
512 | +++ src/provisioningserver/import_images/download_descriptions.py 2014-10-17 01:59:55 +0000 |
513 | @@ -78,14 +78,12 @@ |
514 | """Overridable from `BasicMirrorWriter`.""" |
515 | item = products_exdata(src, pedigree) |
516 | os = get_os_from_product(item) |
517 | - arch, subarches = item['arch'], item['subarches'] |
518 | + arch, subarch = item['arch'], item['subarch'] |
519 | release = item['release'] |
520 | label = item['label'] |
521 | - base_image = ImageSpec(os, arch, None, release, label) |
522 | + base_image = ImageSpec(os, arch, subarch, release, label) |
523 | compact_item = clean_up_repo_item(item) |
524 | - for subarch in subarches.split(','): |
525 | - self.boot_images_dict.setdefault( |
526 | - base_image._replace(subarch=subarch), compact_item) |
527 | + self.boot_images_dict.setdefault(base_image, compact_item) |
528 | |
529 | |
530 | def value_passes_filter_list(filter_list, property_value): |
Ugh, this is a big change.