Repository: apache/libcloud

Issue:
Backblaze B2: AUTH_API_HOST not picking up the default
Running the demo code in the docs:

```
from libcloud.storage.types import Provider
from libcloud.storage.providers import get_driver

account_id = 'XXXXXX'
application_key = 'YYYYYY'

cls = get_driver(Provider.BACKBLAZE_B2)
driver = cls(account_id, application_key)
driver.list_containers()
```

Produces the error:

```
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='none', port=443): Max retries exceeded with url: /b2api/v1/b2_list_buckets?accountId=xxxxxxxxxx (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7f7366797610>: Failed to establish a new connection: [Errno -5] No address associated with hostname'))

```
If the host is supplied to the constructor, then it works as intended:
```
driver = cls(account_id, application_key, host='api.backblaze.com')
```

I can replicate this on Python 2 and Python 3. I am using libcloud via django-storages, so there isn't a way for me to pass the hostname via the constructor. 

Looking at the backblaze provider the default host is provided and correct, but somehow not making it into the connection object before request is called.

I can take another look at this this afternoon, but a patch didn't jump out at me immediately.


Discussion:

Resolving patch:
diff --git a/docs/examples/storage/backblaze_b2/instantiate.py b/docs/examples/storage/backblaze_b2/instantiate.py
--- a/docs/examples/storage/backblaze_b2/instantiate.py
+++ b/docs/examples/storage/backblaze_b2/instantiate.py
@@ -1,8 +1,8 @@
 from libcloud.storage.types import Provider
 from libcloud.storage.providers import get_driver
 
-account_id = 'XXXXXX'
+key_id = 'XXXXXX'
 application_key = 'YYYYYY'
 
 cls = get_driver(Provider.BACKBLAZE_B2)
-driver = cls(account_id, application_key)
+driver = cls(key_id, application_key)
diff --git a/libcloud/storage/drivers/backblaze_b2.py b/libcloud/storage/drivers/backblaze_b2.py
--- a/libcloud/storage/drivers/backblaze_b2.py
+++ b/libcloud/storage/drivers/backblaze_b2.py
@@ -147,7 +147,7 @@ def download_request(self, action, params=None):
         auth_conn = self._auth_conn.authenticate()
 
         # Set host to the download server
-        self.host = auth_conn.download_host
+        self._set_host(auth_conn.download_host)
 
         action = '/file/' + action
         method = 'GET'
@@ -162,7 +162,7 @@ def upload_request(self, action, headers, upload_host, auth_token, data):
         auth_conn = self._auth_conn.authenticate()
 
         # Upload host is dynamically retrieved for each upload request
-        self.host = upload_host
+        self._set_host(host=upload_host)
 
         method = 'POST'
         raw = False
@@ -181,7 +181,7 @@ def request(self, action, params=None, data=None, headers=None,
         auth_conn = self._auth_conn.authenticate()
 
         # Set host
-        self.host = auth_conn.api_host
+        self._set_host(host=auth_conn.api_host)
 
         # Include Content-Type
         if not raw and data:
@@ -223,6 +223,17 @@ def _request(self, auth_conn, action, params=None, data=None, headers=None,
                                                               raw=raw)
         return response
 
+    def _set_host(self, host):
+        """
+        Dynamically set host which will be used for the following HTTP
+        requests.
+
+        NOTE: This is needed because Backblaze uses different hosts for API,
+        download and upload requests.
+        """
+        self.host = host
+        self.connection.host = 'https://%s' % (host)
+
 
 class BackblazeB2StorageDriver(StorageDriver):
     connectionCls = BackblazeB2Connection
@@ -318,8 +329,11 @@ def download_object_as_stream(self, obj, chunk_size=None):
 
         return self._get_object(obj=obj, callback=read_in_chunks,
                                 response=response,
-                                callback_kwargs={'iterator': response.response,
-                                                 'chunk_size': chunk_size},
+                                callback_kwargs={
+                                    'iterator': response.iter_content(
+                                        chunk_size
+                                    ),
+                                    'chunk_size': chunk_size},
                                 success_status_code=httplib.OK)
 
     def upload_object(self, file_path, container, object_name, extra=None,
diff --git a/libcloud/utils/loggingconnection.py b/libcloud/utils/loggingconnection.py
--- a/libcloud/utils/loggingconnection.py
+++ b/libcloud/utils/loggingconnection.py
@@ -124,6 +124,9 @@ def _log_curl(self, method, url, body, headers):
 
         # TODO: in python 2.6, body can be a file-like object.
         if body is not None and len(body) > 0:
+            if isinstance(body, (bytearray, bytes)):
+                body = body.decode('utf-8')
+
             cmd.extend(["--data-binary", pquote(body)])
 
         cmd.extend(["--compress"])


Test patch:
diff --git a/libcloud/test/storage/test_backblaze_b2.py b/libcloud/test/storage/test_backblaze_b2.py
--- a/libcloud/test/storage/test_backblaze_b2.py
+++ b/libcloud/test/storage/test_backblaze_b2.py
@@ -19,8 +19,11 @@
 
 import mock
 import json
+
 from libcloud.storage.drivers.backblaze_b2 import BackblazeB2StorageDriver
 from libcloud.utils.py3 import httplib
+from libcloud.utils.py3 import b
+from libcloud.utils.files import exhaust_iterator
 from libcloud.test import unittest
 from libcloud.test import MockHttp
 from libcloud.test.file_fixtures import StorageFileFixtures
@@ -90,12 +93,13 @@ def test_download_object(self):
                                              overwrite_existing=True)
         self.assertTrue(result)
 
-    @unittest.skip(reason='The API for backblaze download object as stream is wrong')
     def test_download_object_as_stream(self):
         container = self.driver.list_containers()[0]
         obj = self.driver.list_container_objects(container=container)[0]
-        result = self.driver.download_object_as_stream(obj=obj)
-        self.assertEqual(result, 'ab')
+
+        stream = self.driver.download_object_as_stream(obj=obj, chunk_size=1024)
+        self.assertTrue(hasattr(stream, '__iter__'))
+        self.assertEqual(exhaust_iterator(stream), b('ab'))
 
     def test_upload_object(self):
         file_path = os.path.abspath(__file__)
@@ -162,8 +166,8 @@ def _b2api_v1_b2_authorize_account(self, method, url, body, headers):
         if method == 'GET':
             body = json.dumps({
                 'accountId': 'test',
-                'apiUrl': 'test',
-                'downloadUrl': 'test',
+                'apiUrl': 'https://apiNNN.backblazeb2.com',
+                'downloadUrl': 'https://f002.backblazeb2.com',
                 'authorizationToken': 'test'
             })
         else: