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
This commit is contained in:
Tim Burke 2019-08-08 14:05:06 -07:00
parent 8d3d21e668
commit 3ee6de408e
2 changed files with 21 additions and 8 deletions

View File

@ -826,6 +826,7 @@ class SloGetContext(WSGIContext):
conditional_response=True) conditional_response=True)
resp.headers.update({ resp.headers.update({
'Etag': '"%s"' % slo_etag, 'Etag': '"%s"' % slo_etag,
'X-Manifest-Etag': self._response_header_value('etag'),
'Content-Length': slo_size, 'Content-Length': slo_size,
}) })
return resp(req.environ, start_response) return resp(req.environ, start_response)
@ -930,7 +931,9 @@ class SloGetContext(WSGIContext):
response_headers = [] response_headers = []
for header, value in resp_headers: for header, value in resp_headers:
lheader = header.lower() 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)) response_headers.append((header, value))
if lheader == SYSMETA_SLO_ETAG: if lheader == SYSMETA_SLO_ETAG:

View File

@ -54,7 +54,9 @@ def fake_start_response(*args, **kwargs):
def md5hex(s): 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): class SloTestCase(unittest.TestCase):
@ -1532,12 +1534,13 @@ class TestSloHeadOldManifest(SloTestCase):
'hash': 'seg02-hash', 'hash': 'seg02-hash',
'content_type': 'text/plain', 'content_type': 'text/plain',
'last_modified': '2013-11-19T11:33:45.137447'}]) 'last_modified': '2013-11-19T11:33:45.137447'}])
self.manifest_json_etag = md5hex(manifest_json)
manifest_headers = { manifest_headers = {
'Content-Length': str(len(manifest_json)), 'Content-Length': str(len(manifest_json)),
'Content-Type': 'test/data', 'Content-Type': 'test/data',
'X-Static-Large-Object': 'true', 'X-Static-Large-Object': 'true',
'X-Object-Sysmeta-Artisanal-Etag': 'bespoke', 'X-Object-Sysmeta-Artisanal-Etag': 'bespoke',
'Etag': md5hex(manifest_json)} 'Etag': self.manifest_json_etag}
manifest_headers.update(getattr(self, 'extra_manifest_headers', {})) manifest_headers.update(getattr(self, 'extra_manifest_headers', {}))
self.manifest_has_sysmeta = all(h in manifest_headers for h in ( self.manifest_has_sysmeta = all(h in manifest_headers for h in (
'X-Object-Sysmeta-Slo-Etag', 'X-Object-Sysmeta-Slo-Size')) 'X-Object-Sysmeta-Slo-Etag', 'X-Object-Sysmeta-Slo-Size'))
@ -1553,6 +1556,7 @@ class TestSloHeadOldManifest(SloTestCase):
self.assertEqual(status, '200 OK') self.assertEqual(status, '200 OK')
self.assertIn(('Etag', '"%s"' % self.slo_etag), headers) 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-Length', '300'), headers)
self.assertIn(('Content-Type', 'test/data'), headers) self.assertIn(('Content-Type', 'test/data'), headers)
self.assertEqual(body, b'') # it's a HEAD request, after all self.assertEqual(body, b'') # it's a HEAD request, after all
@ -1829,11 +1833,12 @@ class TestSloGetManifest(SloTestCase):
'bytes': 25}, 'bytes': 25},
{'name': '/gettest/d_20', 'hash': md5hex("d" * 20), {'name': '/gettest/d_20', 'hash': md5hex("d" * 20),
'content_type': 'text/plain', 'bytes': '20'}]) 'content_type': 'text/plain', 'bytes': '20'}])
self.abcd_manifest_json_etag = md5hex(_abcd_manifest_json)
self.app.register( self.app.register(
'GET', '/v1/AUTH_test/gettest/manifest-abcd', 'GET', '/v1/AUTH_test/gettest/manifest-abcd',
swob.HTTPOk, {'Content-Type': 'application/json', swob.HTTPOk, {'Content-Type': 'application/json',
'X-Static-Large-Object': 'true', 'X-Static-Large-Object': 'true',
'Etag': md5hex(_abcd_manifest_json)}, 'Etag': self.abcd_manifest_json_etag},
_abcd_manifest_json) _abcd_manifest_json)
# A submanifest segment is created using the response headers from a # 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) status, headers, body = self.call_slo(req)
self.assertEqual(status, '200 OK') self.assertEqual(status, '200 OK')
self.assertTrue( self.assertIn(
('Content-Type', 'application/json; charset=utf-8') in headers, ('Content-Type', 'application/json; charset=utf-8'), headers)
headers)
try: try:
resp_data = json.loads(body) resp_data = json.loads(body)
except ValueError: except ValueError:
@ -2010,6 +2014,7 @@ class TestSloGetManifest(SloTestCase):
{'hash': md5hex('c' * 15), 'bytes': '15', 'name': '/gettest/c_15', {'hash': md5hex('c' * 15), 'bytes': '15', 'name': '/gettest/c_15',
'content_type': 'text/plain'}], 'content_type': 'text/plain'}],
body) body)
self.assertIn(('Etag', md5hex(body)), headers)
def test_get_nonmanifest_passthrough(self): def test_get_nonmanifest_passthrough(self):
req = Request.blank( req = Request.blank(
@ -2167,6 +2172,8 @@ class TestSloGetManifest(SloTestCase):
self.assertEqual(status, '200 OK') self.assertEqual(status, '200 OK')
self.assertEqual(headers['Content-Length'], '50') self.assertEqual(headers['Content-Length'], '50')
self.assertEqual(headers['Etag'], '"%s"' % self.manifest_abcd_etag) self.assertEqual(headers['Etag'], '"%s"' % self.manifest_abcd_etag)
self.assertEqual(headers['X-Manifest-Etag'],
self.abcd_manifest_json_etag)
self.assertEqual( self.assertEqual(
body, b'aaaaabbbbbbbbbbcccccccccccccccdddddddddddddddddddd') body, b'aaaaabbbbbbbbbbcccccccccccccccdddddddddddddddddddd')
@ -3015,6 +3022,8 @@ class TestSloGetManifest(SloTestCase):
self.assertEqual(status, '200 OK') self.assertEqual(status, '200 OK')
self.assertEqual(headers['Content-Length'], '50') self.assertEqual(headers['Content-Length'], '50')
self.assertEqual(headers['Etag'], '"%s"' % self.manifest_abcd_etag) self.assertEqual(headers['Etag'], '"%s"' % self.manifest_abcd_etag)
self.assertEqual(headers['X-Manifest-Etag'],
self.abcd_manifest_json_etag)
self.assertEqual(body, b'') self.assertEqual(body, b'')
# Note the lack of recursive descent into manifest-bc. We know the # 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 # content-length from the outer manifest, so there's no need for any
@ -3772,11 +3781,12 @@ class TestSloConditionalGetOldManifest(SloTestCase):
_bc_manifest_json) _bc_manifest_json)
_abcd_manifest_json = json.dumps(self.slo_data) _abcd_manifest_json = json.dumps(self.slo_data)
self.abcd_manifest_json_etag = md5hex(_abcd_manifest_json)
manifest_headers = { manifest_headers = {
'Content-Length': str(len(_abcd_manifest_json)), 'Content-Length': str(len(_abcd_manifest_json)),
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Static-Large-Object': 'true', 'X-Static-Large-Object': 'true',
'Etag': md5hex(_abcd_manifest_json), 'Etag': self.abcd_manifest_json_etag,
'X-Object-Sysmeta-Custom-Etag': 'a custom etag'} 'X-Object-Sysmeta-Custom-Etag': 'a custom etag'}
manifest_headers.update(getattr(self, 'extra_manifest_headers', {})) manifest_headers.update(getattr(self, 'extra_manifest_headers', {}))
self.manifest_has_sysmeta = all(h in manifest_headers for h in ( self.manifest_has_sysmeta = all(h in manifest_headers for h in (