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
This commit is contained in:
Ihar Hrachyshka 2015-08-06 16:59:53 +02:00
parent f58d14ca02
commit d148e68b71
8 changed files with 117 additions and 68 deletions

View File

@ -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

View File

View 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
"""

View File

@ -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 {}

View File

@ -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))

View File

@ -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'])

View 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)