Merge "Update microversion for for the tests which fail due to multiattach enabled" into mcp/yoga
diff --git a/requirements.txt b/requirements.txt
index c4c7fcc..6908527 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -21,3 +21,4 @@
 PrettyTable>=0.7.1 # BSD
 urllib3>=1.21.1 # MIT
 debtcollector>=1.2.0 # Apache-2.0
+tenacity>=4.4.0 # Apache-2.0
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 696d68d..2dca224 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -19,6 +19,7 @@
 from tempest import exceptions
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
+from tempest.lib.common import waiters as lib_waiters
 from tempest.lib import exceptions as lib_exc
 import tempest.test
 
@@ -223,6 +224,9 @@
             test_utils.call_and_ignore_notfound_exc(
                 cls.routers_client.remove_router_interface, router['id'],
                 subnet_id=i['fixed_ips'][0]['subnet_id'])
+            lib_waiters.wait_router_interface_removed(
+                cls.ports_client, router['id'],
+                subnet_id=i['fixed_ips'][0]['subnet_id'])
         cls.routers_client.delete_router(router['id'])
 
 
diff --git a/tempest/config.py b/tempest/config.py
index 3045759..52c68e9 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -303,6 +303,15 @@
                help="Valid secondary image reference to be used in tests. "
                     "This is a required option, but if only one image is "
                     "available duplicate the value of image_ref above"),
+    cfg.StrOpt('image_full_ref',
+               help="This is image with full OS like ubuntu/centos used"
+                    "in some tests. When not set related tests will be "
+                    "skipped"),
+    cfg.StrOpt('image_full_username',
+               default="ubuntu",
+               help="Username for image_full_ref authentication."),
+    cfg.StrOpt('image_full_flavor_ref',
+               help="Flavor to boot image_full_ref."),
     cfg.StrOpt('certified_image_ref',
                help="Valid image reference to be used in image certificate "
                     "validation tests when enabled. This image must also "
diff --git a/tempest/lib/common/constants.py b/tempest/lib/common/constants.py
new file mode 100644
index 0000000..57fdd93
--- /dev/null
+++ b/tempest/lib/common/constants.py
@@ -0,0 +1,5 @@
+# Retry constants
+RETRY_ATTEMPTS = 30
+RETRY_INITIAL_DELAY = 1
+RETRY_BACKOFF = 3
+RETRY_MAX = 10
diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
index 474f04e..cbac5a6 100644
--- a/tempest/lib/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -16,11 +16,15 @@
 
 import netaddr
 from oslo_log import log as logging
+import tenacity
 
+import tempest.lib.common.constants as const
 from tempest.lib.common import cred_client
 from tempest.lib.common import cred_provider
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common import waiters as lib_waiters
 from tempest.lib import exceptions as lib_exc
+
 from tempest.lib.services import clients
 
 LOG = logging.getLogger(__name__)
@@ -496,6 +500,11 @@
             del self._creds[creds_name]
         return self.get_credentials(roles, scope=scope)
 
+    @tenacity.retry(
+        retry=tenacity.retry_if_exception_type(lib_exc.Conflict),
+        wait=tenacity.wait_incrementing(
+            const.RETRY_INITIAL_DELAY, const.RETRY_BACKOFF, const.RETRY_MAX),
+        stop=tenacity.stop_after_attempt(const.RETRY_ATTEMPTS))
     def _clear_isolated_router(self, router_id, router_name):
         client = self.routers_admin_client
         try:
@@ -537,6 +546,9 @@
                     client.remove_router_interface(
                         creds.router['id'],
                         subnet_id=creds.subnet['id'])
+                    lib_waiters.wait_router_interface_removed(
+                        self.ports_admin_client, creds.router['id'],
+                        subnet_id=creds.subnet['id'])
                 except lib_exc.NotFound:
                     LOG.warning('router with name: %s not found for delete',
                                 creds.router['name'])
diff --git a/tempest/lib/common/waiters.py b/tempest/lib/common/waiters.py
new file mode 100644
index 0000000..7bedd0d
--- /dev/null
+++ b/tempest/lib/common/waiters.py
@@ -0,0 +1,33 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import time
+
+from tempest.lib import exceptions as lib_exc
+
+
+def wait_router_interface_removed(
+    ports_client, router_id, subnet_id, timeout=30, interval=3):
+    """Waits for router inface is removed"""
+    start_time = int(time.time())
+    while int(time.time()) - start_time < timeout:
+        try:
+            ports = ports_client.list_ports(
+                device_id=router_id,
+                fixed_ips=f"subnet_id={subnet_id}")['ports']
+
+            if len(ports) == 0:
+                return
+            time.sleep(interval)
+        except Exception:
+            pass
+    raise lib_exc.TimeoutException()
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index d04cb9a..db8f533 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -41,6 +41,11 @@
         super(TestSnapshotPattern, cls).skip_checks()
         if not CONF.compute_feature_enabled.snapshot:
             raise cls.skipException("Snapshotting is not available.")
+        if not all([CONF.compute.image_full_ref,
+                    CONF.compute.image_full_username,
+                    CONF.compute.image_full_flavor_ref]):
+            raise cls.skipException(
+                "Test requires image_full_* options to be set.")
 
     @decorators.idempotent_id('608e604b-1d63-4a82-8e3e-91bc665c90b4')
     @decorators.attr(type='slow')
@@ -51,16 +56,20 @@
         # prepare for booting an instance
         keypair = self.create_keypair()
         security_group = self.create_security_group()
+        username = CONF.compute.image_full_username
 
         # boot an instance and create a timestamp file in it
         server = self.create_server(
             key_name=keypair['name'],
-            security_groups=[{'name': security_group['name']}])
+            security_groups=[{'name': security_group['name']}],
+            flavor=CONF.compute.image_full_flavor_ref,
+            image_id=CONF.compute.image_full_ref)
 
         instance_ip = self.get_server_ip(server)
         timestamp = self.create_timestamp(instance_ip,
                                           private_key=keypair['private_key'],
-                                          server=server)
+                                          server=server,
+                                          username=username)
 
         # snapshot the instance
         snapshot_image = self.create_server_snapshot(server=server)
@@ -74,13 +83,15 @@
         server_from_snapshot = self.create_server(
             image_id=snapshot_image['id'],
             key_name=keypair['name'],
-            security_groups=[{'name': security_group['name']}])
+            security_groups=[{'name': security_group['name']}],
+            flavor=CONF.compute.image_full_flavor_ref)
 
         # check the existence of the timestamp file in the second instance
         server_from_snapshot_ip = self.get_server_ip(server_from_snapshot)
         timestamp2 = self.get_timestamp(server_from_snapshot_ip,
                                         private_key=keypair['private_key'],
-                                        server=server_from_snapshot)
+                                        server=server_from_snapshot,
+                                        username=username)
         self.assertEqual(timestamp, timestamp2)
 
         # snapshot the instance again
diff --git a/tempest/tests/lib/common/test_dynamic_creds.py b/tempest/tests/lib/common/test_dynamic_creds.py
index b4b1b91..47a2f9d 100644
--- a/tempest/tests/lib/common/test_dynamic_creds.py
+++ b/tempest/tests/lib/common/test_dynamic_creds.py
@@ -518,6 +518,7 @@
         router_interface_mock = self.patch(
             'tempest.lib.services.network.routers_client.RoutersClient.'
             'add_router_interface')
+        self.patch('tempest.lib.common.waiters.wait_router_interface_removed')
         creds.get_primary_creds()
         router_interface_mock.assert_called_once_with('1234', subnet_id='1234')
         router_interface_mock.reset_mock()