serialize non-string nova metadata

This patches ensures that non-string metadata values passed to Nova
are JSON-encoded. This permits setting metadata to complex values,
such as:

    instance0:
      type: OS::Nova::Server
      properties:
        metadata:
          internal_ip: {get_attr: [instance0_eth1, fixed_ips]}

Change-Id: I1a943876d805dce731771b18a39d0c78f617264a
Closes-Bug: 1274155
This commit is contained in:
Lars Kellogg-Stedman
2014-01-29 22:05:47 -05:00
parent 0fe67485f9
commit 3e3fc3e1ff
4 changed files with 88 additions and 4 deletions

View File

@@ -20,6 +20,7 @@ from email.mime.text import MIMEText
import json
import os
import pkgutil
import six
from oslo.config import cfg
@@ -265,8 +266,20 @@ def check_rebuild(server, image_id):
_("Rebuilding server failed, status '%s'") % server.status)
def meta_serialize(metadata):
"""
Serialize non-string metadata values before sending them to
Nova.
"""
return dict((key, (value if isinstance(value,
six.string_types)
else json.dumps(value))
) for (key, value) in metadata.items())
def meta_update(client, server, metadata):
"""Delete/Add the metadata in nova as needed."""
metadata = meta_serialize(metadata)
current_md = server.metadata
to_del = [key for key in current_md.keys() if key not in metadata]
if len(to_del) > 0:

View File

@@ -194,7 +194,9 @@ class Server(resource.Resource):
METADATA: properties.Schema(
properties.Schema.MAP,
_('Arbitrary key/value metadata to store for this server. Both '
'keys and values must be 255 characters or less.'),
'keys and values must be 255 characters or less. Non-string '
'values will be serialized to JSON (and the serialized '
'string must be 255 characters or less).'),
update_allowed=True
),
USER_DATA_FORMAT: properties.Schema(
@@ -292,8 +294,7 @@ class Server(resource.Resource):
instance_meta = self.properties.get(self.METADATA)
if instance_meta is not None:
instance_meta = dict((key, str(value)) for (key, value) in
instance_meta.items())
instance_meta = nova_utils.meta_serialize(instance_meta)
scheduler_hints = self.properties.get(self.SCHEDULER_HINTS)
nics = self._build_nics(self.properties.get(self.NETWORKS))

View File

@@ -126,3 +126,46 @@ class NovaUtilsUserdataTests(HeatTestCase):
self.assertIn("[Boto]", data)
self.assertIn(self.expect, data)
self.m.VerifyAll()
class NovaUtilsMetadataTests(HeatTestCase):
def test_serialize_string(self):
original = {'test_key': 'simple string value'}
self.assertEqual(original, nova_utils.meta_serialize(original))
def test_serialize_int(self):
original = {'test_key': 123}
expected = {'test_key': '123'}
self.assertEqual(expected, nova_utils.meta_serialize(original))
def test_serialize_list(self):
original = {'test_key': [1, 2, 3]}
expected = {'test_key': '[1, 2, 3]'}
self.assertEqual(expected, nova_utils.meta_serialize(original))
def test_serialize_dict(self):
original = {'test_key': {'a': 'b', 'c': 'd'}}
expected = {'test_key': '{"a": "b", "c": "d"}'}
self.assertEqual(expected, nova_utils.meta_serialize(original))
def test_serialize_none(self):
original = {'test_key': None}
expected = {'test_key': 'null'}
self.assertEqual(expected, nova_utils.meta_serialize(original))
def test_serialize_combined(self):
original = {
'test_key_1': 123,
'test_key_2': 'a string',
'test_key_3': {'a': 'b'},
'test_key_4': None,
}
expected = {
'test_key_1': '123',
'test_key_2': 'a string',
'test_key_3': '{"a": "b"}',
'test_key_4': 'null',
}
self.assertEqual(expected, nova_utils.meta_serialize(original))

View File

@@ -23,6 +23,7 @@ from heat.common import template_format
from heat.engine import parser
from heat.engine import resource
from heat.engine import scheduler
from heat.engine.resources import nova_utils
from heat.engine.resources import server as servers
from heat.openstack.common import uuidutils
from heat.openstack.common.gettextutils import _
@@ -491,7 +492,33 @@ class ServersTest(HeatTestCase):
new_meta = {'test': 123}
self.m.StubOutWithMock(self.fc.servers, 'set_meta')
self.fc.servers.set_meta(return_server,
new_meta).AndReturn(None)
nova_utils.meta_serialize(
new_meta)).AndReturn(None)
self.m.ReplayAll()
update_template = copy.deepcopy(server.t)
update_template['Properties']['metadata'] = new_meta
scheduler.TaskRunner(server.update, update_template)()
self.assertEqual((server.UPDATE, server.COMPLETE), server.state)
self.m.VerifyAll()
def test_server_update_nova_metadata_complex(self):
"""
Test that complex metadata values are correctly serialized
to JSON when sent to Nova.
"""
return_server = self.fc.servers.list()[1]
server = self._create_test_server(return_server,
'md_update')
new_meta = {'test': {'testkey': 'testvalue'}}
self.m.StubOutWithMock(self.fc.servers, 'set_meta')
# If we're going to call set_meta() directly we
# need to handle the serialization ourselves.
self.fc.servers.set_meta(return_server,
nova_utils.meta_serialize(
new_meta)).AndReturn(None)
self.m.ReplayAll()
update_template = copy.deepcopy(server.t)
update_template['Properties']['metadata'] = new_meta