Define qos-rules-alias extension

This patch adds qos-rules-alias extension to enable users to perform
GET, PUT and DELETE operations on QoS rules as though they are first
level resources. In other words, the user doesn't have to specify the
QoS policy ID.

Change-Id: Ia7535d83e3ae874106e22652dfd97bd9250ad37b
Partial-Bug: #1777627
This commit is contained in:
Miguel Lavalle 2018-10-28 20:12:29 -05:00 committed by Slawek Kaplonski
parent b6550e568d
commit 636f1b5394
6 changed files with 380 additions and 1 deletions

View File

@ -236,6 +236,105 @@ rules = [
},
]
),
policy.DocumentedRuleDefault(
'get_alias_bandwidth_limit_rule',
'rule:get_policy_bandwidth_limit_rule',
'Get a QoS bandwidth limit rule through alias',
[
{
'method': 'GET',
'path': '/qos/alias_bandwidth_limit_rules/{rule_id}/',
},
]
),
policy.DocumentedRuleDefault(
'update_alias_bandwidth_limit_rule',
'rule:update_policy_bandwidth_limit_rule',
'Update a QoS bandwidth limit rule through alias',
[
{
'method': 'PUT',
'path': '/qos/alias_bandwidth_limit_rules/{rule_id}/',
},
]
),
policy.DocumentedRuleDefault(
'delete_alias_bandwidth_limit_rule',
'rule:delete_policy_bandwidth_limit_rule',
'Delete a QoS bandwidth limit rule through alias',
[
{
'method': 'DELETE',
'path': '/qos/alias_bandwidth_limit_rules/{rule_id}/',
},
]
),
policy.DocumentedRuleDefault(
'get_alias_dscp_marking_rule',
'rule:get_policy_dscp_marking_rule',
'Get a QoS DSCP marking rule through alias',
[
{
'method': 'GET',
'path': '/qos/alias_dscp_marking_rules/{rule_id}/',
},
]
),
policy.DocumentedRuleDefault(
'update_alias_dscp_marking_rule',
'rule:update_policy_dscp_marking_rule',
'Update a QoS DSCP marking rule through alias',
[
{
'method': 'PUT',
'path': '/qos/alias_dscp_marking_rules/{rule_id}/',
},
]
),
policy.DocumentedRuleDefault(
'delete_alias_dscp_marking_rule',
'rule:delete_policy_dscp_marking_rule',
'Delete a QoS DSCP marking rule through alias',
[
{
'method': 'DELETE',
'path': '/qos/alias_dscp_marking_rules/{rule_id}/',
},
]
),
policy.DocumentedRuleDefault(
'get_alias_minimum_bandwidth_rule',
'rule:get_policy_minimum_bandwidth_rule',
'Get a QoS minimum bandwidth rule through alias',
[
{
'method': 'GET',
'path': '/qos/alias_minimum_bandwidth_rules/{rule_id}/',
},
]
),
policy.DocumentedRuleDefault(
'update_alias_minimum_bandwidth_rule',
'rule:update_policy_minimum_bandwidth_rule',
'Update a QoS minimum bandwidth rule through alias',
[
{
'method': 'PUT',
'path': '/qos/alias_minimum_bandwidth_rules/{rule_id}/',
},
]
),
policy.DocumentedRuleDefault(
'delete_alias_minimum_bandwidth_rule',
'rule:delete_policy_minimum_bandwidth_rule',
'Delete a QoS minimum bandwidth rule through alias',
[
{
'method': 'DELETE',
'path': '/qos/alias_minimum_bandwidth_rules/{rule_id}/',
},
]
),
]

View File

@ -97,6 +97,9 @@ class QoSPluginBase(service_base.ServicePluginBase):
r"^((create|update|delete)_policy_(?P<rule_type>.*)_rule)$"),
re.compile(
r"^(get_policy_(?P<rule_type>.*)_(rules|rule))$"),
# The following entry handles rule alias calls
re.compile(
r"^((update|delete|get)_alias_(?P<rule_type>.*)_rule)$"),
]
def __getattr__(self, attrib):
@ -106,6 +109,10 @@ class QoSPluginBase(service_base.ServicePluginBase):
update_policy_rule method will handle requests for both
update_policy_dscp_marking_rule and update_policy_bandwidth_limit_rule.
In the case of rule alias calls, the update_rule method will handle
requests for both update_dscp_marking_rule and
update_bandwidth_limit_rule.
:param attrib: the requested method; in the normal case, this will be,
for example, "update_policy_dscp_marking_rule"
:type attrib: str
@ -121,6 +128,7 @@ class QoSPluginBase(service_base.ServicePluginBase):
# from "delete_policy_dscp_marking_rule" we'll get
# "delete_policy_rule".
proxy_method = attrib.replace(rule_type + '_', '')
proxy_method = proxy_method.replace('alias_', '')
rule_cls = self.rule_objects[rule_type]
return self._call_proxy_method(proxy_method, rule_cls)
@ -163,8 +171,11 @@ class QoSPluginBase(service_base.ServicePluginBase):
args_list = list(args[1:])
params = kwargs
rule_data_name = rule_cls.rule_type + "_rule"
alias_rule_data_name = 'alias_' + rule_data_name
if rule_data_name in params:
params['rule_data'] = params.pop(rule_data_name)
elif alias_rule_data_name in params:
params['rule_data'] = params.pop(alias_rule_data_name)
return getattr(self, method_name)(
context, rule_cls, *args_list, **params
@ -233,3 +244,15 @@ class QoSPluginBase(service_base.ServicePluginBase):
filters=None, fields=None, sorts=None, limit=None,
marker=None, page_reverse=False):
pass
@abc.abstractmethod
def update_rule(self, context, rule_cls, rule_id, rule_data):
pass
@abc.abstractmethod
def delete_rule(self, context, rule_cls, rule_id):
pass
@abc.abstractmethod
def get_rule(self, context, rule_cls, rule_id, fields=None):
pass

View File

@ -0,0 +1,37 @@
# Copyright (c) 2018 Huawei Technology, 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.
from neutron_lib.api.definitions import qos_rules_alias as apidef
from neutron_lib.api import extensions as api_extensions
from neutron_lib.plugins import constants
from neutron.api.v2 import resource_helper
class Qos_rules_alias(api_extensions.APIExtensionDescriptor):
"""Extension class supporting QoS rules alias API resources."""
api_definition = apidef
@classmethod
def get_resources(cls):
"""Returns Ext Resources."""
plural_mappings = resource_helper.build_plural_mappings(
{}, apidef.RESOURCE_ATTRIBUTE_MAP)
return resource_helper.build_resource_info(
plural_mappings,
apidef.RESOURCE_ATTRIBUTE_MAP,
constants.QOS,
translate_name=True,
allow_bulk=True)

View File

@ -18,6 +18,7 @@ from neutron_lib.api.definitions import port_resource_request
from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import qos as qos_apidef
from neutron_lib.api.definitions import qos_bw_minimum_ingress
from neutron_lib.api.definitions import qos_rules_alias
from neutron_lib.callbacks import events as callbacks_events
from neutron_lib.callbacks import registry as callbacks_registry
from neutron_lib.callbacks import resources as callbacks_resources
@ -56,7 +57,8 @@ class QoSPlugin(qos.QoSPluginBase):
'qos-default',
'qos-rule-type-details',
port_resource_request.ALIAS,
qos_bw_minimum_ingress.ALIAS]
qos_bw_minimum_ingress.ALIAS,
qos_rules_alias.ALIAS]
__native_pagination_support = True
__native_sorting_support = True
@ -453,6 +455,37 @@ class QoSPlugin(qos.QoSPluginBase):
return rule
def _get_policy_id(self, context, rule_cls, rule_id):
with db_api.autonested_transaction(context.session):
rule_object = rule_cls.get_object(context, id=rule_id)
if not rule_object:
raise qos_exc.QosRuleNotFound(policy_id="", rule_id=rule_id)
return rule_object.qos_policy_id
def update_rule(self, context, rule_cls, rule_id, rule_data):
"""Update a QoS policy rule alias. This method processes a QoS policy
rule update, where the rule is an API first level resource instead of a
subresource of a policy.
:param context: neutron api request context
:type context: neutron.context.Context
:param rule_cls: the rule object class
:type rule_cls: a class from the rule_object (qos.objects.rule) module
:param rule_id: the id of the QoS policy rule to update
:type rule_id: str uuid
:param rule_data: the new rule data to update
:type rule_data: dict
:returns: a QoS policy rule object
:raises: qos_exc.QosRuleNotFound
"""
policy_id = self._get_policy_id(context, rule_cls, rule_id)
rule_data_name = rule_cls.rule_type + '_rule'
alias_rule_data_name = 'alias_' + rule_data_name
rule_data[rule_data_name] = rule_data.pop(alias_rule_data_name)
return self.update_policy_rule(context, rule_cls, rule_id, policy_id,
rule_data)
def delete_policy_rule(self, context, rule_cls, rule_id, policy_id):
"""Delete a QoS policy rule.
@ -480,6 +513,24 @@ class QoSPlugin(qos.QoSPluginBase):
self.driver_manager.call(qos_consts.UPDATE_POLICY, context, policy)
def delete_rule(self, context, rule_cls, rule_id):
"""Delete a QoS policy rule alias. This method processes a QoS policy
rule delete, where the rule is an API first level resource instead of a
subresource of a policy.
:param context: neutron api request context
:type context: neutron.context.Context
:param rule_cls: the rule object class
:type rule_cls: a class from the rule_object (qos.objects.rule) module
:param rule_id: the id of the QosPolicy Rule to delete
:type rule_id: str uuid
:returns: None
:raises: qos_exc.QosRuleNotFound
"""
policy_id = self._get_policy_id(context, rule_cls, rule_id)
return self.delete_policy_rule(context, rule_cls, rule_id, policy_id)
@db_base_plugin_common.filter_fields
@db_base_plugin_common.convert_result_to_dict
def get_policy_rule(self, context, rule_cls, rule_id, policy_id,
@ -506,6 +557,24 @@ class QoSPlugin(qos.QoSPluginBase):
raise qos_exc.QosRuleNotFound(policy_id=policy_id, rule_id=rule_id)
return rule
def get_rule(self, context, rule_cls, rule_id, fields=None):
"""Get a QoS policy rule alias. This method processes a QoS policy
rule get, where the rule is an API first level resource instead of a
subresource of a policy
:param context: neutron api request context
:type context: neutron.context.Context
:param rule_cls: the rule object class
:type rule_cls: a class from the rule_object (qos.objects.rule) module
:param rule_id: the id of the QoS policy rule to get
:type rule_id: str uuid
:returns: a QoS policy rule object
:raises: qos_exc.QosRuleNotFound
"""
policy_id = self._get_policy_id(context, rule_cls, rule_id)
return self.get_policy_rule(context, rule_cls, rule_id, policy_id)
# TODO(QoS): enforce rule types when accessing rule objects
@db_base_plugin_common.filter_fields
@db_base_plugin_common.convert_result_to_dict

View File

@ -13,6 +13,7 @@
import copy
import mock
from neutron_lib.api.definitions import qos
from neutron_lib.callbacks import events
from neutron_lib import constants as lib_constants
from neutron_lib import context
@ -25,16 +26,20 @@ from neutron_lib.plugins import directory
from neutron_lib.services.qos import constants as qos_consts
from oslo_config import cfg
from oslo_utils import uuidutils
import webob.exc
from neutron.common import constants
from neutron.extensions import qos_rules_alias
from neutron import manager
from neutron.objects.qos import policy as policy_object
from neutron.objects.qos import rule as rule_object
from neutron.services.qos import qos_plugin
from neutron.tests.unit.db import test_db_base_plugin_v2
from neutron.tests.unit.services.qos import base
DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
SERVICE_PLUGIN_KLASS = 'neutron.services.qos.qos_plugin.QoSPlugin'
class TestQosPlugin(base.BaseQosTestCase):
@ -1054,3 +1059,140 @@ class TestQosPlugin(base.BaseQosTestCase):
self.assertLess(
action_index, mock_manager.mock_calls.index(driver_mock_call))
class QoSRuleAliasTestExtensionManager(object):
def get_resources(self):
return qos_rules_alias.Qos_rules_alias.get_resources()
def get_actions(self):
return []
def get_request_extensions(self):
return []
class TestQoSRuleAlias(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
def setUp(self):
# Remove MissingAuthPlugin exception from logs
self.patch_notifier = mock.patch(
'neutron.notifiers.batch_notifier.BatchNotifier._notify')
self.patch_notifier.start()
plugin = 'ml2'
service_plugins = {'qos_plugin_name': SERVICE_PLUGIN_KLASS}
ext_mgr = QoSRuleAliasTestExtensionManager()
super(TestQoSRuleAlias, self).setUp(plugin=plugin, ext_mgr=ext_mgr,
service_plugins=service_plugins)
self.qos_plugin = directory.get_plugin(plugins_constants.QOS)
self.ctxt = context.Context('fake_user', 'fake_tenant')
self.rule_objects = {
'bandwidth_limit': rule_object.QosBandwidthLimitRule,
'dscp_marking': rule_object.QosDscpMarkingRule,
'minimum_bandwidth': rule_object.QosMinimumBandwidthRule
}
self.qos_policy_id = uuidutils.generate_uuid()
self.rule_data = {
'bandwidth_limit_rule': {'max_kbps': 100,
'max_burst_kbps': 150},
'dscp_marking_rule': {'dscp_mark': 16},
'minimum_bandwidth_rule': {'min_kbps': 10}
}
def _update_rule(self, rule_type, rule_id, **kwargs):
data = {'alias_%s_rule' % rule_type: kwargs}
resource = '%s/alias-%s-rules' % (qos.ALIAS,
rule_type.replace('_', '-'))
request = self.new_update_request(resource, data, rule_id, self.fmt)
res = request.get_response(self.ext_api)
if res.status_int >= webob.exc.HTTPClientError.code:
raise webob.exc.HTTPClientError(code=res.status_int)
return self.deserialize(self.fmt, res)
def _show_rule(self, rule_type, rule_id):
resource = '%s/alias-%s-rules' % (qos.ALIAS,
rule_type.replace('_', '-'))
request = self.new_show_request(resource, rule_id, self.fmt)
res = request.get_response(self.ext_api)
if res.status_int >= webob.exc.HTTPClientError.code:
raise webob.exc.HTTPClientError(code=res.status_int)
return self.deserialize(self.fmt, res)
def _delete_rule(self, rule_type, rule_id):
resource = '%s/alias-%s-rules' % (qos.ALIAS,
rule_type.replace('_', '-'))
request = self.new_delete_request(resource, rule_id, self.fmt)
res = request.get_response(self.ext_api)
if res.status_int >= webob.exc.HTTPClientError.code:
raise webob.exc.HTTPClientError(code=res.status_int)
@mock.patch.object(qos_plugin.QoSPlugin, "update_policy_rule")
def test_update_rule(self, update_policy_rule_mock):
calls = []
for rule_type, rule_object_class in self.rule_objects.items():
rule_id = uuidutils.generate_uuid()
rule_data_name = '%s_rule' % rule_type
data = self.rule_data[rule_data_name]
rule = rule_object_class(self.ctxt, id=rule_id,
qos_policy_id=self.qos_policy_id,
**data)
with mock.patch(
'neutron.objects.qos.rule.QosRule.get_object',
return_value=rule
), mock.patch.object(self.qos_plugin, 'get_policy_rule',
return_value=rule.to_dict()):
self._update_rule(rule_type, rule_id, **data)
calls.append(mock.call(mock.ANY, rule_object_class, rule_id,
self.qos_policy_id, {rule_data_name: data}))
update_policy_rule_mock.assert_has_calls(calls, any_order=True)
@mock.patch.object(qos_plugin.QoSPlugin, "get_policy_rule")
def test_show_rule(self, get_policy_rule_mock):
calls = []
for rule_type, rule_object_class in self.rule_objects.items():
rule_id = uuidutils.generate_uuid()
rule_data_name = '%s_rule' % rule_type
data = self.rule_data[rule_data_name]
rule = rule_object_class(self.ctxt, id=rule_id,
qos_policy_id=self.qos_policy_id,
**data)
with mock.patch('neutron.objects.qos.rule.QosRule.get_object',
return_value=rule):
self._show_rule(rule_type, rule_id)
calls.append(mock.call(mock.ANY, rule_object_class, rule_id,
self.qos_policy_id))
get_policy_rule_mock.assert_has_calls(calls, any_order=True)
@mock.patch.object(qos_plugin.QoSPlugin, "delete_policy_rule")
def test_delete_rule(self, delete_policy_rule_mock):
calls = []
for rule_type, rule_object_class in self.rule_objects.items():
rule_id = uuidutils.generate_uuid()
rule_data_name = '%s_rule' % rule_type
data = self.rule_data[rule_data_name]
rule = rule_object_class(self.ctxt, id=rule_id,
qos_policy_id=self.qos_policy_id,
**data)
with mock.patch(
'neutron.objects.qos.rule.QosRule.get_object',
return_value=rule
), mock.patch.object(self.qos_plugin, 'get_policy_rule',
return_value=rule.to_dict()):
self._delete_rule(rule_type, rule_id)
calls.append(mock.call(mock.ANY, rule_object_class, rule_id,
self.qos_policy_id))
delete_policy_rule_mock.assert_has_calls(calls, any_order=True)
def test_show_non_existing_rule(self):
for rule_type, rule_object_class in self.rule_objects.items():
rule_id = uuidutils.generate_uuid()
with mock.patch('neutron.objects.qos.rule.QosRule.get_object',
return_value=None):
resource = '%s/alias-%s-rules' % (qos.ALIAS,
rule_type.replace('_', '-'))
request = self.new_show_request(resource, rule_id, self.fmt)
res = request.get_response(self.ext_api)
self.assertEqual(webob.exc.HTTPNotFound.code, res.status_int)

View File

@ -0,0 +1,9 @@
---
prelude: >
Support alias end points for rules in QoS API.
features:
- |
The ``qos-rules-alias`` API extension was implemented to enable users to
perform GET, PUT and DELETE operations on QoS rules as though they are
first level resources. In other words, the user doesn't have to specify the
QoS policy ID.