Merge "Fix the GET's response code when there is a missing segment in LO"

This commit is contained in:
Jenkins 2015-01-05 16:23:02 +00:00 committed by Gerrit Code Review
commit 1f1cdceabe
5 changed files with 308 additions and 98 deletions

View File

@ -17,10 +17,10 @@ import os
from ConfigParser import ConfigParser, NoSectionError, NoOptionError from ConfigParser import ConfigParser, NoSectionError, NoOptionError
from hashlib import md5 from hashlib import md5
from swift.common import constraints from swift.common import constraints
from swift.common.exceptions import ListingIterError from swift.common.exceptions import ListingIterError, SegmentError
from swift.common.http import is_success from swift.common.http import is_success
from swift.common.swob import Request, Response, \ from swift.common.swob import Request, Response, \
HTTPRequestedRangeNotSatisfiable, HTTPBadRequest HTTPRequestedRangeNotSatisfiable, HTTPBadRequest, HTTPConflict
from swift.common.utils import get_logger, json, \ from swift.common.utils import get_logger, json, \
RateLimitedIterator, read_conf_dir, quote RateLimitedIterator, read_conf_dir, quote
from swift.common.request_helpers import SegmentedIterable from swift.common.request_helpers import SegmentedIterable
@ -131,9 +131,10 @@ class GetContext(WSGIContext):
constraints.CONTAINER_LISTING_LIMIT constraints.CONTAINER_LISTING_LIMIT
first_byte = last_byte = None first_byte = last_byte = None
content_length = None actual_content_length = None
content_length_for_swob_range = None
if req.range and len(req.range.ranges) == 1: if req.range and len(req.range.ranges) == 1:
content_length = sum(o['bytes'] for o in segments) content_length_for_swob_range = sum(o['bytes'] for o in segments)
# This is a hack to handle suffix byte ranges (e.g. "bytes=-5"), # This is a hack to handle suffix byte ranges (e.g. "bytes=-5"),
# which we can't honor unless we have a complete listing. # which we can't honor unless we have a complete listing.
@ -144,28 +145,32 @@ class GetContext(WSGIContext):
# #
# Alternately, we may not have all the segments, but this range # Alternately, we may not have all the segments, but this range
# falls entirely within the first page's segments, so we know # falls entirely within the first page's segments, so we know
# whether or not it's satisfiable. # that it is satisfiable.
if have_complete_listing or range_end < content_length: if (have_complete_listing
byteranges = req.range.ranges_for_length(content_length) or range_end < content_length_for_swob_range):
byteranges = req.range.ranges_for_length(
content_length_for_swob_range)
if not byteranges: if not byteranges:
return HTTPRequestedRangeNotSatisfiable(request=req) return HTTPRequestedRangeNotSatisfiable(request=req)
first_byte, last_byte = byteranges[0] first_byte, last_byte = byteranges[0]
# For some reason, swob.Range.ranges_for_length adds 1 to the # For some reason, swob.Range.ranges_for_length adds 1 to the
# last byte's position. # last byte's position.
last_byte -= 1 last_byte -= 1
actual_content_length = last_byte - first_byte + 1
else: else:
# The range may or may not be satisfiable, but we can't tell # The range may or may not be satisfiable, but we can't tell
# based on just one page of listing, and we're not going to go # based on just one page of listing, and we're not going to go
# get more pages because that would use up too many resources, # get more pages because that would use up too many resources,
# so we ignore the Range header and return the whole object. # so we ignore the Range header and return the whole object.
content_length = None actual_content_length = None
content_length_for_swob_range = None
req.range = None req.range = None
response_headers = [ response_headers = [
(h, v) for h, v in response_headers (h, v) for h, v in response_headers
if h.lower() not in ("content-length", "content-range")] if h.lower() not in ("content-length", "content-range")]
if content_length is not None: if content_length_for_swob_range is not None:
# Here, we have to give swob a big-enough content length so that # Here, we have to give swob a big-enough content length so that
# it can compute the actual content length based on the Range # it can compute the actual content length based on the Range
# header. This value will not be visible to the client; swob will # header. This value will not be visible to the client; swob will
@ -175,10 +180,12 @@ class GetContext(WSGIContext):
# segments, this may be less than the sum of all the segments' # segments, this may be less than the sum of all the segments'
# sizes. However, it'll still be greater than the last byte in the # sizes. However, it'll still be greater than the last byte in the
# Range header, so it's good enough for swob. # Range header, so it's good enough for swob.
response_headers.append(('Content-Length', str(content_length)))
elif have_complete_listing:
response_headers.append(('Content-Length', response_headers.append(('Content-Length',
str(sum(o['bytes'] for o in segments)))) str(content_length_for_swob_range)))
elif have_complete_listing:
actual_content_length = sum(o['bytes'] for o in segments)
response_headers.append(('Content-Length',
str(actual_content_length)))
if have_complete_listing: if have_complete_listing:
response_headers = [(h, v) for h, v in response_headers response_headers = [(h, v) for h, v in response_headers
@ -188,21 +195,30 @@ class GetContext(WSGIContext):
etag.update(seg_dict['hash'].strip('"')) etag.update(seg_dict['hash'].strip('"'))
response_headers.append(('Etag', '"%s"' % etag.hexdigest())) response_headers.append(('Etag', '"%s"' % etag.hexdigest()))
listing_iter = RateLimitedIterator( app_iter = None
self._segment_listing_iterator( if req.method == 'GET':
req, version, account, container, obj_prefix, segments, listing_iter = RateLimitedIterator(
first_byte=first_byte, last_byte=last_byte), self._segment_listing_iterator(
self.dlo.rate_limit_segments_per_sec, req, version, account, container, obj_prefix, segments,
limit_after=self.dlo.rate_limit_after_segment) first_byte=first_byte, last_byte=last_byte),
self.dlo.rate_limit_segments_per_sec,
limit_after=self.dlo.rate_limit_after_segment)
app_iter = SegmentedIterable(
req, self.dlo.app, listing_iter, ua_suffix="DLO MultipartGET",
swift_source="DLO", name=req.path, logger=self.logger,
max_get_time=self.dlo.max_get_time,
response_body_length=actual_content_length)
try:
app_iter.validate_first_segment()
except (SegmentError, ListingIterError):
return HTTPConflict(request=req)
resp = Response(request=req, headers=response_headers, resp = Response(request=req, headers=response_headers,
conditional_response=True, conditional_response=True,
app_iter=SegmentedIterable( app_iter=app_iter)
req, self.dlo.app, listing_iter,
ua_suffix="DLO MultipartGET",
swift_source="DLO",
name=req.path, logger=self.logger,
max_get_time=self.dlo.max_get_time))
resp.app_iter.response = resp
return resp return resp
def handle_request(self, req, start_response): def handle_request(self, req, start_response):

View File

@ -139,11 +139,12 @@ from datetime import datetime
import mimetypes import mimetypes
import re import re
from hashlib import md5 from hashlib import md5
from swift.common.exceptions import ListingIterError from swift.common.exceptions import ListingIterError, SegmentError
from swift.common.swob import Request, HTTPBadRequest, HTTPServerError, \ from swift.common.swob import Request, HTTPBadRequest, HTTPServerError, \
HTTPMethodNotAllowed, HTTPRequestEntityTooLarge, HTTPLengthRequired, \ HTTPMethodNotAllowed, HTTPRequestEntityTooLarge, HTTPLengthRequired, \
HTTPOk, HTTPPreconditionFailed, HTTPException, HTTPNotFound, \ HTTPOk, HTTPPreconditionFailed, HTTPException, HTTPNotFound, \
HTTPUnauthorized, HTTPRequestedRangeNotSatisfiable, Response HTTPUnauthorized, HTTPConflict, HTTPRequestedRangeNotSatisfiable,\
Response
from swift.common.utils import json, get_logger, config_true_value, \ from swift.common.utils import json, get_logger, config_true_value, \
get_valid_utf8_str, override_bytes_from_content_type, split_path, \ get_valid_utf8_str, override_bytes_from_content_type, split_path, \
register_swift_info, RateLimitedIterator, quote register_swift_info, RateLimitedIterator, quote
@ -464,15 +465,27 @@ class SloGetContext(WSGIContext):
start_byte, end_byte) start_byte, end_byte)
for seg_dict, start_byte, end_byte in ratelimited_listing_iter) for seg_dict, start_byte, end_byte in ratelimited_listing_iter)
segmented_iter = SegmentedIterable(
req, self.slo.app, segment_listing_iter,
name=req.path, logger=self.slo.logger,
ua_suffix="SLO MultipartGET",
swift_source="SLO",
max_get_time=self.slo.max_get_time)
try:
segmented_iter.validate_first_segment()
except (ListingIterError, SegmentError):
# Copy from the SLO explanation in top of this file.
# If any of the segments from the manifest are not found or
# their Etag/Content Length no longer match the connection
# will drop. In this case a 409 Conflict will be logged in
# the proxy logs and the user will receive incomplete results.
return HTTPConflict(request=req)
response = Response(request=req, content_length=content_length, response = Response(request=req, content_length=content_length,
headers=response_headers, headers=response_headers,
conditional_response=True, conditional_response=True,
app_iter=SegmentedIterable( app_iter=segmented_iter)
req, self.slo.app, segment_listing_iter,
name=req.path, logger=self.slo.logger,
ua_suffix="SLO MultipartGET",
swift_source="SLO",
max_get_time=self.slo.max_get_time))
if req.range: if req.range:
response.headers.pop('Etag') response.headers.pop('Etag')
return response return response

View File

@ -21,13 +21,14 @@ from swob in here without creating circular imports.
""" """
import hashlib import hashlib
import sys import itertools
import time import time
from contextlib import contextmanager from contextlib import contextmanager
from urllib import unquote from urllib import unquote
from swift import gettext_ as _
from swift.common.constraints import FORMAT2CONTENT_TYPE from swift.common.constraints import FORMAT2CONTENT_TYPE
from swift.common.exceptions import ListingIterError, SegmentError from swift.common.exceptions import ListingIterError, SegmentError
from swift.common.http import is_success, HTTP_SERVICE_UNAVAILABLE from swift.common.http import is_success
from swift.common.swob import HTTPBadRequest, HTTPNotAcceptable from swift.common.swob import HTTPBadRequest, HTTPNotAcceptable
from swift.common.utils import split_path, validate_device_partition from swift.common.utils import split_path, validate_device_partition
from swift.common.wsgi import make_subrequest from swift.common.wsgi import make_subrequest
@ -276,12 +277,13 @@ class SegmentedIterable(object):
(just for logging) (just for logging)
:param ua_suffix: string to append to user-agent. :param ua_suffix: string to append to user-agent.
:param name: name of manifest (used in logging only) :param name: name of manifest (used in logging only)
:param response: optional response object for the response being sent :param response_body_length: optional response body length for
to the client. the response being sent to the client.
""" """
def __init__(self, req, app, listing_iter, max_get_time, def __init__(self, req, app, listing_iter, max_get_time,
logger, ua_suffix, swift_source, logger, ua_suffix, swift_source,
name='<not specified>', response=None): name='<not specified>', response_body_length=None):
self.req = req self.req = req
self.app = app self.app = app
self.listing_iter = listing_iter self.listing_iter = listing_iter
@ -290,26 +292,14 @@ class SegmentedIterable(object):
self.ua_suffix = " " + ua_suffix self.ua_suffix = " " + ua_suffix
self.swift_source = swift_source self.swift_source = swift_source
self.name = name self.name = name
self.response = response self.response_body_length = response_body_length
self.peeked_chunk = None
self.app_iter = self._internal_iter()
self.validated_first_segment = False
def app_iter_range(self, *a, **kw): def _internal_iter(self):
"""
swob.Response will only respond with a 206 status in certain cases; one
of those is if the body iterator responds to .app_iter_range().
However, this object (or really, its listing iter) is smart enough to
handle the range stuff internally, so we just no-op this out for swob.
"""
return self
def __iter__(self):
start_time = time.time() start_time = time.time()
have_yielded_data = False bytes_left = self.response_body_length
if self.response and self.response.content_length:
bytes_left = int(self.response.content_length)
else:
bytes_left = None
try: try:
for seg_path, seg_etag, seg_size, first_byte, last_byte \ for seg_path, seg_etag, seg_size, first_byte, last_byte \
@ -366,7 +356,6 @@ class SegmentedIterable(object):
seg_hash = hashlib.md5() seg_hash = hashlib.md5()
for chunk in seg_resp.app_iter: for chunk in seg_resp.app_iter:
seg_hash.update(chunk) seg_hash.update(chunk)
have_yielded_data = True
if bytes_left is None: if bytes_left is None:
yield chunk yield chunk
elif bytes_left >= len(chunk): elif bytes_left >= len(chunk):
@ -393,29 +382,44 @@ class SegmentedIterable(object):
if bytes_left: if bytes_left:
raise SegmentError( raise SegmentError(
'Not enough bytes for %s; closing connection' % 'Not enough bytes for %s; closing connection' % self.name)
self.name) except (ListingIterError, SegmentError):
self.logger.exception(_('ERROR: An error occurred '
except ListingIterError as err: 'while retrieving segments'))
# I have to save this error because yielding the ' ' below clears
# the exception from the current stack frame.
excinfo = sys.exc_info()
self.logger.exception('ERROR: While processing manifest %s, %s',
self.name, err)
# Normally, exceptions before any data has been yielded will
# cause Eventlet to send a 5xx response. In this particular
# case of ListingIterError we don't want that and we'd rather
# just send the normal 2xx response and then hang up early
# since 5xx codes are often used to judge Service Level
# Agreements and this ListingIterError indicates the user has
# created an invalid condition.
if not have_yielded_data:
yield ' '
raise excinfo
except SegmentError as err:
self.logger.exception(err)
# This doesn't actually change the response status (we're too
# late for that), but this does make it to the logs.
if self.response:
self.response.status = HTTP_SERVICE_UNAVAILABLE
raise raise
def app_iter_range(self, *a, **kw):
"""
swob.Response will only respond with a 206 status in certain cases; one
of those is if the body iterator responds to .app_iter_range().
However, this object (or really, its listing iter) is smart enough to
handle the range stuff internally, so we just no-op this out for swob.
"""
return self
def validate_first_segment(self):
"""
Start fetching object data to ensure that the first segment (if any) is
valid. This is to catch cases like "first segment is missing" or
"first segment's etag doesn't match manifest".
Note: this does not validate that you have any segments. A
zero-segment large object is not erroneous; it is just empty.
"""
if self.validated_first_segment:
return
self.validated_first_segment = True
try:
self.peeked_chunk = self.app_iter.next()
except StopIteration:
pass
def __iter__(self):
if self.peeked_chunk is not None:
pc = self.peeked_chunk
self.peeked_chunk = None
return itertools.chain([pc], self.app_iter)
else:
return self.app_iter

View File

@ -73,7 +73,7 @@ class DloTestCase(unittest.TestCase):
# don't slow down tests with rate limiting # don't slow down tests with rate limiting
'rate_limit_after_segment': '1000000', 'rate_limit_after_segment': '1000000',
})(self.app) })(self.app)
self.dlo.logger = self.app.logger
self.app.register( self.app.register(
'GET', '/v1/AUTH_test/c/seg_01', 'GET', '/v1/AUTH_test/c/seg_01',
swob.HTTPOk, {'Content-Length': '5', 'Etag': md5hex("aaaaa")}, swob.HTTPOk, {'Content-Length': '5', 'Etag': md5hex("aaaaa")},
@ -562,12 +562,11 @@ class TestDloGetManifest(DloTestCase):
req = swob.Request.blank('/v1/AUTH_test/mancon/manifest', req = swob.Request.blank('/v1/AUTH_test/mancon/manifest',
environ={'REQUEST_METHOD': 'GET'}) environ={'REQUEST_METHOD': 'GET'})
status, headers, body, exc = self.call_dlo(req, expect_exception=True) status, headers, body = self.call_dlo(req)
headers = swob.HeaderKeyDict(headers) self.assertEqual(status, "409 Conflict")
self.assertTrue(isinstance(exc, exceptions.SegmentError)) err_log = self.dlo.logger.log_dict['exception'][0][0][0]
self.assertTrue(err_log.startswith('ERROR: An error occurred '
self.assertEqual(status, "200 OK") 'while retrieving segments'))
self.assertEqual(body, '') # error right away -> no body bytes sent
def test_error_fetching_second_segment(self): def test_error_fetching_second_segment(self):
self.app.register( self.app.register(
@ -582,6 +581,9 @@ class TestDloGetManifest(DloTestCase):
self.assertTrue(isinstance(exc, exceptions.SegmentError)) self.assertTrue(isinstance(exc, exceptions.SegmentError))
self.assertEqual(status, "200 OK") self.assertEqual(status, "200 OK")
self.assertEqual(''.join(body), "aaaaa") # first segment made it out self.assertEqual(''.join(body), "aaaaa") # first segment made it out
err_log = self.dlo.logger.log_dict['exception'][0][0][0]
self.assertTrue(err_log.startswith('ERROR: An error occurred '
'while retrieving segments'))
def test_error_listing_container_first_listing_request(self): def test_error_listing_container_first_listing_request(self):
self.app.register( self.app.register(
@ -626,7 +628,7 @@ class TestDloGetManifest(DloTestCase):
self.assertEqual(''.join(body), "aaaaabbWRONGbb") # stop after error self.assertEqual(''.join(body), "aaaaabbWRONGbb") # stop after error
def test_etag_comparison_ignores_quotes(self): def test_etag_comparison_ignores_quotes(self):
# a little future-proofing here in case we ever fix this # a little future-proofing here in case we ever fix this in swob
self.app.register( self.app.register(
'HEAD', '/v1/AUTH_test/mani/festo', 'HEAD', '/v1/AUTH_test/mani/festo',
swob.HTTPOk, {'Content-Length': '0', 'Etag': 'blah', swob.HTTPOk, {'Content-Length': '0', 'Etag': 'blah',

View File

@ -55,6 +55,7 @@ class SloTestCase(unittest.TestCase):
self.app = FakeSwift() self.app = FakeSwift()
self.slo = slo.filter_factory({})(self.app) self.slo = slo.filter_factory({})(self.app)
self.slo.min_segment_size = 1 self.slo.min_segment_size = 1
self.slo.logger = self.app.logger
def call_app(self, req, app=None, expect_exception=False): def call_app(self, req, app=None, expect_exception=False):
if app is None: if app is None:
@ -1286,6 +1287,119 @@ class TestSloGetManifest(SloTestCase):
# make sure we didn't keep asking for segments # make sure we didn't keep asking for segments
self.assertEqual(self.app.call_count, 20) self.assertEqual(self.app.call_count, 20)
def test_sub_slo_recursion(self):
# man1 points to man2 and obj1, man2 points to man3 and obj2...
for i in xrange(11):
self.app.register('GET', '/v1/AUTH_test/gettest/obj%d' % i,
swob.HTTPOk, {'Content-Type': 'text/plain',
'Content-Length': '6',
'Etag': md5hex('body%02d' % i)},
'body%02d' % i)
manifest_json = json.dumps([{'name': '/gettest/obj%d' % i,
'hash': md5hex('body%2d' % i),
'content_type': 'text/plain',
'bytes': '6'}])
self.app.register(
'GET', '/v1/AUTH_test/gettest/man%d' % i,
swob.HTTPOk, {'Content-Type': 'application/json',
'X-Static-Large-Object': 'true',
'Etag': 'man%d' % i},
manifest_json)
self.app.register(
'HEAD', '/v1/AUTH_test/gettest/obj%d' % i,
swob.HTTPOk, {'Content-Length': '6',
'Etag': md5hex('body%2d' % i)},
None)
for i in xrange(9, 0, -1):
manifest_data = [
{'name': '/gettest/man%d' % (i + 1),
'hash': 'man%d' % (i + 1),
'sub_slo': True,
'bytes': len(manifest_json),
'content_type':
'application/json;swift_bytes=%d' % ((10 - i) * 6)},
{'name': '/gettest/obj%d' % i,
'hash': md5hex('body%02d' % i),
'bytes': '6',
'content_type': 'text/plain'}]
manifest_json = json.dumps(manifest_data)
self.app.register(
'GET', '/v1/AUTH_test/gettest/man%d' % i,
swob.HTTPOk, {'Content-Type': 'application/json',
'X-Static-Large-Object': 'true',
'Etag': 'man%d' % i},
manifest_json)
req = Request.blank(
'/v1/AUTH_test/gettest/man1',
environ={'REQUEST_METHOD': 'GET'})
status, headers, body = self.call_slo(req)
self.assertEqual(status, '200 OK')
self.assertEqual(body, ('body10body09body08body07body06' +
'body05body04body03body02body01'))
self.assertEqual(self.app.call_count, 20)
def test_sub_slo_recursion_limit(self):
# man1 points to man2 and obj1, man2 points to man3 and obj2...
for i in xrange(12):
self.app.register('GET', '/v1/AUTH_test/gettest/obj%d' % i,
swob.HTTPOk,
{'Content-Type': 'text/plain',
'Content-Length': '6',
'Etag': md5hex('body%02d' % i)}, 'body%02d' % i)
manifest_json = json.dumps([{'name': '/gettest/obj%d' % i,
'hash': md5hex('body%2d' % i),
'content_type': 'text/plain',
'bytes': '6'}])
self.app.register(
'GET', '/v1/AUTH_test/gettest/man%d' % i,
swob.HTTPOk, {'Content-Type': 'application/json',
'X-Static-Large-Object': 'true',
'Etag': 'man%d' % i},
manifest_json)
self.app.register(
'HEAD', '/v1/AUTH_test/gettest/obj%d' % i,
swob.HTTPOk, {'Content-Length': '6',
'Etag': md5hex('body%2d' % i)},
None)
for i in xrange(11, 0, -1):
manifest_data = [
{'name': '/gettest/man%d' % (i + 1),
'hash': 'man%d' % (i + 1),
'sub_slo': True,
'bytes': len(manifest_json),
'content_type':
'application/json;swift_bytes=%d' % ((12 - i) * 6)},
{'name': '/gettest/obj%d' % i,
'hash': md5hex('body%02d' % i),
'bytes': '6',
'content_type': 'text/plain'}]
manifest_json = json.dumps(manifest_data)
self.app.register('GET', '/v1/AUTH_test/gettest/man%d' % i,
swob.HTTPOk,
{'Content-Type': 'application/json',
'X-Static-Large-Object': 'true',
'Etag': 'man%d' % i},
manifest_json)
req = Request.blank(
'/v1/AUTH_test/gettest/man1',
environ={'REQUEST_METHOD': 'GET'})
status, headers, body = self.call_slo(req)
self.assertEqual(status, '409 Conflict')
self.assertEqual(self.app.call_count, 10)
err_log = self.slo.logger.log_dict['exception'][0][0][0]
self.assertTrue(err_log.startswith('ERROR: An error occurred '
'while retrieving segments'))
def test_get_with_if_modified_since(self): def test_get_with_if_modified_since(self):
# It's important not to pass the If-[Un]Modified-Since header to the # It's important not to pass the If-[Un]Modified-Since header to the
# proxy for segment or submanifest GET requests, as it may result in # proxy for segment or submanifest GET requests, as it may result in
@ -1356,11 +1470,12 @@ class TestSloGetManifest(SloTestCase):
req = Request.blank( req = Request.blank(
'/v1/AUTH_test/gettest/manifest-manifest-a', '/v1/AUTH_test/gettest/manifest-manifest-a',
environ={'REQUEST_METHOD': 'GET'}) environ={'REQUEST_METHOD': 'GET'})
status, headers, body, exc = self.call_slo(req, expect_exception=True) status, headers, body = self.call_slo(req)
self.assertTrue(isinstance(exc, ListingIterError)) self.assertEqual('409 Conflict', status)
self.assertEqual('200 OK', status) err_log = self.slo.logger.log_dict['exception'][0][0][0]
self.assertEqual(body, ' ') self.assertTrue(err_log.startswith('ERROR: An error occurred '
'while retrieving segments'))
def test_invalid_json_submanifest(self): def test_invalid_json_submanifest(self):
self.app.register( self.app.register(
@ -1421,6 +1536,42 @@ class TestSloGetManifest(SloTestCase):
self.assertEqual('200 OK', status) self.assertEqual('200 OK', status)
self.assertEqual(body, 'aaaaa') self.assertEqual(body, 'aaaaa')
def test_first_segment_mismatched_etag(self):
self.app.register('GET', '/v1/AUTH_test/gettest/manifest-badetag',
swob.HTTPOk, {'Content-Type': 'application/json',
'X-Static-Large-Object': 'true'},
json.dumps([{'name': '/gettest/a_5',
'hash': 'wrong!',
'content_type': 'text/plain',
'bytes': '5'}]))
req = Request.blank('/v1/AUTH_test/gettest/manifest-badetag',
environ={'REQUEST_METHOD': 'GET'})
status, headers, body = self.call_slo(req)
self.assertEqual('409 Conflict', status)
err_log = self.slo.logger.log_dict['exception'][0][0][0]
self.assertTrue(err_log.startswith('ERROR: An error occurred '
'while retrieving segments'))
def test_first_segment_mismatched_size(self):
self.app.register('GET', '/v1/AUTH_test/gettest/manifest-badsize',
swob.HTTPOk, {'Content-Type': 'application/json',
'X-Static-Large-Object': 'true'},
json.dumps([{'name': '/gettest/a_5',
'hash': md5hex('a' * 5),
'content_type': 'text/plain',
'bytes': '999999'}]))
req = Request.blank('/v1/AUTH_test/gettest/manifest-badsize',
environ={'REQUEST_METHOD': 'GET'})
status, headers, body = self.call_slo(req)
self.assertEqual('409 Conflict', status)
err_log = self.slo.logger.log_dict['exception'][0][0][0]
self.assertTrue(err_log.startswith('ERROR: An error occurred '
'while retrieving segments'))
def test_download_takes_too_long(self): def test_download_takes_too_long(self):
the_time = [time.time()] the_time = [time.time()]
@ -1454,6 +1605,27 @@ class TestSloGetManifest(SloTestCase):
('GET', '/v1/AUTH_test/gettest/b_10?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/c_15?multipart-manifest=get')])
def test_first_segment_not_exists(self):
self.app.register('GET', '/v1/AUTH_test/gettest/not_exists_obj',
swob.HTTPNotFound, {}, None)
self.app.register('GET', '/v1/AUTH_test/gettest/manifest-not-exists',
swob.HTTPOk, {'Content-Type': 'application/json',
'X-Static-Large-Object': 'true'},
json.dumps([{'name': '/gettest/not_exists_obj',
'hash': md5hex('not_exists_obj'),
'content_type': 'text/plain',
'bytes': '%d' % len('not_exists_obj')
}]))
req = Request.blank('/v1/AUTH_test/gettest/manifest-not-exists',
environ={'REQUEST_METHOD': 'GET'})
status, headers, body = self.call_slo(req)
self.assertEqual('409 Conflict', status)
err_log = self.slo.logger.log_dict['exception'][0][0][0]
self.assertTrue(err_log.startswith('ERROR: An error occurred '
'while retrieving segments'))
class TestSloBulkLogger(unittest.TestCase): class TestSloBulkLogger(unittest.TestCase):
def test_reused_logger(self): def test_reused_logger(self):
@ -1474,18 +1646,21 @@ class TestSloCopyHook(SloTestCase):
'X-Static-Large-Object': 'true'}, 'X-Static-Large-Object': 'true'},
json.dumps([{'name': '/c/o', 'hash': md5hex("obj"), json.dumps([{'name': '/c/o', 'hash': md5hex("obj"),
'bytes': '3'}])) 'bytes': '3'}]))
self.app.register(
'COPY', '/v1/AUTH_test/c/o', swob.HTTPCreated, {})
copy_hook = [None] copy_hook = [None]
# slip this guy in there to pull out the hook # slip this guy in there to pull out the hook
def extract_copy_hook(env, sr): def extract_copy_hook(env, sr):
copy_hook[0] = env['swift.copy_hook'] if env['REQUEST_METHOD'] == 'COPY':
copy_hook[0] = env['swift.copy_hook']
return self.app(env, sr) return self.app(env, sr)
self.slo = slo.filter_factory({})(extract_copy_hook) self.slo = slo.filter_factory({})(extract_copy_hook)
req = Request.blank('/v1/AUTH_test/c/o', req = Request.blank('/v1/AUTH_test/c/o',
environ={'REQUEST_METHOD': 'GET'}) environ={'REQUEST_METHOD': 'COPY'})
self.slo(req.environ, fake_start_response) self.slo(req.environ, fake_start_response)
self.copy_hook = copy_hook[0] self.copy_hook = copy_hook[0]
@ -1514,12 +1689,12 @@ class TestSloCopyHook(SloTestCase):
source_resp = Response(request=source_req, status=200, source_resp = Response(request=source_req, status=200,
headers={"X-Static-Large-Object": "true"}, headers={"X-Static-Large-Object": "true"},
app_iter=[json.dumps([{'name': '/c/o', app_iter=[json.dumps([{'name': '/c/o',
'hash': 'obj-etag', 'hash': md5hex("obj"),
'bytes': '3'}])]) 'bytes': '3'}])])
modified_resp = self.copy_hook(source_req, source_resp, sink_req) modified_resp = self.copy_hook(source_req, source_resp, sink_req)
self.assertTrue(modified_resp is not source_resp) self.assertTrue(modified_resp is not source_resp)
self.assertEqual(modified_resp.etag, md5("obj-etag").hexdigest()) self.assertEqual(modified_resp.etag, md5hex(md5hex("obj")))
class TestSwiftInfo(unittest.TestCase): class TestSwiftInfo(unittest.TestCase):