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 utils
from heat import rpc from heat import rpc
import heat.rpc.common as rpc_common import heat.rpc.common as rpc_common
import heat.engine.api as engine_api
from heat.openstack.common import log as logging from heat.openstack.common import log as logging
@ -79,22 +80,103 @@ class StackController(object):
# FIXME : further investigation into engine errors required # FIXME : further investigation into engine errors required
return exception.HeatInternalFailureError(detail=ex.value) 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): def list(self, req):
""" """
Implements ListStacks API action Implements ListStacks API action
Lists summary information for all stacks 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 con = req.context
parms = dict(req.params) parms = dict(req.params)
stack_list = rpc.call(con, 'engine', try:
{'method': 'list_stacks', # Note show_stack returns details for all stacks when called with
'args': {'params': parms}}) # 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': []} res = {'StackSummaries': [format_stack_summary(s)
if stack_list is not None: for s in stack_list['stacks']]}
for s in stack_list['stacks']:
res['StackSummaries'].append(self._stackid_addprefix(s))
return self._format_response('ListStacks', res) return self._format_response('ListStacks', res)
@ -103,6 +185,53 @@ class StackController(object):
Implements DescribeStacks API action Implements DescribeStacks API action
Gets detailed information for a stack (or all stacks) 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 con = req.context
parms = dict(req.params) parms = dict(req.params)
@ -122,15 +251,7 @@ class StackController(object):
except rpc_common.RemoteError as ex: except rpc_common.RemoteError as ex:
return self._remote_error(ex) return self._remote_error(ex)
res = {'Stacks': []} res = {'Stacks': [format_stack(s) for s in stack_list['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))
return self._format_response('DescribeStacks', res) return self._format_response('DescribeStacks', res)
@ -165,8 +286,29 @@ class StackController(object):
Implements CreateStack API action Implements CreateStack API action
Create stack as defined in template file 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 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: try:
templ = self._get_template(req) templ = self._get_template(req)
@ -189,7 +331,8 @@ class StackController(object):
{'method': 'create_stack', {'method': 'create_stack',
'args': {'stack_name': req.params['StackName'], 'args': {'stack_name': req.params['StackName'],
'template': stack, 'template': stack,
'params': parms}}) 'params': stack_parms,
'args': create_args}})
except rpc_common.RemoteError as ex: except rpc_common.RemoteError as ex:
return self._remote_error(ex) return self._remote_error(ex)
@ -288,6 +431,27 @@ class StackController(object):
Implements the DescribeStackEvents API action Implements the DescribeStackEvents API action
Returns events related to a specified stack (or all stacks) 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 con = req.context
parms = dict(req.params) parms = dict(req.params)
@ -302,9 +466,7 @@ class StackController(object):
events = 'Error' not in event_res and event_res['events'] or [] events = 'Error' not in event_res and event_res['events'] or []
result = [] result = [format_stack_event(e) for e in events]
for e in events:
result.append(self._stackid_addprefix(e))
return self._format_response('DescribeStackEvents', return self._format_response('DescribeStackEvents',
{'StackEvents': result}) {'StackEvents': result})
@ -314,6 +476,28 @@ class StackController(object):
Implements the DescribeStackResource API action Implements the DescribeStackResource API action
Return the details of the given resource belonging to the given stack. 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 con = req.context
args = { args = {
'stack_name': req.params.get('StackName'), 'stack_name': req.params.get('StackName'),
@ -328,8 +512,10 @@ class StackController(object):
except rpc_common.RemoteError as ex: except rpc_common.RemoteError as ex:
return self._remote_error(ex) return self._remote_error(ex)
result = format_resource_detail(resource_details)
return self._format_response('DescribeStackResource', return self._format_response('DescribeStackResource',
{'StackResourceDetail': resource_details}) {'StackResourceDetail': result})
def describe_stack_resources(self, req): def describe_stack_resources(self, req):
""" """
@ -347,6 +533,27 @@ class StackController(object):
`LogicalResourceId`: filter the resources list by the logical resource `LogicalResourceId`: filter the resources list by the logical resource
id. 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 con = req.context
stack_name = req.params.get('StackName') stack_name = req.params.get('StackName')
physical_resource_id = req.params.get('PhysicalResourceId') physical_resource_id = req.params.get('PhysicalResourceId')
@ -368,9 +575,7 @@ class StackController(object):
except rpc_common.RemoteError as ex: except rpc_common.RemoteError as ex:
return self._remote_error(ex) return self._remote_error(ex)
result = [] result = [format_stack_resource(r) for r in resources]
for r in resources:
result.append(self._stackid_addprefix(r))
return self._format_response('DescribeStackResources', return self._format_response('DescribeStackResources',
{'StackResources': result}) {'StackResources': result})
@ -380,6 +585,21 @@ class StackController(object):
Implements the ListStackResources API action Implements the ListStackResources API action
Return summary of the resources belonging to the specified stack. 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 con = req.context
try: try:
@ -390,8 +610,10 @@ class StackController(object):
except rpc_common.RemoteError as ex: except rpc_common.RemoteError as ex:
return self._remote_error(ex) return self._remote_error(ex)
summaries = [format_resource_summary(r) for r in resources]
return self._format_response('ListStackResources', return self._format_response('ListStackResources',
{'StackResourceSummaries': resources}) {'StackResourceSummaries': summaries})
def create_resource(options): def create_resource(options):

View File

@ -20,48 +20,14 @@ from heat.openstack.common import log as logging
logger = logging.getLogger('heat.engine.manager') logger = logging.getLogger('heat.engine.manager')
PARAM_KEYS = ( PARAM_KEYS = (PARAM_TIMEOUT, ) = ('timeout_mins', )
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())
def extract_args(params): def extract_args(params):
''' '''
Extract any arguments passed as parameters through the API and return them 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 = {} kwargs = {}
try: try:
@ -70,63 +36,34 @@ def extract_args(params):
logger.exception('create timeout conversion') logger.exception('create timeout conversion')
else: else:
if timeout_mins > 0: if timeout_mins > 0:
kwargs['timeout_mins'] = timeout_mins kwargs[PARAM_TIMEOUT] = timeout_mins
return kwargs 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_KEYS = (
STACK_NAME, STACK_ID, STACK_NAME, STACK_ID,
STACK_CREATION_TIME, STACK_UPDATED_TIME, STACK_DELETION_TIME, STACK_CREATION_TIME, STACK_UPDATED_TIME, STACK_DELETION_TIME,
STACK_NOTIFICATION_TOPICS, STACK_NOTIFICATION_TOPICS,
STACK_DESCRIPTION, STACK_TMPL_DESCRIPTION, STACK_DESCRIPTION, STACK_TMPL_DESCRIPTION,
STACK_PARAMETERS, STACK_OUTPUTS, STACK_PARAMETERS, STACK_OUTPUTS,
STACK_STATUS, STACK_STATUS_DATA, STACK_STATUS, STACK_STATUS_DATA, STACK_CAPABILITIES,
STACK_TIMEOUT, STACK_DISABLE_ROLLBACK, STACK_TIMEOUT,
) = ( ) = (
'StackName', 'StackId', 'stack_name', 'stack_id',
'CreationTime', 'LastUpdatedTime', 'DeletionTime', 'creation_time', 'updated_time', 'deletion_time',
'NotificationARNs', 'notification_topics',
'Description', 'TemplateDescription', 'description', 'template_description',
'Parameters', 'Outputs', 'parameters', 'outputs',
'StackStatus', 'StackStatusReason', 'stack_status', 'stack_status_reason', 'capabilities',
PARAM_TIMEOUT, '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 = ( STACK_OUTPUT_KEYS = (
OUTPUT_DESCRIPTION, OUTPUT_DESCRIPTION,
OUTPUT_KEY, OUTPUT_VALUE, OUTPUT_KEY, OUTPUT_VALUE,
) = ( ) = (
'Description', 'description',
'OutputKey', 'OutputValue', 'output_key', 'output_value',
) )
@ -144,7 +81,7 @@ def format_stack_outputs(stack, outputs):
return [format_stack_output(key) for key in 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 Return a representation of the given stack that matches the API output
expectations. expectations.
@ -160,6 +97,8 @@ def format_stack(stack, keys=None):
STACK_TMPL_DESCRIPTION: stack.t[parser.DESCRIPTION], STACK_TMPL_DESCRIPTION: stack.t[parser.DESCRIPTION],
STACK_STATUS: stack.state, STACK_STATUS: stack.state,
STACK_STATUS_DATA: stack.state_description, 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, STACK_TIMEOUT: stack.timeout_mins,
} }
@ -167,7 +106,7 @@ def format_stack(stack, keys=None):
if stack.state == stack.CREATE_COMPLETE: if stack.state == stack.CREATE_COMPLETE:
info[STACK_OUTPUTS] = format_stack_outputs(stack, stack.outputs) info[STACK_OUTPUTS] = format_stack_outputs(stack, stack.outputs)
return _filter_keys(info, keys) return info
RES_KEYS = ( RES_KEYS = (
@ -175,42 +114,21 @@ RES_KEYS = (
RES_NAME, RES_PHYSICAL_ID, RES_METADATA, RES_NAME, RES_PHYSICAL_ID, RES_METADATA,
RES_STATUS, RES_STATUS_DATA, RES_TYPE, RES_STATUS, RES_STATUS_DATA, RES_TYPE,
RES_STACK_ID, RES_STACK_NAME, RES_STACK_ID, RES_STACK_NAME,
RES_TIMESTAMP,
) = ( ) = (
'Description', 'LastUpdatedTimestamp', 'description', 'updated_time',
'LogicalResourceId', 'PhysicalResourceId', 'Metadata', 'logical_resource_id', 'physical_resource_id', 'metadata',
'ResourceStatus', 'ResourceStatusReason', 'ResourceType', 'resource_status', 'resource_status_reason', 'resource_type',
STACK_ID, STACK_NAME, 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 Return a representation of the given resource that matches the API output
expectations. expectations.
''' '''
last_updated_time = resource.updated_time or resource.created_time last_updated_time = resource.updated_time or resource.created_time
attrs = { res = {
RES_DESCRIPTION: resource.parsed_template().get('Description', ''), RES_DESCRIPTION: resource.parsed_template().get('Description', ''),
RES_UPDATED_TIME: heat_utils.strtime(last_updated_time), RES_UPDATED_TIME: heat_utils.strtime(last_updated_time),
RES_NAME: resource.name, RES_NAME: resource.name,
@ -221,10 +139,9 @@ def format_stack_resource(resource, keys=None):
RES_TYPE: resource.t['Type'], RES_TYPE: resource.t['Type'],
RES_STACK_ID: resource.stack.id, RES_STACK_ID: resource.stack.id,
RES_STACK_NAME: resource.stack.name, RES_STACK_NAME: resource.stack.name,
RES_TIMESTAMP: heat_utils.strtime(last_updated_time),
} }
return _filter_keys(attrs, keys) return res
EVENT_KEYS = ( EVENT_KEYS = (
@ -235,18 +152,18 @@ EVENT_KEYS = (
EVENT_RES_STATUS, EVENT_RES_STATUS_DATA, EVENT_RES_TYPE, EVENT_RES_STATUS, EVENT_RES_STATUS_DATA, EVENT_RES_TYPE,
EVENT_RES_PROPERTIES, EVENT_RES_PROPERTIES,
) = ( ) = (
'EventId', 'event_id',
STACK_ID, STACK_NAME, STACK_ID, STACK_NAME,
RES_TIMESTAMP, "event_time",
RES_NAME, RES_PHYSICAL_ID, RES_NAME, RES_PHYSICAL_ID,
RES_STATUS, RES_STATUS_DATA, RES_TYPE, RES_STATUS, RES_STATUS_DATA, RES_TYPE,
'ResourceProperties', 'resource_properties',
) )
def format_event(event, keys=None): def format_event(event):
s = event.stack s = event.stack
attrs = { event = {
EVENT_ID: event.id, EVENT_ID: event.id,
EVENT_STACK_ID: s.id, EVENT_STACK_ID: s.id,
EVENT_STACK_NAME: s.name, EVENT_STACK_NAME: s.name,
@ -259,4 +176,4 @@ def format_event(event, keys=None):
EVENT_RES_PROPERTIES: event.resource_properties, 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.""" """Load configuration options and connect to the hypervisor."""
pass 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): def show_stack(self, context, stack_name, params):
""" """
The show_stack method returns the attributes of one stack. The show_stack method returns the attributes of one stack.
@ -100,11 +80,11 @@ class EngineManager(manager.Manager):
def format_stack_detail(s): def format_stack_detail(s):
stack = parser.Stack.load(context, s.id) 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]} 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 The create_stack method creates a new stack using the template
provided. provided.
@ -113,7 +93,8 @@ class EngineManager(manager.Manager):
arg1 -> RPC context. arg1 -> RPC context.
arg2 -> Name of the stack you want to create. arg2 -> Name of the stack you want to create.
arg3 -> Template of 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) logger.info('template is %s' % template)
@ -123,10 +104,11 @@ class EngineManager(manager.Manager):
return {'Error': 'Stack already exists with that name.'} return {'Error': 'Stack already exists with that name.'}
tmpl = parser.Template(template) tmpl = parser.Template(template)
# Extract the template parameters, and any common query parameters # Extract the template parameters, and any common query parameters
template_params = parser.Parameters(stack_name, tmpl, template_params = parser.Parameters(stack_name, tmpl, params)
api.extract_user_params(params)) common_params = api.extract_args(args)
common_params = api.extract_args(params)
stack = parser.Stack(context, stack_name, tmpl, template_params, stack = parser.Stack(context, stack_name, tmpl, template_params,
**common_params) **common_params)
@ -159,8 +141,7 @@ class EngineManager(manager.Manager):
stack_name = 'validate' stack_name = 'validate'
try: try:
tmpl = parser.Template(template) tmpl = parser.Template(template)
user_params = parser.Parameters(stack_name, tmpl, user_params = parser.Parameters(stack_name, tmpl, params)
api.extract_user_params(params))
s = parser.Stack(context, stack_name, tmpl, user_params) s = parser.Stack(context, stack_name, tmpl, user_params)
except KeyError as ex: except KeyError as ex:
res = ('A Fn::FindInMap operation referenced ' res = ('A Fn::FindInMap operation referenced '
@ -272,8 +253,7 @@ class EngineManager(manager.Manager):
if resource.id is None: if resource.id is None:
raise AttributeError('Resource not created') raise AttributeError('Resource not created')
return api.format_stack_resource(stack[resource_name], return api.format_stack_resource(stack[resource_name])
api.KEYS_RESOURCE_DETAIL)
def describe_stack_resources(self, context, stack_name, def describe_stack_resources(self, context, stack_name,
physical_resource_id, logical_resource_id): physical_resource_id, logical_resource_id):
@ -299,7 +279,7 @@ class EngineManager(manager.Manager):
else: else:
name_match = lambda r: True 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 for resource in stack if resource.id is not None and
name_match(resource)] name_match(resource)]
@ -312,7 +292,7 @@ class EngineManager(manager.Manager):
stack = parser.Stack.load(context, s.id) 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] for resource in stack if resource.id is not None]
def metadata_register_address(self, context, url): 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(tag=['unit', 'engine-api'])
@attr(speed='fast') @attr(speed='fast')
class EngineApiTest(unittest.TestCase): 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): def test_timeout_extract(self):
p = {'TimeoutInMinutes': '5'} p = {'timeout_mins': '5'}
args = api.extract_args(p) args = api.extract_args(p)
self.assertEqual(args['timeout_mins'], 5) self.assertEqual(args['timeout_mins'], 5)
def test_timeout_extract_zero(self): def test_timeout_extract_zero(self):
p = {'TimeoutInMinutes': '0'} p = {'timeout_mins': '0'}
args = api.extract_args(p) args = api.extract_args(p)
self.assertTrue('timeout_mins' not in args) self.assertTrue('timeout_mins' not in args)
def test_timeout_extract_garbage(self): def test_timeout_extract_garbage(self):
p = {'TimeoutInMinutes': 'wibble'} p = {'timeout_mins': 'wibble'}
args = api.extract_args(p) args = api.extract_args(p)
self.assertTrue('timeout_mins' not in args) self.assertTrue('timeout_mins' not in args)
def test_timeout_extract_none(self): def test_timeout_extract_none(self):
p = {'TimeoutInMinutes': None} p = {'timeout_mins': None}
args = api.extract_args(p) args = api.extract_args(p)
self.assertTrue('timeout_mins' not in args) self.assertTrue('timeout_mins' not in args)

View File

@ -172,63 +172,48 @@ class stackManagerTest(unittest.TestCase):
self.assertEqual(len(events), 2) self.assertEqual(len(events), 2)
for ev in events: for ev in events:
self.assertTrue('EventId' in ev) self.assertTrue('event_id' in ev)
self.assertTrue(ev['EventId'] > 0) self.assertTrue(ev['event_id'] > 0)
self.assertTrue('LogicalResourceId' in ev) self.assertTrue('logical_resource_id' in ev)
self.assertEqual(ev['LogicalResourceId'], 'WebServer') 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' # Big long user data field.. it mentions 'wordpress'
# a few times so this should work. # 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.assertNotEqual(user_data.find('wordpress'), -1)
self.assertEqual(ev['ResourceProperties']['ImageId'], self.assertEqual(ev['resource_properties']['ImageId'],
'F16-x86_64-gold') 'F16-x86_64-gold')
self.assertEqual(ev['ResourceProperties']['InstanceType'], self.assertEqual(ev['resource_properties']['InstanceType'],
'm1.large') 'm1.large')
self.assertTrue('ResourceStatus' in ev) self.assertTrue('resource_status' in ev)
self.assertTrue(ev['ResourceStatus'] in ('IN_PROGRESS', self.assertTrue(ev['resource_status'] in ('IN_PROGRESS',
'CREATE_COMPLETE')) 'CREATE_COMPLETE'))
self.assertTrue('ResourceStatusReason' in ev) self.assertTrue('resource_status_reason' in ev)
self.assertEqual(ev['ResourceStatusReason'], 'state changed') self.assertEqual(ev['resource_status_reason'], 'state changed')
self.assertTrue('ResourceType' in ev) self.assertTrue('resource_type' in ev)
self.assertEqual(ev['ResourceType'], 'AWS::EC2::Instance') 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.assertTrue('stack_name' in ev)
self.assertEqual(ev['StackName'], self.stack_name) self.assertEqual(ev['stack_name'], self.stack_name)
self.assertTrue('Timestamp' in ev) self.assertTrue('event_time' 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)
def test_stack_describe_all(self): def test_stack_describe_all(self):
sl = self.man.show_stack(self.ctx, None, {}) sl = self.man.show_stack(self.ctx, None, {})
self.assertEqual(len(sl['stacks']), 1) self.assertEqual(len(sl['stacks']), 1)
for s in sl['stacks']: for s in sl['stacks']:
self.assertNotEqual(s['StackId'], None) self.assertNotEqual(s['stack_id'], None)
self.assertNotEqual(s['Description'].find('WordPress'), -1) self.assertNotEqual(s['description'].find('WordPress'), -1)
def test_stack_describe_all_empty(self): def test_stack_describe_all_empty(self):
self.tearDown() self.tearDown()
@ -250,35 +235,35 @@ class stackManagerTest(unittest.TestCase):
self.assertEqual(len(sl['stacks']), 1) self.assertEqual(len(sl['stacks']), 1)
s = sl['stacks'][0] s = sl['stacks'][0]
self.assertTrue('CreationTime' in s) self.assertTrue('creation_time' in s)
self.assertTrue('LastUpdatedTime' in s) self.assertTrue('updated_time' in s)
self.assertTrue('StackId' in s) self.assertTrue('stack_id' in s)
self.assertNotEqual(s['StackId'], None) self.assertNotEqual(s['stack_id'], None)
self.assertTrue('StackName' in s) self.assertTrue('stack_name' in s)
self.assertEqual(s['StackName'], self.stack_name) self.assertEqual(s['stack_name'], self.stack_name)
self.assertTrue('StackStatus' in s) self.assertTrue('stack_status' in s)
self.assertTrue('StackStatusReason' in s) self.assertTrue('stack_status_reason' in s)
self.assertTrue('Description' in s) self.assertTrue('description' in s)
self.assertNotEqual(s['Description'].find('WordPress'), -1) self.assertNotEqual(s['description'].find('WordPress'), -1)
self.assertTrue('Parameters' in s) self.assertTrue('parameters' in s)
def test_stack_resource_describe(self): def test_stack_resource_describe(self):
r = self.man.describe_stack_resource(self.ctx, self.stack_name, r = self.man.describe_stack_resource(self.ctx, self.stack_name,
'WebServer') 'WebServer')
self.assertTrue('Description' in r) self.assertTrue('description' in r)
self.assertTrue('LastUpdatedTimestamp' in r) self.assertTrue('updated_time' in r)
self.assertTrue('StackId' in r) self.assertTrue('stack_id' in r)
self.assertNotEqual(r['StackId'], None) self.assertNotEqual(r['stack_id'], None)
self.assertTrue('StackName' in r) self.assertTrue('stack_name' in r)
self.assertEqual(r['StackName'], self.stack_name) self.assertEqual(r['stack_name'], self.stack_name)
self.assertTrue('Metadata' in r) self.assertTrue('metadata' in r)
self.assertTrue('ResourceStatus' in r) self.assertTrue('resource_status' in r)
self.assertTrue('ResourceStatusReason' in r) self.assertTrue('resource_status_reason' in r)
self.assertTrue('ResourceType' in r) self.assertTrue('resource_type' in r)
self.assertTrue('PhysicalResourceId' in r) self.assertTrue('physical_resource_id' in r)
self.assertTrue('LogicalResourceId' in r) self.assertTrue('logical_resource_id' in r)
self.assertEqual(r['LogicalResourceId'], 'WebServer') self.assertEqual(r['logical_resource_id'], 'WebServer')
def test_stack_resource_describe_nonexist_stack(self): def test_stack_resource_describe_nonexist_stack(self):
self.assertRaises(AttributeError, self.assertRaises(AttributeError,
@ -297,18 +282,18 @@ class stackManagerTest(unittest.TestCase):
self.assertEqual(len(resources), 1) self.assertEqual(len(resources), 1)
r = resources[0] r = resources[0]
self.assertTrue('Description' in r) self.assertTrue('description' in r)
self.assertTrue('Timestamp' in r) self.assertTrue('updated_time' in r)
self.assertTrue('StackId' in r) self.assertTrue('stack_id' in r)
self.assertNotEqual(r['StackId'], None) self.assertNotEqual(r['stack_id'], None)
self.assertTrue('StackName' in r) self.assertTrue('stack_name' in r)
self.assertEqual(r['StackName'], self.stack_name) self.assertEqual(r['stack_name'], self.stack_name)
self.assertTrue('ResourceStatus' in r) self.assertTrue('resource_status' in r)
self.assertTrue('ResourceStatusReason' in r) self.assertTrue('resource_status_reason' in r)
self.assertTrue('ResourceType' in r) self.assertTrue('resource_type' in r)
self.assertTrue('PhysicalResourceId' in r) self.assertTrue('physical_resource_id' in r)
self.assertTrue('LogicalResourceId' in r) self.assertTrue('logical_resource_id' in r)
self.assertEqual(r['LogicalResourceId'], 'WebServer') self.assertEqual(r['logical_resource_id'], 'WebServer')
def test_stack_resources_describe_no_filter(self): def test_stack_resources_describe_no_filter(self):
resources = self.man.describe_stack_resources(self.ctx, resources = self.man.describe_stack_resources(self.ctx,
@ -317,8 +302,8 @@ class stackManagerTest(unittest.TestCase):
self.assertEqual(len(resources), 1) self.assertEqual(len(resources), 1)
r = resources[0] r = resources[0]
self.assertTrue('LogicalResourceId' in r) self.assertTrue('logical_resource_id' in r)
self.assertEqual(r['LogicalResourceId'], 'WebServer') self.assertEqual(r['logical_resource_id'], 'WebServer')
def test_stack_resources_describe_bad_lookup(self): def test_stack_resources_describe_bad_lookup(self):
self.assertRaises(AttributeError, self.assertRaises(AttributeError,
@ -340,13 +325,13 @@ class stackManagerTest(unittest.TestCase):
self.assertEqual(len(resources), 1) self.assertEqual(len(resources), 1)
r = resources[0] r = resources[0]
self.assertTrue('LastUpdatedTimestamp' in r) self.assertTrue('updated_time' in r)
self.assertTrue('PhysicalResourceId' in r) self.assertTrue('physical_resource_id' in r)
self.assertTrue('LogicalResourceId' in r) self.assertTrue('logical_resource_id' in r)
self.assertEqual(r['LogicalResourceId'], 'WebServer') self.assertEqual(r['logical_resource_id'], 'WebServer')
self.assertTrue('ResourceStatus' in r) self.assertTrue('resource_status' in r)
self.assertTrue('ResourceStatusReason' in r) self.assertTrue('resource_status_reason' in r)
self.assertTrue('ResourceType' in r) self.assertTrue('resource_type' in r)
def test_stack_resources_list_nonexist_stack(self): def test_stack_resources_list_nonexist_stack(self):
self.assertRaises(AttributeError, self.assertRaises(AttributeError,