From 3ee6de408e8507c8501a9abb17fbf9e08fd65c17 Mon Sep 17 00:00:00 2001 From: Tim Burke Date: Thu, 8 Aug 2019 14:05:06 -0700 Subject: [PATCH] slo: Add X-Manifest-Etag to responses This matches the ETag of the underlying swift object, as opposed to the MD5-of-MD5s that is the large object's ETag. Change-Id: Ifab726f63739f62aeef495c970939410341694d1 --- swift/common/middleware/slo.py | 5 ++++- test/unit/common/middleware/test_slo.py | 24 +++++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/swift/common/middleware/slo.py b/swift/common/middleware/slo.py index 327b52e628..5302d8b336 100644 --- a/swift/common/middleware/slo.py +++ b/swift/common/middleware/slo.py @@ -826,6 +826,7 @@ class SloGetContext(WSGIContext): conditional_response=True) resp.headers.update({ 'Etag': '"%s"' % slo_etag, + 'X-Manifest-Etag': self._response_header_value('etag'), 'Content-Length': slo_size, }) return resp(req.environ, start_response) @@ -930,7 +931,9 @@ class SloGetContext(WSGIContext): response_headers = [] for header, value in resp_headers: lheader = header.lower() - if lheader not in ('etag', 'content-length'): + if lheader == 'etag': + response_headers.append(('X-Manifest-Etag', value)) + elif lheader != 'content-length': response_headers.append((header, value)) if lheader == SYSMETA_SLO_ETAG: diff --git a/test/unit/common/middleware/test_slo.py b/test/unit/common/middleware/test_slo.py index 025ea5ce11..48067bcf60 100644 --- a/test/unit/common/middleware/test_slo.py +++ b/test/unit/common/middleware/test_slo.py @@ -54,7 +54,9 @@ def fake_start_response(*args, **kwargs): def md5hex(s): - return hashlib.md5(s.encode('ascii')).hexdigest() + if not isinstance(s, bytes): + s = s.encode('ascii') + return hashlib.md5(s).hexdigest() class SloTestCase(unittest.TestCase): @@ -1532,12 +1534,13 @@ class TestSloHeadOldManifest(SloTestCase): 'hash': 'seg02-hash', 'content_type': 'text/plain', 'last_modified': '2013-11-19T11:33:45.137447'}]) + self.manifest_json_etag = md5hex(manifest_json) manifest_headers = { 'Content-Length': str(len(manifest_json)), 'Content-Type': 'test/data', 'X-Static-Large-Object': 'true', 'X-Object-Sysmeta-Artisanal-Etag': 'bespoke', - 'Etag': md5hex(manifest_json)} + 'Etag': self.manifest_json_etag} manifest_headers.update(getattr(self, 'extra_manifest_headers', {})) self.manifest_has_sysmeta = all(h in manifest_headers for h in ( 'X-Object-Sysmeta-Slo-Etag', 'X-Object-Sysmeta-Slo-Size')) @@ -1553,6 +1556,7 @@ class TestSloHeadOldManifest(SloTestCase): self.assertEqual(status, '200 OK') self.assertIn(('Etag', '"%s"' % self.slo_etag), headers) + self.assertIn(('X-Manifest-Etag', self.manifest_json_etag), headers) self.assertIn(('Content-Length', '300'), headers) self.assertIn(('Content-Type', 'test/data'), headers) self.assertEqual(body, b'') # it's a HEAD request, after all @@ -1829,11 +1833,12 @@ class TestSloGetManifest(SloTestCase): 'bytes': 25}, {'name': '/gettest/d_20', 'hash': md5hex("d" * 20), 'content_type': 'text/plain', 'bytes': '20'}]) + self.abcd_manifest_json_etag = md5hex(_abcd_manifest_json) self.app.register( 'GET', '/v1/AUTH_test/gettest/manifest-abcd', swob.HTTPOk, {'Content-Type': 'application/json', 'X-Static-Large-Object': 'true', - 'Etag': md5hex(_abcd_manifest_json)}, + 'Etag': self.abcd_manifest_json_etag}, _abcd_manifest_json) # A submanifest segment is created using the response headers from a @@ -1995,9 +2000,8 @@ class TestSloGetManifest(SloTestCase): status, headers, body = self.call_slo(req) self.assertEqual(status, '200 OK') - self.assertTrue( - ('Content-Type', 'application/json; charset=utf-8') in headers, - headers) + self.assertIn( + ('Content-Type', 'application/json; charset=utf-8'), headers) try: resp_data = json.loads(body) except ValueError: @@ -2010,6 +2014,7 @@ class TestSloGetManifest(SloTestCase): {'hash': md5hex('c' * 15), 'bytes': '15', 'name': '/gettest/c_15', 'content_type': 'text/plain'}], body) + self.assertIn(('Etag', md5hex(body)), headers) def test_get_nonmanifest_passthrough(self): req = Request.blank( @@ -2167,6 +2172,8 @@ class TestSloGetManifest(SloTestCase): self.assertEqual(status, '200 OK') self.assertEqual(headers['Content-Length'], '50') self.assertEqual(headers['Etag'], '"%s"' % self.manifest_abcd_etag) + self.assertEqual(headers['X-Manifest-Etag'], + self.abcd_manifest_json_etag) self.assertEqual( body, b'aaaaabbbbbbbbbbcccccccccccccccdddddddddddddddddddd') @@ -3015,6 +3022,8 @@ class TestSloGetManifest(SloTestCase): self.assertEqual(status, '200 OK') self.assertEqual(headers['Content-Length'], '50') self.assertEqual(headers['Etag'], '"%s"' % self.manifest_abcd_etag) + self.assertEqual(headers['X-Manifest-Etag'], + self.abcd_manifest_json_etag) self.assertEqual(body, b'') # Note the lack of recursive descent into manifest-bc. We know the # content-length from the outer manifest, so there's no need for any @@ -3772,11 +3781,12 @@ class TestSloConditionalGetOldManifest(SloTestCase): _bc_manifest_json) _abcd_manifest_json = json.dumps(self.slo_data) + self.abcd_manifest_json_etag = md5hex(_abcd_manifest_json) manifest_headers = { 'Content-Length': str(len(_abcd_manifest_json)), 'Content-Type': 'application/json', 'X-Static-Large-Object': 'true', - 'Etag': md5hex(_abcd_manifest_json), + 'Etag': self.abcd_manifest_json_etag, 'X-Object-Sysmeta-Custom-Etag': 'a custom etag'} manifest_headers.update(getattr(self, 'extra_manifest_headers', {})) self.manifest_has_sysmeta = all(h in manifest_headers for h in (