Merge remote-tracking branch 'gerrit/master' into feature/deep

Change-Id: I812a5eb3ba7e9dc8fb8814f2785d33c2ecd090cb
This commit is contained in:
Tim Burke 2018-04-12 15:29:33 -07:00
commit 7afa04edac
17 changed files with 450 additions and 65 deletions

View File

@ -16,6 +16,7 @@ import sys
from optparse import OptionParser
from swift.cli.info import print_info, InfoSystemExit
from swift.common.exceptions import LockTimeout
def run_print_info(args, opts):
@ -23,7 +24,7 @@ def run_print_info(args, opts):
print_info('account', *args, **opts)
except InfoSystemExit:
sys.exit(1)
except sqlite3.OperationalError as e:
except (sqlite3.OperationalError, LockTimeout) as e:
if not opts.get('stale_reads_ok'):
opts['stale_reads_ok'] = True
print('Warning: Possibly Stale Data')

View File

@ -16,6 +16,7 @@ import sys
from optparse import OptionParser
from swift.cli.info import print_info, InfoSystemExit
from swift.common.exceptions import LockTimeout
def run_print_info(args, opts):
@ -23,7 +24,7 @@ def run_print_info(args, opts):
print_info('container', *args, **opts)
except InfoSystemExit:
sys.exit(1)
except sqlite3.OperationalError as e:
except (sqlite3.OperationalError, LockTimeout) as e:
if not opts.get('stale_reads_ok'):
opts['stale_reads_ok'] = True
print('Warning: Possibly Stale Data')

View File

@ -29,10 +29,12 @@ if __name__ == '__main__':
parser.add_argument('--devices', default='/srv/node',
dest='devices', help='Path to swift device directory')
parser.add_argument('--skip-mount-check', default=False,
help='Don\'t test if disk is mounted',
action="store_true", dest='skip_mount_check')
parser.add_argument('--logfile', default=None,
dest='logfile')
parser.add_argument('--debug', default=False, action='store_true')
dest='logfile', help='Set log file name')
parser.add_argument('--debug', default=False, action='store_true',
help='Enable debug mode')
args = parser.parse_args()

View File

@ -0,0 +1,75 @@
.\"
.\" Copyright (c) 2017 OpenStack Foundation.
.\"
.\" 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.
.\"
.TH SWIFT-OBJECT-RELINKER "1" "December 2017" "OpenStack Swift"
.SH NAME
\fBswift\-object\-relinker\fR \- relink and cleanup objects to increase partition power
.SH SYNOPSIS
.B swift\-object\-relinker
[\fIoptions\fR] <\fIcommand\fR>
.SH DESCRIPTION
.PP
The relinker prepares an object servers filesystem for a partition power
change by crawling the filesystem and linking existing objects to future
partition directories.
More information can be found at
.BI https://docs.openstack.org/swift/latest/ring_partpower.html
.SH COMMANDS
.TP
\fBrelink\fR
Relink files for partition power increase.
.TP
\fBcleanup\fR
Remove hard links in the old locations.
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
Show this help message and exit
.TP
\fB\-\-swift-dir\fR \fISWIFT_DIR\fR
Path to swift directory
.TP
\fB\-\-devices\fR \fIDEVICES\fR
Path to swift device directory
.TP
\fB\-\-skip\-mount\-check\fR
Don't test if disk is mounted
.TP
\fB\-\-logfile\fR \fILOGFILE\fR
Set log file name
.TP
\fB\-\-debug\fR
Enable debug mode
.SH DOCUMENTATION
.LP
More in depth documentation in regards to
.BI swift\-object\-relinker
and also about OpenStack Swift as a whole can be found at
.BI http://docs.openstack.org/developer/swift/index.html
and
.BI http://docs.openstack.org

8
doc/requirements.txt Normal file
View File

@ -0,0 +1,8 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
# this is required for the docs build jobs
sphinx>=1.6.2 # BSD
openstackdocstheme>=1.11.0 # Apache-2.0
reno>=1.8.0 # Apache-2.0
os-api-ref>=1.0.0 # Apache-2.0

View File

@ -249,6 +249,98 @@ However, the request from the user **must** contain the appropriate
`Referer` header, the referrer ACL has very weak security.
Example: Sharing a Container with Another User
----------------------------------------------
Sharing a Container with another user requires the knowledge of few
parameters regarding the users.
The sharing user must know:
- the ``OpenStack user id`` of the other user
The sharing user must communicate to the other user:
- the name of the shared container
- the ``OS_STORAGE_URL``
Usually the ``OS_STORAGE_URL`` is not exposed directly to the user
because the ``swift client`` by default automatically construct the
``OS_STORAGE_URL`` based on the User credential.
We assume that in the current directory there are the two client
environment script for the two users ``sharing.openrc`` and
``other.openrc``.
The ``sharing.openrc`` should be similar to the following:
.. code-block:: bash
export OS_USERNAME=sharing
# WARNING: Save the password in clear text only for testing purposes
export OS_PASSWORD=password
export OS_TENANT_NAME=projectName
export OS_AUTH_URL=https://identityHost:portNumber/v2.0
# The following lines can be omitted
export OS_TENANT_ID=tenantIDString
export OS_REGION_NAME=regionName
export OS_CACERT=/path/to/cacertFile
The ``other.openrc`` should be similar to the following:
.. code-block:: bash
export OS_USERNAME=other
# WARNING: Save the password in clear text only for testing purposes
export OS_PASSWORD=otherPassword
export OS_TENANT_NAME=otherProjectName
export OS_AUTH_URL=https://identityHost:portNumber/v2.0
# The following lines can be omitted
export OS_TENANT_ID=tenantIDString
export OS_REGION_NAME=regionName
export OS_CACERT=/path/to/cacertFile
For more information see `using the OpenStack RC file
<https://docs.openstack.org/user-guide/common/cli-set-environment-variables-using-openstack-rc.html>`_
First we figure out the other user id::
. other.openrc
OUID="$(openstack user show --format json "${OS_USERNAME}" | jq -r .id)"
or alternatively::
. other.openrc
OUID="$(openstack token issue -f json | jq -r .user_id)"
Then we figure out the storage url of the sharing user::
sharing.openrc
SURL="$(swift auth | awk -F = '/OS_STORAGE_URL/ {print $2}')"
Running as the sharing user create a shared container named ``shared``
in read-only mode with the other user using the proper acl::
sharing.openrc
swift post --read-acl "*:${OUID}" shared
Running as the sharing user create and upload a test file::
touch void
swift upload shared void
Running as the other user list the files in the ``shared`` container::
other.openrc
swift --os-storage-url="${SURL}" list shared
Running as the other user download the ``shared`` container in the
``/tmp`` directory::
cd /tmp
swift --os-storage-url="${SURL}" download shared
.. _account_acls:
------------

View File

@ -115,11 +115,6 @@ paste.filter_factory =
listing_formats = swift.common.middleware.listing_formats:filter_factory
symlink = swift.common.middleware.symlink:filter_factory
[build_sphinx]
all_files = 1
build-dir = doc/build
source-dir = doc/source
warning-is-error = 1
[egg_info]
tag_build =

View File

@ -332,7 +332,7 @@ from swift.common.utils import get_logger, config_true_value, \
register_swift_info, RateLimitedIterator, quote, close_if_possible, \
closing_if_possible, LRUCache, StreamingPile, strict_b64decode
from swift.common.request_helpers import SegmentedIterable, \
get_sys_meta_prefix, update_etag_is_at_header
get_sys_meta_prefix, update_etag_is_at_header, resolve_etag_is_at_header
from swift.common.constraints import check_utf8, MAX_BUFFERED_SLO_SEGMENTS
from swift.common.http import HTTP_NOT_FOUND, HTTP_UNAUTHORIZED, is_success
from swift.common.wsgi import WSGIContext, make_subrequest
@ -792,16 +792,19 @@ class SloGetContext(WSGIContext):
if slo_etag and slo_size and (
req.method == 'HEAD' or is_conditional):
# Since we have length and etag, we can respond immediately
for i, (header, _value) in enumerate(self._response_headers):
lheader = header.lower()
if lheader == 'etag':
self._response_headers[i] = (header, '"%s"' % slo_etag)
elif lheader == 'content-length' and not is_conditional:
self._response_headers[i] = (header, slo_size)
start_response(self._response_status,
self._response_headers,
self._response_exc_info)
return resp_iter
resp = Response(
status=self._response_status,
headers=self._response_headers,
app_iter=resp_iter,
request=req,
conditional_etag=resolve_etag_is_at_header(
req, self._response_headers),
conditional_response=True)
resp.headers.update({
'Etag': '"%s"' % slo_etag,
'Content-Length': slo_size,
})
return resp(req.environ, start_response)
if self._need_to_refetch_manifest(req):
req.environ['swift.non_client_disconnect'] = True
@ -874,14 +877,15 @@ class SloGetContext(WSGIContext):
response_headers = []
for header, value in resp_headers:
lheader = header.lower()
if lheader not in ('etag', 'content-length'):
response_headers.append((header, value))
if lheader == SYSMETA_SLO_ETAG:
slo_etag = value
elif lheader == SYSMETA_SLO_SIZE:
# it's from sysmeta, so we don't worry about non-integer
# values here
content_length = int(value)
elif lheader not in ('etag', 'content-length'):
response_headers.append((header, value))
# Prep to calculate content_length & etag if necessary
if slo_etag is None:
@ -926,7 +930,9 @@ class SloGetContext(WSGIContext):
req, content_length, response_headers, segments)
def _manifest_head_response(self, req, response_headers):
conditional_etag = resolve_etag_is_at_header(req, response_headers)
return HTTPOk(request=req, headers=response_headers, body='',
conditional_etag=conditional_etag,
conditional_response=True)
def _manifest_get_response(self, req, content_length, response_headers,
@ -984,9 +990,11 @@ class SloGetContext(WSGIContext):
# the proxy logs and the user will receive incomplete results.
return HTTPConflict(request=req)
conditional_etag = resolve_etag_is_at_header(req, response_headers)
response = Response(request=req, content_length=content_length,
headers=response_headers,
conditional_response=True,
conditional_etag=conditional_etag,
app_iter=segmented_iter)
return response

View File

@ -432,6 +432,8 @@ class TempAuth(object):
expires, groups = cached_auth_data
if expires < time():
groups = None
else:
groups = groups.encode('utf8')
s3_auth_details = env.get('swift3.auth_details')
if s3_auth_details:
@ -788,7 +790,8 @@ class TempAuth(object):
cached_auth_data = memcache_client.get(memcache_token_key)
if cached_auth_data:
expires, old_groups = cached_auth_data
old_groups = old_groups.split(',')
old_groups = [group.encode('utf8')
for group in old_groups.split(',')]
new_groups = self._get_user_groups(account, account_user,
account_id)

View File

@ -622,7 +622,10 @@ class Match(object):
"""
def __init__(self, headerval):
self.tags = set()
for tag in headerval.split(', '):
for tag in headerval.split(','):
tag = tag.strip()
if not tag:
continue
if tag.startswith('"') and tag.endswith('"'):
self.tags.add(tag[1:-1])
else:
@ -631,6 +634,10 @@ class Match(object):
def __contains__(self, val):
return '*' in self.tags or val in self.tags
def __repr__(self):
return '%s(%r)' % (
self.__class__.__name__, ', '.join(sorted(self.tags)))
class Accept(object):
"""

View File

@ -4902,37 +4902,21 @@ def modify_priority(conf, logger):
def o_tmpfile_in_path_supported(dirpath):
if not hasattr(os, 'O_TMPFILE'):
return False
testfile = os.path.join(dirpath, ".o_tmpfile.test")
hasO_TMPFILE = True
fd = None
try:
fd = os.open(testfile, os.O_CREAT | os.O_WRONLY | os.O_TMPFILE)
fd = os.open(dirpath, os.O_WRONLY | O_TMPFILE)
return True
except OSError as e:
if e.errno == errno.EINVAL:
hasO_TMPFILE = False
if e.errno in (errno.EINVAL, errno.EISDIR, errno.EOPNOTSUPP):
return False
else:
raise Exception("Error on '%(path)s' while checking "
"O_TMPFILE: '%(ex)s'",
{'path': dirpath, 'ex': e})
except Exception as e:
raise Exception("Error on '%(path)s' while checking O_TMPFILE: "
"'%(ex)s'", {'path': dirpath, 'ex': e})
finally:
if fd is not None:
os.close(fd)
# ensure closing the fd will actually remove the file
if os.path.isfile(testfile):
return False
return hasO_TMPFILE
def o_tmpfile_in_tmpdir_supported():
return o_tmpfile_in_path_supported(gettempdir())

View File

@ -8,14 +8,10 @@ coverage>=3.6 # Apache-2.0
nose # LGPL
nosexcover # BSD
nosehtmloutput>=0.0.3 # Apache-2.0
sphinx>=1.6.2 # BSD
openstackdocstheme>=1.11.0 # Apache-2.0
os-api-ref>=1.0.0 # Apache-2.0
os-testr>=0.8.0 # Apache-2.0
mock>=2.0 # BSD
python-swiftclient
python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0
reno>=1.8.0 # Apache-2.0
# Security checks
bandit>=1.1.0 # Apache-2.0

View File

@ -20,7 +20,7 @@ from hashlib import md5
from swift.common import swob
from swift.common.header_key_dict import HeaderKeyDict
from swift.common.request_helpers import is_user_meta, \
is_object_transient_sysmeta
is_object_transient_sysmeta, resolve_etag_is_at_header
from swift.common.swob import HTTPNotImplemented
from swift.common.utils import split_path
@ -154,11 +154,8 @@ class FakeSwift(object):
self._calls.append(
FakeSwiftCall(method, path, HeaderKeyDict(req.headers)))
backend_etag_header = req.headers.get('X-Backend-Etag-Is-At')
conditional_etag = None
if backend_etag_header and backend_etag_header in headers:
# Apply conditional etag overrides
conditional_etag = headers[backend_etag_header]
# Apply conditional etag overrides
conditional_etag = resolve_etag_is_at_header(req, headers)
# range requests ought to work, hence conditional_response=True
if isinstance(body, list):

View File

@ -1392,6 +1392,7 @@ class TestSloHeadOldManifest(SloTestCase):
'Content-Length': str(len(manifest_json)),
'Content-Type': 'test/data',
'X-Static-Large-Object': 'true',
'X-Object-Sysmeta-Artisanal-Etag': 'bespoke',
'Etag': md5hex(manifest_json)}
manifest_headers.update(getattr(self, 'extra_manifest_headers', {}))
self.manifest_has_sysmeta = all(h in manifest_headers for h in (
@ -1449,6 +1450,46 @@ class TestSloHeadOldManifest(SloTestCase):
expected_app_calls.append(('GET', '/v1/AUTH_test/headtest/man'))
self.assertEqual(self.app.calls, expected_app_calls)
def test_if_none_match_etag_matching_with_override(self):
req = Request.blank(
'/v1/AUTH_test/headtest/man',
environ={'REQUEST_METHOD': 'HEAD'},
headers={
'If-None-Match': 'bespoke',
'X-Backend-Etag-Is-At': 'X-Object-Sysmeta-Artisanal-Etag'})
status, headers, body = self.call_slo(req)
self.assertEqual(status, '304 Not Modified')
# We *are not* responsible for replacing the etag; whoever set
# x-backend-etag-is-at is responsible
self.assertIn(('Etag', '"%s"' % self.slo_etag), headers)
self.assertIn(('Content-Length', '0'), headers)
self.assertIn(('Content-Type', 'test/data'), headers)
expected_app_calls = [('HEAD', '/v1/AUTH_test/headtest/man')]
if not self.manifest_has_sysmeta:
expected_app_calls.append(('GET', '/v1/AUTH_test/headtest/man'))
self.assertEqual(self.app.calls, expected_app_calls)
def test_if_match_etag_not_matching_with_override(self):
req = Request.blank(
'/v1/AUTH_test/headtest/man',
environ={'REQUEST_METHOD': 'HEAD'},
headers={
'If-Match': self.slo_etag,
'X-Backend-Etag-Is-At': 'X-Object-Sysmeta-Artisanal-Etag'})
status, headers, body = self.call_slo(req)
self.assertEqual(status, '412 Precondition Failed')
# We *are not* responsible for replacing the etag; whoever set
# x-backend-etag-is-at is responsible
self.assertIn(('Etag', '"%s"' % self.slo_etag), headers)
self.assertIn(('Content-Length', '0'), headers)
self.assertIn(('Content-Type', 'test/data'), headers)
expected_app_calls = [('HEAD', '/v1/AUTH_test/headtest/man')]
if not self.manifest_has_sysmeta:
expected_app_calls.append(('GET', '/v1/AUTH_test/headtest/man'))
self.assertEqual(self.app.calls, expected_app_calls)
class TestSloHeadManifest(TestSloHeadOldManifest):
def setUp(self):
@ -3410,7 +3451,8 @@ class TestSloConditionalGetOldManifest(SloTestCase):
'Content-Length': str(len(_abcd_manifest_json)),
'Content-Type': 'application/json',
'X-Static-Large-Object': 'true',
'Etag': md5hex(_abcd_manifest_json)}
'Etag': md5hex(_abcd_manifest_json),
'X-Object-Sysmeta-Custom-Etag': 'a custom etag'}
manifest_headers.update(getattr(self, 'extra_manifest_headers', {}))
self.manifest_has_sysmeta = all(h in manifest_headers for h in (
'X-Object-Sysmeta-Slo-Etag', 'X-Object-Sysmeta-Slo-Size'))
@ -3521,6 +3563,128 @@ class TestSloConditionalGetOldManifest(SloTestCase):
self.assertEqual(self.app.headers[0].get('X-Backend-Etag-Is-At'),
'x-object-sysmeta-slo-etag')
def test_if_none_match_matches_with_override(self):
req = Request.blank(
'/v1/AUTH_test/gettest/manifest-abcd',
environ={'REQUEST_METHOD': 'GET'},
headers={'If-None-Match': '"a custom etag"',
'X-Backend-Etag-Is-At': 'X-Object-Sysmeta-Custom-Etag'})
status, headers, body = self.call_slo(req)
self.assertEqual(status, '304 Not Modified')
self.assertIn(('Content-Length', '0'), headers)
self.assertIn(('Etag', '"%s"' % self.slo_etag), headers)
self.assertIn(('X-Object-Sysmeta-Custom-Etag', 'a custom etag'),
headers)
self.assertEqual(body, '')
expected_app_calls = [('GET', '/v1/AUTH_test/gettest/manifest-abcd')]
if not self.manifest_has_sysmeta:
# NB: no known middleware would have written a custom etag with
# old-style manifests. but if there *was*, here's what'd happen
expected_app_calls.extend([
# 304, so gotta refetch
('GET', '/v1/AUTH_test/gettest/manifest-abcd'),
# Since the "authoritative" etag didn't come from slo, we still
# verify the first segment
('GET', '/v1/AUTH_test/gettest/manifest-bc'),
('GET', '/v1/AUTH_test/gettest/a_5?multipart-manifest=get'),
])
self.assertEqual(self.app.calls, expected_app_calls)
self.assertEqual(
self.app.headers[0].get('X-Backend-Etag-Is-At'),
'X-Object-Sysmeta-Custom-Etag,x-object-sysmeta-slo-etag')
def test_if_none_match_does_not_match_with_override(self):
req = Request.blank(
'/v1/AUTH_test/gettest/manifest-abcd',
environ={'REQUEST_METHOD': 'GET'},
headers={'If-None-Match': "%s" % self.slo_etag,
'X-Backend-Etag-Is-At': 'X-Object-Sysmeta-Custom-Etag'})
status, headers, body = self.call_slo(req)
self.assertEqual(status, '200 OK')
self.assertIn(('Content-Length', '50'), headers)
self.assertIn(('Etag', '"%s"' % self.slo_etag), headers)
self.assertIn(('X-Object-Sysmeta-Custom-Etag', 'a custom etag'),
headers)
self.assertEqual(
body, 'aaaaabbbbbbbbbbcccccccccccccccdddddddddddddddddddd')
expected_app_calls = [
('GET', '/v1/AUTH_test/gettest/manifest-abcd'),
('GET', '/v1/AUTH_test/gettest/manifest-bc'),
('GET', '/v1/AUTH_test/gettest/a_5?multipart-manifest=get'),
('GET', '/v1/AUTH_test/gettest/b_10?multipart-manifest=get'),
('GET', '/v1/AUTH_test/gettest/c_15?multipart-manifest=get'),
('GET', '/v1/AUTH_test/gettest/d_20?multipart-manifest=get'),
]
self.assertEqual(self.app.calls, expected_app_calls)
self.assertEqual(
self.app.headers[0].get('X-Backend-Etag-Is-At'),
'X-Object-Sysmeta-Custom-Etag,x-object-sysmeta-slo-etag')
def test_if_match_matches_with_override(self):
req = Request.blank(
'/v1/AUTH_test/gettest/manifest-abcd',
environ={'REQUEST_METHOD': 'GET'},
headers={'If-Match': '"a custom etag"',
'X-Backend-Etag-Is-At': 'X-Object-Sysmeta-Custom-Etag'})
status, headers, body = self.call_slo(req)
self.assertEqual(status, '200 OK')
self.assertIn(('Content-Length', '50'), headers)
self.assertIn(('Etag', '"%s"' % self.slo_etag), headers)
self.assertIn(('X-Object-Sysmeta-Custom-Etag', 'a custom etag'),
headers)
self.assertEqual(
body, 'aaaaabbbbbbbbbbcccccccccccccccdddddddddddddddddddd')
expected_app_calls = [
('GET', '/v1/AUTH_test/gettest/manifest-abcd'),
# Match on the override from left of us; no need to refetch
('GET', '/v1/AUTH_test/gettest/manifest-bc'),
('GET', '/v1/AUTH_test/gettest/a_5?multipart-manifest=get'),
('GET', '/v1/AUTH_test/gettest/b_10?multipart-manifest=get'),
('GET', '/v1/AUTH_test/gettest/c_15?multipart-manifest=get'),
('GET', '/v1/AUTH_test/gettest/d_20?multipart-manifest=get'),
]
self.assertEqual(self.app.calls, expected_app_calls)
self.assertEqual(
self.app.headers[0].get('X-Backend-Etag-Is-At'),
'X-Object-Sysmeta-Custom-Etag,x-object-sysmeta-slo-etag')
def test_if_match_does_not_match_with_override(self):
req = Request.blank(
'/v1/AUTH_test/gettest/manifest-abcd',
environ={'REQUEST_METHOD': 'GET'},
headers={'If-Match': "%s" % self.slo_etag,
'X-Backend-Etag-Is-At': 'X-Object-Sysmeta-Custom-Etag'})
status, headers, body = self.call_slo(req)
self.assertEqual(status, '412 Precondition Failed')
self.assertIn(('Content-Length', '0'), headers)
self.assertIn(('Etag', '"%s"' % self.slo_etag), headers)
self.assertIn(('X-Object-Sysmeta-Custom-Etag', 'a custom etag'),
headers)
self.assertEqual(body, '')
expected_app_calls = [('GET', '/v1/AUTH_test/gettest/manifest-abcd')]
if not self.manifest_has_sysmeta:
# NB: no known middleware would have written a custom etag with
# old-style manifests. but if there *was*, here's what'd happen
expected_app_calls.extend([
# Manifest never matches -> got back a 412; need to re-fetch
('GET', '/v1/AUTH_test/gettest/manifest-abcd'),
# We *still* verify the first segment, even though we'll 412
('GET', '/v1/AUTH_test/gettest/manifest-bc'),
('GET', '/v1/AUTH_test/gettest/a_5?multipart-manifest=get'),
])
self.assertEqual(self.app.calls, expected_app_calls)
self.assertEqual(
self.app.headers[0].get('X-Backend-Etag-Is-At'),
'X-Object-Sysmeta-Custom-Etag,x-object-sysmeta-slo-etag')
def test_if_match_matches_and_range(self):
req = Request.blank(
'/v1/AUTH_test/gettest/manifest-abcd',

View File

@ -37,6 +37,14 @@ class FakeMemcache(object):
return self.store.get(key)
def set(self, key, value, time=0):
if isinstance(value, (tuple, list)):
decoded = []
for elem in value:
if type(elem) == str:
decoded.append(elem.decode('utf8'))
else:
decoded.append(elem)
value = tuple(decoded)
self.store[key] = value
return True
@ -908,6 +916,37 @@ class TestAuth(unittest.TestCase):
self.assertEqual(resp.headers.get('Www-Authenticate'),
'Swift realm="BLAH_account"')
def test_successful_token_unicode_user(self):
app = FakeApp(iter(NO_CONTENT_RESP))
ath = auth.filter_factory(
{u'user_t\u00e9st_t\u00e9ster'.encode('utf8'):
u'p\u00e1ss .admin'.encode('utf8')})(app)
memcache = FakeMemcache()
req = self._make_request(
'/auth/v1.0',
headers={'X-Auth-User': u't\u00e9st:t\u00e9ster',
'X-Auth-Key': u'p\u00e1ss'})
req.environ['swift.cache'] = memcache
resp = req.get_response(ath)
self.assertEqual(resp.status_int, 200)
auth_token = resp.headers['X-Auth-Token']
req = self._make_request(
'/auth/v1.0',
headers={'X-Auth-User': u't\u00e9st:t\u00e9ster',
'X-Auth-Key': u'p\u00e1ss'})
req.environ['swift.cache'] = memcache
resp = req.get_response(ath)
self.assertEqual(resp.status_int, 200)
self.assertEqual(auth_token, resp.headers['X-Auth-Token'])
req = self._make_request(
u'/v1/AUTH_t\u00e9st', headers={'X-Auth-Token': auth_token})
req.environ['swift.cache'] = memcache
resp = req.get_response(ath)
self.assertEqual(204, resp.status_int)
class TestAuthWithMultiplePrefixes(TestAuth):
"""

View File

@ -271,12 +271,14 @@ class TestMatch(unittest.TestCase):
self.assertIn('a', match)
self.assertIn('b', match)
self.assertNotIn('c', match)
self.assertEqual(repr(match), "Match('a, b')")
def test_match_star(self):
match = swift.common.swob.Match('"a", "*"')
self.assertIn('a', match)
self.assertIn('b', match)
self.assertIn('c', match)
self.assertEqual(repr(match), "Match('*, a')")
def test_match_noquote(self):
match = swift.common.swob.Match('a, b')
@ -285,6 +287,20 @@ class TestMatch(unittest.TestCase):
self.assertIn('b', match)
self.assertNotIn('c', match)
def test_match_no_optional_white_space(self):
match = swift.common.swob.Match('"a","b"')
self.assertEqual(match.tags, set(('a', 'b')))
self.assertIn('a', match)
self.assertIn('b', match)
self.assertNotIn('c', match)
def test_match_lots_of_optional_white_space(self):
match = swift.common.swob.Match('"a" , , "b" ')
self.assertEqual(match.tags, set(('a', 'b')))
self.assertIn('a', match)
self.assertIn('b', match)
self.assertNotIn('c', match)
class TestTransferEncoding(unittest.TestCase):
def test_is_chunked(self):

17
tox.ini
View File

@ -26,9 +26,9 @@ setenv = VIRTUAL_ENV={envdir}
NOSE_COVER_HTML=1
NOSE_COVER_HTML_DIR={toxinidir}/cover
[testenv:py34]
[testenv:py35]
commands =
nosetests \
nosetests {posargs:\
test/unit/cli/test_dispersion_report.py \
test/unit/cli/test_form_signature.py \
test/unit/cli/test_info.py \
@ -45,10 +45,7 @@ commands =
test/unit/common/test_splice.py \
test/unit/common/test_storage_policy.py \
test/unit/common/test_utils.py \
test/unit/common/test_wsgi.py
[testenv:py35]
commands = {[testenv:py34]commands}
test/unit/common/test_wsgi.py}
[testenv:pep8]
basepython = python2.7
@ -92,15 +89,14 @@ commands = {posargs}
[testenv:docs]
basepython = python2.7
commands = python setup.py build_sphinx
deps = -r{toxinidir}/doc/requirements.txt
commands = sphinx-build -W -b html doc/source doc/build/html
[testenv:api-ref]
# This environment is called from CI scripts to test and publish
# the API Ref to developer.openstack.org.
# we do not use -W here because we are doing some slightly tricky
# things to build a single page document, and as such, we are ok
# ignoring the duplicate stanzas warning.
basepython = python2.7
deps = -r{toxinidir}/doc/requirements.txt
commands =
rm -rf api-ref/build
sphinx-build -W -b html -d api-ref/build/doctrees api-ref/source api-ref/build/html
@ -132,4 +128,5 @@ deps = bindep
commands = bindep test
[testenv:releasenotes]
deps = -r{toxinidir}/doc/requirements.txt
commands = sphinx-build -a -W -E -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html