[openstack-quantum] Upstream patch: add root_helper to quantum agents

Robert A. Kukura rkukura at fedoraproject.org
Wed Mar 14 14:37:39 UTC 2012


commit fb5b86eb89d8563920a9e67a6765811634099209
Author: Bob Kukura <rkukura at redhat.com>
Date:   Wed Mar 14 10:36:41 2012 -0400

    Upstream patch: add root_helper to quantum agents
    
    - Add sudoers file enabling quantum-rootwrap for quantum user
    - Configure plugin agents to use quantum-rootwrap
    - Run plugin agents as quantum user

 openstack-quantum.spec                             |   52 +-
 quantum-linuxbridge-agent.service                  |    3 +-
 quantum-openvswitch-agent.service                  |    3 +-
 quantum-ryu-agent.service                          |    3 +-
 quantum-sudoers                                    |    3 +
 ...-3d3b1c3f78997e08c1f1a876b098d2afb759507b.patch |  931 ++++++++++++++++++++
 6 files changed, 979 insertions(+), 16 deletions(-)
---
diff --git a/openstack-quantum.spec b/openstack-quantum.spec
index a7c55f3..b99560d 100644
--- a/openstack-quantum.spec
+++ b/openstack-quantum.spec
@@ -7,25 +7,32 @@
 
 Name:		openstack-quantum
 Version:	2012.1
-Release:	0.3.%{release_letter}%{milestone}%{?dist}
+Release:	0.4.%{release_letter}%{milestone}%{?dist}
 Summary:	Virtual network service for OpenStack (quantum)
 
 Group:		Applications/System
 License:	ASL 2.0
 URL:		http://launchpad.net/quantum/
+
 Source0:	http://launchpad.net/quantum/%{release_name}/%{release_name}-%{milestone}/+download/quantum-%{version}~%{release_letter}%{milestone}.tar.gz
 Source1:	quantum.logrotate
-Source2:	quantum-server.service
-Source3:	quantum-linuxbridge-agent.service
-Source4:	quantum-openvswitch-agent.service
-Source5:	quantum-ryu-agent.service
+Source2:	quantum-sudoers
+
+Source10:	quantum-server.service
+Source11:	quantum-linuxbridge-agent.service
+Source12:	quantum-openvswitch-agent.service
+Source13:	quantum-ryu-agent.service
+
 
 # Merged upstream patch https://review.openstack.org/#change,4235
 Patch1:		quantum.git-82138245694b452341cc41638058adb3972a9926.patch
 
-# Proposed upstream patch for https://bugs.launchpad.net/quantum/+bug/949261
+# Merged upstream patch for https://bugs.launchpad.net/quantum/+bug/949261
 Patch2:		quantum.git-f614a7ddf57a2a4c30d9410671622caca6f4e434.patch
 
+# Proposed upstream patch for https://bugs.launchpad.net/quantum/+bug/948467
+Patch3:		quantum.git-3d3b1c3f78997e08c1f1a876b098d2afb759507b.patch
+
 BuildArch:	noarch
 
 BuildRequires:	python2-devel
@@ -99,6 +106,7 @@ Group:		Applications/System
 
 Requires:	openstack-quantum = %{version}-%{release}
 Requires:	bridge-utils
+Requires:	sudo
 
 
 %description -n openstack-quantum-linuxbridge
@@ -130,6 +138,7 @@ Group:		Applications/System
 
 Requires:	openstack-quantum = %{version}-%{release}
 Requires:	openvswitch
+Requires:	sudo
 
 
 %description -n openstack-quantum-openvswitch
@@ -145,6 +154,7 @@ Summary:	Quantum ryu plugin
 Group:		Applications/System
 
 Requires:	openstack-quantum = %{version}-%{release}
+Requires:	sudo
 
 
 %description -n openstack-quantum-ryu
@@ -160,6 +170,7 @@ networks using the Ryu Network Operating System.
 
 %patch1 -p1
 %patch2 -p1
+%patch3 -p1
 
 find quantum -name \*.py -exec sed -i '/\/usr\/bin\/env python/d' {} \;
 
@@ -185,19 +196,28 @@ rm %{buildroot}/usr/etc/init.d/quantum-server
 
 # Install execs
 install -p -D -m 755 bin/quantum-server %{buildroot}%{_bindir}/quantum-server
+install -p -D -m 755 bin/quantum-rootwrap %{buildroot}%{_bindir}/quantum-rootwrap
 
 # Move config files to proper location
 install -d -m 755 %{buildroot}%{_sysconfdir}/quantum
 mv %{buildroot}/usr/etc/quantum/* %{buildroot}%{_sysconfdir}/quantum
 
+# Configure plugin agents to use quantum-rootwrap
+for f in %{buildroot}%{_sysconfdir}/quantum/plugins/*/*.ini; do
+    sed -i 's/root_helper = sudo/root_helper = sudo quantum-rootwrap/g' $f
+done
+
 # Install logrotate
 install -p -D -m 644 %{SOURCE1} %{buildroot}%{_sysconfdir}/logrotate.d/openstack-quantum
 
+# Install sudoers
+install -p -D -m 440 %{SOURCE2} %{buildroot}%{_sysconfdir}/sudoers.d/quantum
+
 # Install systemd units
-install -p -D -m 644 %{SOURCE2} %{buildroot}%{_unitdir}/quantum-server.service
-install -p -D -m 644 %{SOURCE3} %{buildroot}%{_unitdir}/quantum-linuxbridge-agent.service
-install -p -D -m 644 %{SOURCE4} %{buildroot}%{_unitdir}/quantum-openvswitch-agent.service
-install -p -D -m 644 %{SOURCE5} %{buildroot}%{_unitdir}/quantum-ryu-agent.service
+install -p -D -m 644 %{SOURCE10} %{buildroot}%{_unitdir}/quantum-server.service
+install -p -D -m 644 %{SOURCE11} %{buildroot}%{_unitdir}/quantum-linuxbridge-agent.service
+install -p -D -m 644 %{SOURCE12} %{buildroot}%{_unitdir}/quantum-openvswitch-agent.service
+install -p -D -m 644 %{SOURCE13} %{buildroot}%{_unitdir}/quantum-ryu-agent.service
 
 # Setup directories
 install -d -m 755 %{buildroot}%{_sharedstatedir}/quantum
@@ -287,12 +307,14 @@ fi
 %doc LICENSE
 %doc README
 %{_bindir}/quantum-server
+%{_bindir}/quantum-rootwrap
 %{_unitdir}/quantum-server.service
 %dir %{_sysconfdir}/quantum
 %config(noreplace) %{_sysconfdir}/quantum/quantum.conf
 %config(noreplace) %{_sysconfdir}/quantum/plugins.ini
 %dir %{_sysconfdir}/quantum/plugins
 %config(noreplace) %{_sysconfdir}/logrotate.d/*
+%config(noreplace) %{_sysconfdir}/sudoers.d/quantum
 %dir %attr(0755, quantum, quantum) %{_sharedstatedir}/quantum
 %dir %attr(0755, quantum, quantum) %{_localstatedir}/log/quantum
 
@@ -317,6 +339,7 @@ fi
 %exclude %{python_sitelib}/quantum/plugins/nicira
 %exclude %{python_sitelib}/quantum/plugins/openvswitch
 %exclude %{python_sitelib}/quantum/plugins/ryu
+%exclude %{python_sitelib}/quantum/rootwrap/*-agent.py*
 %{python_sitelib}/quantum-%%{version}-*.egg-info
 
 
@@ -343,6 +366,7 @@ fi
 %{_bindir}/quantum-linuxbridge-agent
 %{_unitdir}/quantum-linuxbridge-agent.service
 %{python_sitelib}/quantum/plugins/linuxbridge
+%{python_sitelib}/quantum/rootwrap/linuxbridge-agent.py*
 %dir %{_sysconfdir}/quantum/plugins/linuxbridge
 %config(noreplace) %{_sysconfdir}/quantum/plugins/linuxbridge/*.ini
 
@@ -361,6 +385,7 @@ fi
 %{_bindir}/quantum-openvswitch-agent
 %{_unitdir}/quantum-openvswitch-agent.service
 %{python_sitelib}/quantum/plugins/openvswitch
+%{python_sitelib}/quantum/rootwrap/openvswitch-agent.py*
 %dir %{_sysconfdir}/quantum/plugins/openvswitch
 %config(noreplace) %{_sysconfdir}/quantum/plugins/openvswitch/*.ini
 
@@ -371,11 +396,18 @@ fi
 %{_bindir}/quantum-ryu-agent
 %{_unitdir}/quantum-ryu-agent.service
 %{python_sitelib}/quantum/plugins/ryu
+%{python_sitelib}/quantum/rootwrap/ryu-agent.py*
 %dir %{_sysconfdir}/quantum/plugins/ryu
 %config(noreplace) %{_sysconfdir}/quantum/plugins/ryu/*.ini
 
 
 %changelog
+* Wed Mar 14 2012 Robert Kukura <rkukura at redhat.com> - 2012.1-0.4.e4
+- Upstream patch: add root_helper to quantum agents
+- Add sudoers file enabling quantum-rootwrap for quantum user
+- Configure plugin agents to use quantum-rootwrap
+- Run plugin agents as quantum user
+
 * Fri Mar  9 2012 Robert Kukura <rkukura at redhat.com> - 2012.1-0.3.e4
 - Add upstream patch: remove pep8 and strict lxml version from setup.py
 - Remove old fix for pep8 dependency
diff --git a/quantum-linuxbridge-agent.service b/quantum-linuxbridge-agent.service
index 0dcab18..9987526 100644
--- a/quantum-linuxbridge-agent.service
+++ b/quantum-linuxbridge-agent.service
@@ -4,8 +4,7 @@ After=syslog.target network.target
 
 [Service]
 Type=simple
-#User=quantum
-User=root
+User=quantum
 ExecStart=/usr/bin/quantum-linuxbridge-agent /etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
 
 [Install]
diff --git a/quantum-openvswitch-agent.service b/quantum-openvswitch-agent.service
index d53438c..b7a3e19 100644
--- a/quantum-openvswitch-agent.service
+++ b/quantum-openvswitch-agent.service
@@ -4,8 +4,7 @@ After=syslog.target network.target
 
 [Service]
 Type=simple
-#User=quantum
-User=root
+User=quantum
 ExecStart=/usr/bin/quantum-openvswitch-agent /etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini
 
 [Install]
diff --git a/quantum-ryu-agent.service b/quantum-ryu-agent.service
index b912cd2..eab3b87 100644
--- a/quantum-ryu-agent.service
+++ b/quantum-ryu-agent.service
@@ -4,8 +4,7 @@ After=syslog.target network.target
 
 [Service]
 Type=simple
-#User=quantum
-User=root
+User=quantum
 ExecStart=/usr/bin/quantum-ryu-agent /etc/quantum/plugins/ryu/ryu.ini
 
 [Install]
diff --git a/quantum-sudoers b/quantum-sudoers
new file mode 100644
index 0000000..3695e03
--- /dev/null
+++ b/quantum-sudoers
@@ -0,0 +1,3 @@
+Defaults:quantum !requiretty
+
+quantum ALL = (root) NOPASSWD: SETENV: /usr/bin/quantum-rootwrap
diff --git a/quantum.git-3d3b1c3f78997e08c1f1a876b098d2afb759507b.patch b/quantum.git-3d3b1c3f78997e08c1f1a876b098d2afb759507b.patch
new file mode 100644
index 0000000..5d73e53
--- /dev/null
+++ b/quantum.git-3d3b1c3f78997e08c1f1a876b098d2afb759507b.patch
@@ -0,0 +1,931 @@
+From 3d3b1c3f78997e08c1f1a876b098d2afb759507b Mon Sep 17 00:00:00 2001
+From: Bob Kukura <rkukura at redhat.com>
+Date: Tue, 13 Mar 2012 17:23:06 -0400
+Subject: [PATCH] Add root_helper to quantum agents.
+
+When running commands that require root privileges, the linuxbridge,
+openvswitch, and ryu agent now prepend the commands with the value of
+the root_helper config variable. This is set to "sudo" in the plugins'
+.ini files, allowing the agent to run as a non-root user with
+appropriate sudo privilidges.
+
+If root_helper is changed to "sudo quantum-rootwrap",
+then the command being run will be filtered against lists of each
+agent's valid commands in quantum/rootwrap. See
+http://wiki.openstack.org/Packager/Rootwrap for details.
+
+Fixes bug 948467.
+
+Change-Id: I549515068a4ce8ae480905ec5eaab6257445d0c3
+Signed-off-by: Bob Kukura <rkukura at redhat.com>
+---
+ bin/quantum-rootwrap                               |   73 ++++++++++
+ .../plugins/linuxbridge/linuxbridge_conf.ini       |    3 +
+ .../plugins/openvswitch/ovs_quantum_plugin.ini     |    9 ++
+ etc/quantum/plugins/ryu/ryu.ini                    |    5 +
+ .../linuxbridge/agent/linuxbridge_quantum_agent.py |   21 ++-
+ .../tests/unit/_test_linuxbridgeAgent.py           |   13 ++-
+ .../plugins/openvswitch/agent/ovs_quantum_agent.py |   30 +++--
+ .../plugins/openvswitch/tests/unit/test_tunnel.py  |   27 +++--
+ quantum/plugins/ryu/agent/ryu_quantum_agent.py     |   17 ++-
+ quantum/rootwrap/__init__.py                       |   16 +++
+ quantum/rootwrap/filters.py                        |  143 ++++++++++++++++++++
+ quantum/rootwrap/linuxbridge-agent.py              |   46 +++++++
+ quantum/rootwrap/openvswitch-agent.py              |   36 +++++
+ quantum/rootwrap/ryu-agent.py                      |   31 +++++
+ quantum/rootwrap/wrapper.py                        |   63 +++++++++
+ 15 files changed, 496 insertions(+), 37 deletions(-)
+ create mode 100755 bin/quantum-rootwrap
+ create mode 100644 quantum/rootwrap/__init__.py
+ create mode 100644 quantum/rootwrap/filters.py
+ create mode 100644 quantum/rootwrap/linuxbridge-agent.py
+ create mode 100644 quantum/rootwrap/openvswitch-agent.py
+ create mode 100644 quantum/rootwrap/ryu-agent.py
+ create mode 100644 quantum/rootwrap/wrapper.py
+
+diff --git a/bin/quantum-rootwrap b/bin/quantum-rootwrap
+new file mode 100755
+index 0000000..dcdccb9
+--- /dev/null
++++ b/bin/quantum-rootwrap
+@@ -0,0 +1,73 @@
++#!/usr/bin/env python
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright (c) 2012 Openstack, LLC.
++# All Rights Reserved.
++#
++#    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.
++
++"""Root wrapper for Quantum
++
++   Uses modules in quantum.rootwrap containing filters for commands
++   that quantum agents are allowed to run as another user.
++
++   To switch to using this, you should:
++   * Set "--root_helper=sudo quantum-rootwrap" in the agents config file.
++   * Allow quantum to run quantum-rootwrap as root in quantum_sudoers:
++     quantum ALL = (root) NOPASSWD: /usr/bin/quantum-rootwrap
++     (all other commands can be removed from this file)
++
++   To make allowed commands node-specific, your packaging should only
++   install quantum/rootwrap/quantum-*-agent.py on compute nodes where
++   agents that need root privileges are run.
++"""
++
++import os
++import subprocess
++import sys
++
++
++RC_UNAUTHORIZED = 99
++RC_NOCOMMAND = 98
++
++if __name__ == '__main__':
++    # Split arguments, require at least a command
++    execname = sys.argv.pop(0)
++    if len(sys.argv) == 0:
++        print "%s: %s" % (execname, "No command specified")
++        sys.exit(RC_NOCOMMAND)
++
++    userargs = sys.argv[:]
++
++    # Add ../ to sys.path to allow running from branch
++    possible_topdir = os.path.normpath(os.path.join(os.path.abspath(execname),
++                                                    os.pardir, os.pardir))
++    if os.path.exists(os.path.join(possible_topdir, "quantum", "__init__.py")):
++        sys.path.insert(0, possible_topdir)
++
++    from quantum.rootwrap import wrapper
++
++    # Execute command if it matches any of the loaded filters
++    filters = wrapper.load_filters()
++    filtermatch = wrapper.match_filter(filters, userargs)
++    if filtermatch:
++        obj = subprocess.Popen(filtermatch.get_command(userargs),
++                               stdin=sys.stdin,
++                               stdout=sys.stdout,
++                               stderr=sys.stderr,
++                               env=filtermatch.get_environment(userargs))
++        obj.wait()
++        sys.exit(obj.returncode)
++
++    print "Unauthorized command: %s" % ' '.join(userargs)
++    sys.exit(RC_UNAUTHORIZED)
+diff --git a/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini b/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
+index 8bead52..dd25e07 100644
+--- a/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
++++ b/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
+@@ -22,3 +22,6 @@ physical_interface = eth1
+ [AGENT]
+ #agent's polling interval in seconds
+ polling_interval = 2
++# Change to "sudo quantum-rootwrap" to limit commands that can be run
++# as root.
++root_helper = sudo
+diff --git a/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini b/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini
+index d8d5f21..a7a7f1a 100644
+--- a/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini
++++ b/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini
+@@ -31,6 +31,11 @@ integration-bridge = br-int
+ # Set local-ip to be the local IP address of this hypervisor.
+ # local-ip = 10.0.0.3
+ 
++[AGENT]
++# Change to "sudo quantum-rootwrap" to limit commands that can be run
++# as root.
++root_helper = sudo
++
+ #-----------------------------------------------------------------------------
+ # Sample Configurations.
+ #-----------------------------------------------------------------------------
+@@ -41,6 +46,8 @@ integration-bridge = br-int
+ # [OVS]
+ # enable-tunneling = False
+ # integration-bridge = br-int
++# [AGENT]
++# root_helper = sudo
+ #
+ # 2. With tunneling.
+ # [DATABASE]
+@@ -51,3 +58,5 @@ integration-bridge = br-int
+ # tunnel-bridge = br-tun
+ # remote-ip-file = /opt/stack/remote-ips.txt
+ # local-ip = 10.0.0.3
++# [AGENT]
++# root_helper = sudo
+diff --git a/etc/quantum/plugins/ryu/ryu.ini b/etc/quantum/plugins/ryu/ryu.ini
+index 6d732c9..2d5a2c5 100644
+--- a/etc/quantum/plugins/ryu/ryu.ini
++++ b/etc/quantum/plugins/ryu/ryu.ini
+@@ -11,3 +11,8 @@ integration-bridge = br-int
+ # openflow-rest-api = <host IP address of ofp rest api service>:<port: 8080>
+ openflow-controller = 127.0.0.1:6633
+ openflow-rest-api = 127.0.0.1:8080
++
++[AGENT]
++# Change to "sudo quantum-rootwrap" to limit commands that can be run
++# as root.
++root_helper = sudo
+diff --git a/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py b/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py
+index a9fa0b9..45cd2b6 100755
+--- a/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py
++++ b/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py
+@@ -30,6 +30,7 @@ import ConfigParser
+ import logging as LOG
+ import MySQLdb
+ import os
++import shlex
+ import signal
+ import sqlite3
+ import sys
+@@ -53,16 +54,18 @@ DB_CONNECTION = None
+ 
+ 
+ class LinuxBridge:
+-    def __init__(self, br_name_prefix, physical_interface):
++    def __init__(self, br_name_prefix, physical_interface, root_helper):
+         self.br_name_prefix = br_name_prefix
+         self.physical_interface = physical_interface
++        self.root_helper = root_helper
+ 
+     def run_cmd(self, args):
+-        LOG.debug("Running command: " + " ".join(args))
+-        p = Popen(args, stdout=PIPE)
++        cmd = shlex.split(self.root_helper) + args
++        LOG.debug("Running command: " + " ".join(cmd))
++        p = Popen(cmd, stdout=PIPE)
+         retval = p.communicate()[0]
+         if p.returncode == -(signal.SIGALRM):
+-            LOG.debug("Timeout running command: " + " ".join(args))
++            LOG.debug("Timeout running command: " + " ".join(cmd))
+         if retval:
+             LOG.debug("Command returned: %s" % retval)
+         return retval
+@@ -287,12 +290,15 @@ class LinuxBridge:
+ 
+ class LinuxBridgeQuantumAgent:
+ 
+-    def __init__(self, br_name_prefix, physical_interface, polling_interval):
++    def __init__(self, br_name_prefix, physical_interface, polling_interval,
++                 root_helper):
+         self.polling_interval = int(polling_interval)
++        self.root_helper = root_helper
+         self.setup_linux_bridge(br_name_prefix, physical_interface)
+ 
+     def setup_linux_bridge(self, br_name_prefix, physical_interface):
+-        self.linux_br = LinuxBridge(br_name_prefix, physical_interface)
++        self.linux_br = LinuxBridge(br_name_prefix, physical_interface,
++                                    self.root_helper)
+ 
+     def process_port_binding(self, port_id, network_id, interface_id,
+                              vlan_id):
+@@ -439,6 +445,7 @@ def main():
+         br_name_prefix = BRIDGE_NAME_PREFIX
+         physical_interface = config.get("LINUX_BRIDGE", "physical_interface")
+         polling_interval = config.get("AGENT", "polling_interval")
++        root_helper = config.get("AGENT", "root_helper")
+         'Establish database connection and load models'
+         global DB_CONNECTION
+         DB_CONNECTION = config.get("DATABASE", "connection")
+@@ -462,7 +469,7 @@ def main():
+ 
+     try:
+         plugin = LinuxBridgeQuantumAgent(br_name_prefix, physical_interface,
+-                                         polling_interval)
++                                         polling_interval, root_helper)
+         LOG.info("Agent initialized successfully, now running...")
+         plugin.daemon_loop(conn)
+     finally:
+diff --git a/quantum/plugins/linuxbridge/tests/unit/_test_linuxbridgeAgent.py b/quantum/plugins/linuxbridge/tests/unit/_test_linuxbridgeAgent.py
+index 9452189..09d30bf 100644
+--- a/quantum/plugins/linuxbridge/tests/unit/_test_linuxbridgeAgent.py
++++ b/quantum/plugins/linuxbridge/tests/unit/_test_linuxbridgeAgent.py
+@@ -22,6 +22,7 @@ import logging as LOG
+ import unittest
+ import sys
+ import os
++import shlex
+ import signal
+ from subprocess import *
+ 
+@@ -392,20 +393,24 @@ class LinuxBridgeAgentTest(unittest.TestCase):
+             self.physical_interface = config.get("LINUX_BRIDGE",
+                                                  "physical_interface")
+             self.polling_interval = config.get("AGENT", "polling_interval")
++            self.root_helper = config.get("AGENT", "root_helper")
+         except Exception, e:
+             LOG.error("Unable to parse config file \"%s\": \nException%s"
+                       % (self.config_file, str(e)))
+             sys.exit(1)
+         self._linuxbridge = linux_agent.LinuxBridge(self.br_name_prefix,
+-                                                    self.physical_interface)
++                                                    self.physical_interface,
++                                                    self.root_helper)
+         self._linuxbridge_quantum_agent = linux_agent.LinuxBridgeQuantumAgent(
+                                                     self.br_name_prefix,
+                                                     self.physical_interface,
+-                                                    self.polling_interval)
++                                                    self.polling_interval,
++                                                    self.root_helper)
+ 
+     def run_cmd(self, args):
+-        LOG.debug("Running command: " + " ".join(args))
+-        p = Popen(args, stdout=PIPE)
++        cmd = shlex.split(self.root_helper) + args
++        LOG.debug("Running command: " + " ".join(cmd))
++        p = Popen(cmd, stdout=PIPE)
+         retval = p.communicate()[0]
+         if p.returncode == -(signal.SIGALRM):
+             LOG.debug("Timeout running command: " + " ".join(args))
+diff --git a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py
+index ef555a5..9774c2e 100755
+--- a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py
++++ b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py
+@@ -21,6 +21,7 @@
+ 
+ import ConfigParser
+ import logging as LOG
++import shlex
+ import sys
+ import time
+ import signal
+@@ -57,15 +58,17 @@ class VifPort:
+ 
+ 
+ class OVSBridge:
+-    def __init__(self, br_name):
++    def __init__(self, br_name, root_helper):
+         self.br_name = br_name
++        self.root_helper = root_helper
+ 
+     def run_cmd(self, args):
+-        # LOG.debug("## running command: " + " ".join(args))
+-        p = Popen(args, stdout=PIPE)
++        cmd = shlex.split(self.root_helper) + args
++        LOG.debug("## running command: " + " ".join(cmd))
++        p = Popen(cmd, stdout=PIPE)
+         retval = p.communicate()[0]
+         if p.returncode == -(signal.SIGALRM):
+-            LOG.debug("## timeout running command: " + " ".join(args))
++            LOG.debug("## timeout running command: " + " ".join(cmd))
+         return retval
+ 
+     def run_vsctl(self, args):
+@@ -207,7 +210,8 @@ class LocalVLANMapping:
+ 
+ class OVSQuantumAgent(object):
+ 
+-    def __init__(self, integ_br):
++    def __init__(self, integ_br, root_helper):
++        self.root_helper = root_helper
+         self.setup_integration_br(integ_br)
+ 
+     def port_bound(self, port, vlan_id):
+@@ -220,7 +224,7 @@ class OVSQuantumAgent(object):
+             self.int_br.clear_db_attribute("Port", port.port_name, "tag")
+ 
+     def setup_integration_br(self, integ_br):
+-        self.int_br = OVSBridge(integ_br)
++        self.int_br = OVSBridge(integ_br, self.root_helper)
+         self.int_br.remove_all_flows()
+         # switch all traffic using L2 learning
+         self.int_br.add_flow(priority=1, actions="normal")
+@@ -323,13 +327,15 @@ class OVSQuantumTunnelAgent(object):
+     # Upper bound on available vlans.
+     MAX_VLAN_TAG = 4094
+ 
+-    def __init__(self, integ_br, tun_br, remote_ip_file, local_ip):
++    def __init__(self, integ_br, tun_br, remote_ip_file, local_ip,
++                 root_helper):
+         '''Constructor.
+ 
+         :param integ_br: name of the integration bridge.
+         :param tun_br: name of the tunnel bridge.
+         :param remote_ip_file: name of file containing list of hypervisor IPs.
+         :param local_ip: local IP address of this hypervisor.'''
++        self.root_helper = root_helper
+         self.available_local_vlans = set(
+             xrange(OVSQuantumTunnelAgent.MIN_VLAN_TAG,
+                    OVSQuantumTunnelAgent.MAX_VLAN_TAG))
+@@ -423,7 +429,7 @@ class OVSQuantumTunnelAgent(object):
+         Create patch ports and remove all existing flows.
+ 
+         :param integ_br: the name of the integration bridge.'''
+-        self.int_br = OVSBridge(integ_br)
++        self.int_br = OVSBridge(integ_br, self.root_helper)
+         self.int_br.delete_port("patch-tun")
+         self.patch_tun_ofport = self.int_br.add_patch_port("patch-tun",
+                                                            "patch-int")
+@@ -442,7 +448,7 @@ class OVSQuantumTunnelAgent(object):
+         :param remote_ip_file: path to file that contains list of destination
+             IP addresses.
+         :param local_ip: the ip address of this node.'''
+-        self.tun_br = OVSBridge(tun_br)
++        self.tun_br = OVSBridge(tun_br, self.root_helper)
+         self.tun_br.reset_bridge()
+         self.patch_int_ofport = self.tun_br.add_patch_port("patch-int",
+                                                            "patch-tun")
+@@ -630,6 +636,8 @@ def main():
+         if not len(db_connection_url):
+             raise Exception('Empty db_connection_url in configuration file.')
+ 
++        root_helper = config.get("AGENT", "root_helper")
++
+     except Exception, e:
+         LOG.error("Error parsing common params in config_file: '%s': %s"
+                   % (config_file, str(e)))
+@@ -659,10 +667,10 @@ def main():
+             sys.exit(1)
+ 
+         plugin = OVSQuantumTunnelAgent(integ_br, tun_br, remote_ip_file,
+-                                       local_ip)
++                                       local_ip, root_helper)
+     else:
+         # Get parameters for OVSQuantumAgent.
+-        plugin = OVSQuantumAgent(integ_br)
++        plugin = OVSQuantumAgent(integ_br, root_helper)
+ 
+     # Start everything.
+     options = {"sql_connection": db_connection_url}
+diff --git a/quantum/plugins/openvswitch/tests/unit/test_tunnel.py b/quantum/plugins/openvswitch/tests/unit/test_tunnel.py
+index ce6179a..ee408f9 100644
+--- a/quantum/plugins/openvswitch/tests/unit/test_tunnel.py
++++ b/quantum/plugins/openvswitch/tests/unit/test_tunnel.py
+@@ -63,14 +63,16 @@ class TunnelTest(unittest.TestCase):
+         self.TUN_OFPORT = 'PATCH_TUN_OFPORT'
+ 
+         self.mox.StubOutClassWithMocks(ovs_quantum_agent, 'OVSBridge')
+-        self.mock_int_bridge = ovs_quantum_agent.OVSBridge(self.INT_BRIDGE)
++        self.mock_int_bridge = ovs_quantum_agent.OVSBridge(self.INT_BRIDGE,
++                                                           'sudo')
+         self.mock_int_bridge.delete_port('patch-tun')
+         self.mock_int_bridge.add_patch_port(
+             'patch-tun', 'patch-int').AndReturn(self.TUN_OFPORT)
+         self.mock_int_bridge.remove_all_flows()
+         self.mock_int_bridge.add_flow(priority=1, actions='normal')
+ 
+-        self.mock_tun_bridge = ovs_quantum_agent.OVSBridge(self.TUN_BRIDGE)
++        self.mock_tun_bridge = ovs_quantum_agent.OVSBridge(self.TUN_BRIDGE,
++                                                           'sudo')
+         self.mock_tun_bridge.reset_bridge()
+         self.mock_tun_bridge.add_patch_port(
+             'patch-int', 'patch-tun').AndReturn(self.INT_OFPORT)
+@@ -86,7 +88,8 @@ class TunnelTest(unittest.TestCase):
+         b = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
+                                                     self.TUN_BRIDGE,
+                                                     REMOTE_IP_FILE,
+-                                                    '10.0.0.1')
++                                                    '10.0.0.1',
++                                                    'sudo')
+         self.mox.VerifyAll()
+ 
+     def testProvisionLocalVlan(self):
+@@ -105,7 +108,8 @@ class TunnelTest(unittest.TestCase):
+         a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
+                                                     self.TUN_BRIDGE,
+                                                     REMOTE_IP_FILE,
+-                                                    '10.0.0.1')
++                                                    '10.0.0.1',
++                                                    'sudo')
+         a.available_local_vlans = set([LV_ID])
+         a.provision_local_vlan(NET_UUID, LS_ID)
+         self.mox.VerifyAll()
+@@ -121,7 +125,8 @@ class TunnelTest(unittest.TestCase):
+         a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
+                                                     self.TUN_BRIDGE,
+                                                     REMOTE_IP_FILE,
+-                                                    '10.0.0.1')
++                                                    '10.0.0.1',
++                                                    'sudo')
+         a.available_local_vlans = set()
+         a.local_vlan_map[NET_UUID] = LVM
+         a.reclaim_local_vlan(NET_UUID, LVM)
+@@ -137,7 +142,8 @@ class TunnelTest(unittest.TestCase):
+         a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
+                                                     self.TUN_BRIDGE,
+                                                     REMOTE_IP_FILE,
+-                                                    '10.0.0.1')
++                                                    '10.0.0.1',
++                                                    'sudo')
+         a.local_vlan_map[NET_UUID] = LVM
+         a.port_bound(VIF_PORT, NET_UUID, LS_ID)
+         self.mox.VerifyAll()
+@@ -147,7 +153,8 @@ class TunnelTest(unittest.TestCase):
+         a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
+                                                     self.TUN_BRIDGE,
+                                                     REMOTE_IP_FILE,
+-                                                    '10.0.0.1')
++                                                    '10.0.0.1',
++                                                    'sudo')
+         a.available_local_vlans = set([LV_ID])
+         a.local_vlan_map[NET_UUID] = LVM
+         a.port_unbound(VIF_PORT, NET_UUID)
+@@ -165,7 +172,8 @@ class TunnelTest(unittest.TestCase):
+         a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
+                                                     self.TUN_BRIDGE,
+                                                     REMOTE_IP_FILE,
+-                                                    '10.0.0.1')
++                                                    '10.0.0.1',
++                                                    'sudo')
+         a.available_local_vlans = set([LV_ID])
+         a.local_vlan_map[NET_UUID] = LVM
+         a.port_dead(VIF_PORT)
+@@ -187,7 +195,8 @@ class TunnelTest(unittest.TestCase):
+         a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
+                                                     self.TUN_BRIDGE,
+                                                     REMOTE_IP_FILE,
+-                                                    '10.0.0.1')
++                                                    '10.0.0.1',
++                                                    'sudo')
+ 
+         all_bindings = a.get_db_port_bindings(db)
+         lsw_id_bindings = a.get_db_vlan_bindings(db)
+diff --git a/quantum/plugins/ryu/agent/ryu_quantum_agent.py b/quantum/plugins/ryu/agent/ryu_quantum_agent.py
+index 11a2e32..77569fb 100755
+--- a/quantum/plugins/ryu/agent/ryu_quantum_agent.py
++++ b/quantum/plugins/ryu/agent/ryu_quantum_agent.py
+@@ -58,8 +58,9 @@ class VifPort:
+ 
+ 
+ class OVSBridge:
+-    def __init__(self, br_name):
++    def __init__(self, br_name, root_helper):
+         self.br_name = br_name
++        self.root_helper = root_helper
+         self.datapath_id = None
+ 
+     def find_datapath_id(self):
+@@ -71,10 +72,11 @@ class OVSBridge:
+         self.datapath_id = dp_id
+ 
+     def run_cmd(self, args):
+-        pipe = Popen(args, stdout=PIPE)
++        cmd = shlex.split(self.root_helper) + args
++        pipe = Popen(cmd, stdout=PIPE)
+         retval = pipe.communicate()[0]
+         if pipe.returncode == -(signal.SIGALRM):
+-            LOG.debug("## timeout running command: " + " ".join(args))
++            LOG.debug("## timeout running command: " + " ".join(cmd))
+         return retval
+ 
+     def run_vsctl(self, args):
+@@ -190,7 +192,8 @@ def check_ofp_mode(db):
+ 
+ 
+ class OVSQuantumOFPRyuAgent:
+-    def __init__(self, integ_br, db):
++    def __init__(self, integ_br, db, root_helper):
++        self.root_helper = root_helper
+         (ofp_controller_addr, ofp_rest_api_addr) = check_ofp_mode(db)
+ 
+         self.nw_id_external = rest_nw_id.NW_ID_EXTERNAL
+@@ -198,7 +201,7 @@ class OVSQuantumOFPRyuAgent:
+         self._setup_integration_br(integ_br, ofp_controller_addr)
+ 
+     def _setup_integration_br(self, integ_br, ofp_controller_addr):
+-        self.int_br = OVSBridge(integ_br)
++        self.int_br = OVSBridge(integ_br, self.root_helper)
+         self.int_br.find_datapath_id()
+         self.int_br.set_controller(ofp_controller_addr)
+         for port in self.int_br.get_external_ports():
+@@ -297,12 +300,14 @@ def main():
+ 
+     integ_br = config.get("OVS", "integration-bridge")
+ 
++    root_helper = config.get("AGENT", "root_helper")
++
+     options = {"sql_connection": config.get("DATABASE", "sql_connection")}
+     db = SqlSoup(options["sql_connection"])
+ 
+     LOG.info("Connecting to database \"%s\" on %s",
+              db.engine.url.database, db.engine.url.host)
+-    plugin = OVSQuantumOFPRyuAgent(integ_br, db)
++    plugin = OVSQuantumOFPRyuAgent(integ_br, db, root_helper)
+     plugin.daemon_loop(db)
+ 
+     sys.exit(0)
+diff --git a/quantum/rootwrap/__init__.py b/quantum/rootwrap/__init__.py
+new file mode 100644
+index 0000000..9bd7a21
+--- /dev/null
++++ b/quantum/rootwrap/__init__.py
+@@ -0,0 +1,16 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright (c) 2012 Openstack, LLC.
++# All Rights Reserved.
++#
++#    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.
+diff --git a/quantum/rootwrap/filters.py b/quantum/rootwrap/filters.py
+new file mode 100644
+index 0000000..48076f4
+--- /dev/null
++++ b/quantum/rootwrap/filters.py
+@@ -0,0 +1,143 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright (c) 2012 Openstack, LLC.
++# All Rights Reserved.
++#
++#    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 os
++import re
++
++
++class CommandFilter(object):
++    """Command filter only checking that the 1st argument matches exec_path"""
++
++    def __init__(self, exec_path, run_as, *args):
++        self.exec_path = exec_path
++        self.run_as = run_as
++        self.args = args
++
++    def match(self, userargs):
++        """Only check that the first argument (command) matches exec_path"""
++        if (os.path.basename(self.exec_path) == userargs[0]):
++            return True
++        return False
++
++    def get_command(self, userargs):
++        """Returns command to execute (with sudo -u if run_as != root)."""
++        if (self.run_as != 'root'):
++            # Used to run commands at lesser privileges
++            return ['sudo', '-u', self.run_as, self.exec_path] + userargs[1:]
++        return [self.exec_path] + userargs[1:]
++
++    def get_environment(self, userargs):
++        """Returns specific environment to set, None if none"""
++        return None
++
++
++class RegExpFilter(CommandFilter):
++    """Command filter doing regexp matching for every argument"""
++
++    def match(self, userargs):
++        # Early skip if command or number of args don't match
++        if (len(self.args) != len(userargs)):
++            # DENY: argument numbers don't match
++            return False
++        # Compare each arg (anchoring pattern explicitly at end of string)
++        for (pattern, arg) in zip(self.args, userargs):
++            try:
++                if not re.match(pattern + '$', arg):
++                    break
++            except re.error:
++                # DENY: Badly-formed filter
++                return False
++        else:
++            # ALLOW: All arguments matched
++            return True
++
++        # DENY: Some arguments did not match
++        return False
++
++
++class DnsmasqFilter(CommandFilter):
++    """Specific filter for the dnsmasq call (which includes env)"""
++
++    def match(self, userargs):
++        if (userargs[0].startswith("FLAGFILE=") and
++            userargs[1].startswith("NETWORK_ID=") and
++            userargs[2] == "dnsmasq"):
++            return True
++        return False
++
++    def get_command(self, userargs):
++        return [self.exec_path] + userargs[3:]
++
++    def get_environment(self, userargs):
++        env = os.environ.copy()
++        env['FLAGFILE'] = userargs[0].split('=')[-1]
++        env['NETWORK_ID'] = userargs[1].split('=')[-1]
++        return env
++
++
++class KillFilter(CommandFilter):
++    """Specific filter for the kill calls.
++       1st argument is a list of accepted signals (emptystring means no signal)
++       2nd argument is a list of accepted affected executables.
++
++       This filter relies on /proc to accurately determine affected
++       executable, so it will only work on procfs-capable systems (not OSX).
++    """
++
++    def match(self, userargs):
++        if userargs[0] != "kill":
++            return False
++        args = list(userargs)
++        if len(args) == 3:
++            signal = args.pop(1)
++            if signal not in self.args[0]:
++                # Requested signal not in accepted list
++                return False
++        else:
++            if len(args) != 2:
++                # Incorrect number of arguments
++                return False
++            if '' not in self.args[0]:
++                # No signal, but list doesn't include empty string
++                return False
++        try:
++            command = os.readlink("/proc/%d/exe" % int(args[1]))
++            if command not in self.args[1]:
++                # Affected executable not in accepted list
++                return False
++        except (ValueError, OSError):
++            # Incorrect PID
++            return False
++        return True
++
++
++class ReadFileFilter(CommandFilter):
++    """Specific filter for the utils.read_file_as_root call"""
++
++    def __init__(self, file_path, *args):
++        self.file_path = file_path
++        super(ReadFileFilter, self).__init__("/bin/cat", "root", *args)
++
++    def match(self, userargs):
++        if userargs[0] != 'cat':
++            return False
++        if userargs[1] != self.file_path:
++            return False
++        if len(userargs) != 2:
++            return False
++        return True
+diff --git a/quantum/rootwrap/linuxbridge-agent.py b/quantum/rootwrap/linuxbridge-agent.py
+new file mode 100644
+index 0000000..5421ff1
+--- /dev/null
++++ b/quantum/rootwrap/linuxbridge-agent.py
+@@ -0,0 +1,46 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright (c) 2012 Openstack, LLC.
++# All Rights Reserved.
++#
++#    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 quantum.rootwrap import filters
++
++filterlist = [
++    # quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py:
++    #   'brctl', 'addbr', bridge_name
++    #   'brctl', 'addif', bridge_name, interface
++    #   'brctl', 'addif', bridge_name, tap_device_name
++    #   'brctl', 'delbr', bridge_name
++    #   'brctl', 'delif', bridge_name, interface_name
++    #   'brctl', 'delif', current_bridge_name, ...
++    #   'brctl', 'setfd', bridge_name, ...
++    #   'brctl', 'stp', bridge_name, 'off'
++    filters.CommandFilter("/usr/sbin/brctl", "root"),
++    filters.CommandFilter("/sbin/brctl", "root"),
++
++    # quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py:
++    #   'ip', 'link', 'add', 'link', ...
++    #   'ip', 'link', 'delete', interface
++    #   'ip', 'link', 'set', bridge_name, 'down'
++    #   'ip', 'link', 'set', bridge_name, 'up'
++    #   'ip', 'link', 'set', interface, 'down'
++    #   'ip', 'link', 'set', interface, 'up'
++    #   'ip', 'link', 'show', 'dev', device
++    #   'ip', 'tuntap'
++    #   'ip', 'tuntap'
++    filters.CommandFilter("/usr/sbin/ip", "root"),
++    filters.CommandFilter("/sbin/ip", "root"),
++    ]
+diff --git a/quantum/rootwrap/openvswitch-agent.py b/quantum/rootwrap/openvswitch-agent.py
+new file mode 100644
+index 0000000..0c00968
+--- /dev/null
++++ b/quantum/rootwrap/openvswitch-agent.py
+@@ -0,0 +1,36 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright (c) 2012 Openstack, LLC.
++# All Rights Reserved.
++#
++#    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 quantum.rootwrap import filters
++
++filterlist = [
++    # quantum/plugins/openvswitch/agent/ovs_quantum_agent.py:
++    #   "ovs-vsctl", "--timeout=2", ...
++    filters.CommandFilter("/usr/bin/ovs-vsctl", "root"),
++    filters.CommandFilter("/bin/ovs-vsctl", "root"),
++
++    # quantum/plugins/openvswitch/agent/ovs_quantum_agent.py:
++    #   "ovs-ofctl", cmd, self.br_name, args
++    filters.CommandFilter("/usr/bin/ovs-ofctl", "root"),
++    filters.CommandFilter("/bin/ovs-ofctl", "root"),
++
++    # quantum/plugins/openvswitch/agent/ovs_quantum_agent.py:
++    #   "xe", "vif-param-get", ...
++    filters.CommandFilter("/usr/bin/xe", "root"),
++    filters.CommandFilter("/usr/sbin/xe", "root"),
++    ]
+diff --git a/quantum/rootwrap/ryu-agent.py b/quantum/rootwrap/ryu-agent.py
+new file mode 100644
+index 0000000..d413ccf
+--- /dev/null
++++ b/quantum/rootwrap/ryu-agent.py
+@@ -0,0 +1,31 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright (c) 2012 Openstack, LLC.
++# All Rights Reserved.
++#
++#    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 quantum.rootwrap import filters
++
++filterlist = [
++    # quantum/plugins/ryu/agent/ryu_quantum_agent.py:
++    #   "ovs-vsctl", "--timeout=2", ...
++    filters.CommandFilter("/usr/bin/ovs-vsctl", "root"),
++    filters.CommandFilter("/bin/ovs-vsctl", "root"),
++
++    # quantum/plugins/ryu/agent/ryu_quantum_agent.py:
++    #   "xe", "vif-param-get", ...
++    filters.CommandFilter("/usr/bin/xe", "root"),
++    filters.CommandFilter("/usr/sbin/xe", "root"),
++    ]
+diff --git a/quantum/rootwrap/wrapper.py b/quantum/rootwrap/wrapper.py
+new file mode 100644
+index 0000000..bcb0122
+--- /dev/null
++++ b/quantum/rootwrap/wrapper.py
+@@ -0,0 +1,63 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright (c) 2012 Openstack, LLC.
++# All Rights Reserved.
++#
++#    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 os
++import sys
++
++
++FILTERS_MODULES = ['quantum.rootwrap.linuxbridge-agent',
++                   'quantum.rootwrap.openvswitch-agent',
++                   'quantum.rootwrap.ryu-agent',
++                  ]
++
++
++def load_filters():
++    """Load filters from modules present in quantum.rootwrap."""
++    filters = []
++    for modulename in FILTERS_MODULES:
++        try:
++            __import__(modulename)
++            module = sys.modules[modulename]
++            filters = filters + module.filterlist
++        except ImportError:
++            # It's OK to have missing filters, since filter modules
++            # may be shipped with specific nodes
++            pass
++    return filters
++
++
++def match_filter(filters, userargs):
++    """
++    Checks user command and arguments through command filters and
++    returns the first matching filter, or None is none matched.
++    """
++
++    found_filter = None
++
++    for f in filters:
++        if f.match(userargs):
++            # Try other filters if executable is absent
++            if not os.access(f.exec_path, os.X_OK):
++                if not found_filter:
++                    found_filter = f
++                continue
++            # Otherwise return matching filter for execution
++            return f
++
++    # No filter matched or first missing executable
++    return found_filter
+-- 
+1.7.4.1
+


More information about the scm-commits mailing list