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 <shardy@redhat.com>
This commit is contained in:
Steven Hardy 2012-08-21 11:52:24 +01:00
parent 1b6c2dad19
commit ecc5a408a3
4 changed files with 200 additions and 135 deletions

78
heat/api/aws/utils.py Normal file
View File

@ -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

View File

@ -21,10 +21,10 @@ import json
import os import os
import socket import socket
import sys import sys
import re
import urlparse import urlparse
import webob import webob
from heat.api.aws import exception from heat.api.aws import exception
from heat.api.aws import utils as api_utils
from heat.common import wsgi from heat.common import wsgi
from heat.common import config from heat.common import config
from heat.common import context from heat.common import context
@ -62,63 +62,6 @@ class StackController(object):
str(resp['StackId'])]) str(resp['StackId'])])
return resp 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): def list(self, req):
""" """
Implements ListStacks API action Implements ListStacks API action
@ -140,7 +83,7 @@ class StackController(object):
engine_api.STACK_TMPL_DESCRIPTION: 'TemplateDescription', 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 # AWS docs indicate DeletionTime is ommitted for current stacks
# This is still TODO in the engine, we don't keep data for # 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) res = {'StackSummaries': [format_stack_summary(s)
for s in stack_list['stacks']]} for s in stack_list['stacks']]}
return self._format_response('ListStacks', res) return api_utils.format_response('ListStacks', res)
def describe(self, req): def describe(self, req):
""" """
@ -179,7 +122,7 @@ class StackController(object):
engine_api.OUTPUT_VALUE: 'OutputValue', engine_api.OUTPUT_VALUE: 'OutputValue',
} }
return self._reformat_dict_keys(keymap, o) return api_utils.reformat_dict_keys(keymap, o)
def format_stack(s): def format_stack(s):
""" """
@ -200,7 +143,7 @@ class StackController(object):
engine_api.STACK_TIMEOUT: 'TimeoutInMinutes', 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 # Reformat outputs, these are handled separately as they are
# only present in the engine output for a completely created # 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']]} 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): def _get_template(self, req):
""" """
@ -310,7 +253,7 @@ class StackController(object):
con = req.context con = req.context
# Extract the stack input parameters # 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") # Extract any additional arguments ("Request Parameters")
create_args = extract_args(req.params) create_args = extract_args(req.params)
@ -340,7 +283,7 @@ class StackController(object):
except rpc_common.RemoteError as ex: except rpc_common.RemoteError as ex:
return exception.map_remote_error(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): def get_template(self, req):
""" """
@ -363,14 +306,15 @@ class StackController(object):
msg = _('stack not not found') msg = _('stack not not found')
return exception.HeatInvalidParameterValueError(detail=msg) 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): def estimate_template_cost(self, req):
""" """
Implements the EstimateTemplateCost API action Implements the EstimateTemplateCost API action
Get the estimated monthly cost of a template 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'}) {'Url': 'http://en.wikipedia.org/wiki/Gratis'})
def validate_template(self, req): def validate_template(self, req):
@ -423,9 +367,9 @@ class StackController(object):
return exception.map_remote_error(ex) return exception.map_remote_error(ex)
if res is None: if res is None:
return self._format_response('DeleteStack', '') return api_utils.format_response('DeleteStack', '')
else: else:
return self._format_response('DeleteStack', res['Error']) return api_utils.format_response('DeleteStack', res['Error'])
def events_list(self, req): def events_list(self, req):
""" """
@ -449,7 +393,7 @@ class StackController(object):
engine_api.EVENT_TIMESTAMP: 'Timestamp', 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) return self._stackid_addprefix(result)
@ -468,7 +412,7 @@ class StackController(object):
result = [format_stack_event(e) for e in events] result = [format_stack_event(e) for e in events]
return self._format_response('DescribeStackEvents', return api_utils.format_response('DescribeStackEvents',
{'StackEvents': result}) {'StackEvents': result})
def describe_stack_resource(self, req): def describe_stack_resource(self, req):
@ -494,7 +438,7 @@ class StackController(object):
engine_api.RES_STACK_NAME: 'StackName', 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) return self._stackid_addprefix(result)
@ -510,7 +454,7 @@ class StackController(object):
result = format_resource_detail(resource_details) result = format_resource_detail(resource_details)
return self._format_response('DescribeStackResource', return api_utils.format_response('DescribeStackResource',
{'StackResourceDetail': result}) {'StackResourceDetail': result})
def describe_stack_resources(self, req): def describe_stack_resources(self, req):
@ -546,7 +490,7 @@ class StackController(object):
engine_api.RES_UPDATED_TIME: 'Timestamp', 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) return self._stackid_addprefix(result)
@ -568,7 +512,7 @@ class StackController(object):
result = [format_stack_resource(r) for r in resources] result = [format_stack_resource(r) for r in resources]
return self._format_response('DescribeStackResources', return api_utils.format_response('DescribeStackResources',
{'StackResources': result}) {'StackResources': result})
def list_stack_resources(self, req): def list_stack_resources(self, req):
@ -589,7 +533,7 @@ class StackController(object):
engine_api.RES_TYPE: 'ResourceType', engine_api.RES_TYPE: 'ResourceType',
} }
return self._reformat_dict_keys(keymap, r) return api_utils.reformat_dict_keys(keymap, r)
con = req.context con = req.context
@ -601,7 +545,7 @@ class StackController(object):
summaries = [format_resource_summary(r) for r in resources] summaries = [format_resource_summary(r) for r in resources]
return self._format_response('ListStackResources', return api_utils.format_response('ListStackResources',
{'StackResourceSummaries': summaries}) {'StackResourceSummaries': summaries})

101
heat/tests/test_api_aws.py Normal file
View File

@ -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()

View File

@ -60,11 +60,6 @@ class StackControllerTest(unittest.TestCase):
return req return req
# The tests # 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): def test_stackid_addprefix(self):
# Stub socket.gethostname so it returns "ahostname" # Stub socket.gethostname so it returns "ahostname"
@ -79,59 +74,6 @@ class StackControllerTest(unittest.TestCase):
'StackId': 'ahostname:8000:stack/Foo/123'} 'StackId': 'ahostname:8000:stack/Foo/123'}
self.assert_(response == expected) 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): def test_list(self):
# Format a dummy GET request to pass into the WSGI handler # Format a dummy GET request to pass into the WSGI handler
params = {'Action': 'ListStacks'} params = {'Action': 'ListStacks'}