Merge "Add QoS command for ovn northbound db."

This commit is contained in:
Zuul 2018-05-29 19:13:59 +00:00 committed by Gerrit Code Review
commit 4fc6d1d648
5 changed files with 264 additions and 0 deletions

View File

@ -19,6 +19,8 @@ DEVICE_NAME_MAX_LEN = 14
ACL_PRIORITY_MAX = 32767
QOS_DSCP_MAX = 2 ** 6 - 1
QOS_BANDWIDTH_MAX = 2 ** 32 - 1
NAT_SNAT = 'snat'
NAT_DNAT = 'dnat'

View File

@ -140,6 +140,61 @@ class API(api.API):
:returns: :class:`Command` with RowView list result
"""
@abc.abstractmethod
def qos_add(self, switch, direction, priority, match, rate=None,
burst=None, dscp=None, may_exist=False, **columns):
"""Add an Qos rules to 'switch'
:param switch: The name or uuid of the switch
:type switch: string or uuid.UUID
:param direction: The traffic direction to match
:type direction: 'from-lport' or 'to-lport'
:param priority: The priority field of the QoS
:type priority: int
:param match: The match rule
:type match: string
:param dscp: The dscp mark to take upon match
:type dscp: int
:param rate: The rate limit to take upon match
:type rate: int
:param burst: The burst rate limit to take upon match
:type burst: int
:param may_exist: If True, don't fail if the QoS rule already exists
:type may_exist: boolean
:param columns: Additional columns to directly set on the switch
:returns: :class:`Command` with RowView result
"""
@abc.abstractmethod
def qos_del(self, switch, direction=None, priority=None, match=None):
"""Remove Qos rules from 'switch'
If only switch is supplied, all the QoS rules from the logical switch
are deleted. If direction is also specified, then all the flows in
that direction will be deleted from the logical switch. If all the
fields are given, then only flows that match all fields will be
deleted.
:param switch: The name or uuid of the switch
:type switch: string or uuid.UUID
:param direction: The traffic direction to match
:type direction: 'from-lport' or 'to-lport'
:param priority: The priority field of the QoS
:type priority: int
:param match: The match rule
:type match: string
:returns: :class:`Command` with no result
"""
@abc.abstractmethod
def qos_list(self, switch):
"""Get the Qos rules for 'switch'
:param switch: The name or uuid of the switch
:type switch: string or uuid.UUID
:returns: :class:`Command` with RowView list result
"""
@abc.abstractmethod
def lsp_add(self, switch, port, parent_name=None, tag=None,
may_exist=False, **columns):

View File

@ -163,6 +163,102 @@ class AclListCommand(cmd.BaseCommand):
self.result = [rowview.RowView(acl) for acl in ls.acls]
class QoSAddCommand(cmd.AddCommand):
table_name = 'QoS'
def __init__(self, api, switch, direction, priority, match, rate=None,
burst=None, dscp=None, may_exist=False, **columns):
if direction not in ('from-lport', 'to-lport'):
raise TypeError("direction must be either from-lport or to-lport")
if not 0 <= priority <= const.ACL_PRIORITY_MAX:
raise ValueError("priority must be between 0 and %s, inclusive" %
const.ACL_PRIORITY_MAX)
if rate is not None and not 1 <= rate <= const.QOS_BANDWIDTH_MAX:
raise ValueError("rate(%s) must be between 1 and %s, inclusive" %
rate, const.QOS_BANDWIDTH_MAX)
if burst is not None and not 1 <= burst <= const.QOS_BANDWIDTH_MAX:
raise ValueError("burst(%s) must be between 1 and %s, "
"inclusive" % burst, const.QOS_BANDWIDTH_MAX)
if dscp is not None and not 0 <= dscp <= const.QOS_DSCP_MAX:
raise ValueError("dscp(%s) must be between 0 and %s, inclusive" %
dscp, const.QOS_DSCP_MAX)
if rate is None and dscp is None:
raise ValueError("One of the rate or dscp must be configured")
super(QoSAddCommand, self).__init__(api)
self.switch = switch
self.direction = direction
self.priority = priority
self.match = match
self.rate = rate
self.burst = burst
self.dscp = dscp
self.may_exist = may_exist
self.columns = columns
def qos_match(self, row):
return (self.direction == row.direction and
self.priority == row.priority and
self.match == row.match)
def run_idl(self, txn):
ls = self.api.lookup('Logical_Switch', self.switch)
qos_rules = [row for row in ls.qos_rules if self.qos_match(row)]
if qos_rules:
if self.may_exist:
self.result = rowview.RowView(qos_rules[0])
return
raise RuntimeError("QoS (%s, %s, %s) already exists" % (
self.direction, self.priority, self.match))
row = txn.insert(self.api.tables[self.table_name])
row.direction = self.direction
row.priority = self.priority
row.match = self.match
if self.rate:
row.setkey('bandwidth', 'rate', self.rate)
if self.burst:
row.setkey('bandwidth', 'burst', self.burst)
if self.dscp is not None:
row.setkey('action', 'dscp', self.dscp)
self.set_columns(row, **self.columns)
ls.addvalue('qos_rules', row)
self.result = row.uuid
class QoSDelCommand(cmd.BaseCommand):
def __init__(self, api, switch, direction=None,
priority=None, match=None):
if (priority is None) != (match is None):
raise TypeError("Must specify priority and match together")
if priority is not None and not direction:
raise TypeError("Cannot specify priority/match without direction")
super(QoSDelCommand, self).__init__(api)
self.switch = switch
self.conditions = []
if direction:
self.conditions.append(('direction', '=', direction))
# priority can be 0
if match: # and therefor priority due to the above check
self.conditions += [('priority', '=', priority),
('match', '=', match)]
def run_idl(self, txn):
ls = self.api.lookup('Logical_Switch', self.switch)
for row in ls.qos_rules:
if idlutils.row_match(row, self.conditions):
ls.delvalue('qos_rules', row)
row.delete()
class QoSListCommand(cmd.BaseCommand):
def __init__(self, api, switch):
super(QoSListCommand, self).__init__(api)
self.switch = switch
def run_idl(self, txn):
ls = self.api.lookup('Logical_Switch', self.switch)
self.result = [rowview.RowView(row) for row in ls.qos_rules]
class LspAddCommand(cmd.AddCommand):
table_name = 'Logical_Switch_Port'

View File

@ -64,6 +64,17 @@ class OvnNbApiIdlImpl(ovs_idl.Backend, api.API):
def acl_list(self, switch):
return cmd.AclListCommand(self, switch)
def qos_add(self, switch, direction, priority, match, rate=None,
burst=None, dscp=None, may_exist=False, **columns):
return cmd.QoSAddCommand(self, switch, direction, priority, match,
rate, burst, dscp, may_exist, **columns)
def qos_del(self, switch, direction=None, priority=None, match=None):
return cmd.QoSDelCommand(self, switch, direction, priority, match)
def qos_list(self, switch):
return cmd.QoSListCommand(self, switch)
def lsp_add(self, switch, port, parent_name=None, tag=None,
may_exist=False, **columns):
return cmd.LspAddCommand(self, switch, port, parent_name, tag,

View File

@ -178,6 +178,106 @@ class TestAclOps(OvnNorthboundTest):
self.assertIn(r2, acls)
class TestQoSOps(OvnNorthboundTest):
def setUp(self):
super(TestQoSOps, self).setUp()
self.switch = self.useFixture(fixtures.LogicalSwitchFixture()).obj
def _qos_add(self, *args, **kwargs):
cmd = self.api.qos_add(self.switch.uuid, *args, **kwargs)
row = cmd.execute(check_error=True)
self.assertIn(row._row, self.switch.qos_rules)
self.assertEqual(cmd.direction, row.direction)
self.assertEqual(cmd.priority, row.priority)
self.assertEqual(cmd.match, row.match)
self.assertEqual(cmd.rate, row.bandwidth.get('rate', None))
self.assertEqual(cmd.burst, row.bandwidth.get('burst', None))
self.assertEqual(cmd.dscp, row.action.get('dscp', None))
return row
def test_qos_add_dscp(self):
self._qos_add('from-lport', 0, 'output == "fake_port" && ip', dscp=33)
def test_qos_add_rate(self):
self._qos_add('from-lport', 0, 'output == "fake_port" && ip', rate=100)
def test_qos_add_rate_burst(self):
self._qos_add('from-lport', 0, 'output == "fake_port" && ip', rate=101,
burst=1001)
def test_qos_add_rate_dscp(self):
self._qos_add('from-lport', 0, 'output == "fake_port" && ip', rate=102,
burst=1002, dscp=56)
def test_qos_add_raises(self):
self.assertRaises(TypeError, self.api.qos_add, 'from-lport', 0,
'output == "fake_port" && ip')
def test_qos_add_direction_raises(self):
self.assertRaises(TypeError, self.api.qos_add, 'foo', 0, 'ip',
bandwidth={'rate': 102, 'burst': 1002})
def test_qos_add_priority_raises(self):
self.assertRaises(TypeError, self.api.qos_add, 'from-lport', 32768,
'ip', bandwidth={'rate': 102, 'burst': 1002})
def test_qos_add_exists(self):
args = ('from-lport', 0, 'output == "fake_port" && ip', 1000)
self._qos_add(*args)
self.assertRaises(RuntimeError, self._qos_add, *args)
def test_qos_add_may_exist(self):
args = ('from-lport', 0, 'output == "fake_port" && ip', 1000)
row = self._qos_add(*args)
row2 = self._qos_add(*args, may_exist=True)
self.assertEqual(row, row2)
def test_qos_add_extids(self):
external_ids = {'mykey': 'myvalue', 'yourkey': 'yourvalue'}
qos = self._qos_add('from-lport', 0, 'output == "fake_port" && ip',
dscp=11, external_ids=external_ids)
self.assertEqual(external_ids, qos.external_ids)
def test_qos_del_all(self):
r1 = self._qos_add('from-lport', 0, 'output == "fake_port"', 1000)
self.api.qos_del(self.switch.uuid).execute(check_error=True)
self.assertNotIn(r1.uuid, self.api.tables['QoS'].rows)
self.assertEqual([], self.switch.qos_rules)
def test_qos_del_direction(self):
r1 = self._qos_add('from-lport', 0, 'output == "fake_port"', 1000)
r2 = self._qos_add('to-lport', 0, 'output == "fake_port"', 1000)
self.api.qos_del(self.switch.uuid, 'from-lport').execute(
check_error=True)
self.assertNotIn(r1, self.switch.qos_rules)
self.assertIn(r2, self.switch.qos_rules)
def test_qos_del_direction_priority_match(self):
r1 = self._qos_add('from-lport', 0, 'output == "fake_port"', 1000)
r2 = self._qos_add('from-lport', 1, 'output == "fake_port"', 1000)
cmd = self.api.qos_del(self.switch.uuid,
'from-lport', 0, 'output == "fake_port"')
cmd.execute(check_error=True)
self.assertNotIn(r1, self.switch.qos_rules)
self.assertIn(r2, self.switch.qos_rules)
def test_qos_del_priority_without_match(self):
self.assertRaises(TypeError, self.api.qos_del, self.switch.uuid,
'from-lport', 0)
def test_qos_del_priority_without_direction(self):
self.assertRaises(TypeError, self.api.qos_del, self.switch.uuid,
priority=0)
def test_qos_list(self):
r1 = self._qos_add('from-lport', 0, 'output == "fake_port"', 1000)
r2 = self._qos_add('from-lport', 1, 'output == "fake_port2"', 1000)
qos_rules = self.api.qos_list(self.switch.uuid).execute(
check_error=True)
self.assertIn(r1, qos_rules)
self.assertIn(r2, qos_rules)
class TestLspOps(OvnNorthboundTest):
def setUp(self):
super(TestLspOps, self).setUp()