From d148e68b71852f5cc0994a9137975ecf5393fb92 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Thu, 6 Aug 2015 16:59:53 +0200 Subject: [PATCH] Introduce base interface for core resource extensions The interface can be found in neutron.core_extensions.base. Adopted the interface in qos core resource extension. Alos moved qos_extension under neutron.core_extensions.qos. Partially, this is to avoid confusion around the fact that the module does not really contain a neutron API extension but core resource extension. Change-Id: I6f6976aa49694f7ef17afa4e93bc769cd0069f65 Partially-Implements: blueprint quantum-qos-api --- doc/source/devref/quality_of_service.rst | 23 +++--- neutron/core_extensions/__init__.py | 0 neutron/core_extensions/base.py | 48 ++++++++++++ .../qos.py} | 13 ++-- neutron/plugins/ml2/extensions/qos.py | 20 ++--- neutron/tests/api/test_qos.py | 4 +- .../tests/unit/core_extensions/__init__.py | 0 .../test_qos.py} | 77 ++++++++++--------- 8 files changed, 117 insertions(+), 68 deletions(-) create mode 100644 neutron/core_extensions/__init__.py create mode 100644 neutron/core_extensions/base.py rename neutron/{services/qos/qos_extension.py => core_extensions/qos.py} (91%) create mode 100644 neutron/tests/unit/core_extensions/__init__.py rename neutron/tests/unit/{services/qos/test_qos_extension.py => core_extensions/test_qos.py} (67%) diff --git a/doc/source/devref/quality_of_service.rst b/doc/source/devref/quality_of_service.rst index 023eb42f6ea..5895122f799 100644 --- a/doc/source/devref/quality_of_service.rst +++ b/doc/source/devref/quality_of_service.rst @@ -35,19 +35,22 @@ Service side design MQ-based reference notification driver which updates agents via messaging bus, using `RPC callbacks `_. -* neutron.services.qos.qos_extension: - Contains a class that can be used by external code to extend core - (network/port) resources with QoS details (at the moment, it's just - qos_policy_id). This class is designed in a way that should allow its - integration into different plugins. Alternatively, we may want to have a core - resource extension manager that would utilize it, among other extensions, and - that could be easily integrated into plugins. +* neutron.core_extensions.base: + Contains an interface class to implement core resource (port/network) + extensions. Core resource extensions are then easily integrated into + interested plugins. We may need to have a core resource extension manager + that would utilize those extensions, to avoid plugin modifications for every + new core resource extension. + +* neutron.core_extensions.qos: + Contains QoS core resource extension that conforms to the interface described + above. * neutron.plugins.ml2.extensions.qos: Contains ml2 extension driver that handles core resource updates by reusing - the qos_extension module mentioned above. In the future, we would like to see - a plugin-agnostic core resource extension manager that could be integrated - into other plugins with ease. + the core_extensions.qos module mentioned above. In the future, we would like + to see a plugin-agnostic core resource extension manager that could be + integrated into other plugins with ease. Supported QoS rule types diff --git a/neutron/core_extensions/__init__.py b/neutron/core_extensions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/core_extensions/base.py b/neutron/core_extensions/base.py new file mode 100644 index 00000000000..67cbf87e357 --- /dev/null +++ b/neutron/core_extensions/base.py @@ -0,0 +1,48 @@ +# Copyright (c) 2015 Red Hat 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 abc + +import six + + +NETWORK = 'network' +PORT = 'port' + + +CORE_RESOURCES = [NETWORK, PORT] + + +@six.add_metaclass(abc.ABCMeta) +class CoreResourceExtension(object): + + @abc.abstractmethod + def process_fields(self, context, resource_type, + requested_resource, actual_resource): + """Process extension fields. + + :param context: neutron api request context + :param resource_type: core resource type (one of CORE_RESOURCES) + :param requested_resource: resource dict that contains extension fields + :param actual_resource: actual resource dict known to plugin + """ + + @abc.abstractmethod + def extract_fields(self, resource_type, resource): + """Extract extension fields. + + :param resource_type: core resource type (one of CORE_RESOURCES) + :param resource: resource dict that contains extension fields + """ diff --git a/neutron/services/qos/qos_extension.py b/neutron/core_extensions/qos.py similarity index 91% rename from neutron/services/qos/qos_extension.py rename to neutron/core_extensions/qos.py index 77ae4220e06..76f5164e5ca 100644 --- a/neutron/services/qos/qos_extension.py +++ b/neutron/core_extensions/qos.py @@ -13,18 +13,15 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron.core_extensions import base from neutron.db import api as db_api from neutron import manager from neutron.objects.qos import policy as policy_object from neutron.plugins.common import constants as plugin_constants from neutron.services.qos import qos_consts -NETWORK = 'network' -PORT = 'port' - -# TODO(QoS): Add interface to define how this should look like -class QosResourceExtensionHandler(object): +class QosCoreResourceExtension(base.CoreResourceExtension): @property def plugin_loaded(self): @@ -70,15 +67,15 @@ class QosResourceExtensionHandler(object): with db_api.autonested_transaction(context.session): return getattr(self, method_name)(context=context, **kwargs) - def process_resource(self, context, resource_type, requested_resource, - actual_resource): + def process_fields(self, context, resource_type, + requested_resource, actual_resource): if (qos_consts.QOS_POLICY_ID in requested_resource and self.plugin_loaded): self._exec('_update_%s_policy' % resource_type, context, {resource_type: actual_resource, "%s_changes" % resource_type: requested_resource}) - def extract_resource_fields(self, resource_type, resource): + def extract_fields(self, resource_type, resource): if not self.plugin_loaded: return {} diff --git a/neutron/plugins/ml2/extensions/qos.py b/neutron/plugins/ml2/extensions/qos.py index a11b232c7ab..4de7cf653a7 100644 --- a/neutron/plugins/ml2/extensions/qos.py +++ b/neutron/plugins/ml2/extensions/qos.py @@ -15,8 +15,9 @@ from oslo_log import log as logging +from neutron.core_extensions import base as base_core +from neutron.core_extensions import qos as qos_core from neutron.plugins.ml2 import driver_api as api -from neutron.services.qos import qos_extension LOG = logging.getLogger(__name__) @@ -24,27 +25,26 @@ LOG = logging.getLogger(__name__) class QosExtensionDriver(api.ExtensionDriver): def initialize(self): - self.qos_ext_handler = qos_extension.QosResourceExtensionHandler() + self.core_ext_handler = qos_core.QosCoreResourceExtension() LOG.debug("QosExtensionDriver initialization complete") def process_create_network(self, context, data, result): - self.qos_ext_handler.process_resource( - context, qos_extension.NETWORK, data, result) + self.core_ext_handler.process_fields( + context, base_core.NETWORK, data, result) process_update_network = process_create_network def process_create_port(self, context, data, result): - self.qos_ext_handler.process_resource( - context, qos_extension.PORT, data, result) + self.core_ext_handler.process_fields( + context, base_core.PORT, data, result) process_update_port = process_create_port def extend_network_dict(self, session, db_data, result): result.update( - self.qos_ext_handler.extract_resource_fields(qos_extension.NETWORK, - db_data)) + self.core_ext_handler.extract_fields( + base_core.NETWORK, db_data)) def extend_port_dict(self, session, db_data, result): result.update( - self.qos_ext_handler.extract_resource_fields(qos_extension.PORT, - db_data)) + self.core_ext_handler.extract_fields(base_core.PORT, db_data)) diff --git a/neutron/tests/api/test_qos.py b/neutron/tests/api/test_qos.py index 453b85387ff..8c81d14699e 100644 --- a/neutron/tests/api/test_qos.py +++ b/neutron/tests/api/test_qos.py @@ -140,7 +140,7 @@ class QosTestJSON(base.BaseAdminNetworkTest): description='test policy', shared=False) #TODO(QoS): This currently raises an exception on the server side. See - # services/qos/qos_extension.py for comments on this subject. + # core_extensions/qos.py for comments on this subject. network = self.create_network('test network', qos_policy_id=policy['id']) @@ -193,7 +193,7 @@ class QosTestJSON(base.BaseAdminNetworkTest): shared=False) network = self.create_shared_network('test network') #TODO(QoS): This currently raises an exception on the server side. See - # services/qos/qos_extension.py for comments on this subject. + # core_extensions/qos.py for comments on this subject. port = self.create_port(network, qos_policy_id=policy['id']) retrieved_port = self.admin_client.show_port(port['id']) diff --git a/neutron/tests/unit/core_extensions/__init__.py b/neutron/tests/unit/core_extensions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/unit/services/qos/test_qos_extension.py b/neutron/tests/unit/core_extensions/test_qos.py similarity index 67% rename from neutron/tests/unit/services/qos/test_qos_extension.py rename to neutron/tests/unit/core_extensions/test_qos.py index 4252167ea7d..dddfc692f60 100644 --- a/neutron/tests/unit/services/qos/test_qos_extension.py +++ b/neutron/tests/unit/core_extensions/test_qos.py @@ -16,9 +16,10 @@ import mock from neutron import context +from neutron.core_extensions import base as base_core +from neutron.core_extensions import qos as qos_core from neutron.plugins.common import constants as plugin_constants from neutron.services.qos import qos_consts -from neutron.services.qos import qos_extension from neutron.tests import base @@ -27,18 +28,18 @@ def _get_test_dbdata(qos_policy_id): 'network_id': 'fake_net_id'}} -class QosResourceExtensionHandlerTestCase(base.BaseTestCase): +class QosCoreResourceExtensionTestCase(base.BaseTestCase): def setUp(self): - super(QosResourceExtensionHandlerTestCase, self).setUp() - self.ext_handler = qos_extension.QosResourceExtensionHandler() + super(QosCoreResourceExtensionTestCase, self).setUp() + self.core_extension = qos_core.QosCoreResourceExtension() policy_p = mock.patch('neutron.objects.qos.policy.QosPolicy') self.policy_m = policy_p.start() self.context = context.get_admin_context() - def test_process_resource_no_qos_policy_id(self): - self.ext_handler.process_resource( - self.context, qos_extension.PORT, {}, None) + def test_process_fields_no_qos_policy_id(self): + self.core_extension.process_fields( + self.context, base_core.PORT, {}, None) self.assertFalse(self.policy_m.called) def _mock_plugin_loaded(self, plugin_loaded): @@ -48,28 +49,28 @@ class QosResourceExtensionHandlerTestCase(base.BaseTestCase): return mock.patch('neutron.manager.NeutronManager.get_service_plugins', return_value=plugins) - def test_process_resource_no_qos_plugin_loaded(self): + def test_process_fields_no_qos_plugin_loaded(self): with self._mock_plugin_loaded(False): - self.ext_handler.process_resource( - self.context, qos_extension.PORT, + self.core_extension.process_fields( + self.context, base_core.PORT, {qos_consts.QOS_POLICY_ID: None}, None) self.assertFalse(self.policy_m.called) - def test_process_resource_port_new_policy(self): + def test_process_fields_port_new_policy(self): with self._mock_plugin_loaded(True): qos_policy_id = mock.Mock() actual_port = {'id': mock.Mock(), qos_consts.QOS_POLICY_ID: qos_policy_id} qos_policy = mock.MagicMock() self.policy_m.get_by_id = mock.Mock(return_value=qos_policy) - self.ext_handler.process_resource( - self.context, qos_extension.PORT, + self.core_extension.process_fields( + self.context, base_core.PORT, {qos_consts.QOS_POLICY_ID: qos_policy_id}, actual_port) qos_policy.attach_port.assert_called_once_with(actual_port['id']) - def test_process_resource_port_updated_policy(self): + def test_process_fields_port_updated_policy(self): with self._mock_plugin_loaded(True): qos_policy_id = mock.Mock() port_id = mock.Mock() @@ -80,29 +81,29 @@ class QosResourceExtensionHandlerTestCase(base.BaseTestCase): return_value=old_qos_policy) new_qos_policy = mock.MagicMock() self.policy_m.get_by_id = mock.Mock(return_value=new_qos_policy) - self.ext_handler.process_resource( - self.context, qos_extension.PORT, + self.core_extension.process_fields( + self.context, base_core.PORT, {qos_consts.QOS_POLICY_ID: qos_policy_id}, actual_port) old_qos_policy.detach_port.assert_called_once_with(port_id) new_qos_policy.attach_port.assert_called_once_with(port_id) - def test_process_resource_network_new_policy(self): + def test_process_fields_network_new_policy(self): with self._mock_plugin_loaded(True): qos_policy_id = mock.Mock() actual_network = {'id': mock.Mock(), qos_consts.QOS_POLICY_ID: qos_policy_id} qos_policy = mock.MagicMock() self.policy_m.get_by_id = mock.Mock(return_value=qos_policy) - self.ext_handler.process_resource( - self.context, qos_extension.NETWORK, + self.core_extension.process_fields( + self.context, base_core.NETWORK, {qos_consts.QOS_POLICY_ID: qos_policy_id}, actual_network) qos_policy.attach_network.assert_called_once_with( actual_network['id']) - def test_process_resource_network_updated_policy(self): + def test_process_fields_network_updated_policy(self): with self._mock_plugin_loaded(True): qos_policy_id = mock.Mock() network_id = mock.Mock() @@ -113,42 +114,42 @@ class QosResourceExtensionHandlerTestCase(base.BaseTestCase): return_value=old_qos_policy) new_qos_policy = mock.MagicMock() self.policy_m.get_by_id = mock.Mock(return_value=new_qos_policy) - self.ext_handler.process_resource( - self.context, qos_extension.NETWORK, + self.core_extension.process_fields( + self.context, base_core.NETWORK, {qos_consts.QOS_POLICY_ID: qos_policy_id}, actual_network) old_qos_policy.detach_network.assert_called_once_with(network_id) new_qos_policy.attach_network.assert_called_once_with(network_id) - def test_extract_resource_fields_plugin_not_loaded(self): + def test_extract_fields_plugin_not_loaded(self): with self._mock_plugin_loaded(False): - fields = self.ext_handler.extract_resource_fields(None, None) + fields = self.core_extension.extract_fields(None, None) self.assertEqual({}, fields) - def _test_extract_resource_fields_for_port(self, qos_policy_id): + def _test_extract_fields_for_port(self, qos_policy_id): with self._mock_plugin_loaded(True): - fields = self.ext_handler.extract_resource_fields( - qos_extension.PORT, _get_test_dbdata(qos_policy_id)) + fields = self.core_extension.extract_fields( + base_core.PORT, _get_test_dbdata(qos_policy_id)) self.assertEqual({qos_consts.QOS_POLICY_ID: qos_policy_id}, fields) - def test_extract_resource_fields_no_port_policy(self): - self._test_extract_resource_fields_for_port(None) + def test_extract_fields_no_port_policy(self): + self._test_extract_fields_for_port(None) - def test_extract_resource_fields_port_policy_exists(self): + def test_extract_fields_port_policy_exists(self): qos_policy_id = mock.Mock() - self._test_extract_resource_fields_for_port(qos_policy_id) + self._test_extract_fields_for_port(qos_policy_id) - def _test_extract_resource_fields_for_network(self, qos_policy_id): + def _test_extract_fields_for_network(self, qos_policy_id): with self._mock_plugin_loaded(True): - fields = self.ext_handler.extract_resource_fields( - qos_extension.NETWORK, _get_test_dbdata(qos_policy_id)) + fields = self.core_extension.extract_fields( + base_core.NETWORK, _get_test_dbdata(qos_policy_id)) self.assertEqual({qos_consts.QOS_POLICY_ID: qos_policy_id}, fields) - def test_extract_resource_fields_no_network_policy(self): - self._test_extract_resource_fields_for_network(None) + def test_extract_fields_no_network_policy(self): + self._test_extract_fields_for_network(None) - def test_extract_resource_fields_network_policy_exists(self): + def test_extract_fields_network_policy_exists(self): qos_policy_id = mock.Mock() qos_policy = mock.Mock() qos_policy.id = qos_policy_id - self._test_extract_resource_fields_for_network(qos_policy_id) + self._test_extract_fields_for_network(qos_policy_id)