from heat import utils
from heat import rpc
import heat.rpc.common as rpc_common
+import heat.engine.api as engine_api
from heat.openstack.common import log as logging
# FIXME : further investigation into engine errors required
return exception.HeatInternalFailureError(detail=ex.value)
+ @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
Lists summary information for all stacks
"""
+
+ def format_stack_summary(s):
+ """
+ Reformat engine output into the AWS "StackSummary" format
+ """
+ # Map the engine-api format to the AWS StackSummary datatype
+ keymap = {
+ engine_api.STACK_CREATION_TIME: 'CreationTime',
+ engine_api.STACK_UPDATED_TIME: 'LastUpdatedTime',
+ engine_api.STACK_ID: 'StackId',
+ engine_api.STACK_NAME: 'StackName',
+ engine_api.STACK_STATUS: 'StackStatus',
+ engine_api.STACK_STATUS_DATA: 'StackStatusReason',
+ engine_api.STACK_TMPL_DESCRIPTION: 'TemplateDescription',
+ }
+
+ result = self._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
+ # stacks after they are deleted
+ if engine_api.STACK_DELETION_TIME in s:
+ result['DeletionTime'] = s[engine_api.STACK_DELETION_TIME]
+
+ return self._stackid_addprefix(result)
+
con = req.context
parms = dict(req.params)
- stack_list = rpc.call(con, 'engine',
- {'method': 'list_stacks',
- 'args': {'params': parms}})
+ try:
+ # Note show_stack returns details for all stacks when called with
+ # no stack_name, we only use a subset of the result here though
+ stack_list = rpc.call(con, 'engine',
+ {'method': 'show_stack',
+ 'args': {'stack_name': None,
+ 'params': parms}})
+ except rpc_common.RemoteError as ex:
+ return self._remote_error(ex)
- res = {'StackSummaries': []}
- if stack_list is not None:
- for s in stack_list['stacks']:
- res['StackSummaries'].append(self._stackid_addprefix(s))
+ res = {'StackSummaries': [format_stack_summary(s)
+ for s in stack_list['stacks']]}
return self._format_response('ListStacks', res)
Implements DescribeStacks API action
Gets detailed information for a stack (or all stacks)
"""
+ def format_stack_outputs(o):
+ keymap = {
+ engine_api.OUTPUT_DESCRIPTION: 'Description',
+ engine_api.OUTPUT_KEY: 'OutputKey',
+ engine_api.OUTPUT_VALUE: 'OutputValue',
+ }
+
+ return self._reformat_dict_keys(keymap, o)
+
+ def format_stack(s):
+ """
+ Reformat engine output into the AWS "StackSummary" format
+ """
+ keymap = {
+ engine_api.STACK_CAPABILITIES: 'Capabilities',
+ engine_api.STACK_CREATION_TIME: 'CreationTime',
+ engine_api.STACK_DESCRIPTION: 'Description',
+ engine_api.STACK_DISABLE_ROLLBACK: 'DisableRollback',
+ engine_api.STACK_UPDATED_TIME: 'LastUpdatedTime',
+ engine_api.STACK_NOTIFICATION_TOPICS: 'NotificationARNs',
+ engine_api.STACK_PARAMETERS: 'Parameters',
+ engine_api.STACK_ID: 'StackId',
+ engine_api.STACK_NAME: 'StackName',
+ engine_api.STACK_STATUS: 'StackStatus',
+ engine_api.STACK_STATUS_DATA: 'StackStatusReason',
+ engine_api.STACK_TIMEOUT: 'TimeoutInMinutes',
+ }
+
+ result = self._reformat_dict_keys(keymap, s)
+
+ # Reformat outputs, these are handled separately as they are
+ # only present in the engine output for a completely created
+ # stack
+ result['Outputs'] = []
+ if engine_api.STACK_OUTPUTS in s:
+ for o in s[engine_api.STACK_OUTPUTS]:
+ result['Outputs'].append(format_stack_outputs(o))
+
+ # Reformat Parameters dict-of-dict into AWS API format
+ # This is a list-of-dict with nasty "ParameterKey" : key
+ # "ParameterValue" : value format.
+ result['Parameters'] = [{'ParameterKey':k,
+ 'ParameterValue':v.get('Default')}
+ for (k, v) in result['Parameters'].items()]
+
+ return self._stackid_addprefix(result)
+
con = req.context
parms = dict(req.params)
except rpc_common.RemoteError as ex:
return self._remote_error(ex)
- res = {'Stacks': []}
- for s in stack_list['stacks']:
- # Reformat Parameters dict-of-dict into AWS API format
- # This is a list-of-dict with nasty "ParameterKey" : key
- # "ParameterValue" : value format.
- s['Parameters'] = [{'ParameterKey':k,
- 'ParameterValue':v.get('Default')}
- for (k, v) in s['Parameters'].items()]
- res['Stacks'].append(self._stackid_addprefix(s))
+ res = {'Stacks': [format_stack(s) for s in stack_list['stacks']]}
return self._format_response('DescribeStacks', res)
Implements CreateStack API action
Create stack as defined in template file
"""
+ def extract_args(params):
+ """
+ Extract request parameters/arguments and reformat them to match
+ the engine API. FIXME: we currently only support a subset of
+ the AWS defined parameters (both here and in the engine)
+ """
+ # TODO : Capabilities, DisableRollback, NotificationARNs
+ keymap = {'TimeoutInMinutes': engine_api.PARAM_TIMEOUT, }
+
+ result = {}
+ for k in keymap:
+ if k in req.params:
+ result[keymap[k]] = params[k]
+
+ return result
+
con = req.context
- parms = dict(req.params)
+
+ # Extract the stack input parameters
+ stack_parms = self._extract_user_params(req.params)
+
+ # Extract any additional arguments ("Request Parameters")
+ create_args = extract_args(req.params)
try:
templ = self._get_template(req)
{'method': 'create_stack',
'args': {'stack_name': req.params['StackName'],
'template': stack,
- 'params': parms}})
+ 'params': stack_parms,
+ 'args': create_args}})
except rpc_common.RemoteError as ex:
return self._remote_error(ex)
Implements the DescribeStackEvents API action
Returns events related to a specified stack (or all stacks)
"""
+ def format_stack_event(e):
+ """
+ Reformat engine output into the AWS "StackEvent" format
+ """
+ keymap = {
+ engine_api.EVENT_ID: 'EventId',
+ engine_api.EVENT_RES_NAME: 'LogicalResourceId',
+ engine_api.EVENT_RES_PHYSICAL_ID: 'PhysicalResourceId',
+ engine_api.EVENT_RES_PROPERTIES: 'ResourceProperties',
+ engine_api.EVENT_RES_STATUS: 'ResourceStatus',
+ engine_api.EVENT_RES_STATUS_DATA: 'ResourceStatusData',
+ engine_api.EVENT_RES_TYPE: 'ResourceType',
+ engine_api.EVENT_STACK_ID: 'StackId',
+ engine_api.EVENT_STACK_NAME: 'StackName',
+ engine_api.EVENT_TIMESTAMP: 'Timestamp',
+ }
+
+ result = self._reformat_dict_keys(keymap, e)
+
+ return self._stackid_addprefix(result)
+
con = req.context
parms = dict(req.params)
events = 'Error' not in event_res and event_res['events'] or []
- result = []
- for e in events:
- result.append(self._stackid_addprefix(e))
+ result = [format_stack_event(e) for e in events]
return self._format_response('DescribeStackEvents',
{'StackEvents': result})
Implements the DescribeStackResource API action
Return the details of the given resource belonging to the given stack.
"""
+
+ def format_resource_detail(r):
+ """
+ Reformat engine output into the AWS "StackResourceDetail" format
+ """
+ keymap = {
+ engine_api.RES_DESCRIPTION: 'Description',
+ engine_api.RES_UPDATED_TIME: 'LastUpdatedTimestamp',
+ engine_api.RES_NAME: 'LogicalResourceId',
+ engine_api.RES_METADATA: 'Metadata',
+ engine_api.RES_PHYSICAL_ID: 'PhysicalResourceId',
+ engine_api.RES_STATUS: 'ResourceStatus',
+ engine_api.RES_STATUS_DATA: 'ResourceStatusReason',
+ engine_api.RES_TYPE: 'ResourceType',
+ engine_api.RES_STACK_ID: 'StackId',
+ engine_api.RES_STACK_NAME: 'StackName',
+ }
+
+ result = self._reformat_dict_keys(keymap, r)
+
+ return self._stackid_addprefix(result)
+
con = req.context
args = {
'stack_name': req.params.get('StackName'),
except rpc_common.RemoteError as ex:
return self._remote_error(ex)
+ result = format_resource_detail(resource_details)
+
return self._format_response('DescribeStackResource',
- {'StackResourceDetail': resource_details})
+ {'StackResourceDetail': result})
def describe_stack_resources(self, req):
"""
`LogicalResourceId`: filter the resources list by the logical resource
id.
"""
+
+ def format_stack_resource(r):
+ """
+ Reformat engine output into the AWS "StackResource" format
+ """
+ keymap = {
+ engine_api.RES_DESCRIPTION: 'Description',
+ engine_api.RES_NAME: 'LogicalResourceId',
+ engine_api.RES_PHYSICAL_ID: 'PhysicalResourceId',
+ engine_api.RES_STATUS: 'ResourceStatus',
+ engine_api.RES_STATUS_DATA: 'ResourceStatusReason',
+ engine_api.RES_TYPE: 'ResourceType',
+ engine_api.RES_STACK_ID: 'StackId',
+ engine_api.RES_STACK_NAME: 'StackName',
+ engine_api.RES_UPDATED_TIME: 'Timestamp',
+ }
+
+ result = self._reformat_dict_keys(keymap, r)
+
+ return self._stackid_addprefix(result)
+
con = req.context
stack_name = req.params.get('StackName')
physical_resource_id = req.params.get('PhysicalResourceId')
except rpc_common.RemoteError as ex:
return self._remote_error(ex)
- result = []
- for r in resources:
- result.append(self._stackid_addprefix(r))
+ result = [format_stack_resource(r) for r in resources]
return self._format_response('DescribeStackResources',
{'StackResources': result})
Implements the ListStackResources API action
Return summary of the resources belonging to the specified stack.
"""
+ def format_resource_summary(r):
+ """
+ Reformat engine output into the AWS "StackResourceSummary" format
+ """
+ keymap = {
+ engine_api.RES_UPDATED_TIME: 'LastUpdatedTimestamp',
+ engine_api.RES_NAME: 'LogicalResourceId',
+ engine_api.RES_PHYSICAL_ID: 'PhysicalResourceId',
+ engine_api.RES_STATUS: 'ResourceStatus',
+ engine_api.RES_STATUS_DATA: 'ResourceStatusReason',
+ engine_api.RES_TYPE: 'ResourceType',
+ }
+
+ return self._reformat_dict_keys(keymap, r)
+
con = req.context
try:
except rpc_common.RemoteError as ex:
return self._remote_error(ex)
+ summaries = [format_resource_summary(r) for r in resources]
+
return self._format_response('ListStackResources',
- {'StackResourceSummaries': resources})
+ {'StackResourceSummaries': summaries})
def create_resource(options):
logger = logging.getLogger('heat.engine.manager')
-PARAM_KEYS = (
- PARAM_TIMEOUT,
- PARAM_USER_KEY_re,
- PARAM_USER_VALUE_fmt,
-) = (
- 'TimeoutInMinutes',
- re.compile(r'Parameters\.member\.(.*?)\.ParameterKey$'),
- 'Parameters.member.%s.ParameterValue',
-)
-
-
-def extract_user_params(params):
- '''
- Extract a dictionary of user parameters (to e.g. a stack create command)
- from the parameter dictionary passed through the API.
-
- In the API parameters, each user parameter appears as two key-value pairs
- with keys of the form:
-
- Parameters.member.1.ParameterKey
- Parameters.member.1.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())
+PARAM_KEYS = (PARAM_TIMEOUT, ) = ('timeout_mins', )
def extract_args(params):
'''
Extract any arguments passed as parameters through the API and return them
- as a dictionary.
+ as a dictionary. This allows us to filter the passed args and do type
+ conversion where appropriate
'''
kwargs = {}
try:
logger.exception('create timeout conversion')
else:
if timeout_mins > 0:
- kwargs['timeout_mins'] = timeout_mins
+ kwargs[PARAM_TIMEOUT] = timeout_mins
return kwargs
-def _filter_keys(data, keys):
- '''
- Filter the provided data so that only the dictionary keys specified are
- present. If keys is None, return all of the data.
- '''
- if keys is not None:
- data = dict((k, v) for (k, v) in data.iteritems() if k in keys)
-
- return data
-
-
STACK_KEYS = (
STACK_NAME, STACK_ID,
STACK_CREATION_TIME, STACK_UPDATED_TIME, STACK_DELETION_TIME,
STACK_NOTIFICATION_TOPICS,
STACK_DESCRIPTION, STACK_TMPL_DESCRIPTION,
STACK_PARAMETERS, STACK_OUTPUTS,
- STACK_STATUS, STACK_STATUS_DATA,
- STACK_TIMEOUT,
+ STACK_STATUS, STACK_STATUS_DATA, STACK_CAPABILITIES,
+ STACK_DISABLE_ROLLBACK, STACK_TIMEOUT,
) = (
- 'StackName', 'StackId',
- 'CreationTime', 'LastUpdatedTime', 'DeletionTime',
- 'NotificationARNs',
- 'Description', 'TemplateDescription',
- 'Parameters', 'Outputs',
- 'StackStatus', 'StackStatusReason',
- PARAM_TIMEOUT,
+ 'stack_name', 'stack_id',
+ 'creation_time', 'updated_time', 'deletion_time',
+ 'notification_topics',
+ 'description', 'template_description',
+ 'parameters', 'outputs',
+ 'stack_status', 'stack_status_reason', 'capabilities',
+ 'disable_rollback', 'timeout_mins'
)
-KEYS_STACK = (
- STACK_NAME, STACK_ID,
- STACK_CREATION_TIME, STACK_UPDATED_TIME,
- STACK_NOTIFICATION_TOPICS,
- STACK_DESCRIPTION,
- STACK_PARAMETERS, STACK_DESCRIPTION, STACK_OUTPUTS,
- STACK_STATUS, STACK_STATUS_DATA,
- STACK_TIMEOUT,
-)
-KEYS_STACK_SUMMARY = (
- STACK_CREATION_TIME, STACK_DELETION_TIME,
- STACK_UPDATED_TIME,
- STACK_ID, STACK_NAME,
- STACK_TMPL_DESCRIPTION,
- STACK_STATUS, STACK_STATUS_DATA,
-)
-
-
STACK_OUTPUT_KEYS = (
OUTPUT_DESCRIPTION,
OUTPUT_KEY, OUTPUT_VALUE,
) = (
- 'Description',
- 'OutputKey', 'OutputValue',
+ 'description',
+ 'output_key', 'output_value',
)
return [format_stack_output(key) for key in outputs]
-def format_stack(stack, keys=None):
+def format_stack(stack):
'''
Return a representation of the given stack that matches the API output
expectations.
STACK_TMPL_DESCRIPTION: stack.t[parser.DESCRIPTION],
STACK_STATUS: stack.state,
STACK_STATUS_DATA: stack.state_description,
+ STACK_CAPABILITIES: [], # TODO Not implemented yet
+ STACK_DISABLE_ROLLBACK: True, # TODO Not implemented yet
STACK_TIMEOUT: stack.timeout_mins,
}
if stack.state == stack.CREATE_COMPLETE:
info[STACK_OUTPUTS] = format_stack_outputs(stack, stack.outputs)
- return _filter_keys(info, keys)
+ return info
RES_KEYS = (
RES_NAME, RES_PHYSICAL_ID, RES_METADATA,
RES_STATUS, RES_STATUS_DATA, RES_TYPE,
RES_STACK_ID, RES_STACK_NAME,
- RES_TIMESTAMP,
) = (
- 'Description', 'LastUpdatedTimestamp',
- 'LogicalResourceId', 'PhysicalResourceId', 'Metadata',
- 'ResourceStatus', 'ResourceStatusReason', 'ResourceType',
+ 'description', 'updated_time',
+ 'logical_resource_id', 'physical_resource_id', 'metadata',
+ 'resource_status', 'resource_status_reason', 'resource_type',
STACK_ID, STACK_NAME,
- 'Timestamp',
-)
-
-KEYS_RESOURCE_DETAIL = (
- RES_DESCRIPTION, RES_UPDATED_TIME,
- RES_NAME, RES_PHYSICAL_ID, RES_METADATA,
- RES_STATUS, RES_STATUS_DATA, RES_TYPE,
- RES_STACK_ID, RES_STACK_NAME,
-)
-KEYS_RESOURCE = (
- RES_DESCRIPTION,
- RES_NAME, RES_PHYSICAL_ID,
- RES_STATUS, RES_STATUS_DATA, RES_TYPE,
- RES_STACK_ID, RES_STACK_NAME,
- RES_TIMESTAMP,
-)
-KEYS_RESOURCE_SUMMARY = (
- RES_UPDATED_TIME,
- RES_NAME, RES_PHYSICAL_ID,
- RES_STATUS, RES_STATUS_DATA, RES_TYPE,
)
-def format_stack_resource(resource, keys=None):
+def format_stack_resource(resource):
'''
Return a representation of the given resource that matches the API output
expectations.
'''
last_updated_time = resource.updated_time or resource.created_time
- attrs = {
+ res = {
RES_DESCRIPTION: resource.parsed_template().get('Description', ''),
RES_UPDATED_TIME: heat_utils.strtime(last_updated_time),
RES_NAME: resource.name,
RES_TYPE: resource.t['Type'],
RES_STACK_ID: resource.stack.id,
RES_STACK_NAME: resource.stack.name,
- RES_TIMESTAMP: heat_utils.strtime(last_updated_time),
}
- return _filter_keys(attrs, keys)
+ return res
EVENT_KEYS = (
EVENT_RES_STATUS, EVENT_RES_STATUS_DATA, EVENT_RES_TYPE,
EVENT_RES_PROPERTIES,
) = (
- 'EventId',
+ 'event_id',
STACK_ID, STACK_NAME,
- RES_TIMESTAMP,
+ "event_time",
RES_NAME, RES_PHYSICAL_ID,
RES_STATUS, RES_STATUS_DATA, RES_TYPE,
- 'ResourceProperties',
+ 'resource_properties',
)
-def format_event(event, keys=None):
+def format_event(event):
s = event.stack
- attrs = {
+ event = {
EVENT_ID: event.id,
EVENT_STACK_ID: s.id,
EVENT_STACK_NAME: s.name,
EVENT_RES_PROPERTIES: event.resource_properties,
}
- return _filter_keys(attrs, keys)
+ return event
"""Load configuration options and connect to the hypervisor."""
pass
- def list_stacks(self, context, params):
- """
- The list_stacks method is the end point that actually implements
- the 'list' command of the heat API.
- arg1 -> RPC context.
- arg2 -> Dict of http request parameters passed in from API side.
- """
-
- auth.authenticate(context)
-
- stacks = db_api.stack_get_by_user(context)
- if stacks is None:
- stacks = []
-
- def format_stack_summary(s):
- stack = parser.Stack.load(context, s.id)
- return api.format_stack(stack, api.KEYS_STACK_SUMMARY)
-
- return {'stacks': [format_stack_summary(s) for s in stacks]}
-
def show_stack(self, context, stack_name, params):
"""
The show_stack method returns the attributes of one stack.
def format_stack_detail(s):
stack = parser.Stack.load(context, s.id)
- return api.format_stack(stack, api.KEYS_STACK)
+ return api.format_stack(stack)
return {'stacks': [format_stack_detail(s) for s in stacks]}
- def create_stack(self, context, stack_name, template, params):
+ def create_stack(self, context, stack_name, template, params, args):
"""
The create_stack method creates a new stack using the template
provided.
arg1 -> RPC context.
arg2 -> Name of the stack you want to create.
arg3 -> Template of stack you want to create.
- arg4 -> Params passed from API.
+ arg4 -> Stack Input Params
+ arg4 -> Request parameters/args passed from API
"""
logger.info('template is %s' % template)
return {'Error': 'Stack already exists with that name.'}
tmpl = parser.Template(template)
+
# Extract the template parameters, and any common query parameters
- template_params = parser.Parameters(stack_name, tmpl,
- api.extract_user_params(params))
- common_params = api.extract_args(params)
+ template_params = parser.Parameters(stack_name, tmpl, params)
+ common_params = api.extract_args(args)
+
stack = parser.Stack(context, stack_name, tmpl, template_params,
**common_params)
stack_name = 'validate'
try:
tmpl = parser.Template(template)
- user_params = parser.Parameters(stack_name, tmpl,
- api.extract_user_params(params))
+ user_params = parser.Parameters(stack_name, tmpl, params)
s = parser.Stack(context, stack_name, tmpl, user_params)
except KeyError as ex:
res = ('A Fn::FindInMap operation referenced '
if resource.id is None:
raise AttributeError('Resource not created')
- return api.format_stack_resource(stack[resource_name],
- api.KEYS_RESOURCE_DETAIL)
+ return api.format_stack_resource(stack[resource_name])
def describe_stack_resources(self, context, stack_name,
physical_resource_id, logical_resource_id):
else:
name_match = lambda r: True
- return [api.format_stack_resource(resource, api.KEYS_RESOURCE)
+ return [api.format_stack_resource(resource)
for resource in stack if resource.id is not None and
name_match(resource)]
stack = parser.Stack.load(context, s.id)
- return [api.format_stack_resource(resource, api.KEYS_RESOURCE_SUMMARY)
+ return [api.format_stack_resource(resource)
for resource in stack if resource.id is not None]
def metadata_register_address(self, context, url):
--- /dev/null
+# 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 nose
+import unittest
+from nose.plugins.attrib import attr
+
+from heat.common.config import HeatConfigOpts
+import heat.api.v1.stacks as stacks
+
+
+@attr(tag=['unit', 'api-v1-stacks', 'StackController'])
+@attr(speed='fast')
+class StackControllerTest(unittest.TestCase):
+ '''
+ Tests the API class which acts as the WSGI controller,
+ the endpoint processing API requests after they are routed
+ '''
+ 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)
+
+ # TODO : lots more StackController tests..
+
+ def setUp(self):
+ # Create WSGI controller instance
+ options = HeatConfigOpts()
+ self.controller = stacks.StackController(options)
+ print "setup complete"
+
+ def tearDown(self):
+ print "teardown complete"
+
+
+if __name__ == '__main__':
+ sys.argv.append(__file__)
+ nose.main()
@attr(tag=['unit', 'engine-api'])
@attr(speed='fast')
class EngineApiTest(unittest.TestCase):
- 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.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.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.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.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.extract_user_params(p)
- self.assertFalse(params)
-
def test_timeout_extract(self):
- p = {'TimeoutInMinutes': '5'}
+ p = {'timeout_mins': '5'}
args = api.extract_args(p)
self.assertEqual(args['timeout_mins'], 5)
def test_timeout_extract_zero(self):
- p = {'TimeoutInMinutes': '0'}
+ p = {'timeout_mins': '0'}
args = api.extract_args(p)
self.assertTrue('timeout_mins' not in args)
def test_timeout_extract_garbage(self):
- p = {'TimeoutInMinutes': 'wibble'}
+ p = {'timeout_mins': 'wibble'}
args = api.extract_args(p)
self.assertTrue('timeout_mins' not in args)
def test_timeout_extract_none(self):
- p = {'TimeoutInMinutes': None}
+ p = {'timeout_mins': None}
args = api.extract_args(p)
self.assertTrue('timeout_mins' not in args)
self.assertEqual(len(events), 2)
for ev in events:
- self.assertTrue('EventId' in ev)
- self.assertTrue(ev['EventId'] > 0)
+ self.assertTrue('event_id' in ev)
+ self.assertTrue(ev['event_id'] > 0)
- self.assertTrue('LogicalResourceId' in ev)
- self.assertEqual(ev['LogicalResourceId'], 'WebServer')
+ self.assertTrue('logical_resource_id' in ev)
+ self.assertEqual(ev['logical_resource_id'], 'WebServer')
- self.assertTrue('PhysicalResourceId' in ev)
+ self.assertTrue('physical_resource_id' in ev)
- self.assertTrue('ResourceProperties' in ev)
+ self.assertTrue('resource_properties' in ev)
# Big long user data field.. it mentions 'wordpress'
# a few times so this should work.
- user_data = ev['ResourceProperties']['UserData']
+ user_data = ev['resource_properties']['UserData']
self.assertNotEqual(user_data.find('wordpress'), -1)
- self.assertEqual(ev['ResourceProperties']['ImageId'],
+ self.assertEqual(ev['resource_properties']['ImageId'],
'F16-x86_64-gold')
- self.assertEqual(ev['ResourceProperties']['InstanceType'],
+ self.assertEqual(ev['resource_properties']['InstanceType'],
'm1.large')
- self.assertTrue('ResourceStatus' in ev)
- self.assertTrue(ev['ResourceStatus'] in ('IN_PROGRESS',
+ self.assertTrue('resource_status' in ev)
+ self.assertTrue(ev['resource_status'] in ('IN_PROGRESS',
'CREATE_COMPLETE'))
- self.assertTrue('ResourceStatusReason' in ev)
- self.assertEqual(ev['ResourceStatusReason'], 'state changed')
+ self.assertTrue('resource_status_reason' in ev)
+ self.assertEqual(ev['resource_status_reason'], 'state changed')
- self.assertTrue('ResourceType' in ev)
- self.assertEqual(ev['ResourceType'], 'AWS::EC2::Instance')
+ self.assertTrue('resource_type' in ev)
+ self.assertEqual(ev['resource_type'], 'AWS::EC2::Instance')
- self.assertTrue('StackId' in ev)
+ self.assertTrue('stack_id' in ev)
- self.assertTrue('StackName' in ev)
- self.assertEqual(ev['StackName'], self.stack_name)
+ self.assertTrue('stack_name' in ev)
+ self.assertEqual(ev['stack_name'], self.stack_name)
- self.assertTrue('Timestamp' in ev)
-
- def test_stack_list(self):
- sl = self.man.list_stacks(self.ctx, {})
-
- self.assertTrue(len(sl['stacks']) > 0)
- for s in sl['stacks']:
- self.assertTrue('CreationTime' in s)
- self.assertTrue('LastUpdatedTime' in s)
- self.assertTrue('StackId' in s)
- self.assertNotEqual(s['StackId'], None)
- self.assertTrue('StackName' in s)
- self.assertTrue('StackStatus' in s)
- self.assertTrue('StackStatusReason' in s)
- self.assertTrue('TemplateDescription' in s)
- self.assertNotEqual(s['TemplateDescription'].find('WordPress'), -1)
+ self.assertTrue('event_time' in ev)
def test_stack_describe_all(self):
sl = self.man.show_stack(self.ctx, None, {})
self.assertEqual(len(sl['stacks']), 1)
for s in sl['stacks']:
- self.assertNotEqual(s['StackId'], None)
- self.assertNotEqual(s['Description'].find('WordPress'), -1)
+ self.assertNotEqual(s['stack_id'], None)
+ self.assertNotEqual(s['description'].find('WordPress'), -1)
def test_stack_describe_all_empty(self):
self.tearDown()
self.assertEqual(len(sl['stacks']), 1)
s = sl['stacks'][0]
- self.assertTrue('CreationTime' in s)
- self.assertTrue('LastUpdatedTime' in s)
- self.assertTrue('StackId' in s)
- self.assertNotEqual(s['StackId'], None)
- self.assertTrue('StackName' in s)
- self.assertEqual(s['StackName'], self.stack_name)
- self.assertTrue('StackStatus' in s)
- self.assertTrue('StackStatusReason' in s)
- self.assertTrue('Description' in s)
- self.assertNotEqual(s['Description'].find('WordPress'), -1)
- self.assertTrue('Parameters' in s)
+ self.assertTrue('creation_time' in s)
+ self.assertTrue('updated_time' in s)
+ self.assertTrue('stack_id' in s)
+ self.assertNotEqual(s['stack_id'], None)
+ self.assertTrue('stack_name' in s)
+ self.assertEqual(s['stack_name'], self.stack_name)
+ self.assertTrue('stack_status' in s)
+ self.assertTrue('stack_status_reason' in s)
+ self.assertTrue('description' in s)
+ self.assertNotEqual(s['description'].find('WordPress'), -1)
+ self.assertTrue('parameters' in s)
def test_stack_resource_describe(self):
r = self.man.describe_stack_resource(self.ctx, self.stack_name,
'WebServer')
- self.assertTrue('Description' in r)
- self.assertTrue('LastUpdatedTimestamp' in r)
- self.assertTrue('StackId' in r)
- self.assertNotEqual(r['StackId'], None)
- self.assertTrue('StackName' in r)
- self.assertEqual(r['StackName'], self.stack_name)
- self.assertTrue('Metadata' in r)
- self.assertTrue('ResourceStatus' in r)
- self.assertTrue('ResourceStatusReason' in r)
- self.assertTrue('ResourceType' in r)
- self.assertTrue('PhysicalResourceId' in r)
- self.assertTrue('LogicalResourceId' in r)
- self.assertEqual(r['LogicalResourceId'], 'WebServer')
+ self.assertTrue('description' in r)
+ self.assertTrue('updated_time' in r)
+ self.assertTrue('stack_id' in r)
+ self.assertNotEqual(r['stack_id'], None)
+ self.assertTrue('stack_name' in r)
+ self.assertEqual(r['stack_name'], self.stack_name)
+ self.assertTrue('metadata' in r)
+ self.assertTrue('resource_status' in r)
+ self.assertTrue('resource_status_reason' in r)
+ self.assertTrue('resource_type' in r)
+ self.assertTrue('physical_resource_id' in r)
+ self.assertTrue('logical_resource_id' in r)
+ self.assertEqual(r['logical_resource_id'], 'WebServer')
def test_stack_resource_describe_nonexist_stack(self):
self.assertRaises(AttributeError,
self.assertEqual(len(resources), 1)
r = resources[0]
- self.assertTrue('Description' in r)
- self.assertTrue('Timestamp' in r)
- self.assertTrue('StackId' in r)
- self.assertNotEqual(r['StackId'], None)
- self.assertTrue('StackName' in r)
- self.assertEqual(r['StackName'], self.stack_name)
- self.assertTrue('ResourceStatus' in r)
- self.assertTrue('ResourceStatusReason' in r)
- self.assertTrue('ResourceType' in r)
- self.assertTrue('PhysicalResourceId' in r)
- self.assertTrue('LogicalResourceId' in r)
- self.assertEqual(r['LogicalResourceId'], 'WebServer')
+ self.assertTrue('description' in r)
+ self.assertTrue('updated_time' in r)
+ self.assertTrue('stack_id' in r)
+ self.assertNotEqual(r['stack_id'], None)
+ self.assertTrue('stack_name' in r)
+ self.assertEqual(r['stack_name'], self.stack_name)
+ self.assertTrue('resource_status' in r)
+ self.assertTrue('resource_status_reason' in r)
+ self.assertTrue('resource_type' in r)
+ self.assertTrue('physical_resource_id' in r)
+ self.assertTrue('logical_resource_id' in r)
+ self.assertEqual(r['logical_resource_id'], 'WebServer')
def test_stack_resources_describe_no_filter(self):
resources = self.man.describe_stack_resources(self.ctx,
self.assertEqual(len(resources), 1)
r = resources[0]
- self.assertTrue('LogicalResourceId' in r)
- self.assertEqual(r['LogicalResourceId'], 'WebServer')
+ self.assertTrue('logical_resource_id' in r)
+ self.assertEqual(r['logical_resource_id'], 'WebServer')
def test_stack_resources_describe_bad_lookup(self):
self.assertRaises(AttributeError,
self.assertEqual(len(resources), 1)
r = resources[0]
- self.assertTrue('LastUpdatedTimestamp' in r)
- self.assertTrue('PhysicalResourceId' in r)
- self.assertTrue('LogicalResourceId' in r)
- self.assertEqual(r['LogicalResourceId'], 'WebServer')
- self.assertTrue('ResourceStatus' in r)
- self.assertTrue('ResourceStatusReason' in r)
- self.assertTrue('ResourceType' in r)
+ self.assertTrue('updated_time' in r)
+ self.assertTrue('physical_resource_id' in r)
+ self.assertTrue('logical_resource_id' in r)
+ self.assertEqual(r['logical_resource_id'], 'WebServer')
+ self.assertTrue('resource_status' in r)
+ self.assertTrue('resource_status_reason' in r)
+ self.assertTrue('resource_type' in r)
def test_stack_resources_list_nonexist_stack(self):
self.assertRaises(AttributeError,