1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
39 from flumotion.common import messages
40
41
42 from flumotion.twisted import flavors
43 from flumotion.common import componentui
44
45
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
63
64
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
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
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
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
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
145
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
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
175
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
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
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:
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
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
279
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
350
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
366
367
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