Add base API tests for port forwarding

This patch adds base client support and API tests for port forwarding
feature.

This patch also enable port_forwarding service plugin in
neutron_tempest_plugin CI jobs.

Depends-On: https://review.opendev.org/#/c/661581/

Change-Id: Ice58232b640ea8aa28d7a54aa9cf14e6ad0a2bb0
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index 7b91d94..639fa3c 100644
--- a/neutron_tempest_plugin/api/base.py
+++ b/neutron_tempest_plugin/api/base.py
@@ -117,6 +117,7 @@
         cls.ports = []
         cls.routers = []
         cls.floating_ips = []
+        cls.port_forwardings = []
         cls.metering_labels = []
         cls.service_profiles = []
         cls.flavors = []
@@ -144,6 +145,10 @@
             for trunk in cls.trunks:
                 cls._try_delete_resource(cls.delete_trunk, trunk)
 
+            # Clean up port forwardings
+            for pf in cls.port_forwardings:
+                cls._try_delete_resource(cls.delete_port_forwarding, pf)
+
             # Clean up floating IPs
             for floating_ip in cls.floating_ips:
                 cls._try_delete_resource(cls.delete_floatingip, floating_ip)
@@ -652,6 +657,66 @@
         client.delete_floatingip(floating_ip['id'])
 
     @classmethod
+    def create_port_forwarding(cls, fip_id, internal_port_id,
+                               internal_port, external_port,
+                               internal_ip_address=None, protocol="tcp",
+                               client=None):
+        """Creates a port forwarding.
+
+        Create a port forwarding and schedule it for later deletion.
+        If a client is passed, then it is used for deleting the PF too.
+
+        :param fip_id: The ID of the floating IP address.
+
+        :param internal_port_id: The ID of the Neutron port associated to
+        the floating IP port forwarding.
+
+        :param internal_port: The TCP/UDP/other protocol port number of the
+        Neutron port fixed IP address associated to the floating ip
+        port forwarding.
+
+        :param external_port: The TCP/UDP/other protocol port number of
+        the port forwarding floating IP address.
+
+        :param internal_ip_address: The fixed IPv4 address of the Neutron
+        port associated to the floating IP port forwarding.
+
+        :param protocol: The IP protocol used in the floating IP port
+        forwarding.
+
+        :param client: network client to be used for creating and cleaning up
+        the floating IP port forwarding.
+        """
+
+        client = client or cls.client
+
+        pf = client.create_port_forwarding(
+            fip_id, internal_port_id, internal_port, external_port,
+            internal_ip_address, protocol)['port_forwarding']
+
+        # save ID of floating IP associated with port forwarding for final
+        # cleanup
+        pf['floatingip_id'] = fip_id
+
+        # save client to be used later in cls.delete_port_forwarding
+        # for final cleanup
+        pf['client'] = client
+        cls.port_forwardings.append(pf)
+        return pf
+
+    @classmethod
+    def delete_port_forwarding(cls, pf, client=None):
+        """Delete port forwarding
+
+        :param client: Client to be used
+        If client is not given it will use the client used to create
+        the port forwarding, or cls.client if unknown.
+        """
+
+        client = client or pf.get('client') or cls.client
+        client.delete_port_forwarding(pf['floatingip_id'], pf['id'])
+
+    @classmethod
     def create_router_interface(cls, router_id, subnet_id):
         """Wrapper utility that returns a router interface."""
         interface = cls.client.add_router_interface_with_subnet_id(