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