Skip to content

Commit 1625468

Browse files
committed
NSFS | Versioning | Delete of partial directory of nested key results in AccessDeniedError
Signed-off-by: naveenpaul1 <napaul@redhat.com>
1 parent e1bf29e commit 1625468

File tree

4 files changed

+101
-11
lines changed

4 files changed

+101
-11
lines changed

src/sdk/namespace_fs.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1926,7 +1926,10 @@ class NamespaceFS {
19261926
await this._check_path_in_bucket_boundaries(fs_context, file_path);
19271927
dbg.log0('NamespaceFS: delete_object', file_path);
19281928
let res;
1929-
if (this._is_versioning_disabled()) {
1929+
const is_key_dir_path = await this._is_key_dir_path(fs_context, params.key);
1930+
if (this._is_versioning_disabled() || is_key_dir_path) {
1931+
// TODO- Directory object (key/) is currently can't co-exist while key (without slash) exists. see -https://github.com/noobaa/noobaa-core/issues/8320
1932+
// Also, Directory object (key/) is currently not supported combined with versioning - see - https://github.com/noobaa/noobaa-core/issues/8531
19301933
await this._delete_single_object(fs_context, file_path, params);
19311934
} else {
19321935
res = params.version_id ?
@@ -2784,6 +2787,23 @@ class NamespaceFS {
27842787
const versioned_path = this._get_version_path(key, version_id);
27852788
return versioned_path;
27862789
}
2790+
/**
2791+
* _is_key_dir_path will check if key is pointing to a directory or a file
2792+
* @param {nb.NativeFSContext} fs_context
2793+
* @param {string} key
2794+
* @returns {Promise<boolean>}
2795+
*/
2796+
async _is_key_dir_path(fs_context, key) {
2797+
try {
2798+
const key_path = path.normalize(path.join(this.bucket_path, key));
2799+
const key_stat = await nb_native().fs.stat(fs_context, key_path, { skip_user_xattr: true });
2800+
const is_dir = native_fs_utils.isDirectory(key_stat);
2801+
return is_dir;
2802+
} catch (err) {
2803+
dbg.warn('NamespaceFS._is_key_dir_path : error while getting state for key ', key, err);
2804+
}
2805+
return false;
2806+
}
27872807

27882808
_throw_if_delete_marker(stat, params) {
27892809
if (this.versioning === VERSIONING_STATUS_ENUM.VER_ENABLED || this.versioning === VERSIONING_STATUS_ENUM.VER_SUSPENDED) {

src/test/unit_tests/test_bucketspace_versioning.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const XATTR_VERSION_ID = XATTR_INTERNAL_NOOBAA_PREFIX + 'version_id';
2626
const XATTR_DELETE_MARKER = XATTR_INTERNAL_NOOBAA_PREFIX + 'delete_marker';
2727
const NULL_VERSION_ID = 'null';
2828
const HIDDEN_VERSIONS_PATH = '.versions';
29+
const NSFS_FOLDER_OBJECT_NAME = '.folder';
2930

3031
const DEFAULT_FS_CONFIG = get_process_fs_context(IS_GPFS ? 'GPFS' : '');
3132
let CORETEST_ENDPOINT;
@@ -75,6 +76,7 @@ mocha.describe('bucketspace namespace_fs - versioning', function() {
7576
const suspended_dir1_versions_path = path.join(suspended_full_path, dir1, '.versions/');
7677

7778
const dir_path_nested = 'photos/animals/January/';
79+
const dir_path_complete = 'animal/mammals/dog/';
7880
const nested_key_level3 = path.join(dir_path_nested, 'cat.jpeg');
7981

8082
mocha.before(async function() {
@@ -764,11 +766,31 @@ mocha.describe('bucketspace namespace_fs - versioning', function() {
764766

765767
mocha.it('delete object - versioning enabled - nested key (more than 1 level) - delete inside directory', async function() {
766768
const res = await s3_uid6.deleteObject({ Bucket: nested_keys_bucket_name, Key: dir_path_nested });
767-
assert.equal(res.DeleteMarker, true);
769+
// object versioning is not enabled for dir, because of this no delete_marker.
770+
assert.equal(res.DeleteMarker, undefined);
768771
const version_path_nested = path.join(nested_keys_full_path, dir_path_nested, HIDDEN_VERSIONS_PATH);
769772
const exist2 = await fs_utils.file_exists(version_path_nested);
770773
assert.ok(exist2);
771774
});
775+
776+
mocha.it('delete object - versioning enabled - nested key (more than 1 level)- delete partial directory', async function() {
777+
const parital_nested_directory = dir_path_complete.slice(0, -1); // the directory without the last slash
778+
const folder_path_nested = path.join(nested_keys_full_path, dir_path_complete, NSFS_FOLDER_OBJECT_NAME);
779+
const body_of_copied_key = 'make the lemon lemonade';
780+
await s3_uid6.putObject({ Bucket: nested_keys_bucket_name, Key: dir_path_complete, Body: body_of_copied_key });
781+
await fs_utils.file_must_exist(folder_path_nested);
782+
await s3_uid6.deleteObject({ Bucket: nested_keys_bucket_name, Key: parital_nested_directory });
783+
await fs_utils.file_must_exist(folder_path_nested);
784+
});
785+
786+
mocha.it('delete object - versioning enabled - nested key (more than 1 level)- delete complete directory', async function() {
787+
const res = await s3_uid6.deleteObject({ Bucket: nested_keys_bucket_name, Key: dir_path_complete });
788+
// object versioning is not enabled for dir, because of this no delete_marker.
789+
assert.equal(res.DeleteMarker, undefined);
790+
const folder_path_nested = path.join(nested_keys_full_path, dir_path_complete, NSFS_FOLDER_OBJECT_NAME);
791+
await fs_utils.file_must_not_exist(folder_path_nested);
792+
await fs_utils.file_must_not_exist(path.join(nested_keys_full_path, dir_path_complete));
793+
});
772794
});
773795

774796
mocha.describe('copy object (latest version) - versioning suspended - nsfs copy fallback flow', function() {

src/test/unit_tests/test_namespace_fs.js

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* Copyright (C) 2020 NooBaa */
22
/*eslint max-lines-per-function: ["error", 900]*/
3-
/*eslint max-lines: ["error", 2100]*/
3+
/*eslint max-lines: ["error", 2200]*/
44
'use strict';
55

66
const _ = require('lodash');
@@ -426,6 +426,7 @@ mocha.describe('namespace_fs', function() {
426426

427427
const dir_1 = '/a/b/c/';
428428
const dir_2 = '/a/b/';
429+
const dir_3 = '/x/y/z/';
429430
const upload_key_1 = dir_1 + 'upload_key_1';
430431
const upload_key_2 = dir_1 + 'upload_key_2';
431432
const upload_key_3 = dir_2 + 'upload_key_3';
@@ -460,35 +461,82 @@ mocha.describe('namespace_fs', function() {
460461
const source = buffer_utils.buffer_to_read_stream(data);
461462
await upload_object(ns_tmp, upload_bkt, upload_key_3, dummy_object_sdk, source);
462463
await delete_object(ns_tmp, upload_bkt, upload_key_1, dummy_object_sdk);
463-
464464
let entries;
465465
try {
466466
entries = await nb_native().fs.readdir(DEFAULT_FS_CONFIG, ns_tmp_bucket_path + dir_2);
467467
} catch (e) {
468468
assert.ifError(e);
469469
}
470-
console.log('stop when not empty - entries', entries);
471470
assert.strictEqual(entries.length, 1);
472471

473472
});
474473

474+
mocha.it('delete partial dir object without last slash version enabled - /x/y/z', async function() {
475+
const source = buffer_utils.buffer_to_read_stream(data);
476+
ns_tmp.set_bucket_versioning('ENABLED', dummy_object_sdk);
477+
await upload_object(ns_tmp, upload_bkt, dir_3, dummy_object_sdk, source);
478+
await fs_utils.file_must_exist(path.join(ns_tmp_bucket_path, '/x/y/z/', config.NSFS_FOLDER_OBJECT_NAME));
479+
const partial_dir_3 = dir_3.slice(0, -1); // the path without the last slash
480+
await delete_object(ns_tmp, upload_bkt, partial_dir_3, dummy_object_sdk);
481+
await fs_utils.file_must_exist(path.join(ns_tmp_bucket_path, '/x/y/z/', config.NSFS_FOLDER_OBJECT_NAME));
482+
await delete_object(ns_tmp, upload_bkt, dir_3, dummy_object_sdk);
483+
});
484+
485+
mocha.it('delete dir object, version enabled - /x/y/z/', async function() {
486+
const source = buffer_utils.buffer_to_read_stream(data);
487+
ns_tmp.set_bucket_versioning('ENABLED', dummy_object_sdk);
488+
await upload_object(ns_tmp, upload_bkt, dir_3, dummy_object_sdk, source);
489+
await fs_utils.file_must_exist(path.join(ns_tmp_bucket_path, '/x/y/z/', config.NSFS_FOLDER_OBJECT_NAME));
490+
const resp = await delete_object(ns_tmp, upload_bkt, dir_3, dummy_object_sdk);
491+
await fs_utils.file_must_not_exist(path.join(ns_tmp_bucket_path, '/x/y/z/', config.NSFS_FOLDER_OBJECT_NAME));
492+
await fs_utils.file_must_not_exist(path.join(ns_tmp_bucket_path, '/x/y/z/'));
493+
// object versioning is not enabled for dir, because of this no delete_marker.
494+
assert.deepEqual(resp, {});
495+
const res = await ns_tmp.list_object_versions({
496+
bucket: upload_bkt,
497+
prefix: '/x/y/'
498+
}, dummy_object_sdk);
499+
assert.equal(res.objects.length, 0);
500+
});
501+
502+
mocha.it('delete dir object, version enabled - /x/y/z/ - multiple files', async function() {
503+
const source = buffer_utils.buffer_to_read_stream(data);
504+
const source1 = buffer_utils.buffer_to_read_stream(data);
505+
ns_tmp.set_bucket_versioning('ENABLED', dummy_object_sdk);
506+
const dir_3_object = path.join(dir_3, 'obj1');
507+
await upload_object(ns_tmp, upload_bkt, dir_3, dummy_object_sdk, source);
508+
await upload_object(ns_tmp, upload_bkt, dir_3_object, dummy_object_sdk, source1);
509+
await fs_utils.file_must_exist(path.join(ns_tmp_bucket_path, dir_3, config.NSFS_FOLDER_OBJECT_NAME));
510+
await fs_utils.file_must_exist(path.join(ns_tmp_bucket_path, dir_3_object));
511+
const resp = await delete_object(ns_tmp, upload_bkt, dir_3, dummy_object_sdk);
512+
await fs_utils.file_must_not_exist(path.join(ns_tmp_bucket_path, dir_3, config.NSFS_FOLDER_OBJECT_NAME));
513+
await fs_utils.file_must_exist(path.join(ns_tmp_bucket_path, dir_3));
514+
await fs_utils.file_must_exist(path.join(ns_tmp_bucket_path, dir_3_object));
515+
// object versioning is not enabled for dir, because of this no delete_marker.
516+
assert.deepEqual(resp, {});
517+
const res = await ns_tmp.list_object_versions({
518+
bucket: upload_bkt,
519+
prefix: '/x/y/'
520+
}, dummy_object_sdk);
521+
assert.equal(res.objects.length, 1);
522+
});
523+
475524
mocha.after(async function() {
476525
let entries_before;
477526
let entries_after;
478527
try {
479528
entries_before = await nb_native().fs.readdir(DEFAULT_FS_CONFIG, ns_tmp_bucket_path);
480-
481529
const delete_res = await ns_tmp.delete_object({
482530
bucket: upload_bkt,
483531
key: upload_key_3,
484532
}, dummy_object_sdk);
485533
console.log('delete_object response', inspect(delete_res));
486-
487534
entries_after = await nb_native().fs.readdir(DEFAULT_FS_CONFIG, ns_tmp_bucket_path);
488535
} catch (e) {
489536
assert.ifError(e);
490537
}
491-
assert.strictEqual(entries_after.length, entries_before.length - 1);
538+
ns_tmp.set_bucket_versioning('DISABLED', dummy_object_sdk);
539+
assert.strictEqual(entries_after.length, entries_before.length);
492540
});
493541
});
494542

src/util/native_fs_utils.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,9 @@ async function unlink_ignore_enoent(fs_context, to_delete_path) {
254254
try {
255255
await nb_native().fs.unlink(fs_context, to_delete_path);
256256
} catch (err) {
257-
dbg.warn(`native_fs_utils.unlink_ignore_enoent unlink error: file path ${to_delete_path} error`, err);
258-
if (err.code !== 'ENOENT') throw err;
259-
dbg.warn(`native_fs_utils.unlink_ignore_enoent unlink: file ${to_delete_path} already deleted, ignoring..`);
257+
dbg.warn(`native_fs_utils.unlink_ignore_enoent unlink error: file path ${to_delete_path} error`, err, err.code, err.code !== 'EISDIR');
258+
if (err.code !== 'ENOENT' && err.code !== 'EISDIR') throw err;
259+
dbg.warn(`native_fs_utils.unlink_ignore_enoent unlink: file ${to_delete_path} already deleted or key is pointing to dir, ignoring..`);
260260
}
261261
}
262262

0 commit comments

Comments
 (0)