Merge "Add QoS command for ovn northbound db."
This commit is contained in:
commit
4fc6d1d648
|
@ -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'
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue