mirror of
https://github.com/xcat2/confluent.git
synced 2024-11-26 03:19:48 +00:00
8ede431b60
If a console session is started and the server disconnects, cleanly exit instead of being confused by the whole affair.
318 lines
10 KiB
Python
Executable File
318 lines
10 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# ultimately, this should provide an interactive cli for navigating confluent
|
|
# tree and doing console with a socket. <ESC>]0;<string><BELL> can be used to
|
|
# present info such as whether it is in a console or other mode and, if in
|
|
# console, how many other connections are live and looking
|
|
# this means 'wcons' simply needs to make a terminal run and we'll take care of
|
|
# the title while providing more info
|
|
# this also means the socket interface needs to have ways to convey more
|
|
# interesting pieces of data (like concurrent connection count)
|
|
# socket will probably switch to a TLV scheme:
|
|
# 32 bit TL, 8 bits of type code and 24 bit size
|
|
# type codes:
|
|
# 0: string data
|
|
# 1: json data
|
|
# 24 bit size allows the peer to avoid having to do any particular parsing to
|
|
# understand message boundaries (which is a significant burden on the xCAT
|
|
# protocol)
|
|
|
|
# When in a console client mode, will recognize two escape sequences by
|
|
# default:
|
|
# Ctrl-E, c, ?: mimick conserver behavior
|
|
# ctrl-]: go to interactive prompt (telnet escape, but not telnet prompt)
|
|
# esc-( would interfere with normal esc use too much
|
|
# ~ I will not use for now...
|
|
|
|
import fcntl
|
|
import getpass
|
|
import optparse
|
|
import os
|
|
import select
|
|
import shlex
|
|
import socket
|
|
import ssl
|
|
import sys
|
|
import termios
|
|
import tty
|
|
|
|
consoleonly = False
|
|
target = "/"
|
|
path = os.path.dirname(os.path.realpath(__file__))
|
|
path = os.path.realpath(os.path.join(path, '..'))
|
|
sys.path.append(path)
|
|
|
|
import confluent.common.tlvdata as tlvdata
|
|
|
|
SO_PASSCRED = 16
|
|
conserversequence = '\x05c' # ctrl-e, c
|
|
|
|
oldtcattr = termios.tcgetattr(sys.stdin.fileno())
|
|
server = None
|
|
|
|
def prompt():
|
|
sys.stdout.write(target + ' -> ')
|
|
sys.stdout.flush()
|
|
|
|
def parseservervalue(serverstring):
|
|
if serverstring.find(']:') != -1:
|
|
server, port = serverstring[1:].split(']:')
|
|
elif serverstring[0] == '[':
|
|
server = serverstring[1:-1]
|
|
port = 4001
|
|
elif -1 != opts.server.find(':'):
|
|
server, port = opts.server.split(":")
|
|
else:
|
|
server = serverstring
|
|
port = 4001
|
|
return (server, port)
|
|
|
|
|
|
def parse_command(command):
|
|
args = shlex.split(command, posix=True)
|
|
return args
|
|
|
|
|
|
currchildren = None
|
|
|
|
def send_request(operation, path, server):
|
|
tlvdata.send_tlvdata(server, {'operation': operation, 'path': path})
|
|
result = tlvdata.recv_tlvdata(server)
|
|
while '_requestdone' not in result:
|
|
yield result
|
|
result = tlvdata.recv_tlvdata(server)
|
|
|
|
def do_command(command, server):
|
|
global target
|
|
global currconsole
|
|
global currchildren
|
|
if command == "": # result of ctrl-d
|
|
command = "exit\n"
|
|
print("exit")
|
|
command = command[:-1]
|
|
command = command.lower()
|
|
argv = parse_command(command)
|
|
if len(argv) == 0:
|
|
prompt()
|
|
return
|
|
if argv[0] == "exit":
|
|
server.close()
|
|
sys.exit(0)
|
|
if argv[0] == "cd":
|
|
otarget = target
|
|
target = fullpath_target(argv[1], forcepath=True)
|
|
for res in send_request('retrieve', target, server):
|
|
if 'error' in res:
|
|
print target + ': ' + res['error']
|
|
target = otarget
|
|
if argv[0] in ('cat', 'show', 'ls', 'dir'):
|
|
if len(argv) > 1:
|
|
targpath = fullpath_target(argv[1])
|
|
else:
|
|
targpath = target
|
|
for res in send_request('retrieve', targpath, server):
|
|
if 'item' in res: # a link relation
|
|
if type(res['item']) == dict:
|
|
print res['item']["href"]
|
|
else:
|
|
for item in res['item']:
|
|
print item["href"]
|
|
else: # generic attributes to list
|
|
for key in res.iterkeys():
|
|
if 'value' in res[key]:
|
|
print "%s=%s" % (key, res[key]['value'])
|
|
else:
|
|
if 'isset' in res[key] and res[key]['isset']:
|
|
print "%s=********" % key
|
|
else:
|
|
print "%s=" % key
|
|
if argv[0] == 'start':
|
|
targpath = fullpath_target(argv[1])
|
|
currconsole = targpath
|
|
tlvdata.send_tlvdata(server, {'operation': 'start', 'path': targpath})
|
|
status = tlvdata.recv_tlvdata(server)
|
|
if 'error' in status:
|
|
print 'Error: ' + status['error']
|
|
return
|
|
print '[console session started]'
|
|
startconsole()
|
|
return
|
|
prompt()
|
|
|
|
|
|
def fullpath_target(path, forcepath=False):
|
|
global target
|
|
pathcomponents = path.split("/")
|
|
if pathcomponents[0] == "": # absolute path
|
|
ntarget = path
|
|
else:
|
|
targparts = target.split("/")[:-1]
|
|
for component in pathcomponents:
|
|
if component in ('.',''): # ignore these
|
|
continue
|
|
elif component == '..':
|
|
if len(targparts) > 0:
|
|
del targparts[-1]
|
|
else:
|
|
targparts.append(component)
|
|
if forcepath:
|
|
targparts.append('')
|
|
ntarget = '/'.join(targparts)
|
|
if forcepath and (len(ntarget) == 0 or ntarget[-1] != '/'):
|
|
ntarget += '/'
|
|
return ntarget
|
|
|
|
def startconsole():
|
|
global inconsole
|
|
tty.setraw(sys.stdin.fileno())
|
|
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
|
|
inconsole = True
|
|
|
|
def exit(code=0):
|
|
global consoleonly
|
|
global inconsole
|
|
global currconsole
|
|
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
|
|
if consoleonly:
|
|
server.shutdown(socket.SHUT_RDWR)
|
|
server.close()
|
|
sys.exit(code)
|
|
else:
|
|
tlvdata.send_tlvdata(server, {'operation': 'stop', 'path': currconsole})
|
|
inconsole = False
|
|
prompt()
|
|
|
|
def conserver_command(fh, command):
|
|
while not command:
|
|
ready, _, _ = select.select((fh,), (), (), 1)
|
|
if ready:
|
|
command += fh.read()
|
|
if command[0] == '.':
|
|
print("disconnect]\r")
|
|
exit()
|
|
elif command[0] == '?':
|
|
print("help]\r")
|
|
print(". disconnect\r")
|
|
print("<cr> abort command\r")
|
|
elif command[0] == '\x0d':
|
|
print("ignored]\r")
|
|
else: #not a command at all..
|
|
print("unknown -- use '?']\r")
|
|
|
|
|
|
def check_escape_seq(input, fh):
|
|
while conserversequence.startswith(input):
|
|
if input.startswith(conserversequence): # We have full sequence
|
|
sys.stdout.write("[")
|
|
sys.stdout.flush()
|
|
return conserver_command(fh, input[len(conserversequence):])
|
|
ready, _, _ = select.select((fh,), (), (), 3)
|
|
if not ready: # 3 seconds of no typing
|
|
break
|
|
input += fh.read()
|
|
return input
|
|
|
|
|
|
def connect_unix_server(sockpath):
|
|
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
server.setsockopt(socket.SOL_SOCKET, SO_PASSCRED, 1)
|
|
server.connect(sockpath)
|
|
return server
|
|
|
|
|
|
def connect_tls_server(serverstring):
|
|
host, port = parseservervalue(serverstring)
|
|
for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
|
|
socket.SOCK_STREAM):
|
|
af, socktype, proto, cononname, sa = res
|
|
try:
|
|
server = socket.socket(af, socktype, proto)
|
|
except:
|
|
server = None
|
|
continue
|
|
try:
|
|
server.settimeout(5)
|
|
server.connect(sa)
|
|
except:
|
|
server.close()
|
|
server = None
|
|
continue
|
|
break
|
|
if server is None:
|
|
sys.stderr.write("Failed to connect to %s\n" % serverstring)
|
|
sys.exit(1)
|
|
secserver = ssl.wrap_socket(server)
|
|
return secserver
|
|
|
|
parser = optparse.OptionParser()
|
|
parser.add_option("-s", "--server", dest="server",
|
|
help="TLS server to connect to", metavar="SERVER:PORT")
|
|
parser.add_option("-u", "--unixsocket", dest="unixsock",
|
|
help="TLS server to connect to", metavar="UNIXDOMAINSOCKET")
|
|
opts, args = parser.parse_args()
|
|
if opts.server: # going over a TLS network
|
|
server = connect_tls_server(opts.server)
|
|
elif opts.unixsock:
|
|
server = connect_unix_server(opts.unixsock)
|
|
elif os.path.exists("/var/run/confluent/api.sock"):
|
|
server = connect_unix_server("/var/run/confluent/api.sock")
|
|
|
|
#Next stop, reading and writing from whichever of stdin and server goes first.
|
|
#see pyghmi code for solconnect.py
|
|
|
|
banner = tlvdata.recv_tlvdata(server)
|
|
authinfo = tlvdata.recv_tlvdata(server)
|
|
while authinfo['authpassed'] != 1:
|
|
username = raw_input("Name: ")
|
|
passphrase = getpass.getpass("Passphrase: ")
|
|
tlvdata.send_tlvdata(server,
|
|
{'username': username, 'passphrase': passphrase})
|
|
authinfo = tlvdata.recv_tlvdata(server)
|
|
# clear on start can help with readable of TUI, but it
|
|
# can be annoying, so for now don't do it.
|
|
# sys.stdout.write('\x1b[H\x1b[J')
|
|
# sys.stdout.flush()
|
|
|
|
doexit = False
|
|
inconsole = False
|
|
pendingcommand = ""
|
|
if len(args) == 1: # a node name, go straight to trying to console
|
|
consoleonly = True
|
|
do_command("start /node/%s/console/session" % args[0], server)
|
|
else:
|
|
prompt()
|
|
while not doexit:
|
|
rdylist, _, _ = select.select((sys.stdin, server), (), (), 60)
|
|
for fh in rdylist:
|
|
if fh == server:
|
|
# this only should get called in the
|
|
# case of a console session
|
|
# each command should slurp up all relevant
|
|
# recv_tlvdata potential
|
|
#fh.read()
|
|
try:
|
|
data = tlvdata.recv_tlvdata(fh)
|
|
except Exception:
|
|
data = None
|
|
if type(data) == dict:
|
|
print repr(data)
|
|
continue
|
|
if data is not None:
|
|
sys.stdout.write(data)
|
|
sys.stdout.flush()
|
|
else:
|
|
doexit = True
|
|
sys.stdout.write("\r\n[remote disconnected]\r\n")
|
|
break
|
|
else:
|
|
if inconsole:
|
|
input = fh.read()
|
|
input = check_escape_seq(input, fh)
|
|
if input:
|
|
tlvdata.send_tlvdata(server, input)
|
|
else:
|
|
command = fh.readline()
|
|
do_command(command, server)
|
|
consoleonly = True
|
|
exit()
|