terminology: no more plug-ins or queries. They are host filters and drivers.

This commit is contained in:
Sandy Walsh
2011-05-05 18:09:11 -07:00
parent eb2efc7b40
commit fd94dbb2d5
2 changed files with 66 additions and 54 deletions

View File

@@ -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)

View File

@@ -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]