From 7975d8b46ab5c5a86e5155089a8af8a6a8b1b152 Mon Sep 17 00:00:00 2001 From: lvdongbing Date: Fri, 19 Jun 2015 10:54:49 +0800 Subject: [PATCH] Support to list software configs APIImpact List software configs GET /v1/{tenant_id}/software_configs Request params(optional) limit,marker Json response example {'software_configs':[ {'creation_time': '2015-06-25T07:15:56', 'group': 'Heat::Ungrouped', 'id': 'de879d1c-c9a5-4635-ba29-08a5690fec27', 'name': 'foo'}] } Change-Id: Iad691672874b64b393b85d2eb9a55b22facbd263 Closes-Bug: #1464248 --- etc/heat/policy.json | 2 ++ heat/api/openstack/v1/__init__.py | 6 ++++ heat/api/openstack/v1/software_configs.py | 41 +++++++++++++++++++++++ heat/db/api.py | 8 +++++ heat/db/sqlalchemy/api.py | 9 +++++ heat/engine/api.py | 13 +++---- heat/engine/service.py | 11 +++++- heat/engine/service_software_config.py | 10 ++++++ heat/objects/software_config.py | 5 +++ heat/rpc/client.py | 10 ++++++ heat/tests/db/test_sqlalchemy_api.py | 27 ++++++++++----- heat/tests/engine/test_service_engine.py | 2 +- heat/tests/engine/test_software_config.py | 10 ++++++ heat/tests/test_api_openstack_v1.py | 21 ++++++++++++ heat/tests/test_rpc_client.py | 5 +++ 15 files changed, 164 insertions(+), 16 deletions(-) diff --git a/etc/heat/policy.json b/etc/heat/policy.json index 54c845b34..5964faf39 100644 --- a/etc/heat/policy.json +++ b/etc/heat/policy.json @@ -59,6 +59,8 @@ "stacks:list_snapshots": "rule:deny_stack_user", "stacks:restore_snapshot": "rule:deny_stack_user", + "software_configs:global_index": "rule:deny_everybody", + "software_configs:index": "rule:deny_stack_user", "software_configs:create": "rule:deny_stack_user", "software_configs:show": "rule:deny_stack_user", "software_configs:delete": "rule:deny_stack_user", diff --git a/heat/api/openstack/v1/__init__.py b/heat/api/openstack/v1/__init__.py index 8f78165ba..d3f344a72 100644 --- a/heat/api/openstack/v1/__init__.py +++ b/heat/api/openstack/v1/__init__.py @@ -334,6 +334,12 @@ class API(wsgi.Router): connect(controller=software_config_resource, path_prefix='/{tenant_id}/software_configs', routes=[ + { + 'name': 'software_config_index', + 'url': '', + 'action': 'index', + 'method': 'GET' + }, { 'name': 'software_config_create', 'url': '', diff --git a/heat/api/openstack/v1/software_configs.py b/heat/api/openstack/v1/software_configs.py index 2bc5cdc75..d32f65f2c 100644 --- a/heat/api/openstack/v1/software_configs.py +++ b/heat/api/openstack/v1/software_configs.py @@ -11,11 +11,14 @@ # License for the specific language governing permissions and limitations # under the License. +import six from webob import exc from heat.api.openstack.v1 import util +from heat.common import param_utils from heat.common import serializers from heat.common import wsgi +from heat.rpc import api as rpc_api from heat.rpc import client as rpc_client @@ -34,6 +37,44 @@ class SoftwareConfigController(object): def default(self, req, **args): raise exc.HTTPNotFound() + def _extract_bool_param(self, name, value): + try: + return param_utils.extract_bool(name, value) + except ValueError as e: + raise exc.HTTPBadRequest(six.text_type(e)) + + def _index(self, req, tenant_safe=True): + whitelist = { + 'limit': 'single', + 'marker': 'single' + } + params = util.get_allowed_params(req.params, whitelist) + scs = self.rpc_client.list_software_configs(req.context, + tenant_safe=tenant_safe, + **params) + return {'software_configs': scs} + + @util.policy_enforce + def global_index(self, req): + return self._index(req, tenant_safe=False) + + @util.policy_enforce + def index(self, req): + """ + Lists summary information for all software configs + """ + global_tenant = False + name = rpc_api.PARAM_GLOBAL_TENANT + if name in req.params: + global_tenant = self._extract_bool_param( + name, + req.params.get(name)) + + if global_tenant: + return self.global_index(req, req.context.tenant_id) + + return self._index(req) + @util.policy_enforce def show(self, req, config_id): """ diff --git a/heat/db/api.py b/heat/db/api.py index c930268f3..8713a3bfd 100644 --- a/heat/db/api.py +++ b/heat/db/api.py @@ -306,6 +306,14 @@ def software_config_get(context, config_id): return IMPL.software_config_get(context, config_id) +def software_config_get_all(context, limit=None, marker=None, + tenant_safe=True): + return IMPL.software_config_get_all(context, + limit=limit, + marker=marker, + tenant_safe=tenant_safe) + + def software_config_delete(context, config_id): return IMPL.software_config_delete(context, config_id) diff --git a/heat/db/sqlalchemy/api.py b/heat/db/sqlalchemy/api.py index 1ec20e714..ada69a908 100644 --- a/heat/db/sqlalchemy/api.py +++ b/heat/db/sqlalchemy/api.py @@ -870,6 +870,15 @@ def software_config_get(context, config_id): return result +def software_config_get_all(context, limit=None, marker=None, + tenant_safe=True): + query = model_query(context, models.SoftwareConfig) + if tenant_safe: + query = query.filter_by(tenant=context.tenant_id) + return _paginate_query(context, query, models.SoftwareConfig, + limit=limit, marker=marker).all() + + def software_config_delete(context, config_id): config = software_config_get(context, config_id) session = orm_session.Session.object_session(config) diff --git a/heat/engine/api.py b/heat/engine/api.py index 84d25a615..47bffd5ad 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -403,19 +403,20 @@ def format_validate_parameter(param): return res -def format_software_config(sc): +def format_software_config(sc, detail=True): if sc is None: return result = { rpc_api.SOFTWARE_CONFIG_ID: sc.id, rpc_api.SOFTWARE_CONFIG_NAME: sc.name, rpc_api.SOFTWARE_CONFIG_GROUP: sc.group, - rpc_api.SOFTWARE_CONFIG_CONFIG: sc.config['config'], - rpc_api.SOFTWARE_CONFIG_INPUTS: sc.config['inputs'], - rpc_api.SOFTWARE_CONFIG_OUTPUTS: sc.config['outputs'], - rpc_api.SOFTWARE_CONFIG_OPTIONS: sc.config['options'], - rpc_api.SOFTWARE_CONFIG_CREATION_TIME: sc.created_at.isoformat(), + rpc_api.SOFTWARE_CONFIG_CREATION_TIME: sc.created_at.isoformat() } + if detail: + result[rpc_api.SOFTWARE_CONFIG_CONFIG] = sc.config['config'] + result[rpc_api.SOFTWARE_CONFIG_INPUTS] = sc.config['inputs'] + result[rpc_api.SOFTWARE_CONFIG_OUTPUTS] = sc.config['outputs'] + result[rpc_api.SOFTWARE_CONFIG_OPTIONS] = sc.config['options'] return result diff --git a/heat/engine/service.py b/heat/engine/service.py index 623da1e42..7fb23fbc2 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -266,7 +266,7 @@ class EngineService(service.Service): by the RPC caller. """ - RPC_API_VERSION = '1.9' + RPC_API_VERSION = '1.10' def __init__(self, host, topic, manager=None): super(EngineService, self).__init__() @@ -1477,6 +1477,15 @@ class EngineService(service.Service): def show_software_config(self, cnxt, config_id): return self.software_config.show_software_config(cnxt, config_id) + @context.request_context + def list_software_configs(self, cnxt, limit=None, marker=None, + tenant_safe=True): + return self.software_config.list_software_configs( + cnxt, + limit=limit, + marker=marker, + tenant_safe=tenant_safe) + @context.request_context def create_software_config(self, cnxt, group, name, config, inputs, outputs, options): diff --git a/heat/engine/service_software_config.py b/heat/engine/service_software_config.py index 1783566bd..0232c1676 100644 --- a/heat/engine/service_software_config.py +++ b/heat/engine/service_software_config.py @@ -36,6 +36,16 @@ class SoftwareConfigService(service.Service): sc = software_config_object.SoftwareConfig.get_by_id(cnxt, config_id) return api.format_software_config(sc) + def list_software_configs(self, cnxt, limit=None, marker=None, + tenant_safe=True): + scs = software_config_object.SoftwareConfig.get_all( + cnxt, + limit=limit, + marker=marker, + tenant_safe=tenant_safe) + result = [api.format_software_config(sc, detail=False) for sc in scs] + return result + def create_software_config(self, cnxt, group, name, config, inputs, outputs, options): diff --git a/heat/objects/software_config.py b/heat/objects/software_config.py index 2d09a603c..6edda387d 100644 --- a/heat/objects/software_config.py +++ b/heat/objects/software_config.py @@ -56,6 +56,11 @@ class SoftwareConfig(base.VersionedObject, return cls._from_db_object( context, cls(), db_api.software_config_get(context, config_id)) + @classmethod + def get_all(cls, context, **kwargs): + scs = db_api.software_config_get_all(context, **kwargs) + return [cls._from_db_object(context, cls(), sc) for sc in scs] + @classmethod def delete(cls, context, config_id): db_api.software_config_delete(context, config_id) diff --git a/heat/rpc/client.py b/heat/rpc/client.py index 52e94617a..2780e6d07 100644 --- a/heat/rpc/client.py +++ b/heat/rpc/client.py @@ -30,6 +30,7 @@ class EngineClient(object): 1.1 - Add support_status argument to list_resource_types() 1.4 - Add support for service list 1.9 - Add template_type option to generate_template() + 1.10 - Add support for software config list ''' BASE_RPC_API_VERSION = '1.0' @@ -511,6 +512,15 @@ class EngineClient(object): return self.call(cnxt, self.make_msg('show_software_config', config_id=config_id)) + def list_software_configs(self, cnxt, limit=None, marker=None, + tenant_safe=True): + return self.call(cnxt, + self.make_msg('list_software_configs', + limit=limit, + marker=marker, + tenant_safe=tenant_safe), + version='1.10') + def create_software_config(self, cnxt, group, name, config, inputs=None, outputs=None, options=None): inputs = inputs or [] diff --git a/heat/tests/db/test_sqlalchemy_api.py b/heat/tests/db/test_sqlalchemy_api.py index 3f6cd8e1b..92a48f31f 100644 --- a/heat/tests/db/test_sqlalchemy_api.py +++ b/heat/tests/db/test_sqlalchemy_api.py @@ -1030,6 +1030,17 @@ class SqlAlchemyTest(common.HeatTestCase): self.ctx, config_id) + def test_software_config_get_all(self): + self.assertEqual([], db_api.software_config_get_all(self.ctx)) + tenant_id = self.ctx.tenant_id + software_config = db_api.software_config_create( + self.ctx, {'name': 'config_mysql', + 'tenant': tenant_id}) + self.assertIsNotNone(software_config) + software_configs = db_api.software_config_get_all(self.ctx) + self.assertEqual(1, len(software_configs)) + self.assertEqual(software_config, software_configs[0]) + def test_software_config_delete(self): tenant_id = self.ctx.tenant_id config = db_api.software_config_create( @@ -1109,16 +1120,16 @@ class SqlAlchemyTest(common.HeatTestCase): values = self._deployment_values() deployment = db_api.software_deployment_create(self.ctx, values) self.assertIsNotNone(deployment) - all = db_api.software_deployment_get_all(self.ctx) - self.assertEqual(1, len(all)) - self.assertEqual(deployment, all[0]) - all = db_api.software_deployment_get_all( + deployments = db_api.software_deployment_get_all(self.ctx) + self.assertEqual(1, len(deployments)) + self.assertEqual(deployment, deployments[0]) + deployments = db_api.software_deployment_get_all( self.ctx, server_id=values['server_id']) - self.assertEqual(1, len(all)) - self.assertEqual(deployment, all[0]) - all = db_api.software_deployment_get_all( + self.assertEqual(1, len(deployments)) + self.assertEqual(deployment, deployments[0]) + deployments = db_api.software_deployment_get_all( self.ctx, server_id=str(uuid.uuid4())) - self.assertEqual([], all) + self.assertEqual([], deployments) def test_software_deployment_update(self): deployment_id = str(uuid.uuid4()) diff --git a/heat/tests/engine/test_service_engine.py b/heat/tests/engine/test_service_engine.py index f52c0b68b..0d61e60a1 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.9', + '1.10', 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/engine/test_software_config.py b/heat/tests/engine/test_software_config.py index d996df59a..a10ede91a 100644 --- a/heat/tests/engine/test_software_config.py +++ b/heat/tests/engine/test_software_config.py @@ -48,6 +48,16 @@ class SoftwareConfigServiceTest(common.HeatTestCase): return self.engine.create_software_config( self.ctx, group, name, config, inputs, outputs, options) + def test_list_software_configs(self): + config = self._create_software_config() + config_id = config['id'] + self.assertIsNotNone(config) + + configs = self.engine.list_software_configs(self.ctx) + self.assertIsNotNone(configs) + config_ids = [x['id'] for x in configs] + self.assertIn(config_id, config_ids) + def test_show_software_config(self): config_id = str(uuid.uuid4()) diff --git a/heat/tests/test_api_openstack_v1.py b/heat/tests/test_api_openstack_v1.py index 6278d8fba..e582f0698 100644 --- a/heat/tests/test_api_openstack_v1.py +++ b/heat/tests/test_api_openstack_v1.py @@ -3737,6 +3737,15 @@ class RoutesTest(common.HeatTestCase): }) def test_software_configs(self): + self.assertRoute( + self.m, + '/aaaa/software_configs', + 'GET', + 'index', + 'SoftwareConfigController', + { + 'tenant_id': 'aaaa' + }) self.assertRoute( self.m, '/aaaa/software_configs', @@ -4146,6 +4155,18 @@ class SoftwareConfigControllerTest(ControllerTest, common.HeatTestCase): self.assertRaises( webob.exc.HTTPNotFound, self.controller.default, None) + @mock.patch.object(policy.Enforcer, 'enforce') + def test_index(self, mock_enforce): + self._mock_enforce_setup(mock_enforce, 'index') + req = self._get('/software_configs') + with mock.patch.object( + self.controller.rpc_client, + 'list_software_configs', + return_value=[]): + resp = self.controller.index(req, tenant_id=self.tenant) + self.assertEqual( + {'software_configs': []}, resp) + @mock.patch.object(policy.Enforcer, 'enforce') def test_show(self, mock_enforce): self._mock_enforce_setup(mock_enforce, 'show') diff --git a/heat/tests/test_rpc_client.py b/heat/tests/test_rpc_client.py index 5a69078fb..e0789acc5 100644 --- a/heat/tests/test_rpc_client.py +++ b/heat/tests/test_rpc_client.py @@ -268,6 +268,11 @@ class EngineRpcAPITestCase(common.HeatTestCase): self._test_engine_api('set_watch_state', 'call', watch_name='watch1', state="xyz") + def test_list_software_configs(self): + self._test_engine_api('list_software_configs', 'call', + limit=mock.ANY, marker=mock.ANY, + tenant_safe=mock.ANY) + def test_show_software_config(self): self._test_engine_api('show_software_config', 'call', config_id='cda89008-6ea6-4057-b83d-ccde8f0b48c9')