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:
Adit Sarfaty 2016-04-26 10:50:20 +03:00
parent 106a847413
commit 16200ab377
5 changed files with 158 additions and 96 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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):

View File

@ -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)