Loading cccli/command.py +0 −43 Original line number Original line Diff line number Diff line Loading @@ -414,46 +414,3 @@ class TqlCommand(RemoteCommand): args2[self.options.tql_index] = "id=%s"%obj["id"] args2[self.options.tql_index] = "id=%s"%obj["id"] # now we can make call # now we can make call self._unsecure_rpccall(args2, **kwargs) self._unsecure_rpccall(args2, **kwargs) class ConsoleCommand(TqlCommand): def __init__(self, cli, argv0): TqlCommand.__init__(self, cli, argv0) def start_console(self, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr): self.stdin = stdin.fileno() self.stdout = stdout.fileno() self.stderr = stderr.fileno() if self.stdin == sys.stdin.fileno(): sys.stdin.flush() if self.stdout == sys.stdout.fileno(): sys.stdout.flush() if self.stderr == sys.stderr.fileno(): sys.stderr.flush() self._old_settings = termios.tcgetattr(self.stdout) tty.setraw(self.stdin) self.running = True def stop_console(self): # Reset terminal attributes. termios.tcsetattr(sys.stdout, termios.TCSADRAIN, self._old_settings) self.running = False def recv(self, size): return os.read(self.stdin, 1024) def send(self, data): return os.write(self.stdout, data) def fileno(self): return self.stdin def setblocking(self, blocking): # Do NOT be in non-blocking mode : non-blocking mode # causes rshell to get EAGAIN errors when having to # output loads of data, causing the TTY to break. return def close(self): # Reset terminal attributes. termios.tcsetattr(sys.stdout, termios.TCSADRAIN, self._old_settings) cccli/commands/console.py +123 −49 Original line number Original line Diff line number Diff line Loading @@ -2,32 +2,71 @@ #coding=utf8 #coding=utf8 ''' ''' Connection to a remote shell CloudControl Console related commands ''' ''' import os from cccli.command import TqlCommand import sys from cccli.exception import * import tty from cccli.printer import Printer, color from sjrpc.core import AsyncWatcher from sjrpc.core.exceptions import * from sjrpc.core.protocols import TunnelProtocol import fcntl import fcntl import os import signal import signal import struct import struct import sys import termios import termios import threading import time import time import tty from cccli.exception import * class FakeTtySocket(object): from sjrpc.core.exceptions import * from cccli.printer import Printer, color from cccli.command import ConsoleCommand from sjrpc.core.protocols import TunnelProtocol '''Give a tty a socket interface from sjrpc.core import AsyncWatcher This object cannot handle sigwinch directly because close can be called by remote RPC and by the way, called outside the main thread and fail. ''' def __init__(self): if not os.isatty(0): raise cmdError("stdin is not tty") # flush stdin sys.stdin.flush() # save termios settings self._tc_attr = termios.tcgetattr(0) # set tty in raw mode tty.setraw(0) def recv(self, size): return os.read(0, 1024) class Command_console(ConsoleCommand): def send(self, data): '''Start a remote shell on the provided host''' return os.write(0, data) def fileno(self): return 0 def setblocking(self, blocking): # Do NOT be in non-blocking mode : non-blocking mode # causes rshell to get EAGAIN errors when having to # output loads of data, causing the TTY to break. return def close(self): # reset tty original state termios.tcsetattr(0, termios.TCSADRAIN, self._tc_attr) class Command_rshell(TqlCommand): '''Start a shell on the provided host''' def __init__(self, cli, argv0): def __init__(self, cli, argv0): ConsoleCommand.__init__(self, cli, argv0) TqlCommand.__init__(self, cli, argv0) #self.tql_filter = "&con" self.remove_option("--direct") self.remove_option("--quiet") self.tql_filter = "&con" def __call__(self, argv): def __call__(self, argv): # args parse # args parse Loading @@ -35,42 +74,77 @@ class Command_console(ConsoleCommand): if len(self.args) != 1: if len(self.args) != 1: raise cmdBadArgument() raise cmdBadArgument() # rpccall # rpccall self.rpccall("console", self.args[0], _callback=self._cb_open_shell, ans = self.rpccall("rshell", self.args[0], _status=False, _direct=True) _status=True, _direct=True) # alias first object obj = ans['objects'][0] def _cb_open_shell(self, response): # check response is successful if not 'objects' in response: if obj['status'] != 'success': return raise cmdError("%s: %s" % (obj["status"], obj["message"])) response = response['objects'][0] # create a callbeck function for sigwinch if not 'status' in response: def _cb_sig_resize(*args): return '''Callback for terminal resized''' if response['status'] == 'success': size = struct.pack('HHHH', 0, 0, 0, 0) label = response['output'] size = fcntl.ioctl(0, termios.TIOCGWINSZ, size) size = struct.unpack('HHHH', size) try: try: size = 0 self.rpc.call('rshell_resize', obj["output"], *size) self.start_console(sys.stdin, sys.stdout, sys.stderr) except RpcError as e: # Connect to remote end using sjrpc # we dont care of failure of this tun = self.rpc.create_tunnel(endpoint=self) self.printer.debug("Terminal resize failed: %s" % e) # Save current terminal size # Connect to remote end using sjrpc size = struct.pack('HHHH', 0, 0, 0, 0) tun = self.rpc.create_tunnel(obj["output"], endpoint=FakeTtySocket()) size = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, size) try: while self.running: signal.signal(signal.SIGWINCH, _cb_sig_resize) time.sleep(1) # we need an async watcher to be able to handle sigwinch except Exception: watcher = AsyncWatcher() # Cleanup watcher.register(self.rpc.rpc, "rshell_wait", obj["output"]) self.stop_console() watcher.wait() tun.shutdown() finally: tun.close() # restore original signal # Restore terminal size signal.signal(signal.SIGWINCH, signal.SIG_DFL) fcntl.ioctl(sys.stdout.fileno(), termios.TIOCSWINSZ, size) # shutdown is empty, useless raise #tun.shutdown() else: # close tunnel if sjrpc doesn't do it # Cleanup tun.close() self.stop_console() tun.shutdown() tun.close() # Restore terminal size fcntl.ioctl(sys.stdout.fileno(), termios.TIOCSWINSZ, size) def remote_functions(self): def remote_functions(self): return set(("rshell",)) return set(("rshell",)) class Command_console(TqlCommand): '''Start a vm console using libvirt''' def __init__(self, cli, argv0): TqlCommand.__init__(self, cli, argv0) self.remove_option("--direct") self.remove_option("--quiet") self.tql_filter = "&r=vm&status=running" def __call__(self, argv): # args parse self.parse_args(argv) if len(self.args) != 1: raise cmdBadArgument() # rpccall ans = self.rpccall("console", self.args[0], _status=False, _direct=True) # alias first object obj = ans['objects'][0] # check everything is ok if obj['status'] != 'success': raise cmdError("%s: %s" % (obj["status"], obj["message"])) # Connect to remote end using sjrpc ev = threading.Event() def cb_on_close(tun): tun.cb_default_on_close(tun) ev.set() try: tun = self.rpc.create_tunnel(obj["output"], endpoint=FakeTtySocket(), on_close=cb_on_close) ev.wait() finally: # see tun.close in previous command tun.close() def remote_functions(self): return set(("console",)) cccli/commands/rshell.pydeleted 100644 → 0 +0 −95 Original line number Original line Diff line number Diff line #!/usr/bin/env python #coding=utf8 ''' Connection to a remote shell ''' import os import sys import tty import fcntl import signal import struct import termios from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color from cccli.command import ConsoleCommand from sjrpc.core.protocols import TunnelProtocol from sjrpc.core import AsyncWatcher class Command_rshell(ConsoleCommand): '''Start a remote shell on the provided host''' def __init__(self, cli, argv0): ConsoleCommand.__init__(self, cli, argv0) self.tql_filter = "&con" def __call__(self, argv): # args parse self.parse_args(argv) if len(self.args) != 1: raise cmdBadArgument() # rpccall self.rpccall("rshell", self.args[0], _callback=self._cb_open_shell, _status=True, _direct=True) def _cb_open_shell(self, response): tun = None if not 'objects' in response: return response = response['objects'][0] if not 'status' in response: return if response['status'] == 'success': label = response['output'] try: # Create terminal self.start_console(sys.stdin, sys.stdout, sys.stderr) # Connect to remote end using sjrpc tun = self.rpc.create_tunnel(endpoint=self) # Terminal resizing event handler def sig_resize(signum, frame): size = struct.pack('HHHH', 0, 0, 0, 0) size = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, size) size = struct.unpack('HHHH', size) self.rpc.call('rshell_resize', label, *size) signal.signal(signal.SIGWINCH, sig_resize) # Init size for term size = struct.pack('HHHH', 0, 0, 0, 0) size = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, size) size = struct.unpack('HHHH', size) self.rpc.call('rshell_resize', label, *size) # Setup and run event loop watcher = AsyncWatcher() watcher.register(self.rpc.rpc, 'rshell_wait', label) while self.running: returned = watcher.wait(1) if returned: if 'return' in returned[0]: rcode = returned[0]['return'] break elif 'error' in returned[0]: raise RpcError(Exception(returned[0]['error']['message']), Exception(returned[0]['error']['message'])) # Clear signal handlers signal.signal(signal.SIGWINCH, signal.SIG_IGN) except Exception: # Cleanup self.stop_console() if tun: tun.shutdown() tun.close() raise else: # Cleanup self.stop_console() if tun: tun.shutdown() tun.close() def remote_functions(self): return set(("rshell",)) Loading
cccli/command.py +0 −43 Original line number Original line Diff line number Diff line Loading @@ -414,46 +414,3 @@ class TqlCommand(RemoteCommand): args2[self.options.tql_index] = "id=%s"%obj["id"] args2[self.options.tql_index] = "id=%s"%obj["id"] # now we can make call # now we can make call self._unsecure_rpccall(args2, **kwargs) self._unsecure_rpccall(args2, **kwargs) class ConsoleCommand(TqlCommand): def __init__(self, cli, argv0): TqlCommand.__init__(self, cli, argv0) def start_console(self, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr): self.stdin = stdin.fileno() self.stdout = stdout.fileno() self.stderr = stderr.fileno() if self.stdin == sys.stdin.fileno(): sys.stdin.flush() if self.stdout == sys.stdout.fileno(): sys.stdout.flush() if self.stderr == sys.stderr.fileno(): sys.stderr.flush() self._old_settings = termios.tcgetattr(self.stdout) tty.setraw(self.stdin) self.running = True def stop_console(self): # Reset terminal attributes. termios.tcsetattr(sys.stdout, termios.TCSADRAIN, self._old_settings) self.running = False def recv(self, size): return os.read(self.stdin, 1024) def send(self, data): return os.write(self.stdout, data) def fileno(self): return self.stdin def setblocking(self, blocking): # Do NOT be in non-blocking mode : non-blocking mode # causes rshell to get EAGAIN errors when having to # output loads of data, causing the TTY to break. return def close(self): # Reset terminal attributes. termios.tcsetattr(sys.stdout, termios.TCSADRAIN, self._old_settings)
cccli/commands/console.py +123 −49 Original line number Original line Diff line number Diff line Loading @@ -2,32 +2,71 @@ #coding=utf8 #coding=utf8 ''' ''' Connection to a remote shell CloudControl Console related commands ''' ''' import os from cccli.command import TqlCommand import sys from cccli.exception import * import tty from cccli.printer import Printer, color from sjrpc.core import AsyncWatcher from sjrpc.core.exceptions import * from sjrpc.core.protocols import TunnelProtocol import fcntl import fcntl import os import signal import signal import struct import struct import sys import termios import termios import threading import time import time import tty from cccli.exception import * class FakeTtySocket(object): from sjrpc.core.exceptions import * from cccli.printer import Printer, color from cccli.command import ConsoleCommand from sjrpc.core.protocols import TunnelProtocol '''Give a tty a socket interface from sjrpc.core import AsyncWatcher This object cannot handle sigwinch directly because close can be called by remote RPC and by the way, called outside the main thread and fail. ''' def __init__(self): if not os.isatty(0): raise cmdError("stdin is not tty") # flush stdin sys.stdin.flush() # save termios settings self._tc_attr = termios.tcgetattr(0) # set tty in raw mode tty.setraw(0) def recv(self, size): return os.read(0, 1024) class Command_console(ConsoleCommand): def send(self, data): '''Start a remote shell on the provided host''' return os.write(0, data) def fileno(self): return 0 def setblocking(self, blocking): # Do NOT be in non-blocking mode : non-blocking mode # causes rshell to get EAGAIN errors when having to # output loads of data, causing the TTY to break. return def close(self): # reset tty original state termios.tcsetattr(0, termios.TCSADRAIN, self._tc_attr) class Command_rshell(TqlCommand): '''Start a shell on the provided host''' def __init__(self, cli, argv0): def __init__(self, cli, argv0): ConsoleCommand.__init__(self, cli, argv0) TqlCommand.__init__(self, cli, argv0) #self.tql_filter = "&con" self.remove_option("--direct") self.remove_option("--quiet") self.tql_filter = "&con" def __call__(self, argv): def __call__(self, argv): # args parse # args parse Loading @@ -35,42 +74,77 @@ class Command_console(ConsoleCommand): if len(self.args) != 1: if len(self.args) != 1: raise cmdBadArgument() raise cmdBadArgument() # rpccall # rpccall self.rpccall("console", self.args[0], _callback=self._cb_open_shell, ans = self.rpccall("rshell", self.args[0], _status=False, _direct=True) _status=True, _direct=True) # alias first object obj = ans['objects'][0] def _cb_open_shell(self, response): # check response is successful if not 'objects' in response: if obj['status'] != 'success': return raise cmdError("%s: %s" % (obj["status"], obj["message"])) response = response['objects'][0] # create a callbeck function for sigwinch if not 'status' in response: def _cb_sig_resize(*args): return '''Callback for terminal resized''' if response['status'] == 'success': size = struct.pack('HHHH', 0, 0, 0, 0) label = response['output'] size = fcntl.ioctl(0, termios.TIOCGWINSZ, size) size = struct.unpack('HHHH', size) try: try: size = 0 self.rpc.call('rshell_resize', obj["output"], *size) self.start_console(sys.stdin, sys.stdout, sys.stderr) except RpcError as e: # Connect to remote end using sjrpc # we dont care of failure of this tun = self.rpc.create_tunnel(endpoint=self) self.printer.debug("Terminal resize failed: %s" % e) # Save current terminal size # Connect to remote end using sjrpc size = struct.pack('HHHH', 0, 0, 0, 0) tun = self.rpc.create_tunnel(obj["output"], endpoint=FakeTtySocket()) size = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, size) try: while self.running: signal.signal(signal.SIGWINCH, _cb_sig_resize) time.sleep(1) # we need an async watcher to be able to handle sigwinch except Exception: watcher = AsyncWatcher() # Cleanup watcher.register(self.rpc.rpc, "rshell_wait", obj["output"]) self.stop_console() watcher.wait() tun.shutdown() finally: tun.close() # restore original signal # Restore terminal size signal.signal(signal.SIGWINCH, signal.SIG_DFL) fcntl.ioctl(sys.stdout.fileno(), termios.TIOCSWINSZ, size) # shutdown is empty, useless raise #tun.shutdown() else: # close tunnel if sjrpc doesn't do it # Cleanup tun.close() self.stop_console() tun.shutdown() tun.close() # Restore terminal size fcntl.ioctl(sys.stdout.fileno(), termios.TIOCSWINSZ, size) def remote_functions(self): def remote_functions(self): return set(("rshell",)) return set(("rshell",)) class Command_console(TqlCommand): '''Start a vm console using libvirt''' def __init__(self, cli, argv0): TqlCommand.__init__(self, cli, argv0) self.remove_option("--direct") self.remove_option("--quiet") self.tql_filter = "&r=vm&status=running" def __call__(self, argv): # args parse self.parse_args(argv) if len(self.args) != 1: raise cmdBadArgument() # rpccall ans = self.rpccall("console", self.args[0], _status=False, _direct=True) # alias first object obj = ans['objects'][0] # check everything is ok if obj['status'] != 'success': raise cmdError("%s: %s" % (obj["status"], obj["message"])) # Connect to remote end using sjrpc ev = threading.Event() def cb_on_close(tun): tun.cb_default_on_close(tun) ev.set() try: tun = self.rpc.create_tunnel(obj["output"], endpoint=FakeTtySocket(), on_close=cb_on_close) ev.wait() finally: # see tun.close in previous command tun.close() def remote_functions(self): return set(("console",))
cccli/commands/rshell.pydeleted 100644 → 0 +0 −95 Original line number Original line Diff line number Diff line #!/usr/bin/env python #coding=utf8 ''' Connection to a remote shell ''' import os import sys import tty import fcntl import signal import struct import termios from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color from cccli.command import ConsoleCommand from sjrpc.core.protocols import TunnelProtocol from sjrpc.core import AsyncWatcher class Command_rshell(ConsoleCommand): '''Start a remote shell on the provided host''' def __init__(self, cli, argv0): ConsoleCommand.__init__(self, cli, argv0) self.tql_filter = "&con" def __call__(self, argv): # args parse self.parse_args(argv) if len(self.args) != 1: raise cmdBadArgument() # rpccall self.rpccall("rshell", self.args[0], _callback=self._cb_open_shell, _status=True, _direct=True) def _cb_open_shell(self, response): tun = None if not 'objects' in response: return response = response['objects'][0] if not 'status' in response: return if response['status'] == 'success': label = response['output'] try: # Create terminal self.start_console(sys.stdin, sys.stdout, sys.stderr) # Connect to remote end using sjrpc tun = self.rpc.create_tunnel(endpoint=self) # Terminal resizing event handler def sig_resize(signum, frame): size = struct.pack('HHHH', 0, 0, 0, 0) size = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, size) size = struct.unpack('HHHH', size) self.rpc.call('rshell_resize', label, *size) signal.signal(signal.SIGWINCH, sig_resize) # Init size for term size = struct.pack('HHHH', 0, 0, 0, 0) size = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, size) size = struct.unpack('HHHH', size) self.rpc.call('rshell_resize', label, *size) # Setup and run event loop watcher = AsyncWatcher() watcher.register(self.rpc.rpc, 'rshell_wait', label) while self.running: returned = watcher.wait(1) if returned: if 'return' in returned[0]: rcode = returned[0]['return'] break elif 'error' in returned[0]: raise RpcError(Exception(returned[0]['error']['message']), Exception(returned[0]['error']['message'])) # Clear signal handlers signal.signal(signal.SIGWINCH, signal.SIG_IGN) except Exception: # Cleanup self.stop_console() if tun: tun.shutdown() tun.close() raise else: # Cleanup self.stop_console() if tun: tun.shutdown() tun.close() def remote_functions(self): return set(("rshell",))