Merge "Add rootwrap daemon mode support"

This commit is contained in:
Jenkins 2015-03-13 23:30:35 +00:00 committed by Gerrit Code Review
commit 84243b2f08
8 changed files with 85 additions and 24 deletions

View File

@ -31,6 +31,12 @@ ROOT_HELPER_OPTS = [
default=True,
help=_('Use the root helper to read the namespaces from '
'the operating system.')),
# We can't just use root_helper=sudo neutron-rootwrap-daemon $cfg because
# it isn't appropriate for long-lived processes spawned with create_process
# Having a bool use_rootwrap_daemon option precludes specifying the
# rootwrap daemon command, which may be necessary for Xen?
cfg.StrOpt('root_helper_daemon',
help=_('Root helper daemon application to use when possible.')),
]
AGENT_STATE_OPTS = [

View File

@ -20,12 +20,14 @@ import shlex
import socket
import struct
import tempfile
import threading
import eventlet
from eventlet.green import subprocess
from eventlet import greenthread
from oslo_config import cfg
from oslo_log import log as logging
from oslo_rootwrap import client
from oslo_utils import excutils
from neutron.agent.common import config
@ -38,56 +40,95 @@ LOG = logging.getLogger(__name__)
config.register_root_helper(cfg.CONF)
class RootwrapDaemonHelper(object):
__client = None
__lock = threading.Lock()
def __new__(cls):
"""There is no reason to instantiate this class"""
raise NotImplementedError()
@classmethod
def get_client(cls):
with cls.__lock:
if cls.__client is None:
cls.__client = client.Client(
shlex.split(cfg.CONF.AGENT.root_helper_daemon))
return cls.__client
def addl_env_args(addl_env):
"""Build arugments for adding additional environment vars with env"""
# NOTE (twilson) If using rootwrap, an EnvFilter should be set up for the
# command instead of a CommandFilter.
if addl_env is None:
return []
return ['env'] + ['%s=%s' % pair for pair in addl_env.items()]
def create_process(cmd, run_as_root=False, addl_env=None):
"""Create a process object for the given command.
The return value will be a tuple of the process object and the
list of command arguments used to create it.
"""
cmd = map(str, addl_env_args(addl_env) + cmd)
if run_as_root:
cmd = shlex.split(config.get_root_helper(cfg.CONF)) + cmd
cmd = map(str, cmd)
LOG.debug("Running command: %s", cmd)
env = os.environ.copy()
if addl_env:
env.update(addl_env)
obj = utils.subprocess_popen(cmd, shell=False,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env)
stderr=subprocess.PIPE)
return obj, cmd
def execute_rootwrap_daemon(cmd, process_input, addl_env):
cmd = map(str, addl_env_args(addl_env) + cmd)
# NOTE(twilson) oslo_rootwrap.daemon will raise on filter match
# errors, whereas oslo_rootwrap.cmd converts them to return codes.
# In practice, no neutron code should be trying to execute something that
# would throw those errors, and if it does it should be fixed as opposed to
# just logging the execution error.
LOG.debug("Running command (rootwrap daemon): %s", cmd)
client = RootwrapDaemonHelper.get_client()
return client.execute(cmd, process_input)
def execute(cmd, process_input=None, addl_env=None,
check_exit_code=True, return_stderr=False, log_fail_as_error=True,
extra_ok_codes=None, run_as_root=False):
try:
obj, cmd = create_process(cmd, run_as_root=run_as_root,
addl_env=addl_env)
_stdout, _stderr = obj.communicate(process_input)
obj.stdin.close()
m = _("\nCommand: %(cmd)s\nExit code: %(code)s\nStdin: %(stdin)s\n"
"Stdout: %(stdout)s\nStderr: %(stderr)s") % \
{'cmd': cmd,
'code': obj.returncode,
'stdin': process_input or '',
'stdout': _stdout,
'stderr': _stderr}
if run_as_root and cfg.CONF.AGENT.root_helper_daemon:
returncode, _stdout, _stderr = (
execute_rootwrap_daemon(cmd, process_input, addl_env))
else:
obj, cmd = create_process(cmd, run_as_root=run_as_root,
addl_env=addl_env)
_stdout, _stderr = obj.communicate(process_input)
returncode = obj.returncode
obj.stdin.close()
m = _("\nCommand: {cmd}\nExit code: {code}\nStdin: {stdin}\n"
"Stdout: {stdout}\nStderr: {stderr}").format(
cmd=cmd,
code=returncode,
stdin=process_input or '',
stdout=_stdout,
stderr=_stderr)
extra_ok_codes = extra_ok_codes or []
if obj.returncode and obj.returncode in extra_ok_codes:
obj.returncode = None
if returncode and returncode in extra_ok_codes:
returncode = None
if obj.returncode and log_fail_as_error:
if returncode and log_fail_as_error:
LOG.error(m)
else:
LOG.debug(m)
if obj.returncode and check_exit_code:
if returncode and check_exit_code:
raise RuntimeError(m)
finally:
# NOTE(termie): this appears to be necessary to let the subprocess

View File

@ -92,7 +92,7 @@ class L3AgentTestFramework(base.BaseOVSLinuxTestCase):
get_temp_file_path('external/pids'))
conf.set_override('host', host)
agent = neutron_l3_agent.L3NATAgentWithStateReport(host, conf)
mock.patch.object(ip_lib, 'send_gratuitous_arp').start()
mock.patch.object(ip_lib, '_arping').start()
return agent

View File

@ -56,3 +56,6 @@ class BaseSudoTestCase(base.BaseTestCase):
config.register_root_helper(cfg.CONF)
self.config(group='AGENT',
root_helper=os.environ.get('OS_ROOTWRAP_CMD', SUDO_CMD))
self.config(group='AGENT',
root_helper_daemon=os.environ.get(
'OS_ROOTWRAP_DAEMON_CMD'))

View File

@ -34,3 +34,10 @@ class TestRootHelper(base.BaseTestCase):
conf = config.setup_conf()
config.register_root_helper(conf)
self.assertEqual(config.get_root_helper(conf), 'sudo')
def test_agent_root_helper_daemon(self):
conf = config.setup_conf()
config.register_root_helper(conf)
rhd = 'my_root_helper_daemon'
conf.set_override('root_helper_daemon', rhd, 'AGENT')
self.assertEqual(rhd, conf.AGENT.root_helper_daemon)

View File

@ -106,6 +106,7 @@ console_scripts =
neutron-restproxy-agent = neutron.plugins.bigswitch.agent.restproxy_agent:main
neutron-server = neutron.cmd.eventlet.server:main
neutron-rootwrap = oslo_rootwrap.cmd:main
neutron-rootwrap-daemon = oslo_rootwrap.cmd:daemon
neutron-usage-audit = neutron.cmd.usage_audit:main
neutron-metering-agent = neutron.cmd.eventlet.services.metering_agent:main
neutron-sriov-nic-agent = neutron.plugins.sriovnicagent.sriov_nic_agent:main

View File

@ -178,6 +178,7 @@ function _install_rootwrap_sudoers {
VENV_NAME=${venv:-dsvm-functional}
VENV_PATH=$NEUTRON_PATH/.tox/$VENV_NAME
ROOTWRAP_SUDOER_CMD="$VENV_PATH/bin/neutron-rootwrap $VENV_PATH/etc/neutron/rootwrap.conf *"
ROOTWRAP_DAEMON_SUDOER_CMD="$VENV_PATH/bin/neutron-rootwrap-daemon $VENV_PATH/etc/neutron/rootwrap.conf"
TEMPFILE=$(mktemp)
cat << EOF > $TEMPFILE
# A bug in oslo.rootwrap [1] prevents commands executed with 'ip netns
@ -194,6 +195,7 @@ function _install_rootwrap_sudoers {
#
Defaults:$STACK_USER secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$VENV_PATH/bin"
$STACK_USER ALL=(root) NOPASSWD: $ROOTWRAP_SUDOER_CMD
$STACK_USER ALL=(root) NOPASSWD: $ROOTWRAP_DAEMON_SUDOER_CMD
EOF
chmod 0440 $TEMPFILE
sudo chown root:root $TEMPFILE

View File

@ -40,6 +40,7 @@ deps =
setenv = OS_TEST_PATH=./neutron/tests/functional
OS_SUDO_TESTING=1
OS_ROOTWRAP_CMD=sudo {envbindir}/neutron-rootwrap {envdir}/etc/neutron/rootwrap.conf
OS_ROOTWRAP_DAEMON_CMD=sudo {envbindir}/neutron-rootwrap-daemon {envdir}/etc/neutron/rootwrap.conf
OS_FAIL_ON_MISSING_DEPS=1
OS_TEST_TIMEOUT=90
sitepackages=True