add ssh client which has basic bash keybinds and tab complement.
to use, you have to create ssh key at first then specify the location of
the key in ryu configuration files.

configuration file will be like this.

> ryu.conf
> ==
> [DEFAULT]
> cli_ssh_hostkey=/home/user/.ssh/id_rsa

after this, run operator/ssh.py with application.py

$ ryu-manager --config-file=ryu.conf application.py operator/ssh.py

you can login by

$ ssh ryu@localhost -p 4990

Signed-off-by: ISHIDA Wataru <[email protected]>
---
 ryu/services/protocols/bgp/operator/command.py     |    2 +-
 .../protocols/bgp/operator/commands/set.py         |    8 +-
 .../bgp/operator/commands/show/__init__.py         |   11 +-
 .../protocols/bgp/operator/commands/show/memory.py |    2 +-
 .../protocols/bgp/operator/commands/show/rib.py    |   10 +-
 .../commands/show/route_formatter_mixin.py         |   11 +-
 .../protocols/bgp/operator/internal_api.py         |   30 +-
 ryu/services/protocols/bgp/operator/ssh.py         |  472 ++++++++++++++++++++
 8 files changed, 529 insertions(+), 17 deletions(-)
 create mode 100644 ryu/services/protocols/bgp/operator/ssh.py

diff --git a/ryu/services/protocols/bgp/operator/command.py 
b/ryu/services/protocols/bgp/operator/command.py
index bd384f0..64449ef 100644
--- a/ryu/services/protocols/bgp/operator/command.py
+++ b/ryu/services/protocols/bgp/operator/command.py
@@ -42,7 +42,7 @@ class Command(object):
     help_msg = ''
     param_help_msg = None
     command = ''
-    cli_resp_line_template = '{0}: {1}\n\n'
+    cli_resp_line_template = '{0}: {1}\n'
 
     def __init__(self, api=None, parent=None,
                  help_formatter=default_help_formatter,
diff --git a/ryu/services/protocols/bgp/operator/commands/set.py 
b/ryu/services/protocols/bgp/operator/commands/set.py
index b28a80a..c6c9c5d 100644
--- a/ryu/services/protocols/bgp/operator/commands/set.py
+++ b/ryu/services/protocols/bgp/operator/commands/set.py
@@ -3,6 +3,7 @@ import logging
 from ryu.services.protocols.bgp.operator.command import Command
 from ryu.services.protocols.bgp.operator.command import CommandsResponse
 from ryu.services.protocols.bgp.operator.command import STATUS_OK
+from ryu.services.protocols.bgp.operator.command import STATUS_ERROR
 from ryu.services.protocols.bgp.operator.commands.responses import \
     WrongParamResp
 
@@ -19,6 +20,9 @@ class LoggingCmd(Command):
             'level': self.Level
         }
 
+    def action(self, params):
+        return CommandsResponse(STATUS_ERROR, 'Command incomplete')
+
     class On(Command):
         command = 'on'
         help_msg = 'turn-on the logging at the current level'
@@ -56,10 +60,10 @@ class LoggingCmd(Command):
 
 
 class SetCmd(Command):
-    help_msg = 'allows to set runtime settings'
+    help_msg = 'set runtime settings'
     command = 'set'
 
     subcommands = {'logging': LoggingCmd}
 
     def action(self, params):
-        return CommandsResponse(STATUS_OK, True)
+        return CommandsResponse(STATUS_ERROR, 'Command incomplete')
diff --git a/ryu/services/protocols/bgp/operator/commands/show/__init__.py 
b/ryu/services/protocols/bgp/operator/commands/show/__init__.py
index 388a1e7..f0c3072 100644
--- a/ryu/services/protocols/bgp/operator/commands/show/__init__.py
+++ b/ryu/services/protocols/bgp/operator/commands/show/__init__.py
@@ -1,6 +1,7 @@
 from ryu.services.protocols.bgp.operator.command import Command
 from ryu.services.protocols.bgp.operator.command import CommandsResponse
 from ryu.services.protocols.bgp.operator.command import STATUS_OK
+from ryu.services.protocols.bgp.operator.command import STATUS_ERROR
 from ryu.services.protocols.bgp.operator.commands.show import count
 from ryu.services.protocols.bgp.operator.commands.show import importmap
 from ryu.services.protocols.bgp.operator.commands.show import memory
@@ -26,7 +27,7 @@ class ShowCmd(Command):
         }
 
     def action(self, params):
-        return CommandsResponse(STATUS_OK, None)
+        return CommandsResponse(STATUS_ERROR, 'Command incomplete')
 
     class Count(count.Count):
         pass
@@ -51,6 +52,10 @@ class ShowCmd(Command):
         help_msg = 'shows if logging is on/off and current logging level.'
 
         def action(self, params):
-            ret = {'logging': self.api.check_logging(),
-                   'level': self.api.check_logging_level()}
+            if self.api.check_logging():
+                ret = {'logging': self.api.check_logging(),
+                       'level': self.api.check_logging_level()}
+            else:
+                ret = {'logging': self.api.check_logging(),
+                       'level': None}
             return CommandsResponse(STATUS_OK, ret)
diff --git a/ryu/services/protocols/bgp/operator/commands/show/memory.py 
b/ryu/services/protocols/bgp/operator/commands/show/memory.py
index c519adf..97e7573 100644
--- a/ryu/services/protocols/bgp/operator/commands/show/memory.py
+++ b/ryu/services/protocols/bgp/operator/commands/show/memory.py
@@ -48,7 +48,7 @@ class Memory(Command):
                 'summary': []}
 
             for class_name, s in size.items():
-            # Calculate size in MB
+                # Calculate size in MB
                 size_mb = s / 1000000
                 # We are only interested in class which take-up more than a MB
                 if size_mb > 0:
diff --git a/ryu/services/protocols/bgp/operator/commands/show/rib.py 
b/ryu/services/protocols/bgp/operator/commands/show/rib.py
index 2474024..34d4a18 100644
--- a/ryu/services/protocols/bgp/operator/commands/show/rib.py
+++ b/ryu/services/protocols/bgp/operator/commands/show/rib.py
@@ -5,6 +5,7 @@ from ryu.services.protocols.bgp.operator.command import 
CommandsResponse
 from ryu.services.protocols.bgp.operator.command import STATUS_ERROR
 from ryu.services.protocols.bgp.operator.command import STATUS_OK
 
+from ryu.services.protocols.bgp.base import ActivityException
 from ryu.services.protocols.bgp.operator.commands.responses import \
     WrongParamResp
 
@@ -50,9 +51,12 @@ class Rib(RibBase):
             if len(params) != 0:
                 return WrongParamResp()
             ret = {}
-            for family in self.supported_families:
-                ret[family] = self.api.get_single_rib_routes(family)
-            return CommandsResponse(STATUS_OK, ret)
+            try:
+                for family in self.supported_families:
+                    ret[family] = self.api.get_single_rib_routes(family)
+                return CommandsResponse(STATUS_OK, ret)
+            except ActivityException, e:
+                return CommandsResponse(STATUS_ERROR, e)
 
         @classmethod
         def cli_resp_formatter(cls, resp):
diff --git 
a/ryu/services/protocols/bgp/operator/commands/show/route_formatter_mixin.py 
b/ryu/services/protocols/bgp/operator/commands/show/route_formatter_mixin.py
index ff69e06..2f58f68 100644
--- a/ryu/services/protocols/bgp/operator/commands/show/route_formatter_mixin.py
+++ b/ryu/services/protocols/bgp/operator/commands/show/route_formatter_mixin.py
@@ -17,6 +17,10 @@ class RouteFormatterMixin(object):
 
         def _append_path_info(buff, path, is_best, show_prefix):
             aspath = path.get('aspath')
+            origin = path.get('origin')
+            if origin:
+                aspath.append(origin)
+
             bpr = path.get('bpr')
             next_hop = path.get('nexthop')
             med = path.get('metric')
@@ -31,9 +35,10 @@ class RouteFormatterMixin(object):
                 prefix = path.get('prefix')
 
             # Append path info to String buffer.
-            buff.write(' {0:<3s} {1:<32s} {2:<20s} {3:<20s} {4:<10s} {5:<}\n'.
-                       format(path_status, prefix, next_hop, bpr, str(med),
-                              ', '.join(map(str, aspath))))
+            buff.write(
+                ' {0:<3s} {1:<32s} {2:<20s} {3:<20s} {4:<10s} {5:<}\n'.
+                format(path_status, prefix, next_hop, bpr, str(med),
+                       ' '.join(map(str, aspath))))
 
         for dist in dest_list:
             for idx, path in enumerate(dist.get('paths')):
diff --git a/ryu/services/protocols/bgp/operator/internal_api.py 
b/ryu/services/protocols/bgp/operator/internal_api.py
index b83ce0d..c98ab69 100644
--- a/ryu/services/protocols/bgp/operator/internal_api.py
+++ b/ryu/services/protocols/bgp/operator/internal_api.py
@@ -7,6 +7,11 @@ from ryu.lib.packet.bgp import RF_IPv6_UC
 from ryu.lib.packet.bgp import RF_IPv4_VPN
 from ryu.lib.packet.bgp import RF_IPv6_VPN
 from ryu.lib.packet.bgp import RF_RTC_UC
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MULTI_EXIT_DISC
+from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_IGP
+from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_EGP
 
 from ryu.services.protocols.bgp.base import add_bgp_error_metadata
 from ryu.services.protocols.bgp.base import BGPSException
@@ -94,11 +99,27 @@ class InternalApi(object):
                'prefix': dst.nlri.formatted_nlri_str}
 
         def _path_to_dict(dst, path):
-            aspath = path.get_pattr(BGP_ATTR_TYPE_AS_PATH).path_seg_list
-            if aspath is None or len(aspath) == 0:
+
+            path_seg_list = path.get_pattr(BGP_ATTR_TYPE_AS_PATH).path_seg_list
+
+            if type(path_seg_list) == list:
+                aspath = []
+                for as_path_seg in path_seg_list:
+                    for as_num in as_path_seg:
+                        aspath.append(as_num)
+            else:
                 aspath = ''
 
-            nexthop = path.nexthop
+            origin = path.get_pattr(BGP_ATTR_TYPE_ORIGIN).value
+
+            if origin == BGP_ATTR_ORIGIN_IGP:
+                origin = 'i'
+            elif origin == BGP_ATTR_ORIGIN_EGP:
+                origin = 'e'
+            else:
+                origin = None
+
+            nexthop = path.nexthop.value
             # Get the MED path attribute
             med = path.get_pattr(BGP_ATTR_TYPE_MULTI_EXIT_DISC)
             med = med.value if med else ''
@@ -109,7 +130,8 @@ class InternalApi(object):
                     'prefix': path.nlri.formatted_nlri_str,
                     'nexthop': nexthop,
                     'metric': med,
-                    'aspath': aspath}
+                    'aspath': aspath,
+                    'origin': origin}
 
         for path in dst.known_path_list:
             ret['paths'].append(_path_to_dict(dst, path))
diff --git a/ryu/services/protocols/bgp/operator/ssh.py 
b/ryu/services/protocols/bgp/operator/ssh.py
new file mode 100644
index 0000000..84858d7
--- /dev/null
+++ b/ryu/services/protocols/bgp/operator/ssh.py
@@ -0,0 +1,472 @@
+# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
+# Copyright (C) 2013 YAMAMOTO Takashi <yamamoto at valinux co jp>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# a management cli application.
+
+import logging
+import paramiko
+import sys
+from copy import copy
+from oslo.config import cfg
+
+from ryu.lib import hub
+from ryu import version
+from ryu.base import app_manager
+from ryu.services.protocols.bgp.operator.command import Command
+from ryu.services.protocols.bgp.operator.command import CommandsResponse
+from ryu.services.protocols.bgp.operator.commands.root import RootCmd
+from ryu.services.protocols.bgp.operator.internal_api import InternalApi
+from ryu.services.protocols.bgp.operator.command import STATUS_OK
+
+LOG = logging.getLogger('bgpspeaker.cli')
+
+CONF = cfg.CONF
+CONF.register_opts([
+    cfg.ListOpt('cli-transports', default=[], help='cli transports to enable'),
+    cfg.StrOpt('cli-ssh-host', default='localhost',
+               help='cli ssh listen host'),
+    cfg.IntOpt('cli-ssh-port', default=4990, help='cli ssh listen port'),
+    cfg.StrOpt('cli-ssh-hostkey', help='cli ssh host key file'),
+    cfg.StrOpt('cli-ssh-username', default='ryu', help='cli ssh username'),
+    cfg.StrOpt('cli-ssh-password', default='ryu', help='cli ssh password')
+])
+
+
+class SshServer(paramiko.ServerInterface):
+    TERM = "ansi"
+    PROMPT = "bgpd> "
+    WELCOME = """
+Hello, this is Ryu BGP speaker (version %s).
+""" % version
+
+    class HelpCmd(Command):
+        help_msg = 'show this help'
+        command = 'help'
+
+        def action(self, params):
+            return self.parent_cmd.question_mark()[0]
+
+    class QuitCmd(Command):
+        help_msg = 'exit this session'
+        command = 'quit'
+
+        def action(self, params):
+            self.api.sshserver.end_session()
+            return CommandsResponse(STATUS_OK, True)
+
+    def __init__(self, sock, addr):
+        super(SshServer, self).__init__()
+
+        # tweak InternalApi and RootCmd for non-bgp related commands
+        self.api = InternalApi(log_handler=logging.StreamHandler(sys.stderr))
+        setattr(self.api, 'sshserver', self)
+        self.root = RootCmd(self.api)
+        self.root.subcommands['help'] = self.HelpCmd
+        self.root.subcommands['quit'] = self.QuitCmd
+
+        transport = paramiko.Transport(sock)
+        transport.load_server_moduli()
+        host_key = paramiko.RSAKey.from_private_key_file(CONF.cli_ssh_hostkey)
+        transport.add_server_key(host_key)
+        self.transport = transport
+        transport.start_server(server=self)
+
+    def check_auth_none(self, username):
+        return paramiko.AUTH_SUCCESSFUL
+
+    def check_auth_password(self, username, password):
+        if username == CONF.cli_ssh_username and \
+                password == CONF.cli_ssh_password:
+            return paramiko.AUTH_SUCCESSFUL
+        return paramiko.AUTH_FAILED
+
+    def check_channel_request(self, kind, chanid):
+        if kind == 'session':
+            return paramiko.OPEN_SUCCEEDED
+        return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
+
+    def check_channel_shell_request(self, chan):
+        hub.spawn(self._handle_shell_request)
+        return True
+
+    def check_channel_pty_request(self, chan, term, width, height,
+                                  pixelwidth, pixelheight, modes):
+        LOG.debug("termtype: %s" % (term, ))
+        self.TERM = term
+        return True
+
+    def check_channel_window_change_request(self, chan, width, height, pwidth,
+                                            pheight):
+        LOG.info("channel window change")
+        return True
+
+    def _is_echoable(self, c):
+        return not (c < chr(0x20) or c == chr(0x7F))
+
+    def _is_enter(self, c):
+        return c == chr(0x0d)
+
+    def _is_eof(self, c):
+        return c == chr(0x03)
+
+    def _is_esc(self, c):
+        return c == chr(0x1b)
+
+    def _is_hist(self, c):
+        return c == chr(0x10) or c == chr(0x0e)
+
+    def _is_del(self, c):
+        return c == chr(0x04) or c == chr(0x08) or c == chr(0x15) \
+            or c == chr(0x17) or c == chr(0x0c) or c == chr(0x7f)
+
+    def _is_curmov(self, c):
+        return c == chr(0x01) or c == chr(0x02) or c == chr(0x05) \
+            or c == chr(0x06)
+
+    def _is_cmpl(self, c):
+        return c == chr(0x09)
+
+    def _handle_csi_seq(self):
+        c = self.chan.recv(1)
+        if c == 'A':
+            self._lookup_hist_up()
+        elif c == 'B':
+            self._lookup_hist_down()
+        elif c == 'C':
+            self._movcursor(self.curpos+1)
+        elif c == 'D':
+            self._movcursor(self.curpos-1)
+        else:
+            LOG.error("unknown CSI sequence. do nothing: %c" % c)
+
+    def _handle_esc_seq(self):
+        c = self.chan.recv(1)
+        if c == '[':
+            self._handle_csi_seq()
+        else:
+            LOG.error("non CSI sequence. do nothing")
+
+    def _send_csi_seq(self, cmd):
+        self.chan.send('\x1b[' + cmd)
+
+    def _movcursor(self, curpos):
+        if self.prompted and curpos < len(self.PROMPT):
+            self.curpos = len(self.PROMPT)
+        elif self.prompted and curpos > (len(self.PROMPT) + len(self.buf)):
+            self.curpos = len(self.PROMPT) + len(self.buf)
+        else:
+            self._send_csi_seq('%dG' % (curpos + 1))
+            self.curpos = curpos
+
+    def _clearscreen(self, prompt=None):
+        if not prompt and self.prompted:
+            prompt = self.PROMPT
+        # clear screen
+        self._send_csi_seq('2J')
+        # move cursor to the top
+        self._send_csi_seq('d')
+        # redraw prompt and buf
+        self._refreshline(prompt=prompt)
+
+    def _clearline(self, prompt=None):
+        if not prompt and self.prompted:
+            prompt = self.PROMPT
+        self.prompted = False
+        self._movcursor(0)
+        self._send_csi_seq('2K')
+        if prompt:
+            self.prompted = True
+            self.chan.send(prompt)
+            self._movcursor(len(prompt))
+        self.buf = []
+
+    def _refreshline(self, prompt=None):
+        if not prompt and self.prompted:
+            prompt = self.PROMPT
+        buf = copy(self.buf)
+        curpos = copy(self.curpos)
+        self._clearline(prompt=prompt)
+        self.chan.send(''.join(buf))
+        self.buf = buf
+        self.curpos = curpos
+        self._movcursor(curpos)
+
+    def _refreshnewline(self, prompt=None):
+        if not prompt and self.prompted:
+            prompt = self.PROMPT
+        buf = copy(self.buf)
+        curpos = copy(self.curpos)
+        self._startnewline(prompt)
+        self.chan.send(''.join(buf))
+        self.buf = buf
+        self.curpos = curpos
+        self._movcursor(curpos)
+
+    def _startnewline(self, prompt=None, buf=''):
+        if not prompt and self.prompted:
+            prompt = self.PROMPT
+        if type(buf) == str:
+            buf = list(buf)
+        if self.chan:
+            self.buf = buf
+            if prompt:
+                self.chan.send('\n\r' + prompt + ''.join(buf))
+                self.curpos = len(prompt) + len(buf)
+                self.prompted = True
+            else:
+                self.chan.send('\n\r' + ''.join(buf))
+                self.curpos = len(buf)
+                self.prompted = False
+
+    def _lookup_hist_up(self):
+        if len(self.history) == 0:
+            return
+        self.buf = self.history[self.histindex]
+        self.curpos = self.promptlen + len(self.buf)
+        self._refreshline()
+        if self.histindex + 1 < len(self.history):
+            self.histindex += 1
+
+    def _lookup_hist_down(self):
+        if self.histindex > 0:
+            self.histindex -= 1
+            self.buf = self.history[self.histindex]
+            self.curpos = self.promptlen + len(self.buf)
+            self._refreshline()
+        else:
+            self._clearline()
+
+    def _do_cmpl(self, buf, is_exec=False):
+        cmpleter = self.root
+        is_spaced = buf[-1] == ' ' if len(buf) > 0 else False
+        cmds = [tkn.strip() for tkn in ''.join(buf).split()]
+        ret = []
+
+        for i, cmd in enumerate(cmds):
+            subcmds = cmpleter.subcommands
+            matches = [x for x in subcmds.keys() if x.startswith(cmd)]
+
+            if len(matches) == 1:
+                cmpled_cmd = matches[0]
+                cmpleter = subcmds[cmpled_cmd](self.api)
+
+                if is_exec:
+                    ret.append(cmpled_cmd)
+                    continue
+
+                if (i+1) == len(cmds):
+                    if is_spaced:
+                        result, cmd = cmpleter('?')
+                        result = result.value.replace('\n', '\n\r').rstrip()
+                        self.prompted = False
+                        buf = copy(buf)
+                        self._startnewline(buf=result)
+                        self.prompted = True
+                        self._startnewline(buf=buf)
+                    else:
+                        self.buf = buf[:(-1 * len(cmd))] + \
+                            list(cmpled_cmd + ' ')
+                        self.curpos += len(cmpled_cmd) - len(cmd) + 1
+                        self._refreshline()
+            else:
+                self.prompted = False
+                buf = copy(self.buf)
+                if len(matches) == 0:
+                    if cmpleter.param_help_msg:
+                        self.prompted = True
+                        ret.append(cmd)
+                        continue
+                    else:
+                        self._startnewline(buf='Error: Not implemented')
+                else:
+                    if (i+1) < len(cmds):
+                        self._startnewline(buf='Error: Ambiguous command')
+                    else:
+                        self._startnewline(buf=', '.join(matches))
+                ret = False
+                self.prompted = True
+                if not is_exec:
+                    self._startnewline(buf=buf)
+                break
+
+        return ret
+
+    def _execute_cmd(self, cmds):
+        result, cmd = self.root(cmds)
+        LOG.debug("result: %s" % str(result))
+        self.prompted = False
+        self._startnewline()
+        output = result.value.replace('\n', '\n\r').rstrip()
+        self.chan.send(output)
+        self.prompted = True
+        return result.status
+
+    def end_session(self):
+        self._startnewline(prompt=False, buf='bye.\n\r')
+        self.chan.close()
+
+    def _handle_shell_request(self):
+        LOG.info("session start")
+        chan = self.transport.accept(20)
+        if not chan:
+            LOG.info("transport.accept timed out")
+            return
+
+        self.chan = chan
+        self.buf = []
+        self.curpos = 0
+        self.history = []
+        self.histindex = 0
+        self.prompted = True
+        self.chan.send(self.WELCOME)
+        self._startnewline()
+
+        while True:
+            c = self.chan.recv(1)
+
+            if len(c) == 0:
+                break
+
+            LOG.debug("ord:%d, hex:0x%x" % (ord(c), ord(c)))
+            self.promptlen = len(self.PROMPT) if self.prompted else 0
+            if c == '?':
+                cmpleter = self.root
+                cmds = [tkn.strip() for tkn in ''.join(self.buf).split()]
+
+                for i, cmd in enumerate(cmds):
+                    subcmds = cmpleter.subcommands
+                    matches = [x for x in subcmds.keys() if x.startswith(cmd)]
+                    if len(matches) == 1:
+                        cmpled_cmd = matches[0]
+                        cmpleter = subcmds[cmpled_cmd](self.api)
+
+                result, cmd = cmpleter('?')
+                result = result.value.replace('\n', '\n\r').rstrip()
+                self.prompted = False
+                buf = copy(self.buf)
+                self._startnewline(buf=result)
+                self.prompted = True
+                self._startnewline(buf=buf)
+            elif self._is_echoable(c):
+                self.buf.insert(self.curpos - self.promptlen, c)
+                self.curpos += 1
+                self._refreshline()
+            elif self._is_esc(c):
+                self._handle_esc_seq()
+            elif self._is_eof(c):
+                self.end_session()
+            elif self._is_curmov(c):
+                # <C-a>
+                if c == chr(0x01):
+                    self._movcursor(self.promptlen)
+                # <C-b>
+                elif c == chr(0x02):
+                    self._movcursor(self.curpos-1)
+                # <C-e>
+                elif c == chr(0x05):
+                    self._movcursor(self.promptlen+len(self.buf))
+                # <C-f>
+                elif c == chr(0x06):
+                    self._movcursor(self.curpos+1)
+                else:
+                    LOG.error("unknown cursor move cmd.")
+                    continue
+            elif self._is_hist(c):
+                # <C-p>
+                if c == chr(0x10):
+                    self._lookup_hist_up()
+                # <C-n>
+                elif c == chr(0x0e):
+                    self._lookup_hist_down()
+            elif self._is_del(c):
+                # <C-d>
+                if c == chr(0x04):
+                    if self.curpos < (self.promptlen + len(self.buf)):
+                        self.buf.pop(self.curpos - self.promptlen)
+                        self._refreshline()
+                # <C-h> or delete
+                elif c == chr(0x08) or c == chr(0x7f):
+                    if self.curpos > self.promptlen:
+                        self.buf.pop(self.curpos - self.promptlen - 1)
+                        self.curpos -= 1
+                        self._refreshline()
+                # <C-u>
+                elif c == chr(0x15):
+                    self._clearline()
+                # <C-w>
+                elif c == chr(0x17):
+                    pos = self.curpos - self.promptlen
+                    i = pos
+                    flag = False
+                    for c in reversed(self.buf[:pos]):
+                        if flag and c == ' ':
+                            break
+                        if c != ' ':
+                            flag = True
+                        i -= 1
+                    del self.buf[i:pos]
+                    self.curpos = self.promptlen + i
+                    self._refreshline()
+                # <C-l>
+                elif c == chr(0x0c):
+                    self._clearscreen()
+            elif self._is_cmpl(c):
+                self._do_cmpl(self.buf)
+            elif self._is_enter(c):
+                if len(''.join(self.buf).strip()) != 0:
+                    # cmd line interpretation
+                    cmds = self._do_cmpl(self.buf, is_exec=True)
+                    if cmds:
+                        self.history.insert(0, self.buf)
+                        self.histindex = 0
+                        self._execute_cmd(cmds)
+                else:
+                    LOG.debug("blank buf. just start a new line.")
+                self._startnewline()
+
+            LOG.debug("curpos: %d, buf: %s, prompted: %s" % (self.curpos,
+                                                             self.buf,
+                                                             self.prompted))
+
+        LOG.info("session end")
+
+
+class SshServerFactory(object):
+    def __init__(self, *args, **kwargs):
+        super(SshServerFactory, self).__init__(*args, **kwargs)
+
+    def streamserver_handle(self, sock, addr):
+        SshServer(sock, addr)
+
+
+class Cli(app_manager.RyuApp):
+    def __init__(self, *args, **kwargs):
+        super(Cli, self).__init__(*args, **kwargs)
+        something_started = False
+
+    def start(self):
+        LOG.info("starting ssh server at %s:%d",
+                 CONF.cli_ssh_host, CONF.cli_ssh_port)
+        t = hub.spawn(self._ssh_thread)
+        something_started = True
+        return t
+
+    def _ssh_thread(self):
+        factory = SshServerFactory()
+        server = hub.StreamServer((CONF.cli_ssh_host,
+                                   CONF.cli_ssh_port),
+                                  factory.streamserver_handle)
+        server.serve_forever()
-- 
1.7.9.5


------------------------------------------------------------------------------
Is your legacy SCM system holding you back? Join Perforce May 7 to find out:
&#149; 3 signs your SCM is hindering your productivity
&#149; Requirements for releasing software faster
&#149; Expert tips and advice for migrating your SCM now
http://p.sf.net/sfu/perforce
_______________________________________________
Ryu-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to