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 MQ-based reference notification driver which updates agents via messaging
bus, using `RPC callbacks <rpc_callbacks.html>`_. bus, using `RPC callbacks <rpc_callbacks.html>`_.
* neutron.services.qos.qos_extension: * neutron.core_extensions.base:
Contains a class that can be used by external code to extend core Contains an interface class to implement core resource (port/network)
(network/port) resources with QoS details (at the moment, it's just extensions. Core resource extensions are then easily integrated into
qos_policy_id). This class is designed in a way that should allow its interested plugins. We may need to have a core resource extension manager
integration into different plugins. Alternatively, we may want to have a core that would utilize those extensions, to avoid plugin modifications for every
resource extension manager that would utilize it, among other extensions, and new core resource extension.
that could be easily integrated into plugins.
* neutron.core_extensions.qos:
Contains QoS core resource extension that conforms to the interface described
above.
* neutron.plugins.ml2.extensions.qos: * neutron.plugins.ml2.extensions.qos:
Contains ml2 extension driver that handles core resource updates by reusing 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 the core_extensions.qos module mentioned above. In the future, we would like
a plugin-agnostic core resource extension manager that could be integrated to see a plugin-agnostic core resource extension manager that could be
into other plugins with ease. integrated into other plugins with ease.
Supported QoS rule types 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 # License for the specific language governing permissions and limitations
# under the License. # under the License.
from neutron.core_extensions import base
from neutron.db import api as db_api from neutron.db import api as db_api
from neutron import manager from neutron import manager
from neutron.objects.qos import policy as policy_object from neutron.objects.qos import policy as policy_object
from neutron.plugins.common import constants as plugin_constants from neutron.plugins.common import constants as plugin_constants
from neutron.services.qos import qos_consts from neutron.services.qos import qos_consts
NETWORK = 'network'
PORT = 'port'
class QosCoreResourceExtension(base.CoreResourceExtension):
# TODO(QoS): Add interface to define how this should look like
class QosResourceExtensionHandler(object):
@property @property
def plugin_loaded(self): def plugin_loaded(self):
@ -70,15 +67,15 @@ class QosResourceExtensionHandler(object):
with db_api.autonested_transaction(context.session): with db_api.autonested_transaction(context.session):
return getattr(self, method_name)(context=context, **kwargs) return getattr(self, method_name)(context=context, **kwargs)
def process_resource(self, context, resource_type, requested_resource, def process_fields(self, context, resource_type,
actual_resource): requested_resource, actual_resource):
if (qos_consts.QOS_POLICY_ID in requested_resource and if (qos_consts.QOS_POLICY_ID in requested_resource and
self.plugin_loaded): self.plugin_loaded):
self._exec('_update_%s_policy' % resource_type, context, self._exec('_update_%s_policy' % resource_type, context,
{resource_type: actual_resource, {resource_type: actual_resource,
"%s_changes" % resource_type: requested_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: if not self.plugin_loaded:
return {} return {}

View File

@ -15,8 +15,9 @@
from oslo_log import log as logging 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.plugins.ml2 import driver_api as api
from neutron.services.qos import qos_extension
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -24,27 +25,26 @@ LOG = logging.getLogger(__name__)
class QosExtensionDriver(api.ExtensionDriver): class QosExtensionDriver(api.ExtensionDriver):
def initialize(self): def initialize(self):
self.qos_ext_handler = qos_extension.QosResourceExtensionHandler() self.core_ext_handler = qos_core.QosCoreResourceExtension()
LOG.debug("QosExtensionDriver initialization complete") LOG.debug("QosExtensionDriver initialization complete")
def process_create_network(self, context, data, result): def process_create_network(self, context, data, result):
self.qos_ext_handler.process_resource( self.core_ext_handler.process_fields(
context, qos_extension.NETWORK, data, result) context, base_core.NETWORK, data, result)
process_update_network = process_create_network process_update_network = process_create_network
def process_create_port(self, context, data, result): def process_create_port(self, context, data, result):
self.qos_ext_handler.process_resource( self.core_ext_handler.process_fields(
context, qos_extension.PORT, data, result) context, base_core.PORT, data, result)
process_update_port = process_create_port process_update_port = process_create_port
def extend_network_dict(self, session, db_data, result): def extend_network_dict(self, session, db_data, result):
result.update( result.update(
self.qos_ext_handler.extract_resource_fields(qos_extension.NETWORK, self.core_ext_handler.extract_fields(
db_data)) base_core.NETWORK, db_data))
def extend_port_dict(self, session, db_data, result): def extend_port_dict(self, session, db_data, result):
result.update( result.update(
self.qos_ext_handler.extract_resource_fields(qos_extension.PORT, self.core_ext_handler.extract_fields(base_core.PORT, db_data))
db_data))

View File

@ -140,7 +140,7 @@ class QosTestJSON(base.BaseAdminNetworkTest):
description='test policy', description='test policy',
shared=False) shared=False)
#TODO(QoS): This currently raises an exception on the server side. See #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', network = self.create_network('test network',
qos_policy_id=policy['id']) qos_policy_id=policy['id'])
@ -193,7 +193,7 @@ class QosTestJSON(base.BaseAdminNetworkTest):
shared=False) shared=False)
network = self.create_shared_network('test network') network = self.create_shared_network('test network')
#TODO(QoS): This currently raises an exception on the server side. See #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']) port = self.create_port(network, qos_policy_id=policy['id'])
retrieved_port = self.admin_client.show_port(port['id']) retrieved_port = self.admin_client.show_port(port['id'])

View File

@ -16,9 +16,10 @@
import mock import mock
from neutron import context 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.plugins.common import constants as plugin_constants
from neutron.services.qos import qos_consts from neutron.services.qos import qos_consts
from neutron.services.qos import qos_extension
from neutron.tests import base from neutron.tests import base
@ -27,18 +28,18 @@ def _get_test_dbdata(qos_policy_id):
'network_id': 'fake_net_id'}} 'network_id': 'fake_net_id'}}
class QosResourceExtensionHandlerTestCase(base.BaseTestCase): class QosCoreResourceExtensionTestCase(base.BaseTestCase):
def setUp(self): def setUp(self):
super(QosResourceExtensionHandlerTestCase, self).setUp() super(QosCoreResourceExtensionTestCase, self).setUp()
self.ext_handler = qos_extension.QosResourceExtensionHandler() self.core_extension = qos_core.QosCoreResourceExtension()
policy_p = mock.patch('neutron.objects.qos.policy.QosPolicy') policy_p = mock.patch('neutron.objects.qos.policy.QosPolicy')
self.policy_m = policy_p.start() self.policy_m = policy_p.start()
self.context = context.get_admin_context() self.context = context.get_admin_context()
def test_process_resource_no_qos_policy_id(self): def test_process_fields_no_qos_policy_id(self):
self.ext_handler.process_resource( self.core_extension.process_fields(
self.context, qos_extension.PORT, {}, None) self.context, base_core.PORT, {}, None)
self.assertFalse(self.policy_m.called) self.assertFalse(self.policy_m.called)
def _mock_plugin_loaded(self, plugin_loaded): 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 mock.patch('neutron.manager.NeutronManager.get_service_plugins',
return_value=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): with self._mock_plugin_loaded(False):
self.ext_handler.process_resource( self.core_extension.process_fields(
self.context, qos_extension.PORT, self.context, base_core.PORT,
{qos_consts.QOS_POLICY_ID: None}, None) {qos_consts.QOS_POLICY_ID: None}, None)
self.assertFalse(self.policy_m.called) 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): with self._mock_plugin_loaded(True):
qos_policy_id = mock.Mock() qos_policy_id = mock.Mock()
actual_port = {'id': mock.Mock(), actual_port = {'id': mock.Mock(),
qos_consts.QOS_POLICY_ID: qos_policy_id} qos_consts.QOS_POLICY_ID: qos_policy_id}
qos_policy = mock.MagicMock() qos_policy = mock.MagicMock()
self.policy_m.get_by_id = mock.Mock(return_value=qos_policy) self.policy_m.get_by_id = mock.Mock(return_value=qos_policy)
self.ext_handler.process_resource( self.core_extension.process_fields(
self.context, qos_extension.PORT, self.context, base_core.PORT,
{qos_consts.QOS_POLICY_ID: qos_policy_id}, {qos_consts.QOS_POLICY_ID: qos_policy_id},
actual_port) actual_port)
qos_policy.attach_port.assert_called_once_with(actual_port['id']) 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): with self._mock_plugin_loaded(True):
qos_policy_id = mock.Mock() qos_policy_id = mock.Mock()
port_id = mock.Mock() port_id = mock.Mock()
@ -80,29 +81,29 @@ class QosResourceExtensionHandlerTestCase(base.BaseTestCase):
return_value=old_qos_policy) return_value=old_qos_policy)
new_qos_policy = mock.MagicMock() new_qos_policy = mock.MagicMock()
self.policy_m.get_by_id = mock.Mock(return_value=new_qos_policy) self.policy_m.get_by_id = mock.Mock(return_value=new_qos_policy)
self.ext_handler.process_resource( self.core_extension.process_fields(
self.context, qos_extension.PORT, self.context, base_core.PORT,
{qos_consts.QOS_POLICY_ID: qos_policy_id}, {qos_consts.QOS_POLICY_ID: qos_policy_id},
actual_port) actual_port)
old_qos_policy.detach_port.assert_called_once_with(port_id) old_qos_policy.detach_port.assert_called_once_with(port_id)
new_qos_policy.attach_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): with self._mock_plugin_loaded(True):
qos_policy_id = mock.Mock() qos_policy_id = mock.Mock()
actual_network = {'id': mock.Mock(), actual_network = {'id': mock.Mock(),
qos_consts.QOS_POLICY_ID: qos_policy_id} qos_consts.QOS_POLICY_ID: qos_policy_id}
qos_policy = mock.MagicMock() qos_policy = mock.MagicMock()
self.policy_m.get_by_id = mock.Mock(return_value=qos_policy) self.policy_m.get_by_id = mock.Mock(return_value=qos_policy)
self.ext_handler.process_resource( self.core_extension.process_fields(
self.context, qos_extension.NETWORK, self.context, base_core.NETWORK,
{qos_consts.QOS_POLICY_ID: qos_policy_id}, actual_network) {qos_consts.QOS_POLICY_ID: qos_policy_id}, actual_network)
qos_policy.attach_network.assert_called_once_with( qos_policy.attach_network.assert_called_once_with(
actual_network['id']) 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): with self._mock_plugin_loaded(True):
qos_policy_id = mock.Mock() qos_policy_id = mock.Mock()
network_id = mock.Mock() network_id = mock.Mock()
@ -113,42 +114,42 @@ class QosResourceExtensionHandlerTestCase(base.BaseTestCase):
return_value=old_qos_policy) return_value=old_qos_policy)
new_qos_policy = mock.MagicMock() new_qos_policy = mock.MagicMock()
self.policy_m.get_by_id = mock.Mock(return_value=new_qos_policy) self.policy_m.get_by_id = mock.Mock(return_value=new_qos_policy)
self.ext_handler.process_resource( self.core_extension.process_fields(
self.context, qos_extension.NETWORK, self.context, base_core.NETWORK,
{qos_consts.QOS_POLICY_ID: qos_policy_id}, actual_network) {qos_consts.QOS_POLICY_ID: qos_policy_id}, actual_network)
old_qos_policy.detach_network.assert_called_once_with(network_id) old_qos_policy.detach_network.assert_called_once_with(network_id)
new_qos_policy.attach_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): 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) 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): with self._mock_plugin_loaded(True):
fields = self.ext_handler.extract_resource_fields( fields = self.core_extension.extract_fields(
qos_extension.PORT, _get_test_dbdata(qos_policy_id)) base_core.PORT, _get_test_dbdata(qos_policy_id))
self.assertEqual({qos_consts.QOS_POLICY_ID: qos_policy_id}, fields) self.assertEqual({qos_consts.QOS_POLICY_ID: qos_policy_id}, fields)
def test_extract_resource_fields_no_port_policy(self): def test_extract_fields_no_port_policy(self):
self._test_extract_resource_fields_for_port(None) 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() 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): with self._mock_plugin_loaded(True):
fields = self.ext_handler.extract_resource_fields( fields = self.core_extension.extract_fields(
qos_extension.NETWORK, _get_test_dbdata(qos_policy_id)) base_core.NETWORK, _get_test_dbdata(qos_policy_id))
self.assertEqual({qos_consts.QOS_POLICY_ID: qos_policy_id}, fields) self.assertEqual({qos_consts.QOS_POLICY_ID: qos_policy_id}, fields)
def test_extract_resource_fields_no_network_policy(self): def test_extract_fields_no_network_policy(self):
self._test_extract_resource_fields_for_network(None) 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_id = mock.Mock()
qos_policy = mock.Mock() qos_policy = mock.Mock()
qos_policy.id = qos_policy_id 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)