Signed-off-by: YAMAMOTO Takashi <[email protected]> --- ryu/app/cli.py | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 ryu/app/cli.py
diff --git a/ryu/app/cli.py b/ryu/app/cli.py new file mode 100644 index 0000000..22a515b --- /dev/null +++ b/ryu/app/cli.py @@ -0,0 +1,227 @@ +# 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. + +from __future__ import print_function + +import cmd +import functools +import gevent +import gevent.server +import logging +import os +import paramiko +import pty +import select +import signal +import sys + +from oslo.config import cfg + +from ryu import version +from ryu.base import app_manager +from ryu.base import management +from ryu import call_via_pipe +from ryu.plogger import PrefixedLogger + + +CONF = cfg.CONF +CONF.register_opts([ + cfg.ListOpt('cli-transports', default=[], help='cli transports to enable'), + cfg.StrOpt('cli-ssh-host', default='', help='cli ssh listen host'), + cfg.IntOpt('cli-ssh-port', default=4990, help='cli ssh listen port'), + cfg.StrOpt('cli-ssh-hostkey', default=None, help='cli ssh host key file'), + cfg.StrOpt('cli-ssh-username', default=None, help='cli ssh username'), + cfg.StrOpt('cli-ssh-password', default=None, help='cli ssh password') +]) + + +def command_log(f): + @functools.wraps(f) + def wrapper(self, params): + name = wrapper.__name__ + assert(name.startswith('do_')) + command_name = name[len('do_'):] + self.logger.info("command %s %s" % (command_name, params)) + f(self, params) + return wrapper + + +class CliCmd(cmd.Cmd): + prompt = 'ryu-manager %s> ' % version + + def __init__(self, rpipe, wpipe, *args, **kwargs): + cmd.Cmd.__init__(self, *args, **kwargs) + # it's safe to use the same set of pipes as far as we are + # single-threaded. + self.management = call_via_pipe.CallViaPipe(rpipe, wpipe, "management") + self.logger = call_via_pipe.CallViaPipe(rpipe, wpipe, "logger") + + @command_log + def do_set_log_level(self, params): + '''<logger> <level> + set log level of the specified logger + ''' + try: + params = params.split() + name = params[0] + newlvl = int(params[1]) + except (ValueError, IndexError): + print('invalid parameter') + return + try: + oldlvl = self.management.get_log_level(name) + self.management.set_log_level(name, newlvl) + except LookupError: + print('logger %s is unknown' % (name,)) + return + print('logger %s level %s -> %s' % (name, oldlvl, newlvl)) + + @command_log + def do_show_bricks(self, params): + ''' + show a list of configured bricks + ''' + map(lambda b: print('%s' % (b,)), self.management.list_bricks()) + + @command_log + def do_show_loggers(self, params): + ''' + show loggers + ''' + map(lambda name: print('logger %s level %s' % + (name, self.management.get_log_level(name))), + self.management.list_loggers()) + + @command_log + def do_show_options(self, params): + ''' + show options + ''' + # NOTE: this shows CONF of the child process. + # currently it isn't a problem because we don't modify CONF + # after startup. + class MyLogger: + def log(mylogger_self, lvl, fmt, *args): + print(fmt % args) + CONF.log_opt_values(MyLogger(), None) + + +class SshServer(paramiko.ServerInterface): + def __init__(self, logger, sock, addr): + super(SshServer, self).__init__() + self.logger = PrefixedLogger(logger, "CLI-SSH %s" % (addr,)) + 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_password(self, username, password): + print("check_auth_password", 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): + gevent.spawn(self._handle_shell_request) + return True + + def check_channel_pty_request(self, chan, term, width, height, + pixelwidth, pixelheight, modes): + self.term = term + return True + + def _pty_loop(self, chan, fd, rpipe, wpipe): + while True: + rfds, wfds, xfds = select.select([chan.fileno(), fd, rpipe], [], + []) + if fd in rfds: + data = os.read(fd, 1024) + if len(data) == 0: + break + chan.send(data) + if chan.fileno() in rfds: + data = chan.recv(1024) + if len(data) == 0: + break + os.write(fd, data) + if rpipe in rfds: + logger = self.logger + call_via_pipe.serve(rpipe, wpipe, [locals(), globals()]) + chan.close() + + def _handle_shell_request(self): + self.logger.info("session start") + chan = self.transport.accept(20) + if not chan: + self.logger.info("transport.accept timed out") + return + rpipe_request, wpipe_request = os.pipe() + rpipe_reply, wpipe_reply = os.pipe() + child_pid, master_fd = pty.fork() + if not child_pid: + os.close(rpipe_request) + os.close(wpipe_reply) + CliCmd(rpipe_reply, wpipe_request).cmdloop() + return + os.close(wpipe_request) + os.close(rpipe_reply) + self._pty_loop(chan, master_fd, rpipe_request, wpipe_reply) + self.logger.info("session end") + os.kill(child_pid, signal.SIGTERM) + os.waitpid(child_pid, 0) + + +class SshServerFactory(object): + def __init__(self, logger, *args, **kwargs): + super(SshServerFactory, self).__init__(*args, **kwargs) + self.logger = logger + + def streamserver_handle(self, sock, addr): + SshServer(self.logger, sock, addr) + + +class Cli(app_manager.RyuApp): + def __init__(self, *args, **kwargs): + super(Cli, self).__init__(*args, **kwargs) + something_started = False + if 'ssh' in CONF.cli_transports: + self.logger.info("starting ssh server at %s:%d", + CONF.cli_ssh_host, CONF.cli_ssh_port) + gevent.spawn(self._ssh_thread) + something_started = True + if not something_started: + self.logger.warn("cli app has no valid transport configured") + self.logger.debug("cli-transports=%s", CONF.cli_transports) + self.logger.debug("cli-ssh-hostkey=%s", CONF.cli_ssh_hostkey) + + def _ssh_thread(self): + logging.getLogger('paramiko') + logging.getLogger('paramiko.transport') + factory = SshServerFactory(self.logger) + server = gevent.server.StreamServer((CONF.cli_ssh_host, + CONF.cli_ssh_port), + factory.streamserver_handle) + server.serve_forever() -- 1.8.0.1 ------------------------------------------------------------------------------ Everyone hates slow websites. So do we. Make your web apps faster with AppDynamics Download AppDynamics Lite for free today: http://p.sf.net/sfu/appdyn_d2d_mar _______________________________________________ Ryu-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/ryu-devel
