def __init__(self, *args, **kwargs): swob.Response.__init__(self, *args, **kwargs) sw_sysmeta_headers = swob.HeaderKeyDict() sw_headers = swob.HeaderKeyDict() headers = HeaderKeyDict() self.is_slo = False for key, val in self.headers.iteritems(): _key = key.lower() if _key.startswith(sysmeta_prefix('object')) or \ _key.startswith(sysmeta_prefix('container')): sw_sysmeta_headers[key] = val else: sw_headers[key] = val # Handle swift headers for key, val in sw_headers.iteritems(): _key = key.lower() if _key.startswith('x-object-meta-'): # Note that AWS ignores user-defined headers with '=' in the # header name. We translated underscores to '=5F' on the way # in, though. headers['x-amz-meta-' + _key[14:].replace('=5f', '_')] = val elif _key in ('content-length', 'content-type', 'content-range', 'content-encoding', 'content-disposition', 'content-language', 'etag', 'last-modified', 'x-robots-tag', 'cache-control', 'expires', 'x-amz-version-id'): headers[key] = val elif _key == 'x-object-sysmeta-version-id': headers['x-amz-version-id'] = val elif _key == 'x-static-large-object': # for delete slo self.is_slo = config_true_value(val) # Check whether we stored the AWS-style etag on upload override_etag = sw_sysmeta_headers.get(sysmeta_header( 'object', 'etag')) if override_etag not in (None, ''): # Multipart uploads in AWS have ETags like # <MD5(part_etag1 || ... || part_etagN)>-<number of parts> headers['etag'] = override_etag elif self.is_slo and 'etag' in headers: # Many AWS clients use the presence of a '-' to decide whether # to attempt client-side download validation, so even if we # didn't store the AWS-style header, tack on a '-N'. (Use 'N' # because we don't actually know how many parts there are.) headers['etag'] += '-N' self.headers = headers if self.etag: # add double quotes to the etag header self.etag = self.etag # Used for pure swift header handling at the request layer self.sw_headers = sw_headers self.sysmeta_headers = sw_sysmeta_headers
def __call__(self, env, start_response): method = env['REQUEST_METHOD'] path = env['PATH_INFO'] _, acc, cont, obj = split_path(env['PATH_INFO'], 0, 4, rest_with_last=True) if env.get('QUERY_STRING'): path += '?' + env['QUERY_STRING'] if 'swift.authorize' in env: resp = env['swift.authorize']() if resp: return resp(env, start_response) headers = swob.Request(env).headers self._calls.append((method, path, headers)) self.swift_sources.append(env.get('swift.source')) try: resp_class, raw_headers, body = self._responses[(method, path)] headers = swob.HeaderKeyDict(raw_headers) except KeyError: if (env.get('QUERY_STRING') and (method, env['PATH_INFO']) in self._responses): resp_class, raw_headers, body = self._responses[( method, env['PATH_INFO'])] headers = swob.HeaderKeyDict(raw_headers) elif method == 'HEAD' and ('GET', path) in self._responses: resp_class, raw_headers, _ = self._responses[('GET', path)] body = None headers = swob.HeaderKeyDict(raw_headers) elif method == 'GET' and obj and path in self.uploaded: resp_class = swob.HTTPOk headers, body = self.uploaded[path] else: print "Didn't find %r in allowed responses" % ( (method, path), ) raise # simulate object PUT if method == 'PUT' and obj: input = env['wsgi.input'].read() etag = md5(input).hexdigest() headers.setdefault('Etag', etag) headers.setdefault('Content-Length', len(input)) # keep it for subsequent GET requests later self.uploaded[path] = (deepcopy(headers), input) if "CONTENT_TYPE" in env: self.uploaded[path][0]['Content-Type'] = env["CONTENT_TYPE"] # range requests ought to work, hence conditional_response=True req = swob.Request(env) resp = resp_class(req=req, headers=headers, body=body, conditional_response=True) return resp(env, start_response)
def __init__(self, *args, **kwargs): swob.Response.__init__(self, *args, **kwargs) if self.etag: # add double quotes to the etag header self.etag = self.etag sw_sysmeta_headers = swob.HeaderKeyDict() sw_headers = swob.HeaderKeyDict() headers = HeaderKeyDict() self.is_slo = False for key, val in self.headers.iteritems(): _key = key.lower() if _key.startswith(sysmeta_prefix('object')) or \ _key.startswith(sysmeta_prefix('container')): sw_sysmeta_headers[key] = val else: sw_headers[key] = val # Handle swift headers for key, val in sw_headers.iteritems(): _key = key.lower() if _key.startswith('x-object-meta-'): headers['x-amz-meta-' + _key[14:]] = val elif _key in ('content-length', 'content-type', 'content-range', 'content-encoding', 'content-disposition', 'content-language', 'etag', 'last-modified', 'x-robots-tag', 'cache-control', 'expires', 'x-amz-version-id'): headers[key] = val elif _key == 'x-object-sysmeta-version-id': headers['x-amz-version-id'] = val elif _key == 'x-static-large-object': # for delete slo self.is_slo = config_true_value(val) if self.is_slo: # and 'etag' in headers # Multipart uploads in AWS have ETags like # <MD5(part_etag1 || ... || part_etagN)>-<number of parts> if '-' not in headers.get('etag', ''): if 's3_etag' in headers.get('content-type', ''): # ETag was computed at upload, and saved in content-type ctype, s3_etag = extract_s3_etag(headers['content-type']) headers['etag'] = '"%s"' % s3_etag headers['content-type'] = ctype else: # Many AWS clients use the presence of a '-' to decide # whether to attempt client-side download validation, # so tack on a '-N' ('N' because we don't actually know # how many parts there are). headers['etag'] = '"%s-N"' % self.etag self.headers = headers # Used for pure swift header handling at the request layer self.sw_headers = sw_headers self.sysmeta_headers = sw_sysmeta_headers
def test_etag_comparison_ignores_quotes(self): # a little future-proofing here in case we ever fix this in swob self.app.register( 'HEAD', '/v1/AUTH_test/mani/festo', swob.HTTPOk, { 'Content-Length': '0', 'Etag': 'blah', 'X-Object-Manifest': 'c/quotetags' }, None) self.app.register( 'GET', '/v1/AUTH_test/c?format=json&prefix=quotetags', swob.HTTPOk, {'Content-Type': 'application/json; charset=utf-8'}, json.dumps([{ "hash": "\"abc\"", "bytes": 5, "name": "quotetags1", "last_modified": "2013-11-22T02:42:14.261620", "content-type": "application/octet-stream" }, { "hash": "def", "bytes": 5, "name": "quotetags2", "last_modified": "2013-11-22T02:42:14.261620", "content-type": "application/octet-stream" }])) req = swob.Request.blank('/v1/AUTH_test/mani/festo', environ={'REQUEST_METHOD': 'HEAD'}) status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(headers["Etag"], '"' + hashlib.md5("abcdef").hexdigest() + '"')
def check_copy_source(self, app): """ check_copy_source checks the copy source existence and if copying an object to itself, for illegal request parameters :returns: the source HEAD response """ if 'x-oss-copy-source' not in self.headers: self.req.headers['x-object-meta-object-type'] = 'Normal' return None src_path = unquote(self.headers['x-oss-copy-source']) src_path = src_path if src_path.startswith('/') else \ ('/' + src_path) src_bucket, src_obj = split_path(src_path, 0, 2, True) headers = swob.HeaderKeyDict() headers.update(self._copy_source_headers()) src_resp = self.get_response(app, 'HEAD', src_bucket, src_obj, headers=headers) if src_resp.status_int == 304: # pylint: disable-msg=E1101 raise PreconditionFailed() self.headers['x-oss-copy-source'] = \ '/' + self.headers['x-oss-copy-source'].lstrip('/') source_container, source_obj = \ split_path(self.headers['x-oss-copy-source'], 1, 2, True) return src_resp
def test_get_undersize_segment(self): # If we send a Content-Length header to the client, it's based on the # container listing. If a segment gets smaller by the time we get to # it (like if a client uploads a smaller segment w/the same name), we # need to raise an exception so that the connection will be closed by # the WSGI server. Otherwise, the WSGI server will be waiting for the # next request, the client will still be waiting for the rest of the # response, and nobody will be happy. # Shrink it by a single byte self.app.register( 'GET', '/v1/AUTH_test/c/seg_03', swob.HTTPOk, {'Content-Length': '4', 'Etag': md5hex("cccc")}, 'cccc') req = swob.Request.blank( '/v1/AUTH_test/mancon/manifest', environ={'REQUEST_METHOD': 'GET'}) status, headers, body, exc = self.call_dlo(req, expect_exception=True) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, '200 OK') # sanity check self.assertEqual(headers.get('Content-Length'), '25') # sanity check self.assertEqual(body, 'aaaaabbbbbccccdddddeeeee') self.assertTrue(isinstance(exc, exceptions.SegmentError))
def check_copy_source(self, app): """ check_copy_source checks the copy source existence :return : last modified str if copy-source header exist otherwise None """ if 'X-Amz-Copy-Source' in self.headers: src_path = self.headers['X-Amz-Copy-Source'] src_path = src_path if src_path.startswith('/') else \ ('/' + src_path) src_bucket, src_obj = split_path(src_path, 0, 2, True) headers = swob.HeaderKeyDict() headers.update(self._copy_source_headers()) src_resp = self.get_response(app, 'HEAD', src_bucket, src_obj, headers=headers) if src_resp.status_int == 304: # pylint: disable-msg=E1101 raise PreconditionFailed() return src_resp.last_modified.isoformat()[:-6] return None
def test_get_oversize_segment(self): # If we send a Content-Length header to the client, it's based on the # container listing. If a segment gets bigger by the time we get to it # (like if a client uploads a bigger segment w/the same name), we need # to not send anything beyond the length we promised. Also, we should # probably raise an exception. # This is now longer than the original seg_03+seg_04+seg_05 combined self.app.register('GET', '/v1/AUTH_test/c/seg_03', swob.HTTPOk, { 'Content-Length': '20', 'Etag': 'seg03-etag' }, 'cccccccccccccccccccc') req = swob.Request.blank('/v1/AUTH_test/mancon/manifest', environ={'REQUEST_METHOD': 'GET'}) status, headers, body, exc = self.call_dlo(req, expect_exception=True) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, '200 OK') # sanity check self.assertEqual(headers.get('Content-Length'), '25') # sanity check self.assertEqual(body, 'aaaaabbbbbccccccccccccccc') self.assertTrue(isinstance(exc, exceptions.SegmentError)) self.assertEqual( self.app.calls, [('GET', '/v1/AUTH_test/mancon/manifest'), ('GET', '/v1/AUTH_test/c?format=json&prefix=seg'), ('GET', '/v1/AUTH_test/c/seg_01?multipart-manifest=get'), ('GET', '/v1/AUTH_test/c/seg_02?multipart-manifest=get'), ('GET', '/v1/AUTH_test/c/seg_03?multipart-manifest=get')])
def __call__(self, env, start_response): method = env['REQUEST_METHOD'] path = env['PATH_INFO'] _, acc, cont, obj = split_path(env['PATH_INFO'], 0, 4, rest_with_last=True) self.calls.append((method, path)) try: resp_class, raw_headers, body = self._responses[(method, path)] headers = swob.HeaderKeyDict(raw_headers) except KeyError: if method == 'GET' and obj and path in self.uploaded: resp_class = swob.HTTPOk headers, body = self.uploaded[path] else: raise # simulate object PUT if method == 'PUT' and obj: input = env['wsgi.input'].read() etag = md5(input).hexdigest() headers.setdefault('Etag', etag) headers.setdefault('Content-Length', len(input)) # keep it for subsequent GET requests later self.uploaded[path] = (deepcopy(headers), input) if "CONTENT_TYPE" in env: self.uploaded[path][0]['Content-Type'] = env["CONTENT_TYPE"] return resp_class(headers=headers, body=body)(env, start_response)
def getheaders(self): etag = self.etag if not etag: if isinstance(self.body, str): etag = '"' + md5(self.body).hexdigest() + '"' else: etag = '"68b329da9893e34099c7d8ad5cb9c940"' headers = swob.HeaderKeyDict({ 'content-length': len(self.body), 'content-type': 'x-application/test', 'x-timestamp': self.timestamp, 'x-backend-timestamp': self.timestamp, 'last-modified': self.timestamp, 'x-object-meta-test': 'testing', 'x-delete-at': '9876543210', 'etag': etag, 'x-works': 'yes', }) if self.status // 100 == 2: headers['x-account-container-count'] = \ kwargs.get('count', 12345) if not self.timestamp: # when timestamp is None, HeaderKeyDict raises KeyError headers.pop('x-timestamp', None) try: if container_ts_iter.next() is False: headers['x-container-timestamp'] = '1' except StopIteration: pass am_slow, value = self.get_slow() if am_slow: headers['content-length'] = '4' headers.update(self.headers) return headers.items()
def call_mware(self, req, expect_exception=False): status = [None] headers = [None] def start_response(s, h, ei=None): status[0] = s headers[0] = h body_iter = self.undelete(req.environ, start_response) body = '' caught_exc = None try: for chunk in body_iter: body += chunk except Exception as exc: if expect_exception: caught_exc = exc else: raise headerdict = swob.HeaderKeyDict(headers[0]) if expect_exception: return status[0], headerdict, body, caught_exc else: return status[0], headerdict, body
def test_get_range_many_segments_satisfiable(self): req = swob.Request.blank('/v1/AUTH_test/mancon/manifest-many-segments', environ={'REQUEST_METHOD': 'GET'}, headers={'Range': 'bytes=3-12'}) with mock.patch(LIMIT, 3): status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, "206 Partial Content") self.assertEqual(headers["Content-Length"], "10") # The /15 here indicates that this is a 15-byte object. DLO can't tell # if there are more segments or not without fetching more container # listings, though, so we just go with the sum of the lengths of the # segments we can see. In an ideal world, this would be "bytes 3-12/*" # to indicate that we don't know the full object length. However, RFC # 2616 section 14.16 explicitly forbids us from doing that: # # A response with status code 206 (Partial Content) MUST NOT include # a Content-Range field with a byte-range-resp-spec of "*". # # Since the truth is forbidden, we lie. self.assertEqual(headers["Content-Range"], "bytes 3-12/15") self.assertEqual(body, "aabbbbbccc") self.assertEqual( self.app.calls, [('GET', '/v1/AUTH_test/mancon/manifest-many-segments'), ('GET', '/v1/AUTH_test/c?format=json&prefix=seg_'), ('GET', '/v1/AUTH_test/c/seg_01?multipart-manifest=get'), ('GET', '/v1/AUTH_test/c/seg_02?multipart-manifest=get'), ('GET', '/v1/AUTH_test/c/seg_03?multipart-manifest=get')])
def __init__(self, *args, **kwargs): swob.Response.__init__(self, *args, **kwargs) if self.etag: # add double quotes to the etag header self.headers['etag'] = self.etag sw_sysmeta_headers = swob.HeaderKeyDict() sw_headers = swob.HeaderKeyDict() headers = HeaderKeyDict() self.is_slo = False for key, val in self.headers.iteritems(): _key = key.lower() if _key.startswith(sysmeta_prefix('object')) or \ _key.startswith(sysmeta_prefix('container')): sw_sysmeta_headers[key] = val else: sw_headers[key] = val # Handle swift headers for key, val in sw_headers.iteritems(): _key = key.lower() if _key.startswith('x-object-meta-'): if any(_str in _key for _str in ('object-type', 'hash-crc64ecma')): headers['x-oss-' + _key[14:]] = val else: headers['x-oss-meta-' + _key[14:]] = val elif _key.startswith('x-container-meta-'): headers['x-oss-meta-' + _key[17:]] = val elif _key in ('content-length', 'content-type', 'content-range', 'content-encoding', 'content-disposition', 'content-language', 'etag', 'last-modified', 'x-robots-tag', 'cache-control', 'expires'): headers[key] = val elif _key == 'x-static-large-object': # for delete slo self.is_slo = config_true_value(val) if headers['x-oss-meta-location'] is None: headers['x-oss-meta-location'] = '' self.headers = headers # Used for pure swift header handling at the request layer self.sw_headers = sw_headers self.sysmeta_headers = sw_sysmeta_headers
def test_get_range_on_segment_boundaries(self): req = swob.Request.blank('/v1/AUTH_test/mancon/manifest', environ={'REQUEST_METHOD': 'GET'}, headers={'Range': 'bytes=10-19'}) status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, "206 Partial Content") self.assertEqual(headers["Content-Length"], "10") self.assertEqual(body, "cccccddddd")
def test_get_suffix_range(self): req = swob.Request.blank('/v1/AUTH_test/mancon/manifest', environ={'REQUEST_METHOD': 'GET'}, headers={'Range': 'bytes=-40'}) status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, "206 Partial Content") self.assertEqual(headers["Content-Length"], "25") self.assertEqual(body, "aaaaabbbbbcccccdddddeeeee")
def check_copy_source(self, app): """ check_copy_source checks the copy source existence and if copying an object to itself, for illegal request parameters :returns: the source HEAD response """ try: src_path = self.headers['X-Amz-Copy-Source'] except KeyError: return None if '?' in src_path: src_path, qs = src_path.split('?', 1) query = parse_qsl(qs, True) if not query: pass # ignore it elif len(query) > 1 or query[0][0] != 'versionId': raise InvalidArgument('X-Amz-Copy-Source', self.headers['X-Amz-Copy-Source'], 'Unsupported copy source parameter.') elif query[0][1] != 'null': # TODO: once we support versioning, we'll need to translate # src_path to the proper location in the versions container raise S3NotImplemented('Versioning is not yet supported') self.headers['X-Amz-Copy-Source'] = src_path src_path = unquote(src_path) src_path = src_path if src_path.startswith('/') else ('/' + src_path) src_bucket, src_obj = split_path(src_path, 0, 2, True) headers = swob.HeaderKeyDict() headers.update(self._copy_source_headers()) src_resp = self.get_response(app, 'HEAD', src_bucket, src_obj, headers=headers) if src_resp.status_int == 304: # pylint: disable-msg=E1101 raise PreconditionFailed() self.headers['X-Amz-Copy-Source'] = \ '/' + self.headers['X-Amz-Copy-Source'].lstrip('/') source_container, source_obj = \ split_path(self.headers['X-Amz-Copy-Source'], 1, 2, True) if (self.container_name == source_container and self.object_name == source_obj and self.headers.get('x-amz-metadata-directive', 'COPY') == 'COPY'): raise InvalidRequest("This copy request is illegal " "because it is trying to copy an " "object to itself without " "changing the object's metadata, " "storage class, website redirect " "location or encryption " "attributes.") return src_resp
def test_get_range_overlapping_end(self): req = swob.Request.blank('/v1/AUTH_test/mancon/manifest', environ={'REQUEST_METHOD': 'GET'}, headers={'Range': 'bytes=18-30'}) status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, "206 Partial Content") self.assertEqual(headers["Content-Length"], "7") self.assertEqual(headers["Content-Range"], "bytes 18-24/25") self.assertEqual(body, "ddeeeee")
def test_head_large_object_too_many_segments(self): req = swob.Request.blank('/v1/AUTH_test/mancon/manifest-many-segments', environ={'REQUEST_METHOD': 'HEAD'}) with mock.patch(LIMIT, 3): status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) # etag is manifest's etag self.assertEqual(headers["Etag"], "etag-manyseg") self.assertEqual(headers.get("Content-Length"), None)
def test_head_large_object(self): expected_etag = '"%s"' % md5hex( md5hex("aaaaa") + md5hex("bbbbb") + md5hex("ccccc") + md5hex("ddddd") + md5hex("eeeee")) req = swob.Request.blank('/v1/AUTH_test/mancon/manifest', environ={'REQUEST_METHOD': 'HEAD'}) status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(headers["Etag"], expected_etag) self.assertEqual(headers["Content-Length"], "25")
def test_head_large_object(self): expected_etag = '"%s"' % hashlib.md5("seg01-etag" + "seg02-etag" + "seg03-etag" + "seg04-etag" + "seg05-etag").hexdigest() req = swob.Request.blank('/v1/AUTH_test/mancon/manifest', environ={'REQUEST_METHOD': 'HEAD'}) status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(headers["Etag"], expected_etag) self.assertEqual(headers["Content-Length"], "25")
def test_obj_put_legacy_updates(self): ts = (normalize_timestamp(t) for t in itertools.count(int(time()))) policy = POLICIES.get_by_index(0) # setup updater conf = { 'devices': self.devices_dir, 'mount_check': 'false', 'swift_dir': self.testdir, } async_dir = os.path.join(self.sda1, get_async_dir(policy)) os.mkdir(async_dir) account, container, obj = 'a', 'c', 'o' # write an async for op in ('PUT', 'DELETE'): self.logger._clear() daemon = object_updater.ObjectUpdater(conf, logger=self.logger) dfmanager = DiskFileManager(conf, daemon.logger) # don't include storage-policy-index in headers_out pickle headers_out = swob.HeaderKeyDict({ 'x-size': 0, 'x-content-type': 'text/plain', 'x-etag': 'd41d8cd98f00b204e9800998ecf8427e', 'x-timestamp': next(ts), }) data = { 'op': op, 'account': account, 'container': container, 'obj': obj, 'headers': headers_out } dfmanager.pickle_async_update(self.sda1, account, container, obj, data, next(ts), policy) request_log = [] def capture(*args, **kwargs): request_log.append((args, kwargs)) # run once fake_status_codes = [200, 200, 200] with mocked_http_conn(*fake_status_codes, give_connect=capture): daemon.run_once() self.assertEqual(len(fake_status_codes), len(request_log)) for request_args, request_kwargs in request_log: ip, part, method, path, headers, qs, ssl = request_args self.assertEqual(method, op) self.assertEqual(headers['X-Backend-Storage-Policy-Index'], str(int(policy))) self.assertEqual(daemon.logger.get_increment_counts(), { 'successes': 1, 'unlinks': 1, 'async_pendings': 1 })
def __init__(self, *args, **kwargs): swob.Response.__init__(self, *args, **kwargs) if self.etag: # add double quotes to the etag header self.etag = self.etag sw_sysmeta_headers = swob.HeaderKeyDict() sw_headers = swob.HeaderKeyDict() headers = HeaderKeyDict() self.is_slo = False for key, val in self.headers.iteritems(): _key = key.lower() if _key.startswith(sysmeta_prefix('object')) or \ _key.startswith(sysmeta_prefix('container')): sw_sysmeta_headers[key] = val else: sw_headers[key] = val # Handle swift headers for key, val in sw_headers.iteritems(): _key = key.lower() if _key.startswith('x-object-meta-'): # Note that AWS ignores user-defined headers with '=' in the # header name. We translated underscores to '=5F' on the way # in, though. headers['x-amz-meta-' + _key[14:].replace('=5f', '_')] = val elif _key in ('content-length', 'content-type', 'content-range', 'content-encoding', 'content-disposition', 'content-language', 'etag', 'last-modified', 'x-robots-tag', 'cache-control', 'expires'): headers[key] = val elif _key == 'x-static-large-object': # for delete slo self.is_slo = config_true_value(val) self.headers = headers # Used for pure swift header handling at the request layer self.sw_headers = sw_headers self.sysmeta_headers = sw_sysmeta_headers
def test_if_none_match_does_not_match(self): req = swob.Request.blank('/v1/AUTH_test/mancon/manifest', environ={'REQUEST_METHOD': 'GET'}, headers={'If-None-Match': 'not it'}) status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, '200 OK') self.assertEqual(headers['Content-Length'], '25') self.assertEqual(body, 'aaaaabbbbbcccccdddddeeeee')
def test_if_match_does_not_match(self): req = swob.Request.blank('/v1/AUTH_test/mancon/manifest', environ={'REQUEST_METHOD': 'GET'}, headers={'If-Match': 'not it'}) status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, '412 Precondition Failed') self.assertEqual(headers['Content-Length'], '0') self.assertEqual(body, '')
def test_get_suffix_range_many_segments(self): req = swob.Request.blank('/v1/AUTH_test/mancon/manifest-many-segments', environ={'REQUEST_METHOD': 'GET'}, headers={'Range': 'bytes=-5'}) with mock.patch(LIMIT, 3): status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, "200 OK") self.assertEqual(headers.get("Content-Length"), None) self.assertEqual(headers.get("Content-Range"), None) self.assertEqual(body, "aaaaabbbbbcccccdddddeeeee")
def test_error_fetching_second_segment(self): self.app.register('GET', '/v1/AUTH_test/c/seg_02', swob.HTTPForbidden, {}, None) req = swob.Request.blank('/v1/AUTH_test/mancon/manifest', environ={'REQUEST_METHOD': 'GET'}) status, headers, body, exc = self.call_dlo(req, expect_exception=True) headers = swob.HeaderKeyDict(headers) self.assertTrue(isinstance(exc, exceptions.SegmentError)) self.assertEqual(status, "200 OK") self.assertEqual(''.join(body), "aaaaa") # first segment made it out
def test_get_range_many_segments_satisfiability_unknown(self): req = swob.Request.blank('/v1/AUTH_test/mancon/manifest-many-segments', environ={'REQUEST_METHOD': 'GET'}, headers={'Range': 'bytes=10-22'}) with mock.patch(LIMIT, 3): status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, "200 OK") # this requires multiple pages of container listing, so we can't send # a Content-Length header self.assertEqual(headers.get("Content-Length"), None) self.assertEqual(body, "aaaaabbbbbcccccdddddeeeee")
def __init__(self, *args, **kwargs): swob.Response.__init__(self, *args, **kwargs) if self.etag: # add double quotes to the etag header self.etag = self.etag sw_sysmeta_headers = swob.HeaderKeyDict() sw_headers = swob.HeaderKeyDict() headers = HeaderKeyDict() for key, val in self.headers.iteritems(): _key = key.lower() if _key.startswith(sysmeta_prefix('object')) or \ _key.startswith(sysmeta_prefix('container')): sw_sysmeta_headers[key] = val else: sw_headers[key] = val # Handle swift headers for key, val in sw_headers.iteritems(): _key = key.lower() if _key.startswith('x-object-meta-'): headers['x-amz-meta-' + _key[14:]] = val elif _key in ('content-length', 'content-type', 'content-range', 'content-encoding', 'etag', 'last-modified'): headers[key] = val elif _key == 'x-container-object-count': # for ceph/s3tests headers['x-rgw-object-count'] = val elif _key == 'x-container-bytes-used': # for ceph/s3tests headers['x-rgw-bytes-used'] = val self.headers = headers # Used for pure swift header handling at the request layer self.sw_headers = sw_headers self.sysmeta_headers = sw_sysmeta_headers
def test_get_range(self): req = swob.Request.blank('/v1/AUTH_test/mancon/manifest', environ={'REQUEST_METHOD': 'GET'}, headers={'Range': 'bytes=8-17'}) status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, "206 Partial Content") self.assertEqual(headers["Content-Length"], "10") self.assertEqual(body, "bbcccccddd") expected_etag = '"%s"' % md5hex( md5hex("aaaaa") + md5hex("bbbbb") + md5hex("ccccc") + md5hex("ddddd") + md5hex("eeeee")) self.assertEqual(headers.get("Etag"), expected_etag)
def test_get_multi_range(self): # DLO doesn't support multi-range GETs. The way that you express that # in HTTP is to return a 200 response containing the whole entity. req = swob.Request.blank('/v1/AUTH_test/mancon/manifest-many-segments', environ={'REQUEST_METHOD': 'GET'}, headers={'Range': 'bytes=5-9,15-19'}) with mock.patch(LIMIT, 3): status, headers, body = self.call_dlo(req) headers = swob.HeaderKeyDict(headers) self.assertEqual(status, "200 OK") self.assertEqual(headers.get("Content-Length"), None) self.assertEqual(headers.get("Content-Range"), None) self.assertEqual(body, "aaaaabbbbbcccccdddddeeeee")