diff --git a/nova/api/openstack/compute/services.py b/nova/api/openstack/compute/services.py index 818b02e7a7c7..7a35c5665fc7 100644 --- a/nova/api/openstack/compute/services.py +++ b/nova/api/openstack/compute/services.py @@ -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 = '' diff --git a/nova/compute/api.py b/nova/compute/api.py index 34c096a21c2f..e52d5994b705 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -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) diff --git a/nova/compute/cells_api.py b/nova/compute/cells_api.py index 190dbdb4812e..54e622f8a4c7 100644 --- a/nova/compute/cells_api.py +++ b/nova/compute/cells_api.py @@ -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 diff --git a/nova/tests/unit/api/openstack/compute/test_services.py b/nova/tests/unit/api/openstack/compute/test_services.py index cfdae43a6d2e..0ebe374f73b1 100644 --- a/nova/tests/unit/api/openstack/compute/test_services.py +++ b/nova/tests/unit/api/openstack/compute/test_services.py @@ -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) diff --git a/nova/tests/unit/compute/test_host_api.py b/nova/tests/unit/compute/test_host_api.py index bac58ed73261..ca02a87bf9bb 100644 --- a/nova/tests/unit/compute/test_host_api.py +++ b/nova/tests/unit/compute/test_host_api.py @@ -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