Skip to content

Commit 1284792

Browse files
authored
Merge pull request #8808 from romayalon/romy-delete-last-object-dir-obj-size-0
NSFS | Fix delete last object deletes directory object if its size is 0
2 parents 4bb9068 + 79ed147 commit 1284792

File tree

2 files changed

+66
-8
lines changed

2 files changed

+66
-8
lines changed

src/sdk/namespace_fs.js

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2618,12 +2618,32 @@ class NamespaceFS {
26182618
}
26192619
}
26202620

2621+
/**
2622+
* _delete_path_dirs deletes all the paths in the hierarchy that are empty after a successful delete
2623+
* if the original file_path to be deleted is a regular object which means file_path is not a directory and it's not a directory object path -
2624+
* before deletion of the parent directory -
2625+
* if the parent directory is a directory object (has CONTENT_DIR xattr) - stop the deletion loop
2626+
* else - delete the directory - if dir is not empty it will stop at the first non empty dir
2627+
* NOTE - the directory object check is needed because when object size is zero we won't create a .folder file and the dir will be empty
2628+
* therefore the deletion will succeed although we shouldn't delete the directory object
2629+
* @param {String} file_path
2630+
* @param {nb.NativeFSContext} fs_context
2631+
*/
26212632
async _delete_path_dirs(file_path, fs_context) {
26222633
try {
2623-
let dir = path.dirname(file_path);
2624-
while (dir !== this.bucket_path) {
2625-
await nb_native().fs.rmdir(fs_context, dir);
2626-
dir = path.dirname(dir);
2634+
let dir_path = path.dirname(file_path);
2635+
const deleted_file_is_dir = file_path.endsWith('/');
2636+
const deleted_file_is_dir_object = file_path.endsWith(config.NSFS_FOLDER_OBJECT_NAME);
2637+
let should_check_dir_path_is_content_dir = !deleted_file_is_dir && !deleted_file_is_dir_object;
2638+
while (dir_path !== this.bucket_path) {
2639+
if (should_check_dir_path_is_content_dir) {
2640+
const dir_stat = await nb_native().fs.stat(fs_context, dir_path);
2641+
const file_is_disabled_dir_content = dir_stat.xattr && dir_stat.xattr[XATTR_DIR_CONTENT] !== undefined;
2642+
if (file_is_disabled_dir_content) break;
2643+
}
2644+
await nb_native().fs.rmdir(fs_context, dir_path);
2645+
dir_path = path.dirname(dir_path);
2646+
should_check_dir_path_is_content_dir = true;
26272647
}
26282648
} catch (err) {
26292649
if (err.code !== 'ENOTEMPTY' &&
@@ -3068,10 +3088,18 @@ class NamespaceFS {
30683088
return res;
30693089
}
30703090

3071-
// delete version_id -
3072-
// 1. get version info, if it's empty - return
3073-
// 2. unlink key
3074-
// 3. if version is latest version - promote second latest -> latest
3091+
/**
3092+
* delete version_id does the following -
3093+
* 1. get version info, if it's empty - return
3094+
* 2. unlink key
3095+
* 3. if version is latest version - promote second latest -> latest
3096+
* 4. if it's the latest version - delete the directory hirerachy of the key if it's empty
3097+
* if it's a past version - delete .versions/ and the directory hirerachy if it's empty
3098+
* @param {nb.NativeFSContext} fs_context
3099+
* @param {String} file_path
3100+
* @param {Object} params
3101+
* @returns {Promise<{deleted_delete_marker?: string, version_id?: string}>}
3102+
*/
30753103
async _delete_version_id(fs_context, file_path, params) {
30763104
// TODO optimization - GPFS link overrides, no need to unlink before promoting, but if there is nothing to promote we should unlink
30773105
const del_obj_version_info = await this._delete_single_object_versioned(fs_context, params.key, params.version_id);

src/test/unit_tests/test_namespace_fs.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,36 @@ mocha.describe('namespace_fs folders tests', function() {
12011201

12021202
});
12031203

1204+
mocha.it('delete inner object in directory object size 0 - no .folder file but directory still exists', async function() {
1205+
const inner_key = '/inner_obj';
1206+
const key = upload_key_3 + inner_key;
1207+
const source = buffer_utils.buffer_to_read_stream(data);
1208+
await upload_object(ns_tmp, upload_bkt, key, dummy_object_sdk, source);
1209+
const p1 = path.join(ns_tmp_bucket_path, upload_key_3);
1210+
const p2 = path.join(ns_tmp_bucket_path, key);
1211+
await fs_utils.file_must_not_exist(path.join(p1, config.NSFS_FOLDER_OBJECT_NAME));
1212+
const full_xattr1 = await get_xattr(p1);
1213+
assert.deepEqual(full_xattr1, { ...user_md_and_dir_content_xattr, [XATTR_DIR_CONTENT]: obj_sizes_map[upload_key_3] });
1214+
await ns_tmp.delete_object({ bucket: upload_bkt, key: key }, dummy_object_sdk);
1215+
await fs_utils.file_must_exist(p1);
1216+
await fs_utils.file_must_not_exist(p2);
1217+
});
1218+
1219+
mocha.it('delete inner directory object size > 0 in directory object size 0 - no .folder file but directory still exists', async function() {
1220+
const inner_dir_obj_key = '/inner_dir_obj_key';
1221+
const key = upload_key_3 + inner_dir_obj_key;
1222+
const source = buffer_utils.buffer_to_read_stream(data);
1223+
await upload_object(ns_tmp, upload_bkt, key, dummy_object_sdk, source);
1224+
const p1 = path.join(ns_tmp_bucket_path, upload_key_3);
1225+
const p2 = path.join(ns_tmp_bucket_path, key);
1226+
await fs_utils.file_must_not_exist(path.join(p1, config.NSFS_FOLDER_OBJECT_NAME));
1227+
const full_xattr1 = await get_xattr(p1);
1228+
assert.deepEqual(full_xattr1, { ...user_md_and_dir_content_xattr, [XATTR_DIR_CONTENT]: obj_sizes_map[upload_key_3] });
1229+
await ns_tmp.delete_object({ bucket: upload_bkt, key: key }, dummy_object_sdk);
1230+
await fs_utils.file_must_exist(p1);
1231+
await fs_utils.file_must_not_exist(p2);
1232+
});
1233+
12041234
mocha.it('delete object content 0 - no .folder file', async function() {
12051235
const p1 = path.join(ns_tmp_bucket_path, upload_key_3);
12061236
const full_xattr1 = await get_xattr(p1);

0 commit comments

Comments
 (0)