Merge "Introduce base interface for core resource extensions" into feature/qos
This commit is contained in:
commit
8d7bfdf39d
@ -35,19 +35,22 @@ Service side design
|
||||
MQ-based reference notification driver which updates agents via messaging
|
||||
bus, using `RPC callbacks <rpc_callbacks.html>`_.
|
||||
|
||||
* 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
|
||||
|
0
neutron/core_extensions/__init__.py
Normal file
0
neutron/core_extensions/__init__.py
Normal file
48
neutron/core_extensions/base.py
Normal file
48
neutron/core_extensions/base.py
Normal file
@ -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
|
||||
"""
|
@ -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 {}
|
||||
|
@ -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))
|
||||
|
@ -149,7 +149,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'])
|
||||
|
||||
@ -202,7 +202,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'])
|
||||
|
0
neutron/tests/unit/core_extensions/__init__.py
Normal file
0
neutron/tests/unit/core_extensions/__init__.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user