Hilfe bei der Anpassung eines Python-Libvirt Programmes

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
eye
User
Beiträge: 10
Registriert: Dienstag 19. November 2013, 08:36

Hi,
ich bin eine Java-Entwicklerin (eigentlich), die letzten Monate hab ich viel an einem Projekt gearbeitet, dass ich derzeit mit Perl schreibe. Verwaltung von mehreren KVM-Servern über eine Weboberfläche mittels libvirt. Der Unterschied zu den unzähligen anderen Programmen, die es schon gibt, wird sein, dass es möglich ist statt VNC oder Spice - also grafischen Oberflächen - eine serielle Textconsole im Browser zu bekommen. Und das Ganze ohne Java (*heul*), damit es auch mit einem Smartphone Browser und über Umts funktioniert. Das Programm ist soweit "fertig", bis auf diese Textconsole, die ja das Alleinstellungsmerkmal sein soll. Die Perl libvirt-api hat mich an der Stelle schmählich im Stich gelassen (so langsam, dass der Login wegen 180sec timeout nicht geht, sendet alles doppelt und dreifach usw...), da bin ich per Zufall auf ein Python-Script gestoßen, das mehr oder weniger genau das in perfekt und schnell macht, was ich mit Perl nicht hinbekomme. Leider macht es ein bisschen zu viel... Ich hoffe, dass Ihr mir helfen könnt, das zu ändern. Ich kann null Python, hab nie zuvor damit gearbeitet, und verstehe nach mehrmaligem Lesen vom Quellcode ca 2/3 was leider nicht genug ist um es selber so zu ändern, wie ich es gerne hätte. Ein bisschen was hab ich anpassen können, aber das Wichtigste leider nicht.

Das Python Script baut über libvirt eine Verbindung zu der seriellen Console mit Hilfe von einem Stream auf. Die Ausgabe konnte ich so ändern, dass er die Ergebnisse zusätzlich in einen Textfile schreibt. Was ich nicht geschafft habe ist, dass es nicht in Endlosschleife in der Console bleibt, sondern nur einen Befehl abarbeitet. Ich würde gerne das Script mit einem "Befehl" als drittes Argument aufrufen, den er dann abarbeitet, das Ergebnis in die Datei schreibt, und sich dann wieder beendet, statt dass er den Befehl über stdin liest. Ich werde die Stellen im Code kommentieren, wo ich denke, dass ich dort etwas ändern muss, und hoffe dass Ihr mir sagen könnt wie ich das machen kann. Außerdem kann alles Überflüssige raus... Ich hoffe wirklich auf Hilfe, Erklärungen zu Stellen, die ich noch nicht verstehe und dann endlich auf die Fertigstellung meines Programmes...

So ziemlich alles was unten an Kommentaren drin ist hab ich schon versucht, aber hab mehr oder weniger viele Fehlermeldungen produziert, mit denen ich zum Teil nichts anfangen kann... Most welcome ist eine Erklärung zu callback events, die ich nirgendwo für mich verständlich finden konnte, ebenso Codesnippets oder angepasste Programmteile, oder auch nur Bsp. zum abgucken, die mir helfen könnten.

Quelle des gefunden Programmes ist diese Website: http://libvirt.org/git/?p=libvirt.git;a ... allback.py

Code: Alles auswählen

#!/usr/bin/env python
# consolecallback - provide a persistent console that survives guest reboots

import sys, os, libvirt, tty, termios, atexit
#import logging

global filed
filed = open("/var/tmp/testfile", 'ab+')

# die ganzen terminos und tty sachen könnten dann weg - aber ich bin mir doch recht sicher, dass ich die nicht einfach löschen kann....
def reset_term():
    termios.tcsetattr(0, termios.TCSADRAIN, attrs)

# dass die Verbindung bei einem Error offen bleibt ist überflüssig, da ich die verbindung eh nach jedem Befehl wieder schließen würde (ich kann meines Wissens die Verbindung ja nicht offenlassen und trotzdem über Html-Post Daten an das Python Proggie übergeben, während es läuft...) Kann ich das einfach löschen? - Edit: ja, scheint so!
#def error_handler(unused, error):
    # The console stream errors on VM shutdown; we don't care
   #if (error[0] == libvirt.VIR_ERR_RPC and
        #error[1] == libvirt.VIR_FROM_STREAMS):
        #return
    #logging.warn(error)

class Console(object):
    def __init__(self, uri, uuid):
        self.uri = uri
        self.uuid = uuid
        self.connection = libvirt.open(uri)
        self.domain = self.connection.lookupByUUIDString(uuid)
        self.state = self.domain.state(0)
        self.connection.domainEventRegister(lifecycle_callback, self)
        self.stream = None
        self.run_console = True
#        logging.info("%s initial state %d, reason %d",
#                     self.uuid, self.state[0], self.state[1])


def check_console(console):
    if (console.state[0] == libvirt.VIR_DOMAIN_RUNNING or
        console.state[0] == libvirt.VIR_DOMAIN_PAUSED):
        if console.stream is None:
            console.stream = console.connection.newStream(libvirt.VIR_STREAM_NONBLOCK)
            console.domain.openConsole(None, console.stream, 0)
		#auch hiermir hab ich meine Probleme - stream_callback und so - wie funktionieren diese stream-events genau?
            console.stream.eventAddCallback(libvirt.VIR_STREAM_EVENT_READABLE, stream_callback, console)
    else:
        if console.stream:
		#hier genauso - was macht das?
            console.stream.eventRemoveCallback()
            console.stream = None

    return console.run_console

#ich denke diese Methode muss ich in 2 aufteilen, eine die den Stream beendet und eine die "com" sendet?
def stdin_callback(watch, fd, events, console):
    readbuf = os.read(fd, 1024)
    if readbuf.startswith("\x1d"):
        console.run_console = False
        returnfiled.close()
    if console.stream:
        console.stream.send(readbuf)

#ich weiß einfach nicht wie ich diese methode manuell aufgerufen kann, aber hier lese ich die daten von der console...
def stream_callback(stream, events, console):
    try:
        received_data = console.stream.recv(1024)
    except:
        return
	#wenn man das os.write auskommentiert geht garnichts mehr, obwohl ich das doch in eine datei schreibe - warum?
    os.write(0, received_data)
    filed.write(received_data)

# Erklärung bitte - dieser ganze callback Kram ist für mich gerade ein Buch mit sieben Siegeln...
def lifecycle_callback (connection, domain, event, detail, console):
    console.state = console.domain.state(0)
#    logging.info("%s transitioned to state %d, reason %d",
#                 console.uuid, console.state[0], console.state[1])

# main
def main():
    if len(sys.argv) < 2:
        print "Usage:", sys.argv[0], "URI UUID Command"
        print "for example:", sys.argv[0], "'qemu:///system' 'a3953fe1-39bc-2338-eada-295638ad13e' ls -la"
        sys.exit(1)

    uri = sys.argv[1]
    uuid = sys.argv[2]
    com = ' '.join(sys.argv[3:])
    
    print "Escape character is ^]"
#    logging.basicConfig(filename='msg.log', level=logging.DEBUG)
#    logging.info("URI: %s", uri)
#    logging.info("UUID: %s", uuid)

    libvirt.virEventRegisterDefaultImpl()
    #libvirt.registerErrorHandler(error_handler, None)

    atexit.register(reset_term)
    attrs = termios.tcgetattr(0)
    tty.setraw(0)

    console = Console(uri, uuid)
	# mit dieser Zeile hab ich wieder Probleme... ist ja klar....
    console.stdin_watch = libvirt.virEventAddHandle(0, libvirt.VIR_EVENT_HANDLE_READABLE, stdin_callback, console)

	# ich denke das hier kann weg, da es dafür sorgt, dass ich in der Konsole bleibe
    while check_console(console):
        libvirt.virEventRunDefaultImpl()

if __name__=="__main__":
    main();
Zuletzt geändert von eye am Dienstag 19. November 2013, 10:49, insgesamt 4-mal geändert.
Benutzeravatar
eye
User
Beiträge: 10
Registriert: Dienstag 19. November 2013, 08:36

Ok zu Zeile 82: hier hab ich jetzt:

Code: Alles auswählen

if len(sys.argv) < 2:
        print "Usage:", sys.argv[0], "URI UUID COMMAND"
        print "for example:", sys.argv[0], "'qemu:///system' 'a3953fe1-39bc-2338-eada-295638ad13e' ls -la"
    sys.exit(1)
uri = sys.argv[1]
uuid = sys.argv[2]
com = ""
for i in range(3, len(sys.argv)):
    com += sys.argv[i]
print com
Und ich hab rausgefunden, dass er einen Befehl in Anführungszeichen nicht splittet, einen ohne schon, und man keine <>| ungequotet schreiben darf...

Ich editiere alles was neu dazu kommt dann immer oben rein, damit man sieht, wo noch fragen überbleiben oder noch was zu tun ist...
Zuletzt geändert von eye am Dienstag 19. November 2013, 10:30, insgesamt 1-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

eye hat geschrieben:

Code: Alles auswählen

com = ""
for i in range(3, len(sys.argv)):
    com += sys.argv[i]
print com
Diesen Teil kannst du übrigens auch wesentllich kürzer in Python ausdrücken:

Code: Alles auswählen

print ''.join(sys.argv[3:])
`3:` heißt: "Gib alle Elemente von Index 3 bis zum Ende"

`''.join(...)` heißt: "Füge alle übergebenen Elemente zu einem Gesamt-String zusammen", wobei '' (also: leere Zeichenkette) der Trenner ist. Du könntest z.B. auch `' '.join(...)` schreiben, um die Elemente mit Leerzeichen zusammenzufügen.

Im Übrigen empfiehlt es sich, ein Python-Tutorial zu lesen, falls nocht nicht geschehen. ;)
Benutzeravatar
eye
User
Beiträge: 10
Registriert: Dienstag 19. November 2013, 08:36

Danke für den Tipp mit der Kurzschreibweise

Python Tutorials gucke ich immer mal wieder drüber, wenn ich ein Problem hab... suche da eher nach Sachen die nicht sofort klar sind, weil sich Python dort von anderen Sprachen unterscheidet... Ein komplettes Tutorial ist für mich eher sinnlos.


Edit: Error und Logging losgeworden im obigen Quelltext ohne dadurch Fehler zu verursachen
BlackJack

@eye: Ich denke Du solltest nicht ein vorhandenes Skript anpassen was einen nicht unbedeutenden Teil auf die Verbindung der virtuellen Console mit der lokalen Konsole verwendet, sondern Dir lieber von 0 auf ein Programm erstellen was genau das macht was Du haben willst. Dann hat man da (hoffentlich) auch nicht so furchbare nachträglich eingebaute ``global``e Variablen. Wobei das ``global`` an der Stelle unsinnig ist, denn auf Modulebene hat dieses Schlüsselwort überhaupt keinen Effekt.

Ich sehe allerdings so ein bisschen das Problem dass Du herausfinden musst wann Dein Befehl fertig abgearbeitet wurde. Das ist dem Beispielprogramm ja total egal, das arbeitet einfach in einer Endlosschleife und sendet alles was man eingibt, und empfängt und schreibt alles was von der virtuellen Konsole ausgegeben wird. Irgendwelche Grenzen von Befehlen die man eingibt und der Antwort die man erhält, spielen dabei keine Rolle.

Die Ereignisverarbeitung kann man IMHO sogar ohne Dokumentation ziemlich gut erraten. Es gibt ja aber eine API-Dokumentation für die Bibliothek. Und ein Benutzer-Handbuch. Zum Raten: Es wird zum Beispiel ein Stream-Objekt erstellt, das wird bei openConsole() zum öffnen der virtuellen Konsole übergeben, und danach wird eine Ereignisbehandlung mit der Konstanten VIR_STREAM_EVENT_READABLE registriert. Ist ja nun nicht so schwer zu erraten das die aufgerufen wird, wenn auf dem Stream der virtuellen Konsole Daten zum lesen vorliegen, die dann in der Behandlungsfunktion gelesen und verarbeitet werden.

Den `error_handler()` würde ich nicht einfach löschen denn dessen Aufgabe ist es ja die Fehler zu protokollieren. Das ist zur Fehlersuche wichtig, dass die nicht einfach kommentarlos verschluckt werden.

Zu den Rückruffunktionen:

`stdin_callback()` wird immer aufgerufen wenn auf der lokalen Konsole Daten zum lesen vorliegen, also wenn der Benutzer etwas eingetippt hat, oder wenn das Programm selber etwas in die Standardeingabe (jep, kein Tippfehler!) geschrieben hat.

`stream_callback()` kann und soll man nicht manuell aufrufen. Die wird immer dann aufgerufen wenn von der virtuellen Konsole Daten gelesen werden können. Auf die Standardausgabe muss man schreiben, damit das durch `stdin_callback()` erfasst und wieder zur virtuellen Konsole geschickt werden kann. Die möchte also anscheinend gerne ein Echo ihrer Ausgaben haben bevor sie weitermacht.

`lifecycle_callback()` wird immer dann aufgerufen wenn sich der Zustand der Domain geändert hat. Welche Zustände es gibt, kann man der Dokumentation entnehmen. Zum Beispiel so etwas wie die Domain läuft oder ist pausiert. Du siehst ja auch die Informationen die von der Funktion protokolliert werden.

Die Schleife am Ende kannst Du nicht einfach weg lassen, die hält die gesamte Ereignisverarbeitung am laufen.

`__name__` ist ganz offensichtlich '__main__' wenn Du in die Hauptfunktion kommst. Das der Name an '__main__' gebunden wird, wenn man das Modul als Programm ausführt und an den Modulnamen wenn man das Modul importiert, hätte man in der Python-Dokumentation nachlesen können. Oben rechts ist fast überall in der Dokumentation ein Link auf den Index worüber man schnell zu der Dokumentation von Objekten kommt, von denen man nur den Namen hat.
Benutzeravatar
eye
User
Beiträge: 10
Registriert: Dienstag 19. November 2013, 08:36

BlackJack hat geschrieben:@eye: Ich denke Du solltest nicht ein vorhandenes Skript anpassen was einen nicht unbedeutenden Teil auf die Verbindung der virtuellen Console mit der lokalen Konsole verwendet, sondern Dir lieber von 0 auf ein Programm erstellen was genau das macht was Du haben willst.
Das war auch mein erster Ansatz, genau das hab ich ja mit Perl versucht und bin kläglich gescheitert, obwohl ich die Sprache nach 2 Monaten jeden Tag damit arbeiten inzwischen ganz gut kann... Aber zumindest dort war die sys::virt Api auch kaum dokumentiert... Da erschien mir dieses Script wie ein Geschenk des Himmels... ^^
BlackJack hat geschrieben:Dann hat man da (hoffentlich) auch nicht so furchbare nachträglich eingebaute ``global``e Variablen. Wobei das ``global`` an der Stelle unsinnig ist, denn auf Modulebene hat dieses Schlüsselwort überhaupt keinen Effekt.
Das hab ich irgendwo im Netz so gefunden und abgeschrieben - und da es funktioniert hat nicht weiter drüber nachgedacht... *rotwerd*
BlackJack hat geschrieben:Ich sehe allerdings so ein bisschen das Problem dass Du herausfinden musst wann Dein Befehl fertig abgearbeitet wurde. Das ist dem Beispielprogramm ja total egal, das arbeitet einfach in einer Endlosschleife und sendet alles was man eingibt, und empfängt und schreibt alles was von der virtuellen Konsole ausgegeben wird. Irgendwelche Grenzen von Befehlen die man eingibt und der Antwort die man erhält, spielen dabei keine Rolle.
Genau erkannt. Das war unter anderem bei meinem Perl Programm das Problem zusätzlich zur Geschwindigkeit....
BlackJack hat geschrieben:Die Ereignisverarbeitung kann man IMHO sogar ohne Dokumentation ziemlich gut erraten. Es gibt ja aber eine API-Dokumentation für die Bibliothek. Und ein Benutzer-Handbuch. Zum Raten: Es wird zum Beispiel ein Stream-Objekt erstellt, das wird bei openConsole() zum öffnen der virtuellen Konsole übergeben, und danach wird eine Ereignisbehandlung mit der Konstanten VIR_STREAM_EVENT_READABLE registriert. Ist ja nun nicht so schwer zu erraten das die aufgerufen wird, wenn auf dem Stream der virtuellen Konsole Daten zum lesen vorliegen, die dann in der Behandlungsfunktion gelesen und verarbeitet werden.
Natürlich hab ich auch so meine Vermutungen angestellt und bin auch schon mal schrittweise durchgegangen, aber eine Erklärung von einem der genau weiß was er tut ist immer besser als Vermutungen und Reverse Engeneering.
BlackJack hat geschrieben:Den `error_handler()` würde ich nicht einfach löschen denn dessen Aufgabe ist es ja die Fehler zu protokollieren. Das ist zur Fehlersuche wichtig, dass die nicht einfach kommentarlos verschluckt werden.
Naj wenn ich das richtig verstanden habe ist er in diesem einen Fall wohl eher dazu da Fehler zu verschlucken - ok und er schreibt sie in den log ^^
BlackJack hat geschrieben:Zu den Rückruffunktionen:

`stdin_callback()` wird immer aufgerufen wenn auf der lokalen Konsole Daten zum lesen vorliegen, also wenn der Benutzer etwas eingetippt hat, oder wenn das Programm selber etwas in die Standardeingabe (jep, kein Tippfehler!) geschrieben hat.

`stream_callback()` kann und soll man nicht manuell aufrufen. Die wird immer dann aufgerufen wenn von der virtuellen Konsole Daten gelesen werden können. Auf die Standardausgabe muss man schreiben, damit das durch `stdin_callback()` erfasst und wieder zur virtuellen Konsole geschickt werden kann. Die möchte also anscheinend gerne ein Echo ihrer Ausgaben haben bevor sie weitermacht.

`lifecycle_callback()` wird immer dann aufgerufen wenn sich der Zustand der Domain geändert hat. Welche Zustände es gibt, kann man der Dokumentation entnehmen. Zum Beispiel so etwas wie die Domain läuft oder ist pausiert. Du siehst ja auch die Informationen die von der Funktion protokolliert werden.
Vielen Dank für die detailierte Erklärung. Gerade das mit dem "Echo" ist ein super Hinweis.
Die Zustände der Domains kenn ich natürlich, nur dass man die auch so abrufen kann ist mir neu.

BlackJack hat geschrieben:Die Schleife am Ende kannst Du nicht einfach weg lassen, die hält die gesamte Ereignisverarbeitung am laufen.
War mir fast klar... aber durch was ersetze ich sie?
Benutzeravatar
eye
User
Beiträge: 10
Registriert: Dienstag 19. November 2013, 08:36

Nach deinen Erklärungen sieht es für mich so aus, als müsste ich ein Phyton Proggie schreiben, dass die Verbindung aufbaut und offen hält, alles annimmt und "quittiert" was es geschickt bekommt und es für mich in ein textfile schreibt von wo ich es lesen und als json an meine website schicken kann... soweit so gut. Aber wie um alles in der Welt bekomme ich die html posts mit den neuen befehlen in das laufende programm?!?

Das ist ja überhaupt der Grund, dass ich das Programm so für mich so ändern wollte, dass ich es jedes mal aufrufen wollte, wenn ein post ankommt, dass ich eben nicht weiß, wie es anders gehen soll/kann. Aber wie du richtig erkannt hast hätte ich dann wieder das selbe problem, dass ich nicht weiß, wann er fertig ist mit daten senden. also z.B. ein "top" wäre damit unmöglich... entweder hätte ich eine statisches bild oder ich würde endlos auf ein ende vom stream warten... oder ich würde generell nach 80*24 bytes abbrechen, da das ca ein "fenster" ist. Ich würde also an der selben stelle feststecken wie bei perl, nur dass das programm sehr viel schneller reagiert als die perl api (was tatsächlich daran liegen könnte - wenn die sich gleichen - dass ich den Empfang nicht durch ein echo quittiere) Das wird gleich mal geprüft! (Edit: Nein, leider nicht...)
Zuletzt geändert von eye am Dienstag 19. November 2013, 12:31, insgesamt 1-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sicher, dass es immer 80*24 Bytes sind für eine Terminal-"Seite"? Ein Programm wie `top` könnte auch Tab-Stops benutzen, um den Leerraum zwischen den Spalten zu erzeugen - dann hast du *keine* 80 Bytes pro "voller" Zeile. Oder habe ich da einen Denkfehler?

Ich weiß jetzt nicht, wie `top` das tatsächlich macht. Von `ls` weiß ich auf jeden Fall, dass die File-Listings immer viele `\t`s beinhalten.
Benutzeravatar
eye
User
Beiträge: 10
Registriert: Dienstag 19. November 2013, 08:36

Nein nicht sicher, deswegen wäre es ja auch eine "Krücke" die ich nicht benutzen will....
BlackJack

@eye: Was ich tue weiss ich in diesem Zusammenhang auch nicht. :-) Ich habe mir einfach die API ein wenig angeschaut und die Ereignisverarbeitung sieht halt so ähnlich aus wie das bei der `libglib` zum Beispiel auch funktioniert.

Der `error_handler()` ist nicht zum verschlucken der Fehler da, denn dafür bräuchte man einfach gar nichts tun (es sei denn es gibt eine Standardfehlerbehandlung die dann greift wenn man keine eigene Behandlungsfunktion registriert).

Die Schleife am Ende müsstest Du durch etwas ersetzen was solange läuft bis ein Befehl abgearbeitet ist und das Ergebnis zurück geschickt werden kann. Wie man das testet → gute Frage. Vielleicht ist die einzige Möglichkeit die Daten auf den Prompt von der virtuellen Konsole zu testen.

Die Rückgabe in einer Textdatei zu schreiben halte ich für einen unnötigen Umweg. Man kann die sich doch einfach im Speicher merken.

Ein ``top`` wäre sowieso unmöglich, denn das wird a) nicht fertig bis es vom Benutzer abgebrochen wird, und b) benutzt das Programm Terminalsteuerzeichen für die Ausgabe. Man müsste also einen Terminalemulator schreiben oder einen fertigen finden.

Edit: Es hat IMHO schon einen Grund warum zum Beispiel die virtuellen Konsolen von Remote Management Karten die man im Browser aufrufen kann in Java geschrieben sind. Das HTTP-Protokoll ist einfach nicht so wirklich geeignet für so etwas. Interaktion mit einer Konsole ist eine kontinuierliche, asynchrone, bidirektionale, zustandsbehaftete Angelegenheit, im Gegensatz zu dem zustandslosen, Anfrage/Antwort-Modell von HTTP.
Benutzeravatar
eye
User
Beiträge: 10
Registriert: Dienstag 19. November 2013, 08:36

Naja ich überlege halt derzeit an zwei varianten des programmes herum...


Einmal das, was ich oben habe so abzuändern, dass vorne befehl rein, verbindung aufbauen und abbauen und hinten ergebnis raus - da bräuchte ich sicher kein textfile als zwischenspeicher, da hast du vollkommen recht.


die anderen idee, die ich habe, ist ein programm das die Verbindung aufbaut und dann hält, bis man sie im Browser explizit wieder abbaut - da müsst man halt über einen irgendwie gearteten weg die befehle in der programm bekommen, und das könnte dann die ausgabe in ein textfile schreiben dass man von außen auslesen kann... irgendwie so, bin mir da ncihtmal sicher ob das überhaupt machbar und sinnvoll ist. es verhindert auf jeden fall das ständige stream an stream aus, was auch nciht das beste ist, was man machen kann...
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@eye:
Du kannst Dir mal anyterm anschauen, da ist recht viel zu dem Problem erläutert und auch sicherheitsrelavante Gedanken kommen nicht zu kurz.

Kurz mal ein Skizzierung, wenn Du mit Browser-Boardmitteln auskommen willst/musst:

- Du brauchst einen JS-Terminalemulator.
Der kümmert sich um die korrekte Terminalausgabe und verarbeitet die Tasteneingabe. Es gibt da ein paar recht weit Fortgeschrittene, allerdings ist die Tastenbehandlung problematisch, da die Feinheiten (z.B. Drittbelegungen und Terminalsteuerkommandos) von den Browsern unterschiedlich dargeboten werden bzw. bestimmte Kombinationen nicht im Browser landen, sondern schon vom OS abgefangen werden.

- Protokoll der Wahl ist höchstwahrscheinlich HTTP(S). websockets wäre besser geeignet, leider ist das Protokoll immer noch in der Entwicklung und sehr verschieden in den Browsern umgesetzt.
Mit HTTP wirds insofern schwierig, da Du sowohl die Asynchronität als auch die Bidirektionalität abbilden musst. Einfachste Vorgehensweise wäre ständiges Polling. long polling geht mit etwas Verrenkungen auch.

- Du brauchst serverseitig einen (HTTP)-Serverprozess, der den Terminalstream liest, weiterreicht und die Tastatureingaben zurückschreibt. Das geht wunderbar mit Pseudoterminals.

Ich bastle ebenfalls in meiner Freizeit an einer solchen Lösung, leider ist die Zeit viel zu knapp und z.B. der Emulator nicht viel weiter als über den Parser hinaus gediehen. Wobei ich den Parser hier(tss muss die Sourcen mal in ein öffentliches Repo packen) nach JS migriert habe.
Benutzeravatar
eye
User
Beiträge: 10
Registriert: Dienstag 19. November 2013, 08:36

Hm, bisher hatte ich vor das einfacher zu lösen und einfach die Sachen in ein Scrollbares schwarzes HTML Textfield zu schreiben, mit einer input eingabezeile unten. dann gäbe es keine tab completion und die history funktion würde der browser übernehmen, aber ich müsste eben keinen terminal emulator nehmen/schreiben.

Ständiges polling kommt nicht in frage, das belastet die Hypervisor viel zu sehr, also hatte ich vor, ape (http://ape-project.org/wiki/index.php?page=Main+page) zu benutzen.

Du meinst ich muss doch einen terminal emulator nehmen? und nicht einfach nur mit jquery rummurksen?

Danke übrigens für die Links...

Das bringt mir einiges an input für die browserseite, hilft mir aber leider nicht bei der anderen seite weiter, weil ich keine ahnung hab wie ich die daten und tastendrücke und so aus dem script oben an ein pseudoterminal weiterleiten kann/soll...... wie gesagt - erstens python = fremde sprache, gleichzeitig verständnisprobleme was streams angeht... und außerdem nicht mehr ahnung von webprogrammierung als ein hobbyprogrammierer der das seit einem monat macht...
BlackJack

@eye: Die Frage ist halt ob so etwas wie ``top`` tatsächlich möglich sein soll. *Das* kommt ohne Terminalemulation nicht aus, weil es Terminalsteuersequenzen verwendet um den Cursor an bestimmte stellen zu setzen, Reversmodus und eventuell Farben anzuzeigen und so weiter.

Was in Deiner Aufzählung der Dinge die man verstehen muss (oder fertige Software finden muss) vergessen hast sind Pseudoterminals. :-) Da kannst Du das Beispielprogramm von libvirt vielleicht sogar tatsächlich mit leichten (?) Änderungen übernehmen. Nämlich in dem Du statt `stdin` zu verwenden ein Pseudoterminal (`pty`) erstellst (ich gehe jetzt mal von Linux auf dem Server aus) und das irgendwie über das Internet mit einem Terminalemulator im Browser verbindest.
Benutzeravatar
eye
User
Beiträge: 10
Registriert: Dienstag 19. November 2013, 08:36

Mit besonderer Betonung auf irgendwie ^^
Jemand einen Vorschlag wie man das realisieren könnte?

Und ja, ist natürlich ein Linux Server.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@eye:
Für einfache Terminalinteraktion reicht der Ansatz mit einem Eingabefeld und der Ausgabe in einem div. Damit kannst Du aber dann nur genau das - Befehl eingeben - Ausgabe lesen. Für echte Terminalprogramme wie top, vim, mc etc. reicht das leider nicht. Eine Zwischenvariante wäre hier der Einsatz von pexpect, das Pythonmodul bringt einen leichtgewichtigen Terminalemulator mit. Damit könnte man einen "Terminalscraper" realisieren, heisst die Terminalapp schreibt eigentlich auf dem Emulator und Du holst Dir nur die Ausgabe und schreibst sie in den Browser.

Für full featured kommst Du um einen Terminalemulator nicht herum.
Benutzeravatar
eye
User
Beiträge: 10
Registriert: Dienstag 19. November 2013, 08:36

Für full featured kommst Du um einen Terminalemulator nicht herum.
Und nehmen wir mal an ich hätte so einen Terminalemulator wie z.B. GateOne oder ShellInABox - wie bekomme ich die dazu mit dem Python Programm zu sprechen? Wo oder was ist die Schnittstelle?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

eye hat geschrieben:Und nehmen wir mal an ich hätte so einen Terminalemulator wie z.B. GateOne oder ShellInABox - wie bekomme ich die dazu mit dem Python Programm zu sprechen? Wo oder was ist die Schnittstelle?
GateOne scheint mit der entsprechenden Serverkomponente zu kommen.

Prinzipiell läuft das so - Du erzeugst einen neuen Prozess, der am Pseudoterminal hängt - `pty.fork()`. Der Aufruf gibt dem Elternprozess (dem Pythonskript) die PID des Kindprozesses und einen filedescriptor, welcher der MASTER des Pseudoterminals ist. Der Kindprozess erhält PID 0 und einen ungültigen filedescriptor. Im Kindprozess startest Du das gewünschte Programm, z.B. eine Shell.
Über MASTER im Elternprozess sprichst Du mit dem Programm via `read` und `write`. Wichtig zu wissen ist, dass der MASTER eines Pseudoterminals die Terminalseite ist. Das ist zunächst etwas verwirrend, da man normalerweise nur das andere Ende kennt (STDIN/OUT/ERR des Kindprogrammes).
Ein Emulator wie xterm liest jetzt einfach den MASTER, wertet die Steuerkommandos aus, setzt Cursor, Farben, Puffer etc. und schreibt die Eingabe (Tastendruck) nach MASTER. Für einen Terminalemulator im Browser musst Du die Streams vom MASTER noch an den Browser delegieren.

NB: Falls das unklar sein sollte - ein Pseudoterminal ist eine "clevere" Pipe, die ein MASTER- und ein Programm-Ende kennt. Clever deshalb, weil der Datenstrom nicht einfach von A nach B geschaufelt wird, sondern kernelseitig Leitungseigenschaften und Treibermodell früherer echter Terminalanbindungen emuliert werden. Die kannst Du mit `termios` und `ioctl` ansprechen (z.B. Größe des Displays, Eingabemodus etc.).
Antworten