#!/usr/bin/env python # # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. # Copyright (C) 2013 YAMAMOTO Takashi # # 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 simple command line msgpack-rpc client # # a usage example: # % PYTHONPATH=. ./bin/rpc-cli \ # --peers=echo-server=localhost:9999,hoge=localhost:9998 # (Cmd) request echo-server echo ["hoge"] # RESULT hoge # (Cmd) request echo-server notify ["notify-method", ["param1","param2"]] # RESULT notify-method # (Cmd) # NOTIFICATION from echo-server ['notify-method', ['param1', 'param2']] # (Cmd) import ryu.contrib from ryu import cfg import cmd import signal import socket import sys import termios from ryu.lib import rpc CONF = cfg.CONF CONF.register_cli_opts([ # eg. rpc-cli --peers=hoge=localhost:9998,fuga=localhost:9999 cfg.ListOpt('peers', default=[], help='list of peers') ]) class Peer(object): def __init__(self, name, addr): self._name = name self._addr = addr self.client = None try: self.connect() except: pass def connect(self): self.client = None s = socket.create_connection(self._addr) self.client = rpc.Client(s, notification_callback=self.notification) def try_to_connect(self, verbose=False): if self.client: return try: self.connect() assert self.client except Exception, e: if verbose: print "connection failure", e raise EOFError def notification(self, n): print "NOTIFICATION from", self._name, n def call(self, method, params): return self._do(lambda: self.client.call(method, params)) def send_notification(self, method, params): self._do(lambda: self.client.send_notification(method, params)) def _do(self, f): def g(): try: return f() except EOFError: self.client = None raise self.try_to_connect(verbose=True) try: return g() except EOFError: print "disconnected. trying to connect..." self.try_to_connect(verbose=True) print "connected. retrying the request..." return g() peers = {} def add_peer(name, host, port): peers[name] = Peer(name, (host, port)) class Cmd(cmd.Cmd): def __init__(self, *args, **kwargs): self._in_onecmd = False self._notification_check_interval = 1 # worth to be configurable? self._saved_termios = None cmd.Cmd.__init__(self, *args, **kwargs) def _request(self, line, f): args = line.split(None, 2) try: peer = args[0] method = args[1] params = eval(args[2]) except: print "argument error" return try: p = peers[peer] except KeyError: print "unknown peer", peer return try: f(p, method, params) except rpc.RPCError, e: print "RPC ERROR", e except EOFError: print "disconnected" def _complete_peer(self, text, line, _begidx, _endidx): if len((line + 'x').split()) >= 3: return [] return [name for name in peers if name.startswith(text)] def do_request(self, line): """request send a msgpack-rpc request and print a response. is a python code snippet, it should be eval'ed to a list. """ def f(p, method, params): result = p.call(method, params) print "RESULT", result self._request(line, f) def do_notify(self, line): """notify send a msgpack-rpc notification. is a python code snippet, it should be eval'ed to a list. """ def f(p, method, params): p.send_notification(method, params) self._request(line, f) def complete_request(self, text, line, begidx, endidx): return self._complete_peer(text, line, begidx, endidx) def complete_notify(self, text, line, begidx, endidx): return self._complete_peer(text, line, begidx, endidx) def do_EOF(self, _line): sys.exit(0) def emptyline(self): self._peek_notification() def postcmd(self, _stop, _line): self._peek_notification() def _peek_notification(self): for k, p in peers.iteritems(): if p.client: try: p.client.peek_notification() except EOFError: p.client = None print "disconnected", k @staticmethod def _save_termios(): return termios.tcgetattr(sys.stdin.fileno()) @staticmethod def _restore_termios(t): termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, t) def preloop(self): self._saved_termios = self._save_termios() signal.signal(signal.SIGALRM, self._timeout) signal.alarm(1) def onecmd(self, string): self._in_onecmd = True try: return cmd.Cmd.onecmd(self, string) finally: self._in_onecmd = False def _timeout(self, _sig, _frame): if not self._in_onecmd: # restore terminal settings. (cooked/raw, ...) # required for pypy at least. # this doesn't seem to be needed for cpython readline # module but i'm not sure if it's by spec or luck. o = self._save_termios() self._restore_termios(self._saved_termios) self._peek_notification() self._restore_termios(o) signal.alarm(self._notification_check_interval) def main(args=None, prog=None): CONF(args=args, prog=prog, project='rpc-cli', version='rpc-cli') for p_str in CONF.peers: name, addr = p_str.split('=') host, port = addr.rsplit(':', 1) add_peer(name, host, port) Cmd().cmdloop() if __name__ == "__main__": main()