Merge "Fix image import tests for read-only stores"
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 7e647dd..853e73d 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -363,10 +363,12 @@
 
         if all_stores:
             stores_list = ','.join([store['id']
-                                    for store in self.available_stores])
+                                    for store in self.available_stores
+                                    if store.get('read-only') != 'true'])
         else:
-            stores = [store['id'] for store in self.available_stores]
-            stores_list = stores[::len(stores) - 1]
+            stores = [store['id'] for store in self.available_stores
+                      if store.get('read-only') != 'true']
+            stores_list = stores[::max(1, len(stores) - 1)]
 
         return body, stores_list
 
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 53582a6..71599bd 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -233,6 +233,26 @@
 
     exc_cls = lib_exc.TimeoutException
     start = int(time.time())
+
+    # NOTE(danms): Don't wait for stores that are read-only as those
+    # will never complete
+    try:
+        store_info = client.info_stores()['stores']
+        stores = ','.join(sorted([
+            store['id'] for store in store_info
+            if store.get('read-only') != 'true' and
+            (not stores or store['id'] in stores.split(','))]))
+    except lib_exc.NotFound:
+        # If multi-store is not enabled, then we can not resolve which
+        # ones are read-only, and stores must have been passed as None
+        # anyway for us to succeed. If not, then we should raise right
+        # now and avoid waiting since we will never see the stores
+        # appear.
+        if stores is not None:
+            raise lib_exc.TimeoutException(
+                'Image service has no store support; '
+                'cowardly refusing to wait for them.')
+
     while int(time.time()) - start < client.build_timeout:
         image = client.show_image(image_id)
         if image['status'] == 'active' and (stores is None or
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index 1d0ee77..71088a4 100755
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -59,11 +59,19 @@
     def test_wait_for_image_imported_to_stores(self):
         self.client.show_image.return_value = ({'status': 'active',
                                                 'stores': 'fake_store'})
+        self.client.info_stores.return_value = {
+            'stores': [{'id': 'fake_store',
+                        'description': 'A writable store'},
+                       {'id': 'another_fake_store',
+                        'description': 'A read-only store',
+                        'read-only': 'true'}]
+        }
         start_time = int(time.time())
         waiters.wait_for_image_imported_to_stores(
-            self.client, 'fake_image_id', 'fake_store')
+            self.client, 'fake_image_id', 'fake_store,another_fake_store')
         end_time = int(time.time())
-        # Ensure waiter returns before build_timeout
+        # Ensure waiter returns before build_timeout, and did not wait
+        # for the read-only store
         self.assertLess((end_time - start_time), 10)
 
     def test_wait_for_image_imported_to_stores_failure(self):
@@ -95,6 +103,22 @@
                           waiters.wait_for_image_imported_to_stores,
                           client, 'fake_image_id', 'fake_store')
 
+    def test_wait_for_image_imported_to_stores_no_stores(self):
+        client = mock.MagicMock()
+        client.show_image.return_value = ({'status': 'active'})
+        client.info_stores.side_effect = lib_exc.NotFound
+        client.build_timeout = 2
+        start_time = time.time()
+        waiters.wait_for_image_imported_to_stores(
+            client, 'fake_image_id', None)
+        end_time = time.time()
+        self.assertLess(end_time - start_time, 10)
+
+        exc = self.assertRaises(lib_exc.TimeoutException,
+                                waiters.wait_for_image_imported_to_stores,
+                                client, 'fake_image_id', 'foo,bar')
+        self.assertIn('cowardly', str(exc))
+
     def test_wait_for_image_copied_to_stores(self):
         self.client.show_image.return_value = ({
             'status': 'active',