Merge "New API call to get details of supported QoS rule type"

This commit is contained in:
Jenkins 2017-07-12 20:21:59 +00:00 committed by Gerrit Code Review
commit bea5cd24a9
13 changed files with 395 additions and 6 deletions

View File

@ -165,3 +165,7 @@ PORT_BINDING_STATUS_ACTIVE = 'ACTIVE'
PORT_BINDING_STATUS_INACTIVE = 'INACTIVE'
PORT_BINDING_STATUSES = (PORT_BINDING_STATUS_ACTIVE,
PORT_BINDING_STATUS_INACTIVE)
# Possible types of values (e.g. in QoS rule types)
VALUES_TYPE_CHOICES = "choices"
VALUES_TYPE_RANGE = "range"

View File

@ -48,6 +48,8 @@ QOS_RULE_COMMON_FIELDS = {
'is_visible': True},
}
RULE_TYPES = "rule_types"
RESOURCE_ATTRIBUTE_MAP = {
COLLECTION_NAME: {
'id': {'allow_post': False, 'allow_put': False,
@ -66,7 +68,7 @@ RESOURCE_ATTRIBUTE_MAP = {
'is_visible': True},
'rules': {'allow_post': False, 'allow_put': False, 'is_visible': True},
},
'rule_types': {
RULE_TYPES: {
'type': {'allow_post': False, 'allow_put': False,
'is_visible': True}
}
@ -309,6 +311,10 @@ class QoSPluginBase(service_base.ServicePluginBase):
def get_plugin_type(cls):
return constants.QOS
@abc.abstractmethod
def get_rule_type(self, context, rule_type_name, fields=None):
pass
@abc.abstractmethod
def get_rule_types(self, context, filters=None, fields=None, sorts=None,
limit=None, marker=None, page_reverse=False):

View File

@ -0,0 +1,74 @@
# Copyright (c) 2017 OVH SAS
# 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 import extensions as api_extensions
from neutron.extensions import qos
# The name of the extension.
NAME = "Details of QoS rule types"
# The alias of the extension.
ALIAS = "qos-rule-type-details"
# The description of the extension.
DESCRIPTION = ("Expose details about QoS rule types supported by loaded "
"backend drivers")
# The list of required extensions.
REQUIRED_EXTENSIONS = [qos.ALIAS]
# The list of optional extensions.
OPTIONAL_EXTENSIONS = None
# The resource attribute map for the extension.
RESOURCE_ATTRIBUTE_MAP = {
qos.RULE_TYPES: {
'drivers': {'allow_post': False, 'allow_put': False,
'is_visible': True}
}
}
class Qos_rule_type_details(api_extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return NAME
@classmethod
def get_alias(cls):
return ALIAS
@classmethod
def get_description(cls):
return DESCRIPTION
@classmethod
def get_updated(cls):
return "2017-06-22T10:00:00-00:00"
def get_required_extensions(self):
return REQUIRED_EXTENSIONS or []
def get_optional_extensions(self):
return OPTIONAL_EXTENSIONS or []
def get_extended_resources(self, version):
if version == "2.0":
return RESOURCE_ATTRIBUTE_MAP
else:
return {}

View File

@ -13,10 +13,12 @@
from neutron.plugins.common import constants
from neutron_lib.plugins import directory
from oslo_log import log as logging
from oslo_utils import versionutils
from oslo_versionedobjects import base as obj_base
from oslo_versionedobjects import fields as obj_fields
from neutron.objects import base
from neutron.objects import common_types
from neutron.services.qos import qos_consts
LOG = logging.getLogger(__name__)
@ -35,12 +37,29 @@ class QosRuleType(base.NeutronObject):
# Version 1.0: Initial version
# Version 1.1: Added QosDscpMarkingRule
# Version 1.2: Added QosMinimumBandwidthRule
VERSION = '1.2'
# Version 1.3: Added drivers field
VERSION = '1.3'
fields = {
'type': RuleTypeField(),
'drivers': obj_fields.ListOfObjectsField(
'QosRuleTypeDriver', nullable=True)
}
synthetic_fields = ['drivers']
# we don't receive context because we don't need db access at all
@classmethod
def get_object(cls, rule_type_name, **kwargs):
plugin = directory.get_plugin(alias=constants.QOS)
drivers = plugin.supported_rule_type_details(rule_type_name)
drivers_obj = [QosRuleTypeDriver(
name=driver['name'],
supported_parameters=driver['supported_parameters'])
for driver in drivers]
return cls(type=rule_type_name, drivers=drivers_obj)
# we don't receive context because we don't need db access at all
@classmethod
def get_objects(cls, validate_filters=True, **kwargs):
@ -52,3 +71,29 @@ class QosRuleType(base.NeutronObject):
# TODO(ihrachys): apply filters to returned result
return [cls(type=type_) for type_ in rule_types]
def obj_make_compatible(self, primitive, target_version):
_target_version = versionutils.convert_version_to_tuple(target_version)
if _target_version < (1, 3):
primitive.pop('drivers', None)
@obj_base.VersionedObjectRegistry.register
class QosRuleTypeDriver(base.NeutronObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'name': obj_fields.StringField(),
'supported_parameters': common_types.ListOfDictOfMiscValuesField()
}
def to_dict(self):
return {
'name': self.name,
'supported_parameters': self.supported_parameters}
@classmethod
def get_objects(cls, context, **kwargs):
raise NotImplementedError()

View File

@ -20,6 +20,7 @@ from neutron.api.rpc.callbacks import events as rpc_events
from neutron.api.rpc.callbacks.producer import registry as rpc_registry
from neutron.api.rpc.callbacks import resources
from neutron.api.rpc.handlers import resources_rpc
from neutron.common import constants
from neutron.common import exceptions
from neutron.objects.qos import policy as policy_object
from neutron.services.qos import qos_consts
@ -83,6 +84,20 @@ class QosServiceDriverManager(object):
'driver': driver.name})
return False
@staticmethod
def _parse_parameter_values(parameter_values):
validator, possible_values = list(parameter_values.items())[0]
if validator == 'type:range':
parameter_values = {
"start": possible_values[0],
"end": possible_values[1]
}
parameter_type = constants.VALUES_TYPE_RANGE
elif validator == 'type:values':
parameter_values = possible_values
parameter_type = constants.VALUES_TYPE_CHOICES
return parameter_values, parameter_type
def call(self, method_name, *args, **kwargs):
"""Helper method for calling a method across all extension drivers."""
exc_list = []
@ -158,3 +173,27 @@ class QosServiceDriverManager(object):
LOG.debug("Supported QoS rule types "
"(common subset for all loaded QoS drivers): %s", rule_types)
return rule_types
def supported_rule_type_details(self, rule_type_name):
if not self._drivers:
return []
rule_type_drivers = []
for driver in self._drivers:
if rule_type_name in driver.supported_rules:
supported_parameters = []
rule_parameters = driver.supported_rules.get(rule_type_name)
for name, values in rule_parameters.items():
parameter_values, parameter_type = (
self._parse_parameter_values(values))
supported_parameters.append({
"parameter_name": name,
"parameter_values": parameter_values,
"parameter_type": parameter_type
})
rule_type_drivers.append({
"name": driver.name,
"supported_parameters": supported_parameters
})
return rule_type_drivers

View File

@ -16,6 +16,7 @@
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
from neutron_lib import exceptions as lib_exc
from neutron.common import exceptions as n_exc
from neutron.db import api as db_api
@ -40,7 +41,8 @@ class QoSPlugin(qos.QoSPluginBase):
"""
supported_extension_aliases = ['qos',
'qos-bw-limit-direction',
'qos-default']
'qos-default',
'qos-rule-type-details']
__native_pagination_support = True
__native_sorting_support = True
@ -264,6 +266,13 @@ class QoSPlugin(qos.QoSPluginBase):
return policy_object.QosPolicy.get_objects(context, _pager=pager,
**filters)
@db_base_plugin_common.filter_fields
@db_base_plugin_common.convert_result_to_dict
def get_rule_type(self, context, rule_type_name, fields=None):
if not context.is_admin:
raise lib_exc.NotAuthorized()
return rule_type_object.QosRuleType.get_object(rule_type_name)
@db_base_plugin_common.filter_fields
@db_base_plugin_common.convert_result_to_dict
def get_rule_types(self, context, filters=None, fields=None,
@ -273,6 +282,9 @@ class QoSPlugin(qos.QoSPluginBase):
filters = {}
return rule_type_object.QosRuleType.get_objects(**filters)
def supported_rule_type_details(self, rule_type_name):
return self.driver_manager.supported_rule_type_details(rule_type_name)
@property
def supported_rule_types(self):
return self.driver_manager.supported_rule_types

View File

@ -31,6 +31,12 @@ class QosTestJSON(base.BaseAdminNetworkTest):
required_extensions = ['qos']
@staticmethod
def _get_driver_details(rule_type_details, driver_name):
for driver in rule_type_details['drivers']:
if driver['name'] == driver_name:
return driver
@decorators.idempotent_id('108fbdf7-3463-4e47-9871-d07f3dcf5bbb')
def test_create_policy(self):
policy = self.create_qos_policy(name='test-policy',
@ -172,6 +178,31 @@ class QosTestJSON(base.BaseAdminNetworkTest):
for rule in actual_list_rule_types:
self.assertEqual(tuple(expected_rule_keys), tuple(rule.keys()))
@decorators.idempotent_id('8ececa21-ef97-4904-a152-9f04c90f484d')
def test_show_rule_type_details_as_user(self):
self.assertRaises(
exceptions.Forbidden,
self.client.show_qos_rule_type,
qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
@decorators.idempotent_id('d0a2460b-7325-481f-a531-050bd96ab25e')
def test_show_rule_type_details_as_admin(self):
# Since returned rule types depend on loaded backend drivers this test
# is checking only if returned keys are same as expected keys
# In theory, we could make the test conditional on which ml2 drivers
# are enabled in gate, but that option doesn't seem to be
# available through tempest.lib framework
expected_rule_type_details_keys = ['type', 'drivers']
rule_type_details = self.admin_client.show_qos_rule_type(
qos_consts.RULE_TYPE_BANDWIDTH_LIMIT).get("rule_type")
# Verify that only required fields present in rule details
self.assertEqual(
sorted(tuple(expected_rule_type_details_keys)),
sorted(tuple(rule_type_details.keys())))
def _disassociate_network(self, client, network_id):
updated_network = client.update_network(network_id,
qos_policy_id=None)

View File

@ -728,6 +728,14 @@ class NetworkClientJSON(service_client.RestClient):
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)
def show_qos_rule_type(self, rule_type_name):
uri = '%s/qos/rule-types/%s' % (
self.uri_prefix, rule_type_name)
resp, body = self.get(uri)
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)
def create_trunk(self, parent_port_id, subports,
tenant_id=None, name=None, admin_state_up=None,
description=None):

View File

@ -16,6 +16,7 @@
import mock
from oslo_config import cfg
from neutron.common import constants
from neutron import manager
from neutron.objects.qos import rule_type
from neutron.services.qos import qos_consts
@ -25,6 +26,22 @@ from neutron.tests import base as test_base
DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
DRIVER_SUPPORTED_PARAMETERS = [
{
'parameter_name': qos_consts.MAX_KBPS,
'parameter_type': constants.VALUES_TYPE_RANGE,
'parameter_values': {"start": 0, "end": constants.DB_INTEGER_MAX_VALUE}
}, {
'parameter_name': qos_consts.MAX_BURST,
'parameter_type': constants.VALUES_TYPE_RANGE,
'parameter_values': {"start": 0, "end": constants.DB_INTEGER_MAX_VALUE}
}, {
'parameter_name': qos_consts.DIRECTION,
'parameter_type': constants.VALUES_TYPE_CHOICES,
'parameter_values': constants.VALID_DIRECTIONS
}
]
class QosRuleTypeObjectTestCase(test_base.BaseTestCase):
@ -37,6 +54,26 @@ class QosRuleTypeObjectTestCase(test_base.BaseTestCase):
cfg.CONF.set_override("service_plugins", ["qos"])
manager.init()
def test_get_object(self):
driver_details = {
'name': "backend_driver",
'supported_parameters': DRIVER_SUPPORTED_PARAMETERS
}
with mock.patch.object(
qos_plugin.QoSPlugin, 'supported_rule_type_details',
return_value=[driver_details]
):
rule_type_details = rule_type.QosRuleType.get_object(
qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
self.assertEqual(
driver_details['name'], rule_type_details.drivers[0].name)
self.assertEqual(
driver_details['supported_parameters'],
rule_type_details.drivers[0].supported_parameters)
self.assertEqual(1, len(rule_type_details.drivers))
self.assertEqual(
qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, rule_type_details.type)
def test_get_objects(self):
rule_types_mock = mock.PropertyMock(
return_value=set(qos_consts.VALID_RULE_TYPES))
@ -64,3 +101,14 @@ class QosRuleTypeObjectTestCase(test_base.BaseTestCase):
self.assertIn(qos_consts.RULE_TYPE_DSCP_MARKING,
tuple(rule_type_v1_1.fields['type'].AUTO_TYPE.
_valid_values))
def test_object_version_degradation_1_3_to_1_2(self):
drivers_obj = rule_type.QosRuleTypeDriver(
name="backend_driver", supported_parameters=[{}]
)
qos_rule_type = rule_type.QosRuleType(
type=qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, drivers=[drivers_obj])
rule_type_v1_2 = self._policy_through_version(qos_rule_type, '1.2')
self.assertNotIn("drivers", rule_type_v1_2)
self.assertIn("type", rule_type_v1_2)

View File

@ -66,7 +66,8 @@ object_data = {
'QosBandwidthLimitRule': '1.3-51b662b12a8d1dfa89288d826c6d26d3',
'QosDscpMarkingRule': '1.3-0313c6554b34fd10c753cb63d638256c',
'QosMinimumBandwidthRule': '1.3-314c3419f4799067cc31cc319080adff',
'QosRuleType': '1.2-e6fd08fcca152c339cbd5e9b94b1b8e7',
'QosRuleType': '1.3-7286188edeb3a0386f9cf7979b9700fc',
'QosRuleTypeDriver': '1.0-7d8cb9f0ef661ac03700eae97118e3db',
'QosPolicy': '1.6-4adb0cde3102c10d8970ec9487fd7fe7',
'QosPolicyDefault': '1.0-59e5060eedb1f06dd0935a244d27d11c',
'QosPolicyNetworkBinding': '1.0-df53a1e0f675aab8d27a1ccfed38dc42',

View File

@ -206,6 +206,89 @@ class TestQosDriversManagerRules(TestQosDriversManagerBase):
})
self.assertEqual(driver_manager.supported_rule_types, set([]))
def test__parse_parameter_values(self):
range_parameter = {'type:range': [0, 10]}
values_parameter = {'type:values': [1, 10, 100, 1000]}
expected_parsed_range_parameter = {'start': 0, 'end': 10}
expected_parsed_values_parameter = [1, 10, 100, 1000]
parameter_values, parameter_type = (
driver_mgr.QosServiceDriverManager._parse_parameter_values(
range_parameter))
self.assertEqual(
expected_parsed_range_parameter, parameter_values)
self.assertEqual(
constants.VALUES_TYPE_RANGE, parameter_type)
parameter_values, parameter_type = (
driver_mgr.QosServiceDriverManager._parse_parameter_values(
values_parameter))
self.assertEqual(
expected_parsed_values_parameter, parameter_values)
self.assertEqual(
constants.VALUES_TYPE_CHOICES, parameter_type)
def test_supported_rule_type_details(self):
driver_manager = self._create_manager_with_drivers({
'driver-A': {
'is_loaded': True,
'rules': {
qos_consts.RULE_TYPE_BANDWIDTH_LIMIT: {
"max_kbps": {'type:range': [0, 1000]},
"max_burst_kbps": {'type:range': [0, 1000]}
}
}
},
'driver-B': {
'is_loaded': True,
'rules': {
qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: {
"min_kbps": {'type:range': [0, 1000]},
'direction': {
'type:values': constants.VALID_DIRECTIONS}
},
qos_consts.RULE_TYPE_DSCP_MARKING: {
"dscp_mark": {
'type:values': constants.VALID_DSCP_MARKS}
}
}
}
})
expected_rule_type_details = [{
'name': 'driver-A',
'supported_parameters': [{
'parameter_name': 'max_kbps',
'parameter_type': constants.VALUES_TYPE_RANGE,
'parameter_values': {'start': 0, 'end': 1000}
}, {
'parameter_name': 'max_burst_kbps',
'parameter_type': constants.VALUES_TYPE_RANGE,
'parameter_values': {'start': 0, 'end': 1000}
}]
}]
bandwidth_limit_details = driver_manager.supported_rule_type_details(
qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
self.assertEqual(
len(expected_rule_type_details), len(bandwidth_limit_details))
self.assertEqual(
expected_rule_type_details[0]['name'],
bandwidth_limit_details[0]['name'])
self.assertEqual(
len(expected_rule_type_details[0]['supported_parameters']),
len(bandwidth_limit_details[0]['supported_parameters'])
)
for parameter in expected_rule_type_details[0]['supported_parameters']:
self.assertIn(
parameter,
bandwidth_limit_details[0]['supported_parameters'])
def test_supported_rule_type_details_no_drivers_loaded(self):
driver_manager = self._create_manager_with_drivers({})
self.assertEqual(
[],
driver_manager.supported_rule_type_details(
qos_consts.RULE_TYPE_BANDWIDTH_LIMIT))
class TestQosDriversCalls(TestQosDriversManagerBase):
"""Test QoS driver calls"""

View File

@ -12,16 +12,18 @@
import mock
from neutron_lib import context
from neutron_lib import exceptions as lib_exc
from neutron_lib.plugins import directory
from oslo_config import cfg
from oslo_utils import uuidutils
from neutron.common import constants
from neutron.common import exceptions as n_exc
from neutron import manager
from neutron.objects import base as base_object
from neutron.objects.qos import policy as policy_object
from neutron.objects.qos import rule as rule_object
from neutron.plugins.common import constants
from neutron.plugins.common import constants as plugins_constants
from neutron.services.qos import qos_consts
from neutron.services.qos import qos_plugin
from neutron.tests.unit.services.qos import base
@ -55,7 +57,7 @@ class TestQosPlugin(base.BaseQosTestCase):
cfg.CONF.set_override("service_plugins", ["qos"])
manager.init()
self.qos_plugin = directory.get_plugin(constants.QOS)
self.qos_plugin = directory.get_plugin(plugins_constants.QOS)
self.qos_plugin.driver_manager = mock.Mock()
@ -811,6 +813,34 @@ class TestQosPlugin(base.BaseQosTestCase):
self.assertRaises(AttributeError, getattr, self.qos_plugin,
'create_policy_bandwidth_limit_rules')
def test_get_rule_type(self):
admin_ctxt = context.get_admin_context()
drivers_details = [{
'name': 'fake-driver',
'supported_parameters': [{
'parameter_name': 'max_kbps',
'parameter_type': constants.VALUES_TYPE_RANGE,
'parameter_range': {'start': 0, 'end': 100}
}]
}]
with mock.patch.object(
qos_plugin.QoSPlugin, "supported_rule_type_details",
return_value=drivers_details
):
rule_type_details = self.qos_plugin.get_rule_type(
admin_ctxt, qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
self.assertEqual(
qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
rule_type_details['type'])
self.assertEqual(
drivers_details, rule_type_details['drivers'])
def test_get_rule_type_as_user(self):
self.assertRaises(
lib_exc.NotAuthorized,
self.qos_plugin.get_rule_type,
self.ctxt, qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
def test_get_rule_types(self):
rule_types_mock = mock.PropertyMock(
return_value=qos_consts.VALID_RULE_TYPES)

View File

@ -0,0 +1,8 @@
---
prelude: >
New API to get details of supported rule types.
features:
- |
The QoS service plugin can now expose details about supported QoS rule
types in Neutron deployment.
New API call is allowed only for users with admin priviliges.