Merge "Disable smoke test test_upload_too_many_objects" into mcp/ussuri
diff --git a/tempest/api/image/v2/admin/test_images.py b/tempest/api/image/v2/admin/test_images.py
index dbb8c58..9618591 100644
--- a/tempest/api/image/v2/admin/test_images.py
+++ b/tempest/api/image/v2/admin/test_images.py
@@ -12,10 +12,15 @@
 #    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.api.image import base
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+CONF = config.CONF
 
 
 class BasicOperationsImagesAdminTest(base.BaseV2ImageAdminTest):
@@ -49,3 +54,45 @@
         self.assertEqual(random_id_2, updated_image_info['owner'])
         self.assertNotEqual(created_image_info['owner'],
                             updated_image_info['owner'])
+
+
+class ImageWebUploadAdminTest(base.BaseV2ImageAdminTest):
+    @classmethod
+    def skip_checks(cls):
+        super(ImageWebUploadAdminTest, cls).skip_checks()
+        enabled_methods = CONF.image_feature_enabled.enabled_import_methods
+        if "web-download" not in enabled_methods:
+            raise cls.skipException(
+                "Glance image upload via url feature disabled")
+
+    @decorators.idempotent_id('5b2ce43c-924c-4bae-bac0-f5d6ed69d72e')
+    def test_image_upload_via_url(self):
+        # Create image
+        image_name = data_utils.rand_name("image")
+        container_format = CONF.image.container_formats[0]
+        disk_format = CONF.image.disk_formats[0]
+        image = self.create_image(name=image_name,
+                                  container_format=container_format,
+                                  disk_format=disk_format,
+                                  visibility='private')
+        self.assertEqual('queued', image['status'])
+
+        # Upload image via url
+        image_uri = CONF.image.http_image
+        method = {"name": "web-download", "uri": image_uri}
+        self.admin_client.import_image(image_id=image["id"], method=method)
+
+        timeout = CONF.image.build_timeout
+        interval = CONF.image.build_interval
+
+        start_time = int(time.time())
+        while True:
+            body = self.admin_client.show_image(image['id'])
+            if body["status"] == "active":
+                break
+            if int(time.time()) - start_time >= timeout:
+                message = ('Image %(id)s failed to become active within '
+                           'the required time (%(timeout)s s).' %
+                           {'id': image['id'], 'timeout': timeout})
+                raise lib_exc.TimeoutException(message)
+            time.sleep(interval)
diff --git a/tempest/config.py b/tempest/config.py
index d2f957c..b8bad9e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -666,6 +666,9 @@
                                   'are current one. In future, Tempest will '
                                   'test v2 APIs only so this config option '
                                   'will be removed.'),
+    cfg.ListOpt('enabled_import_methods',
+                default=[],
+                help="List of enabled image import methods")
 ]
 
 network_group = cfg.OptGroup(name='network',
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 431a0a0..2dbd356 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -93,6 +93,7 @@
         self.build_interval = build_interval
         self.build_timeout = build_timeout
         self.trace_requests = trace_requests
+        self.ca_certs = ca_certs
 
         self._skip_path = False
         self.general_header_lc = set(('cache-control', 'connection',
diff --git a/tempest/lib/services/image/v2/images_client.py b/tempest/lib/services/image/v2/images_client.py
index 90778da..7680a9c 100644
--- a/tempest/lib/services/image/v2/images_client.py
+++ b/tempest/lib/services/image/v2/images_client.py
@@ -55,6 +55,19 @@
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
+    def import_image(self, image_id, **kwargs):
+        """Import image.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/image/v2/#import-an-image
+        """
+        data = json.dumps(kwargs)
+        url = 'images/%s/import' % image_id
+        resp, body = self.post(url, data)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
     def deactivate_image(self, image_id):
         """Deactivate image.
 
diff --git a/tempest/lib/services/object_storage/object_client.py b/tempest/lib/services/object_storage/object_client.py
index 383aff6..dcd8e49 100644
--- a/tempest/lib/services/object_storage/object_client.py
+++ b/tempest/lib/services/object_storage/object_client.py
@@ -12,6 +12,7 @@
 #    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 ssl
 
 from six.moves import http_client as httplib
 from six.moves.urllib import parse as urlparse
@@ -118,7 +119,7 @@
         path = str(parsed.path) + "/"
         path += "%s/%s" % (str(container), str(object_name))
 
-        conn = _create_connection(parsed)
+        conn = self._create_connection(parsed)
         # Send the PUT request and the headers including the "Expect" header
         conn.putrequest('PUT', path)
 
@@ -151,15 +152,17 @@
 
         return resp.status, resp.reason
 
+    def _create_connection(self, parsed_url):
+        """Helper function to create connection with httplib
 
-def _create_connection(parsed_url):
-    """Helper function to create connection with httplib
+        :param parsed_url: parsed url of the remote location
+        """
+        if parsed_url.scheme == 'https':
+            conn = httplib.HTTPSConnection(
+                parsed_url.netloc,
+                context=ssl.create_default_context(cafile=self.ca_certs),
+            )
+        else:
+            conn = httplib.HTTPConnection(parsed_url.netloc)
 
-    :param parsed_url: parsed url of the remote location
-    """
-    if parsed_url.scheme == 'https':
-        conn = httplib.HTTPSConnection(parsed_url.netloc)
-    else:
-        conn = httplib.HTTPConnection(parsed_url.netloc)
-
-    return conn
+        return conn
diff --git a/tempest/scenario/test_object_storage_basic_ops.py b/tempest/scenario/test_object_storage_basic_ops.py
index b635ca0..1d90219 100644
--- a/tempest/scenario/test_object_storage_basic_ops.py
+++ b/tempest/scenario/test_object_storage_basic_ops.py
@@ -62,7 +62,9 @@
         obj_url = '%s/%s/%s' % (self.object_client.base_url,
                                 container_name, obj_name)
         resp, _ = self.object_client.raw_request(obj_url, 'GET')
-        self.assertEqual(resp.status, 401)
+        # TODO(mshalamov): RGW returns 404 code for unauthorized instead of 401
+        # https://tracker.ceph.com/issues/46295
+        self.assertIn(resp.status, [401, 404])
         metadata_param = {'X-Container-Read': '.r:*'}
         self.container_client.create_update_or_delete_container_metadata(
             container_name, create_update_metadata=metadata_param,
diff --git a/tempest/tests/lib/services/object_storage/test_object_client.py b/tempest/tests/lib/services/object_storage/test_object_client.py
index 1749b03..be66222 100644
--- a/tempest/tests/lib/services/object_storage/test_object_client.py
+++ b/tempest/tests/lib/services/object_storage/test_object_client.py
@@ -31,15 +31,18 @@
         self.object_client = object_client.ObjectClient(self.fake_auth,
                                                         'swift', 'region1')
 
-    @mock.patch.object(object_client, '_create_connection')
+    @mock.patch('tempest.lib.services.object_storage.object_client.'
+                'ObjectClient._create_connection')
     def test_create_object_continue_no_data(self, mock_poc):
         self._validate_create_object_continue(None, mock_poc)
 
-    @mock.patch.object(object_client, '_create_connection')
+    @mock.patch('tempest.lib.services.object_storage.object_client.'
+                'ObjectClient._create_connection')
     def test_create_object_continue_with_data(self, mock_poc):
         self._validate_create_object_continue('hello', mock_poc)
 
-    @mock.patch.object(object_client, '_create_connection')
+    @mock.patch('tempest.lib.services.object_storage.object_client.'
+                'ObjectClient._create_connection')
     def test_create_continue_with_no_continue_received(self, mock_poc):
         self._validate_create_object_continue('hello', mock_poc,
                                               initial_status=201)