diff --git a/etc/heat/policy.json b/etc/heat/policy.json index c926e2fa89..a843ddabf7 100644 --- a/etc/heat/policy.json +++ b/etc/heat/policy.json @@ -46,6 +46,7 @@ "stacks:index": "rule:deny_stack_user", "stacks:list_resource_types": "rule:deny_stack_user", "stacks:list_template_versions": "rule:deny_stack_user", + "stacks:list_template_functions": "rule:deny_stack_user", "stacks:lookup": "", "stacks:preview": "rule:deny_stack_user", "stacks:resource_schema": "rule:deny_stack_user", diff --git a/heat/api/openstack/v1/__init__.py b/heat/api/openstack/v1/__init__.py index d23870d868..3f13e57323 100644 --- a/heat/api/openstack/v1/__init__.py +++ b/heat/api/openstack/v1/__init__.py @@ -119,6 +119,14 @@ class API(wsgi.Router): 'method': 'GET' }, + { + 'name': 'template_functions', + 'url': '/template_versions/{template_version}' + '/functions', + 'action': 'list_template_functions', + 'method': 'GET' + }, + # Stack collection { 'name': 'stack_index', diff --git a/heat/api/openstack/v1/stacks.py b/heat/api/openstack/v1/stacks.py index 4f60a33366..eb7b35f101 100644 --- a/heat/api/openstack/v1/stacks.py +++ b/heat/api/openstack/v1/stacks.py @@ -527,6 +527,17 @@ class StackController(object): self.rpc_client.list_template_versions(req.context) } + @util.policy_enforce + def list_template_functions(self, req, template_version): + """ + Returns a list of available functions in a given template + """ + return { + 'template_functions': + self.rpc_client.list_template_functions(req.context, + template_version) + } + @util.policy_enforce def resource_schema(self, req, type_name): """ diff --git a/heat/engine/service.py b/heat/engine/service.py index 4b2c17489a..939dff17f6 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -44,6 +44,7 @@ from heat.engine.cfn import template as cfntemplate from heat.engine import clients from heat.engine import environment from heat.engine import event as evt +from heat.engine.hot import functions as hot_functions from heat.engine import parameter_groups from heat.engine import properties from heat.engine import resources @@ -267,7 +268,7 @@ class EngineService(service.Service): by the RPC caller. """ - RPC_API_VERSION = '1.12' + RPC_API_VERSION = '1.13' def __init__(self, host, topic, manager=None): super(EngineService, self).__init__() @@ -1037,6 +1038,22 @@ class EngineService(service.Service): versions.append({'version': t[0], 'type': 'hot'}) return versions + def list_template_functions(self, cnxt, template_version): + mgr = templatem._get_template_extension_manager() + tmpl_class = mgr[template_version] + functions = [] + for func_name, func in six.iteritems(tmpl_class.plugin.functions): + if func is not hot_functions.Removed: + if func.__doc__.split('\n')[0]: + desc = func.__doc__.split('\n')[0].strip() + else: + desc = func.__doc__.split('\n')[1].strip() + functions.append( + {'functions': func_name, + 'description': desc} + ) + return functions + def resource_schema(self, cnxt, type_name): """ Return the schema of the specified type. diff --git a/heat/rpc/client.py b/heat/rpc/client.py index 6c5df74415..ea56f6f117 100644 --- a/heat/rpc/client.py +++ b/heat/rpc/client.py @@ -33,6 +33,7 @@ class EngineClient(object): 1.10 - Add support for software config list 1.11 - Add support for template versions list 1.12 - Add with_detail option for stack resources list + 1.13 - Add support for template functions list ''' BASE_RPC_API_VERSION = '1.0' @@ -338,6 +339,18 @@ class EngineClient(object): return self.call(ctxt, self.make_msg('list_template_versions'), version='1.11') + def list_template_functions(self, ctxt, template_version): + """ + Get a list of available functions in a given template + + :param ctxt: RPC context + :param template_name : name of the template which function list you + want to get + """ + return self.call(ctxt, self.make_msg( + 'list_template_functions', template_version=template_version), + version='1.13') + def resource_schema(self, ctxt, type_name): """ Get the schema for a resource type. diff --git a/heat/tests/api/openstack/test_api_openstack_v1.py b/heat/tests/api/openstack/test_api_openstack_v1.py index b7dd65f9dc..2b134a7f77 100644 --- a/heat/tests/api/openstack/test_api_openstack_v1.py +++ b/heat/tests/api/openstack/test_api_openstack_v1.py @@ -2041,6 +2041,26 @@ class StackControllerTest(ControllerTest, common.HeatTestCase): self.assertEqual({'template_versions': engine_response}, response) self.m.VerifyAll() + def test_list_template_functions(self, mock_enforce): + self._mock_enforce_setup(mock_enforce, 'list_template_functions', True) + req = self._get('/template_versions/t1/functions') + + engine_response = [ + {'functions': 'func1', 'description': 'desc1'}, + ] + + self.m.StubOutWithMock(rpc_client.EngineClient, 'call') + rpc_client.EngineClient.call( + req.context, ( + 'list_template_functions', {'template_version': 't1'}), + version="1.13" + ).AndReturn(engine_response) + self.m.ReplayAll() + response = self.controller.list_template_functions( + req, tenant_id=self.tenant, template_version='t1') + self.assertEqual({'template_functions': engine_response}, response) + self.m.VerifyAll() + def test_resource_schema(self, mock_enforce): self._mock_enforce_setup(mock_enforce, 'resource_schema', True) req = self._get('/resource_types/ResourceWithProps') diff --git a/heat/tests/engine/test_service_engine.py b/heat/tests/engine/test_service_engine.py index 770ea1936a..e9931b4b95 100644 --- a/heat/tests/engine/test_service_engine.py +++ b/heat/tests/engine/test_service_engine.py @@ -39,7 +39,7 @@ class ServiceEngineTest(common.HeatTestCase): def test_make_sure_rpc_version(self): self.assertEqual( - '1.12', + '1.13', service.EngineService.RPC_API_VERSION, ('RPC version is changed, please update this test to new version ' 'and make sure additional test cases are added for RPC APIs ' diff --git a/heat/tests/test_engine_service.py b/heat/tests/test_engine_service.py index 9e8e38d5e6..1962f8f5f4 100644 --- a/heat/tests/test_engine_service.py +++ b/heat/tests/test_engine_service.py @@ -30,6 +30,7 @@ from heat.common import template_format from heat.engine.cfn import template as cfntemplate from heat.engine import dependencies from heat.engine import environment +from heat.engine.hot import functions as hot_functions from heat.engine.hot import template as hottemplate from heat.engine import resource as res from heat.engine.resources.aws.ec2 import instance as instances @@ -2227,6 +2228,40 @@ class StackServiceTest(common.HeatTestCase): {'version': 'c.d', 'type': 'hot'}] self.assertEqual(expected, templates) + @mock.patch('heat.engine.template._get_template_extension_manager') + def test_list_template_functions(self, templ_mock): + + class DummyFunc1(object): + """ + Dummy Func1 + + Dummy Func1 Long Description + """ + + class DummyFunc2(object): + """Dummy Func2 + + Dummy Func2 Long Description + """ + + plugin_mock = mock.Mock( + functions={'dummy1': DummyFunc1, + 'dummy2': DummyFunc2, + 'removed': hot_functions.Removed}) + dummy_tmpl = mock.Mock(plugin=plugin_mock) + + class DummyMgr(object): + def __getitem__(self, item): + return dummy_tmpl + + templ_mock.return_value = DummyMgr() + functions = self.eng.list_template_functions(self.ctx, 'dummytemplate') + expected = [{'functions': 'dummy1', + 'description': 'Dummy Func1'}, + {'functions': 'dummy2', + 'description': 'Dummy Func2'}] + self.assertEqual(sorted(expected), sorted(functions)) + @mock.patch.object(res.Resource, 'is_service_available') def test_list_resource_types_unavailable( self,