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:
parent
9fcfdf8558
commit
1c22402b2f
@ -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 = {
|
||||
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(stack, outputs,
|
||||
key, resolve_value=resolve_value)
|
||||
for key in outputs]
|
||||
|
||||
return [format_stack_output(key) 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'),
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user