diff options
| author | Jenkins <jenkins@review.openstack.org> | 2013-02-06 22:25:57 +0000 |
|---|---|---|
| committer | Gerrit Code Review <review@openstack.org> | 2013-02-06 22:25:57 +0000 |
| commit | 749c3db3f63f70d9a71ff26e253b3eeda5d9d742 (patch) | |
| tree | 714012474f0f51cd1e92e4142a24ae68547b7062 | |
| parent | ab987734228f00afb34d7a1cb22f693d29a520fe (diff) | |
| parent | 0dbd2c5d3360a71658a146cb3947047e797a56f6 (diff) | |
| download | nova-749c3db3f63f70d9a71ff26e253b3eeda5d9d742.tar.gz nova-749c3db3f63f70d9a71ff26e253b3eeda5d9d742.tar.xz nova-749c3db3f63f70d9a71ff26e253b3eeda5d9d742.zip | |
Merge "Cells: Add support for compute HostAPI()"
| -rw-r--r-- | nova/api/openstack/compute/contrib/hosts.py | 5 | ||||
| -rw-r--r-- | nova/cells/manager.py | 33 | ||||
| -rw-r--r-- | nova/cells/messaging.py | 61 | ||||
| -rw-r--r-- | nova/cells/rpcapi.py | 30 | ||||
| -rw-r--r-- | nova/cells/utils.py | 43 | ||||
| -rw-r--r-- | nova/compute/api.py | 14 | ||||
| -rw-r--r-- | nova/compute/cells_api.py | 85 | ||||
| -rw-r--r-- | nova/tests/cells/test_cells_manager.py | 86 | ||||
| -rw-r--r-- | nova/tests/cells/test_cells_messaging.py | 137 | ||||
| -rw-r--r-- | nova/tests/cells/test_cells_rpcapi.py | 35 | ||||
| -rw-r--r-- | nova/tests/compute/test_host_api.py | 190 |
11 files changed, 689 insertions, 30 deletions
diff --git a/nova/api/openstack/compute/contrib/hosts.py b/nova/api/openstack/compute/contrib/hosts.py index d1b39d6db..9ce278900 100644 --- a/nova/api/openstack/compute/contrib/hosts.py +++ b/nova/api/openstack/compute/contrib/hosts.py @@ -124,11 +124,12 @@ class HostController(object): """ context = req.environ['nova.context'] authorize(context) - filters = {} + filters = {'disabled': False} zone = req.GET.get('zone', None) if zone: filters['availability_zone'] = zone - services = self.api.service_get_all(context, filters=filters) + services = self.api.service_get_all(context, filters=filters, + set_zones=True) hosts = [] for service in services: hosts.append({'host_name': service['host'], diff --git a/nova/cells/manager.py b/nova/cells/manager.py index 133946794..6acda00c0 100644 --- a/nova/cells/manager.py +++ b/nova/cells/manager.py @@ -65,7 +65,7 @@ class CellsManager(manager.Manager): Scheduling requests get passed to the scheduler class. """ - RPC_API_VERSION = '1.1' + RPC_API_VERSION = '1.2' def __init__(self, *args, **kwargs): # Mostly for tests. @@ -229,3 +229,34 @@ class CellsManager(manager.Manager): """ self.msg_runner.sync_instances(ctxt, project_id, updated_since, deleted) + + def service_get_all(self, ctxt, filters): + """Return services in this cell and in all child cells.""" + responses = self.msg_runner.service_get_all(ctxt, filters) + ret_services = [] + # 1 response per cell. Each response is a list of services. + for response in responses: + services = response.value_or_raise() + for service in services: + cells_utils.add_cell_to_service(service, response.cell_name) + ret_services.append(service) + return ret_services + + def service_get_by_compute_host(self, ctxt, host_name): + """Return a service entry for a compute host in a certain cell.""" + cell_name, host_name = cells_utils.split_cell_and_item(host_name) + response = self.msg_runner.service_get_by_compute_host(ctxt, + cell_name, + host_name) + service = response.value_or_raise() + cells_utils.add_cell_to_service(service, response.cell_name) + return service + + def proxy_rpc_to_manager(self, ctxt, topic, rpc_message, call, timeout): + """Proxy an RPC message as-is to a manager.""" + compute_topic = CONF.compute_topic + cell_and_host = topic[len(compute_topic) + 1:] + cell_name, host_name = cells_utils.split_cell_and_item(cell_and_host) + response = self.msg_runner.proxy_rpc_to_manager(ctxt, cell_name, + host_name, topic, rpc_message, call, timeout) + return response.value_or_raise() diff --git a/nova/cells/messaging.py b/nova/cells/messaging.py index 34ca74855..a1b3f36de 100644 --- a/nova/cells/messaging.py +++ b/nova/cells/messaging.py @@ -37,6 +37,7 @@ from nova.openstack.common import excutils from nova.openstack.common import importutils from nova.openstack.common import jsonutils from nova.openstack.common import log as logging +from nova.openstack.common import rpc from nova.openstack.common.rpc import common as rpc_common from nova.openstack.common import timeutils from nova.openstack.common import uuidutils @@ -60,7 +61,7 @@ LOG = logging.getLogger(__name__) # Separator used between cell names for the 'full cell name' and routing # path. -_PATH_CELL_SEP = '!' +_PATH_CELL_SEP = cells_utils._PATH_CELL_SEP def _reverse_path(path): @@ -678,6 +679,22 @@ class _TargetedMessageMethods(_BaseMessageMethods): """ self.msg_runner.tell_parents_our_capacities(message.ctxt) + def service_get_by_compute_host(self, message, host_name): + """Return the service entry for a compute host.""" + service = self.db.service_get_by_compute_host(message.ctxt, + host_name) + return jsonutils.to_primitive(service) + + def proxy_rpc_to_manager(self, message, host_name, rpc_message, + topic, timeout): + """Proxy RPC to the given compute topic.""" + # Check that the host exists. + self.db.service_get_by_compute_host(message.ctxt, host_name) + if message.need_response: + return rpc.call(message.ctxt, topic, rpc_message, + timeout=timeout) + rpc.cast(message.ctxt, topic, rpc_message) + class _BroadcastMessageMethods(_BaseMessageMethods): """These are the methods that can be called as a part of a broadcast @@ -800,6 +817,21 @@ class _BroadcastMessageMethods(_BaseMessageMethods): for instance in instances: self._sync_instance(message.ctxt, instance) + def service_get_all(self, message, filters): + if filters is None: + filters = {} + disabled = filters.pop('disabled', None) + services = self.db.service_get_all(message.ctxt, disabled=disabled) + ret_services = [] + for service in services: + service = jsonutils.to_primitive(service) + for key, val in filters.iteritems(): + if service[key] != val: + break + else: + ret_services.append(service) + return ret_services + _CELL_MESSAGE_TYPE_TO_MESSAGE_CLS = {'targeted': _TargetedMessage, 'broadcast': _BroadcastMessage, @@ -1038,6 +1070,33 @@ class MessageRunner(object): run_locally=False) message.process() + def service_get_all(self, ctxt, filters=None): + method_kwargs = dict(filters=filters) + message = _BroadcastMessage(self, ctxt, 'service_get_all', + method_kwargs, 'down', + run_locally=True, need_response=True) + return message.process() + + def service_get_by_compute_host(self, ctxt, cell_name, host_name): + method_kwargs = dict(host_name=host_name) + message = _TargetedMessage(self, ctxt, + 'service_get_by_compute_host', + method_kwargs, 'down', cell_name, + need_response=True) + return message.process() + + def proxy_rpc_to_manager(self, ctxt, cell_name, host_name, topic, + rpc_message, call, timeout): + method_kwargs = {'host_name': host_name, + 'topic': topic, + 'rpc_message': rpc_message, + 'timeout': timeout} + message = _TargetedMessage(self, ctxt, + 'proxy_rpc_to_manager', + method_kwargs, 'down', cell_name, + need_response=call) + return message.process() + @staticmethod def get_message_types(): return _CELL_MESSAGE_TYPE_TO_MESSAGE_CLS.keys() diff --git a/nova/cells/rpcapi.py b/nova/cells/rpcapi.py index 0ab4fc352..fde2d25e1 100644 --- a/nova/cells/rpcapi.py +++ b/nova/cells/rpcapi.py @@ -41,6 +41,8 @@ class CellsAPI(rpc_proxy.RpcProxy): 1.0 - Initial version. 1.1 - Adds get_cell_info_for_neighbors() and sync_instances() + 1.2 - Adds service_get_all(), service_get_by_compute_host(), + and proxy_rpc_to_compute_manager() ''' BASE_RPC_API_VERSION = '1.0' @@ -155,3 +157,31 @@ class CellsAPI(rpc_proxy.RpcProxy): updated_since=updated_since, deleted=deleted), version='1.1') + + def service_get_all(self, ctxt, filters=None): + """Ask all cells for their list of services.""" + return self.call(ctxt, + self.make_msg('service_get_all', + filters=filters), + version='1.2') + + def service_get_by_compute_host(self, ctxt, host_name): + """Get the service entry for a host in a particular cell. The + cell name should be encoded within the host_name. + """ + return self.call(ctxt, self.make_msg('service_get_by_compute_host', + host_name=host_name), + version='1.2') + + def proxy_rpc_to_manager(self, ctxt, rpc_message, topic, call=False, + timeout=None): + """Proxy RPC to a compute manager. The host in the topic + should be encoded with the target cell name. + """ + return self.call(ctxt, self.make_msg('proxy_rpc_to_manager', + topic=topic, + rpc_message=rpc_message, + call=call, + timeout=timeout), + timeout=timeout, + version='1.2') diff --git a/nova/cells/utils.py b/nova/cells/utils.py index d25f98fab..76556e0dd 100644 --- a/nova/cells/utils.py +++ b/nova/cells/utils.py @@ -20,6 +20,12 @@ import random from nova import db +# Separator used between cell names for the 'full cell name' and routing +# path +_PATH_CELL_SEP = '!' +# Separator used between cell name and item +_CELL_ITEM_SEP = '@' + def get_instances_to_sync(context, updated_since=None, project_id=None, deleted=True, shuffle=False, uuids_only=False): @@ -46,3 +52,40 @@ def get_instances_to_sync(context, updated_since=None, project_id=None, yield instance['uuid'] else: yield instance + + +def cell_with_item(cell_name, item): + """Turn cell_name and item into <cell_name>@<item>.""" + return cell_name + _CELL_ITEM_SEP + str(item) + + +def split_cell_and_item(cell_and_item): + """Split a combined cell@item and return them.""" + return cell_and_item.rsplit(_CELL_ITEM_SEP, 1) + + +def _add_cell_to_service(service, cell_name): + service['id'] = cell_with_item(cell_name, service['id']) + service['host'] = cell_with_item(cell_name, service['host']) + + +def add_cell_to_compute_node(compute_node, cell_name): + """Fix compute_node attributes that should be unique. Allows + API cell to query the 'id' by cell@id. + """ + compute_node['id'] = cell_with_item(cell_name, compute_node['id']) + # Might have a 'service' backref. But if is_primitive() was used + # on this and it recursed too deep, 'service' may be "?". + service = compute_node.get('service') + if isinstance(service, dict): + _add_cell_to_service(service, cell_name) + + +def add_cell_to_service(service, cell_name): + """Fix service attributes that should be unique. Allows + API cell to query the 'id' or 'host' by cell@id/host. + """ + _add_cell_to_service(service, cell_name) + compute_node = service.get('compute_node') + if compute_node: + add_cell_to_compute_node(compute_node[0], cell_name) diff --git a/nova/compute/api.py b/nova/compute/api.py index 23024ba29..394f09e11 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -2417,16 +2417,12 @@ class HostAPI(base.Base): def set_host_enabled(self, context, host_name, enabled): """Sets the specified host's ability to accept new instances.""" - # NOTE(comstud): No instance_uuid argument to this compute manager - # call self._assert_host_exists(context, host_name) return self.rpcapi.set_host_enabled(context, enabled=enabled, host=host_name) def get_host_uptime(self, context, host_name): """Returns the result of calling "uptime" on the target host.""" - # NOTE(comstud): No instance_uuid argument to this compute manager - # call self._assert_host_exists(context, host_name) return self.rpcapi.get_host_uptime(context, host=host_name) @@ -2443,7 +2439,7 @@ class HostAPI(base.Base): return self.rpcapi.host_maintenance_mode(context, host_param=host_name, mode=mode, host=host_name) - def service_get_all(self, context, filters=None): + def service_get_all(self, context, filters=None, set_zones=False): """Returns a list of services, optionally filtering the results. If specified, 'filters' should be a dictionary containing services @@ -2452,9 +2448,11 @@ class HostAPI(base.Base): """ if filters is None: filters = {} - services = self.db.service_get_all(context, False) - services = availability_zones.set_availability_zones(context, - services) + disabled = filters.pop('disabled', None) + services = self.db.service_get_all(context, disabled=disabled) + if set_zones or 'availability_zone' in filters: + services = availability_zones.set_availability_zones(context, + services) ret_services = [] for service in services: for key, val in filters.iteritems(): diff --git a/nova/compute/cells_api.py b/nova/compute/cells_api.py index e2c2a9a4a..98a49417f 100644 --- a/nova/compute/cells_api.py +++ b/nova/compute/cells_api.py @@ -15,10 +15,13 @@ """Compute API that proxies via Cells Service.""" +from nova import availability_zones from nova import block_device from nova.cells import rpcapi as cells_rpcapi +from nova.cells import utils as cells_utils from nova.compute import api as compute_api from nova.compute import instance_types +from nova.compute import rpcapi as compute_rpcapi from nova.compute import vm_states from nova import exception from nova.openstack.common import excutils @@ -61,6 +64,27 @@ class SchedulerRPCAPIRedirect(object): self.cells_rpcapi.schedule_run_instance(context, **kwargs) +class ComputeRPCProxyAPI(compute_rpcapi.ComputeAPI): + """Class used to substitute Compute RPC API that will proxy + via the cells manager to a compute manager in a child cell. + """ + def __init__(self, *args, **kwargs): + super(ComputeRPCProxyAPI, self).__init__(*args, **kwargs) + self.cells_rpcapi = cells_rpcapi.CellsAPI() + + def cast(self, ctxt, msg, topic=None, version=None): + self._set_version(msg, version) + topic = self._get_topic(topic) + self.cells_rpcapi.proxy_rpc_to_manager(ctxt, msg, topic) + + def call(self, ctxt, msg, topic=None, version=None, timeout=None): + self._set_version(msg, version) + topic = self._get_topic(topic) + return self.cells_rpcapi.proxy_rpc_to_manager(ctxt, msg, topic, + call=True, + timeout=timeout) + + class ComputeCellsAPI(compute_api.API): def __init__(self, *args, **kwargs): super(ComputeCellsAPI, self).__init__(*args, **kwargs) @@ -550,3 +574,64 @@ class ComputeCellsAPI(compute_api.API): except exception.InstanceUnknownCell: pass return rv + + +class HostAPI(compute_api.HostAPI): + """HostAPI() class for cells. + + Implements host management related operations. Works by setting the + RPC API used by the base class to proxy via the cells manager to the + compute manager in the correct cell. Hosts specified with cells will + need to be of the format 'path!to!cell@host'. + + DB methods in the base class are also overridden to proxy via the + cells manager. + """ + def __init__(self): + super(HostAPI, self).__init__(rpcapi=ComputeRPCProxyAPI()) + self.cells_rpcapi = cells_rpcapi.CellsAPI() + + def _assert_host_exists(self, context, host_name): + """Cannot check this in API cell. This will be checked in the + target child cell. + """ + pass + + def service_get_all(self, context, filters=None, set_zones=False): + if filters is None: + filters = {} + if 'availability_zone' in filters: + zone_filter = filters.pop('availability_zone') + set_zones = True + else: + zone_filter = None + services = self.cells_rpcapi.service_get_all(context, + filters=filters) + if set_zones: + services = availability_zones.set_availability_zones(context, + services) + if zone_filter is not None: + services = [s for s in services + if s['availability_zone'] == zone_filter] + return services + + def service_get_by_compute_host(self, context, host_name): + return self.cells_rpcapi.service_get_by_compute_host(context, + host_name) + + def instance_get_all_by_host(self, context, host_name): + """Get all instances by host. Host might have a cell prepended + to it, so we'll need to strip it out. We don't need to proxy + this call to cells, as we have instance information here in + the API cell. + """ + try: + cell_name, host_name = cells_utils.split_cell_and_item(host_name) + except ValueError: + cell_name = None + instances = super(HostAPI, self).instance_get_all_by_host(context, + host_name) + if cell_name: + instances = [i for i in instances + if i['cell_name'] == cell_name] + return instances diff --git a/nova/tests/cells/test_cells_manager.py b/nova/tests/cells/test_cells_manager.py index ef165f4ed..d3d412af7 100644 --- a/nova/tests/cells/test_cells_manager.py +++ b/nova/tests/cells/test_cells_manager.py @@ -15,15 +15,29 @@ """ Tests For CellsManager """ +import copy import datetime from nova.cells import messaging from nova.cells import utils as cells_utils from nova import context +from nova.openstack.common import cfg +from nova.openstack.common import rpc from nova.openstack.common import timeutils from nova import test from nova.tests.cells import fakes +CONF = cfg.CONF +CONF.import_opt('compute_topic', 'nova.compute.rpcapi') + + +FAKE_COMPUTE_NODES = [dict(id=1), dict(id=2)] +FAKE_SERVICES = [dict(id=1, host='host1', + compute_node=[FAKE_COMPUTE_NODES[0]]), + dict(id=2, host='host2', + compute_node=[FAKE_COMPUTE_NODES[1]]), + dict(id=3, host='host3', compute_node=[])] + class CellsManagerClassTestCase(test.TestCase): """Test case for CellsManager class.""" @@ -46,6 +60,14 @@ class CellsManagerClassTestCase(test.TestCase): expected_responses.append(('cell%s' % x, x)) return expected_responses, responses + def _get_fake_response(self, raw_response=None, exc=False): + if exc: + return messaging.Response('fake', test.TestingException(), + True) + if raw_response is None: + raw_response = 'fake-response' + return messaging.Response('fake', raw_response, False) + def test_get_cell_info_for_neighbors(self): self.mox.StubOutWithMock(self.cells_manager.state_manager, 'get_cell_info_for_neighbors') @@ -109,17 +131,13 @@ class CellsManagerClassTestCase(test.TestCase): cell_name = 'fake-cell-name' method_info = 'fake-method-info' - fake_response = messaging.Response('fake', 'fake', False) - self.mox.StubOutWithMock(self.msg_runner, 'run_compute_api_method') - self.mox.StubOutWithMock(fake_response, - 'value_or_raise') + fake_response = self._get_fake_response() self.msg_runner.run_compute_api_method(self.ctxt, cell_name, method_info, True).AndReturn(fake_response) - fake_response.value_or_raise().AndReturn('fake-response') self.mox.ReplayAll() response = self.cells_manager.run_compute_api_method( self.ctxt, cell_name=cell_name, method_info=method_info, @@ -237,3 +255,61 @@ class CellsManagerClassTestCase(test.TestCase): project_id='fake-project', updated_since='fake-time', deleted='fake-deleted') + + def test_service_get_all(self): + responses = [] + expected_response = [] + # 3 cells... so 3 responses. Each response is a list of services. + # Manager should turn these into a single list of responses. + for i in xrange(3): + cell_name = 'path!to!cell%i' % i + services = [] + for service in FAKE_SERVICES: + services.append(copy.deepcopy(service)) + expected_service = copy.deepcopy(service) + cells_utils.add_cell_to_service(expected_service, cell_name) + expected_response.append(expected_service) + response = messaging.Response(cell_name, services, False) + responses.append(response) + + self.mox.StubOutWithMock(self.msg_runner, + 'service_get_all') + self.msg_runner.service_get_all(self.ctxt, + 'fake-filters').AndReturn(responses) + self.mox.ReplayAll() + response = self.cells_manager.service_get_all(self.ctxt, + filters='fake-filters') + self.assertEqual(expected_response, response) + + def test_service_get_by_compute_host(self): + self.mox.StubOutWithMock(self.msg_runner, + 'service_get_by_compute_host') + fake_cell = 'fake-cell' + fake_response = messaging.Response(fake_cell, FAKE_SERVICES[0], + False) + expected_response = copy.deepcopy(FAKE_SERVICES[0]) + cells_utils.add_cell_to_service(expected_response, fake_cell) + + cell_and_host = cells_utils.cell_with_item('fake-cell', 'fake-host') + self.msg_runner.service_get_by_compute_host(self.ctxt, + fake_cell, 'fake-host').AndReturn(fake_response) + self.mox.ReplayAll() + response = self.cells_manager.service_get_by_compute_host(self.ctxt, + host_name=cell_and_host) + self.assertEqual(expected_response, response) + + def test_proxy_rpc_to_manager(self): + self.mox.StubOutWithMock(self.msg_runner, + 'proxy_rpc_to_manager') + fake_response = self._get_fake_response() + cell_and_host = cells_utils.cell_with_item('fake-cell', 'fake-host') + topic = rpc.queue_get_for(self.ctxt, CONF.compute_topic, + cell_and_host) + self.msg_runner.proxy_rpc_to_manager(self.ctxt, 'fake-cell', + 'fake-host', topic, 'fake-rpc-msg', + True, -1).AndReturn(fake_response) + self.mox.ReplayAll() + response = self.cells_manager.proxy_rpc_to_manager(self.ctxt, + topic=topic, rpc_message='fake-rpc-msg', call=True, + timeout=-1) + self.assertEqual('fake-response', response) diff --git a/nova/tests/cells/test_cells_messaging.py b/nova/tests/cells/test_cells_messaging.py index 1208368c2..f6c51325a 100644 --- a/nova/tests/cells/test_cells_messaging.py +++ b/nova/tests/cells/test_cells_messaging.py @@ -19,6 +19,7 @@ from nova.cells import utils as cells_utils from nova import context from nova import exception from nova.openstack.common import cfg +from nova.openstack.common import rpc from nova.openstack.common import timeutils from nova import test from nova.tests.cells import fakes @@ -602,7 +603,7 @@ class CellsTargetedMethodsTestCase(test.TestCase): self.tgt_cell_name, host_sched_kwargs) - def test_call_compute_api_method(self): + def test_run_compute_api_method(self): instance_uuid = 'fake_instance_uuid' method_info = {'method': 'reboot', @@ -612,8 +613,7 @@ class CellsTargetedMethodsTestCase(test.TestCase): self.mox.StubOutWithMock(self.tgt_db_inst, 'instance_get_by_uuid') self.tgt_db_inst.instance_get_by_uuid(self.ctxt, - instance_uuid).AndReturn( - 'fake_instance') + instance_uuid).AndReturn('fake_instance') self.tgt_compute_api.reboot(self.ctxt, 'fake_instance', 2, 3, arg1='val1', arg2='val2').AndReturn('fake_result') self.mox.ReplayAll() @@ -626,7 +626,7 @@ class CellsTargetedMethodsTestCase(test.TestCase): result = response.value_or_raise() self.assertEqual('fake_result', result) - def test_call_compute_api_method_unknown_instance(self): + def test_run_compute_api_method_unknown_instance(self): # Unknown instance should send a broadcast up that instance # is gone. instance_uuid = 'fake_instance_uuid' @@ -725,6 +725,70 @@ class CellsTargetedMethodsTestCase(test.TestCase): self.src_msg_runner.ask_children_for_capacities(self.ctxt) + def test_service_get_by_compute_host(self): + fake_host_name = 'fake-host-name' + + self.mox.StubOutWithMock(self.tgt_db_inst, + 'service_get_by_compute_host') + + self.tgt_db_inst.service_get_by_compute_host(self.ctxt, + fake_host_name).AndReturn('fake-service') + self.mox.ReplayAll() + + response = self.src_msg_runner.service_get_by_compute_host( + self.ctxt, + self.tgt_cell_name, + fake_host_name) + result = response.value_or_raise() + self.assertEqual('fake-service', result) + + def test_proxy_rpc_to_manager_call(self): + fake_topic = 'fake-topic' + fake_rpc_message = 'fake-rpc-message' + fake_host_name = 'fake-host-name' + + self.mox.StubOutWithMock(self.tgt_db_inst, + 'service_get_by_compute_host') + self.mox.StubOutWithMock(rpc, 'call') + + self.tgt_db_inst.service_get_by_compute_host(self.ctxt, + fake_host_name) + rpc.call(self.ctxt, fake_topic, + fake_rpc_message, timeout=5).AndReturn('fake_result') + + self.mox.ReplayAll() + + response = self.src_msg_runner.proxy_rpc_to_manager( + self.ctxt, + self.tgt_cell_name, + fake_host_name, + fake_topic, + fake_rpc_message, True, timeout=5) + result = response.value_or_raise() + self.assertEqual('fake_result', result) + + def test_proxy_rpc_to_manager_cast(self): + fake_topic = 'fake-topic' + fake_rpc_message = 'fake-rpc-message' + fake_host_name = 'fake-host-name' + + self.mox.StubOutWithMock(self.tgt_db_inst, + 'service_get_by_compute_host') + self.mox.StubOutWithMock(rpc, 'cast') + + self.tgt_db_inst.service_get_by_compute_host(self.ctxt, + fake_host_name) + rpc.cast(self.ctxt, fake_topic, fake_rpc_message) + + self.mox.ReplayAll() + + self.src_msg_runner.proxy_rpc_to_manager( + self.ctxt, + self.tgt_cell_name, + fake_host_name, + fake_topic, + fake_rpc_message, False, timeout=None) + class CellsBroadcastMethodsTestCase(test.TestCase): """Test case for _BroadcastMessageMethods class. Most of these @@ -754,6 +818,13 @@ class CellsBroadcastMethodsTestCase(test.TestCase): self.src_db_inst = methods_cls.db self.src_compute_api = methods_cls.compute_api + if not up: + # fudge things so we only have 1 child to broadcast to + state_manager = self.src_msg_runner.state_manager + for cell in state_manager.get_child_cells(): + if cell.name != 'child-cell2': + del state_manager.child_cells[cell.name] + self.mid_msg_runner = fakes.get_message_runner(mid_cell) methods_cls = self.mid_msg_runner.methods_by_type['broadcast'] self.mid_methods_cls = methods_cls @@ -956,3 +1027,61 @@ class CellsBroadcastMethodsTestCase(test.TestCase): self.src_msg_runner.sync_instances(self.ctxt, project_id, updated_since_raw, deleted) + + def test_service_get_all_with_disabled(self): + # Reset this, as this is a broadcast down. + self._setup_attrs(up=False) + + ctxt = self.ctxt.elevated() + + self.mox.StubOutWithMock(self.src_db_inst, 'service_get_all') + self.mox.StubOutWithMock(self.mid_db_inst, 'service_get_all') + self.mox.StubOutWithMock(self.tgt_db_inst, 'service_get_all') + + self.src_db_inst.service_get_all(ctxt, + disabled=None).AndReturn([1, 2]) + self.mid_db_inst.service_get_all(ctxt, + disabled=None).AndReturn([3]) + self.tgt_db_inst.service_get_all(ctxt, + disabled=None).AndReturn([4, 5]) + + self.mox.ReplayAll() + + responses = self.src_msg_runner.service_get_all(ctxt, + filters={}) + response_values = [(resp.cell_name, resp.value_or_raise()) + for resp in responses] + expected = [('api-cell!child-cell2!grandchild-cell1', [4, 5]), + ('api-cell!child-cell2', [3]), + ('api-cell', [1, 2])] + self.assertEqual(expected, response_values) + + def test_service_get_all_without_disabled(self): + # Reset this, as this is a broadcast down. + self._setup_attrs(up=False) + disabled = False + filters = {'disabled': disabled} + + ctxt = self.ctxt.elevated() + + self.mox.StubOutWithMock(self.src_db_inst, 'service_get_all') + self.mox.StubOutWithMock(self.mid_db_inst, 'service_get_all') + self.mox.StubOutWithMock(self.tgt_db_inst, 'service_get_all') + + self.src_db_inst.service_get_all(ctxt, + disabled=disabled).AndReturn([1, 2]) + self.mid_db_inst.service_get_all(ctxt, + disabled=disabled).AndReturn([3]) + self.tgt_db_inst.service_get_all(ctxt, + disabled=disabled).AndReturn([4, 5]) + + self.mox.ReplayAll() + + responses = self.src_msg_runner.service_get_all(ctxt, + filters=filters) + response_values = [(resp.cell_name, resp.value_or_raise()) + for resp in responses] + expected = [('api-cell!child-cell2!grandchild-cell1', [4, 5]), + ('api-cell!child-cell2', [3]), + ('api-cell', [1, 2])] + self.assertEqual(expected, response_values) diff --git a/nova/tests/cells/test_cells_rpcapi.py b/nova/tests/cells/test_cells_rpcapi.py index 5e045aca9..876bc5ce5 100644 --- a/nova/tests/cells/test_cells_rpcapi.py +++ b/nova/tests/cells/test_cells_rpcapi.py @@ -224,3 +224,38 @@ class CellsAPITestCase(test.TestCase): 'deleted': True} self._check_result(call_info, 'sync_instances', expected_args, version='1.1') + + def test_service_get_all(self): + call_info = self._stub_rpc_method('call', 'fake_response') + fake_filters = {'key1': 'val1', 'key2': 'val2'} + result = self.cells_rpcapi.service_get_all(self.fake_context, + filters=fake_filters) + + expected_args = {'filters': fake_filters} + self._check_result(call_info, 'service_get_all', expected_args, + version='1.2') + self.assertEqual(result, 'fake_response') + + def test_service_get_by_compute_host(self): + call_info = self._stub_rpc_method('call', 'fake_response') + result = self.cells_rpcapi.service_get_by_compute_host( + self.fake_context, host_name='fake-host-name') + expected_args = {'host_name': 'fake-host-name'} + self._check_result(call_info, 'service_get_by_compute_host', + expected_args, + version='1.2') + self.assertEqual(result, 'fake_response') + + def test_proxy_rpc_to_manager(self): + call_info = self._stub_rpc_method('call', 'fake_response') + result = self.cells_rpcapi.proxy_rpc_to_manager( + self.fake_context, rpc_message='fake-msg', + topic='fake-topic', call=True, timeout=-1) + expected_args = {'rpc_message': 'fake-msg', + 'topic': 'fake-topic', + 'call': True, + 'timeout': -1} + self._check_result(call_info, 'proxy_rpc_to_manager', + expected_args, + version='1.2') + self.assertEqual(result, 'fake_response') diff --git a/nova/tests/compute/test_host_api.py b/nova/tests/compute/test_host_api.py index 95d3c4926..151743715 100644 --- a/nova/tests/compute/test_host_api.py +++ b/nova/tests/compute/test_host_api.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from nova.cells import utils as cells_utils from nova import compute from nova.compute import rpcapi as compute_rpcapi from nova import context @@ -84,6 +85,42 @@ class ComputeHostAPITestCase(test.TestCase): 'fake_mode') self.assertEqual('fake-result', result) + def test_service_get_all_no_zones(self): + services = [dict(id=1, key1='val1', key2='val2', topic='compute', + host='host1'), + dict(id=2, key1='val2', key3='val3', topic='compute', + host='host2')] + + self.mox.StubOutWithMock(self.host_api.db, + 'service_get_all') + + # Test no filters + self.host_api.db.service_get_all(self.ctxt, + disabled=None).AndReturn(services) + self.mox.ReplayAll() + result = self.host_api.service_get_all(self.ctxt) + self.mox.VerifyAll() + self.assertEqual(services, result) + + # Test no filters #2 + self.mox.ResetAll() + self.host_api.db.service_get_all(self.ctxt, + disabled=None).AndReturn(services) + self.mox.ReplayAll() + result = self.host_api.service_get_all(self.ctxt, filters={}) + self.mox.VerifyAll() + self.assertEqual(services, result) + + # Test w/ filter + self.mox.ResetAll() + self.host_api.db.service_get_all(self.ctxt, + disabled=None).AndReturn(services) + self.mox.ReplayAll() + result = self.host_api.service_get_all(self.ctxt, + filters=dict(key1='val2')) + self.mox.VerifyAll() + self.assertEqual([services[1]], result) + def test_service_get_all(self): services = [dict(id=1, key1='val1', key2='val2', topic='compute', host='host1'), @@ -99,28 +136,163 @@ class ComputeHostAPITestCase(test.TestCase): 'service_get_all') # Test no filters - self.host_api.db.service_get_all(self.ctxt, False).AndReturn( - services) + self.host_api.db.service_get_all(self.ctxt, + disabled=None).AndReturn(services) self.mox.ReplayAll() - result = self.host_api.service_get_all(self.ctxt) + result = self.host_api.service_get_all(self.ctxt, set_zones=True) self.mox.VerifyAll() self.assertEqual(exp_services, result) # Test no filters #2 self.mox.ResetAll() - self.host_api.db.service_get_all(self.ctxt, False).AndReturn( - services) + self.host_api.db.service_get_all(self.ctxt, + disabled=None).AndReturn(services) self.mox.ReplayAll() - result = self.host_api.service_get_all(self.ctxt, filters={}) + result = self.host_api.service_get_all(self.ctxt, filters={}, + set_zones=True) self.mox.VerifyAll() self.assertEqual(exp_services, result) # Test w/ filter self.mox.ResetAll() - self.host_api.db.service_get_all(self.ctxt, False).AndReturn( - services) + self.host_api.db.service_get_all(self.ctxt, + disabled=None).AndReturn(services) self.mox.ReplayAll() result = self.host_api.service_get_all(self.ctxt, - filters=dict(key1='val2')) + filters=dict(key1='val2'), + set_zones=True) self.mox.VerifyAll() self.assertEqual([exp_services[1]], result) + + # Test w/ zone filter but no set_zones arg. + self.mox.ResetAll() + self.host_api.db.service_get_all(self.ctxt, + disabled=None).AndReturn(services) + self.mox.ReplayAll() + filters = {'availability_zone': 'nova'} + result = self.host_api.service_get_all(self.ctxt, + filters=filters) + self.mox.VerifyAll() + self.assertEqual(exp_services, result) + + def test_service_get_by_compute_host(self): + self.mox.StubOutWithMock(self.host_api.db, + 'service_get_by_compute_host') + + self.host_api.db.service_get_by_compute_host(self.ctxt, + 'fake-host').AndReturn('fake-response') + self.mox.ReplayAll() + result = self.host_api.service_get_by_compute_host(self.ctxt, + 'fake-host') + self.assertEqual('fake-response', result) + + def test_instance_get_all_by_host(self): + self.mox.StubOutWithMock(self.host_api.db, + 'instance_get_all_by_host') + + self.host_api.db.instance_get_all_by_host(self.ctxt, + 'fake-host').AndReturn(['fake-responses']) + self.mox.ReplayAll() + result = self.host_api.instance_get_all_by_host(self.ctxt, + 'fake-host') + self.assertEqual(['fake-responses'], result) + + +class ComputeHostAPICellsTestCase(ComputeHostAPITestCase): + def setUp(self): + self.flags(compute_api_class='nova.compute.cells_api.ComputeCellsAPI') + super(ComputeHostAPICellsTestCase, self).setUp() + + def _mock_rpc_call(self, expected_message, result=None): + if result is None: + result = 'fake-result' + # Wrapped with cells call + expected_message = {'method': 'proxy_rpc_to_manager', + 'args': {'topic': 'compute.fake_host', + 'rpc_message': expected_message, + 'call': True, + 'timeout': None}, + 'version': '1.2'} + self.mox.StubOutWithMock(rpc, 'call') + rpc.call(self.ctxt, 'cells', expected_message, + None).AndReturn(result) + + def test_service_get_all_no_zones(self): + services = [dict(id=1, key1='val1', key2='val2', topic='compute', + host='host1'), + dict(id=2, key1='val2', key3='val3', topic='compute', + host='host2')] + + fake_filters = {'key1': 'val1'} + self.mox.StubOutWithMock(self.host_api.cells_rpcapi, + 'service_get_all') + self.host_api.cells_rpcapi.service_get_all(self.ctxt, + filters=fake_filters).AndReturn(services) + self.mox.ReplayAll() + result = self.host_api.service_get_all(self.ctxt, + filters=fake_filters) + self.assertEqual(services, result) + + def test_service_get_all(self): + services = [dict(id=1, key1='val1', key2='val2', topic='compute', + host='host1'), + dict(id=2, key1='val2', key3='val3', topic='compute', + host='host2')] + exp_services = [] + for service in services: + exp_service = {} + exp_service.update(availability_zone='nova', **service) + exp_services.append(exp_service) + + fake_filters = {'key1': 'val1'} + self.mox.StubOutWithMock(self.host_api.cells_rpcapi, + 'service_get_all') + self.host_api.cells_rpcapi.service_get_all(self.ctxt, + filters=fake_filters).AndReturn(services) + self.mox.ReplayAll() + result = self.host_api.service_get_all(self.ctxt, + filters=fake_filters, + set_zones=True) + self.mox.VerifyAll() + self.assertEqual(exp_services, result) + + # Test w/ zone filter but no set_zones arg. + self.mox.ResetAll() + fake_filters = {'availability_zone': 'nova'} + # Zone filter is done client-size, so should be stripped + # from this call. + self.host_api.cells_rpcapi.service_get_all(self.ctxt, + filters={}).AndReturn(services) + self.mox.ReplayAll() + result = self.host_api.service_get_all(self.ctxt, + filters=fake_filters) + self.mox.VerifyAll() + self.assertEqual(exp_services, result) + + def test_service_get_by_compute_host(self): + self.mox.StubOutWithMock(self.host_api.cells_rpcapi, + 'service_get_by_compute_host') + + self.host_api.cells_rpcapi.service_get_by_compute_host(self.ctxt, + 'fake-host').AndReturn('fake-response') + self.mox.ReplayAll() + result = self.host_api.service_get_by_compute_host(self.ctxt, + 'fake-host') + self.assertEqual('fake-response', result) + + def test_instance_get_all_by_host(self): + instances = [dict(id=1, cell_name='cell1', host='host1'), + dict(id=2, cell_name='cell2', host='host1'), + dict(id=3, cell_name='cell1', host='host2')] + + self.mox.StubOutWithMock(self.host_api.db, + 'instance_get_all_by_host') + + self.host_api.db.instance_get_all_by_host(self.ctxt, + 'fake-host').AndReturn(instances) + self.mox.ReplayAll() + expected_result = [instances[0], instances[2]] + cell_and_host = cells_utils.cell_with_item('cell1', 'fake-host') + result = self.host_api.instance_get_all_by_host(self.ctxt, + cell_and_host) + self.assertEqual(expected_result, result) |
