14
14
# under the License.
17
Host Filter is a mechanism for requesting instance resources.
18
Three filters are included: AllHosts, Flavor & JSON. AllHosts just
19
returns the full, unfiltered list of hosts. Flavor is a hard coded
20
matching mechanism based on flavor criteria and JSON is an ad-hoc
23
Why JSON? The requests for instances may come in through the
24
REST interface from a user or a parent Zone.
25
Currently Flavors and/or InstanceTypes are used for
26
specifing the type of instance desired. Specific Nova users have
27
noted a need for a more expressive way of specifying instances.
28
Since we don't want to get into building full DSL this is a simple
29
form as an example of how this could be done. In reality, most
30
consumers will use the more rigid filters such as FlavorFilter.
32
Note: These are "required" capability filters. These capabilities
33
used must be present or the host will be excluded. The hosts
34
returned are then weighed by the Weighted Scheduler. Weights
35
can take the more esoteric factors into consideration (such as
36
server affinity and customer separation).
17
The Host Filter classes are a way to ensure that only hosts that are
18
appropriate are considered when creating a new instance. Hosts that are
19
either incompatible or insufficient to accept a newly-requested instance
20
are removed by Host Filter classes from consideration. Those that pass
21
the filter are then passed on for weighting or other process for ordering.
23
Filters are in the 'filters' directory that is off the 'scheduler'
24
directory of nova. Additional filters can be created and added to that
25
directory; be sure to add them to the filters/__init__.py file so that
26
they are part of the nova.schedulers.filters namespace.
41
31
from nova import exception
42
32
from nova import flags
43
from nova import log as logging
44
from nova.scheduler import zone_aware_scheduler
45
from nova import utils
46
from nova.scheduler import zone_aware_scheduler
48
LOG = logging.getLogger('nova.scheduler.host_filter')
50
36
FLAGS = flags.FLAGS
51
flags.DEFINE_string('default_host_filter',
52
'nova.scheduler.host_filter.AllHostsFilter',
53
'Which filter to use for filtering hosts.')
56
class HostFilter(object):
57
"""Base class for host filters."""
59
def instance_type_to_filter(self, instance_type):
60
"""Convert instance_type into a filter for most common use-case."""
61
raise NotImplementedError()
63
def filter_hosts(self, zone_manager, query):
64
"""Return a list of hosts that fulfill the filter."""
65
raise NotImplementedError()
68
"""module.classname of the filter."""
69
return "%s.%s" % (self.__module__, self.__class__.__name__)
72
class AllHostsFilter(HostFilter):
73
""" NOP host filter. Returns all hosts in ZoneManager.
74
This essentially does what the old Scheduler+Chance used
78
def instance_type_to_filter(self, instance_type):
79
"""Return anything to prevent base-class from raising
81
return (self._full_name(), instance_type)
83
def filter_hosts(self, zone_manager, query):
84
"""Return a list of hosts from ZoneManager list."""
85
return [(host, services)
86
for host, services in zone_manager.service_states.iteritems()]
89
class InstanceTypeFilter(HostFilter):
90
"""HostFilter hard-coded to work with InstanceType records."""
92
def instance_type_to_filter(self, instance_type):
93
"""Use instance_type to filter hosts."""
94
return (self._full_name(), instance_type)
96
def _satisfies_extra_specs(self, capabilities, instance_type):
97
"""Check that the capabilities provided by the compute service
98
satisfy the extra specs associated with the instance type"""
100
if 'extra_specs' not in instance_type:
103
# Note(lorinh): For now, we are just checking exact matching on the
104
# values. Later on, we want to handle numerical
105
# values so we can represent things like number of GPU cards
108
for key, value in instance_type['extra_specs'].iteritems():
109
if capabilities[key] != value:
116
def filter_hosts(self, zone_manager, query):
117
"""Return a list of hosts that can create instance_type."""
118
instance_type = query
120
for host, services in zone_manager.service_states.iteritems():
121
capabilities = services.get('compute', {})
122
host_ram_mb = capabilities['host_memory_free']
123
disk_bytes = capabilities['disk_available']
124
spec_ram = instance_type['memory_mb']
125
spec_disk = instance_type['local_gb']
126
extra_specs = instance_type['extra_specs']
128
if host_ram_mb >= spec_ram and \
129
disk_bytes >= spec_disk and \
130
self._satisfies_extra_specs(capabilities, instance_type):
131
selected_hosts.append((host, capabilities))
132
return selected_hosts
134
#host entries (currently) are like:
135
# {'host_name-description': 'Default install of XenServer',
136
# 'host_hostname': 'xs-mini',
137
# 'host_memory_total': 8244539392,
138
# 'host_memory_overhead': 184225792,
139
# 'host_memory_free': 3868327936,
140
# 'host_memory_free_computed': 3840843776,
141
# 'host_other_config': {},
142
# 'host_ip_address': '192.168.1.109',
143
# 'host_cpu_info': {},
144
# 'disk_available': 32954957824,
145
# 'disk_total': 50394562560,
146
# 'disk_used': 17439604736,
147
# 'host_uuid': 'cedb9b39-9388-41df-8891-c5c9a0c0fe5f',
148
# 'host_name_label': 'xs-mini'}
150
# instance_type table has:
151
#name = Column(String(255), unique=True)
152
#memory_mb = Column(Integer)
153
#vcpus = Column(Integer)
154
#local_gb = Column(Integer)
155
#flavorid = Column(Integer, unique=True)
156
#swap = Column(Integer, nullable=False, default=0)
157
#rxtx_quota = Column(Integer, nullable=False, default=0)
158
#rxtx_cap = Column(Integer, nullable=False, default=0)
161
class JsonFilter(HostFilter):
162
"""Host Filter to allow simple JSON-based grammar for
166
def _equals(self, args):
167
"""First term is == all the other terms."""
176
def _less_than(self, args):
177
"""First term is < all the other terms."""
186
def _greater_than(self, args):
187
"""First term is > all the other terms."""
197
"""First term is in set of remaining terms"""
200
return args[0] in args[1:]
202
def _less_than_equal(self, args):
203
"""First term is <= all the other terms."""
212
def _greater_than_equal(self, args):
213
"""First term is >= all the other terms."""
222
def _not(self, args):
223
"""Flip each of the arguments."""
226
return [not arg for arg in args]
229
"""True if any arg is True."""
232
def _and(self, args):
233
"""True if all args are True."""
234
return False not in args
241
'<=': _less_than_equal,
242
'>=': _greater_than_equal,
248
def instance_type_to_filter(self, instance_type):
249
"""Convert instance_type into JSON filter object."""
250
required_ram = instance_type['memory_mb']
251
required_disk = instance_type['local_gb']
253
['>=', '$compute.host_memory_free', required_ram],
254
['>=', '$compute.disk_available', required_disk]]
255
return (self._full_name(), json.dumps(query))
257
def _parse_string(self, string, host, services):
258
"""Strings prefixed with $ are capability lookups in the
259
form '$service.capability[.subcap*]'
266
path = string[1:].split('.')
268
services = services.get(item, None)
273
def _process_filter(self, zone_manager, query, host, services):
274
"""Recursively parse the query structure."""
278
method = self.commands[cmd] # Let exception fly.
280
for arg in query[1:]:
281
if isinstance(arg, list):
282
arg = self._process_filter(zone_manager, arg, host, services)
283
elif isinstance(arg, basestring):
284
arg = self._parse_string(arg, host, services)
286
cooked_args.append(arg)
287
result = method(self, cooked_args)
290
def filter_hosts(self, zone_manager, query):
291
"""Return a list of hosts that can fulfill filter."""
292
expanded = json.loads(query)
294
for host, services in zone_manager.service_states.iteritems():
295
r = self._process_filter(zone_manager, expanded, host, services)
296
if isinstance(r, list):
299
hosts.append((host, services))
303
FILTERS = [AllHostsFilter, InstanceTypeFilter, JsonFilter]
40
# Imported here to avoid circular imports
41
from nova.scheduler import filters
44
return getattr(filters, nm)
46
return [get_itm(itm) for itm in dir(filters)
47
if (type(get_itm(itm)) is types.TypeType)
48
and issubclass(get_itm(itm), filters.AbstractHostFilter)
49
and get_itm(itm) is not filters.AbstractHostFilter]
306
52
def choose_host_filter(filter_name=None):