Add stack files retrieval API

Similar to the recent addition that enables retrieval of the current
environment for a stack, this enables reading the current files map
for a running stack, which is useful if you want to introspect the
current state, and/or deploy another similar stack without necessarily
having the exact command/repo used initially.

APIImpact
Implements: blueprint files-show

Change-Id: I3198b6a7dc06648af24c198d39470f3b0d5d6f11
This commit is contained in:
Steven Hardy 2016-06-08 12:52:29 +01:00
parent 5dc4d26b7f
commit 316b5b6381
9 changed files with 86 additions and 3 deletions

View File

@ -55,6 +55,7 @@
"stacks:show": "rule:deny_stack_user", "stacks:show": "rule:deny_stack_user",
"stacks:template": "rule:deny_stack_user", "stacks:template": "rule:deny_stack_user",
"stacks:environment": "rule:deny_stack_user", "stacks:environment": "rule:deny_stack_user",
"stacks:files": "rule:deny_stack_user",
"stacks:update": "rule:deny_stack_user", "stacks:update": "rule:deny_stack_user",
"stacks:update_patch": "rule:deny_stack_user", "stacks:update_patch": "rule:deny_stack_user",
"stacks:preview_update": "rule:deny_stack_user", "stacks:preview_update": "rule:deny_stack_user",

View File

@ -171,7 +171,7 @@ class API(wsgi.Router):
'name': 'stack_lookup_subpath', 'name': 'stack_lookup_subpath',
'url': '/stacks/{stack_name}/' 'url': '/stacks/{stack_name}/'
'{path:resources|events|template|actions' '{path:resources|events|template|actions'
'|environment}', '|environment|files}',
'action': 'lookup', 'action': 'lookup',
'method': 'GET' 'method': 'GET'
}, },
@ -200,6 +200,12 @@ class API(wsgi.Router):
'action': 'environment', 'action': 'environment',
'method': 'GET' 'method': 'GET'
}, },
{
'name': 'stack_lookup',
'url': '/stacks/{stack_name}/{stack_id}/files',
'action': 'files',
'method': 'GET'
},
# Stack update/delete # Stack update/delete
{ {

View File

@ -463,6 +463,11 @@ class StackController(object):
return env return env
@util.identified_stack
def files(self, req, identity):
"""Get the files for an existing stack."""
return self.rpc_client.get_files(req.context, identity)
@util.identified_stack @util.identified_stack
def update(self, req, identity, body): def update(self, req, identity, body):
"""Update an existing stack with a new template and/or parameters.""" """Update an existing stack with a new template and/or parameters."""

View File

@ -297,7 +297,7 @@ class EngineService(service.Service):
by the RPC caller. by the RPC caller.
""" """
RPC_API_VERSION = '1.31' RPC_API_VERSION = '1.32'
def __init__(self, host, topic): def __init__(self, host, topic):
super(EngineService, self).__init__() super(EngineService, self).__init__()
@ -1285,6 +1285,19 @@ class EngineService(service.Service):
return s.raw_template.environment return s.raw_template.environment
return None return None
@context.request_context
def get_files(self, cnxt, stack_identity):
"""Returns the files for an existing stack.
:param cnxt: RPC context
:param stack_identity: identifies the stack
:rtype: dict
"""
s = self._get_stack(cnxt, stack_identity, show_deleted=True)
stack = parser.Stack.load(
cnxt, stack=s, resolve_data=False, show_deleted=True)
return dict(stack.t.files)
@context.request_context @context.request_context
def list_outputs(self, cntx, stack_identity): def list_outputs(self, cntx, stack_identity):
"""Get a list of stack outputs. """Get a list of stack outputs.

View File

@ -53,6 +53,7 @@ class EngineClient(object):
1.30 - Add possibility to resource_type_* return descriptions 1.30 - Add possibility to resource_type_* return descriptions
1.31 - Add nested_depth to list_events, when nested_depth is specified 1.31 - Add nested_depth to list_events, when nested_depth is specified
add root_stack_id to response add root_stack_id to response
1.32 - Add get_files call
""" """
BASE_RPC_API_VERSION = '1.0' BASE_RPC_API_VERSION = '1.0'
@ -408,6 +409,19 @@ class EngineClient(object):
stack_identity=stack_identity), stack_identity=stack_identity),
version='1.28') version='1.28')
def get_files(self, context, stack_identity):
"""Returns the files for an existing stack.
:param context: RPC context
:param stack_identity: identifies the stack
:rtype: dict
"""
return self.call(context,
self.make_msg('get_files',
stack_identity=stack_identity),
version='1.32')
def delete_stack(self, ctxt, stack_identity, cast=True): def delete_stack(self, ctxt, stack_identity, cast=True):
"""Deletes a given stack. """Deletes a given stack.

View File

@ -1739,6 +1739,27 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase):
self.m.VerifyAll() self.m.VerifyAll()
def test_get_files(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'files', True)
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')
req = self._get('/stacks/%(stack_name)s/%(stack_id)s' % identity)
files = {'foo.yaml': 'i am yaml'}
self.m.StubOutWithMock(rpc_client.EngineClient, 'call')
rpc_client.EngineClient.call(
req.context,
('get_files', {'stack_identity': dict(identity)},),
version='1.32',
).AndReturn(files)
self.m.ReplayAll()
response = self.controller.files(req, tenant_id=identity.tenant,
stack_name=identity.stack_name,
stack_id=identity.stack_id)
self.assertEqual(files, response)
self.m.VerifyAll()
def test_get_template_err_denied_policy(self, mock_enforce): def test_get_template_err_denied_policy(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'template', False) self._mock_enforce_setup(mock_enforce, 'template', False)
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6') identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')

View File

@ -40,7 +40,7 @@ class ServiceEngineTest(common.HeatTestCase):
def test_make_sure_rpc_version(self): def test_make_sure_rpc_version(self):
self.assertEqual( self.assertEqual(
'1.31', '1.32',
service.EngineService.RPC_API_VERSION, service.EngineService.RPC_API_VERSION,
('RPC version is changed, please update this test to new version ' ('RPC version is changed, please update this test to new version '
'and make sure additional test cases are added for RPC APIs ' 'and make sure additional test cases are added for RPC APIs '

View File

@ -1083,6 +1083,24 @@ class StackServiceTest(common.HeatTestCase):
self.ctx, self.ctx,
'irrelevant') 'irrelevant')
def test_get_files(self):
# Setup
t = template_format.parse(tools.wp_template)
files = {'foo.yaml': 'i am a file'}
tmpl = templatem.Template(t, files=files)
stack = parser.Stack(self.ctx, 'get_env_stack', tmpl)
stack.store()
mock_get_stack = self.patchobject(self.eng, '_get_stack')
mock_get_stack.return_value = mock.MagicMock()
self.patchobject(parser.Stack, 'load', return_value=stack)
# Test
found = self.eng.get_files(self.ctx, stack.identifier())
# Verify
self.assertEqual(files, found)
def test_stack_show_output(self): def test_stack_show_output(self):
t = template_format.parse(tools.wp_template) t = template_format.parse(tools.wp_template)
t['outputs'] = {'test': {'value': 'first', 'description': 'sec'}, t['outputs'] = {'test': {'value': 'first', 'description': 'sec'},

View File

@ -420,3 +420,8 @@ class EngineRpcAPITestCase(common.HeatTestCase):
self._test_engine_api( self._test_engine_api(
'get_environment', 'call', stack_identity=self.identity, 'get_environment', 'call', stack_identity=self.identity,
version='1.28') version='1.28')
def test_get_files(self):
self._test_engine_api(
'get_files', 'call', stack_identity=self.identity,
version='1.32')