diff --git a/vmware_nsx/dvs/dvs.py b/vmware_nsx/dvs/dvs.py index 026bd8717a..36992f543f 100644 --- a/vmware_nsx/dvs/dvs.py +++ b/vmware_nsx/dvs/dvs.py @@ -23,6 +23,8 @@ from vmware_nsx.dvs import dvs_utils LOG = logging.getLogger(__name__) PORTGROUP_PREFIX = 'dvportgroup' +QOS_OUT_DIRECTION = 'outgoingPackets' +QOS_AGENT_NAME = 'dvfilter-generic-vmware' class DvsManager(object): @@ -139,8 +141,10 @@ class DvsManager(object): return pg_spec def update_port_group_spec_qos(self, pg_spec, qos_data): - outPol = pg_spec.defaultPortConfig.outShapingPolicy - if qos_data.enabled: + port_conf = pg_spec.defaultPortConfig + # Update the out bandwidth shaping policy + outPol = port_conf.outShapingPolicy + if qos_data.bandwidthEnabled: outPol.inherited = False outPol.enabled.inherited = False outPol.enabled.value = True @@ -153,6 +157,46 @@ class DvsManager(object): else: outPol.inherited = True + # Update the DSCP marking + if (port_conf.filterPolicy.inherited or + len(port_conf.filterPolicy.filterConfig) == 0 or + len(port_conf.filterPolicy.filterConfig[ + 0].trafficRuleset.rules) == 0): + + if qos_data.dscpMarkEnabled: + # create the entire structure + client_factory = self._session.vim.client.factory + filter_rule = client_factory.create('ns0:DvsTrafficRule') + filter_rule.action = client_factory.create( + 'ns0:DvsUpdateTagNetworkRuleAction') + filter_rule.action.dscpTag = qos_data.dscpMarkValue + # mark only outgoing packets + filter_rule.direction = QOS_OUT_DIRECTION + + traffic_filter_config = client_factory.create( + 'ns0:DvsTrafficFilterConfig') + traffic_filter_config.trafficRuleset.rules = [filter_rule] + traffic_filter_config.trafficRuleset.enabled = True + traffic_filter_config.agentName = QOS_AGENT_NAME + traffic_filter_config.inherited = False + + port_conf.filterPolicy = client_factory.create( + 'ns0:DvsFilterPolicy') + port_conf.filterPolicy.filterConfig = [ + traffic_filter_config] + port_conf.filterPolicy.inherited = False + else: + # The structure was already initialized + filter_policy = port_conf.filterPolicy + if qos_data.dscpMarkEnabled: + # just update the DSCP value + traffic_filter_config = filter_policy.filterConfig[0] + filter_rule = traffic_filter_config.trafficRuleset.rules[0] + filter_rule.action.dscpTag = qos_data.dscpMarkValue + else: + # delete the filter policy data + filter_policy.filterConfig = [] + def _reconfigure_port_group(self, pg_moref, spec_update_calback, spec_update_data): # Get the current configuration of the port group diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 2971a68006..c95b56e32f 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -141,6 +141,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, "advanced-service-providers", "subnet_allocation"] + supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, + qos_consts.RULE_TYPE_DSCP_MARK] + __native_bulk_support = True __native_pagination_support = True __native_sorting_support = True diff --git a/vmware_nsx/services/qos/nsx_v/utils.py b/vmware_nsx/services/qos/nsx_v/utils.py index a12f773b67..40ae8d7744 100644 --- a/vmware_nsx/services/qos/nsx_v/utils.py +++ b/vmware_nsx/services/qos/nsx_v/utils.py @@ -32,14 +32,19 @@ class NsxVQosRule(object): def __init__(self, context=None, qos_policy_id=None): super(NsxVQosRule, self).__init__() - # Data structure to hold the NSX-V representation - # of the neutron qos rule. self._qos_plugin = None - self.enabled = False + + # Data structure to hold the NSX-V representation + # of the neutron QoS Bandwidth rule + self.bandwidthEnabled = False self.averageBandwidth = 0 self.peakBandwidth = 0 self.burstSize = 0 + # And data for the DSCP marking rule + self.dscpMarkEnabled = False + self.dscpMarkValue = 0 + if qos_policy_id is not None: self._init_from_policy_id(context, qos_policy_id) @@ -51,16 +56,18 @@ class NsxVQosRule(object): # init the nsx_v qos data (outShapingPolicy) from a neutron qos policy def _init_from_policy_id(self, context, qos_policy_id): - self.enabled = False + self.bandwidthEnabled = False + self.dscpMarkEnabled = False + # read the neutron policy restrictions if qos_policy_id is not None: - # read the QOS rule from DB plugin = self._get_qos_plugin() + # read the QoS BW rule from DB rules_obj = plugin.get_policy_bandwidth_limit_rules( context, qos_policy_id) if rules_obj is not None and len(rules_obj) > 0: - rule_obj = rules_obj[0] - self.enabled = True + rule_obj = rules_obj[0] # neutron supports only 1 rule for now + self.bandwidthEnabled = True # averageBandwidth: kbps (neutron) -> bps (nsxv) self.averageBandwidth = rule_obj['max_kbps'] * 1024 # peakBandwidth: the same as the average value because the @@ -68,6 +75,15 @@ class NsxVQosRule(object): self.peakBandwidth = self.averageBandwidth # burstSize: kbps (neutron) -> Bytes (nsxv) self.burstSize = rule_obj['max_burst_kbps'] * 128 + + # read the QoS DSCP marking rule from DB + rules_obj = plugin.get_policy_dscp_marking_rules( + context, qos_policy_id) + if rules_obj is not None and len(rules_obj) > 0: + rule_obj = rules_obj[0] # neutron supports only 1 rule for now + self.dscpMarkEnabled = True + self.dscpMarkValue = rule_obj['dscp_mark'] + return self diff --git a/vmware_nsx/tests/unit/services/qos/test_nsxv_notification.py b/vmware_nsx/tests/unit/services/qos/test_nsxv_notification.py index 4366124d7d..6323584bb7 100644 --- a/vmware_nsx/tests/unit/services/qos/test_nsxv_notification.py +++ b/vmware_nsx/tests/unit/services/qos/test_nsxv_notification.py @@ -72,12 +72,17 @@ class TestQosNsxVNotification(test_plugin.NsxVPluginV2TestCase, 'bandwidth_limit_rule': {'id': uuidutils.generate_uuid(), 'max_kbps': 100, '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._net_data = {'network': { 'name': 'test-qos', @@ -88,6 +93,7 @@ class TestQosNsxVNotification(test_plugin.NsxVPluginV2TestCase, 'shared': False }} self._rules = [self.rule_data['bandwidth_limit_rule']] + self._dscp_rules = [self.dscp_rule_data['dscp_marking_rule']] mock.patch('neutron.objects.db.api.create_object').start() mock.patch('neutron.objects.db.api.update_object').start() @@ -117,21 +123,25 @@ class TestQosNsxVNotification(test_plugin.NsxVPluginV2TestCase, # Create a policy with a rule _policy = policy_object.QosPolicy( self.ctxt, **self.policy_data['policy']) - setattr(_policy, "rules", [self.rule]) + setattr(_policy, "rules", [self.rule, self.dscp_rule]) with mock.patch('neutron.services.qos.qos_plugin.QoSPlugin.' 'get_policy_bandwidth_limit_rules', return_value=self._rules) as get_rules_mock: - # create the network to use this policy - net = self._create_net() + with mock.patch('neutron.services.qos.qos_plugin.QoSPlugin.' + 'get_policy_dscp_marking_rules', + return_value=self._dscp_rules) as get_dscp_mock: + # create the network to use this policy + net = self._create_net() - # make sure the network-policy binding was updated - update_bindings_mock.assert_called_once_with( - self.ctxt, net['id'], self.policy.id) - # make sure the qos rule was found - get_rules_mock.assert_called_with(self.ctxt, self.policy.id) - # make sure the dvs was updated - self.assertTrue(dvs_update_mock.called) + # make sure the network-policy binding was updated + update_bindings_mock.assert_called_once_with( + self.ctxt, net['id'], self.policy.id) + # make sure the qos rule was found + get_rules_mock.assert_called_with(self.ctxt, self.policy.id) + get_dscp_mock.assert_called_with(self.ctxt, self.policy.id) + # make sure the dvs was updated + self.assertTrue(dvs_update_mock.called) def _test_rule_action_notification(self, action): with mock.patch.object(qos_com_utils, 'update_network_policy_binding'): @@ -189,3 +199,72 @@ class TestQosNsxVNotification(test_plugin.NsxVPluginV2TestCase, is deleted """ self._test_rule_action_notification('delete') + + def _test_dscp_rule_action_notification(self, action): + with mock.patch.object(qos_com_utils, 'update_network_policy_binding'): + with mock.patch.object(dvs.DvsManager, + 'update_port_groups_config') as dvs_mock: + + # Create a policy with a rule + _policy = policy_object.QosPolicy( + self.ctxt, **self.policy_data['policy']) + + # set the rule in the policy data + if action != 'create': + setattr(_policy, "rules", [self.dscp_rule]) + plugin = self.qos_plugin + with mock.patch('neutron.services.qos.qos_plugin.QoSPlugin.' + 'get_policy_dscp_marking_rules', + return_value=self._dscp_rules) as rules_mock: + with mock.patch('neutron.objects.qos.policy.' + 'QosPolicy.get_object', + return_value=_policy): + # create the network to use this policy + self._create_net() + # create/update/delete the rule + if action == 'create': + with mock.patch('neutron.objects.db.api.' + 'create_object', + return_value=self.dscp_rule_data): + plugin.create_policy_dscp_marking_rule( + self.ctxt, + self.policy.id, + self.dscp_rule_data) + elif action == 'update': + with mock.patch('neutron.objects.db.api.' + 'update_object', + return_value=self.dscp_rule_data): + plugin.update_policy_dscp_marking_rule( + self.ctxt, + self.dscp_rule.id, + self.policy.id, + self.dscp_rule_data) + else: + plugin.delete_policy_dscp_marking_rule( + self.ctxt, + self.dscp_rule.id, + self.policy.id) + + # make sure the qos rule was found + self.assertTrue(rules_mock.called) + + # make sure the dvs was updated + self.assertTrue(dvs_mock.called) + + def test_create_dscp_rule_notification(self): + """Test the DVS update when a QoS DSCP rule, attached to a network, + is created + """ + self._test_dscp_rule_action_notification('create') + + def test_update_dscp_rule_notification(self): + """Test the DVS update when a QoS DSCP rule, attached to a network, + is modified + """ + self._test_dscp_rule_action_notification('update') + + def test_delete_dscp_rule_notification(self): + """Test the DVS update when a QoS DSCP rule, attached to a network, + is deleted + """ + self._test_dscp_rule_action_notification('delete')