Make discover_hosts only query for unmapped ComputeNode records

This should be much more efficient and capable of being run at a higher
frequency on more deployments.

Related to blueprint discover-hosts-faster
Change-Id: I8cbc27291c5f9cbeaef01340572974fdbd8d2656
This commit is contained in:
Dan Smith
2017-02-01 12:24:34 -08:00
committed by Matt Riedemann
parent 221427a043
commit 95c190c23d
5 changed files with 129 additions and 27 deletions

View File

@@ -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 to discover new hosts that have been added to cells. If negative (the
default), no automatic discovery will occur. default), no automatic discovery will occur.
Small deployments may want this periodic task enabled, as surveying the Deployments where compute nodes come and go frequently may want this
cells for new hosts is likely to be lightweight enough to not cause undue enabled, where others may prefer to manually discover hosts when one
burdon to the scheduler. However, larger clouds (and those that are not is added to avoid any overhead from constantly checking. If enabled,
adding hosts regularly) will likely want to disable this automatic every time this runs, we will select any unmapped hosts out of each
behavior and instead use the `nova-manage cell_v2 discover_hosts` command cell database on every run.
when hosts have been added to a cell.
"""), """),
] ]

View File

@@ -167,6 +167,28 @@ class HostMappingList(base.ObjectListBase, base.NovaObject):
return base.obj_make_list(context, cls(), HostMapping, db_mappings) 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): 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 # 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 # 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") % status_fn(_("Getting compute nodes from cell: %(uuid)s") %
{'uuid': cm.uuid}) {'uuid': cm.uuid})
with context.target_cell(ctxt, cm): with context.target_cell(ctxt, cm):
compute_nodes = objects.ComputeNodeList.get_all(ctxt) compute_nodes = objects.ComputeNodeList.get_all_by_not_mapped(
status_fn(_('Found %(num)s computes in cell: %(uuid)s') % ctxt, 1)
status_fn(_('Found %(num)s unmapped computes in cell: %(uuid)s') %
{'num': len(compute_nodes), {'num': len(compute_nodes),
'uuid': cm.uuid}) 'uuid': cm.uuid})
for compute in compute_nodes: added_hm = _check_and_create_host_mappings(ctxt, cm, compute_nodes,
status_fn(_("Checking host mapping for compute host " status_fn)
"'%(host)s': %(uuid)s") % host_mappings.extend(added_hm)
{'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)
return host_mappings return host_mappings

View File

@@ -10,14 +10,17 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import mock
from oslo_utils import uuidutils from oslo_utils import uuidutils
from nova import context from nova import context
from nova import exception from nova import exception
from nova import objects
from nova.objects import cell_mapping from nova.objects import cell_mapping
from nova.objects import host_mapping from nova.objects import host_mapping
from nova import test from nova import test
from nova.tests import fixtures from nova.tests import fixtures
from nova.tests import uuidsentinel as uuids
sample_mapping = {'host': 'fake-host', sample_mapping = {'host': 'fake-host',
@@ -134,3 +137,81 @@ class HostMappingTestCase(test.NoDBTestCase):
self.context, db_host_mapping['cell_id']) self.context, db_host_mapping['cell_id'])
self.assertEqual(1, len(host_mapping_list)) self.assertEqual(1, len(host_mapping_list))
self.assertEqual(db_host_mapping['id'], host_mapping_list[0].id) 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))

View File

@@ -154,7 +154,7 @@ class TestHostMappingDiscovery(test.NoDBTestCase):
@mock.patch('nova.objects.CellMappingList.get_all') @mock.patch('nova.objects.CellMappingList.get_all')
@mock.patch('nova.objects.HostMapping.create') @mock.patch('nova.objects.HostMapping.create')
@mock.patch('nova.objects.HostMapping.get_by_host') @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, def test_discover_hosts_all(self, mock_cn_get, mock_hm_get, mock_hm_create,
mock_cm): mock_cm):
def _hm_get(context, host): def _hm_get(context, host):
@@ -174,7 +174,9 @@ class TestHostMappingDiscovery(test.NoDBTestCase):
uuid=uuids.cm2)] uuid=uuids.cm2)]
mock_cm.return_value = cell_mappings mock_cm.return_value = cell_mappings
ctxt = context.get_admin_context() 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.assertEqual(2, len(hms))
self.assertTrue(mock_hm_create.called) self.assertTrue(mock_hm_create.called)
self.assertEqual(['d', 'e'], self.assertEqual(['d', 'e'],
@@ -183,7 +185,7 @@ class TestHostMappingDiscovery(test.NoDBTestCase):
@mock.patch('nova.objects.CellMapping.get_by_uuid') @mock.patch('nova.objects.CellMapping.get_by_uuid')
@mock.patch('nova.objects.HostMapping.create') @mock.patch('nova.objects.HostMapping.create')
@mock.patch('nova.objects.HostMapping.get_by_host') @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, def test_discover_hosts_one(self, mock_cn_get, mock_hm_get, mock_hm_create,
mock_cm): mock_cm):
def _hm_get(context, host): def _hm_get(context, host):
@@ -202,7 +204,9 @@ class TestHostMappingDiscovery(test.NoDBTestCase):
mock_cm.return_value = objects.CellMapping(name='foo', mock_cm.return_value = objects.CellMapping(name='foo',
uuid=uuids.cm1) uuid=uuids.cm1)
ctxt = context.get_admin_context() 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.assertEqual(1, len(hms))
self.assertTrue(mock_hm_create.called) self.assertTrue(mock_hm_create.called)
self.assertEqual(['d'], self.assertEqual(['d'],

View File

@@ -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.