--- /dev/null
+#!/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)
[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
# 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.
#-----------------------------------------------------------------------------
# [OVS]
# enable-tunneling = False
# integration-bridge = br-int
+# [AGENT]
+# root_helper = sudo
#
# 2. With tunneling.
# [DATABASE]
# tunnel-bridge = br-tun
# remote-ip-file = /opt/stack/remote-ips.txt
# local-ip = 10.0.0.3
+# [AGENT]
+# root_helper = sudo
# 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
to the compute node.
$ Run the following:
- sudo python linuxbridge_quantum_agent.py linuxbridge_conf.ini
+ python linuxbridge_quantum_agent.py linuxbridge_conf.ini
(Use --verbose option to see the logs)
+ Note that the the user running the agent must have sudo priviliges
+ to run various networking commands. Also, the agent can be
+ configured to use quantum-rootwrap, limiting what commands it can
+ run via sudo. See http://wiki.openstack.org/Packager/Rootwrap for
+ details on rootwrap.
+
+ As an alternative to coping the agent python file, if quantum is
+ installed on the compute node, the agent can be run as
+ bin/quantum-linuxbridge-agent.
+
+
# -- Running Tests
(Note: The plugin ships with a default SQLite in-memory database configuration,
import logging as LOG
import MySQLdb
import os
+import shlex
import signal
import sqlite3
import sys
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
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):
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")
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:
import unittest
import sys
import os
+import shlex
import signal
from subprocess import *
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))
import ConfigParser
import logging as LOG
+import shlex
import sys
import time
import signal
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):
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):
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")
# 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))
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")
: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")
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)))
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}
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)
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):
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()
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)
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()
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)
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)
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)
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):
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):
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
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():
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)
--- /dev/null
+# 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.
--- /dev/null
+# 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
--- /dev/null
+# 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"),
+ ]
--- /dev/null
+# 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"),
+ ]
--- /dev/null
+# 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"),
+ ]
--- /dev/null
+# 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