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

Source Code for Module flumotion.component.bouncers.multibouncer

  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 that combines other bouncers. 
 20  """ 
 21   
 22  from twisted.internet import defer 
 23  from twisted.python import util 
 24   
 25  from flumotion.common import keycards, watched 
 26  from flumotion.common import messages, errors, documentation 
 27  from flumotion.common.i18n import N_, gettexter 
 28  from flumotion.component.bouncers import base, component, combinator 
 29   
 30  T_ = gettexter() 
 31   
 32   
33 -class MultiBouncer(component.Bouncer):
34 35 logCategory = 'multibouncer' 36 # FIXME random classes for now, they're going away anyway 37 keycardClasses = (keycards.KeycardHTTPGetArguments, 38 keycards.KeycardGeneric, keycards.KeycardUACPCC, 39 keycards.KeycardUACPP, keycards.KeycardToken) 40
41 - def init(self):
42 self.watchable_keycards = watched.WatchedDict() # keycard id -> Keycard 43 self.contexts = {} # keycard id -> {algorithm name -> bool result} 44 self.algorithms = util.OrderedDict() # name -> algorithm instance 45 self.combinator = None
46
47 - def do_setup(self):
48 49 def add_entry(entry, algorithm): 50 name = entry['type'] 51 if name in self.algorithms: 52 suffix = 1 53 while ('%s-%d' % (name, suffix)) in self.algorithms: 54 suffix += 1 55 name = '%s-%d' % (name, suffix) 56 57 assert name not in self.algorithms 58 self.algorithms[name] = algorithm 59 return name
60 61 # get all algorithm plugs this component has, put them into 62 # self.algorithms with unique names 63 entries = self.config['plugs'].get(base.BOUNCER_ALGORITHM_SOCKET, []) 64 algorithms = self.plugs.get(base.BOUNCER_ALGORITHM_SOCKET, []) 65 66 # check if there's at least one algorithm plug in a separate method, so 67 # subclasses can override it 68 self.check_algorithms(algorithms) 69 70 for entry, algorithm in zip(entries, algorithms): 71 # add the algorithm to the algorithms dictionary 72 name = add_entry(entry, algorithm) 73 # provide the algorithm with the keycard store 74 algorithm.set_keycard_store(self.watchable_keycards) 75 # provide the algorithm with an expiry function crafted especially 76 # for it (containing its unique name) 77 expire = lambda ids: self.algorithm_expire_keycard_ids(ids, name) 78 algorithm.set_expire_function(expire) 79 80 # we don't have any algorithms, stop here (see StaticMultiBouncer to 81 # see why we need this) 82 if not self.algorithms: 83 return 84 85 self.debug("configured with algorithms %r", self.algorithms.keys()) 86 87 # create the algorithm combinator 88 props = self.config['properties'] 89 self.combinator = combinator.AlgorithmCombinator(self.algorithms) 90 91 if 'combination' in props and combinator.pyparsing is None: 92 m = messages.Error(T_(N_( 93 "To use the 'combination' property you need to " 94 "have the 'pyparsing' module installed.\n")), 95 mid='missing-pyparsing') 96 documentation.messageAddPythonInstall(m, 'pyparsing') 97 self.addMessage(m) 98 raise errors.ComponentSetupHandledError() 99 100 # get the combination specification, defaulting to implicit AND 101 spec = props.get('combination', ' and '.join(self.algorithms.keys())) 102 self.debug("using combination %s", spec) 103 try: 104 self.combinator.create_combination(spec) 105 except combinator.ParseException, e: 106 m = messages.Error(T_(N_( 107 "Invalid algorithms combination: %s"), str(e)), 108 mid='wrong-combination') 109 self.addMessage(m) 110 raise errors.ComponentSetupHandledError()
111
112 - def check_algorithms(self, algorithms):
113 if not algorithms: 114 m = messages.Error(T_(N_( 115 "The multibouncer requires at least one bouncer " 116 "algorithm plug to be present")), mid='no-algorithm') 117 self.addMessage(m) 118 raise errors.ComponentSetupHandledError()
119
120 - def do_authenticate(self, keycard):
121 # create a context for this request 122 context = {} 123 # ask the combinator for an answer 124 d = self.combinator.evaluate(keycard, context) 125 126 def authenticated(res, keycard): 127 # the answer is True/False 128 if not res: 129 # False, return None as per the bouncer protocol 130 return None 131 if hasattr(keycard, 'ttl') and keycard.ttl <= 0: 132 # keycard was invalid on input 133 self.log('immediately expiring keycard %r', keycard) 134 return None 135 if self.addKeycard(keycard): 136 # keycard added, set state to AUTHENTICATED, keep the context, 137 # return to caller 138 keycard.state = keycards.AUTHENTICATED 139 self.contexts[keycard.id] = context 140 self.watchable_keycards[keycard.id] = keycard 141 return keycard
142 143 return d.addCallback(authenticated, keycard) 144
145 - def on_keycardRemoved(self, keycard):
146 # clear our references to the keycard 147 del self.contexts[keycard.id] 148 del self.watchable_keycards[keycard.id]
149
150 - def algorithm_expire_keycard_ids(self, keycard_ids, name):
151 # this gets called by a particular algorithm when it wants to expire a 152 # keycard 153 to_expire = [] 154 155 self.debug("algorithm %r requested expiration of keycards %r", 156 name, keycard_ids) 157 158 for keycard_id in keycard_ids: 159 # change the result in the context 160 context = self.contexts[keycard_id] 161 context[name] = False 162 # Reevaluate in the combinator. Because we already got an answer 163 # for that context, it should contain all necesary info, so we 164 # never should call any algorithm method: just do synchronous 165 # evaluation. 166 if not self.combinator.synchronous_evaluate(context): 167 self.log("keycard with id %r will be expired", keycard_id) 168 to_expire.append(keycard_id) 169 170 return self.expireKeycardIds(to_expire)
171 172
173 -class StaticMultiBouncer(MultiBouncer):
174 """A multibouncer that has a static list of bouncer algorithm plugs""" 175 176 algorithmClasses = None 177
178 - def get_main_algorithm(self):
179 for algorithm in self.algorithms.itervalues(): 180 return algorithm
181
182 - def setMedium(self, medium):
183 MultiBouncer.setMedium(self, medium) 184 for algorithm in self.algorithms.itervalues(): 185 self._export_plug_interface(algorithm, medium)
186
187 - def do_setup(self):
188 if self.algorithmClasses is None: 189 raise NotImplementedError("Subclass did not choose algorithm") 190 191 def start_algorithm(d, algorithm, name): 192 self.algorithms[name] = algorithm 193 d.addCallback(lambda _: defer.maybeDeferred(algorithm.start, self)) 194 d.addCallback(algorithm_started, algorithm, name)
195 196 def algorithm_started(_, algorithm, name): 197 algorithm.set_keycard_store(self.watchable_keycards) 198 199 expire = lambda ids: self.algorithm_expire_keycard_ids(ids, name) 200 algorithm.set_expire_function(expire)
201 202 try: 203 klasses = iter(self.algorithmClasses) 204 except TypeError: 205 klasses = iter((self.algorithmClasses, )) 206 207 d = defer.Deferred() 208 for klass in klasses: 209 name = klass.__name__ 210 algorithm = klass({'properties': self.config['properties']}) 211 start_algorithm(d, algorithm, name) 212 213 def create_combinator(_): 214 self.combinator = combinator.AlgorithmCombinator(self.algorithms) 215 spec = ' and '.join(self.algorithms.keys()) 216 self.combinator.create_combination(spec) 217 218 d.addCallback(create_combinator) 219 220 d.callback(None) 221 return d 222
223 - def check_algorithms(self, algorithms):
224 pass
225
226 - def do_stop(self):
227 d = defer.Deferred() 228 for algorithm in self.algorithms.values(): 229 d.addCallback(lambda _: defer.maybeDeferred(algorithm.stop, self)) 230 231 d.callback(None) 232 return d
233