Merge "moving object validation checks to top of PUT method"
This commit is contained in:
@@ -15,12 +15,14 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import urllib
|
import urllib
|
||||||
|
import time
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
from ConfigParser import ConfigParser, NoSectionError, NoOptionError
|
from ConfigParser import ConfigParser, NoSectionError, NoOptionError
|
||||||
|
|
||||||
from swift.common import utils, exceptions
|
from swift.common import utils, exceptions
|
||||||
from swift.common.swob import HTTPBadRequest, HTTPLengthRequired, \
|
from swift.common.swob import HTTPBadRequest, HTTPLengthRequired, \
|
||||||
HTTPRequestEntityTooLarge, HTTPPreconditionFailed
|
HTTPRequestEntityTooLarge, HTTPPreconditionFailed, HTTPNotImplemented, \
|
||||||
|
HTTPException
|
||||||
|
|
||||||
MAX_FILE_SIZE = 5368709122
|
MAX_FILE_SIZE = 5368709122
|
||||||
MAX_META_NAME_LENGTH = 128
|
MAX_META_NAME_LENGTH = 128
|
||||||
@@ -154,24 +156,45 @@ def check_object_creation(req, object_name):
|
|||||||
a chunked request
|
a chunked request
|
||||||
:returns HTTPBadRequest: missing or bad content-type header, or
|
:returns HTTPBadRequest: missing or bad content-type header, or
|
||||||
bad metadata
|
bad metadata
|
||||||
|
:returns HTTPNotImplemented: unsupported transfer-encoding header value
|
||||||
"""
|
"""
|
||||||
if req.content_length and req.content_length > MAX_FILE_SIZE:
|
try:
|
||||||
|
ml = req.message_length()
|
||||||
|
except ValueError as e:
|
||||||
|
return HTTPBadRequest(request=req, content_type='text/plain',
|
||||||
|
body=str(e))
|
||||||
|
except AttributeError as e:
|
||||||
|
return HTTPNotImplemented(request=req, content_type='text/plain',
|
||||||
|
body=str(e))
|
||||||
|
if ml is not None and ml > MAX_FILE_SIZE:
|
||||||
return HTTPRequestEntityTooLarge(body='Your request is too large.',
|
return HTTPRequestEntityTooLarge(body='Your request is too large.',
|
||||||
request=req,
|
request=req,
|
||||||
content_type='text/plain')
|
content_type='text/plain')
|
||||||
if req.content_length is None and \
|
if req.content_length is None and \
|
||||||
req.headers.get('transfer-encoding') != 'chunked':
|
req.headers.get('transfer-encoding') != 'chunked':
|
||||||
return HTTPLengthRequired(request=req)
|
return HTTPLengthRequired(body='Missing Content-Length header.',
|
||||||
|
request=req,
|
||||||
|
content_type='text/plain')
|
||||||
|
|
||||||
if 'X-Copy-From' in req.headers and req.content_length:
|
if 'X-Copy-From' in req.headers and req.content_length:
|
||||||
return HTTPBadRequest(body='Copy requests require a zero byte body',
|
return HTTPBadRequest(body='Copy requests require a zero byte body',
|
||||||
request=req, content_type='text/plain')
|
request=req, content_type='text/plain')
|
||||||
|
|
||||||
if len(object_name) > MAX_OBJECT_NAME_LENGTH:
|
if len(object_name) > MAX_OBJECT_NAME_LENGTH:
|
||||||
return HTTPBadRequest(body='Object name length of %d longer than %d' %
|
return HTTPBadRequest(body='Object name length of %d longer than %d' %
|
||||||
(len(object_name), MAX_OBJECT_NAME_LENGTH),
|
(len(object_name), MAX_OBJECT_NAME_LENGTH),
|
||||||
request=req, content_type='text/plain')
|
request=req, content_type='text/plain')
|
||||||
|
|
||||||
if 'Content-Type' not in req.headers:
|
if 'Content-Type' not in req.headers:
|
||||||
return HTTPBadRequest(request=req, content_type='text/plain',
|
return HTTPBadRequest(request=req, content_type='text/plain',
|
||||||
body='No content type')
|
body='No content type')
|
||||||
|
|
||||||
|
try:
|
||||||
|
req = check_delete_headers(req)
|
||||||
|
except HTTPException as e:
|
||||||
|
return HTTPBadRequest(request=req, body=e.body,
|
||||||
|
content_type='text/plain')
|
||||||
|
|
||||||
if not check_utf8(req.headers['Content-Type']):
|
if not check_utf8(req.headers['Content-Type']):
|
||||||
return HTTPBadRequest(request=req, body='Invalid Content-Type',
|
return HTTPBadRequest(request=req, body='Invalid Content-Type',
|
||||||
content_type='text/plain')
|
content_type='text/plain')
|
||||||
@@ -225,6 +248,46 @@ def valid_timestamp(request):
|
|||||||
content_type='text/plain')
|
content_type='text/plain')
|
||||||
|
|
||||||
|
|
||||||
|
def check_delete_headers(request):
|
||||||
|
"""
|
||||||
|
Validate if 'x-delete' headers are have correct values
|
||||||
|
values should be positive integers and correspond to
|
||||||
|
a time in the future.
|
||||||
|
|
||||||
|
:param request: the swob request object
|
||||||
|
|
||||||
|
:returns: HTTPBadRequest in case of invalid values
|
||||||
|
or None if values are ok
|
||||||
|
"""
|
||||||
|
if 'x-delete-after' in request.headers:
|
||||||
|
try:
|
||||||
|
x_delete_after = int(request.headers['x-delete-after'])
|
||||||
|
except ValueError:
|
||||||
|
raise HTTPBadRequest(request=request,
|
||||||
|
content_type='text/plain',
|
||||||
|
body='Non-integer X-Delete-After')
|
||||||
|
actual_del_time = time.time() + x_delete_after
|
||||||
|
if actual_del_time < time.time():
|
||||||
|
raise HTTPBadRequest(request=request,
|
||||||
|
content_type='text/plain',
|
||||||
|
body='X-Delete-After in past')
|
||||||
|
request.headers['x-delete-at'] = utils.normalize_delete_at_timestamp(
|
||||||
|
actual_del_time)
|
||||||
|
|
||||||
|
if 'x-delete-at' in request.headers:
|
||||||
|
try:
|
||||||
|
x_delete_at = int(utils.normalize_delete_at_timestamp(
|
||||||
|
int(request.headers['x-delete-at'])))
|
||||||
|
except ValueError:
|
||||||
|
raise HTTPBadRequest(request=request, content_type='text/plain',
|
||||||
|
body='Non-integer X-Delete-At')
|
||||||
|
|
||||||
|
if x_delete_at < time.time():
|
||||||
|
raise HTTPBadRequest(request=request, content_type='text/plain',
|
||||||
|
body='X-Delete-At in past')
|
||||||
|
return request
|
||||||
|
|
||||||
|
|
||||||
def check_utf8(string):
|
def check_utf8(string):
|
||||||
"""
|
"""
|
||||||
Validate if a string is valid UTF-8 str or unicode and that it
|
Validate if a string is valid UTF-8 str or unicode and that it
|
||||||
|
@@ -56,7 +56,7 @@ from swift.proxy.controllers.base import Controller, delay_denial, \
|
|||||||
from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPNotFound, \
|
from swift.common.swob import HTTPAccepted, HTTPBadRequest, HTTPNotFound, \
|
||||||
HTTPPreconditionFailed, HTTPRequestEntityTooLarge, HTTPRequestTimeout, \
|
HTTPPreconditionFailed, HTTPRequestEntityTooLarge, HTTPRequestTimeout, \
|
||||||
HTTPServerError, HTTPServiceUnavailable, Request, \
|
HTTPServerError, HTTPServiceUnavailable, Request, \
|
||||||
HTTPClientDisconnect, HTTPNotImplemented
|
HTTPClientDisconnect
|
||||||
from swift.common.request_helpers import is_sys_or_user_meta, is_sys_meta, \
|
from swift.common.request_helpers import is_sys_or_user_meta, is_sys_meta, \
|
||||||
remove_items, copy_header_subset
|
remove_items, copy_header_subset
|
||||||
|
|
||||||
@@ -437,27 +437,11 @@ class ObjectController(Controller):
|
|||||||
delete_at_part = None
|
delete_at_part = None
|
||||||
delete_at_nodes = None
|
delete_at_nodes = None
|
||||||
|
|
||||||
if 'x-delete-after' in req.headers:
|
req = constraints.check_delete_headers(req)
|
||||||
try:
|
|
||||||
x_delete_after = int(req.headers['x-delete-after'])
|
|
||||||
except ValueError:
|
|
||||||
raise HTTPBadRequest(request=req, content_type='text/plain',
|
|
||||||
body='Non-integer X-Delete-After')
|
|
||||||
|
|
||||||
req.headers['x-delete-at'] = normalize_delete_at_timestamp(
|
|
||||||
time.time() + x_delete_after)
|
|
||||||
|
|
||||||
if 'x-delete-at' in req.headers:
|
if 'x-delete-at' in req.headers:
|
||||||
try:
|
x_delete_at = int(normalize_delete_at_timestamp(
|
||||||
x_delete_at = int(normalize_delete_at_timestamp(
|
int(req.headers['x-delete-at'])))
|
||||||
int(req.headers['x-delete-at'])))
|
|
||||||
except ValueError:
|
|
||||||
raise HTTPBadRequest(request=req, content_type='text/plain',
|
|
||||||
body='Non-integer X-Delete-At')
|
|
||||||
|
|
||||||
if x_delete_at < time.time():
|
|
||||||
raise HTTPBadRequest(request=req, content_type='text/plain',
|
|
||||||
body='X-Delete-At in past')
|
|
||||||
|
|
||||||
req.environ.setdefault('swift.log_info', []).append(
|
req.environ.setdefault('swift.log_info', []).append(
|
||||||
'x-delete-at:%s' % x_delete_at)
|
'x-delete-at:%s' % x_delete_at)
|
||||||
@@ -502,16 +486,23 @@ class ObjectController(Controller):
|
|||||||
if not containers:
|
if not containers:
|
||||||
return HTTPNotFound(request=req)
|
return HTTPNotFound(request=req)
|
||||||
|
|
||||||
try:
|
# Sometimes the 'content-type' header exists, but is set to None.
|
||||||
ml = req.message_length()
|
content_type_manually_set = True
|
||||||
except ValueError as e:
|
detect_content_type = \
|
||||||
return HTTPBadRequest(request=req, content_type='text/plain',
|
config_true_value(req.headers.get('x-detect-content-type'))
|
||||||
body=str(e))
|
if detect_content_type or not req.headers.get('content-type'):
|
||||||
except AttributeError as e:
|
guessed_type, _junk = mimetypes.guess_type(req.path_info)
|
||||||
return HTTPNotImplemented(request=req, content_type='text/plain',
|
req.headers['Content-Type'] = guessed_type or \
|
||||||
body=str(e))
|
'application/octet-stream'
|
||||||
if ml is not None and ml > constraints.MAX_FILE_SIZE:
|
if detect_content_type:
|
||||||
return HTTPRequestEntityTooLarge(request=req)
|
req.headers.pop('x-detect-content-type')
|
||||||
|
else:
|
||||||
|
content_type_manually_set = False
|
||||||
|
|
||||||
|
error_response = check_object_creation(req, self.object_name) or \
|
||||||
|
check_content_type(req)
|
||||||
|
if error_response:
|
||||||
|
return error_response
|
||||||
|
|
||||||
partition, nodes = obj_ring.get_nodes(
|
partition, nodes = obj_ring.get_nodes(
|
||||||
self.account_name, self.container_name, self.object_name)
|
self.account_name, self.container_name, self.object_name)
|
||||||
@@ -545,23 +536,6 @@ class ObjectController(Controller):
|
|||||||
else:
|
else:
|
||||||
req.headers['X-Timestamp'] = Timestamp(time.time()).internal
|
req.headers['X-Timestamp'] = Timestamp(time.time()).internal
|
||||||
|
|
||||||
# Sometimes the 'content-type' header exists, but is set to None.
|
|
||||||
content_type_manually_set = True
|
|
||||||
detect_content_type = \
|
|
||||||
config_true_value(req.headers.get('x-detect-content-type'))
|
|
||||||
if detect_content_type or not req.headers.get('content-type'):
|
|
||||||
guessed_type, _junk = mimetypes.guess_type(req.path_info)
|
|
||||||
req.headers['Content-Type'] = guessed_type or \
|
|
||||||
'application/octet-stream'
|
|
||||||
if detect_content_type:
|
|
||||||
req.headers.pop('x-detect-content-type')
|
|
||||||
else:
|
|
||||||
content_type_manually_set = False
|
|
||||||
|
|
||||||
error_response = check_object_creation(req, self.object_name) or \
|
|
||||||
check_content_type(req)
|
|
||||||
if error_response:
|
|
||||||
return error_response
|
|
||||||
if object_versions and not req.environ.get('swift_versioned_copy'):
|
if object_versions and not req.environ.get('swift_versioned_copy'):
|
||||||
if hresp.status_int != HTTP_NOT_FOUND:
|
if hresp.status_int != HTTP_NOT_FOUND:
|
||||||
# This is a version manifest and needs to be handled
|
# This is a version manifest and needs to be handled
|
||||||
|
@@ -1422,6 +1422,16 @@ class TestFile(Base):
|
|||||||
cfg={'no_content_length': True})
|
cfg={'no_content_length': True})
|
||||||
self.assert_status(400)
|
self.assert_status(400)
|
||||||
|
|
||||||
|
# no content-length
|
||||||
|
self.assertRaises(ResponseError, file_item.write_random, file_length,
|
||||||
|
cfg={'no_content_length': True})
|
||||||
|
self.assert_status(411)
|
||||||
|
|
||||||
|
self.assertRaises(ResponseError, file_item.write_random, file_length,
|
||||||
|
hdrs={'transfer-encoding': 'gzip,chunked'},
|
||||||
|
cfg={'no_content_length': True})
|
||||||
|
self.assert_status(501)
|
||||||
|
|
||||||
# bad request types
|
# bad request types
|
||||||
#for req in ('LICK', 'GETorHEAD_base', 'container_info',
|
#for req in ('LICK', 'GETorHEAD_base', 'container_info',
|
||||||
# 'best_response'):
|
# 'best_response'):
|
||||||
|
@@ -23,7 +23,7 @@ from test.unit import MockTrue
|
|||||||
|
|
||||||
from swift.common.swob import HTTPBadRequest, Request, HTTPException
|
from swift.common.swob import HTTPBadRequest, Request, HTTPException
|
||||||
from swift.common.http import HTTP_REQUEST_ENTITY_TOO_LARGE, \
|
from swift.common.http import HTTP_REQUEST_ENTITY_TOO_LARGE, \
|
||||||
HTTP_BAD_REQUEST, HTTP_LENGTH_REQUIRED
|
HTTP_BAD_REQUEST, HTTP_LENGTH_REQUIRED, HTTP_NOT_IMPLEMENTED
|
||||||
from swift.common import constraints, utils
|
from swift.common import constraints, utils
|
||||||
|
|
||||||
|
|
||||||
@@ -125,20 +125,68 @@ class TestConstraints(unittest.TestCase):
|
|||||||
'Content-Type': 'text/plain'}
|
'Content-Type': 'text/plain'}
|
||||||
self.assertEquals(constraints.check_object_creation(Request.blank(
|
self.assertEquals(constraints.check_object_creation(Request.blank(
|
||||||
'/', headers=headers), 'object_name'), None)
|
'/', headers=headers), 'object_name'), None)
|
||||||
|
|
||||||
headers = {'Content-Length': str(constraints.MAX_FILE_SIZE + 1),
|
headers = {'Content-Length': str(constraints.MAX_FILE_SIZE + 1),
|
||||||
'Content-Type': 'text/plain'}
|
'Content-Type': 'text/plain'}
|
||||||
self.assertEquals(constraints.check_object_creation(
|
self.assertEquals(constraints.check_object_creation(
|
||||||
Request.blank('/', headers=headers), 'object_name').status_int,
|
Request.blank('/', headers=headers), 'object_name').status_int,
|
||||||
HTTP_REQUEST_ENTITY_TOO_LARGE)
|
HTTP_REQUEST_ENTITY_TOO_LARGE)
|
||||||
|
|
||||||
headers = {'Transfer-Encoding': 'chunked',
|
headers = {'Transfer-Encoding': 'chunked',
|
||||||
'Content-Type': 'text/plain'}
|
'Content-Type': 'text/plain'}
|
||||||
self.assertEquals(constraints.check_object_creation(Request.blank(
|
self.assertEquals(constraints.check_object_creation(Request.blank(
|
||||||
'/', headers=headers), 'object_name'), None)
|
'/', headers=headers), 'object_name'), None)
|
||||||
|
|
||||||
|
headers = {'Transfer-Encoding': 'gzip',
|
||||||
|
'Content-Type': 'text/plain'}
|
||||||
|
self.assertEquals(constraints.check_object_creation(Request.blank(
|
||||||
|
'/', headers=headers), 'object_name').status_int,
|
||||||
|
HTTP_BAD_REQUEST)
|
||||||
|
|
||||||
headers = {'Content-Type': 'text/plain'}
|
headers = {'Content-Type': 'text/plain'}
|
||||||
self.assertEquals(constraints.check_object_creation(
|
self.assertEquals(constraints.check_object_creation(
|
||||||
Request.blank('/', headers=headers), 'object_name').status_int,
|
Request.blank('/', headers=headers), 'object_name').status_int,
|
||||||
HTTP_LENGTH_REQUIRED)
|
HTTP_LENGTH_REQUIRED)
|
||||||
|
|
||||||
|
headers = {'Content-Length': 'abc',
|
||||||
|
'Content-Type': 'text/plain'}
|
||||||
|
self.assertEquals(constraints.check_object_creation(Request.blank(
|
||||||
|
'/', headers=headers), 'object_name').status_int,
|
||||||
|
HTTP_BAD_REQUEST)
|
||||||
|
|
||||||
|
headers = {'Transfer-Encoding': 'gzip,chunked',
|
||||||
|
'Content-Type': 'text/plain'}
|
||||||
|
self.assertEquals(constraints.check_object_creation(Request.blank(
|
||||||
|
'/', headers=headers), 'object_name').status_int,
|
||||||
|
HTTP_NOT_IMPLEMENTED)
|
||||||
|
|
||||||
|
def test_check_object_creation_copy(self):
|
||||||
|
headers = {'Content-Length': '0',
|
||||||
|
'X-Copy-From': 'c/o2',
|
||||||
|
'Content-Type': 'text/plain'}
|
||||||
|
self.assertEquals(constraints.check_object_creation(Request.blank(
|
||||||
|
'/', headers=headers), 'object_name'), None)
|
||||||
|
|
||||||
|
headers = {'Content-Length': '1',
|
||||||
|
'X-Copy-From': 'c/o2',
|
||||||
|
'Content-Type': 'text/plain'}
|
||||||
|
self.assertEquals(constraints.check_object_creation(Request.blank(
|
||||||
|
'/', headers=headers), 'object_name').status_int,
|
||||||
|
HTTP_BAD_REQUEST)
|
||||||
|
|
||||||
|
headers = {'Transfer-Encoding': 'chunked',
|
||||||
|
'X-Copy-From': 'c/o2',
|
||||||
|
'Content-Type': 'text/plain'}
|
||||||
|
self.assertEquals(constraints.check_object_creation(Request.blank(
|
||||||
|
'/', headers=headers), 'object_name'), None)
|
||||||
|
|
||||||
|
# a content-length header is always required
|
||||||
|
headers = {'X-Copy-From': 'c/o2',
|
||||||
|
'Content-Type': 'text/plain'}
|
||||||
|
self.assertEquals(constraints.check_object_creation(Request.blank(
|
||||||
|
'/', headers=headers), 'object_name').status_int,
|
||||||
|
HTTP_LENGTH_REQUIRED)
|
||||||
|
|
||||||
def test_check_object_creation_name_length(self):
|
def test_check_object_creation_name_length(self):
|
||||||
headers = {'Transfer-Encoding': 'chunked',
|
headers = {'Transfer-Encoding': 'chunked',
|
||||||
'Content-Type': 'text/plain'}
|
'Content-Type': 'text/plain'}
|
||||||
@@ -168,6 +216,115 @@ class TestConstraints(unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, HTTP_BAD_REQUEST)
|
self.assertEquals(resp.status_int, HTTP_BAD_REQUEST)
|
||||||
self.assert_('Content-Type' in resp.body)
|
self.assert_('Content-Type' in resp.body)
|
||||||
|
|
||||||
|
def test_check_object_creation_bad_delete_headers(self):
|
||||||
|
headers = {'Transfer-Encoding': 'chunked',
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
'X-Delete-After': 'abc'}
|
||||||
|
resp = constraints.check_object_creation(
|
||||||
|
Request.blank('/', headers=headers), 'object_name')
|
||||||
|
self.assertEquals(resp.status_int, HTTP_BAD_REQUEST)
|
||||||
|
self.assert_('Non-integer X-Delete-After' in resp.body)
|
||||||
|
|
||||||
|
t = str(int(time.time() - 60))
|
||||||
|
headers = {'Transfer-Encoding': 'chunked',
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
'X-Delete-At': t}
|
||||||
|
resp = constraints.check_object_creation(
|
||||||
|
Request.blank('/', headers=headers), 'object_name')
|
||||||
|
self.assertEquals(resp.status_int, HTTP_BAD_REQUEST)
|
||||||
|
self.assert_('X-Delete-At in past' in resp.body)
|
||||||
|
|
||||||
|
def test_check_delete_headers(self):
|
||||||
|
|
||||||
|
# X-Delete-After
|
||||||
|
headers = {'X-Delete-After': '60'}
|
||||||
|
resp = constraints.check_delete_headers(
|
||||||
|
Request.blank('/', headers=headers))
|
||||||
|
self.assertTrue(isinstance(resp, Request))
|
||||||
|
self.assertTrue('x-delete-at' in resp.headers)
|
||||||
|
|
||||||
|
headers = {'X-Delete-After': 'abc'}
|
||||||
|
try:
|
||||||
|
resp = constraints.check_delete_headers(
|
||||||
|
Request.blank('/', headers=headers))
|
||||||
|
except HTTPException as e:
|
||||||
|
self.assertEquals(e.status_int, HTTP_BAD_REQUEST)
|
||||||
|
self.assertTrue('Non-integer X-Delete-After' in e.body)
|
||||||
|
else:
|
||||||
|
self.fail("Should have failed with HTTPBadRequest")
|
||||||
|
|
||||||
|
headers = {'X-Delete-After': '60.1'}
|
||||||
|
try:
|
||||||
|
resp = constraints.check_delete_headers(
|
||||||
|
Request.blank('/', headers=headers))
|
||||||
|
except HTTPException as e:
|
||||||
|
self.assertEquals(e.status_int, HTTP_BAD_REQUEST)
|
||||||
|
self.assertTrue('Non-integer X-Delete-After' in e.body)
|
||||||
|
else:
|
||||||
|
self.fail("Should have failed with HTTPBadRequest")
|
||||||
|
|
||||||
|
headers = {'X-Delete-After': '-1'}
|
||||||
|
try:
|
||||||
|
resp = constraints.check_delete_headers(
|
||||||
|
Request.blank('/', headers=headers))
|
||||||
|
except HTTPException as e:
|
||||||
|
self.assertEquals(e.status_int, HTTP_BAD_REQUEST)
|
||||||
|
self.assertTrue('X-Delete-After in past' in e.body)
|
||||||
|
else:
|
||||||
|
self.fail("Should have failed with HTTPBadRequest")
|
||||||
|
|
||||||
|
# X-Delete-At
|
||||||
|
t = str(int(time.time() + 100))
|
||||||
|
headers = {'X-Delete-At': t}
|
||||||
|
resp = constraints.check_delete_headers(
|
||||||
|
Request.blank('/', headers=headers))
|
||||||
|
self.assertTrue(isinstance(resp, Request))
|
||||||
|
self.assertTrue('x-delete-at' in resp.headers)
|
||||||
|
self.assertEquals(resp.headers.get('X-Delete-At'), t)
|
||||||
|
|
||||||
|
headers = {'X-Delete-At': 'abc'}
|
||||||
|
try:
|
||||||
|
resp = constraints.check_delete_headers(
|
||||||
|
Request.blank('/', headers=headers))
|
||||||
|
except HTTPException as e:
|
||||||
|
self.assertEquals(e.status_int, HTTP_BAD_REQUEST)
|
||||||
|
self.assertTrue('Non-integer X-Delete-At' in e.body)
|
||||||
|
else:
|
||||||
|
self.fail("Should have failed with HTTPBadRequest")
|
||||||
|
|
||||||
|
t = str(int(time.time() + 100)) + '.1'
|
||||||
|
headers = {'X-Delete-At': t}
|
||||||
|
try:
|
||||||
|
resp = constraints.check_delete_headers(
|
||||||
|
Request.blank('/', headers=headers))
|
||||||
|
except HTTPException as e:
|
||||||
|
self.assertEquals(e.status_int, HTTP_BAD_REQUEST)
|
||||||
|
self.assertTrue('Non-integer X-Delete-At' in e.body)
|
||||||
|
else:
|
||||||
|
self.fail("Should have failed with HTTPBadRequest")
|
||||||
|
|
||||||
|
t = str(int(time.time()))
|
||||||
|
headers = {'X-Delete-At': t}
|
||||||
|
try:
|
||||||
|
resp = constraints.check_delete_headers(
|
||||||
|
Request.blank('/', headers=headers))
|
||||||
|
except HTTPException as e:
|
||||||
|
self.assertEquals(e.status_int, HTTP_BAD_REQUEST)
|
||||||
|
self.assertTrue('X-Delete-At in past' in e.body)
|
||||||
|
else:
|
||||||
|
self.fail("Should have failed with HTTPBadRequest")
|
||||||
|
|
||||||
|
t = str(int(time.time() - 1))
|
||||||
|
headers = {'X-Delete-At': t}
|
||||||
|
try:
|
||||||
|
resp = constraints.check_delete_headers(
|
||||||
|
Request.blank('/', headers=headers))
|
||||||
|
except HTTPException as e:
|
||||||
|
self.assertEquals(e.status_int, HTTP_BAD_REQUEST)
|
||||||
|
self.assertTrue('X-Delete-At in past' in e.body)
|
||||||
|
else:
|
||||||
|
self.fail("Should have failed with HTTPBadRequest")
|
||||||
|
|
||||||
def test_check_mount(self):
|
def test_check_mount(self):
|
||||||
self.assertFalse(constraints.check_mount('', ''))
|
self.assertFalse(constraints.check_mount('', ''))
|
||||||
with mock.patch("swift.common.utils.ismount", MockTrue()):
|
with mock.patch("swift.common.utils.ismount", MockTrue()):
|
||||||
|
@@ -279,10 +279,7 @@ class TestObjController(unittest.TestCase):
|
|||||||
req = swob.Request.blank('/v1/a/c/o', method='POST',
|
req = swob.Request.blank('/v1/a/c/o', method='POST',
|
||||||
headers={'Content-Type': 'foo/bar',
|
headers={'Content-Type': 'foo/bar',
|
||||||
'X-Delete-After': t})
|
'X-Delete-After': t})
|
||||||
x_newest_responses = [200] * self.obj_ring.replicas + \
|
resp = req.get_response(self.app)
|
||||||
[404] * self.obj_ring.max_more_nodes
|
|
||||||
with set_http_connect(*x_newest_responses):
|
|
||||||
resp = req.get_response(self.app)
|
|
||||||
self.assertEqual(resp.status_int, 400)
|
self.assertEqual(resp.status_int, 400)
|
||||||
self.assertEqual('Non-integer X-Delete-After', resp.body)
|
self.assertEqual('Non-integer X-Delete-After', resp.body)
|
||||||
|
|
||||||
@@ -290,22 +287,16 @@ class TestObjController(unittest.TestCase):
|
|||||||
req = swob.Request.blank('/v1/a/c/o', method='POST',
|
req = swob.Request.blank('/v1/a/c/o', method='POST',
|
||||||
headers={'Content-Type': 'foo/bar',
|
headers={'Content-Type': 'foo/bar',
|
||||||
'X-Delete-After': '-60'})
|
'X-Delete-After': '-60'})
|
||||||
x_newest_responses = [200] * self.obj_ring.replicas + \
|
resp = req.get_response(self.app)
|
||||||
[404] * self.obj_ring.max_more_nodes
|
|
||||||
with set_http_connect(*x_newest_responses):
|
|
||||||
resp = req.get_response(self.app)
|
|
||||||
self.assertEqual(resp.status_int, 400)
|
self.assertEqual(resp.status_int, 400)
|
||||||
self.assertEqual('X-Delete-At in past', resp.body)
|
self.assertEqual('X-Delete-After in past', resp.body)
|
||||||
|
|
||||||
def test_POST_delete_at_non_integer(self):
|
def test_POST_delete_at_non_integer(self):
|
||||||
t = str(int(time.time() + 100)) + '.1'
|
t = str(int(time.time() + 100)) + '.1'
|
||||||
req = swob.Request.blank('/v1/a/c/o', method='POST',
|
req = swob.Request.blank('/v1/a/c/o', method='POST',
|
||||||
headers={'Content-Type': 'foo/bar',
|
headers={'Content-Type': 'foo/bar',
|
||||||
'X-Delete-At': t})
|
'X-Delete-At': t})
|
||||||
x_newest_responses = [200] * self.obj_ring.replicas + \
|
resp = req.get_response(self.app)
|
||||||
[404] * self.obj_ring.max_more_nodes
|
|
||||||
with set_http_connect(*x_newest_responses):
|
|
||||||
resp = req.get_response(self.app)
|
|
||||||
self.assertEqual(resp.status_int, 400)
|
self.assertEqual(resp.status_int, 400)
|
||||||
self.assertEqual('Non-integer X-Delete-At', resp.body)
|
self.assertEqual('Non-integer X-Delete-At', resp.body)
|
||||||
|
|
||||||
@@ -314,10 +305,7 @@ class TestObjController(unittest.TestCase):
|
|||||||
req = swob.Request.blank('/v1/a/c/o', method='POST',
|
req = swob.Request.blank('/v1/a/c/o', method='POST',
|
||||||
headers={'Content-Type': 'foo/bar',
|
headers={'Content-Type': 'foo/bar',
|
||||||
'X-Delete-At': t})
|
'X-Delete-At': t})
|
||||||
x_newest_responses = [200] * self.obj_ring.replicas + \
|
resp = req.get_response(self.app)
|
||||||
[404] * self.obj_ring.max_more_nodes
|
|
||||||
with set_http_connect(*x_newest_responses):
|
|
||||||
resp = req.get_response(self.app)
|
|
||||||
self.assertEqual(resp.status_int, 400)
|
self.assertEqual(resp.status_int, 400)
|
||||||
self.assertEqual('X-Delete-At in past', resp.body)
|
self.assertEqual('X-Delete-At in past', resp.body)
|
||||||
|
|
||||||
@@ -363,7 +351,7 @@ class TestObjController(unittest.TestCase):
|
|||||||
with set_http_connect():
|
with set_http_connect():
|
||||||
resp = req.get_response(self.app)
|
resp = req.get_response(self.app)
|
||||||
self.assertEqual(resp.status_int, 400)
|
self.assertEqual(resp.status_int, 400)
|
||||||
self.assertEqual('X-Delete-At in past', resp.body)
|
self.assertEqual('X-Delete-After in past', resp.body)
|
||||||
|
|
||||||
def test_PUT_delete_at(self):
|
def test_PUT_delete_at(self):
|
||||||
t = str(int(time.time() + 100))
|
t = str(int(time.time() + 100))
|
||||||
|
Reference in New Issue
Block a user