diff options
| author | Ed Leafe <ed@leafe.com> | 2011-08-16 13:01:41 +0000 |
|---|---|---|
| committer | Tarmac <> | 2011-08-16 13:01:41 +0000 |
| commit | 76eebe86f383989ab012b9ad8a5b6fc4bbfee4af (patch) | |
| tree | 4d60580e1def837e9e56ed8c12bbee563a79e5d0 | |
| parent | 79209025ee4a14f29183656db8da7bb713661f7d (diff) | |
| parent | f881bee5b1283d5bec2396b45cea9a062cb2a4b2 (diff) | |
| download | nova-76eebe86f383989ab012b9ad8a5b6fc4bbfee4af.tar.gz nova-76eebe86f383989ab012b9ad8a5b6fc4bbfee4af.tar.xz nova-76eebe86f383989ab012b9ad8a5b6fc4bbfee4af.zip | |
Refactored the HostFilterScheduler and LeastCostScheduler classes so that they can be combined into a single class that can do both host filtering and host weighting, allowing subclasses to override those processes as needed. Also renamed the ZoneAwareScheduler to AbstractScheduler, for two reasons: one, the 'zone-aware' designation was necessary when the zone code was being developed; now that it is part of nova, it is not an important distinction. Second, the 'Abstract' part clearly indicates that this is a class that is not designed to be used directly, but rather as the basis for specific scheduler subclasses.
| -rw-r--r-- | nova/scheduler/abstract_scheduler.py (renamed from nova/scheduler/zone_aware_scheduler.py) | 32 | ||||
| -rw-r--r-- | nova/scheduler/host_filter.py | 51 | ||||
| -rw-r--r-- | nova/scheduler/least_cost.py | 7 | ||||
| -rw-r--r-- | nova/tests/scheduler/test_abstract_scheduler.py (renamed from nova/tests/scheduler/test_zone_aware_scheduler.py) | 40 | ||||
| -rw-r--r-- | nova/tests/scheduler/test_least_cost_scheduler.py | 4 |
5 files changed, 61 insertions, 73 deletions
diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/abstract_scheduler.py index d1924c9f9..eb924732a 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/abstract_scheduler.py @@ -14,7 +14,7 @@ # under the License. """ -The Zone Aware Scheduler is a base class Scheduler for creating instances +The AbsractScheduler is a base class Scheduler for creating instances across zones. There are two expansion points to this class for: 1. Assigning Weights to hosts for requested instances 2. Filtering Hosts based on required instance capabilities @@ -40,7 +40,7 @@ from nova.scheduler import api from nova.scheduler import driver FLAGS = flags.FLAGS -LOG = logging.getLogger('nova.scheduler.zone_aware_scheduler') +LOG = logging.getLogger('nova.scheduler.abstract_scheduler') class InvalidBlob(exception.NovaException): @@ -48,8 +48,10 @@ class InvalidBlob(exception.NovaException): "to instance create request.") -class ZoneAwareScheduler(driver.Scheduler): - """Base class for creating Zone Aware Schedulers.""" +class AbstractScheduler(driver.Scheduler): + """Base class for creating Schedulers that can work across any nova + deployment, from simple designs to multiply-nested zones. + """ def _call_zone_method(self, context, method, specs, zones): """Call novaclient zone method. Broken out for testing.""" @@ -266,7 +268,7 @@ class ZoneAwareScheduler(driver.Scheduler): """ if topic != "compute": - raise NotImplementedError(_("Zone Aware Scheduler only understands" + raise NotImplementedError(_("Scheduler only understands" " Compute nodes (for now)")) num_instances = request_spec.get('num_instances', 1) @@ -328,13 +330,31 @@ class ZoneAwareScheduler(driver.Scheduler): requested_mem = instance_type['memory_mb'] * 1024 * 1024 return capabilities['host_memory_free'] >= requested_mem + def hold_filter_hosts(self, topic, request_spec, hosts=None): + """Filter the full host list (from the ZoneManager)""" + # NOTE(dabo): The logic used by the current _schedule() method + # is incorrect. Since this task is just to refactor the classes, + # I'm not fixing the logic now - that will be the next task. + # So for now this method is just renamed; afterwards this will + # become the filter_hosts() method, and the one below will + # be removed. + filter_name = request_spec.get('filter', None) + # Make sure that the requested filter is legitimate. + selected_filter = host_filter.choose_host_filter(filter_name) + + # TODO(sandy): We're only using InstanceType-based specs + # currently. Later we'll need to snoop for more detailed + # host filter requests. + instance_type = request_spec['instance_type'] + name, query = selected_filter.instance_type_to_filter(instance_type) + return selected_filter.filter_hosts(self.zone_manager, query) + def filter_hosts(self, topic, request_spec, host_list=None): """Return a list of hosts which are acceptable for scheduling. Return value should be a list of (hostname, capability_dict)s. Derived classes may override this, but may find the '<topic>_filter' function more appropriate. """ - def _default_filter(self, hostname, capabilities, request_spec): """Default filter function if there's no <topic>_filter""" # NOTE(sirp): The default logic is the equivalent to diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index b7bbbbcb8..45a8f40d8 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -14,7 +14,12 @@ # under the License. """ -Host Filter is a mechanism for requesting instance resources. +The Host Filter classes are a way to ensure that only hosts that are +appropriate are considered when creating a new instance. Hosts that are +either incompatible or insufficient to accept a newly-requested instance +are removed by Host Filter classes from consideration. Those that pass +the filter are then passed on for weighting or other process for ordering. + Three filters are included: AllHosts, Flavor & JSON. AllHosts just returns the full, unfiltered list of hosts. Flavor is a hard coded matching mechanism based on flavor criteria and JSON is an ad-hoc @@ -28,12 +33,6 @@ noted a need for a more expressive way of specifying instances. Since we don't want to get into building full DSL this is a simple form as an example of how this could be done. In reality, most consumers will use the more rigid filters such as FlavorFilter. - -Note: These are "required" capability filters. These capabilities -used must be present or the host will be excluded. The hosts -returned are then weighed by the Weighted Scheduler. Weights -can take the more esoteric factors into consideration (such as -server affinity and customer separation). """ import json @@ -41,9 +40,7 @@ import json from nova import exception from nova import flags from nova import log as logging -from nova.scheduler import zone_aware_scheduler from nova import utils -from nova.scheduler import zone_aware_scheduler LOG = logging.getLogger('nova.scheduler.host_filter') @@ -125,9 +122,8 @@ class InstanceTypeFilter(HostFilter): spec_disk = instance_type['local_gb'] extra_specs = instance_type['extra_specs'] - if host_ram_mb >= spec_ram and \ - disk_bytes >= spec_disk and \ - self._satisfies_extra_specs(capabilities, instance_type): + if ((host_ram_mb >= spec_ram) and (disk_bytes >= spec_disk) and + self._satisfies_extra_specs(capabilities, instance_type)): selected_hosts.append((host, capabilities)) return selected_hosts @@ -309,7 +305,6 @@ def choose_host_filter(filter_name=None): function checks the filter name against a predefined set of acceptable filters. """ - if not filter_name: filter_name = FLAGS.default_host_filter for filter_class in FILTERS: @@ -317,33 +312,3 @@ def choose_host_filter(filter_name=None): if host_match == filter_name: return filter_class() raise exception.SchedulerHostFilterNotFound(filter_name=filter_name) - - -class HostFilterScheduler(zone_aware_scheduler.ZoneAwareScheduler): - """The HostFilterScheduler uses the HostFilter to filter - hosts for weighing. The particular filter used may be passed in - as an argument or the default will be used. - - request_spec = {'filter': <Filter name>, - 'instance_type': <InstanceType dict>} - """ - - def filter_hosts(self, topic, request_spec, hosts=None): - """Filter the full host list (from the ZoneManager)""" - - filter_name = request_spec.get('filter', None) - host_filter = choose_host_filter(filter_name) - - # TODO(sandy): We're only using InstanceType-based specs - # currently. Later we'll need to snoop for more detailed - # host filter requests. - instance_type = request_spec['instance_type'] - name, query = host_filter.instance_type_to_filter(instance_type) - return host_filter.filter_hosts(self.zone_manager, query) - - def weigh_hosts(self, topic, request_spec, hosts): - """Derived classes must override this method and return - a lists of hosts in [{weight, hostname}] format. - """ - return [dict(weight=1, hostname=hostname, capabilities=caps) - for hostname, caps in hosts] diff --git a/nova/scheduler/least_cost.py b/nova/scheduler/least_cost.py index 329107efe..a58b11289 100644 --- a/nova/scheduler/least_cost.py +++ b/nova/scheduler/least_cost.py @@ -22,11 +22,14 @@ The cost-function and weights are tabulated, and the host with the least cost is then selected for provisioning. """ +# TODO(dabo): This class will be removed in the next merge prop; it remains now +# because much of the code will be refactored into different classes. + import collections from nova import flags from nova import log as logging -from nova.scheduler import zone_aware_scheduler +from nova.scheduler import abstract_scheduler from nova import utils from nova import exception @@ -61,7 +64,7 @@ def compute_fill_first_cost_fn(host): return free_mem -class LeastCostScheduler(zone_aware_scheduler.ZoneAwareScheduler): +class LeastCostScheduler(abstract_scheduler.AbstractScheduler): def __init__(self, *args, **kwargs): self.cost_fns_cache = {} super(LeastCostScheduler, self).__init__(*args, **kwargs) diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_abstract_scheduler.py index 788efca52..f4f5cc233 100644 --- a/nova/tests/scheduler/test_zone_aware_scheduler.py +++ b/nova/tests/scheduler/test_abstract_scheduler.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. """ -Tests For Zone Aware Scheduler. +Tests For Abstract Scheduler. """ import json @@ -25,7 +25,7 @@ from nova import rpc from nova import test from nova.compute import api as compute_api from nova.scheduler import driver -from nova.scheduler import zone_aware_scheduler +from nova.scheduler import abstract_scheduler from nova.scheduler import zone_manager @@ -60,7 +60,7 @@ def fake_zone_manager_service_states(num_hosts): return states -class FakeZoneAwareScheduler(zone_aware_scheduler.ZoneAwareScheduler): +class FakeAbstractScheduler(abstract_scheduler.AbstractScheduler): # No need to stub anything at the moment pass @@ -161,15 +161,15 @@ def fake_zone_get_all(context): ] -class ZoneAwareSchedulerTestCase(test.TestCase): - """Test case for Zone Aware Scheduler.""" +class AbstractSchedulerTestCase(test.TestCase): + """Test case for Abstract Scheduler.""" - def test_zone_aware_scheduler(self): + def test_abstract_scheduler(self): """ Create a nested set of FakeZones, try to build multiple instances and ensure that a select call returns the appropriate build plan. """ - sched = FakeZoneAwareScheduler() + sched = FakeAbstractScheduler() self.stubs.Set(sched, '_call_zone_method', fake_call_zone_method) self.stubs.Set(nova.db, 'zone_get_all', fake_zone_get_all) @@ -194,7 +194,7 @@ class ZoneAwareSchedulerTestCase(test.TestCase): properly adjusted based on the scale/offset in the zone db entries. """ - sched = FakeZoneAwareScheduler() + sched = FakeAbstractScheduler() child_results = fake_call_zone_method(None, None, None, None) zones = fake_zone_get_all(None) sched._adjust_child_weights(child_results, zones) @@ -209,11 +209,11 @@ class ZoneAwareSchedulerTestCase(test.TestCase): if zone == 'zone3': # Scale x1000 self.assertEqual(scaled.pop(0), w) - def test_empty_zone_aware_scheduler(self): + def test_empty_abstract_scheduler(self): """ Ensure empty hosts & child_zones result in NoValidHosts exception. """ - sched = FakeZoneAwareScheduler() + sched = FakeAbstractScheduler() self.stubs.Set(sched, '_call_zone_method', fake_empty_call_zone_method) self.stubs.Set(nova.db, 'zone_get_all', fake_zone_get_all) @@ -231,7 +231,7 @@ class ZoneAwareSchedulerTestCase(test.TestCase): If the zone_blob hint was passed in, don't re-schedule. """ global was_called - sched = FakeZoneAwareScheduler() + sched = FakeAbstractScheduler() was_called = False self.stubs.Set(sched, '_provision_resource', fake_provision_resource) request_spec = { @@ -248,7 +248,7 @@ class ZoneAwareSchedulerTestCase(test.TestCase): def test_provision_resource_local(self): """Provision a resource locally or remotely.""" global was_called - sched = FakeZoneAwareScheduler() + sched = FakeAbstractScheduler() was_called = False self.stubs.Set(sched, '_provision_resource_locally', fake_provision_resource_locally) @@ -260,7 +260,7 @@ class ZoneAwareSchedulerTestCase(test.TestCase): def test_provision_resource_remote(self): """Provision a resource locally or remotely.""" global was_called - sched = FakeZoneAwareScheduler() + sched = FakeAbstractScheduler() was_called = False self.stubs.Set(sched, '_provision_resource_from_blob', fake_provision_resource_from_blob) @@ -272,9 +272,9 @@ class ZoneAwareSchedulerTestCase(test.TestCase): def test_provision_resource_from_blob_empty(self): """Provision a resource locally or remotely given no hints.""" global was_called - sched = FakeZoneAwareScheduler() + sched = FakeAbstractScheduler() request_spec = {} - self.assertRaises(zone_aware_scheduler.InvalidBlob, + self.assertRaises(abstract_scheduler.InvalidBlob, sched._provision_resource_from_blob, None, {}, 1, {}, {}) @@ -283,7 +283,7 @@ class ZoneAwareSchedulerTestCase(test.TestCase): Provision a resource locally or remotely when blob hint passed in. """ global was_called - sched = FakeZoneAwareScheduler() + sched = FakeAbstractScheduler() was_called = False def fake_create_db_entry_for_new_instance(self, context, @@ -317,7 +317,7 @@ class ZoneAwareSchedulerTestCase(test.TestCase): passed in. """ global was_called - sched = FakeZoneAwareScheduler() + sched = FakeAbstractScheduler() self.stubs.Set(sched, '_decrypt_blob', fake_decrypt_blob_returns_child_info) was_called = False @@ -336,7 +336,7 @@ class ZoneAwareSchedulerTestCase(test.TestCase): from an immediate child. """ global was_called - sched = FakeZoneAwareScheduler() + sched = FakeAbstractScheduler() was_called = False self.stubs.Set(sched, '_ask_child_zone_to_create_instance', fake_ask_child_zone_to_create_instance) @@ -350,14 +350,14 @@ class ZoneAwareSchedulerTestCase(test.TestCase): def test_decrypt_blob(self): """Test that the decrypt method works.""" - fixture = FakeZoneAwareScheduler() + fixture = FakeAbstractScheduler() test_data = {"foo": "bar"} class StubDecryptor(object): def decryptor(self, key): return lambda blob: blob - self.stubs.Set(zone_aware_scheduler, 'crypto', + self.stubs.Set(abstract_scheduler, 'crypto', StubDecryptor()) self.assertEqual(fixture._decrypt_blob(test_data), diff --git a/nova/tests/scheduler/test_least_cost_scheduler.py b/nova/tests/scheduler/test_least_cost_scheduler.py index fbe6b2f77..de7581d0a 100644 --- a/nova/tests/scheduler/test_least_cost_scheduler.py +++ b/nova/tests/scheduler/test_least_cost_scheduler.py @@ -18,7 +18,7 @@ Tests For Least Cost Scheduler from nova import test from nova.scheduler import least_cost -from nova.tests.scheduler import test_zone_aware_scheduler +from nova.tests.scheduler import test_abstract_scheduler MB = 1024 * 1024 @@ -70,7 +70,7 @@ class LeastCostSchedulerTestCase(test.TestCase): zone_manager = FakeZoneManager() - states = test_zone_aware_scheduler.fake_zone_manager_service_states( + states = test_abstract_scheduler.fake_zone_manager_service_states( num_hosts=10) zone_manager.service_states = states |
