terminology: no more plug-ins or queries. They are host filters and drivers.
This commit is contained in:
@@ -14,14 +14,23 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Query is a plug-in mechanism for requesting instance resources.
|
Host Filter is a driver mechanism for requesting instance resources.
|
||||||
Three plug-ins are included: AllHosts, Flavor & JSON. AllHosts just
|
Three drivers are included: AllHosts, Flavor & JSON. AllHosts just
|
||||||
returns the full, unfiltered list of hosts. Flavor is a hard coded
|
returns the full, unfiltered list of hosts. Flavor is a hard coded
|
||||||
matching mechanism based on flavor criteria and JSON is an ad-hoc
|
matching mechanism based on flavor criteria and JSON is an ad-hoc
|
||||||
query grammar.
|
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.
|
||||||
|
|
||||||
Note: These are hard filters. All capabilities used must be present
|
Note: These are hard filters. All capabilities used must be present
|
||||||
or the host will excluded. If you want soft filters use the weighting
|
or the host will be excluded. If you want soft filters use the weighting
|
||||||
mechanism which is intended for the more touchy-feely capabilities.
|
mechanism which is intended for the more touchy-feely capabilities.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -32,36 +41,36 @@ from nova import flags
|
|||||||
from nova import log as logging
|
from nova import log as logging
|
||||||
from nova import utils
|
from nova import utils
|
||||||
|
|
||||||
LOG = logging.getLogger('nova.scheduler.query')
|
LOG = logging.getLogger('nova.scheduler.host_filter')
|
||||||
|
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
flags.DEFINE_string('default_query_engine',
|
flags.DEFINE_string('default_host_filter_driver',
|
||||||
'nova.scheduler.query.AllHostsQuery',
|
'nova.scheduler.host_filter.AllHostsFilter',
|
||||||
'Which query engine to use for filtering hosts.')
|
'Which driver to use for filtering hosts.')
|
||||||
|
|
||||||
|
|
||||||
class Query(object):
|
class HostFilter(object):
|
||||||
"""Base class for query plug-ins."""
|
"""Base class for host filter drivers."""
|
||||||
|
|
||||||
def instance_type_to_query(self, instance_type):
|
def instance_type_to_filter(self, instance_type):
|
||||||
"""Convert instance_type into a query for most common use-case."""
|
"""Convert instance_type into a filter for most common use-case."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def filter_hosts(self, zone_manager, query):
|
def filter_hosts(self, zone_manager, query):
|
||||||
"""Return a list of hosts that fulfill the query."""
|
"""Return a list of hosts that fulfill the filter."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _full_name(self):
|
def _full_name(self):
|
||||||
"""module.classname of the Query object"""
|
"""module.classname of the filter driver"""
|
||||||
return "%s.%s" % (self.__module__, self.__class__.__name__)
|
return "%s.%s" % (self.__module__, self.__class__.__name__)
|
||||||
|
|
||||||
|
|
||||||
class AllHostsQuery(Query):
|
class AllHostsFilter(HostFilter):
|
||||||
"""NOP query plug-in. Returns all hosts in ZoneManager.
|
"""NOP host filter driver. Returns all hosts in ZoneManager.
|
||||||
This essentially does what the old Scheduler+Chance used
|
This essentially does what the old Scheduler+Chance used
|
||||||
to give us."""
|
to give us."""
|
||||||
|
|
||||||
def instance_type_to_query(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
|
||||||
exception."""
|
exception."""
|
||||||
return (self._full_name(), instance_type)
|
return (self._full_name(), instance_type)
|
||||||
@@ -72,10 +81,10 @@ class AllHostsQuery(Query):
|
|||||||
for host, services in zone_manager.service_states.iteritems()]
|
for host, services in zone_manager.service_states.iteritems()]
|
||||||
|
|
||||||
|
|
||||||
class FlavorQuery(Query):
|
class FlavorFilter(HostFilter):
|
||||||
"""Query plug-in hard-coded to work with flavors."""
|
"""HostFilter driver hard-coded to work with flavors."""
|
||||||
|
|
||||||
def instance_type_to_query(self, instance_type):
|
def instance_type_to_filter(self, instance_type):
|
||||||
"""Use instance_type to filter hosts."""
|
"""Use instance_type to filter hosts."""
|
||||||
return (self._full_name(), instance_type)
|
return (self._full_name(), instance_type)
|
||||||
|
|
||||||
@@ -119,8 +128,9 @@ class FlavorQuery(Query):
|
|||||||
#rxtx_cap = Column(Integer, nullable=False, default=0)
|
#rxtx_cap = Column(Integer, nullable=False, default=0)
|
||||||
|
|
||||||
|
|
||||||
class JsonQuery(Query):
|
class JsonFilter(HostFilter):
|
||||||
"""Query plug-in to allow simple JSON-based grammar for selecting hosts."""
|
"""Host Filter driver to allow simple JSON-based grammar for
|
||||||
|
selecting hosts."""
|
||||||
|
|
||||||
def _equals(self, args):
|
def _equals(self, args):
|
||||||
"""First term is == all the other terms."""
|
"""First term is == all the other terms."""
|
||||||
@@ -204,8 +214,8 @@ class JsonQuery(Query):
|
|||||||
'and': _and,
|
'and': _and,
|
||||||
}
|
}
|
||||||
|
|
||||||
def instance_type_to_query(self, instance_type):
|
def instance_type_to_filter(self, instance_type):
|
||||||
"""Convert instance_type into JSON query object."""
|
"""Convert instance_type into JSON filter object."""
|
||||||
required_ram = instance_type['memory_mb']
|
required_ram = instance_type['memory_mb']
|
||||||
required_disk = instance_type['local_gb']
|
required_disk = instance_type['local_gb']
|
||||||
query = ['and',
|
query = ['and',
|
||||||
@@ -229,7 +239,7 @@ class JsonQuery(Query):
|
|||||||
return None
|
return None
|
||||||
return services
|
return services
|
||||||
|
|
||||||
def _process_query(self, zone_manager, query, host, services):
|
def _process_filter(self, zone_manager, query, host, services):
|
||||||
"""Recursively parse the query structure."""
|
"""Recursively parse the query structure."""
|
||||||
if len(query) == 0:
|
if len(query) == 0:
|
||||||
return True
|
return True
|
||||||
@@ -238,7 +248,7 @@ class JsonQuery(Query):
|
|||||||
cooked_args = []
|
cooked_args = []
|
||||||
for arg in query[1:]:
|
for arg in query[1:]:
|
||||||
if isinstance(arg, list):
|
if isinstance(arg, list):
|
||||||
arg = self._process_query(zone_manager, arg, host, services)
|
arg = self._process_filter(zone_manager, arg, host, services)
|
||||||
elif isinstance(arg, basestring):
|
elif isinstance(arg, basestring):
|
||||||
arg = self._parse_string(arg, host, services)
|
arg = self._parse_string(arg, host, services)
|
||||||
if arg != None:
|
if arg != None:
|
||||||
@@ -247,11 +257,11 @@ class JsonQuery(Query):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def filter_hosts(self, zone_manager, query):
|
def filter_hosts(self, zone_manager, query):
|
||||||
"""Return a list of hosts that can fulfill query."""
|
"""Return a list of hosts that can fulfill filter."""
|
||||||
expanded = json.loads(query)
|
expanded = json.loads(query)
|
||||||
hosts = []
|
hosts = []
|
||||||
for host, services in zone_manager.service_states.iteritems():
|
for host, services in zone_manager.service_states.iteritems():
|
||||||
r = self._process_query(zone_manager, expanded, host, services)
|
r = self._process_filter(zone_manager, expanded, host, services)
|
||||||
if isinstance(r, list):
|
if isinstance(r, list):
|
||||||
r = True in r
|
r = True in r
|
||||||
if r:
|
if r:
|
||||||
@@ -259,7 +269,7 @@ class JsonQuery(Query):
|
|||||||
return hosts
|
return hosts
|
||||||
|
|
||||||
|
|
||||||
DRIVERS = [AllHostsQuery, FlavorQuery, JsonQuery]
|
DRIVERS = [AllHostsFilter, FlavorFilter, JsonFilter]
|
||||||
|
|
||||||
|
|
||||||
def choose_driver(driver_name=None):
|
def choose_driver(driver_name=None):
|
||||||
@@ -269,8 +279,8 @@ def choose_driver(driver_name=None):
|
|||||||
of acceptable drivers."""
|
of acceptable drivers."""
|
||||||
|
|
||||||
if not driver_name:
|
if not driver_name:
|
||||||
driver_name = FLAGS.default_query_engine
|
driver_name = FLAGS.default_host_filter_driver
|
||||||
for driver in DRIVERS:
|
for driver in DRIVERS:
|
||||||
if "%s.%s" % (driver.__module__, driver.__name__) == driver_name:
|
if "%s.%s" % (driver.__module__, driver.__name__) == driver_name:
|
||||||
return driver()
|
return driver()
|
||||||
raise exception.SchedulerQueryDriverNotFound(driver_name=driver_name)
|
raise exception.SchedulerHostFilterDriverNotFound(driver_name=driver_name)
|
||||||
@@ -13,7 +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.
|
||||||
"""
|
"""
|
||||||
Tests For Scheduler Query Drivers
|
Tests For Scheduler Host Filter Drivers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -21,7 +21,7 @@ import json
|
|||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import flags
|
from nova import flags
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova.scheduler import query
|
from nova.scheduler import host_filter
|
||||||
|
|
||||||
FLAGS = flags.FLAGS
|
FLAGS = flags.FLAGS
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@ class FakeZoneManager:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class QueryTestCase(test.TestCase):
|
class HostFilterTestCase(test.TestCase):
|
||||||
"""Test case for query drivers."""
|
"""Test case for host filter drivers."""
|
||||||
|
|
||||||
def _host_caps(self, multiplier):
|
def _host_caps(self, multiplier):
|
||||||
# Returns host capabilities in the following way:
|
# Returns host capabilities in the following way:
|
||||||
@@ -57,8 +57,9 @@ class QueryTestCase(test.TestCase):
|
|||||||
'host_name-label': 'xs-%s' % multiplier}
|
'host_name-label': 'xs-%s' % multiplier}
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.old_flag = FLAGS.default_query_engine
|
self.old_flag = FLAGS.default_host_filter_driver
|
||||||
FLAGS.default_query_engine = 'nova.scheduler.query.AllHostsQuery'
|
FLAGS.default_host_filter_driver = \
|
||||||
|
'nova.scheduler.host_filter.AllHostsFilter'
|
||||||
self.instance_type = dict(name='tiny',
|
self.instance_type = dict(name='tiny',
|
||||||
memory_mb=50,
|
memory_mb=50,
|
||||||
vcpus=10,
|
vcpus=10,
|
||||||
@@ -75,37 +76,38 @@ class QueryTestCase(test.TestCase):
|
|||||||
self.zone_manager.service_states = states
|
self.zone_manager.service_states = states
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
FLAGS.default_query_engine = self.old_flag
|
FLAGS.default_host_filter_driver = self.old_flag
|
||||||
|
|
||||||
def test_choose_driver(self):
|
def test_choose_driver(self):
|
||||||
# Test default driver ...
|
# Test default driver ...
|
||||||
driver = query.choose_driver()
|
driver = host_filter.choose_driver()
|
||||||
self.assertEquals(driver._full_name(),
|
self.assertEquals(driver._full_name(),
|
||||||
'nova.scheduler.query.AllHostsQuery')
|
'nova.scheduler.host_filter.AllHostsFilter')
|
||||||
# Test valid driver ...
|
# Test valid driver ...
|
||||||
driver = query.choose_driver('nova.scheduler.query.FlavorQuery')
|
driver = host_filter.choose_driver(
|
||||||
|
'nova.scheduler.host_filter.FlavorFilter')
|
||||||
self.assertEquals(driver._full_name(),
|
self.assertEquals(driver._full_name(),
|
||||||
'nova.scheduler.query.FlavorQuery')
|
'nova.scheduler.host_filter.FlavorFilter')
|
||||||
# Test invalid driver ...
|
# Test invalid driver ...
|
||||||
try:
|
try:
|
||||||
query.choose_driver('does not exist')
|
host_filter.choose_driver('does not exist')
|
||||||
self.fail("Should not find driver")
|
self.fail("Should not find driver")
|
||||||
except exception.SchedulerQueryDriverNotFound:
|
except exception.SchedulerHostFilterDriverNotFound:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_all_host_driver(self):
|
def test_all_host_driver(self):
|
||||||
driver = query.AllHostsQuery()
|
driver = host_filter.AllHostsFilter()
|
||||||
cooked = driver.instance_type_to_query(self.instance_type)
|
cooked = driver.instance_type_to_filter(self.instance_type)
|
||||||
hosts = driver.filter_hosts(self.zone_manager, cooked)
|
hosts = driver.filter_hosts(self.zone_manager, cooked)
|
||||||
self.assertEquals(10, len(hosts))
|
self.assertEquals(10, len(hosts))
|
||||||
for host, capabilities in hosts:
|
for host, capabilities in hosts:
|
||||||
self.assertTrue(host.startswith('host'))
|
self.assertTrue(host.startswith('host'))
|
||||||
|
|
||||||
def test_flavor_driver(self):
|
def test_flavor_driver(self):
|
||||||
driver = query.FlavorQuery()
|
driver = host_filter.FlavorFilter()
|
||||||
# filter all hosts that can support 50 ram and 500 disk
|
# filter all hosts that can support 50 ram and 500 disk
|
||||||
name, cooked = driver.instance_type_to_query(self.instance_type)
|
name, cooked = driver.instance_type_to_filter(self.instance_type)
|
||||||
self.assertEquals('nova.scheduler.query.FlavorQuery', name)
|
self.assertEquals('nova.scheduler.host_filter.FlavorFilter', name)
|
||||||
hosts = driver.filter_hosts(self.zone_manager, cooked)
|
hosts = driver.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]
|
||||||
@@ -114,10 +116,10 @@ class QueryTestCase(test.TestCase):
|
|||||||
self.assertEquals('host10', just_hosts[5])
|
self.assertEquals('host10', just_hosts[5])
|
||||||
|
|
||||||
def test_json_driver(self):
|
def test_json_driver(self):
|
||||||
driver = query.JsonQuery()
|
driver = host_filter.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 = driver.instance_type_to_query(self.instance_type)
|
name, cooked = driver.instance_type_to_filter(self.instance_type)
|
||||||
self.assertEquals('nova.scheduler.query.JsonQuery', name)
|
self.assertEquals('nova.scheduler.host_filter.JsonFilter', name)
|
||||||
hosts = driver.filter_hosts(self.zone_manager, cooked)
|
hosts = driver.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]
|
||||||
Reference in New Issue
Block a user