diff --git a/nova/conf/scheduler.py b/nova/conf/scheduler.py index 42796e9a0712..7aa2cf0d2896 100644 --- a/nova/conf/scheduler.py +++ b/nova/conf/scheduler.py @@ -133,12 +133,11 @@ This value controls how often (in seconds) the scheduler should attempt to discover new hosts that have been added to cells. If negative (the default), no automatic discovery will occur. -Small deployments may want this periodic task enabled, as surveying the -cells for new hosts is likely to be lightweight enough to not cause undue -burdon to the scheduler. However, larger clouds (and those that are not -adding hosts regularly) will likely want to disable this automatic -behavior and instead use the `nova-manage cell_v2 discover_hosts` command -when hosts have been added to a cell. +Deployments where compute nodes come and go frequently may want this +enabled, where others may prefer to manually discover hosts when one +is added to avoid any overhead from constantly checking. If enabled, +every time this runs, we will select any unmapped hosts out of each +cell database on every run. """), ] diff --git a/nova/objects/host_mapping.py b/nova/objects/host_mapping.py index 397b607f958d..102a0cb5b2d5 100644 --- a/nova/objects/host_mapping.py +++ b/nova/objects/host_mapping.py @@ -167,6 +167,28 @@ class HostMappingList(base.ObjectListBase, base.NovaObject): return base.obj_make_list(context, cls(), HostMapping, db_mappings) +def _check_and_create_host_mappings(ctxt, cm, compute_nodes, status_fn): + host_mappings = [] + for compute in compute_nodes: + status_fn(_("Checking host mapping for compute host " + "'%(host)s': %(uuid)s") % + {'host': compute.host, 'uuid': compute.uuid}) + try: + HostMapping.get_by_host(ctxt, compute.host) + except exception.HostMappingNotFound: + status_fn(_("Creating host mapping for compute host " + "'%(host)s': %(uuid)s") % + {'host': compute.host, 'uuid': compute.uuid}) + host_mapping = HostMapping( + ctxt, host=compute.host, + cell_mapping=cm) + host_mapping.create() + host_mappings.append(host_mapping) + compute.mapped = 1 + compute.save() + return host_mappings + + def discover_hosts(ctxt, cell_uuid=None, status_fn=None): # TODO(alaski): If this is not run on a host configured to use the API # database most of the lookups below will fail and may not provide a @@ -197,23 +219,13 @@ def discover_hosts(ctxt, cell_uuid=None, status_fn=None): status_fn(_("Getting compute nodes from cell: %(uuid)s") % {'uuid': cm.uuid}) with context.target_cell(ctxt, cm): - compute_nodes = objects.ComputeNodeList.get_all(ctxt) - status_fn(_('Found %(num)s computes in cell: %(uuid)s') % + compute_nodes = objects.ComputeNodeList.get_all_by_not_mapped( + ctxt, 1) + status_fn(_('Found %(num)s unmapped computes in cell: %(uuid)s') % {'num': len(compute_nodes), 'uuid': cm.uuid}) - for compute in compute_nodes: - status_fn(_("Checking host mapping for compute host " - "'%(host)s': %(uuid)s") % - {'host': compute.host, 'uuid': compute.uuid}) - try: - objects.HostMapping.get_by_host(ctxt, compute.host) - except exception.HostMappingNotFound: - status_fn(_("Creating host mapping for compute host " - "'%(host)s': %(uuid)s") % - {'host': compute.host, 'uuid': compute.uuid}) - host_mapping = objects.HostMapping( - ctxt, host=compute.host, - cell_mapping=cm) - host_mapping.create() - host_mappings.append(host_mapping) + added_hm = _check_and_create_host_mappings(ctxt, cm, compute_nodes, + status_fn) + host_mappings.extend(added_hm) + return host_mappings diff --git a/nova/tests/functional/db/test_host_mapping.py b/nova/tests/functional/db/test_host_mapping.py index 896346101dc0..21ad08a94567 100644 --- a/nova/tests/functional/db/test_host_mapping.py +++ b/nova/tests/functional/db/test_host_mapping.py @@ -10,14 +10,17 @@ # License for the specific language governing permissions and limitations # under the License. +import mock from oslo_utils import uuidutils from nova import context from nova import exception +from nova import objects from nova.objects import cell_mapping from nova.objects import host_mapping from nova import test from nova.tests import fixtures +from nova.tests import uuidsentinel as uuids sample_mapping = {'host': 'fake-host', @@ -134,3 +137,81 @@ class HostMappingTestCase(test.NoDBTestCase): self.context, db_host_mapping['cell_id']) self.assertEqual(1, len(host_mapping_list)) self.assertEqual(db_host_mapping['id'], host_mapping_list[0].id) + + +class HostMappingDiscoveryTest(test.TestCase): + def _setup_cells(self): + ctxt = context.get_admin_context() + self.celldbs = fixtures.CellDatabases() + cells = [] + for uuid in (uuids.cell1, uuids.cell2, uuids.cell3): + cm = objects.CellMapping(context=ctxt, + uuid=uuid, + database_connection=uuid, + transport_url='fake://') + cm.create() + cells.append(cm) + self.celldbs.add_cell_database(uuid) + self.useFixture(self.celldbs) + + for cell in cells: + for i in (1, 2, 3): + # Make one host in each cell unmapped + mapped = 0 if i == 2 else 1 + + host = 'host-%s-%i' % (cell.uuid, i) + if mapped: + hm = objects.HostMapping(context=ctxt, + cell_mapping=cell, + host=host) + hm.create() + + with context.target_cell(ctxt, cell): + cn = objects.ComputeNode( + context=ctxt, vcpus=1, memory_mb=1, local_gb=1, + vcpus_used=0, memory_mb_used=0, local_gb_used=0, + hypervisor_type='danvm', hypervisor_version='1', + cpu_info='foo', + cpu_allocation_ratio=1.0, + ram_allocation_ratio=1.0, + disk_allocation_ratio=1.0, + mapped=mapped, host=host) + cn.create() + + def test_discover_hosts(self): + status = lambda m: None + + ctxt = context.get_admin_context() + + # NOTE(danms): Three cells, one unmapped host per cell + mappings = host_mapping.discover_hosts(ctxt, status_fn=status) + self.assertEqual(3, len(mappings)) + + # NOTE(danms): All hosts should be mapped now, so we should do + # no lookups for them + with mock.patch('nova.objects.HostMapping.get_by_host') as mock_gbh: + mappings = host_mapping.discover_hosts(ctxt, status_fn=status) + self.assertFalse(mock_gbh.called) + self.assertEqual(0, len(mappings)) + + def test_discover_hosts_one_cell(self): + status = lambda m: None + + ctxt = context.get_admin_context() + cells = objects.CellMappingList.get_all(ctxt) + + # NOTE(danms): One cell, one unmapped host per cell + mappings = host_mapping.discover_hosts(ctxt, cells[1].uuid, + status_fn=status) + self.assertEqual(1, len(mappings)) + + # NOTE(danms): Three cells, two with one more unmapped host + mappings = host_mapping.discover_hosts(ctxt, status_fn=status) + self.assertEqual(2, len(mappings)) + + # NOTE(danms): All hosts should be mapped now, so we should do + # no lookups for them + with mock.patch('nova.objects.HostMapping.get_by_host') as mock_gbh: + mappings = host_mapping.discover_hosts(ctxt, status_fn=status) + self.assertFalse(mock_gbh.called) + self.assertEqual(0, len(mappings)) diff --git a/nova/tests/unit/objects/test_host_mapping.py b/nova/tests/unit/objects/test_host_mapping.py index d1ea9d959b9a..18800768f10d 100644 --- a/nova/tests/unit/objects/test_host_mapping.py +++ b/nova/tests/unit/objects/test_host_mapping.py @@ -154,7 +154,7 @@ class TestHostMappingDiscovery(test.NoDBTestCase): @mock.patch('nova.objects.CellMappingList.get_all') @mock.patch('nova.objects.HostMapping.create') @mock.patch('nova.objects.HostMapping.get_by_host') - @mock.patch('nova.objects.ComputeNodeList.get_all') + @mock.patch('nova.objects.ComputeNodeList.get_all_by_not_mapped') def test_discover_hosts_all(self, mock_cn_get, mock_hm_get, mock_hm_create, mock_cm): def _hm_get(context, host): @@ -174,7 +174,9 @@ class TestHostMappingDiscovery(test.NoDBTestCase): uuid=uuids.cm2)] mock_cm.return_value = cell_mappings ctxt = context.get_admin_context() - hms = host_mapping.discover_hosts(ctxt) + with mock.patch('nova.objects.ComputeNode.save') as mock_save: + hms = host_mapping.discover_hosts(ctxt) + mock_save.assert_has_calls([mock.call(), mock.call()]) self.assertEqual(2, len(hms)) self.assertTrue(mock_hm_create.called) self.assertEqual(['d', 'e'], @@ -183,7 +185,7 @@ class TestHostMappingDiscovery(test.NoDBTestCase): @mock.patch('nova.objects.CellMapping.get_by_uuid') @mock.patch('nova.objects.HostMapping.create') @mock.patch('nova.objects.HostMapping.get_by_host') - @mock.patch('nova.objects.ComputeNodeList.get_all') + @mock.patch('nova.objects.ComputeNodeList.get_all_by_not_mapped') def test_discover_hosts_one(self, mock_cn_get, mock_hm_get, mock_hm_create, mock_cm): def _hm_get(context, host): @@ -202,7 +204,9 @@ class TestHostMappingDiscovery(test.NoDBTestCase): mock_cm.return_value = objects.CellMapping(name='foo', uuid=uuids.cm1) ctxt = context.get_admin_context() - hms = host_mapping.discover_hosts(ctxt, uuids.cm1) + with mock.patch('nova.objects.ComputeNode.save') as mock_save: + hms = host_mapping.discover_hosts(ctxt, uuids.cm1) + mock_save.assert_called_once_with() self.assertEqual(1, len(hms)) self.assertTrue(mock_hm_create.called) self.assertEqual(['d'], diff --git a/releasenotes/notes/discover-hosts-periodic-is-more-efficient-6c55b606a7831750.yaml b/releasenotes/notes/discover-hosts-periodic-is-more-efficient-6c55b606a7831750.yaml new file mode 100644 index 000000000000..bdbd591945f7 --- /dev/null +++ b/releasenotes/notes/discover-hosts-periodic-is-more-efficient-6c55b606a7831750.yaml @@ -0,0 +1,6 @@ +--- +features: + - The `discover_hosts_in_cells_interval` periodic task in the scheduler is now + more efficient in that it can specifically query unmapped compute nodes from + the cell databases instead of having to query them all and compare against + existing host mappings. \ No newline at end of file