From fcb6e4cd3aa6896976733c57c683592358e4c6f0 Mon Sep 17 00:00:00 2001 From: Kota Tsuyuzaki Date: Thu, 2 Jul 2015 00:35:02 -0700 Subject: [PATCH] Last-Modified header support on HEAD/GET container This patch enables to show a x-put-timestamp as a last-modified header in container-server. Note that the last-modified header will be changed only when a request for container (PUT container or POST container) comes into Swift. i.e. some requests for objects (e.g. PUT object, POST object) will never affect the last-modified value but only when using python-swiftclient like as "swift upload", the last-modified will be close to the upload time because python-swiftclient will make a PUT container request for "swift upload" each time. Change-Id: I9971bf90d24eee8921f67c02b7e2c80fd8995623 --- swift/container/server.py | 6 ++++- test/functional/swift_test_client.py | 3 ++- test/functional/tests.py | 39 ++++++++++++++++++++++++++++ test/unit/container/test_server.py | 38 ++++++++++++++++++++++----- test/unit/proxy/test_server.py | 6 +++++ 5 files changed, 84 insertions(+), 8 deletions(-) diff --git a/swift/container/server.py b/swift/container/server.py index a77dadcd22..a531d2fd70 100644 --- a/swift/container/server.py +++ b/swift/container/server.py @@ -17,6 +17,7 @@ import json import os import time import traceback +import math from swift import gettext_ as _ from xml.etree.cElementTree import Element, SubElement, tostring @@ -433,7 +434,9 @@ class ContainerController(BaseStorageServer): if value != '' and (key.lower() in self.save_headers or is_sys_or_user_meta('container', key))) headers['Content-Type'] = out_content_type - return HTTPNoContent(request=req, headers=headers, charset='utf-8') + resp = HTTPNoContent(request=req, headers=headers, charset='utf-8') + resp.last_modified = math.ceil(float(headers['X-PUT-Timestamp'])) + return resp def update_data_record(self, record): """ @@ -530,6 +533,7 @@ class ContainerController(BaseStorageServer): if not container_list: return HTTPNoContent(request=req, headers=resp_headers) ret.body = '\n'.join(rec[0] for rec in container_list) + '\n' + ret.last_modified = math.ceil(float(resp_headers['X-PUT-Timestamp'])) return ret @public diff --git a/test/functional/swift_test_client.py b/test/functional/swift_test_client.py index 3c9bb0b5e2..67a393c9ef 100644 --- a/test/functional/swift_test_client.py +++ b/test/functional/swift_test_client.py @@ -613,7 +613,8 @@ class Container(Base): if self.conn.response.status == 204: required_fields = [['bytes_used', 'x-container-bytes-used'], - ['object_count', 'x-container-object-count']] + ['object_count', 'x-container-object-count'], + ['last_modified', 'last-modified']] optional_fields = [ ['versions', 'x-versions-location'], ['tempurl_key', 'x-container-meta-temp-url-key'], diff --git a/test/functional/tests.py b/test/functional/tests.py index d083aa10c2..c43a60c1f3 100644 --- a/test/functional/tests.py +++ b/test/functional/tests.py @@ -808,6 +808,45 @@ class TestContainer(Base): file_item = cont.file(Utils.create_name()) file_item.write_random() + def testContainerLastModified(self): + container = self.env.account.container(Utils.create_name()) + self.assertTrue(container.create()) + info = container.info() + t0 = info['last_modified'] + # last modified header is in date format which supports in second + # so we need to wait to increment a sec in the header. + eventlet.sleep(1) + + # POST container change last modified timestamp + self.assertTrue( + container.update_metadata({'x-container-meta-japan': 'mitaka'})) + info = container.info() + t1 = info['last_modified'] + self.assertNotEqual(t0, t1) + eventlet.sleep(1) + + # PUT container (overwrite) also change last modified + self.assertTrue(container.create()) + info = container.info() + t2 = info['last_modified'] + self.assertNotEqual(t1, t2) + eventlet.sleep(1) + + # PUT object doesn't change container last modified timestamp + obj = container.file(Utils.create_name()) + self.assertTrue( + obj.write("aaaaa", hdrs={'Content-Type': 'text/plain'})) + info = container.info() + t3 = info['last_modified'] + self.assertEqual(t2, t3) + + # POST object also doesn't change container last modified timestamp + self.assertTrue( + obj.sync_metadata({'us': 'austin'})) + info = container.info() + t4 = info['last_modified'] + self.assertEqual(t2, t4) + class TestContainerUTF8(Base2, TestContainer): set_up = False diff --git a/test/unit/container/test_server.py b/test/unit/container/test_server.py index 706f3c3366..4fc4f32c78 100644 --- a/test/unit/container/test_server.py +++ b/test/unit/container/test_server.py @@ -180,12 +180,7 @@ class TestContainerController(unittest.TestCase): self.assertEqual(response.headers.get('x-container-write'), 'account:user') - def test_HEAD(self): - start = int(time.time()) - ts = (Timestamp(t).internal for t in itertools.count(start)) - req = Request.blank('/sda1/p/a/c', method='PUT', headers={ - 'x-timestamp': next(ts)}) - req.get_response(self.controller) + def _test_head(self, start, ts): req = Request.blank('/sda1/p/a/c', method='HEAD') response = req.get_response(self.controller) self.assertEqual(response.status_int, 204) @@ -213,6 +208,9 @@ class TestContainerController(unittest.TestCase): self.assertTrue(created_at_header >= start) self.assertEqual(response.headers['x-put-timestamp'], Timestamp(start).normal) + self.assertEqual( + response.last_modified.strftime("%a, %d %b %Y %H:%M:%S GMT"), + time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(start))) # backend headers self.assertEqual(int(response.headers @@ -227,6 +225,22 @@ class TestContainerController(unittest.TestCase): self.assertEqual(response.headers['x-backend-status-changed-at'], Timestamp(start).internal) + def test_HEAD(self): + start = int(time.time()) + ts = (Timestamp(t).internal for t in itertools.count(start)) + req = Request.blank('/sda1/p/a/c', method='PUT', headers={ + 'x-timestamp': next(ts)}) + req.get_response(self.controller) + self._test_head(Timestamp(start), ts) + + def test_HEAD_timestamp_with_offset(self): + start = int(time.time()) + ts = (Timestamp(t, offset=1).internal for t in itertools.count(start)) + req = Request.blank('/sda1/p/a/c', method='PUT', headers={ + 'x-timestamp': next(ts)}) + req.get_response(self.controller) + self._test_head(Timestamp(start, offset=1), ts) + def test_HEAD_not_found(self): req = Request.blank('/sda1/p/a/c', method='HEAD') resp = req.get_response(self.controller) @@ -241,6 +255,8 @@ class TestContainerController(unittest.TestCase): Timestamp(0).internal) self.assertEqual(resp.headers['x-backend-delete-timestamp'], Timestamp(0).internal) + self.assertIsNone(resp.last_modified) + for header in ('x-container-object-count', 'x-container-bytes-used', 'x-timestamp', 'x-put-timestamp'): self.assertEqual(resp.headers[header], None) @@ -264,6 +280,7 @@ class TestContainerController(unittest.TestCase): req = Request.blank('/sda1/p/a/c', method=method) resp = req.get_response(self.controller) self.assertEqual(resp.status_int, 404) + self.assertIsNone(resp.last_modified) # backend headers self.assertEqual(int(resp.headers[ 'X-Backend-Storage-Policy-Index']), @@ -2021,6 +2038,9 @@ class TestContainerController(unittest.TestCase): environ={'REQUEST_METHOD': 'GET'}) resp = req.get_response(self.controller) self.assertEqual(resp.content_type, 'application/json') + self.assertEqual( + resp.last_modified.strftime("%a, %d %b %Y %H:%M:%S GMT"), + time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(0))) self.assertEqual(json.loads(resp.body), json_body) self.assertEqual(resp.charset, 'utf-8') @@ -2082,6 +2102,9 @@ class TestContainerController(unittest.TestCase): environ={'REQUEST_METHOD': 'GET'}) resp = req.get_response(self.controller) self.assertEqual(resp.content_type, 'text/plain') + self.assertEqual( + resp.last_modified.strftime("%a, %d %b %Y %H:%M:%S GMT"), + time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(0))) self.assertEqual(resp.body, plain_body) self.assertEqual(resp.charset, 'utf-8') @@ -2212,6 +2235,9 @@ class TestContainerController(unittest.TestCase): environ={'REQUEST_METHOD': 'GET'}) resp = req.get_response(self.controller) self.assertEqual(resp.content_type, 'application/xml') + self.assertEqual( + resp.last_modified.strftime("%a, %d %b %Y %H:%M:%S GMT"), + time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(0))) self.assertEqual(resp.body, xml_body) self.assertEqual(resp.charset, 'utf-8') diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py index 7aac742c19..da28804b53 100644 --- a/test/unit/proxy/test_server.py +++ b/test/unit/proxy/test_server.py @@ -6346,6 +6346,9 @@ class TestContainerController(unittest.TestCase): if expected < 400: self.assertIn('x-works', res.headers) self.assertEqual(res.headers['x-works'], 'yes') + if expected < 300: + self.assertIn('last-modified', res.headers) + self.assertEqual(res.headers['last-modified'], '1') if c_expected: self.assertIn('container/a/c', infocache) self.assertEqual( @@ -6371,6 +6374,9 @@ class TestContainerController(unittest.TestCase): if expected < 400: self.assertTrue('x-works' in res.headers) self.assertEqual(res.headers['x-works'], 'yes') + if expected < 300: + self.assertIn('last-modified', res.headers) + self.assertEqual(res.headers['last-modified'], '1') if c_expected: self.assertIn('container/a/c', infocache) self.assertEqual(