1 # Copyright (c) 2012 Nicira Networks
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
28 Message = ovs.jsonrpc.Message
29 vlog = ovs.vlog.Vlog("unixctl")
31 strtypes = types.StringTypes
34 class _UnixctlCommand(object):
35 def __init__(self, usage, min_args, max_args, callback, aux):
37 self.min_args = min_args
38 self.max_args = max_args
39 self.callback = callback
43 def _unixctl_help(conn, unused_argv, unused_aux):
44 assert isinstance(conn, UnixctlConnection)
45 reply = "The available commands are:\n"
46 command_names = sorted(commands.keys())
47 for name in command_names:
49 usage = commands[name].usage
51 reply += "%-23s %s" % (name, usage)
58 def _unixctl_version(conn, unused_argv, version):
59 assert isinstance(conn, UnixctlConnection)
60 version = "%s (Open vSwitch) %s" % (ovs.util.PROGRAM_NAME, version)
64 def command_register(name, usage, min_args, max_args, callback, aux):
65 """ Registers a command with the given 'name' to be exposed by the
66 UnixctlServer. 'usage' describes the arguments to the command; it is used
67 only for presentation to the user in "help" output.
69 'callback' is called when the command is received. It is passed a
70 UnixctlConnection object, the list of arguments as unicode strings, and
71 'aux'. Normally 'callback' should reply by calling
72 UnixctlConnection.reply() or UnixctlConnection.reply_error() before it
73 returns, but if the command cannot be handled immediately, then it can
74 defer the reply until later. A given connection can only process a single
75 request at a time, so a reply must be made eventually to avoid blocking
78 assert isinstance(name, strtypes)
79 assert isinstance(usage, strtypes)
80 assert isinstance(min_args, int)
81 assert isinstance(max_args, int)
82 assert isinstance(callback, types.FunctionType)
84 if name not in commands:
85 commands[name] = _UnixctlCommand(usage, min_args, max_args, callback,
89 def socket_name_from_target(target):
90 assert isinstance(target, strtypes)
92 if target.startswith("/"):
95 pidfile_name = "%s/%s.pid" % (ovs.dirs.RUNDIR, target)
96 pid = ovs.daemon.read_pidfile(pidfile_name)
98 return -pid, "cannot read pidfile \"%s\"" % pidfile_name
100 return 0, "%s/%s.%d.ctl" % (ovs.dirs.RUNDIR, target, pid)
103 class UnixctlConnection(object):
104 def __init__(self, rpc):
105 assert isinstance(rpc, ovs.jsonrpc.Connection)
107 self._request_id = None
111 error = self._rpc.get_status()
112 if error or self._rpc.get_backlog():
116 if error or self._request_id:
119 error, msg = self._rpc.recv()
121 if msg.type == Message.T_REQUEST:
122 self._process_command(msg)
125 vlog.warn("%s: received unexpected %s message"
127 Message.type_to_string(msg.type)))
131 error = self._rpc.get_status()
135 def reply(self, body):
136 self._reply_impl(True, body)
138 def reply_error(self, body):
139 self._reply_impl(False, body)
141 # Called only by unixctl classes.
144 self._request_id = None
146 def _wait(self, poller):
147 self._rpc.wait(poller)
148 if not self._rpc.get_backlog():
149 self._rpc.recv_wait(poller)
151 def _reply_impl(self, success, body):
152 assert isinstance(success, bool)
153 assert body is None or isinstance(body, strtypes)
155 assert self._request_id is not None
160 if body and not body.endswith("\n"):
164 reply = Message.create_reply(body, self._request_id)
166 reply = Message.create_error(body, self._request_id)
168 self._rpc.send(reply)
169 self._request_id = None
171 def _process_command(self, request):
172 assert isinstance(request, ovs.jsonrpc.Message)
173 assert request.type == ovs.jsonrpc.Message.T_REQUEST
175 self._request_id = request.id
178 params = request.params
179 method = request.method
180 command = commands.get(method)
182 error = '"%s" is not a valid command' % method
183 elif len(params) < command.min_args:
184 error = '"%s" command requires at least %d arguments' \
185 % (method, command.min_args)
186 elif len(params) > command.max_args:
187 error = '"%s" command takes at most %d arguments' \
188 % (method, command.max_args)
191 if not isinstance(param, strtypes):
192 error = '"%s" command has non-string argument' % method
196 unicode_params = [unicode(p) for p in params]
197 command.callback(self, unicode_params, command.aux)
200 self.reply_error(error)
203 class UnixctlServer(object):
204 def __init__(self, listener):
205 assert isinstance(listener, ovs.stream.PassiveStream)
206 self._listener = listener
211 error, stream = self._listener.accept()
213 rpc = ovs.jsonrpc.Connection(stream)
214 self._conns.append(UnixctlConnection(rpc))
215 elif error == errno.EAGAIN:
219 vlog.warn("%s: accept failed: %s" % (self._listener.name,
222 for conn in copy.copy(self._conns):
224 if error and error != errno.EAGAIN:
226 self._conns.remove(conn)
228 def wait(self, poller):
229 self._listener.wait(poller)
230 for conn in self._conns:
234 for conn in self._conns:
238 self._listener.close()
239 self._listener = None
242 def create(path, version=None):
243 """Creates a new UnixctlServer which listens on a unixctl socket
244 created at 'path'. If 'path' is None, the default path is chosen.
245 'version' contains the version of the server as reported by the unixctl
246 version command. If None, ovs.version.VERSION is used."""
248 assert path is None or isinstance(path, strtypes)
251 path = "punix:%s" % ovs.util.abs_file_name(ovs.dirs.RUNDIR, path)
253 path = "punix:%s/%s.%d.ctl" % (ovs.dirs.RUNDIR,
254 ovs.util.PROGRAM_NAME, os.getpid())
257 version = ovs.version.VERSION
259 error, listener = ovs.stream.PassiveStream.open(path)
261 ovs.util.ovs_error(error, "could not initialize control socket %s"
265 command_register("help", "", 0, 0, _unixctl_help, None)
266 command_register("version", "", 0, 0, _unixctl_version, version)
268 return 0, UnixctlServer(listener)
271 class UnixctlClient(object):
272 def __init__(self, conn):
273 assert isinstance(conn, ovs.jsonrpc.Connection)
276 def transact(self, command, argv):
277 assert isinstance(command, strtypes)
278 assert isinstance(argv, list)
280 assert isinstance(arg, strtypes)
282 request = Message.create_request(command, argv)
283 error, reply = self._conn.transact_block(request)
286 vlog.warn("error communicating with %s: %s"
287 % (self._conn.name, os.strerror(error)))
288 return error, None, None
290 if reply.error is not None:
291 return 0, str(reply.error), None
293 assert reply.result is not None
294 return 0, None, str(reply.result)
302 assert isinstance(path, str)
304 unix = "unix:%s" % ovs.util.abs_file_name(ovs.dirs.RUNDIR, path)
305 error, stream = ovs.stream.Stream.open_block(
306 ovs.stream.Stream.open(unix))
309 vlog.warn("failed to connect to %s" % path)
312 return 0, UnixctlClient(ovs.jsonrpc.Connection(stream))