2
0
mirror of https://github.com/xcat2/confluent.git synced 2025-01-17 21:23:18 +00:00

Add regex names and attribute match to noderange

Enhance noderange to implement:
attribute.name==value (also attribute.name=value) -- strict equality match
attribute.name=~value -- regular expression match
attribute.name!=value -- strict negative match
attribute.name!~value -- negative regular expression match
~nameexpression -- search by node name using regular expression

This also goes a step further by intelligently limiting searches when found to the right of @ or ,-.
This commit is contained in:
Jarrod Johnson 2015-03-24 14:47:38 -04:00
parent 8b5d5b1f33
commit a85ffa8f8b
3 changed files with 85 additions and 11 deletions

View File

@ -444,6 +444,76 @@ class ConfigManager(object):
self._cfgstore['nodes'] = {}
self._bg_sync_to_file()
def filter_node_attributes(self, expression, nodes=None):
"""Filtered nodelist according to expression
expression may be:
attribute.name=value
attribute.name==value
attribute.name=~value
attribute.name!=value
attribute.name!~value
== and != do strict equality. The ~ operators do a regular expression.
! negates the sense of the match
:param expression: The expression containing the criteria to match
:param nodes: Optional iterable set of nodes to limit the check
"""
exmatch = None
yieldmatches = True
if nodes is None:
nodes = self._cfgstore['nodes']
if '==' in expression:
attribute, match = expression.split('==')
elif '!=' in expression:
attribute, match = expression.split('!=')
yieldmatches = False
elif '=~' in expression:
attribute, match = expression.split('=~')
exmatch = re.compile(match)
elif '!~' in expression:
attribute, match = expression.split('!~')
exmatch = re.compile(match)
yieldmatches = False
elif '=' in expression:
attribute, match = expression.split('=')
else:
raise Exception('Invalid Expression')
for node in nodes:
try:
currval = self._cfgstore['nodes'][node][attribute]['value']
except KeyError:
# Let's treat 'not set' as being an empty string for this path
currval = ''
if exmatch:
if yieldmatches:
if exmatch.search(currval):
yield node
else:
if not exmatch.search(currval):
yield node
else:
if yieldmatches:
if match == currval:
yield node
else:
if match != currval:
yield node
def filter_nodenames(self, expression, nodes=None):
"""Filter nodenames by regular expression
:param expression: Regular expression for matching nodenames
:param nodes: Optional iterable of candidates
"""
if nodes is None:
nodes = self._cfgstore['nodes']
expression = re.compile(expression)
for node in nodes:
if expression.search(node):
yield node
def watch_attributes(self, nodes, attributes, callback):
"""
Watch a list of attributes for changes on a list of nodes
@ -680,7 +750,7 @@ class ConfigManager(object):
cfgnodeobj = self._cfgstore['nodes'][node]
nodeobj = {}
if len(attributes) == 0:
relattribs = cfgnodeobj.iterkeys()
relattribs = cfgnodeobj
for attribute in relattribs:
if attribute.startswith('_'):
# skip private things

View File

@ -269,7 +269,6 @@ def enumerate_node_collection(collectionpath, configmanager):
collection = nested_lookup(noderesources, collectionpath[2:])
if len(collectionpath) == 2 and collectionpath[0] == 'noderange':
collection['nodes'] = {}
print repr(collection)
if not isinstance(collection, dict):
raise exc.NotFoundException("Invalid element requested")
return iterate_resources(collection)

View File

@ -24,7 +24,7 @@ import itertools
import pyparsing as pp
# construct custom grammar with pyparsing
_nodeword = pp.Word(pp.alphanums + '/=-:.*+')
_nodeword = pp.Word(pp.alphanums + '~^$/=-:.*+!')
_nodebracket = pp.QuotedString(quoteChar='[', endQuoteChar=']',
unquoteResults=False)
_nodeatom = pp.Group(pp.OneOrMore(_nodeword | _nodebracket))
@ -51,11 +51,11 @@ class NodeRange(object):
def nodes(self):
return self._noderange
def _evaluate(self, parsetree):
def _evaluate(self, parsetree, filternodes=None):
current_op = 0 # enum, 0 union, 1 subtract, 2 intersect
current_range = set([])
if not isinstance(parsetree[0], list): # down to a plain text thing
return self._expandstring(parsetree)
return self._expandstring(parsetree, filternodes)
for elem in parsetree:
if elem == ',-':
current_op = 1
@ -66,9 +66,9 @@ class NodeRange(object):
elif current_op == 0:
current_range |= self._evaluate(elem)
elif current_op == 1:
current_range -= self._evaluate(elem)
current_range -= self._evaluate(elem, current_range)
elif current_op == 2:
current_range &= self._evaluate(elem)
current_range &= self._evaluate(elem, current_range)
return current_range
def failorreturn(self, atom):
@ -134,7 +134,7 @@ class NodeRange(object):
grpcfg['noderange']['value'], self.cfm).nodes
return nodes
def _expandstring(self, element):
def _expandstring(self, element, filternodes=None):
prefix = ''
for idx in xrange(len(element)):
if element[idx][0] == '[':
@ -161,10 +161,15 @@ class NodeRange(object):
return self.expandrange(element, '-')
elif ':' in element: # : range for less ambiguity
return self.expandrange(element, ':')
elif '=' in element:
raise Exception('TODO: criteria noderange')
elif '=' in element or '!~' in element:
if self.cfm is None:
raise Exception('Verification configmanager required')
return set(self.cfm.filter_node_attributes(element, filternodes))
elif element[0] in ('/', '~'):
raise Exception('TODO: regex noderange')
nameexpression = element[1:]
if self.cfm is None:
raise Exception('Verification configmanager required')
return set(self.cfm.filter_nodenames(nameexpression, filternodes))
elif '+' in element:
raise Exception('TODO: plus range')
if self.cfm is None: