From c032792bbbb9cf9447415d91b0fa68cc040418e2 Mon Sep 17 00:00:00 2001 From: Andrew Laski Date: Fri, 10 Jun 2016 08:51:25 -0400 Subject: [PATCH] New discover command to add new hosts to a cell While there are some nova-manage commands to take an existing deployment and migrate all of its hosts into a new cellsv2 environment there was no way to add more hosts to a cell. This command can be run at any time after the initial migration and will map any hosts in a cell that have not been seen before. Nothing else changes about adding hosts to a deployment. Configure them to use a nova database and start them up and they'll register themselves with that database. This new command simply lets the API know how to route requests to those hosts. Until this is done instances can not be booted on those hosts. Partially-Implements: bp cells-scheduling-interaction Change-Id: I8c044e5b480edddead28d8c3527d003da566ed1e --- nova/cmd/manage.py | 33 +++++ nova/tests/unit/test_nova_manage.py | 126 ++++++++++++++++++ ...cells-discover-hosts-06a3079ba687e092.yaml | 25 ++++ 3 files changed, 184 insertions(+) create mode 100644 releasenotes/notes/cells-discover-hosts-06a3079ba687e092.yaml diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py index 91f96c73892d..2de97f061459 100644 --- a/nova/cmd/manage.py +++ b/nova/cmd/manage.py @@ -1478,6 +1478,39 @@ class CellV2Commands(object): mapping.cell_mapping.uuid)) return 0 + @args('--cell_uuid', metavar='', dest='cell_uuid', + help='If provided only this cell will be searched for new hosts to ' + 'map.') + def discover_hosts(self, cell_uuid=None): + """Searches cells, or a single cell, and maps found hosts. + + When a new host is added to a deployment it will add a service entry + to the db it's configured to use. This command will check the db for + each cell, or a single one if passed in, and map any hosts which are + not currently mapped. If a host is already mapped nothing will be done. + """ + ctxt = context.RequestContext() + + if cell_uuid: + cell_mappings = [objects.CellMapping.get_by_uuid(ctxt, cell_uuid)] + else: + cell_mappings = objects.CellMappingList.get_all(context) + + for cell_mapping in cell_mappings: + # TODO(alaski): Factor this into helper method on CellMapping + if cell_mapping.uuid == cell_mapping.CELL0_UUID: + continue + with context.target_cell(ctxt, cell_mapping): + compute_nodes = objects.ComputeNodeList.get_all(ctxt) + for compute in compute_nodes: + try: + objects.HostMapping.get_by_host(ctxt, compute.host) + except exception.HostMappingNotFound: + host_mapping = objects.HostMapping( + ctxt, host=compute.host, + cell_mapping=cell_mapping) + host_mapping.create() + CATEGORIES = { 'account': AccountCommands, diff --git a/nova/tests/unit/test_nova_manage.py b/nova/tests/unit/test_nova_manage.py index c431ddbd53ea..ae0f44cac918 100644 --- a/nova/tests/unit/test_nova_manage.py +++ b/nova/tests/unit/test_nova_manage.py @@ -1182,3 +1182,129 @@ class CellV2CommandsTestCase(test.TestCase): # and reasonably verify that path self.assertEqual(1, self.commands.verify_instance(uuidsentinel.foo, quiet=True)) + + def _return_compute_nodes(self, ctxt, num=1): + nodes = [] + for i in range(num): + nodes.append(objects.ComputeNode(ctxt, + uuid=uuidutils.generate_uuid(), + host='fake', + vcpus=1, + memory_mb=1, + local_gb=1, + vcpus_used=0, + memory_mb_used=0, + local_gb_used=0, + hypervisor_type='', + hypervisor_version=1, + cpu_info='')) + return nodes + + @mock.patch.object(context, 'target_cell') + @mock.patch.object(objects, 'HostMapping') + @mock.patch.object(objects.ComputeNodeList, 'get_all') + @mock.patch.object(objects.CellMappingList, 'get_all') + @mock.patch.object(objects.CellMapping, 'get_by_uuid') + def test_discover_hosts_single_cell(self, mock_cell_mapping_get_by_uuid, + mock_cell_mapping_get_all, + mock_compute_get_all, + mock_host_mapping, mock_target_cell): + host_mock = mock.MagicMock() + mock_host_mapping.return_value = host_mock + exc = exception.HostMappingNotFound(name='fake') + mock_host_mapping.get_by_host.side_effect = exc + + ctxt = context.RequestContext() + + compute_nodes = self._return_compute_nodes(ctxt) + mock_compute_get_all.return_value = objects.ComputeNodeList( + objects=compute_nodes) + + cell_mapping = objects.CellMapping(uuid=uuidutils.generate_uuid()) + mock_cell_mapping_get_by_uuid.return_value = cell_mapping + + self.commands.discover_hosts(cell_uuid=cell_mapping.uuid) + + mock_target_cell.assert_called_once_with( + test.MatchType(context.RequestContext), cell_mapping) + host_mock.create.assert_called_once() + mock_host_mapping.assert_called_once_with( + test.MatchType(context.RequestContext), host='fake', + cell_mapping=cell_mapping) + mock_cell_mapping_get_all.assert_not_called() + + @mock.patch.object(context, 'target_cell') + @mock.patch.object(objects, 'HostMapping') + @mock.patch.object(objects.ComputeNodeList, 'get_all') + @mock.patch.object(objects.CellMappingList, 'get_all') + @mock.patch.object(objects.CellMapping, 'get_by_uuid') + def test_discover_hosts_single_cell_no_new_hosts( + self, mock_cell_mapping_get_by_uuid, mock_cell_mapping_get_all, + mock_compute_get_all, mock_host_mapping, mock_target_cell): + + host_mock = mock.MagicMock() + mock_host_mapping.return_value = host_mock + + ctxt = context.RequestContext() + + compute_nodes = self._return_compute_nodes(ctxt) + mock_compute_get_all.return_value = objects.ComputeNodeList( + objects=compute_nodes) + + cell_mapping = objects.CellMapping(uuid=uuidutils.generate_uuid()) + mock_cell_mapping_get_by_uuid.return_value = cell_mapping + + self.commands.discover_hosts(cell_uuid=cell_mapping.uuid) + + mock_target_cell.assert_called_once_with( + test.MatchType(context.RequestContext), cell_mapping) + mock_host_mapping.assert_not_called() + mock_cell_mapping_get_all.assert_not_called() + + @mock.patch.object(context, 'target_cell') + @mock.patch.object(objects, 'HostMapping') + @mock.patch.object(objects.ComputeNodeList, 'get_all') + @mock.patch.object(objects.CellMappingList, 'get_all') + @mock.patch.object(objects.CellMapping, 'get_by_uuid') + def test_discover_hosts_multiple_cells(self, mock_cell_mapping_get_by_uuid, + mock_cell_mapping_get_all, + mock_compute_get_all, + mock_host_mapping, + mock_target_cell): + host_mock = mock.MagicMock() + mock_host_mapping.return_value = host_mock + exc = exception.HostMappingNotFound(name='fake') + mock_host_mapping.get_by_host.side_effect = exc + + ctxt = context.RequestContext() + + compute_nodes = self._return_compute_nodes(ctxt, num=2) + mock_compute_get_all.side_effect = ( + objects.ComputeNodeList(objects=compute_nodes[1:]), + objects.ComputeNodeList(objects=compute_nodes[:1])) + + cell_mapping1 = objects.CellMapping(uuid=uuidutils.generate_uuid()) + cell_mapping2 = objects.CellMapping(uuid=uuidutils.generate_uuid()) + mock_cell_mapping_get_all.return_value = objects.CellMappingList( + objects=[cell_mapping1, cell_mapping2]) + + self.commands.discover_hosts() + + self.assertEqual(2, mock_target_cell.call_count) + target_calls = [mock.call(test.MatchType(context.RequestContext), + cell_mapping1), + mock.call(test.MatchType(context.RequestContext), + cell_mapping2)] + self.assertEqual(target_calls, mock_target_cell.call_args_list) + + self.assertEqual(2, host_mock.create.call_count) + self.assertEqual(2, mock_host_mapping.call_count) + host_mapping_calls = [mock.call(test.MatchType(context.RequestContext), + host=compute_nodes[0].host, + cell_mapping=cell_mapping1), + mock.call(test.MatchType(context.RequestContext), + host=compute_nodes[1].host, + cell_mapping=cell_mapping2)] + self.assertEqual(host_mapping_calls, mock_host_mapping.call_args_list) + + mock_cell_mapping_get_by_uuid.assert_not_called() diff --git a/releasenotes/notes/cells-discover-hosts-06a3079ba687e092.yaml b/releasenotes/notes/cells-discover-hosts-06a3079ba687e092.yaml new file mode 100644 index 000000000000..1b602d7c4f09 --- /dev/null +++ b/releasenotes/notes/cells-discover-hosts-06a3079ba687e092.yaml @@ -0,0 +1,25 @@ +--- +features: + - A new nova-manage command has been added to discover any new hosts that are + added to a cell. If a deployment has migrated to cellsv2 using either the + simple_cell_setup or the map_cell0/map_cell_and_hosts/map_instances combo + then anytime a new host is added to a cell this new + "nova-manage cell_v2 discover_hosts" needs to be run before instances can + be booted on that host. If multiple hosts are added at one time the command + only needs to be run one time to discover all of them. + + Please note that adding a host to a cell and not running this command could + lead to build failures/reschedules if that host is selected by the + scheduler. The discover_hosts command is necessary to route requests to the + host but is not necessary in order for the scheduler to be aware of the + host. In order to avoid that it is advised that new compute hosts are + disabled until the discover command has been run. +issues: + - If a deployer has updated their deployment to using cellsv2 using either + the simple_cell_setup or the map_cell0/map_cell_and_hosts/map_instances + combo and they add a new host into the cell it may cause build failures + or reschedules until they run the "nova-manage cell_v2 discover_hosts" + command. This is because the scheduler will quickly become aware of the + host but nova-api will not know how to route the request to that host until + it has been "discovered". In order to avoid that it is advised that + new computes are disabled until the discover command has been run.