forked from openstack/cinder
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add driver filter and evaluator for scheduler
This patch adds a new filter for the cinder scheduler that can interpret two new properties provided by backends, 'filter_function' and 'goodness_function'. A driver can rely on cinder.conf entries to define these properties for a backend or the driver can generate them some other way. An evaluator is used by the filter to parse the properties. The 'goodness_function' property is used to weigh qualified backends in case multiple ones pass the filter. More details can be found in the spec: https://review.openstack.org/#/c/129330/ Implements: blueprint filtering-weighing-with-driver-supplied-functions DocImpact: New optional backend properties in cinder.conf. New filter and weigher available for scheduler. Change-Id: I38408ab49b6ed869c1faae746ee64a3bae86be58
- Loading branch information
Showing
10 changed files
with
1,177 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,297 @@ | ||
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. | ||
# 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. | ||
|
||
import operator | ||
import re | ||
|
||
import pyparsing | ||
import six | ||
|
||
from cinder import exception | ||
from cinder.i18n import _ | ||
|
||
|
||
def _operatorOperands(tokenList): | ||
it = iter(tokenList) | ||
while 1: | ||
try: | ||
op1 = next(it) | ||
op2 = next(it) | ||
yield(op1, op2) | ||
except StopIteration: | ||
break | ||
|
||
|
||
class EvalConstant(object): | ||
def __init__(self, toks): | ||
self.value = toks[0] | ||
|
||
def eval(self): | ||
result = self.value | ||
if (isinstance(result, six.string_types) and | ||
re.match("^[a-zA-Z_]+\.[a-zA-Z_]+$", result)): | ||
(which_dict, entry) = result.split('.') | ||
try: | ||
result = _vars[which_dict][entry] | ||
except KeyError as e: | ||
msg = _("KeyError: %s") % e | ||
raise exception.EvaluatorParseException(msg) | ||
except TypeError as e: | ||
msg = _("TypeError: %s") % e | ||
raise exception.EvaluatorParseException(msg) | ||
|
||
try: | ||
result = int(result) | ||
except ValueError: | ||
try: | ||
result = float(result) | ||
except ValueError as e: | ||
msg = _("ValueError: %s") % e | ||
raise exception.EvaluatorParseException(msg) | ||
|
||
return result | ||
|
||
|
||
class EvalSignOp(object): | ||
operations = { | ||
'+': 1, | ||
'-': -1, | ||
} | ||
|
||
def __init__(self, toks): | ||
self.sign, self.value = toks[0] | ||
|
||
def eval(self): | ||
return self.operations[self.sign] * self.value.eval() | ||
|
||
|
||
class EvalAddOp(object): | ||
def __init__(self, toks): | ||
self.value = toks[0] | ||
|
||
def eval(self): | ||
sum = self.value[0].eval() | ||
for op, val in _operatorOperands(self.value[1:]): | ||
if op == '+': | ||
sum += val.eval() | ||
elif op == '-': | ||
sum -= val.eval() | ||
return sum | ||
|
||
|
||
class EvalMultOp(object): | ||
def __init__(self, toks): | ||
self.value = toks[0] | ||
|
||
def eval(self): | ||
prod = self.value[0].eval() | ||
for op, val in _operatorOperands(self.value[1:]): | ||
try: | ||
if op == '*': | ||
prod *= val.eval() | ||
elif op == '/': | ||
prod /= float(val.eval()) | ||
except ZeroDivisionError as e: | ||
msg = _("ZeroDivisionError: %s") % e | ||
raise exception.EvaluatorParseException(msg) | ||
return prod | ||
|
||
|
||
class EvalPowerOp(object): | ||
def __init__(self, toks): | ||
self.value = toks[0] | ||
|
||
def eval(self): | ||
prod = self.value[0].eval() | ||
for op, val in _operatorOperands(self.value[1:]): | ||
prod = pow(prod, val.eval()) | ||
return prod | ||
|
||
|
||
class EvalNegateOp(object): | ||
def __init__(self, toks): | ||
self.negation, self.value = toks[0] | ||
|
||
def eval(self): | ||
return not self.value.eval() | ||
|
||
|
||
class EvalComparisonOp(object): | ||
operations = { | ||
"<": operator.lt, | ||
"<=": operator.le, | ||
">": operator.gt, | ||
">=": operator.ge, | ||
"!=": operator.ne, | ||
"==": operator.eq, | ||
"<>": operator.ne, | ||
} | ||
|
||
def __init__(self, toks): | ||
self.value = toks[0] | ||
|
||
def eval(self): | ||
val1 = self.value[0].eval() | ||
for op, val in _operatorOperands(self.value[1:]): | ||
fn = self.operations[op] | ||
val2 = val.eval() | ||
if not fn(val1, val2): | ||
break | ||
val1 = val2 | ||
else: | ||
return True | ||
return False | ||
|
||
|
||
class EvalTernaryOp(object): | ||
def __init__(self, toks): | ||
self.value = toks[0] | ||
|
||
def eval(self): | ||
condition = self.value[0].eval() | ||
if condition: | ||
return self.value[2].eval() | ||
else: | ||
return self.value[4].eval() | ||
|
||
|
||
class EvalFunction(object): | ||
functions = { | ||
"abs": abs, | ||
"max": max, | ||
"min": min, | ||
} | ||
|
||
def __init__(self, toks): | ||
self.func, self.value = toks[0] | ||
|
||
def eval(self): | ||
args = self.value.eval() | ||
if type(args) is list: | ||
return self.functions[self.func](*args) | ||
else: | ||
return self.functions[self.func](args) | ||
|
||
|
||
class EvalCommaSeperator(object): | ||
def __init__(self, toks): | ||
self.value = toks[0] | ||
|
||
def eval(self): | ||
val1 = self.value[0].eval() | ||
val2 = self.value[2].eval() | ||
if type(val2) is list: | ||
val_list = [] | ||
val_list.append(val1) | ||
for val in val2: | ||
val_list.append(val) | ||
return val_list | ||
|
||
return [val1, val2] | ||
|
||
|
||
class EvalBoolAndOp(object): | ||
def __init__(self, toks): | ||
self.value = toks[0] | ||
|
||
def eval(self): | ||
left = self.value[0].eval() | ||
right = self.value[2].eval() | ||
return left and right | ||
|
||
|
||
class EvalBoolOrOp(object): | ||
def __init__(self, toks): | ||
self.value = toks[0] | ||
|
||
def eval(self): | ||
left = self.value[0].eval() | ||
right = self.value[2].eval() | ||
return left or right | ||
|
||
_parser = None | ||
_vars = {} | ||
|
||
|
||
def _def_parser(): | ||
# Enabling packrat parsing greatly speeds up the parsing. | ||
pyparsing.ParserElement.enablePackrat() | ||
|
||
alphas = pyparsing.alphas | ||
Combine = pyparsing.Combine | ||
Forward = pyparsing.Forward | ||
nums = pyparsing.nums | ||
oneOf = pyparsing.oneOf | ||
opAssoc = pyparsing.opAssoc | ||
operatorPrecedence = pyparsing.operatorPrecedence | ||
Word = pyparsing.Word | ||
|
||
integer = Word(nums) | ||
real = Combine(Word(nums) + '.' + Word(nums)) | ||
variable = Word(alphas + '_' + '.') | ||
number = real | integer | ||
expr = Forward() | ||
fn = Word(alphas + '_' + '.') | ||
operand = number | variable | fn | ||
|
||
signop = oneOf('+ -') | ||
addop = oneOf('+ -') | ||
multop = oneOf('* /') | ||
comparisonop = oneOf(' '.join(EvalComparisonOp.operations.keys())) | ||
ternaryop = ('?', ':') | ||
boolandop = oneOf('AND and &&') | ||
boolorop = oneOf('OR or ||') | ||
negateop = oneOf('NOT not !') | ||
|
||
operand.setParseAction(EvalConstant) | ||
expr = operatorPrecedence(operand, [ | ||
(fn, 1, opAssoc.RIGHT, EvalFunction), | ||
("^", 2, opAssoc.RIGHT, EvalPowerOp), | ||
(signop, 1, opAssoc.RIGHT, EvalSignOp), | ||
(multop, 2, opAssoc.LEFT, EvalMultOp), | ||
(addop, 2, opAssoc.LEFT, EvalAddOp), | ||
(negateop, 1, opAssoc.RIGHT, EvalNegateOp), | ||
(comparisonop, 2, opAssoc.LEFT, EvalComparisonOp), | ||
(ternaryop, 3, opAssoc.LEFT, EvalTernaryOp), | ||
(boolandop, 2, opAssoc.LEFT, EvalBoolAndOp), | ||
(boolorop, 2, opAssoc.LEFT, EvalBoolOrOp), | ||
(',', 2, opAssoc.RIGHT, EvalCommaSeperator), ]) | ||
|
||
return expr | ||
|
||
|
||
def evaluate(expression, **kwargs): | ||
"""Evaluates an expression. | ||
Provides the facility to evaluate mathematical expressions, and to | ||
substitute variables from dictionaries into those expressions. | ||
Supports both integer and floating point values, and automatic | ||
promotion where necessary. | ||
""" | ||
global _parser | ||
if _parser is None: | ||
_parser = _def_parser() | ||
|
||
global _vars | ||
_vars = kwargs | ||
|
||
try: | ||
result = _parser.parseString(expression, parseAll=True)[0] | ||
except pyparsing.ParseException as e: | ||
msg = _("ParseException: %s") % e | ||
raise exception.EvaluatorParseException(msg) | ||
|
||
return result.eval() |
Oops, something went wrong.