3 # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
4 # Copyright (C) 2013 YAMAMOTO Takashi <yamamoto at valinux co jp>
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 # a simple command line msgpack-rpc client
22 # % PYTHONPATH=. ./bin/rpc-cli \
23 # --peers=echo-server=localhost:9999,hoge=localhost:9998
24 # (Cmd) request echo-server echo ["hoge"]
26 # (Cmd) request echo-server notify ["notify-method", ["param1","param2"]]
27 # RESULT notify-method
29 # NOTIFICATION from echo-server ['notify-method', ['param1', 'param2']]
32 from __future__ import print_function
42 from ryu.lib import rpc
46 CONF.register_cli_opts([
47 cfg.ListOpt('peers', default=[],
48 help='List of peers, separated by commas. '
49 '(e.g., "hoge=localhost:9998,fuga=localhost:9999")'),
50 cfg.StrOpt('command', short='c', default=None,
51 help='Command to be executed as single command. '
52 'The default is None and opens interactive console.'),
57 def __init__(self, name, addr):
64 except ConnectionError as e:
65 print('Exception when connecting to peer "%s": %s' % (name, e))
69 self.socket = socket.create_connection(self._addr)
70 self.client = rpc.Client(self.socket,
71 notification_callback=self.notification)
73 def try_to_connect(self, verbose=False):
79 except Exception as e:
81 print("connection failure %s" % e)
84 def notification(self, n):
85 print("NOTIFICATION from %s %s" % (self._name, n))
87 def call(self, method, params):
88 return self._do(lambda: self.client.call(method, params))
90 def send_notification(self, method, params):
91 self._do(lambda: self.client.send_notification(method, params))
101 self.try_to_connect(verbose=True)
105 print("disconnected. trying to connect...")
106 self.try_to_connect(verbose=True)
107 print("connected. retrying the request...")
117 def add_peer(name, host, port):
119 peer = Peer(name, (host, port))
120 except ConnectionError:
127 for peer in peers.values():
132 def __init__(self, *args, **kwargs):
133 self._in_onecmd = False
134 self._notification_check_interval = 1 # worth to be configurable?
135 self._saved_termios = None
136 cmd.Cmd.__init__(self, *args, **kwargs)
138 def _request(self, line, f):
139 args = line.split(None, 2)
143 params = ast.literal_eval(args[2])
144 except (IndexError, ValueError) as e:
145 print("argument error: %s" % e)
150 print("unknown peer %s" % peer)
154 except rpc.RPCError as e:
155 print("RPC ERROR %s" % e)
157 print("disconnected")
159 def _complete_peer(self, text, line, _begidx, _endidx):
160 if len((line + 'x').split()) >= 3:
162 return [name for name in peers if name.startswith(text)]
164 def do_request(self, line):
165 """request <peer> <method> <params>
166 send a msgpack-rpc request and print a response.
167 <params> is a python code snippet, it should be eval'ed to a list.
170 def f(p, method, params):
171 result = p.call(method, params)
172 print("RESULT %s" % result)
174 self._request(line, f)
176 def do_notify(self, line):
177 """notify <peer> <method> <params>
178 send a msgpack-rpc notification.
179 <params> is a python code snippet, it should be eval'ed to a list.
182 def f(p, method, params):
183 p.send_notification(method, params)
185 self._request(line, f)
187 def complete_request(self, text, line, begidx, endidx):
188 return self._complete_peer(text, line, begidx, endidx)
190 def complete_notify(self, text, line, begidx, endidx):
191 return self._complete_peer(text, line, begidx, endidx)
193 def do_EOF(self, _line=None):
198 self._peek_notification()
200 def postcmd(self, _stop, _line):
201 self._peek_notification()
203 def _peek_notification(self):
204 for k, p in peers.items():
207 p.client.peek_notification()
210 print("disconnected %s" % k)
214 return termios.tcgetattr(sys.stdin.fileno())
217 def _restore_termios(t):
218 termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, t)
221 self._saved_termios = self._save_termios()
222 signal.signal(signal.SIGALRM, self._timeout)
228 def onecmd(self, string):
229 self._in_onecmd = True
231 return cmd.Cmd.onecmd(self, string)
233 self._in_onecmd = False
235 def _timeout(self, _sig, _frame):
236 if not self._in_onecmd:
237 # restore terminal settings. (cooked/raw, ...)
238 # required for pypy at least.
239 # this doesn't seem to be needed for cpython readline
240 # module but i'm not sure if it's by spec or luck.
241 o = self._save_termios()
242 self._restore_termios(self._saved_termios)
243 self._peek_notification()
244 self._restore_termios(o)
245 signal.alarm(self._notification_check_interval)
248 def main(args=None, prog=None):
249 CONF(args=args, prog=prog, project='rpc-cli', version='rpc-cli')
251 for p_str in CONF.peers:
252 name, addr = p_str.split('=')
253 host, port = addr.rsplit(':', 1)
254 add_peer(name, host, port)
258 command.onecmd(CONF.command)
264 if __name__ == "__main__":