diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/unit/db/sqlalchemy/test_sqlalchemy.py | 61 | ||||
-rw-r--r-- | tests/unit/db/test_api.py | 12 | ||||
-rw-r--r-- | tests/unit/plugin/__init__.py | 14 | ||||
-rw-r--r-- | tests/unit/plugin/test_callback_plugin.py | 92 | ||||
-rw-r--r-- | tests/unit/rpc/test_common.py | 2 | ||||
-rw-r--r-- | tests/unit/rpc/test_kombu.py | 171 | ||||
-rw-r--r-- | tests/unit/rpc/test_qpid.py | 31 | ||||
-rw-r--r-- | tests/unit/rpc/test_zmq.py | 1 | ||||
-rw-r--r-- | tests/unit/test_excutils.py | 111 | ||||
-rw-r--r-- | tests/unit/test_plugin.py | 118 | ||||
-rw-r--r-- | tests/unit/test_rootwrap.py | 127 | ||||
-rw-r--r-- | tests/utils.py | 11 |
12 files changed, 486 insertions, 265 deletions
diff --git a/tests/unit/db/sqlalchemy/test_sqlalchemy.py b/tests/unit/db/sqlalchemy/test_sqlalchemy.py index ac178b8..48d6cf7 100644 --- a/tests/unit/db/sqlalchemy/test_sqlalchemy.py +++ b/tests/unit/db/sqlalchemy/test_sqlalchemy.py @@ -52,15 +52,15 @@ sql_max_overflow=50 sql_connection_debug=60 sql_connection_trace=True """)]) - test_utils.CONF(['--config-file', paths[0]]) - self.assertEquals(test_utils.CONF.database.connection, 'x://y.z') - self.assertEquals(test_utils.CONF.database.min_pool_size, 10) - self.assertEquals(test_utils.CONF.database.max_pool_size, 20) - self.assertEquals(test_utils.CONF.database.max_retries, 30) - self.assertEquals(test_utils.CONF.database.retry_interval, 40) - self.assertEquals(test_utils.CONF.database.max_overflow, 50) - self.assertEquals(test_utils.CONF.database.connection_debug, 60) - self.assertEquals(test_utils.CONF.database.connection_trace, True) + self.conf(['--config-file', paths[0]]) + self.assertEquals(self.conf.database.connection, 'x://y.z') + self.assertEquals(self.conf.database.min_pool_size, 10) + self.assertEquals(self.conf.database.max_pool_size, 20) + self.assertEquals(self.conf.database.max_retries, 30) + self.assertEquals(self.conf.database.retry_interval, 40) + self.assertEquals(self.conf.database.max_overflow, 50) + self.assertEquals(self.conf.database.connection_debug, 60) + self.assertEquals(self.conf.database.connection_trace, True) def test_session_parameters(self): paths = self.create_tempfiles([('test', """[database] @@ -74,16 +74,39 @@ connection_debug=60 connection_trace=True pool_timeout=7 """)]) - test_utils.CONF(['--config-file', paths[0]]) - self.assertEquals(test_utils.CONF.database.connection, 'x://y.z') - self.assertEquals(test_utils.CONF.database.min_pool_size, 10) - self.assertEquals(test_utils.CONF.database.max_pool_size, 20) - self.assertEquals(test_utils.CONF.database.max_retries, 30) - self.assertEquals(test_utils.CONF.database.retry_interval, 40) - self.assertEquals(test_utils.CONF.database.max_overflow, 50) - self.assertEquals(test_utils.CONF.database.connection_debug, 60) - self.assertEquals(test_utils.CONF.database.connection_trace, True) - self.assertEquals(test_utils.CONF.database.pool_timeout, 7) + self.conf(['--config-file', paths[0]]) + self.assertEquals(self.conf.database.connection, 'x://y.z') + self.assertEquals(self.conf.database.min_pool_size, 10) + self.assertEquals(self.conf.database.max_pool_size, 20) + self.assertEquals(self.conf.database.max_retries, 30) + self.assertEquals(self.conf.database.retry_interval, 40) + self.assertEquals(self.conf.database.max_overflow, 50) + self.assertEquals(self.conf.database.connection_debug, 60) + self.assertEquals(self.conf.database.connection_trace, True) + self.assertEquals(self.conf.database.pool_timeout, 7) + + def test_dbapi_database_deprecated_parameters(self): + paths = self.create_tempfiles([('test', + '[DATABASE]\n' + 'sql_connection=fake_connection\n' + 'sql_idle_timeout=100\n' + 'sql_min_pool_size=99\n' + 'sql_max_pool_size=199\n' + 'sql_max_retries=22\n' + 'reconnect_interval=17\n' + 'sqlalchemy_max_overflow=101\n' + 'sqlalchemy_pool_timeout=5\n' + )]) + self.conf(['--config-file', paths[0]]) + self.assertEquals(self.conf.database.connection, + 'fake_connection') + self.assertEquals(self.conf.database.idle_timeout, 100) + self.assertEquals(self.conf.database.min_pool_size, 99) + self.assertEquals(self.conf.database.max_pool_size, 199) + self.assertEquals(self.conf.database.max_retries, 22) + self.assertEquals(self.conf.database.retry_interval, 17) + self.assertEquals(self.conf.database.max_overflow, 101) + self.assertEquals(self.conf.database.pool_timeout, 5) class SessionErrorWrapperTestCase(test_base.DbTestCase): diff --git a/tests/unit/db/test_api.py b/tests/unit/db/test_api.py index f6e0d4c..2a8db3b 100644 --- a/tests/unit/db/test_api.py +++ b/tests/unit/db/test_api.py @@ -40,9 +40,9 @@ class DBAPITestCase(test_utils.BaseTestCase): 'dbapi_use_tpool=True\n' )]) - test_utils.CONF(['--config-file', paths[0]]) - self.assertEquals(test_utils.CONF.database.backend, 'test_123') - self.assertEquals(test_utils.CONF.database.use_tpool, True) + self.conf(['--config-file', paths[0]]) + self.assertEquals(self.conf.database.backend, 'test_123') + self.assertEquals(self.conf.database.use_tpool, True) def test_dbapi_parameters(self): paths = self.create_tempfiles([('test', @@ -51,9 +51,9 @@ class DBAPITestCase(test_utils.BaseTestCase): 'use_tpool=True\n' )]) - test_utils.CONF(['--config-file', paths[0]]) - self.assertEquals(test_utils.CONF.database.backend, 'test_123') - self.assertEquals(test_utils.CONF.database.use_tpool, True) + self.conf(['--config-file', paths[0]]) + self.assertEquals(self.conf.database.backend, 'test_123') + self.assertEquals(self.conf.database.use_tpool, True) def test_dbapi_api_class_method_and_tpool_false(self): backend_mapping = {'test_known': 'tests.unit.db.test_api'} diff --git a/tests/unit/plugin/__init__.py b/tests/unit/plugin/__init__.py deleted file mode 100644 index b706747..0000000 --- a/tests/unit/plugin/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2012 OpenStack Foundation. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. diff --git a/tests/unit/plugin/test_callback_plugin.py b/tests/unit/plugin/test_callback_plugin.py deleted file mode 100644 index 3f3fd63..0000000 --- a/tests/unit/plugin/test_callback_plugin.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2012 OpenStack Foundation. -# 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 pkg_resources - -from openstack.common.notifier import api as notifier_api -from openstack.common.plugin import callbackplugin -from openstack.common.plugin import pluginmanager -from tests import utils as test_utils - -userdatastring = "magic user data string" - - -class TestCBP(callbackplugin.CallbackPlugin): - - def callback1(self, context, message, userdata): - self.callback1count += 1 - - def callback2(self, context, message, userdata): - self.callback2count += 1 - - def callback3(self, context, message, userdata): - self.callback3count += 1 - self.userdata = userdata - - def __init__(self, service_name): - super(TestCBP, self).__init__(service_name) - self.callback1count = 0 - self.callback2count = 0 - self.callback3count = 0 - - self._add_callback(self.callback1, 'type1', None) - self._add_callback(self.callback2, 'type1', None) - self._add_callback(self.callback3, 'type2', 'magic user data string') - - -class CallbackTestCase(test_utils.BaseTestCase): - """Tests for the callback plugin convenience class.""" - - def test_callback_plugin_subclass(self): - - class MockEntrypoint(pkg_resources.EntryPoint): - def load(self): - return TestCBP - - def mock_iter_entry_points(_t): - return [MockEntrypoint("fake", "fake", ["fake"])] - - self.stubs.Set(pkg_resources, 'iter_entry_points', - mock_iter_entry_points) - - plugmgr = pluginmanager.PluginManager("testproject", "testservice") - plugmgr.load_plugins() - self.assertEqual(len(plugmgr.plugins), 1) - plugin = plugmgr.plugins[0] - self.assertEqual(len(plugin.notifiers), 1) - - notifier_api.notify('contextarg', 'publisher_id', 'type1', - notifier_api.WARN, dict(a=3)) - - self.assertEqual(plugin.callback1count, 1) - self.assertEqual(plugin.callback2count, 1) - self.assertEqual(plugin.callback3count, 0) - - notifier_api.notify('contextarg', 'publisher_id', 'type2', - notifier_api.WARN, dict(a=3)) - - self.assertEqual(plugin.callback1count, 1) - self.assertEqual(plugin.callback2count, 1) - self.assertEqual(plugin.callback3count, 1) - self.assertEqual(plugin.userdata, userdatastring) - - plugin._remove_callback(plugin.callback1) - - notifier_api.notify('contextarg', 'publisher_id', 'type1', - notifier_api.WARN, dict(a=3)) - - self.assertEqual(plugin.callback1count, 1) - self.assertEqual(plugin.callback2count, 2) - self.assertEqual(plugin.callback3count, 1) diff --git a/tests/unit/rpc/test_common.py b/tests/unit/rpc/test_common.py index c2432f4..6f32005 100644 --- a/tests/unit/rpc/test_common.py +++ b/tests/unit/rpc/test_common.py @@ -108,7 +108,7 @@ class RpcCommonTestCase(test_utils.BaseTestCase): '__unicode__': str_override}) new_ex_type.__module__ = '%s_Remote' % e.__class__.__module__ e.__class__ = new_ex_type - raise e + raise try: raise_remote_exception() diff --git a/tests/unit/rpc/test_kombu.py b/tests/unit/rpc/test_kombu.py index 159fefb..54f8389 100644 --- a/tests/unit/rpc/test_kombu.py +++ b/tests/unit/rpc/test_kombu.py @@ -23,11 +23,13 @@ import eventlet eventlet.monkey_patch() import contextlib +import functools import logging import mock from oslo.config import cfg import six +import time from openstack.common import exception from openstack.common.rpc import amqp as rpc_amqp @@ -69,6 +71,7 @@ class KombuStubs: @staticmethod def setUp(self): if kombu: + self.conf = FLAGS self.config(fake_rabbit=True) self.config(rpc_response_timeout=5) self.rpc = impl_kombu @@ -77,6 +80,20 @@ class KombuStubs: self.rpc = None +class FakeMessage(object): + acked = False + rejected = False + + def __init__(self, payload): + self.payload = payload + + def ack(self): + self.acked = True + + def reject(self): + self.rejected = True + + class RpcKombuTestCase(amqp.BaseRpcAMQPTestCase): def setUp(self): KombuStubs.setUp(self) @@ -112,6 +129,74 @@ class RpcKombuTestCase(amqp.BaseRpcAMQPTestCase): self.assertEqual(self.received_message, message) + def test_callback_handler_ack_on_error(self): + """The default case will ack on error. Same as before. + """ + def _callback(msg): + pass + + conn = self.rpc.create_connection(FLAGS) + consumer = conn.declare_consumer(functools.partial( + impl_kombu.TopicConsumer, + name=None, + exchange_name=None), + "a_topic", _callback) + message = FakeMessage("some message") + consumer._callback_handler(message, _callback) + self.assertTrue(message.acked) + self.assertFalse(message.rejected) + + def test_callback_handler_ack_on_error_exception(self): + + def _callback(msg): + raise MyException() + + conn = self.rpc.create_connection(FLAGS) + consumer = conn.declare_consumer(functools.partial( + impl_kombu.TopicConsumer, + name=None, + exchange_name=None, + ack_on_error=True), + "a_topic", _callback) + message = FakeMessage("some message") + consumer._callback_handler(message, _callback) + self.assertTrue(message.acked) + self.assertFalse(message.rejected) + + def test_callback_handler_no_ack_on_error_exception(self): + + def _callback(msg): + raise MyException() + + conn = self.rpc.create_connection(FLAGS) + consumer = conn.declare_consumer(functools.partial( + impl_kombu.TopicConsumer, + name=None, + exchange_name=None, + ack_on_error=False), + "a_topic", _callback) + message = FakeMessage("some message") + consumer._callback_handler(message, _callback) + self.assertFalse(message.acked) + self.assertTrue(message.rejected) + + def test_callback_handler_no_ack_on_error(self): + + def _callback(msg): + pass + + conn = self.rpc.create_connection(FLAGS) + consumer = conn.declare_consumer(functools.partial( + impl_kombu.TopicConsumer, + name=None, + exchange_name=None, + ack_on_error=False), + "a_topic", _callback) + message = FakeMessage("some message") + consumer._callback_handler(message, _callback) + self.assertTrue(message.acked) + self.assertFalse(message.rejected) + def test_message_ttl_on_timeout(self): """Test message ttl being set by request timeout. The message should die on the vine and never arrive. @@ -308,6 +393,22 @@ class RpcKombuTestCase(amqp.BaseRpcAMQPTestCase): impl_kombu.cast_to_server(FLAGS, ctxt, server_params, 'fake_topic', {'msg': 'fake'}) + def test_fanout_success(self): + # Overriding the method in the base class because the test + # seems to fail for the same reason as + # test_fanout_send_receive(). + self.skipTest("kombu memory transport seems buggy with " + "fanout queues as this test passes when " + "you use rabbit (fake_rabbit=False)") + + def test_cast_success_despite_missing_args(self): + # Overriding the method in the base class because the test + # seems to fail for the same reason as + # test_fanout_send_receive(). + self.skipTest("kombu memory transport seems buggy with " + "fanout queues as this test passes when " + "you use rabbit (fake_rabbit=False)") + def test_fanout_send_receive(self): """Test sending to a fanout exchange and consuming from 2 queues.""" @@ -514,7 +615,7 @@ class RpcKombuTestCase(amqp.BaseRpcAMQPTestCase): 'pool.name', ) - def test_join_consumer_pool(self): + def test_join_consumer_pool_default(self): meth = 'declare_topic_consumer' with mock.patch.object(self.rpc.Connection, meth) as p: conn = self.rpc.create_connection(FLAGS) @@ -529,8 +630,76 @@ class RpcKombuTestCase(amqp.BaseRpcAMQPTestCase): queue_name='pool.name', exchange_name='exchange.name', topic='topic.name', + ack_on_error=True, ) + def test_join_consumer_pool_no_ack(self): + meth = 'declare_topic_consumer' + with mock.patch.object(self.rpc.Connection, meth) as p: + conn = self.rpc.create_connection(FLAGS) + conn.join_consumer_pool( + callback=lambda *a, **k: (a, k), + pool_name='pool.name', + topic='topic.name', + exchange_name='exchange.name', + ack_on_error=False, + ) + p.assert_called_with( + callback=mock.ANY, # the callback wrapper + queue_name='pool.name', + exchange_name='exchange.name', + topic='topic.name', + ack_on_error=False, + ) + + # used to make unexpected exception tests run faster + def my_time_sleep(self, sleep_time): + return + + def test_service_consume_thread_unexpected_exceptions(self): + + def my_TopicConsumer_consume(myself, *args, **kwargs): + self.consume_calls += 1 + # see if it can sustain three failures + if self.consume_calls < 3: + raise Exception('unexpected exception') + else: + self.orig_TopicConsumer_consume(myself, *args, **kwargs) + + self.consume_calls = 0 + self.orig_TopicConsumer_consume = impl_kombu.TopicConsumer.consume + self.stubs.Set(impl_kombu.TopicConsumer, 'consume', + my_TopicConsumer_consume) + self.stubs.Set(time, 'sleep', self.my_time_sleep) + + value = 42 + result = self.rpc.call(FLAGS, self.context, self.topic, + {"method": "echo", + "args": {"value": value}}) + self.assertEqual(value, result) + + def test_replyproxy_consume_thread_unexpected_exceptions(self): + + def my_DirectConsumer_consume(myself, *args, **kwargs): + self.consume_calls += 1 + # see if it can sustain three failures + if self.consume_calls < 3: + raise Exception('unexpected exception') + else: + self.orig_DirectConsumer_consume(myself, *args, **kwargs) + + self.consume_calls = 1 + self.orig_DirectConsumer_consume = impl_kombu.DirectConsumer.consume + self.stubs.Set(impl_kombu.DirectConsumer, 'consume', + my_DirectConsumer_consume) + self.stubs.Set(time, 'sleep', self.my_time_sleep) + + value = 42 + result = self.rpc.call(FLAGS, self.context, self.topic, + {"method": "echo", + "args": {"value": value}}) + self.assertEqual(value, result) + class RpcKombuHATestCase(utils.BaseTestCase): def setUp(self): diff --git a/tests/unit/rpc/test_qpid.py b/tests/unit/rpc/test_qpid.py index 0bad387..5d51a4b 100644 --- a/tests/unit/rpc/test_qpid.py +++ b/tests/unit/rpc/test_qpid.py @@ -26,6 +26,7 @@ eventlet.monkey_patch() import fixtures import mox from oslo.config import cfg +import time import uuid from openstack.common import context @@ -218,7 +219,7 @@ class RpcQpidTestCase(utils.BaseTestCase): ) connection.close() - def test_topic_consumer(self): + def test_topic_consumer(self, consume_thread_exc=False): self.mock_connection = self.mox.CreateMock(self.orig_connection) self.mock_session = self.mox.CreateMock(self.orig_session) self.mock_receiver = self.mox.CreateMock(self.orig_receiver) @@ -235,6 +236,9 @@ class RpcQpidTestCase(utils.BaseTestCase): self.mock_session.receiver(expected_address).AndReturn( self.mock_receiver) self.mock_receiver.capacity = 1 + if consume_thread_exc: + self.mock_session.next_receiver(timeout=None).AndRaise( + Exception('unexpected exception')) self.mock_connection.close() self.mox.ReplayAll() @@ -244,8 +248,14 @@ class RpcQpidTestCase(utils.BaseTestCase): lambda *_x, **_y: None, queue_name='impl.qpid.test.workers', exchange_name='foobar') + if consume_thread_exc: + connection.consume_in_thread() + time.sleep(0) connection.close() + def test_consume_thread_exception(self): + self.test_topic_consumer(consume_thread_exc=True) + def _test_cast(self, fanout, server_params=None): self.mock_connection = self.mox.CreateMock(self.orig_connection) self.mock_session = self.mox.CreateMock(self.orig_session) @@ -338,7 +348,11 @@ class RpcQpidTestCase(utils.BaseTestCase): self._setup_to_server_tests(server_params) self._test_cast(fanout=True, server_params=server_params) + def my_time_sleep(self, arg): + pass + def _test_call_mock_common(self): + self.stubs.Set(time, 'sleep', self.my_time_sleep) self.mock_connection = self.mox.CreateMock(self.orig_connection) self.mock_session = self.mox.CreateMock(self.orig_session) self.mock_sender = self.mox.CreateMock(self.orig_sender) @@ -367,9 +381,12 @@ class RpcQpidTestCase(utils.BaseTestCase): self.mock_session.close() self.mock_connection.session().AndReturn(self.mock_session) - def _test_call(self, multi): + def _test_call(self, multi, reply_proxy_exc): self._test_call_mock_common() + if reply_proxy_exc: + self.mock_session.next_receiver(timeout=None).AndRaise( + Exception('unexpected exception')) self.mock_session.next_receiver(timeout=None).AndReturn( self.mock_receiver) self.mock_receiver.fetch().AndReturn(qpid.messaging.Message( @@ -393,6 +410,9 @@ class RpcQpidTestCase(utils.BaseTestCase): "failure": False, "ending": False})) self.mock_session.acknowledge(mox.IgnoreArg()) + if reply_proxy_exc: + self.mock_session.next_receiver(timeout=None).AndRaise( + Exception('unexpected exception')) self.mock_session.next_receiver(timeout=None).AndReturn( self.mock_receiver) self.mock_receiver.fetch().AndReturn(qpid.messaging.Message( @@ -425,7 +445,10 @@ class RpcQpidTestCase(utils.BaseTestCase): self.uuid4 = uuid.uuid4() def test_call(self): - self._test_call(multi=False) + self._test_call(multi=False, reply_proxy_exc=False) + + def test_replyproxy_consume_thread_unexpected_exceptions(self): + self._test_call(multi=False, reply_proxy_exc=True) def _test_call_with_timeout(self, timeout, expect_failure): self._test_call_mock_common() @@ -483,7 +506,7 @@ class RpcQpidTestCase(utils.BaseTestCase): self._test_call_with_timeout(timeout=0.1, expect_failure=True) def test_multicall(self): - self._test_call(multi=True) + self._test_call(multi=True, reply_proxy_exc=False) def _test_publisher(self, message=True): """Test that messages containing long strings are correctly serialized diff --git a/tests/unit/rpc/test_zmq.py b/tests/unit/rpc/test_zmq.py index b0f0262..c87a040 100644 --- a/tests/unit/rpc/test_zmq.py +++ b/tests/unit/rpc/test_zmq.py @@ -60,6 +60,7 @@ class _RpcZmqBaseTestCase(common.BaseRpcTestCase): self.reactor = None self.rpc = impl_zmq + self.conf = FLAGS self.config(rpc_zmq_bind_address='127.0.0.1') self.config(rpc_zmq_host='127.0.0.1') self.config(rpc_response_timeout=5) diff --git a/tests/unit/test_excutils.py b/tests/unit/test_excutils.py index 8c8137a..b8f9b96 100644 --- a/tests/unit/test_excutils.py +++ b/tests/unit/test_excutils.py @@ -14,6 +14,10 @@ # License for the specific language governing permissions and limitations # under the License. +import logging +import mox +import time + from openstack.common import excutils from tests import utils @@ -47,3 +51,110 @@ class SaveAndReraiseTest(utils.BaseTestCase): e = _e self.assertEqual(str(e), msg) + + +class ForeverRetryUncaughtExceptionsTest(utils.BaseTestCase): + + @excutils.forever_retry_uncaught_exceptions + def exception_generator(self): + exc = self.exception_to_raise() + while exc is not None: + raise exc + exc = self.exception_to_raise() + + def exception_to_raise(self): + return None + + def my_time_sleep(self, arg): + pass + + def exc_retrier_common_start(self): + self.stubs.Set(time, 'sleep', self.my_time_sleep) + self.mox.StubOutWithMock(logging, 'exception') + self.mox.StubOutWithMock(time, 'time') + self.mox.StubOutWithMock(self, 'exception_to_raise') + + def exc_retrier_sequence(self, exc_id=None, timestamp=None, + exc_count=None): + self.exception_to_raise().AndReturn( + Exception('unexpected %d' % exc_id)) + time.time().AndReturn(timestamp) + if exc_count != 0: + logging.exception(mox.In( + 'Unexpected exception occurred %d time(s)' % exc_count)) + + def exc_retrier_common_end(self): + self.exception_to_raise().AndReturn(None) + self.mox.ReplayAll() + self.exception_generator() + self.addCleanup(self.stubs.UnsetAll) + + def test_exc_retrier_1exc_gives_1log(self): + self.exc_retrier_common_start() + self.exc_retrier_sequence(exc_id=1, timestamp=1, exc_count=1) + self.exc_retrier_common_end() + + def test_exc_retrier_same_10exc_1min_gives_1log(self): + self.exc_retrier_common_start() + self.exc_retrier_sequence(exc_id=1, timestamp=1, exc_count=1) + # By design, the following exception don't get logged because they + # are within the same minute. + for i in range(2, 11): + self.exc_retrier_sequence(exc_id=1, timestamp=i, exc_count=0) + self.exc_retrier_common_end() + + def test_exc_retrier_same_2exc_2min_gives_2logs(self): + self.exc_retrier_common_start() + self.exc_retrier_sequence(exc_id=1, timestamp=1, exc_count=1) + self.exc_retrier_sequence(exc_id=1, timestamp=65, exc_count=1) + self.exc_retrier_common_end() + + def test_exc_retrier_same_10exc_2min_gives_2logs(self): + self.exc_retrier_common_start() + self.exc_retrier_sequence(exc_id=1, timestamp=1, exc_count=1) + self.exc_retrier_sequence(exc_id=1, timestamp=12, exc_count=0) + self.exc_retrier_sequence(exc_id=1, timestamp=23, exc_count=0) + self.exc_retrier_sequence(exc_id=1, timestamp=34, exc_count=0) + self.exc_retrier_sequence(exc_id=1, timestamp=45, exc_count=0) + # The previous 4 exceptions are counted here + self.exc_retrier_sequence(exc_id=1, timestamp=106, exc_count=5) + # Again, the following are not logged due to being within + # the same minute + self.exc_retrier_sequence(exc_id=1, timestamp=117, exc_count=0) + self.exc_retrier_sequence(exc_id=1, timestamp=128, exc_count=0) + self.exc_retrier_sequence(exc_id=1, timestamp=139, exc_count=0) + self.exc_retrier_sequence(exc_id=1, timestamp=150, exc_count=0) + self.exc_retrier_common_end() + + def test_exc_retrier_mixed_4exc_1min_gives_2logs(self): + self.exc_retrier_common_start() + self.exc_retrier_sequence(exc_id=1, timestamp=1, exc_count=1) + # By design, this second 'unexpected 1' exception is not counted. This + # is likely a rare thing and is a sacrifice for code simplicity. + self.exc_retrier_sequence(exc_id=1, timestamp=10, exc_count=0) + self.exc_retrier_sequence(exc_id=2, timestamp=20, exc_count=1) + # Again, trailing exceptions within a minute are not counted. + self.exc_retrier_sequence(exc_id=2, timestamp=30, exc_count=0) + self.exc_retrier_common_end() + + def test_exc_retrier_mixed_4exc_2min_gives_2logs(self): + self.exc_retrier_common_start() + self.exc_retrier_sequence(exc_id=1, timestamp=1, exc_count=1) + # Again, this second exception of the same type is not counted + # for the sake of code simplicity. + self.exc_retrier_sequence(exc_id=1, timestamp=10, exc_count=0) + # The difference between this and the previous case is the log + # is also triggered by more than a minute expiring. + self.exc_retrier_sequence(exc_id=2, timestamp=100, exc_count=1) + self.exc_retrier_sequence(exc_id=2, timestamp=110, exc_count=0) + self.exc_retrier_common_end() + + def test_exc_retrier_mixed_4exc_2min_gives_3logs(self): + self.exc_retrier_common_start() + self.exc_retrier_sequence(exc_id=1, timestamp=1, exc_count=1) + # This time the second 'unexpected 1' exception is counted due + # to the same exception occurring same when the minute expires. + self.exc_retrier_sequence(exc_id=1, timestamp=10, exc_count=0) + self.exc_retrier_sequence(exc_id=1, timestamp=100, exc_count=2) + self.exc_retrier_sequence(exc_id=2, timestamp=110, exc_count=1) + self.exc_retrier_common_end() diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py deleted file mode 100644 index fd653d7..0000000 --- a/tests/unit/test_plugin.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright 2012 OpenStack Foundation. -# 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 pkg_resources - -from openstack.common.notifier import api as notifier_api -from openstack.common.plugin import plugin -from openstack.common.plugin import pluginmanager -from tests import utils - - -class SimpleNotifier(object): - def __init__(self): - self.message_list = [] - - def notify(self, context, message): - self.context = context - self.message_list.append(message) - - -class ManagerTestCase(utils.BaseTestCase): - def test_constructs(self): - manager1 = pluginmanager.PluginManager("testproject", "testservice") - self.assertNotEqual(manager1, False) - - -class NotifyTestCase(utils.BaseTestCase): - """Test case for the plugin notification interface.""" - - def test_add_notifier(self): - notifier1 = SimpleNotifier() - notifier2 = SimpleNotifier() - notifier3 = SimpleNotifier() - - testplugin = plugin.Plugin('service') - testplugin._add_notifier(notifier1) - testplugin._add_notifier(notifier2) - self.assertEqual(len(testplugin.notifiers), 2) - - testplugin._add_notifier(notifier3) - self.assertEqual(len(testplugin.notifiers), 3) - - def test_notifier_action(self): - def mock_iter_entry_points(_t): - return [MockEntrypoint("fake", "fake", ["fake"])] - - self.stubs.Set(pkg_resources, 'iter_entry_points', - mock_iter_entry_points) - - plugmgr = pluginmanager.PluginManager("testproject", "testservice") - plugmgr.load_plugins() - self.assertEqual(len(plugmgr.plugins), 1) - self.assertEqual(len(plugmgr.plugins[0].notifiers), 1) - notifier = plugmgr.plugins[0].notifiers[0] - - notifier_api.notify('contextarg', 'publisher_id', 'event_type', - notifier_api.WARN, dict(a=3)) - - self.assertEqual(len(notifier.message_list), 1) - - -class StubControllerExtension(object): - name = 'stubextension' - alias = 'stubby' - - -class TestPluginClass(plugin.Plugin): - - def __init__(self, service_name): - super(TestPluginClass, self).__init__(service_name) - self._add_api_extension_descriptor(StubControllerExtension) - notifier1 = SimpleNotifier() - self._add_notifier(notifier1) - - -class MockEntrypoint(pkg_resources.EntryPoint): - def load(self): - return TestPluginClass - - -class MockExtManager(): - def __init__(self): - self.descriptors = [] - - def load_extension(self, descriptor): - self.descriptors.append(descriptor) - - -class APITestCase(utils.BaseTestCase): - """Test case for the plugin api extension interface.""" - def test_add_extension(self): - def mock_load(_s): - return TestPluginClass() - - def mock_iter_entry_points(_t): - return [MockEntrypoint("fake", "fake", ["fake"])] - - self.stubs.Set(pkg_resources, 'iter_entry_points', - mock_iter_entry_points) - - mgr = MockExtManager() - plugmgr = pluginmanager.PluginManager("testproject", "testservice") - plugmgr.load_plugins() - plugmgr.plugin_extension_factory(mgr) - - self.assertTrue(StubControllerExtension in mgr.descriptors) diff --git a/tests/unit/test_rootwrap.py b/tests/unit/test_rootwrap.py index 0e08b5e..02789ec 100644 --- a/tests/unit/test_rootwrap.py +++ b/tests/unit/test_rootwrap.py @@ -61,10 +61,11 @@ class RootwrapTestCase(utils.BaseTestCase): self.assertRaises(wrapper.NoFilterMatched, wrapper.match_filter, self.filters, invalid) - def _test_DnsmasqFilter(self, filter_class, config_file_arg): + def _test_EnvFilter_as_DnsMasq(self, config_file_arg): usercmd = ['env', config_file_arg + '=A', 'NETWORK_ID=foobar', 'dnsmasq', 'foo'] - f = filter_class("/usr/bin/dnsmasq", "root") + 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) @@ -72,10 +73,68 @@ class RootwrapTestCase(utils.BaseTestCase): self.assertEqual(env.get('NETWORK_ID'), 'foobar') def test_DnsmasqFilter(self): - self._test_DnsmasqFilter(filters.DnsmasqFilter, 'CONFIG_FILE') + self._test_EnvFilter_as_DnsMasq('CONFIG_FILE') def test_DeprecatedDnsmasqFilter(self): - self._test_DnsmasqFilter(filters.DeprecatedDnsmasqFilter, 'FLAGFILE') + self._test_EnvFilter_as_DnsMasq('FLAGFILE') + + 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()): @@ -169,6 +228,66 @@ class RootwrapTestCase(utils.BaseTestCase): self.assertEqual(f.get_command(usercmd), ['/bin/cat', goodfn]) self.assertTrue(f.match(usercmd)) + def test_IpFilter_non_netns(self): + f = filters.IpFilter('/sbin/ip', 'root') + self.assertTrue(f.match(['ip', 'link', 'list'])) + + def _test_IpFilter_netns_helper(self, action): + f = filters.IpFilter('/sbin/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('/sbin/ip', 'root') + self.assertTrue( + f.match(['ip', 'netns', 'exec', 'foo', 'ip', 'link', 'list'])) + + def test_IpNetnsExecFilter_nomatch(self): + f = filters.IpNetnsExecFilter('/sbin/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('/sbin/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('/sbin/ip', 'root'), + filters.IpFilter('/sbin/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('/sbin/ip', 'root'), + filters.IpFilter('/sbin/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('/sbin/ip', 'root'), + filters.IpFilter('/sbin/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_exec_dirs_search(self): # This test supposes you have /bin/cat or /usr/bin/cat locally f = filters.CommandFilter("cat", "root") diff --git a/tests/utils.py b/tests/utils.py index 794a3d2..e93c278 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -27,17 +27,16 @@ import testtools from openstack.common import exception from openstack.common.fixture import moxstubout -CONF = cfg.CONF - class BaseTestCase(testtools.TestCase): - def setUp(self): + def setUp(self, conf=cfg.CONF): super(BaseTestCase, self).setUp() moxfixture = self.useFixture(moxstubout.MoxStubout()) self.mox = moxfixture.mox self.stubs = moxfixture.stubs - self.addCleanup(CONF.reset) + self.conf = conf + self.addCleanup(self.conf.reset) self.useFixture(fixtures.FakeLogger('openstack.common')) self.useFixture(fixtures.Timeout(30, True)) self.stubs.Set(exception, '_FATAL_EXCEPTION_FORMAT_ERRORS', True) @@ -46,7 +45,7 @@ class BaseTestCase(testtools.TestCase): def tearDown(self): super(BaseTestCase, self).tearDown() - CONF.reset() + self.conf.reset() self.stubs.UnsetAll() self.stubs.SmartUnsetAll() @@ -79,4 +78,4 @@ class BaseTestCase(testtools.TestCase): """ group = kw.pop('group', None) for k, v in kw.iteritems(): - CONF.set_override(k, v, group) + self.conf.set_override(k, v, group) |