Fix a few bugs and re-add a visitor pattern for traversing the tree.

This commit is contained in:
Mike Auty
2016-02-20 23:01:41 +00:00
parent 71b540f6db
commit a9ff081da5
2 changed files with 118 additions and 25 deletions
@@ -46,12 +46,6 @@ class DependencyResolver(validity.ValidityRoutines):
set2 = set(value2)
return set1.intersection(set2)
def configurable_visitor(self, deptree, context, path = None, visitor = None):
"""Visits each provider and attempts to apply suitable information to help """
self._check_type(deptree, RequirementTreeList)
self._check_type(context, interfaces.context.ContextInterface)
self._check_type(visitor, interfaces.configuration.ConfigurableVisitorInterface)
def validate_dependencies(self, deptree, context, path = None):
"""Takes a dependency tree and attempts to resolve the tree by validating each branch and using the first that successfully validates
@@ -91,6 +85,23 @@ class DependencyResolver(validity.ValidityRoutines):
return False
return True
def validate_dependencies2(self, deptree, context, path = None):
"""Takes a dependency tree and attempts to resolve the tree by validating each branch and using the first that successfully validates
DEPTREE = [ REQUIREMENTS ... ]
REQUIREMENT = ( NODE | LEAF )
NODE = req, { candidate : DEPTREE, ... }
LEAF = req
@param path: A path to access the deptree's configuration details
"""
if path is None:
path = ""
self._check_type(deptree, RequirementTreeList)
visitor = ValidatorVisitor(context)
return deptree.traverse(visitor, path, True)
def build_tree(self, configurable):
"""Takes a configurable and produces a priority ordered tree of possible solutions to satisfy the various requirements
@@ -132,23 +143,58 @@ class DependencyError(Exception):
pass
class ValidatorVisitor(interfaces.configuration.ReqTreeVisitorInterface):
def __init__(self, context):
self.ctx = context
def __call__(self, node, config_path):
"""Returns whether a node is valid"""
if node.requirement is None:
return True
if isinstance(node, RequirementTreeChoice) and not node.requirement.optional:
for provider in node.candidates:
try:
provider.fulfill(self.ctx, node.requirement, config_path)
break
except Exception as e:
pass
else:
logging.debug(
"Unable to fulfill requirement " + repr(node.requirement) + " - no fulfillable candidates")
return False
try:
value = self.ctx.config[config_path]
node.requirement.validate(value, self.ctx)
return True
except Exception as e:
if not node.requirement.optional:
logging.debug(
"Unable to fulfill non-optional requirement " + repr(node.requirement) +
" [" + str(e) + "]")
return False
else:
return True
##########################
# Requirement tree classes
##########################
class RequirementTreeReq(interfaces.configuration.RequirementTreeNode):
def __init__(self, requirement = None):
validity.ValidityRoutines.__init__(self)
self._check_type(requirement, interfaces.configuration.RequirementInterface)
self.requirement = requirement
def __repr__(self):
return "<Leaf: " + repr(self.requirement) + ">"
@property
def optional(self):
return self.requirement.optional
def traverse(self, visitor, config_path = None, short_circuit = False):
if config_path is None:
config_path = self.requirement.name
else:
self._check_type(config_path, str)
config_path += interfaces.configuration.CONFIG_SEPARATOR + self.requirement.name
return visitor(self, config_path)
class RequirementTreeChoice(RequirementTreeReq):
@@ -164,17 +210,40 @@ class RequirementTreeChoice(RequirementTreeReq):
def __repr__(self):
return "<Choice: " + repr(self.requirement) + " Candidates: " + repr(self.candidates) + ">"
def traverse(self, visitor, config_path = None, short_circuit = False):
if config_path is None:
config_path = self.requirement.name
else:
self._check_type(config_path, str)
config_path += interfaces.configuration.CONFIG_SEPARATOR + self.requirement.name
success = False
for node in self.candidates.values():
success = success or node.traverse(visitor, config_path, short_circuit)
if short_circuit and success:
return visitor(self, config_path)
return visitor(self, config_path)
class RequirementTreeList(interfaces.configuration.RequirementTreeNode):
def __init__(self, children = None):
interfaces.configuration.RequirementTreeNode.__init__(self, None)
self._check_type(children, list)
for child in children:
self._check_type(child, interfaces.configuration.RequirementTreeNode)
self.children = children
@property
def optional(self):
return False
def __repr__(self):
return "<List Children: " + repr(self.children) + ">"
def traverse(self, visitor, config_path = None, short_circuit = False):
if config_path is None:
config_path = ""
self._check_type(config_path, str)
success = True
for node in self.children:
success = success and node.traverse(visitor, config_path, short_circuit)
if short_circuit and not success:
return False
return success and visitor(self, config_path)
@@ -1,7 +1,8 @@
from abc import ABCMeta, abstractmethod
# We must import interfaces.context this way, since we can't import our parent without cause a loop
from volatility.framework.interfaces import context as interfaces_context
from volatility.framework import validity
from volatility.framework.interfaces import context as context_interface
__author__ = 'mike'
@@ -59,7 +60,7 @@ class ConfigurableInterface(validity.ValidityRoutines):
def __init__(self, context, config_path):
validity.ValidityRoutines.__init__(self)
self._context = self._check_type(context, context_interface.ContextInterface)
self._context = self._check_type(context, interfaces_context.ContextInterface)
self._config_path = self._check_type(config_path, str)
@property
@@ -111,12 +112,35 @@ class ProviderInterface(ConfigurableInterface):
"""Fulfills a context's requirement, altering the context appropriately"""
class ConfigurableVisitorInterface(validity.ValidityRoutines):
pass
class RequirementTreeNode(validity.ValidityRoutines):
def __init__(self, requirement = None):
validity.ValidityRoutines.__init__(self)
if requirement is not None:
self._check_type(requirement, RequirementInterface)
self.requirement = requirement
@property
def optional(self):
"""Determines whether the elements within this tree are required for proper operation"""
return False
if self.requirement is None:
return False
return self.requirement.optional
def traverse(self, visitor, config_path = None, short_circuit = False):
"""Applies the function visitor to each node
The visitor callable should have a signature of visitor(node, config_path) => Bool
When short_circuit is True:
RequirementChoices will stop as soon as one traversal responds with True
RequirementLists will stop as soon as one traversal responds with False
When short_circuit is False the return value of children are always ignored
Returns the result from visitor applied to the node
"""
class ReqTreeVisitorInterface(validity.ValidityRoutines):
def __call__(self, node, config_path):
"""Gets call for a specific node and config_path"""
pass