Commit 4be7e906 authored by Sébastien Luttringer's avatar Sébastien Luttringer
Browse files

Reimplement console and rshell

parent 2a187b83
Loading
Loading
Loading
Loading
+0 −43
Original line number Original line Diff line number Diff line
@@ -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)
+123 −49
Original line number Original line Diff line number Diff line
@@ -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
@@ -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.py

deleted100644 → 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",))