From 39c1b252e8a26e48a2d039114ec2a00a8091d44c Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Mon, 6 Mar 2017 12:00:49 -0800 Subject: [PATCH] Make os-availability-zones know about cells This removes the direct-to-object listing of Service objects by the availability zone routines. Instead, it implements a cell-scanning approach in HostAPI and uses that for consistent behavior. The modified behavior of service_get_all() in HostAPI was originally in another patch, but is moved here because of dependency sequencing. The HostAPI patch later in this series uses this as well. Related to blueprint cells-aware-api Change-Id: I65c2b436bef8837c0e10a5e502e9cd598d9aa0c3 --- .../openstack/compute/availability_zone.py | 8 ++++--- nova/availability_zones.py | 12 ++++++---- nova/compute/api.py | 22 ++++++++++++++++--- nova/compute/cells_api.py | 8 ++++++- nova/tests/unit/compute/test_host_api.py | 15 +++++++++++++ 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/nova/api/openstack/compute/availability_zone.py b/nova/api/openstack/compute/availability_zone.py index 13af2661d6..e83fdf8d40 100644 --- a/nova/api/openstack/compute/availability_zone.py +++ b/nova/api/openstack/compute/availability_zone.py @@ -16,8 +16,8 @@ from nova.api.openstack.compute.schemas import availability_zone as schema from nova.api.openstack import extensions from nova.api.openstack import wsgi from nova import availability_zones +from nova import compute import nova.conf -from nova import objects from nova.policies import availability_zone as az_policies from nova import servicegroup @@ -32,6 +32,7 @@ class AvailabilityZoneController(wsgi.Controller): def __init__(self): super(AvailabilityZoneController, self).__init__() self.servicegroup_api = servicegroup.API() + self.host_api = compute.HostAPI() def _get_filtered_availability_zones(self, zones, is_available): result = [] @@ -62,8 +63,9 @@ class AvailabilityZoneController(wsgi.Controller): availability_zones.get_availability_zones(ctxt) # Available services - enabled_services = objects.ServiceList.get_all(context, disabled=False, - set_zones=True) + enabled_services = self.host_api.service_get_all( + context, {'disabled': False}, set_zones=True, all_cells=True) + zone_hosts = {} host_services = {} api_services = ('nova-osapi_compute', 'nova-ec2', 'nova-metadata') diff --git a/nova/availability_zones.py b/nova/availability_zones.py index 190882d90d..625ee1ff4f 100644 --- a/nova/availability_zones.py +++ b/nova/availability_zones.py @@ -120,8 +120,12 @@ def get_availability_zones(context, get_only_available=False, :param with_hosts: whether to return hosts part of the AZs :type with_hosts: bool """ - enabled_services = objects.ServiceList.get_all(context, disabled=False, - set_zones=True) + # NOTE(danms): Avoid circular import + from nova import compute + hostapi = compute.HostAPI() + + enabled_services = hostapi.service_get_all( + context, {'disabled': False}, set_zones=True, all_cells=True) available_zones = [] for (zone, host) in [(service['availability_zone'], service['host']) @@ -136,8 +140,8 @@ def get_availability_zones(context, get_only_available=False, available_zones = list(_available_zones.items()) if not get_only_available: - disabled_services = objects.ServiceList.get_all(context, disabled=True, - set_zones=True) + disabled_services = hostapi.service_get_all( + context, {'disabled': True}, set_zones=True, all_cells=True) not_available_zones = [] azs = available_zones if not with_hosts else dict(available_zones) zones = [(service['availability_zone'], service['host']) diff --git a/nova/compute/api.py b/nova/compute/api.py index 23d2c5c381..9671ba153d 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -4325,20 +4325,36 @@ class HostAPI(base.Base): payload) return result - def service_get_all(self, context, filters=None, set_zones=False): + def service_get_all(self, context, filters=None, set_zones=False, + all_cells=False): """Returns a list of services, optionally filtering the results. If specified, 'filters' should be a dictionary containing services attributes and matching values. Ie, to get a list of services for the 'compute' topic, use filters={'topic': 'compute'}. + + If all_cells=True, then scan all cells and merge the results. """ if filters is None: filters = {} disabled = filters.pop('disabled', None) if 'availability_zone' in filters: set_zones = True - services = objects.ServiceList.get_all(context, disabled, - set_zones=set_zones) + + # NOTE(danms): Eventually this all_cells nonsense should go away + # and we should always iterate over the cells. However, certain + # callers need the legacy behavior for now. + if all_cells: + load_cells() + services = [] + for cell in CELLS: + with nova_context.target_cell(context, cell): + cell_services = objects.ServiceList.get_all( + context, disabled, set_zones=set_zones) + services.extend(cell_services) + else: + services = objects.ServiceList.get_all(context, disabled, + set_zones=set_zones) ret_services = [] for service in services: for key, val in filters.items(): diff --git a/nova/compute/cells_api.py b/nova/compute/cells_api.py index 91ead953e5..0d0431d653 100644 --- a/nova/compute/cells_api.py +++ b/nova/compute/cells_api.py @@ -546,7 +546,13 @@ class HostAPI(compute_api.HostAPI): """Returns the result of calling "uptime" on the target host.""" return self.cells_rpcapi.get_host_uptime(context, host_name) - def service_get_all(self, context, filters=None, set_zones=False): + def service_get_all(self, context, filters=None, set_zones=False, + all_cells=False): + """Get all services. + + Note that this is the cellsv1 variant, which means we ignore the + "all_cells" parameter. + """ if filters is None: filters = {} if 'availability_zone' in filters: diff --git a/nova/tests/unit/compute/test_host_api.py b/nova/tests/unit/compute/test_host_api.py index 2091e4e755..8e06be479a 100644 --- a/nova/tests/unit/compute/test_host_api.py +++ b/nova/tests/unit/compute/test_host_api.py @@ -173,6 +173,17 @@ class ComputeHostAPITestCase(test.TestCase): _do_test() + def test_service_get_all_cells(self): + cells = objects.CellMappingList.get_all(self.ctxt) + for cell in cells: + with context.target_cell(self.ctxt, cell): + objects.Service(context=self.ctxt, + binary='nova-compute', + host='host-%s' % cell.uuid).create() + services = self.host_api.service_get_all(self.ctxt, all_cells=True) + self.assertEqual(sorted(['host-%s' % cell.uuid for cell in cells]), + sorted([svc.host for svc in services])) + def test_service_get_all_no_zones(self): services = [dict(test_service.fake_service, id=1, topic='compute', host='host1'), @@ -338,6 +349,10 @@ class ComputeHostAPICellsTestCase(ComputeHostAPITestCase): self.flags(cell_type='api', group='cells') super(ComputeHostAPICellsTestCase, self).setUp() + @testtools.skip('cellsv1 does not use this') + def test_service_get_all_cells(self): + pass + def test_service_get_all_no_zones(self): services = [ cells_utils.ServiceProxy(