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>
This commit is contained in:
YAMAMOTO Takashi 2013-11-28 14:30:05 +09:00 committed by FUJITA Tomonori
parent 220b9e2ca7
commit 3e3cb12c06
4 changed files with 259 additions and 1 deletions

20
bin/rpc-cli Executable file
View File

@ -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()

View File

@ -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

237
ryu/cmd/rpc_cli.py Executable file
View File

@ -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()

View File

@ -48,3 +48,4 @@ setup-hooks =
[entry_points]
console_scripts =
ryu-manager = ryu.cmd.manager:main
rpc-cli = ryu.cmd.rpc_cli:main