Minimal construct plumbing for nova service-list when a cell is down

This patch modifies the existing behavior of nova service-list which
ignores a down cell to returning a partial construct consisting of
the binary and host info obtained from the API host_mappings table
for the compute services in the down cells. This is enabled by passing
a new enough microversion, which is added in a later patch in this
series.

Related to blueprint handling-down-cell

Change-Id: I9cf5e4eb7f70c495001fd064c060d7d5b3dd5bff
This commit is contained in:
Surya Seetharaman 2018-08-14 14:30:06 +02:00 committed by Matt Riedemann
parent ef5aa8a818
commit 870d718c17
5 changed files with 70 additions and 8 deletions

View File

@ -54,10 +54,10 @@ class ServiceController(wsgi.Controller):
context.can(services_policies.BASE_POLICY_NAME)
_services = [
s
for s in self.host_api.service_get_all(context, set_zones=True,
all_cells=True)
if s['binary'] not in api_services
s
for s in self.host_api.service_get_all(context, set_zones=True,
all_cells=True, cell_down_support=False)
if s['binary'] not in api_services
]
host = ''

View File

@ -5079,7 +5079,7 @@ class HostAPI(base.Base):
return result
def service_get_all(self, context, filters=None, set_zones=False,
all_cells=False):
all_cells=False, cell_down_support=False):
"""Returns a list of services, optionally filtering the results.
If specified, 'filters' should be a dictionary containing services
@ -5087,6 +5087,10 @@ class HostAPI(base.Base):
the 'compute' topic, use filters={'topic': 'compute'}.
If all_cells=True, then scan all cells and merge the results.
If cell_down_support=True then return minimal service records
for cells that do not respond based on what we have in the
host mappings. These will have only 'binary' and 'host' set.
"""
if filters is None:
filters = {}
@ -5101,9 +5105,27 @@ class HostAPI(base.Base):
services = []
service_dict = nova_context.scatter_gather_all_cells(context,
objects.ServiceList.get_all, disabled, set_zones=set_zones)
for service in service_dict.values():
for cell_uuid, service in service_dict.items():
if not nova_context.is_cell_failure_sentinel(service):
services.extend(service)
elif cell_down_support:
unavailable_services = objects.ServiceList()
cid = [cm.id for cm in nova_context.CELLS
if cm.uuid == cell_uuid]
# We know cid[0] is in the list because we are using the
# same list that scatter_gather_all_cells used
hms = objects.HostMappingList.get_by_cell_id(context,
cid[0])
for hm in hms:
unavailable_services.objects.append(objects.Service(
binary='nova-compute', host=hm.host))
LOG.warning("Cell %s is not responding and hence only "
"partial results are available from this "
"cell.", cell_uuid)
services.extend(unavailable_services)
else:
LOG.warning("Cell %s is not responding and hence skipped "
"from the results.", cell_uuid)
else:
services = objects.ServiceList.get_all(context, disabled,
set_zones=set_zones)

View File

@ -557,7 +557,7 @@ class HostAPI(compute_api.HostAPI):
return self.cells_rpcapi.get_host_uptime(context, host_name)
def service_get_all(self, context, filters=None, set_zones=False,
all_cells=False):
all_cells=False, cell_down_support=False):
"""Get all services.
Note that this is the cellsv1 variant, which means we ignore the

View File

@ -121,7 +121,7 @@ fake_services_list = [
def fake_service_get_all(services):
def service_get_all(context, filters=None, set_zones=False,
all_cells=False):
all_cells=False, cell_down_support=False):
if set_zones or 'availability_zone' in filters:
return availability_zones.set_availability_zones(context,
services)

View File

@ -199,6 +199,42 @@ class ComputeHostAPITestCase(test.TestCase):
self.assertEqual(['host-%s' % uuids.cell1],
[svc.host for svc in services])
@mock.patch('nova.objects.CellMappingList.get_all')
@mock.patch.object(objects.HostMappingList, 'get_by_cell_id')
@mock.patch('nova.context.scatter_gather_all_cells')
def test_service_get_all_cells_with_minimal_constructs(self, mock_sg,
mock_get_hm,
mock_cm_list):
service = objects.Service(binary='nova-compute',
host='host-%s' % uuids.cell0)
cells = [
objects.CellMapping(uuid=uuids.cell1, id=1),
objects.CellMapping(uuid=uuids.cell2, id=2),
]
mock_cm_list.return_value = cells
context.load_cells()
# create two hms in cell1, which is the down cell in this test.
hm1 = objects.HostMapping(self.ctxt, host='host1-unavailable',
cell_mapping=cells[0])
hm1.create()
hm2 = objects.HostMapping(self.ctxt, host='host2-unavailable',
cell_mapping=cells[0])
hm2.create()
mock_sg.return_value = {
cells[0].uuid: [service],
cells[1].uuid: context.did_not_respond_sentinel,
}
mock_get_hm.return_value = [hm1, hm2]
services = self.host_api.service_get_all(self.ctxt, all_cells=True,
cell_down_support=True)
# returns the results from cell0 and minimal construct from cell1.
self.assertEqual(sorted(['host-%s' % uuids.cell0, 'host1-unavailable',
'host2-unavailable']),
sorted([svc.host for svc in services]))
mock_sg.assert_called_once_with(self.ctxt, objects.ServiceList.get_all,
None, set_zones=False)
mock_get_hm.assert_called_once_with(self.ctxt, cells[1].id)
def test_service_get_all_no_zones(self):
services = [dict(test_service.fake_service,
id=1, topic='compute', host='host1'),
@ -541,6 +577,10 @@ class ComputeHostAPICellsTestCase(ComputeHostAPITestCase):
def test_service_get_all_cells_with_failures(self):
pass
@testtools.skip('cellsv1 does not use this')
def test_service_get_all_cells_with_minimal_constructs(self):
pass
@testtools.skip('cellsv1 does not use this')
def test_service_delete_ambiguous(self):
pass