From c7b51a63b0c3b5ad80979060bbfe49287e6da847 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Fri, 1 Dec 2017 23:03:04 +0000 Subject: [PATCH] Add support for listing hosts in cellv2 Add a ``nova-manage cell_v2 list_hosts`` command for listing hosts in one or all v2 cells. Change-Id: Ie8eaa8701aafac10e030568107b8e6255a60434d Closes-Bug: #1735687 --- doc/source/cli/nova-manage.rst | 7 ++ nova/cmd/manage.py | 28 ++++++++ nova/objects/host_mapping.py | 20 ++++-- nova/tests/unit/objects/test_host_mapping.py | 29 ++++++++ nova/tests/unit/objects/test_objects.py | 2 +- nova/tests/unit/test_nova_manage.py | 66 +++++++++++++++++++ ...list-hosts-in-cellv2-7afa67ce0d48b6a2.yaml | 5 ++ 7 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-1735687-add-list-hosts-in-cellv2-7afa67ce0d48b6a2.yaml diff --git a/doc/source/cli/nova-manage.rst b/doc/source/cli/nova-manage.rst index 8f4892a2f2da..d68bf4579045 100644 --- a/doc/source/cli/nova-manage.rst +++ b/doc/source/cli/nova-manage.rst @@ -215,6 +215,13 @@ Nova Cells v2 found for the cell (cell not empty) without ``--force`` option, and 3 if there are instances mapped to the cell (cell not empty). +``nova-manage cell_v2 list_hosts [--cell_uuid ]`` + + Lists the hosts in one or all v2 cells. By default hosts in all v2 cells + are listed. Use the --cell_uuid option to list hosts in a specific cell. + If the cell is not found by uuid, this command will return an exit code + of 1. Otherwise, the exit code will be 0. + ``nova-manage cell_v2 update_cell --cell_uuid [--name ] [--transport-url ] [--database_connection ]`` Updates the properties of a cell by the given uuid. If a diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py index 939f524da357..956d3dba576a 100644 --- a/nova/cmd/manage.py +++ b/nova/cmd/manage.py @@ -1837,6 +1837,34 @@ class CellV2Commands(object): return 0 + @args('--cell_uuid', metavar='', dest='cell_uuid', + help=_('The uuid of the cell.')) + def list_hosts(self, cell_uuid=None): + """Lists the hosts in one or all v2 cells.""" + ctxt = context.get_admin_context() + if cell_uuid: + # Find the CellMapping given the uuid. + try: + cell_mapping = objects.CellMapping.get_by_uuid(ctxt, cell_uuid) + except exception.CellMappingNotFound: + print(_('Cell with uuid %s was not found.') % cell_uuid) + return 1 + + host_mappings = objects.HostMappingList.get_by_cell_id( + ctxt, cell_mapping.id) + else: + host_mappings = objects.HostMappingList.get_all(ctxt) + + field_names = [_('Cell Name'), _('Cell UUID'), _('Hostname')] + + t = prettytable.PrettyTable(field_names) + for host in sorted(host_mappings, key=lambda _host: _host.host): + fields = [host.cell_mapping.name, host.cell_mapping.uuid, + host.host] + t.add_row(fields) + print(t) + return 0 + @args('--cell_uuid', metavar='', dest='cell_uuid', required=True, help=_('The uuid of the cell.')) @args('--host', metavar='', dest='host', diff --git a/nova/objects/host_mapping.py b/nova/objects/host_mapping.py index a4f57acf3504..235d9f968c8d 100644 --- a/nova/objects/host_mapping.py +++ b/nova/objects/host_mapping.py @@ -148,7 +148,8 @@ class HostMapping(base.NovaTimestampObject, base.NovaObject): @base.NovaObjectRegistry.register class HostMappingList(base.ObjectListBase, base.NovaObject): # Version 1.0: Initial version - VERSION = '1.0' + # Version 1.1: Add get_all method + VERSION = '1.1' fields = { 'objects': fields.ListOfObjectsField('HostMapping'), @@ -156,14 +157,21 @@ class HostMappingList(base.ObjectListBase, base.NovaObject): @staticmethod @db_api.api_context_manager.reader - def _get_by_cell_id_from_db(context, cell_id): - return (context.session.query(api_models.HostMapping) - .options(joinedload('cell_mapping')) - .filter(api_models.HostMapping.cell_id == cell_id)).all() + def _get_from_db(context, cell_id=None): + query = (context.session.query(api_models.HostMapping) + .options(joinedload('cell_mapping'))) + if cell_id: + query = query.filter(api_models.HostMapping.cell_id == cell_id) + return query.all() @base.remotable_classmethod def get_by_cell_id(cls, context, cell_id): - db_mappings = cls._get_by_cell_id_from_db(context, cell_id) + db_mappings = cls._get_from_db(context, cell_id) + return base.obj_make_list(context, cls(), HostMapping, db_mappings) + + @base.remotable_classmethod + def get_all(cls, context): + db_mappings = cls._get_from_db(context) return base.obj_make_list(context, cls(), HostMapping, db_mappings) diff --git a/nova/tests/unit/objects/test_host_mapping.py b/nova/tests/unit/objects/test_host_mapping.py index 18800768f10d..b06c4c85b03c 100644 --- a/nova/tests/unit/objects/test_host_mapping.py +++ b/nova/tests/unit/objects/test_host_mapping.py @@ -150,6 +150,35 @@ class TestRemoteHostMappingObject(test_objects._RemoteTest, pass +class _TestHostMappingListObject(object): + def _check_cell_map_value(self, db_val, cell_obj): + self.assertEqual(db_val, cell_obj.id) + + @mock.patch.object(host_mapping.HostMappingList, '_get_from_db') + def test_get_all(self, get_from_db): + fake_cell = test_cell_mapping.get_db_mapping(id=1) + db_mapping = get_db_mapping(mapped_cell=fake_cell) + get_from_db.return_value = [db_mapping] + + mapping_obj = objects.HostMappingList.get_all(self.context) + + get_from_db.assert_called_once_with(self.context) + self.compare_obj(mapping_obj.objects[0], db_mapping, + subs={'cell_mapping': 'cell_id'}, + comparators={ + 'cell_mapping': self._check_cell_map_value}) + + +class TestCellMappingListObject(test_objects._LocalTest, + _TestHostMappingListObject): + pass + + +class TestRemoteCellMappingListObject(test_objects._RemoteTest, + _TestHostMappingListObject): + pass + + class TestHostMappingDiscovery(test.NoDBTestCase): @mock.patch('nova.objects.CellMappingList.get_all') @mock.patch('nova.objects.HostMapping.create') diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index da808254053a..0c8a7d1ca2f2 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1092,7 +1092,7 @@ object_data = { 'FloatingIP': '1.10-52a67d52d85eb8b3f324a5b7935a335b', 'FloatingIPList': '1.12-e4debd21fddb12cf40d36f737225fa9d', 'HostMapping': '1.0-1a3390a696792a552ab7bd31a77ba9ac', - 'HostMappingList': '1.0-267d952a5d48361d6d7604f50775bc34', + 'HostMappingList': '1.1-18ac2bfb8c1eb5545bed856da58a79bc', 'HyperVLiveMigrateData': '1.2-bcb6dad687369348ffe0f41da6888704', 'HVSpec': '1.2-de06bcec472a2f04966b855a49c46b41', 'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502', diff --git a/nova/tests/unit/test_nova_manage.py b/nova/tests/unit/test_nova_manage.py index 88f4e94b4a14..d75f981ff10e 100644 --- a/nova/tests/unit/test_nova_manage.py +++ b/nova/tests/unit/test_nova_manage.py @@ -1813,6 +1813,72 @@ class CellV2CommandsTestCase(test.NoDBTestCase): output = self.output.getvalue().strip() self.assertEqual('', output) + def test_list_hosts(self): + ctxt = context.get_admin_context() + # create the cell mapping + cm1 = objects.CellMapping( + context=ctxt, uuid='9e36a3ed-3eb6-4327', name='london', + database_connection='fake:///db', transport_url='fake:///mq') + cm1.create() + cm2 = objects.CellMapping( + context=ctxt, uuid='8a3c608c-b275-496c', name='dallas', + database_connection='fake:///db', transport_url='fake:///mq') + cm2.create() + # create a host mapping in another cell + hm1 = objects.HostMapping( + context=ctxt, host='fake-host-1', cell_mapping=cm1) + hm1.create() + hm2 = objects.HostMapping( + context=ctxt, host='fake-host-2', cell_mapping=cm2) + hm2.create() + self.assertEqual(0, self.commands.list_hosts()) + output = self.output.getvalue().strip() + self.assertEqual('''\ ++-----------+--------------------+-------------+ +| Cell Name | Cell UUID | Hostname | ++-----------+--------------------+-------------+ +| london | 9e36a3ed-3eb6-4327 | fake-host-1 | +| dallas | 8a3c608c-b275-496c | fake-host-2 | ++-----------+--------------------+-------------+''', + output) + + def test_list_hosts_in_cell(self): + ctxt = context.get_admin_context() + # create the cell mapping + cm1 = objects.CellMapping( + context=ctxt, uuid='9e36a3ed-3eb6-4327', name='london', + database_connection='fake:///db', transport_url='fake:///mq') + cm1.create() + cm2 = objects.CellMapping( + context=ctxt, uuid='8a3c608c-b275-496c', name='dallas', + database_connection='fake:///db', transport_url='fake:///mq') + cm2.create() + # create a host mapping in another cell + hm1 = objects.HostMapping( + context=ctxt, host='fake-host-1', cell_mapping=cm1) + hm1.create() + hm2 = objects.HostMapping( + context=ctxt, host='fake-host-2', cell_mapping=cm2) + hm2.create() + self.assertEqual(0, self.commands.list_hosts( + cell_uuid='9e36a3ed-3eb6-4327')) + output = self.output.getvalue().strip() + self.assertEqual('''\ ++-----------+--------------------+-------------+ +| Cell Name | Cell UUID | Hostname | ++-----------+--------------------+-------------+ +| london | 9e36a3ed-3eb6-4327 | fake-host-1 | ++-----------+--------------------+-------------+''', + output) + + def test_list_hosts_cell_not_found(self): + """Tests trying to delete a host but a specified cell is not found.""" + self.assertEqual(1, self.commands.list_hosts( + cell_uuid=uuidsentinel.cell1)) + output = self.output.getvalue().strip() + self.assertEqual( + 'Cell with uuid %s was not found.' % uuidsentinel.cell1, output) + def test_delete_host_cell_not_found(self): """Tests trying to delete a host but a specified cell is not found.""" self.assertEqual(1, self.commands.delete_host(uuidsentinel.cell1, diff --git a/releasenotes/notes/bug-1735687-add-list-hosts-in-cellv2-7afa67ce0d48b6a2.yaml b/releasenotes/notes/bug-1735687-add-list-hosts-in-cellv2-7afa67ce0d48b6a2.yaml new file mode 100644 index 000000000000..68228384f176 --- /dev/null +++ b/releasenotes/notes/bug-1735687-add-list-hosts-in-cellv2-7afa67ce0d48b6a2.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add a ``nova-manage cell_v2 list_hosts`` command for listing hosts + in one or all v2 cells.