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:
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user