Added functionality to wait for port creation

Added a waiter that compares the current status
of the port to a target value and returns the port
when that is achieved.

Closes-Bug: 1845486
Change-Id: Ibc5434244f3758c60ddefa96d78476948a47ed13
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index f207066..55b39ad 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -584,6 +584,22 @@
     raise lib_exc.TimeoutException()
 
 
+def wait_for_port_status(client, port_id, status):
+    """Wait for a port reach a certain status : ["BUILD" | "DOWN" | "ACTIVE"]
+    :param client: The network client to use when querying the port's
+    status
+    :param status: A string to compare the current port status-to.
+    :param port_id: The uuid of the port we would like queried for status.
+    """
+    start_time = time.time()
+    while (time.time() - start_time <= client.build_timeout):
+        result = client.show_port(port_id)
+        if result['port']['status'].lower() == status.lower():
+            return result
+        time.sleep(client.build_interval)
+    raise lib_exc.TimeoutException
+
+
 def wait_for_ssh(ssh_client, timeout=30):
     """Waits for SSH connection to become usable"""
     start_time = int(time.time())
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 2843498..bf3f62f 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -145,6 +145,7 @@
             - 'binding:vnic_type' - defaults to CONF.network.port_vnic_type
             - 'binding:profile' - defaults to CONF.network.port_profile
         """
+
         if not client:
             client = self.ports_client
         name = data_utils.rand_name(
@@ -158,10 +159,12 @@
             network_id=network_id,
             **kwargs)
         self.assertIsNotNone(result, 'Unable to allocate port')
-        port = result['port']
+        port_id = result['port']['id']
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        client.delete_port, port['id'])
-        return port
+                        client.delete_port, port_id)
+        port = waiters.wait_for_port_status(
+            client=client, port_id=port_id, status="DOWN")
+        return port["port"]
 
     def create_keypair(self, client=None, **kwargs):
         """Creates keypair
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index 1d0ee77..662030e 100755
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -21,6 +21,7 @@
 from tempest import exceptions
 from tempest.lib import exceptions as lib_exc
 from tempest.lib.services.compute import servers_client
+from tempest.lib.services.network import ports_client
 from tempest.lib.services.volume.v2 import volumes_client
 from tempest.tests import base
 import tempest.tests.utils as utils
@@ -588,6 +589,48 @@
         )
 
 
+class TestPortCreationWaiter(base.TestCase):
+    def test_wait_for_port_status(self):
+        """Test that the waiter replies with the port before the timeout"""
+
+        def client_response(self):
+            """Mock client response, replies with the final status after
+            2 calls
+            """
+            if mock_client.call_count >= 2:
+                return mock_port
+            else:
+                mock_client.call_count += 1
+                return mock_port_build
+
+        mock_port = {'port': {'id': '1234', 'status': "DOWN"}}
+        mock_port_build = {'port': {'id': '1234', 'status': "BUILD"}}
+        mock_client = mock.Mock(
+            spec=ports_client.PortsClient,
+            build_timeout=30, build_interval=1,
+            show_port=client_response)
+        fake_port_id = "1234"
+        fake_status = "DOWN"
+        self.assertEqual(mock_port, waiters.wait_for_port_status(
+            mock_client, fake_port_id, fake_status))
+
+    def test_wait_for_port_status_timeout(self):
+        """Negative test - checking that a timeout
+        presented by a small 'fake_timeout' and a static status of
+        'BUILD' in the mock will raise a timeout exception
+        """
+        mock_port = {'port': {'id': '1234', 'status': "BUILD"}}
+        mock_client = mock.Mock(
+            spec=ports_client.PortsClient,
+            build_timeout=2, build_interval=1,
+            show_port=lambda id: mock_port)
+        fake_port_id = "1234"
+        fake_status = "ACTIVE"
+        self.assertRaises(lib_exc.TimeoutException,
+                          waiters.wait_for_port_status, mock_client,
+                          fake_port_id, fake_status)
+
+
 class TestServerFloatingIPWaiters(base.TestCase):
 
     def test_wait_for_server_floating_ip_associate_timeout(self):