Add functions for outputs to heat service

APIImpact

Add new functions for showing and listing stack outputs
to heat service. Methods takes specified stack and resolves
output in specific format.

implements bp api-call-output

Change-Id: I68a5b7cacfb6b604cf58d3a6cf00377cabeb0874
This commit is contained in:
Peter Razumovsky 2015-10-21 16:41:49 +03:00
parent 9fcfdf8558
commit 1c22402b2f
5 changed files with 145 additions and 16 deletions

View File

@ -170,24 +170,30 @@ def translate_filters(params):
return params
def format_stack_outputs(stack, outputs):
def format_stack_outputs(stack, outputs, resolve_value=False):
"""Return a representation of the given output template.
Return a representation of the given output template for the given stack
that matches the API output expectations.
"""
def format_stack_output(k):
output = {
return [format_stack_output(stack, outputs,
key, resolve_value=resolve_value)
for key in outputs]
def format_stack_output(stack, outputs, k, resolve_value=True):
result = {
rpc_api.OUTPUT_KEY: k,
rpc_api.OUTPUT_DESCRIPTION: outputs[k].get('Description',
'No description given'),
rpc_api.OUTPUT_KEY: k,
rpc_api.OUTPUT_VALUE: stack.output(k)
}
if outputs[k].get('error_msg'):
output.update({rpc_api.OUTPUT_ERROR: outputs[k].get('error_msg')})
return output
return [format_stack_output(key) for key in outputs]
if resolve_value:
result.update({rpc_api.OUTPUT_VALUE: stack.output(k)})
if outputs[k].get('error_msg'):
result.update({rpc_api.OUTPUT_ERROR: outputs[k].get('error_msg')})
return result
def format_stack(stack, preview=False):
@ -227,7 +233,8 @@ def format_stack(stack, preview=False):
# allow users to view the outputs of stacks
if stack.action != stack.DELETE and stack.status != stack.IN_PROGRESS:
info[rpc_api.STACK_OUTPUTS] = format_stack_outputs(stack,
stack.outputs)
stack.outputs,
resolve_value=True)
return info

View File

@ -1049,6 +1049,39 @@ class EngineService(service.Service):
return s.raw_template.template
return None
@context.request_context
def list_outputs(self, cntx, stack_identity):
"""Get a list of stack outputs.
:param cntx: RPC context.
:param stack_identity: Name of the stack you want to see.
:return: list of stack outputs in defined format.
"""
s = self._get_stack(cntx, stack_identity)
stack = parser.Stack.load(cntx, stack=s, resolve_data=False)
return api.format_stack_outputs(stack, stack.t[stack.t.OUTPUTS])
@context.request_context
def show_output(self, cntx, stack_identity, output_key):
"""Returns dict with specified output key, value and description.
:param cntx: RPC context.
:param stack_identity: Name of the stack you want to see.
:param output_key: key of desired stack output.
:return: dict with output key, value and description in defined format.
"""
s = self._get_stack(cntx, stack_identity)
stack = parser.Stack.load(cntx, stack=s, resolve_data=False)
outputs = stack.t[stack.t.OUTPUTS]
if output_key not in outputs:
raise exception.NotFound(_('Specified output key %s not '
'found.') % output_key)
output = stack.resolve_static_data(outputs[output_key])
return api.format_stack_output(stack, {output_key: output}, output_key)
def _remote_call(self, cnxt, lock_engine_id, call, **kwargs):
timeout = cfg.CONF.engine_life_check_timeout
self.cctxt = self._client.prepare(

View File

@ -361,7 +361,8 @@ class Stack(collections.Mapping):
@classmethod
def load(cls, context, stack_id=None, stack=None, show_deleted=True,
use_stored_context=False, force_reload=False, cache_data=None):
use_stored_context=False, force_reload=False, cache_data=None,
resolve_data=True):
"""Retrieve a Stack from the database."""
if stack is None:
stack = stack_object.Stack.get_by_id(
@ -378,7 +379,7 @@ class Stack(collections.Mapping):
return cls._from_db(context, stack,
use_stored_context=use_stored_context,
cache_data=cache_data)
cache_data=cache_data, resolve_data=resolve_data)
@classmethod
def load_all(cls, context, limit=None, marker=None, sort_keys=None,

View File

@ -412,7 +412,8 @@ class FormatTest(common.HeatTestCase):
stack.status = 'COMPLETE'
stack['generic'].action = 'CREATE'
stack['generic'].status = 'COMPLETE'
info = api.format_stack_outputs(stack, stack.outputs)
info = api.format_stack_outputs(stack, stack.outputs,
resolve_value=True)
expected = [{'description': 'No description given',
'output_error': 'The Referenced Attribute (generic Bar) '
'is incorrect.',
@ -425,6 +426,37 @@ class FormatTest(common.HeatTestCase):
self.assertEqual(expected, sorted(info, key=lambda k: k['output_key'],
reverse=True))
def test_format_stack_outputs_unresolved(self):
tmpl = template.Template({
'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {
'generic': {'Type': 'GenericResourceType'}
},
'Outputs': {
'correct_output': {
'Description': 'Good output',
'Value': {'Fn::GetAtt': ['generic', 'Foo']}
},
'incorrect_output': {
'Value': {'Fn::GetAtt': ['generic', 'Bar']}
}
}
})
stack = parser.Stack(utils.dummy_context(), 'test_stack',
tmpl, stack_id=str(uuid.uuid4()))
stack.action = 'CREATE'
stack.status = 'COMPLETE'
stack['generic'].action = 'CREATE'
stack['generic'].status = 'COMPLETE'
info = api.format_stack_outputs(stack, stack.outputs)
expected = [{'description': 'No description given',
'output_key': 'incorrect_output'},
{'description': 'Good output',
'output_key': 'correct_output'}]
self.assertEqual(expected, sorted(info, key=lambda k: k['output_key'],
reverse=True))
class FormatValidateParameterTest(common.HeatTestCase):

View File

@ -1004,6 +1004,62 @@ class StackServiceTest(common.HeatTestCase):
msg = "Template with version %s not found" % version
self.assertEqual(msg, six.text_type(ex))
def test_stack_list_outputs(self):
t = template_format.parse(tools.wp_template)
t['outputs'] = {
'test': {'value': '{ get_attr: fir }',
'description': 'sec'},
'test2': {'value': 'sec'}}
tmpl = templatem.Template(t)
stack = parser.Stack(self.ctx, 'service_list_outputs_stack', tmpl)
self.patchobject(self.eng, '_get_stack')
self.patchobject(parser.Stack, 'load', return_value=stack)
outputs = self.eng.list_outputs(self.ctx, mock.ANY)
self.assertIn({'output_key': 'test',
'description': 'sec'}, outputs)
self.assertIn({'output_key': 'test2',
'description': 'No description given'},
outputs)
def test_stack_empty_list_outputs(self):
# Ensure that stack with no output returns empty list
t = template_format.parse(tools.wp_template)
t['outputs'] = {}
tmpl = templatem.Template(t)
stack = parser.Stack(self.ctx, 'service_list_outputs_stack', tmpl)
self.patchobject(self.eng, '_get_stack')
self.patchobject(parser.Stack, 'load', return_value=stack)
outputs = self.eng.list_outputs(self.ctx, mock.ANY)
self.assertEqual([], outputs)
def test_stack_show_output(self):
t = template_format.parse(tools.wp_template)
t['outputs'] = {'test': {'value': 'first', 'description': 'sec'},
'test2': {'value': 'sec'}}
tmpl = templatem.Template(t)
stack = parser.Stack(self.ctx, 'service_list_outputs_stack', tmpl)
self.patchobject(self.eng, '_get_stack')
self.patchobject(parser.Stack, 'load', return_value=stack)
output = self.eng.show_output(self.ctx, mock.ANY, 'test')
self.assertEqual({'output_key': 'test', 'output_value': 'first',
'description': 'sec'},
output)
# Ensure that stack raised NotFound error with incorrect key.
ex = self.assertRaises(dispatcher.ExpectedException,
self.eng.show_output,
self.ctx, mock.ANY, 'bunny')
self.assertEqual(exception.NotFound, ex.exc_info[0])
self.assertEqual('Specified output key bunny not found.',
six.text_type(ex.exc_info[1]))
def test_stack_list_all_empty(self):
sl = self.eng.list_stacks(self.ctx)