From af2e103514bcc4e2d709f11f7bcc6579fb1e5800 Mon Sep 17 00:00:00 2001 From: Clinton Knight Date: Wed, 25 Nov 2015 13:59:42 -0800 Subject: [PATCH] Reorganize scheduler and merge code from Oslo incubator With oslo-incubator going away, we need to pull those classes into the Manila code base, along with their unit tests. This presents a good opportunity to do some long-needed housecleaning. This commit does the following: 1. Moves the scheduler classes from openstack.common to manila. 2. Adds the unit tests from olso-incubator into Manila. 3. Removes duplication among the combined scheduler modules. 4. Moves scheduler drivers into a sub-module. 5. Normalizes class and module naming throughout the scheduler. 6. Splits some unit test files so they match the names of the modules that they test. 7. Converts usage of mox & oslotest to mock & unittest. 8. Adds a few unit tests to boost coverage levels. Implements: blueprint reorganize-manila-scheduler Change-Id: I7aa237e17787e89a95bb198093ea9bc9498279cd --- devstack/plugin.sh | 2 +- doc/source/adminref/quick_start.rst | 2 +- manila/openstack/common/README | 16 - manila/openstack/common/scheduler/filter.py | 71 ---- manila/openstack/common/scheduler/weight.py | 91 ---- manila/opts.py | 18 +- manila/scheduler/__init__.py | 25 -- .../common => }/scheduler/base_handler.py | 4 +- .../drivers}/__init__.py | 0 .../scheduler/{driver.py => drivers/base.py} | 0 manila/scheduler/{ => drivers}/chance.py | 6 +- .../filter.py} | 12 +- manila/scheduler/{ => drivers}/simple.py | 9 +- .../filters/availability_zone.py} | 4 +- .../filters/base.py} | 12 +- .../filters/base_host.py} | 7 +- .../filters/capabilities.py} | 10 +- .../{capacity_filter.py => capacity.py} | 4 +- ...y_group_filter.py => consistency_group.py} | 4 +- .../scheduler/filters/extra_specs_ops.py | 0 .../filters/ignore_attempted_hosts.py} | 4 +- .../filters/json.py} | 21 +- .../filters/{retry_filter.py => retry.py} | 4 +- manila/scheduler/host_manager.py | 12 +- manila/scheduler/manager.py | 26 +- .../{weights => weighers}/__init__.py | 0 .../weighers/base.py} | 16 +- .../weighers/base_host.py} | 13 +- .../{weights => weighers}/capacity.py | 8 +- .../scheduler/{weights => weighers}/pool.py | 4 +- .../{weights => drivers}/__init__.py | 0 manila/tests/scheduler/drivers/test_base.py | 105 +++++ .../test_filter.py} | 16 +- manila/tests/scheduler/drivers/test_simple.py | 169 ++++++++ manila/tests/scheduler/fakes.py | 20 +- manila/tests/scheduler/filters/__init__.py | 0 .../filters/test_availability_zone.py | 66 +++ manila/tests/scheduler/filters/test_base.py | 159 +++++++ .../tests/scheduler/filters/test_base_host.py | 56 +++ .../scheduler/filters/test_capabilities.py | 101 +++++ .../test_capacity.py} | 62 +-- .../scheduler/filters/test_extra_specs_ops.py | 237 +++++++++++ .../filters/test_ignore_attempted_hosts.py | 53 +++ manila/tests/scheduler/filters/test_json.py | 326 +++++++++++++++ manila/tests/scheduler/filters/test_retry.py | 50 +++ manila/tests/scheduler/test_host_manager.py | 6 +- manila/tests/scheduler/test_manager.py | 245 +++++++++++ manila/tests/scheduler/test_scheduler.py | 389 ------------------ .../tests/scheduler/test_scheduler_options.py | 2 +- manila/tests/scheduler/weighers/__init__.py | 0 manila/tests/scheduler/weighers/test_base.py | 64 +++ .../test_capacity.py} | 8 +- .../{weights => weighers}/test_pool.py | 13 +- setup.cfg | 22 +- 54 files changed, 1813 insertions(+), 761 deletions(-) delete mode 100644 manila/openstack/common/README delete mode 100644 manila/openstack/common/scheduler/filter.py delete mode 100644 manila/openstack/common/scheduler/weight.py rename manila/{openstack/common => }/scheduler/base_handler.py (93%) rename manila/{openstack/common/scheduler => scheduler/drivers}/__init__.py (100%) rename manila/scheduler/{driver.py => drivers/base.py} (100%) rename manila/scheduler/{ => drivers}/chance.py (94%) rename manila/scheduler/{filter_scheduler.py => drivers/filter.py} (97%) rename manila/scheduler/{ => drivers}/simple.py (91%) rename manila/{openstack/common/scheduler/filters/availability_zone_filter.py => scheduler/filters/availability_zone.py} (91%) rename manila/{openstack/common/scheduler/base_filter.py => scheduler/filters/base.py} (91%) rename manila/{openstack/common/scheduler/filters/__init__.py => scheduler/filters/base_host.py} (88%) rename manila/{openstack/common/scheduler/filters/capabilities_filter.py => scheduler/filters/capabilities.py} (90%) rename manila/scheduler/filters/{capacity_filter.py => capacity.py} (98%) rename manila/scheduler/filters/{consistency_group_filter.py => consistency_group.py} (94%) rename manila/{openstack/common => }/scheduler/filters/extra_specs_ops.py (100%) rename manila/{openstack/common/scheduler/filters/ignore_attempted_hosts_filter.py => scheduler/filters/ignore_attempted_hosts.py} (94%) rename manila/{openstack/common/scheduler/filters/json_filter.py => scheduler/filters/json.py} (90%) rename manila/scheduler/filters/{retry_filter.py => retry.py} (93%) rename manila/scheduler/{weights => weighers}/__init__.py (100%) rename manila/{openstack/common/scheduler/base_weight.py => scheduler/weighers/base.py} (93%) rename manila/{openstack/common/scheduler/weights/__init__.py => scheduler/weighers/base_host.py} (79%) rename manila/scheduler/{weights => weighers}/capacity.py (93%) rename manila/scheduler/{weights => weighers}/pool.py (94%) rename manila/tests/scheduler/{weights => drivers}/__init__.py (100%) create mode 100644 manila/tests/scheduler/drivers/test_base.py rename manila/tests/scheduler/{test_filter_scheduler.py => drivers/test_filter.py} (97%) create mode 100644 manila/tests/scheduler/drivers/test_simple.py create mode 100644 manila/tests/scheduler/filters/__init__.py create mode 100644 manila/tests/scheduler/filters/test_availability_zone.py create mode 100644 manila/tests/scheduler/filters/test_base.py create mode 100644 manila/tests/scheduler/filters/test_base_host.py create mode 100644 manila/tests/scheduler/filters/test_capabilities.py rename manila/tests/scheduler/{test_host_filters.py => filters/test_capacity.py} (77%) create mode 100644 manila/tests/scheduler/filters/test_extra_specs_ops.py create mode 100644 manila/tests/scheduler/filters/test_ignore_attempted_hosts.py create mode 100644 manila/tests/scheduler/filters/test_json.py create mode 100644 manila/tests/scheduler/filters/test_retry.py create mode 100644 manila/tests/scheduler/test_manager.py delete mode 100644 manila/tests/scheduler/test_scheduler.py create mode 100644 manila/tests/scheduler/weighers/__init__.py create mode 100644 manila/tests/scheduler/weighers/test_base.py rename manila/tests/scheduler/{test_capacity_weigher.py => weighers/test_capacity.py} (97%) rename manila/tests/scheduler/{weights => weighers}/test_pool.py (95%) diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 3ba3f97f92..2e44184104 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -76,7 +76,7 @@ fi # Common opts SHARE_NAME_PREFIX=${SHARE_NAME_PREFIX:-share-} MANILA_ENABLED_SHARE_PROTOCOLS=${ENABLED_SHARE_PROTOCOLS:-"NFS,CIFS"} -MANILA_SCHEDULER_DRIVER=${MANILA_SCHEDULER_DRIVER:-manila.scheduler.filter_scheduler.FilterScheduler} +MANILA_SCHEDULER_DRIVER=${MANILA_SCHEDULER_DRIVER:-manila.scheduler.drivers.filter.FilterScheduler} MANILA_SERVICE_SECGROUP="manila-service" # Following env var defines whether to apply downgrade migrations setting up DB or not. diff --git a/doc/source/adminref/quick_start.rst b/doc/source/adminref/quick_start.rst index 147deaa831..cede7f764e 100644 --- a/doc/source/adminref/quick_start.rst +++ b/doc/source/adminref/quick_start.rst @@ -317,7 +317,7 @@ Open Manila configuration file `/etc/manila/manila.conf`:: share_name_template = share-%s # Set scheduler driver with usage of filters. Recommended. - scheduler_driver = manila.scheduler.filter_scheduler.FilterScheduler + scheduler_driver = manila.scheduler.drivers.filter.FilterScheduler # Set following two opts to ‘True’ to get maximum info in logging. verbose = True diff --git a/manila/openstack/common/README b/manila/openstack/common/README deleted file mode 100644 index 04a616648b..0000000000 --- a/manila/openstack/common/README +++ /dev/null @@ -1,16 +0,0 @@ -oslo-incubator --------------- - -A number of modules from oslo-incubator are imported into this project. -You can clone the oslo-incubator repository using the following url: - - git://git.openstack.org/openstack/oslo-incubator - -These modules are "incubating" in oslo-incubator and are kept in sync -with the help of oslo-incubator's update.py script. See: - - https://wiki.openstack.org/wiki/Oslo#Syncing_Code_from_Incubator - -The copy of the code should never be directly modified here. Please -always update oslo-incubator first and then run the script to copy -the changes across. diff --git a/manila/openstack/common/scheduler/filter.py b/manila/openstack/common/scheduler/filter.py deleted file mode 100644 index 52c18afa37..0000000000 --- a/manila/openstack/common/scheduler/filter.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) 2011-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. - -""" -Filter support -""" - -import inspect - -from stevedore import extension - - -class BaseFilter(object): - """Base class for all filter classes.""" - def _filter_one(self, obj, filter_properties): - """Return True if it passes the filter, False otherwise. - Override this in a subclass. - """ - return True - - def filter_all(self, filter_obj_list, filter_properties): - """Yield objects that pass the filter. - - Can be overriden in a subclass, if you need to base filtering - decisions on all objects. Otherwise, one can just override - _filter_one() to filter a single object. - """ - for obj in filter_obj_list: - if self._filter_one(obj, filter_properties): - yield obj - - -class BaseFilterHandler(object): - """ Base class to handle loading filter classes. - - This class should be subclassed where one needs to use filters. - """ - def __init__(self, filter_class_type, filter_namespace): - self.namespace = filter_namespace - self.filter_class_type = filter_class_type - self.filter_manager = extension.ExtensionManager(filter_namespace) - - def _is_correct_class(self, obj): - """Return whether an object is a class of the correct type and - is not prefixed with an underscore. - """ - return (inspect.isclass(obj) and - not obj.__name__.startswith('_') and - issubclass(obj, self.filter_class_type)) - - def get_all_classes(self): - return [x.plugin for x in self.filter_manager - if self._is_correct_class(x.plugin)] - - def get_filtered_objects(self, filter_classes, objs, - filter_properties): - for filter_cls in filter_classes: - objs = filter_cls().filter_all(objs, filter_properties) - return list(objs) diff --git a/manila/openstack/common/scheduler/weight.py b/manila/openstack/common/scheduler/weight.py deleted file mode 100644 index 82f1d25ee3..0000000000 --- a/manila/openstack/common/scheduler/weight.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) 2011-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. - -""" -Pluggable Weighing support -""" - -import inspect - -from stevedore import extension - - -class WeighedObject(object): - """Object with weight information.""" - def __init__(self, obj, weight): - self.obj = obj - self.weight = weight - - def __repr__(self): - return "" % (self.obj, self.weight) - - -class BaseWeigher(object): - """Base class for pluggable weighers.""" - def _weight_multiplier(self): - """How weighted this weigher should be. Normally this would - be overriden in a subclass based on a config value. - """ - return 1.0 - - def _weigh_object(self, obj, weight_properties): - """Override in a subclass to specify a weight for a specific - object. - """ - return 0.0 - - def weigh_objects(self, weighed_obj_list, weight_properties): - """Weigh multiple objects. Override in a subclass if you need - need access to all objects in order to manipulate weights. - """ - constant = self._weight_multiplier() - for obj in weighed_obj_list: - obj.weight += (constant * - self._weigh_object(obj.obj, weight_properties)) - - -class BaseWeightHandler(object): - object_class = WeighedObject - - def __init__(self, weighed_object_type, weight_namespace): - self.namespace = weight_namespace - self.weighed_object_type = weighed_object_type - self.weight_manager = extension.ExtensionManager(weight_namespace) - - def _is_correct_class(self, obj): - """Return whether an object is a class of the correct type and - is not prefixed with an underscore. - """ - return (inspect.isclass(obj) and - not obj.__name__.startswith('_') and - issubclass(obj, self.weighed_object_type)) - - def get_all_classes(self): - return [x.plugin for x in self.weight_manager - if self._is_correct_class(x.plugin)] - - def get_weighed_objects(self, weigher_classes, obj_list, - weighing_properties): - """Return a sorted (highest score first) list of WeighedObjects.""" - - if not obj_list: - return [] - - weighed_objs = [self.object_class(obj, 0.0) for obj in obj_list] - for weigher_cls in weigher_classes: - weigher = weigher_cls() - weigher.weigh_objects(weighed_objs, weighing_properties) - - return sorted(weighed_objs, key=lambda x: x.weight, reverse=True) diff --git a/manila/opts.py b/manila/opts.py index e325ec8374..17ef814a09 100644 --- a/manila/opts.py +++ b/manila/opts.py @@ -39,14 +39,14 @@ import manila.network.neutron.neutron_network_plugin import manila.network.nova_network_plugin import manila.network.standalone_network_plugin import manila.quota -import manila.scheduler.driver +import manila.scheduler.drivers.base +import manila.scheduler.drivers.simple import manila.scheduler.host_manager import manila.scheduler.manager import manila.scheduler.scheduler_options -import manila.scheduler.simple -import manila.scheduler.weights -import manila.scheduler.weights.capacity -import manila.scheduler.weights.pool +import manila.scheduler.weighers +import manila.scheduler.weighers.capacity +import manila.scheduler.weighers.pool import manila.service import manila.share.api import manila.share.driver @@ -99,13 +99,13 @@ _global_opt_lists = [ manila.network.nova_network_plugin.nova_single_network_plugin_opts, manila.network.standalone_network_plugin.standalone_network_plugin_opts, manila.quota.quota_opts, - manila.scheduler.driver.scheduler_driver_opts, + manila.scheduler.drivers.base.scheduler_driver_opts, manila.scheduler.host_manager.host_manager_opts, [manila.scheduler.manager.scheduler_driver_opt], [manila.scheduler.scheduler_options.scheduler_json_config_location_opt], - manila.scheduler.simple.simple_scheduler_opts, - manila.scheduler.weights.capacity.capacity_weight_opts, - manila.scheduler.weights.pool.pool_weight_opts, + manila.scheduler.drivers.simple.simple_scheduler_opts, + manila.scheduler.weighers.capacity.capacity_weight_opts, + manila.scheduler.weighers.pool.pool_weight_opts, manila.service.service_opts, manila.share.api.share_api_opts, manila.share.driver.ganesha_opts, diff --git a/manila/scheduler/__init__.py b/manila/scheduler/__init__.py index 781d2826e0..e69de29bb2 100644 --- a/manila/scheduler/__init__.py +++ b/manila/scheduler/__init__.py @@ -1,25 +0,0 @@ -# Copyright (c) 2010 OpenStack, LLC. -# -# 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. - -""" -:mod:`manila.scheduler` -- Scheduler Nodes -===================================================== - -.. automodule:: manila.scheduler - :platform: Unix - :synopsis: Module that picks a volume node to create a volume. -.. moduleauthor:: Sandy Walsh -.. moduleauthor:: Ed Leafe -.. moduleauthor:: Chris Behrens -""" diff --git a/manila/openstack/common/scheduler/base_handler.py b/manila/scheduler/base_handler.py similarity index 93% rename from manila/openstack/common/scheduler/base_handler.py rename to manila/scheduler/base_handler.py index 44c8eca5b8..efead1b456 100644 --- a/manila/openstack/common/scheduler/base_handler.py +++ b/manila/scheduler/base_handler.py @@ -32,7 +32,9 @@ class BaseHandler(object): self.extension_manager = extension.ExtensionManager(modifier_namespace) def _is_correct_class(self, cls): - """Return whether an object is a class of the correct type and + """Check if an object is the correct type. + + Return whether an object is a class of the correct type and is not prefixed with an underscore. """ return (inspect.isclass(cls) and diff --git a/manila/openstack/common/scheduler/__init__.py b/manila/scheduler/drivers/__init__.py similarity index 100% rename from manila/openstack/common/scheduler/__init__.py rename to manila/scheduler/drivers/__init__.py diff --git a/manila/scheduler/driver.py b/manila/scheduler/drivers/base.py similarity index 100% rename from manila/scheduler/driver.py rename to manila/scheduler/drivers/base.py diff --git a/manila/scheduler/chance.py b/manila/scheduler/drivers/chance.py similarity index 94% rename from manila/scheduler/chance.py rename to manila/scheduler/drivers/chance.py index 33b4aa2baf..be95a448e7 100644 --- a/manila/scheduler/chance.py +++ b/manila/scheduler/drivers/chance.py @@ -25,12 +25,12 @@ from oslo_config import cfg from manila import exception from manila.i18n import _ -from manila.scheduler import driver +from manila.scheduler.drivers import base CONF = cfg.CONF -class ChanceScheduler(driver.Scheduler): +class ChanceScheduler(base.Scheduler): """Implements Scheduler as a random node selector.""" def _filter_hosts(self, request_spec, hosts, **kwargs): @@ -65,7 +65,7 @@ class ChanceScheduler(driver.Scheduler): share_id = request_spec['share_id'] snapshot_id = request_spec['snapshot_id'] - updated_share = driver.share_update_db(context, share_id, host) + updated_share = base.share_update_db(context, share_id, host) self.share_rpcapi.create_share_instance( context, updated_share.instance, diff --git a/manila/scheduler/filter_scheduler.py b/manila/scheduler/drivers/filter.py similarity index 97% rename from manila/scheduler/filter_scheduler.py rename to manila/scheduler/drivers/filter.py index b95a21d64e..0946a6da11 100644 --- a/manila/scheduler/filter_scheduler.py +++ b/manila/scheduler/drivers/filter.py @@ -26,7 +26,7 @@ from oslo_log import log from manila import exception from manila.i18n import _ from manila.i18n import _LE, _LI -from manila.scheduler import driver +from manila.scheduler.drivers import base from manila.scheduler import scheduler_options from manila.share import share_types @@ -34,7 +34,7 @@ CONF = cfg.CONF LOG = log.getLogger(__name__) -class FilterScheduler(driver.Scheduler): +class FilterScheduler(base.Scheduler): """Scheduler that can be used for filtering and weighing.""" def __init__(self, *args, **kwargs): super(FilterScheduler, self).__init__(*args, **kwargs) @@ -42,10 +42,6 @@ class FilterScheduler(driver.Scheduler): self.options = scheduler_options.SchedulerOptions() self.max_attempts = self._max_attempts() - def schedule(self, context, topic, method, *args, **kwargs): - """Return best-suited host for request.""" - self._schedule(context, topic, *args, **kwargs) - def _get_configuration_options(self): """Fetch options dictionary. Broken out for testing.""" return self.options.get_configuration() @@ -95,7 +91,7 @@ class FilterScheduler(driver.Scheduler): share_id = request_spec['share_id'] snapshot_id = request_spec['snapshot_id'] - updated_share = driver.share_update_db(context, share_id, host) + updated_share = base.share_update_db(context, share_id, host) self._post_select_populate_filter_properties(filter_properties, weighed_host.obj) @@ -296,7 +292,7 @@ class FilterScheduler(driver.Scheduler): msg = _LI("Chose host %(host)s for create_consistency_group %(cg_id)s") LOG.info(msg % {'host': host, 'cg_id': group_id}) - updated_group = driver.cg_update_db(context, group_id, host) + updated_group = base.cg_update_db(context, group_id, host) self.share_rpcapi.create_consistency_group(context, updated_group, host) diff --git a/manila/scheduler/simple.py b/manila/scheduler/drivers/simple.py similarity index 91% rename from manila/scheduler/simple.py rename to manila/scheduler/drivers/simple.py index 51abc4d835..7a2dc7d909 100644 --- a/manila/scheduler/simple.py +++ b/manila/scheduler/drivers/simple.py @@ -24,8 +24,8 @@ from oslo_config import cfg from manila import db from manila import exception from manila.i18n import _ -from manila.scheduler import chance -from manila.scheduler import driver +from manila.scheduler.drivers import base +from manila.scheduler.drivers import chance from manila import utils simple_scheduler_opts = [ @@ -64,8 +64,9 @@ class SimpleScheduler(chance.ChanceScheduler): msg = _("Not enough allocatable share gigabytes remaining") raise exception.NoValidHost(reason=msg) if utils.service_is_up(service) and not service['disabled']: - updated_share = driver.share_update_db(context, share_id, - service['host']) + updated_share = base.share_update_db(context, + share_id, + service['host']) self.share_rpcapi.create_share_instance( context, updated_share.instance, diff --git a/manila/openstack/common/scheduler/filters/availability_zone_filter.py b/manila/scheduler/filters/availability_zone.py similarity index 91% rename from manila/openstack/common/scheduler/filters/availability_zone_filter.py rename to manila/scheduler/filters/availability_zone.py index 1b8d5f3b4a..df43d1826c 100644 --- a/manila/openstack/common/scheduler/filters/availability_zone_filter.py +++ b/manila/scheduler/filters/availability_zone.py @@ -13,10 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from manila.openstack.common.scheduler import filters +from manila.scheduler.filters import base_host -class AvailabilityZoneFilter(filters.BaseHostFilter): +class AvailabilityZoneFilter(base_host.BaseHostFilter): """Filters Hosts by availability zone.""" # Availability zones do not change within a request diff --git a/manila/openstack/common/scheduler/base_filter.py b/manila/scheduler/filters/base.py similarity index 91% rename from manila/openstack/common/scheduler/base_filter.py rename to manila/scheduler/filters/base.py index 488055de57..7150ebc40d 100644 --- a/manila/openstack/common/scheduler/base_filter.py +++ b/manila/scheduler/filters/base.py @@ -18,8 +18,8 @@ Filter support """ import logging -from manila.openstack.common._i18n import _LI -from manila.openstack.common.scheduler import base_handler +from manila.i18n import _LI +from manila.scheduler import base_handler LOG = logging.getLogger(__name__) @@ -27,7 +27,9 @@ LOG = logging.getLogger(__name__) class BaseFilter(object): """Base class for all filter classes.""" def _filter_one(self, obj, filter_properties): - """Return True if it passes the filter, False otherwise. + """Check if an object passes a filter. + + Return True if it passes the filter, False otherwise. Override this in a subclass. """ return True @@ -48,7 +50,9 @@ class BaseFilter(object): run_filter_once_per_request = False def run_filter_for_index(self, index): - """Return True if the filter needs to be run for the "index-th" + """Check if filter needs to be run for the "index-th" instance. + + Return True if the filter needs to be run for the "index-th" instance in a request. Only need to override this if a filter needs anything other than "first only" or "all" behaviour. """ diff --git a/manila/openstack/common/scheduler/filters/__init__.py b/manila/scheduler/filters/base_host.py similarity index 88% rename from manila/openstack/common/scheduler/filters/__init__.py rename to manila/scheduler/filters/base_host.py index 11a5eacef6..e5d4233d5d 100644 --- a/manila/openstack/common/scheduler/filters/__init__.py +++ b/manila/scheduler/filters/base_host.py @@ -17,10 +17,10 @@ Scheduler host filters """ -from manila.openstack.common.scheduler import base_filter +from manila.scheduler.filters import base -class BaseHostFilter(base_filter.BaseFilter): +class BaseHostFilter(base.BaseFilter): """Base class for host filters.""" def _filter_one(self, obj, filter_properties): """Return True if the object passes the filter, otherwise False.""" @@ -28,11 +28,12 @@ class BaseHostFilter(base_filter.BaseFilter): def host_passes(self, host_state, filter_properties): """Return True if the HostState passes the filter, otherwise False. + Override this in a subclass. """ raise NotImplementedError() -class HostFilterHandler(base_filter.BaseFilterHandler): +class HostFilterHandler(base.BaseFilterHandler): def __init__(self, namespace): super(HostFilterHandler, self).__init__(BaseHostFilter, namespace) diff --git a/manila/openstack/common/scheduler/filters/capabilities_filter.py b/manila/scheduler/filters/capabilities.py similarity index 90% rename from manila/openstack/common/scheduler/filters/capabilities_filter.py rename to manila/scheduler/filters/capabilities.py index fd99e718dd..6490300f5b 100644 --- a/manila/openstack/common/scheduler/filters/capabilities_filter.py +++ b/manila/scheduler/filters/capabilities.py @@ -17,17 +17,19 @@ import logging import six -from manila.openstack.common.scheduler import filters -from manila.openstack.common.scheduler.filters import extra_specs_ops +from manila.scheduler.filters import base_host +from manila.scheduler.filters import extra_specs_ops LOG = logging.getLogger(__name__) -class CapabilitiesFilter(filters.BaseHostFilter): +class CapabilitiesFilter(base_host.BaseHostFilter): """HostFilter to work with resource (instance & volume) type records.""" def _satisfies_extra_specs(self, capabilities, resource_type): - """Check that the capabilities provided by the services satisfy + """Compare capabilities against extra specs. + + Check that the capabilities provided by the services satisfy the extra specs associated with the resource type. """ extra_specs = resource_type.get('extra_specs', []) diff --git a/manila/scheduler/filters/capacity_filter.py b/manila/scheduler/filters/capacity.py similarity index 98% rename from manila/scheduler/filters/capacity_filter.py rename to manila/scheduler/filters/capacity.py index bac6a918e2..4f694557d5 100644 --- a/manila/scheduler/filters/capacity_filter.py +++ b/manila/scheduler/filters/capacity.py @@ -23,12 +23,12 @@ from oslo_log import log from manila.i18n import _LE from manila.i18n import _LW -from manila.openstack.common.scheduler import filters +from manila.scheduler.filters import base_host LOG = log.getLogger(__name__) -class CapacityFilter(filters.BaseHostFilter): +class CapacityFilter(base_host.BaseHostFilter): """CapacityFilter filters based on share host's capacity utilization.""" def host_passes(self, host_state, filter_properties): diff --git a/manila/scheduler/filters/consistency_group_filter.py b/manila/scheduler/filters/consistency_group.py similarity index 94% rename from manila/scheduler/filters/consistency_group_filter.py rename to manila/scheduler/filters/consistency_group.py index ab7b40bbe5..e4da2fab78 100644 --- a/manila/scheduler/filters/consistency_group_filter.py +++ b/manila/scheduler/filters/consistency_group.py @@ -16,13 +16,13 @@ from oslo_log import log -from manila.openstack.common.scheduler import filters +from manila.scheduler.filters import base_host from manila.share import utils as share_utils LOG = log.getLogger(__name__) -class ConsistencyGroupFilter(filters.BaseHostFilter): +class ConsistencyGroupFilter(base_host.BaseHostFilter): """ConsistencyGroupFilter filters host based on compatibility with CG.""" def host_passes(self, host_state, filter_properties): diff --git a/manila/openstack/common/scheduler/filters/extra_specs_ops.py b/manila/scheduler/filters/extra_specs_ops.py similarity index 100% rename from manila/openstack/common/scheduler/filters/extra_specs_ops.py rename to manila/scheduler/filters/extra_specs_ops.py diff --git a/manila/openstack/common/scheduler/filters/ignore_attempted_hosts_filter.py b/manila/scheduler/filters/ignore_attempted_hosts.py similarity index 94% rename from manila/openstack/common/scheduler/filters/ignore_attempted_hosts_filter.py rename to manila/scheduler/filters/ignore_attempted_hosts.py index 48bae455bb..ab57796b7d 100644 --- a/manila/openstack/common/scheduler/filters/ignore_attempted_hosts_filter.py +++ b/manila/scheduler/filters/ignore_attempted_hosts.py @@ -15,12 +15,12 @@ import logging -from manila.openstack.common.scheduler import filters +from manila.scheduler.filters import base_host LOG = logging.getLogger(__name__) -class IgnoreAttemptedHostsFilter(filters.BaseHostFilter): +class IgnoreAttemptedHostsFilter(base_host.BaseHostFilter): """Filter out previously attempted hosts A host passes this filter if it has not already been attempted for diff --git a/manila/openstack/common/scheduler/filters/json_filter.py b/manila/scheduler/filters/json.py similarity index 90% rename from manila/openstack/common/scheduler/filters/json_filter.py rename to manila/scheduler/filters/json.py index 5cb506220a..48e2359cd2 100644 --- a/manila/openstack/common/scheduler/filters/json_filter.py +++ b/manila/scheduler/filters/json.py @@ -18,15 +18,16 @@ import operator from oslo_serialization import jsonutils import six -from manila.openstack.common.scheduler import filters +from manila.scheduler.filters import base_host -class JsonFilter(filters.BaseHostFilter): - """Host Filter to allow simple JSON-based grammar for - selecting hosts. - """ +class JsonFilter(base_host.BaseHostFilter): + """Host Filter to allow simple JSON-based grammar for selecting hosts.""" + def _op_compare(self, args, op): - """Returns True if the specified operator can successfully + """Check if operator can compare the first arg with the others. + + Returns True if the specified operator can successfully compare the first item in the args with all the rest. Will return False if only one item is in the list. """ @@ -88,7 +89,9 @@ class JsonFilter(filters.BaseHostFilter): } def _parse_string(self, string, host_state): - """Strings prefixed with $ are capability lookups in the + """Parse string. + + Strings prefixed with $ are capability lookups in the form '$variable' where 'variable' is an attribute in the HostState class. If $variable is a dictionary, you may use: $variable.dictkey @@ -126,7 +129,9 @@ class JsonFilter(filters.BaseHostFilter): return result def host_passes(self, host_state, filter_properties): - """Return a list of hosts that can fulfill the requirements + """Filters hosts. + + Return a list of hosts that can fulfill the requirements specified in the query. """ # TODO(zhiteng) Add description for filter_properties structure diff --git a/manila/scheduler/filters/retry_filter.py b/manila/scheduler/filters/retry.py similarity index 93% rename from manila/scheduler/filters/retry_filter.py rename to manila/scheduler/filters/retry.py index a07643229e..3eb210babb 100644 --- a/manila/scheduler/filters/retry_filter.py +++ b/manila/scheduler/filters/retry.py @@ -15,12 +15,12 @@ from oslo_log import log -from manila.openstack.common.scheduler import filters +from manila.scheduler.filters import base_host LOG = log.getLogger(__name__) -class RetryFilter(filters.BaseHostFilter): +class RetryFilter(base_host.BaseHostFilter): """Filter out already tried nodes for scheduling purposes.""" def host_passes(self, host_state, filter_properties): diff --git a/manila/scheduler/host_manager.py b/manila/scheduler/host_manager.py index c1cdf6606e..8d5b795543 100644 --- a/manila/scheduler/host_manager.py +++ b/manila/scheduler/host_manager.py @@ -34,8 +34,8 @@ import six from manila import db from manila import exception from manila.i18n import _LI, _LW -from manila.openstack.common.scheduler import filters -from manila.openstack.common.scheduler import weights +from manila.scheduler.filters import base_host as base_host_filter +from manila.scheduler.weighers import base_host as base_host_weigher from manila.share import utils as share_utils from manila import utils @@ -379,11 +379,11 @@ class HostManager(object): def __init__(self): self.service_states = {} # { : {: {cap k : v}}} self.host_state_map = {} - self.filter_handler = filters.HostFilterHandler('manila.scheduler.' - 'filters') + self.filter_handler = base_host_filter.HostFilterHandler( + 'manila.scheduler.filters') self.filter_classes = self.filter_handler.get_all_classes() - self.weight_handler = weights.HostWeightHandler('manila.scheduler.' - 'weights') + self.weight_handler = base_host_weigher.HostWeightHandler( + 'manila.scheduler.weighers') self.weight_classes = self.weight_handler.get_all_classes() def _choose_host_filters(self, filter_cls_names): diff --git a/manila/scheduler/manager.py b/manila/scheduler/manager.py index 2a41076fa0..689819ead6 100644 --- a/manila/scheduler/manager.py +++ b/manila/scheduler/manager.py @@ -37,13 +37,24 @@ from manila.share import rpcapi as share_rpcapi LOG = log.getLogger(__name__) scheduler_driver_opt = cfg.StrOpt('scheduler_driver', - default='manila.scheduler.filter_scheduler.' - 'FilterScheduler', + default='manila.scheduler.drivers.' + 'filter.FilterScheduler', help='Default scheduler driver to use.') CONF = cfg.CONF CONF.register_opt(scheduler_driver_opt) +# Drivers that need to change module paths or class names can add their +# old/new path here to maintain backward compatibility. +MAPPING = { + 'manila.scheduler.chance.ChanceScheduler': + 'manila.scheduler.drivers.chance.ChanceScheduler', + 'manila.scheduler.filter_scheduler.FilterScheduler': + 'manila.scheduler.drivers.filter.FilterScheduler', + 'manila.scheduler.simple.SimpleScheduler': + 'manila.scheduler.drivers.simple.SimpleScheduler', +} + class SchedulerManager(manager.Manager): """Chooses a host to create shares.""" @@ -52,8 +63,19 @@ class SchedulerManager(manager.Manager): def __init__(self, scheduler_driver=None, service_name=None, *args, **kwargs): + if not scheduler_driver: scheduler_driver = CONF.scheduler_driver + if scheduler_driver in MAPPING: + msg_args = { + 'old': scheduler_driver, + 'new': MAPPING[scheduler_driver], + } + LOG.warning(_LW("Scheduler driver path %(old)s is deprecated, " + "update your configuration to the new path " + "%(new)s"), msg_args) + scheduler_driver = MAPPING[scheduler_driver] + self.driver = importutils.import_object(scheduler_driver) super(SchedulerManager, self).__init__(*args, **kwargs) diff --git a/manila/scheduler/weights/__init__.py b/manila/scheduler/weighers/__init__.py similarity index 100% rename from manila/scheduler/weights/__init__.py rename to manila/scheduler/weighers/__init__.py diff --git a/manila/openstack/common/scheduler/base_weight.py b/manila/scheduler/weighers/base.py similarity index 93% rename from manila/openstack/common/scheduler/base_weight.py rename to manila/scheduler/weighers/base.py index 92dbf5443a..f45264a8ff 100644 --- a/manila/openstack/common/scheduler/base_weight.py +++ b/manila/scheduler/weighers/base.py @@ -21,7 +21,7 @@ import abc import six -from manila.openstack.common.scheduler import base_handler +from manila.scheduler import base_handler def normalize(weight_list, minval=None, maxval=None): @@ -70,7 +70,7 @@ class BaseWeigher(object): The attributes maxval and minval can be specified to set up the maximum and minimum values for the weighed objects. These values will then be taken into account in the normalization step, instead of taking the values - from the calculated weights. + from the calculated weighers. """ minval = None @@ -87,18 +87,16 @@ class BaseWeigher(object): @abc.abstractmethod def _weigh_object(self, obj, weight_properties): - """Override in a subclass to specify a weight for a specific - object. - """ + """Override in a subclass to specify a weight for a specific object.""" def weigh_objects(self, weighed_obj_list, weight_properties): """Weigh multiple objects. Override in a subclass if you need access to all objects in order - to calculate weights. Do not modify the weight of an object here, - just return a list of weights. + to calculate weighers. Do not modify the weight of an object here, + just return a list of weighers. """ - # Calculate the weights + # Calculate the weighers weights = [] for obj in weighed_obj_list: weight = self._weigh_object(obj.obj, weight_properties) @@ -135,7 +133,7 @@ class BaseWeightHandler(base_handler.BaseHandler): weigher = weigher_cls() weights = weigher.weigh_objects(weighed_objs, weighing_properties) - # Normalize the weights + # Normalize the weighers weights = normalize(weights, minval=weigher.minval, maxval=weigher.maxval) diff --git a/manila/openstack/common/scheduler/weights/__init__.py b/manila/scheduler/weighers/base_host.py similarity index 79% rename from manila/openstack/common/scheduler/weights/__init__.py rename to manila/scheduler/weighers/base_host.py index eda99315ef..83659031d8 100644 --- a/manila/openstack/common/scheduler/weights/__init__.py +++ b/manila/scheduler/weighers/base_host.py @@ -14,14 +14,13 @@ # under the License. """ -Scheduler host weights +Scheduler host weighers """ - -from manila.openstack.common.scheduler import base_weight +from manila.scheduler.weighers import base -class WeighedHost(base_weight.WeighedObject): +class WeighedHost(base.WeighedObject): def to_dict(self): return { 'weight': self.weight, @@ -33,12 +32,12 @@ class WeighedHost(base_weight.WeighedObject): (self.obj.host, self.weight)) -class BaseHostWeigher(base_weight.BaseWeigher): - """Base class for host weights.""" +class BaseHostWeigher(base.BaseWeigher): + """Base class for host weighers.""" pass -class HostWeightHandler(base_weight.BaseWeightHandler): +class HostWeightHandler(base.BaseWeightHandler): object_class = WeighedHost def __init__(self, namespace): diff --git a/manila/scheduler/weights/capacity.py b/manila/scheduler/weighers/capacity.py similarity index 93% rename from manila/scheduler/weights/capacity.py rename to manila/scheduler/weighers/capacity.py index 79f6f4bed7..6943e10156 100644 --- a/manila/scheduler/weights/capacity.py +++ b/manila/scheduler/weighers/capacity.py @@ -32,7 +32,7 @@ import math from oslo_config import cfg -from manila.openstack.common.scheduler import weights +from manila.scheduler.weighers import base_host capacity_weight_opts = [ cfg.FloatOpt('capacity_weight_multiplier', @@ -45,13 +45,13 @@ CONF = cfg.CONF CONF.register_opts(capacity_weight_opts) -class CapacityWeigher(weights.BaseHostWeigher): +class CapacityWeigher(base_host.BaseHostWeigher): def weight_multiplier(self): """Override the weight multiplier.""" return CONF.capacity_weight_multiplier def _weigh_object(self, host_state, weight_properties): - """Higher weights win. We want spreading to be the default.""" + """Higher weighers win. We want spreading to be the default.""" reserved = float(host_state.reserved_percentage) / 100 free_space = host_state.free_capacity_gb total_space = host_state.total_capacity_gb @@ -81,7 +81,7 @@ class CapacityWeigher(weights.BaseHostWeigher): weight_properties) # NOTE(u_glide): Replace -inf with (minimum - 1) and # inf with (maximum + 1) to avoid errors in - # manila.openstack.common.scheduler.base_weight.normalize() method + # manila.scheduler.weighers.base.normalize() method if self.minval == float('-inf'): self.minval = self.maxval for val in weights: diff --git a/manila/scheduler/weights/pool.py b/manila/scheduler/weighers/pool.py similarity index 94% rename from manila/scheduler/weights/pool.py rename to manila/scheduler/weighers/pool.py index ad5f27ec63..11ede37518 100644 --- a/manila/scheduler/weights/pool.py +++ b/manila/scheduler/weighers/pool.py @@ -17,7 +17,7 @@ from oslo_config import cfg from manila import context from manila.db import api as db_api -from manila.openstack.common.scheduler import weights +from manila.scheduler.weighers import base_host from manila.share import utils pool_weight_opts = [ @@ -32,7 +32,7 @@ CONF = cfg.CONF CONF.register_opts(pool_weight_opts) -class PoolWeigher(weights.BaseHostWeigher): +class PoolWeigher(base_host.BaseHostWeigher): def weight_multiplier(self): """Override the weight multiplier.""" return CONF.pool_weight_multiplier diff --git a/manila/tests/scheduler/weights/__init__.py b/manila/tests/scheduler/drivers/__init__.py similarity index 100% rename from manila/tests/scheduler/weights/__init__.py rename to manila/tests/scheduler/drivers/__init__.py diff --git a/manila/tests/scheduler/drivers/test_base.py b/manila/tests/scheduler/drivers/test_base.py new file mode 100644 index 0000000000..c0f48b4809 --- /dev/null +++ b/manila/tests/scheduler/drivers/test_base.py @@ -0,0 +1,105 @@ +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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 Base Scheduler +""" + +import mock +from oslo_config import cfg +from oslo_utils import timeutils + +from manila import context +from manila import db +from manila.scheduler.drivers import base +from manila import test +from manila import utils + +CONF = cfg.CONF + + +class SchedulerTestCase(test.TestCase): + """Test case for base scheduler driver class.""" + + # So we can subclass this test and re-use tests if we need. + driver_cls = base.Scheduler + + def setUp(self): + super(SchedulerTestCase, self).setUp() + self.driver = self.driver_cls() + self.context = context.RequestContext('fake_user', 'fake_project') + self.topic = 'fake_topic' + + def test_update_service_capabilities(self): + service_name = 'fake_service' + host = 'fake_host' + capabilities = {'fake_capability': 'fake_value'} + with mock.patch.object(self.driver.host_manager, + 'update_service_capabilities', mock.Mock()): + self.driver.update_service_capabilities( + service_name, host, capabilities) + self.driver.host_manager.update_service_capabilities.\ + assert_called_once_with(service_name, host, capabilities) + + def test_hosts_up(self): + service1 = {'host': 'host1'} + service2 = {'host': 'host2'} + services = [service1, service2] + + def fake_service_is_up(*args, **kwargs): + if args[0]['host'] == 'host1': + return False + return True + + with mock.patch.object(db, 'service_get_all_by_topic', + mock.Mock(return_value=services)): + with mock.patch.object(utils, 'service_is_up', + mock.Mock(side_effect=fake_service_is_up)): + result = self.driver.hosts_up(self.context, self.topic) + self.assertEqual(['host2'], result) + db.service_get_all_by_topic.assert_called_once_with( + self.context, self.topic) + + +class SchedulerDriverBaseTestCase(SchedulerTestCase): + """Test cases for base scheduler driver class methods. + + These can't fail if the driver is changed. + """ + + def test_unimplemented_schedule(self): + fake_args = (1, 2, 3) + fake_kwargs = {'cat': 'meow'} + + self.assertRaises(NotImplementedError, self.driver.schedule, + self.context, self.topic, 'schedule_something', + *fake_args, **fake_kwargs) + + +class SchedulerDriverModuleTestCase(test.TestCase): + """Test case for scheduler driver module methods.""" + + def setUp(self): + super(SchedulerDriverModuleTestCase, self).setUp() + self.context = context.RequestContext('fake_user', 'fake_project') + + @mock.patch.object(db, 'share_update', mock.Mock()) + def test_share_host_update_db(self): + with mock.patch.object(timeutils, 'utcnow', + mock.Mock(return_value='fake-now')): + base.share_update_db(self.context, 31337, 'fake_host') + db.share_update.assert_called_once_with( + self.context, 31337, + {'host': 'fake_host', 'scheduled_at': 'fake-now'}) diff --git a/manila/tests/scheduler/test_filter_scheduler.py b/manila/tests/scheduler/drivers/test_filter.py similarity index 97% rename from manila/tests/scheduler/test_filter_scheduler.py rename to manila/tests/scheduler/drivers/test_filter.py index e7b93c01d1..9ddd1838e1 100644 --- a/manila/tests/scheduler/test_filter_scheduler.py +++ b/manila/tests/scheduler/drivers/test_filter.py @@ -23,20 +23,20 @@ from oslo_utils import strutils from manila.common import constants from manila import context from manila import exception -from manila.scheduler import driver -from manila.scheduler import filter_scheduler +from manila.scheduler.drivers import base +from manila.scheduler.drivers import filter from manila.scheduler import host_manager +from manila.tests.scheduler.drivers import test_base from manila.tests.scheduler import fakes -from manila.tests.scheduler import test_scheduler SNAPSHOT_SUPPORT = constants.ExtraSpecs.SNAPSHOT_SUPPORT @ddt.ddt -class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): +class FilterSchedulerTestCase(test_base.SchedulerTestCase): """Test case for Filter Scheduler.""" - driver_cls = filter_scheduler.FilterScheduler + driver_cls = filter.FilterScheduler def test_create_share_no_hosts(self): # Ensure empty hosts/child_zones result in NoValidHosts exception. @@ -315,7 +315,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): self.mock_object(sched, "_get_best_host_for_consistency_group", mock.Mock(return_value=fake_host)) fake_updated_group = mock.Mock() - self.mock_object(driver, "cg_update_db", mock.Mock( + self.mock_object(base, "cg_update_db", mock.Mock( return_value=fake_updated_group)) self.mock_object(sched.share_rpcapi, "create_consistency_group") @@ -324,8 +324,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): sched._get_best_host_for_consistency_group.assert_called_once_with( fake_context, request_spec) - driver.cg_update_db.assert_called_once_with(fake_context, 'fake_id', - fake_host) + base.cg_update_db.assert_called_once_with( + fake_context, 'fake_id', fake_host) sched.share_rpcapi.create_consistency_group.assert_called_once_with( fake_context, fake_updated_group, fake_host) diff --git a/manila/tests/scheduler/drivers/test_simple.py b/manila/tests/scheduler/drivers/test_simple.py new file mode 100644 index 0000000000..488fc51b23 --- /dev/null +++ b/manila/tests/scheduler/drivers/test_simple.py @@ -0,0 +1,169 @@ +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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 Simple Scheduler +""" + +import mock +from oslo_config import cfg + +from manila import context +from manila import db +from manila import exception +from manila.scheduler.drivers import base +from manila.scheduler.drivers import simple +from manila.share import rpcapi as share_rpcapi +from manila import test +from manila.tests import db_utils +from manila import utils + +CONF = cfg.CONF + + +class SimpleSchedulerSharesTestCase(test.TestCase): + """Test case for simple scheduler create share method.""" + + def setUp(self): + super(SimpleSchedulerSharesTestCase, self).setUp() + self.mock_object(share_rpcapi, 'ShareAPI') + self.driver = simple.SimpleScheduler() + + self.context = context.RequestContext('fake_user', 'fake_project') + self.admin_context = context.RequestContext('fake_admin_user', + 'fake_project') + self.admin_context.is_admin = True + + @mock.patch.object(utils, 'service_is_up', mock.Mock(return_value=True)) + def test_create_share_if_two_services_up(self): + share_id = 'fake' + fake_share = {'id': share_id, 'size': 1} + fake_service_1 = {'disabled': False, 'host': 'fake_host1'} + fake_service_2 = {'disabled': False, 'host': 'fake_host2'} + fake_result = [(fake_service_1, 2), (fake_service_2, 1)] + fake_request_spec = { + 'share_id': share_id, + 'share_properties': fake_share, + } + self.mock_object(db, 'service_get_all_share_sorted', + mock.Mock(return_value=fake_result)) + self.mock_object(base, 'share_update_db', + mock.Mock(return_value=db_utils.create_share())) + + self.driver.schedule_create_share(self.context, + fake_request_spec, {}) + utils.service_is_up.assert_called_once_with(utils.IsAMatcher(dict)) + db.service_get_all_share_sorted.assert_called_once_with( + utils.IsAMatcher(context.RequestContext)) + base.share_update_db.assert_called_once_with( + utils.IsAMatcher(context.RequestContext), share_id, 'fake_host1') + + def test_create_share_if_services_not_available(self): + share_id = 'fake' + fake_share = {'id': share_id, 'size': 1} + fake_result = [] + fake_request_spec = { + 'share_id': share_id, + 'share_properties': fake_share, + } + with mock.patch.object(db, 'service_get_all_share_sorted', + mock.Mock(return_value=fake_result)): + self.assertRaises(exception.NoValidHost, + self.driver.schedule_create_share, + self.context, fake_request_spec, {}) + db.service_get_all_share_sorted.assert_called_once_with( + utils.IsAMatcher(context.RequestContext)) + + def test_create_share_if_max_gigabytes_exceeded(self): + share_id = 'fake' + fake_share = {'id': share_id, 'size': 10001} + fake_service_1 = {'disabled': False, 'host': 'fake_host1'} + fake_service_2 = {'disabled': False, 'host': 'fake_host2'} + fake_result = [(fake_service_1, 5), (fake_service_2, 7)] + fake_request_spec = { + 'share_id': share_id, + 'share_properties': fake_share, + } + with mock.patch.object(db, 'service_get_all_share_sorted', + mock.Mock(return_value=fake_result)): + self.assertRaises(exception.NoValidHost, + self.driver.schedule_create_share, + self.context, fake_request_spec, {}) + db.service_get_all_share_sorted.assert_called_once_with( + utils.IsAMatcher(context.RequestContext)) + + @mock.patch.object(utils, 'service_is_up', mock.Mock(return_value=True)) + def test_create_share_availability_zone(self): + share_id = 'fake' + fake_share = { + 'id': share_id, + 'size': 1, + } + fake_instance = { + 'availability_zone_id': 'fake', + } + fake_service_1 = { + 'disabled': False, 'host': 'fake_host1', + 'availability_zone_id': 'fake', + } + fake_service_2 = { + 'disabled': False, 'host': 'fake_host2', + 'availability_zone_id': 'super_fake', + } + fake_result = [(fake_service_1, 0), (fake_service_2, 1)] + fake_request_spec = { + 'share_id': share_id, + 'share_properties': fake_share, + 'share_instance_properties': fake_instance, + } + self.mock_object(db, 'service_get_all_share_sorted', + mock.Mock(return_value=fake_result)) + self.mock_object(base, 'share_update_db', + mock.Mock(return_value=db_utils.create_share())) + + self.driver.schedule_create_share(self.context, + fake_request_spec, {}) + utils.service_is_up.assert_called_once_with(fake_service_1) + base.share_update_db.assert_called_once_with( + utils.IsAMatcher(context.RequestContext), share_id, + fake_service_1['host']) + db.service_get_all_share_sorted.assert_called_once_with( + utils.IsAMatcher(context.RequestContext)) + + @mock.patch.object(utils, 'service_is_up', mock.Mock(return_value=True)) + def test_create_share_availability_zone_on_host(self): + share_id = 'fake' + fake_share = { + 'id': share_id, + 'availability_zone': 'fake:fake', + 'size': 1, + } + fake_service = {'disabled': False, 'host': 'fake'} + fake_request_spec = { + 'share_id': share_id, + 'share_properties': fake_share, + } + self.mock_object(db, 'service_get_all_share_sorted', + mock.Mock(return_value=[(fake_service, 1)])) + self.mock_object(base, 'share_update_db', + mock.Mock(return_value=db_utils.create_share())) + + self.driver.schedule_create_share(self.admin_context, + fake_request_spec, {}) + utils.service_is_up.assert_called_once_with(fake_service) + db.service_get_all_share_sorted.assert_called_once_with( + utils.IsAMatcher(context.RequestContext)) + base.share_update_db.assert_called_once_with( + utils.IsAMatcher(context.RequestContext), share_id, 'fake') diff --git a/manila/tests/scheduler/fakes.py b/manila/tests/scheduler/fakes.py index 1776f38224..0a6884c30b 100644 --- a/manila/tests/scheduler/fakes.py +++ b/manila/tests/scheduler/fakes.py @@ -19,8 +19,9 @@ Fakes For Scheduler tests. from oslo_utils import timeutils import six -from manila.scheduler import filter_scheduler +from manila.scheduler.drivers import filter from manila.scheduler import host_manager +from manila.scheduler.weighers import base_host as base_host_weigher SHARE_SERVICES_NO_POOLS = [ dict(id=1, host='host1', topic='share', disabled=False, @@ -167,7 +168,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = { } -class FakeFilterScheduler(filter_scheduler.FilterScheduler): +class FakeFilterScheduler(filter.FilterScheduler): def __init__(self, *args, **kwargs): super(FakeFilterScheduler, self).__init__(*args, **kwargs) self.host_manager = host_manager.HostManager() @@ -260,3 +261,18 @@ def mock_host_manager_db_calls(mock_obj, disabled=None): else: mock_obj.return_value = [service for service in services if service['disabled'] == disabled] + + +class FakeWeigher1(base_host_weigher.BaseHostWeigher): + def __init__(self): + pass + + +class FakeWeigher2(base_host_weigher.BaseHostWeigher): + def __init__(self): + pass + + +class FakeClass(object): + def __init__(self): + pass diff --git a/manila/tests/scheduler/filters/__init__.py b/manila/tests/scheduler/filters/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manila/tests/scheduler/filters/test_availability_zone.py b/manila/tests/scheduler/filters/test_availability_zone.py new file mode 100644 index 0000000000..cc74145e83 --- /dev/null +++ b/manila/tests/scheduler/filters/test_availability_zone.py @@ -0,0 +1,66 @@ +# Copyright 2011 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. + +""" +Tests For AvailabilityZoneFilter. +""" + +from oslo_context import context + +from manila.scheduler.filters import availability_zone +from manila import test +from manila.tests.scheduler import fakes + + +class HostFiltersTestCase(test.TestCase): + """Test case for AvailabilityZoneFilter.""" + + def setUp(self): + super(HostFiltersTestCase, self).setUp() + self.context = context.RequestContext('fake', 'fake') + self.filter = availability_zone.AvailabilityZoneFilter() + + @staticmethod + def _make_zone_request(zone, is_admin=False): + ctxt = context.RequestContext('fake', 'fake', is_admin=is_admin) + return { + 'context': ctxt, + 'request_spec': { + 'resource_properties': { + 'availability_zone_id': zone + } + } + } + + def test_availability_zone_filter_same(self): + service = {'availability_zone_id': 'nova'} + request = self._make_zone_request('nova') + host = fakes.FakeHostState('host1', + {'service': service}) + self.assertTrue(self.filter.host_passes(host, request)) + + def test_availability_zone_filter_different(self): + service = {'availability_zone_id': 'nova'} + request = self._make_zone_request('bad') + host = fakes.FakeHostState('host1', + {'service': service}) + self.assertFalse(self.filter.host_passes(host, request)) + + def test_availability_zone_filter_empty(self): + service = {'availability_zone_id': 'nova'} + request = {} + host = fakes.FakeHostState('host1', + {'service': service}) + self.assertTrue(self.filter.host_passes(host, request)) diff --git a/manila/tests/scheduler/filters/test_base.py b/manila/tests/scheduler/filters/test_base.py new file mode 100644 index 0000000000..59cac8ef3f --- /dev/null +++ b/manila/tests/scheduler/filters/test_base.py @@ -0,0 +1,159 @@ +# Copyright (c) 2013 OpenStack Foundation. +# +# 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. + +import mock + +from manila.scheduler.filters import base +from manila import test + + +class TestBaseFilter(test.TestCase): + + def setUp(self): + super(TestBaseFilter, self).setUp() + self.filter = base.BaseFilter() + + def test_filter_one_is_called(self): + + filters = [1, 2, 3, 4] + filter_properties = {'x': 'y'} + + side_effect = lambda value, props: value in [2, 3] + self.mock_object(self.filter, + '_filter_one', + mock.Mock(side_effect=side_effect)) + + result = list(self.filter.filter_all(filters, filter_properties)) + + self.assertEqual([2, 3], result) + + +class FakeExtension(object): + + def __init__(self, plugin): + self.plugin = plugin + + +class BaseFakeFilter(base.BaseFilter): + pass + + +class FakeFilter1(BaseFakeFilter): + """Derives from BaseFakeFilter and has a fake entry point defined. + + Entry point is returned by fake ExtensionManager. + Should be included in the output of all_classes. + """ + + +class FakeFilter2(BaseFakeFilter): + """Derives from BaseFakeFilter but has no entry point. + + Should be not included in all_classes. + """ + + +class FakeFilter3(base.BaseFilter): + """Does not derive from BaseFakeFilter. + + Should not be included. + """ + + +class FakeFilter4(BaseFakeFilter): + """Derives from BaseFakeFilter and has an entry point. + + Should be included. + """ + + +class FakeFilter5(BaseFakeFilter): + """Derives from BaseFakeFilter but has no entry point. + + Should not be included. + """ + run_filter_once_per_request = True + + +class FakeExtensionManager(list): + + def __init__(self, namespace): + classes = [FakeFilter1, FakeFilter3, FakeFilter4] + exts = map(FakeExtension, classes) + super(FakeExtensionManager, self).__init__(exts) + self.namespace = namespace + + +class TestBaseFilterHandler(test.TestCase): + + def setUp(self): + super(TestBaseFilterHandler, self).setUp() + self.mock_object(base.base_handler.extension, + 'ExtensionManager', + FakeExtensionManager) + self.handler = base.BaseFilterHandler(BaseFakeFilter, 'fake_filters') + + def test_get_all_classes(self): + # In order for a FakeFilter to be returned by get_all_classes, it has + # to comply with these rules: + # * It must be derived from BaseFakeFilter + # AND + # * It must have a python entrypoint assigned (returned by + # FakeExtensionManager) + expected = [FakeFilter1, FakeFilter4] + result = self.handler.get_all_classes() + self.assertEqual(expected, result) + + def _get_filtered_objects(self, filter_classes, index=0): + filter_objs_initial = [1, 2, 3, 4] + filter_properties = {'x': 'y'} + return self.handler.get_filtered_objects(filter_classes, + filter_objs_initial, + filter_properties, + index) + + @mock.patch.object(FakeFilter4, 'filter_all') + @mock.patch.object(FakeFilter3, 'filter_all', return_value=None) + def test_get_filtered_objects_return_none(self, fake3_filter_all, + fake4_filter_all): + filter_classes = [FakeFilter1, FakeFilter2, FakeFilter3, FakeFilter4] + result = self._get_filtered_objects(filter_classes) + self.assertIsNone(result) + self.assertFalse(fake4_filter_all.called) + + def test_get_filtered_objects(self): + filter_objs_expected = [1, 2, 3, 4] + filter_classes = [FakeFilter1, FakeFilter2, FakeFilter3, FakeFilter4] + result = self._get_filtered_objects(filter_classes) + self.assertEqual(filter_objs_expected, result) + + def test_get_filtered_objects_with_filter_run_once(self): + filter_objs_expected = [1, 2, 3, 4] + filter_classes = [FakeFilter5] + + with mock.patch.object(FakeFilter5, 'filter_all', + return_value=filter_objs_expected + ) as fake5_filter_all: + result = self._get_filtered_objects(filter_classes) + self.assertEqual(filter_objs_expected, result) + self.assertEqual(1, fake5_filter_all.call_count) + + result = self._get_filtered_objects(filter_classes, index=1) + self.assertEqual(filter_objs_expected, result) + self.assertEqual(1, fake5_filter_all.call_count) + + result = self._get_filtered_objects(filter_classes, index=2) + self.assertEqual(filter_objs_expected, result) + self.assertEqual(1, fake5_filter_all.call_count) diff --git a/manila/tests/scheduler/filters/test_base_host.py b/manila/tests/scheduler/filters/test_base_host.py new file mode 100644 index 0000000000..cf51d293ef --- /dev/null +++ b/manila/tests/scheduler/filters/test_base_host.py @@ -0,0 +1,56 @@ +# Copyright 2011 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. + +""" +Tests For Scheduler Host Filters. +""" + +from oslo_context import context +from oslo_serialization import jsonutils + +from manila.scheduler.filters import base_host +from manila import test + + +class TestFilter(test.TestCase): + pass + + +class TestBogusFilter(object): + """Class that doesn't inherit from BaseHostFilter.""" + pass + + +class HostFiltersTestCase(test.TestCase): + """Test case for host filters.""" + + def setUp(self): + super(HostFiltersTestCase, self).setUp() + self.context = context.RequestContext('fake', 'fake') + self.json_query = jsonutils.dumps( + ['and', ['>=', '$free_ram_mb', 1024], + ['>=', '$free_disk_mb', 200 * 1024]]) + namespace = 'manila.scheduler.filters' + filter_handler = base_host.HostFilterHandler(namespace) + classes = filter_handler.get_all_classes() + self.class_map = {} + for cls in classes: + self.class_map[cls.__name__] = cls + + def test_all_filters(self): + # Double check at least a couple of known filters exist + self.assertTrue('JsonFilter' in self.class_map) + self.assertTrue('CapabilitiesFilter' in self.class_map) + self.assertTrue('AvailabilityZoneFilter' in self.class_map) diff --git a/manila/tests/scheduler/filters/test_capabilities.py b/manila/tests/scheduler/filters/test_capabilities.py new file mode 100644 index 0000000000..0041afb14c --- /dev/null +++ b/manila/tests/scheduler/filters/test_capabilities.py @@ -0,0 +1,101 @@ +# Copyright 2011 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. + +""" +Tests For CapabilitiesFilter. +""" + +from oslo_context import context + +from manila.scheduler.filters import capabilities +from manila import test +from manila.tests.scheduler import fakes + + +class HostFiltersTestCase(test.TestCase): + """Test case for CapabilitiesFilter.""" + + def setUp(self): + super(HostFiltersTestCase, self).setUp() + self.context = context.RequestContext('fake', 'fake') + self.filter = capabilities.CapabilitiesFilter() + + def _do_test_type_filter_extra_specs(self, ecaps, especs, passes): + capabilities = {'enabled': True} + capabilities.update(ecaps) + service = {'disabled': False} + filter_properties = {'resource_type': {'name': 'fake_type', + 'extra_specs': especs}} + host = fakes.FakeHostState('host1', + {'free_capacity_gb': 1024, + 'capabilities': capabilities, + 'service': service}) + assertion = self.assertTrue if passes else self.assertFalse + assertion(self.filter.host_passes(host, filter_properties)) + + def test_capability_filter_passes_extra_specs_simple(self): + self._do_test_type_filter_extra_specs( + ecaps={'opt1': '1', 'opt2': '2'}, + especs={'opt1': '1', 'opt2': '2'}, + passes=True) + + def test_capability_filter_fails_extra_specs_simple(self): + self._do_test_type_filter_extra_specs( + ecaps={'opt1': '1', 'opt2': '2'}, + especs={'opt1': '1', 'opt2': '222'}, + passes=False) + + def test_capability_filter_passes_extra_specs_complex(self): + self._do_test_type_filter_extra_specs( + ecaps={'opt1': 10, 'opt2': 5}, + especs={'opt1': '>= 2', 'opt2': '<= 8'}, + passes=True) + + def test_capability_filter_fails_extra_specs_complex(self): + self._do_test_type_filter_extra_specs( + ecaps={'opt1': 10, 'opt2': 5}, + especs={'opt1': '>= 2', 'opt2': '>= 8'}, + passes=False) + + def test_capability_filter_passes_scope_extra_specs(self): + self._do_test_type_filter_extra_specs( + ecaps={'scope_lv1': {'opt1': 10}}, + especs={'capabilities:scope_lv1:opt1': '>= 2'}, + passes=True) + + def test_capability_filter_passes_fakescope_extra_specs(self): + self._do_test_type_filter_extra_specs( + ecaps={'scope_lv1': {'opt1': 10}, 'opt2': 5}, + especs={'scope_lv1:opt1': '= 2', 'opt2': '>= 3'}, + passes=True) + + def test_capability_filter_fails_scope_extra_specs(self): + self._do_test_type_filter_extra_specs( + ecaps={'scope_lv1': {'opt1': 10}}, + especs={'capabilities:scope_lv1:opt1': '<= 2'}, + passes=False) + + def test_capability_filter_passes_multi_level_scope_extra_specs(self): + self._do_test_type_filter_extra_specs( + ecaps={'scope_lv0': {'scope_lv1': + {'scope_lv2': {'opt1': 10}}}}, + especs={'capabilities:scope_lv0:scope_lv1:scope_lv2:opt1': '>= 2'}, + passes=True) + + def test_capability_filter_fails_wrong_scope_extra_specs(self): + self._do_test_type_filter_extra_specs( + ecaps={'scope_lv0': {'opt1': 10}}, + especs={'capabilities:scope_lv1:opt1': '>= 2'}, + passes=False) diff --git a/manila/tests/scheduler/test_host_filters.py b/manila/tests/scheduler/filters/test_capacity.py similarity index 77% rename from manila/tests/scheduler/test_host_filters.py rename to manila/tests/scheduler/filters/test_capacity.py index 4d47819b28..a88df8c67c 100644 --- a/manila/tests/scheduler/test_host_filters.py +++ b/manila/tests/scheduler/filters/test_capacity.py @@ -12,14 +12,13 @@ # License for the specific language governing permissions and limitations # under the License. """ -Tests For Scheduler Host Filters. +Tests For CapacityFilter. """ import ddt -from oslo_serialization import jsonutils from manila import context -from manila.openstack.common.scheduler import filters +from manila.scheduler.filters import capacity from manila import test from manila.tests.scheduler import fakes from manila import utils @@ -27,21 +26,12 @@ from manila import utils @ddt.ddt class HostFiltersTestCase(test.TestCase): - """Test case for host filters.""" + """Test case CapacityFilter.""" def setUp(self): super(HostFiltersTestCase, self).setUp() self.context = context.RequestContext('fake', 'fake') - self.json_query = jsonutils.dumps( - ['and', ['>=', '$free_capacity_gb', 1024], - ['>=', '$total_capacity_gb', 10 * 1024]]) - # This has a side effect of testing 'get_filter_classes' - # when specifying a method (in this case, our standard filters) - filter_handler = filters.HostFilterHandler('manila.scheduler.filters') - classes = filter_handler.get_all_classes() - self.class_map = {} - for cls in classes: - self.class_map[cls.__name__] = cls + self.filter = capacity.CapacityFilter() def _stub_service_is_up(self, ret_value): def fake_service_is_up(service): @@ -54,7 +44,6 @@ class HostFiltersTestCase(test.TestCase): @ddt.unpack def test_capacity_filter_passes(self, size, share_on, host): self._stub_service_is_up(True) - filt_cls = self.class_map['CapacityFilter']() filter_properties = {'size': size, 'share_exists_on': share_on} service = {'disabled': False} @@ -63,7 +52,7 @@ class HostFiltersTestCase(test.TestCase): 'free_capacity_gb': 200, 'updated_at': None, 'service': service}) - self.assertTrue(filt_cls.host_passes(host, filter_properties)) + self.assertTrue(self.filter.host_passes(host, filter_properties)) @ddt.data( {'free_capacity': 120, 'total_capacity': 200, @@ -74,7 +63,6 @@ class HostFiltersTestCase(test.TestCase): def test_capacity_filter_fails(self, free_capacity, total_capacity, reserved): self._stub_service_is_up(True) - filt_cls = self.class_map['CapacityFilter']() filter_properties = {'size': 100} service = {'disabled': False} host = fakes.FakeHostState('host1', @@ -83,19 +71,18 @@ class HostFiltersTestCase(test.TestCase): 'reserved_percentage': reserved, 'updated_at': None, 'service': service}) - self.assertFalse(filt_cls.host_passes(host, filter_properties)) + self.assertFalse(self.filter.host_passes(host, filter_properties)) def test_capacity_filter_passes_unknown(self): free = 'unknown' self._stub_service_is_up(True) - filt_cls = self.class_map['CapacityFilter']() filter_properties = {'size': 100} service = {'disabled': False} host = fakes.FakeHostState('host1', {'free_capacity_gb': free, 'updated_at': None, 'service': service}) - self.assertTrue(filt_cls.host_passes(host, filter_properties)) + self.assertTrue(self.filter.host_passes(host, filter_properties)) @ddt.data( {'free_capacity': 'unknown', 'total_capacity': 'unknown'}, @@ -104,7 +91,6 @@ class HostFiltersTestCase(test.TestCase): def test_capacity_filter_passes_total(self, free_capacity, total_capacity): self._stub_service_is_up(True) - filt_cls = self.class_map['CapacityFilter']() filter_properties = {'size': 100} service = {'disabled': False} host = fakes.FakeHostState('host1', @@ -113,7 +99,7 @@ class HostFiltersTestCase(test.TestCase): 'reserved_percentage': 0, 'updated_at': None, 'service': service}) - self.assertTrue(filt_cls.host_passes(host, filter_properties)) + self.assertTrue(self.filter.host_passes(host, filter_properties)) @ddt.data( {'free': 200, 'total': 'unknown', 'reserved': 5}, @@ -122,7 +108,6 @@ class HostFiltersTestCase(test.TestCase): @ddt.unpack def test_capacity_filter_fails_total(self, free, total, reserved): self._stub_service_is_up(True) - filt_cls = self.class_map['CapacityFilter']() filter_properties = {'size': 100} service = {'disabled': False} host = fakes.FakeHostState('host1', @@ -131,7 +116,7 @@ class HostFiltersTestCase(test.TestCase): 'reserved_percentage': reserved, 'updated_at': None, 'service': service}) - self.assertFalse(filt_cls.host_passes(host, filter_properties)) + self.assertFalse(self.filter.host_passes(host, filter_properties)) @ddt.data( {'size': 100, 'cap_thin': ' True', @@ -159,7 +144,6 @@ class HostFiltersTestCase(test.TestCase): def test_filter_thin_passes(self, size, cap_thin, total, free, provisioned, max_ratio, reserved, thin_prov): self._stub_service_is_up(True) - filt_cls = self.class_map['CapacityFilter']() filter_properties = {'size': size, 'capabilities:thin_provisioning': cap_thin} service = {'disabled': False} @@ -172,7 +156,7 @@ class HostFiltersTestCase(test.TestCase): 'thin_provisioning': thin_prov, 'updated_at': None, 'service': service}) - self.assertTrue(filt_cls.host_passes(host, filter_properties)) + self.assertTrue(self.filter.host_passes(host, filter_properties)) @ddt.data( {'size': 200, 'cap_thin': ' True', @@ -203,7 +187,6 @@ class HostFiltersTestCase(test.TestCase): def test_filter_thin_fails(self, size, cap_thin, total, free, provisioned, max_ratio, reserved, thin_prov): self._stub_service_is_up(True) - filt_cls = self.class_map['CapacityFilter']() filter_properties = {'size': size, 'capabilities:thin_provisioning': cap_thin} service = {'disabled': False} @@ -216,27 +199,4 @@ class HostFiltersTestCase(test.TestCase): 'thin_provisioning': thin_prov, 'updated_at': None, 'service': service}) - self.assertFalse(filt_cls.host_passes(host, filter_properties)) - - def test_retry_filter_disabled(self): - # Test case where retry/re-scheduling is disabled. - filt_cls = self.class_map['RetryFilter']() - host = fakes.FakeHostState('host1', {}) - filter_properties = {} - self.assertTrue(filt_cls.host_passes(host, filter_properties)) - - def test_retry_filter_pass(self): - # Node not previously tried. - filt_cls = self.class_map['RetryFilter']() - host = fakes.FakeHostState('host1', {}) - retry = dict(num_attempts=2, hosts=['host2']) - filter_properties = dict(retry=retry) - self.assertTrue(filt_cls.host_passes(host, filter_properties)) - - def test_retry_filter_fail(self): - # Node was already tried. - filt_cls = self.class_map['RetryFilter']() - host = fakes.FakeHostState('host1', {}) - retry = dict(num_attempts=1, hosts=['host1']) - filter_properties = dict(retry=retry) - self.assertFalse(filt_cls.host_passes(host, filter_properties)) + self.assertFalse(self.filter.host_passes(host, filter_properties)) diff --git a/manila/tests/scheduler/filters/test_extra_specs_ops.py b/manila/tests/scheduler/filters/test_extra_specs_ops.py new file mode 100644 index 0000000000..83df077979 --- /dev/null +++ b/manila/tests/scheduler/filters/test_extra_specs_ops.py @@ -0,0 +1,237 @@ +# Copyright 2011 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. + +""" +Tests For Scheduler Host Filters. +""" + +from manila.scheduler.filters import extra_specs_ops +from manila import test + + +class ExtraSpecsOpsTestCase(test.TestCase): + def _do_extra_specs_ops_test(self, value, req, matches): + assertion = self.assertTrue if matches else self.assertFalse + assertion(extra_specs_ops.match(value, req)) + + def test_extra_specs_matches_simple(self): + self._do_extra_specs_ops_test( + value='1', + req='1', + matches=True) + + def test_extra_specs_fails_simple(self): + self._do_extra_specs_ops_test( + value='', + req='1', + matches=False) + + def test_extra_specs_fails_simple2(self): + self._do_extra_specs_ops_test( + value='3', + req='1', + matches=False) + + def test_extra_specs_fails_simple3(self): + self._do_extra_specs_ops_test( + value='222', + req='2', + matches=False) + + def test_extra_specs_fails_with_bogus_ops(self): + self._do_extra_specs_ops_test( + value='4', + req='> 2', + matches=False) + + def test_extra_specs_matches_with_op_eq(self): + self._do_extra_specs_ops_test( + value='123', + req='= 123', + matches=True) + + def test_extra_specs_matches_with_op_eq2(self): + self._do_extra_specs_ops_test( + value='124', + req='= 123', + matches=True) + + def test_extra_specs_fails_with_op_eq(self): + self._do_extra_specs_ops_test( + value='34', + req='= 234', + matches=False) + + def test_extra_specs_fails_with_op_eq3(self): + self._do_extra_specs_ops_test( + value='34', + req='=', + matches=False) + + def test_extra_specs_matches_with_op_seq(self): + self._do_extra_specs_ops_test( + value='123', + req='s== 123', + matches=True) + + def test_extra_specs_fails_with_op_seq(self): + self._do_extra_specs_ops_test( + value='1234', + req='s== 123', + matches=False) + + def test_extra_specs_matches_with_op_sneq(self): + self._do_extra_specs_ops_test( + value='1234', + req='s!= 123', + matches=True) + + def test_extra_specs_fails_with_op_sneq(self): + self._do_extra_specs_ops_test( + value='123', + req='s!= 123', + matches=False) + + def test_extra_specs_fails_with_op_sge(self): + self._do_extra_specs_ops_test( + value='1000', + req='s>= 234', + matches=False) + + def test_extra_specs_fails_with_op_sle(self): + self._do_extra_specs_ops_test( + value='1234', + req='s<= 1000', + matches=False) + + def test_extra_specs_fails_with_op_sl(self): + self._do_extra_specs_ops_test( + value='2', + req='s< 12', + matches=False) + + def test_extra_specs_fails_with_op_sg(self): + self._do_extra_specs_ops_test( + value='12', + req='s> 2', + matches=False) + + def test_extra_specs_matches_with_op_in(self): + self._do_extra_specs_ops_test( + value='12311321', + req=' 11', + matches=True) + + def test_extra_specs_matches_with_op_in2(self): + self._do_extra_specs_ops_test( + value='12311321', + req=' 12311321', + matches=True) + + def test_extra_specs_matches_with_op_in3(self): + self._do_extra_specs_ops_test( + value='12311321', + req=' 12311321 ', + matches=True) + + def test_extra_specs_fails_with_op_in(self): + self._do_extra_specs_ops_test( + value='12310321', + req=' 11', + matches=False) + + def test_extra_specs_fails_with_op_in2(self): + self._do_extra_specs_ops_test( + value='12310321', + req=' 11 ', + matches=False) + + def test_extra_specs_matches_with_op_is(self): + self._do_extra_specs_ops_test( + value=True, + req=' True', + matches=True) + + def test_extra_specs_matches_with_op_is2(self): + self._do_extra_specs_ops_test( + value=False, + req=' False', + matches=True) + + def test_extra_specs_matches_with_op_is3(self): + self._do_extra_specs_ops_test( + value=False, + req=' Nonsense', + matches=True) + + def test_extra_specs_fails_with_op_is(self): + self._do_extra_specs_ops_test( + value=True, + req=' False', + matches=False) + + def test_extra_specs_fails_with_op_is2(self): + self._do_extra_specs_ops_test( + value=False, + req=' True', + matches=False) + + def test_extra_specs_matches_with_op_or(self): + self._do_extra_specs_ops_test( + value='12', + req=' 11 12', + matches=True) + + def test_extra_specs_matches_with_op_or2(self): + self._do_extra_specs_ops_test( + value='12', + req=' 11 12 ', + matches=True) + + def test_extra_specs_fails_with_op_or(self): + self._do_extra_specs_ops_test( + value='13', + req=' 11 12', + matches=False) + + def test_extra_specs_fails_with_op_or2(self): + self._do_extra_specs_ops_test( + value='13', + req=' 11 12 ', + matches=False) + + def test_extra_specs_matches_with_op_le(self): + self._do_extra_specs_ops_test( + value='2', + req='<= 10', + matches=True) + + def test_extra_specs_fails_with_op_le(self): + self._do_extra_specs_ops_test( + value='3', + req='<= 2', + matches=False) + + def test_extra_specs_matches_with_op_ge(self): + self._do_extra_specs_ops_test( + value='3', + req='>= 1', + matches=True) + + def test_extra_specs_fails_with_op_ge(self): + self._do_extra_specs_ops_test( + value='2', + req='>= 3', + matches=False) diff --git a/manila/tests/scheduler/filters/test_ignore_attempted_hosts.py b/manila/tests/scheduler/filters/test_ignore_attempted_hosts.py new file mode 100644 index 0000000000..85dc0b9517 --- /dev/null +++ b/manila/tests/scheduler/filters/test_ignore_attempted_hosts.py @@ -0,0 +1,53 @@ +# Copyright 2011 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. + +""" +Tests For IgnoreAttemptedHost filter. +""" + +from oslo_context import context + +from manila.scheduler.filters import ignore_attempted_hosts +from manila import test +from manila.tests.scheduler import fakes + + +class HostFiltersTestCase(test.TestCase): + """Test case for IgnoreAttemptedHost filter.""" + + def setUp(self): + super(HostFiltersTestCase, self).setUp() + self.context = context.RequestContext('fake', 'fake') + self.filter = ignore_attempted_hosts.IgnoreAttemptedHostsFilter() + + def test_ignore_attempted_hosts_filter_disabled(self): + # Test case where re-scheduling is disabled. + host = fakes.FakeHostState('host1', {}) + filter_properties = {} + self.assertTrue(self.filter.host_passes(host, filter_properties)) + + def test_ignore_attempted_hosts_filter_pass(self): + # Node not previously tried. + host = fakes.FakeHostState('host1', {}) + attempted = dict(num_attempts=2, hosts=['host2']) + filter_properties = dict(retry=attempted) + self.assertTrue(self.filter.host_passes(host, filter_properties)) + + def test_ignore_attempted_hosts_filter_fail(self): + # Node was already tried. + host = fakes.FakeHostState('host1', {}) + attempted = dict(num_attempts=2, hosts=['host1']) + filter_properties = dict(retry=attempted) + self.assertFalse(self.filter.host_passes(host, filter_properties)) diff --git a/manila/tests/scheduler/filters/test_json.py b/manila/tests/scheduler/filters/test_json.py new file mode 100644 index 0000000000..d9169a38e7 --- /dev/null +++ b/manila/tests/scheduler/filters/test_json.py @@ -0,0 +1,326 @@ +# Copyright 2011 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. + +""" +Tests For JsonFilter. +""" + +from oslo_context import context +from oslo_serialization import jsonutils + +from manila.scheduler.filters import json +from manila import test +from manila.tests.scheduler import fakes + + +class HostFiltersTestCase(test.TestCase): + """Test case for JsonFilter.""" + + def setUp(self): + super(HostFiltersTestCase, self).setUp() + self.context = context.RequestContext('fake', 'fake') + self.json_query = jsonutils.dumps( + ['and', ['>=', '$free_ram_mb', 1024], + ['>=', '$free_disk_mb', 200 * 1024]]) + self.filter = json.JsonFilter() + + def test_json_filter_passes(self): + filter_properties = {'resource_type': {'memory_mb': 1024, + 'root_gb': 200, + 'ephemeral_gb': 0}, + 'scheduler_hints': {'query': self.json_query}} + capabilities = {'enabled': True} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 1024, + 'free_disk_mb': 200 * 1024, + 'capabilities': capabilities}) + self.assertTrue(self.filter.host_passes(host, filter_properties)) + + def test_json_filter_passes_with_no_query(self): + filter_properties = {'resource_type': {'memory_mb': 1024, + 'root_gb': 200, + 'ephemeral_gb': 0}} + capabilities = {'enabled': True} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 0, + 'free_disk_mb': 0, + 'capabilities': capabilities}) + self.assertTrue(self.filter.host_passes(host, filter_properties)) + + def test_json_filter_fails_on_memory(self): + filter_properties = {'resource_type': {'memory_mb': 1024, + 'root_gb': 200, + 'ephemeral_gb': 0}, + 'scheduler_hints': {'query': self.json_query}} + capabilities = {'enabled': True} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 1023, + 'free_disk_mb': 200 * 1024, + 'capabilities': capabilities}) + self.assertFalse(self.filter.host_passes(host, filter_properties)) + + def test_json_filter_fails_on_disk(self): + filter_properties = {'resource_type': {'memory_mb': 1024, + 'root_gb': 200, + 'ephemeral_gb': 0}, + 'scheduler_hints': {'query': self.json_query}} + capabilities = {'enabled': True} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 1024, + 'free_disk_mb': (200 * 1024) - 1, + 'capabilities': capabilities}) + self.assertFalse(self.filter.host_passes(host, filter_properties)) + + def test_json_filter_fails_on_caps_disabled(self): + json_query = jsonutils.dumps( + ['and', ['>=', '$free_ram_mb', 1024], + ['>=', '$free_disk_mb', 200 * 1024], + '$capabilities.enabled']) + filter_properties = {'resource_type': {'memory_mb': 1024, + 'root_gb': 200, + 'ephemeral_gb': 0}, + 'scheduler_hints': {'query': json_query}} + capabilities = {'enabled': False} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 1024, + 'free_disk_mb': 200 * 1024, + 'capabilities': capabilities}) + self.assertFalse(self.filter.host_passes(host, filter_properties)) + + def test_json_filter_fails_on_service_disabled(self): + json_query = jsonutils.dumps( + ['and', ['>=', '$free_ram_mb', 1024], + ['>=', '$free_disk_mb', 200 * 1024], + ['not', '$service.disabled']]) + filter_properties = {'resource_type': {'memory_mb': 1024, + 'local_gb': 200}, + 'scheduler_hints': {'query': json_query}} + capabilities = {'enabled': True} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 1024, + 'free_disk_mb': 200 * 1024, + 'capabilities': capabilities}) + self.assertFalse(self.filter.host_passes(host, filter_properties)) + + def test_json_filter_happy_day(self): + """Test json filter more thoroughly.""" + raw = ['and', + '$capabilities.enabled', + ['=', '$capabilities.opt1', 'match'], + ['or', + ['and', + ['<', '$free_ram_mb', 30], + ['<', '$free_disk_mb', 300]], + ['and', + ['>', '$free_ram_mb', 30], + ['>', '$free_disk_mb', 300]]]] + filter_properties = { + 'scheduler_hints': { + 'query': jsonutils.dumps(raw), + }, + } + + # Passes + capabilities = {'enabled': True, 'opt1': 'match'} + service = {'disabled': False} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 10, + 'free_disk_mb': 200, + 'capabilities': capabilities, + 'service': service}) + self.assertTrue(self.filter.host_passes(host, filter_properties)) + + # Passes + capabilities = {'enabled': True, 'opt1': 'match'} + service = {'disabled': False} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 40, + 'free_disk_mb': 400, + 'capabilities': capabilities, + 'service': service}) + self.assertTrue(self.filter.host_passes(host, filter_properties)) + + # Fails due to capabilities being disabled + capabilities = {'enabled': False, 'opt1': 'match'} + service = {'disabled': False} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 40, + 'free_disk_mb': 400, + 'capabilities': capabilities, + 'service': service}) + self.assertFalse(self.filter.host_passes(host, filter_properties)) + + # Fails due to being exact memory/disk we don't want + capabilities = {'enabled': True, 'opt1': 'match'} + service = {'disabled': False} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 30, + 'free_disk_mb': 300, + 'capabilities': capabilities, + 'service': service}) + self.assertFalse(self.filter.host_passes(host, filter_properties)) + + # Fails due to memory lower but disk higher + capabilities = {'enabled': True, 'opt1': 'match'} + service = {'disabled': False} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 20, + 'free_disk_mb': 400, + 'capabilities': capabilities, + 'service': service}) + self.assertFalse(self.filter.host_passes(host, filter_properties)) + + # Fails due to capabilities 'opt1' not equal + capabilities = {'enabled': True, 'opt1': 'no-match'} + service = {'enabled': True} + host = fakes.FakeHostState('host1', + {'free_ram_mb': 20, + 'free_disk_mb': 400, + 'capabilities': capabilities, + 'service': service}) + self.assertFalse(self.filter.host_passes(host, filter_properties)) + + def test_json_filter_basic_operators(self): + host = fakes.FakeHostState('host1', + {'capabilities': {'enabled': True}}) + # (operator, arguments, expected_result) + ops_to_test = [ + ['=', [1, 1], True], + ['=', [1, 2], False], + ['<', [1, 2], True], + ['<', [1, 1], False], + ['<', [2, 1], False], + ['>', [2, 1], True], + ['>', [2, 2], False], + ['>', [2, 3], False], + ['<=', [1, 2], True], + ['<=', [1, 1], True], + ['<=', [2, 1], False], + ['>=', [2, 1], True], + ['>=', [2, 2], True], + ['>=', [2, 3], False], + ['in', [1, 1], True], + ['in', [1, 1, 2, 3], True], + ['in', [4, 1, 2, 3], False], + ['not', [True], False], + ['not', [False], True], + ['or', [True, False], True], + ['or', [False, False], False], + ['and', [True, True], True], + ['and', [False, False], False], + ['and', [True, False], False], + # Nested ((True or False) and (2 > 1)) == Passes + ['and', [['or', True, False], ['>', 2, 1]], True]] + + for (op, args, expected) in ops_to_test: + raw = [op] + args + filter_properties = { + 'scheduler_hints': { + 'query': jsonutils.dumps(raw), + }, + } + self.assertEqual(expected, + self.filter.host_passes(host, filter_properties)) + + # This results in [False, True, False, True] and if any are True + # then it passes... + raw = ['not', True, False, True, False] + filter_properties = { + 'scheduler_hints': { + 'query': jsonutils.dumps(raw), + }, + } + self.assertTrue(self.filter.host_passes(host, filter_properties)) + + # This results in [False, False, False] and if any are True + # then it passes...which this doesn't + raw = ['not', True, True, True] + filter_properties = { + 'scheduler_hints': { + 'query': jsonutils.dumps(raw), + }, + } + self.assertFalse(self.filter.host_passes(host, filter_properties)) + + def test_json_filter_unknown_operator_raises(self): + raw = ['!=', 1, 2] + filter_properties = { + 'scheduler_hints': { + 'query': jsonutils.dumps(raw), + }, + } + host = fakes.FakeHostState('host1', + {'capabilities': {'enabled': True}}) + self.assertRaises(KeyError, + self.filter.host_passes, host, filter_properties) + + def test_json_filter_empty_filters_pass(self): + host = fakes.FakeHostState('host1', + {'capabilities': {'enabled': True}}) + + raw = [] + filter_properties = { + 'scheduler_hints': { + 'query': jsonutils.dumps(raw), + }, + } + self.assertTrue(self.filter.host_passes(host, filter_properties)) + raw = {} + filter_properties = { + 'scheduler_hints': { + 'query': jsonutils.dumps(raw), + }, + } + self.assertTrue(self.filter.host_passes(host, filter_properties)) + + def test_json_filter_invalid_num_arguments_fails(self): + host = fakes.FakeHostState('host1', + {'capabilities': {'enabled': True}}) + + raw = ['>', ['and', ['or', ['not', ['<', ['>=', ['<=', ['in', ]]]]]]]] + filter_properties = { + 'scheduler_hints': { + 'query': jsonutils.dumps(raw), + }, + } + self.assertFalse(self.filter.host_passes(host, filter_properties)) + + raw = ['>', 1] + filter_properties = { + 'scheduler_hints': { + 'query': jsonutils.dumps(raw), + }, + } + self.assertFalse(self.filter.host_passes(host, filter_properties)) + + def test_json_filter_unknown_variable_ignored(self): + host = fakes.FakeHostState('host1', + {'capabilities': {'enabled': True}}) + + raw = ['=', '$........', 1, 1] + filter_properties = { + 'scheduler_hints': { + 'query': jsonutils.dumps(raw), + }, + } + self.assertTrue(self.filter.host_passes(host, filter_properties)) + + raw = ['=', '$foo', 2, 2] + filter_properties = { + 'scheduler_hints': { + 'query': jsonutils.dumps(raw), + }, + } + self.assertTrue(self.filter.host_passes(host, filter_properties)) diff --git a/manila/tests/scheduler/filters/test_retry.py b/manila/tests/scheduler/filters/test_retry.py new file mode 100644 index 0000000000..26a5c9e899 --- /dev/null +++ b/manila/tests/scheduler/filters/test_retry.py @@ -0,0 +1,50 @@ +# Copyright 2011 OpenStack LLC. # 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 RetryFilter. +""" + +from manila import context +from manila.scheduler.filters import retry +from manila import test +from manila.tests.scheduler import fakes + + +class HostFiltersTestCase(test.TestCase): + """Test case for RetryFilter.""" + + def setUp(self): + super(HostFiltersTestCase, self).setUp() + self.context = context.RequestContext('fake', 'fake') + self.filter = retry.RetryFilter() + + def test_retry_filter_disabled(self): + # Test case where retry/re-scheduling is disabled. + host = fakes.FakeHostState('host1', {}) + filter_properties = {} + self.assertTrue(self.filter.host_passes(host, filter_properties)) + + def test_retry_filter_pass(self): + # Node not previously tried. + host = fakes.FakeHostState('host1', {}) + retry = dict(num_attempts=2, hosts=['host2']) + filter_properties = dict(retry=retry) + self.assertTrue(self.filter.host_passes(host, filter_properties)) + + def test_retry_filter_fail(self): + # Node was already tried. + host = fakes.FakeHostState('host1', {}) + retry = dict(num_attempts=1, hosts=['host1']) + filter_properties = dict(retry=retry) + self.assertFalse(self.filter.host_passes(host, filter_properties)) diff --git a/manila/tests/scheduler/test_host_manager.py b/manila/tests/scheduler/test_host_manager.py index 2242df7913..ca8529e7dd 100644 --- a/manila/tests/scheduler/test_host_manager.py +++ b/manila/tests/scheduler/test_host_manager.py @@ -26,7 +26,7 @@ from six import moves from manila import db from manila import exception -from manila.openstack.common.scheduler import filters +from manila.scheduler.filters import base_host from manila.scheduler import host_manager from manila import test from manila.tests.scheduler import fakes @@ -36,12 +36,12 @@ from manila import utils CONF = cfg.CONF -class FakeFilterClass1(filters.BaseHostFilter): +class FakeFilterClass1(base_host.BaseHostFilter): def host_passes(self, host_state, filter_properties): pass -class FakeFilterClass2(filters.BaseHostFilter): +class FakeFilterClass2(base_host.BaseHostFilter): def host_passes(self, host_state, filter_properties): pass diff --git a/manila/tests/scheduler/test_manager.py b/manila/tests/scheduler/test_manager.py new file mode 100644 index 0000000000..71c853fb24 --- /dev/null +++ b/manila/tests/scheduler/test_manager.py @@ -0,0 +1,245 @@ +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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 Scheduler Manager +""" + +import ddt +import mock +from oslo_config import cfg + +from manila import context +from manila import db +from manila import exception +from manila.scheduler.drivers import base +from manila.scheduler.drivers import filter +from manila.scheduler import manager +from manila.share import rpcapi as share_rpcapi +from manila import test +from manila.tests import db_utils + +CONF = cfg.CONF + + +@ddt.ddt +class SchedulerManagerTestCase(test.TestCase): + """Test case for scheduler manager.""" + + manager_cls = manager.SchedulerManager + driver_cls = base.Scheduler + driver_cls_name = 'manila.scheduler.drivers.base.Scheduler' + + def setUp(self): + super(SchedulerManagerTestCase, self).setUp() + self.flags(scheduler_driver=self.driver_cls_name) + self.manager = self.manager_cls() + self.context = context.RequestContext('fake_user', 'fake_project') + self.topic = 'fake_topic' + self.fake_args = (1, 2, 3) + self.fake_kwargs = {'cat': 'meow', 'dog': 'woof'} + + def test_1_correct_init(self): + # Correct scheduler driver + manager = self.manager + self.assertTrue(isinstance(manager.driver, + self.driver_cls)) + + @ddt.data('manila.scheduler.filter_scheduler.FilterScheduler', + 'manila.scheduler.drivers.filter.FilterScheduler') + def test_scheduler_driver_mapper(self, driver_class): + + test_manager = manager.SchedulerManager(scheduler_driver=driver_class) + + self.assertTrue(isinstance(test_manager.driver, + filter.FilterScheduler)) + + def test_init_host(self): + + self.mock_object(context, + 'get_admin_context', + mock.Mock(return_value='fake_admin_context')) + self.mock_object(self.manager, 'request_service_capabilities') + + self.manager.init_host() + + self.manager.request_service_capabilities.assert_called_once_with( + 'fake_admin_context') + + def test_get_host_list(self): + + self.mock_object(self.manager.driver, 'get_host_list') + + self.manager.get_host_list(context) + + self.manager.driver.get_host_list.assert_called_once_with() + + def test_get_service_capabilities(self): + + self.mock_object(self.manager.driver, 'get_service_capabilities') + + self.manager.get_service_capabilities(context) + + self.manager.driver.get_service_capabilities.assert_called_once_with() + + def test_update_service_capabilities(self): + service_name = 'fake_service' + host = 'fake_host' + with mock.patch.object(self.manager.driver, + 'update_service_capabilities', mock.Mock()): + self.manager.update_service_capabilities( + self.context, service_name=service_name, host=host) + (self.manager.driver.update_service_capabilities. + assert_called_once_with(service_name, host, {})) + with mock.patch.object(self.manager.driver, + 'update_service_capabilities', mock.Mock()): + capabilities = {'fake_capability': 'fake_value'} + self.manager.update_service_capabilities( + self.context, service_name=service_name, host=host, + capabilities=capabilities) + (self.manager.driver.update_service_capabilities. + assert_called_once_with(service_name, host, capabilities)) + + @mock.patch.object(db, 'share_update', mock.Mock()) + def test_create_share_exception_puts_share_in_error_state(self): + """Test NoValidHost exception for create_share. + + Puts the share in 'error' state and eats the exception. + """ + def raise_no_valid_host(*args, **kwargs): + raise exception.NoValidHost(reason="") + + fake_share_id = 1 + + request_spec = {'share_id': fake_share_id} + with mock.patch.object(self.manager.driver, + 'schedule_create_share', + mock.Mock(side_effect=raise_no_valid_host)): + self.mock_object(manager.LOG, 'error') + + self.manager.create_share_instance( + self.context, request_spec=request_spec, filter_properties={}) + + db.share_update.assert_called_once_with( + self.context, fake_share_id, {'status': 'error'}) + (self.manager.driver.schedule_create_share. + assert_called_once_with(self.context, request_spec, {})) + manager.LOG.error.assert_called_once_with(mock.ANY, mock.ANY) + + @mock.patch.object(db, 'share_update', mock.Mock()) + def test_create_share_other_exception_puts_share_in_error_state(self): + """Test any exception except NoValidHost for create_share. + + Puts the share in 'error' state and re-raises the exception. + """ + fake_share_id = 1 + + request_spec = {'share_id': fake_share_id} + with mock.patch.object(self.manager.driver, + 'schedule_create_share', + mock.Mock(side_effect=exception.QuotaError)): + self.mock_object(manager.LOG, 'error') + + self.assertRaises(exception.QuotaError, + self.manager.create_share_instance, + self.context, + request_spec=request_spec, + filter_properties={}) + + db.share_update.assert_called_once_with( + self.context, fake_share_id, {'status': 'error'}) + (self.manager.driver.schedule_create_share. + assert_called_once_with(self.context, request_spec, {})) + manager.LOG.error.assert_called_once_with(mock.ANY, mock.ANY) + + def test_get_pools(self): + """Ensure get_pools exists and calls base_scheduler.get_pools.""" + mock_get_pools = self.mock_object(self.manager.driver, + 'get_pools', + mock.Mock(return_value='fake_pools')) + + result = self.manager.get_pools(self.context, filters='fake_filters') + + mock_get_pools.assert_called_once_with(self.context, 'fake_filters') + self.assertEqual('fake_pools', result) + + @mock.patch.object(db, 'consistency_group_update', mock.Mock()) + def test_create_cg_no_valid_host_puts_cg_in_error_state(self): + """Test that NoValidHost is raised for create_consistency_group. + + Puts the share in 'error' state and eats the exception. + """ + def raise_no_valid_host(*args, **kwargs): + raise exception.NoValidHost(reason="") + + fake_cg_id = 1 + cg_id = fake_cg_id + request_spec = {"consistency_group_id": cg_id} + with mock.patch.object(self.manager.driver, + 'schedule_create_consistency_group', + mock.Mock(side_effect=raise_no_valid_host)): + self.manager.create_consistency_group(self.context, + fake_cg_id, + request_spec=request_spec, + filter_properties={}) + db.consistency_group_update.assert_called_once_with( + self.context, fake_cg_id, {'status': 'error'}) + (self.manager.driver.schedule_create_consistency_group. + assert_called_once_with(self.context, cg_id, request_spec, {})) + + @mock.patch.object(db, 'consistency_group_update', mock.Mock()) + def test_create_cg_exception_puts_cg_in_error_state(self): + """Test that exceptions for create_consistency_group. + + Puts the share in 'error' state and raises the exception. + """ + + fake_cg_id = 1 + cg_id = fake_cg_id + request_spec = {"consistency_group_id": cg_id} + with mock.patch.object(self.manager.driver, + 'schedule_create_consistency_group', + mock.Mock(side_effect=exception.NotFound)): + self.assertRaises(exception.NotFound, + self.manager.create_consistency_group, + self.context, fake_cg_id, + request_spec=request_spec, + filter_properties={}) + + def test_migrate_share_to_host(self): + + share = db_utils.create_share() + host = 'fake@backend#pool' + + self.mock_object(db, 'share_get', mock.Mock(return_value=share)) + self.mock_object(share_rpcapi.ShareAPI, 'migrate_share') + self.mock_object(base.Scheduler, + 'host_passes_filters', + mock.Mock(return_value=host)) + + self.manager.migrate_share_to_host(self.context, share['id'], host, + False, {}, None) + + def test_migrate_share_to_host_no_valid_host(self): + + share = db_utils.create_share() + host = 'fake@backend#pool' + + self.mock_object( + base.Scheduler, 'host_passes_filters', + mock.Mock(side_effect=[exception.NoValidHost('fake')])) + + self.manager.migrate_share_to_host(self.context, share['id'], host, + False, {}, None) diff --git a/manila/tests/scheduler/test_scheduler.py b/manila/tests/scheduler/test_scheduler.py deleted file mode 100644 index 8fc71c7e52..0000000000 --- a/manila/tests/scheduler/test_scheduler.py +++ /dev/null @@ -1,389 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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 Scheduler -""" - -import mock -from oslo_config import cfg -from oslo_utils import timeutils - -from manila import context -from manila import db -from manila import exception -from manila.scheduler import driver -from manila.scheduler import manager -from manila.scheduler import simple -from manila.share import rpcapi as share_rpcapi -from manila import test -from manila.tests import db_utils -from manila import utils - -CONF = cfg.CONF - - -class SchedulerManagerTestCase(test.TestCase): - """Test case for scheduler manager.""" - - manager_cls = manager.SchedulerManager - driver_cls = driver.Scheduler - driver_cls_name = 'manila.scheduler.driver.Scheduler' - - def setUp(self): - super(SchedulerManagerTestCase, self).setUp() - self.flags(scheduler_driver=self.driver_cls_name) - self.manager = self.manager_cls() - self.context = context.RequestContext('fake_user', 'fake_project') - self.topic = 'fake_topic' - self.fake_args = (1, 2, 3) - self.fake_kwargs = {'cat': 'meow', 'dog': 'woof'} - - def test_1_correct_init(self): - # Correct scheduler driver - manager = self.manager - self.assertTrue(isinstance(manager.driver, self.driver_cls)) - - def test_update_service_capabilities(self): - service_name = 'fake_service' - host = 'fake_host' - with mock.patch.object(self.manager.driver, - 'update_service_capabilities', mock.Mock()): - self.manager.update_service_capabilities( - self.context, service_name=service_name, host=host) - self.manager.driver.update_service_capabilities.\ - assert_called_once_with(service_name, host, {}) - with mock.patch.object(self.manager.driver, - 'update_service_capabilities', mock.Mock()): - capabilities = {'fake_capability': 'fake_value'} - self.manager.update_service_capabilities( - self.context, service_name=service_name, host=host, - capabilities=capabilities) - self.manager.driver.update_service_capabilities.\ - assert_called_once_with(service_name, host, capabilities) - - @mock.patch.object(db, 'share_update', mock.Mock()) - def test_create_share_exception_puts_share_in_error_state(self): - """Test that a NoValideHost exception for create_share. - - Puts the share in 'error' state and eats the exception. - """ - def raise_no_valid_host(*args, **kwargs): - raise exception.NoValidHost(reason="") - - fake_share_id = 1 - - request_spec = {'share_id': fake_share_id} - with mock.patch.object(self.manager.driver, - 'schedule_create_share', - mock.Mock(side_effect=raise_no_valid_host)): - self.mock_object(manager.LOG, 'error') - self.manager.create_share_instance( - self.context, request_spec=request_spec, filter_properties={}) - db.share_update.assert_called_once_with( - self.context, fake_share_id, {'status': 'error'}) - self.manager.driver.schedule_create_share.assert_called_once_with( - self.context, request_spec, {}) - manager.LOG.error.assert_called_once_with(mock.ANY, mock.ANY) - - def test_get_pools(self): - """Ensure get_pools exists and calls driver.get_pools.""" - mock_get_pools = self.mock_object(self.manager.driver, 'get_pools', - mock.Mock(return_value='fake_pools')) - - result = self.manager.get_pools(self.context, filters='fake_filters') - - mock_get_pools.assert_called_once_with(self.context, 'fake_filters') - self.assertEqual('fake_pools', result) - - @mock.patch.object(db, 'consistency_group_update', mock.Mock()) - def test_create_cg_no_valid_host_puts_cg_in_error_state(self): - """Test that NoValidHost is raised for create_consistency_group. - - Puts the share in 'error' state and eats the exception. - """ - def raise_no_valid_host(*args, **kwargs): - raise exception.NoValidHost(reason="") - - fake_cg_id = 1 - cg_id = fake_cg_id - request_spec = {"consistency_group_id": cg_id} - with mock.patch.object(self.manager.driver, - 'schedule_create_consistency_group', - mock.Mock(side_effect=raise_no_valid_host)): - self.manager.create_consistency_group(self.context, - fake_cg_id, - request_spec=request_spec, - filter_properties={}) - db.consistency_group_update.assert_called_once_with( - self.context, fake_cg_id, {'status': 'error'}) - self.manager.driver.schedule_create_consistency_group\ - .assert_called_once_with(self.context, cg_id, - request_spec, {}) - - @mock.patch.object(db, 'consistency_group_update', mock.Mock()) - def test_create_cg_exception_puts_cg_in_error_state(self): - """Test that exceptions for create_consistency_group. - - Puts the share in 'error' state and raises the exception. - """ - - fake_cg_id = 1 - cg_id = fake_cg_id - request_spec = {"consistency_group_id": cg_id} - with mock.patch.object(self.manager.driver, - 'schedule_create_consistency_group', - mock.Mock(side_effect=exception.NotFound)): - self.assertRaises(exception.NotFound, - self.manager.create_consistency_group, - self.context, fake_cg_id, - request_spec=request_spec, - filter_properties={}) - - def test_migrate_share_to_host(self): - - share = db_utils.create_share() - host = 'fake@backend#pool' - - self.mock_object(db, 'share_get', mock.Mock(return_value=share)) - self.mock_object(share_rpcapi.ShareAPI, 'migrate_share') - self.mock_object(driver.Scheduler, 'host_passes_filters', - mock.Mock(return_value=host)) - - self.manager.migrate_share_to_host(self.context, share['id'], host, - False, {}, None) - - def test_migrate_share_to_host_no_valid_host(self): - - share = db_utils.create_share() - host = 'fake@backend#pool' - - self.mock_object( - driver.Scheduler, 'host_passes_filters', - mock.Mock(side_effect=[exception.NoValidHost('fake')])) - - self.manager.migrate_share_to_host(self.context, share['id'], host, - False, {}, None) - - -class SchedulerTestCase(test.TestCase): - """Test case for base scheduler driver class.""" - - # So we can subclass this test and re-use tests if we need. - driver_cls = driver.Scheduler - - def setUp(self): - super(SchedulerTestCase, self).setUp() - self.driver = self.driver_cls() - self.context = context.RequestContext('fake_user', 'fake_project') - self.topic = 'fake_topic' - - def test_update_service_capabilities(self): - service_name = 'fake_service' - host = 'fake_host' - capabilities = {'fake_capability': 'fake_value'} - with mock.patch.object(self.driver.host_manager, - 'update_service_capabilities', mock.Mock()): - self.driver.update_service_capabilities( - service_name, host, capabilities) - self.driver.host_manager.update_service_capabilities.\ - assert_called_once_with(service_name, host, capabilities) - - def test_hosts_up(self): - service1 = {'host': 'host1'} - service2 = {'host': 'host2'} - services = [service1, service2] - - def fake_service_is_up(*args, **kwargs): - if args[0]['host'] == 'host1': - return False - return True - - with mock.patch.object(db, 'service_get_all_by_topic', - mock.Mock(return_value=services)): - with mock.patch.object(utils, 'service_is_up', - mock.Mock(side_effect=fake_service_is_up)): - result = self.driver.hosts_up(self.context, self.topic) - self.assertEqual(['host2'], result) - db.service_get_all_by_topic.assert_called_once_with( - self.context, self.topic) - - -class SchedulerDriverBaseTestCase(SchedulerTestCase): - """Test cases for base scheduler driver class methods. - - These can't fail if the driver is changed. - """ - - def test_unimplemented_schedule(self): - fake_args = (1, 2, 3) - fake_kwargs = {'cat': 'meow'} - - self.assertRaises(NotImplementedError, self.driver.schedule, - self.context, self.topic, 'schedule_something', - *fake_args, **fake_kwargs) - - -class SchedulerDriverModuleTestCase(test.TestCase): - """Test case for scheduler driver module methods.""" - - def setUp(self): - super(SchedulerDriverModuleTestCase, self).setUp() - self.context = context.RequestContext('fake_user', 'fake_project') - - @mock.patch.object(db, 'share_update', mock.Mock()) - def test_share_host_update_db(self): - with mock.patch.object(timeutils, 'utcnow', - mock.Mock(return_value='fake-now')): - driver.share_update_db(self.context, 31337, 'fake_host') - db.share_update.assert_called_once_with( - self.context, 31337, - {'host': 'fake_host', 'scheduled_at': 'fake-now'}) - - -class SimpleSchedulerSharesTestCase(test.TestCase): - """Test case for simple scheduler create share method.""" - - def setUp(self): - super(SimpleSchedulerSharesTestCase, self).setUp() - self.mock_object(share_rpcapi, 'ShareAPI') - self.driver = simple.SimpleScheduler() - - self.context = context.RequestContext('fake_user', 'fake_project') - self.admin_context = context.RequestContext('fake_admin_user', - 'fake_project') - self.admin_context.is_admin = True - - @mock.patch.object(utils, 'service_is_up', mock.Mock(return_value=True)) - def test_create_share_if_two_services_up(self): - share_id = 'fake' - fake_share = {'id': share_id, 'size': 1} - fake_service_1 = {'disabled': False, 'host': 'fake_host1'} - fake_service_2 = {'disabled': False, 'host': 'fake_host2'} - fake_result = [(fake_service_1, 2), (fake_service_2, 1)] - fake_request_spec = { - 'share_id': share_id, - 'share_properties': fake_share, - } - self.mock_object(db, 'service_get_all_share_sorted', - mock.Mock(return_value=fake_result)) - self.mock_object(driver, 'share_update_db', - mock.Mock(return_value=db_utils.create_share())) - - self.driver.schedule_create_share(self.context, - fake_request_spec, {}) - utils.service_is_up.assert_called_once_with(utils.IsAMatcher(dict)) - db.service_get_all_share_sorted.assert_called_once_with( - utils.IsAMatcher(context.RequestContext)) - driver.share_update_db.assert_called_once_with( - utils.IsAMatcher(context.RequestContext), share_id, 'fake_host1') - - def test_create_share_if_services_not_available(self): - share_id = 'fake' - fake_share = {'id': share_id, 'size': 1} - fake_result = [] - fake_request_spec = { - 'share_id': share_id, - 'share_properties': fake_share, - } - with mock.patch.object(db, 'service_get_all_share_sorted', - mock.Mock(return_value=fake_result)): - self.assertRaises(exception.NoValidHost, - self.driver.schedule_create_share, - self.context, fake_request_spec, {}) - db.service_get_all_share_sorted.assert_called_once_with( - utils.IsAMatcher(context.RequestContext)) - - def test_create_share_if_max_gigabytes_exceeded(self): - share_id = 'fake' - fake_share = {'id': share_id, 'size': 10001} - fake_service_1 = {'disabled': False, 'host': 'fake_host1'} - fake_service_2 = {'disabled': False, 'host': 'fake_host2'} - fake_result = [(fake_service_1, 5), (fake_service_2, 7)] - fake_request_spec = { - 'share_id': share_id, - 'share_properties': fake_share, - } - with mock.patch.object(db, 'service_get_all_share_sorted', - mock.Mock(return_value=fake_result)): - self.assertRaises(exception.NoValidHost, - self.driver.schedule_create_share, - self.context, fake_request_spec, {}) - db.service_get_all_share_sorted.assert_called_once_with( - utils.IsAMatcher(context.RequestContext)) - - @mock.patch.object(utils, 'service_is_up', mock.Mock(return_value=True)) - def test_create_share_availability_zone(self): - share_id = 'fake' - fake_share = { - 'id': share_id, - 'size': 1, - } - fake_instance = { - 'availability_zone_id': 'fake', - } - fake_service_1 = { - 'disabled': False, 'host': 'fake_host1', - 'availability_zone_id': 'fake', - } - fake_service_2 = { - 'disabled': False, 'host': 'fake_host2', - 'availability_zone_id': 'super_fake', - } - fake_result = [(fake_service_1, 0), (fake_service_2, 1)] - fake_request_spec = { - 'share_id': share_id, - 'share_properties': fake_share, - 'share_instance_properties': fake_instance, - } - self.mock_object(db, 'service_get_all_share_sorted', - mock.Mock(return_value=fake_result)) - self.mock_object(driver, 'share_update_db', - mock.Mock(return_value=db_utils.create_share())) - - self.driver.schedule_create_share(self.context, - fake_request_spec, {}) - utils.service_is_up.assert_called_once_with(fake_service_1) - driver.share_update_db.assert_called_once_with( - utils.IsAMatcher(context.RequestContext), share_id, - fake_service_1['host']) - db.service_get_all_share_sorted.assert_called_once_with( - utils.IsAMatcher(context.RequestContext)) - - @mock.patch.object(utils, 'service_is_up', mock.Mock(return_value=True)) - def test_create_share_availability_zone_on_host(self): - share_id = 'fake' - fake_share = { - 'id': share_id, - 'availability_zone': 'fake:fake', - 'size': 1, - } - fake_service = {'disabled': False, 'host': 'fake'} - fake_request_spec = { - 'share_id': share_id, - 'share_properties': fake_share, - } - self.mock_object(db, 'service_get_all_share_sorted', - mock.Mock(return_value=[(fake_service, 1)])) - self.mock_object(driver, 'share_update_db', - mock.Mock(return_value=db_utils.create_share())) - - self.driver.schedule_create_share(self.admin_context, - fake_request_spec, {}) - utils.service_is_up.assert_called_once_with(fake_service) - db.service_get_all_share_sorted.assert_called_once_with( - utils.IsAMatcher(context.RequestContext)) - driver.share_update_db.assert_called_once_with( - utils.IsAMatcher(context.RequestContext), share_id, 'fake') diff --git a/manila/tests/scheduler/test_scheduler_options.py b/manila/tests/scheduler/test_scheduler_options.py index 0a43e36d74..1bc9d433fd 100644 --- a/manila/tests/scheduler/test_scheduler_options.py +++ b/manila/tests/scheduler/test_scheduler_options.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. """ -Tests For PickledScheduler. +Tests For scheduler options. """ import datetime diff --git a/manila/tests/scheduler/weighers/__init__.py b/manila/tests/scheduler/weighers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manila/tests/scheduler/weighers/test_base.py b/manila/tests/scheduler/weighers/test_base.py new file mode 100644 index 0000000000..b09c596171 --- /dev/null +++ b/manila/tests/scheduler/weighers/test_base.py @@ -0,0 +1,64 @@ +# Copyright 2011-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. + +""" +Tests For Scheduler weighers. +""" + +from manila.scheduler.weighers import base +from manila import test +from manila.tests.scheduler import fakes + + +class TestWeightHandler(test.TestCase): + def test_get_all_classes(self): + namespace = "manila.tests.scheduler.fakes" + handler = base.BaseWeightHandler( + base.BaseWeigher, namespace) + classes = handler.get_all_classes() + self.assertTrue(fakes.FakeWeigher1 in classes) + self.assertTrue(fakes.FakeWeigher2 in classes) + self.assertFalse(fakes.FakeClass in classes) + + def test_no_multiplier(self): + class FakeWeigher(base.BaseWeigher): + def _weigh_object(self, *args, **kwargs): + pass + + self.assertEqual(1.0, + FakeWeigher().weight_multiplier()) + + def test_no_weight_object(self): + class FakeWeigher(base.BaseWeigher): + def weight_multiplier(self, *args, **kwargs): + pass + self.assertRaises(TypeError, + FakeWeigher) + + def test_normalization(self): + # weight_list, expected_result, minval, maxval + map_ = ( + ((), (), None, None), + ((0.0, 0.0), (0.0, 0.0), None, None), + ((1.0, 1.0), (0.0, 0.0), None, None), + + ((20.0, 50.0), (0.0, 1.0), None, None), + ((20.0, 50.0), (0.0, 0.375), None, 100.0), + ((20.0, 50.0), (0.4, 1.0), 0.0, None), + ((20.0, 50.0), (0.2, 0.5), 0.0, 100.0), + ) + for seq, result, minval, maxval in map_: + ret = base.normalize(seq, minval=minval, maxval=maxval) + self.assertEqual(result, tuple(ret)) diff --git a/manila/tests/scheduler/test_capacity_weigher.py b/manila/tests/scheduler/weighers/test_capacity.py similarity index 97% rename from manila/tests/scheduler/test_capacity_weigher.py rename to manila/tests/scheduler/weighers/test_capacity.py index 7ee7047363..e01bade9b0 100644 --- a/manila/tests/scheduler/test_capacity_weigher.py +++ b/manila/tests/scheduler/weighers/test_capacity.py @@ -20,8 +20,8 @@ import mock from oslo_config import cfg from manila import context -from manila.openstack.common.scheduler import weights -from manila.scheduler.weights import capacity +from manila.scheduler.weighers import base_host +from manila.scheduler.weighers import capacity from manila.share import utils from manila import test from manila.tests.scheduler import fakes @@ -33,8 +33,8 @@ class CapacityWeigherTestCase(test.TestCase): def setUp(self): super(CapacityWeigherTestCase, self).setUp() self.host_manager = fakes.FakeHostManager() - self.weight_handler = weights.HostWeightHandler( - 'manila.scheduler.weights') + self.weight_handler = base_host.HostWeightHandler( + 'manila.scheduler.weighers') def _get_weighed_host(self, hosts, weight_properties=None, index=0): if weight_properties is None: diff --git a/manila/tests/scheduler/weights/test_pool.py b/manila/tests/scheduler/weighers/test_pool.py similarity index 95% rename from manila/tests/scheduler/weights/test_pool.py rename to manila/tests/scheduler/weighers/test_pool.py index 0ae6de76e9..41ac789278 100644 --- a/manila/tests/scheduler/weights/test_pool.py +++ b/manila/tests/scheduler/weighers/test_pool.py @@ -12,6 +12,9 @@ # 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 Pool Weigher. +""" import mock from oslo_config import cfg @@ -19,8 +22,8 @@ from oslo_utils import timeutils from manila import context from manila.db import api as db_api -from manila.openstack.common.scheduler import weights -from manila.scheduler.weights import pool +from manila.scheduler.weighers import base_host +from manila.scheduler.weighers import pool from manila.share import utils from manila import test from manila.tests.scheduler import fakes @@ -32,8 +35,8 @@ class PoolWeigherTestCase(test.TestCase): def setUp(self): super(PoolWeigherTestCase, self).setUp() self.host_manager = fakes.FakeHostManager() - self.weight_handler = weights.HostWeightHandler( - 'manila.scheduler.weights') + self.weight_handler = base_host.HostWeightHandler( + 'manila.scheduler.weighers') share_servers = [ {'id': 'fake_server_id0'}, {'id': 'fake_server_id1'}, @@ -170,7 +173,7 @@ class PoolWeigherTestCase(test.TestCase): # host4: weight = 1*(1.0) # host5: weight = 1*(1.0) - # But after normalization all weights will be 0 + # But after normalization all weighers will be 0 weighed_host = self._get_weighed_host(self._get_all_hosts(), weight_properties) diff --git a/setup.cfg b/setup.cfg index 3074d99596..89521befc3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,15 +34,16 @@ console_scripts = manila-scheduler = manila.cmd.scheduler:main manila-share = manila.cmd.share:main manila.scheduler.filters = - AvailabilityZoneFilter = manila.openstack.common.scheduler.filters.availability_zone_filter:AvailabilityZoneFilter - CapabilitiesFilter = manila.openstack.common.scheduler.filters.capabilities_filter:CapabilitiesFilter - CapacityFilter = manila.scheduler.filters.capacity_filter:CapacityFilter - ConsistencyGroupFilter = manila.scheduler.filters.consistency_group_filter:ConsistencyGroupFilter - JsonFilter = manila.openstack.common.scheduler.filters.json_filter:JsonFilter - RetryFilter = manila.scheduler.filters.retry_filter:RetryFilter -manila.scheduler.weights = - CapacityWeigher = manila.scheduler.weights.capacity:CapacityWeigher - PoolWeigher = manila.scheduler.weights.pool:PoolWeigher + AvailabilityZoneFilter = manila.scheduler.filters.availability_zone:AvailabilityZoneFilter + CapabilitiesFilter = manila.scheduler.filters.capabilities:CapabilitiesFilter + CapacityFilter = manila.scheduler.filters.capacity:CapacityFilter + ConsistencyGroupFilter = manila.scheduler.filters.consistency_group:ConsistencyGroupFilter + IgnoreAttemptedHostsFilter = manila.scheduler.filters.ignore_attempted_hosts:IgnoreAttemptedHostsFilter + JsonFilter = manila.scheduler.filters.json:JsonFilter + RetryFilter = manila.scheduler.filters.retry:RetryFilter +manila.scheduler.weighers = + CapacityWeigher = manila.scheduler.weighers.capacity:CapacityWeigher + PoolWeigher = manila.scheduler.weighers.pool:PoolWeigher # These are for backwards compat with Havana notification_driver configuration values oslo_messaging.notify.drivers = manila.openstack.common.notifier.log_notifier = oslo_messaging.notify._impl_log:LogDriver @@ -55,6 +56,9 @@ oslo.config.opts = manila.share.drivers.emc.plugins = vnx = manila.share.drivers.emc.plugins.vnx.connection:VNXStorageConnection isilon = manila.share.drivers.emc.plugins.isilon.isilon:IsilonStorageConnection +manila.tests.scheduler.fakes = + FakeWeigher1 = manila.tests.scheduler.fakes:FakeWeigher1 + FakeWeigher2 = manila.tests.scheduler.fakes:FakeWeigher2 tempest.test_plugins = manila_tests = manila_tempest_tests.plugin:ManilaTempestPlugin