Merge "Use requests module for HTTP/HTTPS"

This commit is contained in:
Jenkins
2014-04-25 02:08:04 +00:00
committed by Gerrit Code Review
8 changed files with 79 additions and 74 deletions

View File

@@ -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):
@@ -89,7 +89,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'
@@ -102,7 +102,6 @@ class HTTPClient(httplib2.Http):
auth_strategy='keystone', ca_cert=None, log_credentials=False,
service_type='network',
**kwargs):
super(HTTPClient, self).__init__(timeout=timeout, ca_certs=ca_cert)
self.username = username
self.tenant_name = tenant_name
@@ -112,6 +111,7 @@ class HTTPClient(httplib2.Http):
self.service_type = service_type
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
@@ -119,8 +119,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 = {}
@@ -147,7 +149,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
@@ -155,11 +157,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:
@@ -181,6 +178,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
@@ -234,16 +247,10 @@ class HTTPClient(httplib2.Http):
raise exceptions.NoAuthURLProvided()
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)
@@ -305,10 +312,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

View File

@@ -178,7 +178,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):

View File

@@ -15,11 +15,11 @@
#
import copy
import httplib2
import json
import uuid
import mox
import requests
import testtools
from neutronclient import client
@@ -68,6 +68,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 CLITestAuthNoAuth(testtools.TestCase):
def setUp(self):
@@ -86,8 +93,7 @@ class CLITestAuthNoAuth(testtools.TestCase):
def test_get_noauth(self):
self.mox.StubOutWithMock(self.client, "request")
res200 = self.mox.CreateMock(httplib2.Response)
res200.status = 200
res200 = get_response(200)
self.client.request(
mox.StrContains(ENDPOINT_URL + '/resource'), 'GET',
@@ -136,8 +142,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',
@@ -159,10 +164,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(
@@ -187,8 +190,7 @@ class CLITestAuthKeystone(testtools.TestCase):
self.client.auth_token = TOKEN
self.client.endpoint_url = ENDPOINT_URL
res401 = self.mox.CreateMock(httplib2.Response)
res401.status = 401
res401 = get_response(401)
# If a token is expired, neutron server returns 401
self.client.request(
@@ -212,8 +214,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',
@@ -236,9 +237,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',
@@ -255,9 +254,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',
@@ -274,10 +271,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',
@@ -433,8 +428,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))

View File

@@ -48,8 +48,9 @@ class FakeStdout:
class MyResp(object):
def __init__(self, status, reason=None):
self.status = status
def __init__(self, status_code, headers=None, reason=None):
self.status_code = status_code
self.headers = headers or {}
self.reason = reason
@@ -533,7 +534,7 @@ class ClientV2TestJson(CLITestV20Base):
end_url('/test', query=expect_query, format=self.format),
'PUT', body='',
headers=mox.ContainsKeyValue('X-Auth-Token', 'token')
).AndReturn((MyResp(400, 'An error'), ''))
).AndReturn((MyResp(400, reason='An error'), ''))
self.mox.ReplayAll()
error = self.assertRaises(exceptions.NeutronClientException,

View File

@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import httplib2
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()
@@ -64,7 +63,7 @@ class TestHTTPClient(testtools.TestCase):
def test_request_unauthorized(self):
rv_should_be = MyResp(401), 'unauthorized message'
httplib2.Http.request(
HTTPClient.request(
URL, METHOD, headers=mox.IgnoreArg()
).AndReturn(rv_should_be)
self.mox.ReplayAll()
@@ -76,7 +75,7 @@ class TestHTTPClient(testtools.TestCase):
def test_request_forbidden_is_returned_to_caller(self):
rv_should_be = MyResp(403), 'forbidden message'
httplib2.Http.request(
HTTPClient.request(
URL, METHOD, headers=mox.IgnoreArg()
).AndReturn(rv_should_be)
self.mox.ReplayAll()

View File

@@ -14,8 +14,8 @@
# under the License.
import fixtures
import httplib2
import mox
import requests
import testtools
from neutronclient.client import HTTPClient
@@ -126,10 +126,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(

View File

@@ -14,11 +14,11 @@
# under the License.
#
import httplib
import logging
import time
import urllib
import requests
import six.moves.urllib.parse as urlparse
from neutronclient import client
@@ -1231,10 +1231,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:
if not replybody:
@@ -1247,13 +1247,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.

View File

@@ -4,6 +4,7 @@ cliff>=1.4.3
httplib2>=0.7.5
iso8601>=0.1.9
netaddr>=0.7.6
requests>=1.1
simplejson>=2.0.9
six>=1.6.0
Babel>=1.3