Skip to content

Commit

Permalink
Merge branch 'connection_refactoring' into trunk
Browse files Browse the repository at this point in the history
Closes #976
  • Loading branch information
tonybaloney committed Jan 13, 2017
2 parents 5be8b04 + ee5a227 commit f48cc67
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 65 deletions.
9 changes: 6 additions & 3 deletions libcloud/common/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,9 +674,12 @@ def request(self, action, params=None, data=None, headers=None,
return response

def morph_action_hook(self, action):
if not action.startswith("/"):
action = "/" + action
return self.request_path + action
url = urlparse.urljoin(self.request_path.lstrip('/').rstrip('/') +
'/', action.lstrip('/'))
if not url.startswith('/'):
return '/' + url
else:
return url

def add_default_params(self, params):
"""
Expand Down
2 changes: 1 addition & 1 deletion libcloud/compute/drivers/profitbricks.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def request(self, action, params=None, data=None, headers=None,
host and protocol components.
'''
if not with_full_url or with_full_url is False:
action = self.api_prefix + action
action = self.api_prefix + action.lstrip('/')
else:
action = action.replace(
'https://{host}'.format(host=self.host),
Expand Down
65 changes: 13 additions & 52 deletions libcloud/httplib_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"""

import os
import socket
import warnings
import requests

Expand All @@ -32,25 +31,9 @@
'LibcloudConnection'
]

HTTP_PROXY_ENV_VARIABLE_NAME = 'http_proxy'
ALLOW_REDIRECTS = 1

# Error message which is thrown when establishing SSL / TLS connection fails
UNSUPPORTED_TLS_VERSION_ERROR_MSG = """
Failed to establish SSL / TLS connection (%s). It is possible that the server \
doesn't support requested SSL / TLS version (%s).
For information on how to work around this issue, please see \
https://libcloud.readthedocs.org/en/latest/other/\
ssl-certificate-validation.html#changing-used-ssl-tls-version
""".strip()

# Maps ssl.PROTOCOL_* constant to the actual SSL / TLS version name
SSL_CONSTANT_TO_TLS_VERSION_MAP = {
0: 'SSL v2',
2: 'SSLv3, TLS v1.0, TLS v1.1, TLS v1.2',
3: 'TLS v1.0',
4: 'TLS v1.1',
5: 'TLS v1.2'
}
HTTP_PROXY_ENV_VARIABLE_NAME = 'http_proxy'


class LibcloudBaseConnection(object):
Expand Down Expand Up @@ -182,16 +165,24 @@ def __init__(self, host, port, secure=None, **kwargs):
self.set_http_proxy(proxy_url=proxy_url)
self.session.timeout = kwargs.get('timeout', 60)

@property
def verification(self):
"""
The option for SSL verification given to underlying requests
"""
return self.ca_cert if self.ca_cert is not None else self.verify

def request(self, method, url, body=None, headers=None, raw=False,
stream=False):
url = urlparse.urljoin(self.host, url)
self.response = self.session.request(
method=method.lower(),
url=''.join([self.host, url]),
url=url,
data=body,
headers=headers,
allow_redirects=1,
allow_redirects=ALLOW_REDIRECTS,
stream=stream,
verify=self.ca_cert if self.ca_cert is not None else self.verify
verify=self.verification
)

def prepared_request(self, method, url, body=None,
Expand Down Expand Up @@ -280,33 +271,3 @@ def reason(self):
def version(self):
# requests doesn't expose this
return '11'


def get_socket_error_exception(ssl_version, exc):
"""
Function which intercepts socket.error exceptions and re-throws an
exception with a more user-friendly message in case server doesn't support
requested SSL version.
"""
exc_msg = str(exc)

# Re-throw an exception with a more friendly error message
if 'connection reset by peer' in exc_msg.lower():
ssl_version_name = SSL_CONSTANT_TO_TLS_VERSION_MAP[ssl_version]
msg = (UNSUPPORTED_TLS_VERSION_ERROR_MSG %
(exc_msg, ssl_version_name))

# Note: In some cases arguments are (errno, message) and in
# other it's just (message,)
exc_args = getattr(exc, 'args', [])

if len(exc_args) == 2:
new_exc_args = [exc.args[0], msg]
else:
new_exc_args = [msg]

new_exc = socket.error(*new_exc_args)
new_exc.original_exc = exc
return new_exc
else:
return exc
10 changes: 2 additions & 8 deletions libcloud/storage/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,20 +650,14 @@ def _hash_buffered_stream(self, stream, hasher, blocksize=65536):
return (hasher.hexdigest(), total_len)
if not hasattr(stream, '__exit__'):
for s in stream:
if isinstance(s, str):
hasher.update(s)
else:
hasher.update(s)
hasher.update(s)
total_len = total_len + len(s)
return (hasher.hexdigest(), total_len)
with stream:
buf = stream.read(blocksize)
while len(buf) > 0:
total_len = total_len + len(buf)
if isinstance(buf, str):
hasher.update(buf)
else:
hasher.update(buf)
hasher.update(buf)
buf = stream.read(blocksize)
return (hasher.hexdigest(), total_len)

Expand Down
2 changes: 1 addition & 1 deletion libcloud/test/compute/test_profitbricks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4962,7 +4962,7 @@ def _cloudapi_v3_datacenters_dc_1_lans_10(
GET - fetch a list of snapshots
'''
def _cloudapi_v3__snapshots(
def _cloudapi_v3_snapshots(
self, method, url, body, headers
):
if method == 'GET':
Expand Down
51 changes: 51 additions & 0 deletions libcloud/test/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

from mock import Mock, patch

import requests_mock

from libcloud.test import unittest
from libcloud.common.base import Connection
from libcloud.httplib_ssl import LibcloudBaseConnection
Expand Down Expand Up @@ -109,6 +111,55 @@ def test_connection_to_unusual_port(self):
conn = LibcloudConnection(host='localhost', port=80)
self.assertEqual(conn.host, 'http://localhost')

def test_connection_url_merging(self):
"""
Test that the connection class will parse URLs correctly
"""
conn = Connection(url='http://test.com/')
conn.connect()
self.assertEqual(conn.connection.host, 'http://test.com')
with requests_mock.mock() as m:
m.get('http://test.com/test', text='data')
response = conn.request('/test')
self.assertEqual(response.body, 'data')

def test_morph_action_hook(self):
conn = Connection(url="http://test.com")

conn.request_path = ''
self.assertEqual(conn.morph_action_hook('/test'), '/test')
self.assertEqual(conn.morph_action_hook('test'), '/test')

conn.request_path = '/v1'
self.assertEqual(conn.morph_action_hook('/test'), '/v1/test')
self.assertEqual(conn.morph_action_hook('test'), '/v1/test')

conn.request_path = '/v1'
self.assertEqual(conn.morph_action_hook('/test'), '/v1/test')
self.assertEqual(conn.morph_action_hook('test'), '/v1/test')

conn.request_path = 'v1'
self.assertEqual(conn.morph_action_hook('/test'), '/v1/test')
self.assertEqual(conn.morph_action_hook('test'), '/v1/test')

conn.request_path = 'v1/'
self.assertEqual(conn.morph_action_hook('/test'), '/v1/test')
self.assertEqual(conn.morph_action_hook('test'), '/v1/test')

def test_connect_with_prefix(self):
"""
Test that a connection with a base path (e.g. /v1/) will
add the base path to requests
"""
conn = Connection(url='http://test.com/')
conn.connect()
conn.request_path = '/v1'
self.assertEqual(conn.connection.host, 'http://test.com')
with requests_mock.mock() as m:
m.get('http://test.com/v1/test', text='data')
response = conn.request('/test')
self.assertEqual(response.body, 'data')

def test_secure_connection_unusual_port(self):
"""
Test that the connection class will default to secure (https) even
Expand Down

0 comments on commit f48cc67

Please sign in to comment.