1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import gobject
23 import os
24 import string
25 import curses
26
27 from twisted.internet import reactor
28 from twisted.python import rebuild
29 from zope.interface import implements
30
31 from flumotion.admin.admin import AdminModel
32 from flumotion.common import log, errors, worker, planet, common
33 from flumotion.configure import configure
34 from flumotion.twisted import flavors, reflect
35 from flumotion.common.planet import moods
36
37 from flumotion.admin.text import misc_curses
38
39 -class AdminTextView(log.Loggable, gobject.GObject, misc_curses.CursesStdIO):
40
41 implements(flavors.IStateListener)
42
43 logCategory = 'admintextview'
44
45 global_commands = [ 'startall', 'stopall', 'clearall', 'quit' ]
46
47 LINES_BEFORE_COMPONENTS = 5
48 LINES_AFTER_COMPONENTS = 6
49
50 - def __init__(self, model, stdscr):
51 self.initialised = False
52 self.stdscr = stdscr
53 self.inputText = ''
54 self.command_result = ""
55 self.lastcommands = []
56 self.nextcommands = []
57 self.rows, self.cols = self.stdscr.getmaxyx()
58 self.max_components_per_page = self.rows - \
59 self.LINES_BEFORE_COMPONENTS - \
60 self.LINES_AFTER_COMPONENTS
61 self._first_onscreen_component = 0
62
63 self._components = {}
64 self._comptextui = {}
65 self._setAdminModel(model)
66
67 self.setPlanetState(self.admin.planet)
68
69
70 - def _setAdminModel(self, model):
71 self.admin = model
72
73 self.admin.connect('connected', self.admin_connected_cb)
74 self.admin.connect('disconnected', self.admin_disconnected_cb)
75 self.admin.connect('connection-refused',
76 self.admin_connection_refused_cb)
77 self.admin.connect('connection-failed',
78 self.admin_connection_failed_cb)
79
80
81 self.admin.connect('update', self.admin_update_cb)
82
83
85 self.initialised = True
86 self.stdscr.addstr(0,0, "Main Menu")
87 self.show_components()
88 self.display_status()
89 self.stdscr.move(self.lasty,0)
90 self.stdscr.clrtoeol()
91 self.stdscr.move(self.lasty+1,0)
92 self.stdscr.clrtoeol()
93 self.stdscr.addstr(self.lasty+1,0, "Prompt: %s" % self.inputText)
94 self.stdscr.refresh()
95
96
97
98
100 if self.initialised:
101 self.stdscr.addstr(2,0, "Components:")
102
103 names = self._components.keys()
104 names.sort()
105
106 cury = 4
107
108
109
110
111 if len(names) > self.max_components_per_page:
112 if self._first_onscreen_component > 0:
113 self.stdscr.move(cury,0)
114 self.stdscr.clrtoeol()
115 self.stdscr.addstr(cury,0,
116 "Press page up to scroll up components list")
117 cury=cury+1
118 cur_component = self._first_onscreen_component
119 for name in names[self._first_onscreen_component:len(names)]:
120
121 if cury - self.LINES_BEFORE_COMPONENTS >= \
122 self.max_components_per_page:
123 self.stdscr.move(cury,0)
124 self.stdscr.clrtoeol()
125 self.stdscr.addstr(cury,0,
126 "Press page down to scroll down components list")
127 cury = cury + 1
128 break
129
130 component = self._components[name]
131 mood = component.get('mood')
132
133 self.stdscr.move(cury,0)
134 self.stdscr.clrtoeol()
135
136 self.stdscr.addstr(cury,0,"%s: %s" % (name, moods[mood].name))
137 cury = cury + 1
138 cur_component = cur_component + 1
139
140 self.lasty = cury
141
142
143
144 - def gotEntryCallback(self, result, name):
145 entryPath, filename, methodName = result
146 filepath = os.path.join(entryPath, filename)
147 self.debug('Got the UI for %s and it lives in %s' % (name,filepath))
148 self.uidir = os.path.split(filepath)[0]
149
150
151
152
153
154 moduleName = common.pathToModuleName(filename)
155 statement = 'import %s' % moduleName
156 self.debug('running %s' % statement)
157 try:
158 exec(statement)
159 except SyntaxError, e:
160
161 where = getattr(e, 'filename', "<entry file>")
162 lineno = getattr(e, 'lineno', 0)
163 msg = "Syntax Error at %s:%d while executing %s" % (
164 where, lineno, filename)
165 self.warning(msg)
166 raise errors.EntrySyntaxError(msg)
167 except NameError, e:
168
169 msg = "NameError while executing %s: %s" % (filename,
170 " ".join(e.args))
171 self.warning(msg)
172 raise errors.EntrySyntaxError(msg)
173 except ImportError, e:
174 msg = "ImportError while executing %s: %s" % (filename,
175 " ".join(e.args))
176 self.warning(msg)
177 raise errors.EntrySyntaxError(msg)
178
179
180 module = reflect.namedAny(moduleName)
181 rebuild.rebuild(module)
182
183
184 if not hasattr(module, methodName):
185 self.warning('method %s not found in file %s' % (
186 methodName, filename))
187 raise
188 klass = getattr(module, methodName)
189
190
191
192
193 instance = klass(self._components[name], self.admin)
194 self.debug("Created entry instance %r" % instance)
195
196
197
198 self._comptextui[name] = instance
199
200
201 - def gotEntryNoBundleErrback(self, failure, name):
202 failure.trap(errors.NoBundleError)
203 self.debug("No admin ui for component %s" % name)
204
207
208 - def getEntry(self, componentState, type):
209 """
210 Do everything needed to set up the entry point for the given
211 component and type, including transferring and setting up bundles.
212
213 Caller is responsible for adding errbacks to the deferred.
214
215 @returns: a deferred returning (entryPath, filename, methodName) with
216 entryPath: the full local path to the bundle's base
217 fileName: the relative location of the bundled file
218 methodName: the method to instantiate with
219 """
220 lexicalVariableHack = []
221
222 def gotEntry(res):
223 fileName, methodName = res
224 lexicalVariableHack.append(res)
225 self.debug("entry for %r of type %s is in file %s and method %s",
226 componentState, type, fileName, methodName)
227 return self.bundleLoader.getBundles(fileName=fileName)
228
229 def gotBundles(res):
230 name, bundlePath = res[-1]
231 fileName, methodName = lexicalVariableHack[0]
232 return (bundlePath, fileName, methodName)
233
234 d = self.admin.callRemote('getEntryByType', componentState, type)
235 d.addCallback(gotEntry)
236 d.addCallback(gotBundles)
237 return d
238
239 - def update_components(self, components):
240 for name in self._components.keys():
241 component = self._components[name]
242 try:
243 component.removeListener(self)
244 except KeyError:
245
246 self.debug("silly")
247
248 def compStateSet(state, key, value):
249 self.log('stateSet: state %r, key %s, value %r' % (state, key, value))
250
251 if key == 'mood':
252
253
254 d = self.admin.getEntry(state, 'admin/text')
255 d.addCallback(self.gotEntryCallback, state.get('name'))
256 d.addErrback(self.gotEntryNoBundleErrback, state.get('name'))
257 d.addErrback(self.gotEntrySleepingComponentErrback)
258
259 self.show()
260 elif key == 'name':
261 if value:
262 self.show()
263
264 self._components = components
265 for name in self._components.keys():
266 component = self._components[name]
267 component.addListener(self, compStateSet)
268
269
270 d = self.admin.getEntry(component, 'admin/text')
271 d.addCallback(self.gotEntryCallback, name)
272 d.addErrback(self.gotEntryNoBundleErrback, name)
273 d.addErrback(self.gotEntrySleepingComponentErrback)
274
275 self.show()
276
277 - def setPlanetState(self, planetState):
278 def flowStateAppend(state, key, value):
279 self.debug('flow state append: key %s, value %r' % (key, value))
280 if state.get('name') != 'default':
281 return
282 if key == 'components':
283 self._components[value.get('name')] = value
284
285 self.update_components(self._components)
286
287 def flowStateRemove(state, key, value):
288 if state.get('name') != 'default':
289 return
290 if key == 'components':
291 name = value.get('name')
292 self.debug('removing component %s' % name)
293 del self._components[name]
294
295 self.update_components(self._components)
296
297 def atmosphereStateAppend(state, key, value):
298 if key == 'components':
299 self._components[value.get('name')] = value
300
301 self.update_components(self._components)
302
303 def atmosphereStateRemove(state, key, value):
304 if key == 'components':
305 name = value.get('name')
306 self.debug('removing component %s' % name)
307 del self._components[name]
308
309 self.update_components(self._components)
310
311 def planetStateAppend(state, key, value):
312 if key == 'flows':
313 if value.get('name') != 'default':
314 return
315
316 value.addListener(self, flowStateAppend,
317 flowStateRemove)
318 for c in value.get('components'):
319 flowStateAppend(value, 'components', c)
320
321 def planetStateRemove(state, key, value):
322 self.debug('something got removed from the planet')
323
324 self.debug('parsing planetState %r' % planetState)
325 self._planetState = planetState
326
327
328 self._components = {}
329
330 planetState.addListener(self, append=planetStateAppend,
331 remove=planetStateRemove)
332
333 a = planetState.get('atmosphere')
334 a.addListener(self, append=atmosphereStateAppend,
335 remove=atmosphereStateRemove)
336 for c in a.get('components'):
337 atmosphereStateAppend(a, 'components', c)
338
339 for f in planetState.get('flows'):
340 planetStateAppend(f, 'flows', f)
341
342 - def _component_stop(self, state):
343 return self._component_do(state, 'Stop', 'Stopping', 'Stopped')
344
345 - def _component_start(self, state):
346 return self._component_do(state, 'Start', 'Starting', 'Started')
347
348 - def _component_do(self, state, action, doing, done):
349 name = state.get('name')
350 if not name:
351 return None
352
353 self.admin.callRemote('component'+action, state)
354
355 - def run_command(self, command):
356
357 can_stop = True
358 can_start = True
359 for x in self._components.values():
360 mood = moods.get(x.get('mood'))
361 can_stop = can_stop and (mood != moods.lost and mood != moods.sleeping)
362 can_start = can_start and (mood == moods.sleeping)
363 can_clear = can_start and not can_stop
364
365 if string.lower(command) == 'quit':
366 reactor.stop()
367 elif string.lower(command) == 'startall':
368 if can_start:
369 for c in self._components.values():
370 self._component_start(c)
371 self.command_result = 'Attempting to start all components'
372 else:
373 self.command_result = 'Components not all in state to be started'
374
375
376 elif string.lower(command) == 'stopall':
377 if can_stop:
378 for c in self._components.values():
379 self._component_stop(c)
380 self.command_result = 'Attempting to stop all components'
381 else:
382 self.command_result = 'Components not all in state to be stopped'
383 elif string.lower(command) == 'clearall':
384 if can_clear:
385 self.admin.cleanComponents()
386 self.command_result = 'Attempting to clear all components'
387 else:
388 self.command_result = 'Components not all in state to be cleared'
389 else:
390 command_split = command.split()
391
392 if len(command_split)>1:
393
394 for c in self._components.values():
395 if string.lower(c.get('name')) == string.lower(command_split[0]):
396
397 if string.lower(command_split[1]) == 'start':
398
399 self._component_start(c)
400 elif string.lower(command_split[1]) == 'stop':
401
402 self._component_stop(c)
403 else:
404
405 try:
406 textui = self._comptextui[c.get('name')]
407
408 if textui:
409 d = textui.runCommand(' '.join(command_split[1:]))
410 self.debug("textui runcommand defer: %r" % d)
411
412 d.addCallback(self._runCommand_cb)
413
414 except KeyError:
415 pass
416
417
418 - def _runCommand_cb(self, result):
419 self.command_result = result
420 self.debug("Result received: %s" % result)
421 self.show()
422
423
424
425
426 - def get_available_commands(self, input):
427 input_split = input.split()
428 last_input=''
429 if len(input_split) >0:
430 last_input = input_split[len(input_split)-1]
431 available_commands = []
432 if len(input_split) <= 1 and not input.endswith(' '):
433
434 can_stop = True
435 can_start = True
436 for x in self._components.values():
437 mood = moods.get(x.get('mood'))
438 can_stop = can_stop and (mood != moods.lost and mood != moods.sleeping)
439 can_start = can_start and (mood == moods.sleeping)
440 can_clear = can_start and not can_stop
441
442 for command in self.global_commands:
443 command_ok = (command != 'startall' and command != 'stopall' and command != 'clearall')
444 command_ok = command_ok or (command == 'startall' and can_start)
445 command_ok = command_ok or (command == 'stopall' and can_stop)
446 command_ok = command_ok or (command == 'clearall' and can_clear)
447
448 if command_ok and string.lower(command).startswith(string.lower(last_input)):
449 available_commands.append(command)
450 else:
451 available_commands = available_commands + self.get_available_commands_for_component(input_split[0], input)
452
453 return available_commands
454
456 self.debug("getting commands for component %s" % comp)
457 commands = []
458 for c in self._components:
459 if c == comp:
460 component_commands = [ 'start', 'stop' ]
461 textui = None
462 try:
463 textui = self._comptextui[comp]
464 except KeyError:
465 self.debug("no text ui for component %s" % comp)
466
467 input_split = input.split()
468
469 if len(input_split) >= 2 or input.endswith(' '):
470 for command in component_commands:
471 if len(input_split) == 2:
472 if command.startswith(input_split[1]):
473 commands.append(command)
474 elif len(input_split) == 1:
475 commands.append(command)
476 if textui:
477 self.debug("getting component commands from ui of %s" % comp)
478 comp_input = ' '.join(input_split[1:])
479 if input.endswith(' '):
480 comp_input = comp_input + ' '
481 commands = commands + textui.getCompletions(comp_input)
482
483 return commands
484
485
487 completions = self.get_available_commands(input)
488
489
490 if len(input.split()) <= 1:
491 for c in self._components:
492 if c.startswith(input):
493 completions.append(c)
494
495 return completions
496
497 - def display_status(self):
498 availablecommands = self.get_available_commands(self.inputText)
499 available_commands = ' '.join(availablecommands)
500
501
502 self.stdscr.move(self.lasty+2,0)
503 self.stdscr.clrtoeol()
504
505 self.stdscr.addstr(self.lasty+2, 0,
506 "Available Commands: %s" % available_commands)
507
508 self.stdscr.move(self.lasty+3,0)
509 self.stdscr.clrtoeol()
510 self.stdscr.move(self.lasty+4,0)
511 self.stdscr.clrtoeol()
512
513 if self.command_result != "":
514 self.stdscr.addstr(self.lasty+4, 0, "Result: %s" % self.command_result)
515 self.stdscr.clrtobot()
516
517
518 - def admin_connected_cb(self, admin):
519 self.info('Connected to manager')
520
521
522 self.setPlanetState(self.admin.planet)
523
524 if not self._components:
525 self.debug('no components detected, running wizard')
526
527 self.show()
528
529 - def admin_disconnected_cb(self, admin):
530 message = "Lost connection to manager, reconnecting ..."
531 print message
532
534 log.debug('textadminclient', "handling connection-refused")
535
536 log.debug('textadminclient', "handled connection-refused")
537
539 log.debug('textadminclient', "handling connection-failed")
540
541 log.debug('textadminclient', "handled connection-failed")
542
543 - def admin_update_cb(self, admin):
544 self.update_components(self._components)
545
546 - def connectionLost(self, why):
549
550 - def whsStateAppend(self, state, key, value):
551 if key == 'names':
552 self.debug('Worker %s logged in.' % value)
553
554 - def whsStateRemove(self, state, key, value):
555 if key == 'names':
556 self.debug('Worker %s logged out.' % value)
557
558
560 """ Input is ready! """
561 try:
562 c = self.stdscr.getch()
563
564 if c == curses.KEY_BACKSPACE or c == 127:
565 self.inputText = self.inputText[:-1]
566 elif c == curses.KEY_STAB or c == 9:
567 available_commands = self.get_available_completions(self.inputText)
568 if len(available_commands) == 1:
569 input_split = self.inputText.split()
570 if len(input_split) > 1:
571 if not self.inputText.endswith(' '):
572 input_split.pop()
573 self.inputText = ' '.join(input_split) + ' ' + available_commands[0]
574 else:
575 self.inputText = available_commands[0]
576
577 elif c == curses.KEY_ENTER or c == 10:
578
579 self.run_command(self.inputText)
580
581 self.display_status()
582
583 self.stdscr.move(self.lasty+1,0)
584 self.stdscr.clrtoeol()
585 self.stdscr.addstr(self.lasty+1,0,'Prompt: ')
586 self.stdscr.refresh()
587 if len(self.nextcommands) > 0:
588 self.lastcommands = self.lastcommands + self.nextcommands
589 self.nextcommands = []
590 self.lastcommands.append(self.inputText)
591 self.inputText = ''
592 self.command_result = ''
593 elif c == curses.KEY_UP:
594 lastcommand = ""
595 if len(self.lastcommands) > 0:
596 lastcommand = self.lastcommands.pop()
597 if self.inputText != "":
598 self.nextcommands.append(self.inputText)
599 self.inputText = lastcommand
600 elif c == curses.KEY_DOWN:
601 nextcommand = ""
602 if len(self.nextcommands) > 0:
603 nextcommand = self.nextcommands.pop()
604 if self.inputText != "":
605 self.lastcommands.append(self.inputText)
606 self.inputText = nextcommand
607 elif c == curses.KEY_PPAGE:
608 if self._first_onscreen_component > 0:
609 self._first_onscreen_component = \
610 self._first_onscreen_component - 1
611 self.show()
612 elif c == curses.KEY_NPAGE:
613 if self._first_onscreen_component < len(self._components) - \
614 self.max_components_per_page:
615 self._first_onscreen_component = \
616 self._first_onscreen_component + 1
617 self.show()
618
619 else:
620
621 if len(self.inputText) == self.cols-2: return
622
623 if c<=256:
624 self.inputText = self.inputText + chr(c)
625
626
627 self.display_status()
628
629 self.stdscr.move(self.lasty+1,0)
630 self.stdscr.clrtoeol()
631
632 self.stdscr.addstr(self.lasty+1, 0,
633 'Prompt: %s' % self.inputText)
634 self.stdscr.refresh()
635 except Exception, e:
636 print e
637
638
639
640
641 - def componentCall(self, componentState, methodName, *args, **kwargs):
642
643
644
645
646 self.log("componentCall received for %r.%s ..." % (
647 componentState, methodName))
648 localMethodName = "component_%s" % methodName
649 name = componentState.get('name')
650
651 try:
652 textui = self._comptextui[name]
653 except KeyError:
654 return
655
656 if not hasattr(textui, localMethodName):
657 self.log("... but does not have method %s" % localMethodName)
658 self.warning("Component view %s does not implement %s" % (
659 name, localMethodName))
660 return
661 self.log("... and executing")
662 method = getattr(textui, localMethodName)
663
664
665 try:
666 result = method(*args, **kwargs)
667 except TypeError:
668 msg = "component method %s did not accept *a %s and **kwa %s (or TypeError)" % (
669 methodName, args, kwargs)
670 self.debug(msg)
671 raise errors.RemoteRunError(msg)
672 self.log("component: returning result: %r to caller" % result)
673 return result
674