File 0001-Use-requests-module-for-HTTP-HTTPS.patch of Package python-neutronclient
From f2db8f1a2528f5d2671dbaf202ecc90b775fdaac Mon Sep 17 00:00:00 2001
From: armando-migliaccio <armamig@gmail.com>
Date: Tue, 15 Apr 2014 13:46:12 -0700
Subject: [PATCH 1/2] Use requests module for HTTP/HTTPS
This change introduces the use of requests in lieu of httplib2
to ensure proper handling of SSL termination.
Implements: blueprint tls-verify
Change-Id: If182f2addf26421873b8c3d2b60f8cba9b7a9450
Conflicts:
neutronclient/tests/unit/test_auth.py
neutronclient/tests/unit/test_cli20.py
neutronclient/tests/unit/test_http.py
neutronclient/tests/unit/test_ssl.py
neutronclient/v2_0/client.py
requirements.txt
---
neutronclient/client.py | 63 +++++++++++++++++++---------------
neutronclient/common/utils.py | 5 ++-
neutronclient/tests/unit/test_auth.py | 38 +++++++++-----------
neutronclient/tests/unit/test_cli20.py | 6 ++--
neutronclient/tests/unit/test_http.py | 7 ++--
neutronclient/tests/unit/test_ssl.py | 8 ++---
neutronclient/v2_0/client.py | 14 ++++----
requirements.txt | 1 +
8 files changed, 75 insertions(+), 67 deletions(-)
diff --git a/neutronclient/client.py b/neutronclient/client.py
index 5d32280..fea983d 100644
--- a/neutronclient/client.py
+++ b/neutronclient/client.py
@@ -21,7 +21,7 @@ except ImportError:
import logging
import os
-import httplib2
+import requests
from neutronclient.common import exceptions
from neutronclient.common import utils
@@ -29,15 +29,15 @@ from neutronclient.openstack.common.gettextutils import _
_logger = logging.getLogger(__name__)
-# httplib2 retries requests on socket.timeout which
-# is not idempotent and can lead to orhan objects.
-# See: https://code.google.com/p/httplib2/issues/detail?id=124
-httplib2.RETRIES = 1
-
if os.environ.get('NEUTRONCLIENT_DEBUG'):
ch = logging.StreamHandler()
_logger.setLevel(logging.DEBUG)
_logger.addHandler(ch)
+ _requests_log_level = logging.DEBUG
+else:
+ _requests_log_level = logging.WARNING
+
+logging.getLogger("requests").setLevel(_requests_log_level)
class ServiceCatalog(object):
@@ -88,7 +88,7 @@ class ServiceCatalog(object):
return matching_endpoints[0][endpoint_type]
-class HTTPClient(httplib2.Http):
+class HTTPClient(object):
"""Handles the REST calls and responses, include authn."""
USER_AGENT = 'python-neutronclient'
@@ -100,7 +100,6 @@ class HTTPClient(httplib2.Http):
endpoint_type='publicURL',
auth_strategy='keystone', ca_cert=None, log_credentials=False,
**kwargs):
- super(HTTPClient, self).__init__(timeout=timeout, ca_certs=ca_cert)
self.username = username
self.tenant_name = tenant_name
@@ -109,6 +108,7 @@ class HTTPClient(httplib2.Http):
self.auth_url = auth_url.rstrip('/') if auth_url else None
self.endpoint_type = endpoint_type
self.region_name = region_name
+ self.timeout = timeout
self.auth_token = token
self.auth_tenant_id = None
self.auth_user_id = None
@@ -116,8 +116,10 @@ class HTTPClient(httplib2.Http):
self.endpoint_url = endpoint_url
self.auth_strategy = auth_strategy
self.log_credentials = log_credentials
- # httplib2 overrides
- self.disable_ssl_certificate_validation = insecure
+ if insecure:
+ self.verify_cert = False
+ else:
+ self.verify_cert = ca_cert if ca_cert else True
def _cs_request(self, *args, **kwargs):
kargs = {}
@@ -144,7 +146,7 @@ class HTTPClient(httplib2.Http):
utils.http_log_req(_logger, args, log_kargs)
try:
resp, body = self.request(*args, **kargs)
- except httplib2.SSLHandshakeError as e:
+ except requests.exceptions.SSLError as e:
raise exceptions.SslCertificateValidationError(reason=e)
except Exception as e:
# Wrap the low-level connection error (socket timeout, redirect
@@ -152,11 +154,6 @@ class HTTPClient(httplib2.Http):
# connection exception (it is excepted in the upper layers of code)
_logger.debug("throwing ConnectionFailed : %s", e)
raise exceptions.ConnectionFailed(reason=e)
- finally:
- # Temporary Fix for gate failures. RPC calls and HTTP requests
- # seem to be stepping on each other resulting in bogus fd's being
- # picked up for making http requests
- self.connections.clear()
utils.http_log_resp(_logger, resp, body)
status_code = self.get_status_code(resp)
if status_code == 401:
@@ -180,6 +177,22 @@ class HTTPClient(httplib2.Http):
elif not self.endpoint_url:
self.endpoint_url = self._get_endpoint_url()
+ def request(self, url, method, **kwargs):
+ kwargs.setdefault('headers', kwargs.get('headers', {}))
+ kwargs['headers']['User-Agent'] = self.USER_AGENT
+ kwargs['headers']['Accept'] = 'application/json'
+ if 'body' in kwargs:
+ kwargs['headers']['Content-Type'] = 'application/json'
+ kwargs['data'] = kwargs['body']
+ del kwargs['body']
+ resp = requests.request(
+ method,
+ url,
+ verify=self.verify_cert,
+ **kwargs)
+
+ return resp, resp.text
+
def do_request(self, url, method, **kwargs):
self.authenticate_and_fetch_endpoint_url()
# Perform the request once. If we get a 401 back then it
@@ -229,16 +242,10 @@ class HTTPClient(httplib2.Http):
'tenantName': self.tenant_name, }, }
token_url = self.auth_url + "/tokens"
-
- # Make sure we follow redirects when trying to reach Keystone
- tmp_follow_all_redirects = self.follow_all_redirects
- self.follow_all_redirects = True
- try:
- resp, resp_body = self._cs_request(token_url, "POST",
- body=json.dumps(body),
- content_type="application/json")
- finally:
- self.follow_all_redirects = tmp_follow_all_redirects
+ resp, resp_body = self._cs_request(token_url, "POST",
+ body=json.dumps(body),
+ content_type="application/json",
+ allow_redirects=True)
status_code = self.get_status_code(resp)
if status_code != 200:
raise exceptions.Unauthorized(message=resp_body)
@@ -281,10 +288,10 @@ class HTTPClient(httplib2.Http):
def get_status_code(self, response):
"""Returns the integer status code from the response.
- Either a Webob.Response (used in testing) or httplib.Response
+ Either a Webob.Response (used in testing) or requests.Response
is returned.
"""
if hasattr(response, 'status_int'):
return response.status_int
else:
- return response.status
+ return response.status_code
diff --git a/neutronclient/common/utils.py b/neutronclient/common/utils.py
index 532d7ea..13fbddc 100644
--- a/neutronclient/common/utils.py
+++ b/neutronclient/common/utils.py
@@ -176,7 +176,10 @@ def http_log_req(_logger, args, kwargs):
def http_log_resp(_logger, resp, body):
if not _logger.isEnabledFor(logging.DEBUG):
return
- _logger.debug(_("RESP:%(resp)s %(body)s\n"), {'resp': resp, 'body': body})
+ _logger.debug(_("RESP:%(code)s %(headers)s %(body)s\n"),
+ {'code': resp.status_code,
+ 'headers': resp.headers,
+ 'body': body})
def _safe_encode_without_obj(data):
diff --git a/neutronclient/tests/unit/test_auth.py b/neutronclient/tests/unit/test_auth.py
index 62b9c2f..0eda33d 100644
--- a/neutronclient/tests/unit/test_auth.py
+++ b/neutronclient/tests/unit/test_auth.py
@@ -15,11 +15,11 @@
#
import copy
-import httplib2
import json
import uuid
from mox3 import mox
+import requests
import testtools
from neutronclient import client
@@ -67,6 +67,13 @@ ENDPOINTS_RESULT = {
}
+def get_response(status_code, headers=None):
+ response = mox.Mox().CreateMock(requests.Response)
+ response.headers = headers or {}
+ response.status_code = status_code
+ return response
+
+
class CLITestAuthKeystone(testtools.TestCase):
# Auth Body expected when using tenant name
@@ -104,8 +111,7 @@ class CLITestAuthKeystone(testtools.TestCase):
def test_get_token(self):
self.mox.StubOutWithMock(self.client, "request")
- res200 = self.mox.CreateMock(httplib2.Response)
- res200.status = 200
+ res200 = get_response(200)
self.client.request(
AUTH_URL + '/tokens', 'POST',
@@ -127,10 +133,8 @@ class CLITestAuthKeystone(testtools.TestCase):
self.client.auth_token = TOKEN
self.client.endpoint_url = ENDPOINT_URL
- res200 = self.mox.CreateMock(httplib2.Response)
- res200.status = 200
- res401 = self.mox.CreateMock(httplib2.Response)
- res401.status = 401
+ res200 = get_response(200)
+ res401 = get_response(401)
# If a token is expired, neutron server retruns 401
self.client.request(
@@ -153,8 +157,7 @@ class CLITestAuthKeystone(testtools.TestCase):
self.client.auth_token = TOKEN
- res200 = self.mox.CreateMock(httplib2.Response)
- res200.status = 200
+ res200 = get_response(200)
self.client.request(
mox.StrContains(AUTH_URL + '/tokens/%s/endpoints' % TOKEN), 'GET',
@@ -177,9 +180,7 @@ class CLITestAuthKeystone(testtools.TestCase):
self.mox.StubOutWithMock(self.client, "request")
self.client.auth_token = TOKEN
-
- res200 = self.mox.CreateMock(httplib2.Response)
- res200.status = 200
+ res200 = get_response(200)
self.client.request(
mox.StrContains(ENDPOINT_OVERRIDE + '/resource'), 'GET',
@@ -196,9 +197,7 @@ class CLITestAuthKeystone(testtools.TestCase):
self.mox.StubOutWithMock(self.client, "request")
self.client.auth_token = TOKEN
-
- res200 = self.mox.CreateMock(httplib2.Response)
- res200.status = 200
+ res200 = get_response(200)
self.client.request(
mox.StrContains(AUTH_URL + '/tokens/%s/endpoints' % TOKEN), 'GET',
@@ -215,10 +214,8 @@ class CLITestAuthKeystone(testtools.TestCase):
self.client.auth_token = TOKEN
- res200 = self.mox.CreateMock(httplib2.Response)
- res200.status = 200
- res401 = self.mox.CreateMock(httplib2.Response)
- res401.status = 401
+ res200 = get_response(200)
+ res401 = get_response(401)
self.client.request(
mox.StrContains(AUTH_URL + '/tokens/%s/endpoints' % TOKEN), 'GET',
@@ -374,8 +371,7 @@ class CLITestAuthKeystone(testtools.TestCase):
self.mox.StubOutWithMock(self.client, "request")
self.mox.StubOutWithMock(utils, "http_log_req")
- res200 = self.mox.CreateMock(httplib2.Response)
- res200.status = 200
+ res200 = get_response(200)
utils.http_log_req(mox.IgnoreArg(), mox.IgnoreArg(), mox.Func(
verify_no_credentials))
diff --git a/neutronclient/tests/unit/test_cli20.py b/neutronclient/tests/unit/test_cli20.py
index 03de021..c1047cc 100644
--- a/neutronclient/tests/unit/test_cli20.py
+++ b/neutronclient/tests/unit/test_cli20.py
@@ -48,8 +48,10 @@ class FakeStdout:
class MyResp(object):
- def __init__(self, status):
- self.status = status
+ def __init__(self, status_code, headers=None, reason=None):
+ self.status_code = status_code
+ self.headers = headers or {}
+ self.reason = reason
class MyApp(object):
diff --git a/neutronclient/tests/unit/test_http.py b/neutronclient/tests/unit/test_http.py
index 2f94c30..d5db70d 100644
--- a/neutronclient/tests/unit/test_http.py
+++ b/neutronclient/tests/unit/test_http.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import httplib2
from mox3 import mox
import testtools
@@ -33,13 +32,13 @@ class TestHTTPClient(testtools.TestCase):
super(TestHTTPClient, self).setUp()
self.mox = mox.Mox()
- self.mox.StubOutWithMock(httplib2.Http, 'request')
+ self.mox.StubOutWithMock(HTTPClient, 'request')
self.addCleanup(self.mox.UnsetStubs)
self.http = HTTPClient(token=AUTH_TOKEN, endpoint_url=END_URL)
def test_request_error(self):
- httplib2.Http.request(
+ HTTPClient.request(
URL, METHOD, headers=mox.IgnoreArg()
).AndRaise(Exception('error msg'))
self.mox.ReplayAll()
@@ -54,7 +53,7 @@ class TestHTTPClient(testtools.TestCase):
def test_request_success(self):
rv_should_be = MyResp(200), 'test content'
- httplib2.Http.request(
+ HTTPClient.request(
URL, METHOD, headers=mox.IgnoreArg()
).AndReturn(rv_should_be)
self.mox.ReplayAll()
diff --git a/neutronclient/tests/unit/test_ssl.py b/neutronclient/tests/unit/test_ssl.py
index e7fda22..3411f8a 100644
--- a/neutronclient/tests/unit/test_ssl.py
+++ b/neutronclient/tests/unit/test_ssl.py
@@ -14,8 +14,8 @@
# under the License.
import fixtures
-import httplib2
from mox3 import mox
+import requests
import testtools
from neutronclient.client import HTTPClient
@@ -124,10 +124,10 @@ class TestSSL(testtools.TestCase):
def test_proper_exception_is_raised_when_cert_validation_fails(self):
http = HTTPClient(token=AUTH_TOKEN, endpoint_url=END_URL)
- self.mox.StubOutWithMock(httplib2.Http, 'request')
- httplib2.Http.request(
+ self.mox.StubOutWithMock(HTTPClient, 'request')
+ HTTPClient.request(
URL, METHOD, headers=mox.IgnoreArg()
- ).AndRaise(httplib2.SSLHandshakeError)
+ ).AndRaise(requests.exceptions.SSLError)
self.mox.ReplayAll()
self.assertRaises(
diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py
index 99ff6e2..37e6398 100644
--- a/neutronclient/v2_0/client.py
+++ b/neutronclient/v2_0/client.py
@@ -14,8 +14,8 @@
# under the License.
#
-import httplib
import logging
+import requests
import time
import urllib
import urlparse
@@ -1155,10 +1155,10 @@ class Client(object):
self.httpclient.content_type = self.content_type()
resp, replybody = self.httpclient.do_request(action, method, body=body)
status_code = self.get_status_code(resp)
- if status_code in (httplib.OK,
- httplib.CREATED,
- httplib.ACCEPTED,
- httplib.NO_CONTENT):
+ if status_code in (requests.codes.ok,
+ requests.codes.created,
+ requests.codes.accepted,
+ requests.codes.no_content):
return self.deserialize(replybody, status_code)
else:
self._handle_fault_response(status_code, replybody)
@@ -1169,13 +1169,13 @@ class Client(object):
def get_status_code(self, response):
"""Returns the integer status code from the response.
- Either a Webob.Response (used in testing) or httplib.Response
+ Either a Webob.Response (used in testing) or requests.Response
is returned.
"""
if hasattr(response, 'status_int'):
return response.status_int
else:
- return response.status
+ return response.status_code
def serialize(self, data):
"""Serializes a dictionary into either xml or json.
diff --git a/requirements.txt b/requirements.txt
index d6d98bf..a5ec75f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,6 +3,7 @@ argparse
cliff>=1.4.3
httplib2>=0.7.5
iso8601>=0.1.4
+requests>=1.1
simplejson>=2.0.9
six>=1.4.1
Babel>=1.3
--
1.8.4.5