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@redhat.com>
This commit is contained in:
parent
f88a1f7582
commit
a06b316cb4
73
bin/quantum-rootwrap
Executable file
73
bin/quantum-rootwrap
Executable file
@ -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)
|
@ -22,3 +22,6 @@ physical_interface = eth1
|
|||||||
[AGENT]
|
[AGENT]
|
||||||
#agent's polling interval in seconds
|
#agent's polling interval in seconds
|
||||||
polling_interval = 2
|
polling_interval = 2
|
||||||
|
# Change to "sudo quantum-rootwrap" to limit commands that can be run
|
||||||
|
# as root.
|
||||||
|
root_helper = sudo
|
||||||
|
@ -31,6 +31,11 @@ integration-bridge = br-int
|
|||||||
# Set local-ip to be the local IP address of this hypervisor.
|
# Set local-ip to be the local IP address of this hypervisor.
|
||||||
# local-ip = 10.0.0.3
|
# 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.
|
# Sample Configurations.
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
@ -41,6 +46,8 @@ integration-bridge = br-int
|
|||||||
# [OVS]
|
# [OVS]
|
||||||
# enable-tunneling = False
|
# enable-tunneling = False
|
||||||
# integration-bridge = br-int
|
# integration-bridge = br-int
|
||||||
|
# [AGENT]
|
||||||
|
# root_helper = sudo
|
||||||
#
|
#
|
||||||
# 2. With tunneling.
|
# 2. With tunneling.
|
||||||
# [DATABASE]
|
# [DATABASE]
|
||||||
@ -51,3 +58,5 @@ integration-bridge = br-int
|
|||||||
# tunnel-bridge = br-tun
|
# tunnel-bridge = br-tun
|
||||||
# remote-ip-file = /opt/stack/remote-ips.txt
|
# remote-ip-file = /opt/stack/remote-ips.txt
|
||||||
# local-ip = 10.0.0.3
|
# local-ip = 10.0.0.3
|
||||||
|
# [AGENT]
|
||||||
|
# root_helper = sudo
|
||||||
|
@ -11,3 +11,8 @@ integration-bridge = br-int
|
|||||||
# openflow-rest-api = <host IP address of ofp rest api service>:<port: 8080>
|
# openflow-rest-api = <host IP address of ofp rest api service>:<port: 8080>
|
||||||
openflow-controller = 127.0.0.1:6633
|
openflow-controller = 127.0.0.1:6633
|
||||||
openflow-rest-api = 127.0.0.1:8080
|
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
|
||||||
|
@ -116,9 +116,20 @@ mysql> FLUSH PRIVILEGES;
|
|||||||
to the compute node.
|
to the compute node.
|
||||||
|
|
||||||
$ Run the following:
|
$ 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)
|
(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
|
# -- Running Tests
|
||||||
|
|
||||||
(Note: The plugin ships with a default SQLite in-memory database configuration,
|
(Note: The plugin ships with a default SQLite in-memory database configuration,
|
||||||
|
@ -30,6 +30,7 @@ import ConfigParser
|
|||||||
import logging as LOG
|
import logging as LOG
|
||||||
import MySQLdb
|
import MySQLdb
|
||||||
import os
|
import os
|
||||||
|
import shlex
|
||||||
import signal
|
import signal
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
@ -53,16 +54,18 @@ DB_CONNECTION = None
|
|||||||
|
|
||||||
|
|
||||||
class LinuxBridge:
|
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.br_name_prefix = br_name_prefix
|
||||||
self.physical_interface = physical_interface
|
self.physical_interface = physical_interface
|
||||||
|
self.root_helper = root_helper
|
||||||
|
|
||||||
def run_cmd(self, args):
|
def run_cmd(self, args):
|
||||||
LOG.debug("Running command: " + " ".join(args))
|
cmd = shlex.split(self.root_helper) + args
|
||||||
p = Popen(args, stdout=PIPE)
|
LOG.debug("Running command: " + " ".join(cmd))
|
||||||
|
p = Popen(cmd, stdout=PIPE)
|
||||||
retval = p.communicate()[0]
|
retval = p.communicate()[0]
|
||||||
if p.returncode == -(signal.SIGALRM):
|
if p.returncode == -(signal.SIGALRM):
|
||||||
LOG.debug("Timeout running command: " + " ".join(args))
|
LOG.debug("Timeout running command: " + " ".join(cmd))
|
||||||
if retval:
|
if retval:
|
||||||
LOG.debug("Command returned: %s" % retval)
|
LOG.debug("Command returned: %s" % retval)
|
||||||
return retval
|
return retval
|
||||||
@ -287,12 +290,15 @@ class LinuxBridge:
|
|||||||
|
|
||||||
class LinuxBridgeQuantumAgent:
|
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.polling_interval = int(polling_interval)
|
||||||
|
self.root_helper = root_helper
|
||||||
self.setup_linux_bridge(br_name_prefix, physical_interface)
|
self.setup_linux_bridge(br_name_prefix, physical_interface)
|
||||||
|
|
||||||
def setup_linux_bridge(self, 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,
|
def process_port_binding(self, port_id, network_id, interface_id,
|
||||||
vlan_id):
|
vlan_id):
|
||||||
@ -439,6 +445,7 @@ def main():
|
|||||||
br_name_prefix = BRIDGE_NAME_PREFIX
|
br_name_prefix = BRIDGE_NAME_PREFIX
|
||||||
physical_interface = config.get("LINUX_BRIDGE", "physical_interface")
|
physical_interface = config.get("LINUX_BRIDGE", "physical_interface")
|
||||||
polling_interval = config.get("AGENT", "polling_interval")
|
polling_interval = config.get("AGENT", "polling_interval")
|
||||||
|
root_helper = config.get("AGENT", "root_helper")
|
||||||
'Establish database connection and load models'
|
'Establish database connection and load models'
|
||||||
global DB_CONNECTION
|
global DB_CONNECTION
|
||||||
DB_CONNECTION = config.get("DATABASE", "connection")
|
DB_CONNECTION = config.get("DATABASE", "connection")
|
||||||
@ -462,7 +469,7 @@ def main():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
plugin = LinuxBridgeQuantumAgent(br_name_prefix, physical_interface,
|
plugin = LinuxBridgeQuantumAgent(br_name_prefix, physical_interface,
|
||||||
polling_interval)
|
polling_interval, root_helper)
|
||||||
LOG.info("Agent initialized successfully, now running...")
|
LOG.info("Agent initialized successfully, now running...")
|
||||||
plugin.daemon_loop(conn)
|
plugin.daemon_loop(conn)
|
||||||
finally:
|
finally:
|
||||||
|
@ -22,6 +22,7 @@ import logging as LOG
|
|||||||
import unittest
|
import unittest
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import shlex
|
||||||
import signal
|
import signal
|
||||||
from subprocess import *
|
from subprocess import *
|
||||||
|
|
||||||
@ -392,20 +393,24 @@ class LinuxBridgeAgentTest(unittest.TestCase):
|
|||||||
self.physical_interface = config.get("LINUX_BRIDGE",
|
self.physical_interface = config.get("LINUX_BRIDGE",
|
||||||
"physical_interface")
|
"physical_interface")
|
||||||
self.polling_interval = config.get("AGENT", "polling_interval")
|
self.polling_interval = config.get("AGENT", "polling_interval")
|
||||||
|
self.root_helper = config.get("AGENT", "root_helper")
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
LOG.error("Unable to parse config file \"%s\": \nException%s"
|
LOG.error("Unable to parse config file \"%s\": \nException%s"
|
||||||
% (self.config_file, str(e)))
|
% (self.config_file, str(e)))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
self._linuxbridge = linux_agent.LinuxBridge(self.br_name_prefix,
|
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._linuxbridge_quantum_agent = linux_agent.LinuxBridgeQuantumAgent(
|
||||||
self.br_name_prefix,
|
self.br_name_prefix,
|
||||||
self.physical_interface,
|
self.physical_interface,
|
||||||
self.polling_interval)
|
self.polling_interval,
|
||||||
|
self.root_helper)
|
||||||
|
|
||||||
def run_cmd(self, args):
|
def run_cmd(self, args):
|
||||||
LOG.debug("Running command: " + " ".join(args))
|
cmd = shlex.split(self.root_helper) + args
|
||||||
p = Popen(args, stdout=PIPE)
|
LOG.debug("Running command: " + " ".join(cmd))
|
||||||
|
p = Popen(cmd, stdout=PIPE)
|
||||||
retval = p.communicate()[0]
|
retval = p.communicate()[0]
|
||||||
if p.returncode == -(signal.SIGALRM):
|
if p.returncode == -(signal.SIGALRM):
|
||||||
LOG.debug("Timeout running command: " + " ".join(args))
|
LOG.debug("Timeout running command: " + " ".join(args))
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
import ConfigParser
|
import ConfigParser
|
||||||
import logging as LOG
|
import logging as LOG
|
||||||
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import signal
|
import signal
|
||||||
@ -57,15 +58,17 @@ class VifPort:
|
|||||||
|
|
||||||
|
|
||||||
class OVSBridge:
|
class OVSBridge:
|
||||||
def __init__(self, br_name):
|
def __init__(self, br_name, root_helper):
|
||||||
self.br_name = br_name
|
self.br_name = br_name
|
||||||
|
self.root_helper = root_helper
|
||||||
|
|
||||||
def run_cmd(self, args):
|
def run_cmd(self, args):
|
||||||
# LOG.debug("## running command: " + " ".join(args))
|
cmd = shlex.split(self.root_helper) + args
|
||||||
p = Popen(args, stdout=PIPE)
|
LOG.debug("## running command: " + " ".join(cmd))
|
||||||
|
p = Popen(cmd, stdout=PIPE)
|
||||||
retval = p.communicate()[0]
|
retval = p.communicate()[0]
|
||||||
if p.returncode == -(signal.SIGALRM):
|
if p.returncode == -(signal.SIGALRM):
|
||||||
LOG.debug("## timeout running command: " + " ".join(args))
|
LOG.debug("## timeout running command: " + " ".join(cmd))
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def run_vsctl(self, args):
|
def run_vsctl(self, args):
|
||||||
@ -207,7 +210,8 @@ class LocalVLANMapping:
|
|||||||
|
|
||||||
class OVSQuantumAgent(object):
|
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)
|
self.setup_integration_br(integ_br)
|
||||||
|
|
||||||
def port_bound(self, port, vlan_id):
|
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")
|
self.int_br.clear_db_attribute("Port", port.port_name, "tag")
|
||||||
|
|
||||||
def setup_integration_br(self, integ_br):
|
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()
|
self.int_br.remove_all_flows()
|
||||||
# switch all traffic using L2 learning
|
# switch all traffic using L2 learning
|
||||||
self.int_br.add_flow(priority=1, actions="normal")
|
self.int_br.add_flow(priority=1, actions="normal")
|
||||||
@ -323,13 +327,15 @@ class OVSQuantumTunnelAgent(object):
|
|||||||
# Upper bound on available vlans.
|
# Upper bound on available vlans.
|
||||||
MAX_VLAN_TAG = 4094
|
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.
|
'''Constructor.
|
||||||
|
|
||||||
:param integ_br: name of the integration bridge.
|
:param integ_br: name of the integration bridge.
|
||||||
:param tun_br: name of the tunnel bridge.
|
:param tun_br: name of the tunnel bridge.
|
||||||
:param remote_ip_file: name of file containing list of hypervisor IPs.
|
:param remote_ip_file: name of file containing list of hypervisor IPs.
|
||||||
:param local_ip: local IP address of this hypervisor.'''
|
:param local_ip: local IP address of this hypervisor.'''
|
||||||
|
self.root_helper = root_helper
|
||||||
self.available_local_vlans = set(
|
self.available_local_vlans = set(
|
||||||
xrange(OVSQuantumTunnelAgent.MIN_VLAN_TAG,
|
xrange(OVSQuantumTunnelAgent.MIN_VLAN_TAG,
|
||||||
OVSQuantumTunnelAgent.MAX_VLAN_TAG))
|
OVSQuantumTunnelAgent.MAX_VLAN_TAG))
|
||||||
@ -423,7 +429,7 @@ class OVSQuantumTunnelAgent(object):
|
|||||||
Create patch ports and remove all existing flows.
|
Create patch ports and remove all existing flows.
|
||||||
|
|
||||||
:param integ_br: the name of the integration bridge.'''
|
: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.int_br.delete_port("patch-tun")
|
||||||
self.patch_tun_ofport = self.int_br.add_patch_port("patch-tun",
|
self.patch_tun_ofport = self.int_br.add_patch_port("patch-tun",
|
||||||
"patch-int")
|
"patch-int")
|
||||||
@ -442,7 +448,7 @@ class OVSQuantumTunnelAgent(object):
|
|||||||
:param remote_ip_file: path to file that contains list of destination
|
:param remote_ip_file: path to file that contains list of destination
|
||||||
IP addresses.
|
IP addresses.
|
||||||
:param local_ip: the ip address of this node.'''
|
: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.tun_br.reset_bridge()
|
||||||
self.patch_int_ofport = self.tun_br.add_patch_port("patch-int",
|
self.patch_int_ofport = self.tun_br.add_patch_port("patch-int",
|
||||||
"patch-tun")
|
"patch-tun")
|
||||||
@ -630,6 +636,8 @@ def main():
|
|||||||
if not len(db_connection_url):
|
if not len(db_connection_url):
|
||||||
raise Exception('Empty db_connection_url in configuration file.')
|
raise Exception('Empty db_connection_url in configuration file.')
|
||||||
|
|
||||||
|
root_helper = config.get("AGENT", "root_helper")
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
LOG.error("Error parsing common params in config_file: '%s': %s"
|
LOG.error("Error parsing common params in config_file: '%s': %s"
|
||||||
% (config_file, str(e)))
|
% (config_file, str(e)))
|
||||||
@ -659,10 +667,10 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
plugin = OVSQuantumTunnelAgent(integ_br, tun_br, remote_ip_file,
|
plugin = OVSQuantumTunnelAgent(integ_br, tun_br, remote_ip_file,
|
||||||
local_ip)
|
local_ip, root_helper)
|
||||||
else:
|
else:
|
||||||
# Get parameters for OVSQuantumAgent.
|
# Get parameters for OVSQuantumAgent.
|
||||||
plugin = OVSQuantumAgent(integ_br)
|
plugin = OVSQuantumAgent(integ_br, root_helper)
|
||||||
|
|
||||||
# Start everything.
|
# Start everything.
|
||||||
options = {"sql_connection": db_connection_url}
|
options = {"sql_connection": db_connection_url}
|
||||||
|
@ -63,14 +63,16 @@ class TunnelTest(unittest.TestCase):
|
|||||||
self.TUN_OFPORT = 'PATCH_TUN_OFPORT'
|
self.TUN_OFPORT = 'PATCH_TUN_OFPORT'
|
||||||
|
|
||||||
self.mox.StubOutClassWithMocks(ovs_quantum_agent, 'OVSBridge')
|
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.delete_port('patch-tun')
|
||||||
self.mock_int_bridge.add_patch_port(
|
self.mock_int_bridge.add_patch_port(
|
||||||
'patch-tun', 'patch-int').AndReturn(self.TUN_OFPORT)
|
'patch-tun', 'patch-int').AndReturn(self.TUN_OFPORT)
|
||||||
self.mock_int_bridge.remove_all_flows()
|
self.mock_int_bridge.remove_all_flows()
|
||||||
self.mock_int_bridge.add_flow(priority=1, actions='normal')
|
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.reset_bridge()
|
||||||
self.mock_tun_bridge.add_patch_port(
|
self.mock_tun_bridge.add_patch_port(
|
||||||
'patch-int', 'patch-tun').AndReturn(self.INT_OFPORT)
|
'patch-int', 'patch-tun').AndReturn(self.INT_OFPORT)
|
||||||
@ -86,7 +88,8 @@ class TunnelTest(unittest.TestCase):
|
|||||||
b = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
b = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||||
self.TUN_BRIDGE,
|
self.TUN_BRIDGE,
|
||||||
REMOTE_IP_FILE,
|
REMOTE_IP_FILE,
|
||||||
'10.0.0.1')
|
'10.0.0.1',
|
||||||
|
'sudo')
|
||||||
self.mox.VerifyAll()
|
self.mox.VerifyAll()
|
||||||
|
|
||||||
def testProvisionLocalVlan(self):
|
def testProvisionLocalVlan(self):
|
||||||
@ -105,7 +108,8 @@ class TunnelTest(unittest.TestCase):
|
|||||||
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||||
self.TUN_BRIDGE,
|
self.TUN_BRIDGE,
|
||||||
REMOTE_IP_FILE,
|
REMOTE_IP_FILE,
|
||||||
'10.0.0.1')
|
'10.0.0.1',
|
||||||
|
'sudo')
|
||||||
a.available_local_vlans = set([LV_ID])
|
a.available_local_vlans = set([LV_ID])
|
||||||
a.provision_local_vlan(NET_UUID, LS_ID)
|
a.provision_local_vlan(NET_UUID, LS_ID)
|
||||||
self.mox.VerifyAll()
|
self.mox.VerifyAll()
|
||||||
@ -121,7 +125,8 @@ class TunnelTest(unittest.TestCase):
|
|||||||
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||||
self.TUN_BRIDGE,
|
self.TUN_BRIDGE,
|
||||||
REMOTE_IP_FILE,
|
REMOTE_IP_FILE,
|
||||||
'10.0.0.1')
|
'10.0.0.1',
|
||||||
|
'sudo')
|
||||||
a.available_local_vlans = set()
|
a.available_local_vlans = set()
|
||||||
a.local_vlan_map[NET_UUID] = LVM
|
a.local_vlan_map[NET_UUID] = LVM
|
||||||
a.reclaim_local_vlan(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,
|
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||||
self.TUN_BRIDGE,
|
self.TUN_BRIDGE,
|
||||||
REMOTE_IP_FILE,
|
REMOTE_IP_FILE,
|
||||||
'10.0.0.1')
|
'10.0.0.1',
|
||||||
|
'sudo')
|
||||||
a.local_vlan_map[NET_UUID] = LVM
|
a.local_vlan_map[NET_UUID] = LVM
|
||||||
a.port_bound(VIF_PORT, NET_UUID, LS_ID)
|
a.port_bound(VIF_PORT, NET_UUID, LS_ID)
|
||||||
self.mox.VerifyAll()
|
self.mox.VerifyAll()
|
||||||
@ -147,7 +153,8 @@ class TunnelTest(unittest.TestCase):
|
|||||||
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||||
self.TUN_BRIDGE,
|
self.TUN_BRIDGE,
|
||||||
REMOTE_IP_FILE,
|
REMOTE_IP_FILE,
|
||||||
'10.0.0.1')
|
'10.0.0.1',
|
||||||
|
'sudo')
|
||||||
a.available_local_vlans = set([LV_ID])
|
a.available_local_vlans = set([LV_ID])
|
||||||
a.local_vlan_map[NET_UUID] = LVM
|
a.local_vlan_map[NET_UUID] = LVM
|
||||||
a.port_unbound(VIF_PORT, NET_UUID)
|
a.port_unbound(VIF_PORT, NET_UUID)
|
||||||
@ -165,7 +172,8 @@ class TunnelTest(unittest.TestCase):
|
|||||||
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||||
self.TUN_BRIDGE,
|
self.TUN_BRIDGE,
|
||||||
REMOTE_IP_FILE,
|
REMOTE_IP_FILE,
|
||||||
'10.0.0.1')
|
'10.0.0.1',
|
||||||
|
'sudo')
|
||||||
a.available_local_vlans = set([LV_ID])
|
a.available_local_vlans = set([LV_ID])
|
||||||
a.local_vlan_map[NET_UUID] = LVM
|
a.local_vlan_map[NET_UUID] = LVM
|
||||||
a.port_dead(VIF_PORT)
|
a.port_dead(VIF_PORT)
|
||||||
@ -187,7 +195,8 @@ class TunnelTest(unittest.TestCase):
|
|||||||
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||||
self.TUN_BRIDGE,
|
self.TUN_BRIDGE,
|
||||||
REMOTE_IP_FILE,
|
REMOTE_IP_FILE,
|
||||||
'10.0.0.1')
|
'10.0.0.1',
|
||||||
|
'sudo')
|
||||||
|
|
||||||
all_bindings = a.get_db_port_bindings(db)
|
all_bindings = a.get_db_port_bindings(db)
|
||||||
lsw_id_bindings = a.get_db_vlan_bindings(db)
|
lsw_id_bindings = a.get_db_vlan_bindings(db)
|
||||||
|
@ -58,8 +58,9 @@ class VifPort:
|
|||||||
|
|
||||||
|
|
||||||
class OVSBridge:
|
class OVSBridge:
|
||||||
def __init__(self, br_name):
|
def __init__(self, br_name, root_helper):
|
||||||
self.br_name = br_name
|
self.br_name = br_name
|
||||||
|
self.root_helper = root_helper
|
||||||
self.datapath_id = None
|
self.datapath_id = None
|
||||||
|
|
||||||
def find_datapath_id(self):
|
def find_datapath_id(self):
|
||||||
@ -71,10 +72,11 @@ class OVSBridge:
|
|||||||
self.datapath_id = dp_id
|
self.datapath_id = dp_id
|
||||||
|
|
||||||
def run_cmd(self, args):
|
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]
|
retval = pipe.communicate()[0]
|
||||||
if pipe.returncode == -(signal.SIGALRM):
|
if pipe.returncode == -(signal.SIGALRM):
|
||||||
LOG.debug("## timeout running command: " + " ".join(args))
|
LOG.debug("## timeout running command: " + " ".join(cmd))
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def run_vsctl(self, args):
|
def run_vsctl(self, args):
|
||||||
@ -190,7 +192,8 @@ def check_ofp_mode(db):
|
|||||||
|
|
||||||
|
|
||||||
class OVSQuantumOFPRyuAgent:
|
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)
|
(ofp_controller_addr, ofp_rest_api_addr) = check_ofp_mode(db)
|
||||||
|
|
||||||
self.nw_id_external = rest_nw_id.NW_ID_EXTERNAL
|
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)
|
self._setup_integration_br(integ_br, ofp_controller_addr)
|
||||||
|
|
||||||
def _setup_integration_br(self, 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.find_datapath_id()
|
||||||
self.int_br.set_controller(ofp_controller_addr)
|
self.int_br.set_controller(ofp_controller_addr)
|
||||||
for port in self.int_br.get_external_ports():
|
for port in self.int_br.get_external_ports():
|
||||||
@ -297,12 +300,14 @@ def main():
|
|||||||
|
|
||||||
integ_br = config.get("OVS", "integration-bridge")
|
integ_br = config.get("OVS", "integration-bridge")
|
||||||
|
|
||||||
|
root_helper = config.get("AGENT", "root_helper")
|
||||||
|
|
||||||
options = {"sql_connection": config.get("DATABASE", "sql_connection")}
|
options = {"sql_connection": config.get("DATABASE", "sql_connection")}
|
||||||
db = SqlSoup(options["sql_connection"])
|
db = SqlSoup(options["sql_connection"])
|
||||||
|
|
||||||
LOG.info("Connecting to database \"%s\" on %s",
|
LOG.info("Connecting to database \"%s\" on %s",
|
||||||
db.engine.url.database, db.engine.url.host)
|
db.engine.url.database, db.engine.url.host)
|
||||||
plugin = OVSQuantumOFPRyuAgent(integ_br, db)
|
plugin = OVSQuantumOFPRyuAgent(integ_br, db, root_helper)
|
||||||
plugin.daemon_loop(db)
|
plugin.daemon_loop(db)
|
||||||
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
16
quantum/rootwrap/__init__.py
Normal file
16
quantum/rootwrap/__init__.py
Normal file
@ -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.
|
143
quantum/rootwrap/filters.py
Normal file
143
quantum/rootwrap/filters.py
Normal file
@ -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
|
46
quantum/rootwrap/linuxbridge-agent.py
Normal file
46
quantum/rootwrap/linuxbridge-agent.py
Normal file
@ -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"),
|
||||||
|
]
|
36
quantum/rootwrap/openvswitch-agent.py
Normal file
36
quantum/rootwrap/openvswitch-agent.py
Normal file
@ -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"),
|
||||||
|
]
|
31
quantum/rootwrap/ryu-agent.py
Normal file
31
quantum/rootwrap/ryu-agent.py
Normal file
@ -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"),
|
||||||
|
]
|
63
quantum/rootwrap/wrapper.py
Normal file
63
quantum/rootwrap/wrapper.py
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user