From 90dfca5dfc60e48544ff25f63c3fa59cb88fc521 Mon Sep 17 00:00:00 2001 From: Lucas Alvares Gomes Date: Fri, 25 May 2018 15:18:31 +0100 Subject: [PATCH] Add Port_Group commands This patch is adding commands to manipulate the new Port_Group table introduced in the OVN Northbound Database. Four new commands are being added: * pg_add() -> To add a new Port Group * pg_del() -> To delete a Port Group * pg_add_ports() -> To add a list of LSP to a Port Group * pg_add_acls() -> To add a list of ACL to a Port Group * pg_del_ports() -> To delete a list of LSP from a Port Group * pg_del_acls() -> To delete a list of ACL from a Port Group Change-Id: I9bae9a5681501d8e8a85c25ccb6d496d5b3f8681 --- lower-constraints.txt | 1 + ovsdbapp/schema/ovn_northbound/api.py | 86 ++++++++++++++++ ovsdbapp/schema/ovn_northbound/commands.py | 99 +++++++++++++++++++ ovsdbapp/schema/ovn_northbound/impl_idl.py | 18 ++++ .../schema/ovn_northbound/test_impl_idl.py | 91 +++++++++++++++++ requirements.txt | 1 + 6 files changed, 296 insertions(+) diff --git a/lower-constraints.txt b/lower-constraints.txt index d0f7e396..d75d9291 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -24,6 +24,7 @@ netaddr==0.7.18 openstackdocstheme==1.18.1 os-client-config==1.28.0 os-testr==1.0.0 +oslo.utils==3.33.0 oslotest==3.2.0 ovs==2.8.0 pbr==2.0.0 diff --git a/ovsdbapp/schema/ovn_northbound/api.py b/ovsdbapp/schema/ovn_northbound/api.py index 505ed666..9476a1b4 100644 --- a/ovsdbapp/schema/ovn_northbound/api.py +++ b/ovsdbapp/schema/ovn_northbound/api.py @@ -766,3 +766,89 @@ class API(api.API): :type external_ids: key: string, value: string :returns: :class:`Command` with no result """ + + @abc.abstractmethod + def pg_add(self, name, may_exist=False, **columns): + """Create a port group + + :param name: The name of the port group + :type name: string + :param may_exist: If True, don't fail if the port group already + exists + :type may_exist: bool + :param columns: Additional columns to directly set on the port + group (e.g external_ids, ports, acls) + :type columns: dictionary + :returns: :class:`Command` with RowView result + """ + + @abc.abstractmethod + def pg_del(self, name, if_exists=False): + """Delete a port group + + :param name: The name of the port group + :type name: string + :param if_exists: If True, don't fail if the router doesn't exist + :type if_exists: boolean + :returns: :class:`Command` with no result + """ + + @abc.abstractmethod + def pg_add_ports(self, pg_id, lsp): + """Add a list of logical port to a port group + + :param pg_id: The name or uuid of the port group + :type pg_id: string or uuid.UUID + :param lsp: A list of :class:`Command` with a Logical_Switch_Port + instance result or UUID + :type lsp: A list of :class:`Command` with a Logical_Switch_Port + or string or uuid.UUID + A Logical_Switch_Port instance or string + or uuid.UUID + :returns: :class:`Command` with no result + """ + + @abc.abstractmethod + def pg_del_ports(self, pg_id, lsp, if_exists=False): + """Delete a list of logical port from a port group + + :param pg_id: The name or uuid of the port group + :type pg_id: string or uuid.UUID + :param lsp: A list of :class:`Command` with a Logical_Switch_Port + instance result or UUID + :type lsp: A list of :class:`Command` with a Logical_Switch_Port + or string or uuid.UUID + :type if_exists: If True, don't fail if the logical port(s) doesn't + exist + :type if_exists: boolean + :returns: :class:`Command` with no result + """ + + @abc.abstractmethod + def pg_add_acls(self, pg_id, acl): + """Add a list of ACL to a port group + + :param pg_id: The name or uuid of the port group + :type pg_id: string or uuid.UUID + :param acl: The ACL instance or UUID + :type acl: A list of ACL instance or string or uuid.UUID + :param acl: A list of :class:`Command` with an ACL instance + result or UUID + :type acl: A :class:`Command` with an ACL or string + r uuid.UUID + :returns: :class:`Command` with no result + """ + + @abc.abstractmethod + def pg_del_acls(self, pg_id, acl, if_exists=False): + """Delete a list of ACL from a port group + + :param pg_id: The name or uuid of the port group + :type pg_id: string or uuid.UUID + :type acl: A list of ACL instance or string or uuid.UUID + :param acl: A list of :class:`Command` with an ACL instance + result or UUID + :type if_exists: If True, don't fail if the ACL(s) doesn't exist + :type if_exists: boolean + :returns: :class:`Command` with no result + """ diff --git a/ovsdbapp/schema/ovn_northbound/commands.py b/ovsdbapp/schema/ovn_northbound/commands.py index 7e85f8e9..1e343891 100644 --- a/ovsdbapp/schema/ovn_northbound/commands.py +++ b/ovsdbapp/schema/ovn_northbound/commands.py @@ -12,6 +12,7 @@ import re import netaddr +from oslo_utils import uuidutils from ovsdbapp.backend.ovs_idl import command as cmd from ovsdbapp.backend.ovs_idl import idlutils @@ -1056,3 +1057,101 @@ class DnsSetExternalIdsCommand(cmd.BaseCommand): except idlutils.RowNotFound: msg = "DNS %s does not exist" % self.row_uuid raise RuntimeError(msg) + + +class PgAddCommand(cmd.AddCommand): + table_name = 'Port_Group' + + def __init__(self, api, name, may_exist=False, **columns): + super(PgAddCommand, self).__init__(api) + self.name = name + self.may_exist = may_exist + self.columns = columns + + def run_idl(self, txn): + if self.may_exist: + try: + pg = self.api.lookup(self.table_name, self.name) + self.result = rowview.RowView(pg) + except idlutils.RowNotFound: + pass + + pg = txn.insert(self.api._tables[self.table_name]) + pg.name = self.name + self.set_columns(pg, **self.columns) + self.result = pg.uuid + + +class PgDelCommand(cmd.BaseCommand): + table_name = 'Port_Group' + + def __init__(self, api, name, if_exists=False): + super(PgDelCommand, self).__init__(api) + self.name = name + self.if_exists = if_exists + + def run_idl(self, txn): + try: + pg = self.api.lookup(self.table_name, self.name) + pg.delete() + except idlutils.RowNotFound: + if self.if_exists: + return + raise RuntimeError('Port group %s does not exist' % self.name) + + +class _PgUpdateHelper(cmd.BaseCommand): + method = None + + def __init__(self, api, port_group, lsp=None, acl=None, if_exists=False): + super(_PgUpdateHelper, self).__init__(api) + self.port_group = port_group + self.lsp = [] if lsp is None else self._listify(lsp) + self.acl = [] if acl is None else self._listify(acl) + self.if_exists = if_exists + + def _listify(self, res): + return res if isinstance(res, (list, tuple)) else [res] + + def _fetch_resource(self, type_, uuid_): + table = 'Logical_Switch_Port' if type_ == 'ports' else 'ACL' + return self.api.lookup(table, uuid_) + + def _run_method(self, pg, column, resource): + if not resource: + return + + if isinstance(resource, cmd.BaseCommand): + resource = resource.result + elif uuidutils.is_uuid_like(resource): + try: + resource = self._fetch_resource(column, resource) + except idlutils.RowNotFound: + if self.if_exists: + return + raise RuntimeError( + 'Resource %(res)s of type "%(type)s" does not exist' % + {'res': resource, 'type': column}) + + getattr(pg, self.method)(column, resource) + + def run_idl(self, txn): + try: + pg = self.api.lookup('Port_Group', self.port_group) + except idlutils.RowNotFound: + raise RuntimeError('Port group %s does not exist' % + self.port_group) + + for lsp in self.lsp: + self._run_method(pg, 'ports', lsp) + + for acl in self.acl: + self._run_method(pg, 'acls', acl) + + +class PgAddDataCommand(_PgUpdateHelper): + method = 'addvalue' + + +class PgDelDataCommand(_PgUpdateHelper): + method = 'delvalue' diff --git a/ovsdbapp/schema/ovn_northbound/impl_idl.py b/ovsdbapp/schema/ovn_northbound/impl_idl.py index 37a16fd4..69445471 100644 --- a/ovsdbapp/schema/ovn_northbound/impl_idl.py +++ b/ovsdbapp/schema/ovn_northbound/impl_idl.py @@ -249,3 +249,21 @@ class OvnNbApiIdlImpl(ovs_idl.Backend, api.API): def dns_set_external_ids(self, uuid, **external_ids): return cmd.DnsSetExternalIdsCommand(self, uuid, **external_ids) + + def pg_add(self, name, may_exist=False, **columns): + return cmd.PgAddCommand(self, name, may_exist=may_exist, **columns) + + def pg_del(self, name, if_exists=False): + return cmd.PgDelCommand(self, name, if_exists=if_exists) + + def pg_add_ports(self, pg_id, lsp): + return cmd.PgAddDataCommand(self, pg_id, lsp=lsp) + + def pg_del_ports(self, pg_id, lsp, if_exists=False): + return cmd.PgDelDataCommand(self, pg_id, lsp=lsp, if_exists=if_exists) + + def pg_add_acls(self, pg_id, acl): + return cmd.PgAddDataCommand(self, pg_id, acl=acl) + + def pg_del_acls(self, pg_id, acl, if_exists=False): + return cmd.PgDelDataCommand(self, pg_id, acl=acl, if_exists=if_exists) 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 5eaf8b61..442e614b 100644 --- a/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py +++ b/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py @@ -11,6 +11,7 @@ # under the License. import netaddr +from oslo_utils import uuidutils import testscenarios from ovsdbapp.backend.ovs_idl import idlutils @@ -1197,3 +1198,93 @@ class TestLsDnsOps(OvnNorthboundTest): [dns.uuid for dns in ls1.dns_records]) self.api.ls_remove_dns_record(ls1.uuid, dns1.uuid).execute() self.assertEqual([], ls1.dns_records) + + +class TestPortGroup(OvnNorthboundTest): + + def setUp(self): + super(TestPortGroup, self).setUp() + self.switch = self.useFixture(fixtures.LogicalSwitchFixture()).obj + self.pg_name = 'testpg-%s' % uuidutils.generate_uuid() + + def test_port_group(self): + # Assert the Port Group was added + self.api.pg_add(self.pg_name).execute(check_error=True) + row = self.api.db_find( + 'Port_Group', + ('name', '=', self.pg_name)).execute(check_error=True) + self.assertIsNotNone(row) + self.assertEqual(self.pg_name, row[0]['name']) + self.assertEqual([], row[0]['ports']) + self.assertEqual([], row[0]['acls']) + + # Assert the Port Group was deleted + self.api.pg_del(self.pg_name).execute(check_error=True) + row = self.api.db_find( + 'Port_Group', + ('name', '=', self.pg_name)).execute(check_error=True) + self.assertEqual([], row) + + def test_port_group_ports_and_acls(self): + lsp_add_cmd = self.api.lsp_add(self.switch.uuid, 'testport') + acl_add_cmd_1 = self.api.acl_add( + self.switch.uuid, 'from-lport', 0, 'output == "fake_port"', + 'drop') + acl_add_cmd_2 = self.api.acl_add( + self.switch.uuid, 'from-lport', 0, 'output == "fake_port" && ip', + 'drop') + with self.api.transaction(check_error=True) as txn: + txn.add(lsp_add_cmd) + txn.add(acl_add_cmd_1) + txn.add(acl_add_cmd_2) + txn.add(self.api.pg_add(self.pg_name)) + txn.add(self.api.pg_add_acls( + self.pg_name, [acl_add_cmd_1, acl_add_cmd_2])) + + port_uuid = lsp_add_cmd.result.uuid + acl_uuid_1 = acl_add_cmd_1.result.uuid + acl_uuid_2 = acl_add_cmd_2.result.uuid + + # Lets add the port using the UUID instead of a `Command` to + # exercise the API + self.api.pg_add_ports(self.pg_name, port_uuid).execute( + check_error=True) + row = self.api.db_find( + 'Port_Group', + ('name', '=', self.pg_name)).execute(check_error=True) + self.assertIsNotNone(row) + self.assertEqual(self.pg_name, row[0]['name']) + # Assert the port and ACLs were added from the Port Group + self.assertEqual([port_uuid], row[0]['ports']) + self.assertEqual(sorted([acl_uuid_1, acl_uuid_2]), + sorted(row[0]['acls'])) + + # Delete an ACL and the Port from the Port Group + with self.api.transaction(check_error=True) as txn: + txn.add(self.api.pg_del_ports(self.pg_name, port_uuid)) + txn.add(self.api.pg_del_acls(self.pg_name, acl_uuid_1)) + + row = self.api.db_find( + 'Port_Group', + ('name', '=', self.pg_name)).execute(check_error=True) + self.assertIsNotNone(row) + self.assertEqual(self.pg_name, row[0]['name']) + # Assert the port and ACL were removed from the Port Group + self.assertEqual([], row[0]['ports']) + self.assertEqual([acl_uuid_2], row[0]['acls']) + + def test_pg_del_ports_and_acls_if_exists(self): + self.api.pg_add(self.pg_name).execute(check_error=True) + non_existent_res = uuidutils.generate_uuid() + + # Assert that if if_exists is False (default) it will raise an error + self.assertRaises(RuntimeError, self.api.pg_del_ports(self.pg_name, + non_existent_res).execute, True) + self.assertRaises(RuntimeError, self.api.pg_del_acls(self.pg_name, + non_existent_res).execute, True) + + # Assert that if if_exists is True it won't raise an error + self.api.pg_del_ports(self.pg_name, non_existent_res, + if_exists=True).execute(check_error=True) + self.api.pg_del_acls(self.pg_name, non_existent_res, + if_exists=True).execute(check_error=True) diff --git a/requirements.txt b/requirements.txt index ff6ceb46..dd26a80c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ fixtures>=3.0.0 # Apache-2.0/BSD netaddr>=0.7.18 # BSD +oslo.utils>=3.33.0 # Apache-2.0 ovs>=2.8.0 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0 six>=1.10.0 # MIT