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