
This review implements blueprint python-request and replaces the old http client implementation in favor of a new one based on python-requests. Major changes: * raw_request and json_request removed since everything is now being handled by the same method "_request" * New methods that match HTTP's methods were added: - get - put - post - head - patch - delete * Content-Type is now being "inferred" based on the data being sent: - if it is file-like object it chunks the request - if it is a python type not instance of basestring then it'll try to serialize it to json - Every other case will keep the incoming content-type and will send the data as is. * Glanceclient's HTTPSConnection implementation will be used if no-compression flag is set to True. Co-Author: Flavio Percoco<flaper87@gmail.com> Change-Id: I09f70eee3e2777f52ce040296015d41649c2586a
251 lines
9.0 KiB
Python
251 lines
9.0 KiB
Python
# Copyright 2012 OpenStack Foundation
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# 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 json
|
|
|
|
from mox3 import mox
|
|
import requests
|
|
import six
|
|
from six.moves.urllib import parse
|
|
import testtools
|
|
import types
|
|
|
|
import glanceclient
|
|
from glanceclient.common import http
|
|
from glanceclient.common import https
|
|
from glanceclient import exc
|
|
from tests import utils
|
|
|
|
|
|
class TestClient(testtools.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestClient, self).setUp()
|
|
self.mock = mox.Mox()
|
|
self.mock.StubOutWithMock(requests.Session, 'request')
|
|
|
|
self.endpoint = 'http://example.com:9292'
|
|
self.client = http.HTTPClient(self.endpoint, token=u'abc123')
|
|
|
|
def tearDown(self):
|
|
super(TestClient, self).tearDown()
|
|
self.mock.UnsetStubs()
|
|
|
|
def test_identity_headers_and_token(self):
|
|
identity_headers = {
|
|
'X-Auth-Token': 'auth_token',
|
|
'X-User-Id': 'user',
|
|
'X-Tenant-Id': 'tenant',
|
|
'X-Roles': 'roles',
|
|
'X-Identity-Status': 'Confirmed',
|
|
'X-Service-Catalog': 'service_catalog',
|
|
}
|
|
#with token
|
|
kwargs = {'token': u'fake-token',
|
|
'identity_headers': identity_headers}
|
|
http_client_object = http.HTTPClient(self.endpoint, **kwargs)
|
|
self.assertEqual('auth_token', http_client_object.auth_token)
|
|
self.assertTrue(http_client_object.identity_headers.
|
|
get('X-Auth-Token') is None)
|
|
|
|
def test_identity_headers_and_no_token_in_header(self):
|
|
identity_headers = {
|
|
'X-User-Id': 'user',
|
|
'X-Tenant-Id': 'tenant',
|
|
'X-Roles': 'roles',
|
|
'X-Identity-Status': 'Confirmed',
|
|
'X-Service-Catalog': 'service_catalog',
|
|
}
|
|
#without X-Auth-Token in identity headers
|
|
kwargs = {'token': u'fake-token',
|
|
'identity_headers': identity_headers}
|
|
http_client_object = http.HTTPClient(self.endpoint, **kwargs)
|
|
self.assertEqual(u'fake-token', http_client_object.auth_token)
|
|
self.assertTrue(http_client_object.identity_headers.
|
|
get('X-Auth-Token') is None)
|
|
|
|
def test_connection_refused(self):
|
|
"""
|
|
Should receive a CommunicationError if connection refused.
|
|
And the error should list the host and port that refused the
|
|
connection
|
|
"""
|
|
requests.Session.request(
|
|
mox.IgnoreArg(),
|
|
mox.IgnoreArg(),
|
|
data=mox.IgnoreArg(),
|
|
headers=mox.IgnoreArg(),
|
|
stream=mox.IgnoreArg(),
|
|
).AndRaise(requests.exceptions.ConnectionError())
|
|
self.mock.ReplayAll()
|
|
try:
|
|
self.client.get('/v1/images/detail?limit=20')
|
|
#NOTE(alaski) We expect exc.CommunicationError to be raised
|
|
# so we should never reach this point. try/except is used here
|
|
# rather than assertRaises() so that we can check the body of
|
|
# the exception.
|
|
self.fail('An exception should have bypassed this line.')
|
|
except glanceclient.exc.CommunicationError as comm_err:
|
|
fail_msg = ("Exception message '%s' should contain '%s'" %
|
|
(comm_err.message, self.endpoint))
|
|
self.assertTrue(self.endpoint in comm_err.message, fail_msg)
|
|
|
|
def test_http_encoding(self):
|
|
# Lets fake the response
|
|
# returned by requests
|
|
response = 'Ok'
|
|
headers = {"Content-Type": "text/plain"}
|
|
fake = utils.FakeResponse(headers, six.StringIO(response))
|
|
requests.Session.request(
|
|
mox.IgnoreArg(),
|
|
mox.IgnoreArg(),
|
|
data=mox.IgnoreArg(),
|
|
stream=mox.IgnoreArg(),
|
|
headers=mox.IgnoreArg()).AndReturn(fake)
|
|
self.mock.ReplayAll()
|
|
|
|
headers = {"test": u'ni\xf1o'}
|
|
resp, body = self.client.get('/v1/images/detail', headers=headers)
|
|
self.assertEqual(resp, fake)
|
|
|
|
def test_headers_encoding(self):
|
|
value = u'ni\xf1o'
|
|
headers = {"test": value}
|
|
encoded = self.client.encode_headers(headers)
|
|
if six.PY2:
|
|
self.assertEqual("ni\xc3\xb1o", encoded["test"])
|
|
else:
|
|
self.assertEqual(value, encoded["test"])
|
|
|
|
def test_raw_request(self):
|
|
" Verify the path being used for HTTP requests reflects accurately. "
|
|
headers = {"Content-Type": "text/plain"}
|
|
response = 'Ok'
|
|
fake = utils.FakeResponse({}, six.StringIO(response))
|
|
requests.Session.request(
|
|
mox.IgnoreArg(),
|
|
mox.IgnoreArg(),
|
|
data=mox.IgnoreArg(),
|
|
stream=mox.IgnoreArg(),
|
|
headers=mox.IgnoreArg()).AndReturn(fake)
|
|
self.mock.ReplayAll()
|
|
|
|
resp, body = self.client.get('/v1/images/detail', headers=headers)
|
|
self.assertEqual(resp, fake)
|
|
|
|
def test_parse_endpoint(self):
|
|
endpoint = 'http://example.com:9292'
|
|
test_client = http.HTTPClient(endpoint, token=u'adc123')
|
|
actual = test_client.parse_endpoint(endpoint)
|
|
expected = parse.SplitResult(scheme='http',
|
|
netloc='example.com:9292', path='',
|
|
query='', fragment='')
|
|
self.assertEqual(expected, actual)
|
|
|
|
def test_get_connections_kwargs_http(self):
|
|
endpoint = 'http://example.com:9292'
|
|
test_client = http.HTTPClient(endpoint, token=u'adc123')
|
|
self.assertEqual(test_client.timeout, 600.0)
|
|
|
|
def test_http_chunked_request(self):
|
|
# Lets fake the response
|
|
# returned by requests
|
|
response = "Ok"
|
|
data = six.StringIO(response)
|
|
fake = utils.FakeResponse({}, data)
|
|
requests.Session.request(
|
|
mox.IgnoreArg(),
|
|
mox.IgnoreArg(),
|
|
stream=mox.IgnoreArg(),
|
|
data=mox.IsA(types.GeneratorType),
|
|
headers=mox.IgnoreArg()).AndReturn(fake)
|
|
self.mock.ReplayAll()
|
|
|
|
headers = {"test": u'chunked_request'}
|
|
resp, body = self.client.post('/v1/images/',
|
|
headers=headers, data=data)
|
|
self.assertEqual(resp, fake)
|
|
|
|
def test_http_json(self):
|
|
data = {"test": "json_request"}
|
|
fake = utils.FakeResponse({}, "OK")
|
|
|
|
def test_json(passed_data):
|
|
"""
|
|
This function tests whether the data
|
|
being passed to request's method is
|
|
a valid json or not.
|
|
|
|
This function will be called by pymox
|
|
|
|
:params passed_data: The data being
|
|
passed to requests.Session.request.
|
|
"""
|
|
if not isinstance(passed_data, six.string_types):
|
|
return False
|
|
|
|
try:
|
|
passed_data = json.loads(passed_data)
|
|
return data == passed_data
|
|
except (TypeError, ValueError):
|
|
return False
|
|
|
|
requests.Session.request(
|
|
mox.IgnoreArg(),
|
|
mox.IgnoreArg(),
|
|
stream=mox.IgnoreArg(),
|
|
data=mox.Func(test_json),
|
|
headers=mox.IgnoreArg()).AndReturn(fake)
|
|
self.mock.ReplayAll()
|
|
|
|
headers = {"test": u'chunked_request'}
|
|
resp, body = self.client.post('/v1/images/',
|
|
headers=headers,
|
|
data=data)
|
|
self.assertEqual(resp, fake)
|
|
|
|
def test_http_chunked_response(self):
|
|
headers = {"Content-Type": "application/octet-stream"}
|
|
data = "TEST"
|
|
fake = utils.FakeResponse(headers, six.StringIO(data))
|
|
|
|
requests.Session.request(
|
|
mox.IgnoreArg(),
|
|
mox.IgnoreArg(),
|
|
stream=mox.IgnoreArg(),
|
|
data=mox.IgnoreArg(),
|
|
headers=mox.IgnoreArg()).AndReturn(fake)
|
|
self.mock.ReplayAll()
|
|
headers = {"test": u'chunked_request'}
|
|
resp, body = self.client.get('/v1/images/')
|
|
self.assertTrue(isinstance(body, types.GeneratorType))
|
|
self.assertEqual([data], list(body))
|
|
|
|
|
|
class TestVerifiedHTTPSConnection(testtools.TestCase):
|
|
"""Test fixture for glanceclient.common.http.VerifiedHTTPSConnection."""
|
|
|
|
def test_setcontext_unable_to_load_cacert(self):
|
|
"""Add this UT case with Bug#1265730."""
|
|
self.assertRaises(exc.SSLConfigurationError,
|
|
https.VerifiedHTTPSConnection,
|
|
"127.0.0.1",
|
|
None,
|
|
None,
|
|
None,
|
|
"gx_cacert",
|
|
None,
|
|
False,
|
|
True)
|