heat engine/API : Internal API rework

Refactor engine-api code so that AWS specific
details like key names are handled in the API
Fixes #172

Change-Id: I4c5b153557216c03e5a98193e54cf75e3c7b97dd
Signed-off-by: Steven Hardy <shardy@redhat.com>
This commit is contained in:
Steven Hardy 2012-07-10 18:13:23 +01:00
parent 90e22dabde
commit d06c4ce416
6 changed files with 452 additions and 303 deletions

View File

@ -31,6 +31,7 @@ from heat.common import context
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
@ -79,22 +80,103 @@ class StackController(object):
# 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)
@ -103,6 +185,53 @@ class StackController(object):
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)
@ -122,15 +251,7 @@ class StackController(object):
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)
@ -165,8 +286,29 @@ class StackController(object):
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)
@ -189,7 +331,8 @@ class StackController(object):
{'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)
@ -288,6 +431,27 @@ class StackController(object):
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)
@ -302,9 +466,7 @@ class StackController(object):
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})
@ -314,6 +476,28 @@ class StackController(object):
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'),
@ -328,8 +512,10 @@ class StackController(object):
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):
"""
@ -347,6 +533,27 @@ class StackController(object):
`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')
@ -368,9 +575,7 @@ class StackController(object):
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})
@ -380,6 +585,21 @@ class StackController(object):
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:
@ -390,8 +610,10 @@ class StackController(object):
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):

View File

@ -20,48 +20,14 @@ from heat.openstack.common import log as logging
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:
@ -70,63 +36,34 @@ def extract_args(params):
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',
)
@ -144,7 +81,7 @@ def format_stack_outputs(stack, outputs):
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.
@ -160,6 +97,8 @@ def format_stack(stack, keys=None):
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,
}
@ -167,7 +106,7 @@ def format_stack(stack, keys=None):
if stack.state == stack.CREATE_COMPLETE:
info[STACK_OUTPUTS] = format_stack_outputs(stack, stack.outputs)
return _filter_keys(info, keys)
return info
RES_KEYS = (
@ -175,42 +114,21 @@ 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,
@ -221,10 +139,9 @@ def format_stack_resource(resource, keys=None):
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 = (
@ -235,18 +152,18 @@ 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,
@ -259,4 +176,4 @@ def format_event(event, keys=None):
EVENT_RES_PROPERTIES: event.resource_properties,
}
return _filter_keys(attrs, keys)
return event

View File

@ -60,26 +60,6 @@ class EngineManager(manager.Manager):
"""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.
@ -100,11 +80,11 @@ class EngineManager(manager.Manager):
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.
@ -113,7 +93,8 @@ class EngineManager(manager.Manager):
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)
@ -123,10 +104,11 @@ class EngineManager(manager.Manager):
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)
@ -159,8 +141,7 @@ class EngineManager(manager.Manager):
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 '
@ -272,8 +253,7 @@ class EngineManager(manager.Manager):
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):
@ -299,7 +279,7 @@ class EngineManager(manager.Manager):
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)]
@ -312,7 +292,7 @@ class EngineManager(manager.Manager):
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):

91
heat/tests/test_api_v1.py Normal file
View File

@ -0,0 +1,91 @@
# 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()

View File

@ -23,69 +23,23 @@ import heat.engine.api as api
@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)

View File

@ -172,63 +172,48 @@ class stackManagerTest(unittest.TestCase):
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()
@ -250,35 +235,35 @@ class stackManagerTest(unittest.TestCase):
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,
@ -297,18 +282,18 @@ class stackManagerTest(unittest.TestCase):
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,
@ -317,8 +302,8 @@ class stackManagerTest(unittest.TestCase):
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,
@ -340,13 +325,13 @@ class stackManagerTest(unittest.TestCase):
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,