Delete/overwrite symlinks better

Previously, when deleting a symlink that points to an xLO, we'd clean
up the xLO's segments then delete the symlink, leaving the xLO itself
busted.

Similar trouble would come from overwriting a symlink pointing to an
xLO. Check for a Content-Location in the HEAD response and leave such
segments.

Co-Authored-By: Clay Gerrard <clay.gerrard@gmail.com>
Change-Id: I45b210cf380a68bd88187c91fa2d63a8b2bb709b
This commit is contained in:
Tim Burke 2019-07-26 22:56:08 -07:00
parent 5bd66947fc
commit 7875398746
3 changed files with 41 additions and 9 deletions

View File

@ -2070,7 +2070,8 @@ class SwiftService(object):
'status': 'skipped-changed'
})
return res
if not options['leave_segments']:
if not options['leave_segments'] and not headers.get(
'content-location'):
old_manifest = headers.get('x-object-manifest')
if is_slo:
old_slo_manifest_paths.extend(
@ -2515,7 +2516,8 @@ class SwiftService(object):
if not options['leave_segments']:
try:
headers = conn.head_object(container, obj,
headers=_headers)
headers=_headers,
query_string='symlink=get')
old_manifest = headers.get('x-object-manifest')
if config_true_value(headers.get('x-static-large-object')):
query_string = 'multipart-manifest=delete'

View File

@ -312,8 +312,8 @@ class TestServiceDelete(_TestServiceBase):
s = SwiftService()
r = s._delete_object(mock_conn, 'test_c', 'test_o', self.opts, mock_q)
mock_conn.head_object.assert_called_once_with('test_c', 'test_o',
headers={})
mock_conn.head_object.assert_called_once_with(
'test_c', 'test_o', query_string='symlink=get', headers={})
mock_conn.delete_object.assert_called_once_with(
'test_c', 'test_o', query_string=None, response_dict={},
headers={}
@ -335,7 +335,8 @@ class TestServiceDelete(_TestServiceBase):
r = s._delete_object(mock_conn, 'test_c', 'test_o', opt_c, mock_q)
mock_conn.head_object.assert_called_once_with(
'test_c', 'test_o', headers={'Skip-Middleware': 'Test'})
'test_c', 'test_o', headers={'Skip-Middleware': 'Test'},
query_string='symlink=get')
mock_conn.delete_object.assert_called_once_with(
'test_c', 'test_o', query_string=None, response_dict={},
headers={'Skip-Middleware': 'Test'}
@ -362,8 +363,8 @@ class TestServiceDelete(_TestServiceBase):
r = s._delete_object(mock_conn, 'test_c', 'test_o', self.opts, mock_q)
after = time.time()
mock_conn.head_object.assert_called_once_with('test_c', 'test_o',
headers={})
mock_conn.head_object.assert_called_once_with(
'test_c', 'test_o', query_string='symlink=get', headers={})
mock_conn.delete_object.assert_called_once_with(
'test_c', 'test_o', query_string=None, response_dict={},
headers={}
@ -389,8 +390,8 @@ class TestServiceDelete(_TestServiceBase):
s = SwiftService()
r = s._delete_object(mock_conn, 'test_c', 'test_o', self.opts, mock_q)
mock_conn.head_object.assert_called_once_with('test_c', 'test_o',
headers={})
mock_conn.head_object.assert_called_once_with(
'test_c', 'test_o', query_string='symlink=get', headers={})
mock_conn.delete_object.assert_called_once_with(
'test_c', 'test_o',
query_string='multipart-manifest=delete',

View File

@ -832,6 +832,35 @@ class TestShell(unittest.TestCase):
sorted(connection.return_value.delete_object.mock_calls)
)
@mock.patch('swiftclient.service.Connection')
def test_upload_over_symlink_to_slo(self, connection):
# Upload delete existing segments
connection.return_value.head_container.return_value = {
'x-storage-policy': 'one'}
connection.return_value.attempts = 0
connection.return_value.head_object.side_effect = [
{'x-static-large-object': 'true',
'content-location': '/v1/a/c/manifest',
'content-length': '2'},
]
connection.return_value.get_object.return_value = (
{'content-location': '/v1/a/c/manifest'},
b'[{"name": "container1/old_seg1"},'
b' {"name": "container2/old_seg2"}]'
)
connection.return_value.put_object.return_value = EMPTY_ETAG
connection.return_value.delete_object.return_value = None
argv = ["", "upload", "container", self.tmpfile]
swiftclient.shell.main(argv)
connection.return_value.put_object.assert_called_with(
'container',
self.tmpfile.lstrip('/'),
mock.ANY,
content_length=0,
headers={'x-object-meta-mtime': mock.ANY},
response_dict={})
self.assertEqual([], connection.return_value.delete_object.mock_calls)
@mock.patch('swiftclient.service.Connection')
def test_upload_leave_slo_segments(self, connection):
# Test upload overwriting a manifest respects --leave-segments