end of day
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from abstract_filter import AbstractHostFilter
|
||||||
from all_hosts_filter import AllHostsFilter
|
from all_hosts_filter import AllHostsFilter
|
||||||
from instance_type_filter import InstanceTypeFilter
|
from instance_type_filter import InstanceTypeFilter
|
||||||
from json_filter import JsonFilter
|
from json_filter import JsonFilter
|
||||||
|
|||||||
@@ -13,44 +13,15 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
filter grammar.
|
|
||||||
|
|
||||||
Why JSON? The requests for instances may come in through the
|
|
||||||
REST interface from a user or a parent Zone.
|
|
||||||
Currently Flavors and/or InstanceTypes are used for
|
|
||||||
specifing the type of instance desired. Specific Nova users have
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from nova import exception
|
|
||||||
from nova import flags
|
|
||||||
from nova import log as logging
|
|
||||||
|
|
||||||
import nova.scheduler
|
import nova.scheduler
|
||||||
|
from nova import flags
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger('nova.scheduler.host_filter')
|
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
flags.DEFINE_string('default_host_filter',
|
flags.DEFINE_string('default_host_filter',
|
||||||
'nova.scheduler.host_filter.AllHostsFilter',
|
'nova.scheduler.filters.AllHostsFilter',
|
||||||
'Which filter to use for filtering hosts')
|
'Which filter to use for filtering hosts')
|
||||||
|
|
||||||
|
|
||||||
class AbstractHostFilter(object):
|
class AbstractHostFilter(object):
|
||||||
"""Base class for host filters."""
|
"""Base class for host filters."""
|
||||||
def instance_type_to_filter(self, instance_type):
|
def instance_type_to_filter(self, instance_type):
|
||||||
@@ -64,24 +35,3 @@ class AbstractHostFilter(object):
|
|||||||
def _full_name(self):
|
def _full_name(self):
|
||||||
"""module.classname of the filter."""
|
"""module.classname of the filter."""
|
||||||
return "%s.%s" % (self.__module__, self.__class__.__name__)
|
return "%s.%s" % (self.__module__, self.__class__.__name__)
|
||||||
|
|
||||||
|
|
||||||
def _get_filters():
|
|
||||||
from nova.scheduler import filters
|
|
||||||
return [itm for itm in dir(filters)
|
|
||||||
if issubclass(itm, AbstractHostFilter)]
|
|
||||||
|
|
||||||
|
|
||||||
def choose_host_filter(filter_name=None):
|
|
||||||
"""Since the caller may specify which filter to use we need
|
|
||||||
to have an authoritative list of what is permissible. This
|
|
||||||
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 _get_filters():
|
|
||||||
host_match = "%s.%s" % (filter_class.__module__, filter_class.__name__)
|
|
||||||
if host_match == filter_name:
|
|
||||||
return filter_class()
|
|
||||||
raise exception.SchedulerHostFilterNotFound(filter_name=filter_name)
|
|
||||||
|
|||||||
@@ -15,9 +15,10 @@
|
|||||||
|
|
||||||
|
|
||||||
import nova.scheduler
|
import nova.scheduler
|
||||||
|
from nova.scheduler.filters import abstract_filter
|
||||||
|
|
||||||
|
|
||||||
class AllHostsFilter(nova.scheduler.host_filter.AbstractHostFilter):
|
class AllHostsFilter(abstract_filter.AbstractHostFilter):
|
||||||
"""NOP host filter. Returns all hosts in ZoneManager."""
|
"""NOP host filter. Returns all hosts in ZoneManager."""
|
||||||
def instance_type_to_filter(self, instance_type):
|
def instance_type_to_filter(self, instance_type):
|
||||||
"""Return anything to prevent base-class from raising
|
"""Return anything to prevent base-class from raising
|
||||||
|
|||||||
@@ -14,10 +14,11 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
from nova.scheduler import host_filter
|
import nova.scheduler
|
||||||
|
from nova.scheduler.filters import abstract_filter
|
||||||
|
|
||||||
|
|
||||||
class InstanceTypeFilter(host_filter.AbstractHostFilter):
|
class InstanceTypeFilter(abstract_filter.AbstractHostFilter):
|
||||||
"""HostFilter hard-coded to work with InstanceType records."""
|
"""HostFilter hard-coded to work with InstanceType records."""
|
||||||
def instance_type_to_filter(self, instance_type):
|
def instance_type_to_filter(self, instance_type):
|
||||||
"""Use instance_type to filter hosts."""
|
"""Use instance_type to filter hosts."""
|
||||||
|
|||||||
@@ -14,49 +14,64 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
from nova.scheduler import host_filter
|
import nova.scheduler
|
||||||
|
from nova.scheduler.filters import abstract_filter
|
||||||
|
|
||||||
|
def debug(*args):
|
||||||
|
with file("/tmp/debug", "a") as dbg:
|
||||||
|
msg = " ".join([str(arg) for arg in args])
|
||||||
|
dbg.write("%s\n" % msg)
|
||||||
|
|
||||||
|
|
||||||
class JsonFilter(host_filter.AbstractHostFilter):
|
class JsonFilter(abstract_filter.AbstractHostFilter):
|
||||||
"""Host Filter to allow simple JSON-based grammar for
|
"""Host Filter to allow simple JSON-based grammar for
|
||||||
selecting hosts.
|
selecting hosts.
|
||||||
"""
|
"""
|
||||||
def _op_comp(self, args, op):
|
def _op_compare(self, args, op):
|
||||||
"""Returns True if the specified operator can successfully
|
"""Returns True if the specified operator can successfully
|
||||||
compare the first item in the args with all the rest. Will
|
compare the first item in the args with all the rest. Will
|
||||||
return False if only one item is in the list.
|
return False if only one item is in the list.
|
||||||
"""
|
"""
|
||||||
if len(args) < 2:
|
if len(args) < 2:
|
||||||
return False
|
return False
|
||||||
|
if op is operator.contains:
|
||||||
|
debug("ARGS", type(args), args)
|
||||||
|
debug("op", op)
|
||||||
|
debug("REVERSED!!!")
|
||||||
|
# operator.contains reverses the param order.
|
||||||
|
bad = [arg for arg in args[1:]
|
||||||
|
if not op(args, args[0])]
|
||||||
|
else:
|
||||||
bad = [arg for arg in args[1:]
|
bad = [arg for arg in args[1:]
|
||||||
if not op(args[0], arg)]
|
if not op(args[0], arg)]
|
||||||
return not bool(bad)
|
return not bool(bad)
|
||||||
|
|
||||||
def _equals(self, args):
|
def _equals(self, args):
|
||||||
"""First term is == all the other terms."""
|
"""First term is == all the other terms."""
|
||||||
return self._op_comp(args, operator.eq)
|
return self._op_compare(args, operator.eq)
|
||||||
|
|
||||||
def _less_than(self, args):
|
def _less_than(self, args):
|
||||||
"""First term is < all the other terms."""
|
"""First term is < all the other terms."""
|
||||||
return self._op_comp(args, operator.lt)
|
return self._op_compare(args, operator.lt)
|
||||||
|
|
||||||
def _greater_than(self, args):
|
def _greater_than(self, args):
|
||||||
"""First term is > all the other terms."""
|
"""First term is > all the other terms."""
|
||||||
return self._op_comp(args, operator.gt)
|
return self._op_compare(args, operator.gt)
|
||||||
|
|
||||||
def _in(self, args):
|
def _in(self, args):
|
||||||
"""First term is in set of remaining terms"""
|
"""First term is in set of remaining terms"""
|
||||||
return self._op_comp(args, operator.contains)
|
return self._op_compare(args, operator.contains)
|
||||||
|
|
||||||
def _less_than_equal(self, args):
|
def _less_than_equal(self, args):
|
||||||
"""First term is <= all the other terms."""
|
"""First term is <= all the other terms."""
|
||||||
return self._op_comp(args, operator.le)
|
return self._op_compare(args, operator.le)
|
||||||
|
|
||||||
def _greater_than_equal(self, args):
|
def _greater_than_equal(self, args):
|
||||||
"""First term is >= all the other terms."""
|
"""First term is >= all the other terms."""
|
||||||
return self._op_comp(args, operator.ge)
|
return self._op_compare(args, operator.ge)
|
||||||
|
|
||||||
def _not(self, args):
|
def _not(self, args):
|
||||||
"""Flip each of the arguments."""
|
"""Flip each of the arguments."""
|
||||||
@@ -129,6 +144,8 @@ class JsonFilter(host_filter.AbstractHostFilter):
|
|||||||
specified in the query.
|
specified in the query.
|
||||||
"""
|
"""
|
||||||
expanded = json.loads(query)
|
expanded = json.loads(query)
|
||||||
|
|
||||||
|
debug("expanded", type(expanded), expanded)
|
||||||
filtered_hosts = []
|
filtered_hosts = []
|
||||||
for host, services in zone_manager.service_states.iteritems():
|
for host, services in zone_manager.service_states.iteritems():
|
||||||
result = self._process_filter(zone_manager, expanded, host,
|
result = self._process_filter(zone_manager, expanded, host,
|
||||||
|
|||||||
75
nova/scheduler/host_filter.py
Normal file
75
nova/scheduler/host_filter.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Copyright (c) 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
filter grammar.
|
||||||
|
|
||||||
|
Why JSON? The requests for instances may come in through the
|
||||||
|
REST interface from a user or a parent Zone.
|
||||||
|
Currently Flavors and/or InstanceTypes are used for
|
||||||
|
specifing the type of instance desired. Specific Nova users have
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import types
|
||||||
|
|
||||||
|
from nova import exception
|
||||||
|
from nova import flags
|
||||||
|
from nova import log as logging
|
||||||
|
|
||||||
|
import nova.scheduler
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger('nova.scheduler.host_filter')
|
||||||
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
|
|
||||||
|
def _get_filters():
|
||||||
|
from nova.scheduler import filters
|
||||||
|
def get_itm(nm):
|
||||||
|
return getattr(filters, nm)
|
||||||
|
|
||||||
|
return [get_itm(itm) for itm in dir(filters)
|
||||||
|
if (type(get_itm(itm)) is types.TypeType)
|
||||||
|
and issubclass(get_itm(itm), filters.AbstractHostFilter)]
|
||||||
|
|
||||||
|
|
||||||
|
def choose_host_filter(filter_name=None):
|
||||||
|
"""Since the caller may specify which filter to use we need
|
||||||
|
to have an authoritative list of what is permissible. This
|
||||||
|
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 _get_filters():
|
||||||
|
host_match = "%s.%s" % (filter_class.__module__, filter_class.__name__)
|
||||||
|
if (host_match.startswith("nova.scheduler.filters") and
|
||||||
|
(host_match.split(".")[-1] == filter_name)):
|
||||||
|
return filter_class()
|
||||||
|
raise exception.SchedulerHostFilterNotFound(filter_name=filter_name)
|
||||||
@@ -20,6 +20,7 @@ import json
|
|||||||
|
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import test
|
from nova import test
|
||||||
|
from nova.scheduler import host_filter
|
||||||
from nova.scheduler import filters
|
from nova.scheduler import filters
|
||||||
|
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ class HostFilterTestCase(test.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(HostFilterTestCase, self).setUp()
|
super(HostFilterTestCase, self).setUp()
|
||||||
default_host_filter = 'nova.scheduler.filteris.AllHostsFilter'
|
default_host_filter = 'AllHostsFilter'
|
||||||
self.flags(default_host_filter=default_host_filter)
|
self.flags(default_host_filter=default_host_filter)
|
||||||
self.instance_type = dict(name='tiny',
|
self.instance_type = dict(name='tiny',
|
||||||
memory_mb=50,
|
memory_mb=50,
|
||||||
@@ -98,13 +99,10 @@ class HostFilterTestCase(test.TestCase):
|
|||||||
def test_choose_filter(self):
|
def test_choose_filter(self):
|
||||||
# Test default filter ...
|
# Test default filter ...
|
||||||
hf = host_filter.choose_host_filter()
|
hf = host_filter.choose_host_filter()
|
||||||
self.assertEquals(hf._full_name(),
|
self.assertEquals(hf._full_name().split(".")[-1], 'AllHostsFilter')
|
||||||
'nova.scheduler.host_filter.AllHostsFilter')
|
|
||||||
# Test valid filter ...
|
# Test valid filter ...
|
||||||
hf = host_filter.choose_host_filter(
|
hf = host_filter.choose_host_filter('InstanceTypeFilter')
|
||||||
'nova.scheduler.host_filter.InstanceTypeFilter')
|
self.assertEquals(hf._full_name().split(".")[-1], 'InstanceTypeFilter')
|
||||||
self.assertEquals(hf._full_name(),
|
|
||||||
'nova.scheduler.host_filter.InstanceTypeFilter')
|
|
||||||
# Test invalid filter ...
|
# Test invalid filter ...
|
||||||
try:
|
try:
|
||||||
host_filter.choose_host_filter('does not exist')
|
host_filter.choose_host_filter('does not exist')
|
||||||
@@ -113,7 +111,7 @@ class HostFilterTestCase(test.TestCase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def test_all_host_filter(self):
|
def test_all_host_filter(self):
|
||||||
hf = host_filter.AllHostsFilter()
|
hf = filters.AllHostsFilter()
|
||||||
cooked = hf.instance_type_to_filter(self.instance_type)
|
cooked = hf.instance_type_to_filter(self.instance_type)
|
||||||
hosts = hf.filter_hosts(self.zone_manager, cooked)
|
hosts = hf.filter_hosts(self.zone_manager, cooked)
|
||||||
self.assertEquals(10, len(hosts))
|
self.assertEquals(10, len(hosts))
|
||||||
@@ -121,11 +119,10 @@ class HostFilterTestCase(test.TestCase):
|
|||||||
self.assertTrue(host.startswith('host'))
|
self.assertTrue(host.startswith('host'))
|
||||||
|
|
||||||
def test_instance_type_filter(self):
|
def test_instance_type_filter(self):
|
||||||
hf = host_filter.InstanceTypeFilter()
|
hf = filters.InstanceTypeFilter()
|
||||||
# filter all hosts that can support 50 ram and 500 disk
|
# filter all hosts that can support 50 ram and 500 disk
|
||||||
name, cooked = hf.instance_type_to_filter(self.instance_type)
|
name, cooked = hf.instance_type_to_filter(self.instance_type)
|
||||||
self.assertEquals('nova.scheduler.host_filter.InstanceTypeFilter',
|
self.assertEquals(name.split(".")[-1], 'InstanceTypeFilter')
|
||||||
name)
|
|
||||||
hosts = hf.filter_hosts(self.zone_manager, cooked)
|
hosts = hf.filter_hosts(self.zone_manager, cooked)
|
||||||
self.assertEquals(6, len(hosts))
|
self.assertEquals(6, len(hosts))
|
||||||
just_hosts = [host for host, caps in hosts]
|
just_hosts = [host for host, caps in hosts]
|
||||||
@@ -134,21 +131,20 @@ class HostFilterTestCase(test.TestCase):
|
|||||||
self.assertEquals('host10', just_hosts[5])
|
self.assertEquals('host10', just_hosts[5])
|
||||||
|
|
||||||
def test_instance_type_filter_extra_specs(self):
|
def test_instance_type_filter_extra_specs(self):
|
||||||
hf = host_filter.InstanceTypeFilter()
|
hf = filters.InstanceTypeFilter()
|
||||||
# filter all hosts that can support 50 ram and 500 disk
|
# filter all hosts that can support 50 ram and 500 disk
|
||||||
name, cooked = hf.instance_type_to_filter(self.gpu_instance_type)
|
name, cooked = hf.instance_type_to_filter(self.gpu_instance_type)
|
||||||
self.assertEquals('nova.scheduler.host_filter.InstanceTypeFilter',
|
self.assertEquals(name.split(".")[-1], 'InstanceTypeFilter')
|
||||||
name)
|
|
||||||
hosts = hf.filter_hosts(self.zone_manager, cooked)
|
hosts = hf.filter_hosts(self.zone_manager, cooked)
|
||||||
self.assertEquals(1, len(hosts))
|
self.assertEquals(1, len(hosts))
|
||||||
just_hosts = [host for host, caps in hosts]
|
just_hosts = [host for host, caps in hosts]
|
||||||
self.assertEquals('host07', just_hosts[0])
|
self.assertEquals('host07', just_hosts[0])
|
||||||
|
|
||||||
def test_json_filter(self):
|
def test_json_filter(self):
|
||||||
hf = host_filter.JsonFilter()
|
hf = filters.JsonFilter()
|
||||||
# filter all hosts that can support 50 ram and 500 disk
|
# filter all hosts that can support 50 ram and 500 disk
|
||||||
name, cooked = hf.instance_type_to_filter(self.instance_type)
|
name, cooked = hf.instance_type_to_filter(self.instance_type)
|
||||||
self.assertEquals('nova.scheduler.host_filter.JsonFilter', name)
|
self.assertEquals(name.split(".")[-1], 'JsonFilter')
|
||||||
hosts = hf.filter_hosts(self.zone_manager, cooked)
|
hosts = hf.filter_hosts(self.zone_manager, cooked)
|
||||||
self.assertEquals(6, len(hosts))
|
self.assertEquals(6, len(hosts))
|
||||||
just_hosts = [host for host, caps in hosts]
|
just_hosts = [host for host, caps in hosts]
|
||||||
@@ -191,6 +187,12 @@ class HostFilterTestCase(test.TestCase):
|
|||||||
|
|
||||||
raw = ['in', '$compute.host_memory_free', 20, 40, 60, 80, 100]
|
raw = ['in', '$compute.host_memory_free', 20, 40, 60, 80, 100]
|
||||||
cooked = json.dumps(raw)
|
cooked = json.dumps(raw)
|
||||||
|
def debug(*args):
|
||||||
|
with file("/tmp/debug", "a") as dbg:
|
||||||
|
msg = " ".join([str(arg) for arg in args])
|
||||||
|
dbg.write("%s\n" % msg)
|
||||||
|
|
||||||
|
debug("cooked", cooked, type(cooked))
|
||||||
hosts = hf.filter_hosts(self.zone_manager, cooked)
|
hosts = hf.filter_hosts(self.zone_manager, cooked)
|
||||||
|
|
||||||
self.assertEquals(5, len(hosts))
|
self.assertEquals(5, len(hosts))
|
||||||
|
|||||||
Reference in New Issue
Block a user