1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """
23 model abstraction for administration clients supporting different views
24 """
25
26 from twisted.internet import error, defer, reactor
27 from zope.interface import implements
28
29 from flumotion.common import common, errors, interfaces, log
30 from flumotion.common import keycards, planet, medium, package
31 from flumotion.common import messages, signals, connection
32 from flumotion.configure import configure
33 from flumotion.twisted import pb as fpb
34
35
36
37 from flumotion.common import planet, worker
38
39 from flumotion.common.messages import N_
40 T_ = messages.gettexter('flumotion')
41
123
124
125 d.addCallbacks(success, error)
126 return d
127
128
129
130 -class AdminModel(medium.PingingMedium, signals.SignalMixin):
131 """
132 I live in the admin client.
133 I am a data model for any admin view implementing a UI to
134 communicate with one manager.
135 I send signals when things happen.
136
137 Manager calls on us through L{flumotion.manager.admin.AdminAvatar}
138 """
139 __signals__ = ('connected', 'disconnected', 'connection-refused',
140 'connection-failed', 'connection-error', 'reloading',
141 'message', 'update')
142
143 logCategory = 'adminmodel'
144
145 implements(interfaces.IAdminMedium)
146
147
148 planet = None
149
151
152 self.connectionInfo = None
153 self.keepTrying = None
154 self._writeConnection = True
155
156 self.managerId = '<uninitialized>'
157
158 self.connected = False
159 self.clientFactory = None
160
161 self._deferredConnect = None
162
163 self._components = {}
164 self.planet = None
165 self._workerHeavenState = None
166
167 - def connectToManager(self, connectionInfo, keepTrying=False,
168 writeConnection=True):
169 'Connect to a host.'
170 assert self.clientFactory is None
171
172 self.connectionInfo = connectionInfo
173 self._writeConnection = writeConnection
174
175
176
177
178 self.managerId = str(connectionInfo)
179 self.logName = self.managerId
180
181 self.info('Connecting to manager %s with %s',
182 self.managerId, connectionInfo.use_ssl and 'SSL' or 'TCP')
183
184 self.clientFactory = AdminClientFactory(self,
185 extraTenacious=keepTrying,
186 maxDelay=20)
187 self.clientFactory.startLogin(connectionInfo.authenticator)
188
189 if connectionInfo.use_ssl:
190 common.assertSSLAvailable()
191 from twisted.internet import ssl
192 reactor.connectSSL(connectionInfo.host, connectionInfo.port,
193 self.clientFactory, ssl.ClientContextFactory())
194 else:
195 reactor.connectTCP(connectionInfo.host, connectionInfo.port,
196 self.clientFactory)
197
198 def connected(model, d):
199
200 d.callback(model)
201
202 def disconnected(model, d):
203
204
205 if not keepTrying:
206 d.errback(errors.ConnectionFailedError('Lost connection'))
207
208 def connection_refused(model, d):
209 if not keepTrying:
210 d.errback(errors.ConnectionRefusedError())
211
212 def connection_failed(model, reason, d):
213 if not keepTrying:
214 d.errback(errors.ConnectionFailedError(reason))
215
216 def connection_error(model, failure, d):
217 if not keepTrying:
218 d.errback(failure)
219
220 d = defer.Deferred()
221 ids = []
222 ids.append(self.connect('connected', connected, d))
223 ids.append(self.connect('disconnected', disconnected, d))
224 ids.append(self.connect('connection-refused', connection_refused, d))
225 ids.append(self.connect('connection-failed', connection_failed, d))
226 ids.append(self.connect('connection-error', connection_error, d))
227
228 def success(model):
229 map(self.disconnect, ids)
230 self._deferredConnect = None
231 return model
232
233 def failure(f):
234 map(self.disconnect, ids)
235 self._deferredConnect = None
236 return f
237
238 d.addCallbacks(success, failure)
239 self._deferredConnect = d
240 return d
241
243 self.debug('shutting down')
244 if self.clientFactory is not None:
245
246
247 self.clientFactory.stopTrying()
248 self.clientFactory.disconnect()
249 self.clientFactory = None
250
251 if self._deferredConnect is not None:
252
253 self.debug('cancelling connection attempt')
254 self._deferredConnect.errback(errors.ConnectionCancelledError())
255
257 """Close any existing connection to the manager and
258 reconnect."""
259 self.debug('asked to log in again')
260 self.shutdown()
261 return self.connectToManager(self.connectionInfo, keepTrying)
262
263
265 return self.managerId
266
268 return '%s:%s (%s)' % (self.connectionInfo.host,
269 self.connectionInfo.port,
270 self.connectionInfo.use_ssl
271 and 'https' or 'http')
272
273
275 assert self.planet
276 return '%s (%s)' % (self.planet.get('name'), self.managerId)
277
294
296 self.debug("setRemoteReference %r", remoteReference)
297 def gotPlanetState(planet):
298 self.planet = planet
299
300 self.planet.admin = self
301 self.debug('got planet state')
302 return self.callRemote('getWorkerHeavenState')
303
304 def gotWorkerHeavenState(whs):
305 self._workerHeavenState = whs
306 self.debug('got worker state')
307
308 self.debug('Connected to manager and retrieved all state')
309 self.connected = True
310 self.emit('connected')
311
312 def writeConnection():
313 i = self.connectionInfo
314 if not (i.authenticator.username
315 and i.authenticator.password):
316 self.log('not caching connection information')
317 return
318 s = ''.join(['<connection>',
319 '<host>%s</host>' % i.host,
320 '<manager>%s</manager>' % self.planet.get('name'),
321 '<port>%d</port>' % i.port,
322 '<use_insecure>%d</use_insecure>'
323 % ((not i.use_ssl) and 1 or 0),
324 '<user>%s</user>' % i.authenticator.username,
325 '<passwd>%s</passwd>' % i.authenticator.password,
326 '</connection>'])
327
328 import os
329 import md5
330 sum = md5.new(s).hexdigest()
331 f = os.path.join(configure.registrydir, '%s.connection' % sum)
332 try:
333 h = open(f, 'w')
334 h.write(s)
335 h.close()
336 except Exception, e:
337 self.info('failed to write connection cache file %s: %s',
338 f, log.getExceptionMessage(e))
339
340
341 medium.PingingMedium.setRemoteReference(self, remoteReference)
342
343
344 def remoteDisconnected(remoteReference):
345 self.debug("emitting disconnected")
346 self.connected = False
347 self.emit('disconnected')
348 self.debug("emitted disconnected")
349 self.remote.notifyOnDisconnect(remoteDisconnected)
350
351 d = self.callRemote('getPlanetState')
352 d.addCallback(gotPlanetState)
353 d.addCallback(gotWorkerHeavenState)
354 if self._writeConnection:
355 d.addCallback(lambda _: writeConnection())
356 return d
357
358
359
360
363
364
366 """
367 Call the given method on the given component with the given args.
368
369 @param componentState: component to call the method on
370 @type componentState: L{flumotion.common.planet.AdminComponentState}
371 @param methodName: name of method to call; serialized to a
372 remote_methodName on the worker's medium
373
374 @rtype: L{twisted.internet.defer.Deferred}
375 """
376 d = self.callRemote('componentCallRemote',
377 componentState, methodName,
378 *args, **kwargs)
379 def errback(failure):
380 msg = None
381 if failure.check(errors.NoMethodError):
382 msg = "Remote method '%s' does not exist." % methodName
383 msg += "\n" + failure.value
384 else:
385 msg = log.getFailureMessage(failure)
386
387
388
389
390 self.warning(msg)
391 m = messages.Warning(T_(N_("Internal error in component.")),
392 debug=msg)
393 componentState.observe_append('messages', m)
394 return failure
395
396 d.addErrback(errback)
397
398 return d
399
401 """
402 Call the the given method on the given worker with the given args.
403
404 @param workerName: name of the worker to call the method on
405 @param methodName: name of method to call; serialized to a
406 remote_methodName on the worker's medium
407
408 @rtype: L{twisted.internet.defer.Deferred}
409 """
410 return self.callRemote('workerCallRemote', workerName,
411 methodName, *args, **kwargs)
412
413
415 return self.callRemote('loadConfiguration', xml_string)
416
419
422
423
426
429
430 - def workerRun(self, workerName, moduleName, functionName, *args, **kwargs):
431 """
432 Run the given function and args on the given worker. If the
433 worker does not already have the module, or it is out of date,
434 it will be retrieved from the manager.
435
436 @rtype: L{twisted.internet.defer.Deferred} firing an
437 L{flumotion.common.messages.Result}
438 """
439 return self.workerCallRemote(workerName, 'runFunction', moduleName,
440 functionName, *args, **kwargs)
441
443 return self._workerHeavenState
444