From ecc5a408a33f83b428a77376000c0b6f409393cd Mon Sep 17 00:00:00 2001 From: Steven Hardy Date: Tue, 21 Aug 2012 11:52:24 +0100 Subject: [PATCH] heat API : Move aws api common code into aws/utils.py Move heat-api AWS common utility functions into a new utils.py, so these functions can be reused by cloudwatch Change-Id: I030d796b1048ffc4e7c40f7c8760121ab2854733 Signed-off-by: Steven Hardy --- heat/api/aws/utils.py | 78 ++++++++++++++++++++++++++++ heat/api/v1/stacks.py | 98 ++++++++--------------------------- heat/tests/test_api_aws.py | 101 +++++++++++++++++++++++++++++++++++++ heat/tests/test_api_v1.py | 58 --------------------- 4 files changed, 200 insertions(+), 135 deletions(-) create mode 100644 heat/api/aws/utils.py create mode 100644 heat/tests/test_api_aws.py diff --git a/heat/api/aws/utils.py b/heat/api/aws/utils.py new file mode 100644 index 00000000..e3b6cd3a --- /dev/null +++ b/heat/api/aws/utils.py @@ -0,0 +1,78 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +''' +Helper utilities related to the AWS API implementations +''' + +import re + + +def format_response(action, response): + """ + Format response from engine into API format + """ + return {'%sResponse' % action: {'%sResult' % action: response}} + + +def extract_user_params(params): + """ + Extract a dictionary of user input parameters for the stack + + In the AWS API parameters, each user parameter appears as two key-value + pairs with keys of the form below: + + Parameters.member.1.ParameterKey + Parameters.member.1.ParameterValue + + We reformat this into a normal dict here to match the heat + engine API expected format + + Note this implemented outside of "create" as it will also be + used by update (and EstimateTemplateCost if appropriate..) + """ + # Define the AWS key format to extract + PARAM_KEYS = ( + PARAM_USER_KEY_re, + PARAM_USER_VALUE_fmt, + ) = ( + re.compile(r'Parameters\.member\.(.*?)\.ParameterKey$'), + 'Parameters.member.%s.ParameterValue', + ) + + def get_param_pairs(): + for k in params: + keymatch = PARAM_USER_KEY_re.match(k) + if keymatch: + key = params[k] + v = PARAM_USER_VALUE_fmt % keymatch.group(1) + try: + value = params[v] + except KeyError: + logger.error('Could not apply parameter %s' % key) + + yield (key, value) + + return dict(get_param_pairs()) + + +def reformat_dict_keys(keymap={}, inputdict={}): + ''' + Utility function for mapping one dict format to another + ''' + result = {} + for key in keymap: + result[keymap[key]] = inputdict[key] + return result diff --git a/heat/api/v1/stacks.py b/heat/api/v1/stacks.py index 960b1387..d0b06b21 100644 --- a/heat/api/v1/stacks.py +++ b/heat/api/v1/stacks.py @@ -21,10 +21,10 @@ import json import os import socket import sys -import re import urlparse import webob from heat.api.aws import exception +from heat.api.aws import utils as api_utils from heat.common import wsgi from heat.common import config from heat.common import context @@ -62,63 +62,6 @@ class StackController(object): str(resp['StackId'])]) return resp - def _format_response(self, action, response): - """ - Format response from engine into API format - """ - return {'%sResponse' % action: {'%sResult' % action: response}} - - @staticmethod - def _extract_user_params(params): - """ - Extract a dictionary of user input parameters for the stack - - In the AWS API parameters, each user parameter appears as two key-value - pairs with keys of the form below: - - Parameters.member.1.ParameterKey - Parameters.member.1.ParameterValue - - We reformat this into a normal dict here to match the heat - engine API expected format - - Note this implemented outside of "create" as it will also be - used by update (and EstimateTemplateCost if appropriate..) - """ - # Define the AWS key format to extract - PARAM_KEYS = ( - PARAM_USER_KEY_re, - PARAM_USER_VALUE_fmt, - ) = ( - re.compile(r'Parameters\.member\.(.*?)\.ParameterKey$'), - 'Parameters.member.%s.ParameterValue', - ) - - def get_param_pairs(): - for k in params: - keymatch = PARAM_USER_KEY_re.match(k) - if keymatch: - key = params[k] - v = PARAM_USER_VALUE_fmt % keymatch.group(1) - try: - value = params[v] - except KeyError: - logger.error('Could not apply parameter %s' % key) - - yield (key, value) - - return dict(get_param_pairs()) - - @staticmethod - def _reformat_dict_keys(keymap={}, inputdict={}): - ''' - Utility function for mapping one dict format to another - ''' - result = {} - for key in keymap: - result[keymap[key]] = inputdict[key] - return result - def list(self, req): """ Implements ListStacks API action @@ -140,7 +83,7 @@ class StackController(object): engine_api.STACK_TMPL_DESCRIPTION: 'TemplateDescription', } - result = self._reformat_dict_keys(keymap, s) + result = api_utils.reformat_dict_keys(keymap, s) # AWS docs indicate DeletionTime is ommitted for current stacks # This is still TODO in the engine, we don't keep data for @@ -165,7 +108,7 @@ class StackController(object): res = {'StackSummaries': [format_stack_summary(s) for s in stack_list['stacks']]} - return self._format_response('ListStacks', res) + return api_utils.format_response('ListStacks', res) def describe(self, req): """ @@ -179,7 +122,7 @@ class StackController(object): engine_api.OUTPUT_VALUE: 'OutputValue', } - return self._reformat_dict_keys(keymap, o) + return api_utils.reformat_dict_keys(keymap, o) def format_stack(s): """ @@ -200,7 +143,7 @@ class StackController(object): engine_api.STACK_TIMEOUT: 'TimeoutInMinutes', } - result = self._reformat_dict_keys(keymap, s) + result = api_utils.reformat_dict_keys(keymap, s) # Reformat outputs, these are handled separately as they are # only present in the engine output for a completely created @@ -239,7 +182,7 @@ class StackController(object): res = {'Stacks': [format_stack(s) for s in stack_list['stacks']]} - return self._format_response('DescribeStacks', res) + return api_utils.format_response('DescribeStacks', res) def _get_template(self, req): """ @@ -310,7 +253,7 @@ class StackController(object): con = req.context # Extract the stack input parameters - stack_parms = self._extract_user_params(req.params) + stack_parms = api_utils.extract_user_params(req.params) # Extract any additional arguments ("Request Parameters") create_args = extract_args(req.params) @@ -340,7 +283,7 @@ class StackController(object): except rpc_common.RemoteError as ex: return exception.map_remote_error(ex) - return self._format_response(action, self._stackid_addprefix(res)) + return api_utils.format_response(action, self._stackid_addprefix(res)) def get_template(self, req): """ @@ -363,14 +306,15 @@ class StackController(object): msg = _('stack not not found') return exception.HeatInvalidParameterValueError(detail=msg) - return self._format_response('GetTemplate', {'TemplateBody': templ}) + return api_utils.format_response('GetTemplate', + {'TemplateBody': templ}) def estimate_template_cost(self, req): """ Implements the EstimateTemplateCost API action Get the estimated monthly cost of a template """ - return self._format_response('EstimateTemplateCost', + return api_utils.format_response('EstimateTemplateCost', {'Url': 'http://en.wikipedia.org/wiki/Gratis'}) def validate_template(self, req): @@ -423,9 +367,9 @@ class StackController(object): return exception.map_remote_error(ex) if res is None: - return self._format_response('DeleteStack', '') + return api_utils.format_response('DeleteStack', '') else: - return self._format_response('DeleteStack', res['Error']) + return api_utils.format_response('DeleteStack', res['Error']) def events_list(self, req): """ @@ -449,7 +393,7 @@ class StackController(object): engine_api.EVENT_TIMESTAMP: 'Timestamp', } - result = self._reformat_dict_keys(keymap, e) + result = api_utils.reformat_dict_keys(keymap, e) return self._stackid_addprefix(result) @@ -468,7 +412,7 @@ class StackController(object): result = [format_stack_event(e) for e in events] - return self._format_response('DescribeStackEvents', + return api_utils.format_response('DescribeStackEvents', {'StackEvents': result}) def describe_stack_resource(self, req): @@ -494,7 +438,7 @@ class StackController(object): engine_api.RES_STACK_NAME: 'StackName', } - result = self._reformat_dict_keys(keymap, r) + result = api_utils.reformat_dict_keys(keymap, r) return self._stackid_addprefix(result) @@ -510,7 +454,7 @@ class StackController(object): result = format_resource_detail(resource_details) - return self._format_response('DescribeStackResource', + return api_utils.format_response('DescribeStackResource', {'StackResourceDetail': result}) def describe_stack_resources(self, req): @@ -546,7 +490,7 @@ class StackController(object): engine_api.RES_UPDATED_TIME: 'Timestamp', } - result = self._reformat_dict_keys(keymap, r) + result = api_utils.reformat_dict_keys(keymap, r) return self._stackid_addprefix(result) @@ -568,7 +512,7 @@ class StackController(object): result = [format_stack_resource(r) for r in resources] - return self._format_response('DescribeStackResources', + return api_utils.format_response('DescribeStackResources', {'StackResources': result}) def list_stack_resources(self, req): @@ -589,7 +533,7 @@ class StackController(object): engine_api.RES_TYPE: 'ResourceType', } - return self._reformat_dict_keys(keymap, r) + return api_utils.reformat_dict_keys(keymap, r) con = req.context @@ -601,7 +545,7 @@ class StackController(object): summaries = [format_resource_summary(r) for r in resources] - return self._format_response('ListStackResources', + return api_utils.format_response('ListStackResources', {'StackResourceSummaries': summaries}) diff --git a/heat/tests/test_api_aws.py b/heat/tests/test_api_aws.py new file mode 100644 index 00000000..c23407c3 --- /dev/null +++ b/heat/tests/test_api_aws.py @@ -0,0 +1,101 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import sys +import socket +import nose +import json +import unittest +from nose.plugins.attrib import attr + +import re +from heat.api.aws import utils as api_utils + + +@attr(tag=['unit', 'api-aws', 'AWSCommon']) +@attr(speed='fast') +class AWSCommon(unittest.TestCase): + ''' + Tests the api/aws common componenents + ''' + # The tests + def test_format_response(self): + response = api_utils.format_response("Foo", "Bar") + expected = {'FooResponse': {'FooResult': 'Bar'}} + self.assert_(response == expected) + + def test_params_extract(self): + p = {'Parameters.member.Foo.ParameterKey': 'foo', + 'Parameters.member.Foo.ParameterValue': 'bar', + 'Parameters.member.Blarg.ParameterKey': 'blarg', + 'Parameters.member.Blarg.ParameterValue': 'wibble'} + params = api_utils.extract_user_params(p) + self.assertEqual(len(params), 2) + self.assertTrue('foo' in params) + self.assertEqual(params['foo'], 'bar') + self.assertTrue('blarg' in params) + self.assertEqual(params['blarg'], 'wibble') + + def test_params_extract_dots(self): + p = {'Parameters.member.Foo.Bar.ParameterKey': 'foo', + 'Parameters.member.Foo.Bar.ParameterValue': 'bar', + 'Parameters.member.Foo.Baz.ParameterKey': 'blarg', + 'Parameters.member.Foo.Baz.ParameterValue': 'wibble'} + params = api_utils.extract_user_params(p) + self.assertEqual(len(params), 2) + self.assertTrue('foo' in params) + self.assertEqual(params['foo'], 'bar') + self.assertTrue('blarg' in params) + self.assertEqual(params['blarg'], 'wibble') + + def test_params_extract_garbage(self): + p = {'Parameters.member.Foo.Bar.ParameterKey': 'foo', + 'Parameters.member.Foo.Bar.ParameterValue': 'bar', + 'Foo.Baz.ParameterKey': 'blarg', + 'Foo.Baz.ParameterValue': 'wibble'} + params = api_utils.extract_user_params(p) + self.assertEqual(len(params), 1) + self.assertTrue('foo' in params) + self.assertEqual(params['foo'], 'bar') + + def test_params_extract_garbage_prefix(self): + p = {'prefixParameters.member.Foo.Bar.ParameterKey': 'foo', + 'Parameters.member.Foo.Bar.ParameterValue': 'bar'} + params = api_utils.extract_user_params(p) + self.assertFalse(params) + + def test_params_extract_garbage_suffix(self): + p = {'Parameters.member.Foo.Bar.ParameterKeysuffix': 'foo', + 'Parameters.member.Foo.Bar.ParameterValue': 'bar'} + params = api_utils.extract_user_params(p) + self.assertFalse(params) + + def test_reformat_dict_keys(self): + keymap = {"foo": "bar"} + data = {"foo": 123} + expected = {"bar": 123} + result = api_utils.reformat_dict_keys(keymap, data) + self.assertEqual(result, expected) + + def setUp(self): + print "setup complete" + + def tearDown(self): + print "teardown complete" + + +if __name__ == '__main__': + sys.argv.append(__file__) + nose.main() diff --git a/heat/tests/test_api_v1.py b/heat/tests/test_api_v1.py index d7259093..218372da 100644 --- a/heat/tests/test_api_v1.py +++ b/heat/tests/test_api_v1.py @@ -60,11 +60,6 @@ class StackControllerTest(unittest.TestCase): return req # The tests - def test_format_response(self): - response = self.controller._format_response("Foo", "Bar") - expected = {'FooResponse': {'FooResult': 'Bar'}} - self.assert_(response == expected) - def test_stackid_addprefix(self): # Stub socket.gethostname so it returns "ahostname" @@ -79,59 +74,6 @@ class StackControllerTest(unittest.TestCase): 'StackId': 'ahostname:8000:stack/Foo/123'} self.assert_(response == expected) - def test_params_extract(self): - p = {'Parameters.member.Foo.ParameterKey': 'foo', - 'Parameters.member.Foo.ParameterValue': 'bar', - 'Parameters.member.Blarg.ParameterKey': 'blarg', - 'Parameters.member.Blarg.ParameterValue': 'wibble'} - params = self.controller._extract_user_params(p) - self.assertEqual(len(params), 2) - self.assertTrue('foo' in params) - self.assertEqual(params['foo'], 'bar') - self.assertTrue('blarg' in params) - self.assertEqual(params['blarg'], 'wibble') - - def test_params_extract_dots(self): - p = {'Parameters.member.Foo.Bar.ParameterKey': 'foo', - 'Parameters.member.Foo.Bar.ParameterValue': 'bar', - 'Parameters.member.Foo.Baz.ParameterKey': 'blarg', - 'Parameters.member.Foo.Baz.ParameterValue': 'wibble'} - params = self.controller._extract_user_params(p) - self.assertEqual(len(params), 2) - self.assertTrue('foo' in params) - self.assertEqual(params['foo'], 'bar') - self.assertTrue('blarg' in params) - self.assertEqual(params['blarg'], 'wibble') - - def test_params_extract_garbage(self): - p = {'Parameters.member.Foo.Bar.ParameterKey': 'foo', - 'Parameters.member.Foo.Bar.ParameterValue': 'bar', - 'Foo.Baz.ParameterKey': 'blarg', - 'Foo.Baz.ParameterValue': 'wibble'} - params = self.controller._extract_user_params(p) - self.assertEqual(len(params), 1) - self.assertTrue('foo' in params) - self.assertEqual(params['foo'], 'bar') - - def test_params_extract_garbage_prefix(self): - p = {'prefixParameters.member.Foo.Bar.ParameterKey': 'foo', - 'Parameters.member.Foo.Bar.ParameterValue': 'bar'} - params = self.controller._extract_user_params(p) - self.assertFalse(params) - - def test_params_extract_garbage_suffix(self): - p = {'Parameters.member.Foo.Bar.ParameterKeysuffix': 'foo', - 'Parameters.member.Foo.Bar.ParameterValue': 'bar'} - params = self.controller._extract_user_params(p) - self.assertFalse(params) - - def test_reformat_dict_keys(self): - keymap = {"foo": "bar"} - data = {"foo": 123} - expected = {"bar": 123} - result = self.controller._reformat_dict_keys(keymap, data) - self.assertEqual(result, expected) - def test_list(self): # Format a dummy GET request to pass into the WSGI handler params = {'Action': 'ListStacks'} -- 2.45.2