From 16200ab3774fc444d6984b408cc2d93ccf014ba9 Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Tue, 26 Apr 2016 10:50:20 +0300 Subject: [PATCH] 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 --- vmware_nsx/nsxlib/v3/__init__.py | 40 +++---- vmware_nsx/plugins/nsx_v3/plugin.py | 3 + vmware_nsx/services/qos/nsx_v3/utils.py | 104 +++++++++++------- .../nsxlib/v3/test_qos_switching_profile.py | 53 ++++----- .../services/qos/test_nsxv3_notification.py | 54 ++++++++- 5 files changed, 158 insertions(+), 96 deletions(-) diff --git a/vmware_nsx/nsxlib/v3/__init__.py b/vmware_nsx/nsxlib/v3/__init__.py index 728a550d76..56a2400e58 100644 --- a/vmware_nsx/nsxlib/v3/__init__.py +++ b/vmware_nsx/nsxlib/v3/__init__.py @@ -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) diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index 84bd1f704d..924eb0e1d6 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -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, diff --git a/vmware_nsx/services/qos/nsx_v3/utils.py b/vmware_nsx/services/qos/nsx_v3/utils.py index cd7faa7a24..21d55e4821 100644 --- a/vmware_nsx/services/qos/nsx_v3/utils.py +++ b/vmware_nsx/services/qos/nsx_v3/utils.py @@ -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) diff --git a/vmware_nsx/tests/unit/nsxlib/v3/test_qos_switching_profile.py b/vmware_nsx/tests/unit/nsxlib/v3/test_qos_switching_profile.py index 8266deb664..8549d72005 100644 --- a/vmware_nsx/tests/unit/nsxlib/v3/test_qos_switching_profile.py +++ b/vmware_nsx/tests/unit/nsxlib/v3/test_qos_switching_profile.py @@ -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): diff --git a/vmware_nsx/tests/unit/services/qos/test_nsxv3_notification.py b/vmware_nsx/tests/unit/services/qos/test_nsxv3_notification.py index c6c8529dee..1a2c9e9c2b 100644 --- a/vmware_nsx/tests/unit/services/qos/test_nsxv3_notification.py +++ b/vmware_nsx/tests/unit/services/qos/test_nsxv3_notification.py @@ -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)