From bfa2392d16a803e1d95edfff125b2ef0b4db1ec2 Mon Sep 17 00:00:00 2001 From: Lingxian Kong Date: Wed, 21 Oct 2020 22:25:12 +1300 Subject: [PATCH] Support to get instances of a specified project This patch also fixes a performance issue when the adm user is getting all the instances. Change-Id: Icd6345d6c97648cdfbfaa8d9edac7315a1409356 --- api-ref/source/instances.inc | 11 ++- trove/extensions/mgmt/instances/models.py | 17 ++-- trove/extensions/mgmt/instances/service.py | 8 +- .../extensions/mgmt/instances/test_service.py | 88 +++++++++++++++++++ 4 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 trove/tests/unittests/extensions/mgmt/instances/test_service.py diff --git a/api-ref/source/instances.inc b/api-ref/source/instances.inc index 2987cad632..1279bc21e3 100644 --- a/api-ref/source/instances.inc +++ b/api-ref/source/instances.inc @@ -38,9 +38,14 @@ List database instances(admin) .. rest_method:: GET /v1.0/{project_id}/mgmt/instances -Admin only API. Get all the instances, supported filters: deleted, -include_clustered. Could show more information such as Cinder volume ID, Nova -server information, etc. +Admin only API. Get all the instances, Could show more information such as +Cinder volume ID, Nova server information, etc. + +Supported filters: + +* ``deleted``. +* ``include_clustered``. +* ``project_id``: Get instances of a speficied project. Normal response codes: 200 diff --git a/trove/extensions/mgmt/instances/models.py b/trove/extensions/mgmt/instances/models.py index db4d68c559..c70a550e5d 100644 --- a/trove/extensions/mgmt/instances/models.py +++ b/trove/extensions/mgmt/instances/models.py @@ -18,7 +18,6 @@ from oslo_log import log as logging from trove.common import cfg from trove.common import clients from trove.common import exception -from trove.common.i18n import _ from trove.common import timeutils from trove.extensions.mysql import models as mysql_models from trove.instance import models as instance_models @@ -29,20 +28,24 @@ CONF = cfg.CONF def load_mgmt_instances(context, deleted=None, client=None, - include_clustered=None): + include_clustered=None, project_id=None): if not client: client = clients.create_nova_client( context, CONF.service_credentials.region_name ) - mgmt_servers = client.servers.list(search_opts={'all_tenants': 1}, - limit=-1) + + search_opts = {'all_tenants': False} + mgmt_servers = client.servers.list(search_opts=search_opts, limit=-1) LOG.info("Found %d servers in Nova", len(mgmt_servers if mgmt_servers else [])) + args = {} if deleted is not None: args['deleted'] = deleted if not include_clustered: args['cluster_id'] = None + if project_id: + args['tenant_id'] = project_id db_infos = instance_models.DBInstance.find_all(**args) @@ -153,11 +156,11 @@ class MgmtInstances(instance_models.Instances): def load_instance(context, db, status, server=None): return SimpleMgmtInstance(context, db, server, status) - if context is None: - raise TypeError(_("Argument context not defined.")) find_server = instance_models.create_server_list_matcher(servers) + instances = instance_models.Instances._load_servers_status( load_instance, context, db_infos, find_server) + _load_servers(instances, find_server) return instances @@ -170,7 +173,7 @@ def _load_servers(instances, find_server): server = find_server(db.id, db.compute_instance_id) instance.server = server except Exception as ex: - LOG.exception(ex) + LOG.warning(ex) return instances diff --git a/trove/extensions/mgmt/instances/service.py b/trove/extensions/mgmt/instances/service.py index 0d14b7781d..b05b94af42 100644 --- a/trove/extensions/mgmt/instances/service.py +++ b/trove/extensions/mgmt/instances/service.py @@ -50,8 +50,7 @@ class MgmtInstanceController(InstanceController): def index(self, req, tenant_id, detailed=False): """Return all instances.""" LOG.info("Indexing a database instance for tenant '%(tenant_id)s'\n" - "req : '%(req)s'\n\n", { - "tenant_id": tenant_id, "req": req}) + "req : '%(req)s'\n\n", {"tenant_id": tenant_id, "req": req}) context = req.environ[wsgi.CONTEXT_KEY] deleted = None deleted_q = req.GET.get('deleted', '').lower() @@ -61,9 +60,12 @@ class MgmtInstanceController(InstanceController): deleted = False clustered_q = req.GET.get('include_clustered', '').lower() include_clustered = clustered_q == 'true' + project_id = req.GET.get('project_id') + try: instances = models.load_mgmt_instances( - context, deleted=deleted, include_clustered=include_clustered) + context, deleted=deleted, include_clustered=include_clustered, + project_id=project_id) except nova_exceptions.ClientException as e: LOG.exception(e) return wsgi.Result(str(e), 403) diff --git a/trove/tests/unittests/extensions/mgmt/instances/test_service.py b/trove/tests/unittests/extensions/mgmt/instances/test_service.py new file mode 100644 index 0000000000..534b05f33a --- /dev/null +++ b/trove/tests/unittests/extensions/mgmt/instances/test_service.py @@ -0,0 +1,88 @@ +# Copyright 2020 Catalyst Cloud +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from unittest import mock + +from trove.datastore import models as ds_models +from trove.extensions.mgmt.instances import service as ins_service +from trove.instance import models as ins_models +from trove.instance import service_status as srvstatus +from trove.tests.unittests import trove_testtools +from trove.tests.unittests.util import util + + +class TestMgmtInstanceController(trove_testtools.TestCase): + @classmethod + def setUpClass(cls): + util.init_db() + cls.controller = ins_service.MgmtInstanceController() + + cls.ds_name = cls.random_name('datastore') + ds_models.update_datastore(name=cls.ds_name, default_version=None) + ds_models.update_datastore_version( + cls.ds_name, 'test_version', 'mysql', cls.random_uuid(), '', '', 1) + cls.ds = ds_models.Datastore.load(cls.ds_name) + cls.ds_version = ds_models.DatastoreVersion.load(cls.ds, + 'test_version') + + cls.ins_name = cls.random_name('instance') + cls.project_id = cls.random_uuid() + cls.server_id = cls.random_uuid() + cls.instance = ins_models.DBInstance.create( + name=cls.ins_name, flavor_id=cls.random_uuid(), + tenant_id=cls.project_id, + volume_size=1, + datastore_version_id=cls.ds_version.id, + task_status=ins_models.InstanceTasks.BUILDING, + compute_instance_id=cls.server_id + ) + ins_models.InstanceServiceStatus.create( + instance_id=cls.instance.id, + status=srvstatus.ServiceStatuses.NEW + ) + + super(TestMgmtInstanceController, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + util.cleanup_db() + + super(TestMgmtInstanceController, cls).tearDownClass() + + @mock.patch('trove.common.clients.create_nova_client') + def test_index_project_id(self, mock_create_client): + req = mock.MagicMock() + req.GET = { + 'project_id': self.project_id + } + + mock_nova_client = mock.MagicMock() + mock_nova_client.servers.list.return_value = [ + mock.MagicMock(id=self.server_id)] + mock_create_client.return_value = mock_nova_client + + result = self.controller.index(req, mock.ANY) + + self.assertEqual(200, result.status) + data = result.data(None) + self.assertEqual(1, len(data['instances'])) + + req.GET = { + 'project_id': self.random_uuid() + } + + result = self.controller.index(req, mock.ANY) + + self.assertEqual(200, result.status) + data = result.data(None) + self.assertEqual(0, len(data['instances']))