From 53cf9963ccc60b443d738b31fbb446bc79170693 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 14 May 2012 11:26:36 -0700 Subject: [PATCH] python: Break unixctl implementation into registry, client, and server. I wish to add some unixctl commands to the Python vlog module. However, importing ovs.unixctl in ovs.vlog creates a circular dependency, because ovs.unixctl imports ovs.vlog already. The solution, in this commit, is to break the unixctl module into three parts: a register (ovs.unixctl) that does not depend on ovs.vlog, and client (ovs.unixctl.client) and server (ovs.unixctl.server) modules that do. This breaks the circular dependency. Signed-off-by: Ben Pfaff --- debian/ovs-monitor-ipsec | 3 +- python/automake.mk | 4 +- python/ovs/unixctl/__init__.py | 83 ++++++++++++++++++ python/ovs/unixctl/client.py | 70 +++++++++++++++ python/ovs/{unixctl.py => unixctl/server.py} | 85 +++---------------- tests/appctl.py | 3 +- tests/test-unixctl.py | 3 +- ...sr_share_openvswitch_scripts_ovs-xapi-sync | 3 +- 8 files changed, 174 insertions(+), 80 deletions(-) create mode 100644 python/ovs/unixctl/__init__.py create mode 100644 python/ovs/unixctl/client.py rename python/ovs/{unixctl.py => unixctl/server.py} (75%) diff --git a/debian/ovs-monitor-ipsec b/debian/ovs-monitor-ipsec index 94048047c..ffaa979d4 100755 --- a/debian/ovs-monitor-ipsec +++ b/debian/ovs-monitor-ipsec @@ -38,6 +38,7 @@ import ovs.util import ovs.daemon import ovs.db.idl import ovs.unixctl +import ovs.unixctl.server import ovs.vlog vlog = ovs.vlog.Vlog("ovs-monitor-ipsec") @@ -414,7 +415,7 @@ def main(): ovs.daemon.daemonize() ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None) - error, unixctl_server = ovs.unixctl.UnixctlServer.create(None) + error, unixctl_server = ovs.unixctl.server.UnixctlServer.create(None) if error: ovs.util.ovs_fatal(error, "could not create unixctl server", vlog) diff --git a/python/automake.mk b/python/automake.mk index 4c0e78b4d..96869e30e 100644 --- a/python/automake.mk +++ b/python/automake.mk @@ -27,7 +27,9 @@ ovs_pyfiles = \ python/ovs/socket_util.py \ python/ovs/stream.py \ python/ovs/timeval.py \ - python/ovs/unixctl.py \ + python/ovs/unixctl/__init__.py \ + python/ovs/unixctl/client.py \ + python/ovs/unixctl/server.py \ python/ovs/util.py \ python/ovs/version.py \ python/ovs/vlog.py diff --git a/python/ovs/unixctl/__init__.py b/python/ovs/unixctl/__init__.py new file mode 100644 index 000000000..715f2db5f --- /dev/null +++ b/python/ovs/unixctl/__init__.py @@ -0,0 +1,83 @@ +# Copyright (c) 2011, 2012 Nicira, Inc. +# +# 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. + +import types + +import ovs.util + +commands = {} +strtypes = types.StringTypes + + +class _UnixctlCommand(object): + def __init__(self, usage, min_args, max_args, callback, aux): + self.usage = usage + self.min_args = min_args + self.max_args = max_args + self.callback = callback + self.aux = aux + + +def _unixctl_help(conn, unused_argv, unused_aux): + reply = "The available commands are:\n" + command_names = sorted(commands.keys()) + for name in command_names: + reply += " " + usage = commands[name].usage + if usage: + reply += "%-23s %s" % (name, usage) + else: + reply += name + reply += "\n" + conn.reply(reply) + + +def command_register(name, usage, min_args, max_args, callback, aux): + """ Registers a command with the given 'name' to be exposed by the + UnixctlServer. 'usage' describes the arguments to the command; it is used + only for presentation to the user in "help" output. + + 'callback' is called when the command is received. It is passed a + UnixctlConnection object, the list of arguments as unicode strings, and + 'aux'. Normally 'callback' should reply by calling + UnixctlConnection.reply() or UnixctlConnection.reply_error() before it + returns, but if the command cannot be handled immediately, then it can + defer the reply until later. A given connection can only process a single + request at a time, so a reply must be made eventually to avoid blocking + that connection.""" + + assert isinstance(name, strtypes) + assert isinstance(usage, strtypes) + assert isinstance(min_args, int) + assert isinstance(max_args, int) + assert isinstance(callback, types.FunctionType) + + if name not in commands: + commands[name] = _UnixctlCommand(usage, min_args, max_args, callback, + aux) + +def socket_name_from_target(target): + assert isinstance(target, strtypes) + + if target.startswith("/"): + return 0, target + + pidfile_name = "%s/%s.pid" % (ovs.dirs.RUNDIR, target) + pid = ovs.daemon.read_pidfile(pidfile_name) + if pid < 0: + return -pid, "cannot read pidfile \"%s\"" % pidfile_name + + return 0, "%s/%s.%d.ctl" % (ovs.dirs.RUNDIR, target, pid) + +command_register("help", "", 0, 0, _unixctl_help, None) diff --git a/python/ovs/unixctl/client.py b/python/ovs/unixctl/client.py new file mode 100644 index 000000000..2176009ac --- /dev/null +++ b/python/ovs/unixctl/client.py @@ -0,0 +1,70 @@ +# Copyright (c) 2011, 2012 Nicira, Inc. +# +# 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. + +import copy +import errno +import os +import types + +import ovs.jsonrpc +import ovs.stream +import ovs.util + + +vlog = ovs.vlog.Vlog("unixctl_client") +strtypes = types.StringTypes + + +class UnixctlClient(object): + def __init__(self, conn): + assert isinstance(conn, ovs.jsonrpc.Connection) + self._conn = conn + + def transact(self, command, argv): + assert isinstance(command, strtypes) + assert isinstance(argv, list) + for arg in argv: + assert isinstance(arg, strtypes) + + request = ovs.jsonrpc.Message.create_request(command, argv) + error, reply = self._conn.transact_block(request) + + if error: + vlog.warn("error communicating with %s: %s" + % (self._conn.name, os.strerror(error))) + return error, None, None + + if reply.error is not None: + return 0, str(reply.error), None + else: + assert reply.result is not None + return 0, None, str(reply.result) + + def close(self): + self._conn.close() + self.conn = None + + @staticmethod + def create(path): + assert isinstance(path, str) + + unix = "unix:%s" % ovs.util.abs_file_name(ovs.dirs.RUNDIR, path) + error, stream = ovs.stream.Stream.open_block( + ovs.stream.Stream.open(unix)) + + if error: + vlog.warn("failed to connect to %s" % path) + return error, None + + return 0, UnixctlClient(ovs.jsonrpc.Connection(stream)) diff --git a/python/ovs/unixctl.py b/python/ovs/unixctl/server.py similarity index 75% rename from python/ovs/unixctl.py rename to python/ovs/unixctl/server.py index 0fadaeb6a..18e1cf20b 100644 --- a/python/ovs/unixctl.py +++ b/python/ovs/unixctl/server.py @@ -17,89 +17,19 @@ import errno import os import types -import ovs.daemon import ovs.dirs import ovs.jsonrpc import ovs.stream +import ovs.unixctl import ovs.util import ovs.version import ovs.vlog Message = ovs.jsonrpc.Message -vlog = ovs.vlog.Vlog("unixctl") -commands = {} +vlog = ovs.vlog.Vlog("unixctl_server") strtypes = types.StringTypes -class _UnixctlCommand(object): - def __init__(self, usage, min_args, max_args, callback, aux): - self.usage = usage - self.min_args = min_args - self.max_args = max_args - self.callback = callback - self.aux = aux - - -def _unixctl_help(conn, unused_argv, unused_aux): - assert isinstance(conn, UnixctlConnection) - reply = "The available commands are:\n" - command_names = sorted(commands.keys()) - for name in command_names: - reply += " " - usage = commands[name].usage - if usage: - reply += "%-23s %s" % (name, usage) - else: - reply += name - reply += "\n" - conn.reply(reply) - - -def _unixctl_version(conn, unused_argv, version): - assert isinstance(conn, UnixctlConnection) - version = "%s (Open vSwitch) %s" % (ovs.util.PROGRAM_NAME, version) - conn.reply(version) - - -def command_register(name, usage, min_args, max_args, callback, aux): - """ Registers a command with the given 'name' to be exposed by the - UnixctlServer. 'usage' describes the arguments to the command; it is used - only for presentation to the user in "help" output. - - 'callback' is called when the command is received. It is passed a - UnixctlConnection object, the list of arguments as unicode strings, and - 'aux'. Normally 'callback' should reply by calling - UnixctlConnection.reply() or UnixctlConnection.reply_error() before it - returns, but if the command cannot be handled immediately, then it can - defer the reply until later. A given connection can only process a single - request at a time, so a reply must be made eventually to avoid blocking - that connection.""" - - assert isinstance(name, strtypes) - assert isinstance(usage, strtypes) - assert isinstance(min_args, int) - assert isinstance(max_args, int) - assert isinstance(callback, types.FunctionType) - - if name not in commands: - commands[name] = _UnixctlCommand(usage, min_args, max_args, callback, - aux) - - -def socket_name_from_target(target): - assert isinstance(target, strtypes) - - if target.startswith("/"): - return 0, target - - pidfile_name = "%s/%s.pid" % (ovs.dirs.RUNDIR, target) - pid = ovs.daemon.read_pidfile(pidfile_name) - if pid < 0: - return -pid, "cannot read pidfile \"%s\"" % pidfile_name - - return 0, "%s/%s.%d.ctl" % (ovs.dirs.RUNDIR, target, pid) - - class UnixctlConnection(object): def __init__(self, rpc): assert isinstance(rpc, ovs.jsonrpc.Connection) @@ -177,7 +107,7 @@ class UnixctlConnection(object): error = None params = request.params method = request.method - command = commands.get(method) + command = ovs.unixctl.commands.get(method) if command is None: error = '"%s" is not a valid command' % method elif len(params) < command.min_args: @@ -200,6 +130,11 @@ class UnixctlConnection(object): self.reply_error(error) +def _unixctl_version(conn, unused_argv, version): + assert isinstance(conn, UnixctlConnection) + version = "%s (Open vSwitch) %s" % (ovs.util.PROGRAM_NAME, version) + conn.reply(version) + class UnixctlServer(object): def __init__(self, listener): assert isinstance(listener, ovs.stream.PassiveStream) @@ -262,8 +197,8 @@ class UnixctlServer(object): % path) return error, None - command_register("help", "", 0, 0, _unixctl_help, None) - command_register("version", "", 0, 0, _unixctl_version, version) + ovs.unixctl.command_register("version", "", 0, 0, _unixctl_version, + version) return 0, UnixctlServer(listener) diff --git a/tests/appctl.py b/tests/appctl.py index e5698ef87..e5bcf2c03 100644 --- a/tests/appctl.py +++ b/tests/appctl.py @@ -18,6 +18,7 @@ import sys import ovs.daemon import ovs.unixctl +import ovs.unixctl.client import ovs.util import ovs.vlog @@ -29,7 +30,7 @@ def connect_to_target(target): else: socket_name = str_result - error, client = ovs.unixctl.UnixctlClient.create(socket_name) + error, client = ovs.unixctl.client.UnixctlClient.create(socket_name) if error: ovs.util.ovs_fatal(error, "cannot connect to \"%s\"" % socket_name) diff --git a/tests/test-unixctl.py b/tests/test-unixctl.py index 038c1bd68..3d86db17d 100644 --- a/tests/test-unixctl.py +++ b/tests/test-unixctl.py @@ -17,6 +17,7 @@ import sys import ovs.daemon import ovs.unixctl +import ovs.unixctl.server vlog = ovs.vlog.Vlog("test-unixctl") exiting = False @@ -55,7 +56,7 @@ def main(): ovs.vlog.handle_args(args) ovs.daemon.daemonize_start() - error, server = ovs.unixctl.UnixctlServer.create(args.unixctl) + error, server = ovs.unixctl.server.UnixctlServer.create(args.unixctl) if error: ovs.util.ovs_fatal(error, "could not create unixctl server at %s" % args.unixctl, vlog) diff --git a/xenserver/usr_share_openvswitch_scripts_ovs-xapi-sync b/xenserver/usr_share_openvswitch_scripts_ovs-xapi-sync index fc2158247..5083bbd16 100755 --- a/xenserver/usr_share_openvswitch_scripts_ovs-xapi-sync +++ b/xenserver/usr_share_openvswitch_scripts_ovs-xapi-sync @@ -35,6 +35,7 @@ from ovs.db import types import ovs.daemon import ovs.db.idl import ovs.unixctl +import ovs.unixctl.server vlog = ovs.vlog.Vlog("ovs-xapi-sync") session = None @@ -236,7 +237,7 @@ def main(): ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None) ovs.unixctl.command_register("flush-cache", "", 0, 0, unixctl_flush_cache, None) - error, unixctl_server = ovs.unixctl.UnixctlServer.create(None) + error, unixctl_server = ovs.unixctl.server.UnixctlServer.create(None) if error: ovs.util.ovs_fatal(error, "could not create unixctl server", vlog) -- 2.43.0