Wait for port status when getting IPv4 address

Depending on neutron backend, ports may take some time to become active,
and im most cases we need them active to be able to successfully access
the node (e.g. via SSH).

This patch reuses `waiters.wait_for_interface_status` function
(which was amended to accept list of port statuses to wait for),
which in turn uses the build_timeout and build_inerval options from
'compute' config sections. The logic of the function is preserved.

Change-Id: I8baa3e61789b0553b9f85214b60c9e0b8dafde31
Closes-Bug: #1728600
Related-prod:PROD-14826, PROD-18630
(cherry picked from commit a6d0fdb24c7634fb2de0b12058a66ee67b6bd84c)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 08e2a12..4f3afc5 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -14,6 +14,7 @@
 import time
 
 from oslo_log import log as logging
+import six
 
 from tempest.common import image as common_image
 from tempest import config
@@ -265,13 +266,16 @@
 
 
 def wait_for_interface_status(client, server_id, port_id, status):
-    """Waits for an interface to reach a given status."""
+    """Waits for an interface to reach a (one of) given status(es)."""
+    if isinstance(status, six.string_types):
+        status = [status]
+
     body = (client.show_interface(server_id, port_id)
             ['interfaceAttachment'])
     interface_status = body['port_state']
     start = int(time.time())
 
-    while(interface_status != status):
+    while(interface_status not in status):
         time.sleep(client.build_interval)
         body = (client.show_interface(server_id, port_id)
                 ['interfaceAttachment'])
@@ -279,8 +283,8 @@
 
         timed_out = int(time.time()) - start >= client.build_timeout
 
-        if interface_status != status and timed_out:
-            message = ('Interface %s failed to reach %s status '
+        if interface_status not in status and timed_out:
+            message = ('Interface %s failed to reach either of %s statuses '
                        '(current %s) within the required time (%s s).' %
                        (port_id, status, interface_status,
                         client.build_timeout))
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 0b38761..ef49adc 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -773,8 +773,14 @@
         ports = self.os_admin.ports_client.list_ports(
             device_id=server['id'], fixed_ip=ip_addr)['ports']
         # A port can have more than one IP address in some cases.
-        # If the network is dual-stack (IPv4 + IPv6), this port is associated
-        # with 2 subnets
+        # If the network is dual-stack (IPv4 + IPv6), this port
+        # is associated with 2 subnets
+        port_map = [{"id": p["id"],
+                     "ip": fxip["ip_address"],
+                     "status": p["status"]}
+                    for p in ports
+                    for fxip in p["fixed_ips"]
+                    if netutils.is_valid_ipv4(fxip["ip_address"])]
         p_status = ['ACTIVE']
         # NOTE(vsaienko) With Ironic, instances live on separate hardware
         # servers. Neutron does not bind ports for Ironic instances, as a
@@ -782,22 +788,31 @@
         # TODO(vsaienko) remove once bug: #1599836 is resolved.
         if getattr(CONF.service_available, 'ironic', False):
             p_status.append('DOWN')
-        port_map = [(p["id"], fxip["ip_address"])
-                    for p in ports
-                    for fxip in p["fixed_ips"]
-                    if (netutils.is_valid_ipv4(fxip["ip_address"]) and
-                        p['status'] in p_status)]
-        inactive = [p for p in ports if p['status'] != 'ACTIVE']
+        # TODO(pas-ha) add a new waiter that will wait for all/at least one
+        # port on instance at once, and use it here
+        for pm in port_map:
+            try:
+                port = waiters.wait_for_interface_status(self.interface_client,
+                                                         server['id'],
+                                                         pm["id"], p_status)
+                pm["status"] = port['port_state']
+            except lib_exc.TimeoutException:
+                # NOTE(pas-ha) as server might have several IPv4 ports
+                # we need at least one of them to be in appropriate state
+                # so just ignore timeouts and deal with it later
+                pass
+        port_map = [p for p in port_map if p["status"] in p_status]
+        inactive = [p for p in port_map if p["status"] != 'ACTIVE']
         if inactive:
-            LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
-
+            LOG.warning("Instance has port that are not ACTIVE: %s", inactive)
         self.assertNotEmpty(port_map,
                             "No IPv4 addresses found in: %s" % ports)
         self.assertEqual(len(port_map), 1,
                          "Found multiple IPv4 addresses: %s. "
                          "Unable to determine which port to target."
                          % port_map)
-        return port_map[0]
+
+        return port_map[0]["id"], port_map[0]["ip"]
 
     def _get_network_by_name(self, network_name):
         net = self.os_admin.networks_client.list_networks(