diff --git a/ovsdbapp/schema/ovn_northbound/api.py b/ovsdbapp/schema/ovn_northbound/api.py index 1c2b65ac..f5e7b706 100644 --- a/ovsdbapp/schema/ovn_northbound/api.py +++ b/ovsdbapp/schema/ovn_northbound/api.py @@ -249,6 +249,19 @@ class API(api.API, metaclass=abc.ABCMeta): :returns: :class:`Command` with RowView list result """ + @abc.abstractmethod + def qos_del_ext_ids(self, lswitch_name, external_ids, if_exists=True): + """Delete all QoS rules related to a floating IP. + + :param lswitch_name: The unique name of the logical switch + :type lswitch_name: string + :param external_ids: Pairs of key/value to find in the "external_ids" + :type external_ids: dict + :param if_exists: Do not fail if the Logical_Switch row does not exist + :type if_exists: bool + :returns: :class:`Command` with no result + """ + @abc.abstractmethod def lsp_add(self, switch, port, parent_name=None, tag=None, may_exist=False, **columns): diff --git a/ovsdbapp/schema/ovn_northbound/commands.py b/ovsdbapp/schema/ovn_northbound/commands.py index 66fd30ed..dee9d102 100644 --- a/ovsdbapp/schema/ovn_northbound/commands.py +++ b/ovsdbapp/schema/ovn_northbound/commands.py @@ -303,6 +303,31 @@ class QoSListCommand(cmd.ReadOnlyCommand): self.result = [rowview.RowView(row) for row in ls.qos_rules] +class QoSDelExtIdCommand(cmd.BaseCommand): + def __init__(self, api, lswitch, external_ids, if_exists=False): + if not external_ids: + raise TypeError('external_ids dictionary cannot be empty') + super(QoSDelExtIdCommand, self).__init__(api) + self.lswitch = lswitch + self.external_ids = external_ids + self.if_exists = if_exists + + def run_idl(self, txn): + try: + lswitch = idlutils.row_by_value(self.api.idl, 'Logical_Switch', + 'name', self.lswitch) + except idlutils.RowNotFound: + if self.if_exists: + return + msg = 'Logical Switch %s does not exist' % self.lswitch + raise RuntimeError(msg) + + for qos in lswitch.qos_rules: + if self.external_ids.items() <= qos.external_ids.items(): + lswitch.delvalue('qos_rules', qos) + qos.delete() + + class LspAddCommand(cmd.AddCommand): table_name = 'Logical_Switch_Port' diff --git a/ovsdbapp/schema/ovn_northbound/impl_idl.py b/ovsdbapp/schema/ovn_northbound/impl_idl.py index 483a8ec1..a1bc7919 100644 --- a/ovsdbapp/schema/ovn_northbound/impl_idl.py +++ b/ovsdbapp/schema/ovn_northbound/impl_idl.py @@ -89,6 +89,10 @@ class OvnNbApiIdlImpl(ovs_idl.Backend, api.API): def qos_list(self, switch): return cmd.QoSListCommand(self, switch) + def qos_del_ext_ids(self, lswitch_name, external_ids, if_exists=True): + return cmd.QoSDelExtIdCommand(self, lswitch_name, external_ids, + if_exists=if_exists) + def lsp_add(self, switch, port, parent_name=None, tag=None, may_exist=False, **columns): return cmd.LspAddCommand(self, switch, port, parent_name, tag, diff --git a/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py b/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py index e3496e81..08222b83 100644 --- a/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py +++ b/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py @@ -318,6 +318,69 @@ class TestQoSOps(OvnNorthboundTest): self.assertIn(r1, qos_rules) self.assertIn(r2, qos_rules) + def _create_fip_qoses(self): + ext_ids_1 = {'key1': 'value1', 'key2': 'value2'} + self.qos_1 = self._qos_add('from-lport', 0, 'output == "fake_port1"', + dscp=11, external_ids=ext_ids_1) + ext_ids_2 = {'key3': 'value3', 'key4': 'value4'} + self.qos_2 = self._qos_add('from-lport', 1, 'output == "fake_port2"', + dscp=11, external_ids=ext_ids_2) + self.qos_3 = self._qos_add('from-lport', 2, 'output == "fake_port3"', + dscp=10) + + def test_qos_delete_external_ids(self): + self._create_fip_qoses() + self.api.qos_del_ext_ids(self.switch.name, + {'key1': 'value1'}).execute(check_error=True) + qos_rules = self.api.qos_list( + self.switch.uuid).execute(check_error=True) + self.assertCountEqual([self.qos_2, self.qos_3], qos_rules) + + self.api.qos_del_ext_ids( + self.switch.name, + {'key3': 'value3', 'key4': 'value4'}).execute(check_error=True) + qos_rules = self.api.qos_list( + self.switch.uuid).execute(check_error=True) + self.assertCountEqual([self.qos_3], qos_rules) + + def test_qos_delete_external_ids_wrong_keys_or_values(self): + self._create_fip_qoses() + self.api.qos_del_ext_ids(self.switch.name, + {'key_z': 'value1'}).execute(check_error=True) + qos_rules = self.api.qos_list( + self.switch.uuid).execute(check_error=True) + self.assertCountEqual([self.qos_1, self.qos_2, self.qos_3], qos_rules) + + self.api.qos_del_ext_ids(self.switch.name, + {'key1': 'value_z'}).execute(check_error=True) + qos_rules = self.api.qos_list( + self.switch.uuid).execute(check_error=True) + self.assertCountEqual([self.qos_1, self.qos_2, self.qos_3], qos_rules) + + self.api.qos_del_ext_ids( + self.switch.name, + {'key3': 'value3', 'key4': 'value_z'}).execute(check_error=True) + qos_rules = self.api.qos_list( + self.switch.uuid).execute(check_error=True) + self.assertCountEqual([self.qos_1, self.qos_2, self.qos_3], qos_rules) + + def test_qos_delete_external_ids_empty_dict(self): + self.assertRaises(TypeError, self.api.qos_del_ext_ids, + self.switch.name, {}) + + def test_qos_delete_external_ids_if_exists(self): + self._create_fip_qoses() + cmd = self.api.qos_del_ext_ids('wrong_ls_name', + {'key1': 'value1'}, if_exists=False) + self.assertRaises(RuntimeError, cmd.execute, check_error=True) + + self.api.qos_del_ext_ids('wrong_ls_name', + {'key1': 'value1'}).execute(check_error=True) + # No qos rule has been deleted from the correct logical switch. + qos_rules = self.api.qos_list( + self.switch.uuid).execute(check_error=True) + self.assertCountEqual([self.qos_1, self.qos_2, self.qos_3], qos_rules) + class TestLspOps(OvnNorthboundTest): def setUp(self):