Get all the database instances by admin

Currently, there is no way for the admin user to get databases of all
the projects, which is very important for auditing or billing purpose.

With this change, admin user can get all the database instances by
running:

$ openstack database instances list --all-projects

Change-Id: I318907b1d4a63017f4a6f7967312770f8784c1f4
Story: #2005735
Task: #33393
This commit is contained in:
Lingxian Kong 2019-05-20 10:32:34 +12:00
parent d0d53a2c91
commit 7a791177e8
9 changed files with 124 additions and 73 deletions

View File

@ -28,7 +28,7 @@ def append_query_strings(url, **query_strings):
if not query_strings:
return url
query = '&'.join('{0}={1}'.format(key, val)
for key, val in query_strings.items() if val)
for key, val in query_strings.items() if val is not None)
return url + ('?' + query if query else "")

View File

@ -69,10 +69,13 @@ def set_attributes_for_print_detail(instance):
class ListDatabaseInstances(command.Lister):
_description = _("List database instances")
columns = ['ID', 'Name', 'Datastore', 'Datastore Version', 'Status',
'Flavor ID', 'Size', 'Region']
admin_columns = [
'ID', 'Name', 'Tenant ID', 'Datastore', 'Datastore Version', 'Status',
'Flavor ID', 'Size'
]
def get_parser(self, prog_name):
parser = super(ListDatabaseInstances, self).get_parser(prog_name)
@ -103,19 +106,34 @@ class ListDatabaseInstances(command.Lister):
"deprecated in the future, retaining just "
"--include_clustered.")
)
parser.add_argument(
'--all-projects',
dest='all_projects',
action="store_true",
default=False,
help=_("Include database instances of all projects (admin only)")
)
return parser
def take_action(self, parsed_args):
db_instances = self.app.client_manager.database.instances
instances = db_instances.list(limit=parsed_args.limit,
marker=parsed_args.marker,
include_clustered=(parsed_args.
include_clustered))
if parsed_args.all_projects:
db_instances = self.app.client_manager.database.mgmt_instances
cols = self.admin_columns
else:
db_instances = self.app.client_manager.database.instances
cols = self.columns
instances = db_instances.list(
limit=parsed_args.limit,
marker=parsed_args.marker,
include_clustered=parsed_args.include_clustered
)
if instances:
instances = set_attributes_for_print(instances)
instances = [osc_utils.get_item_properties(i, self.columns)
instances = [osc_utils.get_item_properties(i, cols)
for i in instances]
return self.columns, instances
return cols, instances
class ShowDatabaseInstance(command.ShowOne):

View File

@ -158,25 +158,37 @@ class FakeHTTPClient(base_client.HTTPClient):
return r, body
def get_instances(self, **kw):
return (200, {}, {"instances": [
return (
200,
{},
{
"id": "1234",
"name": "test-member-1",
"status": "ACTIVE",
"ip": ["10.0.0.13"],
"volume": {"size": 2},
"flavor": {"id": "02"},
"region": "regionOne",
"datastore": {"version": "5.6", "type": "mysql"}},
{
"id": "5678",
"name": "test-member-2",
"status": "ACTIVE",
"ip": ["10.0.0.14"],
"volume": {"size": 2},
"flavor": {"id": "2"},
"region": "regionOne",
"datastore": {"version": "5.6", "type": "mysql"}}]})
"instances": [
{
"id": "1234",
"name": "test-member-1",
"status": "ACTIVE",
"ip": ["10.0.0.13"],
"volume": {"size": 2},
"flavor": {"id": "02"},
"region": "regionOne",
"datastore": {"version": "5.6", "type": "mysql"},
"tenant_id": "fake_tenant_id",
},
{
"id": "5678",
"name": "test-member-2",
"status": "ACTIVE",
"ip": ["10.0.0.14"],
"volume": {"size": 2},
"flavor": {"id": "2"},
"region": "regionOne",
"datastore": {"version": "5.6", "type": "mysql"},
"tenant_id": "fake_tenant_id",
},
]
}
)
def get_instance_counts(self, **kw):
return (200, {}, {"instances": [

View File

@ -21,44 +21,71 @@ from troveclient.tests.osc.v1 import fakes
class TestInstances(fakes.TestDatabasev1):
fake_instances = fakes.FakeInstances()
def setUp(self):
super(TestInstances, self).setUp()
self.fake_instances = fakes.FakeInstances()
self.instance_client = self.app.client_manager.database.instances
self.mgmt_client = self.app.client_manager.database.mgmt_instances
class TestInstanceList(TestInstances):
defaults = {
'include_clustered': False,
'limit': None,
'marker': None
}
columns = database_instances.ListDatabaseInstances.columns
values = [('1234', 'test-member-1', 'mysql', '5.6', 'ACTIVE', '02', 2,
'regionOne'), ('5678', 'test-member-2', 'mysql', '5.6',
'ACTIVE', '2', 2, 'regionOne')]
def setUp(self):
super(TestInstanceList, self).setUp()
self.cmd = database_instances.ListDatabaseInstances(self.app, None)
self.data = self.fake_instances.get_instances()
self.instance_client.list.return_value = common.Paginated(self.data)
def test_instance_list_defaults(self):
self.instance_client.list.return_value = common.Paginated(self.data)
parsed_args = self.check_parser(self.cmd, [], [])
columns, data = self.cmd.take_action(parsed_args)
self.instance_client.list.assert_called_once_with(**self.defaults)
self.assertEqual(self.columns, columns)
self.assertEqual(self.values, data)
self.assertEqual(
database_instances.ListDatabaseInstances.columns,
columns
)
values = [
('1234', 'test-member-1', 'mysql', '5.6', 'ACTIVE', '02', 2,
'regionOne'),
('5678', 'test-member-2', 'mysql', '5.6', 'ACTIVE', '2', 2,
'regionOne')
]
self.assertEqual(values, data)
def test_instance_list_all_projects(self):
self.mgmt_client.list.return_value = common.Paginated(self.data)
parsed_args = self.check_parser(self.cmd, ["--all-projects"],
[("all_projects", True)])
columns, instances = self.cmd.take_action(parsed_args)
self.mgmt_client.list.assert_called_once_with(**self.defaults)
self.assertEqual(
database_instances.ListDatabaseInstances.admin_columns,
columns
)
expected_instances = [
('1234', 'test-member-1', 'fake_tenant_id', 'mysql', '5.6',
'ACTIVE', '02', 2),
('5678', 'test-member-2', 'fake_tenant_id', 'mysql', '5.6',
'ACTIVE', '2', 2)
]
self.assertEqual(expected_instances, instances)
class TestInstanceShow(TestInstances):
values = ('mysql', '5.6', '02', '1234', '10.0.0.13',
'test-member-1', 'regionOne', 'ACTIVE', 2)
'test-member-1', 'regionOne', 'ACTIVE', 'fake_tenant_id', 2)
def setUp(self):
super(TestInstanceShow, self).setUp()
@ -74,6 +101,7 @@ class TestInstanceShow(TestInstances):
'name',
'region',
'status',
'tenant_id',
'volume',
)

View File

@ -60,6 +60,13 @@ class CommonTest(testtools.TestCase):
self.assertIn("key1=%s" % opts['key1'], result)
self.assertNotIn("key2=%s" % opts['key2'], result)
opts = {'key1': 'value1', 'key2': None, 'key3': False}
result = common.append_query_strings(url, **opts)
self.assertTrue(result.startswith(url + '?'))
self.assertIn("key1=value1", result)
self.assertNotIn("key2", result)
self.assertIn("key3=False", result)
class PaginatedTest(testtools.TestCase):

View File

@ -73,18 +73,17 @@ class ManagementTest(testtools.TestCase):
p, i = self.management.show(1)
self.assertEqual(('/mgmt/instances/instance1', 'instance'), (p, i))
def test_index(self):
def test_list(self):
page_mock = mock.Mock()
self.management._paginated = page_mock
self.management.index(deleted=True)
page_mock.assert_called_with('/mgmt/instances?deleted=true',
'instances', None, None)
self.management.index(deleted=False)
page_mock.assert_called_with('/mgmt/instances?deleted=false',
'instances', None, None)
self.management.index(deleted=True, limit=10, marker="foo")
page_mock.assert_called_with('/mgmt/instances?deleted=true',
'instances', 10, "foo")
self.management.list(deleted=True)
page_mock.assert_called_with('/mgmt/instances', 'instances', None,
None, query_strings={'deleted': True})
self.management.list(deleted=False, limit=10, marker="foo")
page_mock.assert_called_with('/mgmt/instances', 'instances', 10, "foo",
query_strings={"deleted": False})
def test_root_enabled_history(self):
self.management.api.client.get = mock.Mock(return_value=('resp', None))

View File

@ -162,7 +162,7 @@ class ShellTest(utils.TestCase):
def test_instance_list(self):
self.run_command('list')
self.assert_called('GET', '/instances')
self.assert_called('GET', '/instances?include_clustered=False')
def test_instance_show(self):
self.run_command('show 1234')

View File

@ -23,7 +23,7 @@ from troveclient.v1 import datastores
from troveclient.v1 import flavors
from troveclient.v1 import instances
from troveclient.v1 import limits
# from troveclient.v1 import management
from troveclient.v1 import management
from troveclient.v1 import metadata
from troveclient.v1 import modules
from troveclient.v1 import quota
@ -82,11 +82,11 @@ class Client(object):
self.configuration_parameters = config_parameters
self.metadata = metadata.Metadata(self)
self.modules = modules.Modules(self)
self.quota = quota.Quotas(self)
self.mgmt_instances = management.Management(self)
# self.hosts = Hosts(self)
self.quota = quota.Quotas(self)
# self.storage = StorageInfo(self)
# self.management = Management(self)
# self.management = MgmtClusters(self)
# self.mgmt_flavor = MgmtFlavors(self)
# self.accounts = Accounts(self)

View File

@ -35,10 +35,6 @@ class Management(base.ManagerWithFind):
"""Manage :class:`Instances` resources."""
resource_class = instances.Instance
# Appease the abc gods
def list(self):
pass
def show(self, instance):
"""Get details of one instance.
@ -48,22 +44,13 @@ class Management(base.ManagerWithFind):
return self._get("/mgmt/instances/%s" % base.getid(instance),
'instance')
def index(self, deleted=None, limit=None, marker=None):
"""Show an overview of all local instances.
def list(self, limit=None, marker=None, deleted=False, **kwargs):
"""Get all the database instances."""
url = "/mgmt/instances"
kwargs["deleted"] = deleted
Optionally, filter by deleted status.
:rtype: list of :class:`Instance`.
"""
form = ''
if deleted is not None:
if deleted:
form = "?deleted=true"
else:
form = "?deleted=false"
url = "/mgmt/instances%s" % form
return self._paginated(url, "instances", limit, marker)
return self._paginated(url, "instances", limit, marker,
query_strings=kwargs)
def root_enabled_history(self, instance):
"""Get root access history of one instance."""