change the last-modified header value with valid one

the Last-Modified header in Response didn't have a suitable
value - an integer part of object's timestamp.
This leads that the the if-[un]modified-since header with the
value from last-modified is always earlier than timestamp
and results the content is always newer than value of these
conditional headers.
Patched code returns math.ceil() of object's timestamp
in Last-Modified header so the later conditional header works
correctly

Closes-Bug: #1248818
Change-Id: I1ece7d008551bf989da74d23f0ed6307c45c5436
This commit is contained in:
Kiyoung Jung 2013-11-07 04:45:27 +00:00
parent deaddf003b
commit d69e013519
6 changed files with 140 additions and 7 deletions
swift
obj
proxy/controllers
test

@ -21,6 +21,7 @@ import os
import time import time
import traceback import traceback
import socket import socket
import math
from datetime import datetime from datetime import datetime
from swift import gettext_ as _ from swift import gettext_ as _
from hashlib import md5 from hashlib import md5
@ -483,7 +484,7 @@ class ObjectController(object):
except (OverflowError, ValueError): except (OverflowError, ValueError):
# catches timestamps before the epoch # catches timestamps before the epoch
return HTTPPreconditionFailed(request=request) return HTTPPreconditionFailed(request=request)
if if_modified_since and file_x_ts_utc < if_modified_since: if if_modified_since and file_x_ts_utc <= if_modified_since:
return HTTPNotModified(request=request) return HTTPNotModified(request=request)
keep_cache = (self.keep_cache_private or keep_cache = (self.keep_cache_private or
('X-Auth-Token' not in request.headers and ('X-Auth-Token' not in request.headers and
@ -499,7 +500,7 @@ class ObjectController(object):
key.lower() in self.allowed_headers: key.lower() in self.allowed_headers:
response.headers[key] = value response.headers[key] = value
response.etag = metadata['ETag'] response.etag = metadata['ETag']
response.last_modified = file_x_ts_flt response.last_modified = math.ceil(file_x_ts_flt)
response.content_length = obj_size response.content_length = obj_size
try: try:
response.content_encoding = metadata[ response.content_encoding = metadata[
@ -541,7 +542,7 @@ class ObjectController(object):
response.headers[key] = value response.headers[key] = value
response.etag = metadata['ETag'] response.etag = metadata['ETag']
ts = metadata['X-Timestamp'] ts = metadata['X-Timestamp']
response.last_modified = float(ts) response.last_modified = math.ceil(float(ts))
# Needed for container sync feature # Needed for container sync feature
response.headers['X-Timestamp'] = ts response.headers['X-Timestamp'] = ts
response.content_length = int(metadata['Content-Length']) response.content_length = int(metadata['Content-Length'])

@ -28,6 +28,7 @@ import itertools
import mimetypes import mimetypes
import re import re
import time import time
import math
from datetime import datetime from datetime import datetime
from swift import gettext_ as _ from swift import gettext_ as _
from urllib import unquote, quote from urllib import unquote, quote
@ -1169,7 +1170,7 @@ class ObjectController(Controller):
resp.headers['X-Copied-From-Last-Modified'] = \ resp.headers['X-Copied-From-Last-Modified'] = \
source_resp.headers['last-modified'] source_resp.headers['last-modified']
copy_headers_into(req, resp) copy_headers_into(req, resp)
resp.last_modified = float(req.headers['X-Timestamp']) resp.last_modified = math.ceil(float(req.headers['X-Timestamp']))
return resp return resp
@public @public

@ -697,7 +697,8 @@ class File(Base):
else: else:
raise RuntimeError raise RuntimeError
def write(self, data='', hdrs={}, parms={}, callback=None, cfg={}): def write(self, data='', hdrs={}, parms={}, callback=None, cfg={},
return_resp=False):
block_size = 2 ** 20 block_size = 2 ** 20
if isinstance(data, file): if isinstance(data, file):
@ -736,6 +737,9 @@ class File(Base):
self.md5 = self.compute_md5sum(data) self.md5 = self.compute_md5sum(data)
if return_resp:
return self.conn.response
return True return True
def write_random(self, size=None, hdrs={}, parms={}, cfg={}): def write_random(self, size=None, hdrs={}, parms={}, cfg={}):
@ -744,3 +748,12 @@ class File(Base):
raise ResponseError(self.conn.response) raise ResponseError(self.conn.response)
self.md5 = self.compute_md5sum(StringIO.StringIO(data)) self.md5 = self.compute_md5sum(StringIO.StringIO(data))
return data return data
def write_random_return_resp(self, size=None, hdrs={}, parms={}, cfg={}):
data = self.random_data(size)
resp = self.write(data, hdrs=hdrs, parms=parms, cfg=cfg,
return_resp=True)
if not resp:
raise ResponseError(self.conn.response)
self.md5 = self.compute_md5sum(StringIO.StringIO(data))
return resp

@ -1620,6 +1620,28 @@ class TestFileComparison(Base):
self.assertRaises(ResponseError, file_item.read, hdrs=hdrs) self.assertRaises(ResponseError, file_item.read, hdrs=hdrs)
self.assert_status(412) self.assert_status(412)
def testLastModified(self):
file_name = Utils.create_name()
content_type = Utils.create_name()
file = self.env.container.file(file_name)
file.content_type = content_type
resp = file.write_random_return_resp(self.env.file_size)
put_last_modified = resp.getheader('last-modified')
file = self.env.container.file(file_name)
info = file.info()
self.assert_('last_modified' in info)
last_modified = info['last_modified']
self.assertEqual(put_last_modified, info['last_modified'])
hdrs = {'If-Modified-Since': last_modified}
self.assertRaises(ResponseError, file.read, hdrs=hdrs)
self.assert_status(304)
hdrs = {'If-Unmodified-Since': last_modified}
self.assert_(file.read(hdrs=hdrs))
class TestFileComparisonUTF8(Base2, TestFileComparison): class TestFileComparisonUTF8(Base2, TestFileComparison):
set_up = False set_up = False

@ -21,6 +21,7 @@ import operator
import os import os
import mock import mock
import unittest import unittest
import math
from shutil import rmtree from shutil import rmtree
from StringIO import StringIO from StringIO import StringIO
from time import gmtime, strftime, time from time import gmtime, strftime, time
@ -672,7 +673,8 @@ class TestObjectController(unittest.TestCase):
self.assertEquals(resp.headers['content-type'], 'application/x-test') self.assertEquals(resp.headers['content-type'], 'application/x-test')
self.assertEquals( self.assertEquals(
resp.headers['last-modified'], resp.headers['last-modified'],
strftime('%a, %d %b %Y %H:%M:%S GMT', gmtime(float(timestamp)))) strftime('%a, %d %b %Y %H:%M:%S GMT',
gmtime(math.ceil(float(timestamp)))))
self.assertEquals(resp.headers['etag'], self.assertEquals(resp.headers['etag'],
'"0b4c12d7e0a73840c1c4f148fda3b037"') '"0b4c12d7e0a73840c1c4f148fda3b037"')
self.assertEquals(resp.headers['x-object-meta-1'], 'One') self.assertEquals(resp.headers['x-object-meta-1'], 'One')
@ -774,7 +776,8 @@ class TestObjectController(unittest.TestCase):
self.assertEquals(resp.headers['content-type'], 'application/x-test') self.assertEquals(resp.headers['content-type'], 'application/x-test')
self.assertEquals( self.assertEquals(
resp.headers['last-modified'], resp.headers['last-modified'],
strftime('%a, %d %b %Y %H:%M:%S GMT', gmtime(float(timestamp)))) strftime('%a, %d %b %Y %H:%M:%S GMT',
gmtime(math.ceil(float(timestamp)))))
self.assertEquals(resp.headers['etag'], self.assertEquals(resp.headers['etag'],
'"0b4c12d7e0a73840c1c4f148fda3b037"') '"0b4c12d7e0a73840c1c4f148fda3b037"')
self.assertEquals(resp.headers['x-object-meta-1'], 'One') self.assertEquals(resp.headers['x-object-meta-1'], 'One')
@ -976,6 +979,37 @@ class TestObjectController(unittest.TestCase):
resp = req.get_response(self.object_controller) resp = req.get_response(self.object_controller)
self.assertEquals(resp.status_int, 304) self.assertEquals(resp.status_int, 304)
req = Request.blank('/sda1/p/a/c/o',
environ={'REQUEST_METHOD': 'HEAD'})
resp = req.get_response(self.object_controller)
since = resp.headers['Last-Modified']
self.assertEquals(since, strftime('%a, %d %b %Y %H:%M:%S GMT',
gmtime(math.ceil(float(timestamp)))))
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
headers={'If-Modified-Since': since})
resp = self.object_controller.GET(req)
self.assertEquals(resp.status_int, 304)
timestamp = normalize_timestamp(int(time()))
req = Request.blank('/sda1/p/a/c/o2',
environ={'REQUEST_METHOD': 'PUT'},
headers={
'X-Timestamp': timestamp,
'Content-Type': 'application/octet-stream',
'Content-Length': '4'})
req.body = 'test'
resp = req.get_response(self.object_controller)
self.assertEquals(resp.status_int, 201)
since = strftime('%a, %d %b %Y %H:%M:%S GMT',
gmtime(float(timestamp)))
req = Request.blank('/sda1/p/a/c/o2',
environ={'REQUEST_METHOD': 'GET'},
headers={'If-Modified-Since': since})
resp = req.get_response(self.object_controller)
self.assertEquals(resp.status_int, 304)
def test_GET_if_unmodified_since(self): def test_GET_if_unmodified_since(self):
timestamp = normalize_timestamp(time()) timestamp = normalize_timestamp(time())
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
@ -1012,6 +1046,18 @@ class TestObjectController(unittest.TestCase):
resp = req.get_response(self.object_controller) resp = req.get_response(self.object_controller)
self.assertEquals(resp.status_int, 200) self.assertEquals(resp.status_int, 200)
req = Request.blank('/sda1/p/a/c/o',
environ={'REQUEST_METHOD': 'HEAD'})
resp = req.get_response(self.object_controller)
since = resp.headers['Last-Modified']
self.assertEquals(since, strftime('%a, %d %b %Y %H:%M:%S GMT',
gmtime(math.ceil(float(timestamp)))))
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'},
headers={'If-Unmodified-Since': since})
resp = self.object_controller.GET(req)
self.assertEquals(resp.status_int, 200)
def test_GET_quarantine(self): def test_GET_quarantine(self):
# Test swift.obj.server.ObjectController.GET # Test swift.obj.server.ObjectController.GET
timestamp = normalize_timestamp(time()) timestamp = normalize_timestamp(time())

@ -998,6 +998,56 @@ class TestObjectController(unittest.TestCase):
finally: finally:
swift.proxy.controllers.obj.MAX_FILE_SIZE = MAX_FILE_SIZE swift.proxy.controllers.obj.MAX_FILE_SIZE = MAX_FILE_SIZE
def test_PUT_last_modified(self):
prolis = _test_sockets[0]
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
fd = sock.makefile()
fd.write('PUT /v1/a/c/o.last_modified HTTP/1.1\r\n'
'Host: localhost\r\nConnection: close\r\n'
'X-Storage-Token: t\r\nContent-Length: 0\r\n\r\n')
fd.flush()
headers = readuntil2crlfs(fd)
exp = 'HTTP/1.1 201'
lm_hdr = 'Last-Modified: '
self.assertEqual(headers[:len(exp)], exp)
last_modified_put = [line for line in headers.split('\r\n')
if lm_hdr in line][0][len(lm_hdr):]
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
fd = sock.makefile()
fd.write('HEAD /v1/a/c/o.last_modified HTTP/1.1\r\n'
'Host: localhost\r\nConnection: close\r\n'
'X-Storage-Token: t\r\n\r\n')
fd.flush()
headers = readuntil2crlfs(fd)
exp = 'HTTP/1.1 200'
self.assertEqual(headers[:len(exp)], exp)
last_modified_head = [line for line in headers.split('\r\n')
if lm_hdr in line][0][len(lm_hdr):]
self.assertEqual(last_modified_put, last_modified_head)
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
fd = sock.makefile()
fd.write('GET /v1/a/c/o.last_modified HTTP/1.1\r\n'
'Host: localhost\r\nConnection: close\r\n'
'If-Modified-Since: %s\r\n'
'X-Storage-Token: t\r\n\r\n' % last_modified_put)
fd.flush()
headers = readuntil2crlfs(fd)
exp = 'HTTP/1.1 304'
self.assertEqual(headers[:len(exp)], exp)
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
fd = sock.makefile()
fd.write('GET /v1/a/c/o.last_modified HTTP/1.1\r\n'
'Host: localhost\r\nConnection: close\r\n'
'If-Unmodified-Since: %s\r\n'
'X-Storage-Token: t\r\n\r\n' % last_modified_put)
fd.flush()
headers = readuntil2crlfs(fd)
exp = 'HTTP/1.1 200'
self.assertEqual(headers[:len(exp)], exp)
def test_expirer_DELETE_on_versioned_object(self): def test_expirer_DELETE_on_versioned_object(self):
test_errors = [] test_errors = []