Use requests module for HTTP/HTTPS

* Implement correct certificate verification
* Add --os-cacert
* Rework tests for requests

Pinned requests module to < 1.0 as 1.0.2 is now current in pipi
as of 17Dec2012.

Blueprint: tls-verify

Change-Id: I71066ff7297f3b70c08b7ae1c8ae8b6a1b82bbae
This commit is contained in:
Dean Troyer 2012-12-13 12:01:07 -06:00
parent d3603535d2
commit 82e47d0866
10 changed files with 294 additions and 215 deletions

@ -7,9 +7,9 @@
OpenStack Client interface. Handles the REST calls and responses.
"""
import httplib2
import logging
import os
import sys
import urlparse
try:
from eventlet import sleep
@ -26,22 +26,27 @@ if not hasattr(urlparse, 'parse_qsl'):
import cgi
urlparse.parse_qsl = cgi.parse_qsl
import requests
from cinderclient import exceptions
from cinderclient import service_catalog
from cinderclient import utils
class HTTPClient(httplib2.Http):
class HTTPClient(object):
USER_AGENT = 'python-cinderclient'
requests_config = {
'danger_mode': False,
}
def __init__(self, user, password, projectid, auth_url, insecure=False,
timeout=None, tenant_id=None, proxy_tenant_id=None,
proxy_token=None, region_name=None,
endpoint_type='publicURL', service_type=None,
service_name=None, volume_service_name=None, retries=None,
http_log_debug=False):
super(HTTPClient, self).__init__(timeout=timeout)
http_log_debug=False, cacert=None):
self.user = user
self.password = password
self.projectid = projectid
@ -61,15 +66,20 @@ class HTTPClient(httplib2.Http):
self.proxy_token = proxy_token
self.proxy_tenant_id = proxy_tenant_id
# httplib2 overrides
self.force_exception_to_status_code = True
self.disable_ssl_certificate_validation = insecure
if insecure:
self.verify_cert = False
else:
if cacert:
self.verify_cert = cacert
else:
self.verify_cert = True
self._logger = logging.getLogger(__name__)
if self.http_log_debug:
ch = logging.StreamHandler()
self._logger.setLevel(logging.DEBUG)
self._logger.addHandler(ch)
self.requests_config['verbose'] = sys.stderr
def http_log_req(self, args, kwargs):
if not self.http_log_debug:
@ -90,32 +100,43 @@ class HTTPClient(httplib2.Http):
string_parts.append(" -d '%s'" % (kwargs['body']))
self._logger.debug("\nREQ: %s\n" % "".join(string_parts))
def http_log_resp(self, resp, body):
def http_log_resp(self, resp):
if not self.http_log_debug:
return
self._logger.debug("RESP: %s\nRESP BODY: %s\n", resp, body)
self._logger.debug(
"RESP: [%s] %s\nRESP BODY: %s\n",
resp.status_code,
resp.headers,
resp.text)
def request(self, *args, **kwargs):
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['body'] = json.dumps(kwargs['body'])
kwargs['data'] = json.dumps(kwargs['body'])
del kwargs['body']
self.http_log_req(args, kwargs)
resp, body = super(HTTPClient, self).request(*args, **kwargs)
self.http_log_resp(resp, body)
self.http_log_req((url, method,), kwargs)
resp = requests.request(
method,
url,
verify=self.verify_cert,
config=self.requests_config,
**kwargs)
self.http_log_resp(resp)
if body:
if resp.text:
try:
body = json.loads(body)
body = json.loads(resp.text)
except ValueError:
pass
body = None
else:
body = None
if resp.status >= 400:
if resp.status_code >= 400:
raise exceptions.from_response(resp, body)
return resp, body
@ -138,10 +159,6 @@ class HTTPClient(httplib2.Http):
except exceptions.BadRequest as e:
if attempts > self.retries:
raise
# Socket errors show up here (400) when
# force_exception_to_status_code = True
if e.message != 'n/a':
raise
except exceptions.Unauthorized:
if auth_attempts > 0:
raise
@ -158,6 +175,10 @@ class HTTPClient(httplib2.Http):
pass
else:
raise
except requests.exceptions.ConnectionError as e:
# Catch a connection refused from requests.request
self._logger.debug("Connection refused: %s" % e)
raise
self._logger.debug(
"Failed attempt(%s of %s), retrying in %s seconds" %
(attempts, self.retries, backoff))
@ -181,7 +202,7 @@ class HTTPClient(httplib2.Http):
We may get redirected to another site, fail or actually get
back a service catalog with a token and our endpoints."""
if resp.status == 200: # content must always present
if resp.status_code == 200: # content must always present
try:
self.auth_url = url
self.service_catalog = \
@ -209,7 +230,7 @@ class HTTPClient(httplib2.Http):
print "Could not find any suitable endpoint. Correct region?"
raise
elif resp.status == 305:
elif resp.status_code == 305:
return resp['location']
else:
raise exceptions.from_response(resp, body)
@ -292,16 +313,16 @@ class HTTPClient(httplib2.Http):
headers['X-Auth-Project-Id'] = self.projectid
resp, body = self.request(url, 'GET', headers=headers)
if resp.status in (200, 204): # in some cases we get No Content
if resp.status_code in (200, 204): # in some cases we get No Content
try:
mgmt_header = 'x-server-management-url'
self.management_url = resp[mgmt_header].rstrip('/')
self.auth_token = resp['x-auth-token']
self.management_url = resp.headers[mgmt_header].rstrip('/')
self.auth_token = resp.headers['x-auth-token']
self.auth_url = url
except KeyError:
except (KeyError, TypeError):
raise exceptions.AuthorizationFailure()
elif resp.status == 305:
return resp['location']
elif resp.status_code == 305:
return resp.headers['location']
else:
raise exceptions.from_response(resp, body)
@ -333,13 +354,11 @@ class HTTPClient(httplib2.Http):
token_url = 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, body = self.request(token_url, "POST", body=body)
finally:
self.follow_all_redirects = tmp_follow_all_redirects
resp, body = self.request(
token_url,
"POST",
body=body,
allow_redirects=True)
return self._extract_service_catalog(url, resp, body)

@ -124,16 +124,19 @@ _code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized,
def from_response(response, body):
"""
Return an instance of an ClientException or subclass
based on an httplib2 response.
based on an requests response.
Usage::
resp, body = http.request(...)
if resp.status != 200:
raise exception_from_response(resp, body)
resp, body = requests.request(...)
if resp.status_code != 200:
raise exception_from_response(resp, rest.text)
"""
cls = _code_map.get(response.status, ClientException)
request_id = response.get('x-compute-request-id')
cls = _code_map.get(response.status_code, ClientException)
if response.headers:
request_id = response.headers.get('x-compute-request-id')
else:
request_id = None
if body:
message = "n/a"
details = "n/a"
@ -141,7 +144,7 @@ def from_response(response, body):
error = body[body.keys()[0]]
message = error.get('message', None)
details = error.get('details', None)
return cls(code=response.status, message=message, details=details,
return cls(code=response.status_code, message=message, details=details,
request_id=request_id)
else:
return cls(code=response.status, request_id=request_id)
return cls(code=response.status_code, request_id=request_id)

@ -20,7 +20,6 @@ Command-line interface to the OpenStack Volume API.
import argparse
import glob
import httplib2
import imp
import itertools
import os
@ -164,6 +163,13 @@ class OpenStackCinderShell(object):
parser.add_argument('--os_volume_api_version',
help=argparse.SUPPRESS)
parser.add_argument('--os-cacert',
metavar='<ca-certificate>',
default=utils.env('OS_CACERT', default=None),
help='Specify a CA bundle file to use in '
'verifying a TLS (https) server certificate. '
'Defaults to env[OS_CACERT]')
parser.add_argument('--insecure',
default=utils.env('CINDERCLIENT_INSECURE',
default=False),
@ -308,8 +314,6 @@ class OpenStackCinderShell(object):
logger.setLevel(logging.DEBUG)
logger.addHandler(streamhandler)
httplib2.debuglevel = 1
def main(self, argv):
# Parse args once to find version and debug settings
parser = self.get_base_parser()
@ -343,14 +347,14 @@ class OpenStackCinderShell(object):
(os_username, os_password, os_tenant_name, os_auth_url,
os_region_name, endpoint_type, insecure,
service_type, service_name, volume_service_name,
username, apikey, projectid, url, region_name) = (
username, apikey, projectid, url, region_name, cacert) = (
args.os_username, args.os_password,
args.os_tenant_name, args.os_auth_url,
args.os_region_name, args.endpoint_type,
args.insecure, args.service_type, args.service_name,
args.volume_service_name, args.username,
args.apikey, args.projectid,
args.url, args.region_name)
args.url, args.region_name, args.os_cacert)
if not endpoint_type:
endpoint_type = DEFAULT_CINDER_ENDPOINT_TYPE
@ -417,7 +421,8 @@ class OpenStackCinderShell(object):
service_name=service_name,
volume_service_name=volume_service_name,
retries=options.retries,
http_log_debug=args.debug)
http_log_debug=args.debug,
cacert=cacert)
try:
if not utils.isunauthenticated(args.func):

@ -28,7 +28,8 @@ class Client(object):
endpoint_type='publicURL', extensions=None,
service_type='volume', service_name=None,
volume_service_name=None, retries=None,
http_log_debug=False):
http_log_debug=False,
cacert=None):
# FIXME(comstud): Rename the api_key argument above when we
# know it's not being used as keyword argument
password = api_key
@ -64,7 +65,8 @@ class Client(object):
service_name=service_name,
volume_service_name=volume_service_name,
retries=retries,
http_log_debug=http_log_debug)
http_log_debug=http_log_debug,
cacert=cacert)
def authenticate(self):
"""

@ -1,14 +1,35 @@
import httplib2
import mock
import requests
from cinderclient import client
from cinderclient import exceptions
from tests import utils
fake_response = httplib2.Response({"status": 200})
fake_body = '{"hi": "there"}'
mock_request = mock.Mock(return_value=(fake_response, fake_body))
fake_response = utils.TestResponse({
"status_code": 200,
"text": '{"hi": "there"}',
})
mock_request = mock.Mock(return_value=(fake_response))
bad_400_response = utils.TestResponse({
"status_code": 400,
"text": '{"error": {"message": "n/a", "details": "Terrible!"}}',
})
bad_400_request = mock.Mock(return_value=(bad_400_response))
bad_401_response = utils.TestResponse({
"status_code": 401,
"text": '{"error": {"message": "FAILED!", "details": "DETAILS!"}}',
})
bad_401_request = mock.Mock(return_value=(bad_401_response))
bad_500_response = utils.TestResponse({
"status_code": 500,
"text": '{"error": {"message": "FAILED!", "details": "DETAILS!"}}',
})
bad_500_request = mock.Mock(return_value=(bad_500_response))
def get_client(retries=0):
@ -29,7 +50,7 @@ class ClientTest(utils.TestCase):
def test_get(self):
cl = get_authed_client()
@mock.patch.object(httplib2.Http, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
@ -37,8 +58,11 @@ class ClientTest(utils.TestCase):
"X-Auth-Project-Id": "project_id",
"User-Agent": cl.USER_AGENT,
'Accept': 'application/json', }
mock_request.assert_called_with("http://example.com/hi",
"GET", headers=headers)
mock_request.assert_called_with(
"GET",
"http://example.com/hi",
headers=headers,
**self.TEST_REQUEST_BASE)
# Automatic JSON parsing
self.assertEqual(body, {"hi": "there"})
@ -47,10 +71,7 @@ class ClientTest(utils.TestCase):
def test_get_reauth_0_retries(self):
cl = get_authed_client(retries=0)
bad_response = httplib2.Response({"status": 401})
bad_body = '{"error": {"message": "FAILED!", "details": "DETAILS!"}}'
bad_request = mock.Mock(return_value=(bad_response, bad_body))
self.requests = [bad_request, mock_request]
self.requests = [bad_401_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
@ -61,7 +82,7 @@ class ClientTest(utils.TestCase):
cl.auth_token = "token"
@mock.patch.object(cl, 'authenticate', reauth)
@mock.patch.object(httplib2.Http, "request", request)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
@ -72,16 +93,13 @@ class ClientTest(utils.TestCase):
def test_get_retry_500(self):
cl = get_authed_client(retries=1)
bad_response = httplib2.Response({"status": 500})
bad_body = '{"error": {"message": "FAILED!", "details": "DETAILS!"}}'
bad_request = mock.Mock(return_value=(bad_response, bad_body))
self.requests = [bad_request, mock_request]
self.requests = [bad_500_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(httplib2.Http, "request", request)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
@ -92,16 +110,13 @@ class ClientTest(utils.TestCase):
def test_retry_limit(self):
cl = get_authed_client(retries=1)
bad_response = httplib2.Response({"status": 500})
bad_body = '{"error": {"message": "FAILED!", "details": "DETAILS!"}}'
bad_request = mock.Mock(return_value=(bad_response, bad_body))
self.requests = [bad_request, bad_request, mock_request]
self.requests = [bad_500_request, bad_500_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(httplib2.Http, "request", request)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
@ -110,18 +125,15 @@ class ClientTest(utils.TestCase):
self.assertEqual(self.requests, [mock_request])
def test_get_no_retry_400(self):
cl = get_authed_client(retries=1)
cl = get_authed_client(retries=0)
bad_response = httplib2.Response({"status": 400})
bad_body = '{"error": {"message": "Bad!", "details": "Terrible!"}}'
bad_request = mock.Mock(return_value=(bad_response, bad_body))
self.requests = [bad_request, mock_request]
self.requests = [bad_400_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(httplib2.Http, "request", request)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
@ -132,16 +144,13 @@ class ClientTest(utils.TestCase):
def test_get_retry_400_socket(self):
cl = get_authed_client(retries=1)
bad_response = httplib2.Response({"status": 400})
bad_body = '{"error": {"message": "n/a", "details": "n/a"}}'
bad_request = mock.Mock(return_value=(bad_response, bad_body))
self.requests = [bad_request, mock_request]
self.requests = [bad_400_request, mock_request]
def request(*args, **kwargs):
next_request = self.requests.pop(0)
return next_request(*args, **kwargs)
@mock.patch.object(httplib2.Http, "request", request)
@mock.patch.object(requests, "request", request)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
@ -152,7 +161,7 @@ class ClientTest(utils.TestCase):
def test_post(self):
cl = get_authed_client()
@mock.patch.object(httplib2.Http, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_post_call():
cl.post("/hi", body=[1, 2, 3])
headers = {
@ -162,8 +171,12 @@ class ClientTest(utils.TestCase):
'Accept': 'application/json',
"User-Agent": cl.USER_AGENT
}
mock_request.assert_called_with("http://example.com/hi", "POST",
headers=headers, body='[1, 2, 3]')
mock_request.assert_called_with(
"POST",
"http://example.com/hi",
headers=headers,
data='[1, 2, 3]',
**self.TEST_REQUEST_BASE)
test_post_call()
@ -171,7 +184,7 @@ class ClientTest(utils.TestCase):
cl = get_client()
# response must not have x-server-management-url header
@mock.patch.object(httplib2.Http, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.AuthorizationFailure, cl.authenticate)

@ -1,6 +1,5 @@
import cStringIO
import os
import httplib2
import sys
from cinderclient import exceptions
@ -44,11 +43,6 @@ class ShellTest(utils.TestCase):
def test_help_unknown_command(self):
self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo')
def test_debug(self):
httplib2.debuglevel = 0
self.shell('--debug help')
assert httplib2.debuglevel == 1
def test_help(self):
required = [
'^usage: ',

@ -1,5 +1,33 @@
import unittest2
import requests
class TestCase(unittest2.TestCase):
pass
TEST_REQUEST_BASE = {
'config': {'danger_mode': False},
'verify': True,
}
class TestResponse(requests.Response):
""" Class used to wrap requests.Response and provide some
convenience to initialize with a dict """
def __init__(self, data):
self._text = None
super(TestResponse, self)
if isinstance(data, dict):
self.status_code = data.get('status_code', None)
self.headers = data.get('headers', None)
# Fake the text attribute to streamline Response creation
self._text = data.get('text', None)
else:
self.status_code = data
def __eq__(self, other):
return self.__dict__ == other.__dict__
@property
def text(self):
return self._text

@ -13,12 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import httplib2
import urlparse
from cinderclient import client as base_client
from cinderclient.v1 import client
from tests import fakes
import tests.utils as utils
def _stub_volume(**kwargs):
@ -97,28 +97,35 @@ class FakeHTTPClient(base_client.HTTPClient):
# Note the call
self.callstack.append((method, url, kwargs.get('body', None)))
status, body = getattr(self, callback)(**kwargs)
status, headers, body = getattr(self, callback)(**kwargs)
r = utils.TestResponse({
"status_code": status,
"text": body,
"headers": headers,
})
return r, body
if hasattr(status, 'items'):
return httplib2.Response(status), body
return utils.TestResponse(status), body
else:
return httplib2.Response({"status": status}), body
return utils.TestResponse({"status": status}), body
#
# Snapshots
#
def get_snapshots_detail(self, **kw):
return (200, {'snapshots': [
return (200, {}, {'snapshots': [
_stub_snapshot(),
]})
def get_snapshots_1234(self, **kw):
return (200, {'snapshot': _stub_snapshot(id='1234')})
return (200, {}, {'snapshot': _stub_snapshot(id='1234')})
def put_snapshots_1234(self, **kw):
snapshot = _stub_snapshot(id='1234')
snapshot.update(kw['body']['snapshot'])
return (200, {'snapshot': snapshot})
return (200, {}, {'snapshot': snapshot})
#
# Volumes
@ -127,10 +134,10 @@ class FakeHTTPClient(base_client.HTTPClient):
def put_volumes_1234(self, **kw):
volume = _stub_volume(id='1234')
volume.update(kw['body']['volume'])
return (200, {'volume': volume})
return (200, {}, {'volume': volume})
def get_volumes(self, **kw):
return (200, {"volumes": [
return (200, {}, {"volumes": [
{'id': 1234, 'name': 'sample-volume'},
{'id': 5678, 'name': 'sample-volume2'}
]})
@ -138,15 +145,15 @@ class FakeHTTPClient(base_client.HTTPClient):
# TODO(jdg): This will need to change
# at the very least it's not complete
def get_volumes_detail(self, **kw):
return (200, {"volumes": [
return (200, {}, {"volumes": [
{'id': 1234,
'name': 'sample-volume',
'attachments': [{'server_id': 1234}]},
]})
def get_volumes_1234(self, **kw):
r = {'volume': self.get_volumes_detail()[1]['volumes'][0]}
return (200, r)
r = {'volume': self.get_volumes_detail()[2]['volumes'][0]}
return (200, {}, r)
def post_volumes_1234_action(self, body, **kw):
_body = None
@ -163,7 +170,7 @@ class FakeHTTPClient(base_client.HTTPClient):
assert body[action] is None
elif action == 'os-initialize_connection':
assert body[action].keys() == ['connector']
return (202, {'connection_info': 'foos'})
return (202, {}, {'connection_info': 'foos'})
elif action == 'os-terminate_connection':
assert body[action].keys() == ['connector']
elif action == 'os-begin_detaching':
@ -172,68 +179,68 @@ class FakeHTTPClient(base_client.HTTPClient):
assert body[action] is None
else:
raise AssertionError("Unexpected server action: %s" % action)
return (resp, _body)
return (resp, {}, _body)
def post_volumes(self, **kw):
return (202, {'volume': {}})
return (202, {}, {'volume': {}})
def delete_volumes_1234(self, **kw):
return (202, None)
return (202, {}, None)
#
# Quotas
#
def get_os_quota_sets_test(self, **kw):
return (200, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 1,
'gigabytes': 1}})
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 1,
'gigabytes': 1}})
def get_os_quota_sets_test_defaults(self):
return (200, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 1,
'gigabytes': 1}})
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 1,
'gigabytes': 1}})
def put_os_quota_sets_test(self, body, **kw):
assert body.keys() == ['quota_set']
fakes.assert_has_keys(body['quota_set'],
required=['tenant_id'])
return (200, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 2,
'gigabytes': 1}})
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 2,
'gigabytes': 1}})
#
# Quota Classes
#
def get_os_quota_class_sets_test(self, **kw):
return (200, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'volumes': 1,
'gigabytes': 1}})
return (200, {}, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'volumes': 1,
'gigabytes': 1}})
def put_os_quota_class_sets_test(self, body, **kw):
assert body.keys() == ['quota_class_set']
fakes.assert_has_keys(body['quota_class_set'],
required=['class_name'])
return (200, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'volumes': 2,
'gigabytes': 1}})
return (200, {}, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'volumes': 2,
'gigabytes': 1}})
#
# VolumeTypes
#
def get_types(self, **kw):
return (200, {
return (200, {}, {
'volume_types': [{'id': 1,
'name': 'test-type-1',
'extra_specs':{}},
@ -242,21 +249,21 @@ class FakeHTTPClient(base_client.HTTPClient):
'extra_specs':{}}]})
def get_types_1(self, **kw):
return (200, {'volume_type': {'id': 1,
'name': 'test-type-1',
'extra_specs': {}}})
return (200, {}, {'volume_type': {'id': 1,
'name': 'test-type-1',
'extra_specs': {}}})
def post_types(self, body, **kw):
return (202, {'volume_type': {'id': 3,
'name': 'test-type-3',
'extra_specs': {}}})
return (202, {}, {'volume_type': {'id': 3,
'name': 'test-type-3',
'extra_specs': {}}})
def post_types_1_extra_specs(self, body, **kw):
assert body.keys() == ['extra_specs']
return (200, {'extra_specs': {'k': 'v'}})
return (200, {}, {'extra_specs': {'k': 'v'}})
def delete_types_1_extra_specs_k(self, **kw):
return(204, None)
return(204, {}, None)
def delete_types_1(self, **kw):
return (202, None)
return (202, {}, None)

@ -1,20 +1,13 @@
import httplib2
import json
import mock
import requests
from cinderclient.v1 import client
from cinderclient import exceptions
from tests import utils
def to_http_response(resp_dict):
"""Converts dict of response attributes to httplib response."""
resp = httplib2.Response(resp_dict)
for k, v in resp_dict['headers'].items():
resp[k] = v
return resp
class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id",
@ -40,14 +33,14 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
],
},
}
auth_response = httplib2.Response({
"status": 200,
"body": json.dumps(resp), })
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response,
json.dumps(resp)))
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(httplib2.Http, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
@ -66,9 +59,13 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(token_url, "POST",
headers=headers,
body=json.dumps(body))
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
@ -108,14 +105,14 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
],
},
}
auth_response = httplib2.Response({
"status": 200,
"body": json.dumps(resp), })
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response,
json.dumps(resp)))
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(httplib2.Http, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
@ -134,9 +131,13 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(token_url, "POST",
headers=headers,
body=json.dumps(body))
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
@ -152,14 +153,14 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0")
resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
auth_response = httplib2.Response({
"status": 401,
"body": json.dumps(resp), })
auth_response = utils.TestResponse({
"status_code": 401,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response,
json.dumps(resp)))
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(httplib2.Http, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
@ -192,29 +193,28 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
correct_response = json.dumps(dict_correct_response)
dict_responses = [
{"headers": {'location':'http://127.0.0.1:5001'},
"status": 305,
"body": "Use proxy"},
"status_code": 305,
"text": "Use proxy"},
# Configured on admin port, cinder redirects to v2.0 port.
# When trying to connect on it, keystone auth succeed by v1.0
# protocol (through headers) but tokens are being returned in
# body (looks like keystone bug). Leaved for compatibility.
{"headers": {},
"status": 200,
"body": correct_response},
"status_code": 200,
"text": correct_response},
{"headers": {},
"status": 200,
"body": correct_response}
"status_code": 200,
"text": correct_response}
]
responses = [(to_http_response(resp), resp['body'])
for resp in dict_responses]
responses = [(utils.TestResponse(resp)) for resp in dict_responses]
def side_effect(*args, **kwargs):
return responses.pop(0)
mock_request = mock.Mock(side_effect=side_effect)
@mock.patch.object(httplib2.Http, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
@ -233,9 +233,13 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(token_url, "POST",
headers=headers,
body=json.dumps(body))
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
resp = dict_correct_response
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
@ -282,16 +286,14 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
],
},
}
auth_response = httplib2.Response(
{
"status": 200,
"body": json.dumps(resp),
})
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response,
json.dumps(resp)))
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(httplib2.Http, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.AmbiguousEndpoints,
cs.client.authenticate)
@ -302,15 +304,17 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
class AuthenticationTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id", "auth_url")
management_url = 'https://servers.api.rackspacecloud.com/v1.1/443470'
auth_response = httplib2.Response({
'status': 204,
'x-server-management-url': management_url,
'x-auth-token': '1b751d74-de0c-46ae-84f0-915744b582d1',
management_url = 'https://localhost/v1.1/443470'
auth_response = utils.TestResponse({
'status_code': 204,
'headers': {
'x-server-management-url': management_url,
'x-auth-token': '1b751d74-de0c-46ae-84f0-915744b582d1',
},
})
mock_request = mock.Mock(return_value=(auth_response, None))
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(httplib2.Http, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
@ -320,21 +324,25 @@ class AuthenticationTests(utils.TestCase):
'X-Auth-Project-Id': 'project_id',
'User-Agent': cs.client.USER_AGENT
}
mock_request.assert_called_with(cs.client.auth_url, 'GET',
headers=headers)
mock_request.assert_called_with(
"GET",
cs.client.auth_url,
headers=headers,
**self.TEST_REQUEST_BASE)
self.assertEqual(cs.client.management_url,
auth_response['x-server-management-url'])
auth_response.headers['x-server-management-url'])
self.assertEqual(cs.client.auth_token,
auth_response['x-auth-token'])
auth_response.headers['x-auth-token'])
test_auth_call()
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id", "auth_url")
auth_response = httplib2.Response({'status': 401})
mock_request = mock.Mock(return_value=(auth_response, None))
auth_response = utils.TestResponse({"status_code": 401})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(httplib2.Http, "request", mock_request)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)

@ -1,4 +1,4 @@
argparse
httplib2
prettytable
requests<1.0
simplejson