Package flumotion :: Package manager :: Module base
[hide private]

Source Code for Module flumotion.manager.base

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_manager_common -*- 
  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  common classes and code to support manager-side objects 
 20  """ 
 21   
 22  from twisted.internet import reactor, defer 
 23  from twisted.spread import pb 
 24  from twisted.python import reflect 
 25   
 26  from flumotion.common import errors, log, common 
 27  from flumotion.common.planet import moods 
 28  from flumotion.twisted import pb as fpb 
 29   
 30  __version__ = "$Rev$" 
 31   
 32   
33 -class ManagerAvatar(fpb.PingableAvatar, log.Loggable):
34 """ 35 I am a base class for manager-side avatars to subclass from. 36 37 @ivar avatarId: the id for this avatar, unique inside the heaven 38 @type avatarId: str 39 @ivar heaven: the heaven this avatar is part of 40 @type heaven: L{flumotion.manager.base.ManagerHeaven} 41 @ivar mind: a remote reference to the client-side Medium 42 @type mind: L{twisted.spread.pb.RemoteReference} 43 @ivar vishnu: the vishnu that manages this avatar's heaven 44 @type vishnu: L{flumotion.manager.manager.Vishnu} 45 """ 46 remoteLogName = 'medium' 47 logCategory = 'manager-avatar' 48
49 - def __init__(self, heaven, avatarId, remoteIdentity, mind):
50 """ 51 @param heaven: the heaven this avatar is part of 52 @type heaven: L{flumotion.manager.base.ManagerHeaven} 53 @param avatarId: id of the avatar to create 54 @type avatarId: str 55 @param remoteIdentity: manager-assigned identity object for this 56 avatar 57 @type remoteIdentity: L{flumotion.common.identity.RemoteIdentity} 58 @param mind: a remote reference to the client-side Medium 59 @type mind: L{twisted.spread.pb.RemoteReference} 60 """ 61 fpb.PingableAvatar.__init__(self, avatarId) 62 self.heaven = heaven 63 self.logName = avatarId 64 self.setMind(mind) 65 self.vishnu = heaven.vishnu 66 self.remoteIdentity = remoteIdentity 67 68 self.debug("created new Avatar with id %s", avatarId)
69
70 - def perspective_writeFluDebugMarker(self, level, marker):
71 """ 72 Sets a marker that will be prefixed to the log strings. Setting this 73 marker to multiple elements at a time helps debugging. 74 @param marker: A string to prefix all the log strings. 75 @param level: The log level. It can be log.ERROR, log.DEBUG, 76 log.WARN, log.INFO or log.LOG 77 """ 78 79 self.writeMarker(marker, level) 80 workers = self.vishnu.workerHeaven.state.get('names') 81 componentStates = self.vishnu.getComponentStates() 82 for worker in workers: 83 self.perspective_workerCallRemote(worker, 'writeFluDebugMarker', 84 level, marker) 85 for componentState in componentStates: 86 m = self.vishnu.getComponentMapper(componentState) 87 if m.avatar: 88 self.perspective_componentCallRemote(componentState, 89 'writeFluDebugMarker', 90 level, marker)
91
92 - def makeAvatarInitArgs(klass, heaven, avatarId, remoteIdentity, mind):
93 return defer.succeed((heaven, avatarId, remoteIdentity, mind))
94 makeAvatarInitArgs = classmethod(makeAvatarInitArgs) 95
96 - def makeAvatar(klass, heaven, avatarId, remoteIdentity, mind):
97 log.debug('manager-avatar', 'making avatar with avatarId %s', 98 avatarId) 99 100 def have_args(args): 101 log.debug('manager-avatar', 'instantiating with args=%r', args) 102 return klass(*args)
103 d = klass.makeAvatarInitArgs(heaven, avatarId, remoteIdentity, mind) 104 d.addCallback(have_args) 105 return d
106 makeAvatar = classmethod(makeAvatar) 107
108 - def onShutdown(self):
109 self.stopPingChecking()
110
111 - def mindCallRemote(self, name, *args, **kwargs):
112 """ 113 Call the given remote method, and log calling and returning nicely. 114 115 @param name: name of the remote method 116 @type name: str 117 """ 118 level = log.DEBUG 119 if name == 'ping': 120 level = log.LOG 121 122 return self.mindCallRemoteLogging(level, -1, name, *args, **kwargs)
123
124 - def getClientAddress(self):
125 """ 126 Get the IPv4 address of the machine the PB client is connecting from, 127 as seen from the avatar. 128 129 @returns: the IPv4 address the client is coming from, or None. 130 @rtype: str or None 131 """ 132 if self.mind: 133 peer = self.mind.broker.transport.getPeer() 134 return peer.host 135 136 return None
137
138 - def perspective_getBundleSums(self, bundleName=None, fileName=None, 139 moduleName=None):
140 """ 141 Get a list of (bundleName, md5sum) of all dependency bundles, 142 starting with this bundle, in the correct order. 143 Any of bundleName, fileName, moduleName may be given. 144 145 @type bundleName: str or list of str 146 @param bundleName: the name of the bundle for fetching 147 @type fileName: str or list of str 148 @param fileName: the name of the file requested for fetching 149 @type moduleName: str or list of str 150 @param moduleName: the name of the module requested for import 151 152 @rtype: list of (str, str) tuples of (bundleName, md5sum) 153 """ 154 bundleNames = [] 155 fileNames = [] 156 moduleNames = [] 157 if bundleName: 158 if isinstance(bundleName, str): 159 bundleNames.append(bundleName) 160 else: 161 bundleNames.extend(bundleName) 162 self.debug('asked to get bundle sums for bundles %r' % bundleName) 163 if fileName: 164 if isinstance(fileName, str): 165 fileNames.append(fileName) 166 else: 167 fileNames.extend(fileName) 168 self.debug('asked to get bundle sums for files %r' % fileNames) 169 if moduleName: 170 if isinstance(moduleName, str): 171 moduleNames.append(moduleName) 172 else: 173 moduleNames.extend(moduleName) 174 self.debug('asked to get bundle sums for modules %r' % moduleNames) 175 176 basket = self.vishnu.getBundlerBasket() 177 178 # will raise an error if bundleName not known 179 for fileName in fileNames: 180 bundleName = basket.getBundlerNameByFile(fileName) 181 if not bundleName: 182 msg = 'containing ' + fileName 183 self.warning('No bundle %s' % msg) 184 raise errors.NoBundleError(msg) 185 else: 186 bundleNames.append(bundleName) 187 188 for moduleName in moduleNames: 189 bundleName = basket.getBundlerNameByImport(moduleName) 190 if not bundleName: 191 msg = 'for module ' + moduleName 192 self.warning('No bundle %s' % msg) 193 raise errors.NoBundleError(msg) 194 else: 195 bundleNames.append(bundleName) 196 197 deps = [] 198 for bundleName in bundleNames: 199 thisdeps = basket.getDependencies(bundleName) 200 self.debug('dependencies of %s: %r' % (bundleName, thisdeps[1:])) 201 deps.extend(thisdeps) 202 203 sums = [] 204 for dep in deps: 205 bundler = basket.getBundlerByName(dep) 206 if not bundler: 207 self.warning('Did not find bundle with name %s' % dep) 208 else: 209 sums.append((dep, bundler.bundle().md5sum)) 210 211 self.debug('requested bundles: %r' % [x[0] for x in sums]) 212 return sums
213
214 - def perspective_getBundleSumsByFile(self, filename):
215 """ 216 Get a list of (bundleName, md5sum) of all dependency bundles, 217 starting with this bundle, in the correct order. 218 219 @param filename: the name of the file in a bundle 220 @type filename: str 221 222 @returns: list of (bundleName, md5sum) tuples 223 @rtype: list of (str, str) tuples 224 """ 225 self.debug('asked to get bundle sums for file %s' % filename) 226 basket = self.vishnu.getBundlerBasket() 227 bundleName = basket.getBundlerNameByFile(filename) 228 if not bundleName: 229 self.warning('Did not find a bundle for file %s' % filename) 230 raise errors.NoBundleError("for file %s" % filename) 231 232 return self.perspective_getBundleSums(bundleName)
233
234 - def perspective_getBundleZips(self, bundles):
235 """ 236 Get the zip files for the given list of bundles. 237 238 @param bundles: the names of the bundles to get 239 @type bundles: list of str 240 241 @returns: dictionary of bundleName -> zipdata 242 @rtype: dict of str -> str 243 """ 244 basket = self.vishnu.getBundlerBasket() 245 zips = {} 246 for name in bundles: 247 bundler = basket.getBundlerByName(name) 248 if not bundler: 249 raise errors.NoBundleError( 250 'The bundle named "%s" was not found' % (name, )) 251 zips[name] = bundler.bundle().getZip() 252 return zips
253
254 - def perspective_authenticate(self, bouncerName, keycard):
255 """ 256 Authenticate the given keycard. 257 If no bouncerName given, authenticate against the manager's bouncer. 258 If a bouncerName is given, authenticate against the given bouncer 259 in the atmosphere. 260 261 @since: 0.3.1 262 263 @param bouncerName: the name of the atmosphere bouncer, or None 264 @type bouncerName: str or None 265 @param keycard: the keycard to authenticate 266 @type keycard: L{flumotion.common.keycards.Keycard} 267 268 @returns: a deferred, returning the keycard or None. 269 """ 270 if not bouncerName: 271 self.debug( 272 'asked to authenticate keycard %r using manager bouncer' % 273 keycard) 274 return self.vishnu.bouncer.authenticate(keycard) 275 276 self.debug('asked to authenticate keycard %r using bouncer %s' % ( 277 keycard, bouncerName)) 278 avatarId = common.componentId('atmosphere', bouncerName) 279 if not self.heaven.hasAvatar(avatarId): 280 self.warning('No bouncer with id %s registered' % avatarId) 281 raise errors.UnknownComponentError(avatarId) 282 283 bouncerAvatar = self.heaven.getAvatar(avatarId) 284 return bouncerAvatar.authenticate(keycard)
285
286 - def perspective_keepAlive(self, bouncerName, issuerName, ttl):
287 """ 288 Resets the expiry timeout for keycards issued by issuerName. See 289 L{flumotion.component.bouncers.bouncer} for more information. 290 291 @since: 0.4.3 292 293 @param bouncerName: the name of the atmosphere bouncer, or None 294 @type bouncerName: str or None 295 @param issuerName: the issuer for which keycards should be kept 296 alive; that is to say, keycards with the 297 attribute 'issuerName' set to this value will 298 have their ttl values reset. 299 @type issuerName: str 300 @param ttl: the new expiry timeout 301 @type ttl: number 302 303 @returns: a deferred which will fire success or failure. 304 """ 305 self.debug('keycards keepAlive on behalf of %s, ttl=%d', 306 issuerName, ttl) 307 308 if not bouncerName: 309 return self.vishnu.bouncer.keepAlive(issuerName, ttl) 310 311 self.debug('looking for bouncer %s in atmosphere', bouncerName) 312 avatarId = common.componentId('atmosphere', bouncerName) 313 if not self.heaven.hasAvatar(avatarId): 314 self.warning('No bouncer with id %s registered', avatarId) 315 raise errors.UnknownComponentError(avatarId) 316 317 bouncerAvatar = self.heaven.getAvatar(avatarId) 318 return bouncerAvatar.keepAlive(issuerName, ttl)
319
320 - def perspective_getKeycardClasses(self):
321 """ 322 Get the keycard classes the manager's bouncer can authenticate. 323 324 @since: 0.3.1 325 326 @returns: a deferred, returning a list of keycard class names 327 @rtype: L{twisted.internet.defer.Deferred} firing list of str 328 """ 329 classes = self.vishnu.bouncer.keycardClasses 330 return [reflect.qual(c) for c in classes]
331 332
333 -class ManagerHeaven(pb.Root, log.Loggable):
334 """ 335 I am a base class for heavens in the manager. 336 337 @cvar avatarClass: the class object this heaven instantiates avatars from. 338 To be set in subclass. 339 @ivar avatars: a dict of avatarId -> Avatar 340 @type avatars: dict of str -> L{ManagerAvatar} 341 @ivar vishnu: the Vishnu in control of all the heavens 342 @type vishnu: L{flumotion.manager.manager.Vishnu} 343 """ 344 avatarClass = None 345
346 - def __init__(self, vishnu):
347 """ 348 @param vishnu: the Vishnu in control of all the heavens 349 @type vishnu: L{flumotion.manager.manager.Vishnu} 350 """ 351 self.vishnu = vishnu 352 self.avatars = {} # avatarId -> avatar; populated by
353 # manager.Dispatcher 354 355 ### ManagerHeaven methods 356
357 - def getAvatar(self, avatarId):
358 """ 359 Get the avatar with the given id. 360 361 @param avatarId: id of the avatar to get 362 @type avatarId: str 363 364 @returns: the avatar with the given id 365 @rtype: L{ManagerAvatar} 366 """ 367 return self.avatars[avatarId]
368
369 - def hasAvatar(self, avatarId):
370 """ 371 Check if a component with that name is registered. 372 373 @param avatarId: id of the avatar to check 374 @type avatarId: str 375 376 @returns: True if an avatar with that id is registered 377 @rtype: bool 378 """ 379 return avatarId in self.avatars
380
381 - def getAvatars(self):
382 """ 383 Get all avatars in this heaven. 384 385 @returns: a list of all avatars in this heaven 386 @rtype: list of L{ManagerAvatar} 387 """ 388 return self.avatars.values()
389