Skip to content

Commit 2f21700

Browse files
committed
NSFS | Fix delete last object deletes directory object if its size is 0
Signed-off-by: Romy <35330373+romayalon@users.noreply.github.com> (cherry picked from commit 79ed147)
1 parent 5aeb709 commit 2f21700

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
@@ -2565,12 +2565,32 @@ class NamespaceFS {
25652565
}
25662566
}
25672567

2568+
/**
2569+
* _delete_path_dirs deletes all the paths in the hierarchy that are empty after a successful delete
2570+
* 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 -
2571+
* before deletion of the parent directory -
2572+
* if the parent directory is a directory object (has CONTENT_DIR xattr) - stop the deletion loop
2573+
* else - delete the directory - if dir is not empty it will stop at the first non empty dir
2574+
* 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
2575+
* therefore the deletion will succeed although we shouldn't delete the directory object
2576+
* @param {String} file_path
2577+
* @param {nb.NativeFSContext} fs_context
2578+
*/
25682579
async _delete_path_dirs(file_path, fs_context) {
25692580
try {
2570-
let dir = path.dirname(file_path);
2571-
while (dir !== this.bucket_path) {
2572-
await nb_native().fs.rmdir(fs_context, dir);
2573-
dir = path.dirname(dir);
2581+
let dir_path = path.dirname(file_path);
2582+
const deleted_file_is_dir = file_path.endsWith('/');
2583+
const deleted_file_is_dir_object = file_path.endsWith(config.NSFS_FOLDER_OBJECT_NAME);
2584+
let should_check_dir_path_is_content_dir = !deleted_file_is_dir && !deleted_file_is_dir_object;
2585+
while (dir_path !== this.bucket_path) {
2586+
if (should_check_dir_path_is_content_dir) {
2587+
const dir_stat = await nb_native().fs.stat(fs_context, dir_path);
2588+
const file_is_disabled_dir_content = dir_stat.xattr && dir_stat.xattr[XATTR_DIR_CONTENT] !== undefined;
2589+
if (file_is_disabled_dir_content) break;
2590+
}
2591+
await nb_native().fs.rmdir(fs_context, dir_path);
2592+
dir_path = path.dirname(dir_path);
2593+
should_check_dir_path_is_content_dir = true;
25742594
}
25752595
} catch (err) {
25762596
if (err.code !== 'ENOTEMPTY' &&
@@ -2999,10 +3019,18 @@ class NamespaceFS {
29993019
return res;
30003020
}
30013021

3002-
// delete version_id -
3003-
// 1. get version info, if it's empty - return
3004-
// 2. unlink key
3005-
// 3. if version is latest version - promote second latest -> latest
3022+
/**
3023+
* delete version_id does the following -
3024+
* 1. get version info, if it's empty - return
3025+
* 2. unlink key
3026+
* 3. if version is latest version - promote second latest -> latest
3027+
* 4. if it's the latest version - delete the directory hirerachy of the key if it's empty
3028+
* if it's a past version - delete .versions/ and the directory hirerachy if it's empty
3029+
* @param {nb.NativeFSContext} fs_context
3030+
* @param {String} file_path
3031+
* @param {Object} params
3032+
* @returns {Promise<{deleted_delete_marker?: string, version_id?: string}>}
3033+
*/
30063034
async _delete_version_id(fs_context, file_path, params) {
30073035
// TODO optimization - GPFS link overrides, no need to unlink before promoting, but if there is nothing to promote we should unlink
30083036
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
@@ -1200,6 +1200,36 @@ mocha.describe('namespace_fs folders tests', function() {
12001200

12011201
});
12021202

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

0 commit comments

Comments
 (0)