NSX|V3 QoS DSCP marking support
Adding support for the QoS DSCP marking rules for ports & networks, and updating the backend QoS switching profile Change-Id: I852ad20965c78c2d6011d350ca4f226f462d03fd
This commit is contained in:
parent
106a847413
commit
16200ab377
@ -178,23 +178,14 @@ def update_logical_router_advertisement(logical_router_id, **kwargs):
|
||||
return update_resource_with_retry(resource, kwargs)
|
||||
|
||||
|
||||
def _build_qos_switching_profile_args(tags, qos_marking=None, dscp=None,
|
||||
name=None, description=None):
|
||||
def _build_qos_switching_profile_args(tags, name=None, description=None):
|
||||
body = {"resource_type": "QosSwitchingProfile",
|
||||
"tags": tags}
|
||||
|
||||
return _update_qos_switching_profile_args(
|
||||
body, qos_marking=qos_marking, dscp=dscp,
|
||||
name=name, description=description)
|
||||
body, name=name, description=description)
|
||||
|
||||
|
||||
def _update_qos_switching_profile_args(body, qos_marking=None, dscp=None,
|
||||
name=None, description=None):
|
||||
if qos_marking:
|
||||
body["dscp"] = {}
|
||||
body["dscp"]["mode"] = qos_marking.upper()
|
||||
if dscp:
|
||||
body["dscp"]["priority"] = dscp
|
||||
def _update_qos_switching_profile_args(body, name=None, description=None):
|
||||
if name:
|
||||
body["display_name"] = name
|
||||
if description:
|
||||
@ -232,28 +223,36 @@ def _disable_shaping_in_args(body):
|
||||
return body
|
||||
|
||||
|
||||
def create_qos_switching_profile(tags, qos_marking=None, dscp=None, name=None,
|
||||
def _update_dscp_in_args(body, qos_marking, dscp):
|
||||
body["dscp"] = {}
|
||||
body["dscp"]["mode"] = qos_marking.upper()
|
||||
if dscp:
|
||||
body["dscp"]["priority"] = dscp
|
||||
|
||||
return body
|
||||
|
||||
|
||||
def create_qos_switching_profile(tags, name=None,
|
||||
description=None):
|
||||
resource = 'switching-profiles'
|
||||
body = _build_qos_switching_profile_args(tags, qos_marking, dscp,
|
||||
name, description)
|
||||
body = _build_qos_switching_profile_args(tags, name, description)
|
||||
return client.create_resource(resource, body)
|
||||
|
||||
|
||||
def update_qos_switching_profile(profile_id, tags, qos_marking=None,
|
||||
dscp=None, name=None, description=None):
|
||||
def update_qos_switching_profile(profile_id, tags, name=None,
|
||||
description=None):
|
||||
resource = 'switching-profiles/%s' % profile_id
|
||||
# get the current configuration
|
||||
body = get_qos_switching_profile(profile_id)
|
||||
# update the relevant fields
|
||||
body = _update_qos_switching_profile_args(body, qos_marking, dscp,
|
||||
name, description)
|
||||
body = _update_qos_switching_profile_args(body, name, description)
|
||||
return update_resource_with_retry(resource, body)
|
||||
|
||||
|
||||
def update_qos_switching_profile_shaping(profile_id, shaping_enabled=False,
|
||||
burst_size=None, peak_bandwidth=None,
|
||||
average_bandwidth=None):
|
||||
average_bandwidth=None,
|
||||
qos_marking=None, dscp=None):
|
||||
resource = 'switching-profiles/%s' % profile_id
|
||||
# get the current configuration
|
||||
body = get_qos_switching_profile(profile_id)
|
||||
@ -265,6 +264,7 @@ def update_qos_switching_profile_shaping(profile_id, shaping_enabled=False,
|
||||
average_bandwidth=average_bandwidth)
|
||||
else:
|
||||
body = _disable_shaping_in_args(body)
|
||||
body = _update_dscp_in_args(body, qos_marking, dscp)
|
||||
return update_resource_with_retry(resource, body)
|
||||
|
||||
|
||||
|
@ -132,6 +132,9 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
"subnet_allocation",
|
||||
"security-group-logging"]
|
||||
|
||||
supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
|
||||
qos_consts.RULE_TYPE_DSCP_MARK]
|
||||
|
||||
@resource_registry.tracked_resources(
|
||||
network=models_v2.Network,
|
||||
port=models_v2.Port,
|
||||
|
@ -17,6 +17,7 @@
|
||||
from neutron.api.rpc.callbacks import events as callbacks_events
|
||||
from neutron import context as n_context
|
||||
from neutron.objects.qos import policy as qos_policy
|
||||
from neutron.services.qos import qos_consts
|
||||
from neutron_lib.api import validators
|
||||
from oslo_log import log as logging
|
||||
|
||||
@ -58,16 +59,18 @@ def handle_qos_notification(policy_obj, event_type):
|
||||
|
||||
elif (event_type == callbacks_events.UPDATED):
|
||||
if (hasattr(policy_obj, "rules")):
|
||||
# With rules - the policy rule was created / deleted / updated
|
||||
rules = policy_obj["rules"]
|
||||
if not len(rules):
|
||||
# the rule was deleted
|
||||
handler.delete_policy_bandwidth_limit_rule(
|
||||
context, policy_obj.id)
|
||||
else:
|
||||
# New or updated rule
|
||||
handler.create_or_update_policy_bandwidth_limit_rule(
|
||||
context, policy_obj.id, rules[0])
|
||||
# Rebuild the QoS data of this policy
|
||||
# we may have up to 1 rule of each type
|
||||
bw_rule = None
|
||||
dscp_rule = None
|
||||
for rule in policy_obj["rules"]:
|
||||
if rule.rule_type == qos_consts.RULE_TYPE_BANDWIDTH_LIMIT:
|
||||
bw_rule = rule
|
||||
else:
|
||||
dscp_rule = rule
|
||||
|
||||
handler.update_policy_rules(
|
||||
context, policy_obj.id, bw_rule, dscp_rule)
|
||||
else:
|
||||
# Without rules - need to update only name / description
|
||||
handler.update_policy(context, policy_obj.id, policy)
|
||||
@ -127,48 +130,67 @@ class QosNotificationsHandler(object):
|
||||
values expected by the NSX-v3 QoS switch profile,
|
||||
and validate that those are legal
|
||||
"""
|
||||
# validate the max_kbps - it must be at least 1Mbps for the
|
||||
# switch profile configuration to succeed.
|
||||
if (bw_rule.max_kbps < MAX_KBPS_MIN_VALUE):
|
||||
# Since failing the action from the notification callback is not
|
||||
# possible, just log the warning and use the minimal value
|
||||
LOG.warning(_LW("Invalid input for max_kbps. "
|
||||
"The minimal legal value is 1024"))
|
||||
bw_rule.max_kbps = MAX_KBPS_MIN_VALUE
|
||||
if bw_rule:
|
||||
shaping_enabled = True
|
||||
|
||||
# 'None' value means we will keep the old value
|
||||
burst_size = peak_bandwidth = average_bandwidth = None
|
||||
# validate the max_kbps - it must be at least 1Mbps for the
|
||||
# switch profile configuration to succeed.
|
||||
if (bw_rule.max_kbps < MAX_KBPS_MIN_VALUE):
|
||||
# Since failing the action from the notification callback
|
||||
# is not possible, just log the warning and use the
|
||||
# minimal value.
|
||||
LOG.warning(_LW("Invalid input for max_kbps. "
|
||||
"The minimal legal value is 1024"))
|
||||
bw_rule.max_kbps = MAX_KBPS_MIN_VALUE
|
||||
|
||||
# translate kbps -> bytes
|
||||
burst_size = int(bw_rule.max_burst_kbps) * 128
|
||||
# 'None' value means we will keep the old value
|
||||
burst_size = peak_bandwidth = average_bandwidth = None
|
||||
|
||||
# translate kbps -> Mbps
|
||||
peak_bandwidth = int(float(bw_rule.max_kbps) / 1024)
|
||||
# neutron QoS does not support this parameter
|
||||
average_bandwidth = peak_bandwidth
|
||||
# translate kbps -> bytes
|
||||
burst_size = int(bw_rule.max_burst_kbps) * 128
|
||||
|
||||
return burst_size, peak_bandwidth, average_bandwidth
|
||||
# translate kbps -> Mbps
|
||||
peak_bandwidth = int(float(bw_rule.max_kbps) / 1024)
|
||||
# neutron QoS does not support this parameter
|
||||
average_bandwidth = peak_bandwidth
|
||||
else:
|
||||
shaping_enabled = False
|
||||
burst_size = None
|
||||
peak_bandwidth = None
|
||||
average_bandwidth = None
|
||||
|
||||
def create_or_update_policy_bandwidth_limit_rule(self, context, policy_id,
|
||||
bw_rule):
|
||||
"""Update the QoS switch profile with the BW limitations of a
|
||||
new or updated bandwidth limit rule
|
||||
return shaping_enabled, burst_size, peak_bandwidth, average_bandwidth
|
||||
|
||||
def _get_dscp_values_from_rule(self, dscp_rule):
|
||||
"""Translate the neutron DSCP marking rule values, into the
|
||||
values expected by the NSX-v3 QoS switch profile
|
||||
"""
|
||||
if dscp_rule:
|
||||
qos_marking = 'untrusted'
|
||||
dscp = dscp_rule.dscp_mark
|
||||
else:
|
||||
qos_marking = 'trusted'
|
||||
dscp = 0
|
||||
|
||||
return qos_marking, dscp
|
||||
|
||||
def update_policy_rules(self, context, policy_id, bw_rule, dscp_rule):
|
||||
"""Update the QoS switch profile with the BW limitations and
|
||||
DSCP marking configuration
|
||||
"""
|
||||
profile_id = nsx_db.get_switch_profile_by_qos_policy(
|
||||
context.session, policy_id)
|
||||
|
||||
burst_size, peak_bw, average_bw = self._get_bw_values_from_rule(
|
||||
bw_rule)
|
||||
(shaping_enabled, burst_size, peak_bw,
|
||||
average_bw) = self._get_bw_values_from_rule(bw_rule)
|
||||
|
||||
qos_marking, dscp = self._get_dscp_values_from_rule(dscp_rule)
|
||||
|
||||
nsxlib.update_qos_switching_profile_shaping(
|
||||
profile_id,
|
||||
shaping_enabled=True,
|
||||
shaping_enabled=shaping_enabled,
|
||||
burst_size=burst_size,
|
||||
peak_bandwidth=peak_bw,
|
||||
average_bandwidth=average_bw)
|
||||
|
||||
def delete_policy_bandwidth_limit_rule(self, context, policy_id):
|
||||
profile_id = nsx_db.get_switch_profile_by_qos_policy(
|
||||
context.session, policy_id)
|
||||
nsxlib.update_qos_switching_profile_shaping(
|
||||
profile_id, shaping_enabled=False)
|
||||
average_bandwidth=average_bw,
|
||||
qos_marking=qos_marking,
|
||||
dscp=dscp)
|
||||
|
@ -35,10 +35,8 @@ class NsxLibQosTestCase(nsxlib_testcase.NsxClientTestCase):
|
||||
"tags": []
|
||||
}
|
||||
if qos_marking:
|
||||
body["dscp"] = {}
|
||||
body["dscp"]["mode"] = qos_marking.upper()
|
||||
if dscp:
|
||||
body["dscp"]["priority"] = dscp
|
||||
body = nsxlib._update_dscp_in_args(body, qos_marking, dscp)
|
||||
|
||||
body["display_name"] = test_constants_v3.FAKE_NAME
|
||||
body["description"] = description
|
||||
|
||||
@ -48,7 +46,9 @@ class NsxLibQosTestCase(nsxlib_testcase.NsxClientTestCase):
|
||||
burst_size=None,
|
||||
peak_bandwidth=None,
|
||||
average_bandwidth=None,
|
||||
description=test_constants_v3.FAKE_NAME):
|
||||
description=test_constants_v3.FAKE_NAME,
|
||||
qos_marking=None,
|
||||
dscp=0):
|
||||
body = test_constants_v3.FAKE_QOS_PROFILE
|
||||
body["display_name"] = test_constants_v3.FAKE_NAME
|
||||
body["description"] = description
|
||||
@ -65,40 +65,26 @@ class NsxLibQosTestCase(nsxlib_testcase.NsxClientTestCase):
|
||||
shaper["average_bandwidth_mbps"] = average_bandwidth
|
||||
break
|
||||
|
||||
if qos_marking:
|
||||
body = nsxlib._update_dscp_in_args(body, qos_marking, dscp)
|
||||
|
||||
return body
|
||||
|
||||
def test_create_qos_switching_profile_untrusted(self):
|
||||
def test_create_qos_switching_profile(self):
|
||||
"""
|
||||
Test creating a qos-switching profile returns the correct response
|
||||
"""
|
||||
api = self.mocked_rest_fns(nsxlib, 'client')
|
||||
|
||||
nsxlib.create_qos_switching_profile(
|
||||
qos_marking="untrusted", dscp=25, tags=[],
|
||||
tags=[],
|
||||
name=test_constants_v3.FAKE_NAME,
|
||||
description=test_constants_v3.FAKE_NAME)
|
||||
|
||||
test_client.assert_json_call(
|
||||
'post', api,
|
||||
'https://1.2.3.4/api/v1/switching-profiles',
|
||||
data=jsonutils.dumps(self._body(qos_marking='UNTRUSTED', dscp=25),
|
||||
sort_keys=True))
|
||||
|
||||
def test_create_qos_switching_profile_trusted(self):
|
||||
"""
|
||||
Test creating a qos-switching profile returns the correct response
|
||||
"""
|
||||
api = self.mocked_rest_fns(nsxlib, 'client')
|
||||
|
||||
nsxlib.create_qos_switching_profile(
|
||||
qos_marking="trusted", dscp=0, tags=[],
|
||||
name=test_constants_v3.FAKE_NAME,
|
||||
description=test_constants_v3.FAKE_NAME)
|
||||
|
||||
test_client.assert_json_call(
|
||||
'post', api,
|
||||
'https://1.2.3.4/api/v1/switching-profiles',
|
||||
data=jsonutils.dumps(self._body(qos_marking='trusted', dscp=0),
|
||||
data=jsonutils.dumps(self._body(),
|
||||
sort_keys=True))
|
||||
|
||||
def test_update_qos_switching_profile(self):
|
||||
@ -134,6 +120,8 @@ class NsxLibQosTestCase(nsxlib_testcase.NsxClientTestCase):
|
||||
burst_size = 100
|
||||
peak_bandwidth = 200
|
||||
average_bandwidth = 300
|
||||
qos_marking = "untrusted"
|
||||
dscp = 10
|
||||
with mock.patch.object(nsxlib.client, 'get_resource',
|
||||
return_value=original_profile):
|
||||
# update the bw shaping of the profile
|
||||
@ -142,7 +130,9 @@ class NsxLibQosTestCase(nsxlib_testcase.NsxClientTestCase):
|
||||
shaping_enabled=True,
|
||||
burst_size=burst_size,
|
||||
peak_bandwidth=peak_bandwidth,
|
||||
average_bandwidth=average_bandwidth)
|
||||
average_bandwidth=average_bandwidth,
|
||||
qos_marking=qos_marking,
|
||||
dscp=dscp)
|
||||
|
||||
test_client.assert_json_call(
|
||||
'put', api,
|
||||
@ -153,7 +143,8 @@ class NsxLibQosTestCase(nsxlib_testcase.NsxClientTestCase):
|
||||
shaping_enabled=True,
|
||||
burst_size=burst_size,
|
||||
peak_bandwidth=peak_bandwidth,
|
||||
average_bandwidth=average_bandwidth),
|
||||
average_bandwidth=average_bandwidth,
|
||||
qos_marking="untrusted", dscp=10),
|
||||
sort_keys=True))
|
||||
|
||||
def test_disable_qos_switching_profile_shaping(self):
|
||||
@ -169,20 +160,22 @@ class NsxLibQosTestCase(nsxlib_testcase.NsxClientTestCase):
|
||||
shaping_enabled=True,
|
||||
burst_size=burst_size,
|
||||
peak_bandwidth=peak_bandwidth,
|
||||
average_bandwidth=average_bandwidth)
|
||||
average_bandwidth=average_bandwidth,
|
||||
qos_marking="untrusted",
|
||||
dscp=10)
|
||||
with mock.patch.object(nsxlib.client, 'get_resource',
|
||||
return_value=original_profile):
|
||||
# update the bw shaping of the profile
|
||||
nsxlib.update_qos_switching_profile_shaping(
|
||||
test_constants_v3.FAKE_QOS_PROFILE['id'],
|
||||
shaping_enabled=False)
|
||||
shaping_enabled=False, qos_marking="trusted")
|
||||
|
||||
test_client.assert_json_call(
|
||||
'put', api,
|
||||
'https://1.2.3.4/api/v1/switching-profiles/%s'
|
||||
% test_constants_v3.FAKE_QOS_PROFILE['id'],
|
||||
data=jsonutils.dumps(
|
||||
self._body_with_shaping(),
|
||||
self._body_with_shaping(qos_marking="trusted"),
|
||||
sort_keys=True))
|
||||
|
||||
def test_delete_qos_switching_profile(self):
|
||||
|
@ -58,12 +58,17 @@ class TestQosNsxV3Notification(nsxlib_testcase.NsxClientTestCase,
|
||||
'bandwidth_limit_rule': {'id': uuidutils.generate_uuid(),
|
||||
'max_kbps': 2000,
|
||||
'max_burst_kbps': 150}}
|
||||
self.dscp_rule_data = {
|
||||
'dscp_marking_rule': {'id': uuidutils.generate_uuid(),
|
||||
'dscp_mark': 22}}
|
||||
|
||||
self.policy = policy_object.QosPolicy(
|
||||
self.ctxt, **self.policy_data['policy'])
|
||||
|
||||
self.rule = rule_object.QosBandwidthLimitRule(
|
||||
self.ctxt, **self.rule_data['bandwidth_limit_rule'])
|
||||
self.dscp_rule = rule_object.QosDscpMarkingRule(
|
||||
self.ctxt, **self.dscp_rule_data['dscp_marking_rule'])
|
||||
|
||||
self.fake_profile_id = 'fake_profile'
|
||||
self.fake_profile = {'id': self.fake_profile_id}
|
||||
@ -130,8 +135,8 @@ class TestQosNsxV3Notification(nsxlib_testcase.NsxClientTestCase,
|
||||
)
|
||||
|
||||
@mock.patch.object(policy_object.QosPolicy, 'reload_rules')
|
||||
def test_rule_create_profile(self, *mocks):
|
||||
# test the switch profile update when a QoS rule is created
|
||||
def test_bw_rule_create_profile(self, *mocks):
|
||||
# test the switch profile update when a QoS BW rule is created
|
||||
_policy = policy_object.QosPolicy(
|
||||
self.ctxt, **self.policy_data['policy'])
|
||||
# add a rule to the policy
|
||||
@ -152,7 +157,9 @@ class TestQosNsxV3Notification(nsxlib_testcase.NsxClientTestCase,
|
||||
average_bandwidth=expected_bw,
|
||||
burst_size=expected_burst,
|
||||
peak_bandwidth=expected_bw,
|
||||
shaping_enabled=True
|
||||
shaping_enabled=True,
|
||||
qos_marking='trusted',
|
||||
dscp=0
|
||||
)
|
||||
|
||||
@mock.patch.object(policy_object.QosPolicy, 'reload_rules')
|
||||
@ -188,9 +195,41 @@ class TestQosNsxV3Notification(nsxlib_testcase.NsxClientTestCase,
|
||||
average_bandwidth=expected_bw,
|
||||
burst_size=expected_burst,
|
||||
peak_bandwidth=expected_bw,
|
||||
shaping_enabled=True
|
||||
shaping_enabled=True,
|
||||
dscp=0,
|
||||
qos_marking='trusted'
|
||||
)
|
||||
|
||||
@mock.patch.object(policy_object.QosPolicy, 'reload_rules')
|
||||
def test_dscp_rule_create_profile(self, *mocks):
|
||||
# test the switch profile update when a QoS DSCP rule is created
|
||||
_policy = policy_object.QosPolicy(
|
||||
self.ctxt, **self.policy_data['policy'])
|
||||
# add a rule to the policy
|
||||
setattr(_policy, "rules", [self.dscp_rule])
|
||||
with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object',
|
||||
return_value=_policy):
|
||||
with mock.patch.object(nsxlib,
|
||||
'update_qos_switching_profile_shaping') as update_profile:
|
||||
with mock.patch('neutron.objects.db.api.'
|
||||
'update_object', return_value=self.dscp_rule_data):
|
||||
self.qos_plugin.update_policy_dscp_marking_rule(
|
||||
self.ctxt, self.dscp_rule.id,
|
||||
_policy.id, self.dscp_rule_data)
|
||||
|
||||
# validate the data on the profile
|
||||
rule_dict = self.dscp_rule_data['dscp_marking_rule']
|
||||
dscp_mark = rule_dict['dscp_mark']
|
||||
update_profile.assert_called_once_with(
|
||||
self.fake_profile_id,
|
||||
average_bandwidth=None,
|
||||
burst_size=None,
|
||||
peak_bandwidth=None,
|
||||
shaping_enabled=False,
|
||||
qos_marking='untrusted',
|
||||
dscp=dscp_mark
|
||||
)
|
||||
|
||||
def test_rule_delete_profile(self):
|
||||
# test the switch profile update when a QoS rule is deleted
|
||||
_policy = policy_object.QosPolicy(
|
||||
@ -207,7 +246,12 @@ class TestQosNsxV3Notification(nsxlib_testcase.NsxClientTestCase,
|
||||
# validate the data on the profile
|
||||
update_profile.assert_called_once_with(
|
||||
self.fake_profile_id,
|
||||
shaping_enabled=False
|
||||
shaping_enabled=False,
|
||||
average_bandwidth=None,
|
||||
burst_size=None,
|
||||
dscp=0,
|
||||
peak_bandwidth=None,
|
||||
qos_marking='trusted'
|
||||
)
|
||||
|
||||
@mock.patch('neutron.objects.db.api.get_object', return_value=None)
|
||||
|
Loading…
Reference in New Issue
Block a user