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