Skip to content

Commit

Permalink
Add driver filter and evaluator for scheduler
Browse files Browse the repository at this point in the history
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
leeantho committed Jan 6, 2015
1 parent 48eb05a commit 59bf887
Show file tree
Hide file tree
Showing 10 changed files with 1,177 additions and 0 deletions.
4 changes: 4 additions & 0 deletions cinder/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,10 @@ class ExtendVolumeError(CinderException):
message = _("Error extending volume: %(reason)s")


class EvaluatorParseException(Exception):
message = _("Error during evaluator parsing: %(reason)s")


# Driver specific exceptions
# Coraid
class CoraidException(VolumeDriverException):
Expand Down
Empty file.
297 changes: 297 additions & 0 deletions cinder/scheduler/evaluator/evaluator.py
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()
Loading

0 comments on commit 59bf887

Please sign in to comment.