diff --git a/neutron_lib/tests/unit/utils/test_runtime.py b/neutron_lib/tests/unit/utils/test_runtime.py new file mode 100644 index 000000000..f8b71aac8 --- /dev/null +++ b/neutron_lib/tests/unit/utils/test_runtime.py @@ -0,0 +1,53 @@ +# 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 mock + +from neutron_lib.tests import _base as base +from neutron_lib.utils import runtime + + +class _DummyDriver(object): + driver = mock.sentinel.dummy_driver + + +class TestRunTime(base.BaseTestCase): + + @mock.patch.object(runtime, 'LOG') + def test_load_class_by_alias_or_classname_no_name(self, mock_log): + self.assertRaises( + ImportError, + runtime.load_class_by_alias_or_classname, 'ns', None) + + @mock.patch.object(runtime.driver, 'DriverManager', + return_value=_DummyDriver) + @mock.patch.object(runtime, 'LOG') + def test_load_class_by_alias_or_classname_dummy_driver( + self, mock_log, mock_driver): + self.assertEqual(_DummyDriver.driver, + runtime.load_class_by_alias_or_classname('ns', 'n')) + + @mock.patch.object(runtime, 'LOG') + def test_load_class_by_alias_or_classname_bad_classname(self, mock_log): + self.assertRaises( + ImportError, + runtime.load_class_by_alias_or_classname, 'ns', '_NoClass') + + @mock.patch.object(runtime.importutils, 'import_class', + return_value=mock.sentinel.dummy_class) + @mock.patch.object(runtime, 'LOG') + def test_load_class_by_alias_or_classname_with_classname( + self, mock_log, mock_import): + self.assertEqual( + mock.sentinel.dummy_class, + runtime.load_class_by_alias_or_classname('ns', 'n')) diff --git a/neutron_lib/utils/runtime.py b/neutron_lib/utils/runtime.py new file mode 100644 index 000000000..ea9187425 --- /dev/null +++ b/neutron_lib/utils/runtime.py @@ -0,0 +1,59 @@ +# 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 sys + +from oslo_concurrency import lockutils +from oslo_log import log as logging +from oslo_utils import importutils +from stevedore import driver + +from neutron_lib._i18n import _ + +LOG = logging.getLogger(__name__) +SYNCHRONIZED_PREFIX = 'neutron-' + + +# common synchronization decorator +synchronized = lockutils.synchronized_with_prefix(SYNCHRONIZED_PREFIX) + + +def load_class_by_alias_or_classname(namespace, name): + """Load a class using stevedore alias or the class name. + + :param namespace: The namespace where the alias is defined. + :param name: The alias or class name of the class to be loaded. + :returns: Class if it can be loaded. + :raises ImportError if class cannot be loaded. + """ + + if not name: + LOG.error("Alias or class name is not set") + raise ImportError(_("Class not found.")) + try: + # Try to resolve class by alias + mgr = driver.DriverManager( + namespace, name, warn_on_missing_entrypoint=False) + class_to_load = mgr.driver + except RuntimeError: + e1_info = sys.exc_info() + # Fallback to class name + try: + class_to_load = importutils.import_class(name) + except (ImportError, ValueError): + LOG.error("Error loading class by alias", + exc_info=e1_info) + LOG.error("Error loading class by class name", + exc_info=True) + raise ImportError(_("Class not found.")) + return class_to_load diff --git a/releasenotes/notes/rehome-runtime-utils-acb4451326cbe4d9.yaml b/releasenotes/notes/rehome-runtime-utils-acb4451326cbe4d9.yaml new file mode 100644 index 000000000..4c2edfe21 --- /dev/null +++ b/releasenotes/notes/rehome-runtime-utils-acb4451326cbe4d9.yaml @@ -0,0 +1,6 @@ +--- +features: + - The ``load_class_by_alias_or_classname`` function from + ``neutron.common.utils`` is now available in ``neutron_lib.utils.runtime``. + - The ``synchronized`` decorator from ``neutron.common.utils`` is now + available in ``neutron_lib.utils.runtime``. diff --git a/requirements.txt b/requirements.txt index 7f897d09b..922ccff7b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT debtcollector>=1.2.0 # Apache-2.0 +stevedore>=1.20.0 # Apache-2.0 oslo.concurrency>=3.8.0 # Apache-2.0 oslo.config>=3.22.0 # Apache-2.0 oslo.context>=2.12.0 # Apache-2.0