Finalize Python3 support
Set the environment variable PYTHONHASHSEED to 0 to have predictive tests under Python 3. Change-Id: Ia15a9383e0f20bd0e4572e9f9b9772f1704dff86
This commit is contained in:
parent
f2e0610628
commit
628c541a69
|
@ -19,6 +19,7 @@ import hashlib
|
||||||
import logging
|
import logging
|
||||||
import posixpath
|
import posixpath
|
||||||
import socket
|
import socket
|
||||||
|
import ssl
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
@ -63,6 +64,13 @@ USER_AGENT = 'python-glanceclient'
|
||||||
CHUNKSIZE = 1024 * 64 # 64kB
|
CHUNKSIZE = 1024 * 64 # 64kB
|
||||||
|
|
||||||
|
|
||||||
|
def to_bytes(s):
|
||||||
|
if isinstance(s, six.string_types):
|
||||||
|
return six.b(s)
|
||||||
|
else:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
class HTTPClient(object):
|
class HTTPClient(object):
|
||||||
|
|
||||||
def __init__(self, endpoint, **kwargs):
|
def __init__(self, endpoint, **kwargs):
|
||||||
|
@ -149,8 +157,9 @@ class HTTPClient(object):
|
||||||
dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
|
dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
|
||||||
dump.append('')
|
dump.append('')
|
||||||
if body:
|
if body:
|
||||||
|
body = strutils.safe_decode(body)
|
||||||
dump.extend([body, ''])
|
dump.extend([body, ''])
|
||||||
LOG.debug(strutils.safe_encode('\n'.join(dump)))
|
LOG.debug('\n'.join([strutils.safe_encode(x) for x in dump]))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def encode_headers(headers):
|
def encode_headers(headers):
|
||||||
|
@ -239,9 +248,9 @@ class HTTPClient(object):
|
||||||
|
|
||||||
# Read body into string if it isn't obviously image data
|
# Read body into string if it isn't obviously image data
|
||||||
if resp.getheader('content-type', None) != 'application/octet-stream':
|
if resp.getheader('content-type', None) != 'application/octet-stream':
|
||||||
body_str = ''.join([chunk for chunk in body_iter])
|
body_str = b''.join([to_bytes(chunk) for chunk in body_iter])
|
||||||
self.log_http_response(resp, body_str)
|
self.log_http_response(resp, body_str)
|
||||||
body_iter = six.StringIO(body_str)
|
body_iter = six.BytesIO(body_str)
|
||||||
else:
|
else:
|
||||||
self.log_http_response(resp)
|
self.log_http_response(resp)
|
||||||
|
|
||||||
|
@ -349,16 +358,28 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||||
def __init__(self, host, port=None, key_file=None, cert_file=None,
|
def __init__(self, host, port=None, key_file=None, cert_file=None,
|
||||||
cacert=None, timeout=None, insecure=False,
|
cacert=None, timeout=None, insecure=False,
|
||||||
ssl_compression=True):
|
ssl_compression=True):
|
||||||
HTTPSConnection.__init__(self, host, port,
|
# List of exceptions reported by Python3 instead of
|
||||||
key_file=key_file,
|
# SSLConfigurationError
|
||||||
cert_file=cert_file)
|
if six.PY3:
|
||||||
self.key_file = key_file
|
excp_lst = (TypeError, FileNotFoundError, ssl.SSLError)
|
||||||
self.cert_file = cert_file
|
else:
|
||||||
self.timeout = timeout
|
excp_lst = ()
|
||||||
self.insecure = insecure
|
try:
|
||||||
self.ssl_compression = ssl_compression
|
HTTPSConnection.__init__(self, host, port,
|
||||||
self.cacert = cacert
|
key_file=key_file,
|
||||||
self.setcontext()
|
cert_file=cert_file)
|
||||||
|
self.key_file = key_file
|
||||||
|
self.cert_file = cert_file
|
||||||
|
self.timeout = timeout
|
||||||
|
self.insecure = insecure
|
||||||
|
self.ssl_compression = ssl_compression
|
||||||
|
self.cacert = cacert
|
||||||
|
self.setcontext()
|
||||||
|
# ssl exceptions are reported in various form in Python 3
|
||||||
|
# so to be compatible, we report the same kind as under
|
||||||
|
# Python2
|
||||||
|
except excp_lst as e:
|
||||||
|
raise exc.SSLConfigurationError(str(e))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def host_matches_cert(host, x509):
|
def host_matches_cert(host, x509):
|
||||||
|
@ -388,7 +409,7 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||||
san_list = None
|
san_list = None
|
||||||
for i in range(x509.get_extension_count()):
|
for i in range(x509.get_extension_count()):
|
||||||
ext = x509.get_extension(i)
|
ext = x509.get_extension(i)
|
||||||
if ext.get_short_name() == 'subjectAltName':
|
if ext.get_short_name() == b'subjectAltName':
|
||||||
san_list = str(ext)
|
san_list = str(ext)
|
||||||
for san in ''.join(san_list.split()).split(','):
|
for san in ''.join(san_list.split()).split(','):
|
||||||
if san.startswith('DNS:'):
|
if san.startswith('DNS:'):
|
||||||
|
@ -458,7 +479,7 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||||
|
|
||||||
if self.cacert:
|
if self.cacert:
|
||||||
try:
|
try:
|
||||||
self.context.load_verify_locations(self.cacert)
|
self.context.load_verify_locations(to_bytes(self.cacert))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = ('Unable to load CA from "%(cacert)s" %(exc)s' %
|
msg = ('Unable to load CA from "%(cacert)s" %(exc)s' %
|
||||||
dict(cacert=self.cacert, exc=e))
|
dict(cacert=self.cacert, exc=e))
|
||||||
|
@ -537,6 +558,8 @@ class ResponseBodyIterator(object):
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
yield chunk
|
yield chunk
|
||||||
|
if isinstance(chunk, six.string_types):
|
||||||
|
chunk = six.b(chunk)
|
||||||
md5sum.update(chunk)
|
md5sum.update(chunk)
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
|
|
|
@ -267,7 +267,8 @@ def get_file_size(file_obj):
|
||||||
:param file_obj: file-like object.
|
:param file_obj: file-like object.
|
||||||
:retval The file's size or None if it cannot be determined.
|
:retval The file's size or None if it cannot be determined.
|
||||||
"""
|
"""
|
||||||
if hasattr(file_obj, 'seek') and hasattr(file_obj, 'tell'):
|
if (hasattr(file_obj, 'seek') and hasattr(file_obj, 'tell') and
|
||||||
|
(six.PY2 or six.PY3 and file_obj.seekable())):
|
||||||
try:
|
try:
|
||||||
curr = file_obj.tell()
|
curr = file_obj.tell()
|
||||||
file_obj.seek(0, os.SEEK_END)
|
file_obj.seek(0, os.SEEK_END)
|
||||||
|
|
|
@ -20,6 +20,7 @@ Command-line interface to the OpenStack Images API.
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
@ -440,8 +441,12 @@ class OpenStackImagesShell(object):
|
||||||
|
|
||||||
def main(self, argv):
|
def main(self, argv):
|
||||||
# Parse args once to find version
|
# Parse args once to find version
|
||||||
|
|
||||||
|
#NOTE(flepied) Under Python3, parsed arguments are removed
|
||||||
|
# from the list so make a copy for the first parsing
|
||||||
|
base_argv = copy.deepcopy(argv)
|
||||||
parser = self.get_base_parser()
|
parser = self.get_base_parser()
|
||||||
(options, args) = parser.parse_known_args(argv)
|
(options, args) = parser.parse_known_args(base_argv)
|
||||||
|
|
||||||
# build available subcommands based on version
|
# build available subcommands based on version
|
||||||
api_version = options.os_image_api_version
|
api_version = options.os_image_api_version
|
||||||
|
|
|
@ -16,6 +16,8 @@ classifier =
|
||||||
Programming Language :: Python :: 2
|
Programming Language :: Python :: 2
|
||||||
Programming Language :: Python :: 2.7
|
Programming Language :: Python :: 2.7
|
||||||
Programming Language :: Python :: 2.6
|
Programming Language :: Python :: 2.6
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3.3
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
packages =
|
packages =
|
||||||
|
|
|
@ -105,7 +105,7 @@ class TestClient(testtools.TestCase):
|
||||||
|
|
||||||
def test_request_redirected(self):
|
def test_request_redirected(self):
|
||||||
resp = utils.FakeResponse({'location': 'http://www.example.com'},
|
resp = utils.FakeResponse({'location': 'http://www.example.com'},
|
||||||
status=302, body=six.StringIO())
|
status=302, body=six.BytesIO())
|
||||||
http_client.HTTPConnection.request(
|
http_client.HTTPConnection.request(
|
||||||
mox.IgnoreArg(),
|
mox.IgnoreArg(),
|
||||||
mox.IgnoreArg(),
|
mox.IgnoreArg(),
|
||||||
|
@ -114,8 +114,8 @@ class TestClient(testtools.TestCase):
|
||||||
http_client.HTTPConnection.getresponse().AndReturn(resp)
|
http_client.HTTPConnection.getresponse().AndReturn(resp)
|
||||||
|
|
||||||
# The second request should be to the redirected location
|
# The second request should be to the redirected location
|
||||||
expected_response = 'Ok'
|
expected_response = b'Ok'
|
||||||
resp2 = utils.FakeResponse({}, six.StringIO(expected_response))
|
resp2 = utils.FakeResponse({}, six.BytesIO(expected_response))
|
||||||
http_client.HTTPConnection.request(
|
http_client.HTTPConnection.request(
|
||||||
'GET',
|
'GET',
|
||||||
'http://www.example.com',
|
'http://www.example.com',
|
||||||
|
@ -135,8 +135,8 @@ class TestClient(testtools.TestCase):
|
||||||
|
|
||||||
# Lets fake the response
|
# Lets fake the response
|
||||||
# returned by httplib
|
# returned by httplib
|
||||||
expected_response = 'Ok'
|
expected_response = b'Ok'
|
||||||
fake = utils.FakeResponse({}, six.StringIO(expected_response))
|
fake = utils.FakeResponse({}, six.BytesIO(expected_response))
|
||||||
http_client.HTTPConnection.getresponse().AndReturn(fake)
|
http_client.HTTPConnection.getresponse().AndReturn(fake)
|
||||||
self.mock.ReplayAll()
|
self.mock.ReplayAll()
|
||||||
|
|
||||||
|
@ -146,9 +146,13 @@ class TestClient(testtools.TestCase):
|
||||||
self.assertEqual(fake, resp)
|
self.assertEqual(fake, resp)
|
||||||
|
|
||||||
def test_headers_encoding(self):
|
def test_headers_encoding(self):
|
||||||
headers = {"test": u'ni\xf1o'}
|
value = u'ni\xf1o'
|
||||||
|
headers = {"test": value}
|
||||||
encoded = self.client.encode_headers(headers)
|
encoded = self.client.encode_headers(headers)
|
||||||
self.assertEqual("ni\xc3\xb1o", encoded["test"])
|
if six.PY2:
|
||||||
|
self.assertEqual("ni\xc3\xb1o", encoded["test"])
|
||||||
|
else:
|
||||||
|
self.assertEqual(value, encoded["test"])
|
||||||
|
|
||||||
def test_raw_request(self):
|
def test_raw_request(self):
|
||||||
" Verify the path being used for HTTP requests reflects accurately. "
|
" Verify the path being used for HTTP requests reflects accurately. "
|
||||||
|
@ -164,7 +168,7 @@ class TestClient(testtools.TestCase):
|
||||||
headers=mox.IgnoreArg()).WithSideEffects(check_request)
|
headers=mox.IgnoreArg()).WithSideEffects(check_request)
|
||||||
|
|
||||||
# fake the response returned by httplib
|
# fake the response returned by httplib
|
||||||
fake = utils.FakeResponse({}, six.StringIO('Ok'))
|
fake = utils.FakeResponse({}, six.BytesIO(b'Ok'))
|
||||||
http_client.HTTPConnection.getresponse().AndReturn(fake)
|
http_client.HTTPConnection.getresponse().AndReturn(fake)
|
||||||
self.mock.ReplayAll()
|
self.mock.ReplayAll()
|
||||||
|
|
||||||
|
@ -192,7 +196,7 @@ class TestClient(testtools.TestCase):
|
||||||
headers=mox.IgnoreArg()).WithSideEffects(check_request)
|
headers=mox.IgnoreArg()).WithSideEffects(check_request)
|
||||||
|
|
||||||
# fake the response returned by httplib
|
# fake the response returned by httplib
|
||||||
fake = utils.FakeResponse({}, six.StringIO('Ok'))
|
fake = utils.FakeResponse({}, six.BytesIO(b'Ok'))
|
||||||
http_client.HTTPConnection.getresponse().AndReturn(fake)
|
http_client.HTTPConnection.getresponse().AndReturn(fake)
|
||||||
self.mock.ReplayAll()
|
self.mock.ReplayAll()
|
||||||
|
|
||||||
|
@ -396,19 +400,19 @@ class TestVerifiedHTTPSConnection(testtools.TestCase):
|
||||||
class TestResponseBodyIterator(testtools.TestCase):
|
class TestResponseBodyIterator(testtools.TestCase):
|
||||||
|
|
||||||
def test_iter_default_chunk_size_64k(self):
|
def test_iter_default_chunk_size_64k(self):
|
||||||
resp = utils.FakeResponse({}, six.StringIO('X' * 98304))
|
resp = utils.FakeResponse({}, six.BytesIO(b'X' * 98304))
|
||||||
iterator = http.ResponseBodyIterator(resp)
|
iterator = http.ResponseBodyIterator(resp)
|
||||||
chunks = list(iterator)
|
chunks = list(iterator)
|
||||||
self.assertEqual(['X' * 65536, 'X' * 32768], chunks)
|
self.assertEqual([b'X' * 65536, b'X' * 32768], chunks)
|
||||||
|
|
||||||
def test_integrity_check_with_correct_checksum(self):
|
def test_integrity_check_with_correct_checksum(self):
|
||||||
resp = utils.FakeResponse({}, six.StringIO('CCC'))
|
resp = utils.FakeResponse({}, six.BytesIO(b'CCC'))
|
||||||
body = http.ResponseBodyIterator(resp)
|
body = http.ResponseBodyIterator(resp)
|
||||||
body.set_checksum('defb99e69a9f1f6e06f15006b1f166ae')
|
body.set_checksum('defb99e69a9f1f6e06f15006b1f166ae')
|
||||||
list(body)
|
list(body)
|
||||||
|
|
||||||
def test_integrity_check_with_wrong_checksum(self):
|
def test_integrity_check_with_wrong_checksum(self):
|
||||||
resp = utils.FakeResponse({}, six.StringIO('BB'))
|
resp = utils.FakeResponse({}, six.BytesIO(b'BB'))
|
||||||
body = http.ResponseBodyIterator(resp)
|
body = http.ResponseBodyIterator(resp)
|
||||||
body.set_checksum('wrong')
|
body.set_checksum('wrong')
|
||||||
try:
|
try:
|
||||||
|
@ -418,7 +422,7 @@ class TestResponseBodyIterator(testtools.TestCase):
|
||||||
self.assertEqual(errno.EPIPE, e.errno)
|
self.assertEqual(errno.EPIPE, e.errno)
|
||||||
|
|
||||||
def test_set_checksum_in_consumed_iterator(self):
|
def test_set_checksum_in_consumed_iterator(self):
|
||||||
resp = utils.FakeResponse({}, six.StringIO('CCC'))
|
resp = utils.FakeResponse({}, six.BytesIO(b'CCC'))
|
||||||
body = http.ResponseBodyIterator(resp)
|
body = http.ResponseBodyIterator(resp)
|
||||||
list(body)
|
list(body)
|
||||||
# Setting checksum for an already consumed iterator should raise an
|
# Setting checksum for an already consumed iterator should raise an
|
||||||
|
@ -430,6 +434,6 @@ class TestResponseBodyIterator(testtools.TestCase):
|
||||||
def test_body_size(self):
|
def test_body_size(self):
|
||||||
size = 1000000007
|
size = 1000000007
|
||||||
resp = utils.FakeResponse(
|
resp = utils.FakeResponse(
|
||||||
{'content-length': str(size)}, six.StringIO('BB'))
|
{'content-length': str(size)}, six.BytesIO(b'BB'))
|
||||||
body = http.ResponseBodyIterator(resp)
|
body = http.ResponseBodyIterator(resp)
|
||||||
self.assertEqual(size, len(body))
|
self.assertEqual(size, len(body))
|
||||||
|
|
|
@ -66,6 +66,8 @@ class TestVerifiedHTTPSConnection(testtools.TestCase):
|
||||||
conn = http.VerifiedHTTPSConnection('127.0.0.1', 0,
|
conn = http.VerifiedHTTPSConnection('127.0.0.1', 0,
|
||||||
key_file=key_file,
|
key_file=key_file,
|
||||||
cacert=cacert)
|
cacert=cacert)
|
||||||
|
except exc.SSLConfigurationError:
|
||||||
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
self.fail('Failed to init VerifiedHTTPSConnection.')
|
self.fail('Failed to init VerifiedHTTPSConnection.')
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,10 @@ class TestUtils(testtools.TestCase):
|
||||||
self.assertEqual('error message', ret)
|
self.assertEqual('error message', ret)
|
||||||
|
|
||||||
ret = utils.exception_to_str(Exception('\xa5 error message'))
|
ret = utils.exception_to_str(Exception('\xa5 error message'))
|
||||||
self.assertEqual(' error message', ret)
|
if six.PY2:
|
||||||
|
self.assertEqual(' error message', ret)
|
||||||
|
else:
|
||||||
|
self.assertEqual('\xa5 error message', ret)
|
||||||
|
|
||||||
ret = utils.exception_to_str(FakeException('\xa5 error message'))
|
ret = utils.exception_to_str(FakeException('\xa5 error message'))
|
||||||
self.assertEqual("Caught '%(exception)s' exception." %
|
self.assertEqual("Caught '%(exception)s' exception." %
|
||||||
|
|
|
@ -347,7 +347,7 @@ fixtures = {
|
||||||
'HEAD': (
|
'HEAD': (
|
||||||
{
|
{
|
||||||
'x-image-meta-id': '3',
|
'x-image-meta-id': '3',
|
||||||
'x-image-meta-name': "ni\xc3\xb1o"
|
'x-image-meta-name': u"ni\xf1o"
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
|
@ -622,9 +622,13 @@ class ImageManagerTest(testtools.TestCase):
|
||||||
self.assertEqual(expect, self.api.calls)
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
|
||||||
def test_image_meta_from_headers_encoding(self):
|
def test_image_meta_from_headers_encoding(self):
|
||||||
fields = {"x-image-meta-name": "ni\xc3\xb1o"}
|
value = u"ni\xf1o"
|
||||||
|
if six.PY2:
|
||||||
|
fields = {"x-image-meta-name": "ni\xc3\xb1o"}
|
||||||
|
else:
|
||||||
|
fields = {"x-image-meta-name": value}
|
||||||
headers = self.mgr._image_meta_from_headers(fields)
|
headers = self.mgr._image_meta_from_headers(fields)
|
||||||
self.assertEqual(u"ni\xf1o", headers["name"])
|
self.assertEqual(value, headers["name"])
|
||||||
|
|
||||||
def test_image_list_with_owner(self):
|
def test_image_list_with_owner(self):
|
||||||
images = self.mgr.list(owner='A', page_size=20)
|
images = self.mgr.list(owner='A', page_size=20)
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
import errno
|
import errno
|
||||||
import testtools
|
import testtools
|
||||||
|
|
||||||
|
import six
|
||||||
import warlock
|
import warlock
|
||||||
|
|
||||||
from glanceclient.v2 import images
|
from glanceclient.v2 import images
|
||||||
|
@ -403,8 +404,10 @@ class TestController(testtools.TestCase):
|
||||||
# /v2/images?owner=ni%C3%B1o&limit=20
|
# /v2/images?owner=ni%C3%B1o&limit=20
|
||||||
# We just want to make sure filters are correctly encoded.
|
# We just want to make sure filters are correctly encoded.
|
||||||
pass
|
pass
|
||||||
|
if six.PY2:
|
||||||
self.assertEqual("ni\xc3\xb1o", filters["owner"])
|
self.assertEqual("ni\xc3\xb1o", filters["owner"])
|
||||||
|
else:
|
||||||
|
self.assertEqual("ni\xf1o", filters["owner"])
|
||||||
|
|
||||||
def test_list_images_for_tag_single_image(self):
|
def test_list_images_for_tag_single_image(self):
|
||||||
img_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
img_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
|
||||||
|
|
1
tox.ini
1
tox.ini
|
@ -9,6 +9,7 @@ install_command = pip install -U {opts} {packages}
|
||||||
setenv = VIRTUAL_ENV={envdir}
|
setenv = VIRTUAL_ENV={envdir}
|
||||||
OS_STDOUT_NOCAPTURE=False
|
OS_STDOUT_NOCAPTURE=False
|
||||||
OS_STDERR_NOCAPTURE=False
|
OS_STDERR_NOCAPTURE=False
|
||||||
|
PYTHONHASHSEED=0
|
||||||
|
|
||||||
deps = -r{toxinidir}/requirements.txt
|
deps = -r{toxinidir}/requirements.txt
|
||||||
-r{toxinidir}/test-requirements.txt
|
-r{toxinidir}/test-requirements.txt
|
||||||
|
|
Loading…
Reference in New Issue