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

Source Code for Module flumotion.manager.admin

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_manager_admin -*- 
  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  manager-side objects to handle administrative clients 
 24  """ 
 25   
 26  import re 
 27  import os 
 28  from StringIO import StringIO 
 29   
 30  from twisted.internet import reactor, defer 
 31  from twisted.spread import pb 
 32  from twisted.python import failure 
 33  from zope.interface import implements 
 34   
 35  from flumotion.manager import base 
 36  from flumotion.common import errors, interfaces, log, planet, registry 
 37   
 38  # make Result and Message proxyable 
 39  from flumotion.common import messages 
 40   
 41  # make ComponentState proxyable 
 42  from flumotion.twisted import flavors 
 43  from flumotion.common import componentui 
 44   
 45  # FIXME: rename to Avatar since we are in the admin. namespace ? 
46 -class AdminAvatar(base.ManagerAvatar):
47 """ 48 I am an avatar created for an administrative client interface. 49 A reference to me is given (for example, to gui.AdminInterface) 50 when logging in and requesting an "admin" avatar. 51 I live in the manager. 52 """ 53 logCategory = 'admin-avatar' 54 55 # override pb.Avatar implementation so we can run admin actions
56 - def perspectiveMessageReceived(self, broker, message, args, kwargs):
57 benignMethods = ('ping',) 58 if message not in benignMethods: 59 self.vishnu.adminAction(self.remoteIdentity, message, args, kwargs) 60 61 return base.ManagerAvatar.perspectiveMessageReceived( 62 self, broker, message, args, kwargs)
63 64 ### pb.Avatar IPerspective methods
66 """ 67 Get the planet state. 68 69 @rtype: L{flumotion.common.planet.ManagerPlanetState} 70 """ 71 self.debug("returning planet state %r" % self.vishnu.state) 72 return self.vishnu.state
73
75 """ 76 Get the worker heaven state. 77 78 @rtype: L{flumotion.common.worker.ManagerWorkerHeavenState} 79 """ 80 self.debug("returning worker heaven state %r" % self.vishnu.state) 81 return self.vishnu.workerHeaven.state
82
83 - def perspective_componentStart(self, componentState):
84 """ 85 Start the given component. The component should be sleeping before 86 this. 87 88 @type componentState: L{planet.ManagerComponentState} 89 """ 90 self.debug('perspective_componentStart(%r)' % componentState) 91 return self.vishnu.componentCreate(componentState)
92
93 - def perspective_componentStop(self, componentState):
94 """ 95 Stop the given component. 96 If the component was sad, we clear its sad state as well, 97 since the stop was explicitly requested by the admin. 98 99 @type componentState: L{planet.ManagerComponentState} 100 """ 101 self.debug('perspective_componentStop(%r)' % componentState) 102 return self.vishnu.componentStop(componentState)
103
104 - def perspective_componentRestart(self, componentState):
105 """ 106 Restart the given component. 107 108 @type componentState: L{planet.ManagerComponentState} 109 """ 110 self.debug('perspective_componentRestart(%r)' % componentState) 111 d = self.perspective_componentStop(componentState) 112 d.addCallback(lambda *x: self.perspective_componentStart(componentState)) 113 return d
114 115 # Generic interface to call into a component
116 - def perspective_componentCallRemote(self, componentState, methodName, 117 *args, **kwargs):
118 """ 119 Call a method on the given component on behalf of an admin client. 120 121 @param componentState: state of the component to call the method on 122 @type componentState: L{planet.ManagerComponentState} 123 @param methodName: name of the method to call. Gets proxied to 124 L{flumotion.component.component.""" \ 125 """BaseComponentMedium}'s remote_(methodName) 126 @type methodName: str 127 128 @rtype: L{twisted.internet.defer.Deferred} 129 """ 130 assert isinstance(componentState, planet.ManagerComponentState) 131 132 if methodName == "start": 133 self.warning('forwarding "start" to perspective_componentStart') 134 return self.perspective_componentStart(componentState) 135 136 m = self.vishnu.getComponentMapper(componentState) 137 avatar = m.avatar 138 139 if not avatar: 140 self.warning('No avatar for %s, cannot call remote' % 141 componentState.get('name')) 142 raise errors.SleepingComponentError() 143 144 # XXX: Maybe we need to have a prefix, so we can limit what an 145 # admin interface can call on a component 146 try: 147 return avatar.mindCallRemote(methodName, *args, **kwargs) 148 except Exception, e: 149 msg = "exception on remote call %s: %s" % (methodName, 150 log.getExceptionMessage(e)) 151 self.warning(msg) 152 raise errors.RemoteMethodError(methodName, 153 log.getExceptionMessage(e))
154
155 - def perspective_workerCallRemote(self, workerName, methodName, 156 *args, **kwargs):
157 """ 158 Call a remote method on the worker. 159 This is used so that admin clients can call methods from the interface 160 to the worker. 161 162 @param workerName: the worker to call 163 @type workerName: str 164 @param methodName: Name of the method to call. Gets proxied to 165 L{flumotion.worker.worker.WorkerMedium} 's 166 remote_(methodName) 167 @type methodName: str 168 """ 169 170 self.debug('AdminAvatar.workerCallRemote(%r, %r)' % ( 171 workerName, methodName)) 172 workerAvatar = self.vishnu.workerHeaven.getAvatar(workerName) 173 174 # XXX: Maybe we need to a prefix, so we can limit what an admin 175 # interface can call on a worker 176 try: 177 return workerAvatar.mindCallRemote(methodName, *args, **kwargs) 178 except Exception, e: 179 self.warning("exception on remote call: %s" % 180 log.getExceptionMessage(e)) 181 return failure.Failure(errors.RemoteMethodError(methodName, 182 log.getExceptionMessage(e)))
183
184 - def perspective_getEntryByType(self, componentState=None, type=None, 185 componentType=None):
186 """ 187 Get the entry point for a piece of bundled code by the type. 188 189 Returns: a (filename, methodName) tuple, or raises a Failure 190 """ 191 if componentType is None: 192 assert componentState is not None 193 m = self.vishnu.getComponentMapper(componentState) 194 componentName = componentState.get('name') 195 if not m.avatar: 196 self.debug('component %s not logged in yet, no entry', 197 componentName) 198 raise errors.SleepingComponentError(componentName) 199 componentType = m.avatar.getType() 200 201 self.debug('getting entry of type %s for component type %s', 202 type, componentType) 203 try: 204 componentRegistryEntry = registry.getRegistry().getComponent( 205 componentType) 206 # FIXME: add logic here for default entry points and functions 207 entry = componentRegistryEntry.getEntryByType(type) 208 except KeyError: 209 self.warning("Could not find bundle for %s(%s)" % ( 210 componentType, type)) 211 raise errors.NoBundleError("entry type %s in component type %s" % 212 (type, componentType)) 213 214 filename = os.path.join(componentRegistryEntry.base, entry.location) 215 self.debug('entry point is in file path %s and function %s' % ( 216 filename, entry.function)) 217 return (filename, entry.function)
218
220 """ 221 Reload modules in the manager. 222 """ 223 self.info('reloading manager code') 224 from flumotion.common.reload import reload as freload 225 freload()
226
228 """ 229 Get the configuration of the manager as an XML string. 230 231 @rtype: str 232 """ 233 return self.vishnu.getConfiguration()
234
235 - def _saveFlowFile(self, filename):
236 """Opens a file that the flow should be written to. 237 238 Note that the returned file object might be an existing file, 239 opened in append mode; if the loadConfiguration operation 240 succeeds, the file should first be truncated before writing. 241 """ 242 self.vishnu.adminAction(self.remoteIdentity, 243 '_saveFlowFile', (), {}) 244 def ensure_sane(name, extra=''): 245 if not re.match('^[a-zA-Z0-9_' + extra + '-]+$', name): 246 raise errors.ConfigError, \ 247 'Invalid planet or saveAs name: %s' % name
248 249 ensure_sane(self.vishnu.configDir, '/') 250 ensure_sane(filename) 251 dir = os.path.join(self.vishnu.configDir, "flows") 252 self.debug('told to save flow as %s/%s.xml', dir, filename) 253 try: 254 os.makedirs(dir, 0770) 255 except OSError, e: 256 if e.errno != 17: # 17 == EEXIST 257 raise e 258 prev = os.umask(0007) 259 output = open(os.path.join(dir, filename + '.xml'), 'a') 260 os.umask(prev) 261 return output
262
263 - def perspective_loadConfiguration(self, xml, saveAs=None):
264 """ 265 Load the given XML configuration into the manager. If the 266 optional saveAs parameter is passed, the XML snippet will be 267 saved to disk in the manager's flows directory. 268 269 @param xml: the XML configuration snippet. 270 @type xml: str 271 @param saveAs: The name of a file to save the XML as. 272 @type saveAs: str 273 """ 274 275 if saveAs: 276 output = self._saveFlowFile(saveAs) 277 278 # Update the registry if needed, so that new/changed component types 279 # can be parsed. 280 registry.getRegistry().verify() 281 282 f = StringIO(xml) 283 res = self.vishnu.loadComponentConfigurationXML(f, self.remoteIdentity) 284 f.close() 285 286 if saveAs: 287 def success(res): 288 self.debug('loadConfiguration succeeded, writing flow to %r', 289 output) 290 output.truncate(0) 291 output.write(xml) 292 output.close() 293 return res
294 def failure(res): 295 self.debug('loadConfiguration failed, leaving %r as it was', 296 output) 297 output.close() 298 return res 299 res.addCallbacks(success, failure) 300 301 return res 302
303 - def perspective_loadComponent(self, componentType, componentId, 304 componentLabel, properties, workerName, 305 plugs=None, eaters=None, 306 isClockMaster=None, virtualFeeds=None):
307 """ 308 Load a component into the manager configuration. 309 Returns a deferred that will be called with the component state. 310 311 @param componentType: The registered type of the component to be added 312 @type componentType: str 313 @param componentId: The identifier of the component to add, 314 should be created by the function 315 L{flumotion.common.common.componentId} 316 @type componentId: str 317 @param componentLabel: The human-readable label of the component. 318 if None, no label will be set. 319 @type componentLabel: str or None 320 @param properties: List of property name-value pairs. 321 See L{flumotion.common.config.buildPropertyDict} 322 @type properties: [(str, object)] 323 @param workerName: the name of the worker where the added 324 component should run. 325 @type workerName: str 326 @param plugs: List of plugs, as type-propertyList pairs. 327 See {flumotion.common.config.buildPlugsSet}. 328 @type plugs: [(str, [(str, object)])] 329 @param eaters: List of (eater name, feed ID) pairs. 330 See L{flumotion.common.config.buildEatersDict} 331 @type eaters: [(str, str)] 332 @param isClockMaster: True if the component to be added must be 333 a clock master. Passing False here means 334 that the manager will choose what 335 component, if any, will be clock master 336 for this flow. 337 @type isClockMaster: bool 338 @param virtualFeeds: List of (virtual feed, feeder name) pairs. 339 See L{flumotion.common.config.buildVirtualFeeds} 340 @type virtualFeeds: [(str, str)] 341 """ 342 return self.vishnu.loadComponent(self.remoteIdentity, componentType, 343 componentId, componentLabel, 344 properties, workerName, 345 plugs or [], eaters or [], 346 isClockMaster, virtualFeeds or [])
347
348 - def perspective_deleteFlow(self, flowName):
349 return self.vishnu.deleteFlow(flowName)
350
351 - def perspective_deleteComponent(self, componentState):
352 """Delete a component from the manager. 353 354 A component can only be deleted when it is sleeping or sad. It 355 is the caller's job to ensure this is the case; calling this 356 function on a running component will raise a ComponentBusyError. 357 358 @returns: a deferred that will fire when all listeners have been 359 notified of the component removal 360 """ 361 return self.vishnu.deleteComponent(componentState)
362 363 # Deprecated -- remove me when no one uses me any more
364 - def perspective_cleanComponents(self):
365 return self.vishnu.emptyPlanet()
366 367
368 -class AdminHeaven(base.ManagerHeaven):
369 """ 370 I interface between the Manager and administrative clients. 371 For each client I create an L{AdminAvatar} to handle requests. 372 I live in the manager. 373 """ 374 375 logCategory = "admin-heaven" 376 implements(interfaces.IHeaven) 377 avatarClass = AdminAvatar
378