Upgrade swifclient to 2.2.0

This commit is contained in:
Matthew Jones 2014-08-06 15:37:02 -04:00
parent 07bfe7c275
commit 15c2627968
7 changed files with 1776 additions and 89 deletions

View File

@ -46,7 +46,7 @@ prettytable==0.7.2 (prettytable.py)
pyrax==1.9.0 (pyrax/*)
python-dateutil==2.2 (dateutil/*)
python-novaclient==2.18.1 (novaclient/*, excluded bin/nova)
python-swiftclient==2.0.3 (swiftclient/*, excluded bin/swift)
python-swiftclient==2.2.0 (swiftclient/*, excluded bin/swift)
pytz==2014.4 (pytz/*)
rackspace-auth-openstack==1.3 (rackspace_auth_openstack/*)
rackspace-novaclient==1.4 (no files)

View File

@ -1,4 +1,4 @@
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-
# Copyright (c) 2012 Rackspace
# flake8: noqa
# Licensed under the Apache License, Version 2.0 (the "License");
@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
""""
"""
OpenStack Swift Python client binding.
"""
from .client import *

View File

@ -22,14 +22,17 @@ import requests
import sys
import logging
import warnings
import functools
from distutils.version import StrictVersion
from requests.exceptions import RequestException, SSLError
from urllib import quote as _quote
from urlparse import urlparse, urlunparse
from six.moves.urllib.parse import quote as _quote
from six.moves.urllib.parse import urlparse, urlunparse
from time import sleep, time
import six
from swiftclient.exceptions import ClientException, InvalidHeadersException
from swiftclient import version as swiftclient_version
from swiftclient.exceptions import ClientException
from swiftclient.utils import LengthWrapper
try:
@ -85,8 +88,8 @@ def http_log(args, kwargs, resp, body):
else:
log_method = logger.info
log_method("REQ: %s" % "".join(string_parts))
log_method("RESP STATUS: %s %s" % (resp.status, resp.reason))
log_method("REQ: %s", "".join(string_parts))
log_method("RESP STATUS: %s %s", resp.status, resp.reason)
log_method("RESP HEADERS: %s", resp.getheaders())
if body:
log_method("RESP BODY: %s", body)
@ -94,30 +97,20 @@ def http_log(args, kwargs, resp, body):
def quote(value, safe='/'):
"""
Patched version of urllib.quote that encodes utf8 strings before quoting
Patched version of urllib.quote that encodes utf8 strings before quoting.
On Python 3, call directly urllib.parse.quote().
"""
if six.PY3:
return _quote(value, safe=safe)
value = encode_utf8(value)
if isinstance(value, str):
if isinstance(value, bytes):
return _quote(value, safe)
else:
return value
def validate_headers(headers):
if headers:
for key, raw_value in headers.iteritems():
value = str(encode_utf8(raw_value))
if '\n' in value:
raise InvalidHeadersException("%r header contained a "
"newline" % key)
if '\r' in value:
raise InvalidHeadersException("%r header contained a "
"carriage return" % key)
def encode_utf8(value):
if isinstance(value, unicode):
if isinstance(value, six.text_type):
value = value.encode('utf8')
return value
@ -133,7 +126,7 @@ except ImportError:
class HTTPConnection:
def __init__(self, url, proxy=None, cacert=None, insecure=False,
ssl_compression=False):
ssl_compression=False, default_user_agent=None):
"""
Make an HTTPConnection or HTTPSConnection
@ -147,6 +140,12 @@ class HTTPConnection:
:param ssl_compression: SSL compression should be disabled by default
and this setting is not usable as of now. The
parameter is kept for backward compatibility.
:param default_user_agent: Set the User-Agent header on every request.
If set to None (default), the user agent
will be "python-swiftclient-<version>". This
may be overridden on a per-request basis by
explicitly setting the user-agent header on
a call to request().
:raises ClientException: Unable to handle protocol scheme
"""
self.url = url
@ -154,6 +153,7 @@ class HTTPConnection:
self.host = self.parsed_url.netloc
self.port = self.parsed_url.port
self.requests_args = {}
self.request_session = requests.Session()
if self.parsed_url.scheme not in ('http', 'https'):
raise ClientException("Unsupported scheme")
self.requests_args['verify'] = not insecure
@ -171,24 +171,49 @@ class HTTPConnection:
)
}
self.requests_args['stream'] = True
if default_user_agent is None:
default_user_agent = \
'python-swiftclient-%s' % swiftclient_version.version_string
self.default_user_agent = default_user_agent
def _request(self, *arg, **kwarg):
""" Final wrapper before requests call, to be patched in tests """
return requests.request(*arg, **kwarg)
return self.request_session.request(*arg, **kwarg)
def request(self, method, full_path, data=None, headers={}, files=None):
def _encode_meta_headers(self, items):
"""Only encode metadata headers keys"""
ret = {}
for header, value in items:
value = encode_utf8(value)
header = header.lower()
if isinstance(header, six.string_types):
for target_type in 'container', 'account', 'object':
prefix = 'x-%s-meta-' % target_type
if header.startswith(prefix):
header = encode_utf8(header)
break
ret[header] = value
return ret
def request(self, method, full_path, data=None, headers=None, files=None):
""" Encode url and header, then call requests.request """
headers = dict((encode_utf8(x), encode_utf8(y)) for x, y in
headers.iteritems())
url = encode_utf8("%s://%s%s" % (
if headers is None:
headers = {}
else:
headers = self._encode_meta_headers(headers.items())
# set a default User-Agent header if it wasn't passed in
if 'user-agent' not in headers:
headers['user-agent'] = self.default_user_agent
url = "%s://%s%s" % (
self.parsed_url.scheme,
self.parsed_url.netloc,
full_path))
full_path)
self.resp = self._request(method, url, headers=headers, data=data,
files=files, **self.requests_args)
return self.resp
def putrequest(self, full_path, data=None, headers={}, files=None):
def putrequest(self, full_path, data=None, headers=None, files=None):
"""
Use python-requests files upload
@ -210,7 +235,8 @@ class HTTPConnection:
self.resp.getheaders = getheaders
self.resp.getheader = getheader
self.resp.read = self.resp.raw.read
self.resp.read = functools.partial(self.resp.raw.read,
decode_content=True)
return self.resp
@ -236,9 +262,8 @@ def get_auth_1_0(url, user, key, snet, **kwargs):
# if we don't have a x-storage-url header and if we get a body.
if resp.status < 200 or resp.status >= 300 or (body and not url):
raise ClientException('Auth GET failed', http_scheme=parsed.scheme,
http_host=conn.host, http_port=conn.port,
http_path=parsed.path, http_status=resp.status,
http_reason=resp.reason)
http_host=conn.host, http_path=parsed.path,
http_status=resp.status, http_reason=resp.reason)
if snet:
parsed = list(urlparse(url))
# Second item in the list is the netloc
@ -251,7 +276,7 @@ def get_auth_1_0(url, user, key, snet, **kwargs):
def get_keystoneclient_2_0(auth_url, user, key, os_options, **kwargs):
"""
Authenticate against a auth 2.0 server.
Authenticate against an auth 2.0 server.
We are using the keystoneclient library for our 2.0 authentication.
"""
@ -336,7 +361,7 @@ def get_auth(auth_url, user, key, **kwargs):
if kwargs.get('tenant_name'):
os_options['tenant_name'] = kwargs['tenant_name']
if (not 'tenant_name' in os_options):
if not (os_options.get('tenant_name') or os_options.get('tenant_id')):
raise ClientException('No tenant specified')
cacert = kwargs.get('cacert', None)
@ -430,9 +455,9 @@ def get_account(url, token, marker=None, limit=None, prefix=None,
resp_headers[header.lower()] = value
if resp.status < 200 or resp.status >= 300:
raise ClientException('Account GET failed', http_scheme=parsed.scheme,
http_host=conn.host, http_port=conn.port,
http_path=parsed.path, http_query=qs,
http_status=resp.status, http_reason=resp.reason,
http_host=conn.host, http_path=parsed.path,
http_query=qs, http_status=resp.status,
http_reason=resp.reason,
http_response_content=body)
if resp.status == 204:
return resp_headers, []
@ -463,9 +488,8 @@ def head_account(url, token, http_conn=None):
http_log((url, method,), {'headers': headers}, resp, body)
if resp.status < 200 or resp.status >= 300:
raise ClientException('Account HEAD failed', http_scheme=parsed.scheme,
http_host=conn.host, http_port=conn.port,
http_path=parsed.path, http_status=resp.status,
http_reason=resp.reason,
http_host=conn.host, http_path=parsed.path,
http_status=resp.status, http_reason=resp.reason,
http_response_content=body)
resp_headers = {}
for header, value in resp.getheaders():
@ -503,7 +527,6 @@ def post_account(url, token, headers, http_conn=None, response_dict=None):
raise ClientException('Account POST failed',
http_scheme=parsed.scheme,
http_host=conn.host,
http_port=conn.port,
http_path=parsed.path,
http_status=resp.status,
http_reason=resp.reason,
@ -580,9 +603,8 @@ def get_container(url, token, container, marker=None, limit=None,
if resp.status < 200 or resp.status >= 300:
raise ClientException('Container GET failed',
http_scheme=parsed.scheme, http_host=conn.host,
http_port=conn.port, http_path=cont_path,
http_query=qs, http_status=resp.status,
http_reason=resp.reason,
http_path=cont_path, http_query=qs,
http_status=resp.status, http_reason=resp.reason,
http_response_content=body)
resp_headers = {}
for header, value in resp.getheaders():
@ -623,8 +645,8 @@ def head_container(url, token, container, http_conn=None, headers=None):
if resp.status < 200 or resp.status >= 300:
raise ClientException('Container HEAD failed',
http_scheme=parsed.scheme, http_host=conn.host,
http_port=conn.port, http_path=path,
http_status=resp.status, http_reason=resp.reason,
http_path=path, http_status=resp.status,
http_reason=resp.reason,
http_response_content=body)
resp_headers = {}
for header, value in resp.getheaders():
@ -656,7 +678,7 @@ def put_container(url, token, container, headers=None, http_conn=None,
if not headers:
headers = {}
headers['X-Auth-Token'] = token
if not 'content-length' in (k.lower() for k in headers):
if 'content-length' not in (k.lower() for k in headers):
headers['Content-Length'] = '0'
conn.request(method, path, '', headers)
resp = conn.getresponse()
@ -669,8 +691,8 @@ def put_container(url, token, container, headers=None, http_conn=None,
if resp.status < 200 or resp.status >= 300:
raise ClientException('Container PUT failed',
http_scheme=parsed.scheme, http_host=conn.host,
http_port=conn.port, http_path=path,
http_status=resp.status, http_reason=resp.reason,
http_path=path, http_status=resp.status,
http_reason=resp.reason,
http_response_content=body)
@ -696,7 +718,7 @@ def post_container(url, token, container, headers, http_conn=None,
path = '%s/%s' % (parsed.path, quote(container))
method = 'POST'
headers['X-Auth-Token'] = token
if not 'content-length' in (k.lower() for k in headers):
if 'content-length' not in (k.lower() for k in headers):
headers['Content-Length'] = '0'
conn.request(method, path, '', headers)
resp = conn.getresponse()
@ -709,8 +731,8 @@ def post_container(url, token, container, headers, http_conn=None,
if resp.status < 200 or resp.status >= 300:
raise ClientException('Container POST failed',
http_scheme=parsed.scheme, http_host=conn.host,
http_port=conn.port, http_path=path,
http_status=resp.status, http_reason=resp.reason,
http_path=path, http_status=resp.status,
http_reason=resp.reason,
http_response_content=body)
@ -746,8 +768,8 @@ def delete_container(url, token, container, http_conn=None,
if resp.status < 200 or resp.status >= 300:
raise ClientException('Container DELETE failed',
http_scheme=parsed.scheme, http_host=conn.host,
http_port=conn.port, http_path=path,
http_status=resp.status, http_reason=resp.reason,
http_path=path, http_status=resp.status,
http_reason=resp.reason,
http_response_content=body)
@ -799,8 +821,8 @@ def get_object(url, token, container, name, http_conn=None,
http_log(('%s%s' % (url.replace(parsed.path, ''), path), method,),
{'headers': headers}, resp, body)
raise ClientException('Object GET failed', http_scheme=parsed.scheme,
http_host=conn.host, http_port=conn.port,
http_path=path, http_status=resp.status,
http_host=conn.host, http_path=path,
http_status=resp.status,
http_reason=resp.reason,
http_response_content=body)
if resp_chunk_size:
@ -847,9 +869,8 @@ def head_object(url, token, container, name, http_conn=None):
{'headers': headers}, resp, body)
if resp.status < 200 or resp.status >= 300:
raise ClientException('Object HEAD failed', http_scheme=parsed.scheme,
http_host=conn.host, http_port=conn.port,
http_path=path, http_status=resp.status,
http_reason=resp.reason,
http_host=conn.host, http_path=path,
http_status=resp.status, http_reason=resp.reason,
http_response_content=body)
resp_headers = {}
for header, value in resp.getheaders():
@ -878,11 +899,10 @@ def put_object(url, token=None, container=None, name=None, contents=None,
encoding will be used
:param etag: etag of contents; if None, no etag will be sent
:param chunk_size: chunk size of data to write; it defaults to 65536;
used only if the the contents object has a 'read'
method, eg. file-like objects, ignored otherwise
:param content_type: value to send as content-type header; if None, no
content-type will be set (remote end will likely try
to auto-detect it)
used only if the contents object has a 'read'
method, e.g. file-like objects, ignored otherwise
:param content_type: value to send as content-type header; if None, an
empty string value will be sent
:param headers: additional headers to include in the request, if any
:param http_conn: HTTP connection object (If None, it will create the
conn object)
@ -916,11 +936,13 @@ def put_object(url, token=None, container=None, name=None, contents=None,
if content_length is not None:
headers['Content-Length'] = str(content_length)
else:
for n, v in headers.iteritems():
for n, v in headers.items():
if n.lower() == 'content-length':
content_length = int(v)
if content_type is not None:
headers['Content-Type'] = content_type
else: # python-requests sets application/x-www-form-urlencoded otherwise
headers['Content-Type'] = ''
if not contents:
headers['Content-Length'] = '0'
if hasattr(contents, 'read'):
@ -954,9 +976,8 @@ def put_object(url, token=None, container=None, name=None, contents=None,
if resp.status < 200 or resp.status >= 300:
raise ClientException('Object PUT failed', http_scheme=parsed.scheme,
http_host=conn.host, http_port=conn.port,
http_path=path, http_status=resp.status,
http_reason=resp.reason,
http_host=conn.host, http_path=path,
http_status=resp.status, http_reason=resp.reason,
http_response_content=body)
return resp.getheader('etag', '').strip('"')
@ -994,9 +1015,8 @@ def post_object(url, token, container, name, headers, http_conn=None,
if resp.status < 200 or resp.status >= 300:
raise ClientException('Object POST failed', http_scheme=parsed.scheme,
http_host=conn.host, http_port=conn.port,
http_path=path, http_status=resp.status,
http_reason=resp.reason,
http_host=conn.host, http_path=path,
http_status=resp.status, http_reason=resp.reason,
http_response_content=body)
@ -1050,8 +1070,8 @@ def delete_object(url, token=None, container=None, name=None, http_conn=None,
if resp.status < 200 or resp.status >= 300:
raise ClientException('Object DELETE failed',
http_scheme=parsed.scheme, http_host=conn.host,
http_port=conn.port, http_path=path,
http_status=resp.status, http_reason=resp.reason,
http_path=path, http_status=resp.status,
http_reason=resp.reason,
http_response_content=body)
@ -1071,9 +1091,8 @@ def get_capabilities(http_conn):
if resp.status < 200 or resp.status >= 300:
raise ClientException('Capabilities GET failed',
http_scheme=parsed.scheme,
http_host=conn.host, http_port=conn.port,
http_path=parsed.path, http_status=resp.status,
http_reason=resp.reason,
http_host=conn.host, http_path=parsed.path,
http_status=resp.status, http_reason=resp.reason,
http_response_content=body)
return json_loads(body)
@ -1101,7 +1120,7 @@ class Connection(object):
:param max_backoff: maximum delay between retries (seconds)
:param auth_version: OpenStack auth version, default is 1.0
:param tenant_name: The tenant/account name, required when connecting
to a auth 2.0 system.
to an auth 2.0 system.
:param os_options: The OpenStack options which can have tenant_id,
auth_token, service_type, endpoint_type,
tenant_name, object_storage_url, region_name

View File

@ -68,5 +68,5 @@ class ClientException(Exception):
return b and '%s: %s' % (a, b) or a
class InvalidHeadersException(Exception):
class SkipTest(Exception):
pass

View File

@ -12,10 +12,14 @@
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
from itertools import chain
import six
import sys
from time import sleep
from Queue import Queue
from six.moves.queue import Queue
from threading import Thread
from traceback import format_exception
@ -181,7 +185,7 @@ class MultiThreadingManager(object):
(defaults to ``sys.stdout``) and the :meth:`error` method will print to the
supplied ``error_stream`` (defaults to ``sys.stderr``). Both of these
printing methods will format the given string with any supplied ``*args``
(a la printf) and encode the result to utf8 if necessary.
(a la printf). On Python 2, Unicode messages are encoded to utf8.
The attribute :attr:`self.error_count` is incremented once per error
message printed, so an application can tell if any worker threads
@ -193,9 +197,11 @@ class MultiThreadingManager(object):
def __init__(self, print_stream=sys.stdout, error_stream=sys.stderr):
"""
:param print_stream: The stream to which :meth:`print_msg` sends
formatted messages, encoded to utf8 if necessary.
formatted messages
:param error_stream: The stream to which :meth:`error` sends formatted
messages, encoded to utf8 if necessary.
messages
On Python 2, Unicode messages are encoded to utf8.
"""
self.print_stream = print_stream
self.printer = QueueFunctionManager(self._print, 1, self)
@ -256,9 +262,9 @@ class MultiThreadingManager(object):
def _print(self, item, stream=None):
if stream is None:
stream = self.print_stream
if isinstance(item, unicode):
if six.PY2 and isinstance(item, unicode):
item = item.encode('utf8')
print >>stream, item
print(item, file=stream)
def _print_error(self, item):
self.error_count += 1

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Miscellaneous utility functions for use with Swift."""
import hashlib
import hmac
import logging
import time
import six
TRUE_VALUES = set(('true', '1', 'yes', 'on', 't', 'y'))
@ -24,7 +30,7 @@ def config_true_value(value):
This function come from swift.common.utils.config_true_value()
"""
return value is True or \
(isinstance(value, basestring) and value.lower() in TRUE_VALUES)
(isinstance(value, six.string_types) and value.lower() in TRUE_VALUES)
def prt_bytes(bytes, human_flag):
@ -57,6 +63,51 @@ def prt_bytes(bytes, human_flag):
return(bytes)
def generate_temp_url(path, seconds, key, method):
""" Generates a temporary URL that gives unauthenticated access to the
Swift object.
:param path: The full path to the Swift object. Example:
/v1/AUTH_account/c/o.
:param seconds: The amount of time in seconds the temporary URL will
be valid for.
:param key: The secret temporary URL key set on the Swift cluster.
To set a key, run 'swift post -m
"Temp-URL-Key:b3968d0207b54ece87cccc06515a89d4"'
:param method: A HTTP method, typically either GET or PUT, to allow for
this temporary URL.
:raises: ValueError if seconds is not a positive integer
:raises: TypeError if seconds is not an integer
:return: the path portion of a temporary URL
"""
if seconds < 0:
raise ValueError('seconds must be a positive integer')
try:
expiration = int(time.time() + seconds)
except TypeError:
raise TypeError('seconds must be an integer')
standard_methods = ['GET', 'PUT', 'HEAD', 'POST', 'DELETE']
if method.upper() not in standard_methods:
logger = logging.getLogger("swiftclient")
logger.warning('Non default HTTP method %s for tempurl specified, '
'possibly an error', method.upper())
hmac_body = '\n'.join([method.upper(), str(expiration), path])
# Encode to UTF-8 for py3 compatibility
sig = hmac.new(key.encode(),
hmac_body.encode(),
hashlib.sha1).hexdigest()
return ('{path}?temp_url_sig='
'{sig}&temp_url_expires={exp}'.format(
path=path,
sig=sig,
exp=expiration)
)
class LengthWrapper(object):
def __init__(self, readable, length):