Package flumotion :: Package component :: Package bouncers :: Module combinator
[hide private]

Source Code for Module flumotion.component.bouncers.combinator

  1  # -*- Mode: Python -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3   
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. 
  6  # Copyright (C) 2010,2011 Flumotion Services, S.A. 
  7  # All rights reserved. 
  8  # 
  9  # This file may be distributed and/or modified under the terms of 
 10  # the GNU Lesser General Public License version 2.1 as published by 
 11  # the Free Software Foundation. 
 12  # This file is distributed without any warranty; without even the implied 
 13  # warranty of merchantability or fitness for a particular purpose. 
 14  # See "LICENSE.LGPL" in the source distribution for more information. 
 15  # 
 16  # Headers in this file shall remain intact. 
 17   
 18  """ 
 19  A bouncer-algorithm combinator, using the pyparsing module and a simple logic 
 20  expression language. 
 21  """ 
 22   
 23  try: 
 24      import pyparsing 
 25  except ImportError: 
 26      pyparsing = None 
 27   
 28  from twisted.internet import defer 
 29   
 30  from flumotion.common import keycards, log 
 31   
 32   
33 -class ParseException(Exception):
34 """ 35 Error parsing combination specification. 36 37 @cvar line: Line that triggered the error. 38 @type line: string 39 """
40 41
42 -class CombinatorNode(object, log.Loggable):
43 44 logCategory = "combinatornode"
45 46
47 -class NotNode(CombinatorNode):
48 49 logCategory = "notnode" 50
51 - def __init__(self, tokens):
52 self.child = tokens[0][1] 53 self.debug("creating combinator node using %r", self.child)
54
55 - def evaluate(self, keycard, context):
56 d = self.child.evaluate(keycard, context) 57 return d.addCallback(lambda (res, volatile): (not res, volatile))
58
59 - def synchronous_evaluate(self, context):
60 return not self.child.synchronous_evaluate(context)
61 62
63 -class AndNode(CombinatorNode):
64 65 logCategory = "andnode" 66
67 - def __init__(self, tokens):
68 self.children = tokens[0][0::2] 69 self.debug("creating combinator node using %r", self.children)
70
71 - def evaluate(self, keycard, context):
72 results = [(True, False)] 73 74 d = defer.Deferred() 75 76 for child in self.children: 77 d.addCallback(self.set_result, keycard, 78 child, results, context) 79 80 def decide_result(_): 81 # nonvolatile False is nonvolatile False 82 if results[-1] == (False, False): 83 return False, False 84 85 assert len(results) - 1 == len(self.children) 86 87 result, volatile = True, False 88 for res, vol in results: 89 if not res: 90 assert vol 91 result = False 92 if vol: 93 volatile = True 94 return result, volatile
95 96 d.addCallback(decide_result) 97 d.callback(None) 98 return d
99
100 - def set_result(self, _, keycard, child, results, context):
101 self.log("processing results %r", results) 102 103 # nonvolatile False is instant failure 104 if results[-1] == (False, False): 105 return 106 107 d = child.evaluate(keycard, context) 108 return d.addCallback(lambda (res, volatile): 109 results.append((res, volatile)))
110
111 - def synchronous_evaluate(self, context):
112 for child in self.children: 113 if not child.synchronous_evaluate(context): 114 return False 115 return True
116 117
118 -class OrNode(CombinatorNode):
119 120 logCategory = "ornode" 121
122 - def __init__(self, tokens):
123 self.children = tokens[0][0::2] 124 self.debug("creating combinator node using %r", self.children)
125
126 - def evaluate(self, keycard, context):
127 results = [(False, False)] 128 129 d = defer.Deferred() 130 131 for child in self.children: 132 d.addCallback(self.set_result, keycard, 133 child, results, context) 134 135 def decide_result(_): 136 # nonvolatile True is nonvolatile True 137 if results[-1] == (True, False): 138 return True, False 139 140 assert len(results) - 1 == len(self.children) 141 142 result, volatile = False, False 143 for res, vol in results: 144 if res: 145 assert vol 146 result = True 147 if vol: 148 volatile = True 149 return result, volatile
150 151 d.addCallback(decide_result) 152 d.callback(None) 153 return d
154
155 - def set_result(self, _, keycard, child, results, context):
156 self.log("processing results %r", results) 157 158 # nonvolatile True is instant success 159 if results[-1] == (True, False): 160 return 161 162 d = child.evaluate(keycard, context) 163 return d.addCallback(lambda (res, volatile): 164 results.append((res, volatile)))
165
166 - def synchronous_evaluate(self, context):
167 for child in self.children: 168 if child.synchronous_evaluate(context): 169 return True 170 return False
171 172
173 -class AlgorithmNode(CombinatorNode):
174 175 logCategory = "algorithmnode" 176
177 - def __init__(self, name, call_function, volatile):
178 self.debug("creating combinator node %r", name) 179 self.name = name 180 self.call_function = call_function 181 self.volatile = volatile
182
183 - def get_state_and_reset(self, keycard, context):
184 ret = bool(keycard and keycard.state == keycards.AUTHENTICATED) 185 self.debug("node %r got response from algorithm for keycard %r: %r", 186 self.name, keycard, ret) 187 if keycard: 188 keycard.state = keycards.REQUESTING 189 context[self.name] = ret 190 return ret, self.result_volatile(ret)
191
192 - def evaluate(self, keycard, context):
193 self.log("node %r evaluating %r in context %r", 194 self.name, keycard, context) 195 if self.name in context: 196 self.log("node %r found value in context: %r", 197 self.name, context[self.name]) 198 result = context[self.name] 199 return defer.succeed((result, self.result_volatile(result))) 200 self.debug("node %r calling algorithm with keycard %r", 201 self.name, keycard) 202 d = defer.maybeDeferred(self.call_function, keycard) 203 return d.addCallback(self.get_state_and_reset, context)
204
205 - def result_volatile(self, result):
206 # failures are always nonvolatile 207 if not result: 208 return False 209 # success can be volatile depending on the bouncer 210 return self.volatile
211
212 - def synchronous_evaluate(self, context):
213 self.debug("node %r evaluating synchronously in context %r", 214 self.name, context) 215 return context[self.name]
216 217
218 -class AlgorithmCombinator(log.Loggable):
219 220 logCategory = 'combinator' 221
222 - def __init__(self, algorithms):
223 self.algorithms = algorithms # name -> algorithm class
224
225 - def call_algorithm(self, name, keycard):
226 return self.algorithms[name].authenticate(keycard)
227
228 - def create_combination(self, combination_spec):
229 if pyparsing is None: 230 self.create_fake_combination() 231 return 232 233 parser = self.create_parser(self.call_algorithm) 234 try: 235 self.combination = parser.parseString(combination_spec)[0] 236 except pyparsing.ParseException, e: 237 raise ParseException(e.line)
238
239 - def create_fake_combination(self):
240 self.combination = AndNode([[]]) 241 self.combination.children = [ 242 AlgorithmNode(name, algorithm.authenticate, algorithm.volatile) 243 for name, algorithm in self.algorithms.items()]
244
245 - def evaluate(self, keycard, context):
246 d = self.combination.evaluate(keycard, context) 247 return d.addCallback(lambda (ret, volatile): ret)
248
249 - def synchronous_evaluate(self, context):
250 return self.combination.synchronous_evaluate(context)
251
252 - def create_parser(self, call_function):
253 254 def create_algorithm_node(tokens): 255 name = tokens[0] 256 algorithm = self.algorithms[name] 257 ret = AlgorithmNode(name, 258 algorithm.authenticate, 259 algorithm.volatile) 260 return ret
261 262 algorithm = pyparsing.oneOf(self.algorithms.keys()) 263 algorithm.setParseAction(create_algorithm_node) 264 265 openended_expr = pyparsing.operatorPrecedence( 266 algorithm, 267 [("not", 1, pyparsing.opAssoc.RIGHT, NotNode), 268 ("or", 2, pyparsing.opAssoc.LEFT, OrNode), 269 ("and", 2, pyparsing.opAssoc.LEFT, AndNode)]) 270 271 return openended_expr + pyparsing.StringEnd()
272