From 817dcc89a941fa4e16cc5fff5f08b85ea69bf3fc Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 4 Apr 2019 15:39:08 +0100 Subject: [PATCH] Remove cells code Thankfully the bulk of this is neatly organized in a single directory and can be removed, now that the bulk of the references to it have been removed. The only complicated area is the tests, though effort has been taken to minimise the diff here wherever possible. Part of blueprint remove-cells-v1 Change-Id: Ib0e0b708c46e4330e51f8f8fdfbb02d45aaf0f44 Signed-off-by: Stephen Finucane --- nova/cells/__init__.py | 19 - nova/cells/driver.py | 41 - nova/cells/filters/__init__.py | 56 - nova/cells/filters/different_cell.py | 62 - nova/cells/filters/image_properties.py | 67 - nova/cells/filters/target_cell.py | 71 - nova/cells/manager.py | 529 ------ nova/cells/messaging.py | 1685 ----------------- nova/cells/opts.py | 28 - nova/cells/rpc_driver.py | 168 -- nova/cells/rpcapi.py | 568 ------ nova/cells/scheduler.py | 244 --- nova/cells/state.py | 499 ----- nova/cells/utils.py | 228 --- nova/cells/weights/__init__.py | 43 - nova/cells/weights/mute_child.py | 59 - nova/cells/weights/ram_by_instance_type.py | 46 - nova/cells/weights/weight_offset.py | 39 - nova/compute/__init__.py | 15 +- nova/compute/api.py | 213 +-- nova/compute/cells_api.py | 683 ------- nova/compute/manager.py | 2 - nova/compute/rpcapi.py | 1 - nova/exception.py | 20 - nova/notifications/objects/base.py | 2 + nova/objects/fields.py | 2 + nova/policies/__init__.py | 2 - nova/policies/cells_scheduler.py | 41 - .../api/openstack/compute/test_services.py | 74 - nova/tests/unit/cells/__init__.py | 0 nova/tests/unit/cells/fakes.py | 213 --- nova/tests/unit/cells/test_cells_filters.py | 230 --- nova/tests/unit/cells/test_cells_manager.py | 813 -------- nova/tests/unit/cells/test_cells_messaging.py | 1667 ---------------- .../tests/unit/cells/test_cells_rpc_driver.py | 225 --- nova/tests/unit/cells/test_cells_rpcapi.py | 734 ------- nova/tests/unit/cells/test_cells_scheduler.py | 474 ----- .../unit/cells/test_cells_state_manager.py | 380 ---- nova/tests/unit/cells/test_cells_utils.py | 233 --- nova/tests/unit/cells/test_cells_weights.py | 216 --- nova/tests/unit/compute/test_compute_api.py | 642 ++----- nova/tests/unit/compute/test_compute_cells.py | 688 ------- nova/tests/unit/compute/test_host_api.py | 191 -- nova/tests/unit/test_policy.py | 2 - nova/tests/unit/test_profiler.py | 2 - 45 files changed, 199 insertions(+), 12018 deletions(-) delete mode 100644 nova/cells/__init__.py delete mode 100644 nova/cells/driver.py delete mode 100644 nova/cells/filters/__init__.py delete mode 100644 nova/cells/filters/different_cell.py delete mode 100644 nova/cells/filters/image_properties.py delete mode 100644 nova/cells/filters/target_cell.py delete mode 100644 nova/cells/manager.py delete mode 100644 nova/cells/messaging.py delete mode 100644 nova/cells/opts.py delete mode 100644 nova/cells/rpc_driver.py delete mode 100644 nova/cells/rpcapi.py delete mode 100644 nova/cells/scheduler.py delete mode 100644 nova/cells/state.py delete mode 100644 nova/cells/utils.py delete mode 100644 nova/cells/weights/__init__.py delete mode 100644 nova/cells/weights/mute_child.py delete mode 100644 nova/cells/weights/ram_by_instance_type.py delete mode 100644 nova/cells/weights/weight_offset.py delete mode 100644 nova/compute/cells_api.py delete mode 100644 nova/policies/cells_scheduler.py delete mode 100644 nova/tests/unit/cells/__init__.py delete mode 100644 nova/tests/unit/cells/fakes.py delete mode 100644 nova/tests/unit/cells/test_cells_filters.py delete mode 100644 nova/tests/unit/cells/test_cells_manager.py delete mode 100644 nova/tests/unit/cells/test_cells_messaging.py delete mode 100644 nova/tests/unit/cells/test_cells_rpc_driver.py delete mode 100644 nova/tests/unit/cells/test_cells_rpcapi.py delete mode 100644 nova/tests/unit/cells/test_cells_scheduler.py delete mode 100644 nova/tests/unit/cells/test_cells_state_manager.py delete mode 100644 nova/tests/unit/cells/test_cells_utils.py delete mode 100644 nova/tests/unit/cells/test_cells_weights.py delete mode 100644 nova/tests/unit/compute/test_compute_cells.py diff --git a/nova/cells/__init__.py b/nova/cells/__init__.py deleted file mode 100644 index caf1eab3f2..0000000000 --- a/nova/cells/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Cells -""" - -TOPIC = 'cells' diff --git a/nova/cells/driver.py b/nova/cells/driver.py deleted file mode 100644 index 922e42e549..0000000000 --- a/nova/cells/driver.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Base Cells Communication Driver -""" - - -class BaseCellsDriver(object): - """The base class for cells communication. - - One instance of this class will be created for every neighbor cell - that we find in the DB and it will be associated with the cell in - its CellState. - - One instance is also created by the cells manager for setting up - the consumers. - """ - def start_servers(self, msg_runner): - """Start any messaging servers the driver may need.""" - raise NotImplementedError() - - def stop_servers(self): - """Stop accepting messages.""" - raise NotImplementedError() - - def send_message_to_cell(self, cell_state, message): - """Send a message to a cell.""" - raise NotImplementedError() diff --git a/nova/cells/filters/__init__.py b/nova/cells/filters/__init__.py deleted file mode 100644 index 33988c2ccf..0000000000 --- a/nova/cells/filters/__init__.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (c) 2012-2013 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Cell scheduler filters -""" - -from nova import filters - - -class BaseCellFilter(filters.BaseFilter): - """Base class for cell filters.""" - - def authorized(self, ctxt): - """Return whether or not the context is authorized for this filter - based on policy. - The policy action is "cells_scheduler_filter:" where - is the name of the filter class. - """ - name = 'cells_scheduler_filter:' + self.__class__.__name__ - return ctxt.can(name, fatal=False) - - def _filter_one(self, cell, filter_properties): - return self.cell_passes(cell, filter_properties) - - def cell_passes(self, cell, filter_properties): - """Return True if the CellState passes the filter, otherwise False. - Override this in a subclass. - """ - raise NotImplementedError() - - -class CellFilterHandler(filters.BaseFilterHandler): - def __init__(self): - super(CellFilterHandler, self).__init__(BaseCellFilter) - - -def all_filters(): - """Return a list of filter classes found in this directory. - - This method is used as the default for available scheduler filters - and should return a list of all filter classes available. - """ - return CellFilterHandler().get_all_classes() diff --git a/nova/cells/filters/different_cell.py b/nova/cells/filters/different_cell.py deleted file mode 100644 index 00607fad81..0000000000 --- a/nova/cells/filters/different_cell.py +++ /dev/null @@ -1,62 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Different cell filter. - -A scheduler hint of 'different_cell' with a value of a full cell name may be -specified to route a build away from a particular cell. -""" - -import six - -from nova.cells import filters -from nova.cells import utils as cells_utils - - -class DifferentCellFilter(filters.BaseCellFilter): - """Different cell filter. Works by specifying a scheduler hint of - 'different_cell'. The value should be the full cell path. - """ - def filter_all(self, cells, filter_properties): - """Override filter_all() which operates on the full list - of cells... - """ - scheduler_hints = filter_properties.get('scheduler_hints') - if not scheduler_hints: - return cells - - cell_routes = scheduler_hints.get('different_cell') - if not cell_routes: - return cells - if isinstance(cell_routes, six.string_types): - cell_routes = [cell_routes] - - if not self.authorized(filter_properties['context']): - # No filtering, if not authorized. - return cells - - routing_path = filter_properties['routing_path'] - filtered_cells = [] - for cell in cells: - if not self._cell_state_matches(cell, routing_path, cell_routes): - filtered_cells.append(cell) - - return filtered_cells - - def _cell_state_matches(self, cell_state, routing_path, cell_routes): - cell_route = routing_path - if not cell_state.is_me: - cell_route += cells_utils.PATH_CELL_SEP + cell_state.name - if cell_route in cell_routes: - return True - return False diff --git a/nova/cells/filters/image_properties.py b/nova/cells/filters/image_properties.py deleted file mode 100644 index 69ef33b611..0000000000 --- a/nova/cells/filters/image_properties.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) 2012-2013 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Image properties filter. - -Image metadata named 'hypervisor_version_requires' with a version specification -may be specified to ensure the build goes to a cell which has hypervisors of -the required version. - -If either the version requirement on the image or the hypervisor capability -of the cell is not present, this filter returns without filtering out the -cells. -""" - -from distutils import versionpredicate - -from nova.cells import filters - - -class ImagePropertiesFilter(filters.BaseCellFilter): - """Image properties filter. Works by specifying the hypervisor required in - the image metadata and the supported hypervisor version in cell - capabilities. - """ - - def filter_all(self, cells, filter_properties): - """Override filter_all() which operates on the full list - of cells... - """ - request_spec = filter_properties.get('request_spec', {}) - image_properties = request_spec.get('image', {}).get('properties', {}) - hypervisor_version_requires = image_properties.get( - 'hypervisor_version_requires') - - if hypervisor_version_requires is None: - return cells - - filtered_cells = [] - for cell in cells: - version = cell.capabilities.get('prominent_hypervisor_version') - if version: - l = list(version) - version = str(l[0]) - - if not version or self._matches_version(version, - hypervisor_version_requires): - filtered_cells.append(cell) - - return filtered_cells - - def _matches_version(self, version, version_requires): - predicate = versionpredicate.VersionPredicate( - 'prop (%s)' % version_requires) - return predicate.satisfied_by(version) diff --git a/nova/cells/filters/target_cell.py b/nova/cells/filters/target_cell.py deleted file mode 100644 index fdf2362daa..0000000000 --- a/nova/cells/filters/target_cell.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) 2012-2013 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Target cell filter. - -A scheduler hint of 'target_cell' with a value of a full cell name may be -specified to route a build to a particular cell. No error handling is -done as there's no way to know whether the full path is a valid. -""" - -from oslo_log import log as logging - -from nova.cells import filters - -LOG = logging.getLogger(__name__) - - -class TargetCellFilter(filters.BaseCellFilter): - """Target cell filter. Works by specifying a scheduler hint of - 'target_cell'. The value should be the full cell path. - """ - - def filter_all(self, cells, filter_properties): - """Override filter_all() which operates on the full list - of cells... - """ - scheduler_hints = filter_properties.get('scheduler_hints') - if not scheduler_hints: - return cells - - # This filter only makes sense at the top level, as a full - # cell name is specified. So we pop 'target_cell' out of the - # hints dict. - cell_name = scheduler_hints.pop('target_cell', None) - if not cell_name: - return cells - - # This authorization is after popping off target_cell, so - # that in case this fails, 'target_cell' is not left in the - # dict when child cells go to schedule. - if not self.authorized(filter_properties['context']): - # No filtering, if not authorized. - return cells - - LOG.info("Forcing direct route to %(cell_name)s because " - "of 'target_cell' scheduler hint", - {'cell_name': cell_name}) - - scheduler = filter_properties['scheduler'] - if cell_name == filter_properties['routing_path']: - return [scheduler.state_manager.get_my_state()] - ctxt = filter_properties['context'] - - scheduler.msg_runner.build_instances(ctxt, cell_name, - filter_properties['host_sched_kwargs']) - - # Returning None means to skip further scheduling, because we - # handled it. diff --git a/nova/cells/manager.py b/nova/cells/manager.py deleted file mode 100644 index 6efa4d27f6..0000000000 --- a/nova/cells/manager.py +++ /dev/null @@ -1,529 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Cells Service Manager -""" -import datetime -import time - -from oslo_log import log as logging -import oslo_messaging -from oslo_service import periodic_task -from oslo_utils import timeutils - -from six.moves import range - -from nova.cells import messaging -from nova.cells import rpc_driver as cells_rpc_driver -from nova.cells import state as cells_state -from nova.cells import utils as cells_utils -from nova.compute import rpcapi as compute_rpcapi -import nova.conf -from nova import context -from nova import exception -from nova import manager -from nova import objects -from nova.objects import instance as instance_obj - - -CONF = nova.conf.CONF - -LOG = logging.getLogger(__name__) - - -class CellsManager(manager.Manager): - """The nova-cells manager class. This class defines RPC - methods that the local cell may call. This class is NOT used for - messages coming from other cells. That communication is - driver-specific. - - Communication to other cells happens via the nova.cells.messaging module. - The MessageRunner from that module will handle routing the message to - the correct cell via the communications driver. Most methods below - create 'targeted' (where we want to route a message to a specific cell) - or 'broadcast' (where we want a message to go to multiple cells) - messages. - - Scheduling requests get passed to the scheduler class. - """ - - target = oslo_messaging.Target(version='1.38') - - def __init__(self, *args, **kwargs): - LOG.warning('The cells feature of Nova is considered experimental ' - 'by the OpenStack project because it receives much ' - 'less testing than the rest of Nova. This may change ' - 'in the future, but current deployers should be aware ' - 'that the use of it in production right now may be ' - 'risky. Also note that cells does not currently ' - 'support rolling upgrades, it is assumed that cells ' - 'deployments are upgraded lockstep so n-1 cells ' - 'compatibility does not work.') - # Mostly for tests. - cell_state_manager = kwargs.pop('cell_state_manager', None) - super(CellsManager, self).__init__(service_name='cells', - *args, **kwargs) - if cell_state_manager is None: - cell_state_manager = cells_state.CellStateManager - self.state_manager = cell_state_manager() - self.msg_runner = messaging.MessageRunner(self.state_manager) - self.driver = cells_rpc_driver.CellsRPCDriver() - self.instances_to_heal = iter([]) - - def post_start_hook(self): - """Have the driver start its servers for inter-cell communication. - Also ask our child cells for their capacities and capabilities so - we get them more quickly than just waiting for the next periodic - update. Receiving the updates from the children will cause us to - update our parents. If we don't have any children, just update - our parents immediately. - """ - # FIXME(comstud): There's currently no hooks when services are - # stopping, so we have no way to stop servers cleanly. - self.driver.start_servers(self.msg_runner) - ctxt = context.get_admin_context() - if self.state_manager.get_child_cells(): - self.msg_runner.ask_children_for_capabilities(ctxt) - self.msg_runner.ask_children_for_capacities(ctxt) - else: - self._update_our_parents(ctxt) - - @periodic_task.periodic_task - def _update_our_parents(self, ctxt): - """Update our parent cells with our capabilities and capacity - if we're at the bottom of the tree. - """ - self.msg_runner.tell_parents_our_capabilities(ctxt) - self.msg_runner.tell_parents_our_capacities(ctxt) - - @periodic_task.periodic_task - def _heal_instances(self, ctxt): - """Periodic task to send updates for a number of instances to - parent cells. - - On every run of the periodic task, we will attempt to sync - 'CONF.cells.instance_update_num_instances' number of instances. - When we get the list of instances, we shuffle them so that multiple - nova-cells services aren't attempting to sync the same instances - in lockstep. - - If CONF.cells.instance_update_at_threshold is set, only attempt - to sync instances that have been updated recently. The CONF - setting defines the maximum number of seconds old the updated_at - can be. Ie, a threshold of 3600 means to only update instances - that have modified in the last hour. - """ - - if not self.state_manager.get_parent_cells(): - # No need to sync up if we have no parents. - return - - info = {'updated_list': False} - - def _next_instance(): - try: - instance = next(self.instances_to_heal) - except StopIteration: - if info['updated_list']: - return - threshold = CONF.cells.instance_updated_at_threshold - updated_since = None - if threshold > 0: - updated_since = timeutils.utcnow() - datetime.timedelta( - seconds=threshold) - self.instances_to_heal = cells_utils.get_instances_to_sync( - ctxt, updated_since=updated_since, shuffle=True, - uuids_only=True) - info['updated_list'] = True - try: - instance = next(self.instances_to_heal) - except StopIteration: - return - return instance - - rd_context = ctxt.elevated(read_deleted='yes') - - for i in range(CONF.cells.instance_update_num_instances): - while True: - # Yield to other greenthreads - time.sleep(0) - instance_uuid = _next_instance() - if not instance_uuid: - return - try: - instance = objects.Instance.get_by_uuid(rd_context, - instance_uuid) - except exception.InstanceNotFound: - continue - self._sync_instance(ctxt, instance) - break - - def _sync_instance(self, ctxt, instance): - """Broadcast an instance_update or instance_destroy message up to - parent cells. - """ - pass - - def build_instances(self, ctxt, build_inst_kwargs): - """Pick a cell (possibly ourselves) to build new instance(s) and - forward the request accordingly. - """ - # Target is ourselves first. - filter_properties = build_inst_kwargs.get('filter_properties') - if (filter_properties is not None and - not isinstance(filter_properties['instance_type'], - objects.Flavor)): - # NOTE(danms): Handle pre-1.30 build_instances() call. Remove me - # when we bump the RPC API version to 2.0. - flavor = objects.Flavor(**filter_properties['instance_type']) - build_inst_kwargs['filter_properties'] = dict( - filter_properties, instance_type=flavor) - instances = build_inst_kwargs['instances'] - if not isinstance(instances[0], objects.Instance): - # NOTE(danms): Handle pre-1.32 build_instances() call. Remove me - # when we bump the RPC API version to 2.0 - build_inst_kwargs['instances'] = instance_obj._make_instance_list( - ctxt, objects.InstanceList(), instances, ['system_metadata', - 'metadata']) - our_cell = self.state_manager.get_my_state() - self.msg_runner.build_instances(ctxt, our_cell, build_inst_kwargs) - - def get_cell_info_for_neighbors(self, _ctxt): - """Return cell information for our neighbor cells.""" - return self.state_manager.get_cell_info_for_neighbors() - - def run_compute_api_method(self, ctxt, cell_name, method_info, call): - """Call a compute API method in a specific cell.""" - response = self.msg_runner.run_compute_api_method(ctxt, - cell_name, - method_info, - call) - if call: - return response.value_or_raise() - - def instance_delete_everywhere(self, ctxt, instance, delete_type): - """This is used by API cell when it didn't know what cell - an instance was in, but the instance was requested to be - deleted or soft_deleted. So, we'll broadcast this everywhere. - """ - if isinstance(instance, dict): - instance = objects.Instance._from_db_object(ctxt, - objects.Instance(), instance) - self.msg_runner.instance_delete_everywhere(ctxt, instance, - delete_type) - - def sync_instances(self, ctxt, project_id, updated_since, deleted): - """Force a sync of all instances, potentially by project_id, - and potentially since a certain date/time. - """ - 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: - service = cells_utils.add_cell_to_service( - service, response.cell_name) - ret_services.append(service) - return ret_services - - @oslo_messaging.expected_exceptions(exception.CellRoutingInconsistency) - 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() - service = cells_utils.add_cell_to_service(service, response.cell_name) - return service - - def get_host_uptime(self, ctxt, host_name): - """Return host uptime for a compute host in a certain cell - - :param host_name: fully qualified hostname. It should be in format of - parent!child@host_id - """ - cell_name, host_name = cells_utils.split_cell_and_item(host_name) - response = self.msg_runner.get_host_uptime(ctxt, cell_name, - host_name) - return response.value_or_raise() - - def service_update(self, ctxt, host_name, binary, params_to_update): - """Used to enable/disable a service. For compute services, setting to - disabled stops new builds arriving on that host. - - :param host_name: the name of the host machine that the service is - running - :param binary: The name of the executable that the service runs as - :param params_to_update: eg. {'disabled': True} - :returns: the service reference - """ - cell_name, host_name = cells_utils.split_cell_and_item(host_name) - response = self.msg_runner.service_update( - ctxt, cell_name, host_name, binary, params_to_update) - service = response.value_or_raise() - service = cells_utils.add_cell_to_service(service, response.cell_name) - return service - - def service_delete(self, ctxt, cell_service_id): - """Deletes the specified service.""" - cell_name, service_id = cells_utils.split_cell_and_item( - cell_service_id) - self.msg_runner.service_delete(ctxt, cell_name, service_id) - - @oslo_messaging.expected_exceptions(exception.CellRoutingInconsistency) - def proxy_rpc_to_manager(self, ctxt, topic, rpc_message, call, timeout): - """Proxy an RPC message as-is to a manager.""" - compute_topic = compute_rpcapi.RPC_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() - - def task_log_get_all(self, ctxt, task_name, period_beginning, - period_ending, host=None, state=None): - """Get task logs from the DB from all cells or a particular - cell. - - If 'host' is not None, host will be of the format 'cell!name@host', - with '@host' being optional. The query will be directed to the - appropriate cell and return all task logs, or task logs matching - the host if specified. - - 'state' also may be None. If it's not, filter by the state as well. - """ - if host is None: - cell_name = None - else: - cell_name, host = cells_utils.split_cell_and_item(host) - # If no cell name was given, assume that the host name is the - # cell_name and that the target is all hosts - if cell_name is None: - cell_name, host = host, cell_name - responses = self.msg_runner.task_log_get_all(ctxt, cell_name, - task_name, period_beginning, period_ending, - host=host, state=state) - # 1 response per cell. Each response is a list of task log - # entries. - ret_task_logs = [] - for response in responses: - task_logs = response.value_or_raise() - for task_log in task_logs: - cells_utils.add_cell_to_task_log(task_log, - response.cell_name) - ret_task_logs.append(task_log) - return ret_task_logs - - @oslo_messaging.expected_exceptions(exception.CellRoutingInconsistency) - def compute_node_get(self, ctxt, compute_id): - """Get a compute node by ID or UUID in a specific cell.""" - cell_name, compute_id = cells_utils.split_cell_and_item( - compute_id) - response = self.msg_runner.compute_node_get(ctxt, cell_name, - compute_id) - node = response.value_or_raise() - node = cells_utils.add_cell_to_compute_node(node, cell_name) - return node - - def compute_node_get_all(self, ctxt, hypervisor_match=None): - """Return list of compute nodes in all cells.""" - responses = self.msg_runner.compute_node_get_all(ctxt, - hypervisor_match=hypervisor_match) - # 1 response per cell. Each response is a list of compute_node - # entries. - ret_nodes = [] - for response in responses: - nodes = response.value_or_raise() - for node in nodes: - node = cells_utils.add_cell_to_compute_node(node, - response.cell_name) - ret_nodes.append(node) - return ret_nodes - - def compute_node_stats(self, ctxt): - """Return compute node stats totals from all cells.""" - responses = self.msg_runner.compute_node_stats(ctxt) - totals = {} - for response in responses: - data = response.value_or_raise() - for key, val in data.items(): - totals.setdefault(key, 0) - totals[key] += val - return totals - - def actions_get(self, ctxt, cell_name, instance_uuid): - response = self.msg_runner.actions_get(ctxt, cell_name, instance_uuid) - return response.value_or_raise() - - def action_get_by_request_id(self, ctxt, cell_name, instance_uuid, - request_id): - response = self.msg_runner.action_get_by_request_id(ctxt, cell_name, - instance_uuid, - request_id) - return response.value_or_raise() - - def action_events_get(self, ctxt, cell_name, action_id): - response = self.msg_runner.action_events_get(ctxt, cell_name, - action_id) - return response.value_or_raise() - - def consoleauth_delete_tokens(self, ctxt, instance_uuid): - """Delete consoleauth tokens for an instance in API cells.""" - self.msg_runner.consoleauth_delete_tokens(ctxt, instance_uuid) - - def validate_console_port(self, ctxt, instance_uuid, console_port, - console_type): - """Validate console port with child cell compute node.""" - instance = objects.Instance.get_by_uuid(ctxt, instance_uuid) - if not instance.cell_name: - raise exception.InstanceUnknownCell(instance_uuid=instance_uuid) - response = self.msg_runner.validate_console_port(ctxt, - instance.cell_name, instance_uuid, console_port, - console_type) - return response.value_or_raise() - - def get_capacities(self, ctxt, cell_name): - return self.state_manager.get_capacities(cell_name) - - def get_migrations(self, ctxt, filters): - """Fetch migrations applying the filters.""" - target_cell = None - if "cell_name" in filters: - _path_cell_sep = cells_utils.PATH_CELL_SEP - target_cell = '%s%s%s' % (CONF.cells.name, _path_cell_sep, - filters['cell_name']) - - responses = self.msg_runner.get_migrations(ctxt, target_cell, - False, filters) - migrations = [] - for response in responses: - # response.value_or_raise returns MigrationList objects. - # MigrationList.objects returns the list of Migration objects. - migrations.extend(response.value_or_raise().objects) - return objects.MigrationList(objects=migrations) - - def start_instance(self, ctxt, instance): - """Start an instance in its cell.""" - self.msg_runner.start_instance(ctxt, instance) - - def stop_instance(self, ctxt, instance, do_cast=True, - clean_shutdown=True): - """Stop an instance in its cell.""" - response = self.msg_runner.stop_instance(ctxt, instance, - do_cast=do_cast, - clean_shutdown=clean_shutdown) - if not do_cast: - return response.value_or_raise() - - def cell_create(self, ctxt, values): - return self.state_manager.cell_create(ctxt, values) - - def cell_update(self, ctxt, cell_name, values): - return self.state_manager.cell_update(ctxt, cell_name, values) - - def cell_delete(self, ctxt, cell_name): - return self.state_manager.cell_delete(ctxt, cell_name) - - def cell_get(self, ctxt, cell_name): - return self.state_manager.cell_get(ctxt, cell_name) - - def reboot_instance(self, ctxt, instance, reboot_type): - """Reboot an instance in its cell.""" - self.msg_runner.reboot_instance(ctxt, instance, reboot_type) - - def pause_instance(self, ctxt, instance): - """Pause an instance in its cell.""" - self.msg_runner.pause_instance(ctxt, instance) - - def unpause_instance(self, ctxt, instance): - """Unpause an instance in its cell.""" - self.msg_runner.unpause_instance(ctxt, instance) - - def suspend_instance(self, ctxt, instance): - """Suspend an instance in its cell.""" - self.msg_runner.suspend_instance(ctxt, instance) - - def resume_instance(self, ctxt, instance): - """Resume an instance in its cell.""" - self.msg_runner.resume_instance(ctxt, instance) - - def terminate_instance(self, ctxt, instance, delete_type='delete'): - """Delete an instance in its cell.""" - # NOTE(rajesht): The `delete_type` parameter is passed so that it will - # be routed to destination cell, where instance deletion will happen. - self.msg_runner.terminate_instance(ctxt, instance, - delete_type=delete_type) - - def soft_delete_instance(self, ctxt, instance): - """Soft-delete an instance in its cell.""" - self.msg_runner.soft_delete_instance(ctxt, instance) - - def resize_instance(self, ctxt, instance, flavor, - extra_instance_updates, - clean_shutdown=True): - """Resize an instance in its cell.""" - self.msg_runner.resize_instance(ctxt, instance, - flavor, extra_instance_updates, - clean_shutdown=clean_shutdown) - - def live_migrate_instance(self, ctxt, instance, block_migration, - disk_over_commit, host_name): - """Live migrate an instance in its cell.""" - self.msg_runner.live_migrate_instance(ctxt, instance, - block_migration, - disk_over_commit, - host_name) - - def revert_resize(self, ctxt, instance): - """Revert a resize for an instance in its cell.""" - self.msg_runner.revert_resize(ctxt, instance) - - def confirm_resize(self, ctxt, instance): - """Confirm a resize for an instance in its cell.""" - self.msg_runner.confirm_resize(ctxt, instance) - - def reset_network(self, ctxt, instance): - """Reset networking for an instance in its cell.""" - self.msg_runner.reset_network(ctxt, instance) - - def inject_network_info(self, ctxt, instance): - """Inject networking for an instance in its cell.""" - self.msg_runner.inject_network_info(ctxt, instance) - - def snapshot_instance(self, ctxt, instance, image_id): - """Snapshot an instance in its cell.""" - self.msg_runner.snapshot_instance(ctxt, instance, image_id) - - def backup_instance(self, ctxt, instance, image_id, backup_type, rotation): - """Backup an instance in its cell.""" - self.msg_runner.backup_instance(ctxt, instance, image_id, - backup_type, rotation) - - def rebuild_instance(self, ctxt, instance, image_href, admin_password, - files_to_inject, preserve_ephemeral, kwargs): - self.msg_runner.rebuild_instance(ctxt, instance, image_href, - admin_password, files_to_inject, - preserve_ephemeral, kwargs) - - def set_admin_password(self, ctxt, instance, new_pass): - self.msg_runner.set_admin_password(ctxt, instance, new_pass) diff --git a/nova/cells/messaging.py b/nova/cells/messaging.py deleted file mode 100644 index 710393a201..0000000000 --- a/nova/cells/messaging.py +++ /dev/null @@ -1,1685 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# Copyright 2013 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Cell messaging module. - -This module defines the different message types that are passed between -cells and the methods that they can call when the target cell has been -reached. - -The interface into this module is the MessageRunner class. -""" - -import sys -import traceback - -from eventlet import queue -from oslo_log import log as logging -import oslo_messaging as messaging -from oslo_serialization import jsonutils -from oslo_utils import importutils -from oslo_utils import timeutils -from oslo_utils import uuidutils -import six -from six.moves import range - -from nova.cells import state as cells_state -from nova.cells import utils as cells_utils -from nova import compute -from nova.compute import instance_actions -from nova.compute import rpcapi as compute_rpcapi -from nova.compute import task_states -from nova.compute import vm_states -import nova.conf -from nova.consoleauth import rpcapi as consoleauth_rpcapi -from nova import context -from nova.db import base -from nova import exception -from nova.i18n import _ -from nova import objects -from nova.objects import base as objects_base -from nova import rpc - -CONF = nova.conf.CONF - -LOG = logging.getLogger(__name__) - -# Separator used between cell names for the 'full cell name' and routing -# path. -_PATH_CELL_SEP = cells_utils.PATH_CELL_SEP - - -def _reverse_path(path): - """Reverse a path. Used for sending responses upstream.""" - path_parts = path.split(_PATH_CELL_SEP) - path_parts.reverse() - return _PATH_CELL_SEP.join(path_parts) - - -def _response_cell_name_from_path(routing_path, neighbor_only=False): - """Reverse the routing_path. If we only want to send to our parent, - set neighbor_only to True. - """ - path = _reverse_path(routing_path) - if not neighbor_only or len(path) == 1: - return path - return _PATH_CELL_SEP.join(path.split(_PATH_CELL_SEP)[:2]) - - -# -# Message classes. -# - - -class _BaseMessage(object): - """Base message class. It defines data that is passed with every - single message through every cell. - - Messages are JSON-ified before sending and turned back into a - class instance when being received. - - Every message has a unique ID. This is used to route responses - back to callers. In the future, this might be used to detect - receiving the same message more than once. - - routing_path is updated on every hop through a cell. The current - cell name is appended to it (cells are separated by - _PATH_CELL_SEP ('!')). This is used to tell if we've reached the - target cell and also to determine the source of a message for - responses by reversing it. - - hop_count is incremented and compared against max_hop_count. The - only current usefulness of this is to break out of a routing loop - if someone has a broken config. - - fanout means to send to all nova-cells services running in a cell. - This is useful for capacity and capability broadcasting as well - as making sure responses get back to the nova-cells service that - is waiting. - """ - - # Override message_type in a subclass - message_type = None - - base_attrs_to_json = ['message_type', - 'ctxt', - 'method_name', - 'method_kwargs', - 'direction', - 'need_response', - 'fanout', - 'uuid', - 'routing_path', - 'hop_count', - 'max_hop_count'] - - def __init__(self, msg_runner, ctxt, method_name, method_kwargs, - direction, need_response=False, fanout=False, uuid=None, - routing_path=None, hop_count=0, max_hop_count=None, - **kwargs): - self.ctxt = ctxt - self.resp_queue = None - self.msg_runner = msg_runner - self.state_manager = msg_runner.state_manager - # Copy these. - self.base_attrs_to_json = self.base_attrs_to_json[:] - # Normally this would just be CONF.cells.name, but going through - # the msg_runner allows us to stub it more easily. - self.our_path_part = self.msg_runner.our_name - self.uuid = uuid - if self.uuid is None: - self.uuid = uuidutils.generate_uuid() - self.method_name = method_name - self.method_kwargs = method_kwargs - self.direction = direction - self.need_response = need_response - self.fanout = fanout - self.routing_path = routing_path - self.hop_count = hop_count - if max_hop_count is None: - max_hop_count = CONF.cells.max_hop_count - self.max_hop_count = max_hop_count - self.is_broadcast = False - self._append_hop() - # Each sub-class should set this when the message is initialized - self.next_hops = [] - self.resp_queue = None - self.serializer = objects_base.NovaObjectSerializer() - - def __repr__(self): - _dict = self._to_dict() - _dict.pop('method_kwargs') - return "<%s: %s>" % (self.__class__.__name__, _dict) - - def _append_hop(self): - """Add our hop to the routing_path.""" - routing_path = (self.routing_path and - self.routing_path + _PATH_CELL_SEP or '') - self.routing_path = routing_path + self.our_path_part - self.hop_count += 1 - - def _process_locally(self): - """Its been determined that we should process this message in this - cell. Go through the MessageRunner to call the appropriate - method for this message. Catch the response and/or exception and - encode it within a Response instance. Return it so the caller - can potentially return it to another cell... or return it to - a caller waiting in this cell. - """ - try: - resp_value = self.msg_runner._process_message_locally(self) - failure = False - except Exception: - resp_value = sys.exc_info() - failure = True - LOG.exception("Error processing message locally") - return Response(self.ctxt, self.routing_path, resp_value, failure) - - def _setup_response_queue(self): - """Shortcut to creating a response queue in the MessageRunner.""" - self.resp_queue = self.msg_runner._setup_response_queue(self) - - def _cleanup_response_queue(self): - """Shortcut to deleting a response queue in the MessageRunner.""" - if self.resp_queue: - self.msg_runner._cleanup_response_queue(self) - self.resp_queue = None - - def _wait_for_json_responses(self, num_responses=1): - """Wait for response(s) to be put into the eventlet queue. Since - each queue entry actually contains a list of JSON-ified responses, - combine them all into a single list to return. - - Destroy the eventlet queue when done. - """ - if not self.resp_queue: - # Source is not actually expecting a response - return - responses = [] - wait_time = CONF.cells.call_timeout - try: - for x in range(num_responses): - json_responses = self.resp_queue.get(timeout=wait_time) - responses.extend(json_responses) - except queue.Empty: - raise exception.CellTimeout() - finally: - self._cleanup_response_queue() - return responses - - def _send_json_responses(self, json_responses, neighbor_only=False, - fanout=False): - """Send list of responses to this message. Responses passed here - are JSON-ified. Targeted messages have a single response while - Broadcast messages may have multiple responses. - - If this cell was the source of the message, these responses will - be returned from self.process(). - - Otherwise, we will route the response to the source of the - request. If 'neighbor_only' is True, the response will be sent - to the neighbor cell, not the original requester. Broadcast - messages get aggregated at each hop, so neighbor_only will be - True for those messages. - """ - if not self.need_response: - return - if self.source_is_us(): - responses = [] - for json_response in json_responses: - responses.append(Response.from_json(self.ctxt, json_response)) - return responses - direction = self.direction == 'up' and 'down' or 'up' - response_kwargs = {'orig_message': self.to_json(), - 'responses': json_responses} - target_cell = _response_cell_name_from_path(self.routing_path, - neighbor_only=neighbor_only) - response = self.msg_runner._create_response_message(self.ctxt, - direction, target_cell, self.uuid, response_kwargs, - fanout=fanout) - response.process() - - def _send_response(self, response, neighbor_only=False): - """Send a response to this message. If the source of the - request was ourselves, just return the response. It'll be - passed back to the caller of self.process(). See DocString for - _send_json_responses() as it handles most of the real work for - this method. - - 'response' is an instance of Response class. - """ - if not self.need_response: - return - if self.source_is_us(): - return response - self._send_json_responses([response.to_json()], - neighbor_only=neighbor_only) - - def _send_response_from_exception(self, exc_info): - """Take an exception as returned from sys.exc_info(), encode - it in a Response, and send it. - """ - response = Response(self.ctxt, self.routing_path, exc_info, True) - return self._send_response(response) - - def _to_dict(self): - """Convert a message to a dictionary. Only used internally.""" - _dict = {} - for key in self.base_attrs_to_json: - _dict[key] = getattr(self, key) - return _dict - - def to_json(self): - """Convert a message into JSON for sending to a sibling cell.""" - _dict = self._to_dict() - # Convert context to dict. - _dict['ctxt'] = _dict['ctxt'].to_dict() - # NOTE(comstud): 'method_kwargs' needs special serialization - # because it may contain objects. - method_kwargs = _dict['method_kwargs'] - for k, v in method_kwargs.items(): - method_kwargs[k] = self.serializer.serialize_entity(self.ctxt, v) - return jsonutils.dumps(_dict) - - def source_is_us(self): - """Did this cell create this message?""" - return self.routing_path == self.our_path_part - - def process(self): - """Process a message. Deal with it locally and/or forward it to a - sibling cell. - - Override in a subclass. - """ - raise NotImplementedError() - - -class _TargetedMessage(_BaseMessage): - """A targeted message is a message that is destined for a specific - single cell. - - 'target_cell' can be a full cell name like 'api!child-cell' or it can - be an instance of the CellState class if the target is a neighbor cell. - """ - message_type = 'targeted' - - def __init__(self, msg_runner, ctxt, method_name, method_kwargs, - direction, target_cell, **kwargs): - super(_TargetedMessage, self).__init__(msg_runner, ctxt, - method_name, method_kwargs, direction, **kwargs) - if isinstance(target_cell, cells_state.CellState): - # Neighbor cell or ourselves. Convert it to a 'full path'. - if target_cell.is_me: - target_cell = self.our_path_part - else: - target_cell = '%s%s%s' % (self.our_path_part, - _PATH_CELL_SEP, - target_cell.name) - # NOTE(alaski): This occurs when hosts are specified with no cells - # routing information. - if target_cell is None: - reason = _('No cell given in routing path.') - raise exception.CellRoutingInconsistency(reason=reason) - self.target_cell = target_cell - self.base_attrs_to_json.append('target_cell') - - def _get_next_hop(self): - """Return the cell name for the next hop. If the next hop is - the current cell, return None. - """ - if self.target_cell == self.routing_path: - return self.state_manager.my_cell_state - target_cell = self.target_cell - routing_path = self.routing_path - current_hops = routing_path.count(_PATH_CELL_SEP) - next_hop_num = current_hops + 1 - dest_hops = target_cell.count(_PATH_CELL_SEP) - if dest_hops < current_hops: - reason_args = {'target_cell': target_cell, - 'routing_path': routing_path} - reason = _("destination is %(target_cell)s but routing_path " - "is %(routing_path)s") % reason_args - raise exception.CellRoutingInconsistency(reason=reason) - dest_name_parts = target_cell.split(_PATH_CELL_SEP) - if (_PATH_CELL_SEP.join(dest_name_parts[:next_hop_num]) != - routing_path): - reason_args = {'target_cell': target_cell, - 'routing_path': routing_path} - reason = _("destination is %(target_cell)s but routing_path " - "is %(routing_path)s") % reason_args - raise exception.CellRoutingInconsistency(reason=reason) - next_hop_name = dest_name_parts[next_hop_num] - if self.direction == 'up': - next_hop = self.state_manager.get_parent_cell(next_hop_name) - else: - next_hop = self.state_manager.get_child_cell(next_hop_name) - if not next_hop: - cell_type = 'parent' if self.direction == 'up' else 'child' - reason_args = {'cell_type': cell_type, - 'target_cell': target_cell} - reason = _("Unknown %(cell_type)s when routing to " - "%(target_cell)s") % reason_args - raise exception.CellRoutingInconsistency(reason=reason) - return next_hop - - def process(self): - """Process a targeted message. This is called for all cells - that touch this message. If the local cell is the one that - created this message, we reply directly with a Response instance. - If the local cell is not the target, an eventlet queue is created - and we wait for the response to show up via another thread - receiving the Response back. - - Responses to targeted messages are routed directly back to the - source. No eventlet queues are created in intermediate hops. - - All exceptions for processing the message across the whole - routing path are caught and encoded within the Response and - returned to the caller. - """ - try: - next_hop = self._get_next_hop() - except Exception: - exc_info = sys.exc_info() - LOG.exception("Error locating next hop for message") - return self._send_response_from_exception(exc_info) - - if next_hop.is_me: - # Final destination. - response = self._process_locally() - return self._send_response(response) - - # Need to forward via neighbor cell. - if self.need_response and self.source_is_us(): - # A response is needed and the source of the message is - # this cell. Create the eventlet queue. - self._setup_response_queue() - wait_for_response = True - else: - wait_for_response = False - - try: - # This is inside the try block, so we can encode the - # exception and return it to the caller. - if self.hop_count >= self.max_hop_count: - raise exception.CellMaxHopCountReached( - hop_count=self.hop_count) - next_hop.send_message(self) - except Exception: - exc_info = sys.exc_info() - err_str = "Failed to send message to cell: %(next_hop)s" - LOG.exception(err_str, {'next_hop': next_hop}) - self._cleanup_response_queue() - return self._send_response_from_exception(exc_info) - - if wait_for_response: - # Targeted messages only have 1 response. - remote_response = self._wait_for_json_responses()[0] - return Response.from_json(self.ctxt, remote_response) - - -class _BroadcastMessage(_BaseMessage): - """A broadcast message. This means to call a method in every single - cell going in a certain direction. - """ - message_type = 'broadcast' - - def __init__(self, msg_runner, ctxt, method_name, method_kwargs, - direction, run_locally=True, **kwargs): - super(_BroadcastMessage, self).__init__(msg_runner, ctxt, - method_name, method_kwargs, direction, **kwargs) - # The local cell creating this message has the option - # to be able to process the message locally or not. - self.run_locally = run_locally - self.is_broadcast = True - - def _get_next_hops(self): - """Set the next hops and return the number of hops. The next - hops may include ourself. - """ - if self.hop_count >= self.max_hop_count: - return [] - if self.direction == 'down': - return self.state_manager.get_child_cells() - else: - return self.state_manager.get_parent_cells() - - def _send_to_cells(self, target_cells): - """Send a message to multiple cells.""" - for cell in target_cells: - cell.send_message(self) - - def _send_json_responses(self, json_responses): - """Responses to broadcast messages always need to go to the - neighbor cell from which we received this message. That - cell aggregates the responses and makes sure to forward them - to the correct source. - """ - return super(_BroadcastMessage, self)._send_json_responses( - json_responses, neighbor_only=True, fanout=True) - - def process(self): - """Process a broadcast message. This is called for all cells - that touch this message. - - The message is sent to all cells in the certain direction and - the creator of this message has the option of whether or not - to process it locally as well. - - If responses from all cells are required, each hop creates an - eventlet queue and waits for responses from its immediate - neighbor cells. All responses are then aggregated into a - single list and are returned to the neighbor cell until the - source is reached. - - When the source is reached, a list of Response instances are - returned to the caller. - - All exceptions for processing the message across the whole - routing path are caught and encoded within the Response and - returned to the caller. It is possible to get a mix of - successful responses and failure responses. The caller is - responsible for dealing with this. - """ - try: - next_hops = self._get_next_hops() - except Exception: - exc_info = sys.exc_info() - LOG.exception("Error locating next hops for message") - return self._send_response_from_exception(exc_info) - - # Short circuit if we don't need to respond - if not self.need_response: - if self.run_locally: - self._process_locally() - self._send_to_cells(next_hops) - return - - # We'll need to aggregate all of the responses (from ourself - # and our sibling cells) into 1 response - try: - self._setup_response_queue() - self._send_to_cells(next_hops) - except Exception: - # Error just trying to send to cells. Send a single response - # with the failure. - exc_info = sys.exc_info() - LOG.exception("Error sending message to next hops.") - self._cleanup_response_queue() - return self._send_response_from_exception(exc_info) - - if self.run_locally: - # Run locally and store the Response. - local_response = self._process_locally() - else: - local_response = None - - try: - remote_responses = self._wait_for_json_responses( - num_responses=len(next_hops)) - except Exception: - # Error waiting for responses, most likely a timeout. - # Send a single response back with the failure. - exc_info = sys.exc_info() - LOG.exception("Error waiting for responses from neighbor cells") - return self._send_response_from_exception(exc_info) - - if local_response: - remote_responses.append(local_response.to_json()) - return self._send_json_responses(remote_responses) - - -class _ResponseMessage(_TargetedMessage): - """A response message is really just a special targeted message, - saying to call 'parse_responses' when we reach the source of a 'call'. - - The 'fanout' attribute on this message may be true if we're responding - to a broadcast or if we're about to respond to the source of an - original target message. Because multiple nova-cells services may - be running within a cell, we need to make sure the response gets - back to the correct one, so we have to fanout. - """ - message_type = 'response' - - def __init__(self, msg_runner, ctxt, method_name, method_kwargs, - direction, target_cell, response_uuid, **kwargs): - super(_ResponseMessage, self).__init__(msg_runner, ctxt, - method_name, method_kwargs, direction, target_cell, **kwargs) - self.response_uuid = response_uuid - self.base_attrs_to_json.append('response_uuid') - - def process(self): - """Process a response. If the target is the local cell, process - the response here. Otherwise, forward it to where it needs to - go. - """ - next_hop = self._get_next_hop() - if next_hop.is_me: - self._process_locally() - return - if self.fanout is False: - # Really there's 1 more hop on each of these below, but - # it doesn't matter for this logic. - target_hops = self.target_cell.count(_PATH_CELL_SEP) - current_hops = self.routing_path.count(_PATH_CELL_SEP) - if current_hops + 1 == target_hops: - # Next hop is the target.. so we must fanout. See - # DocString above. - self.fanout = True - next_hop.send_message(self) - - -# -# Methods that may be called when processing messages after reaching -# a target cell. -# - - -class _BaseMessageMethods(base.Base): - """Base class for defining methods by message types.""" - def __init__(self, msg_runner): - super(_BaseMessageMethods, self).__init__() - self.msg_runner = msg_runner - self.state_manager = msg_runner.state_manager - self.compute_api = compute.API() - self.compute_rpcapi = compute_rpcapi.ComputeAPI() - self.consoleauth_rpcapi = consoleauth_rpcapi.ConsoleAuthAPI() - self.host_api = compute.HostAPI() - - def task_log_get_all(self, message, task_name, period_beginning, - period_ending, host, state): - """Get task logs from the DB. The message could have - directly targeted this cell, or it could have been a broadcast - message. - - If 'host' is not None, filter by host. - If 'state' is not None, filter by state. - """ - task_logs = self.db.task_log_get_all(message.ctxt, task_name, - period_beginning, - period_ending, - host=host, - state=state) - return jsonutils.to_primitive(task_logs) - - -class _ResponseMessageMethods(_BaseMessageMethods): - """Methods that are called from a ResponseMessage. There's only - 1 method (parse_responses) and it is called when the message reaches - the source of a 'call'. All we do is stuff the response into the - eventlet queue to signal the caller that's waiting. - """ - def parse_responses(self, message, orig_message, responses): - self.msg_runner._put_response(message.response_uuid, - responses) - - -class _TargetedMessageMethods(_BaseMessageMethods): - """These are the methods that can be called when routing a message - to a specific cell. - """ - def __init__(self, *args, **kwargs): - super(_TargetedMessageMethods, self).__init__(*args, **kwargs) - - def build_instances(self, message, build_inst_kwargs): - """Parent cell told us to schedule new instance creation.""" - self.msg_runner.scheduler.build_instances(message, build_inst_kwargs) - - def run_compute_api_method(self, message, method_info): - """Run a method in the compute api class.""" - method = method_info['method'] - fn = getattr(self.compute_api, method, None) - if not fn: - detail = _("Unknown method '%(method)s' in compute API") - raise exception.CellServiceAPIMethodNotFound( - detail=detail % {'method': method}) - args = list(method_info['method_args']) - # 1st arg is instance_uuid that we need to turn into the - # instance object. - instance_uuid = args[0] - # NOTE: compute/api.py loads these when retrieving an instance for an - # API request, so there's a good chance that this is what was loaded. - expected_attrs = ['metadata', 'system_metadata', 'security_groups', - 'info_cache'] - - instance = objects.Instance.get_by_uuid(message.ctxt, - instance_uuid, expected_attrs=expected_attrs) - args[0] = instance - - return fn(message.ctxt, *args, **method_info['method_kwargs']) - - def update_capabilities(self, message, cell_name, capabilities): - """A child cell told us about their capabilities.""" - LOG.debug("Received capabilities from child cell " - "%(cell_name)s: %(capabilities)s", - {'cell_name': cell_name, 'capabilities': capabilities}) - self.state_manager.update_cell_capabilities(cell_name, - capabilities) - # Go ahead and update our parents now that a child updated us - self.msg_runner.tell_parents_our_capabilities(message.ctxt) - - def update_capacities(self, message, cell_name, capacities): - """A child cell told us about their capacity.""" - LOG.debug("Received capacities from child cell " - "%(cell_name)s: %(capacities)s", - {'cell_name': cell_name, 'capacities': capacities}) - self.state_manager.update_cell_capacities(cell_name, - capacities) - # Go ahead and update our parents now that a child updated us - self.msg_runner.tell_parents_our_capacities(message.ctxt) - - def announce_capabilities(self, message): - """A parent cell has told us to send our capabilities, so let's - do so. - """ - self.msg_runner.tell_parents_our_capabilities(message.ctxt) - - def announce_capacities(self, message): - """A parent cell has told us to send our capacity, so let's - do so. - """ - 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.""" - return objects.Service.get_by_compute_host(message.ctxt, host_name) - - def service_update(self, message, host_name, binary, params_to_update): - """Used to enable/disable a service. For compute services, setting to - disabled stops new builds arriving on that host. - - :param host_name: the name of the host machine that the service is - running - :param binary: The name of the executable that the service runs as - :param params_to_update: eg. {'disabled': True} - """ - return self.host_api._service_update(message.ctxt, host_name, binary, - params_to_update) - - def service_delete(self, message, service_id): - """Deletes the specified service.""" - self.host_api._service_delete(message.ctxt, service_id) - - 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. - objects.Service.get_by_compute_host(message.ctxt, host_name) - - topic, _sep, server = topic.partition('.') - - cctxt = rpc.get_client(messaging.Target(topic=topic, - server=server or None)) - method = rpc_message['method'] - kwargs = rpc_message['args'] - - if message.need_response: - cctxt = cctxt.prepare(timeout=timeout) - return cctxt.call(message.ctxt, method, **kwargs) - else: - cctxt.cast(message.ctxt, method, **kwargs) - - def compute_node_get(self, message, compute_id): - """Get compute node by ID or UUID.""" - if uuidutils.is_uuid_like(compute_id): - return objects.ComputeNode.get_by_uuid(message.ctxt, compute_id) - return objects.ComputeNode.get_by_id(message.ctxt, compute_id) - - def actions_get(self, message, instance_uuid): - actions = self.db.actions_get(message.ctxt, instance_uuid) - return jsonutils.to_primitive(actions) - - def action_get_by_request_id(self, message, instance_uuid, request_id): - action = self.db.action_get_by_request_id(message.ctxt, instance_uuid, - request_id) - return jsonutils.to_primitive(action) - - def action_events_get(self, message, action_id): - action_events = self.db.action_events_get(message.ctxt, action_id) - return jsonutils.to_primitive(action_events) - - def validate_console_port(self, message, instance_uuid, console_port, - console_type): - """Validate console port with child cell compute node.""" - # 1st arg is instance_uuid that we need to turn into the - # instance object. - instance = objects.Instance.get_by_uuid(message.ctxt, - instance_uuid) - return self.compute_rpcapi.validate_console_port(message.ctxt, - instance, console_port, console_type) - - def get_migrations(self, message, filters): - return self.compute_api.get_migrations(message.ctxt, filters) - - def _call_compute_api_with_obj(self, ctxt, instance, method, *args, - **kwargs): - try: - # NOTE(comstud): We need to refresh the instance from this - # cell's view in the DB. - instance.refresh() - except exception.InstanceInfoCacheNotFound: - if method not in ('delete', 'force_delete'): - raise - - fn = getattr(self.compute_api, method, None) - return fn(ctxt, instance, *args, **kwargs) - - def start_instance(self, message, instance): - """Start an instance via compute_api.start().""" - self._call_compute_api_with_obj(message.ctxt, instance, 'start') - - def stop_instance(self, message, instance, clean_shutdown=True): - """Stop an instance via compute_api.stop().""" - do_cast = not message.need_response - return self._call_compute_api_with_obj(message.ctxt, instance, - 'stop', do_cast=do_cast, - clean_shutdown=clean_shutdown) - - def reboot_instance(self, message, instance, reboot_type): - """Reboot an instance via compute_api.reboot().""" - self._call_compute_api_with_obj(message.ctxt, instance, 'reboot', - reboot_type=reboot_type) - - def suspend_instance(self, message, instance): - """Suspend an instance via compute_api.suspend().""" - self._call_compute_api_with_obj(message.ctxt, instance, 'suspend') - - def resume_instance(self, message, instance): - """Resume an instance via compute_api.suspend().""" - self._call_compute_api_with_obj(message.ctxt, instance, 'resume') - - def get_host_uptime(self, message, host_name): - return self.host_api.get_host_uptime(message.ctxt, host_name) - - def terminate_instance(self, message, instance, delete_type='delete'): - self._call_compute_api_with_obj(message.ctxt, instance, delete_type) - - def soft_delete_instance(self, message, instance): - self._call_compute_api_with_obj(message.ctxt, instance, 'soft_delete') - - def pause_instance(self, message, instance): - """Pause an instance via compute_api.pause().""" - self._call_compute_api_with_obj(message.ctxt, instance, 'pause') - - def unpause_instance(self, message, instance): - """Unpause an instance via compute_api.pause().""" - self._call_compute_api_with_obj(message.ctxt, instance, 'unpause') - - def resize_instance(self, message, instance, flavor, - extra_instance_updates, clean_shutdown=True): - """Resize an instance via compute_api.resize().""" - self._call_compute_api_with_obj(message.ctxt, instance, 'resize', - flavor_id=flavor['flavorid'], - clean_shutdown=clean_shutdown, - **extra_instance_updates) - - def live_migrate_instance(self, message, instance, block_migration, - disk_over_commit, host_name): - """Live migrate an instance via compute_api.live_migrate().""" - self._call_compute_api_with_obj(message.ctxt, instance, - 'live_migrate', block_migration, - disk_over_commit, host_name) - - def revert_resize(self, message, instance): - """Revert a resize for an instance in its cell.""" - self._call_compute_api_with_obj(message.ctxt, instance, - 'revert_resize') - - def confirm_resize(self, message, instance): - """Confirm a resize for an instance in its cell.""" - self._call_compute_api_with_obj(message.ctxt, instance, - 'confirm_resize') - - def reset_network(self, message, instance): - """Reset networking for an instance in its cell.""" - self._call_compute_api_with_obj(message.ctxt, instance, - 'reset_network') - - def inject_network_info(self, message, instance): - """Inject networking for an instance in its cell.""" - self._call_compute_api_with_obj(message.ctxt, instance, - 'inject_network_info') - - def snapshot_instance(self, message, instance, image_id): - """Snapshot an instance in its cell.""" - instance.refresh() - instance.task_state = task_states.IMAGE_SNAPSHOT_PENDING - instance.save(expected_task_state=[None]) - - objects.InstanceAction.action_start( - message.ctxt, instance.uuid, instance_actions.CREATE_IMAGE, - want_result=False) - - self.compute_rpcapi.snapshot_instance(message.ctxt, - instance, - image_id) - - def backup_instance(self, message, instance, image_id, - backup_type, rotation): - """Backup an instance in its cell.""" - instance.refresh() - instance.task_state = task_states.IMAGE_BACKUP - instance.save(expected_task_state=[None]) - - objects.InstanceAction.action_start( - message.ctxt, instance.uuid, instance_actions.BACKUP, - want_result=False) - - self.compute_rpcapi.backup_instance(message.ctxt, - instance, - image_id, - backup_type, - rotation) - - def rebuild_instance(self, message, instance, image_href, admin_password, - files_to_inject, preserve_ephemeral, kwargs): - kwargs['preserve_ephemeral'] = preserve_ephemeral - self._call_compute_api_with_obj(message.ctxt, instance, 'rebuild', - image_href, admin_password, - files_to_inject, **kwargs) - - def set_admin_password(self, message, instance, new_pass): - self._call_compute_api_with_obj(message.ctxt, instance, - 'set_admin_password', new_pass) - - -class _BroadcastMessageMethods(_BaseMessageMethods): - """These are the methods that can be called as a part of a broadcast - message. - """ - def _at_the_top(self): - """Are we the API level?""" - return not self.state_manager.get_parent_cells() - - def _get_expected_vm_state(self, instance): - """To attempt to address out-of-order messages, do some sanity - checking on the VM states. Add some requirements for - vm_state to the instance.save() call if necessary. - """ - expected_vm_state_map = { - # For updates containing 'vm_state' of 'building', - # only allow them to occur if the DB already says - # 'building' or if the vm_state is None. None - # really shouldn't be possible as instances always - # start out in 'building' anyway.. but just in case. - vm_states.BUILDING: [vm_states.BUILDING, None]} - - if instance.obj_attr_is_set('vm_state'): - return expected_vm_state_map.get(instance.vm_state) - - def _get_expected_task_state(self, instance): - """To attempt to address out-of-order messages, do some sanity - checking on the task states. Add some requirements for - task_state to the instance.save() call if necessary. - """ - expected_task_state_map = { - # Always allow updates when task_state doesn't change, - # but also make sure we don't set resize/rebuild task - # states for old messages when we've potentially already - # processed the ACTIVE/None messages. Ie, these checks - # will prevent stomping on any ACTIVE/None messages - # we already processed. - task_states.REBUILD_BLOCK_DEVICE_MAPPING: - [task_states.REBUILD_BLOCK_DEVICE_MAPPING, - task_states.REBUILDING], - task_states.REBUILD_SPAWNING: - [task_states.REBUILD_SPAWNING, - task_states.REBUILD_BLOCK_DEVICE_MAPPING, - task_states.REBUILDING], - task_states.RESIZE_MIGRATING: - [task_states.RESIZE_MIGRATING, - task_states.RESIZE_PREP], - task_states.RESIZE_MIGRATED: - [task_states.RESIZE_MIGRATED, - task_states.RESIZE_MIGRATING, - task_states.RESIZE_PREP], - task_states.RESIZE_FINISH: - [task_states.RESIZE_FINISH, - task_states.RESIZE_MIGRATED, - task_states.RESIZE_MIGRATING, - task_states.RESIZE_PREP]} - - if instance.obj_attr_is_set('task_state'): - return expected_task_state_map.get(instance.task_state) - - def instance_delete_everywhere(self, message, instance, delete_type, - **kwargs): - """Call compute API delete() or soft_delete() in every cell. - This is used when the API cell doesn't know what cell an instance - belongs to but the instance was requested to be deleted or - soft-deleted. So, we'll run it everywhere. - """ - LOG.debug("Got broadcast to %(delete_type)s delete instance", - {'delete_type': delete_type}, instance=instance) - if delete_type == 'soft': - self.compute_api.soft_delete(message.ctxt, instance) - else: - self.compute_api.delete(message.ctxt, instance) - - def _sync_instance(self, ctxt, instance): - pass - - def sync_instances(self, message, project_id, updated_since, deleted, - **kwargs): - projid_str = project_id is None and "" or project_id - since_str = updated_since is None and "" or updated_since - LOG.info("Forcing a sync of instances, project_id=" - "%(projid_str)s, updated_since=%(since_str)s", - {'projid_str': projid_str, 'since_str': since_str}) - if updated_since is not None: - updated_since = timeutils.parse_isotime(updated_since) - instances = cells_utils.get_instances_to_sync(message.ctxt, - updated_since=updated_since, project_id=project_id, - deleted=deleted) - 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 = objects.ServiceList.get_all(message.ctxt, disabled=disabled) - ret_services = [] - for service in services: - for key, val in filters.items(): - if getattr(service, key) != val: - break - else: - ret_services.append(service) - return ret_services - - def compute_node_get_all(self, message, hypervisor_match): - """Return compute nodes in this cell.""" - if hypervisor_match is not None: - return objects.ComputeNodeList.get_by_hypervisor(message.ctxt, - hypervisor_match) - return objects.ComputeNodeList.get_all(message.ctxt) - - def compute_node_stats(self, message): - """Return compute node stats from this cell.""" - return self.db.compute_node_statistics(message.ctxt) - - def consoleauth_delete_tokens(self, message, instance_uuid): - """Delete consoleauth tokens for an instance in API cells.""" - if not self._at_the_top(): - return - self.consoleauth_rpcapi.delete_tokens_for_instance(message.ctxt, - instance_uuid) - - def get_migrations(self, message, filters): - return self.compute_api.get_migrations(message.ctxt, filters) - - -_CELL_MESSAGE_TYPE_TO_MESSAGE_CLS = {'targeted': _TargetedMessage, - 'broadcast': _BroadcastMessage, - 'response': _ResponseMessage} -_CELL_MESSAGE_TYPE_TO_METHODS_CLS = {'targeted': _TargetedMessageMethods, - 'broadcast': _BroadcastMessageMethods, - 'response': _ResponseMessageMethods} - - -# -# Below are the public interfaces into this module. -# - - -class MessageRunner(object): - """This class is the main interface into creating messages and - processing them. - - Public methods in this class are typically called by the CellsManager - to create a new message and process it with the exception of - 'message_from_json' which should be used by CellsDrivers to convert - a JSONified message it has received back into the appropriate Message - class. - - Private methods are used internally when we need to keep some - 'global' state. For instance, eventlet queues used for responses are - held in this class. Also, when a Message is process()ed above and - it's determined we should take action locally, - _process_message_locally() will be called. - - When needing to add a new method to call in a Cell2Cell message, - define the new method below and also add it to the appropriate - MessageMethods class where the real work will be done. - """ - - def __init__(self, state_manager): - self.state_manager = state_manager - cells_scheduler_cls = importutils.import_class( - CONF.cells.scheduler) - self.scheduler = cells_scheduler_cls(self) - self.response_queues = {} - self.methods_by_type = {} - self.our_name = CONF.cells.name - for msg_type, cls in _CELL_MESSAGE_TYPE_TO_METHODS_CLS.items(): - self.methods_by_type[msg_type] = cls(self) - self.serializer = objects_base.NovaObjectSerializer() - - def _process_message_locally(self, message): - """Message processing will call this when its determined that - the message should be processed within this cell. Find the - method to call based on the message type, and call it. The - caller is responsible for catching exceptions and returning - results to cells, if needed. - """ - methods = self.methods_by_type[message.message_type] - fn = getattr(methods, message.method_name) - return fn(message, **message.method_kwargs) - - def _put_response(self, response_uuid, response): - """Put a response into a response queue. This is called when - a _ResponseMessage is processed in the cell that initiated a - 'call' to another cell. - """ - resp_queue = self.response_queues.get(response_uuid) - if not resp_queue: - # Response queue is gone. We must have restarted or we - # received a response after our timeout period. - return - resp_queue.put(response) - - def _setup_response_queue(self, message): - """Set up an eventlet queue to use to wait for replies. - - Replies come back from the target cell as a _ResponseMessage - being sent back to the source. - """ - resp_queue = queue.Queue() - self.response_queues[message.uuid] = resp_queue - return resp_queue - - def _cleanup_response_queue(self, message): - """Stop tracking the response queue either because we're - done receiving responses, or we've timed out. - """ - try: - del self.response_queues[message.uuid] - except KeyError: - # Ignore if queue is gone already somehow. - pass - - def _create_response_message(self, ctxt, direction, target_cell, - response_uuid, response_kwargs, **kwargs): - """Create a ResponseMessage. This is used internally within - the nova.cells.messaging module. - """ - return _ResponseMessage(self, ctxt, 'parse_responses', - response_kwargs, direction, target_cell, - response_uuid, **kwargs) - - def _get_migrations_for_cell(self, ctxt, cell_name, filters): - method_kwargs = dict(filters=filters) - message = _TargetedMessage(self, ctxt, 'get_migrations', - method_kwargs, 'down', cell_name, - need_response=True) - - response = message.process() - if response.failure and isinstance(response.value[1], - exception.CellRoutingInconsistency): - return [] - - return [response] - - def message_from_json(self, json_message): - """Turns a message in JSON format into an appropriate Message - instance. This is called when cells receive a message from - another cell. - """ - message_dict = jsonutils.loads(json_message) - # Need to convert context back. - ctxt = message_dict['ctxt'] - message_dict['ctxt'] = context.RequestContext.from_dict(ctxt) - # NOTE(comstud): We also need to re-serialize any objects that - # exist in 'method_kwargs'. - method_kwargs = message_dict['method_kwargs'] - for k, v in method_kwargs.items(): - method_kwargs[k] = self.serializer.deserialize_entity( - message_dict['ctxt'], v) - message_type = message_dict.pop('message_type') - message_cls = _CELL_MESSAGE_TYPE_TO_MESSAGE_CLS[message_type] - return message_cls(self, **message_dict) - - def ask_children_for_capabilities(self, ctxt): - """Tell child cells to send us capabilities. This is typically - called on startup of the nova-cells service. - """ - child_cells = self.state_manager.get_child_cells() - for child_cell in child_cells: - message = _TargetedMessage(self, ctxt, - 'announce_capabilities', - dict(), 'down', child_cell) - message.process() - - def ask_children_for_capacities(self, ctxt): - """Tell child cells to send us capacities. This is typically - called on startup of the nova-cells service. - """ - child_cells = self.state_manager.get_child_cells() - for child_cell in child_cells: - message = _TargetedMessage(self, ctxt, 'announce_capacities', - dict(), 'down', child_cell) - message.process() - - def tell_parents_our_capabilities(self, ctxt): - """Send our capabilities to parent cells.""" - parent_cells = self.state_manager.get_parent_cells() - if not parent_cells: - return - my_cell_info = self.state_manager.get_my_state() - capabs = self.state_manager.get_our_capabilities() - parent_cell_names = ','.join(x.name for x in parent_cells) - LOG.debug("Updating parents [%(parent_cell_names)s] with " - "our capabilities: %(capabs)s", - {'parent_cell_names': parent_cell_names, - 'capabs': capabs}) - # We have to turn the sets into lists so they can potentially - # be json encoded when the raw message is sent. - for key, values in capabs.items(): - capabs[key] = list(values) - method_kwargs = {'cell_name': my_cell_info.name, - 'capabilities': capabs} - for cell in parent_cells: - message = _TargetedMessage(self, ctxt, 'update_capabilities', - method_kwargs, 'up', cell, fanout=True) - message.process() - - def tell_parents_our_capacities(self, ctxt): - """Send our capacities to parent cells.""" - parent_cells = self.state_manager.get_parent_cells() - if not parent_cells: - return - my_cell_info = self.state_manager.get_my_state() - capacities = self.state_manager.get_our_capacities() - parent_cell_names = ','.join(x.name for x in parent_cells) - LOG.debug("Updating parents [%(parent_cell_names)s] with " - "our capacities: %(capacities)s", - {'parent_cell_names': parent_cell_names, - 'capacities': capacities}) - method_kwargs = {'cell_name': my_cell_info.name, - 'capacities': capacities} - for cell in parent_cells: - message = _TargetedMessage(self, ctxt, 'update_capacities', - method_kwargs, 'up', cell, fanout=True) - message.process() - - def build_instances(self, ctxt, target_cell, build_inst_kwargs): - """Called by the cell scheduler to tell a child cell to build - instance(s). - """ - method_kwargs = dict(build_inst_kwargs=build_inst_kwargs) - message = _TargetedMessage(self, ctxt, 'build_instances', - method_kwargs, 'down', target_cell) - message.process() - - def run_compute_api_method(self, ctxt, cell_name, method_info, call): - """Call a compute API method in a specific cell.""" - message = _TargetedMessage(self, ctxt, 'run_compute_api_method', - dict(method_info=method_info), 'down', - cell_name, need_response=call) - return message.process() - - def instance_delete_everywhere(self, ctxt, instance, delete_type): - """This is used by API cell when it didn't know what cell - an instance was in, but the instance was requested to be - deleted or soft_deleted. So, we'll broadcast this everywhere. - """ - method_kwargs = dict(instance=instance, delete_type=delete_type) - message = _BroadcastMessage(self, ctxt, - 'instance_delete_everywhere', - method_kwargs, 'down', - run_locally=False) - message.process() - - def sync_instances(self, ctxt, project_id, updated_since, deleted): - """Force a sync of all instances, potentially by project_id, - and potentially since a certain date/time. - """ - method_kwargs = dict(project_id=project_id, - updated_since=updated_since, - deleted=deleted) - message = _BroadcastMessage(self, ctxt, 'sync_instances', - method_kwargs, 'down', - 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 get_host_uptime(self, ctxt, cell_name, host_name): - method_kwargs = dict(host_name=host_name) - message = _TargetedMessage(self, ctxt, - 'get_host_uptime', - method_kwargs, 'down', cell_name, - need_response=True) - return message.process() - - def service_update(self, ctxt, cell_name, host_name, binary, - params_to_update): - """Used to enable/disable a service. For compute services, setting to - disabled stops new builds arriving on that host. - - :param host_name: the name of the host machine that the service is - running - :param binary: The name of the executable that the service runs as - :param params_to_update: eg. {'disabled': True} - :returns: the update service object - """ - method_kwargs = dict(host_name=host_name, binary=binary, - params_to_update=params_to_update) - message = _TargetedMessage(self, ctxt, - 'service_update', - method_kwargs, 'down', cell_name, - need_response=True) - return message.process() - - def service_delete(self, ctxt, cell_name, service_id): - """Deletes the specified service.""" - method_kwargs = {'service_id': service_id} - message = _TargetedMessage(self, ctxt, - 'service_delete', - method_kwargs, 'down', cell_name, - need_response=True) - 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() - - def task_log_get_all(self, ctxt, cell_name, task_name, - period_beginning, period_ending, - host=None, state=None): - """Get task logs from the DB from all cells or a particular - cell. - - If 'cell_name' is None or '', get responses from all cells. - If 'host' is not None, filter by host. - If 'state' is not None, filter by state. - - Return a list of Response objects. - """ - method_kwargs = dict(task_name=task_name, - period_beginning=period_beginning, - period_ending=period_ending, - host=host, state=state) - if cell_name: - message = _TargetedMessage(self, ctxt, 'task_log_get_all', - method_kwargs, 'down', - cell_name, need_response=True) - # Caller should get a list of Responses. - return [message.process()] - message = _BroadcastMessage(self, ctxt, 'task_log_get_all', - method_kwargs, 'down', - run_locally=True, need_response=True) - return message.process() - - def compute_node_get_all(self, ctxt, hypervisor_match=None): - """Return list of compute nodes in all child cells.""" - method_kwargs = dict(hypervisor_match=hypervisor_match) - message = _BroadcastMessage(self, ctxt, 'compute_node_get_all', - method_kwargs, 'down', - run_locally=True, need_response=True) - return message.process() - - def compute_node_stats(self, ctxt): - """Return compute node stats from all child cells.""" - method_kwargs = dict() - message = _BroadcastMessage(self, ctxt, 'compute_node_stats', - method_kwargs, 'down', - run_locally=True, need_response=True) - return message.process() - - def compute_node_get(self, ctxt, cell_name, compute_id): - """Return compute node entry from a specific cell by ID or UUID.""" - method_kwargs = dict(compute_id=compute_id) - message = _TargetedMessage(self, ctxt, 'compute_node_get', - method_kwargs, 'down', - cell_name, need_response=True) - return message.process() - - def actions_get(self, ctxt, cell_name, instance_uuid): - method_kwargs = dict(instance_uuid=instance_uuid) - message = _TargetedMessage(self, ctxt, 'actions_get', - method_kwargs, 'down', - cell_name, need_response=True) - return message.process() - - def action_get_by_request_id(self, ctxt, cell_name, instance_uuid, - request_id): - method_kwargs = dict(instance_uuid=instance_uuid, - request_id=request_id) - message = _TargetedMessage(self, ctxt, 'action_get_by_request_id', - method_kwargs, 'down', - cell_name, need_response=True) - return message.process() - - def action_events_get(self, ctxt, cell_name, action_id): - method_kwargs = dict(action_id=action_id) - message = _TargetedMessage(self, ctxt, 'action_events_get', - method_kwargs, 'down', - cell_name, need_response=True) - return message.process() - - def consoleauth_delete_tokens(self, ctxt, instance_uuid): - """Delete consoleauth tokens for an instance in API cells.""" - message = _BroadcastMessage(self, ctxt, 'consoleauth_delete_tokens', - dict(instance_uuid=instance_uuid), - 'up', run_locally=False) - message.process() - - def validate_console_port(self, ctxt, cell_name, instance_uuid, - console_port, console_type): - """Validate console port with child cell compute node.""" - method_kwargs = {'instance_uuid': instance_uuid, - 'console_port': console_port, - 'console_type': console_type} - message = _TargetedMessage(self, ctxt, 'validate_console_port', - method_kwargs, 'down', - cell_name, need_response=True) - return message.process() - - def get_migrations(self, ctxt, cell_name, run_locally, filters): - """Fetch all migrations applying the filters for a given cell or all - cells. - """ - method_kwargs = dict(filters=filters) - if cell_name: - return self._get_migrations_for_cell(ctxt, cell_name, filters) - - message = _BroadcastMessage(self, ctxt, 'get_migrations', - method_kwargs, 'down', - run_locally=run_locally, - need_response=True) - return message.process() - - def _instance_action(self, ctxt, instance, method, extra_kwargs=None, - need_response=False): - """Call instance_ in correct cell for instance.""" - cell_name = instance.cell_name - if not cell_name: - LOG.warning("No cell_name for %(method)s() from API", - dict(method=method), instance=instance) - return - method_kwargs = {'instance': instance} - if extra_kwargs: - method_kwargs.update(extra_kwargs) - message = _TargetedMessage(self, ctxt, method, method_kwargs, - 'down', cell_name, - need_response=need_response) - return message.process() - - def start_instance(self, ctxt, instance): - """Start an instance in its cell.""" - self._instance_action(ctxt, instance, 'start_instance') - - def stop_instance(self, ctxt, instance, do_cast=True, clean_shutdown=True): - """Stop an instance in its cell.""" - extra_kwargs = dict(clean_shutdown=clean_shutdown) - if do_cast: - self._instance_action(ctxt, instance, 'stop_instance', - extra_kwargs=extra_kwargs) - else: - return self._instance_action(ctxt, instance, 'stop_instance', - extra_kwargs=extra_kwargs, - need_response=True) - - def reboot_instance(self, ctxt, instance, reboot_type): - """Reboot an instance in its cell.""" - extra_kwargs = dict(reboot_type=reboot_type) - self._instance_action(ctxt, instance, 'reboot_instance', - extra_kwargs=extra_kwargs) - - def suspend_instance(self, ctxt, instance): - """Suspend an instance in its cell.""" - self._instance_action(ctxt, instance, 'suspend_instance') - - def resume_instance(self, ctxt, instance): - """Resume an instance in its cell.""" - self._instance_action(ctxt, instance, 'resume_instance') - - def terminate_instance(self, ctxt, instance, delete_type='delete'): - extra_kwargs = dict(delete_type=delete_type) - self._instance_action(ctxt, instance, 'terminate_instance', - extra_kwargs=extra_kwargs) - - def soft_delete_instance(self, ctxt, instance): - self._instance_action(ctxt, instance, 'soft_delete_instance') - - def pause_instance(self, ctxt, instance): - """Pause an instance in its cell.""" - self._instance_action(ctxt, instance, 'pause_instance') - - def unpause_instance(self, ctxt, instance): - """Unpause an instance in its cell.""" - self._instance_action(ctxt, instance, 'unpause_instance') - - def resize_instance(self, ctxt, instance, flavor, - extra_instance_updates, - clean_shutdown=True): - """Resize an instance in its cell.""" - extra_kwargs = dict(flavor=flavor, - extra_instance_updates=extra_instance_updates, - clean_shutdown=clean_shutdown) - self._instance_action(ctxt, instance, 'resize_instance', - extra_kwargs=extra_kwargs) - - def live_migrate_instance(self, ctxt, instance, block_migration, - disk_over_commit, host_name): - """Live migrate an instance in its cell.""" - extra_kwargs = dict(block_migration=block_migration, - disk_over_commit=disk_over_commit, - host_name=host_name) - self._instance_action(ctxt, instance, 'live_migrate_instance', - extra_kwargs=extra_kwargs) - - def revert_resize(self, ctxt, instance): - """Revert a resize for an instance in its cell.""" - self._instance_action(ctxt, instance, 'revert_resize') - - def confirm_resize(self, ctxt, instance): - """Confirm a resize for an instance in its cell.""" - self._instance_action(ctxt, instance, 'confirm_resize') - - def reset_network(self, ctxt, instance): - """Reset networking for an instance in its cell.""" - self._instance_action(ctxt, instance, 'reset_network') - - def inject_network_info(self, ctxt, instance): - """Inject networking for an instance in its cell.""" - self._instance_action(ctxt, instance, 'inject_network_info') - - def snapshot_instance(self, ctxt, instance, image_id): - """Snapshot an instance in its cell.""" - extra_kwargs = dict(image_id=image_id) - self._instance_action(ctxt, instance, 'snapshot_instance', - extra_kwargs=extra_kwargs) - - def backup_instance(self, ctxt, instance, image_id, backup_type, - rotation): - """Backup an instance in its cell.""" - extra_kwargs = dict(image_id=image_id, backup_type=backup_type, - rotation=rotation) - self._instance_action(ctxt, instance, 'backup_instance', - extra_kwargs=extra_kwargs) - - def rebuild_instance(self, ctxt, instance, image_href, admin_password, - files_to_inject, preserve_ephemeral, kwargs): - extra_kwargs = dict(image_href=image_href, - admin_password=admin_password, - files_to_inject=files_to_inject, - preserve_ephemeral=preserve_ephemeral, - kwargs=kwargs) - self._instance_action(ctxt, instance, 'rebuild_instance', - extra_kwargs=extra_kwargs) - - def set_admin_password(self, ctxt, instance, new_pass): - self._instance_action(ctxt, instance, 'set_admin_password', - extra_kwargs={'new_pass': new_pass}) - - @staticmethod - def get_message_types(): - return _CELL_MESSAGE_TYPE_TO_MESSAGE_CLS.keys() - - -class Response(object): - """Holds a response from a cell. If there was a failure, 'failure' - will be True and 'response' will contain an encoded Exception. - """ - def __init__(self, ctxt, cell_name, value, failure): - self.failure = failure - self.cell_name = cell_name - self.value = value - self.ctxt = ctxt - self.serializer = objects_base.NovaObjectSerializer() - - def to_json(self): - resp_value = self.serializer.serialize_entity(self.ctxt, self.value) - if self.failure: - resp_value = serialize_remote_exception(resp_value, - log_failure=False) - _dict = {'cell_name': self.cell_name, - 'value': resp_value, - 'failure': self.failure} - return jsonutils.dumps(_dict) - - @classmethod - def from_json(cls, ctxt, json_message): - _dict = jsonutils.loads(json_message) - if _dict['failure']: - resp_value = deserialize_remote_exception(_dict['value'], - rpc.get_allowed_exmods()) - _dict['value'] = resp_value - response = cls(ctxt, **_dict) - response.value = response.serializer.deserialize_entity( - response.ctxt, response.value) - return response - - def value_or_raise(self): - if self.failure: - if isinstance(self.value, (tuple, list)): - six.reraise(*self.value) - else: - raise self.value - return self.value - - -_REMOTE_POSTFIX = '_Remote' - - -def serialize_remote_exception(failure_info, log_failure=True): - """Prepares exception data to be sent over rpc. - - Failure_info should be a sys.exc_info() tuple. - - """ - tb = traceback.format_exception(*failure_info) - failure = failure_info[1] - if log_failure: - LOG.error("Returning exception %s to caller", - six.text_type(failure)) - LOG.error(tb) - - kwargs = {} - if hasattr(failure, 'kwargs'): - kwargs = failure.kwargs - - # NOTE(matiu): With cells, it's possible to re-raise remote, remote - # exceptions. Lets turn it back into the original exception type. - cls_name = str(failure.__class__.__name__) - mod_name = str(failure.__class__.__module__) - if (cls_name.endswith(_REMOTE_POSTFIX) and - mod_name.endswith(_REMOTE_POSTFIX)): - cls_name = cls_name[:-len(_REMOTE_POSTFIX)] - mod_name = mod_name[:-len(_REMOTE_POSTFIX)] - - data = { - 'class': cls_name, - 'module': mod_name, - 'message': six.text_type(failure), - 'tb': tb, - 'args': failure.args, - 'kwargs': kwargs - } - - json_data = jsonutils.dumps(data) - - return json_data - - -def deserialize_remote_exception(data, allowed_remote_exmods): - failure = jsonutils.loads(str(data)) - - trace = failure.get('tb', []) - message = failure.get('message', "") + "\n" + "\n".join(trace) - name = failure.get('class') - module = failure.get('module') - - # NOTE(ameade): We DO NOT want to allow just any module to be imported, in - # order to prevent arbitrary code execution. - if module != 'exceptions' and module not in allowed_remote_exmods: - return messaging.RemoteError(name, failure.get('message'), trace) - - try: - mod = importutils.import_module(module) - klass = getattr(mod, name) - if not issubclass(klass, Exception): - raise TypeError("Can only deserialize Exceptions") - - failure = klass(*failure.get('args', []), **failure.get('kwargs', {})) - except (AttributeError, TypeError, ImportError): - return messaging.RemoteError(name, failure.get('message'), trace) - - ex_type = type(failure) - str_override = lambda self: message - new_ex_type = type(ex_type.__name__ + _REMOTE_POSTFIX, (ex_type,), - {'__str__': str_override, '__unicode__': str_override}) - new_ex_type.__module__ = '%s%s' % (module, _REMOTE_POSTFIX) - try: - # NOTE(ameade): Dynamically create a new exception type and swap it in - # as the new type for the exception. This only works on user defined - # Exceptions and not core python exceptions. This is important because - # we cannot necessarily change an exception message so we must override - # the __str__ method. - failure.__class__ = new_ex_type - except TypeError: - # NOTE(ameade): If a core exception then just add the traceback to the - # first exception argument. - failure.args = (message,) + failure.args[1:] - return failure diff --git a/nova/cells/opts.py b/nova/cells/opts.py deleted file mode 100644 index f9d122d5e4..0000000000 --- a/nova/cells/opts.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Global cells config options -""" -import nova.conf - -CONF = nova.conf.CONF - - -def get_cell_type(): - """Return the cell type, 'api', 'compute', or None (if cells is disabled). - """ - if not CONF.cells.enable: - return - return CONF.cells.cell_type diff --git a/nova/cells/rpc_driver.py b/nova/cells/rpc_driver.py deleted file mode 100644 index 3f45af6e32..0000000000 --- a/nova/cells/rpc_driver.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# Copyright 2013 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Cells RPC Communication Driver -""" -import oslo_messaging as messaging - -from nova.cells import driver -import nova.conf -from nova import rpc - - -CONF = nova.conf.CONF - - -class CellsRPCDriver(driver.BaseCellsDriver): - """Driver for cell<->cell communication via RPC. This is used to - setup the RPC consumers as well as to send a message to another cell. - - One instance of this class will be created for every neighbor cell - that we find in the DB and it will be associated with the cell in - its CellState. - - One instance is also created by the cells manager for setting up - the consumers. - """ - - def __init__(self, *args, **kwargs): - super(CellsRPCDriver, self).__init__(*args, **kwargs) - self.rpc_servers = [] - self.intercell_rpcapi = InterCellRPCAPI() - - def start_servers(self, msg_runner): - """Start RPC servers. - - Start up 2 separate servers for handling inter-cell - communication via RPC. Both handle the same types of - messages, but requests/replies are separated to solve - potential deadlocks. (If we used the same queue for both, - it's possible to exhaust the RPC thread pool while we wait - for replies.. such that we'd never consume a reply.) - """ - topic_base = CONF.cells.rpc_driver_queue_base - proxy_manager = InterCellRPCDispatcher(msg_runner) - for msg_type in msg_runner.get_message_types(): - target = messaging.Target(topic='%s.%s' % (topic_base, msg_type), - server=CONF.host) - # NOTE(comstud): We do not need to use the object serializer - # on this because object serialization is taken care for us in - # the nova.cells.messaging module. - server = rpc.get_server(target, endpoints=[proxy_manager]) - server.start() - self.rpc_servers.append(server) - - def stop_servers(self): - """Stop RPC servers. - - NOTE: Currently there's no hooks when stopping services - to have managers cleanup, so this is not currently called. - """ - for server in self.rpc_servers: - server.stop() - - def send_message_to_cell(self, cell_state, message): - """Use the IntercellRPCAPI to send a message to a cell.""" - self.intercell_rpcapi.send_message_to_cell(cell_state, message) - - -class InterCellRPCAPI(object): - """Client side of the Cell<->Cell RPC API. - - The CellsRPCDriver uses this to make calls to another cell. - - API version history: - 1.0 - Initial version. - - ... Grizzly supports message version 1.0. So, any changes to existing - methods in 2.x after that point should be done such that they can - handle the version_cap being set to 1.0. - """ - - VERSION_ALIASES = { - 'grizzly': '1.0', - } - - def __init__(self): - super(InterCellRPCAPI, self).__init__() - self.version_cap = ( - self.VERSION_ALIASES.get(CONF.upgrade_levels.intercell, - CONF.upgrade_levels.intercell)) - self.transports = {} - - def _get_client(self, next_hop, topic): - """Turn the DB information for a cell into a messaging.RPCClient.""" - transport = self._get_transport(next_hop) - target = messaging.Target(topic=topic, version='1.0') - serializer = rpc.RequestContextSerializer(None) - return messaging.RPCClient(transport, - target, - version_cap=self.version_cap, - serializer=serializer) - - def _get_transport(self, next_hop): - """NOTE(belliott) Each Transport object contains connection pool - state. Maintain references to them to avoid continual reconnects - to the message broker. - """ - transport_url = next_hop.db_info['transport_url'] - if transport_url not in self.transports: - transport = messaging.get_rpc_transport( - nova.conf.CONF, transport_url) - self.transports[transport_url] = transport - else: - transport = self.transports[transport_url] - - return transport - - def send_message_to_cell(self, cell_state, message): - """Send a message to another cell by JSON-ifying the message and - making an RPC cast to 'process_message'. If the message says to - fanout, do it. The topic that is used will be - 'CONF.rpc_driver_queue_base.'. - """ - topic_base = CONF.cells.rpc_driver_queue_base - topic = '%s.%s' % (topic_base, message.message_type) - cctxt = self._get_client(cell_state, topic) - if message.fanout: - cctxt = cctxt.prepare(fanout=message.fanout) - return cctxt.cast(message.ctxt, 'process_message', - message=message.to_json()) - - -class InterCellRPCDispatcher(object): - """RPC Dispatcher to handle messages received from other cells. - - All messages received here have come from a sibling cell. Depending - on the ultimate target and type of message, we may process the message - in this cell, relay the message to another sibling cell, or both. This - logic is defined by the message class in the nova.cells.messaging module. - """ - - target = messaging.Target(version='1.0') - - def __init__(self, msg_runner): - """Init the Intercell RPC Dispatcher.""" - self.msg_runner = msg_runner - - def process_message(self, _ctxt, message): - """We received a message from another cell. Use the MessageRunner - to turn this from JSON back into an instance of the correct - Message class. Then process it! - """ - message = self.msg_runner.message_from_json(message) - message.process() diff --git a/nova/cells/rpcapi.py b/nova/cells/rpcapi.py deleted file mode 100644 index 88daa4894c..0000000000 --- a/nova/cells/rpcapi.py +++ /dev/null @@ -1,568 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# Copyright 2013 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Client side of nova-cells RPC API (for talking to the nova-cells service -within a cell). - -This is different than communication between child and parent nova-cells -services. That communication is handled by the cells driver via the -messaging module. -""" - -from oslo_log import log as logging -import oslo_messaging as messaging -from oslo_serialization import jsonutils -from oslo_utils import uuidutils - -from nova import cells -import nova.conf -from nova import exception -from nova.objects import base as objects_base -from nova import profiler -from nova import rpc - -LOG = logging.getLogger(__name__) - -CONF = nova.conf.CONF - - -@profiler.trace_cls("rpc") -class CellsAPI(object): - '''Cells client-side RPC API - - API version history: - - * 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() - * 1.3 - Adds task_log_get_all() - * 1.4 - Adds compute_node_get(), compute_node_get_all(), and - compute_node_stats() - * 1.5 - Adds actions_get(), action_get_by_request_id(), and - action_events_get() - * 1.6 - Adds consoleauth_delete_tokens() and validate_console_port() - - ... Grizzly supports message version 1.6. So, any changes to existing - methods in 2.x after that point should be done such that they can - handle the version_cap being set to 1.6. - - * 1.7 - Adds service_update() - * 1.8 - Adds build_instances(), deprecates schedule_run_instance() - * 1.9 - Adds get_capacities() - * 1.10 - Adds bdm_update_or_create_at_top(), and bdm_destroy_at_top() - * 1.11 - Adds get_migrations() - * 1.12 - Adds instance_start() and instance_stop() - * 1.13 - Adds cell_create(), cell_update(), cell_delete(), and - cell_get() - * 1.14 - Adds reboot_instance() - * 1.15 - Adds suspend_instance() and resume_instance() - * 1.16 - Adds instance_update_from_api() - * 1.17 - Adds get_host_uptime() - * 1.18 - Adds terminate_instance() and soft_delete_instance() - * 1.19 - Adds pause_instance() and unpause_instance() - * 1.20 - Adds resize_instance() and live_migrate_instance() - * 1.21 - Adds revert_resize() and confirm_resize() - * 1.22 - Adds reset_network() - * 1.23 - Adds inject_network_info() - * 1.24 - Adds backup_instance() and snapshot_instance() - - ... Havana supports message version 1.24. So, any changes to existing - methods in 1.x after that point should be done such that they can - handle the version_cap being set to 1.24. - - * 1.25 - Adds rebuild_instance() - * 1.26 - Adds service_delete() - * 1.27 - Updates instance_delete_everywhere() for instance objects - - ... Icehouse supports message version 1.27. So, any changes to - existing methods in 1.x after that point should be done such that they - can handle the version_cap being set to 1.27. - - * 1.28 - Make bdm_update_or_create_at_top and use bdm objects - * 1.29 - Adds set_admin_password() - - ... Juno supports message version 1.29. So, any changes to - existing methods in 1.x after that point should be done such that they - can handle the version_cap being set to 1.29. - - * 1.30 - Make build_instances() use flavor object - * 1.31 - Add clean_shutdown to stop, resize, rescue, and shelve - * 1.32 - Send objects for instances in build_instances() - * 1.33 - Add clean_shutdown to resize_instance() - * 1.34 - build_instances uses BlockDeviceMapping objects, drops - legacy_bdm argument - - ... Kilo supports message version 1.34. So, any changes to - existing methods in 1.x after that point should be done such that they - can handle the version_cap being set to 1.34. - - * 1.35 - Make instance_update_at_top, instance_destroy_at_top - and instance_info_cache_update_at_top use instance objects - * 1.36 - Added 'delete_type' parameter to terminate_instance() - * 1.37 - Add get_keypair_at_top to fetch keypair from api cell - - ... Liberty, Mitaka, Newton, and Ocata support message version 1.37. - So, any changes to existing methods in 1.x after that point should be - done such that they can handle the version_cap being set to - 1.37. - - * 1.38 - Handle uuid parameter in compute_node_get() method. - ''' - - VERSION_ALIASES = { - 'grizzly': '1.6', - 'havana': '1.24', - 'icehouse': '1.27', - 'juno': '1.29', - 'kilo': '1.34', - 'liberty': '1.37', - 'mitaka': '1.37', - 'newton': '1.37', - 'ocata': '1.37', - } - - def __init__(self): - super(CellsAPI, self).__init__() - target = messaging.Target(topic=cells.TOPIC, version='1.0') - version_cap = self.VERSION_ALIASES.get(CONF.upgrade_levels.cells, - CONF.upgrade_levels.cells) - # NOTE(sbauza): Yes, this is ugly but cells_utils is calling cells.db - # which itself calls cells.rpcapi... You meant import cycling ? Gah. - from nova.cells import utils as cells_utils - serializer = cells_utils.ProxyObjectSerializer() - self.client = rpc.get_client(target, - version_cap=version_cap, - serializer=serializer) - - def cast_compute_api_method(self, ctxt, cell_name, method, - *args, **kwargs): - """Make a cast to a compute API method in a certain cell.""" - method_info = {'method': method, - 'method_args': args, - 'method_kwargs': kwargs} - self.client.cast(ctxt, 'run_compute_api_method', - cell_name=cell_name, - method_info=method_info, - call=False) - - def call_compute_api_method(self, ctxt, cell_name, method, - *args, **kwargs): - """Make a call to a compute API method in a certain cell.""" - method_info = {'method': method, - 'method_args': args, - 'method_kwargs': kwargs} - return self.client.call(ctxt, 'run_compute_api_method', - cell_name=cell_name, - method_info=method_info, - call=True) - - def build_instances(self, ctxt, **kwargs): - """Build instances.""" - build_inst_kwargs = kwargs - instances = build_inst_kwargs['instances'] - build_inst_kwargs['image'] = jsonutils.to_primitive( - build_inst_kwargs['image']) - - version = '1.34' - if self.client.can_send_version('1.34'): - build_inst_kwargs.pop('legacy_bdm', None) - else: - bdm_p = objects_base.obj_to_primitive( - build_inst_kwargs['block_device_mapping']) - build_inst_kwargs['block_device_mapping'] = bdm_p - version = '1.32' - if not self.client.can_send_version('1.32'): - instances_p = [jsonutils.to_primitive(inst) for inst in instances] - build_inst_kwargs['instances'] = instances_p - version = '1.30' - if not self.client.can_send_version('1.30'): - if 'filter_properties' in build_inst_kwargs: - filter_properties = build_inst_kwargs['filter_properties'] - flavor = filter_properties['instance_type'] - flavor_p = objects_base.obj_to_primitive(flavor) - filter_properties['instance_type'] = flavor_p - version = '1.8' - cctxt = self.client.prepare(version=version) - cctxt.cast(ctxt, 'build_instances', - build_inst_kwargs=build_inst_kwargs) - - def instance_delete_everywhere(self, ctxt, instance, delete_type): - """Delete instance everywhere. delete_type may be 'soft' - or 'hard'. This is generally only used to resolve races - when API cell doesn't know to what cell an instance belongs. - """ - if self.client.can_send_version('1.27'): - version = '1.27' - else: - version = '1.0' - instance = jsonutils.to_primitive(instance) - cctxt = self.client.prepare(version=version) - cctxt.cast(ctxt, 'instance_delete_everywhere', instance=instance, - delete_type=delete_type) - - def get_cell_info_for_neighbors(self, ctxt): - """Get information about our neighbor cells from the manager.""" - if not CONF.cells.enable: - return [] - cctxt = self.client.prepare(version='1.1') - return cctxt.call(ctxt, 'get_cell_info_for_neighbors') - - def sync_instances(self, ctxt, project_id=None, updated_since=None, - deleted=False): - """Ask all cells to sync instance data.""" - cctxt = self.client.prepare(version='1.1') - return cctxt.cast(ctxt, 'sync_instances', - project_id=project_id, - updated_since=updated_since, - deleted=deleted) - - def service_get_all(self, ctxt, filters=None): - """Ask all cells for their list of services.""" - cctxt = self.client.prepare(version='1.2') - return cctxt.call(ctxt, 'service_get_all', filters=filters) - - 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. - """ - cctxt = self.client.prepare(version='1.2') - return cctxt.call(ctxt, 'service_get_by_compute_host', - host_name=host_name) - - def get_host_uptime(self, context, host_name): - """Gets the host uptime in a particular cell. The cell name should - be encoded within the host_name - """ - cctxt = self.client.prepare(version='1.17') - return cctxt.call(context, 'get_host_uptime', host_name=host_name) - - def service_update(self, ctxt, host_name, binary, params_to_update): - """Used to enable/disable a service. For compute services, setting to - disabled stops new builds arriving on that host. - - :param host_name: the name of the host machine that the service is - running - :param binary: The name of the executable that the service runs as - :param params_to_update: eg. {'disabled': True} - """ - cctxt = self.client.prepare(version='1.7') - return cctxt.call(ctxt, 'service_update', - host_name=host_name, - binary=binary, - params_to_update=params_to_update) - - def service_delete(self, ctxt, cell_service_id): - """Deletes the specified service.""" - cctxt = self.client.prepare(version='1.26') - cctxt.call(ctxt, 'service_delete', - cell_service_id=cell_service_id) - - 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. - """ - cctxt = self.client.prepare(version='1.2', timeout=timeout) - return cctxt.call(ctxt, 'proxy_rpc_to_manager', - topic=topic, - rpc_message=rpc_message, - call=call) - - def task_log_get_all(self, ctxt, task_name, period_beginning, - period_ending, host=None, state=None): - """Get the task logs from the DB in child cells.""" - cctxt = self.client.prepare(version='1.3') - return cctxt.call(ctxt, 'task_log_get_all', - task_name=task_name, - period_beginning=period_beginning, - period_ending=period_ending, - host=host, state=state) - - def compute_node_get(self, ctxt, compute_id): - """Get a compute node by ID or UUID in a specific cell.""" - version = '1.38' - if uuidutils.is_uuid_like(compute_id): - if not self.client.can_send_version(version): - LOG.warning('Unable to get compute node by UUID %s; service ' - 'is too old or the version is capped.', compute_id) - raise exception.ComputeHostNotFound(host=compute_id) - else: - version = '1.4' - cctxt = self.client.prepare(version=version) - return cctxt.call(ctxt, 'compute_node_get', compute_id=compute_id) - - def compute_node_get_all(self, ctxt, hypervisor_match=None): - """Return list of compute nodes in all cells, optionally - filtering by hypervisor host. - """ - cctxt = self.client.prepare(version='1.4') - return cctxt.call(ctxt, 'compute_node_get_all', - hypervisor_match=hypervisor_match) - - def compute_node_stats(self, ctxt): - """Return compute node stats from all cells.""" - cctxt = self.client.prepare(version='1.4') - return cctxt.call(ctxt, 'compute_node_stats') - - def actions_get(self, ctxt, instance): - if not instance['cell_name']: - raise exception.InstanceUnknownCell(instance_uuid=instance['uuid']) - cctxt = self.client.prepare(version='1.5') - return cctxt.call(ctxt, 'actions_get', - cell_name=instance['cell_name'], - instance_uuid=instance['uuid']) - - def action_get_by_request_id(self, ctxt, instance, request_id): - if not instance['cell_name']: - raise exception.InstanceUnknownCell(instance_uuid=instance['uuid']) - cctxt = self.client.prepare(version='1.5') - return cctxt.call(ctxt, 'action_get_by_request_id', - cell_name=instance['cell_name'], - instance_uuid=instance['uuid'], - request_id=request_id) - - def action_events_get(self, ctxt, instance, action_id): - if not instance['cell_name']: - raise exception.InstanceUnknownCell(instance_uuid=instance['uuid']) - cctxt = self.client.prepare(version='1.5') - return cctxt.call(ctxt, 'action_events_get', - cell_name=instance['cell_name'], - action_id=action_id) - - def consoleauth_delete_tokens(self, ctxt, instance_uuid): - """Delete consoleauth tokens for an instance in API cells.""" - cctxt = self.client.prepare(version='1.6') - cctxt.cast(ctxt, 'consoleauth_delete_tokens', - instance_uuid=instance_uuid) - - def validate_console_port(self, ctxt, instance_uuid, console_port, - console_type): - """Validate console port with child cell compute node.""" - cctxt = self.client.prepare(version='1.6') - return cctxt.call(ctxt, 'validate_console_port', - instance_uuid=instance_uuid, - console_port=console_port, - console_type=console_type) - - def get_capacities(self, ctxt, cell_name=None): - cctxt = self.client.prepare(version='1.9') - return cctxt.call(ctxt, 'get_capacities', cell_name=cell_name) - - def get_migrations(self, ctxt, filters): - """Get all migrations applying the filters.""" - cctxt = self.client.prepare(version='1.11') - return cctxt.call(ctxt, 'get_migrations', filters=filters) - - def start_instance(self, ctxt, instance): - """Start an instance in its cell. - - This method takes a new-world instance object. - """ - cctxt = self.client.prepare(version='1.12') - cctxt.cast(ctxt, 'start_instance', instance=instance) - - def stop_instance(self, ctxt, instance, do_cast=True, clean_shutdown=True): - """Stop an instance in its cell. - - This method takes a new-world instance object. - """ - msg_args = {'instance': instance, - 'do_cast': do_cast} - if self.client.can_send_version('1.31'): - version = '1.31' - msg_args['clean_shutdown'] = clean_shutdown - else: - version = '1.12' - cctxt = self.client.prepare(version=version) - method = do_cast and cctxt.cast or cctxt.call - return method(ctxt, 'stop_instance', **msg_args) - - def cell_create(self, ctxt, values): - cctxt = self.client.prepare(version='1.13') - return cctxt.call(ctxt, 'cell_create', values=values) - - def cell_update(self, ctxt, cell_name, values): - cctxt = self.client.prepare(version='1.13') - return cctxt.call(ctxt, 'cell_update', - cell_name=cell_name, values=values) - - def cell_delete(self, ctxt, cell_name): - cctxt = self.client.prepare(version='1.13') - return cctxt.call(ctxt, 'cell_delete', cell_name=cell_name) - - def cell_get(self, ctxt, cell_name): - cctxt = self.client.prepare(version='1.13') - return cctxt.call(ctxt, 'cell_get', cell_name=cell_name) - - def reboot_instance(self, ctxt, instance, block_device_info, - reboot_type): - """Reboot an instance in its cell. - - This method takes a new-world instance object. - """ - cctxt = self.client.prepare(version='1.14') - cctxt.cast(ctxt, 'reboot_instance', instance=instance, - reboot_type=reboot_type) - - def pause_instance(self, ctxt, instance): - """Pause an instance in its cell. - - This method takes a new-world instance object. - """ - cctxt = self.client.prepare(version='1.19') - cctxt.cast(ctxt, 'pause_instance', instance=instance) - - def unpause_instance(self, ctxt, instance): - """Unpause an instance in its cell. - - This method takes a new-world instance object. - """ - cctxt = self.client.prepare(version='1.19') - cctxt.cast(ctxt, 'unpause_instance', instance=instance) - - def suspend_instance(self, ctxt, instance): - """Suspend an instance in its cell. - - This method takes a new-world instance object. - """ - cctxt = self.client.prepare(version='1.15') - cctxt.cast(ctxt, 'suspend_instance', instance=instance) - - def resume_instance(self, ctxt, instance): - """Resume an instance in its cell. - - This method takes a new-world instance object. - """ - cctxt = self.client.prepare(version='1.15') - cctxt.cast(ctxt, 'resume_instance', instance=instance) - - def terminate_instance(self, ctxt, instance, bdms, reservations=None, - delete_type='delete'): - """Delete an instance in its cell. - - This method takes a new-world instance object. - """ - msg_kwargs = {'instance': instance} - if self.client.can_send_version('1.36'): - version = '1.36' - msg_kwargs['delete_type'] = delete_type - else: - version = '1.18' - cctxt = self.client.prepare(version=version) - cctxt.cast(ctxt, 'terminate_instance', **msg_kwargs) - - def soft_delete_instance(self, ctxt, instance, reservations=None): - """Soft-delete an instance in its cell. - - This method takes a new-world instance object. - """ - cctxt = self.client.prepare(version='1.18') - cctxt.cast(ctxt, 'soft_delete_instance', instance=instance) - - def resize_instance(self, ctxt, instance, extra_instance_updates, - scheduler_hint, flavor, reservations=None, - clean_shutdown=True, - request_spec=None): - # NOTE(sbauza): Since Cells v1 is quite feature-frozen, we don't want - # to pass down request_spec to the manager and rather keep the - # cell conductor providing a new RequestSpec like the original - # behaviour - flavor_p = jsonutils.to_primitive(flavor) - version = '1.33' - msg_args = {'instance': instance, - 'flavor': flavor_p, - 'extra_instance_updates': extra_instance_updates, - 'clean_shutdown': clean_shutdown} - if not self.client.can_send_version(version): - del msg_args['clean_shutdown'] - version = '1.20' - - cctxt = self.client.prepare(version=version) - cctxt.cast(ctxt, 'resize_instance', **msg_args) - - def live_migrate_instance(self, ctxt, instance, host_name, - block_migration, disk_over_commit, - request_spec=None): - # NOTE(sbauza): Since Cells v1 is quite feature-freeze, we don't want - # to pass down request_spec to the manager and rather keep the - # cell conductor providing a new RequestSpec like the original - # behaviour - cctxt = self.client.prepare(version='1.20') - cctxt.cast(ctxt, 'live_migrate_instance', - instance=instance, - block_migration=block_migration, - disk_over_commit=disk_over_commit, - host_name=host_name) - - def revert_resize(self, ctxt, instance, migration, host, - reservations=None): - cctxt = self.client.prepare(version='1.21') - cctxt.cast(ctxt, 'revert_resize', instance=instance) - - def confirm_resize(self, ctxt, instance, migration, host, - reservations=None, cast=True): - # NOTE(comstud): This is only used in the API cell where we should - # always cast and ignore the 'cast' kwarg. - # Also, the compute api method normally takes an optional - # 'migration_ref' argument. But this is only used from the manager - # back to the API... which would happen in the child cell. - cctxt = self.client.prepare(version='1.21') - cctxt.cast(ctxt, 'confirm_resize', instance=instance) - - def reset_network(self, ctxt, instance): - """Reset networking for an instance.""" - cctxt = self.client.prepare(version='1.22') - cctxt.cast(ctxt, 'reset_network', instance=instance) - - def inject_network_info(self, ctxt, instance): - """Inject networking for an instance.""" - cctxt = self.client.prepare(version='1.23') - cctxt.cast(ctxt, 'inject_network_info', instance=instance) - - def snapshot_instance(self, ctxt, instance, image_id): - cctxt = self.client.prepare(version='1.24') - cctxt.cast(ctxt, 'snapshot_instance', - instance=instance, image_id=image_id) - - def backup_instance(self, ctxt, instance, image_id, backup_type, rotation): - cctxt = self.client.prepare(version='1.24') - cctxt.cast(ctxt, 'backup_instance', - instance=instance, - image_id=image_id, - backup_type=backup_type, - rotation=rotation) - - def rebuild_instance(self, ctxt, instance, new_pass, injected_files, - image_ref, orig_image_ref, orig_sys_metadata, bdms, - recreate=False, on_shared_storage=False, host=None, - preserve_ephemeral=False, request_spec=None, - kwargs=None): - # NOTE(sbauza): Since Cells v1 is quite feature-freeze, we don't want - # to pass down request_spec to the manager and rather keep the - # cell conductor providing a new RequestSpec like the original - # behaviour - cctxt = self.client.prepare(version='1.25') - cctxt.cast(ctxt, 'rebuild_instance', - instance=instance, image_href=image_ref, - admin_password=new_pass, files_to_inject=injected_files, - preserve_ephemeral=preserve_ephemeral, kwargs=kwargs) - - def set_admin_password(self, ctxt, instance, new_pass): - cctxt = self.client.prepare(version='1.29') - cctxt.cast(ctxt, 'set_admin_password', instance=instance, - new_pass=new_pass) diff --git a/nova/cells/scheduler.py b/nova/cells/scheduler.py deleted file mode 100644 index 104a6109e5..0000000000 --- a/nova/cells/scheduler.py +++ /dev/null @@ -1,244 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Cells Scheduler -""" -import copy -import time - -from oslo_log import log as logging -from six.moves import range - -from nova.cells import filters -from nova.cells import weights -from nova import compute -from nova.compute import instance_actions -from nova.compute import vm_states -from nova import conductor -import nova.conf -from nova.db import base -from nova import exception -from nova import objects -from nova.objects import base as obj_base -from nova.scheduler import utils as scheduler_utils -from nova import utils - -LOG = logging.getLogger(__name__) - -CONF = nova.conf.CONF - - -class CellsScheduler(base.Base): - """The cells scheduler.""" - - def __init__(self, msg_runner): - super(CellsScheduler, self).__init__() - self.msg_runner = msg_runner - self.state_manager = msg_runner.state_manager - self.compute_api = compute.API() - self.compute_task_api = conductor.ComputeTaskAPI() - self.filter_handler = filters.CellFilterHandler() - filter_classes = self.filter_handler.get_matching_classes( - CONF.cells.scheduler_filter_classes) - self.filters = [cls() for cls in filter_classes] - self.weight_handler = weights.CellWeightHandler() - weigher_classes = self.weight_handler.get_matching_classes( - CONF.cells.scheduler_weight_classes) - self.weighers = [cls() for cls in weigher_classes] - - def _create_instances_here(self, ctxt, instance_uuids, instance_properties, - instance_type, image, security_groups, block_device_mapping): - instance_values = copy.copy(instance_properties) - # The parent may pass these metadata values as lists, and the - # create call expects it to be a dict. - instance_values['metadata'] = utils.instance_meta(instance_values) - # Pop out things that will get set properly when re-creating the - # instance record. - instance_values.pop('id') - instance_values.pop('name') - instance_values.pop('info_cache') - instance_values.pop('security_groups') - instance_values.pop('flavor') - - # FIXME(danms): The instance was brutally serialized before being - # sent over RPC to us. Thus, the pci_requests value wasn't really - # sent in a useful form. Since it was getting ignored for cells - # before it was part of the Instance, skip it now until cells RPC - # is sending proper instance objects. - instance_values.pop('pci_requests', None) - - # FIXME(danms): Same for ec2_ids - instance_values.pop('ec2_ids', None) - - # FIXME(danms): Same for keypairs - instance_values.pop('keypairs', None) - - instances = [] - num_instances = len(instance_uuids) - security_groups = ( - self.compute_api.security_group_api.populate_security_groups( - security_groups)) - for i, instance_uuid in enumerate(instance_uuids): - instance = objects.Instance(context=ctxt) - instance.update(instance_values) - instance.uuid = instance_uuid - instance.flavor = instance_type - instance.old_flavor = None - instance.new_flavor = None - instance = self.compute_api.create_db_entry_for_new_instance( - ctxt, - instance_type, - image, - instance, - security_groups, - block_device_mapping, - num_instances, i) - block_device_mapping = ( - self.compute_api._bdm_validate_set_size_and_instance( - ctxt, instance, instance_type, block_device_mapping)) - self.compute_api._create_block_device_mapping(block_device_mapping) - - instances.append(instance) - return instances - - def _create_action_here(self, ctxt, instance_uuids): - for instance_uuid in instance_uuids: - objects.InstanceAction.action_start( - ctxt, - instance_uuid, - instance_actions.CREATE, - want_result=False) - - def _get_possible_cells(self): - cells = self.state_manager.get_child_cells() - our_cell = self.state_manager.get_my_state() - # Include our cell in the list, if we have any capacity info - if not cells or our_cell.capacities: - cells.append(our_cell) - return cells - - def _grab_target_cells(self, filter_properties): - cells = self._get_possible_cells() - cells = self.filter_handler.get_filtered_objects(self.filters, cells, - filter_properties) - # NOTE(comstud): I know this reads weird, but the 'if's are nested - # this way to optimize for the common case where 'cells' is a list - # containing at least 1 entry. - if not cells: - if cells is None: - # None means to bypass further scheduling as a filter - # took care of everything. - return - raise exception.NoCellsAvailable() - - weighted_cells = self.weight_handler.get_weighed_objects( - self.weighers, cells, filter_properties) - LOG.debug("Weighted cells: %(weighted_cells)s", - {'weighted_cells': weighted_cells}) - target_cells = [cell.obj for cell in weighted_cells] - return target_cells - - def _build_instances(self, message, target_cells, instance_uuids, - build_inst_kwargs): - """Attempt to build instance(s) or send msg to child cell.""" - ctxt = message.ctxt - instance_properties = obj_base.obj_to_primitive( - build_inst_kwargs['instances'][0]) - filter_properties = build_inst_kwargs['filter_properties'] - instance_type = filter_properties['instance_type'] - image = build_inst_kwargs['image'] - security_groups = build_inst_kwargs['security_groups'] - block_device_mapping = build_inst_kwargs['block_device_mapping'] - - LOG.debug("Building instances with routing_path=%(routing_path)s", - {'routing_path': message.routing_path}) - - for target_cell in target_cells: - try: - if target_cell.is_me: - # Need to create instance DB entries as the conductor - # expects that the instance(s) already exists. - instances = self._create_instances_here(ctxt, - instance_uuids, instance_properties, instance_type, - image, security_groups, block_device_mapping) - build_inst_kwargs['instances'] = instances - # Need to record the create action in the db as the - # conductor expects it to already exist. - self._create_action_here(ctxt, instance_uuids) - self.compute_task_api.build_instances(ctxt, - **build_inst_kwargs) - return - self.msg_runner.build_instances(ctxt, target_cell, - build_inst_kwargs) - return - except Exception: - LOG.exception("Couldn't communicate with cell '%s'", - target_cell.name) - # FIXME(comstud): Would be nice to kick this back up so that - # the parent cell could retry, if we had a parent. - LOG.error("Couldn't communicate with any cells") - raise exception.NoCellsAvailable() - - def build_instances(self, message, build_inst_kwargs): - image = build_inst_kwargs['image'] - instance_uuids = [inst['uuid'] for inst in - build_inst_kwargs['instances']] - instances = build_inst_kwargs['instances'] - request_spec = scheduler_utils.build_request_spec(image, instances) - filter_properties = copy.copy(build_inst_kwargs['filter_properties']) - filter_properties.update({'context': message.ctxt, - 'scheduler': self, - 'routing_path': message.routing_path, - 'host_sched_kwargs': build_inst_kwargs, - 'request_spec': request_spec}) - - self._schedule_build_to_cells(message, instance_uuids, - filter_properties, self._build_instances, build_inst_kwargs) - - def _schedule_build_to_cells(self, message, instance_uuids, - filter_properties, method, method_kwargs): - """Pick a cell where we should create a new instance(s).""" - try: - for i in range(max(0, CONF.cells.scheduler_retries) + 1): - try: - target_cells = self._grab_target_cells(filter_properties) - if target_cells is None: - # a filter took care of scheduling. skip. - return - - return method(message, target_cells, instance_uuids, - method_kwargs) - except exception.NoCellsAvailable: - if i == max(0, CONF.cells.scheduler_retries): - raise - sleep_time = max(1, CONF.cells.scheduler_retry_delay) - LOG.info("No cells available when scheduling. Will " - "retry in %(sleep_time)s second(s)", - {'sleep_time': sleep_time}) - time.sleep(sleep_time) - continue - except Exception: - LOG.exception("Error scheduling instances %(instance_uuids)s", - {'instance_uuids': instance_uuids}) - ctxt = message.ctxt - for instance_uuid in instance_uuids: - instance = objects.Instance(context=ctxt, uuid=instance_uuid, - vm_state=vm_states.ERROR) - try: - instance.vm_state = vm_states.ERROR - instance.save() - except Exception: - pass diff --git a/nova/cells/state.py b/nova/cells/state.py deleted file mode 100644 index 1d471220f2..0000000000 --- a/nova/cells/state.py +++ /dev/null @@ -1,499 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -CellState Manager -""" -import collections -import copy -import datetime -import functools -import time - -from oslo_config import cfg -from oslo_db import exception as db_exc -from oslo_log import log as logging -from oslo_serialization import jsonutils -from oslo_utils import timeutils -from oslo_utils import units -import six - -from nova.cells import rpc_driver -import nova.conf -from nova import context -from nova.db import base -from nova import exception -from nova import objects -from nova import rpc -from nova import servicegroup -from nova import utils - - -LOG = logging.getLogger(__name__) - -CONF = nova.conf.CONF - - -class CellState(object): - """Holds information for a particular cell.""" - def __init__(self, cell_name, is_me=False): - self.name = cell_name - self.is_me = is_me - self.last_seen = datetime.datetime.min - self.capabilities = {} - self.capacities = {} - self.db_info = {} - # TODO(comstud): The DB will specify the driver to use to talk - # to this cell, but there's no column for this yet. The only - # available driver is the rpc driver. - self.driver = rpc_driver.CellsRPCDriver() - - def update_db_info(self, cell_db_info): - """Update cell credentials from db.""" - self.db_info = {k: v for k, v in cell_db_info.items() - if k != 'name'} - - def update_capabilities(self, cell_metadata): - """Update cell capabilities for a cell.""" - self.last_seen = timeutils.utcnow() - self.capabilities = cell_metadata - - def update_capacities(self, capacities): - """Update capacity information for a cell.""" - self.last_seen = timeutils.utcnow() - self.capacities = capacities - - def get_cell_info(self): - """Return subset of cell information for OS API use.""" - db_fields_to_return = ['is_parent', 'weight_scale', 'weight_offset'] - url_fields_to_return = { - 'username': 'username', - 'hostname': 'rpc_host', - 'port': 'rpc_port', - } - cell_info = dict(name=self.name, capabilities=self.capabilities) - if self.db_info: - for field in db_fields_to_return: - cell_info[field] = self.db_info[field] - - url = rpc.get_transport_url(self.db_info['transport_url']) - if url.hosts: - for field, canonical in url_fields_to_return.items(): - cell_info[canonical] = getattr(url.hosts[0], field) - return cell_info - - def send_message(self, message): - """Send a message to a cell. Just forward this to the driver, - passing ourselves and the message as arguments. - """ - self.driver.send_message_to_cell(self, message) - - def __repr__(self): - me = "me" if self.is_me else "not_me" - return "Cell '%s' (%s)" % (self.name, me) - - -def sync_before(f): - """Use as a decorator to wrap methods that use cell information to - make sure they sync the latest information from the DB periodically. - """ - @functools.wraps(f) - def wrapper(self, *args, **kwargs): - self._cell_data_sync() - return f(self, *args, **kwargs) - return wrapper - - -def sync_after(f): - """Use as a decorator to wrap methods that update cell information - in the database to make sure the data is synchronized immediately. - """ - @functools.wraps(f) - def wrapper(self, *args, **kwargs): - result = f(self, *args, **kwargs) - self._cell_data_sync(force=True) - return result - return wrapper - - -_unset = object() - - -class CellStateManager(base.Base): - def __new__(cls, cell_state_cls=None, cells_config=_unset): - if cls is not CellStateManager: - return super(CellStateManager, cls).__new__(cls) - - if cells_config is _unset: - cells_config = CONF.cells.cells_config - - if cells_config: - return CellStateManagerFile(cell_state_cls) - - return CellStateManagerDB(cell_state_cls) - - def __init__(self, cell_state_cls=None): - super(CellStateManager, self).__init__() - if not cell_state_cls: - cell_state_cls = CellState - self.cell_state_cls = cell_state_cls - self.my_cell_state = cell_state_cls(CONF.cells.name, is_me=True) - self.parent_cells = {} - self.child_cells = {} - self.last_cell_db_check = datetime.datetime.min - self.servicegroup_api = servicegroup.API() - - attempts = 0 - while True: - try: - self._cell_data_sync(force=True) - break - except db_exc.DBError: - attempts += 1 - if attempts > 120: - raise - LOG.exception('DB error') - time.sleep(30) - - my_cell_capabs = {} - for cap in CONF.cells.capabilities: - name, value = cap.split('=', 1) - if ';' in value: - values = set(value.split(';')) - else: - values = set([value]) - my_cell_capabs[name] = values - self.my_cell_state.update_capabilities(my_cell_capabs) - - def _refresh_cells_from_dict(self, db_cells_dict): - """Make our cell info map match the db.""" - - # Update current cells. Delete ones that disappeared - for cells_dict in (self.parent_cells, self.child_cells): - for cell_name, cell_info in cells_dict.items(): - is_parent = cell_info.db_info['is_parent'] - db_dict = db_cells_dict.get(cell_name) - if db_dict and is_parent == db_dict['is_parent']: - cell_info.update_db_info(db_dict) - else: - del cells_dict[cell_name] - - # Add new cells - for cell_name, db_info in db_cells_dict.items(): - if db_info['is_parent']: - cells_dict = self.parent_cells - else: - cells_dict = self.child_cells - if cell_name not in cells_dict: - cells_dict[cell_name] = self.cell_state_cls(cell_name) - cells_dict[cell_name].update_db_info(db_info) - - def _time_to_sync(self): - """Is it time to sync the DB against our memory cache?""" - diff = timeutils.utcnow() - self.last_cell_db_check - return diff.seconds >= CONF.cells.db_check_interval - - def _update_our_capacity(self, ctxt=None): - """Update our capacity in the self.my_cell_state CellState. - - This will add/update 2 entries in our CellState.capacities, - 'ram_free' and 'disk_free'. - - The values of these are both dictionaries with the following - format: - - {'total_mb': , - 'units_by_mb: } - - contains the number of units that we can build for - every distinct memory or disk requirement that we have based on - instance types. This number is computed by looking at room available - on every compute_node. - - Take the following instance_types as an example: - - [{'memory_mb': 1024, 'root_gb': 10, 'ephemeral_gb': 100}, - {'memory_mb': 2048, 'root_gb': 20, 'ephemeral_gb': 200}] - - capacities['ram_free']['units_by_mb'] would contain the following: - - {'1024': , - '2048': } - - capacities['disk_free']['units_by_mb'] would contain the following: - - {'122880': , - '225280': } - - Units are in MB, so 122880 = (10 + 100) * 1024. - - NOTE(comstud): Perhaps we should only report a single number - available per instance_type. - """ - - if not ctxt: - ctxt = context.get_admin_context() - - reserve_level = CONF.cells.reserve_percent / 100.0 - - def _defaultdict_int(): - return collections.defaultdict(int) - compute_hosts = collections.defaultdict(_defaultdict_int) - - def _get_compute_hosts(): - service_refs = {service.host: service - for service in objects.ServiceList.get_by_binary( - ctxt, 'nova-compute')} - - compute_nodes = objects.ComputeNodeList.get_all(ctxt) - for compute in compute_nodes: - host = compute.host - service = service_refs.get(host) - if not service or service['disabled']: - continue - - # NOTE: This works because it is only used for computes found - # in the cell this is run in. It can not be used to check on - # computes in a child cell from the api cell. If this is run - # in the api cell objects.ComputeNodeList.get_all() above will - # return an empty list. - alive = self.servicegroup_api.service_is_up(service) - if not alive: - continue - - chost = compute_hosts[host] - chost['free_ram_mb'] += max(0, compute.free_ram_mb) - chost['free_disk_mb'] += max(0, compute.free_disk_gb) * 1024 - chost['total_ram_mb'] += max(0, compute.memory_mb) - chost['total_disk_mb'] += max(0, compute.local_gb) * 1024 - - _get_compute_hosts() - if not compute_hosts: - self.my_cell_state.update_capacities({}) - return - - ram_mb_free_units = {} - disk_mb_free_units = {} - total_ram_mb_free = 0 - total_disk_mb_free = 0 - - def _free_units(total, free, per_inst): - if per_inst: - min_free = total * reserve_level - free = max(0, free - min_free) - return int(free / per_inst) - else: - return 0 - - flavors = objects.FlavorList.get_all(ctxt) - memory_mb_slots = frozenset( - [flavor.memory_mb for flavor in flavors]) - disk_mb_slots = frozenset( - [(flavor.root_gb + flavor.ephemeral_gb) * units.Ki - for flavor in flavors]) - - for compute_values in compute_hosts.values(): - total_ram_mb_free += compute_values['free_ram_mb'] - total_disk_mb_free += compute_values['free_disk_mb'] - for memory_mb_slot in memory_mb_slots: - ram_mb_free_units.setdefault(str(memory_mb_slot), 0) - free_units = _free_units(compute_values['total_ram_mb'], - compute_values['free_ram_mb'], memory_mb_slot) - ram_mb_free_units[str(memory_mb_slot)] += free_units - for disk_mb_slot in disk_mb_slots: - disk_mb_free_units.setdefault(str(disk_mb_slot), 0) - free_units = _free_units(compute_values['total_disk_mb'], - compute_values['free_disk_mb'], disk_mb_slot) - disk_mb_free_units[str(disk_mb_slot)] += free_units - - capacities = {'ram_free': {'total_mb': total_ram_mb_free, - 'units_by_mb': ram_mb_free_units}, - 'disk_free': {'total_mb': total_disk_mb_free, - 'units_by_mb': disk_mb_free_units}} - self.my_cell_state.update_capacities(capacities) - - @sync_before - def get_cell_info_for_neighbors(self): - """Return cell information for all neighbor cells.""" - cell_list = [cell.get_cell_info() - for cell in six.itervalues(self.child_cells)] - cell_list.extend([cell.get_cell_info() - for cell in six.itervalues(self.parent_cells)]) - return cell_list - - @sync_before - def get_my_state(self): - """Return information for my (this) cell.""" - return self.my_cell_state - - @sync_before - def get_child_cells(self): - """Return list of child cell_infos.""" - return list(self.child_cells.values()) - - @sync_before - def get_parent_cells(self): - """Return list of parent cell_infos.""" - return list(self.parent_cells.values()) - - @sync_before - def get_parent_cell(self, cell_name): - return self.parent_cells.get(cell_name) - - @sync_before - def get_child_cell(self, cell_name): - return self.child_cells.get(cell_name) - - @sync_before - def update_cell_capabilities(self, cell_name, capabilities): - """Update capabilities for a cell.""" - cell = (self.child_cells.get(cell_name) or - self.parent_cells.get(cell_name)) - if not cell: - LOG.error("Unknown cell '%(cell_name)s' when trying to " - "update capabilities", - {'cell_name': cell_name}) - return - # Make sure capabilities are sets. - for capab_name, values in capabilities.items(): - capabilities[capab_name] = set(values) - cell.update_capabilities(capabilities) - - @sync_before - def update_cell_capacities(self, cell_name, capacities): - """Update capacities for a cell.""" - cell = (self.child_cells.get(cell_name) or - self.parent_cells.get(cell_name)) - if not cell: - LOG.error("Unknown cell '%(cell_name)s' when trying to " - "update capacities", - {'cell_name': cell_name}) - return - cell.update_capacities(capacities) - - @sync_before - def get_our_capabilities(self, include_children=True): - capabs = copy.deepcopy(self.my_cell_state.capabilities) - if include_children: - for cell in self.child_cells.values(): - if timeutils.is_older_than(cell.last_seen, - CONF.cells.mute_child_interval): - continue - for capab_name, values in cell.capabilities.items(): - if capab_name not in capabs: - capabs[capab_name] = set([]) - capabs[capab_name] |= values - return capabs - - def _add_to_dict(self, target, src): - for key, value in src.items(): - if isinstance(value, dict): - target.setdefault(key, {}) - self._add_to_dict(target[key], value) - continue - target.setdefault(key, 0) - target[key] += value - - @sync_before - def get_our_capacities(self, include_children=True): - capacities = copy.deepcopy(self.my_cell_state.capacities) - if include_children: - for cell in self.child_cells.values(): - self._add_to_dict(capacities, cell.capacities) - return capacities - - @sync_before - def get_capacities(self, cell_name=None): - if not cell_name or cell_name == self.my_cell_state.name: - return self.get_our_capacities() - if cell_name in self.child_cells: - return self.child_cells[cell_name].capacities - raise exception.CellNotFound(cell_name=cell_name) - - @sync_before - def cell_get(self, ctxt, cell_name): - for cells_dict in (self.parent_cells, self.child_cells): - if cell_name in cells_dict: - return cells_dict[cell_name] - - raise exception.CellNotFound(cell_name=cell_name) - - -class CellStateManagerDB(CellStateManager): - @utils.synchronized('cell-db-sync') - def _cell_data_sync(self, force=False): - """Update cell status for all cells from the backing data store - when necessary. - - :param force: If True, cell status will be updated regardless - of whether it's time to do so. - """ - if force or self._time_to_sync(): - LOG.debug("Updating cell cache from db.") - self.last_cell_db_check = timeutils.utcnow() - ctxt = context.get_admin_context() - db_cells = self.db.cell_get_all(ctxt) - db_cells_dict = {cell['name']: cell for cell in db_cells} - self._refresh_cells_from_dict(db_cells_dict) - self._update_our_capacity(ctxt) - - @sync_after - def cell_create(self, ctxt, values): - return self.db.cell_create(ctxt, values) - - @sync_after - def cell_update(self, ctxt, cell_name, values): - return self.db.cell_update(ctxt, cell_name, values) - - @sync_after - def cell_delete(self, ctxt, cell_name): - return self.db.cell_delete(ctxt, cell_name) - - -class CellStateManagerFile(CellStateManager): - def __init__(self, cell_state_cls=None): - cells_config = CONF.cells.cells_config - self.cells_config_path = CONF.find_file(cells_config) - if not self.cells_config_path: - raise cfg.ConfigFilesNotFoundError(config_files=[cells_config]) - super(CellStateManagerFile, self).__init__(cell_state_cls) - - def _cell_data_sync(self, force=False): - """Update cell status for all cells from the backing data store - when necessary. - - :param force: If True, cell status will be updated regardless - of whether it's time to do so. - """ - reloaded, data = utils.read_cached_file(self.cells_config_path, - force_reload=force) - - if reloaded: - LOG.debug("Updating cell cache from config file.") - self.cells_config_data = jsonutils.loads(data) - self._refresh_cells_from_dict(self.cells_config_data) - - if force or self._time_to_sync(): - self.last_cell_db_check = timeutils.utcnow() - self._update_our_capacity() - - def cell_create(self, ctxt, values): - raise exception.CellsUpdateUnsupported() - - def cell_update(self, ctxt, cell_name, values): - raise exception.CellsUpdateUnsupported() - - def cell_delete(self, ctxt, cell_name): - raise exception.CellsUpdateUnsupported() diff --git a/nova/cells/utils.py b/nova/cells/utils.py deleted file mode 100644 index 461a3f9c94..0000000000 --- a/nova/cells/utils.py +++ /dev/null @@ -1,228 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Cells Utility Methods -""" -import random -import sys - -import six - -import nova.conf -from nova import objects -from nova.objects import base as obj_base - - -# 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 = '@' - -CONF = nova.conf.CONF - - -class ProxyObjectSerializer(obj_base.NovaObjectSerializer): - def __init__(self): - super(ProxyObjectSerializer, self).__init__() - self.serializer = super(ProxyObjectSerializer, self) - - def _process_object(self, context, objprim): - return _CellProxy.obj_from_primitive(self.serializer, objprim, context) - - -class _CellProxy(object): - def __init__(self, obj, cell_path): - self._obj = obj - self._cell_path = cell_path - - @property - def id(self): - return cell_with_item(self._cell_path, self._obj.id) - - @property - def host(self): - return cell_with_item(self._cell_path, self._obj.host) - - def __getitem__(self, key): - if key == 'id': - return self.id - if key == 'host': - return self.host - - return getattr(self._obj, key) - - def __contains__(self, key): - """Pass-through "in" check to the wrapped object. - - This is needed to proxy any types of checks in the calling code - like:: - - if 'availability_zone' in service: - ... - - :param key: They key to look for in the wrapped object. - :returns: True if key is in the wrapped object, False otherwise. - """ - return key in self._obj - - def obj_to_primitive(self): - obj_p = self._obj.obj_to_primitive() - obj_p['cell_proxy.class_name'] = self.__class__.__name__ - obj_p['cell_proxy.cell_path'] = self._cell_path - return obj_p - - @classmethod - def obj_from_primitive(cls, serializer, primitive, context=None): - obj_primitive = primitive.copy() - cell_path = obj_primitive.pop('cell_proxy.cell_path', None) - klass_name = obj_primitive.pop('cell_proxy.class_name', None) - obj = serializer._process_object(context, obj_primitive) - if klass_name is not None and cell_path is not None: - klass = getattr(sys.modules[__name__], klass_name) - return klass(obj, cell_path) - else: - return obj - - # dict-ish syntax sugar - def _iteritems(self): - """For backwards-compatibility with dict-based objects. - - NOTE(sbauza): May be removed in the future. - """ - for name in self._obj.obj_fields: - if (self._obj.obj_attr_is_set(name) or - name in self._obj.obj_extra_fields): - if name == 'id': - yield name, self.id - elif name == 'host': - yield name, self.host - else: - yield name, getattr(self._obj, name) - - if six.PY2: - iteritems = _iteritems - else: - items = _iteritems - - def __getattr__(self, key): - return getattr(self._obj, key) - - -class ComputeNodeProxy(_CellProxy): - pass - - -class ServiceProxy(_CellProxy): - def __getattr__(self, key): - if key == 'compute_node': - # NOTE(sbauza): As the Service object is still having a nested - # ComputeNode object that consumers of this Proxy don't use, we can - # safely remove it from what's returned - raise AttributeError - # NOTE(claudiub): needed for py34 compatibility. - # get self._obj first, without ending into an infinite recursion. - return getattr(self.__getattribute__("_obj"), key) - - -def get_instances_to_sync(context, updated_since=None, project_id=None, - deleted=True, shuffle=False, uuids_only=False): - """Return a generator that will return a list of active and - deleted instances to sync with parent cells. The list may - optionally be shuffled for periodic updates so that multiple - cells services aren't self-healing the same instances in nearly - lockstep. - """ - def _get_paginated_instances(context, filters, shuffle, limit, marker): - instances = objects.InstanceList.get_by_filters( - context, filters, sort_key='deleted', sort_dir='asc', - limit=limit, marker=marker) - if len(instances) > 0: - marker = instances[-1]['uuid'] - # NOTE(melwitt/alaski): Need a list that supports assignment for - # shuffle. And pop() on the returned result. - instances = list(instances) - if shuffle: - random.shuffle(instances) - return instances, marker - - filters = {} - if updated_since is not None: - filters['changes-since'] = updated_since - if project_id is not None: - filters['project_id'] = project_id - if not deleted: - filters['deleted'] = False - # Active instances first. - limit = CONF.cells.instance_update_sync_database_limit - marker = None - - instances = [] - while True: - if not instances: - instances, marker = _get_paginated_instances(context, filters, - shuffle, limit, marker) - if not instances: - break - instance = instances.pop(0) - if uuids_only: - yield instance.uuid - else: - yield instance - - -def cell_with_item(cell_name, item): - """Turn cell_name and item into @.""" - if cell_name is None: - return 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.""" - result = cell_and_item.rsplit(CELL_ITEM_SEP, 1) - if len(result) == 1: - return (None, cell_and_item) - else: - return result - - -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. - """ - # NOTE(sbauza): As compute_node is a ComputeNode object, we need to wrap it - # for adding the cell_path information - compute_proxy = ComputeNodeProxy(compute_node, cell_name) - return compute_proxy - - -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. - """ - # NOTE(sbauza): As service is a Service object, we need to wrap it - # for adding the cell_path information - service_proxy = ServiceProxy(service, cell_name) - return service_proxy - - -def add_cell_to_task_log(task_log, cell_name): - """Fix task_log attributes that should be unique. In particular, - the 'id' and 'host' fields should be prepended with cell name. - """ - task_log['id'] = cell_with_item(cell_name, task_log['id']) - task_log['host'] = cell_with_item(cell_name, task_log['host']) diff --git a/nova/cells/weights/__init__.py b/nova/cells/weights/__init__.py deleted file mode 100644 index 202a6a31a8..0000000000 --- a/nova/cells/weights/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2012-2013 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Cell Scheduler weights -""" - -from nova import weights - - -class WeightedCell(weights.WeighedObject): - def __repr__(self): - return "WeightedCell [cell: %s, weight: %s]" % ( - self.obj.name, self.weight) - - -class BaseCellWeigher(weights.BaseWeigher): - """Base class for cell weights.""" - pass - - -class CellWeightHandler(weights.BaseWeightHandler): - object_class = WeightedCell - - def __init__(self): - super(CellWeightHandler, self).__init__(BaseCellWeigher) - - -def all_weighers(): - """Return a list of weight plugin classes found in this directory.""" - return CellWeightHandler().get_all_classes() diff --git a/nova/cells/weights/mute_child.py b/nova/cells/weights/mute_child.py deleted file mode 100644 index ecf3638ca7..0000000000 --- a/nova/cells/weights/mute_child.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) 2013 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -If a child cell hasn't sent capacity or capability updates in a while, -downgrade its likelihood of being chosen for scheduling requests. -""" - -from oslo_log import log as logging -from oslo_utils import timeutils - -from nova.cells import weights -import nova.conf - -LOG = logging.getLogger(__name__) - -CONF = nova.conf.CONF - - -class MuteChildWeigher(weights.BaseCellWeigher): - """If a child cell hasn't been heard from, greatly lower its selection - weight. - """ - - MUTE_WEIGH_VALUE = 1.0 - - def weight_multiplier(self, host_state): - # negative multiplier => lower weight - return CONF.cells.mute_weight_multiplier - - def _weigh_object(self, cell, weight_properties): - """Check cell against the last_seen timestamp that indicates the time - that the most recent capability or capacity update was received from - the given cell. - """ - - last_seen = cell.last_seen - secs = CONF.cells.mute_child_interval - - if timeutils.is_older_than(last_seen, secs): - # yep, that's a mute child; recommend highly that it be skipped! - LOG.warning("%(cell)s has not been seen since %(last_seen)s " - "and is being treated as mute.", - {'cell': cell, 'last_seen': last_seen}) - return self.MUTE_WEIGH_VALUE - else: - return 0 diff --git a/nova/cells/weights/ram_by_instance_type.py b/nova/cells/weights/ram_by_instance_type.py deleted file mode 100644 index f0e904d145..0000000000 --- a/nova/cells/weights/ram_by_instance_type.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) 2012-2013 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Weigh cells by memory needed in a way that spreads instances. -""" - -from nova.cells import weights -import nova.conf - - -CONF = nova.conf.CONF - - -class RamByInstanceTypeWeigher(weights.BaseCellWeigher): - """Weigh cells by instance_type requested.""" - - def weight_multiplier(self, host_state): - return CONF.cells.ram_weight_multiplier - - def _weigh_object(self, cell, weight_properties): - """Use the 'ram_free' for a particular instance_type advertised from a - child cell's capacity to compute a weight. We want to direct the - build to a cell with a higher capacity. Since higher weights win, - we just return the number of units available for the instance_type. - """ - request_spec = weight_properties['request_spec'] - instance_type = request_spec['instance_type'] - memory_needed = instance_type['memory_mb'] - - ram_free = cell.capacities.get('ram_free', {}) - units_by_mb = ram_free.get('units_by_mb', {}) - - return units_by_mb.get(str(memory_needed), 0) diff --git a/nova/cells/weights/weight_offset.py b/nova/cells/weights/weight_offset.py deleted file mode 100644 index 44d0614ed5..0000000000 --- a/nova/cells/weights/weight_offset.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2012-2013 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Weigh cells by their weight_offset in the DB. Cells with higher -weight_offsets in the DB will be preferred. -""" - -from nova.cells import weights -import nova.conf - - -CONF = nova.conf.CONF - - -class WeightOffsetWeigher(weights.BaseCellWeigher): - """Weight cell by weight_offset db field. - Originally designed so you can set a default cell by putting - its weight_offset to 999999999999999 (highest weight wins) - """ - - def weight_multiplier(self, host_state): - return CONF.cells.offset_weight_multiplier - - def _weigh_object(self, cell, weight_properties): - """Returns whatever was in the DB for weight_offset.""" - return cell.db_info.get('weight_offset', 0) diff --git a/nova/compute/__init__.py b/nova/compute/__init__.py index feb11b0c62..46cba14b7c 100644 --- a/nova/compute/__init__.py +++ b/nova/compute/__init__.py @@ -16,22 +16,13 @@ from oslo_utils import importutils -# Importing full names to not pollute the namespace and cause possible -# collisions with use of 'from nova.compute import ' elsewhere. -import nova.cells.opts -import nova.exception - - -CELL_TYPE_TO_CLS_NAME = {'api': 'nova.compute.cells_api.ComputeCellsAPI', - 'compute': 'nova.compute.api.API', - None: 'nova.compute.api.API', - } +# TODO(stephenfin): Remove this nonsense +CELL_TYPE_TO_CLS_NAME = {None: 'nova.compute.api.API'} def _get_compute_api_class_name(): """Returns the name of compute API class.""" - cell_type = nova.cells.opts.get_cell_type() - return CELL_TYPE_TO_CLS_NAME[cell_type] + return CELL_TYPE_TO_CLS_NAME[None] def API(*args, **kwargs): diff --git a/nova/compute/api.py b/nova/compute/api.py index ce86c3cdb3..bae5967082 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -39,7 +39,6 @@ from six.moves import range from nova import availability_zones from nova import block_device -from nova.cells import opts as cells_opts from nova.compute import flavors from nova.compute import instance_actions from nova.compute import instance_list @@ -207,14 +206,6 @@ def check_instance_lock(function): return inner -def check_instance_cell(fn): - @six.wraps(fn) - def _wrapped(self, context, instance, *args, **kwargs): - self._validate_cell(instance) - return fn(self, context, instance, *args, **kwargs) - return _wrapped - - def _diff_dict(orig, new): """Return a dict describing how to change orig to new. The keys correspond to values that have changed; the value will be a list @@ -280,18 +271,6 @@ class API(base.Base): self.host = CONF.host super(API, self).__init__(**kwargs) - @property - def cell_type(self): - return getattr(self, '_cell_type', cells_opts.get_cell_type()) - - def _validate_cell(self, instance): - if self.cell_type != 'api': - return - cell_name = instance.cell_name - if not cell_name: - raise exception.InstanceUnknownCell( - instance_uuid=instance.uuid) - def _record_action_start(self, context, instance, action): objects.InstanceAction.action_start(context, instance.uuid, action, want_result=False) @@ -1329,50 +1308,16 @@ class API(base.Base): instances.append(instance) request_specs.append(rs) - if CONF.cells.enable: - # NOTE(danms): CellsV1 can't do the new thing, so we - # do the old thing here. We can remove this path once - # we stop supporting v1. - for instance in instances: - instance.create() - # NOTE(melwitt): We recheck the quota after creating the objects - # to prevent users from allocating more resources than their - # allowed quota in the event of a race. This is configurable - # because it can be expensive if strict quota limits are not - # required in a deployment. - if CONF.quota.recheck_quota: - try: - compute_utils.check_num_instances_quota( - context, instance_type, 0, 0, - orig_num_req=len(instances)) - except exception.TooManyInstances: - with excutils.save_and_reraise_exception(): - # Need to clean up all the instances we created - # along with the build requests, request specs, - # and instance mappings. - self._cleanup_build_artifacts(instances, - instances_to_build) - - self.compute_task_api.build_instances(context, - instances=instances, image=boot_meta, - filter_properties=filter_properties, - admin_password=admin_password, - injected_files=injected_files, - requested_networks=requested_networks, - security_groups=security_groups, - block_device_mapping=block_device_mapping, - legacy_bdm=False) - else: - self.compute_task_api.schedule_and_build_instances( - context, - build_requests=build_requests, - request_spec=request_specs, - image=boot_meta, - admin_password=admin_password, - injected_files=injected_files, - requested_networks=requested_networks, - block_device_mapping=block_device_mapping, - tags=tags) + self.compute_task_api.schedule_and_build_instances( + context, + build_requests=build_requests, + request_spec=request_specs, + image=boot_meta, + admin_password=admin_password, + injected_files=injected_files, + requested_networks=requested_networks, + block_device_mapping=block_device_mapping, + tags=tags) return instances, reservation_id @@ -1907,16 +1852,13 @@ class API(base.Base): # guaranteed everyone is using cellsv2. pass - if (inst_map is None or inst_map.cell_mapping is None or - CONF.cells.enable): + if inst_map is None or inst_map.cell_mapping is None: # If inst_map is None then the deployment has not migrated to # cellsv2 yet. # If inst_map.cell_mapping is None then the instance is not in a # cell yet. Until instance creation moves to the conductor the # instance can be found in the configured database, so attempt # to look it up. - # If we're on cellsv1, we can't yet short-circuit the cells - # messaging path cell = None try: instance = objects.Instance.get_by_uuid(context, uuid) @@ -2089,12 +2031,6 @@ class API(base.Base): self.consoleauth_rpcapi.delete_tokens_for_instance( context, instance.uuid) - if self.cell_type == 'api': - # NOTE(comstud): If we're in the API cell, we need to - # skip all remaining logic and just call the callback, - # which will cause a cast to the child cell. - cb(context, instance, bdms) - return if not instance.host and not may_have_ports_or_volumes: try: with compute_utils.notify_about_instance_delete( @@ -2271,27 +2207,26 @@ class API(base.Base): delete_type if delete_type != 'soft_delete' else 'delete'): elevated = context.elevated() - if self.cell_type != 'api': - # NOTE(liusheng): In nova-network multi_host scenario,deleting - # network info of the instance may need instance['host'] as - # destination host of RPC call. If instance in - # SHELVED_OFFLOADED state, instance['host'] is None, here, use - # shelved_host as host to deallocate network info and reset - # instance['host'] after that. Here we shouldn't use - # instance.save(), because this will mislead user who may think - # the instance's host has been changed, and actually, the - # instance.host is always None. - orig_host = instance.host - try: - if instance.vm_state == vm_states.SHELVED_OFFLOADED: - sysmeta = getattr(instance, - obj_base.get_attrname( - 'system_metadata')) - instance.host = sysmeta.get('shelved_host') - self.network_api.deallocate_for_instance(elevated, - instance) - finally: - instance.host = orig_host + # NOTE(liusheng): In nova-network multi_host scenario,deleting + # network info of the instance may need instance['host'] as + # destination host of RPC call. If instance in + # SHELVED_OFFLOADED state, instance['host'] is None, here, use + # shelved_host as host to deallocate network info and reset + # instance['host'] after that. Here we shouldn't use + # instance.save(), because this will mislead user who may think + # the instance's host has been changed, and actually, the + # instance.host is always None. + orig_host = instance.host + try: + if instance.vm_state == vm_states.SHELVED_OFFLOADED: + sysmeta = getattr(instance, + obj_base.get_attrname( + 'system_metadata')) + instance.host = sysmeta.get('shelved_host') + self.network_api.deallocate_for_instance(elevated, + instance) + finally: + instance.host = orig_host # cleanup volumes self._local_cleanup_bdm_volumes(bdms, instance, context) @@ -2350,7 +2285,6 @@ class API(base.Base): # NOTE(maoy): we allow delete to be called no matter what vm_state says. @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=None, task_state=None, must_have_launched=True) def soft_delete(self, context, instance): @@ -2367,7 +2301,6 @@ class API(base.Base): task_state=task_states.DELETING) @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=None, task_state=None, must_have_launched=False) def delete(self, context, instance): @@ -2425,7 +2358,6 @@ class API(base.Base): @check_instance_lock @check_instance_host - @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.ERROR]) def stop(self, context, instance, do_cast=True, clean_shutdown=True): """Stop an instance.""" @@ -2433,7 +2365,6 @@ class API(base.Base): @check_instance_lock @check_instance_host - @check_instance_cell @check_instance_state(vm_state=[vm_states.STOPPED]) def start(self, context, instance): """Start an instance.""" @@ -2450,7 +2381,6 @@ class API(base.Base): @check_instance_lock @check_instance_host - @check_instance_cell @check_instance_state(vm_state=vm_states.ALLOW_TRIGGER_CRASH_DUMP) def trigger_crash_dump(self, context, instance): """Trigger crash dump in an instance.""" @@ -2577,11 +2507,6 @@ class API(base.Base): def _get_instance(self, context, instance_uuid, expected_attrs, cell_down_support=False): - # If we're on cellsv1, we need to consult the top-level - # merged replica instead of the cell directly. - if CONF.cells.enable: - return objects.Instance.get_by_uuid(context, instance_uuid, - expected_attrs=expected_attrs) inst_map = self._get_instance_map_or_none(context, instance_uuid) if inst_map and (inst_map.cell_mapping is not None): instance = self._get_instance_from_cell(context, inst_map, @@ -2956,7 +2881,6 @@ class API(base.Base): # NOTE(melwitt): We don't check instance lock for backup because lock is # intended to prevent accidental change/delete of instances - @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED, vm_states.PAUSED, vm_states.SUSPENDED]) def backup(self, context, instance, name, backup_type, rotation, @@ -3001,7 +2925,6 @@ class API(base.Base): # NOTE(melwitt): We don't check instance lock for snapshot because lock is # intended to prevent accidental change/delete of instances - @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED, vm_states.PAUSED, vm_states.SUSPENDED]) def snapshot(self, context, instance, name, extra_properties=None): @@ -3236,7 +3159,6 @@ class API(base.Base): reboot_type='HARD') @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED, vm_states.ERROR]) def rebuild(self, context, instance, image_href, admin_password, @@ -3461,7 +3383,6 @@ class API(base.Base): allowed=total_alloweds) @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=[vm_states.RESIZED]) def revert_resize(self, context, instance): """Reverts a resize, deleting the 'new' instance in the process.""" @@ -3514,7 +3435,6 @@ class API(base.Base): migration.dest_compute) @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=[vm_states.RESIZED]) def confirm_resize(self, context, instance, migration=None): """Confirms a migration/resize and deletes the 'old' instance.""" @@ -3539,28 +3459,7 @@ class API(base.Base): migration, migration.source_compute) - @staticmethod - def _resize_cells_support(context, instance, - current_instance_type, new_instance_type): - """Special API cell logic for resize.""" - # NOTE(johannes/comstud): The API cell needs a local migration - # record for later resize_confirm and resize_reverts. - # We don't need source and/or destination - # information, just the old and new flavors. Status is set to - # 'finished' since nothing else will update the status along - # the way. - mig = objects.Migration(context=context.elevated()) - mig.instance_uuid = instance.uuid - mig.old_instance_type_id = current_instance_type['id'] - mig.new_instance_type_id = new_instance_type['id'] - mig.status = 'finished' - mig.migration_type = ( - mig.old_instance_type_id != mig.new_instance_type_id and - 'resize' or 'migration') - mig.create() - @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED]) def resize(self, context, instance, flavor_id=None, clean_shutdown=True, host_name=None, **extra_instance_updates): @@ -3621,7 +3520,7 @@ class API(base.Base): if not same_instance_type and new_instance_type.get('disabled'): raise exception.FlavorNotFound(flavor_id=flavor_id) - if same_instance_type and flavor_id and self.cell_type != 'compute': + if same_instance_type and flavor_id: raise exception.CannotResizeToSameFlavor() # ensure there is sufficient headroom for upsizes @@ -3665,12 +3564,6 @@ class API(base.Base): instance.update(extra_instance_updates) instance.save(expected_task_state=[None]) - if self.cell_type == 'api': - # Create migration record. - self._resize_cells_support(context, instance, - current_instance_type, - new_instance_type) - if not flavor_id: self._record_action_start(context, instance, instance_actions.MIGRATE) @@ -3767,7 +3660,6 @@ class API(base.Base): instance=instance, address=address) @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE]) def pause(self, context, instance): """Pause the given instance.""" @@ -3777,7 +3669,6 @@ class API(base.Base): self.compute_rpcapi.pause_instance(context, instance) @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=[vm_states.PAUSED]) def unpause(self, context, instance): """Unpause the given instance.""" @@ -3798,7 +3689,6 @@ class API(base.Base): instance=instance) @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE]) def suspend(self, context, instance): """Suspend the given instance.""" @@ -3808,7 +3698,6 @@ class API(base.Base): self.compute_rpcapi.suspend_instance(context, instance) @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=[vm_states.SUSPENDED]) def resume(self, context, instance): """Resume the given instance.""" @@ -3856,7 +3745,6 @@ class API(base.Base): self.compute_rpcapi.unrescue_instance(context, instance=instance) @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE]) def set_admin_password(self, context, instance, password=None): """Set the root/admin password for the given instance. @@ -3896,13 +3784,6 @@ class API(base.Base): return {'url': connect_info['access_url']} - @check_instance_host - def get_vnc_connect_info(self, context, instance, console_type): - """Used in a child cell to get console info.""" - connect_info = self.compute_rpcapi.get_vnc_console(context, - instance=instance, console_type=console_type) - return connect_info - @check_instance_host @reject_instance_state( task_state=[task_states.DELETING, task_states.MIGRATING]) @@ -3923,13 +3804,6 @@ class API(base.Base): return {'url': connect_info['access_url']} - @check_instance_host - def get_spice_connect_info(self, context, instance, console_type): - """Used in a child cell to get console info.""" - connect_info = self.compute_rpcapi.get_spice_console(context, - instance=instance, console_type=console_type) - return connect_info - @check_instance_host @reject_instance_state( task_state=[task_states.DELETING, task_states.MIGRATING]) @@ -3950,13 +3824,6 @@ class API(base.Base): return {'url': connect_info['access_url']} - @check_instance_host - def get_rdp_connect_info(self, context, instance, console_type): - """Used in a child cell to get console info.""" - connect_info = self.compute_rpcapi.get_rdp_console(context, - instance=instance, console_type=console_type) - return connect_info - @check_instance_host @reject_instance_state( task_state=[task_states.DELETING, task_states.MIGRATING]) @@ -3977,13 +3844,6 @@ class API(base.Base): access_url=connect_info['access_url']) return {'url': connect_info['access_url']} - @check_instance_host - def get_serial_console_connect_info(self, context, instance, console_type): - """Used in a child cell to get serial console.""" - connect_info = self.compute_rpcapi.get_serial_console(context, - instance=instance, console_type=console_type) - return connect_info - @check_instance_host @reject_instance_state( task_state=[task_states.DELETING, task_states.MIGRATING]) @@ -4064,13 +3924,11 @@ class API(base.Base): source=fields_obj.NotificationSource.API) @check_instance_lock - @check_instance_cell def reset_network(self, context, instance): """Reset networking on the instance.""" self.compute_rpcapi.reset_network(context, instance=instance) @check_instance_lock - @check_instance_cell def inject_network_info(self, context, instance): """Inject network info for the instance.""" self.compute_rpcapi.inject_network_info(context, instance=instance) @@ -4181,6 +4039,8 @@ class API(base.Base): 'create a new style volume attachment.') self.volume_api.reserve_volume(context, volume_id) + # TODO(stephenfin): Fold this back in now that cells v1 no longer needs to + # override it. def _attach_volume(self, context, instance, volume, device, disk_bus, device_type, tag=None, supports_multiattach=False): @@ -4318,6 +4178,8 @@ class API(base.Base): disk_bus, device_type, tag=tag, supports_multiattach=supports_multiattach) + # TODO(stephenfin): Fold this back in now that cells v1 no longer needs to + # override it. def _detach_volume(self, context, instance, volume): """Detach volume from instance. @@ -4520,7 +4382,6 @@ class API(base.Base): return _metadata @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED]) def live_migrate(self, context, instance, block_migration, disk_over_commit, host_name, force=None, async_=False): @@ -4584,7 +4445,6 @@ class API(base.Base): messaging_timeout) @check_instance_lock - @check_instance_cell @check_instance_state(vm_state=[vm_states.ACTIVE], task_state=[task_states.MIGRATING]) def live_migrate_force_complete(self, context, instance, migration_id): @@ -4616,7 +4476,6 @@ class API(base.Base): context, instance, migration) @check_instance_lock - @check_instance_cell @check_instance_state(task_state=[task_states.MIGRATING]) def live_migrate_abort(self, context, instance, migration_id, support_abort_in_queue=False): diff --git a/nova/compute/cells_api.py b/nova/compute/cells_api.py deleted file mode 100644 index 109e1aec1c..0000000000 --- a/nova/compute/cells_api.py +++ /dev/null @@ -1,683 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# Copyright 2013 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Compute API that proxies via Cells Service. - -This relates to cells v1. This layer is basically responsible for intercepting -compute/api calls at the top layer and forwarding to the child cell to be -replayed there. -""" - -import oslo_messaging as messaging -from oslo_utils import excutils - -from nova import availability_zones -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 rpcapi as compute_rpcapi -from nova.compute import task_states -from nova.compute import vm_states -from nova import exception -from nova.i18n import _ -from nova import objects -from nova.objects import base as obj_base -from nova import rpc - - -check_instance_state = compute_api.check_instance_state -reject_instance_state = compute_api.reject_instance_state -check_instance_lock = compute_api.check_instance_lock -check_instance_cell = compute_api.check_instance_cell - - -class ComputeRPCAPIRedirect(object): - # NOTE(comstud): These are a list of methods where the cells_rpcapi - # and the compute_rpcapi methods have the same signatures. This - # is for transitioning to a common interface where we can just - # swap out the compute_rpcapi class with the cells_rpcapi class. - cells_compatible = ['start_instance', 'stop_instance', - 'reboot_instance', 'suspend_instance', - 'resume_instance', 'terminate_instance', - 'soft_delete_instance', 'pause_instance', - 'unpause_instance', 'revert_resize', - 'confirm_resize', 'reset_network', - 'inject_network_info', - 'backup_instance', 'snapshot_instance', - 'set_admin_password'] - - def __init__(self, cells_rpcapi): - self.cells_rpcapi = cells_rpcapi - - def __getattr__(self, key): - if key in self.cells_compatible: - return getattr(self.cells_rpcapi, key) - - def _noop_rpc_wrapper(*args, **kwargs): - return None - return _noop_rpc_wrapper - - -class ConductorTaskRPCAPIRedirect(object): - # NOTE(comstud): These are a list of methods where the cells_rpcapi - # and the compute_task_rpcapi methods have the same signatures. This - # is for transitioning to a common interface where we can just - # swap out the compute_task_rpcapi class with the cells_rpcapi class. - cells_compatible = ['build_instances', 'resize_instance', - 'live_migrate_instance', 'rebuild_instance'] - - def __init__(self, cells_rpcapi_obj): - self.cells_rpcapi = cells_rpcapi_obj - - def __getattr__(self, key): - if key in self.cells_compatible: - return getattr(self.cells_rpcapi, key) - - def _noop_rpc_wrapper(*args, **kwargs): - return None - return _noop_rpc_wrapper - - -class RPCClientCellsProxy(object): - - def __init__(self, target, version_cap): - super(RPCClientCellsProxy, self).__init__() - - self.target = target - self.version_cap = version_cap - self._server = None - self._version = None - - self.cells_rpcapi = cells_rpcapi.CellsAPI() - - def prepare(self, **kwargs): - ret = type(self)(self.target, self.version_cap) - ret.cells_rpcapi = self.cells_rpcapi - - server = kwargs.pop('server', None) - version = kwargs.pop('version', None) - - if kwargs: - raise ValueError(_("Unsupported kwargs: %s") % kwargs.keys()) - - if server: - ret._server = server - if version: - ret._version = version - - return ret - - def _check_version_cap(self, version): - client = rpc.get_client(self.target, version_cap=self.version_cap) - if not client.can_send_version(version): - raise messaging.RPCVersionCapError(version=version, - version_cap=self.version_cap) - - def _make_msg(self, method, **kwargs): - version = self._version if self._version else self.target.version - self._check_version_cap(version) - return { - 'method': method, - 'namespace': None, - 'version': version, - 'args': kwargs - } - - def _get_topic(self): - if self._server is not None: - return '%s.%s' % (self.target.topic, self._server) - else: - return self.target.topic - - def can_send_version(self, version): - client = rpc.get_client(self.target, version_cap=self.version_cap) - return client.can_send_version(version) - - def cast(self, ctxt, method, **kwargs): - msg = self._make_msg(method, **kwargs) - topic = self._get_topic() - self.cells_rpcapi.proxy_rpc_to_manager(ctxt, msg, topic) - - def call(self, ctxt, method, **kwargs): - msg = self._make_msg(method, **kwargs) - topic = self._get_topic() - return self.cells_rpcapi.proxy_rpc_to_manager(ctxt, msg, - topic, call=True) - - -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 get_client(self, target, version_cap, serializer): - return RPCClientCellsProxy(target, version_cap) - - -class ComputeCellsAPI(compute_api.API): - def __init__(self, *args, **kwargs): - super(ComputeCellsAPI, self).__init__(*args, **kwargs) - self.cells_rpcapi = cells_rpcapi.CellsAPI() - # Avoid casts/calls directly to compute - self.compute_rpcapi = ComputeRPCAPIRedirect(self.cells_rpcapi) - # Redirect conductor build_instances to cells - self.compute_task_api = ConductorTaskRPCAPIRedirect(self.cells_rpcapi) - self._cell_type = 'api' - - def _cast_to_cells(self, context, instance, method, *args, **kwargs): - instance_uuid = instance.uuid - cell_name = instance.cell_name - if not cell_name: - raise exception.InstanceUnknownCell(instance_uuid=instance_uuid) - self.cells_rpcapi.cast_compute_api_method(context, cell_name, - method, instance_uuid, *args, **kwargs) - - def _call_to_cells(self, context, instance, method, *args, **kwargs): - instance_uuid = instance.uuid - cell_name = instance.cell_name - if not cell_name: - raise exception.InstanceUnknownCell(instance_uuid=instance_uuid) - return self.cells_rpcapi.call_compute_api_method(context, cell_name, - method, instance_uuid, *args, **kwargs) - - def _check_requested_networks(self, context, requested_networks, - max_count): - """Override compute API's checking of this. It'll happen in - child cell - """ - return max_count - - def create(self, *args, **kwargs): - """We can use the base functionality, but I left this here just - for completeness. - """ - return super(ComputeCellsAPI, self).create(*args, **kwargs) - - def _create_block_device_mapping(self, *args, **kwargs): - """Don't create block device mappings in the API cell. - - The child cell will create it and propagate it up to the parent cell. - """ - pass - - def force_delete(self, context, instance): - self._handle_cell_delete(context, instance, 'force_delete') - - def soft_delete(self, context, instance): - self._handle_cell_delete(context, instance, 'soft_delete') - - def delete(self, context, instance): - self._handle_cell_delete(context, instance, 'delete') - - def _handle_cell_delete(self, context, instance, method_name): - if not instance.cell_name: - delete_type = method_name == 'soft_delete' and 'soft' or 'hard' - self.cells_rpcapi.instance_delete_everywhere(context, - instance, delete_type) - # NOTE(danms): If we try to delete an instance with no cell, - # there isn't anything to salvage, so we can hard-delete here. - try: - if self._delete_while_booting(context, instance): - return - except exception.ObjectActionError: - # NOTE(alaski): We very likely got here because the host - # constraint in instance.destroy() failed. This likely means - # that an update came up from a child cell and cell_name is - # set now. We handle this similarly to how the - # ObjectActionError is handled below. - with excutils.save_and_reraise_exception() as exc: - _cell, instance = self._lookup_instance(context, - instance.uuid) - if instance is None: - exc.reraise = False - elif instance.cell_name: - exc.reraise = False - self._handle_cell_delete(context, instance, - method_name) - return - # If instance.cell_name was not set it's possible that the Instance - # object here was pulled from a BuildRequest object and is not - # fully populated. Notably it will be missing an 'id' field which - # will prevent instance.destroy from functioning properly. A - # lookup is attempted which will either return a full Instance or - # None if not found. If not found then it's acceptable to skip the - # rest of the delete processing. - _cell, instance = self._lookup_instance(context, instance.uuid) - if instance is None: - # Instance has been deleted out from under us - return - - bdms = objects.BlockDeviceMappingList.get_by_instance_uuid( - context, instance.uuid) - try: - super(ComputeCellsAPI, self)._local_delete(context, instance, - bdms, method_name, - self._do_delete) - except exception.ObjectActionError: - # NOTE(alaski): We very likely got here because the host - # constraint in instance.destroy() failed. This likely means - # that an update came up from a child cell and cell_name is - # set now. If so try the delete again. - with excutils.save_and_reraise_exception() as exc: - instance.refresh() - if instance.cell_name: - exc.reraise = False - self._handle_cell_delete(context, instance, - method_name) - return - - method = getattr(super(ComputeCellsAPI, self), method_name) - method(context, instance) - - @check_instance_cell - def restore(self, context, instance): - """Restore a previously deleted (but not reclaimed) instance.""" - super(ComputeCellsAPI, self).restore(context, instance) - self._cast_to_cells(context, instance, 'restore') - - @check_instance_cell - def evacuate(self, context, instance, host, *args, **kwargs): - """Evacuate the given instance with the provided attributes.""" - if host: - cell_path, host = cells_utils.split_cell_and_item(host) - self._cast_to_cells(context, instance, 'evacuate', - host, *args, **kwargs) - - @check_instance_cell - def add_fixed_ip(self, context, instance, *args, **kwargs): - """Add fixed_ip from specified network to given instance.""" - super(ComputeCellsAPI, self).add_fixed_ip(context, instance, - *args, **kwargs) - self._cast_to_cells(context, instance, 'add_fixed_ip', - *args, **kwargs) - - @check_instance_cell - def remove_fixed_ip(self, context, instance, *args, **kwargs): - """Remove fixed_ip from specified network to given instance.""" - super(ComputeCellsAPI, self).remove_fixed_ip(context, instance, - *args, **kwargs) - self._cast_to_cells(context, instance, 'remove_fixed_ip', - *args, **kwargs) - - def get_diagnostics(self, context, instance): - """Retrieve diagnostics for the given instance.""" - # FIXME(comstud): Cache this? - # Also: only calling super() to get state/policy checking - super(ComputeCellsAPI, self).get_diagnostics(context, instance) - return self._call_to_cells(context, instance, 'get_diagnostics') - - def get_instance_diagnostics(self, context, instance): - """Retrieve diagnostics for the given instance.""" - # FIXME(comstud): Cache this? - # Also: only calling super() to get state/policy checking - super(ComputeCellsAPI, self).get_instance_diagnostics(context, - instance) - return self._call_to_cells(context, instance, - 'get_instance_diagnostics') - - @check_instance_cell - def rescue(self, context, instance, rescue_password=None, - rescue_image_ref=None, clean_shutdown=True): - """Rescue the given instance.""" - super(ComputeCellsAPI, self).rescue(context, instance, - rescue_password=rescue_password, - rescue_image_ref=rescue_image_ref, - clean_shutdown=clean_shutdown) - self._cast_to_cells(context, instance, 'rescue', - rescue_password=rescue_password, - rescue_image_ref=rescue_image_ref, - clean_shutdown=clean_shutdown) - - @check_instance_cell - def unrescue(self, context, instance): - """Unrescue the given instance.""" - super(ComputeCellsAPI, self).unrescue(context, instance) - self._cast_to_cells(context, instance, 'unrescue') - - @check_instance_cell - @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED, - vm_states.PAUSED, vm_states.SUSPENDED]) - def shelve(self, context, instance, clean_shutdown=True): - """Shelve the given instance.""" - self._cast_to_cells(context, instance, 'shelve', - clean_shutdown=clean_shutdown) - - @check_instance_cell - def shelve_offload(self, context, instance, clean_shutdown=True): - """Offload the shelved instance.""" - super(ComputeCellsAPI, self).shelve_offload(context, instance, - clean_shutdown=clean_shutdown) - self._cast_to_cells(context, instance, 'shelve_offload', - clean_shutdown=clean_shutdown) - - @check_instance_cell - @check_instance_state(vm_state=[vm_states.SHELVED, - vm_states.SHELVED_OFFLOADED]) - def unshelve(self, context, instance): - """Unshelve the given instance.""" - self._cast_to_cells(context, instance, 'unshelve') - - @check_instance_cell - @reject_instance_state( - task_state=[task_states.DELETING, task_states.MIGRATING]) - def get_vnc_console(self, context, instance, console_type): - """Get a url to a VNC Console.""" - if not instance.host: - raise exception.InstanceNotReady(instance_id=instance.uuid) - - connect_info = self._call_to_cells(context, instance, - 'get_vnc_connect_info', console_type) - - self.consoleauth_rpcapi.authorize_console(context, - connect_info['token'], console_type, connect_info['host'], - connect_info['port'], connect_info['internal_access_path'], - instance.uuid, access_url=connect_info['access_url']) - return {'url': connect_info['access_url']} - - @check_instance_cell - @reject_instance_state( - task_state=[task_states.DELETING, task_states.MIGRATING]) - def get_spice_console(self, context, instance, console_type): - """Get a url to a SPICE Console.""" - if not instance.host: - raise exception.InstanceNotReady(instance_id=instance.uuid) - - connect_info = self._call_to_cells(context, instance, - 'get_spice_connect_info', console_type) - - self.consoleauth_rpcapi.authorize_console(context, - connect_info['token'], console_type, connect_info['host'], - connect_info['port'], connect_info['internal_access_path'], - instance.uuid, access_url=connect_info['access_url']) - return {'url': connect_info['access_url']} - - @check_instance_cell - @reject_instance_state( - task_state=[task_states.DELETING, task_states.MIGRATING]) - def get_rdp_console(self, context, instance, console_type): - """Get a url to a RDP Console.""" - if not instance.host: - raise exception.InstanceNotReady(instance_id=instance.uuid) - - connect_info = self._call_to_cells(context, instance, - 'get_rdp_connect_info', console_type) - - self.consoleauth_rpcapi.authorize_console(context, - connect_info['token'], console_type, connect_info['host'], - connect_info['port'], connect_info['internal_access_path'], - instance.uuid, access_url=connect_info['access_url']) - return {'url': connect_info['access_url']} - - @check_instance_cell - @reject_instance_state( - task_state=[task_states.DELETING, task_states.MIGRATING]) - def get_serial_console(self, context, instance, console_type): - """Get a url to a serial console.""" - if not instance.host: - raise exception.InstanceNotReady(instance_id=instance.uuid) - - connect_info = self._call_to_cells(context, instance, - 'get_serial_console_connect_info', console_type) - - self.consoleauth_rpcapi.authorize_console(context, - connect_info['token'], console_type, connect_info['host'], - connect_info['port'], connect_info['internal_access_path'], - instance.uuid, access_url=connect_info['access_url']) - return {'url': connect_info['access_url']} - - @check_instance_cell - def get_console_output(self, context, instance, *args, **kwargs): - """Get console output for an instance.""" - # NOTE(comstud): Calling super() just to get policy check - super(ComputeCellsAPI, self).get_console_output(context, instance, - *args, **kwargs) - return self._call_to_cells(context, instance, 'get_console_output', - *args, **kwargs) - - @check_instance_cell - def _attach_volume(self, context, instance, volume, device, - disk_bus, device_type, tag=None, - supports_multiattach=False): - """Attach an existing volume to an existing instance.""" - if tag: - raise exception.VolumeTaggedAttachNotSupported() - if volume['multiattach']: - # We don't support multiattach volumes with cells v1. - raise exception.MultiattachSupportNotYetAvailable() - self.volume_api.check_availability_zone(context, volume, - instance=instance) - - return self._call_to_cells(context, instance, 'attach_volume', - volume['id'], device, disk_bus, device_type) - - @check_instance_cell - def _detach_volume(self, context, instance, volume): - """Detach a volume from an instance.""" - self._cast_to_cells(context, instance, 'detach_volume', - volume) - - @check_instance_cell - def associate_floating_ip(self, context, instance, address): - """Makes calls to network_api to associate_floating_ip. - - :param address: is a string floating ip address - """ - self._cast_to_cells(context, instance, 'associate_floating_ip', - address) - - @check_instance_cell - def delete_instance_metadata(self, context, instance, key): - """Delete the given metadata item from an instance.""" - super(ComputeCellsAPI, self).delete_instance_metadata(context, - instance, key) - self._cast_to_cells(context, instance, 'delete_instance_metadata', - key) - - @check_instance_cell - def update_instance_metadata(self, context, instance, - metadata, delete=False): - rv = super(ComputeCellsAPI, self).update_instance_metadata(context, - instance, metadata, delete=delete) - try: - self._cast_to_cells(context, instance, - 'update_instance_metadata', - metadata, delete=delete) - except exception.InstanceUnknownCell: - pass - return rv - - def get_migrations(self, context, filters): - return self.cells_rpcapi.get_migrations(context, filters) - - -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, must_be_up=False): - """Cannot check this in API cell. This will be checked in the - target child cell. - """ - pass - - def set_host_enabled(self, context, host_name, enabled): - try: - result = super(HostAPI, self).set_host_enabled(context, host_name, - enabled) - except exception.CellRoutingInconsistency: - raise exception.HostNotFound(host=host_name) - - return result - - def host_power_action(self, context, host_name, action): - try: - result = super(HostAPI, self).host_power_action(context, host_name, - action) - except exception.CellRoutingInconsistency: - raise exception.HostNotFound(host=host_name) - - return result - - def get_host_uptime(self, context, host_name): - """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, - all_cells=False, cell_down_support=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: - 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: - # TODO(sbauza): set_availability_zones returns flat dicts, - # we should rather modify the RPC API to amend service_get_all by - # adding a set_zones argument - 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] - - # NOTE(sbauza): As services is a list of flat dicts, we need to - # rehydrate the corresponding ServiceProxy objects - cell_paths = [] - for service in services: - cell_path, id = cells_utils.split_cell_and_item(service['id']) - cell_path, host = cells_utils.split_cell_and_item( - service['host']) - service['id'] = id - service['host'] = host - cell_paths.append(cell_path) - services = obj_base.obj_make_list(context, - objects.ServiceList(), - objects.Service, - services) - services = [cells_utils.ServiceProxy(s, c) - for s, c in zip(services, cell_paths)] - - return services - - def service_get_by_compute_host(self, context, host_name): - try: - return self.cells_rpcapi.service_get_by_compute_host(context, - host_name) - except exception.CellRoutingInconsistency: - raise exception.ComputeHostNotFound(host=host_name) - - def service_update(self, context, host_name, binary, params_to_update): - """Used to enable/disable a service. For compute services, setting to - disabled stops new builds arriving on that host. - - :param host_name: the name of the host machine that the service is - running - :param binary: The name of the executable that the service runs as - :param params_to_update: eg. {'disabled': True} - """ - return self.cells_rpcapi.service_update( - context, host_name, binary, params_to_update) - - def service_delete(self, context, service_id): - """Deletes the specified service.""" - self.cells_rpcapi.service_delete(context, service_id) - - 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. - """ - cell_name, host_name = cells_utils.split_cell_and_item(host_name) - 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 - - def task_log_get_all(self, context, task_name, beginning, ending, - host=None, state=None): - """Return the task logs within a given range from cells, - optionally filtering by the host and/or state. For cells, the - host should be a path like 'path!to!cell@host'. If no @host - is given, only task logs from a particular cell will be returned. - """ - return self.cells_rpcapi.task_log_get_all(context, - task_name, - beginning, - ending, - host=host, - state=state) - - def compute_node_get(self, context, compute_id): - """Get a compute node from a particular cell by its integer ID or UUID. - compute_id should be in the format of 'path!to!cell@ID'. - """ - try: - return self.cells_rpcapi.compute_node_get(context, compute_id) - except exception.CellRoutingInconsistency: - raise exception.ComputeHostNotFound(host=compute_id) - - def compute_node_get_all(self, context, limit=None, marker=None): - # NOTE(lyj): No pagination for cells, just make sure the arguments - # for the method are the same with the compute.api for now. - return self.cells_rpcapi.compute_node_get_all(context) - - def compute_node_search_by_hypervisor(self, context, hypervisor_match): - return self.cells_rpcapi.compute_node_get_all(context, - hypervisor_match=hypervisor_match) - - def compute_node_statistics(self, context): - return self.cells_rpcapi.compute_node_stats(context) - - -class InstanceActionAPI(compute_api.InstanceActionAPI): - """InstanceActionAPI() class for cells.""" - def __init__(self): - super(InstanceActionAPI, self).__init__() - self.cells_rpcapi = cells_rpcapi.CellsAPI() - - def actions_get(self, context, instance, limit=None, marker=None, - filters=None): - # Paging and filtering isn't supported in cells v1. - return self.cells_rpcapi.actions_get(context, instance) - - def action_get_by_request_id(self, context, instance, request_id): - return self.cells_rpcapi.action_get_by_request_id(context, instance, - request_id) - - def action_events_get(self, context, instance, action_id): - return self.cells_rpcapi.action_events_get(context, instance, - action_id) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 9ef1346631..9dba2dfbba 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -56,7 +56,6 @@ import six from six.moves import range from nova import block_device -from nova.cells import rpcapi as cells_rpcapi from nova import compute from nova.compute import build_results from nova.compute import claims @@ -507,7 +506,6 @@ class ComputeManager(manager.Manager): self.compute_task_api = conductor.ComputeTaskAPI() self.is_neutron_security_groups = ( openstack_driver.is_neutron_security_groups()) - self.cells_rpcapi = cells_rpcapi.CellsAPI() self.query_client = query.SchedulerQueryClient() self.instance_events = InstanceEvents() self._sync_power_pool = eventlet.GreenPool( diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py index bfd6a3b6ae..0ebc459a0e 100644 --- a/nova/compute/rpcapi.py +++ b/nova/compute/rpcapi.py @@ -462,7 +462,6 @@ class ComputeAPI(object): 'service': service_version}) return version_cap - # Cells overrides this def get_client(self, target, version_cap, serializer): if CONF.rpc_response_timeout > rpc.HEARTBEAT_THRESHOLD: # NOTE(danms): If the operator has overridden RPC timeout diff --git a/nova/exception.py b/nova/exception.py index 61839fd1e1..6f5a7f7dc9 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -1281,30 +1281,10 @@ class CellExists(NovaException): msg_fmt = _("Cell with name %(name)s already exists.") -class CellRoutingInconsistency(NovaException): - msg_fmt = _("Inconsistency in cell routing: %(reason)s") - - -class CellServiceAPIMethodNotFound(NotFound): - msg_fmt = _("Service API method not found: %(detail)s") - - class CellTimeout(NotFound): msg_fmt = _("Timeout waiting for response from cell") -class CellMaxHopCountReached(NovaException): - msg_fmt = _("Cell message has reached maximum hop count: %(hop_count)s") - - -class NoCellsAvailable(NovaException): - msg_fmt = _("No cells available matching scheduling criteria.") - - -class CellsUpdateUnsupported(NovaException): - msg_fmt = _("Cannot update cells configuration file.") - - class InstanceUnknownCell(NotFound): msg_fmt = _("Cell is not known for instance %(instance_uuid)s") diff --git a/nova/notifications/objects/base.py b/nova/notifications/objects/base.py index 3753d50da6..18485d583f 100644 --- a/nova/notifications/objects/base.py +++ b/nova/notifications/objects/base.py @@ -172,6 +172,8 @@ class NotificationPublisher(NotificationObject): # 2.2: New enum for source fields added VERSION = '2.2' + # TODO(stephenfin): Remove 'nova-cells' from 'NotificationSourceField' enum + # when bumping this object to version 3.0 fields = { 'host': fields.StringField(nullable=False), 'source': fields.NotificationSourceField(nullable=False), diff --git a/nova/objects/fields.py b/nova/objects/fields.py index 02056e21b0..041ba64fef 100644 --- a/nova/objects/fields.py +++ b/nova/objects/fields.py @@ -766,6 +766,8 @@ class NotificationSource(BaseNovaEnum): SCHEDULER = 'nova-scheduler' NETWORK = 'nova-network' CONSOLEAUTH = 'nova-consoleauth' + # TODO(stephenfin): Remove when 'NotificationPublisher' object version is + # bumped to 3.0 CELLS = 'nova-cells' CONSOLE = 'nova-console' METADATA = 'nova-metadata' diff --git a/nova/policies/__init__.py b/nova/policies/__init__.py index 8a27447c7f..b31b350fea 100644 --- a/nova/policies/__init__.py +++ b/nova/policies/__init__.py @@ -22,7 +22,6 @@ from nova.policies import attach_interfaces from nova.policies import availability_zone from nova.policies import baremetal_nodes from nova.policies import base -from nova.policies import cells_scheduler from nova.policies import console_auth_tokens from nova.policies import console_output from nova.policies import consoles @@ -85,7 +84,6 @@ def list_rules(): attach_interfaces.list_rules(), availability_zone.list_rules(), baremetal_nodes.list_rules(), - cells_scheduler.list_rules(), console_auth_tokens.list_rules(), console_output.list_rules(), consoles.list_rules(), diff --git a/nova/policies/cells_scheduler.py b/nova/policies/cells_scheduler.py deleted file mode 100644 index 06d68bd1bf..0000000000 --- a/nova/policies/cells_scheduler.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2016 Cloudbase Solutions Srl -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslo_policy import policy - - -POLICY_ROOT = 'cells_scheduler_filter:%s' - - -cells_scheduler_policies = [ - policy.RuleDefault( - POLICY_ROOT % 'DifferentCellFilter', - 'is_admin:True', - """Different cell filter to route a build away from a particular cell - -This policy is read by nova-scheduler process. -"""), - policy.RuleDefault( - POLICY_ROOT % 'TargetCellFilter', - 'is_admin:True', - """Target cell filter to route a build to a particular cell - -This policy is read by nova-scheduler process. -""") -] - - -def list_rules(): - return cells_scheduler_policies diff --git a/nova/tests/unit/api/openstack/compute/test_services.py b/nova/tests/unit/api/openstack/compute/test_services.py index 458e673956..8492752a2f 100644 --- a/nova/tests/unit/api/openstack/compute/test_services.py +++ b/nova/tests/unit/api/openstack/compute/test_services.py @@ -16,7 +16,6 @@ import copy import datetime -import iso8601 import mock from oslo_utils import fixture as utils_fixture from oslo_utils.fixture import uuidsentinel @@ -26,7 +25,6 @@ import webob.exc from nova.api.openstack.compute import services as services_v21 from nova.api.openstack import wsgi as os_wsgi from nova import availability_zones -from nova.cells import utils as cells_utils from nova import compute from nova import context from nova import exception @@ -1317,78 +1315,6 @@ class ServicesTestV253(test.TestCase): six.text_type(ex)) -class ServicesCellsTestV21(test.TestCase): - - def setUp(self): - super(ServicesCellsTestV21, self).setUp() - - host_api = compute.cells_api.HostAPI() - - self._set_up_controller() - self.controller.host_api = host_api - - self.useFixture(utils_fixture.TimeFixture(fake_utcnow())) - - services_list = [] - for service in fake_services_list: - service = service.copy() - del service['version'] - service_obj = objects.Service(**service) - service_proxy = cells_utils.ServiceProxy(service_obj, 'cell1') - services_list.append(service_proxy) - - host_api.cells_rpcapi.service_get_all = ( - mock.Mock(side_effect=fake_service_get_all(services_list))) - - def _set_up_controller(self): - self.controller = services_v21.ServiceController() - - def _process_out(self, res_dict): - for res in res_dict['services']: - res.pop('disabled_reason') - - def test_services_detail(self): - req = fakes.HTTPRequest.blank('/fake/services', - use_admin_context=True) - res_dict = self.controller.index(req) - utc = iso8601.UTC - response = {'services': [ - {'id': 'cell1@1', - 'binary': 'nova-scheduler', - 'host': 'cell1@host1', - 'zone': 'internal', - 'status': 'disabled', - 'state': 'up', - 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2, - tzinfo=utc)}, - {'id': 'cell1@2', - 'binary': 'nova-compute', - 'host': 'cell1@host1', - 'zone': 'nova', - 'status': 'disabled', - 'state': 'up', - 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5, - tzinfo=utc)}, - {'id': 'cell1@3', - 'binary': 'nova-scheduler', - 'host': 'cell1@host2', - 'zone': 'internal', - 'status': 'enabled', - 'state': 'down', - 'updated_at': datetime.datetime(2012, 9, 19, 6, 55, 34, - tzinfo=utc)}, - {'id': 'cell1@4', - 'binary': 'nova-compute', - 'host': 'cell1@host2', - 'zone': 'nova', - 'status': 'disabled', - 'state': 'down', - 'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38, - tzinfo=utc)}]} - self._process_out(res_dict) - self.assertEqual(response, res_dict) - - class ServicesPolicyEnforcementV21(test.NoDBTestCase): def setUp(self): diff --git a/nova/tests/unit/cells/__init__.py b/nova/tests/unit/cells/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/nova/tests/unit/cells/fakes.py b/nova/tests/unit/cells/fakes.py deleted file mode 100644 index 7135f14f46..0000000000 --- a/nova/tests/unit/cells/fakes.py +++ /dev/null @@ -1,213 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Fakes For Cells tests. -""" - -from nova.cells import driver -from nova.cells import manager as cells_manager -from nova.cells import state as cells_state -from nova.cells import utils as cells_utils -import nova.conf -import nova.db.api -from nova.db import base -from nova import exception -from nova import objects - -CONF = nova.conf.CONF - - -# Fake Cell Hierarchy -FAKE_TOP_LEVEL_CELL_NAME = 'api-cell' -FAKE_CELL_LAYOUT = [{'child-cell1': []}, - {'child-cell2': [{'grandchild-cell1': []}]}, - {'child-cell3': [{'grandchild-cell2': []}, - {'grandchild-cell3': []}]}, - {'child-cell4': []}] - -# build_cell_stub_infos() below will take the above layout and create -# a fake view of the DB from the perspective of each of the cells. -# For each cell, a CellStubInfo will be created with this info. -CELL_NAME_TO_STUB_INFO = {} - - -class FakeDBApi(object): - """Cells uses a different DB in each cell. This means in order to - stub out things differently per cell, I need to create a fake DBApi - object that is instantiated by each fake cell. - """ - def __init__(self, cell_db_entries): - self.cell_db_entries = cell_db_entries - - def __getattr__(self, key): - return getattr(nova.db.api, key) - - def cell_get_all(self, ctxt): - return self.cell_db_entries - - def instance_get_all_by_filters(self, ctxt, *args, **kwargs): - return [] - - def instance_get_by_uuid(self, ctxt, instance_uuid): - raise exception.InstanceNotFound(instance_id=instance_uuid) - - -class FakeCellsDriver(driver.BaseCellsDriver): - pass - - -class FakeCellState(cells_state.CellState): - def send_message(self, message): - message_runner = get_message_runner(self.name) - orig_ctxt = message.ctxt - json_message = message.to_json() - message = message_runner.message_from_json(json_message) - # Restore this so we can use mox and verify same context - message.ctxt = orig_ctxt - message.process() - - -class FakeCellStateManager(cells_state.CellStateManagerDB): - def __init__(self, *args, **kwargs): - super(FakeCellStateManager, self).__init__(*args, - cell_state_cls=FakeCellState, **kwargs) - - -class FakeCellsManager(cells_manager.CellsManager): - def __init__(self, *args, **kwargs): - super(FakeCellsManager, self).__init__(*args, - cell_state_manager=FakeCellStateManager, - **kwargs) - - -class CellStubInfo(object): - def __init__(self, test_case, cell_name, db_entries): - self.test_case = test_case - self.cell_name = cell_name - self.db_entries = db_entries - - def fake_base_init(_self, *args, **kwargs): - _self.db = FakeDBApi(db_entries) - - @staticmethod - def _fake_compute_node_get_all(context): - return [] - - @staticmethod - def _fake_service_get_by_binary(context, binary): - return [] - - test_case.stubs.Set(base.Base, '__init__', fake_base_init) - test_case.stubs.Set(objects.ComputeNodeList, 'get_all', - _fake_compute_node_get_all) - test_case.stubs.Set(objects.ServiceList, 'get_by_binary', - _fake_service_get_by_binary) - self.cells_manager = FakeCellsManager() - # Fix the cell name, as it normally uses CONF.cells.name - msg_runner = self.cells_manager.msg_runner - msg_runner.our_name = self.cell_name - self.cells_manager.state_manager.my_cell_state.name = self.cell_name - - -def _build_cell_transport_url(cur_db_id): - username = 'username%s' % cur_db_id - password = 'password%s' % cur_db_id - hostname = 'rpc_host%s' % cur_db_id - port = 3090 + cur_db_id - virtual_host = 'rpc_vhost%s' % cur_db_id - - return 'rabbit://%s:%s@%s:%s/%s' % (username, password, hostname, port, - virtual_host) - - -def _build_cell_stub_info(test_case, our_name, parent_path, children): - cell_db_entries = [] - cur_db_id = 1 - sep_char = cells_utils.PATH_CELL_SEP - if parent_path: - cell_db_entries.append( - dict(id=cur_db_id, - name=parent_path.split(sep_char)[-1], - is_parent=True, - transport_url=_build_cell_transport_url(cur_db_id))) - cur_db_id += 1 - our_path = parent_path + sep_char + our_name - else: - our_path = our_name - for child in children: - for child_name, grandchildren in child.items(): - _build_cell_stub_info(test_case, child_name, our_path, - grandchildren) - cell_entry = dict(id=cur_db_id, - name=child_name, - transport_url=_build_cell_transport_url( - cur_db_id), - is_parent=False) - cell_db_entries.append(cell_entry) - cur_db_id += 1 - stub_info = CellStubInfo(test_case, our_name, cell_db_entries) - CELL_NAME_TO_STUB_INFO[our_name] = stub_info - - -def _build_cell_stub_infos(test_case): - _build_cell_stub_info(test_case, FAKE_TOP_LEVEL_CELL_NAME, '', - FAKE_CELL_LAYOUT) - - -def init(test_case): - global CELL_NAME_TO_STUB_INFO - CELL_NAME_TO_STUB_INFO = {} - _build_cell_stub_infos(test_case) - - -def _get_cell_stub_info(cell_name): - return CELL_NAME_TO_STUB_INFO[cell_name] - - -def get_state_manager(cell_name): - return _get_cell_stub_info(cell_name).cells_manager.state_manager - - -def get_cell_state(cur_cell_name, tgt_cell_name): - state_manager = get_state_manager(cur_cell_name) - cell = state_manager.child_cells.get(tgt_cell_name) - if cell is None: - cell = state_manager.parent_cells.get(tgt_cell_name) - return cell - - -def get_cells_manager(cell_name): - return _get_cell_stub_info(cell_name).cells_manager - - -def get_message_runner(cell_name): - return _get_cell_stub_info(cell_name).cells_manager.msg_runner - - -def stub_tgt_method(test_case, cell_name, method_name, method): - msg_runner = get_message_runner(cell_name) - tgt_msg_methods = msg_runner.methods_by_type['targeted'] - setattr(tgt_msg_methods, method_name, method) - - -def stub_bcast_method(test_case, cell_name, method_name, method): - msg_runner = get_message_runner(cell_name) - tgt_msg_methods = msg_runner.methods_by_type['broadcast'] - setattr(tgt_msg_methods, method_name, method) - - -def stub_bcast_methods(test_case, method_name, method): - for cell_name in CELL_NAME_TO_STUB_INFO.keys(): - stub_bcast_method(test_case, cell_name, method_name, method) diff --git a/nova/tests/unit/cells/test_cells_filters.py b/nova/tests/unit/cells/test_cells_filters.py deleted file mode 100644 index b6c697ddec..0000000000 --- a/nova/tests/unit/cells/test_cells_filters.py +++ /dev/null @@ -1,230 +0,0 @@ -# Copyright (c) 2012-2013 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Unit Tests for cells scheduler filters. -""" - -from nova.cells import filters -from nova.cells import state -from nova import context -from nova.db.sqlalchemy import models -from nova import test -from nova.tests.unit.cells import fakes - - -class FiltersTestCase(test.NoDBTestCase): - """Makes sure the proper filters are in the directory.""" - - def test_all_filters(self): - filter_classes = filters.all_filters() - class_names = [cls.__name__ for cls in filter_classes] - self.assertIn("TargetCellFilter", class_names) - self.assertIn("DifferentCellFilter", class_names) - - -class _FilterTestClass(test.NoDBTestCase): - """Base class for testing individual filter plugins.""" - filter_cls_name = None - - def setUp(self): - super(_FilterTestClass, self).setUp() - fakes.init(self) - self.msg_runner = fakes.get_message_runner('api-cell') - self.scheduler = self.msg_runner.scheduler - self.my_cell_state = self.msg_runner.state_manager.get_my_state() - self.filter_handler = filters.CellFilterHandler() - filter_classes = self.filter_handler.get_matching_classes( - [self.filter_cls_name]) - self.filters = [cls() for cls in filter_classes] - self.context = context.RequestContext('fake', 'fake', - is_admin=True) - - def _filter_cells(self, cells, filter_properties): - return self.filter_handler.get_filtered_objects(self.filters, - cells, - filter_properties) - - -class ImagePropertiesFilter(_FilterTestClass): - filter_cls_name = \ - 'nova.cells.filters.image_properties.ImagePropertiesFilter' - - def setUp(self): - super(ImagePropertiesFilter, self).setUp() - self.cell1 = models.Cell() - self.cell2 = models.Cell() - self.cell3 = models.Cell() - self.cells = [self.cell1, self.cell2, self.cell3] - for cell in self.cells: - cell.capabilities = {} - self.filter_props = {'context': self.context, 'request_spec': {}} - - def test_missing_image_properties(self): - self.assertEqual(self.cells, - self._filter_cells(self.cells, self.filter_props)) - - def test_missing_hypervisor_version_requires(self): - self.filter_props['request_spec'] = {'image': {'properties': {}}} - for cell in self.cells: - cell.capabilities = {"prominent_hypervisor_version": set([u"6.2"])} - self.assertEqual(self.cells, - self._filter_cells(self.cells, self.filter_props)) - - def test_missing_hypervisor_version_in_cells(self): - image = {'properties': {'hypervisor_version_requires': '>6.2.1'}} - self.filter_props['request_spec'] = {'image': image} - self.cell1.capabilities = {"prominent_hypervisor_version": set([])} - self.assertEqual(self.cells, - self._filter_cells(self.cells, self.filter_props)) - - def test_cells_matching_hypervisor_version(self): - image = {'properties': {'hypervisor_version_requires': '>6.0, <=6.3'}} - self.filter_props['request_spec'] = {'image': image} - - self.cell1.capabilities = {"prominent_hypervisor_version": - set([u"6.2"])} - self.cell2.capabilities = {"prominent_hypervisor_version": - set([u"6.3"])} - self.cell3.capabilities = {"prominent_hypervisor_version": - set([u"6.0"])} - - self.assertEqual([self.cell1, self.cell2], - self._filter_cells(self.cells, self.filter_props)) - - # assert again to verify filter doesn't mutate state - # LP bug #1325705 - self.assertEqual([self.cell1, self.cell2], - self._filter_cells(self.cells, self.filter_props)) - - -class TestTargetCellFilter(_FilterTestClass): - filter_cls_name = 'nova.cells.filters.target_cell.TargetCellFilter' - - def test_missing_scheduler_hints(self): - cells = [1, 2, 3] - # No filtering - filter_props = {'context': self.context} - self.assertEqual(cells, self._filter_cells(cells, filter_props)) - - def test_no_target_cell_hint(self): - cells = [1, 2, 3] - filter_props = {'scheduler_hints': {}, - 'context': self.context} - # No filtering - self.assertEqual(cells, self._filter_cells(cells, filter_props)) - - def test_target_cell_specified_me(self): - cells = [1, 2, 3] - target_cell = 'fake!cell!path' - current_cell = 'fake!cell!path' - filter_props = {'scheduler_hints': {'target_cell': target_cell}, - 'routing_path': current_cell, - 'scheduler': self.scheduler, - 'context': self.context} - # Only myself in the list. - self.assertEqual([self.my_cell_state], - self._filter_cells(cells, filter_props)) - - def test_target_cell_specified_me_but_not_admin(self): - ctxt = context.RequestContext('fake', 'fake') - cells = [1, 2, 3] - target_cell = 'fake!cell!path' - current_cell = 'fake!cell!path' - filter_props = {'scheduler_hints': {'target_cell': target_cell}, - 'routing_path': current_cell, - 'scheduler': self.scheduler, - 'context': ctxt} - # No filtering, because not an admin. - self.assertEqual(cells, self._filter_cells(cells, filter_props)) - - def test_target_cell_specified_not_me(self): - info = {} - - def _fake_build_instances(self, ctxt, cell, sched_kwargs): - info['ctxt'] = ctxt - info['cell'] = cell - info['sched_kwargs'] = sched_kwargs - - self.stub_out('nova.cells.messaging.MessageRunner.build_instances', - _fake_build_instances) - cells = [1, 2, 3] - target_cell = 'fake!cell!path' - current_cell = 'not!the!same' - filter_props = {'scheduler_hints': {'target_cell': target_cell}, - 'routing_path': current_cell, - 'scheduler': self.scheduler, - 'context': self.context, - 'host_sched_kwargs': 'meow'} - # None is returned to bypass further scheduling. - self.assertIsNone(self._filter_cells(cells, filter_props)) - # The filter should have re-scheduled to the child cell itself. - expected_info = {'ctxt': self.context, - 'cell': 'fake!cell!path', - 'sched_kwargs': 'meow'} - self.assertEqual(expected_info, info) - - -class TestDifferentCellFilter(_FilterTestClass): - filter_cls_name = 'nova.cells.filters.different_cell.DifferentCellFilter' - - def setUp(self): - super(TestDifferentCellFilter, self).setUp() - # We only load one filter so we know the first one is the one we want - self.policy.set_rules({'cells_scheduler_filter:DifferentCellFilter': - ''}) - self.cells = [state.CellState('1'), - state.CellState('2'), - state.CellState('3')] - - def test_missing_scheduler_hints(self): - filter_props = {'context': self.context} - # No filtering - self.assertEqual(self.cells, - self._filter_cells(self.cells, filter_props)) - - def test_no_different_cell_hint(self): - filter_props = {'scheduler_hints': {}, - 'context': self.context} - # No filtering - self.assertEqual(self.cells, - self._filter_cells(self.cells, filter_props)) - - def test_different_cell(self): - filter_props = {'scheduler_hints': {'different_cell': 'fake!2'}, - 'routing_path': 'fake', - 'context': self.context} - filtered_cells = self._filter_cells(self.cells, filter_props) - self.assertEqual(2, len(filtered_cells)) - self.assertNotIn(self.cells[1], filtered_cells) - - def test_different_multiple_cells(self): - filter_props = {'scheduler_hints': - {'different_cell': ['fake!1', 'fake!2']}, - 'routing_path': 'fake', - 'context': self.context} - filtered_cells = self._filter_cells(self.cells, filter_props) - self.assertEqual(1, len(filtered_cells)) - self.assertNotIn(self.cells[0], filtered_cells) - self.assertNotIn(self.cells[1], filtered_cells) - - def test_different_cell_specified_me_not_authorized(self): - self.policy.set_rules({'cells_scheduler_filter:DifferentCellFilter': - '!'}) - filter_props = {'scheduler_hints': {'different_cell': 'fake!2'}, - 'routing_path': 'fake', - 'context': self.context} - # No filtering, because not an admin. - self.assertEqual(self.cells, - self._filter_cells(self.cells, filter_props)) diff --git a/nova/tests/unit/cells/test_cells_manager.py b/nova/tests/unit/cells/test_cells_manager.py deleted file mode 100644 index e1f494008f..0000000000 --- a/nova/tests/unit/cells/test_cells_manager.py +++ /dev/null @@ -1,813 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Tests For CellsManager -""" -import copy -import datetime - -import mock -from oslo_utils import timeutils -from six.moves import range - -from nova.cells import messaging -from nova.cells import utils as cells_utils -from nova.compute import rpcapi as compute_rpcapi -import nova.conf -from nova import context -from nova import objects -from nova import test -from nova.tests.unit.cells import fakes -from nova.tests.unit import fake_instance -from nova.tests.unit import fake_server_actions -from nova.tests.unit.objects import test_flavor - -CONF = nova.conf.CONF - -FAKE_COMPUTE_NODES = [dict(id=1, host='host1'), dict(id=2, host='host2')] -FAKE_SERVICES = [dict(id=1, host='host1'), - dict(id=2, host='host2'), - dict(id=3, host='host3')] -FAKE_TASK_LOGS = [dict(id=1, host='host1'), - dict(id=2, host='host2')] - - -class CellsManagerClassTestCase(test.NoDBTestCase): - """Test case for CellsManager class.""" - - def setUp(self): - super(CellsManagerClassTestCase, self).setUp() - fakes.init(self) - # pick a child cell to use for tests. - self.our_cell = 'grandchild-cell1' - self.cells_manager = fakes.get_cells_manager(self.our_cell) - self.msg_runner = self.cells_manager.msg_runner - self.state_manager = fakes.get_state_manager(self.our_cell) - self.driver = self.cells_manager.driver - self.ctxt = 'fake_context' - - def _get_fake_response(self, raw_response=None, exc=False): - if exc: - return messaging.Response(self.ctxt, 'fake', - test.TestingException(), - True) - if raw_response is None: - raw_response = 'fake-response' - return messaging.Response(self.ctxt, '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') - self.cells_manager.state_manager.get_cell_info_for_neighbors() - self.mox.ReplayAll() - self.cells_manager.get_cell_info_for_neighbors(self.ctxt) - - def test_post_start_hook_child_cell(self): - self.mox.StubOutWithMock(self.driver, 'start_servers') - self.mox.StubOutWithMock(context, 'get_admin_context') - self.mox.StubOutWithMock(self.cells_manager, '_update_our_parents') - - self.driver.start_servers(self.msg_runner) - context.get_admin_context().AndReturn(self.ctxt) - self.cells_manager._update_our_parents(self.ctxt) - self.mox.ReplayAll() - self.cells_manager.post_start_hook() - - def test_post_start_hook_middle_cell(self): - cells_manager = fakes.get_cells_manager('child-cell2') - msg_runner = cells_manager.msg_runner - driver = cells_manager.driver - - self.mox.StubOutWithMock(driver, 'start_servers') - self.mox.StubOutWithMock(context, 'get_admin_context') - self.mox.StubOutWithMock(msg_runner, - 'ask_children_for_capabilities') - self.mox.StubOutWithMock(msg_runner, - 'ask_children_for_capacities') - - driver.start_servers(msg_runner) - context.get_admin_context().AndReturn(self.ctxt) - msg_runner.ask_children_for_capabilities(self.ctxt) - msg_runner.ask_children_for_capacities(self.ctxt) - self.mox.ReplayAll() - cells_manager.post_start_hook() - - def test_update_our_parents(self): - self.mox.StubOutWithMock(self.msg_runner, - 'tell_parents_our_capabilities') - self.mox.StubOutWithMock(self.msg_runner, - 'tell_parents_our_capacities') - - self.msg_runner.tell_parents_our_capabilities(self.ctxt) - self.msg_runner.tell_parents_our_capacities(self.ctxt) - self.mox.ReplayAll() - self.cells_manager._update_our_parents(self.ctxt) - - def test_build_instances(self): - build_inst_kwargs = {'instances': [objects.Instance(), - objects.Instance()]} - self.mox.StubOutWithMock(self.msg_runner, 'build_instances') - our_cell = self.msg_runner.state_manager.get_my_state() - self.msg_runner.build_instances(self.ctxt, our_cell, build_inst_kwargs) - self.mox.ReplayAll() - self.cells_manager.build_instances(self.ctxt, - build_inst_kwargs=build_inst_kwargs) - - def test_build_instances_old_flavor(self): - flavor_dict = test_flavor.fake_flavor - args = {'filter_properties': {'instance_type': flavor_dict}, - 'instances': [objects.Instance()]} - with mock.patch.object(self.msg_runner, 'build_instances') as mock_bi: - self.cells_manager.build_instances(self.ctxt, - build_inst_kwargs=args) - filter_properties = mock_bi.call_args[0][2]['filter_properties'] - self.assertIsInstance(filter_properties['instance_type'], - objects.Flavor) - - def test_build_instances_old_instances(self): - args = {'instances': [fake_instance.fake_db_instance()]} - with mock.patch.object(self.msg_runner, 'build_instances') as mock_bi: - self.cells_manager.build_instances(self.ctxt, - build_inst_kwargs=args) - self.assertIsInstance(mock_bi.call_args[0][2]['instances'][0], - objects.Instance) - - def test_run_compute_api_method(self): - # Args should just be silently passed through - cell_name = 'fake-cell-name' - method_info = 'fake-method-info' - - self.mox.StubOutWithMock(self.msg_runner, - 'run_compute_api_method') - fake_response = self._get_fake_response() - self.msg_runner.run_compute_api_method(self.ctxt, - cell_name, - method_info, - True).AndReturn(fake_response) - self.mox.ReplayAll() - response = self.cells_manager.run_compute_api_method( - self.ctxt, cell_name=cell_name, method_info=method_info, - call=True) - self.assertEqual('fake-response', response) - - def test_instance_delete_everywhere(self): - self.mox.StubOutWithMock(self.msg_runner, - 'instance_delete_everywhere') - self.msg_runner.instance_delete_everywhere(self.ctxt, - 'fake-instance', - 'fake-type') - self.mox.ReplayAll() - self.cells_manager.instance_delete_everywhere( - self.ctxt, instance='fake-instance', - delete_type='fake-type') - - def test_heal_instances(self): - self.flags(instance_updated_at_threshold=1000, - instance_update_num_instances=2, - group='cells') - - fake_context = context.RequestContext('fake', 'fake') - stalled_time = timeutils.utcnow() - updated_since = stalled_time - datetime.timedelta(seconds=1000) - - def utcnow(): - return stalled_time - - call_info = {'get_instances': 0, 'sync_instances': []} - - instances = ['instance1', 'instance2', 'instance3'] - - def get_instances_to_sync(context, **kwargs): - self.assertEqual(fake_context, context) - call_info['shuffle'] = kwargs.get('shuffle') - call_info['project_id'] = kwargs.get('project_id') - call_info['updated_since'] = kwargs.get('updated_since') - call_info['get_instances'] += 1 - return iter(instances) - - @staticmethod - def instance_get_by_uuid(context, uuid): - return instances[int(uuid[-1]) - 1] - - def sync_instance(context, instance): - self.assertEqual(fake_context, context) - call_info['sync_instances'].append(instance) - - self.stubs.Set(cells_utils, 'get_instances_to_sync', - get_instances_to_sync) - self.stubs.Set(objects.Instance, 'get_by_uuid', - instance_get_by_uuid) - self.stubs.Set(self.cells_manager, '_sync_instance', - sync_instance) - self.stubs.Set(timeutils, 'utcnow', utcnow) - - self.cells_manager._heal_instances(fake_context) - self.assertTrue(call_info['shuffle']) - self.assertIsNone(call_info['project_id']) - self.assertEqual(updated_since, call_info['updated_since']) - self.assertEqual(1, call_info['get_instances']) - # Only first 2 - self.assertEqual(instances[:2], call_info['sync_instances']) - - call_info['sync_instances'] = [] - self.cells_manager._heal_instances(fake_context) - self.assertTrue(call_info['shuffle']) - self.assertIsNone(call_info['project_id']) - self.assertEqual(updated_since, call_info['updated_since']) - self.assertEqual(2, call_info['get_instances']) - # Now the last 1 and the first 1 - self.assertEqual([instances[-1], instances[0]], - call_info['sync_instances']) - - def test_sync_instances(self): - self.mox.StubOutWithMock(self.msg_runner, - 'sync_instances') - self.msg_runner.sync_instances(self.ctxt, 'fake-project', - 'fake-time', 'fake-deleted') - self.mox.ReplayAll() - self.cells_manager.sync_instances(self.ctxt, - 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 range(3): - cell_name = 'path!to!cell%i' % i - services = [] - for service in FAKE_SERVICES: - fake_service = objects.Service(**service) - services.append(fake_service) - expected_service = cells_utils.ServiceProxy(fake_service, - cell_name) - expected_response.append( - (cell_name, expected_service, fake_service)) - response = messaging.Response(self.ctxt, cell_name, services, - False) - responses.append(response) - - self.mox.StubOutWithMock(self.msg_runner, - 'service_get_all') - self.mox.StubOutWithMock(cells_utils, 'add_cell_to_service') - self.msg_runner.service_get_all(self.ctxt, - 'fake-filters').AndReturn(responses) - # Calls are done by cells, so we need to sort the list by the cell name - expected_response.sort(key=lambda k: k[0]) - for cell_name, service_proxy, service in expected_response: - cells_utils.add_cell_to_service( - service, cell_name).AndReturn(service_proxy) - self.mox.ReplayAll() - response = self.cells_manager.service_get_all(self.ctxt, - filters='fake-filters') - self.assertEqual([proxy for cell, proxy, service in expected_response], - response) - - def test_service_get_by_compute_host(self): - fake_cell = 'fake-cell' - fake_service = objects.Service(**FAKE_SERVICES[0]) - fake_response = messaging.Response(self.ctxt, fake_cell, - fake_service, - False) - expected_response = cells_utils.ServiceProxy(fake_service, fake_cell) - cell_and_host = cells_utils.cell_with_item('fake-cell', 'fake-host') - - self.mox.StubOutWithMock(self.msg_runner, - 'service_get_by_compute_host') - self.mox.StubOutWithMock(cells_utils, 'add_cell_to_service') - self.msg_runner.service_get_by_compute_host(self.ctxt, - fake_cell, 'fake-host').AndReturn(fake_response) - cells_utils.add_cell_to_service(fake_service, fake_cell).AndReturn( - expected_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_get_host_uptime(self): - fake_cell = 'parent!fake-cell' - fake_host = 'fake-host' - fake_cell_and_host = cells_utils.cell_with_item(fake_cell, fake_host) - host_uptime = (" 08:32:11 up 93 days, 18:25, 12 users, load average:" - " 0.20, 0.12, 0.14") - fake_response = messaging.Response(self.ctxt, fake_cell, host_uptime, - False) - - self.mox.StubOutWithMock(self.msg_runner, - 'get_host_uptime') - self.msg_runner.get_host_uptime(self.ctxt, fake_cell, fake_host).\ - AndReturn(fake_response) - self.mox.ReplayAll() - - response = self.cells_manager.get_host_uptime(self.ctxt, - fake_cell_and_host) - self.assertEqual(host_uptime, response) - - def test_service_update(self): - fake_cell = 'fake-cell' - fake_service = objects.Service(**FAKE_SERVICES[0]) - fake_response = messaging.Response( - self.ctxt, fake_cell, fake_service, False) - expected_response = cells_utils.ServiceProxy(fake_service, fake_cell) - cell_and_host = cells_utils.cell_with_item('fake-cell', 'fake-host') - params_to_update = {'disabled': True} - - self.mox.StubOutWithMock(self.msg_runner, 'service_update') - self.mox.StubOutWithMock(cells_utils, 'add_cell_to_service') - self.msg_runner.service_update(self.ctxt, - fake_cell, 'fake-host', 'nova-api', - params_to_update).AndReturn(fake_response) - cells_utils.add_cell_to_service(fake_service, fake_cell).AndReturn( - expected_response) - self.mox.ReplayAll() - - response = self.cells_manager.service_update( - self.ctxt, host_name=cell_and_host, binary='nova-api', - params_to_update=params_to_update) - self.assertEqual(expected_response, response) - - def test_service_delete(self): - fake_cell = 'fake-cell' - service_id = '1' - cell_service_id = cells_utils.cell_with_item(fake_cell, service_id) - - with mock.patch.object(self.msg_runner, - 'service_delete') as service_delete: - self.cells_manager.service_delete(self.ctxt, cell_service_id) - service_delete.assert_called_once_with( - self.ctxt, fake_cell, service_id) - - 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 = "%s.%s" % (compute_rpcapi.RPC_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) - - def _build_task_log_responses(self, num): - responses = [] - expected_response = [] - # 3 cells... so 3 responses. Each response is a list of task log - # entries. Manager should turn these into a single list of - # task log entries. - for i in range(num): - cell_name = 'path!to!cell%i' % i - task_logs = [] - for task_log in FAKE_TASK_LOGS: - task_logs.append(copy.deepcopy(task_log)) - expected_task_log = copy.deepcopy(task_log) - cells_utils.add_cell_to_task_log(expected_task_log, - cell_name) - expected_response.append(expected_task_log) - response = messaging.Response(self.ctxt, cell_name, task_logs, - False) - responses.append(response) - return expected_response, responses - - def test_task_log_get_all(self): - expected_response, responses = self._build_task_log_responses(3) - self.mox.StubOutWithMock(self.msg_runner, - 'task_log_get_all') - self.msg_runner.task_log_get_all(self.ctxt, None, - 'fake-name', 'fake-begin', - 'fake-end', host=None, state=None).AndReturn(responses) - self.mox.ReplayAll() - response = self.cells_manager.task_log_get_all(self.ctxt, - task_name='fake-name', - period_beginning='fake-begin', period_ending='fake-end') - self.assertEqual(expected_response, response) - - def test_task_log_get_all_with_filters(self): - expected_response, responses = self._build_task_log_responses(1) - cell_and_host = cells_utils.cell_with_item('fake-cell', 'fake-host') - self.mox.StubOutWithMock(self.msg_runner, - 'task_log_get_all') - self.msg_runner.task_log_get_all(self.ctxt, 'fake-cell', - 'fake-name', 'fake-begin', 'fake-end', host='fake-host', - state='fake-state').AndReturn(responses) - self.mox.ReplayAll() - response = self.cells_manager.task_log_get_all(self.ctxt, - task_name='fake-name', - period_beginning='fake-begin', period_ending='fake-end', - host=cell_and_host, state='fake-state') - self.assertEqual(expected_response, response) - - def test_task_log_get_all_with_cell_but_no_host_filters(self): - expected_response, responses = self._build_task_log_responses(1) - # Host filter only has cell name. - cell_and_host = 'fake-cell' - self.mox.StubOutWithMock(self.msg_runner, - 'task_log_get_all') - self.msg_runner.task_log_get_all(self.ctxt, 'fake-cell', - 'fake-name', 'fake-begin', 'fake-end', host=None, - state='fake-state').AndReturn(responses) - self.mox.ReplayAll() - response = self.cells_manager.task_log_get_all(self.ctxt, - task_name='fake-name', - period_beginning='fake-begin', period_ending='fake-end', - host=cell_and_host, state='fake-state') - self.assertEqual(expected_response, response) - - def test_compute_node_get_all(self): - responses = [] - expected_response = [] - # 3 cells... so 3 responses. Each response is a list of computes. - # Manager should turn these into a single list of responses. - for i in range(3): - cell_name = 'path!to!cell%i' % i - compute_nodes = [] - for compute_node in FAKE_COMPUTE_NODES: - fake_compute = objects.ComputeNode(**compute_node) - fake_compute._cached_service = None - compute_nodes.append(fake_compute) - expected_compute_node = cells_utils.ComputeNodeProxy( - fake_compute, cell_name) - expected_response.append( - (cell_name, expected_compute_node, fake_compute)) - response = messaging.Response(self.ctxt, cell_name, compute_nodes, - False) - responses.append(response) - self.mox.StubOutWithMock(self.msg_runner, - 'compute_node_get_all') - self.mox.StubOutWithMock(cells_utils, 'add_cell_to_compute_node') - self.msg_runner.compute_node_get_all(self.ctxt, - hypervisor_match='fake-match').AndReturn(responses) - # Calls are done by cells, so we need to sort the list by the cell name - expected_response.sort(key=lambda k: k[0]) - for cell_name, compute_proxy, compute_node in expected_response: - cells_utils.add_cell_to_compute_node( - compute_node, cell_name).AndReturn(compute_proxy) - self.mox.ReplayAll() - response = self.cells_manager.compute_node_get_all(self.ctxt, - hypervisor_match='fake-match') - self.assertEqual([proxy for cell, proxy, compute in expected_response], - response) - - def test_compute_node_stats(self): - raw_resp1 = {'key1': 1, 'key2': 2} - raw_resp2 = {'key2': 1, 'key3': 2} - raw_resp3 = {'key3': 1, 'key4': 2} - responses = [messaging.Response(self.ctxt, 'cell1', raw_resp1, False), - messaging.Response(self.ctxt, 'cell2', raw_resp2, False), - messaging.Response(self.ctxt, 'cell2', raw_resp3, False)] - expected_resp = {'key1': 1, 'key2': 3, 'key3': 3, 'key4': 2} - - self.mox.StubOutWithMock(self.msg_runner, - 'compute_node_stats') - self.msg_runner.compute_node_stats(self.ctxt).AndReturn(responses) - self.mox.ReplayAll() - response = self.cells_manager.compute_node_stats(self.ctxt) - self.assertEqual(expected_resp, response) - - def test_compute_node_get(self): - fake_cell = 'fake-cell' - fake_compute = objects.ComputeNode(**FAKE_COMPUTE_NODES[0]) - fake_compute._cached_service = None - fake_response = messaging.Response(self.ctxt, fake_cell, - fake_compute, - False) - - expected_response = cells_utils.ComputeNodeProxy(fake_compute, - fake_cell) - cell_and_id = cells_utils.cell_with_item(fake_cell, 'fake-id') - self.mox.StubOutWithMock(self.msg_runner, - 'compute_node_get') - self.mox.StubOutWithMock(cells_utils, 'add_cell_to_compute_node') - self.msg_runner.compute_node_get(self.ctxt, - 'fake-cell', 'fake-id').AndReturn(fake_response) - cells_utils.add_cell_to_compute_node( - fake_compute, fake_cell).AndReturn(expected_response) - self.mox.ReplayAll() - response = self.cells_manager.compute_node_get(self.ctxt, - compute_id=cell_and_id) - self.assertEqual(expected_response, response) - - def test_actions_get(self): - fake_uuid = fake_server_actions.FAKE_UUID - fake_req_id = fake_server_actions.FAKE_REQUEST_ID1 - fake_act = fake_server_actions.FAKE_ACTIONS[fake_uuid][fake_req_id] - fake_response = messaging.Response(self.ctxt, 'fake-cell', [fake_act], - False) - expected_response = [fake_act] - self.mox.StubOutWithMock(self.msg_runner, 'actions_get') - self.msg_runner.actions_get(self.ctxt, 'fake-cell', - 'fake-uuid').AndReturn(fake_response) - self.mox.ReplayAll() - response = self.cells_manager.actions_get(self.ctxt, 'fake-cell', - 'fake-uuid') - self.assertEqual(expected_response, response) - - def test_action_get_by_request_id(self): - fake_uuid = fake_server_actions.FAKE_UUID - fake_req_id = fake_server_actions.FAKE_REQUEST_ID1 - fake_act = fake_server_actions.FAKE_ACTIONS[fake_uuid][fake_req_id] - fake_response = messaging.Response(self.ctxt, 'fake-cell', fake_act, - False) - expected_response = fake_act - self.mox.StubOutWithMock(self.msg_runner, 'action_get_by_request_id') - self.msg_runner.action_get_by_request_id(self.ctxt, 'fake-cell', - 'fake-uuid', 'req-fake').AndReturn(fake_response) - self.mox.ReplayAll() - response = self.cells_manager.action_get_by_request_id(self.ctxt, - 'fake-cell', - 'fake-uuid', - 'req-fake') - self.assertEqual(expected_response, response) - - def test_action_events_get(self): - fake_action_id = fake_server_actions.FAKE_ACTION_ID1 - fake_events = fake_server_actions.FAKE_EVENTS[fake_action_id] - fake_response = messaging.Response(self.ctxt, 'fake-cell', fake_events, - False) - expected_response = fake_events - self.mox.StubOutWithMock(self.msg_runner, 'action_events_get') - self.msg_runner.action_events_get(self.ctxt, 'fake-cell', - 'fake-action').AndReturn(fake_response) - self.mox.ReplayAll() - response = self.cells_manager.action_events_get(self.ctxt, 'fake-cell', - 'fake-action') - self.assertEqual(expected_response, response) - - def test_consoleauth_delete_tokens(self): - instance_uuid = 'fake-instance-uuid' - - self.mox.StubOutWithMock(self.msg_runner, - 'consoleauth_delete_tokens') - self.msg_runner.consoleauth_delete_tokens(self.ctxt, instance_uuid) - self.mox.ReplayAll() - self.cells_manager.consoleauth_delete_tokens(self.ctxt, - instance_uuid=instance_uuid) - - def test_get_capacities(self): - cell_name = 'cell_name' - response = {"ram_free": - {"units_by_mb": {"64": 20, "128": 10}, "total_mb": 1491}} - self.mox.StubOutWithMock(self.state_manager, - 'get_capacities') - self.state_manager.get_capacities(cell_name).AndReturn(response) - self.mox.ReplayAll() - self.assertEqual(response, - self.cells_manager.get_capacities(self.ctxt, cell_name)) - - def test_validate_console_port(self): - instance_uuid = 'fake-instance-uuid' - cell_name = 'fake-cell-name' - instance = objects.Instance(cell_name=cell_name) - console_port = 'fake-console-port' - console_type = 'fake-console-type' - - self.mox.StubOutWithMock(self.msg_runner, - 'validate_console_port') - self.mox.StubOutWithMock(objects.Instance, 'get_by_uuid') - fake_response = self._get_fake_response() - - objects.Instance.get_by_uuid(self.ctxt, - instance_uuid).AndReturn(instance) - self.msg_runner.validate_console_port(self.ctxt, cell_name, - instance_uuid, console_port, - console_type).AndReturn(fake_response) - self.mox.ReplayAll() - response = self.cells_manager.validate_console_port(self.ctxt, - instance_uuid=instance_uuid, console_port=console_port, - console_type=console_type) - self.assertEqual('fake-response', response) - - def test_get_migrations(self): - filters = {'status': 'confirmed'} - cell1_migrations = objects.MigrationList( - objects=[objects.Migration(id=123)]) - cell2_migrations = objects.MigrationList( - objects=[objects.Migration(id=456)]) - fake_responses = [self._get_fake_response(cell1_migrations), - self._get_fake_response(cell2_migrations)] - self.mox.StubOutWithMock(self.msg_runner, - 'get_migrations') - self.msg_runner.get_migrations(self.ctxt, None, False, filters).\ - AndReturn(fake_responses) - self.mox.ReplayAll() - - response = self.cells_manager.get_migrations(self.ctxt, filters) - - self.assertEqual(cell1_migrations.objects + cell2_migrations.objects, - response.objects) - - def test_get_migrations_for_a_given_cell(self): - filters = {'status': 'confirmed', 'cell_name': 'ChildCell1'} - target_cell = '%s%s%s' % (CONF.cells.name, '!', filters['cell_name']) - migrations = objects.MigrationList(objects=[objects.Migration(id=123)]) - fake_responses = [self._get_fake_response(migrations)] - self.mox.StubOutWithMock(self.msg_runner, - 'get_migrations') - self.msg_runner.get_migrations(self.ctxt, target_cell, False, - filters).AndReturn(fake_responses) - self.mox.ReplayAll() - - response = self.cells_manager.get_migrations(self.ctxt, filters) - self.assertEqual(migrations.objects, response.objects) - - def test_start_instance(self): - self.mox.StubOutWithMock(self.msg_runner, 'start_instance') - self.msg_runner.start_instance(self.ctxt, 'fake-instance') - self.mox.ReplayAll() - self.cells_manager.start_instance(self.ctxt, instance='fake-instance') - - def test_stop_instance(self): - self.mox.StubOutWithMock(self.msg_runner, 'stop_instance') - self.msg_runner.stop_instance(self.ctxt, 'fake-instance', - do_cast='meow', - clean_shutdown='purr') - self.mox.ReplayAll() - self.cells_manager.stop_instance(self.ctxt, - instance='fake-instance', - do_cast='meow', - clean_shutdown='purr') - - def test_cell_create(self): - values = 'values' - response = 'created_cell' - self.mox.StubOutWithMock(self.state_manager, - 'cell_create') - self.state_manager.cell_create(self.ctxt, values).\ - AndReturn(response) - self.mox.ReplayAll() - self.assertEqual(response, - self.cells_manager.cell_create(self.ctxt, values)) - - def test_cell_update(self): - cell_name = 'cell_name' - values = 'values' - response = 'updated_cell' - self.mox.StubOutWithMock(self.state_manager, - 'cell_update') - self.state_manager.cell_update(self.ctxt, cell_name, values).\ - AndReturn(response) - self.mox.ReplayAll() - self.assertEqual(response, - self.cells_manager.cell_update(self.ctxt, cell_name, - values)) - - def test_cell_delete(self): - cell_name = 'cell_name' - response = 1 - self.mox.StubOutWithMock(self.state_manager, - 'cell_delete') - self.state_manager.cell_delete(self.ctxt, cell_name).\ - AndReturn(response) - self.mox.ReplayAll() - self.assertEqual(response, - self.cells_manager.cell_delete(self.ctxt, cell_name)) - - def test_cell_get(self): - cell_name = 'cell_name' - response = 'cell_info' - self.mox.StubOutWithMock(self.state_manager, - 'cell_get') - self.state_manager.cell_get(self.ctxt, cell_name).\ - AndReturn(response) - self.mox.ReplayAll() - self.assertEqual(response, - self.cells_manager.cell_get(self.ctxt, cell_name)) - - def test_reboot_instance(self): - self.mox.StubOutWithMock(self.msg_runner, 'reboot_instance') - self.msg_runner.reboot_instance(self.ctxt, 'fake-instance', - 'HARD') - self.mox.ReplayAll() - self.cells_manager.reboot_instance(self.ctxt, - instance='fake-instance', - reboot_type='HARD') - - def test_suspend_instance(self): - self.mox.StubOutWithMock(self.msg_runner, 'suspend_instance') - self.msg_runner.suspend_instance(self.ctxt, 'fake-instance') - self.mox.ReplayAll() - self.cells_manager.suspend_instance(self.ctxt, - instance='fake-instance') - - def test_resume_instance(self): - self.mox.StubOutWithMock(self.msg_runner, 'resume_instance') - self.msg_runner.resume_instance(self.ctxt, 'fake-instance') - self.mox.ReplayAll() - self.cells_manager.resume_instance(self.ctxt, - instance='fake-instance') - - def test_terminate_instance(self): - self.mox.StubOutWithMock(self.msg_runner, 'terminate_instance') - self.msg_runner.terminate_instance(self.ctxt, 'fake-instance', - delete_type='delete') - self.mox.ReplayAll() - self.cells_manager.terminate_instance(self.ctxt, - instance='fake-instance', - delete_type='delete') - - def test_soft_delete_instance(self): - self.mox.StubOutWithMock(self.msg_runner, 'soft_delete_instance') - self.msg_runner.soft_delete_instance(self.ctxt, 'fake-instance') - self.mox.ReplayAll() - self.cells_manager.soft_delete_instance(self.ctxt, - instance='fake-instance') - - def _test_resize_instance(self, clean_shutdown=True): - self.mox.StubOutWithMock(self.msg_runner, 'resize_instance') - self.msg_runner.resize_instance(self.ctxt, 'fake-instance', - 'fake-flavor', 'fake-updates', - clean_shutdown=clean_shutdown) - self.mox.ReplayAll() - self.cells_manager.resize_instance( - self.ctxt, instance='fake-instance', flavor='fake-flavor', - extra_instance_updates='fake-updates', - clean_shutdown=clean_shutdown) - - def test_resize_instance(self): - self._test_resize_instance() - - def test_resize_instance_forced_shutdown(self): - self._test_resize_instance(clean_shutdown=False) - - def test_live_migrate_instance(self): - self.mox.StubOutWithMock(self.msg_runner, 'live_migrate_instance') - self.msg_runner.live_migrate_instance(self.ctxt, 'fake-instance', - 'fake-block', 'fake-commit', - 'fake-host') - self.mox.ReplayAll() - self.cells_manager.live_migrate_instance( - self.ctxt, instance='fake-instance', - block_migration='fake-block', disk_over_commit='fake-commit', - host_name='fake-host') - - def test_revert_resize(self): - self.mox.StubOutWithMock(self.msg_runner, 'revert_resize') - self.msg_runner.revert_resize(self.ctxt, 'fake-instance') - self.mox.ReplayAll() - self.cells_manager.revert_resize(self.ctxt, instance='fake-instance') - - def test_confirm_resize(self): - self.mox.StubOutWithMock(self.msg_runner, 'confirm_resize') - self.msg_runner.confirm_resize(self.ctxt, 'fake-instance') - self.mox.ReplayAll() - self.cells_manager.confirm_resize(self.ctxt, instance='fake-instance') - - def test_reset_network(self): - self.mox.StubOutWithMock(self.msg_runner, 'reset_network') - self.msg_runner.reset_network(self.ctxt, 'fake-instance') - self.mox.ReplayAll() - self.cells_manager.reset_network(self.ctxt, instance='fake-instance') - - def test_inject_network_info(self): - self.mox.StubOutWithMock(self.msg_runner, 'inject_network_info') - self.msg_runner.inject_network_info(self.ctxt, 'fake-instance') - self.mox.ReplayAll() - self.cells_manager.inject_network_info(self.ctxt, - instance='fake-instance') - - def test_snapshot_instance(self): - self.mox.StubOutWithMock(self.msg_runner, 'snapshot_instance') - self.msg_runner.snapshot_instance(self.ctxt, 'fake-instance', - 'fake-id') - self.mox.ReplayAll() - self.cells_manager.snapshot_instance(self.ctxt, - instance='fake-instance', - image_id='fake-id') - - def test_backup_instance(self): - self.mox.StubOutWithMock(self.msg_runner, 'backup_instance') - self.msg_runner.backup_instance(self.ctxt, 'fake-instance', - 'fake-id', 'backup-type', - 'rotation') - self.mox.ReplayAll() - self.cells_manager.backup_instance(self.ctxt, - instance='fake-instance', - image_id='fake-id', - backup_type='backup-type', - rotation='rotation') - - def test_set_admin_password(self): - with mock.patch.object(self.msg_runner, - 'set_admin_password') as set_admin_password: - self.cells_manager.set_admin_password(self.ctxt, - instance='fake-instance', new_pass='fake-password') - set_admin_password.assert_called_once_with(self.ctxt, - 'fake-instance', 'fake-password') diff --git a/nova/tests/unit/cells/test_cells_messaging.py b/nova/tests/unit/cells/test_cells_messaging.py deleted file mode 100644 index bb90d49c08..0000000000 --- a/nova/tests/unit/cells/test_cells_messaging.py +++ /dev/null @@ -1,1667 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# Copyright 2013 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Tests For Cells Messaging module -""" - -import mock -from mox3 import mox -import oslo_messaging -from oslo_serialization import jsonutils -from oslo_utils.fixture import uuidsentinel as uuids -from oslo_utils import timeutils - -from nova.cells import messaging -from nova.cells import utils as cells_utils -from nova.compute import instance_actions -from nova.compute import task_states -from nova.compute import vm_states -from nova import context -from nova.db import api as db -from nova import exception -from nova import objects -from nova.objects import base as objects_base -from nova.objects import fields as objects_fields -from nova import rpc -from nova import test -from nova.tests.unit.cells import fakes -from nova.tests.unit import fake_instance -from nova.tests.unit import fake_server_actions - - -class CellsMessageClassesTestCase(test.NoDBTestCase): - """Test case for the main Cells Message classes.""" - def setUp(self): - super(CellsMessageClassesTestCase, self).setUp() - fakes.init(self) - self.ctxt = context.RequestContext('fake', 'fake') - self.our_name = 'api-cell' - self.msg_runner = fakes.get_message_runner(self.our_name) - self.state_manager = self.msg_runner.state_manager - - def test_reverse_path(self): - path = 'a!b!c!d' - expected = 'd!c!b!a' - rev_path = messaging._reverse_path(path) - self.assertEqual(expected, rev_path) - - def test_response_cell_name_from_path(self): - # test array with tuples of inputs/expected outputs - test_paths = [('cell1', 'cell1'), - ('cell1!cell2', 'cell2!cell1'), - ('cell1!cell2!cell3', 'cell3!cell2!cell1')] - - for test_input, expected_output in test_paths: - self.assertEqual(expected_output, - messaging._response_cell_name_from_path(test_input)) - - def test_response_cell_name_from_path_neighbor_only(self): - # test array with tuples of inputs/expected outputs - test_paths = [('cell1', 'cell1'), - ('cell1!cell2', 'cell2!cell1'), - ('cell1!cell2!cell3', 'cell3!cell2')] - - for test_input, expected_output in test_paths: - self.assertEqual(expected_output, - messaging._response_cell_name_from_path(test_input, - neighbor_only=True)) - - def test_response_to_json_and_from_json(self): - fake_uuid = uuids.fake - response = messaging.Response(self.ctxt, 'child-cell!api-cell', - objects.Instance(id=1, uuid=fake_uuid), - False) - json_response = response.to_json() - deserialized_response = messaging.Response.from_json(self.ctxt, - json_response) - obj = deserialized_response.value - self.assertIsInstance(obj, objects.Instance) - self.assertEqual(1, obj.id) - self.assertEqual(fake_uuid, obj.uuid) - - def test_targeted_message(self): - self.flags(max_hop_count=99, group='cells') - target_cell = 'api-cell!child-cell2!grandchild-cell1' - method = 'fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell) - self.assertEqual(self.ctxt, tgt_message.ctxt) - self.assertEqual(method, tgt_message.method_name) - self.assertEqual(method_kwargs, tgt_message.method_kwargs) - self.assertEqual(direction, tgt_message.direction) - self.assertEqual(target_cell, target_cell) - self.assertFalse(tgt_message.fanout) - self.assertFalse(tgt_message.need_response) - self.assertEqual(self.our_name, tgt_message.routing_path) - self.assertEqual(1, tgt_message.hop_count) - self.assertEqual(99, tgt_message.max_hop_count) - self.assertFalse(tgt_message.is_broadcast) - # Correct next hop? - next_hop = tgt_message._get_next_hop() - child_cell = self.state_manager.get_child_cell('child-cell2') - self.assertEqual(child_cell, next_hop) - - def test_create_targeted_message_with_response(self): - self.flags(max_hop_count=99, group='cells') - our_name = 'child-cell1' - target_cell = 'child-cell1!api-cell' - msg_runner = fakes.get_message_runner(our_name) - method = 'fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'up' - tgt_message = messaging._TargetedMessage(msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell, - need_response=True) - self.assertEqual(self.ctxt, tgt_message.ctxt) - self.assertEqual(method, tgt_message.method_name) - self.assertEqual(method_kwargs, tgt_message.method_kwargs) - self.assertEqual(direction, tgt_message.direction) - self.assertEqual(target_cell, target_cell) - self.assertFalse(tgt_message.fanout) - self.assertTrue(tgt_message.need_response) - self.assertEqual(our_name, tgt_message.routing_path) - self.assertEqual(1, tgt_message.hop_count) - self.assertEqual(99, tgt_message.max_hop_count) - self.assertFalse(tgt_message.is_broadcast) - # Correct next hop? - next_hop = tgt_message._get_next_hop() - parent_cell = msg_runner.state_manager.get_parent_cell('api-cell') - self.assertEqual(parent_cell, next_hop) - - def test_targeted_message_when_target_is_cell_state(self): - method = 'fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - target_cell = self.state_manager.get_child_cell('child-cell2') - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell) - self.assertEqual('api-cell!child-cell2', tgt_message.target_cell) - # Correct next hop? - next_hop = tgt_message._get_next_hop() - self.assertEqual(target_cell, next_hop) - - def test_targeted_message_when_target_cell_state_is_me(self): - method = 'fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - target_cell = self.state_manager.get_my_state() - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell) - self.assertEqual('api-cell', tgt_message.target_cell) - # Correct next hop? - next_hop = tgt_message._get_next_hop() - self.assertEqual(target_cell, next_hop) - - def test_create_broadcast_message(self): - self.flags(max_hop_count=99, group='cells') - self.flags(name='api-cell', max_hop_count=99, group='cells') - method = 'fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - bcast_message = messaging._BroadcastMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction) - self.assertEqual(self.ctxt, bcast_message.ctxt) - self.assertEqual(method, bcast_message.method_name) - self.assertEqual(method_kwargs, bcast_message.method_kwargs) - self.assertEqual(direction, bcast_message.direction) - self.assertFalse(bcast_message.fanout) - self.assertFalse(bcast_message.need_response) - self.assertEqual(self.our_name, bcast_message.routing_path) - self.assertEqual(1, bcast_message.hop_count) - self.assertEqual(99, bcast_message.max_hop_count) - self.assertTrue(bcast_message.is_broadcast) - # Correct next hops? - next_hops = bcast_message._get_next_hops() - child_cells = self.state_manager.get_child_cells() - self.assertEqual(child_cells, next_hops) - - def test_create_broadcast_message_with_response(self): - self.flags(max_hop_count=99, group='cells') - our_name = 'child-cell1' - msg_runner = fakes.get_message_runner(our_name) - method = 'fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'up' - bcast_message = messaging._BroadcastMessage(msg_runner, self.ctxt, - method, method_kwargs, direction, need_response=True) - self.assertEqual(self.ctxt, bcast_message.ctxt) - self.assertEqual(method, bcast_message.method_name) - self.assertEqual(method_kwargs, bcast_message.method_kwargs) - self.assertEqual(direction, bcast_message.direction) - self.assertFalse(bcast_message.fanout) - self.assertTrue(bcast_message.need_response) - self.assertEqual(our_name, bcast_message.routing_path) - self.assertEqual(1, bcast_message.hop_count) - self.assertEqual(99, bcast_message.max_hop_count) - self.assertTrue(bcast_message.is_broadcast) - # Correct next hops? - next_hops = bcast_message._get_next_hops() - parent_cells = msg_runner.state_manager.get_parent_cells() - self.assertEqual(parent_cells, next_hops) - - def test_self_targeted_message(self): - target_cell = 'api-cell' - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - call_info = {} - - def our_fake_method(message, **kwargs): - call_info['context'] = message.ctxt - call_info['routing_path'] = message.routing_path - call_info['kwargs'] = kwargs - - fakes.stub_tgt_method(self, 'api-cell', 'our_fake_method', - our_fake_method) - - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell) - tgt_message.process() - - self.assertEqual(self.ctxt, call_info['context']) - self.assertEqual(method_kwargs, call_info['kwargs']) - self.assertEqual(target_cell, call_info['routing_path']) - - def test_child_targeted_message(self): - target_cell = 'api-cell!child-cell1' - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - call_info = {} - - def our_fake_method(message, **kwargs): - call_info['context'] = message.ctxt - call_info['routing_path'] = message.routing_path - call_info['kwargs'] = kwargs - - fakes.stub_tgt_method(self, 'child-cell1', 'our_fake_method', - our_fake_method) - - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell) - tgt_message.process() - - self.assertEqual(self.ctxt, call_info['context']) - self.assertEqual(method_kwargs, call_info['kwargs']) - self.assertEqual(target_cell, call_info['routing_path']) - - def test_child_targeted_message_with_object(self): - target_cell = 'api-cell!child-cell1' - method = 'our_fake_method' - direction = 'down' - - call_info = {} - - class CellsMsgingTestObject(objects_base.NovaObject): - """Test object. We just need 1 field in order to test - that this gets serialized properly. - """ - fields = {'test': objects_fields.StringField()} - - objects_base.NovaObjectRegistry.register(CellsMsgingTestObject) - - test_obj = CellsMsgingTestObject() - test_obj.test = 'meow' - - method_kwargs = dict(obj=test_obj, arg1=1, arg2=2) - - def our_fake_method(message, **kwargs): - call_info['context'] = message.ctxt - call_info['routing_path'] = message.routing_path - call_info['kwargs'] = kwargs - - fakes.stub_tgt_method(self, 'child-cell1', 'our_fake_method', - our_fake_method) - - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell) - tgt_message.process() - - self.assertEqual(self.ctxt, call_info['context']) - self.assertEqual(target_cell, call_info['routing_path']) - self.assertEqual(3, len(call_info['kwargs'])) - self.assertEqual(1, call_info['kwargs']['arg1']) - self.assertEqual(2, call_info['kwargs']['arg2']) - # Verify we get a new object with what we expect. - obj = call_info['kwargs']['obj'] - self.assertIsInstance(obj, CellsMsgingTestObject) - self.assertNotEqual(id(test_obj), id(obj)) - self.assertEqual(test_obj.test, obj.test) - - def test_grandchild_targeted_message(self): - target_cell = 'api-cell!child-cell2!grandchild-cell1' - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - call_info = {} - - def our_fake_method(message, **kwargs): - call_info['context'] = message.ctxt - call_info['routing_path'] = message.routing_path - call_info['kwargs'] = kwargs - - fakes.stub_tgt_method(self, 'grandchild-cell1', 'our_fake_method', - our_fake_method) - - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell) - tgt_message.process() - - self.assertEqual(self.ctxt, call_info['context']) - self.assertEqual(method_kwargs, call_info['kwargs']) - self.assertEqual(target_cell, call_info['routing_path']) - - def test_grandchild_targeted_message_with_response(self): - target_cell = 'api-cell!child-cell2!grandchild-cell1' - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - call_info = {} - - def our_fake_method(message, **kwargs): - call_info['context'] = message.ctxt - call_info['routing_path'] = message.routing_path - call_info['kwargs'] = kwargs - return 'our_fake_response' - - fakes.stub_tgt_method(self, 'grandchild-cell1', 'our_fake_method', - our_fake_method) - - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell, - need_response=True) - response = tgt_message.process() - - self.assertEqual(self.ctxt, call_info['context']) - self.assertEqual(method_kwargs, call_info['kwargs']) - self.assertEqual(target_cell, call_info['routing_path']) - self.assertFalse(response.failure) - self.assertEqual('our_fake_response', response.value_or_raise()) - - def test_grandchild_targeted_message_with_error(self): - target_cell = 'api-cell!child-cell2!grandchild-cell1' - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - def our_fake_method(message, **kwargs): - raise test.TestingException('this should be returned') - - fakes.stub_tgt_method(self, 'grandchild-cell1', 'our_fake_method', - our_fake_method) - - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell, - need_response=True) - response = tgt_message.process() - self.assertTrue(response.failure) - self.assertRaises(test.TestingException, response.value_or_raise) - - def test_grandchild_targeted_message_max_hops(self): - self.flags(max_hop_count=2, group='cells') - target_cell = 'api-cell!child-cell2!grandchild-cell1' - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - def our_fake_method(message, **kwargs): - raise test.TestingException('should not be reached') - - fakes.stub_tgt_method(self, 'grandchild-cell1', 'our_fake_method', - our_fake_method) - - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell, - need_response=True) - response = tgt_message.process() - self.assertTrue(response.failure) - self.assertRaises(exception.CellMaxHopCountReached, - response.value_or_raise) - - def test_targeted_message_invalid_cell(self): - target_cell = 'api-cell!child-cell2!grandchild-cell4' - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell, - need_response=True) - response = tgt_message.process() - self.assertTrue(response.failure) - self.assertRaises(exception.CellRoutingInconsistency, - response.value_or_raise) - - def test_targeted_message_invalid_cell2(self): - target_cell = 'unknown-cell!child-cell2' - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - tgt_message = messaging._TargetedMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, direction, - target_cell, - need_response=True) - response = tgt_message.process() - self.assertTrue(response.failure) - self.assertRaises(exception.CellRoutingInconsistency, - response.value_or_raise) - - def test_targeted_message_target_cell_none(self): - target_cell = None - method = 'our_fake_method' - method_kwargs = dict(arg=1, arg2=2) - direction = 'down' - - self.assertRaises(exception.CellRoutingInconsistency, - messaging._TargetedMessage, self.msg_runner, self.ctxt, method, - method_kwargs, direction, target_cell, need_response=False) - - def test_broadcast_routing(self): - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - cells = set() - - def our_fake_method(message, **kwargs): - cells.add(message.routing_path) - - fakes.stub_bcast_methods(self, 'our_fake_method', our_fake_method) - - bcast_message = messaging._BroadcastMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, - direction, - run_locally=True) - bcast_message.process() - # fakes creates 8 cells (including ourself). - self.assertEqual(8, len(cells)) - - def test_broadcast_routing_up(self): - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'up' - msg_runner = fakes.get_message_runner('grandchild-cell3') - - cells = set() - - def our_fake_method(message, **kwargs): - cells.add(message.routing_path) - - fakes.stub_bcast_methods(self, 'our_fake_method', our_fake_method) - - bcast_message = messaging._BroadcastMessage(msg_runner, self.ctxt, - method, method_kwargs, - direction, - run_locally=True) - bcast_message.process() - # Paths are reversed, since going 'up' - expected = set(['grandchild-cell3', 'grandchild-cell3!child-cell3', - 'grandchild-cell3!child-cell3!api-cell']) - self.assertEqual(expected, cells) - - def test_broadcast_routing_without_ourselves(self): - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - cells = set() - - def our_fake_method(message, **kwargs): - cells.add(message.routing_path) - - fakes.stub_bcast_methods(self, 'our_fake_method', our_fake_method) - - bcast_message = messaging._BroadcastMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, - direction, - run_locally=False) - bcast_message.process() - # fakes creates 8 cells (including ourself). So we should see - # only 7 here. - self.assertEqual(7, len(cells)) - - def test_broadcast_routing_with_response(self): - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - def our_fake_method(message, **kwargs): - return 'response-%s' % message.routing_path - - fakes.stub_bcast_methods(self, 'our_fake_method', our_fake_method) - - bcast_message = messaging._BroadcastMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, - direction, - run_locally=True, - need_response=True) - responses = bcast_message.process() - self.assertEqual(8, len(responses)) - for response in responses: - self.assertFalse(response.failure) - self.assertEqual('response-%s' % response.cell_name, - response.value_or_raise()) - - def test_broadcast_routing_with_response_max_hops(self): - self.flags(max_hop_count=2, group='cells') - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - def our_fake_method(message, **kwargs): - return 'response-%s' % message.routing_path - - fakes.stub_bcast_methods(self, 'our_fake_method', our_fake_method) - - bcast_message = messaging._BroadcastMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, - direction, - run_locally=True, - need_response=True) - responses = bcast_message.process() - # Should only get responses from our immediate children (and - # ourselves) - self.assertEqual(5, len(responses)) - for response in responses: - self.assertFalse(response.failure) - self.assertEqual('response-%s' % response.cell_name, - response.value_or_raise()) - - def test_broadcast_routing_with_all_erroring(self): - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - def our_fake_method(message, **kwargs): - raise test.TestingException('fake failure') - - fakes.stub_bcast_methods(self, 'our_fake_method', our_fake_method) - - bcast_message = messaging._BroadcastMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, - direction, - run_locally=True, - need_response=True) - responses = bcast_message.process() - self.assertEqual(8, len(responses)) - for response in responses: - self.assertTrue(response.failure) - self.assertRaises(test.TestingException, response.value_or_raise) - - def test_broadcast_routing_with_two_erroring(self): - method = 'our_fake_method' - method_kwargs = dict(arg1=1, arg2=2) - direction = 'down' - - def our_fake_method_failing(message, **kwargs): - raise test.TestingException('fake failure') - - def our_fake_method(message, **kwargs): - return 'response-%s' % message.routing_path - - fakes.stub_bcast_methods(self, 'our_fake_method', our_fake_method) - fakes.stub_bcast_method(self, 'child-cell2', 'our_fake_method', - our_fake_method_failing) - fakes.stub_bcast_method(self, 'grandchild-cell3', 'our_fake_method', - our_fake_method_failing) - - bcast_message = messaging._BroadcastMessage(self.msg_runner, - self.ctxt, method, - method_kwargs, - direction, - run_locally=True, - need_response=True) - responses = bcast_message.process() - self.assertEqual(8, len(responses)) - failure_responses = [resp for resp in responses if resp.failure] - success_responses = [resp for resp in responses if not resp.failure] - self.assertEqual(2, len(failure_responses)) - self.assertEqual(6, len(success_responses)) - - for response in success_responses: - self.assertFalse(response.failure) - self.assertEqual('response-%s' % response.cell_name, - response.value_or_raise()) - - for response in failure_responses: - self.assertIn(response.cell_name, ['api-cell!child-cell2', - 'api-cell!child-cell3!grandchild-cell3']) - self.assertTrue(response.failure) - self.assertRaises(test.TestingException, response.value_or_raise) - - -class CellsTargetedMethodsWithDatabaseTestCase(test.TestCase): - """These tests access the database unlike the others.""" - - def setUp(self): - super(CellsTargetedMethodsWithDatabaseTestCase, self).setUp() - fakes.init(self) - self.ctxt = context.RequestContext('fake', 'fake') - self._setup_attrs('api-cell', 'api-cell!child-cell2') - - def _setup_attrs(self, source_cell, target_cell): - self.tgt_cell_name = target_cell - self.src_msg_runner = fakes.get_message_runner(source_cell) - - def test_service_delete(self): - fake_service = dict(id=42, host='fake_host', binary='nova-compute', - topic='compute') - - ctxt = self.ctxt.elevated() - db.service_create(ctxt, fake_service) - - self.src_msg_runner.service_delete( - ctxt, self.tgt_cell_name, fake_service['id']) - self.assertRaises(exception.ServiceNotFound, - db.service_get, ctxt, fake_service['id']) - - -class CellsTargetedMethodsTestCase(test.NoDBTestCase): - """Test case for _TargetedMessageMethods class. Most of these - tests actually test the full path from the MessageRunner through - to the functionality of the message method. Hits 2 birds with 1 - stone, even though it's a little more than a unit test. - """ - def setUp(self): - super(CellsTargetedMethodsTestCase, self).setUp() - fakes.init(self) - self.ctxt = context.RequestContext('fake', 'fake') - self._setup_attrs('api-cell', 'api-cell!child-cell2') - - def _setup_attrs(self, source_cell, target_cell): - self.tgt_cell_name = target_cell - self.src_msg_runner = fakes.get_message_runner(source_cell) - self.src_state_manager = self.src_msg_runner.state_manager - tgt_shortname = target_cell.split('!')[-1] - self.tgt_cell_mgr = fakes.get_cells_manager(tgt_shortname) - self.tgt_msg_runner = self.tgt_cell_mgr.msg_runner - self.tgt_scheduler = self.tgt_msg_runner.scheduler - self.tgt_state_manager = self.tgt_msg_runner.state_manager - methods_cls = self.tgt_msg_runner.methods_by_type['targeted'] - self.tgt_methods_cls = methods_cls - self.tgt_compute_api = methods_cls.compute_api - self.tgt_host_api = methods_cls.host_api - self.tgt_db_inst = methods_cls.db - self.tgt_c_rpcapi = methods_cls.compute_rpcapi - - def test_build_instances(self): - build_inst_kwargs = {'filter_properties': {}, - 'key1': 'value1', - 'key2': 'value2'} - with mock.patch.object(self.tgt_scheduler, - 'build_instances') as mock_build_instances: - self.src_msg_runner.build_instances(self.ctxt, self.tgt_cell_name, - build_inst_kwargs) - mock_build_instances.assert_called_with( - test.MatchType(messaging._TargetedMessage), - build_inst_kwargs) - - def _run_compute_api_method(self, method_name): - instance = fake_instance.fake_instance_obj(self.ctxt) - method_info = {'method': method_name, - 'method_args': (instance.uuid, 2, 3), - 'method_kwargs': {'arg1': 'val1', 'arg2': 'val2'}} - expected_attrs = ['metadata', 'system_metadata', 'security_groups', - 'info_cache'] - - @mock.patch.object(self.tgt_compute_api, method_name, - return_value='fake-result') - @mock.patch.object(objects.Instance, 'get_by_uuid', - return_value=instance) - def run_method(mock_get_by_uuid, mock_method): - response = self.src_msg_runner.run_compute_api_method( - self.ctxt, - self.tgt_cell_name, - method_info, - True) - result = response.value_or_raise() - self.assertEqual('fake-result', result) - - mock_get_by_uuid.assert_called_once_with(self.ctxt, instance.uuid, - expected_attrs=expected_attrs) - mock_method.assert_called_once_with(self.ctxt, instance, 2, 3, - arg1='val1', arg2='val2') - - run_method() - - def test_run_compute_api_method_expects_obj(self): - # Run compute_api start method - self._run_compute_api_method('start') - - def test_run_compute_api_method_shelve_with_info_cache(self): - # Run compute_api shelve method as it requires info_cache and - # metadata to be present in instance object - self._run_compute_api_method('shelve') - - def test_update_capabilities(self): - # Route up to API - self._setup_attrs('child-cell2', 'child-cell2!api-cell') - capabs = {'cap1': set(['val1', 'val2']), - 'cap2': set(['val3'])} - # The list(set([])) seems silly, but we can't assume the order - # of the list... This behavior should match the code we're - # testing... which is check that a set was converted to a list. - expected_capabs = {'cap1': list(set(['val1', 'val2'])), - 'cap2': ['val3']} - self.src_state_manager.get_our_capabilities = mock.Mock() - self.src_state_manager.get_our_capabilities.return_value = capabs - self.tgt_state_manager.update_cell_capabilities = mock.Mock() - self.tgt_msg_runner.tell_parents_our_capabilities = mock.Mock() - - self.src_msg_runner.tell_parents_our_capabilities(self.ctxt) - self.tgt_state_manager.update_cell_capabilities.\ - assert_called_with('child-cell2', expected_capabs) - self.tgt_msg_runner.tell_parents_our_capabilities.\ - assert_called_with(self.ctxt) - self.src_state_manager.get_our_capabilities.\ - assert_called_once() - - def test_update_capacities(self): - self._setup_attrs('child-cell2', 'child-cell2!api-cell') - capacs = 'fake_capacs' - self.src_state_manager.get_our_capacities = mock.Mock() - self.src_state_manager.get_our_capacities.return_value = capacs - self.tgt_state_manager.update_cell_capacities = mock.Mock() - self.tgt_msg_runner.tell_parents_our_capacities = mock.Mock() - - self.src_msg_runner.tell_parents_our_capacities(self.ctxt) - self.tgt_state_manager.update_cell_capacities.\ - assert_called_with('child-cell2', capacs) - self.tgt_msg_runner.tell_parents_our_capacities.\ - assert_called_with(self.ctxt) - - def test_announce_capabilities(self): - self._setup_attrs('api-cell', 'api-cell!child-cell1') - # To make this easier to test, make us only have 1 child cell. - cell_state = self.src_state_manager.child_cells['child-cell1'] - self.src_state_manager.child_cells = {'child-cell1': cell_state} - - self.tgt_msg_runner.tell_parents_our_capabilities = mock.Mock() - - self.src_msg_runner.ask_children_for_capabilities(self.ctxt) - self.tgt_msg_runner.tell_parents_our_capabilities.\ - assert_called_with(self.ctxt) - - def test_announce_capacities(self): - self._setup_attrs('api-cell', 'api-cell!child-cell1') - # To make this easier to test, make us only have 1 child cell. - cell_state = self.src_state_manager.child_cells['child-cell1'] - self.src_state_manager.child_cells = {'child-cell1': cell_state} - - self.tgt_msg_runner.tell_parents_our_capacities = mock.Mock() - - self.src_msg_runner.ask_children_for_capacities(self.ctxt) - self.tgt_msg_runner.tell_parents_our_capacities.\ - assert_called_with(self.ctxt) - - @mock.patch.object(objects.Service, 'get_by_compute_host') - def test_service_get_by_compute_host(self, mock_cp_host): - fake_host_name = 'fake-host-name' - - mock_cp_host.return_value = 'fake-service' - - 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) - mock_cp_host.assert_called_with(self.ctxt, fake_host_name) - - @mock.patch.object(objects.Service, 'get_by_args') - @mock.patch.object(objects.Service, 'save') - def test_service_update(self, mock_save, mock_get_args): - binary = 'nova-compute' - params_to_update = {'disabled': True, 'report_count': 13} - - fake_service = objects.Service(id=42, host='fake_host', - binary='nova-compute', - topic='compute') - fake_service.compute_node = objects.ComputeNode(id=1, host='fake_host') - mock_get_args.return_value = fake_service - - response = self.src_msg_runner.service_update( - self.ctxt, self.tgt_cell_name, - 'fake_host', binary, params_to_update) - result = response.value_or_raise() - self.assertIsInstance(result, objects.Service) - self.assertTrue(objects_base.obj_equal_prims(fake_service, result)) - mock_get_args.assert_called_with(self.ctxt, - 'fake_host', 'nova-compute') - mock_save.assert_called_once() - - @mock.patch.object(objects.Service, 'get_by_compute_host') - def test_proxy_rpc_to_manager_call(self, mock_cp_host): - fake_topic = 'fake-topic' - fake_rpc_message = {'method': 'fake_rpc_method', 'args': {}} - fake_host_name = 'fake-host-name' - - class FakeRPCClient(object): - - def prepare(self, timeout=5): - return self - - def call(self, ctxt, method, **kwargs): - return 'fake_result' - - def fake_get_client(target): - return FakeRPCClient() - - # (Fixme) mock will mess up when mock an object of mock - # how to mock a function of mock object's return value - self.stub_out('nova.rpc.get_client', fake_get_client) - - 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) - mock_cp_host.assert_called_with(self.ctxt, fake_host_name) - - @mock.patch.object(rpc, 'get_client') - @mock.patch.object(objects.Service, 'get_by_compute_host') - def test_proxy_rpc_to_manager_cast(self, mock_cp_host, mock_client): - fake_topic = 'fake-topic' - fake_rpc_message = {'method': 'fake_rpc_method', 'args': {}} - fake_host_name = 'fake-host-name' - target = oslo_messaging.Target(topic='fake-topic') - - rpcclient = mock.Mock() - - mock_client.return_value = rpcclient - - 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) - mock_cp_host.assert_called_with(self.ctxt, fake_host_name) - rpcclient.cast.assert_called_with(mock.ANY, 'fake_rpc_method') - mock_client.assert_called_with(target) - - def test_task_log_get_all_targeted(self): - task_name = 'fake_task_name' - begin = 'fake_begin' - end = 'fake_end' - host = 'fake_host' - state = 'fake_state' - - with mock.patch.object(self.tgt_db_inst, 'task_log_get_all') as log_a: - log_a.return_value = ['fake_result'] - response = self.src_msg_runner.task_log_get_all(self.ctxt, - self.tgt_cell_name, task_name, begin, end, host=host, - state=state) - log_a.assert_called_with(self.ctxt, task_name, - begin, end, host=host, state=state) - self.assertIsInstance(response, list) - self.assertEqual(1, len(response)) - result = response[0].value_or_raise() - self.assertEqual(['fake_result'], result) - - @mock.patch.object(objects.ComputeNode, 'get_by_id') - def test_compute_node_get(self, mock_get_id): - compute_id = 'fake-id' - mock_get_id.return_value = 'fake_result' - - response = self.src_msg_runner.compute_node_get(self.ctxt, - self.tgt_cell_name, compute_id) - result = response.value_or_raise() - self.assertEqual('fake_result', result) - mock_get_id.assert_called_with(self.ctxt, compute_id) - - @mock.patch.object(objects.ComputeNode, 'get_by_uuid', - return_value=objects.ComputeNode(uuid=uuids.cn_uuid)) - def test_compute_node_get_using_uuid(self, compute_node_get_by_uuid): - """Tests that _TargetedMessageMethods.compute_node_get handles a - UUID for the query parameter. - """ - response = self.src_msg_runner.compute_node_get( - self.ctxt, self.tgt_cell_name, uuids.cn_uuid) - result = response.value_or_raise() - self.assertEqual(uuids.cn_uuid, result.uuid) - compute_node_get_by_uuid.assert_called_once_with( - self.ctxt, uuids.cn_uuid) - - def test_actions_get(self): - fake_uuid = fake_server_actions.FAKE_UUID - fake_req_id = fake_server_actions.FAKE_REQUEST_ID1 - fake_act = fake_server_actions.FAKE_ACTIONS[fake_uuid][fake_req_id] - - with mock.patch.object(self.tgt_db_inst, 'actions_get') as ac_get: - ac_get.return_value = [fake_act] - - response = self.src_msg_runner.actions_get(self.ctxt, - self.tgt_cell_name, - fake_uuid) - result = response.value_or_raise() - self.assertEqual([jsonutils.to_primitive(fake_act)], result) - ac_get.assert_called_with(self.ctxt, fake_uuid) - - def test_action_get_by_request_id(self): - fake_uuid = fake_server_actions.FAKE_UUID - fake_req_id = fake_server_actions.FAKE_REQUEST_ID1 - fake_act = fake_server_actions.FAKE_ACTIONS[fake_uuid][fake_req_id] - - with mock.patch.object(self.tgt_db_inst, - 'action_get_by_request_id') as act_id: - act_id.return_value = fake_act - - response = self.src_msg_runner.action_get_by_request_id(self.ctxt, - self.tgt_cell_name, fake_uuid, 'req-fake') - result = response.value_or_raise() - self.assertEqual(jsonutils.to_primitive(fake_act), result) - act_id.assert_called_with(self.ctxt, fake_uuid, 'req-fake') - - def test_action_events_get(self): - fake_action_id = fake_server_actions.FAKE_ACTION_ID1 - fake_events = fake_server_actions.FAKE_EVENTS[fake_action_id] - - with mock.patch.object(self.tgt_db_inst, - 'action_events_get') as act_get: - act_get.return_value = fake_events - - response = self.src_msg_runner.action_events_get(self.ctxt, - self.tgt_cell_name, - 'fake-action') - result = response.value_or_raise() - self.assertEqual(jsonutils.to_primitive(fake_events), result) - act_get.assert_called_with(self.ctxt, 'fake-action') - - def test_validate_console_port(self): - instance_uuid = uuids.instance - instance = objects.Instance(uuid=instance_uuid) - console_port = 'fake-port' - console_type = 'fake-type' - - @mock.patch.object(objects.Instance, 'get_by_uuid', - return_value=instance) - @mock.patch.object(self.tgt_c_rpcapi, 'validate_console_port', - return_value='fake_result') - def do_test(mock_validate, mock_get): - response = self.src_msg_runner.validate_console_port(self.ctxt, - self.tgt_cell_name, instance_uuid, console_port, - console_type) - result = response.value_or_raise() - self.assertEqual('fake_result', result) - mock_get.assert_called_once_with(self.ctxt, instance_uuid) - mock_validate.assert_called_once_with(self.ctxt, instance, - console_port, console_type) - do_test() - - def test_get_migrations_for_a_given_cell(self): - filters = {'cell_name': 'child-cell2', 'status': 'confirmed'} - migrations_in_progress = [{'id': 123}] - with mock.patch.object(self.tgt_compute_api, - 'get_migrations') as get_mig: - get_mig.return_value = migrations_in_progress - - responses = self.src_msg_runner.get_migrations( - self.ctxt, - self.tgt_cell_name, False, filters) - result = responses[0].value_or_raise() - self.assertEqual(migrations_in_progress, result) - get_mig.assert_called_with(self.ctxt, filters) - - def test_get_migrations_for_an_invalid_cell(self): - filters = {'cell_name': 'invalid_Cell', 'status': 'confirmed'} - - responses = self.src_msg_runner.get_migrations( - self.ctxt, - 'api_cell!invalid_cell', False, filters) - - self.assertEqual(0, len(responses)) - - def test_call_compute_api_with_obj(self): - instance = objects.Instance() - instance.uuid = uuids.fake - self.mox.StubOutWithMock(instance, 'refresh') - # Using 'snapshot' for this test, because it - # takes args and kwargs. - self.mox.StubOutWithMock(self.tgt_compute_api, 'snapshot') - instance.refresh() - self.tgt_compute_api.snapshot( - self.ctxt, instance, 'name', - extra_properties='props').AndReturn('foo') - - self.mox.ReplayAll() - result = self.tgt_methods_cls._call_compute_api_with_obj( - self.ctxt, instance, 'snapshot', 'name', - extra_properties='props') - self.assertEqual('foo', result) - - def test_call_compute_api_with_obj_no_cache(self): - instance = objects.Instance() - instance.uuid = uuids.fake - error = exception.InstanceInfoCacheNotFound( - instance_uuid=instance.uuid) - with mock.patch.object(instance, 'refresh', side_effect=error): - self.assertRaises(exception.InstanceInfoCacheNotFound, - self.tgt_methods_cls._call_compute_api_with_obj, - self.ctxt, instance, 'snapshot') - - def test_call_delete_compute_api_with_obj_no_cache(self): - instance = objects.Instance() - instance.uuid = uuids.fake - error = exception.InstanceInfoCacheNotFound( - instance_uuid=instance.uuid) - with test.nested( - mock.patch.object(instance, 'refresh', - side_effect=error), - mock.patch.object(self.tgt_compute_api, 'delete')) as (inst, - delete): - self.tgt_methods_cls._call_compute_api_with_obj(self.ctxt, - instance, - 'delete') - delete.assert_called_once_with(self.ctxt, instance) - - def _test_instance_action_method(self, method, args, kwargs, - expected_args, expected_kwargs, - expect_result): - class FakeMessage(object): - pass - - message = FakeMessage() - message.ctxt = self.ctxt - message.need_response = expect_result - - meth_cls = self.tgt_methods_cls - self.mox.StubOutWithMock(meth_cls, '_call_compute_api_with_obj') - - method_corrections = { - 'terminate': 'delete', - } - api_method = method_corrections.get(method, method) - - meth_cls._call_compute_api_with_obj( - self.ctxt, 'fake-instance', api_method, - *expected_args, **expected_kwargs).AndReturn('meow') - - self.mox.ReplayAll() - - method_translations = {'revert_resize': 'revert_resize', - 'confirm_resize': 'confirm_resize', - 'reset_network': 'reset_network', - 'inject_network_info': 'inject_network_info', - 'set_admin_password': 'set_admin_password', - } - tgt_method = method_translations.get(method, - '%s_instance' % method) - result = getattr(meth_cls, tgt_method)( - message, 'fake-instance', *args, **kwargs) - if expect_result: - self.assertEqual('meow', result) - - def test_start_instance(self): - self._test_instance_action_method('start', (), {}, (), {}, False) - - def test_stop_instance_cast(self): - self._test_instance_action_method('stop', (), {}, (), - {'do_cast': True, - 'clean_shutdown': True}, False) - - def test_stop_instance_call(self): - self._test_instance_action_method('stop', (), {}, (), - {'do_cast': False, - 'clean_shutdown': True}, True) - - def test_reboot_instance(self): - kwargs = dict(reboot_type='HARD') - self._test_instance_action_method('reboot', (), kwargs, (), - kwargs, False) - - def test_suspend_instance(self): - self._test_instance_action_method('suspend', (), {}, (), {}, False) - - def test_resume_instance(self): - self._test_instance_action_method('resume', (), {}, (), {}, False) - - def test_get_host_uptime(self): - host_name = "fake-host" - host_uptime = (" 08:32:11 up 93 days, 18:25, 12 users, load average:" - " 0.20, 0.12, 0.14") - self.mox.StubOutWithMock(self.tgt_host_api, 'get_host_uptime') - self.tgt_host_api.get_host_uptime(self.ctxt, host_name).\ - AndReturn(host_uptime) - self.mox.ReplayAll() - response = self.src_msg_runner.get_host_uptime(self.ctxt, - self.tgt_cell_name, - host_name) - expected_host_uptime = response.value_or_raise() - self.assertEqual(host_uptime, expected_host_uptime) - - def test_terminate_instance(self): - self._test_instance_action_method('terminate', - (), {}, (), {}, False) - - def test_soft_delete_instance(self): - self._test_instance_action_method('soft_delete', - (), {}, (), {}, False) - - def test_pause_instance(self): - self._test_instance_action_method('pause', (), {}, (), {}, False) - - def test_unpause_instance(self): - self._test_instance_action_method('unpause', (), {}, (), {}, False) - - def _test_resize_instance(self, clean_shutdown=True): - kwargs = dict(flavor=dict(id=42, flavorid='orangemocchafrappuccino'), - extra_instance_updates=dict(cow='moo'), - clean_shutdown=clean_shutdown) - expected_kwargs = dict(flavor_id='orangemocchafrappuccino', cow='moo', - clean_shutdown=clean_shutdown) - self._test_instance_action_method('resize', (), kwargs, - (), expected_kwargs, - False) - - def test_resize_instance(self): - self._test_resize_instance() - - def test_resize_instance_forced_shutdown(self): - self._test_resize_instance(clean_shutdown=False) - - def test_live_migrate_instance(self): - kwargs = dict(block_migration='fake-block-mig', - disk_over_commit='fake-commit', - host_name='fake-host') - expected_args = ('fake-block-mig', 'fake-commit', 'fake-host') - self._test_instance_action_method('live_migrate', (), kwargs, - expected_args, {}, False) - - def test_revert_resize(self): - self._test_instance_action_method('revert_resize', - (), {}, (), {}, False) - - def test_confirm_resize(self): - self._test_instance_action_method('confirm_resize', - (), {}, (), {}, False) - - def test_reset_network(self): - self._test_instance_action_method('reset_network', - (), {}, (), {}, False) - - def test_inject_network_info(self): - self._test_instance_action_method('inject_network_info', - (), {}, (), {}, False) - - @mock.patch.object(objects.InstanceAction, 'action_start') - def test_snapshot_instance(self, action_start): - inst = objects.Instance(uuid=uuids.instance) - meth_cls = self.tgt_methods_cls - - self.mox.StubOutWithMock(inst, 'refresh') - self.mox.StubOutWithMock(inst, 'save') - self.mox.StubOutWithMock(meth_cls.compute_rpcapi, 'snapshot_instance') - - def check_state(expected_task_state=None): - self.assertEqual(task_states.IMAGE_SNAPSHOT_PENDING, - inst.task_state) - - inst.refresh() - inst.save(expected_task_state=[None]).WithSideEffects(check_state) - - meth_cls.compute_rpcapi.snapshot_instance(self.ctxt, - inst, 'image-id') - - self.mox.ReplayAll() - - class FakeMessage(object): - pass - - message = FakeMessage() - message.ctxt = self.ctxt - message.need_response = False - - meth_cls.snapshot_instance(message, inst, image_id='image-id') - action_start.assert_called_once_with( - message.ctxt, inst.uuid, instance_actions.CREATE_IMAGE, - want_result=False) - - @mock.patch.object(objects.InstanceAction, 'action_start') - def test_backup_instance(self, action_start): - inst = objects.Instance(uuid=uuids.instance) - meth_cls = self.tgt_methods_cls - - self.mox.StubOutWithMock(inst, 'refresh') - self.mox.StubOutWithMock(inst, 'save') - self.mox.StubOutWithMock(meth_cls.compute_rpcapi, 'backup_instance') - - def check_state(expected_task_state=None): - self.assertEqual(task_states.IMAGE_BACKUP, inst.task_state) - - inst.refresh() - inst.save(expected_task_state=[None]).WithSideEffects(check_state) - - meth_cls.compute_rpcapi.backup_instance(self.ctxt, - inst, - 'image-id', - 'backup-type', - 'rotation') - - self.mox.ReplayAll() - - class FakeMessage(object): - pass - - message = FakeMessage() - message.ctxt = self.ctxt - message.need_response = False - - meth_cls.backup_instance(message, inst, - image_id='image-id', - backup_type='backup-type', - rotation='rotation') - action_start.assert_called_once_with( - message.ctxt, inst.uuid, instance_actions.BACKUP, - want_result=False) - - def test_set_admin_password(self): - args = ['fake-password'] - self._test_instance_action_method('set_admin_password', args, {}, args, - {}, False) - - -class CellsBroadcastMethodsTestCase(test.NoDBTestCase): - """Test case for _BroadcastMessageMethods class. Most of these - tests actually test the full path from the MessageRunner through - to the functionality of the message method. Hits 2 birds with 1 - stone, even though it's a little more than a unit test. - """ - - def setUp(self): - super(CellsBroadcastMethodsTestCase, self).setUp() - fakes.init(self) - self.ctxt = context.RequestContext('fake', 'fake') - self._setup_attrs() - - def _setup_attrs(self, up=True): - mid_cell = 'child-cell2' - if up: - src_cell = 'grandchild-cell1' - tgt_cell = 'api-cell' - else: - src_cell = 'api-cell' - tgt_cell = 'grandchild-cell1' - - self.src_msg_runner = fakes.get_message_runner(src_cell) - methods_cls = self.src_msg_runner.methods_by_type['broadcast'] - self.src_methods_cls = methods_cls - self.src_db_inst = methods_cls.db - self.src_compute_api = methods_cls.compute_api - self.src_ca_rpcapi = methods_cls.consoleauth_rpcapi - - 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 - self.mid_db_inst = methods_cls.db - self.mid_compute_api = methods_cls.compute_api - self.mid_ca_rpcapi = methods_cls.consoleauth_rpcapi - - self.tgt_msg_runner = fakes.get_message_runner(tgt_cell) - methods_cls = self.tgt_msg_runner.methods_by_type['broadcast'] - self.tgt_methods_cls = methods_cls - self.tgt_db_inst = methods_cls.db - self.tgt_compute_api = methods_cls.compute_api - self.tgt_ca_rpcapi = methods_cls.consoleauth_rpcapi - - def test_at_the_top(self): - self.assertTrue(self.tgt_methods_cls._at_the_top()) - self.assertFalse(self.mid_methods_cls._at_the_top()) - self.assertFalse(self.src_methods_cls._at_the_top()) - - def test_apply_expected_states_building(self): - instance_info = objects.Instance(vm_state=vm_states.BUILDING) - expected = instance_info.obj_clone() - expected.expected_vm_state = [vm_states.BUILDING, None] - expected_vm_state = self.src_methods_cls._get_expected_vm_state( - instance_info) - self.assertEqual(expected.expected_vm_state, expected_vm_state) - - def test_apply_expected_states_resize_finish(self): - instance_info = objects.Instance(task_state=task_states.RESIZE_FINISH) - exp_states = [task_states.RESIZE_FINISH, - task_states.RESIZE_MIGRATED, - task_states.RESIZE_MIGRATING, - task_states.RESIZE_PREP] - expected = instance_info.obj_clone() - expected.expected_task_state = exp_states - expected_task_state = self.src_methods_cls._get_expected_task_state( - instance_info) - self.assertEqual(expected.expected_task_state, expected_task_state) - - def test_instance_hard_delete_everywhere(self): - # Reset this, as this is a broadcast down. - self._setup_attrs(up=False) - instance = {'uuid': 'meow'} - - # Should not be called in src (API cell) - self.mox.StubOutWithMock(self.src_compute_api, 'delete') - - self.mox.StubOutWithMock(self.mid_compute_api, 'delete') - self.mox.StubOutWithMock(self.tgt_compute_api, 'delete') - - self.mid_compute_api.delete(self.ctxt, instance) - self.tgt_compute_api.delete(self.ctxt, instance) - - self.mox.ReplayAll() - - self.src_msg_runner.instance_delete_everywhere(self.ctxt, - instance, 'hard') - - def test_instance_soft_delete_everywhere(self): - # Reset this, as this is a broadcast down. - self._setup_attrs(up=False) - instance = {'uuid': 'meow'} - - # Should not be called in src (API cell) - self.mox.StubOutWithMock(self.src_compute_api, 'soft_delete') - - self.mox.StubOutWithMock(self.mid_compute_api, 'soft_delete') - self.mox.StubOutWithMock(self.tgt_compute_api, 'soft_delete') - - self.mid_compute_api.soft_delete(self.ctxt, instance) - self.tgt_compute_api.soft_delete(self.ctxt, instance) - - self.mox.ReplayAll() - - self.src_msg_runner.instance_delete_everywhere(self.ctxt, - instance, 'soft') - - def test_sync_instances(self): - # Reset this, as this is a broadcast down. - self._setup_attrs(up=False) - project_id = 'fake_project_id' - updated_since_raw = 'fake_updated_since_raw' - updated_since_parsed = 'fake_updated_since_parsed' - deleted = 'fake_deleted' - - instance1 = objects.Instance(uuid=uuids.instance_1, deleted=False) - instance2 = objects.Instance(uuid=uuids.instance_2, deleted=True) - fake_instances = [instance1, instance2] - - self.mox.StubOutWithMock(timeutils, 'parse_isotime') - self.mox.StubOutWithMock(cells_utils, 'get_instances_to_sync') - - # Middle cell. - timeutils.parse_isotime(updated_since_raw).AndReturn( - updated_since_parsed) - cells_utils.get_instances_to_sync(self.ctxt, - updated_since=updated_since_parsed, - project_id=project_id, - deleted=deleted).AndReturn([]) - - # Bottom/Target cell - timeutils.parse_isotime(updated_since_raw).AndReturn( - updated_since_parsed) - cells_utils.get_instances_to_sync(self.ctxt, - updated_since=updated_since_parsed, - project_id=project_id, - deleted=deleted).AndReturn(fake_instances) - - self.mox.ReplayAll() - - 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(objects.ServiceList, 'get_all') - - # Calls are made from grandchild-cell to api-cell - objects.ServiceList.get_all( - mox.IgnoreArg(), disabled=None).AndReturn([4, 5]) - objects.ServiceList.get_all( - mox.IgnoreArg(), disabled=None).AndReturn([3]) - objects.ServiceList.get_all( - mox.IgnoreArg(), disabled=None).AndReturn([1, 2]) - - 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(objects.ServiceList, 'get_all') - - # Calls are made from grandchild-cell to api-cell - objects.ServiceList.get_all( - mox.IgnoreArg(), disabled=disabled).AndReturn([4, 5]) - objects.ServiceList.get_all( - mox.IgnoreArg(), disabled=disabled).AndReturn([3]) - objects.ServiceList.get_all( - mox.IgnoreArg(), disabled=disabled).AndReturn([1, 2]) - - 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) - - def test_task_log_get_all_broadcast(self): - # Reset this, as this is a broadcast down. - self._setup_attrs(up=False) - task_name = 'fake_task_name' - begin = 'fake_begin' - end = 'fake_end' - host = 'fake_host' - state = 'fake_state' - - ctxt = self.ctxt.elevated() - - self.mox.StubOutWithMock(self.src_db_inst, 'task_log_get_all') - self.mox.StubOutWithMock(self.mid_db_inst, 'task_log_get_all') - self.mox.StubOutWithMock(self.tgt_db_inst, 'task_log_get_all') - - self.src_db_inst.task_log_get_all(ctxt, task_name, - begin, end, host=host, state=state).AndReturn([1, 2]) - self.mid_db_inst.task_log_get_all(ctxt, task_name, - begin, end, host=host, state=state).AndReturn([3]) - self.tgt_db_inst.task_log_get_all(ctxt, task_name, - begin, end, host=host, state=state).AndReturn([4, 5]) - - self.mox.ReplayAll() - - responses = self.src_msg_runner.task_log_get_all(ctxt, None, - task_name, begin, end, host=host, state=state) - 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_compute_node_get_all(self): - # Reset this, as this is a broadcast down. - self._setup_attrs(up=False) - - ctxt = self.ctxt.elevated() - - self.mox.StubOutWithMock(objects.ComputeNodeList, 'get_all') - - # Calls are made from grandchild-cell to api-cell - objects.ComputeNodeList.get_all(mox.IgnoreArg()).AndReturn([4, 5]) - objects.ComputeNodeList.get_all(mox.IgnoreArg()).AndReturn([3]) - objects.ComputeNodeList.get_all(mox.IgnoreArg()).AndReturn([1, 2]) - - self.mox.ReplayAll() - - responses = self.src_msg_runner.compute_node_get_all(ctxt) - 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_compute_node_get_all_with_hyp_match(self): - # Reset this, as this is a broadcast down. - self._setup_attrs(up=False) - hypervisor_match = 'meow' - - ctxt = self.ctxt.elevated() - - self.mox.StubOutWithMock(objects.ComputeNodeList, 'get_by_hypervisor') - - # Calls are made from grandchild-cell to api-cell - objects.ComputeNodeList.get_by_hypervisor( - ctxt, hypervisor_match).AndReturn([4, 5]) - objects.ComputeNodeList.get_by_hypervisor( - ctxt, hypervisor_match).AndReturn([3]) - objects.ComputeNodeList.get_by_hypervisor( - ctxt, hypervisor_match).AndReturn([1, 2]) - - self.mox.ReplayAll() - - responses = self.src_msg_runner.compute_node_get_all(ctxt, - hypervisor_match=hypervisor_match) - 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_compute_node_stats(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, - 'compute_node_statistics') - self.mox.StubOutWithMock(self.mid_db_inst, - 'compute_node_statistics') - self.mox.StubOutWithMock(self.tgt_db_inst, - 'compute_node_statistics') - - self.src_db_inst.compute_node_statistics(ctxt).AndReturn([1, 2]) - self.mid_db_inst.compute_node_statistics(ctxt).AndReturn([3]) - self.tgt_db_inst.compute_node_statistics(ctxt).AndReturn([4, 5]) - - self.mox.ReplayAll() - - responses = self.src_msg_runner.compute_node_stats(ctxt) - 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_consoleauth_delete_tokens(self): - fake_uuid = 'fake-instance-uuid' - - # To show these should not be called in src/mid-level cell - self.mox.StubOutWithMock(self.src_ca_rpcapi, - 'delete_tokens_for_instance') - self.mox.StubOutWithMock(self.mid_ca_rpcapi, - 'delete_tokens_for_instance') - - self.mox.StubOutWithMock(self.tgt_ca_rpcapi, - 'delete_tokens_for_instance') - self.tgt_ca_rpcapi.delete_tokens_for_instance(self.ctxt, fake_uuid) - - self.mox.ReplayAll() - - self.src_msg_runner.consoleauth_delete_tokens(self.ctxt, fake_uuid) - - def test_get_migrations(self): - self._setup_attrs(up=False) - filters = {'status': 'confirmed'} - migrations_from_cell1 = [{'id': 123}] - migrations_from_cell2 = [{'id': 456}] - self.mox.StubOutWithMock(self.mid_compute_api, - 'get_migrations') - - self.mid_compute_api.get_migrations(self.ctxt, filters).\ - AndReturn(migrations_from_cell1) - - self.mox.StubOutWithMock(self.tgt_compute_api, - 'get_migrations') - - self.tgt_compute_api.get_migrations(self.ctxt, filters).\ - AndReturn(migrations_from_cell2) - - self.mox.ReplayAll() - - responses = self.src_msg_runner.get_migrations( - self.ctxt, - None, False, filters) - self.assertEqual(2, len(responses)) - for response in responses: - self.assertIn(response.value_or_raise(), [migrations_from_cell1, - migrations_from_cell2]) - - -class CellsPublicInterfacesTestCase(test.NoDBTestCase): - """Test case for the public interfaces into cells messaging.""" - def setUp(self): - super(CellsPublicInterfacesTestCase, self).setUp() - fakes.init(self) - self.ctxt = context.RequestContext('fake', 'fake') - self.our_name = 'api-cell' - self.msg_runner = fakes.get_message_runner(self.our_name) - self.state_manager = self.msg_runner.state_manager - - @mock.patch.object(messaging, '_TargetedMessage') - def test_resize_instance(self, mock_message): - instance = objects.Instance(cell_name='api-cell!child-cell') - flavor = 'fake' - extra_instance_updates = {'fake': 'fake'} - clean_shutdown = True - self.msg_runner.resize_instance(self.ctxt, instance, flavor, - extra_instance_updates, - clean_shutdown=clean_shutdown) - extra_kwargs = dict(flavor=flavor, - extra_instance_updates=extra_instance_updates, - clean_shutdown=clean_shutdown) - method_kwargs = {'instance': instance} - method_kwargs.update(extra_kwargs) - mock_message.assert_called_once_with(self.msg_runner, self.ctxt, - 'resize_instance', - method_kwargs, 'down', - instance.cell_name, - need_response=False) diff --git a/nova/tests/unit/cells/test_cells_rpc_driver.py b/nova/tests/unit/cells/test_cells_rpc_driver.py deleted file mode 100644 index 8a0ce67999..0000000000 --- a/nova/tests/unit/cells/test_cells_rpc_driver.py +++ /dev/null @@ -1,225 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# Copyright 2013 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Tests For Cells RPC Communication Driver -""" - -import mock -import oslo_messaging - -from nova.cells import messaging -from nova.cells import rpc_driver -import nova.conf -from nova import context -from nova import test -from nova.tests.unit.cells import fakes - -CONF = nova.conf.CONF - - -class CellsRPCDriverTestCase(test.NoDBTestCase): - """Test case for Cells communication via RPC.""" - - def setUp(self): - super(CellsRPCDriverTestCase, self).setUp() - fakes.init(self) - self.ctxt = context.RequestContext('fake', 'fake') - self.driver = rpc_driver.CellsRPCDriver() - - @mock.patch('nova.rpc.get_server') - def test_start_servers(self, mock_get_server): - self.flags(rpc_driver_queue_base='cells.intercell42', group='cells') - fake_msg_runner = fakes.get_message_runner('api-cell') - - class FakeInterCellRPCDispatcher(object): - def __init__(_self, msg_runner): - self.assertEqual(fake_msg_runner, msg_runner) - - endpoints = [test.MatchType(FakeInterCellRPCDispatcher)] - self.stub_out('nova.cells.rpc_driver.InterCellRPCDispatcher', - FakeInterCellRPCDispatcher) - rpcserver = mock.Mock() - mock_get_server.return_value = rpcserver - - expected_mock_get_server_called_list = [] - for message_type in messaging.MessageRunner.get_message_types(): - topic = 'cells.intercell42.' + message_type - target = oslo_messaging.Target(topic=topic, server=CONF.host) - expected_mock_get_server_called_list.append( - mock.call(target, endpoints=endpoints)) - - self.driver.start_servers(fake_msg_runner) - rpcserver.start.assert_called() - self.assertEqual(expected_mock_get_server_called_list, - mock_get_server.call_args_list) - self.assertEqual(len(messaging.MessageRunner.get_message_types()), - rpcserver.start.call_count) - self.assertEqual(len(messaging.MessageRunner.get_message_types()), - mock_get_server.call_count) - - def test_stop_servers(self): - call_info = {'stopped': []} - - class FakeRPCServer(object): - def stop(self): - call_info['stopped'].append(self) - - fake_servers = [FakeRPCServer() for x in range(5)] - self.driver.rpc_servers = fake_servers - self.driver.stop_servers() - self.assertEqual(fake_servers, call_info['stopped']) - - def test_create_transport_once(self): - # should only construct each Transport once - rpcapi = self.driver.intercell_rpcapi - - transport_url = 'amqp://fakeurl' - next_hop = fakes.FakeCellState('cellname') - next_hop.db_info['transport_url'] = transport_url - - # first call to _get_transport creates a oslo.messaging.Transport obj - with mock.patch.object(oslo_messaging, - 'get_rpc_transport') as get_trans: - transport = rpcapi._get_transport(next_hop) - get_trans.assert_called_once_with(rpc_driver.CONF, transport_url) - self.assertIn(transport_url, rpcapi.transports) - self.assertEqual(transport, rpcapi.transports[transport_url]) - - # subsequent calls should return the pre-created Transport obj - transport2 = rpcapi._get_transport(next_hop) - self.assertEqual(transport, transport2) - - def test_send_message_to_cell_cast(self): - msg_runner = fakes.get_message_runner('api-cell') - cell_state = fakes.get_cell_state('api-cell', 'child-cell2') - message = messaging._TargetedMessage(msg_runner, - self.ctxt, 'fake', {}, 'down', cell_state, fanout=False) - - expected_server_params = {'hostname': 'rpc_host2', - 'password': 'password2', - 'port': 3092, - 'username': 'username2', - 'virtual_host': 'rpc_vhost2'} - expected_url = ('rabbit://%(username)s:%(password)s@' - '%(hostname)s:%(port)d/%(virtual_host)s' % - expected_server_params) - - rpcapi = self.driver.intercell_rpcapi - rpcclient = mock.Mock() - - with mock.patch.object(rpcapi, '_get_client') as m_get_client: - m_get_client.return_value = rpcclient - - self.driver.send_message_to_cell(cell_state, message) - m_get_client.assert_called_with(cell_state, - 'cells.intercell.targeted') - self.assertEqual(expected_url, - cell_state.db_info['transport_url']) - rpcclient.cast.assert_called_with(mock.ANY, - 'process_message', - message=message.to_json()) - - def test_send_message_to_cell_fanout_cast(self): - msg_runner = fakes.get_message_runner('api-cell') - cell_state = fakes.get_cell_state('api-cell', 'child-cell2') - message = messaging._TargetedMessage(msg_runner, - self.ctxt, 'fake', {}, 'down', cell_state, fanout=True) - - expected_server_params = {'hostname': 'rpc_host2', - 'password': 'password2', - 'port': 3092, - 'username': 'username2', - 'virtual_host': 'rpc_vhost2'} - expected_url = ('rabbit://%(username)s:%(password)s@' - '%(hostname)s:%(port)d/%(virtual_host)s' % - expected_server_params) - - rpcapi = self.driver.intercell_rpcapi - rpcclient = mock.Mock() - - with mock.patch.object(rpcapi, '_get_client') as m_get_client: - m_get_client.return_value = rpcclient - - rpcclient.return_value = rpcclient - rpcclient.prepare.return_value = rpcclient - - self.driver.send_message_to_cell(cell_state, message) - m_get_client.assert_called_with(cell_state, - 'cells.intercell.targeted') - self.assertEqual(expected_url, - cell_state.db_info['transport_url']) - rpcclient.prepare.assert_called_with(fanout=True) - rpcclient.cast.assert_called_with(mock.ANY, - 'process_message', - message=message.to_json()) - - def test_rpc_topic_uses_message_type(self): - self.flags(rpc_driver_queue_base='cells.intercell42', group='cells') - msg_runner = fakes.get_message_runner('api-cell') - cell_state = fakes.get_cell_state('api-cell', 'child-cell2') - message = messaging._BroadcastMessage(msg_runner, - self.ctxt, 'fake', {}, 'down', fanout=True) - message.message_type = 'fake-message-type' - - expected_server_params = {'hostname': 'rpc_host2', - 'password': 'password2', - 'port': 3092, - 'username': 'username2', - 'virtual_host': 'rpc_vhost2'} - expected_url = ('rabbit://%(username)s:%(password)s@' - '%(hostname)s:%(port)d/%(virtual_host)s' % - expected_server_params) - - rpcapi = self.driver.intercell_rpcapi - rpcclient = mock.Mock() - - with mock.patch.object(rpcapi, '_get_client') as m_get_client: - m_get_client.return_value = rpcclient - - rpcclient.prepare(fanout=True) - rpcclient.prepare.return_value = rpcclient - - self.driver.send_message_to_cell(cell_state, message) - m_get_client.assert_called_with(cell_state, - 'cells.intercell42.fake-message-type') - self.assertEqual(expected_url, - cell_state.db_info['transport_url']) - rpcclient.prepare.assert_called_with(fanout=True) - rpcclient.cast.assert_called_with(mock.ANY, - 'process_message', - message=message.to_json()) - - def test_process_message(self): - msg_runner = fakes.get_message_runner('api-cell') - dispatcher = rpc_driver.InterCellRPCDispatcher(msg_runner) - message = messaging._BroadcastMessage(msg_runner, - self.ctxt, 'fake', {}, 'down', fanout=True) - - call_info = {} - - def _fake_message_from_json(json_message): - call_info['json_message'] = json_message - self.assertEqual(message.to_json(), json_message) - return message - - def _fake_process(): - call_info['process_called'] = True - - msg_runner.message_from_json = _fake_message_from_json - message.process = _fake_process - dispatcher.process_message(self.ctxt, message.to_json()) - self.assertEqual(message.to_json(), call_info['json_message']) - self.assertTrue(call_info['process_called']) diff --git a/nova/tests/unit/cells/test_cells_rpcapi.py b/nova/tests/unit/cells/test_cells_rpcapi.py deleted file mode 100644 index ccbc7d3e32..0000000000 --- a/nova/tests/unit/cells/test_cells_rpcapi.py +++ /dev/null @@ -1,734 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Tests For Cells RPCAPI -""" - -import mock -from oslo_utils.fixture import uuidsentinel as uuids -import six - -from nova.cells import rpcapi as cells_rpcapi -from nova import exception -from nova import objects -from nova import test -from nova.tests.unit import fake_instance - - -class CellsAPITestCase(test.NoDBTestCase): - """Test case for cells.api interfaces.""" - - def setUp(self): - super(CellsAPITestCase, self).setUp() - self.fake_topic = 'cells' - self.fake_context = 'fake_context' - self.flags(enable=True, group='cells') - self.cells_rpcapi = cells_rpcapi.CellsAPI() - - def _stub_rpc_method(self, rpc_method, result): - call_info = {} - - orig_prepare = self.cells_rpcapi.client.prepare - - def fake_rpc_prepare(self_rpcclient, **kwargs): - if 'version' in kwargs: - call_info['version'] = kwargs.pop('version') - return self.cells_rpcapi.client - - def fake_csv(self, version): - return orig_prepare(version).can_send_version() - - def fake_rpc_method(self, ctxt, method, **kwargs): - call_info['context'] = ctxt - call_info['method'] = method - call_info['args'] = kwargs - return result - - self.stub_out('oslo_messaging.rpc.client.RPCClient.prepare', - fake_rpc_prepare) - self.stub_out('oslo_messaging.rpc.client.RPCClient.can_send_version', - fake_csv) - self.stub_out('oslo_messaging.rpc.client.RPCClient.%s' % rpc_method, - fake_rpc_method) - - return call_info - - def _check_result(self, call_info, method, args, version=None): - self.assertEqual(self.fake_topic, - self.cells_rpcapi.client.target.topic) - self.assertEqual(self.fake_context, call_info['context']) - self.assertEqual(method, call_info['method']) - self.assertEqual(args, call_info['args']) - if version is not None: - self.assertIn('version', call_info) - self.assertIsInstance(call_info['version'], six.string_types, - msg="Message version %s is not a string" % - call_info['version']) - self.assertEqual(version, call_info['version']) - else: - self.assertNotIn('version', call_info) - - def test_cast_compute_api_method(self): - fake_cell_name = 'fake_cell_name' - fake_method = 'fake_method' - fake_method_args = (1, 2) - fake_method_kwargs = {'kwarg1': 10, 'kwarg2': 20} - - expected_method_info = {'method': fake_method, - 'method_args': fake_method_args, - 'method_kwargs': fake_method_kwargs} - expected_args = {'method_info': expected_method_info, - 'cell_name': fake_cell_name, - 'call': False} - - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.cast_compute_api_method(self.fake_context, - fake_cell_name, fake_method, - *fake_method_args, **fake_method_kwargs) - self._check_result(call_info, 'run_compute_api_method', - expected_args) - - def test_call_compute_api_method(self): - fake_cell_name = 'fake_cell_name' - fake_method = 'fake_method' - fake_method_args = (1, 2) - fake_method_kwargs = {'kwarg1': 10, 'kwarg2': 20} - fake_response = 'fake_response' - - expected_method_info = {'method': fake_method, - 'method_args': fake_method_args, - 'method_kwargs': fake_method_kwargs} - expected_args = {'method_info': expected_method_info, - 'cell_name': fake_cell_name, - 'call': True} - - call_info = self._stub_rpc_method('call', fake_response) - - result = self.cells_rpcapi.call_compute_api_method(self.fake_context, - fake_cell_name, fake_method, - *fake_method_args, **fake_method_kwargs) - self._check_result(call_info, 'run_compute_api_method', - expected_args) - self.assertEqual(fake_response, result) - - def test_build_instances(self): - call_info = self._stub_rpc_method('cast', None) - - instances = [objects.Instance(id=1), - objects.Instance(id=2)] - - self.cells_rpcapi.build_instances( - self.fake_context, instances=instances, - image={'fake': 'image'}, arg1=1, arg2=2, arg3=3) - - expected_args = {'build_inst_kwargs': {'instances': instances, - 'image': {'fake': 'image'}, - 'arg1': 1, - 'arg2': 2, - 'arg3': 3}} - self._check_result(call_info, 'build_instances', - expected_args, version='1.34') - - def test_get_capacities(self): - capacity_info = {"capacity": "info"} - call_info = self._stub_rpc_method('call', - result=capacity_info) - result = self.cells_rpcapi.get_capacities(self.fake_context, - cell_name="name") - self._check_result(call_info, 'get_capacities', - {'cell_name': 'name'}, version='1.9') - self.assertEqual(capacity_info, result) - - def test_instance_delete_everywhere(self): - instance = fake_instance.fake_instance_obj(self.fake_context) - - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.instance_delete_everywhere( - self.fake_context, instance, - 'fake-type') - - expected_args = {'instance': instance, - 'delete_type': 'fake-type'} - self._check_result(call_info, 'instance_delete_everywhere', - expected_args, version='1.27') - - def test_get_cell_info_for_neighbors(self): - call_info = self._stub_rpc_method('call', 'fake_response') - result = self.cells_rpcapi.get_cell_info_for_neighbors( - self.fake_context) - self._check_result(call_info, 'get_cell_info_for_neighbors', {}, - version='1.1') - self.assertEqual('fake_response', result) - - def test_sync_instances(self): - call_info = self._stub_rpc_method('cast', None) - self.cells_rpcapi.sync_instances(self.fake_context, - project_id='fake_project', updated_since='fake_time', - deleted=True) - - expected_args = {'project_id': 'fake_project', - 'updated_since': 'fake_time', - '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('fake_response', result) - - 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('fake_response', result) - - def test_get_host_uptime(self): - call_info = self._stub_rpc_method('call', 'fake_response') - result = self.cells_rpcapi.get_host_uptime( - self.fake_context, host_name='fake-host-name') - expected_args = {'host_name': 'fake-host-name'} - self._check_result(call_info, 'get_host_uptime', - expected_args, - version='1.17') - self.assertEqual('fake_response', result) - - def test_service_update(self): - call_info = self._stub_rpc_method('call', 'fake_response') - result = self.cells_rpcapi.service_update( - self.fake_context, host_name='fake-host-name', - binary='nova-api', params_to_update={'disabled': True}) - expected_args = { - 'host_name': 'fake-host-name', - 'binary': 'nova-api', - 'params_to_update': {'disabled': True}} - self._check_result(call_info, 'service_update', - expected_args, - version='1.7') - self.assertEqual('fake_response', result) - - def test_service_delete(self): - call_info = self._stub_rpc_method('call', None) - cell_service_id = 'cell@id' - result = self.cells_rpcapi.service_delete( - self.fake_context, cell_service_id=cell_service_id) - expected_args = {'cell_service_id': cell_service_id} - self._check_result(call_info, 'service_delete', - expected_args, version='1.26') - self.assertIsNone(result) - - 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) - expected_args = {'rpc_message': 'fake-msg', - 'topic': 'fake-topic', - 'call': True} - self._check_result(call_info, 'proxy_rpc_to_manager', - expected_args, - version='1.2') - self.assertEqual('fake_response', result) - - def test_task_log_get_all(self): - call_info = self._stub_rpc_method('call', 'fake_response') - result = self.cells_rpcapi.task_log_get_all(self.fake_context, - task_name='fake_name', - period_beginning='fake_begin', - period_ending='fake_end', - host='fake_host', - state='fake_state') - - expected_args = {'task_name': 'fake_name', - 'period_beginning': 'fake_begin', - 'period_ending': 'fake_end', - 'host': 'fake_host', - 'state': 'fake_state'} - self._check_result(call_info, 'task_log_get_all', expected_args, - version='1.3') - self.assertEqual('fake_response', result) - - def test_compute_node_get_all(self): - call_info = self._stub_rpc_method('call', 'fake_response') - result = self.cells_rpcapi.compute_node_get_all(self.fake_context, - hypervisor_match='fake-match') - - expected_args = {'hypervisor_match': 'fake-match'} - self._check_result(call_info, 'compute_node_get_all', expected_args, - version='1.4') - self.assertEqual('fake_response', result) - - def test_compute_node_stats(self): - call_info = self._stub_rpc_method('call', 'fake_response') - result = self.cells_rpcapi.compute_node_stats(self.fake_context) - expected_args = {} - self._check_result(call_info, 'compute_node_stats', - expected_args, version='1.4') - self.assertEqual('fake_response', result) - - def test_compute_node_get(self): - call_info = self._stub_rpc_method('call', 'fake_response') - result = self.cells_rpcapi.compute_node_get(self.fake_context, - 'fake_compute_id') - expected_args = {'compute_id': 'fake_compute_id'} - self._check_result(call_info, 'compute_node_get', - expected_args, version='1.4') - self.assertEqual('fake_response', result) - - def test_compute_node_get_too_old(self): - """Tests that ComputeHostNotFound is raised if passed a compute node - uuid but we can't send it on the 1.38 version. - """ - with mock.patch.object(self.cells_rpcapi.client, 'can_send_version', - return_value=False) as can_send_version_mock: - self.assertRaises(exception.ComputeHostNotFound, - self.cells_rpcapi.compute_node_get, - self.fake_context, uuids.compute_id) - can_send_version_mock.assert_called_once_with('1.38') - - def test_compute_node_get_with_uuid(self): - """Tests that we send a message at 1.38 if the compute node uuid is - passed in and the version check passes. - """ - fake_compute_node = objects.ComputeNode(uuid=uuids.compute_id) - call_info = self._stub_rpc_method('call', fake_compute_node) - self.assertEqual(fake_compute_node, - self.cells_rpcapi.compute_node_get( - self.fake_context, uuids.compute_id)) - args = dict(compute_id=uuids.compute_id) - self._check_result(call_info, 'compute_node_get', args, version='1.38') - - def test_actions_get(self): - fake_instance = {'uuid': uuids.instance, 'cell_name': 'region!child'} - call_info = self._stub_rpc_method('call', 'fake_response') - result = self.cells_rpcapi.actions_get(self.fake_context, - fake_instance) - expected_args = {'cell_name': 'region!child', - 'instance_uuid': fake_instance['uuid']} - self._check_result(call_info, 'actions_get', expected_args, - version='1.5') - self.assertEqual('fake_response', result) - - def test_actions_get_no_cell(self): - fake_instance = {'uuid': uuids.instance, 'cell_name': None} - self.assertRaises(exception.InstanceUnknownCell, - self.cells_rpcapi.actions_get, self.fake_context, - fake_instance) - - def test_action_get_by_request_id(self): - fake_instance = {'uuid': uuids.instance, 'cell_name': 'region!child'} - call_info = self._stub_rpc_method('call', 'fake_response') - result = self.cells_rpcapi.action_get_by_request_id(self.fake_context, - fake_instance, - 'req-fake') - expected_args = {'cell_name': 'region!child', - 'instance_uuid': fake_instance['uuid'], - 'request_id': 'req-fake'} - self._check_result(call_info, 'action_get_by_request_id', - expected_args, version='1.5') - self.assertEqual('fake_response', result) - - def test_action_get_by_request_id_no_cell(self): - fake_instance = {'uuid': uuids.instance, 'cell_name': None} - self.assertRaises(exception.InstanceUnknownCell, - self.cells_rpcapi.action_get_by_request_id, - self.fake_context, fake_instance, 'req-fake') - - def test_action_events_get(self): - fake_instance = {'uuid': uuids.instance, 'cell_name': 'region!child'} - call_info = self._stub_rpc_method('call', 'fake_response') - result = self.cells_rpcapi.action_events_get(self.fake_context, - fake_instance, - 'fake-action') - expected_args = {'cell_name': 'region!child', - 'action_id': 'fake-action'} - self._check_result(call_info, 'action_events_get', expected_args, - version='1.5') - self.assertEqual('fake_response', result) - - def test_action_events_get_no_cell(self): - fake_instance = {'uuid': uuids.instance, 'cell_name': None} - self.assertRaises(exception.InstanceUnknownCell, - self.cells_rpcapi.action_events_get, - self.fake_context, fake_instance, 'fake-action') - - def test_consoleauth_delete_tokens(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.consoleauth_delete_tokens(self.fake_context, - uuids.instance) - - expected_args = {'instance_uuid': uuids.instance} - self._check_result(call_info, 'consoleauth_delete_tokens', - expected_args, version='1.6') - - def test_validate_console_port(self): - call_info = self._stub_rpc_method('call', 'fake_response') - - result = self.cells_rpcapi.validate_console_port(self.fake_context, - uuids.instance, 'fake-port', 'fake-type') - - expected_args = {'instance_uuid': uuids.instance, - 'console_port': 'fake-port', - 'console_type': 'fake-type'} - self._check_result(call_info, 'validate_console_port', - expected_args, version='1.6') - self.assertEqual('fake_response', result) - - def test_get_migrations(self): - call_info = self._stub_rpc_method('call', None) - filters = {'cell_name': 'ChildCell', 'status': 'confirmed'} - - self.cells_rpcapi.get_migrations(self.fake_context, filters) - - expected_args = {'filters': filters} - self._check_result(call_info, 'get_migrations', expected_args, - version="1.11") - - def test_start_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.start_instance( - self.fake_context, 'fake-instance') - - expected_args = {'instance': 'fake-instance'} - self._check_result(call_info, 'start_instance', - expected_args, version='1.12') - - def test_stop_instance_cast(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.stop_instance( - self.fake_context, 'fake-instance', do_cast=True, - clean_shutdown=True) - - expected_args = {'instance': 'fake-instance', - 'do_cast': True, - 'clean_shutdown': True} - self._check_result(call_info, 'stop_instance', - expected_args, version='1.31') - - def test_stop_instance_call(self): - call_info = self._stub_rpc_method('call', 'fake_response') - - result = self.cells_rpcapi.stop_instance( - self.fake_context, 'fake-instance', do_cast=False, - clean_shutdown=True) - - expected_args = {'instance': 'fake-instance', - 'do_cast': False, - 'clean_shutdown': True} - self._check_result(call_info, 'stop_instance', - expected_args, version='1.31') - self.assertEqual('fake_response', result) - - def test_cell_create(self): - call_info = self._stub_rpc_method('call', 'fake_response') - - result = self.cells_rpcapi.cell_create(self.fake_context, 'values') - - expected_args = {'values': 'values'} - self._check_result(call_info, 'cell_create', - expected_args, version='1.13') - self.assertEqual('fake_response', result) - - def test_cell_update(self): - call_info = self._stub_rpc_method('call', 'fake_response') - - result = self.cells_rpcapi.cell_update(self.fake_context, - 'cell_name', 'values') - - expected_args = {'cell_name': 'cell_name', - 'values': 'values'} - self._check_result(call_info, 'cell_update', - expected_args, version='1.13') - self.assertEqual('fake_response', result) - - def test_cell_delete(self): - call_info = self._stub_rpc_method('call', 'fake_response') - - result = self.cells_rpcapi.cell_delete(self.fake_context, - 'cell_name') - - expected_args = {'cell_name': 'cell_name'} - self._check_result(call_info, 'cell_delete', - expected_args, version='1.13') - self.assertEqual('fake_response', result) - - def test_cell_get(self): - call_info = self._stub_rpc_method('call', 'fake_response') - - result = self.cells_rpcapi.cell_get(self.fake_context, - 'cell_name') - - expected_args = {'cell_name': 'cell_name'} - self._check_result(call_info, 'cell_get', - expected_args, version='1.13') - self.assertEqual('fake_response', result) - - def test_reboot_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.reboot_instance( - self.fake_context, 'fake-instance', - block_device_info='ignored', reboot_type='HARD') - - expected_args = {'instance': 'fake-instance', - 'reboot_type': 'HARD'} - self._check_result(call_info, 'reboot_instance', - expected_args, version='1.14') - - def test_pause_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.pause_instance( - self.fake_context, 'fake-instance') - - expected_args = {'instance': 'fake-instance'} - self._check_result(call_info, 'pause_instance', - expected_args, version='1.19') - - def test_unpause_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.unpause_instance( - self.fake_context, 'fake-instance') - - expected_args = {'instance': 'fake-instance'} - self._check_result(call_info, 'unpause_instance', - expected_args, version='1.19') - - def test_suspend_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.suspend_instance( - self.fake_context, 'fake-instance') - - expected_args = {'instance': 'fake-instance'} - self._check_result(call_info, 'suspend_instance', - expected_args, version='1.15') - - def test_resume_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.resume_instance( - self.fake_context, 'fake-instance') - - expected_args = {'instance': 'fake-instance'} - self._check_result(call_info, 'resume_instance', - expected_args, version='1.15') - - def test_terminate_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.terminate_instance(self.fake_context, - 'fake-instance', [], - delete_type='delete') - expected_args = {'instance': 'fake-instance', - 'delete_type': 'delete'} - self._check_result(call_info, 'terminate_instance', - expected_args, version='1.36') - - def test_soft_delete_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.soft_delete_instance(self.fake_context, - 'fake-instance') - expected_args = {'instance': 'fake-instance'} - self._check_result(call_info, 'soft_delete_instance', - expected_args, version='1.18') - - def test_resize_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.resize_instance(self.fake_context, - 'fake-instance', - dict(cow='moo'), - 'fake-hint', - 'fake-flavor', - 'fake-reservations', - clean_shutdown=True) - expected_args = {'instance': 'fake-instance', - 'flavor': 'fake-flavor', - 'extra_instance_updates': dict(cow='moo'), - 'clean_shutdown': True} - self._check_result(call_info, 'resize_instance', - expected_args, version='1.33') - - def test_resize_instance_not_passing_request_spec(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.resize_instance(self.fake_context, - 'fake-instance', - dict(cow='moo'), - 'fake-hint', - 'fake-flavor', - 'fake-reservations', - clean_shutdown=True, - request_spec='fake-spec') - expected_args = {'instance': 'fake-instance', - 'flavor': 'fake-flavor', - 'extra_instance_updates': dict(cow='moo'), - 'clean_shutdown': True} - self._check_result(call_info, 'resize_instance', - expected_args, version='1.33') - - def test_live_migrate_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.live_migrate_instance(self.fake_context, - 'fake-instance', - 'fake-host', - 'fake-block', - 'fake-commit') - expected_args = {'instance': 'fake-instance', - 'block_migration': 'fake-block', - 'disk_over_commit': 'fake-commit', - 'host_name': 'fake-host'} - self._check_result(call_info, 'live_migrate_instance', - expected_args, version='1.20') - - def test_live_migrate_instance_not_passing_request_spec(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.live_migrate_instance(self.fake_context, - 'fake-instance', - 'fake-host', - 'fake-block', - 'fake-commit', - 'fake-spec') - expected_args = {'instance': 'fake-instance', - 'block_migration': 'fake-block', - 'disk_over_commit': 'fake-commit', - 'host_name': 'fake-host'} - self._check_result(call_info, 'live_migrate_instance', - expected_args, version='1.20') - - def test_rebuild_instance_not_passing_request_spec(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.rebuild_instance(self.fake_context, - 'fake-instance', - 'fake-pass', - 'fake-files', - 'fake-image_ref', - 'fake-orig_image_ref', - 'fake-orig_sys_metadata', - 'fake-bdms', - recreate=False, - on_shared_storage=False, - host=None, - preserve_ephemeral=False, - request_spec='fake-spec', - kwargs=None) - expected_args = {'instance': 'fake-instance', - 'image_href': 'fake-image_ref', - 'admin_password': 'fake-pass', - 'files_to_inject': 'fake-files', - 'preserve_ephemeral': False, - 'kwargs': None} - self._check_result(call_info, 'rebuild_instance', - expected_args, version='1.25') - - def test_revert_resize(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.revert_resize(self.fake_context, - 'fake-instance', - 'fake-migration', - 'fake-dest', - 'resvs') - expected_args = {'instance': 'fake-instance'} - self._check_result(call_info, 'revert_resize', - expected_args, version='1.21') - - def test_confirm_resize(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.confirm_resize(self.fake_context, - 'fake-instance', - 'fake-migration', - 'fake-source', - 'resvs') - expected_args = {'instance': 'fake-instance'} - self._check_result(call_info, 'confirm_resize', - expected_args, version='1.21') - - def test_reset_network(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.reset_network(self.fake_context, - 'fake-instance') - expected_args = {'instance': 'fake-instance'} - self._check_result(call_info, 'reset_network', - expected_args, version='1.22') - - def test_inject_network_info(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.inject_network_info(self.fake_context, - 'fake-instance') - expected_args = {'instance': 'fake-instance'} - self._check_result(call_info, 'inject_network_info', - expected_args, version='1.23') - - def test_snapshot_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.snapshot_instance(self.fake_context, - 'fake-instance', - 'image-id') - expected_args = {'instance': 'fake-instance', - 'image_id': 'image-id'} - self._check_result(call_info, 'snapshot_instance', - expected_args, version='1.24') - - def test_backup_instance(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.backup_instance(self.fake_context, - 'fake-instance', - 'image-id', - 'backup-type', - 'rotation') - expected_args = {'instance': 'fake-instance', - 'image_id': 'image-id', - 'backup_type': 'backup-type', - 'rotation': 'rotation'} - self._check_result(call_info, 'backup_instance', - expected_args, version='1.24') - - def test_set_admin_password(self): - call_info = self._stub_rpc_method('cast', None) - - self.cells_rpcapi.set_admin_password(self.fake_context, - 'fake-instance', 'fake-password') - - expected_args = {'instance': 'fake-instance', - 'new_pass': 'fake-password'} - self._check_result(call_info, 'set_admin_password', - expected_args, version='1.29') diff --git a/nova/tests/unit/cells/test_cells_scheduler.py b/nova/tests/unit/cells/test_cells_scheduler.py deleted file mode 100644 index f49b9ce496..0000000000 --- a/nova/tests/unit/cells/test_cells_scheduler.py +++ /dev/null @@ -1,474 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Tests For CellsScheduler -""" -import copy - -import mock -from oslo_utils.fixture import uuidsentinel -from oslo_utils import uuidutils - -from nova import block_device -from nova.cells import filters -from nova.cells import weights -from nova.compute import vm_states -from nova import context -from nova import exception -from nova import objects -from nova import test -from nova.tests.unit.cells import fakes -from nova.tests.unit import fake_block_device - - -class FakeFilterClass1(filters.BaseCellFilter): - pass - - -class FakeFilterClass2(filters.BaseCellFilter): - pass - - -class FakeWeightClass1(weights.BaseCellWeigher): - def _weigh_object(self, obj, weight_properties): - pass - - -class FakeWeightClass2(weights.BaseCellWeigher): - def _weigh_object(self, obj, weight_properties): - pass - - -class CellsSchedulerTestCase(test.TestCase): - """Test case for CellsScheduler class.""" - - def setUp(self): - super(CellsSchedulerTestCase, self).setUp() - self.flags(scheduler_filter_classes=[], scheduler_weight_classes=[], - group='cells') - self._init_cells_scheduler() - - def _init_cells_scheduler(self): - fakes.init(self) - self.msg_runner = fakes.get_message_runner('api-cell') - self.scheduler = self.msg_runner.scheduler - self.state_manager = self.msg_runner.state_manager - self.my_cell_state = self.state_manager.get_my_state() - self.ctxt = context.RequestContext('fake', 'fake') - instance_uuids = [] - for x in range(3): - instance_uuids.append(uuidutils.generate_uuid()) - self.instance_uuids = instance_uuids - self.instances = [objects.Instance(uuid=uuid, id=id) - for id, uuid in enumerate(instance_uuids)] - self.request_spec = { - 'num_instances': len(instance_uuids), - 'instance_properties': self.instances[0], - 'instance_type': 'fake_type', - 'image': 'fake_image'} - self.build_inst_kwargs = { - 'instances': self.instances, - 'image': 'fake_image', - 'filter_properties': {'instance_type': 'fake_type'}, - 'security_groups': 'fake_sec_groups', - 'block_device_mapping': 'fake_bdm'} - - @mock.patch('nova.objects.Instance.update') - def test_create_instances_here_pops_problematic_properties(self, - mock_update): - values = { - 'uuid': uuidsentinel.instance, - 'metadata': [], - 'id': 1, - 'name': 'foo', - 'info_cache': 'bar', - 'security_groups': 'not secure', - 'flavor': 'chocolate', - 'pci_requests': 'no thanks', - 'ec2_ids': 'prime', - } - block_device_mapping = [ - objects.BlockDeviceMapping(context=self.ctxt, - **fake_block_device.FakeDbBlockDeviceDict( - block_device.create_image_bdm( - uuidsentinel.fake_image_ref), - anon=True)) - ] - - @mock.patch.object(self.scheduler.compute_api, - 'create_db_entry_for_new_instance') - @mock.patch.object(self.scheduler.compute_api, - '_bdm_validate_set_size_and_instance') - def test(mock_bdm_validate, mock_create_db): - self.scheduler._create_instances_here( - self.ctxt, [uuidsentinel.instance], values, - objects.Flavor(), 'foo', [], block_device_mapping) - - test() - - # NOTE(danms): Make sure that only the expected properties - # are applied to the instance object. The complex ones that - # would have been mangled over RPC should be removed. - mock_update.assert_called_once_with( - {'uuid': uuidsentinel.instance, - 'metadata': {}}) - - def test_build_instances_selects_child_cell(self): - # Make sure there's no capacity info so we're sure to - # select a child cell - our_cell_info = self.state_manager.get_my_state() - our_cell_info.capacities = {} - - call_info = {'times': 0} - - orig_fn = self.msg_runner.build_instances - - def msg_runner_build_instances(self_mr, ctxt, target_cell, - build_inst_kwargs): - # This gets called twice. Once for our running it - # in this cell.. and then it'll get called when the - # child cell is picked. So, first time.. just run it - # like normal. - if not call_info['times']: - call_info['times'] += 1 - return orig_fn(ctxt, target_cell, build_inst_kwargs) - call_info['ctxt'] = ctxt - call_info['target_cell'] = target_cell - call_info['build_inst_kwargs'] = build_inst_kwargs - - def fake_build_request_spec(image, instances): - request_spec = { - 'num_instances': len(instances), - 'image': image} - return request_spec - - self.stub_out('nova.cells.messaging.MessageRunner.build_instances', - msg_runner_build_instances) - self.stub_out('nova.scheduler.utils.build_request_spec', - fake_build_request_spec) - - self.msg_runner.build_instances(self.ctxt, self.my_cell_state, - self.build_inst_kwargs) - - self.assertEqual(self.ctxt, call_info['ctxt']) - self.assertEqual(self.build_inst_kwargs, - call_info['build_inst_kwargs']) - child_cells = self.state_manager.get_child_cells() - self.assertIn(call_info['target_cell'], child_cells) - - def test_build_instances_selects_current_cell(self): - self.flags(scheduler='nova.cells.scheduler.CellsScheduler', - group='cells') - # Make sure there's no child cells so that we will be - # selected - self.state_manager.child_cells = {} - - call_info = {} - build_inst_kwargs = copy.deepcopy(self.build_inst_kwargs) - - def fake_create_instances_here(self_cs, ctxt, instance_uuids, - instance_properties, instance_type, image, security_groups, - block_device_mapping): - call_info['ctxt'] = ctxt - call_info['instance_uuids'] = instance_uuids - call_info['instance_properties'] = instance_properties - call_info['instance_type'] = instance_type - call_info['image'] = image - call_info['security_groups'] = security_groups - call_info['block_device_mapping'] = block_device_mapping - return self.instances - - def fake_rpc_build_instances(self, ctxt, **build_inst_kwargs): - call_info['build_inst_kwargs'] = build_inst_kwargs - - def fake_build_request_spec(image, instances): - request_spec = { - 'num_instances': len(instances), - 'image': image} - return request_spec - - self.stub_out('nova.cells.scheduler.CellsScheduler.' - '_create_instances_here', - fake_create_instances_here) - self.stub_out('nova.conductor.api.ComputeTaskAPI.' - 'build_instances', fake_rpc_build_instances) - self.stub_out('nova.scheduler.utils.build_request_spec', - fake_build_request_spec) - - self.msg_runner.build_instances(self.ctxt, self.my_cell_state, - build_inst_kwargs) - - self.assertEqual(self.ctxt, call_info['ctxt']) - self.assertEqual(self.instance_uuids, call_info['instance_uuids']) - self.assertEqual(self.build_inst_kwargs['instances'][0]['id'], - call_info['instance_properties']['id']) - self.assertEqual( - self.build_inst_kwargs['filter_properties']['instance_type'], - call_info['instance_type']) - self.assertEqual(self.build_inst_kwargs['image'], call_info['image']) - self.assertEqual(self.build_inst_kwargs['security_groups'], - call_info['security_groups']) - self.assertEqual(self.build_inst_kwargs['block_device_mapping'], - call_info['block_device_mapping']) - self.assertEqual(build_inst_kwargs, - call_info['build_inst_kwargs']) - self.assertEqual(self.instance_uuids, call_info['instance_uuids']) - - def test_build_instances_retries_when_no_cells_avail(self): - self.flags(scheduler='nova.cells.scheduler.CellsScheduler', - scheduler_retries=7, group='cells') - - call_info = {'num_tries': 0, 'errored_uuids': []} - - def fake_grab_target_cells(self, filter_properties): - call_info['num_tries'] += 1 - raise exception.NoCellsAvailable() - - def fake_sleep(_secs): - return - - def fake_instance_save(inst): - self.assertEqual(vm_states.ERROR, inst.vm_state) - call_info['errored_uuids'].append(inst.uuid) - - def fake_build_request_spec(image, instances): - request_spec = { - 'num_instances': len(instances), - 'image': image} - return request_spec - - self.stub_out('nova.cells.scheduler.CellsScheduler._grab_target_cells', - fake_grab_target_cells) - self.stub_out('time.sleep', fake_sleep) - self.stub_out('nova.objects.Instance.save', fake_instance_save) - self.stub_out('nova.scheduler.utils.build_request_spec', - fake_build_request_spec) - - self.msg_runner.build_instances(self.ctxt, self.my_cell_state, - self.build_inst_kwargs) - - self.assertEqual(8, call_info['num_tries']) - self.assertEqual(self.instance_uuids, call_info['errored_uuids']) - - def test_filter_schedule_skipping(self): - # if a filter handles scheduling, short circuit - mock_func = mock.Mock() - self.scheduler._grab_target_cells = mock.Mock(return_value=None) - self.scheduler._schedule_build_to_cells(None, None, None, - mock_func, None) - mock_func.assert_not_called() - - def test_cells_filter_args_correct(self): - # Re-init our fakes with some filters. - our_path = 'nova.tests.unit.cells.test_cells_scheduler' - cls_names = [our_path + '.' + 'FakeFilterClass1', - our_path + '.' + 'FakeFilterClass2'] - self.flags(scheduler='nova.cells.scheduler.CellsScheduler', - scheduler_filter_classes=cls_names, group='cells') - self._init_cells_scheduler() - - # Make sure there's no child cells so that we will be - # selected. Makes stubbing easier. - self.state_manager.child_cells = {} - - call_info = {} - - def fake_create_instances_here(self_cs, ctxt, instance_uuids, - instance_properties, instance_type, image, security_groups, - block_device_mapping): - call_info['ctxt'] = ctxt - call_info['instance_uuids'] = instance_uuids - call_info['instance_properties'] = instance_properties - call_info['instance_type'] = instance_type - call_info['image'] = image - call_info['security_groups'] = security_groups - call_info['block_device_mapping'] = block_device_mapping - - def fake_rpc_build_instances(self, ctxt, **host_sched_kwargs): - call_info['host_sched_kwargs'] = host_sched_kwargs - - def fake_get_filtered_objs(self, filters, cells, filt_properties): - call_info['filt_objects'] = filters - call_info['filt_cells'] = cells - call_info['filt_props'] = filt_properties - return cells - - def fake_build_request_spec(image, instances): - request_spec = { - 'num_instances': len(instances), - 'instance_properties': instances[0], - 'image': image, - 'instance_type': 'fake_type'} - return request_spec - - self.stub_out('nova.cells.scheduler.CellsScheduler.' - '_create_instances_here', - fake_create_instances_here) - self.stub_out('nova.conductor.api.ComputeTaskAPI.' - 'build_instances', fake_rpc_build_instances) - self.stub_out('nova.scheduler.utils.build_request_spec', - fake_build_request_spec) - self.stub_out('nova.cells.filters.CellFilterHandler.' - 'get_filtered_objects', - fake_get_filtered_objs) - - host_sched_kwargs = {'image': 'fake_image', - 'instances': self.instances, - 'filter_properties': - {'instance_type': 'fake_type'}, - 'security_groups': 'fake_sec_groups', - 'block_device_mapping': 'fake_bdm'} - - self.msg_runner.build_instances(self.ctxt, - self.my_cell_state, host_sched_kwargs) - # Our cell was selected. - self.assertEqual(self.ctxt, call_info['ctxt']) - self.assertEqual(self.instance_uuids, call_info['instance_uuids']) - self.assertEqual(self.request_spec['instance_properties']['id'], - call_info['instance_properties']['id']) - self.assertEqual(self.request_spec['instance_type'], - call_info['instance_type']) - self.assertEqual(self.request_spec['image'], call_info['image']) - self.assertEqual(host_sched_kwargs, call_info['host_sched_kwargs']) - # Filter args are correct - expected_filt_props = {'context': self.ctxt, - 'scheduler': self.scheduler, - 'routing_path': self.my_cell_state.name, - 'host_sched_kwargs': host_sched_kwargs, - 'request_spec': self.request_spec, - 'instance_type': 'fake_type'} - self.assertEqual(expected_filt_props, call_info['filt_props']) - self.assertEqual([FakeFilterClass1, FakeFilterClass2], - [obj.__class__ for obj in call_info['filt_objects']]) - self.assertEqual([self.my_cell_state], call_info['filt_cells']) - - def test_cells_filter_returning_none(self): - # Re-init our fakes with some filters. - our_path = 'nova.tests.unit.cells.test_cells_scheduler' - cls_names = [our_path + '.' + 'FakeFilterClass1', - our_path + '.' + 'FakeFilterClass2'] - self.flags(scheduler='nova.cells.scheduler.CellsScheduler', - scheduler_filter_classes=cls_names, group='cells') - self._init_cells_scheduler() - - # Make sure there's no child cells so that we will be - # selected. Makes stubbing easier. - self.state_manager.child_cells = {} - - call_info = {'scheduled': False} - - def fake_create_instances_here(self, ctxt, request_spec): - # Should not be called - call_info['scheduled'] = True - - def fake_get_filtered_objs(filter_classes, cells, filt_properties): - # Should cause scheduling to be skipped. Means that the - # filter did it. - return None - - self.stub_out('nova.cells.scheduler.CellsScheduler.' - '_create_instances_here', - fake_create_instances_here) - self.stub_out('nova.cells.filters.CellFilterHandler.' - 'get_filtered_objects', - fake_get_filtered_objs) - - self.msg_runner.build_instances(self.ctxt, - self.my_cell_state, {}) - self.assertFalse(call_info['scheduled']) - - def test_cells_weight_args_correct(self): - # Re-init our fakes with some filters. - our_path = 'nova.tests.unit.cells.test_cells_scheduler' - cls_names = [our_path + '.' + 'FakeWeightClass1', - our_path + '.' + 'FakeWeightClass2'] - self.flags(scheduler='nova.cells.scheduler.CellsScheduler', - scheduler_weight_classes=cls_names, group='cells') - self._init_cells_scheduler() - - # Make sure there's no child cells so that we will be - # selected. Makes stubbing easier. - self.state_manager.child_cells = {} - - call_info = {} - - def fake_create_instances_here(self_cs, ctxt, instance_uuids, - instance_properties, instance_type, image, security_groups, - block_device_mapping): - call_info['ctxt'] = ctxt - call_info['instance_uuids'] = instance_uuids - call_info['instance_properties'] = instance_properties - call_info['instance_type'] = instance_type - call_info['image'] = image - call_info['security_groups'] = security_groups - call_info['block_device_mapping'] = block_device_mapping - - def fake_rpc_build_instances(self, ctxt, **host_sched_kwargs): - call_info['host_sched_kwargs'] = host_sched_kwargs - - def fake_get_weighed_objs(self, weighers, cells, filt_properties): - call_info['weighers'] = weighers - call_info['weight_cells'] = cells - call_info['weight_props'] = filt_properties - return [weights.WeightedCell(cells[0], 0.0)] - - def fake_build_request_spec(image, instances): - request_spec = { - 'num_instances': len(instances), - 'instance_properties': instances[0], - 'image': image, - 'instance_type': 'fake_type'} - return request_spec - - self.stub_out('nova.cells.scheduler.CellsScheduler.' - '_create_instances_here', - fake_create_instances_here) - self.stub_out('nova.scheduler.utils.build_request_spec', - fake_build_request_spec) - self.stub_out('nova.conductor.api.ComputeTaskAPI.' - 'build_instances', fake_rpc_build_instances) - self.stub_out('nova.cells.weights.CellWeightHandler.' - 'get_weighed_objects', - fake_get_weighed_objs) - - host_sched_kwargs = {'image': 'fake_image', - 'instances': self.instances, - 'filter_properties': - {'instance_type': 'fake_type'}, - 'security_groups': 'fake_sec_groups', - 'block_device_mapping': 'fake_bdm'} - - self.msg_runner.build_instances(self.ctxt, - self.my_cell_state, host_sched_kwargs) - # Our cell was selected. - self.assertEqual(self.ctxt, call_info['ctxt']) - self.assertEqual(self.instance_uuids, call_info['instance_uuids']) - self.assertEqual(self.request_spec['instance_properties']['id'], - call_info['instance_properties']['id']) - self.assertEqual(self.request_spec['instance_type'], - call_info['instance_type']) - self.assertEqual(self.request_spec['image'], call_info['image']) - self.assertEqual(host_sched_kwargs, call_info['host_sched_kwargs']) - # Weight args are correct - expected_filt_props = {'context': self.ctxt, - 'scheduler': self.scheduler, - 'routing_path': self.my_cell_state.name, - 'host_sched_kwargs': host_sched_kwargs, - 'request_spec': self.request_spec, - 'instance_type': 'fake_type'} - self.assertEqual(expected_filt_props, call_info['weight_props']) - self.assertEqual([FakeWeightClass1, FakeWeightClass2], - [obj.__class__ for obj in call_info['weighers']]) - self.assertEqual([self.my_cell_state], call_info['weight_cells']) diff --git a/nova/tests/unit/cells/test_cells_state_manager.py b/nova/tests/unit/cells/test_cells_state_manager.py deleted file mode 100644 index 8ea891a1e8..0000000000 --- a/nova/tests/unit/cells/test_cells_state_manager.py +++ /dev/null @@ -1,380 +0,0 @@ -# Copyright (c) 2013 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Tests For CellStateManager -""" - -import datetime -import time - -import mock -from oslo_config import cfg -from oslo_db import exception as db_exc -from oslo_utils import timeutils -import six - -from nova.cells import state -from nova.db.sqlalchemy import models -from nova import exception -from nova import objects -from nova import test -from nova import utils - -FAKE_COMPUTES = [ - ('host1', 1024, 100, 0, 0), - ('host2', 1024, 100, -1, -1), - ('host3', 1024, 100, 1024, 100), - ('host4', 1024, 100, 300, 30), -] - -FAKE_COMPUTES_N_TO_ONE = [ - ('host1', 1024, 100, 0, 0), - ('host1', 1024, 100, -1, -1), - ('host2', 1024, 100, 1024, 100), - ('host2', 1024, 100, 300, 30), -] - -FAKE_SERVICES = [ - ('host1', 0), - ('host2', 0), - ('host3', 0), - ('host4', 3600), -] - -# NOTE(alaski): It's important to have multiple types that end up having the -# same memory and disk requirements. So two types need the same first value, -# and two need the second and third values to add up to the same thing. -FAKE_ITYPES = [ - (0, 0, 0), - (50, 12, 13), - (50, 2, 4), - (10, 20, 5), -] - - -def _create_fake_node(host, total_mem, total_disk, free_mem, free_disk): - return objects.ComputeNode(host=host, - memory_mb=total_mem, - local_gb=total_disk, - free_ram_mb=free_mem, - free_disk_gb=free_disk) - - -@classmethod -def _fake_service_get_all_by_binary(cls, context, binary): - def _node(host, total_mem, total_disk, free_mem, free_disk): - now = timeutils.utcnow() - return objects.Service(host=host, - disabled=False, - forced_down=False, - last_seen_up=now) - - return [_node(*fake) for fake in FAKE_COMPUTES] - - -@classmethod -def _fake_service_get_all_by_binary_nodedown(cls, context, binary): - def _service(host, noupdate_sec): - now = timeutils.utcnow() - last_seen = now - datetime.timedelta(seconds=noupdate_sec) - return objects.Service(host=host, - disabled=False, - forced_down=False, - last_seen_up=last_seen, - binary=binary) - - return [_service(*fake) for fake in FAKE_SERVICES] - - -@classmethod -def _fake_compute_node_get_all(cls, context): - return [_create_fake_node(*fake) for fake in FAKE_COMPUTES] - - -@classmethod -def _fake_compute_node_n_to_one_get_all(cls, context): - return [_create_fake_node(*fake) for fake in FAKE_COMPUTES_N_TO_ONE] - - -def _fake_cell_get_all(context): - return [] - - -def _fake_instance_type_all(*args): - def _type(mem, root, eph): - return objects.Flavor(root_gb=root, - ephemeral_gb=eph, - memory_mb=mem) - - return [_type(*fake) for fake in FAKE_ITYPES] - - -class TestCellsStateManager(test.NoDBTestCase): - - def setUp(self): - super(TestCellsStateManager, self).setUp() - - self.stub_out('nova.objects.ComputeNodeList.get_all', - _fake_compute_node_get_all) - self.stub_out('nova.objects.ServiceList.get_by_binary', - _fake_service_get_all_by_binary) - self.stub_out('nova.objects.FlavorList.get_all', - _fake_instance_type_all) - self.stub_out('nova.db.api.cell_get_all', _fake_cell_get_all) - - def test_cells_config_not_found(self): - self.flags(cells_config='no_such_file_exists.conf', group='cells') - e = self.assertRaises(cfg.ConfigFilesNotFoundError, - state.CellStateManager) - self.assertEqual(['no_such_file_exists.conf'], e.config_files) - - @mock.patch.object(cfg.ConfigOpts, 'find_file') - @mock.patch.object(utils, 'read_cached_file') - def test_filemanager_returned(self, mock_read_cached_file, mock_find_file): - mock_find_file.return_value = "/etc/nova/cells.json" - mock_read_cached_file.return_value = (False, six.StringIO('{}')) - self.flags(cells_config='cells.json', group='cells') - manager = state.CellStateManager() - self.assertIsInstance(manager, - state.CellStateManagerFile) - self.assertRaises(exception.CellsUpdateUnsupported, - manager.cell_create, None, None) - self.assertRaises(exception.CellsUpdateUnsupported, - manager.cell_update, None, None, None) - self.assertRaises(exception.CellsUpdateUnsupported, - manager.cell_delete, None, None) - - def test_dbmanager_returned(self): - self.assertIsInstance(state.CellStateManager(), - state.CellStateManagerDB) - - def test_capacity_no_reserve(self): - # utilize entire cell - cap = self._capacity(0.0) - - cell_free_ram = sum(max(0, compute[3]) for compute in FAKE_COMPUTES) - self.assertEqual(cell_free_ram, cap['ram_free']['total_mb']) - - cell_free_disk = 1024 * sum(max(0, compute[4]) - for compute in FAKE_COMPUTES) - self.assertEqual(cell_free_disk, cap['disk_free']['total_mb']) - - self.assertEqual(0, cap['ram_free']['units_by_mb']['0']) - self.assertEqual(0, cap['disk_free']['units_by_mb']['0']) - - units = cell_free_ram // 50 - self.assertEqual(units, cap['ram_free']['units_by_mb']['50']) - - sz = 25 * 1024 - units = 5 # 4 on host 3, 1 on host4 - self.assertEqual(units, cap['disk_free']['units_by_mb'][str(sz)]) - - def test_capacity_full_reserve(self): - # reserve the entire cell. (utilize zero percent) - cap = self._capacity(100.0) - - cell_free_ram = sum(max(0, compute[3]) for compute in FAKE_COMPUTES) - self.assertEqual(cell_free_ram, cap['ram_free']['total_mb']) - - cell_free_disk = 1024 * sum(max(0, compute[4]) - for compute in FAKE_COMPUTES) - self.assertEqual(cell_free_disk, cap['disk_free']['total_mb']) - - self.assertEqual(0, cap['ram_free']['units_by_mb']['0']) - self.assertEqual(0, cap['disk_free']['units_by_mb']['0']) - self.assertEqual(0, cap['ram_free']['units_by_mb']['50']) - - sz = 25 * 1024 - self.assertEqual(0, cap['disk_free']['units_by_mb'][str(sz)]) - - def test_capacity_part_reserve(self): - # utilize half the cell's free capacity - cap = self._capacity(50.0) - - cell_free_ram = sum(max(0, compute[3]) for compute in FAKE_COMPUTES) - self.assertEqual(cell_free_ram, cap['ram_free']['total_mb']) - - cell_free_disk = 1024 * sum(max(0, compute[4]) - for compute in FAKE_COMPUTES) - self.assertEqual(cell_free_disk, cap['disk_free']['total_mb']) - - self.assertEqual(0, cap['ram_free']['units_by_mb']['0']) - self.assertEqual(0, cap['disk_free']['units_by_mb']['0']) - - units = 10 # 10 from host 3 - self.assertEqual(units, cap['ram_free']['units_by_mb']['50']) - - sz = 25 * 1024 - units = 2 # 2 on host 3 - self.assertEqual(units, cap['disk_free']['units_by_mb'][str(sz)]) - - def _get_state_manager(self, reserve_percent=0.0): - self.flags(reserve_percent=reserve_percent, group='cells') - return state.CellStateManager() - - def _capacity(self, reserve_percent): - state_manager = self._get_state_manager(reserve_percent) - my_state = state_manager.get_my_state() - return my_state.capacities - - -class TestCellsStateManagerNToOne(TestCellsStateManager): - def setUp(self): - super(TestCellsStateManagerNToOne, self).setUp() - - self.stub_out('nova.objects.ComputeNodeList.get_all', - _fake_compute_node_n_to_one_get_all) - - def test_capacity_part_reserve(self): - # utilize half the cell's free capacity - cap = self._capacity(50.0) - - cell_free_ram = sum(max(0, compute[3]) - for compute in FAKE_COMPUTES_N_TO_ONE) - self.assertEqual(cell_free_ram, cap['ram_free']['total_mb']) - - cell_free_disk = (1024 * - sum(max(0, compute[4]) - for compute in FAKE_COMPUTES_N_TO_ONE)) - self.assertEqual(cell_free_disk, cap['disk_free']['total_mb']) - - self.assertEqual(0, cap['ram_free']['units_by_mb']['0']) - self.assertEqual(0, cap['disk_free']['units_by_mb']['0']) - - units = 6 # 6 from host 2 - self.assertEqual(units, cap['ram_free']['units_by_mb']['50']) - - sz = 25 * 1024 - units = 1 # 1 on host 2 - self.assertEqual(units, cap['disk_free']['units_by_mb'][str(sz)]) - - -class TestCellsStateManagerNodeDown(test.NoDBTestCase): - def setUp(self): - super(TestCellsStateManagerNodeDown, self).setUp() - - self.stub_out('nova.objects.ComputeNodeList.get_all', - _fake_compute_node_get_all) - self.stub_out('nova.objects.ServiceList.get_by_binary', - _fake_service_get_all_by_binary_nodedown) - self.stub_out('nova.objects.FlavorList.get_all', - _fake_instance_type_all) - self.stub_out('nova.db.api.cell_get_all', _fake_cell_get_all) - - def test_capacity_no_reserve_nodedown(self): - cap = self._capacity(0.0) - - cell_free_ram = sum(max(0, compute[3]) - for compute in FAKE_COMPUTES[:-1]) - self.assertEqual(cell_free_ram, cap['ram_free']['total_mb']) - - free_disk = sum(max(0, compute[4]) - for compute in FAKE_COMPUTES[:-1]) - cell_free_disk = 1024 * free_disk - self.assertEqual(cell_free_disk, cap['disk_free']['total_mb']) - - def _get_state_manager(self, reserve_percent=0.0): - self.flags(reserve_percent=reserve_percent, group='cells') - return state.CellStateManager() - - def _capacity(self, reserve_percent): - state_manager = self._get_state_manager(reserve_percent) - my_state = state_manager.get_my_state() - return my_state.capacities - - -class TestCellStateManagerException(test.NoDBTestCase): - @mock.patch.object(time, 'sleep') - def test_init_db_error(self, mock_sleep): - class TestCellStateManagerDB(state.CellStateManagerDB): - def __init__(self): - self._cell_data_sync = mock.Mock() - self._cell_data_sync.side_effect = [db_exc.DBError(), []] - super(TestCellStateManagerDB, self).__init__() - test = TestCellStateManagerDB() - mock_sleep.assert_called_once_with(30) - self.assertEqual(2, test._cell_data_sync.call_count) - - -class TestCellsGetCapacity(TestCellsStateManager): - def setUp(self): - super(TestCellsGetCapacity, self).setUp() - self.capacities = {"ram_free": 1234} - self.state_manager = self._get_state_manager() - cell = models.Cell(name="cell_name") - other_cell = models.Cell(name="other_cell_name") - cell.capacities = self.capacities - other_cell.capacities = self.capacities - self.state_manager.child_cells = {"cell_name": cell, - "other_cell_name": other_cell} - self.state_manager.my_cell_state.capacities = self.capacities - - def test_get_cell_capacity_for_all_cells(self): - capacities = self.state_manager.get_capacities() - self.assertEqual({"ram_free": 3702}, capacities) - - def test_get_cell_capacity_for_the_parent_cell(self): - capacities = self.state_manager.\ - get_capacities(self.state_manager.my_cell_state.name) - self.assertEqual({"ram_free": 3702}, capacities) - - def test_get_cell_capacity_for_a_cell(self): - self.assertEqual(self.capacities, - self.state_manager.get_capacities(cell_name="cell_name")) - - def test_get_cell_capacity_for_non_existing_cell(self): - self.assertRaises(exception.CellNotFound, - self.state_manager.get_capacities, - cell_name="invalid_cell_name") - - -class FakeCellStateManager(object): - def __init__(self): - self.called = [] - - def _cell_data_sync(self, force=False): - self.called.append(('_cell_data_sync', force)) - - -class TestSyncDecorators(test.NoDBTestCase): - def test_sync_before(self): - manager = FakeCellStateManager() - - def test(inst, *args, **kwargs): - self.assertEqual(manager, inst) - self.assertEqual((1, 2, 3), args) - self.assertEqual(dict(a=4, b=5, c=6), kwargs) - return 'result' - wrapper = state.sync_before(test) - - result = wrapper(manager, 1, 2, 3, a=4, b=5, c=6) - - self.assertEqual('result', result) - self.assertEqual([('_cell_data_sync', False)], manager.called) - - def test_sync_after(self): - manager = FakeCellStateManager() - - def test(inst, *args, **kwargs): - self.assertEqual(manager, inst) - self.assertEqual((1, 2, 3), args) - self.assertEqual(dict(a=4, b=5, c=6), kwargs) - return 'result' - wrapper = state.sync_after(test) - - result = wrapper(manager, 1, 2, 3, a=4, b=5, c=6) - - self.assertEqual('result', result) - self.assertEqual([('_cell_data_sync', True)], manager.called) diff --git a/nova/tests/unit/cells/test_cells_utils.py b/nova/tests/unit/cells/test_cells_utils.py deleted file mode 100644 index c2eca53f7e..0000000000 --- a/nova/tests/unit/cells/test_cells_utils.py +++ /dev/null @@ -1,233 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Tests For Cells Utility methods -""" -import inspect -import random - -import mock - -from nova.cells import utils as cells_utils -from nova import exception -from nova import objects -from nova import test -from nova.tests.unit import fake_instance - - -class CellsUtilsTestCase(test.NoDBTestCase): - """Test case for Cells utility methods.""" - def test_get_instances_to_sync(self): - fake_context = 'fake_context' - - call_info = {'get_all': 0, 'shuffle': 0} - - def random_shuffle(_list): - call_info['shuffle'] += 1 - - @staticmethod - def instance_get_all_by_filters(context, filters, - sort_key, sort_dir, limit, marker): - # Pretend we return a full list the first time otherwise we loop - # infinitely - if marker is not None: - return [] - self.assertEqual(fake_context, context) - self.assertEqual('deleted', sort_key) - self.assertEqual('asc', sort_dir) - call_info['got_filters'] = filters - call_info['get_all'] += 1 - instances = [fake_instance.fake_db_instance() for i in range(3)] - return instances - - self.stub_out('nova.objects.InstanceList.get_by_filters', - instance_get_all_by_filters) - self.stub_out('random.shuffle', random_shuffle) - - instances = cells_utils.get_instances_to_sync(fake_context) - self.assertTrue(inspect.isgenerator(instances)) - self.assertEqual(3, len([x for x in instances])) - self.assertEqual(1, call_info['get_all']) - self.assertEqual({}, call_info['got_filters']) - self.assertEqual(0, call_info['shuffle']) - - instances = cells_utils.get_instances_to_sync(fake_context, - shuffle=True) - self.assertTrue(inspect.isgenerator(instances)) - self.assertEqual(3, len([x for x in instances])) - self.assertEqual(2, call_info['get_all']) - self.assertEqual({}, call_info['got_filters']) - self.assertEqual(1, call_info['shuffle']) - - instances = cells_utils.get_instances_to_sync(fake_context, - updated_since='fake-updated-since') - self.assertTrue(inspect.isgenerator(instances)) - self.assertEqual(3, len([x for x in instances])) - self.assertEqual(3, call_info['get_all']) - self.assertEqual({'changes-since': 'fake-updated-since'}, - call_info['got_filters']) - self.assertEqual(1, call_info['shuffle']) - - instances = cells_utils.get_instances_to_sync(fake_context, - project_id='fake-project', - updated_since='fake-updated-since', shuffle=True) - self.assertTrue(inspect.isgenerator(instances)) - self.assertEqual(3, len([x for x in instances])) - self.assertEqual(4, call_info['get_all']) - self.assertEqual({'changes-since': 'fake-updated-since', - 'project_id': 'fake-project'}, call_info['got_filters']) - self.assertEqual(2, call_info['shuffle']) - - @mock.patch.object(objects.InstanceList, 'get_by_filters') - @mock.patch.object(random, 'shuffle') - def _test_get_instances_pagination(self, mock_shuffle, - mock_get_by_filters, shuffle=False, updated_since=None, - project_id=None): - fake_context = 'fake_context' - - instances0 = objects.instance._make_instance_list(fake_context, - objects.InstanceList(), - [fake_instance.fake_db_instance() for i in range(3)], - expected_attrs=None) - marker0 = instances0[-1]['uuid'] - instances1 = objects.instance._make_instance_list(fake_context, - objects.InstanceList(), - [fake_instance.fake_db_instance() for i in range(3)], - expected_attrs=None) - marker1 = instances1[-1]['uuid'] - - mock_get_by_filters.side_effect = [instances0, instances1, []] - - instances = cells_utils.get_instances_to_sync(fake_context, - updated_since, project_id, shuffle=shuffle) - self.assertEqual(len([x for x in instances]), 6) - - filters = {} - if updated_since is not None: - filters['changes-since'] = updated_since - if project_id is not None: - filters['project_id'] = project_id - limit = 100 - expected_calls = [mock.call(fake_context, filters, sort_key='deleted', - sort_dir='asc', limit=limit, marker=None), - mock.call(fake_context, filters, sort_key='deleted', - sort_dir='asc', limit=limit, marker=marker0), - mock.call(fake_context, filters, sort_key='deleted', - sort_dir='asc', limit=limit, marker=marker1)] - mock_get_by_filters.assert_has_calls(expected_calls) - self.assertEqual(3, mock_get_by_filters.call_count) - - def test_get_instances_to_sync_limit(self): - self._test_get_instances_pagination() - - def test_get_instances_to_sync_shuffle(self): - self._test_get_instances_pagination(shuffle=True) - - def test_get_instances_to_sync_updated_since(self): - self._test_get_instances_pagination(updated_since='fake-updated-since') - - def test_get_instances_to_sync_multiple_params(self): - self._test_get_instances_pagination(project_id='fake-project', - updated_since='fake-updated-since', shuffle=True) - - def test_split_cell_and_item(self): - path = 'australia', 'queensland', 'gold_coast' - cell = cells_utils.PATH_CELL_SEP.join(path) - item = 'host_5' - together = cells_utils.cell_with_item(cell, item) - self.assertEqual(cells_utils.CELL_ITEM_SEP.join([cell, item]), - together) - - # Test normal usage - result_cell, result_item = cells_utils.split_cell_and_item(together) - self.assertEqual(cell, result_cell) - self.assertEqual(item, result_item) - - # Test with no cell - cell = None - together = cells_utils.cell_with_item(cell, item) - self.assertEqual(item, together) - result_cell, result_item = cells_utils.split_cell_and_item(together) - self.assertEqual(cell, result_cell) - self.assertEqual(item, result_item) - - def test_add_cell_to_compute_node(self): - fake_compute = objects.ComputeNode(id=1, host='fake') - cell_path = 'fake_path' - - proxy = cells_utils.add_cell_to_compute_node(fake_compute, cell_path) - - self.assertIsInstance(proxy, cells_utils.ComputeNodeProxy) - self.assertEqual(cells_utils.cell_with_item(cell_path, 1), proxy.id) - self.assertEqual(cells_utils.cell_with_item(cell_path, 'fake'), - proxy.host) - - @mock.patch.object(objects.Service, 'obj_load_attr') - def test_add_cell_to_service_no_compute_node(self, mock_get_by_id): - fake_service = objects.Service(id=1, host='fake') - mock_get_by_id.side_effect = exception.ServiceNotFound(service_id=1) - cell_path = 'fake_path' - - proxy = cells_utils.add_cell_to_service(fake_service, cell_path) - - self.assertIsInstance(proxy, cells_utils.ServiceProxy) - self.assertEqual(cells_utils.cell_with_item(cell_path, 1), proxy.id) - self.assertEqual(cells_utils.cell_with_item(cell_path, 'fake'), - proxy.host) - self.assertRaises(AttributeError, - getattr, proxy, 'compute_node') - - def test_add_cell_to_service_with_compute_node(self): - fake_service = objects.Service(id=1, host='fake') - fake_service.compute_node = objects.ComputeNode(id=1, host='fake') - cell_path = 'fake_path' - - proxy = cells_utils.add_cell_to_service(fake_service, cell_path) - - self.assertIsInstance(proxy, cells_utils.ServiceProxy) - self.assertEqual(cells_utils.cell_with_item(cell_path, 1), proxy.id) - self.assertEqual(cells_utils.cell_with_item(cell_path, 'fake'), - proxy.host) - self.assertRaises(AttributeError, - getattr, proxy, 'compute_node') - - def test_proxy_object_serializer_to_primitive(self): - obj = objects.ComputeNode(id=1, host='fake') - obj_proxy = cells_utils.ComputeNodeProxy(obj, 'fake_path') - serializer = cells_utils.ProxyObjectSerializer() - - primitive = serializer.serialize_entity('ctx', obj_proxy) - self.assertIsInstance(primitive, dict) - class_name = primitive.pop('cell_proxy.class_name') - cell_path = primitive.pop('cell_proxy.cell_path') - self.assertEqual('ComputeNodeProxy', class_name) - self.assertEqual('fake_path', cell_path) - self.assertEqual(obj.obj_to_primitive(), primitive) - - def test_proxy_object_serializer_from_primitive(self): - obj = objects.ComputeNode(id=1, host='fake') - serializer = cells_utils.ProxyObjectSerializer() - - # Recreating the primitive by hand to isolate the test for only - # the deserializing method - primitive = obj.obj_to_primitive() - primitive['cell_proxy.class_name'] = 'ComputeNodeProxy' - primitive['cell_proxy.cell_path'] = 'fake_path' - - result = serializer.deserialize_entity('ctx', primitive) - self.assertIsInstance(result, cells_utils.ComputeNodeProxy) - self.assertEqual(obj.obj_to_primitive(), - result._obj.obj_to_primitive()) - self.assertEqual('fake_path', result._cell_path) diff --git a/nova/tests/unit/cells/test_cells_weights.py b/nova/tests/unit/cells/test_cells_weights.py deleted file mode 100644 index dcf03fe5aa..0000000000 --- a/nova/tests/unit/cells/test_cells_weights.py +++ /dev/null @@ -1,216 +0,0 @@ -# Copyright (c) 2012 OpenStack Foundation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Unit Tests for testing the cells weight algorithms. - -Cells with higher weights should be given priority for new builds. -""" - -import datetime - -from oslo_utils import fixture as utils_fixture -from oslo_utils import timeutils - -from nova.cells import state -from nova.cells import weights -from nova import test - - -class FakeCellState(state.CellState): - def __init__(self, cell_name): - super(FakeCellState, self).__init__(cell_name) - self.capacities['ram_free'] = {'total_mb': 0, - 'units_by_mb': {}} - self.db_info = {} - - def _update_ram_free(self, *args): - ram_free = self.capacities['ram_free'] - for ram_size, units in args: - ram_free['total_mb'] += units * ram_size - ram_free['units_by_mb'][str(ram_size)] = units - - -def _get_fake_cells(): - - cell1 = FakeCellState('cell1') - cell1._update_ram_free((512, 1), (1024, 4), (2048, 3)) - cell1.db_info['weight_offset'] = -200.0 - cell2 = FakeCellState('cell2') - cell2._update_ram_free((512, 2), (1024, 3), (2048, 4)) - cell2.db_info['weight_offset'] = -200.1 - cell3 = FakeCellState('cell3') - cell3._update_ram_free((512, 3), (1024, 2), (2048, 1)) - cell3.db_info['weight_offset'] = 400.0 - cell4 = FakeCellState('cell4') - cell4._update_ram_free((512, 4), (1024, 1), (2048, 2)) - cell4.db_info['weight_offset'] = 300.0 - - return [cell1, cell2, cell3, cell4] - - -class CellsWeightsTestCase(test.NoDBTestCase): - """Makes sure the proper weighers are in the directory.""" - - def test_all_weighers(self): - weighers = weights.all_weighers() - # Check at least a couple that we expect are there - self.assertGreaterEqual(len(weighers), 2) - class_names = [cls.__name__ for cls in weighers] - self.assertIn('WeightOffsetWeigher', class_names) - self.assertIn('RamByInstanceTypeWeigher', class_names) - - -class _WeigherTestClass(test.NoDBTestCase): - """Base class for testing individual weigher plugins.""" - weigher_cls_name = None - - def setUp(self): - super(_WeigherTestClass, self).setUp() - self.weight_handler = weights.CellWeightHandler() - weigher_classes = self.weight_handler.get_matching_classes( - [self.weigher_cls_name]) - self.weighers = [cls() for cls in weigher_classes] - - def _get_weighed_cells(self, cells, weight_properties): - return self.weight_handler.get_weighed_objects(self.weighers, - cells, weight_properties) - - -class RAMByInstanceTypeWeigherTestClass(_WeigherTestClass): - - weigher_cls_name = ('nova.cells.weights.ram_by_instance_type.' - 'RamByInstanceTypeWeigher') - - def test_default_spreading(self): - """Test that cells with more ram available return a higher weight.""" - cells = _get_fake_cells() - # Simulate building a new 512MB instance. - instance_type = {'memory_mb': 512} - weight_properties = {'request_spec': {'instance_type': instance_type}} - weighed_cells = self._get_weighed_cells(cells, weight_properties) - self.assertEqual(4, len(weighed_cells)) - resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] - expected_cells = [cells[3], cells[2], cells[1], cells[0]] - self.assertEqual(expected_cells, resulting_cells) - - # Simulate building a new 1024MB instance. - instance_type = {'memory_mb': 1024} - weight_properties = {'request_spec': {'instance_type': instance_type}} - weighed_cells = self._get_weighed_cells(cells, weight_properties) - self.assertEqual(4, len(weighed_cells)) - resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] - expected_cells = [cells[0], cells[1], cells[2], cells[3]] - self.assertEqual(expected_cells, resulting_cells) - - # Simulate building a new 2048MB instance. - instance_type = {'memory_mb': 2048} - weight_properties = {'request_spec': {'instance_type': instance_type}} - weighed_cells = self._get_weighed_cells(cells, weight_properties) - self.assertEqual(4, len(weighed_cells)) - resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] - expected_cells = [cells[1], cells[0], cells[3], cells[2]] - self.assertEqual(expected_cells, resulting_cells) - - def test_negative_multiplier(self): - """Test that cells with less ram available return a higher weight.""" - self.flags(ram_weight_multiplier=-1.0, group='cells') - cells = _get_fake_cells() - # Simulate building a new 512MB instance. - instance_type = {'memory_mb': 512} - weight_properties = {'request_spec': {'instance_type': instance_type}} - weighed_cells = self._get_weighed_cells(cells, weight_properties) - self.assertEqual(4, len(weighed_cells)) - resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] - expected_cells = [cells[0], cells[1], cells[2], cells[3]] - self.assertEqual(expected_cells, resulting_cells) - - # Simulate building a new 1024MB instance. - instance_type = {'memory_mb': 1024} - weight_properties = {'request_spec': {'instance_type': instance_type}} - weighed_cells = self._get_weighed_cells(cells, weight_properties) - self.assertEqual(4, len(weighed_cells)) - resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] - expected_cells = [cells[3], cells[2], cells[1], cells[0]] - self.assertEqual(expected_cells, resulting_cells) - - # Simulate building a new 2048MB instance. - instance_type = {'memory_mb': 2048} - weight_properties = {'request_spec': {'instance_type': instance_type}} - weighed_cells = self._get_weighed_cells(cells, weight_properties) - self.assertEqual(4, len(weighed_cells)) - resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] - expected_cells = [cells[2], cells[3], cells[0], cells[1]] - self.assertEqual(expected_cells, resulting_cells) - - -class WeightOffsetWeigherTestClass(_WeigherTestClass): - """Test the RAMWeigher class.""" - weigher_cls_name = 'nova.cells.weights.weight_offset.WeightOffsetWeigher' - - def test_weight_offset(self): - """Test that cells with higher weight_offsets return higher - weights. - """ - cells = _get_fake_cells() - weighed_cells = self._get_weighed_cells(cells, {}) - self.assertEqual(4, len(weighed_cells)) - expected_cells = [cells[2], cells[3], cells[0], cells[1]] - resulting_cells = [weighed_cell.obj for weighed_cell in weighed_cells] - self.assertEqual(expected_cells, resulting_cells) - - -class MuteWeigherTestClass(_WeigherTestClass): - weigher_cls_name = 'nova.cells.weights.mute_child.MuteChildWeigher' - - def setUp(self): - super(MuteWeigherTestClass, self).setUp() - self.flags(mute_weight_multiplier=-10.0, mute_child_interval=100, - group='cells') - - self.now = timeutils.utcnow() - self.useFixture(utils_fixture.TimeFixture(self.now)) - - self.cells = _get_fake_cells() - for cell in self.cells: - cell.last_seen = self.now - - def test_non_mute(self): - weight_properties = {} - weighed_cells = self._get_weighed_cells(self.cells, weight_properties) - self.assertEqual(4, len(weighed_cells)) - - for weighed_cell in weighed_cells: - self.assertEqual(0, weighed_cell.weight) - - def test_mutes(self): - # make 2 of them mute: - self.cells[0].last_seen = (self.cells[0].last_seen - - datetime.timedelta(seconds=200)) - self.cells[1].last_seen = (self.cells[1].last_seen - - datetime.timedelta(seconds=200)) - - weight_properties = {} - weighed_cells = self._get_weighed_cells(self.cells, weight_properties) - self.assertEqual(4, len(weighed_cells)) - - for i in range(2): - weighed_cell = weighed_cells.pop(0) - self.assertEqual(0, weighed_cell.weight) - self.assertIn(weighed_cell.obj.name, ['cell3', 'cell4']) - - for i in range(2): - weighed_cell = weighed_cells.pop(0) - self.assertEqual(-10.0, weighed_cell.weight) - self.assertIn(weighed_cell.obj.name, ['cell1', 'cell2']) diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py index 9e3e775fc5..414aee11fb 100644 --- a/nova/tests/unit/compute/test_compute_api.py +++ b/nova/tests/unit/compute/test_compute_api.py @@ -29,7 +29,6 @@ from oslo_utils import uuidutils import six from nova.compute import api as compute_api -from nova.compute import cells_api as compute_cells_api from nova.compute import flavors from nova.compute import instance_actions from nova.compute import rpcapi as compute_rpcapi @@ -141,7 +140,6 @@ class _ComputeAPIUnitTestMixIn(object): instance._context = self.context instance.id = 1 instance.uuid = uuidutils.generate_uuid() - instance.cell_name = 'api!child' instance.vm_state = vm_states.ACTIVE instance.task_state = None instance.image_ref = FAKE_IMAGE_REF @@ -646,10 +644,7 @@ class _ComputeAPIUnitTestMixIn(object): self.assertEqual(instance.vm_state, vm_states.ACTIVE) self.assertIsNone(instance.task_state) - if self.cell_type == 'api': - rpcapi = self.compute_api.cells_rpcapi - else: - rpcapi = self.compute_api.compute_rpcapi + rpcapi = self.compute_api.compute_rpcapi with test.nested( mock.patch.object(instance, 'save'), @@ -685,10 +680,7 @@ class _ComputeAPIUnitTestMixIn(object): self.assertEqual(instance.vm_state, vm_states.SUSPENDED) self.assertIsNone(instance.task_state) - if self.cell_type == 'api': - rpcapi = self.compute_api.cells_rpcapi - else: - rpcapi = self.compute_api.compute_rpcapi + rpcapi = self.compute_api.compute_rpcapi with test.nested( mock.patch.object(instance, 'save'), @@ -709,10 +701,7 @@ class _ComputeAPIUnitTestMixIn(object): params = dict(vm_state=vm_states.STOPPED) instance = self._create_instance_obj(params=params) - if self.cell_type == 'api': - rpcapi = self.compute_api.cells_rpcapi - else: - rpcapi = self.compute_api.compute_rpcapi + rpcapi = self.compute_api.compute_rpcapi with test.nested( mock.patch.object(instance, 'save'), @@ -747,10 +736,7 @@ class _ComputeAPIUnitTestMixIn(object): params = dict(task_state=None, progress=99, vm_state=vm_state) instance = self._create_instance_obj(params=params) - if self.cell_type == 'api': - rpcapi = self.compute_api.cells_rpcapi - else: - rpcapi = self.compute_api.compute_rpcapi + rpcapi = self.compute_api.compute_rpcapi with test.nested( mock.patch.object(instance, 'save'), @@ -831,11 +817,7 @@ class _ComputeAPIUnitTestMixIn(object): _record_action_start.assert_called_once_with(self.context, instance, instance_actions.TRIGGER_CRASH_DUMP) - if self.cell_type == 'api': - # cell api has not been implemented. - pass - else: - trigger_crash_dump.assert_called_once_with(self.context, instance) + trigger_crash_dump.assert_called_once_with(self.context, instance) self.assertIsNone(instance.task_state) @@ -870,10 +852,7 @@ class _ComputeAPIUnitTestMixIn(object): if reboot_type == 'HARD': expected_task_state = task_states.ALLOW_REBOOT - if self.cell_type == 'api': - rpcapi = self.compute_api.cells_rpcapi - else: - rpcapi = self.compute_api.compute_rpcapi + rpcapi = self.compute_api.compute_rpcapi with test.nested( mock.patch.object(self.context, 'elevated'), @@ -1068,8 +1047,6 @@ class _ComputeAPIUnitTestMixIn(object): snapshot_id = self._set_delete_shelved_part(inst, mock_image_delete) - if self.cell_type == 'api': - rpcapi = self.compute_api.cells_rpcapi mock_terminate = self.useFixture( fixtures.MockPatchObject(rpcapi, 'terminate_instance')).mock mock_soft_delete = self.useFixture( @@ -1080,68 +1057,64 @@ class _ComputeAPIUnitTestMixIn(object): # NOTE(comstud): This is getting messy. But what we are wanting # to test is: - # If cells is enabled and we're the API cell: - # * Cast to cells_rpcapi. - # Otherwise: - # * Check for downed host - # * If downed host: - # * Clean up instance, destroying it, sending notifications. - # (Tested in _test_downed_host_part()) - # * If not downed host: - # * Record the action start. - # * Cast to compute_rpcapi. + # * Check for downed host + # * If downed host: + # * Clean up instance, destroying it, sending notifications. + # (Tested in _test_downed_host_part()) + # * If not downed host: + # * Record the action start. + # * Cast to compute_rpcapi. cast = True is_downed_host = inst.host == 'down-host' or inst.host is None - if self.cell_type != 'api': - if inst.vm_state == vm_states.RESIZED: - migration = objects.Migration._from_db_object( - self.context, objects.Migration(), - test_migration.fake_db_migration()) - mock_elevated.return_value = self.context - expected_elevated_calls.append(mock.call()) - mock_mig_get.return_value = migration - expected_record_calls.append( - mock.call(self.context, inst, - instance_actions.CONFIRM_RESIZE)) + if inst.vm_state == vm_states.RESIZED: + migration = objects.Migration._from_db_object( + self.context, objects.Migration(), + test_migration.fake_db_migration()) + mock_elevated.return_value = self.context + expected_elevated_calls.append(mock.call()) + mock_mig_get.return_value = migration + expected_record_calls.append( + mock.call(self.context, inst, + instance_actions.CONFIRM_RESIZE)) - # After confirm resize action, instance task_state - # is reset to None, so is the expected value. But - # for soft delete, task_state will be again reset - # back to soft-deleting in the code to avoid status - # checking failure. - updates['task_state'] = None - if delete_type == 'soft_delete': - expected_save_calls.append(mock.call()) - updates['task_state'] = 'soft-deleting' - - if inst.host is not None: - mock_elevated.return_value = self.context - expected_elevated_calls.append(mock.call()) - mock_get_cn.return_value = objects.Service() - mock_up.return_value = (inst.host != 'down-host') - - if is_downed_host: - mock_elevated.return_value = self.context - expected_elevated_calls.append(mock.call()) + # After confirm resize action, instance task_state + # is reset to None, so is the expected value. But + # for soft delete, task_state will be again reset + # back to soft-deleting in the code to avoid status + # checking failure. + updates['task_state'] = None + if delete_type == 'soft_delete': expected_save_calls.append(mock.call()) - state = ('soft' in delete_type and vm_states.SOFT_DELETED or - vm_states.DELETED) - updates.update({'vm_state': state, - 'task_state': None, - 'terminated_at': delete_time, - 'deleted_at': delete_time, - 'deleted': True}) - fake_inst = fake_instance.fake_db_instance(**updates) - mock_inst_destroy.return_value = fake_inst - cell = objects.CellMapping(uuid=uuids.cell, - transport_url='fake://', - database_connection='fake://') - im = objects.InstanceMapping(cell_mapping=cell) - mock_get_inst.return_value = im - cast = False + updates['task_state'] = 'soft-deleting' - if cast and self.cell_type != 'api': + if inst.host is not None: + mock_elevated.return_value = self.context + expected_elevated_calls.append(mock.call()) + mock_get_cn.return_value = objects.Service() + mock_up.return_value = (inst.host != 'down-host') + + if is_downed_host: + mock_elevated.return_value = self.context + expected_elevated_calls.append(mock.call()) + expected_save_calls.append(mock.call()) + state = ('soft' in delete_type and vm_states.SOFT_DELETED or + vm_states.DELETED) + updates.update({'vm_state': state, + 'task_state': None, + 'terminated_at': delete_time, + 'deleted_at': delete_time, + 'deleted': True}) + fake_inst = fake_instance.fake_db_instance(**updates) + mock_inst_destroy.return_value = fake_inst + cell = objects.CellMapping(uuid=uuids.cell, + transport_url='fake://', + database_connection='fake://') + im = objects.InstanceMapping(cell_mapping=cell) + mock_get_inst.return_value = im + cast = False + + if cast: expected_record_calls.append(mock.call(self.context, inst, instance_actions.DELETE)) @@ -1162,39 +1135,38 @@ class _ComputeAPIUnitTestMixIn(object): if expected_elevated_calls: mock_elevated.assert_has_calls(expected_elevated_calls) - if self.cell_type != 'api': - if inst.vm_state == vm_states.RESIZED: - mock_mig_get.assert_called_once_with( - self.context, instance_uuid, 'finished') - mock_confirm.assert_called_once_with( - self.context, inst, migration, migration['source_compute'], - cast=False) - if instance_host is not None: - mock_get_cn.assert_called_once_with(self.context, - instance_host) - mock_up.assert_called_once_with( - test.MatchType(objects.Service)) - if is_downed_host: - if 'soft' in delete_type: - mock_notify_legacy.assert_has_calls([ - mock.call(self.compute_api.notifier, self.context, - inst, 'delete.start'), - mock.call(self.compute_api.notifier, self.context, - inst, 'delete.end')]) - else: - mock_notify_legacy.assert_has_calls([ - mock.call(self.compute_api.notifier, self.context, - inst, '%s.start' % delete_type), - mock.call(self.compute_api.notifier, self.context, - inst, '%s.end' % delete_type)]) - mock_deallocate.assert_called_once_with(self.context, inst) - mock_inst_destroy.assert_called_once_with( - self.context, instance_uuid, constraint=None, - hard_delete=False) - mock_get_inst.assert_called_with(self.context, instance_uuid) - self.assertEqual(2, mock_get_inst.call_count) - self.assertTrue(mock_get_inst.return_value.queued_for_delete) - mock_save_im.assert_called_once_with() + if inst.vm_state == vm_states.RESIZED: + mock_mig_get.assert_called_once_with( + self.context, instance_uuid, 'finished') + mock_confirm.assert_called_once_with( + self.context, inst, migration, migration['source_compute'], + cast=False) + if instance_host is not None: + mock_get_cn.assert_called_once_with(self.context, + instance_host) + mock_up.assert_called_once_with( + test.MatchType(objects.Service)) + if is_downed_host: + if 'soft' in delete_type: + mock_notify_legacy.assert_has_calls([ + mock.call(self.compute_api.notifier, self.context, + inst, 'delete.start'), + mock.call(self.compute_api.notifier, self.context, + inst, 'delete.end')]) + else: + mock_notify_legacy.assert_has_calls([ + mock.call(self.compute_api.notifier, self.context, + inst, '%s.start' % delete_type), + mock.call(self.compute_api.notifier, self.context, + inst, '%s.end' % delete_type)]) + mock_deallocate.assert_called_once_with(self.context, inst) + mock_inst_destroy.assert_called_once_with( + self.context, instance_uuid, constraint=None, + hard_delete=False) + mock_get_inst.assert_called_with(self.context, instance_uuid) + self.assertEqual(2, mock_get_inst.call_count) + self.assertTrue(mock_get_inst.return_value.queued_for_delete) + mock_save_im.assert_called_once_with() if cast: if delete_type == 'soft_delete': @@ -1313,16 +1285,10 @@ class _ComputeAPIUnitTestMixIn(object): with mock.patch.object(self.compute_api.compute_rpcapi, 'terminate_instance') as mock_terminate: self.compute_api.delete(self.context, inst) - if self.cell_type == 'api': - mock_terminate.assert_called_once_with( - self.context, inst, mock_bdm_get.return_value, - delete_type='delete') - mock_local_delete.assert_not_called() - else: - mock_local_delete.assert_called_once_with( - self.context, inst, mock_bdm_get.return_value, - 'delete', self.compute_api._do_delete) - mock_terminate.assert_not_called() + mock_local_delete.assert_called_once_with( + self.context, inst, mock_bdm_get.return_value, + 'delete', self.compute_api._do_delete) + mock_terminate.assert_not_called() mock_service_get.assert_not_called() @mock.patch('nova.compute.api.API._delete_while_booting', @@ -1348,21 +1314,14 @@ class _ComputeAPIUnitTestMixIn(object): with mock.patch.object(self.compute_api.compute_rpcapi, 'terminate_instance') as mock_terminate: self.compute_api.delete(self.context, inst) - if self.cell_type == 'api': - mock_terminate.assert_called_once_with( - self.context, inst, mock_bdm_get.return_value, - delete_type='delete') - mock_local_delete.assert_not_called() - mock_service_get.assert_not_called() - else: - mock_service_get.assert_called_once_with( - mock_elevated.return_value, 'fake-host') - mock_service_up.assert_called_once_with( - mock_service_get.return_value) - mock_terminate.assert_called_once_with( - self.context, inst, mock_bdm_get.return_value, - delete_type='delete') - mock_local_delete.assert_not_called() + mock_service_get.assert_called_once_with( + mock_elevated.return_value, 'fake-host') + mock_service_up.assert_called_once_with( + mock_service_get.return_value) + mock_terminate.assert_called_once_with( + self.context, inst, mock_bdm_get.return_value, + delete_type='delete') + mock_local_delete.assert_not_called() def test_delete_forced_when_task_state_is_not_none(self): for vm_state in self._get_vm_states(): @@ -1389,27 +1348,18 @@ class _ComputeAPIUnitTestMixIn(object): fixtures.MockPatchObject(self.compute_api, '_lookup_instance')).mock - if self.cell_type == 'api': - rpcapi = self.compute_api.cells_rpcapi - else: - rpcapi = self.compute_api.compute_rpcapi - mock_terminate = self.useFixture( - fixtures.MockPatchObject(rpcapi, - 'terminate_instance')).mock - mock_lookup.return_value = (None, inst) mock_bdm_get.return_value = objects.BlockDeviceMappingList() mock_br_get.side_effect = exception.BuildRequestNotFound( uuid=inst.uuid) - if self.cell_type != 'api': - mock_cons.return_value = 'constraint' - delete_time = datetime.datetime(1955, 11, 5, 9, 30, - tzinfo=iso8601.UTC) - updates['deleted_at'] = delete_time - updates['deleted'] = True - fake_inst = fake_instance.fake_db_instance(**updates) - mock_inst_destroy.return_value = fake_inst + mock_cons.return_value = 'constraint' + delete_time = datetime.datetime(1955, 11, 5, 9, 30, + tzinfo=iso8601.UTC) + updates['deleted_at'] = delete_time + updates['deleted'] = True + fake_inst = fake_instance.fake_db_instance(**updates) + mock_inst_destroy.return_value = fake_inst instance_uuid = inst.uuid self.compute_api.delete(self.context, inst) @@ -1422,27 +1372,21 @@ class _ComputeAPIUnitTestMixIn(object): mock_br_get.assert_called_once_with(self.context, instance_uuid) mock_save.assert_called_once_with() - if self.cell_type == 'api': - mock_terminate.assert_called_once_with( - self.context, inst, - test.MatchType(objects.BlockDeviceMappingList), - delete_type='delete') - else: - mock_notify_legacy.assert_has_calls([ - mock.call(self.compute_api.notifier, self.context, - inst, 'delete.start'), - mock.call(self.compute_api.notifier, self.context, - inst, 'delete.end')]) - mock_notify.assert_has_calls([ - mock.call(self.context, inst, host='fake-mini', - source='nova-api', action='delete', phase='start'), - mock.call(self.context, inst, host='fake-mini', - source='nova-api', action='delete', phase='end')]) + mock_notify_legacy.assert_has_calls([ + mock.call(self.compute_api.notifier, self.context, + inst, 'delete.start'), + mock.call(self.compute_api.notifier, self.context, + inst, 'delete.end')]) + mock_notify.assert_has_calls([ + mock.call(self.context, inst, host='fake-mini', + source='nova-api', action='delete', phase='start'), + mock.call(self.context, inst, host='fake-mini', + source='nova-api', action='delete', phase='end')]) - mock_cons.assert_called_once_with(host=mock.ANY) - mock_inst_destroy.assert_called_once_with( - self.context, instance_uuid, constraint='constraint', - hard_delete=False) + mock_cons.assert_called_once_with(host=mock.ANY) + mock_inst_destroy.assert_called_once_with( + self.context, instance_uuid, constraint='constraint', + hard_delete=False) def _fake_do_delete(context, instance, bdms, rservations=None, local=False): @@ -1489,8 +1433,7 @@ class _ComputeAPIUnitTestMixIn(object): mock_bdm_destroy.assert_called_once_with() mock_inst_destroy.assert_called_once_with() - if self.cell_type != 'api': - mock_dealloc.assert_called_once_with(self.context, inst) + mock_dealloc.assert_called_once_with(self.context, inst) @mock.patch.object(objects.BlockDeviceMapping, 'destroy') def test_local_cleanup_bdm_volumes_stashed_connector(self, mock_destroy): @@ -1784,16 +1727,12 @@ class _ComputeAPIUnitTestMixIn(object): def test(mock_inst_get, mock_map_get): cell, ret_instance = self.compute_api._lookup_instance( self.context, instance.uuid) - expected_cell = (self.cell_type is None and - inst_map.cell_mapping or None) + expected_cell = inst_map.cell_mapping self.assertEqual((expected_cell, instance), (cell, ret_instance)) mock_inst_get.assert_called_once_with(self.context, instance.uuid) - if self.cell_type is None: - mock_target_cell.assert_called_once_with(self.context, - inst_map.cell_mapping) - else: - self.assertFalse(mock_target_cell.called) + mock_target_cell.assert_called_once_with(self.context, + inst_map.cell_mapping) test() @@ -1987,8 +1926,7 @@ class _ComputeAPIUnitTestMixIn(object): else: new_flavor = current_flavor - if (self.cell_type == 'compute' or - not (flavor_id_passed and same_flavor)): + if not (flavor_id_passed and same_flavor): project_id, user_id = quotas_obj.ids_from_instance(self.context, fake_inst) if flavor_id_passed: @@ -2014,27 +1952,6 @@ class _ComputeAPIUnitTestMixIn(object): else: filter_properties = {'ignore_hosts': [fake_inst['host']]} - if self.cell_type == 'api': - mig = mock.MagicMock() - mock_migration.return_value = mig - - def _check_mig(): - self.assertEqual(fake_inst.uuid, mig.instance_uuid) - self.assertEqual(current_flavor.id, - mig.old_instance_type_id) - self.assertEqual(new_flavor.id, - mig.new_instance_type_id) - self.assertEqual('finished', mig.status) - if new_flavor.id != current_flavor.id: - self.assertEqual('resize', mig.migration_type) - else: - self.assertEqual('migration', mig.migration_type) - - mock_elevated = self.useFixture( - fixtures.MockPatchObject(self.context, 'elevated')).mock - mock_elevated.return_value = self.context - mig.create.side_effect = _check_mig - if request_spec: fake_spec = objects.RequestSpec() if requested_destination: @@ -2092,8 +2009,7 @@ class _ComputeAPIUnitTestMixIn(object): mock_get_flavor.assert_called_once_with('new-flavor-id', read_deleted='no') - if (self.cell_type == 'compute' or - not (flavor_id_passed and same_flavor)): + if not (flavor_id_passed and same_flavor): if flavor_id_passed: mock_upsize.assert_called_once_with( test.MatchType(objects.Flavor), @@ -2122,12 +2038,7 @@ class _ComputeAPIUnitTestMixIn(object): # This is a migration mock_validate.assert_not_called() - if self.cell_type == 'api' and request_spec: - mock_migration.assert_called_once_with(context=self.context) - mock_elevated.assert_called_once_with() - mig.create.assert_called_once_with() - else: - mock_migration.assert_not_called() + mock_migration.assert_not_called() mock_get_by_instance_uuid.assert_called_once_with(self.context, fake_inst.uuid) @@ -2312,13 +2223,11 @@ class _ComputeAPIUnitTestMixIn(object): @mock.patch('nova.compute.api.API._validate_flavor_image_nostatus') @mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid') @mock.patch('nova.compute.api.API._record_action_start') - @mock.patch('nova.compute.api.API._resize_cells_support') @mock.patch('nova.conductor.conductor_api.ComputeTaskAPI.resize_instance') @mock.patch.object(flavors, 'get_flavor_by_flavor_id') def test_resize_to_zero_disk_flavor_volume_backed(self, get_flavor_by_flavor_id, resize_instance_mock, - cells_support_mock, record_mock, get_by_inst, validate_mock): @@ -2460,10 +2369,7 @@ class _ComputeAPIUnitTestMixIn(object): self.assertEqual(instance.vm_state, vm_states.ACTIVE) self.assertIsNone(instance.task_state) - if self.cell_type == 'api': - rpcapi = self.compute_api.cells_rpcapi - else: - rpcapi = self.compute_api.compute_rpcapi + rpcapi = self.compute_api.compute_rpcapi mock_pause = self.useFixture( fixtures.MockPatchObject(rpcapi, 'pause_instance')).mock @@ -2501,10 +2407,7 @@ class _ComputeAPIUnitTestMixIn(object): self.assertEqual(instance.vm_state, vm_states.PAUSED) self.assertIsNone(instance.task_state) - if self.cell_type == 'api': - rpcapi = self.compute_api.cells_rpcapi - else: - rpcapi = self.compute_api.compute_rpcapi + rpcapi = self.compute_api.compute_rpcapi with mock.patch.object(rpcapi, 'unpause_instance') as mock_unpause: self.compute_api.unpause(self.context, instance) @@ -2550,10 +2453,7 @@ class _ComputeAPIUnitTestMixIn(object): add_instance_fault_from_exc, mock_nodelist): instance = self._create_instance_obj() - if self.cell_type == 'api': - api = self.compute_api.cells_rpcapi - else: - api = conductor.api.ComputeTaskAPI + api = conductor.api.ComputeTaskAPI with mock.patch.object(api, 'live_migrate_instance', side_effect=oslo_exceptions.MessagingTimeout): @@ -2588,13 +2488,7 @@ class _ComputeAPIUnitTestMixIn(object): @mock.patch.object(objects.Instance, 'save') @mock.patch.object(objects.InstanceAction, 'action_start') def _live_migrate_instance(self, instance, _save, _action, get_spec): - # TODO(gilliard): This logic is upside-down (different - # behaviour depending on which class this method is mixed-into. Once - # we have cellsv2 we can remove this kind of logic from this test - if self.cell_type == 'api': - api = self.compute_api.cells_rpcapi - else: - api = conductor.api.ComputeTaskAPI + api = conductor.api.ComputeTaskAPI fake_spec = objects.RequestSpec() get_spec.return_value = fake_spec with mock.patch.object(api, 'live_migrate_instance') as task: @@ -5486,9 +5380,6 @@ class _ComputeAPIUnitTestMixIn(object): def test_live_migrate_force_complete_succeeded( self, action_start, get_by_id_and_instance): - if self.cell_type == 'api': - # cell api has not been implemented. - return rpcapi = self.compute_api.compute_rpcapi instance = self._create_instance_obj() @@ -5917,21 +5808,10 @@ class _ComputeAPIUnitTestMixIn(object): mock_get_inst_map): self.useFixture(nova_fixtures.AllServicesCurrent()) - if self.cell_type is None: - # No Mapping means NotFound - self.assertRaises(exception.InstanceNotFound, - self.compute_api.get, self.context, - uuids.inst_uuid) - else: - self.compute_api.get(self.context, uuids.inst_uuid) - mock_get_build_req.assert_not_called() - mock_get_inst.assert_called_once_with(self.context, - uuids.inst_uuid, - expected_attrs=[ - 'metadata', - 'system_metadata', - 'security_groups', - 'info_cache']) + # No Mapping means NotFound + self.assertRaises(exception.InstanceNotFound, + self.compute_api.get, self.context, + uuids.inst_uuid) @mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid') @mock.patch.object(objects.BuildRequest, 'get_by_instance_uuid') @@ -5946,16 +5826,10 @@ class _ComputeAPIUnitTestMixIn(object): mock_get_inst.return_value = instance inst_from_build_req = self.compute_api.get(self.context, instance.uuid) - if self.cell_type is None: - mock_get_inst_map.assert_called_once_with(self.context, - instance.uuid) - mock_get_build_req.assert_called_once_with(self.context, - instance.uuid) - else: - mock_get_inst.assert_called_once_with( - self.context, instance.uuid, - expected_attrs=['metadata', 'system_metadata', - 'security_groups', 'info_cache']) + mock_get_inst_map.assert_called_once_with(self.context, + instance.uuid) + mock_get_build_req.assert_called_once_with(self.context, + instance.uuid) self.assertEqual(instance, inst_from_build_req) @mock.patch('nova.compute.api.API._save_user_id_in_instance_mapping', @@ -5987,11 +5861,10 @@ class _ComputeAPIUnitTestMixIn(object): inst_map_calls = [mock.call(self.context, instance.uuid), mock.call(self.context, instance.uuid)] - if self.cell_type is None: - mock_get_inst_map.assert_has_calls(inst_map_calls) - self.assertEqual(2, mock_get_inst_map.call_count) - mock_get_build_req.assert_called_once_with(self.context, - instance.uuid) + mock_get_inst_map.assert_has_calls(inst_map_calls) + self.assertEqual(2, mock_get_inst_map.call_count) + mock_get_build_req.assert_called_once_with(self.context, + instance.uuid) mock_get_inst.assert_called_once_with(self.context, instance.uuid, expected_attrs=[ @@ -6029,21 +5902,9 @@ class _ComputeAPIUnitTestMixIn(object): uuid=instance.uuid) mock_get_inst.return_value = instance - if self.cell_type is None: - self.assertRaises(exception.InstanceNotFound, - self.compute_api.get, - self.context, instance.uuid) - else: - inst_from_get = self.compute_api.get(self.context, instance.uuid) - - mock_get_inst.assert_called_once_with(self.context, - instance.uuid, - expected_attrs=[ - 'metadata', - 'system_metadata', - 'security_groups', - 'info_cache']) - self.assertEqual(instance, inst_from_get) + self.assertRaises(exception.InstanceNotFound, + self.compute_api.get, + self.context, instance.uuid) @mock.patch('nova.objects.InstanceMapping.save') def test_save_user_id_in_instance_mapping(self, im_save): @@ -6087,13 +5948,10 @@ class _ComputeAPIUnitTestMixIn(object): returned_inst = self.compute_api.get(self.context, instance.uuid) mock_get_build_req.assert_not_called() - if self.cell_type is None: - mock_get_inst_map.assert_called_once_with(self.context, - instance.uuid) - # Verify that user_id is populated during a compute_api.get(). - mock_save_uid.assert_called_once_with(inst_map, instance) - else: - self.assertFalse(mock_get_inst_map.called) + mock_get_inst_map.assert_called_once_with(self.context, + instance.uuid) + # Verify that user_id is populated during a compute_api.get(). + mock_save_uid.assert_called_once_with(inst_map, instance) self.assertEqual(instance, returned_inst) mock_get_inst.assert_called_once_with(self.context, instance.uuid, expected_attrs=[ @@ -6242,9 +6100,9 @@ class _ComputeAPIUnitTestMixIn(object): limit=10, marker='fake-marker', sort_keys=['baz'], sort_dirs=['desc']) - if self.cell_type is None: - for cm in mock_cm_get_all.return_value: - mock_target_cell.assert_any_call(self.context, cm) + for cm in mock_cm_get_all.return_value: + mock_target_cell.assert_any_call(self.context, cm) + fields = ['metadata', 'info_cache', 'security_groups'] mock_inst_get.assert_called_once_with( mock.ANY, {'foo': 'bar'}, @@ -6467,7 +6325,6 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase): def setUp(self): super(ComputeAPIUnitTestCase, self).setUp() self.compute_api = compute_api.API() - self.cell_type = None def test_resize_same_flavor_fails(self): self.assertRaises(exception.CannotResizeToSameFlavor, @@ -6832,193 +6689,6 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase): mock_report_client.assert_called_once_with() -class Cellsv1DeprecatedTestMixIn(object): - def test_get_all_build_requests_decrement_limit(self): - self.skipTest("Removing cells v1") - - def test_get_all_cell0_marker_not_found(self): - self.skipTest("Removing cells v1") - - def test_get_all_includes_build_request_cell0(self): - self.skipTest("Removing cells v1") - - def test_get_all_includes_build_requests(self): - self.skipTest("Removing cells v1") - - def test_get_all_includes_build_requests_filter_dupes(self): - self.skipTest("Removing cells v1") - - def test_tenant_to_project_conversion(self): - self.skipTest("Removing cells v1") - - def test_get_all_with_cell_down_support(self): - self.skipTest("Cell down handling is not supported for cells_v1.") - - def test_get_all_without_cell_down_support(self): - self.skipTest("Cell down handling is not supported for cells_v1.") - - def test_get_all_with_cell_down_support_all_tenants(self): - self.skipTest("Cell down handling is not supported for cells_v1.") - - -class ComputeAPIAPICellUnitTestCase(Cellsv1DeprecatedTestMixIn, - _ComputeAPIUnitTestMixIn, - test.NoDBTestCase): - def setUp(self): - super(ComputeAPIAPICellUnitTestCase, self).setUp() - self.flags(cell_type='api', enable=True, group='cells') - self.compute_api = compute_cells_api.ComputeCellsAPI() - self.cell_type = 'api' - - def test_resize_same_flavor_fails(self): - self.assertRaises(exception.CannotResizeToSameFlavor, - self._test_resize, same_flavor=True) - - @mock.patch.object(compute_cells_api, 'ComputeRPCAPIRedirect') - def test_create_volume_bdm_call_reserve_dev_name(self, mock_reserve): - instance = self._create_instance_obj() - # In the cells rpcapi there isn't the call for the - # reserve_block_device_name so the volume_bdm returned - # by the _create_volume_bdm is None - volume = {'id': '1', 'multiattach': False} - result = self.compute_api._create_volume_bdm(self.context, - instance, - 'vda', - volume, - None, - None) - self.assertIsNone(result, None) - - @mock.patch.object(compute_cells_api.ComputeCellsAPI, '_call_to_cells') - @mock.patch.object(objects.Service, 'get_minimum_version', - return_value=COMPUTE_VERSION_OLD_ATTACH_FLOW) - def test_attach_volume(self, mock_get_min_ver, mock_attach): - instance = self._create_instance_obj() - volume = fake_volume.fake_volume(1, 'test-vol', 'test-vol', - None, None, None, None, None) - - mock_volume_api = mock.patch.object(self.compute_api, 'volume_api', - mock.MagicMock(spec=cinder.API)) - with mock_volume_api as mock_v_api: - mock_v_api.get.return_value = volume - self.compute_api.attach_volume( - self.context, instance, volume['id']) - mock_v_api.check_availability_zone.assert_called_once_with( - self.context, volume, instance=instance) - mock_attach.assert_called_once_with(self.context, instance, - 'attach_volume', volume['id'], - None, None, None) - - @mock.patch.object(compute_cells_api.ComputeCellsAPI, '_call_to_cells') - @mock.patch.object(objects.Service, 'get_minimum_version', - return_value=COMPUTE_VERSION_NEW_ATTACH_FLOW) - @mock.patch.object(cinder, 'is_microversion_supported') - @mock.patch.object(objects.BlockDeviceMapping, - 'get_by_volume_and_instance') - def test_attach_volume_new_flow(self, mock_no_bdm, - mock_cinder_mv_supported, - mock_get_min_ver, mock_attach): - mock_no_bdm.side_effect = exception.VolumeBDMNotFound( - volume_id='test-vol') - instance = self._create_instance_obj() - volume = fake_volume.fake_volume(1, 'test-vol', 'test-vol', - None, None, None, None, None) - - mock_volume_api = mock.patch.object(self.compute_api, 'volume_api', - mock.MagicMock(spec=cinder.API)) - - with mock_volume_api as mock_v_api: - mock_v_api.get.return_value = volume - self.compute_api.attach_volume( - self.context, instance, volume['id']) - mock_v_api.check_availability_zone.assert_called_once_with( - self.context, volume, instance=instance) - mock_attach.assert_called_once_with(self.context, instance, - 'attach_volume', volume['id'], - None, None, None) - - @mock.patch.object(objects.Service, 'get_minimum_version', - return_value=COMPUTE_VERSION_OLD_ATTACH_FLOW) - @mock.patch('nova.volume.cinder.API.get') - def test_tagged_volume_attach(self, mock_vol_get, mock_get_min_ver): - instance = self._create_instance_obj() - volume = fake_volume.fake_volume(1, 'test-vol', 'test-vol', - None, None, None, None, None) - mock_vol_get.return_value = volume - self.assertRaises(exception.VolumeTaggedAttachNotSupported, - self.compute_api.attach_volume, self.context, - instance, volume['id'], tag='foo') - - @mock.patch.object(objects.Service, 'get_minimum_version', - return_value=COMPUTE_VERSION_NEW_ATTACH_FLOW) - @mock.patch.object(cinder, 'is_microversion_supported') - @mock.patch.object(objects.BlockDeviceMapping, - 'get_by_volume_and_instance') - @mock.patch('nova.volume.cinder.API.get') - def test_tagged_volume_attach_new_flow(self, mock_get_vol, mock_no_bdm, - mock_cinder_mv_supported, - mock_get_min_ver): - mock_no_bdm.side_effect = exception.VolumeBDMNotFound( - volume_id='test-vol') - instance = self._create_instance_obj() - volume = fake_volume.fake_volume(1, 'test-vol', 'test-vol', - None, None, None, None, None) - mock_get_vol.return_value = volume - self.assertRaises(exception.VolumeTaggedAttachNotSupported, - self.compute_api.attach_volume, self.context, - instance, volume['id'], tag='foo') - - def test_create_with_networks_max_count_none(self): - self.skipTest("This test does not test any rpcapi.") - - def test_attach_volume_reserve_fails(self): - self.skipTest("Reserve is never done in the API cell.") - - def test_attach_volume_attachment_create_fails(self): - self.skipTest("Reserve is never done in the API cell.") - - def test_check_requested_networks_no_requested_networks(self): - # The API cell just returns the number of instances passed in since the - # actual validation happens in the child (compute) cell. - self.assertEqual( - 2, self.compute_api._check_requested_networks( - self.context, None, 2)) - - def test_check_requested_networks_auto_allocate(self): - # The API cell just returns the number of instances passed in since the - # actual validation happens in the child (compute) cell. - requested_networks = ( - objects.NetworkRequestList( - objects=[objects.NetworkRequest(network_id='auto')])) - count = self.compute_api._check_requested_networks( - self.context, requested_networks, 5) - self.assertEqual(5, count) - - def test_attach_volume_with_multiattach_volume_fails(self): - """Tests that the cells v1 API doesn't support attaching multiattach - volumes. - """ - instance = objects.Instance(cell_name='foo') - volume = {'multiattach': True} - device = disk_bus = disk_type = None - self.assertRaises(exception.MultiattachSupportNotYetAvailable, - self.compute_api._attach_volume, self.context, - instance, volume, device, disk_bus, disk_type) - - -class ComputeAPIComputeCellUnitTestCase(Cellsv1DeprecatedTestMixIn, - _ComputeAPIUnitTestMixIn, - test.NoDBTestCase): - def setUp(self): - super(ComputeAPIComputeCellUnitTestCase, self).setUp() - self.flags(cell_type='compute', enable=True, group='cells') - self.compute_api = compute_api.API() - self.cell_type = 'compute' - - def test_resize_same_flavor_passes(self): - self._test_resize(same_flavor=True) - - class DiffDictTestCase(test.NoDBTestCase): """Unit tests for _diff_dict().""" diff --git a/nova/tests/unit/compute/test_compute_cells.py b/nova/tests/unit/compute/test_compute_cells.py deleted file mode 100644 index 65f6be7bab..0000000000 --- a/nova/tests/unit/compute/test_compute_cells.py +++ /dev/null @@ -1,688 +0,0 @@ -# Copyright (c) 2012 Rackspace Hosting -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Tests For Compute w/ Cells -""" -import copy -import functools -import inspect - -import ddt -import mock -from oslo_utils.fixture import uuidsentinel as uuids -from oslo_utils import timeutils - -from nova import block_device -from nova.cells import manager -from nova.compute import api as compute_api -from nova.compute import cells_api as compute_cells_api -from nova.compute import flavors -from nova.compute import power_state -from nova.compute import utils as compute_utils -from nova.compute import vm_states -from nova import context -from nova.db import api as db -from nova.db.sqlalchemy import api as db_api -from nova.db.sqlalchemy import api_models -from nova import exception -from nova import objects -from nova.objects import fields as obj_fields -from nova import quota -from nova import test -from nova.tests.unit.compute import test_compute -from nova.tests.unit.compute import test_shelve -from nova.tests.unit import fake_instance -from nova.tests.unit.objects import test_flavor - - -ORIG_COMPUTE_API = None -FAKE_IMAGE_REF = uuids.image_ref - -NODENAME = 'fakenode1' -NODENAME2 = 'fakenode2' - - -def stub_call_to_cells(context, instance, method, *args, **kwargs): - fn = getattr(ORIG_COMPUTE_API, method) - original_instance = kwargs.pop('original_instance', None) - if original_instance: - instance = original_instance - # Restore this in 'child cell DB' - db.instance_update(context, instance['uuid'], - dict(vm_state=instance['vm_state'], - task_state=instance['task_state'])) - - # Use NoopQuotaDriver in child cells. - saved_quotas = quota.QUOTAS - quota.QUOTAS = quota.QuotaEngine( - quota_driver=quota.NoopQuotaDriver()) - compute_api.QUOTAS = quota.QUOTAS - try: - return fn(context, instance, *args, **kwargs) - finally: - quota.QUOTAS = saved_quotas - compute_api.QUOTAS = saved_quotas - - -def stub_cast_to_cells(context, instance, method, *args, **kwargs): - fn = getattr(ORIG_COMPUTE_API, method) - original_instance = kwargs.pop('original_instance', None) - if original_instance: - instance = original_instance - # Restore this in 'child cell DB' - db.instance_update(context, instance['uuid'], - dict(vm_state=instance['vm_state'], - task_state=instance['task_state'])) - - # Use NoopQuotaDriver in child cells. - saved_quotas = quota.QUOTAS - quota.QUOTAS = quota.QuotaEngine( - quota_driver=quota.NoopQuotaDriver()) - compute_api.QUOTAS = quota.QUOTAS - try: - fn(context, instance, *args, **kwargs) - finally: - quota.QUOTAS = saved_quotas - compute_api.QUOTAS = saved_quotas - - -def deploy_stubs(stubs, api, original_instance=None): - call = stub_call_to_cells - cast = stub_cast_to_cells - - if original_instance: - kwargs = dict(original_instance=original_instance) - call = functools.partial(stub_call_to_cells, **kwargs) - cast = functools.partial(stub_cast_to_cells, **kwargs) - - stubs.Set(api, '_call_to_cells', call) - stubs.Set(api, '_cast_to_cells', cast) - - -@ddt.ddt -class CellsComputeAPITestCase(test_compute.ComputeAPITestCase): - def setUp(self): - self.flags(use_neutron=False) - super(CellsComputeAPITestCase, self).setUp() - global ORIG_COMPUTE_API - ORIG_COMPUTE_API = self.compute_api - self.flags(enable=True, group='cells') - - def _fake_validate_cell(*args, **kwargs): - return - - self.compute_api = compute_cells_api.ComputeCellsAPI() - self.stubs.Set(self.compute_api, '_validate_cell', - _fake_validate_cell) - - deploy_stubs(self.stubs, self.compute_api) - - def tearDown(self): - global ORIG_COMPUTE_API - self.compute_api = ORIG_COMPUTE_API - super(CellsComputeAPITestCase, self).tearDown() - - def test_instance_metadata(self): - self.skipTest("Test is incompatible with cells.") - - def _test_evacuate(self, force=None): - @mock.patch.object(compute_api.API, 'evacuate') - def _test(mock_evacuate): - instance = objects.Instance(uuid=uuids.evacuate_instance, - cell_name='fake_cell_name') - dest_host = 'fake_cell_name@fakenode2' - self.compute_api.evacuate(self.context, instance, host=dest_host, - force=force) - mock_evacuate.assert_called_once_with( - self.context, instance, 'fakenode2', force=force) - - _test() - - def test_error_evacuate(self): - self.skipTest("Test is incompatible with cells.") - - def test_create_instance_sets_system_metadata(self): - self.skipTest("Test is incompatible with cells.") - - def test_create_saves_flavor(self): - self.skipTest("Test is incompatible with cells.") - - def test_create_instance_associates_security_groups(self): - self.skipTest("Test is incompatible with cells.") - - @mock.patch('nova.objects.quotas.Quotas.check_deltas') - def test_create_instance_over_quota_during_recheck( - self, check_deltas_mock): - self.stub_out('nova.tests.unit.image.fake._FakeImageService.show', - self.fake_show) - - # Simulate a race where the first check passes and the recheck fails. - fake_quotas = {'instances': 5, 'cores': 10, 'ram': 4096} - fake_headroom = {'instances': 5, 'cores': 10, 'ram': 4096} - fake_usages = {'instances': 5, 'cores': 10, 'ram': 4096} - exc = exception.OverQuota(overs=['instances'], quotas=fake_quotas, - headroom=fake_headroom, usages=fake_usages) - check_deltas_mock.side_effect = [None, exc] - - inst_type = objects.Flavor.get_by_name(self.context, 'm1.small') - # Try to create 3 instances. - self.assertRaises(exception.QuotaError, self.compute_api.create, - self.context, inst_type, self.fake_image['id'], min_count=3) - - project_id = self.context.project_id - - self.assertEqual(2, check_deltas_mock.call_count) - call1 = mock.call(self.context, - {'instances': 3, 'cores': inst_type.vcpus * 3, - 'ram': inst_type.memory_mb * 3}, - project_id, user_id=None, - check_project_id=project_id, check_user_id=None) - call2 = mock.call(self.context, {'instances': 0, 'cores': 0, 'ram': 0}, - project_id, user_id=None, - check_project_id=project_id, check_user_id=None) - check_deltas_mock.assert_has_calls([call1, call2]) - - # Verify we removed the artifacts that were added after the first - # quota check passed. - instances = objects.InstanceList.get_all(self.context) - self.assertEqual(0, len(instances)) - build_requests = objects.BuildRequestList.get_all(self.context) - self.assertEqual(0, len(build_requests)) - - @db_api.api_context_manager.reader - def request_spec_get_all(context): - return context.session.query(api_models.RequestSpec).all() - - request_specs = request_spec_get_all(self.context) - self.assertEqual(0, len(request_specs)) - - instance_mappings = objects.InstanceMappingList.get_by_project_id( - self.context, project_id) - self.assertEqual(0, len(instance_mappings)) - - @mock.patch('nova.objects.quotas.Quotas.check_deltas') - def test_create_instance_no_quota_recheck( - self, check_deltas_mock): - self.stub_out('nova.tests.unit.image.fake._FakeImageService.show', - self.fake_show) - # Disable recheck_quota. - self.flags(recheck_quota=False, group='quota') - - inst_type = objects.Flavor.get_by_name(self.context, 'm1.small') - (refs, resv_id) = self.compute_api.create(self.context, - inst_type, - self.fake_image['id']) - self.assertEqual(1, len(refs)) - - project_id = self.context.project_id - - # check_deltas should have been called only once. - check_deltas_mock.assert_called_once_with(self.context, - {'instances': 1, - 'cores': inst_type.vcpus, - 'ram': inst_type.memory_mb}, - project_id, user_id=None, - check_project_id=project_id, - check_user_id=None) - - @mock.patch.object(compute_api.API, '_local_delete') - @mock.patch.object(compute_api.API, '_lookup_instance', - return_value=(None, None)) - def test_delete_instance_no_cell_instance_disappear(self, mock_lookup, - mock_local_delete): - inst = self._create_fake_instance_obj() - - @mock.patch.object(self.compute_api.cells_rpcapi, - 'instance_delete_everywhere') - def test(mock_inst_del): - self.compute_api.delete(self.context, inst) - mock_lookup.assert_called_once_with(self.context, inst.uuid) - mock_inst_del.assert_called_once_with(self.context, inst, 'hard') - self.assertFalse(mock_local_delete.called) - - test() - - @mock.patch.object(compute_api.API, '_local_delete') - def _test_delete_instance_no_cell(self, method_name, mock_local_delete): - cells_rpcapi = self.compute_api.cells_rpcapi - inst = self._create_fake_instance_obj() - delete_type = method_name == 'soft_delete' and 'soft' or 'hard' - - @mock.patch.object(cells_rpcapi, - 'instance_delete_everywhere') - @mock.patch.object(compute_api.API, '_lookup_instance', - return_value=(None, inst)) - def test(mock_lookup, mock_inst_del): - self.stub_out('nova.network.api.deallocate_for_instance', - lambda *a, **kw: None) - getattr(self.compute_api, method_name)(self.context, inst) - mock_lookup.assert_called_once_with(self.context, inst.uuid) - mock_local_delete.assert_called_once_with(self.context, inst, - mock.ANY, method_name, - mock.ANY) - mock_inst_del.assert_called_once_with(self.context, - inst, delete_type) - - test() - - def test_delete_instance_no_cell_constraint_failure_does_not_loop(self): - inst = self._create_fake_instance_obj() - inst.cell_name = None - - inst.destroy = mock.MagicMock() - inst.destroy.side_effect = exception.ObjectActionError(action='', - reason='') - inst.refresh = mock.MagicMock() - - @mock.patch.object(self.compute_api.cells_rpcapi, - 'instance_delete_everywhere') - @mock.patch.object(compute_api.API, '_lookup_instance', - return_value=(None, inst)) - @mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid') - def _test(mock_get_im, _mock_lookup_inst, _mock_delete_everywhere): - self.assertRaises(exception.ObjectActionError, - self.compute_api.delete, self.context, inst) - inst.destroy.assert_called_once_with() - mock_get_im.assert_called_once_with(self.context, inst.uuid) - - _test() - - def test_delete_instance_no_cell_constraint_failure_corrects_itself(self): - - def add_cell_name(context, instance, delete_type): - instance.cell_name = 'fake_cell_name' - - inst = self._create_fake_instance_obj() - inst.cell_name = None - - inst.destroy = mock.MagicMock() - inst.destroy.side_effect = exception.ObjectActionError(action='', - reason='') - inst.refresh = mock.MagicMock() - - @mock.patch.object(compute_api.API, 'delete') - @mock.patch.object(self.compute_api.cells_rpcapi, - 'instance_delete_everywhere', side_effect=add_cell_name) - @mock.patch.object(compute_api.API, '_lookup_instance', - return_value=(None, inst)) - @mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid') - def _test(mock_get_im, _mock_lookup_inst, mock_delete_everywhere, - mock_compute_delete): - self.compute_api.delete(self.context, inst) - inst.destroy.assert_called_once_with() - - mock_compute_delete.assert_called_once_with(self.context, inst) - mock_get_im.assert_called_once_with(self.context, inst.uuid) - - _test() - - def test_soft_delete_instance_no_cell(self): - self._test_delete_instance_no_cell('soft_delete') - - def test_delete_instance_no_cell(self): - self._test_delete_instance_no_cell('delete') - - def test_force_delete_instance_no_cell(self): - self._test_delete_instance_no_cell('force_delete') - - @mock.patch.object(compute_api.API, '_delete_while_booting', - side_effect=exception.ObjectActionError( - action='delete', reason='host now set')) - @mock.patch.object(compute_api.API, '_local_delete') - @mock.patch.object(compute_api.API, '_lookup_instance') - @mock.patch.object(compute_api.API, 'delete') - def test_delete_instance_no_cell_then_cell(self, mock_delete, - mock_lookup_instance, - mock_local_delete, - mock_delete_while_booting): - # This checks the case where initially an instance has no cell_name, - # and therefore no host, set but instance.destroy fails because - # there is now a host. - instance = self._create_fake_instance_obj() - instance_with_cell = copy.deepcopy(instance) - instance_with_cell.cell_name = 'foo' - mock_lookup_instance.return_value = None, instance_with_cell - - cells_rpcapi = self.compute_api.cells_rpcapi - - @mock.patch.object(cells_rpcapi, 'instance_delete_everywhere') - def test(mock_inst_delete_everywhere): - self.compute_api.delete(self.context, instance) - mock_local_delete.assert_not_called() - mock_delete.assert_called_once_with(self.context, - instance_with_cell) - - test() - - @mock.patch.object(compute_api.API, '_delete_while_booting', - side_effect=exception.ObjectActionError( - action='delete', reason='host now set')) - @mock.patch.object(compute_api.API, '_local_delete') - @mock.patch.object(compute_api.API, '_lookup_instance') - @mock.patch.object(compute_api.API, 'delete') - def test_delete_instance_no_cell_then_no_instance(self, - mock_delete, mock_lookup_instance, mock_local_delete, - mock_delete_while_booting): - # This checks the case where initially an instance has no cell_name, - # and therefore no host, set but instance.destroy fails because - # there is now a host. And then the instance can't be looked up. - instance = self._create_fake_instance_obj() - mock_lookup_instance.return_value = None, None - - cells_rpcapi = self.compute_api.cells_rpcapi - - @mock.patch.object(cells_rpcapi, 'instance_delete_everywhere') - def test(mock_inst_delete_everywhere): - self.compute_api.delete(self.context, instance) - mock_local_delete.assert_not_called() - mock_delete.assert_not_called() - - test() - - def test_get_migrations(self): - filters = {'cell_name': 'ChildCell', 'status': 'confirmed'} - migrations = {'migrations': [{'id': 1234}]} - - @mock.patch.object(self.compute_api.cells_rpcapi, 'get_migrations', - return_value=migrations) - def test(mock_cell_get_migrations): - response = self.compute_api.get_migrations(self.context, - filters) - mock_cell_get_migrations.assert_called_once_with(self.context, - filters) - self.assertEqual(migrations, response) - - test() - - def test_create_block_device_mapping(self): - instance_type = {'swap': 1, 'ephemeral_gb': 1} - instance = self._create_fake_instance_obj() - bdms = [block_device.BlockDeviceDict({'source_type': 'image', - 'destination_type': 'local', - 'image_id': uuids.image, - 'boot_index': 0})] - self.compute_api._create_block_device_mapping( - instance_type, instance.uuid, bdms) - bdms = db.block_device_mapping_get_all_by_instance( - self.context, instance['uuid']) - self.assertEqual(0, len(bdms)) - - def test_create_bdm_from_flavor(self): - self.skipTest("Test is incompatible with cells.") - - @mock.patch('nova.cells.messaging._TargetedMessage') - def test_rebuild_sig(self, mock_msg): - # TODO(belliott) Cells could benefit from better testing to ensure API - # and manager signatures stay up to date - - def wire(version): - # wire the rpc cast directly to the manager method to make sure - # the signature matches - cells_mgr = manager.CellsManager() - - def cast(context, method, *args, **kwargs): - fn = getattr(cells_mgr, method) - fn(context, *args, **kwargs) - - cells_mgr.cast = cast - return cells_mgr - - cells_rpcapi = self.compute_api.cells_rpcapi - client = cells_rpcapi.client - - with mock.patch.object(client, 'prepare', side_effect=wire): - inst = self._create_fake_instance_obj() - inst.cell_name = 'mycell' - - cells_rpcapi.rebuild_instance(self.context, inst, 'pass', None, - None, None, None, None, - recreate=False, - on_shared_storage=False, host='host', - preserve_ephemeral=True, kwargs=None) - - # one targeted message should have been created - self.assertEqual(1, mock_msg.call_count) - - def test_populate_instance_for_create(self): - self.skipTest("Removing cells v1") - - def test_multi_instance_display_name(self): - self.skipTest("Removing cells v1") - - @ddt.data(True, False) - def test_rdp_console(self, enabled_consoleauth): - self.skipTest("Removing cells v1") - - @ddt.data(True, False) - def test_spice_console(self, enabled_consoleauth): - self.skipTest("Removing cells v1") - - @ddt.data(True, False) - def test_vnc_console(self, enabled_consoleauth): - self.skipTest("Removing cells v1") - - -class CellsShelveComputeAPITestCase(test_shelve.ShelveComputeAPITestCase): - def setUp(self): - super(CellsShelveComputeAPITestCase, self).setUp() - global ORIG_COMPUTE_API - ORIG_COMPUTE_API = self.compute_api - self.compute_api = compute_cells_api.ComputeCellsAPI() - - def _fake_validate_cell(*args, **kwargs): - return - - self.stub_out('nova.compute.api.API._validate_cell', - _fake_validate_cell) - - def _create_fake_instance_obj(self, params=None, type_name='m1.tiny', - services=False, context=None): - flavor = objects.Flavor.get_by_name(self.context, type_name) - inst = objects.Instance(context=context or self.context) - inst.cell_name = 'api!child' - inst.vm_state = vm_states.ACTIVE - inst.task_state = None - inst.power_state = power_state.RUNNING - inst.image_ref = FAKE_IMAGE_REF - inst.reservation_id = 'r-fakeres' - inst.user_id = self.user_id - inst.project_id = self.project_id - inst.host = self.compute.host - inst.node = NODENAME - inst.instance_type_id = flavor.id - inst.ami_launch_index = 0 - inst.memory_mb = 0 - inst.vcpus = 0 - inst.root_gb = 0 - inst.ephemeral_gb = 0 - inst.architecture = obj_fields.Architecture.X86_64 - inst.os_type = 'Linux' - inst.system_metadata = ( - params and params.get('system_metadata', {}) or {}) - inst.locked = False - inst.created_at = timeutils.utcnow() - inst.updated_at = timeutils.utcnow() - inst.launched_at = timeutils.utcnow() - inst.security_groups = objects.SecurityGroupList(objects=[]) - inst.flavor = flavor - inst.old_flavor = None - inst.new_flavor = None - if params: - inst.flavor.update(params.pop('flavor', {})) - inst.update(params) - inst.create() - - return inst - - def _test_shelve(self, vm_state=vm_states.ACTIVE, - boot_from_volume=False, clean_shutdown=True): - params = dict(task_state=None, vm_state=vm_state, - display_name='fake-name') - instance = self._create_fake_instance_obj(params=params) - with mock.patch.object(self.compute_api, - '_cast_to_cells') as cast_to_cells: - self.compute_api.shelve(self.context, instance, - clean_shutdown=clean_shutdown) - cast_to_cells.assert_called_once_with(self.context, - instance, 'shelve', - clean_shutdown=clean_shutdown - ) - - def test_unshelve(self): - # Ensure instance can be unshelved on cell environment. - # The super class tests nova-shelve. - instance = self._create_fake_instance_obj() - - self.assertIsNone(instance['task_state']) - - self.compute_api.shelve(self.context, instance) - - instance.task_state = None - instance.vm_state = vm_states.SHELVED - instance.save() - - with mock.patch.object(self.compute_api, - '_cast_to_cells') as cast_to_cells: - self.compute_api.unshelve(self.context, instance) - cast_to_cells.assert_called_once_with(self.context, - instance, 'unshelve') - - def tearDown(self): - global ORIG_COMPUTE_API - self.compute_api = ORIG_COMPUTE_API - super(CellsShelveComputeAPITestCase, self).tearDown() - - -class CellsConductorAPIRPCRedirect(test.NoDBTestCase): - def setUp(self): - super(CellsConductorAPIRPCRedirect, self).setUp() - - self.compute_api = compute_cells_api.ComputeCellsAPI() - self.cells_rpcapi = mock.MagicMock() - self.compute_api.compute_task_api.cells_rpcapi = self.cells_rpcapi - - self.context = context.RequestContext('fake', 'fake') - - @mock.patch.object(compute_api.API, '_record_action_start') - @mock.patch.object(compute_api.API, '_provision_instances') - @mock.patch.object(compute_api.API, '_check_and_transform_bdm') - @mock.patch.object(compute_api.API, '_get_image') - @mock.patch.object(compute_api.API, '_validate_and_build_base_options') - @mock.patch.object(compute_api.API, '_checks_for_create_and_rebuild') - def test_build_instances(self, _checks_for_create_and_rebuild, - _validate, _get_image, _check_bdm, - _provision, _record_action_start): - _get_image.return_value = (None, 'fake-image') - _validate.return_value = ({}, 1, None, ['default'], None) - _check_bdm.return_value = objects.BlockDeviceMappingList() - _provision.return_value = [] - - with mock.patch.object(self.compute_api.compute_task_api, - 'schedule_and_build_instances') as sbi: - self.compute_api.create(self.context, 'fake-flavor', 'fake-image') - - # Subsequent tests in class are verifying the hooking. We - # don't check args since this is verified in compute test - # code. - self.assertTrue(sbi.called) - - @mock.patch.object(compute_api.API, '_validate_flavor_image_nostatus') - @mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid') - @mock.patch.object(compute_api.API, '_record_action_start') - @mock.patch.object(compute_api.API, '_resize_cells_support') - @mock.patch.object(compute_utils, 'upsize_quota_delta') - @mock.patch.object(objects.Instance, 'save') - @mock.patch.object(flavors, 'extract_flavor') - @mock.patch.object(compute_api.API, '_check_auto_disk_config') - @mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid') - def test_resize_instance(self, _bdms, _check, _extract, _save, _upsize, - _cells, _record, _spec_get_by_uuid, - mock_validate): - flavor = objects.Flavor(**test_flavor.fake_flavor) - _extract.return_value = flavor - orig_system_metadata = {} - instance = fake_instance.fake_instance_obj(self.context, - vm_state=vm_states.ACTIVE, cell_name='fake-cell', - launched_at=timeutils.utcnow(), - system_metadata=orig_system_metadata, - expected_attrs=['system_metadata']) - instance.flavor = flavor - instance.old_flavor = instance.new_flavor = None - self.compute_api.resize(self.context, instance) - self.assertTrue(self.cells_rpcapi.resize_instance.called) - - @mock.patch.object(objects.ComputeNodeList, 'get_all_by_host') - @mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid') - @mock.patch.object(compute_api.API, '_record_action_start') - @mock.patch.object(objects.Instance, 'save') - def test_live_migrate_instance(self, instance_save, _record, _get_spec, - mock_nodelist): - orig_system_metadata = {} - instance = fake_instance.fake_instance_obj(self.context, - vm_state=vm_states.ACTIVE, cell_name='fake-cell', - launched_at=timeutils.utcnow(), - system_metadata=orig_system_metadata, - expected_attrs=['system_metadata']) - - self.compute_api.live_migrate(self.context, instance, - True, True, 'fake_dest_host') - - self.assertTrue(self.cells_rpcapi.live_migrate_instance.called) - - @mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid') - @mock.patch.object(objects.Instance, 'save') - @mock.patch.object(objects.Instance, 'get_flavor') - @mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid') - @mock.patch.object(compute_api.API, '_get_image') - @mock.patch.object(compute_api.API, '_check_auto_disk_config') - @mock.patch.object(compute_api.API, '_checks_for_create_and_rebuild') - @mock.patch.object(compute_api.API, '_record_action_start') - def test_rebuild_instance(self, _record_action_start, - _checks_for_create_and_rebuild, _check_auto_disk_config, - _get_image, bdm_get_by_instance_uuid, get_flavor, instance_save, - _req_spec_get_by_inst_uuid): - orig_system_metadata = {} - instance = fake_instance.fake_instance_obj(self.context, - vm_state=vm_states.ACTIVE, cell_name='fake-cell', - launched_at=timeutils.utcnow(), image_ref=uuids.image_id, - system_metadata=orig_system_metadata, - expected_attrs=['system_metadata']) - get_flavor.return_value = {} - # The API request schema validates that a UUID is passed for the - # imageRef parameter so we need to provide an image. - image_href = uuids.image_id - image = {"min_ram": 10, "min_disk": 1, - "properties": {'architecture': 'x86_64'}, - "id": uuids.image_id} - admin_pass = '' - files_to_inject = [] - bdms = objects.BlockDeviceMappingList() - - _get_image.return_value = (None, image) - bdm_get_by_instance_uuid.return_value = bdms - - self.compute_api.rebuild(self.context, instance, image_href, - admin_pass, files_to_inject) - - self.assertTrue(self.cells_rpcapi.rebuild_instance.called) - - def test_check_equal(self): - task_api = self.compute_api.compute_task_api - tests = set() - for (name, value) in inspect.getmembers(self, inspect.ismethod): - if name.startswith('test_') and name != 'test_check_equal': - tests.add(name[5:]) - if tests != set(task_api.cells_compatible): - self.fail("Testcases not equivalent to cells_compatible list") diff --git a/nova/tests/unit/compute/test_host_api.py b/nova/tests/unit/compute/test_host_api.py index 7d07931a02..9f575da521 100644 --- a/nova/tests/unit/compute/test_host_api.py +++ b/nova/tests/unit/compute/test_host_api.py @@ -14,16 +14,11 @@ # License for the specific language governing permissions and limitations # under the License. -import copy - import fixtures import mock -from oslo_serialization import jsonutils from oslo_utils.fixture import uuidsentinel as uuids -import testtools from nova.api.openstack.compute import services -from nova.cells import utils as cells_utils from nova import compute from nova.compute import api as compute_api from nova import context @@ -563,192 +558,6 @@ class ComputeHostAPITestCase(test.TestCase): [mock.call(self.ctxt, uuids.cn_uuid)] * 2) -class ComputeHostAPICellsTestCase(ComputeHostAPITestCase): - def setUp(self): - self.flags(enable=True, group='cells') - 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 - - @testtools.skip('cellsv1 does not use this') - 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 - - def test_service_get_all_no_zones(self): - services = [ - cells_utils.ServiceProxy( - objects.Service(id=1, topic='compute', host='host1'), - 'cell1'), - cells_utils.ServiceProxy( - objects.Service(id=2, topic='compute', host='host2'), - 'cell1')] - - fake_filters = {'host': 'host1'} - - @mock.patch.object(self.host_api.cells_rpcapi, 'service_get_all') - def _do_test(mock_service_get_all): - mock_service_get_all.return_value = services - result = self.host_api.service_get_all(self.ctxt, - filters=fake_filters) - self.assertEqual(services, result) - - _do_test() - - def _test_service_get_all(self, fake_filters, **kwargs): - service_attrs = dict(test_service.fake_service) - del service_attrs['version'] - services = [ - cells_utils.ServiceProxy( - objects.Service(**dict(service_attrs, id=1, - topic='compute', host='host1')), - 'cell1'), - cells_utils.ServiceProxy( - objects.Service(**dict(service_attrs, id=2, - topic='compute', host='host2')), - 'cell1')] - exp_services = [] - for service in services: - exp_service = copy.copy(service) - exp_service.update({'availability_zone': 'nova'}) - exp_services.append(exp_service) - - @mock.patch.object(self.host_api.cells_rpcapi, 'service_get_all') - def _do_test(mock_service_get_all): - mock_service_get_all.return_value = services - result = self.host_api.service_get_all(self.ctxt, - filters=fake_filters, - **kwargs) - mock_service_get_all.assert_called_once_with(self.ctxt, - filters=fake_filters) - self.assertEqual(jsonutils.to_primitive(exp_services), - jsonutils.to_primitive(result)) - - _do_test() - - def test_service_get_all(self): - fake_filters = {'availability_zone': 'nova'} - self._test_service_get_all(fake_filters) - - def test_service_get_all_set_zones(self): - fake_filters = {'key1': 'val1'} - self._test_service_get_all(fake_filters, set_zones=True) - - def test_service_get_by_compute_host(self): - obj = objects.Service(id=1, host='fake') - fake_service = cells_utils.ServiceProxy(obj, 'cell1') - - @mock.patch.object(self.host_api.cells_rpcapi, - 'service_get_by_compute_host') - def _do_test(mock_service_get_by_compute_host): - mock_service_get_by_compute_host.return_value = fake_service - result = self.host_api.service_get_by_compute_host(self.ctxt, - 'fake-host') - self.assertEqual(fake_service, result) - - _do_test() - - def test_service_update(self): - host_name = 'fake-host' - binary = 'nova-compute' - params_to_update = dict(disabled=True) - - obj = objects.Service(id=42, host='fake') - fake_service = cells_utils.ServiceProxy(obj, 'cell1') - - @mock.patch.object(self.host_api.cells_rpcapi, 'service_update') - def _do_test(mock_service_update): - mock_service_update.return_value = fake_service - - result = self.host_api.service_update( - self.ctxt, host_name, binary, params_to_update) - self.assertEqual(fake_service, result) - - _do_test() - - def test_service_delete(self): - cell_service_id = cells_utils.cell_with_item('cell1', 1) - with mock.patch.object(self.host_api.cells_rpcapi, - 'service_delete') as service_delete: - self.host_api.service_delete(self.ctxt, cell_service_id) - service_delete.assert_called_once_with( - self.ctxt, cell_service_id) - - @testtools.skip('cells do not support host aggregates') - def test_service_delete_compute_in_aggregate(self): - # this test is not valid for cell - pass - - @mock.patch.object(objects.InstanceList, 'get_by_host') - def test_instance_get_all_by_host(self, mock_get): - 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')] - - mock_get.return_value = instances - 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) - - def test_task_log_get_all(self): - @mock.patch.object(self.host_api.cells_rpcapi, 'task_log_get_all', - return_value='fake-response') - def _do_test(mock_task_log_get_all): - result = self.host_api.task_log_get_all(self.ctxt, 'fake-name', - 'fake-begin', 'fake-end', - host='fake-host', - state='fake-state') - self.assertEqual('fake-response', result) - - _do_test() - - def test_get_host_uptime_service_down(self): - # The corresponding Compute test case depends on the - # _assert_host_exists which is a no-op in the cells api - pass - - def test_get_host_uptime(self): - @mock.patch.object(self.host_api.cells_rpcapi, 'get_host_uptime', - return_value='fake-response') - def _do_test(mock_get_host_uptime): - result = self.host_api.get_host_uptime(self.ctxt, 'fake-host') - self.assertEqual('fake-response', result) - - _do_test() - - def test_compute_node_statistics(self): - # Not implementing cross-cellsv2 for cellsv1 - pass - - def test_compute_node_get_using_uuid(self): - cell_compute_uuid = cells_utils.cell_with_item('cell1', uuids.cn_uuid) - with mock.patch.object(self.host_api.cells_rpcapi, - 'compute_node_get') as compute_node_get: - self.host_api.compute_node_get(self.ctxt, cell_compute_uuid) - compute_node_get.assert_called_once_with(self.ctxt, cell_compute_uuid) - - def test_compute_node_get_not_found(self): - cell_compute_uuid = cells_utils.cell_with_item('cell1', uuids.cn_uuid) - with mock.patch.object(self.host_api.cells_rpcapi, 'compute_node_get', - side_effect=exception.CellRoutingInconsistency( - reason='because_cells_v1')): - self.assertRaises(exception.ComputeHostNotFound, - self.host_api.compute_node_get, - self.ctxt, cell_compute_uuid) - - class ComputeAggregateAPITestCase(test.TestCase): def setUp(self): super(ComputeAggregateAPITestCase, self).setUp() diff --git a/nova/tests/unit/test_policy.py b/nova/tests/unit/test_policy.py index ae56369d41..b91355da5e 100644 --- a/nova/tests/unit/test_policy.py +++ b/nova/tests/unit/test_policy.py @@ -284,8 +284,6 @@ class RealRolePolicyTestCase(test.NoDBTestCase): self.fake_policy = jsonutils.loads(fake_policy.policy_data) self.admin_only_rules = ( -"cells_scheduler_filter:DifferentCellFilter", -"cells_scheduler_filter:TargetCellFilter", "network:attach_external_network", "os_compute_api:servers:create:forced_host", "os_compute_api:servers:detail:get_all_tenants", diff --git a/nova/tests/unit/test_profiler.py b/nova/tests/unit/test_profiler.py index fd30ba027d..b93e1c02c0 100644 --- a/nova/tests/unit/test_profiler.py +++ b/nova/tests/unit/test_profiler.py @@ -48,8 +48,6 @@ class TestProfiler(test.NoDBTestCase): classes = [ 'nova.api.manager.MetadataManager', - 'nova.cells.manager.CellsManager', - 'nova.cells.rpcapi.CellsAPI', 'nova.compute.api.API', 'nova.compute.manager.ComputeManager', 'nova.compute.rpcapi.ComputeAPI',