diff options
-rw-r--r-- | .testr.conf | 9 | ||||
-rw-r--r-- | HACKING.rst | 10 | ||||
-rw-r--r-- | MAINTAINERS | 6 | ||||
-rwxr-xr-x | openstack/common/config/generator.py | 1 | ||||
-rw-r--r-- | openstack/common/exception.py | 4 | ||||
-rw-r--r-- | openstack/common/plugin/__init__.py | 14 | ||||
-rw-r--r-- | openstack/common/plugin/callbackplugin.py | 93 | ||||
-rw-r--r-- | openstack/common/plugin/plugin.py | 86 | ||||
-rw-r--r-- | openstack/common/plugin/pluginmanager.py | 78 | ||||
-rw-r--r-- | openstack/common/rootwrap/filters.py | 113 | ||||
-rw-r--r-- | openstack/common/rootwrap/wrapper.py | 14 | ||||
-rw-r--r-- | openstack/common/rpc/amqp.py | 7 | ||||
-rw-r--r-- | test-requirements.txt | 8 | ||||
-rw-r--r-- | tests/unit/db/sqlalchemy/test_sqlalchemy.py | 57 | ||||
-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 | 1 | ||||
-rw-r--r-- | tests/unit/rpc/test_zmq.py | 1 | ||||
-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 | ||||
-rw-r--r-- | tox.ini | 18 |
24 files changed, 325 insertions, 571 deletions
diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..d54ffb8 --- /dev/null +++ b/.testr.conf @@ -0,0 +1,9 @@ +[DEFAULT] +TESTS_PATH=./test +test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ + OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ + OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ + ${PYTHON:-python} -m subunit.run discover -t ./ $TESTS_PATH $LISTOPT $IDOPTION + +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/HACKING.rst b/HACKING.rst index 3cea316..846c1b1 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -46,6 +46,16 @@ General pass +- Use 'raise' instead of 'raise e' to preserve original traceback or exception being reraised:: + + except Exception as e: + ... + raise e # BAD + + except Exception: + ... + raise # OKAY + TODO vs FIXME ------------- diff --git a/MAINTAINERS b/MAINTAINERS index 128cb22..0500c53 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -199,12 +199,6 @@ M: Michael Still <mikal@stillhq.com> S: Maintained F: periodic_task.py -== plugins == - -M: -S: Orphan -F: plugin/ - == policy == M: diff --git a/openstack/common/config/generator.py b/openstack/common/config/generator.py index 09649e7..8ebfba1 100755 --- a/openstack/common/config/generator.py +++ b/openstack/common/config/generator.py @@ -205,6 +205,7 @@ def _print_opt(opt): opt_name, opt_default, opt_help = opt.dest, opt.default, opt.help if not opt_help: sys.stderr.write('WARNING: "%s" is missing help string.\n' % opt_name) + opt_help = "" opt_type = None try: opt_type = OPTION_REGEX.search(str(type(opt))).group(0) diff --git a/openstack/common/exception.py b/openstack/common/exception.py index cdf40f3..f6c8463 100644 --- a/openstack/common/exception.py +++ b/openstack/common/exception.py @@ -122,9 +122,9 @@ class OpenstackException(Exception): try: self._error_string = self.message % kwargs - except Exception as e: + except Exception: if _FATAL_EXCEPTION_FORMAT_ERRORS: - raise e + raise else: # at least get the core message out if something happened self._error_string = self.message diff --git a/openstack/common/plugin/__init__.py b/openstack/common/plugin/__init__.py deleted file mode 100644 index b706747..0000000 --- a/openstack/common/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/openstack/common/plugin/callbackplugin.py b/openstack/common/plugin/callbackplugin.py deleted file mode 100644 index 2de7fb0..0000000 --- a/openstack/common/plugin/callbackplugin.py +++ /dev/null @@ -1,93 +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. - -from openstack.common import log as logging -from openstack.common.plugin import plugin - - -LOG = logging.getLogger(__name__) - - -class _CallbackNotifier(object): - """Manages plugin-defined notification callbacks. - - For each Plugin, a CallbackNotifier will be added to the - notification driver list. Calls to notify() with appropriate - messages will be hooked and prompt callbacks. - - A callback should look like this: - def callback(context, message, user_data) - """ - - def __init__(self): - self._callback_dict = {} - - def _add_callback(self, event_type, callback, user_data): - callback_list = self._callback_dict.get(event_type, []) - callback_list.append({'function': callback, - 'user_data': user_data}) - self._callback_dict[event_type] = callback_list - - def _remove_callback(self, callback): - for callback_list in self._callback_dict.values(): - for entry in callback_list: - if entry['function'] == callback: - callback_list.remove(entry) - - def notify(self, context, message): - if message.get('event_type') not in self._callback_dict: - return - - for entry in self._callback_dict[message.get('event_type')]: - entry['function'](context, message, entry.get('user_data')) - - def callbacks(self): - return self._callback_dict - - -class CallbackPlugin(plugin.Plugin): - """Plugin with a simple callback interface. - - This class is provided as a convenience for producing a simple - plugin that only watches a couple of events. For example, here's - a subclass which prints a line the first time an instance is created. - - class HookInstanceCreation(CallbackPlugin): - - def __init__(self, _service_name): - super(HookInstanceCreation, self).__init__() - self._add_callback(self.magic, 'compute.instance.create.start') - - def magic(self): - print "An instance was created!" - self._remove_callback(self, self.magic) - """ - - def __init__(self, service_name): - super(CallbackPlugin, self).__init__(service_name) - self._callback_notifier = _CallbackNotifier() - self._add_notifier(self._callback_notifier) - - def _add_callback(self, callback, event_type, user_data=None): - """Add callback for a given event notification. - - Subclasses can call this as an alternative to implementing - a fullblown notify notifier. - """ - self._callback_notifier._add_callback(event_type, callback, user_data) - - def _remove_callback(self, callback): - """Remove all notification callbacks to specified function.""" - self._callback_notifier._remove_callback(callback) diff --git a/openstack/common/plugin/plugin.py b/openstack/common/plugin/plugin.py deleted file mode 100644 index d2be0b3..0000000 --- a/openstack/common/plugin/plugin.py +++ /dev/null @@ -1,86 +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. - -from openstack.common import log as logging - - -LOG = logging.getLogger(__name__) - - -class Plugin(object): - """Defines an interface for adding functionality to an OpenStack service. - - A plugin interacts with a service via the following pathways: - - - An optional set of notifiers, managed by calling add_notifier() - or by overriding _notifiers() - - - A set of api extensions, managed via add_api_extension_descriptor() - - - Direct calls to service functions. - - - Whatever else the plugin wants to do on its own. - - This is the reference implementation. - """ - - # The following functions are provided as convenience methods - # for subclasses. Subclasses should call them but probably not - # override them. - def _add_api_extension_descriptor(self, descriptor): - """Subclass convenience method which adds an extension descriptor. - - Subclass constructors should call this method when - extending a project's REST interface. - - Note that once the api service has loaded, the - API extension set is more-or-less fixed, so - this should mainly be called by subclass constructors. - """ - self._api_extension_descriptors.append(descriptor) - - def _add_notifier(self, notifier): - """Subclass convenience method which adds a notifier. - - Notifier objects should implement the function notify(message). - Each notifier receives a notify() call whenever an openstack - service broadcasts a notification. - - Best to call this during construction. Notifiers are enumerated - and registered by the pluginmanager at plugin load time. - """ - self._notifiers.append(notifier) - - # The following methods are called by OpenStack services to query - # plugin features. Subclasses should probably not override these. - def _notifiers(self): - """Returns list of notifiers for this plugin.""" - return self._notifiers - - notifiers = property(_notifiers) - - def _api_extension_descriptors(self): - """Return a list of API extension descriptors. - - Called by a project API during its load sequence. - """ - return self._api_extension_descriptors - - api_extension_descriptors = property(_api_extension_descriptors) - - # Most plugins will override this: - def __init__(self, service_name): - self._notifiers = [] - self._api_extension_descriptors = [] diff --git a/openstack/common/plugin/pluginmanager.py b/openstack/common/plugin/pluginmanager.py deleted file mode 100644 index 3962447..0000000 --- a/openstack/common/plugin/pluginmanager.py +++ /dev/null @@ -1,78 +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 oslo.config import cfg - -from openstack.common.gettextutils import _ -from openstack.common import log as logging -from openstack.common.notifier import api as notifier_api - - -CONF = cfg.CONF -LOG = logging.getLogger(__name__) - - -class PluginManager(object): - """Manages plugin entrypoints and loading. - - For a service to implement this plugin interface for callback purposes: - - - Make use of the openstack-common notifier system - - Instantiate this manager in each process (passing in - project and service name) - - For an API service to extend itself using this plugin interface, - it needs to query the plugin_extension_factory provided by - the already-instantiated PluginManager. - """ - - def __init__(self, project_name, service_name): - """Construct Plugin Manager; load and initialize plugins. - - project_name (e.g. 'nova' or 'glance') is used - to construct the entry point that identifies plugins. - - The service_name (e.g. 'compute') is passed on to - each plugin as a raw string for it to do what it will. - """ - self._project_name = project_name - self._service_name = service_name - self.plugins = [] - - def load_plugins(self): - self.plugins = [] - - for entrypoint in pkg_resources.iter_entry_points('%s.plugin' % - self._project_name): - try: - pluginclass = entrypoint.load() - plugin = pluginclass(self._service_name) - self.plugins.append(plugin) - except Exception as exc: - LOG.error(_("Failed to load plugin %(plug)s: %(exc)s") % - {'plug': entrypoint, 'exc': exc}) - - # Register individual notifiers. - for plugin in self.plugins: - for notifier in plugin.notifiers: - notifier_api.add_driver(notifier) - - def plugin_extension_factory(self, ext_mgr): - for plugin in self.plugins: - descriptors = plugin.api_extension_descriptors - for descriptor in descriptors: - ext_mgr.load_extension(descriptor) diff --git a/openstack/common/rootwrap/filters.py b/openstack/common/rootwrap/filters.py index 0cc55ce..dfec412 100644 --- a/openstack/common/rootwrap/filters.py +++ b/openstack/common/rootwrap/filters.py @@ -235,3 +235,116 @@ class ReadFileFilter(CommandFilter): if len(userargs) != 2: return False return True + + +class IpFilter(CommandFilter): + """Specific filter for the ip utility to that does not match exec.""" + + def match(self, userargs): + if userargs[0] == 'ip': + if userargs[1] == 'netns': + return (userargs[2] in ('list', 'add', 'delete')) + else: + return True + + +class EnvFilter(CommandFilter): + """Specific filter for the env utility. + + Behaves like CommandFilter, except that it handles + leading env A=B.. strings appropriately. + """ + + def _extract_env(self, arglist): + """Extract all leading NAME=VALUE arguments from arglist.""" + + envs = set() + for arg in arglist: + if '=' not in arg: + break + envs.add(arg.partition('=')[0]) + return envs + + def __init__(self, exec_path, run_as, *args): + super(EnvFilter, self).__init__(exec_path, run_as, *args) + + env_list = self._extract_env(self.args) + # Set exec_path to X when args are in the form of + # env A=a B=b C=c X Y Z + if "env" in exec_path and len(env_list) < len(self.args): + self.exec_path = self.args[len(env_list)] + + def match(self, userargs): + # ignore leading 'env' + if userargs[0] == 'env': + userargs.pop(0) + + # require one additional argument after configured ones + if len(userargs) < len(self.args): + return False + + # extract all env args + user_envs = self._extract_env(userargs) + filter_envs = self._extract_env(self.args) + user_command = userargs[len(user_envs):len(user_envs) + 1] + + # match first non-env argument with CommandFilter + return (super(EnvFilter, self).match(user_command) + and len(filter_envs) and user_envs == filter_envs) + + def exec_args(self, userargs): + args = userargs[:] + + # ignore leading 'env' + if args[0] == 'env': + args.pop(0) + + # Throw away leading NAME=VALUE arguments + while args and '=' in args[0]: + args.pop(0) + + return args + + def get_command(self, userargs, exec_dirs=[]): + to_exec = self.get_exec(exec_dirs=exec_dirs) or self.exec_path + return [to_exec] + self.exec_args(userargs)[1:] + + def get_environment(self, userargs): + env = os.environ.copy() + + # ignore leading 'env' + if userargs[0] == 'env': + userargs.pop(0) + + # Handle leading NAME=VALUE pairs + for a in userargs: + env_name, equals, env_value = a.partition('=') + if not equals: + break + if env_name and env_value: + env[env_name] = env_value + + return env + + +class ChainingFilter(CommandFilter): + def exec_args(self, userargs): + return [] + + +class IpNetnsExecFilter(ChainingFilter): + """Specific filter for the ip utility to that does match exec.""" + + def match(self, userargs): + # Network namespaces currently require root + # require <ns> argument + if self.run_as != "root" or len(userargs) < 4: + return False + + return (userargs[:3] == ['ip', 'netns', 'exec']) + + def exec_args(self, userargs): + args = userargs[4:] + if args: + args[0] = os.path.basename(args[0]) + return args diff --git a/openstack/common/rootwrap/wrapper.py b/openstack/common/rootwrap/wrapper.py index 5390c1b..df1a9f4 100644 --- a/openstack/common/rootwrap/wrapper.py +++ b/openstack/common/rootwrap/wrapper.py @@ -131,6 +131,20 @@ def match_filter(filter_list, userargs, exec_dirs=[]): for f in filter_list: if f.match(userargs): + if isinstance(f, filters.ChainingFilter): + # This command calls exec verify that remaining args + # matches another filter. + def non_chain_filter(fltr): + return (fltr.run_as == f.run_as + and not isinstance(fltr, filters.ChainingFilter)) + + leaf_filters = [fltr for fltr in filter_list + if non_chain_filter(fltr)] + args = f.exec_args(userargs) + if (not args or not match_filter(leaf_filters, + args, exec_dirs=exec_dirs)): + continue + # Try other filters if executable is absent if not f.get_exec(exec_dirs=exec_dirs): if not first_not_executable_filter: diff --git a/openstack/common/rpc/amqp.py b/openstack/common/rpc/amqp.py index 1ab7ae4..c3e4e26 100644 --- a/openstack/common/rpc/amqp.py +++ b/openstack/common/rpc/amqp.py @@ -221,12 +221,7 @@ def msg_reply(conf, msg_id, reply_q, connection_pool, reply=None, failure = rpc_common.serialize_remote_exception(failure, log_failure) - try: - msg = {'result': reply, 'failure': failure} - except TypeError: - msg = {'result': dict((k, repr(v)) - for k, v in reply.__dict__.iteritems()), - 'failure': failure} + msg = {'result': reply, 'failure': failure} if ending: msg['ending'] = True _add_unique_id(msg) diff --git a/test-requirements.txt b/test-requirements.txt index a19b4af..7ffabfe 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,15 +1,11 @@ coverage +discover fixtures>=0.3.12 flake8==2.0 hacking>=0.5.3,<0.6 mock mox==0.5.3 mysql-python -nose -nose-exclude -nosexcover -openstack.nose_plugin -nosehtmloutput pep8==1.4.5 pyflakes==0.7.2 pylint @@ -17,5 +13,5 @@ pyzmq==2.2.0.1 redis setuptools-git>=0.4 sphinx +testrepository>=0.0.13 testtools>=0.9.22 -webtest diff --git a/tests/unit/db/sqlalchemy/test_sqlalchemy.py b/tests/unit/db/sqlalchemy/test_sqlalchemy.py index 46a0cb2..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,16 @@ 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', @@ -97,17 +97,16 @@ pool_timeout=7 'sqlalchemy_max_overflow=101\n' 'sqlalchemy_pool_timeout=5\n' )]) - - test_utils.CONF(['--config-file', paths[0]]) - self.assertEquals(test_utils.CONF.database.connection, + self.conf(['--config-file', paths[0]]) + self.assertEquals(self.conf.database.connection, 'fake_connection') - self.assertEquals(test_utils.CONF.database.idle_timeout, 100) - self.assertEquals(test_utils.CONF.database.min_pool_size, 99) - self.assertEquals(test_utils.CONF.database.max_pool_size, 199) - self.assertEquals(test_utils.CONF.database.max_retries, 22) - self.assertEquals(test_utils.CONF.database.retry_interval, 17) - self.assertEquals(test_utils.CONF.database.max_overflow, 101) - self.assertEquals(test_utils.CONF.database.pool_timeout, 5) + 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 2476008..54f8389 100644 --- a/tests/unit/rpc/test_kombu.py +++ b/tests/unit/rpc/test_kombu.py @@ -71,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 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_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) @@ -2,26 +2,22 @@ envlist = py26,py27,py33,pep8,pylint [testenv] +sitepackages = False setenv = VIRTUAL_ENV={envdir} - NOSE_WITH_OPENSTACK=1 - NOSE_OPENSTACK_COLOR=1 - NOSE_OPENSTACK_RED=0.05 - NOSE_OPENSTACK_YELLOW=0.025 - NOSE_OPENSTACK_SHOW_ELAPSED=1 - NOSE_OPENSTACK_STDOUT=1 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python tools/patch_tox_venv.py - nosetests --with-doctest --exclude-dir=tests/testmods {posargs} + # due to dependencies between tests (bug 1192207) we use `--concurrency=1` option + python setup.py testr --slowest --testr-args='--subunit --concurrency=1 {posargs}' [flake8] show-source = True -ignore = H202,H302,H304 +ignore = H302,H304 exclude = .venv,.tox,dist,doc,*.egg,.update-venv [testenv:pep8] -commands = flake8 +commands = flake8 {posargs} [testenv:pylint] deps = pylint>=0.26.0 @@ -30,7 +26,9 @@ commands = python ./tools/lint.py ./openstack [testenv:cover] setenv = VIRTUAL_ENV={envdir} - NOSE_WITH_COVERAGE=1 +commands = + python tools/patch_tox_venv.py + python setup.py testr --coverage --testr-args='{posargs}' [testenv:venv] commands = {posargs} |