Merge lp:~yamahata/glance/lp802893 into lp:~hudson-openstack/glance/trunk

Proposed by Isaku Yamahata
Status: Rejected
Rejected by: Jay Pipes
Proposed branch: lp:~yamahata/glance/lp802893
Merge into: lp:~hudson-openstack/glance/trunk
Diff against target: 459 lines (+330/-34)
3 files modified
glance/registry/server.py (+16/-3)
glance/utils.py (+232/-31)
tests/unit/test_utils.py (+82/-0)
To merge this branch: bzr merge lp:~yamahata/glance/lp802893
Reviewer Review Type Date Requested Status
Glance Core security contacts Pending
Review via email: mp+66096@code.launchpad.net

Commit message

Glance doesn't allow structured metadata

boot-from-volume patch series needs to store the informations about
block-device-mapping in metadata.
It has structured data as following. That is, its value can be
a dictionary or a list. And its child value also can be...

local image service (which was deleted) and fakeimage service can
properly store such metadata as they just use json.dumps/loads.
However, glance image service doesn't so that block-device-mapping
informations can't be stored properly.

This patch teaches structured metadata to glance api/registry daemons.

metadata = {'name': 'fake public image',
           'properties': {
               'mappings': [
                   {'device_name': '/dev/sda1',
                    'snapshot_id': 0x12345678,
                    'delete_on_termination': False},
                   {'device_name': '/dev/sda2',
                    'no_device': True}],
               'block_device_mapping': [
   {'virtual': 'ami', 'device': 'sda1'},
                        {'virtual': 'root', 'device': '/dev/sda1'},

                        {'virtual': 'swap', 'device': 'sdb1'},
                        {'virtual': 'swap', 'device': 'sdb2'},

                        {'virtual': 'ephemeral0', 'device': 'sdc1'},
                        {'virtual': 'ephemeral1', 'device': 'sdc2'}]}}

Description of the change

boot-from-volume patch series needs to store the informations about
block-device-mapping in metadata.
It has structured data as following. That is, its value can be
a dictionary or a list. And its child value also can be...

local image service (which was deleted) and fakeimage service can
properly store such metadata as they just use json.dumps/loads.
However, glance image service doesn't so that block-device-mapping
informations can't be stored properly.

This patch teaches structured metadata to glance api/registry daemons.

metadata = {'name': 'fake public image',
           'properties': {
               'mappings': [
                   {'device_name': '/dev/sda1',
                    'snapshot_id': 0x12345678,
                    'delete_on_termination': False},
                   {'device_name': '/dev/sda2',
                    'no_device': True}],
               'block_device_mapping': [
   {'virtual': 'ami', 'device': 'sda1'},
                        {'virtual': 'root', 'device': '/dev/sda1'},

                        {'virtual': 'swap', 'device': 'sdb1'},
                        {'virtual': 'swap', 'device': 'sdb2'},

                        {'virtual': 'ephemeral0', 'device': 'sdc1'},
                        {'virtual': 'ephemeral1', 'device': 'sdc2'}]}}

To post a comment you must log in.
Revision history for this message
Brian Waldon (bcwaldon) wrote :

This is an interesting idea, but I'm a little hesitant to call this a bug. Keeping metadata simple was an explicit design design. I think this should probably be filed as a blueprint targeted at diablo-3, assuming this is something we want. Thoughts, Jay?

I would also like to see a lot more testing around this. I would want to guarantee this will work at every level, not just the utils function that does the mapping.

Revision history for this message
Christopher MacGown (0x44) wrote :

I agree that this is probably something we should file as a blueprint and discuss. I think that if we need more complex metadata in Glance that it's more appropriate to make a specific effort for Glance to support querying against OVF manifests/metadata than rolling our own.

Revision history for this message
Isaku Yamahata (yamahata) wrote :

On Tue, Jun 28, 2011 at 02:48:38PM -0000, Brian Waldon wrote:
> This is an interesting idea, but I'm a little hesitant to call this a bug. Keeping metadata simple was an explicit design design. I think this should probably be filed as a blueprint targeted at diablo-3, assuming this is something we want. Thoughts, Jay?

Fair enough, I'll file a blueprint and let's start discussion.
I think the discussion involves both nova and glance. So I'll also start
a discussion thread on openstack devel ml in order to draw attention from
nova developer. So far I've thought it's Glance issue so that I created
this patch to Glance. Other option would be to change nova metadata handling.

Jay, any comments?

thanks,

> I would also like to see a lot more testing around this. I would want to guarantee this will work at every level, not just the utils function that does the mapping.
--
yamahata

Revision history for this message
Jay Pipes (jaypipes) wrote :

> On Tue, Jun 28, 2011 at 02:48:38PM -0000, Brian Waldon wrote:
> > This is an interesting idea, but I'm a little hesitant to call this a bug.
> Keeping metadata simple was an explicit design design. I think this should
> probably be filed as a blueprint targeted at diablo-3, assuming this is
> something we want. Thoughts, Jay?
>
> Fair enough, I'll file a blueprint and let's start discussion.

Cool. Agreed it's big enough to be a blueprint discussion.

> I think the discussion involves both nova and glance. So I'll also start
> a discussion thread on openstack devel ml in order to draw attention from
> nova developer. So far I've thought it's Glance issue so that I created
> this patch to Glance. Other option would be to change nova metadata handling.
>
> Jay, any comments?

I think it's a good feature to add, but I'd almost prefer to add it as new middleware on the registry, or even as an extension? That way you could do something like:

PUT /images/<ID>/container-format

with a body of something like:

'format': {
  'mappings': [
                   {'device_name': '/dev/sda1',
                    'snapshot_id': 0x12345678,
                    'delete_on_termination': False},
                   {'device_name': '/dev/sda2',
                    'no_device': True}
  ],
  'block_device_mapping': [
                   {'virtual': 'ami', 'device': 'sda1'},
                   {'virtual': 'root', 'device': '/dev/sda1'},
                   {'virtual': 'swap', 'device': 'sdb1'},
                   {'virtual': 'swap', 'device': 'sdb2'},
                   {'virtual': 'ephemeral0', 'device': 'sdc1'},
                   {'virtual': 'ephemeral1', 'device': 'sdc2'}
  ],
}

And then, the extension/middleware can store specific information about the container in separate tables in the database that can be queried using a more specific API than the very limited custom key/value properties we currently have.

Thoughts?
-jay

Revision history for this message
Vish Ishaya (vishvananda) wrote :

Is there any reason you can't just do a json.dumps and save it as a text blob, then use json.loads on the other end? I don't see why complex metadata needs to be split into a bunch of separate properties.

Vish

On Jun 29, 2011, at 2:08 PM, Jay Pipes wrote:

>> On Tue, Jun 28, 2011 at 02:48:38PM -0000, Brian Waldon wrote:
>>> This is an interesting idea, but I'm a little hesitant to call this a bug.
>> Keeping metadata simple was an explicit design design. I think this should
>> probably be filed as a blueprint targeted at diablo-3, assuming this is
>> something we want. Thoughts, Jay?
>>
>> Fair enough, I'll file a blueprint and let's start discussion.
>
> Cool. Agreed it's big enough to be a blueprint discussion.
>
>> I think the discussion involves both nova and glance. So I'll also start
>> a discussion thread on openstack devel ml in order to draw attention from
>> nova developer. So far I've thought it's Glance issue so that I created
>> this patch to Glance. Other option would be to change nova metadata handling.
>>
>> Jay, any comments?
>
> I think it's a good feature to add, but I'd almost prefer to add it as new middleware on the registry, or even as an extension? That way you could do something like:
>
> PUT /images/<ID>/container-format
>
> with a body of something like:
>
> 'format': {
> 'mappings': [
> {'device_name': '/dev/sda1',
> 'snapshot_id': 0x12345678,
> 'delete_on_termination': False},
> {'device_name': '/dev/sda2',
> 'no_device': True}
> ],
> 'block_device_mapping': [
> {'virtual': 'ami', 'device': 'sda1'},
> {'virtual': 'root', 'device': '/dev/sda1'},
> {'virtual': 'swap', 'device': 'sdb1'},
> {'virtual': 'swap', 'device': 'sdb2'},
> {'virtual': 'ephemeral0', 'device': 'sdc1'},
> {'virtual': 'ephemeral1', 'device': 'sdc2'}
> ],
> }
>
> And then, the extension/middleware can store specific information about the container in separate tables in the database that can be queried using a more specific API than the very limited custom key/value properties we currently have.
>
> Thoughts?
> -jay
>
> --
> https://code.launchpad.net/~yamahata/glance/lp802893/+merge/66096
> Your team Glance Core is requested to review the proposed merge of lp:~yamahata/glance/lp802893 into lp:glance.

Revision history for this message
Jay Pipes (jaypipes) wrote :

On Wed, Jun 29, 2011 at 4:43 PM, Vish Ishaya <email address hidden> wrote:
> Is there any reason you can't just do a json.dumps and save it as a text blob, then use json.loads on the other end?  I don't see why complex metadata needs to be split into a bunch of separate properties.

That's a perfectly reasonable solution.

Of course, it would preclude any ability to filter on such things. If
I wanted to find all images where delete_on_terminate was set, the
query would be a beast and issue a full table scan over
image_properties, since there would be no ability to do an equality
search.

So, long term, I think if it's information we want to query, we should
look to a more robust method of storing the pieces of information.

Cheers,
jay

Revision history for this message
Isaku Yamahata (yamahata) wrote :

On Wed, Jun 29, 2011 at 09:08:42PM -0000, Jay Pipes wrote:
> > On Tue, Jun 28, 2011 at 02:48:38PM -0000, Brian Waldon wrote:
> > > This is an interesting idea, but I'm a little hesitant to call this a bug.
> > Keeping metadata simple was an explicit design design. I think this should
> > probably be filed as a blueprint targeted at diablo-3, assuming this is
> > something we want. Thoughts, Jay?
> >
> > Fair enough, I'll file a blueprint and let's start discussion.
>
> Cool. Agreed it's big enough to be a blueprint discussion.

I created the blueprint and the spec page.

https://blueprints.launchpad.net/glance/+spec/structured-data-in-metadata
http://wiki.openstack.org/StructuredMetadata

I've listed ideas for implementation candidate for the discussion base.
--
yamahata

Revision history for this message
Isaku Yamahata (yamahata) wrote :

On Wed, Jun 29, 2011 at 09:49:27PM -0000, Jay Pipes wrote:
> On Wed, Jun 29, 2011 at 4:43 PM, Vish Ishaya <email address hidden> wrote:
> > Is there any reason you can't just do a json.dumps and save it as a text blob, then use json.loads on the other end? ??I don't see why complex metadata needs to be split into a bunch of separate properties.
>
> That's a perfectly reasonable solution.
>
> Of course, it would preclude any ability to filter on such things. If
> I wanted to find all images where delete_on_terminate was set, the
> query would be a beast and issue a full table scan over
> image_properties, since there would be no ability to do an equality
> search.
>
> So, long term, I think if it's information we want to query, we should
> look to a more robust method of storing the pieces of information.

If there is no major objection, I'd like to go for the middleware
idea that Jay suggested.

--
yamahata

Revision history for this message
Jay Pipes (jaypipes) wrote :

> If there is no major objection, I'd like to go for the middleware
> idea that Jay suggested.

Go for it! No need for permission :)

-jay

Revision history for this message
Jay Pipes (jaypipes) wrote :

Yamahata,

Feel free to hop onto IRC freenode.net #openstack-dev if you want to discuss this or need some pointers on where to start :)

-jay

Unmerged revisions

147. By Isaku Yamahata

test_utils: added unit tests for new meta data (de)serializer.

146. By Isaku Yamahata

utils, glance/registry: meta data serializer/deserialize allow dict/list value

This patch teaches registry sever structured json format.
So far glance-registry accepts only simple key value pair. It doesn't
accept python dict/list as value. On the other hand nova local image
service which was deleted/fake image service do.

On the other hand image meta data needs to have dict/list as value
for boot-from-volume. So this patch makes it possible.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'glance/registry/server.py'
2--- glance/registry/server.py 2011-06-15 14:47:00 +0000
3+++ glance/registry/server.py 2011-06-28 09:41:34 +0000
4@@ -19,12 +19,12 @@
5 Reference implementation registry server WSGI controller
6 """
7
8-import json
9 import logging
10
11 import routes
12 from webob import exc
13
14+from glance import utils
15 from glance.common import wsgi
16 from glance.common import exception
17 from glance.registry.db import api as db_api
18@@ -196,6 +196,8 @@
19 # Ensure the image has a status set
20 image_data.setdefault('status', 'active')
21
22+ flatten_properties(image_data)
23+
24 context = None
25 try:
26 image_data = db_api.image_create(context, image_data)
27@@ -220,6 +222,7 @@
28
29 """
30 image_data = body['image']
31+ flatten_properties(image_data)
32
33 purge_props = req.headers.get("X-Glance-Registry-Purge-Props", "false")
34 context = None
35@@ -262,6 +265,14 @@
36 super(API, self).__init__(mapper)
37
38
39+def flatten_properties(image_data):
40+ """if necessary, convert json string into flatten key-value dict whose
41+ values aren't dict nor list"""
42+ properties = image_data.get('properties')
43+ if properties:
44+ image_data['properties'] = utils.properties_to_flatten_dict(properties)
45+
46+
47 def make_image_dict(image):
48 """
49 Create a dict representation of an image which we can use to
50@@ -275,8 +286,10 @@
51 # TODO(sirp): should this be a dict, or a list of dicts?
52 # A plain dict is more convenient, but list of dicts would provide
53 # access to created_at, etc
54- properties = dict((p['name'], p['value'])
55- for p in image['properties'] if not p['deleted'])
56+ flatten_dict = dict((p['name'], p['value'])
57+ for p in image['properties'] if not p['deleted'])
58+ properties = utils.flatten_dict_to_properties(flatten_dict)
59+ utils.convert_properties_value(properties)
60
61 image_dict = _fetch_attrs(image, db_api.IMAGE_ATTRS)
62
63
64=== modified file 'glance/utils.py'
65--- glance/utils.py 2011-06-11 18:18:03 +0000
66+++ glance/utils.py 2011-06-28 09:41:34 +0000
67@@ -19,6 +19,148 @@
68 A few utility routines used throughout Glance
69 """
70
71+import functools
72+import re
73+
74+
75+def _list_to_enum_dict(image_meta):
76+ """Convert list value to enumerated dict.
77+ e.g.
78+ {k: [v1, v2, v3]} => {k: {'1': v1, '2': v2, '3': v3}}
79+ {a: x,
80+ b: [{b: y},
81+ {c: z}]}
82+ =>
83+ {a: x,
84+ b: {'1': {b: y},
85+ '2': {c: z}}}
86+ """
87+ result = {}
88+ for k, v in image_meta.items():
89+ if isinstance(v, list):
90+ result[k] = dict(zip([str(x) for x in range(len(v))],
91+ [_list_to_enum_dict(y) for y in v]))
92+ elif isinstance(v, dict):
93+ result[k] = _list_to_enum_dict(v)
94+ else:
95+ result[k] = v
96+
97+ return result
98+
99+
100+def _enum_dict_to_list(enum_dict):
101+ """convert enumerated dict elements in a given dict into lists
102+ e.g.
103+
104+ {k: {'1': v1, '2': v2, '3': v3}} => {k: [v1, v2, v3]}
105+ {a: x,
106+ b: {'1': {b: y},
107+ '2': {c: z}}}
108+ =>
109+ {a: x,
110+ b: [{b: y},
111+ {c: z}]}
112+ """
113+ result = {}
114+ for k, v in enum_dict.items():
115+ assert not isinstance(v, list)
116+
117+ if isinstance(v, dict):
118+ if all([k1.isdigit() for k1 in v.keys()]):
119+ result[k] = [_enum_dict_to_list(x) for x in v.values()]
120+ else:
121+ result[k] = _enum_dict_to_list(v)
122+ else:
123+ result[k] = v
124+
125+ return result
126+
127+
128+_DELIMITOR = '--'
129+
130+
131+def _flatten_dict(prefix, image_meta, result=None):
132+ """ convert possibly nested dict into flatten dict by encoding
133+ the child keys into the primary k
134+ e.g.
135+ {'k1': {'k2': {'k3': v}} => {prefix + '--k1--k2--k3': unicode(v)}
136+ """
137+ result = result or {}
138+ for k, v in image_meta.items():
139+ # list should be converted by _list_to_dict() beforehand
140+ assert not isinstance(v, list)
141+ if v is None:
142+ v = ''
143+
144+ # k can be ''
145+ tmp_prefix = prefix
146+ if k:
147+ tmp_prefix += _DELIMITOR + k.replace('-', '_').lower()
148+
149+ if isinstance(v, dict):
150+ _flatten_dict(tmp_prefix, v, result)
151+ else:
152+ result[tmp_prefix] = unicode(v)
153+
154+ return result
155+
156+
157+def _flatten_dict_to_dict(flatten_dict):
158+ """ construct a nested dict from a flatten dict whose key is properly
159+ encoded
160+ e.g.
161+ {'k1--k2--k3': v} => {'k1': {'k2': {'k3': v or None}}
162+ """
163+ def _descend(parent, k):
164+ """descend down the potentially-nested dict.
165+ If the value doesn't exist create new dict"""
166+ if k in parent:
167+ p = parent[k]
168+ if not isinstance(p, dict):
169+ parent[k] = {'': p}
170+ else:
171+ parent[k] = {}
172+
173+ return parent[k]
174+
175+ result = {}
176+ for k, v in flatten_dict.items():
177+ v = v or None
178+
179+ child = result
180+ for k in k.split(_DELIMITOR):
181+ k = k.replace('-', '_')
182+ parent = child
183+ child = _descend(parent, k)
184+
185+ p = parent[k]
186+ assert isinstance(p, dict)
187+ if p:
188+ p[''] = v
189+ else:
190+ parent[k] = v
191+
192+ return result
193+
194+
195+def properties_to_flatten_dict(properties):
196+ """converted properties into flattened key-value dict."""
197+ enum_dict = _list_to_enum_dict(properties)
198+ flatten_dict = _flatten_dict('', enum_dict)
199+ r = re.compile('^' + _DELIMITOR)
200+ flatten_dict = dict([(r.sub('', item[0]), item[1]) for item in
201+ flatten_dict.items()])
202+ return flatten_dict
203+
204+
205+def flatten_dict_to_properties(flatten_dict):
206+ """reverse conversion of the above function, properties_to_flatten_dict.
207+ converted flattened key-value dict into properties.
208+ """
209+ enum_dict = _flatten_dict_to_dict(flatten_dict)
210+ properties = _enum_dict_to_list(enum_dict)
211+ return properties
212+
213
214 def image_meta_to_http_headers(image_meta):
215 """
216@@ -28,19 +170,83 @@
217
218 :param image_meta: Mapping of image metadata
219 """
220- headers = {}
221- for k, v in image_meta.items():
222- if v is None:
223- v = ''
224- if k == 'properties':
225- for pk, pv in v.items():
226- if pv is None:
227- pv = ''
228- headers["x-image-meta-property-%s"
229- % pk.lower()] = unicode(pv)
230- else:
231- headers["x-image-meta-%s" % k.lower()] = unicode(v)
232- return headers
233+
234+ # NOTE(yamahata):
235+ # image_meta['properties'] is mapped to 'x-image-meta-property'.
236+ # So the value of key, 'property', can't be dict or list
237+ assert (('property' not in image_meta) or
238+ (not (isinstance(image_meta['property'], dict) or
239+ isinstance(image_meta['property'], list))))
240+
241+ result = properties_to_flatten_dict(image_meta)
242+
243+ r = re.compile('^' + 'properties' + _DELIMITOR)
244+ result = dict([('x-image-meta-' + r.sub('property-', item[0]), item[1])
245+ for item in result.items()])
246+ return result
247+
248+
249+def _conv(conv_list, result):
250+ """convert value in metadata into python type"""
251+ for (k, v) in result.items():
252+ if isinstance(v, list):
253+ tmp_list = []
254+
255+ for elem in v:
256+ tmp_result = {k: elem}
257+ _conv(conv_list, tmp_result)
258+ tmp_list.append(tmp_result[k])
259+
260+ result[k] = tmp_list
261+ continue
262+
263+ for conv in conv_list:
264+ if k != conv[0]:
265+ continue
266+
267+ conv = conv[1:]
268+ if len(conv) > 1:
269+ if isinstance(v, dict):
270+ _conv((conv,), v)
271+ result[k] = v
272+ else:
273+ conv = conv[0]
274+ assert isinstance(v, basestring)
275+ assert callable(conv)
276+ result[k] = conv(v)
277+
278+
279+def convert_properties_value(properties):
280+ """convert values in properties of unicode into python type"""
281+ conversion = (('block_device_mapping', 'snapshot_id',
282+ functools.partial(int, base=0)),
283+ ('block_device_mapping', 'volume_id',
284+ functools.partial(int, base=0)),
285+ ('block_device_mapping', 'delete_on_termination',
286+ bool_from_header_value),
287+ ('block_device_mapping', 'no_device',
288+ bool_from_header_value))
289+
290+ _conv(conversion, properties)
291+
292+
293+def _convert_value(result):
294+ """Convert metadata values stored in http header as unicode into python
295+ type where appropriate"""
296+ conversion = (('id', int),
297+ ('size', int),
298+ ('is_public', bool_from_header_value),
299+ ('deleted', bool_from_header_value),
300+ ('properties', 'block_device_mapping', 'snapshot_id',
301+ functools.partial(int, base=0)),
302+ ('properties', 'block_device_mapping', 'volume_id',
303+ functools.partial(int, base=0)),
304+ ('properties', 'block_device_mapping',
305+ 'delete_on_termination', bool_from_header_value),
306+ ('properties', 'block_device_mapping', 'no_device',
307+ bool_from_header_value))
308+
309+ _conv(conversion, result)
310
311
312 def get_image_meta_from_headers(response):
313@@ -52,30 +258,25 @@
314 :param response: Response to process
315 """
316 result = {}
317- properties = {}
318
319 if hasattr(response, 'getheaders'): # httplib.HTTPResponse
320 headers = response.getheaders()
321 else: # webob.Response
322 headers = response.headers.items()
323
324- for key, value in headers:
325- key = str(key.lower())
326- if key.startswith('x-image-meta-property-'):
327- field_name = key[len('x-image-meta-property-'):].replace('-', '_')
328- properties[field_name] = value or None
329- elif key.startswith('x-image-meta-'):
330- field_name = key[len('x-image-meta-'):].replace('-', '_')
331- result[field_name] = value or None
332- result['properties'] = properties
333- if 'id' in result:
334- result['id'] = int(result['id'])
335- if 'size' in result:
336- result['size'] = int(result['size'])
337- if 'is_public' in result:
338- result['is_public'] = bool_from_header_value(result['is_public'])
339- if 'deleted' in result:
340- result['deleted'] = bool_from_header_value(result['deleted'])
341+ r1 = re.compile('^x-image-meta-', flags=re.IGNORECASE)
342+ r2 = re.compile('^property-', flags=re.IGNORECASE)
343+ r3 = re.compile('([^-])-([^-])')
344+ result = dict([(r3.sub('\\1_\\2',
345+ r2.sub('properties' + _DELIMITOR,
346+ r1.sub('', str(item[0].lower())))),
347+ item[1])
348+ for item in headers if r1.match(item[0])])
349+
350+ result = flatten_dict_to_properties(result)
351+
352+ _convert_value(result)
353+ result.setdefault('properties', {})
354 return result
355
356
357
358=== modified file 'tests/unit/test_utils.py'
359--- tests/unit/test_utils.py 2011-04-12 21:45:16 +0000
360+++ tests/unit/test_utils.py 2011-06-28 09:41:34 +0000
361@@ -24,6 +24,18 @@
362
363 """Test routines in glance.utils"""
364
365+ def _assertDictMatch(self, D1, D2):
366+ self.assertEqual(len(D1), len(D2))
367+ for k in D1.keys():
368+ self.assertTrue(k in D1)
369+ self.assertEqual(D1[k], D2[k])
370+
371+ def _assertDictListMatch(self, L1_sorted, L2_sorted):
372+ self.assertEqual(len(L1_sorted), len(L2_sorted))
373+
374+ for d1, d2 in zip(L1_sorted, L2_sorted):
375+ self._assertDictMatch(d1, d2)
376+
377 def test_headers_are_unicode(self):
378 """
379 Verifies that the headers returned by conversion code are unicode.
380@@ -63,9 +75,79 @@
381 response = FakeResponse()
382 response.headers = headers
383 result = utils.get_image_meta_from_headers(response)
384+ print "\nfixture %s\n" % fixture
385 for k, v in fixture.iteritems():
386+ print "k %s v %s result[k] %s" % (k, v, result[k])
387 self.assertEqual(v, result[k])
388
389+ def test_data_passed_properly_through_headers_mapping(self):
390+ """
391+ Verifies that data is the same after being passed through headers
392+ """
393+ mappings = [{'virtual': 'ami', 'device': 'sda1'},
394+ {'virtual': 'root', 'device': '/dev/sda1'},
395+
396+ {'virtual': 'swap', 'device': 'sdb1'},
397+ {'virtual': 'swap', 'device': 'sdb2'},
398+
399+ {'virtual': 'ephemeral0', 'device': 'sdc1'},
400+ {'virtual': 'ephemeral1', 'device': 'sdc2'}]
401+ block_device_mapping = [
402+ # root
403+ {'device_name': '/dev/sda1',
404+ 'snapshot_id': 0x12345678,
405+ 'delete_on_termination': False},
406+ {'device_name': '/dev/sda2',
407+ 'no_device': True},
408+
409+ # overwrite swap
410+ {'device_name': '/dev/sdb2',
411+ 'snapshot_id': 0x23456789,
412+ 'delete_on_termination': False},
413+ {'device_name': '/dev/sdb3',
414+ 'snapshot_id': 0x3456789A},
415+
416+ # overwrite ephemeral
417+ {'device_name': '/dev/sdc2',
418+ 'snapshot_id': 0x3456789A,
419+ 'delete_on_termination': False},
420+ {'device_name': '/dev/sdc3',
421+ 'snapshot_id': 0x456789AB},
422+
423+ # volume
424+ {'device_name': '/dev/sdd1',
425+ 'volume_id': 0x87654321,
426+ 'delete_on_termination': False},
427+ {'device_name': '/dev/sdd2',
428+ 'volume_id': 0x98765432}]
429+
430+ fixture = {'name': 'fake public image',
431+ 'properties': {
432+ 'mappings': mappings,
433+ 'block_device_mapping': block_device_mapping}}
434+ headers = utils.image_meta_to_http_headers(fixture)
435+
436+ class FakeResponse():
437+ pass
438+
439+ response = FakeResponse()
440+ response.headers = headers
441+ result = utils.get_image_meta_from_headers(response)
442+ properties = result['properties']
443+
444+ def _mappings_key(bdm):
445+ return bdm['device']
446+ mappings.sort(key=_mappings_key)
447+ properties['mappings'].sort(key=_mappings_key)
448+ self._assertDictListMatch(properties['mappings'], mappings)
449+
450+ def _block_device_mappings_key(bdm):
451+ return bdm['device_name']
452+ block_device_mapping.sort(key=_block_device_mappings_key)
453+ properties['block_device_mapping'].sort(key=_block_device_mappings_key)
454+ self._assertDictListMatch(properties['block_device_mapping'],
455+ block_device_mapping)
456+
457 def test_boolean_header_values(self):
458 """
459 Tests that boolean headers like is_public can be set

Subscribers

People subscribed via source and target branches