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:
parent
b6550e568d
commit
636f1b5394
@ -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}/',
|
||||
},
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
37
neutron/extensions/qos_rules_alias.py
Normal file
37
neutron/extensions/qos_rules_alias.py
Normal 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)
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user