summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--oslo/__init__.py13
-rw-r--r--oslo/rootwrap/__init__.py26
-rw-r--r--oslo/rootwrap/client.py13
-rw-r--r--oslo/rootwrap/cmd.py13
-rw-r--r--oslo/rootwrap/daemon.py13
-rw-r--r--oslo/rootwrap/filters.py13
-rw-r--r--oslo/rootwrap/jsonrpc.py13
-rw-r--r--oslo/rootwrap/wrapper.py13
-rw-r--r--setup.cfg4
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/run_daemon.py57
-rw-r--r--tests/test_functional.py239
-rw-r--r--tests/test_functional_eventlet.py31
-rw-r--r--tests/test_rootwrap.py571
-rw-r--r--tests/test_warning.py61
15 files changed, 0 insertions, 1080 deletions
diff --git a/oslo/__init__.py b/oslo/__init__.py
deleted file mode 100644
index dc130d6..0000000
--- a/oslo/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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__('pkg_resources').declare_namespace(__name__)
diff --git a/oslo/rootwrap/__init__.py b/oslo/rootwrap/__init__.py
deleted file mode 100644
index 73e54f3..0000000
--- a/oslo/rootwrap/__init__.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# 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 warnings
-
-
-def deprecated():
- new_name = __name__.replace('.', '_')
- warnings.warn(
- ('The oslo namespace package is deprecated. Please use %s instead.' %
- new_name),
- DeprecationWarning,
- stacklevel=3,
- )
-
-
-deprecated()
diff --git a/oslo/rootwrap/client.py b/oslo/rootwrap/client.py
deleted file mode 100644
index 44d3338..0000000
--- a/oslo/rootwrap/client.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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 oslo_rootwrap.client import * # noqa
diff --git a/oslo/rootwrap/cmd.py b/oslo/rootwrap/cmd.py
deleted file mode 100644
index fd7ed80..0000000
--- a/oslo/rootwrap/cmd.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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 oslo_rootwrap.cmd import * # noqa
diff --git a/oslo/rootwrap/daemon.py b/oslo/rootwrap/daemon.py
deleted file mode 100644
index 42d3caf..0000000
--- a/oslo/rootwrap/daemon.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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 oslo_rootwrap.daemon import * # noqa
diff --git a/oslo/rootwrap/filters.py b/oslo/rootwrap/filters.py
deleted file mode 100644
index 92b502b..0000000
--- a/oslo/rootwrap/filters.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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 oslo_rootwrap.filters import * # noqa
diff --git a/oslo/rootwrap/jsonrpc.py b/oslo/rootwrap/jsonrpc.py
deleted file mode 100644
index a9b1a9f..0000000
--- a/oslo/rootwrap/jsonrpc.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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 oslo_rootwrap.jsonrpc import * # noqa
diff --git a/oslo/rootwrap/wrapper.py b/oslo/rootwrap/wrapper.py
deleted file mode 100644
index 16b7938..0000000
--- a/oslo/rootwrap/wrapper.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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 oslo_rootwrap.wrapper import * # noqa
diff --git a/setup.cfg b/setup.cfg
index fd5c69f..60ff77d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -21,11 +21,7 @@ classifier =
[files]
packages =
- oslo
- oslo.rootwrap
oslo_rootwrap
-namespace_packages =
- oslo
[build_sphinx]
source-dir = doc/source
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tests/__init__.py
+++ /dev/null
diff --git a/tests/run_daemon.py b/tests/run_daemon.py
deleted file mode 100644
index fa1f3df..0000000
--- a/tests/run_daemon.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright (c) 2014 Mirantis Inc.
-# 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 logging
-import subprocess
-import sys
-import threading
-
-from oslo.rootwrap import cmd
-
-
-def forward_stream(fr, to):
- while True:
- line = fr.readline()
- if not line:
- break
- to.write(line)
-
-
-def forwarding_popen(f, old_popen=subprocess.Popen):
- def popen(*args, **kwargs):
- p = old_popen(*args, **kwargs)
- t = threading.Thread(target=forward_stream, args=(p.stderr, f))
- t.daemon = True
- t.start()
- return p
- return popen
-
-
-class nonclosing(object):
- def __init__(self, f):
- self._f = f
-
- def __getattr__(self, name):
- return getattr(self._f, name)
-
- def close(self):
- pass
-
-log_format = ("%(asctime)s | [%(process)5s]+%(levelname)5s | "
- "%(message)s")
-if __name__ == '__main__':
- logging.basicConfig(level=logging.DEBUG, format=log_format)
- sys.stderr = nonclosing(sys.stderr)
- cmd.daemon()
diff --git a/tests/test_functional.py b/tests/test_functional.py
deleted file mode 100644
index 221b691..0000000
--- a/tests/test_functional.py
+++ /dev/null
@@ -1,239 +0,0 @@
-# Copyright (c) 2014 Mirantis Inc.
-# 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 contextlib
-import io
-import logging
-import os
-import signal
-import subprocess
-import sys
-import threading
-
-try:
- import StringIO
-except ImportError:
- StringIO = io.StringIO
-else:
- StringIO = StringIO.StringIO
-
-try:
- import eventlet
-except ImportError:
- eventlet = None
-
-import fixtures
-import mock
-import testtools
-from testtools import content
-
-from oslo.rootwrap import client
-from oslo.rootwrap import wrapper
-from tests import run_daemon
-
-
-class _FunctionalBase(object):
- def setUp(self):
- super(_FunctionalBase, self).setUp()
- tmpdir = self.useFixture(fixtures.TempDir()).path
- self.config_file = os.path.join(tmpdir, 'rootwrap.conf')
- filters_dir = os.path.join(tmpdir, 'filters.d')
- filters_file = os.path.join(tmpdir, 'filters.d', 'test.filters')
- os.mkdir(filters_dir)
- with open(self.config_file, 'w') as f:
- f.write("""[DEFAULT]
-filters_path=%s
-exec_dirs=/bin""" % (filters_dir,))
- with open(filters_file, 'w') as f:
- f.write("""[Filters]
-echo: CommandFilter, /bin/echo, root
-cat: CommandFilter, /bin/cat, root
-sh: CommandFilter, /bin/sh, root
-""")
-
- def test_run_once(self):
- code, out, err = self.execute(['echo', 'teststr'])
- self.assertEqual(0, code)
- self.assertEqual(b'teststr\n', out)
- self.assertEqual(b'', err)
-
- def test_run_with_stdin(self):
- code, out, err = self.execute(['cat'], stdin=b'teststr')
- self.assertEqual(0, code)
- self.assertEqual(b'teststr', out)
- self.assertEqual(b'', err)
-
-
-class RootwrapTest(_FunctionalBase, testtools.TestCase):
- def setUp(self):
- super(RootwrapTest, self).setUp()
- self.cmd = [
- # We need to explicitly ignore the DeprecationWarning
- # generated by importing oslo.rootwrap instead of
- # oslo_rootwrap under python 2.6 but it is going to be
- # ignored by default for versions after 2.7.
- sys.executable, '-W', 'ignore::DeprecationWarning', '-c',
- 'from oslo.rootwrap import cmd; cmd.main()',
- self.config_file]
-
- def execute(self, cmd, stdin=None):
- proc = subprocess.Popen(
- self.cmd + cmd,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- out, err = proc.communicate(stdin)
- self.addDetail('stdout',
- content.text_content(out.decode('utf-8', 'replace')))
- self.addDetail('stderr',
- content.text_content(err.decode('utf-8', 'replace')))
- return proc.returncode, out, err
-
-
-class RootwrapDaemonTest(_FunctionalBase, testtools.TestCase):
- def assert_unpatched(self):
- # We need to verify that these tests are run without eventlet patching
- if eventlet and eventlet.patcher.is_monkey_patched('socket'):
- self.fail("Standard library should not be patched by eventlet"
- " for this test")
-
- def setUp(self):
- self.assert_unpatched()
-
- super(RootwrapDaemonTest, self).setUp()
-
- # Collect daemon logs
- daemon_log = io.BytesIO()
- p = mock.patch('subprocess.Popen',
- run_daemon.forwarding_popen(daemon_log))
- p.start()
- self.addCleanup(p.stop)
-
- # Collect client logs
- client_log = StringIO()
- handler = logging.StreamHandler(client_log)
- log_format = run_daemon.log_format.replace('+', ' ')
- handler.setFormatter(logging.Formatter(log_format))
- logger = logging.getLogger('oslo.rootwrap')
- logger.addHandler(handler)
- logger.setLevel(logging.DEBUG)
- self.addCleanup(logger.removeHandler, handler)
-
- # Add all logs as details
- @self.addCleanup
- def add_logs():
- self.addDetail('daemon_log', content.Content(
- content.UTF8_TEXT,
- lambda: [daemon_log.getvalue()]))
- self.addDetail('client_log', content.Content(
- content.UTF8_TEXT,
- lambda: [client_log.getvalue().encode('utf-8')]))
-
- # Create client
- self.client = client.Client([
- sys.executable, run_daemon.__file__,
- self.config_file])
-
- # _finalize is set during Client.execute()
- @self.addCleanup
- def finalize_client():
- if self.client._initialized:
- self.client._finalize()
-
- self.execute = self.client.execute
-
- def test_error_propagation(self):
- self.assertRaises(wrapper.NoFilterMatched, self.execute, ['other'])
-
- def test_daemon_ressurection(self):
- # Let the client start a daemon
- self.execute(['cat'])
- # Make daemon go away
- os.kill(self.client._process.pid, signal.SIGTERM)
- # Expect client to succesfully restart daemon and run simple request
- self.test_run_once()
-
- def _exec_thread(self, fifo_path):
- try:
- # Run a shell script that signals calling process through FIFO and
- # then hangs around for 1 sec
- self._thread_res = self.execute([
- 'sh', '-c', 'echo > "%s"; sleep 1; echo OK' % fifo_path])
- except Exception as e:
- self._thread_res = e
-
- def test_graceful_death(self):
- # Create a fifo in a temporary dir
- tmpdir = self.useFixture(fixtures.TempDir()).path
- fifo_path = os.path.join(tmpdir, 'fifo')
- os.mkfifo(fifo_path)
- # Start daemon
- self.execute(['cat'])
- # Begin executing shell script
- t = threading.Thread(target=self._exec_thread, args=(fifo_path,))
- t.start()
- # Wait for shell script to actually start
- with open(fifo_path) as f:
- f.readline()
- # Gracefully kill daemon process
- os.kill(self.client._process.pid, signal.SIGTERM)
- # Expect daemon to wait for our request to finish
- t.join()
- if isinstance(self._thread_res, Exception):
- raise self._thread_res # Python 3 will even provide nice traceback
- code, out, err = self._thread_res
- self.assertEqual(0, code)
- self.assertEqual(b'OK\n', out)
- self.assertEqual(b'', err)
-
- @contextlib.contextmanager
- def _test_daemon_cleanup(self):
- # Start a daemon
- self.execute(['cat'])
- socket_path = self.client._manager._address
- # Stop it one way or another
- yield
- process = self.client._process
- stop = threading.Event()
-
- # Start background thread that would kill process in 1 second if it
- # doesn't die by then
- def sleep_kill():
- stop.wait(1)
- if not stop.is_set():
- os.kill(process.pid, signal.SIGKILL)
- threading.Thread(target=sleep_kill).start()
- # Wait for process to finish one way or another
- self.client._process.wait()
- # Notify background thread that process is dead (no need to kill it)
- stop.set()
- # Fail if the process got killed by the background thread
- self.assertNotEqual(-signal.SIGKILL, process.returncode,
- "Server haven't stopped in one second")
- # Verify that socket is deleted
- self.assertFalse(os.path.exists(socket_path),
- "Server didn't remove its temporary directory")
-
- def test_daemon_cleanup_client(self):
- # Run _test_daemon_cleanup stopping daemon as Client instance would
- # normally do
- with self._test_daemon_cleanup():
- self.client._finalize()
-
- def test_daemon_cleanup_signal(self):
- # Run _test_daemon_cleanup stopping daemon with SIGTERM signal
- with self._test_daemon_cleanup():
- os.kill(self.client._process.pid, signal.SIGTERM)
diff --git a/tests/test_functional_eventlet.py b/tests/test_functional_eventlet.py
deleted file mode 100644
index c94bc69..0000000
--- a/tests/test_functional_eventlet.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (c) 2014 Mirantis Inc.
-# 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
-
-if os.environ.get('TEST_EVENTLET', False):
- import eventlet
- eventlet.monkey_patch()
-
- from tests import test_functional
-
- class RootwrapDaemonTest(test_functional.RootwrapDaemonTest):
- def assert_unpatched(self):
- # This test case is specifically for eventlet testing
- pass
-
- def test_graceful_death(self):
- # This test fails with eventlet on Python 2.6.6 on CentOS
- self.skip("Eventlet doesn't like FIFOs")
diff --git a/tests/test_rootwrap.py b/tests/test_rootwrap.py
deleted file mode 100644
index a573ba5..0000000
--- a/tests/test_rootwrap.py
+++ /dev/null
@@ -1,571 +0,0 @@
-# Copyright 2011 OpenStack Foundation
-#
-# 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 logging
-import logging.handlers
-import os
-import subprocess
-import uuid
-
-import fixtures
-import mock
-from six import moves
-import testtools
-
-from oslo.rootwrap import cmd
-from oslo.rootwrap import filters
-from oslo.rootwrap import wrapper
-
-
-class RootwrapTestCase(testtools.TestCase):
- if os.path.exists('/sbin/ip'):
- _ip = '/sbin/ip'
- else:
- _ip = '/bin/ip'
-
- def setUp(self):
- super(RootwrapTestCase, self).setUp()
- self.filters = [
- filters.RegExpFilter("/bin/ls", "root", 'ls', '/[a-z]+'),
- filters.CommandFilter("/usr/bin/foo_bar_not_exist", "root"),
- filters.RegExpFilter("/bin/cat", "root", 'cat', '/[a-z]+'),
- filters.CommandFilter("/nonexistent/cat", "root"),
- filters.CommandFilter("/bin/cat", "root") # Keep this one last
- ]
-
- def test_CommandFilter(self):
- f = filters.CommandFilter("sleep", 'root', '10')
- self.assertFalse(f.match(["sleep2"]))
-
- # verify that any arguments are accepted
- self.assertTrue(f.match(["sleep"]))
- self.assertTrue(f.match(["sleep", "anything"]))
- self.assertTrue(f.match(["sleep", "10"]))
- f = filters.CommandFilter("sleep", 'root')
- self.assertTrue(f.match(["sleep", "10"]))
-
- def test_empty_commandfilter(self):
- f = filters.CommandFilter("sleep", "root")
- self.assertFalse(f.match([]))
- self.assertFalse(f.match(None))
-
- def test_empty_regexpfilter(self):
- f = filters.RegExpFilter("sleep", "root", "sleep")
- self.assertFalse(f.match([]))
- self.assertFalse(f.match(None))
-
- def test_empty_invalid_regexpfilter(self):
- f = filters.RegExpFilter("sleep", "root")
- self.assertFalse(f.match(["anything"]))
- self.assertFalse(f.match([]))
-
- def test_RegExpFilter_match(self):
- usercmd = ["ls", "/root"]
- filtermatch = wrapper.match_filter(self.filters, usercmd)
- self.assertFalse(filtermatch is None)
- self.assertEqual(filtermatch.get_command(usercmd),
- ["/bin/ls", "/root"])
-
- def test_RegExpFilter_reject(self):
- usercmd = ["ls", "root"]
- self.assertRaises(wrapper.NoFilterMatched,
- wrapper.match_filter, self.filters, usercmd)
-
- def test_missing_command(self):
- valid_but_missing = ["foo_bar_not_exist"]
- invalid = ["foo_bar_not_exist_and_not_matched"]
- self.assertRaises(wrapper.FilterMatchNotExecutable,
- wrapper.match_filter,
- self.filters, valid_but_missing)
- self.assertRaises(wrapper.NoFilterMatched,
- wrapper.match_filter, self.filters, invalid)
-
- def _test_EnvFilter_as_DnsMasq(self, config_file_arg):
- usercmd = ['env', config_file_arg + '=A', 'NETWORK_ID=foobar',
- 'dnsmasq', 'foo']
- f = filters.EnvFilter("env", "root", config_file_arg + '=A',
- 'NETWORK_ID=', "/usr/bin/dnsmasq")
- self.assertTrue(f.match(usercmd))
- self.assertEqual(f.get_command(usercmd), ['/usr/bin/dnsmasq', 'foo'])
- env = f.get_environment(usercmd)
- self.assertEqual(env.get(config_file_arg), 'A')
- self.assertEqual(env.get('NETWORK_ID'), 'foobar')
-
- def test_EnvFilter(self):
- envset = ['A=/some/thing', 'B=somethingelse']
- envcmd = ['env'] + envset
- realcmd = ['sleep', '10']
- usercmd = envcmd + realcmd
-
- f = filters.EnvFilter("env", "root", "A=", "B=ignored", "sleep")
- # accept with leading env
- self.assertTrue(f.match(envcmd + ["sleep"]))
- # accept without leading env
- self.assertTrue(f.match(envset + ["sleep"]))
-
- # any other command does not match
- self.assertFalse(f.match(envcmd + ["sleep2"]))
- self.assertFalse(f.match(envset + ["sleep2"]))
-
- # accept any trailing arguments
- self.assertTrue(f.match(usercmd))
-
- # require given environment variables to match
- self.assertFalse(f.match([envcmd, 'C=ELSE']))
- self.assertFalse(f.match(['env', 'C=xx']))
- self.assertFalse(f.match(['env', 'A=xx']))
-
- # require env command to be given
- # (otherwise CommandFilters should match
- self.assertFalse(f.match(realcmd))
- # require command to match
- self.assertFalse(f.match(envcmd))
- self.assertFalse(f.match(envcmd[1:]))
-
- # ensure that the env command is stripped when executing
- self.assertEqual(f.exec_args(usercmd), realcmd)
- env = f.get_environment(usercmd)
- # check that environment variables are set
- self.assertEqual(env.get('A'), '/some/thing')
- self.assertEqual(env.get('B'), 'somethingelse')
- self.assertFalse('sleep' in env.keys())
-
- def test_EnvFilter_without_leading_env(self):
- envset = ['A=/some/thing', 'B=somethingelse']
- envcmd = ['env'] + envset
- realcmd = ['sleep', '10']
-
- f = filters.EnvFilter("sleep", "root", "A=", "B=ignored")
-
- # accept without leading env
- self.assertTrue(f.match(envset + ["sleep"]))
-
- self.assertEqual(f.get_command(envcmd + realcmd), realcmd)
- self.assertEqual(f.get_command(envset + realcmd), realcmd)
-
- env = f.get_environment(envset + realcmd)
- # check that environment variables are set
- self.assertEqual(env.get('A'), '/some/thing')
- self.assertEqual(env.get('B'), 'somethingelse')
- self.assertFalse('sleep' in env.keys())
-
- def test_KillFilter(self):
- if not os.path.exists("/proc/%d" % os.getpid()):
- self.skipTest("Test requires /proc filesystem (procfs)")
- p = subprocess.Popen(["cat"], stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- try:
- f = filters.KillFilter("root", "/bin/cat", "-9", "-HUP")
- f2 = filters.KillFilter("root", "/usr/bin/cat", "-9", "-HUP")
- usercmd = ['kill', '-ALRM', p.pid]
- # Incorrect signal should fail
- self.assertFalse(f.match(usercmd) or f2.match(usercmd))
- usercmd = ['kill', p.pid]
- # Providing no signal should fail
- self.assertFalse(f.match(usercmd) or f2.match(usercmd))
- # Providing matching signal should be allowed
- usercmd = ['kill', '-9', p.pid]
- self.assertTrue(f.match(usercmd) or f2.match(usercmd))
-
- f = filters.KillFilter("root", "/bin/cat")
- f2 = filters.KillFilter("root", "/usr/bin/cat")
- usercmd = ['kill', os.getpid()]
- # Our own PID does not match /bin/sleep, so it should fail
- self.assertFalse(f.match(usercmd) or f2.match(usercmd))
- usercmd = ['kill', 999999]
- # Nonexistent PID should fail
- self.assertFalse(f.match(usercmd) or f2.match(usercmd))
- usercmd = ['kill', p.pid]
- # Providing no signal should work
- self.assertTrue(f.match(usercmd) or f2.match(usercmd))
-
- # verify that relative paths are matched against $PATH
- f = filters.KillFilter("root", "cat")
- # Our own PID does not match so it should fail
- usercmd = ['kill', os.getpid()]
- self.assertFalse(f.match(usercmd))
- # Filter should find cat in /bin or /usr/bin
- usercmd = ['kill', p.pid]
- self.assertTrue(f.match(usercmd))
- # Filter shouldn't be able to find binary in $PATH, so fail
- with fixtures.EnvironmentVariable("PATH", "/foo:/bar"):
- self.assertFalse(f.match(usercmd))
- # ensure that unset $PATH is not causing an exception
- with fixtures.EnvironmentVariable("PATH"):
- self.assertFalse(f.match(usercmd))
- finally:
- # Terminate the "cat" process and wait for it to finish
- p.terminate()
- p.wait()
-
- def test_KillFilter_no_raise(self):
- """Makes sure ValueError from bug 926412 is gone."""
- f = filters.KillFilter("root", "")
- # Providing anything other than kill should be False
- usercmd = ['notkill', 999999]
- self.assertFalse(f.match(usercmd))
- # Providing something that is not a pid should be False
- usercmd = ['kill', 'notapid']
- self.assertFalse(f.match(usercmd))
- # no arguments should also be fine
- self.assertFalse(f.match([]))
- self.assertFalse(f.match(None))
-
- def test_KillFilter_deleted_exe(self):
- """Makes sure deleted exe's are killed correctly."""
- f = filters.KillFilter("root", "/bin/commandddddd")
- usercmd = ['kill', 1234]
- # Providing no signal should work
- with mock.patch('os.readlink') as readlink:
- readlink.return_value = '/bin/commandddddd (deleted)'
- self.assertTrue(f.match(usercmd))
-
- def test_KillFilter_upgraded_exe(self):
- """Makes sure upgraded exe's are killed correctly."""
- f = filters.KillFilter("root", "/bin/commandddddd")
- usercmd = ['kill', 1234]
- with mock.patch('os.readlink') as readlink:
- readlink.return_value = '/bin/commandddddd\0\05190bfb2 (deleted)'
- self.assertTrue(f.match(usercmd))
-
- def test_ReadFileFilter(self):
- goodfn = '/good/file.name'
- f = filters.ReadFileFilter(goodfn)
- usercmd = ['cat', '/bad/file']
- self.assertFalse(f.match(['cat', '/bad/file']))
- usercmd = ['cat', goodfn]
- self.assertEqual(f.get_command(usercmd), ['/bin/cat', goodfn])
- self.assertTrue(f.match(usercmd))
-
- def test_IpFilter_non_netns(self):
- f = filters.IpFilter(self._ip, 'root')
- self.assertTrue(f.match(['ip', 'link', 'list']))
- self.assertTrue(f.match(['ip', '-s', 'link', 'list']))
- self.assertTrue(f.match(['ip', '-s', '-v', 'netns', 'add']))
- self.assertTrue(f.match(['ip', 'link', 'set', 'interface',
- 'netns', 'somens']))
-
- def test_IpFilter_netns(self):
- f = filters.IpFilter(self._ip, 'root')
- self.assertFalse(f.match(['ip', 'netns', 'exec', 'foo']))
- self.assertFalse(f.match(['ip', 'netns', 'exec']))
- self.assertFalse(f.match(['ip', '-s', 'netns', 'exec']))
- self.assertFalse(f.match(['ip', '-l', '42', 'netns', 'exec']))
-
- def _test_IpFilter_netns_helper(self, action):
- f = filters.IpFilter(self._ip, 'root')
- self.assertTrue(f.match(['ip', 'link', action]))
-
- def test_IpFilter_netns_add(self):
- self._test_IpFilter_netns_helper('add')
-
- def test_IpFilter_netns_delete(self):
- self._test_IpFilter_netns_helper('delete')
-
- def test_IpFilter_netns_list(self):
- self._test_IpFilter_netns_helper('list')
-
- def test_IpNetnsExecFilter_match(self):
- f = filters.IpNetnsExecFilter(self._ip, 'root')
- self.assertTrue(
- f.match(['ip', 'netns', 'exec', 'foo', 'ip', 'link', 'list']))
-
- def test_IpNetnsExecFilter_nomatch(self):
- f = filters.IpNetnsExecFilter(self._ip, 'root')
- self.assertFalse(f.match(['ip', 'link', 'list']))
-
- # verify that at least a NS is given
- self.assertFalse(f.match(['ip', 'netns', 'exec']))
-
- def test_IpNetnsExecFilter_nomatch_nonroot(self):
- f = filters.IpNetnsExecFilter(self._ip, 'user')
- self.assertFalse(
- f.match(['ip', 'netns', 'exec', 'foo', 'ip', 'link', 'list']))
-
- def test_match_filter_recurses_exec_command_filter_matches(self):
- filter_list = [filters.IpNetnsExecFilter(self._ip, 'root'),
- filters.IpFilter(self._ip, 'root')]
- args = ['ip', 'netns', 'exec', 'foo', 'ip', 'link', 'list']
-
- self.assertIsNotNone(wrapper.match_filter(filter_list, args))
-
- def test_match_filter_recurses_exec_command_matches_user(self):
- filter_list = [filters.IpNetnsExecFilter(self._ip, 'root'),
- filters.IpFilter(self._ip, 'user')]
- args = ['ip', 'netns', 'exec', 'foo', 'ip', 'link', 'list']
-
- # Currently ip netns exec requires root, so verify that
- # no non-root filter is matched, as that would escalate privileges
- self.assertRaises(wrapper.NoFilterMatched,
- wrapper.match_filter, filter_list, args)
-
- def test_match_filter_recurses_exec_command_filter_does_not_match(self):
- filter_list = [filters.IpNetnsExecFilter(self._ip, 'root'),
- filters.IpFilter(self._ip, 'root')]
- args = ['ip', 'netns', 'exec', 'foo', 'ip', 'netns', 'exec', 'bar',
- 'ip', 'link', 'list']
-
- self.assertRaises(wrapper.NoFilterMatched,
- wrapper.match_filter, filter_list, args)
-
- def test_ChainingRegExpFilter_match(self):
- filter_list = [filters.ChainingRegExpFilter('nice', 'root',
- 'nice', '-?\d+'),
- filters.CommandFilter('cat', 'root')]
- args = ['nice', '5', 'cat', '/a']
- dirs = ['/bin', '/usr/bin']
-
- self.assertIsNotNone(wrapper.match_filter(filter_list, args, dirs))
-
- def test_ChainingRegExpFilter_not_match(self):
- filter_list = [filters.ChainingRegExpFilter('nice', 'root',
- 'nice', '-?\d+'),
- filters.CommandFilter('cat', 'root')]
- args_invalid = (['nice', '5', 'ls', '/a'],
- ['nice', '--5', 'cat', '/a'],
- ['nice2', '5', 'cat', '/a'],
- ['nice', 'cat', '/a'],
- ['nice', '5'])
- dirs = ['/bin', '/usr/bin']
-
- for args in args_invalid:
- self.assertRaises(wrapper.NoFilterMatched,
- wrapper.match_filter, filter_list, args, dirs)
-
- def test_ChainingRegExpFilter_multiple(self):
- filter_list = [filters.ChainingRegExpFilter('ionice', 'root', 'ionice',
- '-c[0-3]'),
- filters.ChainingRegExpFilter('ionice', 'root', 'ionice',
- '-c[0-3]', '-n[0-7]'),
- filters.CommandFilter('cat', 'root')]
- # both filters match to ['ionice', '-c2'], but only the second accepts
- args = ['ionice', '-c2', '-n7', 'cat', '/a']
- dirs = ['/bin', '/usr/bin']
-
- self.assertIsNotNone(wrapper.match_filter(filter_list, args, dirs))
-
- def test_ReadFileFilter_empty_args(self):
- goodfn = '/good/file.name'
- f = filters.ReadFileFilter(goodfn)
- self.assertFalse(f.match([]))
- self.assertFalse(f.match(None))
-
- def test_exec_dirs_search(self):
- # This test supposes you have /bin/cat or /usr/bin/cat locally
- f = filters.CommandFilter("cat", "root")
- usercmd = ['cat', '/f']
- self.assertTrue(f.match(usercmd))
- self.assertTrue(f.get_command(usercmd,
- exec_dirs=['/bin', '/usr/bin'])
- in (['/bin/cat', '/f'], ['/usr/bin/cat', '/f']))
-
- def test_skips(self):
- # Check that all filters are skipped and that the last matches
- usercmd = ["cat", "/"]
- filtermatch = wrapper.match_filter(self.filters, usercmd)
- self.assertTrue(filtermatch is self.filters[-1])
-
- def test_RootwrapConfig(self):
- raw = moves.configparser.RawConfigParser()
-
- # Empty config should raise configparser.Error
- self.assertRaises(moves.configparser.Error,
- wrapper.RootwrapConfig, raw)
-
- # Check default values
- raw.set('DEFAULT', 'filters_path', '/a,/b')
- config = wrapper.RootwrapConfig(raw)
- self.assertEqual(config.filters_path, ['/a', '/b'])
- self.assertEqual(config.exec_dirs, os.environ["PATH"].split(':'))
-
- with fixtures.EnvironmentVariable("PATH"):
- c = wrapper.RootwrapConfig(raw)
- self.assertEqual(c.exec_dirs, [])
-
- self.assertFalse(config.use_syslog)
- self.assertFalse(config.use_syslog_rfc_format)
- self.assertEqual(config.syslog_log_facility,
- logging.handlers.SysLogHandler.LOG_SYSLOG)
- self.assertEqual(config.syslog_log_level, logging.ERROR)
-
- # Check general values
- raw.set('DEFAULT', 'exec_dirs', '/a,/x')
- config = wrapper.RootwrapConfig(raw)
- self.assertEqual(config.exec_dirs, ['/a', '/x'])
-
- raw.set('DEFAULT', 'use_syslog', 'oui')
- self.assertRaises(ValueError, wrapper.RootwrapConfig, raw)
- raw.set('DEFAULT', 'use_syslog', 'true')
- config = wrapper.RootwrapConfig(raw)
- self.assertTrue(config.use_syslog)
-
- raw.set('DEFAULT', 'use_syslog_rfc_format', 'oui')
- self.assertRaises(ValueError, wrapper.RootwrapConfig, raw)
- raw.set('DEFAULT', 'use_syslog_rfc_format', 'true')
- config = wrapper.RootwrapConfig(raw)
- self.assertTrue(config.use_syslog_rfc_format)
-
- raw.set('DEFAULT', 'syslog_log_facility', 'moo')
- self.assertRaises(ValueError, wrapper.RootwrapConfig, raw)
- raw.set('DEFAULT', 'syslog_log_facility', 'local0')
- config = wrapper.RootwrapConfig(raw)
- self.assertEqual(config.syslog_log_facility,
- logging.handlers.SysLogHandler.LOG_LOCAL0)
- raw.set('DEFAULT', 'syslog_log_facility', 'LOG_AUTH')
- config = wrapper.RootwrapConfig(raw)
- self.assertEqual(config.syslog_log_facility,
- logging.handlers.SysLogHandler.LOG_AUTH)
-
- raw.set('DEFAULT', 'syslog_log_level', 'bar')
- self.assertRaises(ValueError, wrapper.RootwrapConfig, raw)
- raw.set('DEFAULT', 'syslog_log_level', 'INFO')
- config = wrapper.RootwrapConfig(raw)
- self.assertEqual(config.syslog_log_level, logging.INFO)
-
-
-class PathFilterTestCase(testtools.TestCase):
- def setUp(self):
- super(PathFilterTestCase, self).setUp()
-
- tmpdir = fixtures.TempDir('/tmp')
- self.useFixture(tmpdir)
-
- self.f = filters.PathFilter('/bin/chown', 'root', 'nova', tmpdir.path)
-
- gen_name = lambda: str(uuid.uuid4())
-
- self.SIMPLE_FILE_WITHIN_DIR = os.path.join(tmpdir.path, 'some')
- self.SIMPLE_FILE_OUTSIDE_DIR = os.path.join('/tmp', 'some')
- self.TRAVERSAL_WITHIN_DIR = os.path.join(tmpdir.path, 'a', '..',
- 'some')
- self.TRAVERSAL_OUTSIDE_DIR = os.path.join(tmpdir.path, '..', 'some')
-
- self.TRAVERSAL_SYMLINK_WITHIN_DIR = os.path.join(tmpdir.path,
- gen_name())
- os.symlink(os.path.join(tmpdir.path, 'a', '..', 'a'),
- self.TRAVERSAL_SYMLINK_WITHIN_DIR)
-
- self.TRAVERSAL_SYMLINK_OUTSIDE_DIR = os.path.join(tmpdir.path,
- gen_name())
- os.symlink(os.path.join(tmpdir.path, 'a', '..', '..', '..', 'etc'),
- self.TRAVERSAL_SYMLINK_OUTSIDE_DIR)
-
- self.SYMLINK_WITHIN_DIR = os.path.join(tmpdir.path, gen_name())
- os.symlink(os.path.join(tmpdir.path, 'a'), self.SYMLINK_WITHIN_DIR)
-
- self.SYMLINK_OUTSIDE_DIR = os.path.join(tmpdir.path, gen_name())
- os.symlink(os.path.join('/tmp', 'some_file'), self.SYMLINK_OUTSIDE_DIR)
-
- def test_empty_args(self):
- self.assertFalse(self.f.match([]))
- self.assertFalse(self.f.match(None))
-
- def test_argument_pass_constraint(self):
- f = filters.PathFilter('/bin/chown', 'root', 'pass', 'pass')
-
- args = ['chown', 'something', self.SIMPLE_FILE_OUTSIDE_DIR]
- self.assertTrue(f.match(args))
-
- def test_argument_equality_constraint(self):
- f = filters.PathFilter('/bin/chown', 'root', 'nova', '/tmp/spam/eggs')
-
- args = ['chown', 'nova', '/tmp/spam/eggs']
- self.assertTrue(f.match(args))
-
- args = ['chown', 'quantum', '/tmp/spam/eggs']
- self.assertFalse(f.match(args))
-
- def test_wrong_arguments_number(self):
- args = ['chown', '-c', 'nova', self.SIMPLE_FILE_WITHIN_DIR]
- self.assertFalse(self.f.match(args))
-
- def test_wrong_exec_command(self):
- args = ['wrong_exec', self.SIMPLE_FILE_WITHIN_DIR]
- self.assertFalse(self.f.match(args))
-
- def test_match(self):
- args = ['chown', 'nova', self.SIMPLE_FILE_WITHIN_DIR]
- self.assertTrue(self.f.match(args))
-
- def test_match_traversal(self):
- args = ['chown', 'nova', self.TRAVERSAL_WITHIN_DIR]
- self.assertTrue(self.f.match(args))
-
- def test_match_symlink(self):
- args = ['chown', 'nova', self.SYMLINK_WITHIN_DIR]
- self.assertTrue(self.f.match(args))
-
- def test_match_traversal_symlink(self):
- args = ['chown', 'nova', self.TRAVERSAL_SYMLINK_WITHIN_DIR]
- self.assertTrue(self.f.match(args))
-
- def test_reject(self):
- args = ['chown', 'nova', self.SIMPLE_FILE_OUTSIDE_DIR]
- self.assertFalse(self.f.match(args))
-
- def test_reject_traversal(self):
- args = ['chown', 'nova', self.TRAVERSAL_OUTSIDE_DIR]
- self.assertFalse(self.f.match(args))
-
- def test_reject_symlink(self):
- args = ['chown', 'nova', self.SYMLINK_OUTSIDE_DIR]
- self.assertFalse(self.f.match(args))
-
- def test_reject_traversal_symlink(self):
- args = ['chown', 'nova', self.TRAVERSAL_SYMLINK_OUTSIDE_DIR]
- self.assertFalse(self.f.match(args))
-
- def test_get_command(self):
- args = ['chown', 'nova', self.SIMPLE_FILE_WITHIN_DIR]
- expected = ['/bin/chown', 'nova', self.SIMPLE_FILE_WITHIN_DIR]
-
- self.assertEqual(expected, self.f.get_command(args))
-
- def test_get_command_traversal(self):
- args = ['chown', 'nova', self.TRAVERSAL_WITHIN_DIR]
- expected = ['/bin/chown', 'nova',
- os.path.realpath(self.TRAVERSAL_WITHIN_DIR)]
-
- self.assertEqual(expected, self.f.get_command(args))
-
- def test_get_command_symlink(self):
- args = ['chown', 'nova', self.SYMLINK_WITHIN_DIR]
- expected = ['/bin/chown', 'nova',
- os.path.realpath(self.SYMLINK_WITHIN_DIR)]
-
- self.assertEqual(expected, self.f.get_command(args))
-
- def test_get_command_traversal_symlink(self):
- args = ['chown', 'nova', self.TRAVERSAL_SYMLINK_WITHIN_DIR]
- expected = ['/bin/chown', 'nova',
- os.path.realpath(self.TRAVERSAL_SYMLINK_WITHIN_DIR)]
-
- self.assertEqual(expected, self.f.get_command(args))
-
-
-class RunOneCommandTestCase(testtools.TestCase):
- def _test_returncode_helper(self, returncode, expected):
- start_name = 'oslo_rootwrap.wrapper.start_subprocess'
- with mock.patch(start_name) as mock_start:
- with mock.patch('sys.exit') as mock_exit:
- mock_start.return_value.wait.return_value = returncode
- cmd.run_one_command(None, mock.Mock(), None, None)
- mock_exit.assert_called_once_with(expected)
-
- def test_positive_returncode(self):
- self._test_returncode_helper(1, 1)
-
- def test_negative_returncode(self):
- self._test_returncode_helper(-1, 129)
diff --git a/tests/test_warning.py b/tests/test_warning.py
deleted file mode 100644
index 23202e0..0000000
--- a/tests/test_warning.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# 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 imp
-import os
-import warnings
-
-import mock
-from oslotest import base as test_base
-import six
-
-
-class DeprecationWarningTest(test_base.BaseTestCase):
-
- @mock.patch('warnings.warn')
- def test_warning(self, mock_warn):
- import oslo.rootwrap
- imp.reload(oslo.rootwrap)
- self.assertTrue(mock_warn.called)
- args = mock_warn.call_args
- self.assertIn('oslo_rootwrap', args[0][0])
- self.assertIn('deprecated', args[0][0])
- self.assertTrue(issubclass(args[0][1], DeprecationWarning))
-
- def test_real_warning(self):
- with warnings.catch_warnings(record=True) as warning_msgs:
- warnings.resetwarnings()
- warnings.simplefilter('always', DeprecationWarning)
- import oslo.rootwrap
-
- # Use a separate function to get the stack level correct
- # so we know the message points back to this file. This
- # corresponds to an import or reload, which isn't working
- # inside the test under Python 3.3. That may be due to a
- # difference in the import implementation not triggering
- # warnings properly when the module is reloaded, or
- # because the warnings module is mostly implemented in C
- # and something isn't cleanly resetting the global state
- # used to track whether a warning needs to be
- # emitted. Whatever the cause, we definitely see the
- # warnings.warn() being invoked on a reload (see the test
- # above) and warnings are reported on the console when we
- # run the tests. A simpler test script run outside of
- # testr does correctly report the warnings.
- def foo():
- oslo.rootwrap.deprecated()
-
- foo()
- self.assertEqual(1, len(warning_msgs))
- msg = warning_msgs[0]
- self.assertIn('oslo_rootwrap', six.text_type(msg.message))
- self.assertEqual('test_warning.py', os.path.basename(msg.filename))