From 9fd537a08245cf6a34c1abdf8b7c42bfd3669c74 Mon Sep 17 00:00:00 2001 From: Tim Burke Date: Tue, 8 Dec 2015 10:45:07 -0800 Subject: Use application/directory content-type for dir markers Previously, we were using a content-type of text/directory, but that is already defined in RFC 2425 and doesn't reflect our usage: The text/directory Content-Type is defined for holding a variety of directory information, for example, name, or email address, or logo. (From there it goes on to describe a superset of the vCard format defined in RFC 2426.) application/directory, on the other hand, is used by Static Web [1] and is used by cloudfuse [2]. Seems like as sane a choice as any to standardize on. [1] https://github.com/openstack/swift/blob/2.5.0/swift/common/middleware/staticweb.py#L71-L75 [2] https://github.com/redbo/cloudfuse/blob/1.0/README#L105-L106 Change-Id: I19e30484270886292d83f50e7ee997b6e1623ec7 --- swiftclient/service.py | 15 +++-- tests/unit/test_service.py | 136 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 6 deletions(-) diff --git a/swiftclient/service.py b/swiftclient/service.py index 99c833e..232815f 100644 --- a/swiftclient/service.py +++ b/swiftclient/service.py @@ -189,6 +189,10 @@ _default_local_options = { } POLICY = 'X-Storage-Policy' +KNOWN_DIR_MARKERS = ( + 'application/directory', # Preferred + 'text/directory', # Historically relevant +) def get_from_queue(q, timeout=864000): @@ -1130,9 +1134,8 @@ class SwiftService(object): fp = None try: - content_type = headers.get('content-type') - if (content_type and - content_type.split(';', 1)[0] == 'text/directory'): + content_type = headers.get('content-type', '').split(';', 1)[0] + if content_type in KNOWN_DIR_MARKERS: make_dir = not no_file and out_file != "-" if make_dir and not isdir(path): mkdirs(path) @@ -1590,12 +1593,12 @@ class SwiftService(object): if options['changed']: try: headers = conn.head_object(container, obj) - ct = headers.get('content-type') + ct = headers.get('content-type', '').split(';', 1)[0] cl = int(headers.get('content-length')) et = headers.get('etag') mt = headers.get('x-object-meta-mtime') - if (ct.split(';', 1)[0] == 'text/directory' and + if (ct in KNOWN_DIR_MARKERS and cl == 0 and et == EMPTY_ETAG and mt == put_headers['x-object-meta-mtime']): @@ -1614,7 +1617,7 @@ class SwiftService(object): return res try: conn.put_object(container, obj, '', content_length=0, - content_type='text/directory', + content_type=KNOWN_DIR_MARKERS[0], headers=put_headers, response_dict=results_dict) res.update({ diff --git a/tests/unit/test_service.py b/tests/unit/test_service.py index e9310aa..8651b05 100644 --- a/tests/unit/test_service.py +++ b/tests/unit/test_service.py @@ -1348,6 +1348,142 @@ class TestServiceUpload(_TestServiceBase): errors.append(msg) self.assertFalse(errors, "\nERRORS:\n%s" % '\n'.join(errors)) + def test_create_dir_marker_job_unchanged(self): + mock_conn = mock.Mock() + mock_conn.head_object.return_value = { + 'content-type': 'application/directory', + 'content-length': '0', + 'x-object-meta-mtime': '1.234000', + 'etag': md5().hexdigest()} + + s = SwiftService() + with mock.patch('swiftclient.service.get_conn', + return_value=mock_conn): + with mock.patch('swiftclient.service.getmtime', + return_value=1.234): + r = s._create_dir_marker_job(conn=mock_conn, + container='test_c', + obj='test_o', + path='test', + options={'changed': True, + 'skip_identical': True, + 'leave_segments': True, + 'header': '', + 'segment_size': 10}) + self.assertEqual({ + 'action': 'create_dir_marker', + 'container': 'test_c', + 'object': 'test_o', + 'path': 'test', + 'headers': {'x-object-meta-mtime': '1.234000'}, + # NO response dict! + 'success': True, + }, r) + self.assertEqual([], mock_conn.put_object.mock_calls) + + def test_create_dir_marker_job_unchanged_old_type(self): + mock_conn = mock.Mock() + mock_conn.head_object.return_value = { + 'content-type': 'text/directory', + 'content-length': '0', + 'x-object-meta-mtime': '1.000000', + 'etag': md5().hexdigest()} + + s = SwiftService() + with mock.patch('swiftclient.service.get_conn', + return_value=mock_conn): + with mock.patch('swiftclient.service.time', + return_value=1.234): + r = s._create_dir_marker_job(conn=mock_conn, + container='test_c', + obj='test_o', + options={'changed': True, + 'skip_identical': True, + 'leave_segments': True, + 'header': '', + 'segment_size': 10}) + self.assertEqual({ + 'action': 'create_dir_marker', + 'container': 'test_c', + 'object': 'test_o', + 'path': None, + 'headers': {'x-object-meta-mtime': '1.000000'}, + # NO response dict! + 'success': True, + }, r) + self.assertEqual([], mock_conn.put_object.mock_calls) + + def test_create_dir_marker_job_overwrites_bad_type(self): + mock_conn = mock.Mock() + mock_conn.head_object.return_value = { + 'content-type': 'text/plain', + 'content-length': '0', + 'x-object-meta-mtime': '1.000000', + 'etag': md5().hexdigest()} + + s = SwiftService() + with mock.patch('swiftclient.service.get_conn', + return_value=mock_conn): + with mock.patch('swiftclient.service.time', + return_value=1.234): + r = s._create_dir_marker_job(conn=mock_conn, + container='test_c', + obj='test_o', + options={'changed': True, + 'skip_identical': True, + 'leave_segments': True, + 'header': '', + 'segment_size': 10}) + self.assertEqual({ + 'action': 'create_dir_marker', + 'container': 'test_c', + 'object': 'test_o', + 'path': None, + 'headers': {'x-object-meta-mtime': '1.000000'}, + 'response_dict': {}, + 'success': True, + }, r) + self.assertEqual([mock.call( + 'test_c', 'test_o', '', + content_length=0, + content_type='application/directory', + headers={'x-object-meta-mtime': '1.000000'}, + response_dict={})], mock_conn.put_object.mock_calls) + + def test_create_dir_marker_job_missing(self): + mock_conn = mock.Mock() + mock_conn.head_object.side_effect = \ + ClientException('Not Found', http_status=404) + + s = SwiftService() + with mock.patch('swiftclient.service.get_conn', + return_value=mock_conn): + with mock.patch('swiftclient.service.time', + return_value=1.234): + r = s._create_dir_marker_job(conn=mock_conn, + container='test_c', + obj='test_o', + options={'changed': True, + 'skip_identical': True, + 'leave_segments': True, + 'header': '', + 'segment_size': 10}) + self.assertEqual({ + 'action': 'create_dir_marker', + 'container': 'test_c', + 'object': 'test_o', + 'path': None, + 'headers': {'x-object-meta-mtime': '1.000000'}, + 'response_dict': {}, + 'success': True, + }, r) + self.assertEqual([mock.call( + 'test_c', 'test_o', '', + content_length=0, + content_type='application/directory', + headers={'x-object-meta-mtime': '1.000000'}, + response_dict={})], mock_conn.put_object.mock_calls) + class TestServiceDownload(_TestServiceBase): -- cgit v1.2.1