summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorYAMAMOTO Takashi <yamamoto@valinux.co.jp>2013-11-28 14:30:05 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2013-11-29 09:23:41 +0900
commit3e3cb12c0636fcefbf0b4e922023cf59d8b01573 (patch)
treed8f63c5980aed389d9efff48ff5ed1817b6a92af
parent220b9e2ca73afaaeff72c53482e21bd282430c61 (diff)
a simple command line msgpack-rpc client
Signed-off-by: YAMAMOTO Takashi <yamamoto@valinux.co.jp> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rwxr-xr-xbin/rpc-cli20
-rwxr-xr-xrun_tests.sh2
-rwxr-xr-xryu/cmd/rpc_cli.py237
-rw-r--r--setup.cfg1
4 files changed, 259 insertions, 1 deletions
diff --git a/bin/rpc-cli b/bin/rpc-cli
new file mode 100755
index 00000000..cdb832a5
--- /dev/null
+++ b/bin/rpc-cli
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# 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.
+
+from ryu.cmd.rpc_cli import main
+main()
diff --git a/run_tests.sh b/run_tests.sh
index e791ed94..89153a2c 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -90,7 +90,7 @@ run_tests() {
run_pylint() {
echo "Running pylint ..."
PYLINT_OPTIONS="--rcfile=.pylintrc --output-format=parseable"
- PYLINT_INCLUDE="ryu ryu/tests/bin/ryu-client"
+ PYLINT_INCLUDE="ryu bin/rpc-cli ryu/tests/bin/ryu-client"
export PYTHONPATH=$PYTHONPATH:.ryu
PYLINT_LOG=pylint.log
diff --git a/ryu/cmd/rpc_cli.py b/ryu/cmd/rpc_cli.py
new file mode 100755
index 00000000..05696b32
--- /dev/null
+++ b/ryu/cmd/rpc_cli.py
@@ -0,0 +1,237 @@
+#!/usr/bin/env python
+#
+# 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 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 oslo.config 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 <peer> <method> <params>
+ send a msgpack-rpc request and print a response.
+ <params> 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 <peer> <method> <params>
+ send a msgpack-rpc notification.
+ <params> 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():
+ CONF(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()
diff --git a/setup.cfg b/setup.cfg
index de434ed4..262dee2b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -48,3 +48,4 @@ setup-hooks =
[entry_points]
console_scripts =
ryu-manager = ryu.cmd.manager:main
+ rpc-cli = ryu.cmd.rpc_cli:main