02548717ac
When completing a multipart-upload, include the upload-id in sysmeta. If we can't find the upload marker, check the final object name; if it has an upload-id in sysmeta and it matches the upload-id that we're trying to complete, allow the complete to continue. Also add an early return if the already-completed upload's ETag matches the computed ETag for the user's request. This should help clients that can't take advantage of how we dribble out whitespace to try to keep the conneciton alive: The client times out, retries, and if the upload actually completed, it gets a fast 200 response. Change-Id: I38958839be5b250c9d268ec7c50a56cdb56c2fa2
98 lines
3.5 KiB
Python
98 lines
3.5 KiB
Python
# Copyright (c) 2011-2014 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.
|
|
|
|
import unittest
|
|
import traceback
|
|
from contextlib import contextmanager
|
|
import logging
|
|
import test.functional as tf
|
|
from test.functional.s3api.s3_test_client import (
|
|
Connection, get_boto3_conn, tear_down_s3)
|
|
|
|
|
|
def setUpModule():
|
|
tf.setup_package()
|
|
|
|
|
|
def tearDownModule():
|
|
tf.teardown_package()
|
|
|
|
|
|
class S3ApiBase(unittest.TestCase):
|
|
def __init__(self, method_name):
|
|
super(S3ApiBase, self).__init__(method_name)
|
|
self.method_name = method_name
|
|
|
|
@contextmanager
|
|
def quiet_boto_logging(self):
|
|
try:
|
|
logging.getLogger('boto').setLevel(logging.INFO)
|
|
yield
|
|
finally:
|
|
logging.getLogger('boto').setLevel(logging.DEBUG)
|
|
|
|
def setUp(self):
|
|
if 's3api' not in tf.cluster_info:
|
|
raise tf.SkipTest('s3api middleware is not enabled')
|
|
try:
|
|
self.conn = Connection(
|
|
tf.config['s3_access_key'], tf.config['s3_secret_key'],
|
|
user_id='%s:%s' % (tf.config['account'],
|
|
tf.config['username']))
|
|
|
|
self.conn.reset()
|
|
except Exception:
|
|
message = '%s got an error during initialize process.\n\n%s' % \
|
|
(self.method_name, traceback.format_exc())
|
|
# TODO: Find a way to make this go to FAIL instead of Error
|
|
self.fail(message)
|
|
|
|
def assertCommonResponseHeaders(self, headers, etag=None):
|
|
"""
|
|
asserting common response headers with args
|
|
:param headers: a dict of response headers
|
|
:param etag: a string of md5(content).hexdigest() if not given,
|
|
this won't assert anything about etag. (e.g. DELETE obj)
|
|
"""
|
|
self.assertTrue(headers['x-amz-id-2'] is not None)
|
|
self.assertTrue(headers['x-amz-request-id'] is not None)
|
|
self.assertTrue(headers['date'] is not None)
|
|
# TODO; requires consideration
|
|
# self.assertTrue(headers['server'] is not None)
|
|
if etag is not None:
|
|
self.assertTrue('etag' in headers) # sanity
|
|
self.assertEqual(etag, headers['etag'].strip('"'))
|
|
|
|
|
|
class S3ApiBaseBoto3(S3ApiBase):
|
|
def setUp(self):
|
|
if 's3api' not in tf.cluster_info:
|
|
raise tf.SkipTest('s3api middleware is not enabled')
|
|
try:
|
|
self.conn = get_boto3_conn(
|
|
tf.config['s3_access_key'], tf.config['s3_secret_key'])
|
|
self.endpoint_url = self.conn._endpoint.host
|
|
self.access_key = self.conn._request_signer._credentials.access_key
|
|
self.region = self.conn._client_config.region_name
|
|
tear_down_s3(self.conn)
|
|
except Exception:
|
|
message = '%s got an error during initialize process.\n\n%s' % \
|
|
(self.method_name, traceback.format_exc())
|
|
# TODO: Find a way to make this go to FAIL instead of Error
|
|
self.fail(message)
|
|
|
|
def tearDown(self):
|
|
tear_down_s3(self.conn)
|