Browse Source

Merge "Support QoS by meter action" into stable/rocky

changes/29/733629/2
Zuul 1 month ago
committed by Gerrit Code Review
parent
commit
93ecf6fbbd
5 changed files with 136 additions and 14 deletions
  1. +102
    -6
      networking_ovn/common/ovn_client.py
  2. +14
    -3
      networking_ovn/ml2/qos_driver.py
  3. +5
    -2
      networking_ovn/tests/functional/test_qos_driver.py
  4. +4
    -3
      networking_ovn/tests/unit/ml2/test_qos_driver.py
  5. +11
    -0
      releasenotes/notes/support-QoS-by-meter-action-03bb9fa57e35e5bd.yaml

+ 102
- 6
networking_ovn/common/ovn_client.py View File

@@ -202,11 +202,9 @@ class OVNClient(object):
return [lsp.name for lsp in ls.ports for ps in lsp.port_security
if lsp.name != port['id'] and virtual_ip in ps]

def _get_port_options(self, port, qos_options=None):
def _get_port_options(self, port):
context = n_context.get_admin_context()
binding_prof = utils.validate_and_get_data_from_binding_profile(port)
if qos_options is None:
qos_options = self._qos_driver.get_qos_options(port)
vtep_physical_switch = binding_prof.get('vtep-physical-switch')

port_type = ''
@@ -221,7 +219,7 @@ class OVNClient(object):
tag = []
port_security = []
else:
options = qos_options
options = {}
parent_name = binding_prof.get('parent_name', [])
tag = binding_prof.get('tag', [])
address = port['mac_address']
@@ -410,6 +408,14 @@ class OVNClient(object):
if self.is_dns_required_for_port(port):
self.add_txns_to_sync_port_dns_records(txn, port)

# Add qos for port by qos table of logical flow instead of tc
qos_options = self._qos_driver.get_qos_options(port)

if qos_options:
qos_rule_column = self._create_qos_rules(qos_options,
port, lswitch_name)
txn.add(self._nb_idl.qos_add(**qos_rule_column))

db_rev.bump_revision(port, ovn_const.TYPE_PORTS)

# TODO(lucasagomes): Remove this helper method in the Rocky release
@@ -451,8 +457,8 @@ class OVNClient(object):
def update_port(self, port, qos_options=None, port_object=None):
if utils.is_lsp_ignored(port):
return
port_info = self._get_port_options(port, qos_options)
# Does not need to add qos rule to port_info
port_info = self._get_port_options(port)
external_ids = {ovn_const.OVN_PORT_NAME_EXT_ID_KEY: port['name'],
ovn_const.OVN_DEVID_EXT_ID_KEY: port['device_id'],
ovn_const.OVN_PROJID_EXT_ID_KEY: port['project_id'],
@@ -466,6 +472,7 @@ class OVNClient(object):
ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(
utils.get_revision_number(
port, ovn_const.TYPE_PORTS))}
lswitch_name = utils.ovn_name(port['network_id'])
admin_context = n_context.get_admin_context()
sg_cache = {}
subnet_cache = {}
@@ -634,6 +641,51 @@ class OVNClient(object):
addrs_add=addr_add,
addrs_remove=addr_remove))

# Update QoS policy rule, delete the old one, then add the new one
# If we create port with qos_policy, we also need execute
# update_port method, which qos_policy is in port dict, so we
# also need get policy from port dict if qos_options is None
qos_options_new = (qos_options if qos_options
else self._qos_driver.get_qos_options(port))
# If port_object is None, we also need to get necessary params
# to delete the qos rule
qos_options_old = (self._qos_driver.get_qos_options(port_object)
if port_object else qos_options_new)

ovn_net = self._nb_idl.get_lswitch(lswitch_name)
ovn_net_qos_policy = (ovn_net.external_ids
[ovn_const.OVN_QOS_POLICY_EXT_ID_KEY]
if ovn_const.OVN_QOS_POLICY_EXT_ID_KEY
in ovn_net.external_ids else None)

if qos_options_new:
qos_rule_column_old = self._create_qos_rules(qos_options_old,
port,
lswitch_name,
if_delete=True)
# Delete old QoS rule first
txn.add(self._nb_idl.qos_del(**qos_rule_column_old))
# Add new QoS rule
qos_rule_column_new = self._create_qos_rules(qos_options_new,
port,
lswitch_name)
txn.add(self._nb_idl.qos_add(**qos_rule_column_new))
# If we want to delete port qos_rule by using
# param '--no-qos-policy'
elif qos_options_old:

qos_rule_column_old = self._create_qos_rules(qos_options_old,
port,
lswitch_name,
if_delete=True)
# Delete old QoS rule
txn.add(self._nb_idl.qos_del(**qos_rule_column_old))

# If we want to delete network qos_rule by using
# param '--no-qos-policy'
elif not qos_options_old and ovn_net_qos_policy:
txn.add(self._nb_idl.qos_del(lswitch_name))

if self.is_dns_required_for_port(port):
self.add_txns_to_sync_port_dns_records(
txn, port, original_port=port_object)
@@ -644,6 +696,36 @@ class OVNClient(object):
if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
db_rev.bump_revision(port, ovn_const.TYPE_PORTS)

def _create_qos_rules(self, qos_options, port, lswitch_name,
if_delete=False):
qos_rule = {}
direction = 'from-lport' if qos_options['direction'] ==\
'egress' else 'to-lport'
qos_rule.update(switch=lswitch_name, direction=direction,
priority=2002)

if direction == 'from-lport':
match = 'inport == ' + '"{}"'.format(port['id'])
qos_rule.update(match=match)
else:
match = 'outport == ' + '"{}"'.format(port['id'])
qos_rule.update(match=match)
# QoS of bandwidth_limit
if 'qos_max_rate' in qos_options:
qos_rule.update(rate=int(qos_options['qos_max_rate']),
burst=int(qos_options['qos_burst']),
dscp=None)
# QoS of dscp
elif 'dscp_mark' in qos_options:
qos_rule.update(rate=None, burst=None,
dscp=qos_options['dscp_mark'])
# There is no need 'rate', 'burst' or 'dscp' for deleted method
if if_delete is True:
qos_rule.pop('rate')
qos_rule.pop('burst')
qos_rule.pop('dscp')
return qos_rule

def _delete_port(self, port_id, port_object=None):
ovn_port = self._nb_idl.lookup(
'Logical_Switch_Port', port_id, default=None)
@@ -679,6 +761,20 @@ class OVNClient(object):
addrs_add=None,
addrs_remove=addr_list,
if_exists=True))
# Delete qos rule of port
try:
if (not port_object or 'qos_policy_id' not in port_object or
port_object['qos_policy_id'] is None):
pass
else:
qos_options = self._qos_driver.get_qos_options(port_object)
qos_rule_column = self._create_qos_rules(qos_options,
port_object,
network_id,
if_delete=True)
txn.add(self._nb_idl.qos_del(**qos_rule_column))
except KeyError:
pass

if port_object and self.is_dns_required_for_port(port_object):
self.add_txns_to_remove_port_dns_records(txn, port_object)


+ 14
- 3
networking_ovn/ml2/qos_driver.py View File

@@ -36,8 +36,11 @@ SUPPORTED_RULES = {
qos_consts.MAX_BURST: {
'type:range': [0, db_consts.DB_INTEGER_MAX_VALUE]},
qos_consts.DIRECTION: {
'type:values': [constants.EGRESS_DIRECTION]}
'type:values': constants.VALID_DIRECTIONS},
},
qos_consts.RULE_TYPE_DSCP_MARKING: {
qos_consts.DSCP_MARK: {'type:values': constants.VALID_DSCP_MARKS},
}
}

VIF_TYPES = [portbindings.VIF_TYPE_OVS, portbindings.VIF_TYPE_VHOST_USER]
@@ -103,9 +106,17 @@ class OVNQosDriver(object):
for rule in all_rules:
if isinstance(rule, qos_rule.QosBandwidthLimitRule):
if rule.max_kbps:
options['qos_max_rate'] = str(rule.max_kbps * 1000)
options['qos_max_rate'] = str(rule.max_kbps)
if rule.max_burst_kbps:
options['qos_burst'] = str(rule.max_burst_kbps * 1000)
options['qos_burst'] = str(rule.max_burst_kbps)
# OVN could support both directions
if rule.direction:
options['direction'] = rule.direction
# Add dscp_mark support
if isinstance(rule, qos_rule.QosDscpMarkingRule):
if rule.dscp_mark:
options['dscp_mark'] = rule.dscp_mark
options['direction'] = 'egress'
return options

def get_qos_options(self, port):


+ 5
- 2
networking_ovn/tests/functional/test_qos_driver.py View File

@@ -107,8 +107,11 @@ class TestOVNQosDriver(base.TestOVNFunctionalBase):
if 'qos_max_rate' in lsp.options:
observed_lsp_qos_options['qos_max_rate'] = lsp.options.get(
'qos_max_rate')

self.assertEqual(expected_lsp_qos_options, observed_lsp_qos_options)
# TODO(Taoyunxiang): The qos table in OVN does not
# contain "name" column, which could link database of neutron
# and OVN. Effective test will be added after qos
# table of OVN completed.
self.assertEqual({}, observed_lsp_qos_options)

def test_port_qos_options_add_and_remove(self):
expected_burst = 100


+ 4
- 3
networking_ovn/tests/unit/ml2/test_qos_driver.py View File

@@ -66,13 +66,14 @@ class TestOVNQosDriver(base.BaseTestCase):
self.policy = self._create_fake_policy()
self.port = self._create_fake_port()
self.rule = self._create_bw_limit_rule()
self.expected = {'qos_max_rate': '1000', 'qos_burst': '100000'}
self.expected = {'direction': 'egress', 'qos_max_rate': '1000',
'qos_burst': '100000'}

def _create_bw_limit_rule(self):
rule_obj = qos_rule.QosBandwidthLimitRule()
rule_obj.id = uuidutils.generate_uuid()
rule_obj.max_kbps = 1
rule_obj.max_burst_kbps = 100
rule_obj.max_kbps = 1000
rule_obj.max_burst_kbps = 100000
rule_obj.obj_reset_changes()
return rule_obj



+ 11
- 0
releasenotes/notes/support-QoS-by-meter-action-03bb9fa57e35e5bd.yaml View File

@@ -0,0 +1,11 @@
---
fixes:
- |
Currently, QoS is implemented by TC command of HV OS.
This solution has many drawbacks, such as not supported
VLAN network or VMs from same HV.
Meter is a comfortable solution, which is implement
in OpenFlow as an action, so it is flexible and exact.
By meter action, we can implemented QoS from both directions,
also, we can support DSCP. If we want to add other matches,
it is also feasible.

Loading…
Cancel
Save