summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEoghan Glynn <eglynn@redhat.com>2014-04-08 10:53:12 +0000
committerEoghan Glynn <eglynn@redhat.com>2014-04-08 16:14:18 +0100
commitcd13ec0f71c2745d8e740652ec4703962d491ba5 (patch)
treea8305abea0c40a61f67217cf0f94197e47f53aa2
parentfd2c558c0a3d54e67004443e249cbff47deb59f1 (diff)
downloadceilometer-2014.1.rc2.tar.gz
Allowed nested resource metadata in POST'd samples2014.1.rc2
Fixes bug 1302664 Previously, posting samples with nested metadata caused the mongo driver to fail on the embedded period in a metadata key. Now, we explicitly unwind the flattened resource metadata before publishing the sample. Change-Id: Ibb0980afc880218962328a9b7fe792015d58d1d2 (cherry picked from commit 2e8fa7c48fe4cca5674f895bd3985dc9243a9702)
-rw-r--r--ceilometer/api/controllers/v2.py3
-rw-r--r--ceilometer/tests/api/v2/test_post_samples_scenarios.py30
-rw-r--r--ceilometer/tests/test_utils.py35
-rw-r--r--ceilometer/utils.py14
4 files changed, 81 insertions, 1 deletions
diff --git a/ceilometer/api/controllers/v2.py b/ceilometer/api/controllers/v2.py
index 6785309c..beba57f1 100644
--- a/ceilometer/api/controllers/v2.py
+++ b/ceilometer/api/controllers/v2.py
@@ -870,7 +870,8 @@ class MeterController(rest.RestController):
project_id=s.project_id,
resource_id=s.resource_id,
timestamp=s.timestamp.isoformat(),
- resource_metadata=s.resource_metadata,
+ resource_metadata=utils.restore_nesting(s.resource_metadata,
+ separator='.'),
source=s.source)
published_samples.append(published_sample)
diff --git a/ceilometer/tests/api/v2/test_post_samples_scenarios.py b/ceilometer/tests/api/v2/test_post_samples_scenarios.py
index b9f13b7d..9d875dbc 100644
--- a/ceilometer/tests/api/v2/test_post_samples_scenarios.py
+++ b/ceilometer/tests/api/v2/test_post_samples_scenarios.py
@@ -69,6 +69,36 @@ class TestPostSamples(FunctionalTest,
self.assertEqual(s1, data.json)
self.assertEqual(s1[0], self.published[0][1]['args']['data'][0])
+ def test_nested_metadata(self):
+ s1 = [{'counter_name': 'apples',
+ 'counter_type': 'gauge',
+ 'counter_unit': 'instance',
+ 'counter_volume': 1,
+ 'resource_id': 'bd9431c1-8d69-4ad3-803a-8d4a6b89fd36',
+ 'project_id': '35b17138-b364-4e6a-a131-8f3099c5be68',
+ 'user_id': 'efd87807-12d2-4b38-9c70-5f5c2ac427ff',
+ 'resource_metadata': {'nest.name1': 'value1',
+ 'name2': 'value2',
+ 'nest.name2': 'value3'}}]
+
+ data = self.post_json('/meters/apples/', s1)
+
+ # timestamp not given so it is generated.
+ s1[0]['timestamp'] = data.json[0]['timestamp']
+ # Ignore message id that is randomly generated
+ s1[0]['message_id'] = data.json[0]['message_id']
+ # source is generated if not provided.
+ s1[0]['source'] = '%s:openstack' % s1[0]['project_id']
+
+ unwound = copy.copy(s1[0])
+ unwound['resource_metadata'] = {'nest': {'name1': 'value1',
+ 'name2': 'value3'},
+ 'name2': 'value2'}
+ # only the published sample should be unwound, not the representation
+ # in the API response
+ self.assertEqual(s1[0], data.json[0])
+ self.assertEqual(unwound, self.published[0][1]['args']['data'][0])
+
def test_invalid_counter_type(self):
s1 = [{'counter_name': 'my_counter_name',
'counter_type': 'INVALID_TYPE',
diff --git a/ceilometer/tests/test_utils.py b/ceilometer/tests/test_utils.py
index 91326d53..d847016b 100644
--- a/ceilometer/tests/test_utils.py
+++ b/ceilometer/tests/test_utils.py
@@ -78,6 +78,41 @@ class TestUtils(test.BaseTestCase):
pairs = list(utils.recursive_keypairs(data))
self.assertEqual(expected, pairs)
+ def test_restore_nesting_unested(self):
+ metadata = {'a': 'A', 'b': 'B'}
+ unwound = utils.restore_nesting(metadata)
+ self.assertIs(metadata, unwound)
+
+ def test_restore_nesting(self):
+ metadata = {'a': 'A', 'b': 'B',
+ 'nested:a': 'A',
+ 'nested:b': 'B',
+ 'nested:twice:c': 'C',
+ 'nested:twice:d': 'D',
+ 'embedded:e': 'E'}
+ unwound = utils.restore_nesting(metadata)
+ expected = {'a': 'A', 'b': 'B',
+ 'nested': {'a': 'A', 'b': 'B',
+ 'twice': {'c': 'C', 'd': 'D'}},
+ 'embedded': {'e': 'E'}}
+ self.assertEqual(expected, unwound)
+ self.assertIsNot(metadata, unwound)
+
+ def test_restore_nesting_with_separator(self):
+ metadata = {'a': 'A', 'b': 'B',
+ 'nested.a': 'A',
+ 'nested.b': 'B',
+ 'nested.twice.c': 'C',
+ 'nested.twice.d': 'D',
+ 'embedded.e': 'E'}
+ unwound = utils.restore_nesting(metadata, separator='.')
+ expected = {'a': 'A', 'b': 'B',
+ 'nested': {'a': 'A', 'b': 'B',
+ 'twice': {'c': 'C', 'd': 'D'}},
+ 'embedded': {'e': 'E'}}
+ self.assertEqual(expected, unwound)
+ self.assertIsNot(metadata, unwound)
+
def test_decimal_to_dt_with_none_parameter(self):
self.assertIsNone(utils.decimal_to_dt(None))
diff --git a/ceilometer/utils.py b/ceilometer/utils.py
index 61576427..23fc7f6a 100644
--- a/ceilometer/utils.py
+++ b/ceilometer/utils.py
@@ -54,6 +54,20 @@ def recursive_keypairs(d, separator=':'):
yield name, value
+def restore_nesting(d, separator=':'):
+ """Unwinds a flattened dict to restore nesting.
+ """
+ d = copy.copy(d) if any([separator in k for k in d.keys()]) else d
+ for k, v in d.items():
+ if separator in k:
+ top, rem = k.split(separator, 1)
+ nest = d[top] if isinstance(d.get(top), dict) else {}
+ nest[rem] = v
+ d[top] = restore_nesting(nest, separator)
+ del d[k]
+ return d
+
+
def dt_to_decimal(utc):
"""Datetime to Decimal.