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