Merge ~thedac/simplestreams:bug/1790904 into simplestreams:master
- Git
- lp:~thedac/simplestreams
- bug/1790904
- Merge into master
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | efb19f32e9f7c4b435a4119047947d5aad73b2c5 | ||||
Proposed branch: | ~thedac/simplestreams:bug/1790904 | ||||
Merge into: | simplestreams:master | ||||
Diff against target: |
436 lines (+257/-22) 3 files modified
simplestreams/mirrors/glance.py (+75/-13) simplestreams/openstack.py (+3/-0) tests/unittests/test_glancemirror.py (+179/-9) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Needs Fixing | |
Scott Moser (community) | Approve | ||
Ryan Beisner (community) | Needs Resubmitting | ||
Review via email: mp+354369@code.launchpad.net |
Commit message
glance: Support Glance version 2.
OpenStack at Rocky has removed glance v1 entirely. V2 has been around
for some time.
This change updates simplestreams to use glance v2 when "non-legacy"
versions of the clients are used.
LP: #1790904
Description of the change
Use Glance v2
OpenStack at Rocky has removed glance v1 entirely. V2 has been around
for some time.
This change updates simplestreams to use glance v2 when "non-legacy"
versions of the clients are used.
I am open to other solutions, this change represents what needs to
be updated for v2.
Server Team CI bot (server-team-bot) wrote : | # |
Scott Moser (smoser) wrote : | # |
it seems some of your changes are probably better suited to 'prepare_
Returns a dict to use as keyword arguments passed directly to GlanceClient.
other comments inline.
obviously you have to make the c-i bot happy too. some test changes needed, and we'd want some tests on the new path.
Scott Moser (smoser) : | # |
Ryan Beisner (1chb1n) wrote : | # |
This MP is more an illustration as to what we think would need to change. Can we have #ubuntu-server run with the bug?
David Ames (thedac) wrote : | # |
Addressing the comments.
Specifically moving what can be moved to 'prepare_
And using self.glance_
I'll have an update as soon as I get unit tests working properly.
Scott Moser (smoser) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:9e963623373
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
@thedac,
please comment on the size/hash. Doesnt that seem just wrong to you?
The only way to test if something went wrong in the upload is to launch it?
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:53fe151e849
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
David Ames (thedac) wrote : | # |
> @thedac,
> please comment on the size/hash. Doesnt that seem just wrong to you?
> The only way to test if something went wrong in the upload is to launch it?
@smoser
I have scoured the glance v2 api documentation [0]. The API has changed such
that we cannot send the expected checksum during the creation/upload process.
For instance the glanceclient.
None of the create, upload or update methods take a checksum.
What we can do is validate the checksum *after* we upload it. Let me know if
something like [1] will work for you or please suggest/implement a validation.
[0] https:/
[1] https:/
Scott Moser (smoser) wrote : | # |
David,
If that works, then I guess it is generally ok.
More than just "silently" deleting the image, we should probably raise an error of some kind. I guess IOError would make sense.
I think its worth checking size also and updating the message to provide expected and found size and checksum. I realize its probably very unlikely that size matches but checksum does not.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:f9cd45f43bb
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
David,
Several cleanups at
https:/
Can you grab that ?
then i think we're pretty good and i'm willing to pull this and put it into dingo.
David Ames (thedac) wrote : | # |
Scott,
I have merged your changes and pushed up to my branch. However, strangely LP does not seem to notice.
When you grab my branch from LP it has your commit in it.
git clone -b bug/1790904 https:/
Not sure what we need to do to kick LP.
Scott Moser (smoser) : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:0c1fff74b3f
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Autolanding.
Unapproved changes made after approval.
https:/
Executed test runs:
None: https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:f9cd45f43bb
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Autolanding.
Unapproved changes made after approval.
https:/
Executed test runs:
None: https:/
Scott Moser (smoser) wrote : | # |
not sure why launchpad/the autolander doesnt like this branch.
so i put the same content at
https:/
Preview Diff
1 | diff --git a/simplestreams/mirrors/glance.py b/simplestreams/mirrors/glance.py |
2 | index 0e694f2..297db27 100644 |
3 | --- a/simplestreams/mirrors/glance.py |
4 | +++ b/simplestreams/mirrors/glance.py |
5 | @@ -151,7 +151,9 @@ class GlanceMirror(mirrors.BasicMirrorWriter): |
6 | |
7 | conn_info = client.get_service_conn_info( |
8 | 'image', **self.keystone_creds) |
9 | - self.gclient = get_glanceclient(**conn_info) |
10 | + self.glance_api_version = conn_info['glance_version'] |
11 | + self.gclient = get_glanceclient(version=self.glance_api_version, |
12 | + **conn_info) |
13 | self.tenant_id = conn_info['tenant_id'] |
14 | |
15 | self.region = self.keystone_creds.get('region_name', 'nullregion') |
16 | @@ -195,12 +197,15 @@ class GlanceMirror(mirrors.BasicMirrorWriter): |
17 | |
18 | images = self.gclient.images.list() |
19 | for image in images: |
20 | - image = image.to_dict() |
21 | + if self.glance_api_version == "1": |
22 | + image = image.to_dict() |
23 | + props = image['properties'] |
24 | + else: |
25 | + props = copy.deepcopy(image) |
26 | |
27 | if image['owner'] != self.tenant_id: |
28 | continue |
29 | |
30 | - props = image['properties'] |
31 | if props.get('content_id') != my_cid: |
32 | continue |
33 | |
34 | @@ -321,15 +326,22 @@ class GlanceMirror(mirrors.BasicMirrorWriter): |
35 | 'properties': image_properties, |
36 | } |
37 | |
38 | - if 'size' in image_metadata: |
39 | - create_kwargs['size'] = image_metadata.get('size') |
40 | - if 'md5' in image_metadata: |
41 | - create_kwargs['checksum'] = image_metadata.get('md5') |
42 | - if image_md5_hash and image_size: |
43 | - create_kwargs.update({ |
44 | - 'checksum': image_md5_hash, |
45 | - 'size': image_size, |
46 | - }) |
47 | + # In v2 is_public=True is visibility='public' |
48 | + if self.glance_api_version == "2": |
49 | + del create_kwargs['is_public'] |
50 | + create_kwargs['visibility'] = 'public' |
51 | + |
52 | + # v2 automatically calculates size and checksum |
53 | + if self.glance_api_version == "1": |
54 | + if 'size' in image_metadata: |
55 | + create_kwargs['size'] = image_metadata.get('size') |
56 | + if 'md5' in image_metadata: |
57 | + create_kwargs['checksum'] = image_metadata.get('md5') |
58 | + if image_md5_hash and image_size: |
59 | + create_kwargs.update({ |
60 | + 'checksum': image_md5_hash, |
61 | + 'size': image_size, |
62 | + }) |
63 | |
64 | if 'ftype' in image_metadata: |
65 | create_kwargs['disk_format'] = ( |
66 | @@ -459,9 +471,28 @@ class GlanceMirror(mirrors.BasicMirrorWriter): |
67 | new_size) |
68 | |
69 | try: |
70 | - create_kwargs['data'] = open(tmp_path, 'rb') |
71 | + if self.glance_api_version == "1": |
72 | + # Set data as string if v1 |
73 | + create_kwargs['data'] = open(tmp_path, 'rb') |
74 | + else: |
75 | + # Keep properties for v2 update call |
76 | + _properties = create_kwargs['properties'] |
77 | + del create_kwargs['properties'] |
78 | + |
79 | glance_image = self.gclient.images.create(**create_kwargs) |
80 | target_sstream_item['id'] = glance_image.id |
81 | + |
82 | + if self.glance_api_version == "2": |
83 | + # Upload for v2 |
84 | + self.gclient.images.upload(glance_image.id, |
85 | + open(tmp_path, 'rb')) |
86 | + # Update properties for v2 |
87 | + self.gclient.images.update(glance_image.id, **_properties) |
88 | + |
89 | + # Validate the image checksum and size. This will throw an |
90 | + # IOError if they do not match. |
91 | + self.validate_image(glance_image.id, new_md5, new_size) |
92 | + |
93 | print("created %s: %s" % (glance_image.id, full_image_name)) |
94 | |
95 | finally: |
96 | @@ -473,6 +504,37 @@ class GlanceMirror(mirrors.BasicMirrorWriter): |
97 | # unused in insert_products below. |
98 | self.insert_products(None, target, None) |
99 | |
100 | + def validate_image(self, image_id, checksum, size): |
101 | + """Validate an image's checksum and size after upload. |
102 | + |
103 | + Check for expected checksum and size. |
104 | + Throw an IOError if they do not match. |
105 | + |
106 | + :param image_id: str Glance Image ID |
107 | + :param checksum: str Expected MD5 sum of the image |
108 | + :param size: str Expected size in bytes of the image |
109 | + :returns: None |
110 | + """ |
111 | + _image = self.gclient.images.get(image_id) |
112 | + # Validate checksum |
113 | + if not checksum == _image.checksum: |
114 | + # If we do not delete the image, the next run will |
115 | + # be unaware the image is invalid. |
116 | + self.gclient.images.delete(image_id) |
117 | + raise IOError( |
118 | + "Invalid glance image: %s: Checksum mismatch: " |
119 | + "expected checksum: %s actual: %s. Deleting the image." |
120 | + % (image_id, checksum, _image.checksum)) |
121 | + # Validate size |
122 | + if not size == _image.size: |
123 | + # If we do not delete the image, the next run will |
124 | + # be unaware the image is invalid. |
125 | + self.gclient.images.delete(image_id) |
126 | + raise IOError( |
127 | + "Invalid glance image: %s: Size mismatch: " |
128 | + "expected size: %s actual: %s. Deleting the image." |
129 | + % (image_id, size, _image.size)) |
130 | + |
131 | def insert_item(self, data, src, target, pedigree, contentsource): |
132 | """Queue item to be inserted in subsequent call to insert_version |
133 | |
134 | diff --git a/simplestreams/openstack.py b/simplestreams/openstack.py |
135 | index 525f423..4899df7 100644 |
136 | --- a/simplestreams/openstack.py |
137 | +++ b/simplestreams/openstack.py |
138 | @@ -188,6 +188,9 @@ def get_service_conn_info(service='image', client=None, **kwargs): |
139 | 'tenant_id': tenant_id} |
140 | if not _LEGACY_CLIENTS: |
141 | info['session'] = client.session |
142 | + info['glance_version'] = '2' |
143 | + else: |
144 | + info['glance_version'] = '1' |
145 | |
146 | return info |
147 | |
148 | diff --git a/tests/unittests/test_glancemirror.py b/tests/unittests/test_glancemirror.py |
149 | index eb537d8..5fba5ed 100644 |
150 | --- a/tests/unittests/test_glancemirror.py |
151 | +++ b/tests/unittests/test_glancemirror.py |
152 | @@ -98,7 +98,8 @@ class FakeOpenstack(object): |
153 | |
154 | def get_service_conn_info(self, url, region_name=None, auth_url=None): |
155 | return {"endpoint": "http://objectstore/api/", |
156 | - "tenant_id": "bar456"} |
157 | + "tenant_id": "bar456", |
158 | + "glance_version": "1"} |
159 | |
160 | |
161 | class FakeImage(object): |
162 | @@ -106,16 +107,45 @@ class FakeImage(object): |
163 | def __init__(self, identifier): |
164 | self.id = identifier |
165 | |
166 | + @property |
167 | + def checksum(self): |
168 | + # Using None and None as self.mirror.download_image gets this value |
169 | + # from the fake setup. |
170 | + return None |
171 | + |
172 | + @property |
173 | + def size(self): |
174 | + # Using None and None as self.mirror.download_image gets this value |
175 | + # from the fake setup. |
176 | + return None |
177 | + |
178 | |
179 | class FakeImages(object): |
180 | - """Fake GlanceClient.images implementation to track create() calls.""" |
181 | + """Fake GlanceClient.images implementation to track method calls.""" |
182 | def __init__(self): |
183 | self.create_calls = [] |
184 | + self.delete_calls = [] |
185 | + self.get_calls = [] |
186 | + self.update_calls = [] |
187 | + self.upload_calls = [] |
188 | |
189 | def create(self, **kwargs): |
190 | self.create_calls.append(kwargs) |
191 | return FakeImage('image-%d' % len(self.create_calls)) |
192 | |
193 | + def delete(self, *args, **kwargs): |
194 | + self.delete_calls.append(kwargs) |
195 | + |
196 | + def get(self, *args, **kwargs): |
197 | + self.get_calls.append(kwargs) |
198 | + return FakeImage('image-%d' % len(self.get_calls)) |
199 | + |
200 | + def update(self, *args, **kwargs): |
201 | + self.update_calls.append(kwargs) |
202 | + |
203 | + def upload(self, *args, **kwargs): |
204 | + self.upload_calls.append(kwargs) |
205 | + |
206 | |
207 | class FakeGlanceClient(object): |
208 | """Fake GlanceClient implementation to track images.create() calls.""" |
209 | @@ -306,9 +336,10 @@ class TestGlanceMirror(TestCase): |
210 | '{"os": "ubuntu", "version": "16.04"}', |
211 | properties["simplestreams_metadata"]) |
212 | |
213 | - def test_prepare_glance_arguments(self): |
214 | + def test_prepare_glance_arguments_v1(self): |
215 | # Prepares arguments to pass to GlanceClient.images.create() |
216 | # based on image metadata from the simplestreams source. |
217 | + self.mirror.glance_api_version = "1" |
218 | source_entry = {} |
219 | create_arguments = self.mirror.prepare_glance_arguments( |
220 | "foobuntu-X", source_entry, image_md5_hash=None, image_size=None, |
221 | @@ -324,6 +355,25 @@ class TestGlanceMirror(TestCase): |
222 | "properties": None}, |
223 | create_arguments) |
224 | |
225 | + def test_prepare_glance_arguments_v2(self): |
226 | + # Prepares arguments to pass to GlanceClient.images.create() |
227 | + # based on image metadata from the simplestreams source. |
228 | + self.mirror.glance_api_version = "2" |
229 | + source_entry = {} |
230 | + create_arguments = self.mirror.prepare_glance_arguments( |
231 | + "foobuntu-X", source_entry, image_md5_hash=None, image_size=None, |
232 | + image_properties=None) |
233 | + |
234 | + # Arguments to always pass in contain the image name, container format, |
235 | + # disk format, whether image is public, and any passed-in properties. |
236 | + self.assertEqual( |
237 | + {"name": "foobuntu-X", |
238 | + "container_format": 'bare', |
239 | + "disk_format": "qcow2", |
240 | + "visibility": "public", |
241 | + "properties": None}, |
242 | + create_arguments) |
243 | + |
244 | def test_prepare_glance_arguments_disk_format(self): |
245 | # Disk format is based on the image 'ftype' (if defined). |
246 | source_entry = {"ftype": "root.tar.gz"} |
247 | @@ -342,8 +392,9 @@ class TestGlanceMirror(TestCase): |
248 | |
249 | self.assertEqual("squashfs", create_arguments["disk_format"]) |
250 | |
251 | - def test_prepare_glance_arguments_size(self): |
252 | + def test_prepare_glance_arguments_size_v1(self): |
253 | # Size is read from image metadata if defined. |
254 | + self.mirror.glance_api_version = "1" |
255 | source_entry = {"size": 5} |
256 | create_arguments = self.mirror.prepare_glance_arguments( |
257 | "foobuntu-X", source_entry, image_md5_hash=None, image_size=None, |
258 | @@ -351,8 +402,19 @@ class TestGlanceMirror(TestCase): |
259 | |
260 | self.assertEqual(5, create_arguments["size"]) |
261 | |
262 | - def test_prepare_glance_arguments_checksum(self): |
263 | + def test_prepare_glance_arguments_size_v2(self): |
264 | + # Size is read from image metadata if defined. |
265 | + self.mirror.glance_api_version = "2" |
266 | + source_entry = {"size": 5} |
267 | + create_arguments = self.mirror.prepare_glance_arguments( |
268 | + "foobuntu-X", source_entry, image_md5_hash=None, image_size=None, |
269 | + image_properties=None) |
270 | + |
271 | + self.assertEqual(None, create_arguments.get("size")) |
272 | + |
273 | + def test_prepare_glance_arguments_checksum_v1(self): |
274 | # Checksum is based on the source entry 'md5' value, if defined. |
275 | + self.mirror.glance_api_version = "1" |
276 | source_entry = {"md5": "foo123"} |
277 | create_arguments = self.mirror.prepare_glance_arguments( |
278 | "foobuntu-X", source_entry, image_md5_hash=None, image_size=None, |
279 | @@ -360,7 +422,7 @@ class TestGlanceMirror(TestCase): |
280 | |
281 | self.assertEqual("foo123", create_arguments["checksum"]) |
282 | |
283 | - def test_prepare_glance_arguments_size_and_md5_override(self): |
284 | + def test_prepare_glance_arguments_size_and_md5_override_v1(self): |
285 | # Size and md5 hash are overridden from the passed-in values even if |
286 | # defined on the source entry. |
287 | source_entry = {"size": 5, "md5": "foo123"} |
288 | @@ -371,7 +433,7 @@ class TestGlanceMirror(TestCase): |
289 | self.assertEqual(10, create_arguments["size"]) |
290 | self.assertEqual("bar456", create_arguments["checksum"]) |
291 | |
292 | - def test_prepare_glance_arguments_size_and_md5_no_override_hash(self): |
293 | + def test_prepare_glance_arguments_size_and_md5_no_override_hash_v1(self): |
294 | # If only one of image_md5_hash or image_size is passed directly in, |
295 | # the other value is not overridden either. |
296 | source_entry = {"size": 5, "md5": "foo123"} |
297 | @@ -382,7 +444,7 @@ class TestGlanceMirror(TestCase): |
298 | self.assertEqual(5, create_arguments["size"]) |
299 | self.assertEqual("foo123", create_arguments["checksum"]) |
300 | |
301 | - def test_prepare_glance_arguments_size_and_md5_no_override_size(self): |
302 | + def test_prepare_glance_arguments_size_and_md5_no_override_size_v1(self): |
303 | # If only one of image_md5_hash or image_size is passed directly in, |
304 | # the other value is not overridden either. |
305 | source_entry = {"size": 5, "md5": "foo123"} |
306 | @@ -393,6 +455,16 @@ class TestGlanceMirror(TestCase): |
307 | self.assertEqual(5, create_arguments["size"]) |
308 | self.assertEqual("foo123", create_arguments["checksum"]) |
309 | |
310 | + def test_prepare_glance_arguments_checksum_v2(self): |
311 | + # Checksum is based on the source entry 'md5' value, if defined. |
312 | + self.mirror.glance_api_version = "2" |
313 | + source_entry = {"md5": "foo123"} |
314 | + create_arguments = self.mirror.prepare_glance_arguments( |
315 | + "foobuntu-X", source_entry, image_md5_hash=None, image_size=None, |
316 | + image_properties=None) |
317 | + |
318 | + self.assertEqual(None, create_arguments.get("checksum")) |
319 | + |
320 | def test_download_image(self): |
321 | # Downloads image from a contentsource. |
322 | content = "foo bazes the bar" |
323 | @@ -538,10 +610,11 @@ class TestGlanceMirror(TestCase): |
324 | pedigree[2]) |
325 | self.assertEqual(u"http://keystone/api/", target_image["endpoint"]) |
326 | |
327 | - def test_insert_item_full(self): |
328 | + def test_insert_item_full_v1(self): |
329 | # This test uses the full sample entries from the source simplestreams |
330 | # index from cloud-images.u.c and resulting local simplestreams index |
331 | # files. |
332 | + self.mirror.glance_api_version = "1" |
333 | source_index = copy.deepcopy(TEST_SOURCE_INDEX_ENTRY) |
334 | |
335 | # "Pedigree" is basically a "path" to get to the image data in |
336 | @@ -655,3 +728,100 @@ class TestGlanceMirror(TestCase): |
337 | del stored_index[u"updated"] |
338 | |
339 | self.assertEqual(EXPECTED_OUTPUT_INDEX, stored_index) |
340 | + |
341 | + def test_insert_item_full_v2(self): |
342 | + # This test uses the full sample entries from the source simplestreams |
343 | + # index from cloud-images.u.c and resulting local simplestreams index |
344 | + # files. |
345 | + self.mirror.glance_api_version = "2" |
346 | + source_index = copy.deepcopy(TEST_SOURCE_INDEX_ENTRY) |
347 | + |
348 | + # "Pedigree" is basically a "path" to get to the image data in |
349 | + # simplestreams index, going through "products", their "versions", |
350 | + # and nested "items". |
351 | + pedigree = ( |
352 | + u'com.ubuntu.cloud:server:14.04:amd64', u'20160602', u'disk1.img') |
353 | + product = source_index[u'products'][pedigree[0]] |
354 | + ver_data = product[u'versions'][pedigree[1]] |
355 | + image_data = ver_data[u'items'][pedigree[2]] |
356 | + |
357 | + content_source = MemoryContentSource( |
358 | + url="http://image-store/fooubuntu-X-disk1.img", |
359 | + content="image-data") |
360 | + |
361 | + # Use a fake GlanceClient to track arguments passed into |
362 | + # GlanceClient.images.create(). |
363 | + self.addCleanup(setattr, self.mirror, "gclient", self.mirror.gclient) |
364 | + self.mirror.gclient = FakeGlanceClient() |
365 | + |
366 | + target = { |
367 | + 'content_id': 'auto.sync', |
368 | + 'datatype': 'image-ids', |
369 | + 'format': 'products:1.0', |
370 | + } |
371 | + |
372 | + self.mirror.insert_item( |
373 | + image_data, source_index, target, pedigree, content_source) |
374 | + self.mirror.insert_version( |
375 | + image_data, source_index, target, pedigree[0:2]) |
376 | + |
377 | + passed_create_kwargs = self.mirror.gclient.images.create_calls[0] |
378 | + passed_update_kwargs = self.mirror.gclient.images.update_calls[0] |
379 | + |
380 | + expected_create_kwargs = { |
381 | + 'name': ('auto-sync/' |
382 | + 'ubuntu-trusty-14.04-amd64-server-20160602-disk1.img'), |
383 | + 'disk_format': 'qcow2', |
384 | + 'container_format': 'bare', |
385 | + 'visibility': "public"} |
386 | + |
387 | + expected_update_kwargs = { |
388 | + 'os_distro': u'ubuntu', |
389 | + 'item_name': u'disk1.img', |
390 | + 'os_version': u'14.04', |
391 | + 'architecture': 'x86_64', |
392 | + 'version_name': u'20160602', |
393 | + 'content_id': 'auto.sync', |
394 | + 'product_name': u'com.ubuntu.cloud:server:14.04:amd64', |
395 | + 'simplestreams_metadata': ( |
396 | + '{"aliases": "14.04,default,lts,t,trusty", ' |
397 | + '"arch": "amd64", "ftype": "disk1.img", ' |
398 | + '"label": "release", "md5": ' |
399 | + '"e5436cd36ae6cc298f081bf0f6b413f1", "os": "ubuntu", ' |
400 | + '"pubname": "ubuntu-trusty-14.04-amd64-server-20160602", ' |
401 | + '"release": "trusty", "release_codename": "Trusty Tahr", ' |
402 | + '"release_title": "14.04 LTS", "sha256": ' |
403 | + '"5b982d7d4dd1a03e88ae5f35f02ed44f' |
404 | + '579e2711f3e0f27ea2bff20aef8c8d9e", "size": "259850752", ' |
405 | + '"support_eol": "2019-04-17", "supported": "True", ' |
406 | + '"version": "14.04"}'), |
407 | + 'source_content_id': u'com.ubuntu.cloud:released:download'} |
408 | + |
409 | + self.assertEqual(expected_create_kwargs, passed_create_kwargs) |
410 | + self.assertEqual(expected_update_kwargs, passed_update_kwargs) |
411 | + |
412 | + # Apply the condensing as done in GlanceMirror.insert_products() |
413 | + # to ensure we compare with the desired resulting simplestreams data. |
414 | + sticky = ['ftype', 'md5', 'sha256', 'size', 'name', 'id', 'endpoint', |
415 | + 'region'] |
416 | + simplestreams.util.products_condense(target, sticky) |
417 | + |
418 | + self.assertEqual(EXPECTED_OUTPUT_INDEX, target) |
419 | + |
420 | + def test_validate_image(self): |
421 | + # Ensure validate_image throws IOError when expected. |
422 | + |
423 | + # Successful validation. Using None and None as |
424 | + # self.mirror.download_image gets these values from the fake setup. |
425 | + self.mirror.gclient = FakeGlanceClient() |
426 | + self.assertEqual( |
427 | + None, |
428 | + self.mirror.validate_image("image-id", None, None)) |
429 | + |
430 | + # Mismatched checksum |
431 | + with self.assertRaises(IOError): |
432 | + self.mirror.validate_image("image-id", "Wrong-checksum", None) |
433 | + |
434 | + # Mismatched size |
435 | + with self.assertRaises(IOError): |
436 | + self.mirror.validate_image("image-id", None, "Wrong-size") |
FAILED: Continuous integration, rev:e5b90bdef26 88a0c654bd41ee3 40077263aa7357 /jenkins. ubuntu. com/server/ job/simplestrea ms-ci/68/ /jenkins. ubuntu. com/server/ job/simplestrea ms-ci/nodes= metal-amd64/ 68/console /jenkins. ubuntu. com/server/ job/simplestrea ms-ci/nodes= metal-ppc64el/ 68/console /jenkins. ubuntu. com/server/ job/simplestrea ms-ci/nodes= metal-s390x/ 68/console
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/simplestrea ms-ci/68/ rebuild
https:/