Skip to content

Commit 6db9733

Browse files
authored
Merge pull request #8975 from romayalon/romy-non-current-timestamp
NC | Add non current timestamp xattr support
2 parents 923e708 + a0df317 commit 6db9733

File tree

2 files changed

+106
-13
lines changed

2 files changed

+106
-13
lines changed

src/sdk/namespace_fs.js

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const XATTR_PART_ETAG = XATTR_NOOBAA_INTERNAL_PREFIX + 'part_etag';
6262
const XATTR_VERSION_ID = XATTR_NOOBAA_INTERNAL_PREFIX + 'version_id';
6363
const XATTR_DELETE_MARKER = XATTR_NOOBAA_INTERNAL_PREFIX + 'delete_marker';
6464
const XATTR_DIR_CONTENT = XATTR_NOOBAA_INTERNAL_PREFIX + 'dir_content';
65+
const XATTR_NON_CURRENT_TIMESTASMP = XATTR_NOOBAA_INTERNAL_PREFIX + 'non_current_timestamp';
6566
const XATTR_TAG = XATTR_NOOBAA_INTERNAL_PREFIX + 'tag.';
6667
const HIDDEN_VERSIONS_PATH = '.versions';
6768
const NULL_VERSION_ID = 'null';
@@ -1399,10 +1400,10 @@ class NamespaceFS {
13991400
fs_xattr = this._assign_md5_to_fs_xattr(digest, fs_xattr);
14001401
}
14011402
if (part_upload) {
1402-
fs_xattr = await this._assign_part_props_to_fs_xattr(fs_context, params.size, digest, offset, fs_xattr);
1403+
fs_xattr = this._assign_part_props_to_fs_xattr(params.size, digest, offset, fs_xattr);
14031404
}
14041405
if (!part_upload && (this._is_versioning_enabled() || this._is_versioning_suspended())) {
1405-
fs_xattr = await this._assign_versions_to_fs_xattr(stat, fs_xattr, undefined);
1406+
fs_xattr = this._assign_versions_to_fs_xattr(stat, fs_xattr, undefined);
14061407
}
14071408
if (!part_upload && params.storage_class) {
14081409
fs_xattr = Object.assign(fs_xattr || {}, {
@@ -1556,6 +1557,7 @@ class NamespaceFS {
15561557
await native_fs_utils._make_path_dirs(versioned_path, fs_context);
15571558
await native_fs_utils.safe_move(fs_context, latest_ver_path, versioned_path, latest_ver_info,
15581559
gpfs_options?.move_to_versions, bucket_tmp_dir_path);
1560+
await this._set_non_current_timestamp_on_past_version(fs_context, versioned_path);
15591561
}
15601562
if (is_dir_content) {
15611563
await this._move_directory_content_xattr_to_versioned_file(fs_context, key, versioned_path, latest_ver_path);
@@ -1597,17 +1599,19 @@ class NamespaceFS {
15971599
const directory_stat = await native_fs_utils.stat_ignore_enoent(fs_context, latest_version_dir_path);
15981600
const is_disabled_dir_content = directory_stat && directory_stat.xattr && directory_stat.xattr[XATTR_DIR_CONTENT];
15991601
if (is_disabled_dir_content) {
1602+
let xattr = filter_fs_xattr(directory_stat.xattr);
1603+
xattr = this._assign_non_current_timestamp_xattr(xattr);
16001604
if (this._is_versioning_enabled()) {
16011605
dbg.log1('NamespaceFS._move_to_dest_version latest object is a directory object with attributes on the directory. move the xattr to the new .version file');
16021606
if (versioned_path) {
1603-
await this.set_fs_xattr_op(fs_context, versioned_path, filter_fs_xattr(directory_stat.xattr), undefined);
1607+
await this.set_fs_xattr_op(fs_context, versioned_path, xattr, undefined);
16041608
} else {
16051609
//if no versioned_path, then we have empty content dir. need to create new .folder file
16061610
//this scenario happens only after moving from disabled to enabled mode or after upgrade. version-id is always null
16071611
versioned_path = this._get_version_path(key, NULL_VERSION_ID, true);
16081612
await native_fs_utils._make_path_dirs(versioned_path, fs_context);
16091613
//in case of empty directory object .folder of the latest doesn't exist. use 'w' to create it if its missing
1610-
await this.set_fs_xattr_op(fs_context, versioned_path, filter_fs_xattr(directory_stat.xattr), undefined, "w");
1614+
await this.set_fs_xattr_op(fs_context, versioned_path, xattr, undefined, 'w');
16111615
}
16121616
}
16131617
await this._clear_user_xattr(fs_context, latest_version_dir_path, XATTR_USER_PREFIX);
@@ -2384,17 +2388,30 @@ class NamespaceFS {
23842388
return fs_xattr;
23852389
}
23862390

2387-
async _assign_versions_to_fs_xattr(new_ver_stat, fs_xattr, delete_marker) {
2391+
/**
2392+
* _assign_versions_to_fs_xattr assigns version related xattrs to the file
2393+
* 1. assign version_id xattr
2394+
* 2. if delete_marker -
2395+
* 2.1. assigns delete_marker xattr
2396+
* 2.2. assigns non_current_timestamp xattr - on the current structure - delete marker is under .versions/
2397+
* @param {nb.NativeFSStats} new_ver_stat
2398+
* @param {nb.NativeFSXattr} fs_xattr
2399+
* @param {Boolean} [delete_marker]
2400+
* @returns {nb.NativeFSXattr}
2401+
*/
2402+
_assign_versions_to_fs_xattr(new_ver_stat, fs_xattr, delete_marker = undefined) {
23882403
fs_xattr = Object.assign(fs_xattr || {}, {
23892404
[XATTR_VERSION_ID]: this._get_version_id_by_mode(new_ver_stat)
23902405
});
23912406

2392-
if (delete_marker) fs_xattr[XATTR_DELETE_MARKER] = delete_marker;
2393-
2407+
if (delete_marker) {
2408+
fs_xattr[XATTR_DELETE_MARKER] = String(delete_marker);
2409+
fs_xattr = this._assign_non_current_timestamp_xattr(fs_xattr);
2410+
}
23942411
return fs_xattr;
23952412
}
23962413

2397-
async _assign_part_props_to_fs_xattr(fs_context, size, digest, offset, fs_xattr) {
2414+
_assign_part_props_to_fs_xattr(size, digest, offset, fs_xattr) {
23982415
fs_xattr = Object.assign(fs_xattr || {}, {
23992416
[XATTR_PART_SIZE]: size,
24002417
[XATTR_PART_OFFSET]: offset,
@@ -2404,6 +2421,39 @@ class NamespaceFS {
24042421
return fs_xattr;
24052422
}
24062423

2424+
/**
2425+
* _assign_non_current_timestamp_xattr assigns non current timestamp xattr to file xattr
2426+
* @param {nb.NativeFSXattr} fs_xattr
2427+
* @returns {nb.NativeFSXattr}
2428+
*/
2429+
_assign_non_current_timestamp_xattr(fs_xattr = {}) {
2430+
fs_xattr = Object.assign(fs_xattr, {
2431+
[XATTR_NON_CURRENT_TIMESTASMP]: String(Date.now())
2432+
});
2433+
return fs_xattr;
2434+
}
2435+
2436+
/**
2437+
* _set_non_current_timestamp_on_past_version sets non current timestamp on past version - used as a hint for lifecycle process
2438+
* @param {nb.NativeFSContext} fs_context
2439+
* @param {String} versioned_path
2440+
* @returns {Promise<Void>}
2441+
*/
2442+
async _set_non_current_timestamp_on_past_version(fs_context, versioned_path) {
2443+
const xattr = this._assign_non_current_timestamp_xattr();
2444+
await this.set_fs_xattr_op(fs_context, versioned_path, xattr);
2445+
}
2446+
2447+
/**
2448+
* _unset_non_current_timestamp_on_past_version unsets non current timestamp on past version - used as a hint for lifecycle process
2449+
* @param {nb.NativeFSContext} fs_context
2450+
* @param {String} versioned_path
2451+
* @returns {Promise<Void>}
2452+
*/
2453+
async _unset_non_current_timestamp_on_past_version(fs_context, versioned_path) {
2454+
await this._clear_user_xattr(fs_context, versioned_path, XATTR_NON_CURRENT_TIMESTASMP);
2455+
}
2456+
24072457
/**
24082458
*
24092459
* @param {*} fs_context - fs context object
@@ -3191,6 +3241,8 @@ class NamespaceFS {
31913241
const bucket_tmp_dir_path = this.get_bucket_tmpdir_full_path();
31923242
await native_fs_utils.safe_move_posix(fs_context, max_past_ver_info.path, latest_ver_path,
31933243
max_past_ver_info, bucket_tmp_dir_path);
3244+
// TODO - catch error if no such xattr
3245+
await this._unset_non_current_timestamp_on_past_version(fs_context, latest_ver_path);
31943246
break;
31953247
} catch (err) {
31963248
dbg.warn(`NamespaceFS: _promote_version_to_latest failed error: retries=${retries}`, err);
@@ -3249,8 +3301,9 @@ class NamespaceFS {
32493301
const bucket_tmp_dir_path = this.get_bucket_tmpdir_full_path();
32503302
if (this._is_versioning_enabled() || suspended_and_latest_is_not_null) {
32513303
await native_fs_utils._make_path_dirs(versioned_path, fs_context);
3252-
await native_fs_utils.safe_move_posix(fs_context, latest_ver_path, versioned_path, latest_ver_info,
3304+
await native_fs_utils.safe_move_posix(fs_context, latest_ver_path, versioned_path, latest_ver_info,
32533305
bucket_tmp_dir_path);
3306+
await this._set_non_current_timestamp_on_past_version(fs_context, versioned_path);
32543307
if (suspended_and_latest_is_not_null) {
32553308
// remove a version (or delete marker) with null version ID from .versions/ (if exists)
32563309
await this._delete_null_version_from_versions_directory(params.key, fs_context);
@@ -3333,7 +3386,7 @@ class NamespaceFS {
33333386
}
33343387
const file_path = this._get_version_path(params.key, delete_marker_version_id, is_dir);
33353388

3336-
const fs_xattr = await this._assign_versions_to_fs_xattr(stat, undefined, true);
3389+
const fs_xattr = this._assign_versions_to_fs_xattr(stat, undefined, true);
33373390
if (fs_xattr) await upload_params.target_file.replacexattr(fs_context, fs_xattr);
33383391
// create .version in case we don't have it yet
33393392
await native_fs_utils._make_path_dirs(file_path, fs_context);

src/test/unit_tests/test_bucketspace_versioning.js

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ coretest.setup({});
2525
const XATTR_INTERNAL_NOOBAA_PREFIX = 'user.noobaa.';
2626
const XATTR_VERSION_ID = XATTR_INTERNAL_NOOBAA_PREFIX + 'version_id';
2727
const XATTR_DELETE_MARKER = XATTR_INTERNAL_NOOBAA_PREFIX + 'delete_marker';
28-
const XATTR_DIR_CONTENT = XATTR_INTERNAL_NOOBAA_PREFIX + "dir_content";
28+
const XATTR_NON_CURRENT_TIMESTASMP = XATTR_INTERNAL_NOOBAA_PREFIX + 'non_current_timestamp';
29+
const XATTR_DIR_CONTENT = XATTR_INTERNAL_NOOBAA_PREFIX + 'dir_content';
2930
const XATTR_USER_PREFIX = 'user.';
3031
const NULL_VERSION_ID = 'null';
3132
const HIDDEN_VERSIONS_PATH = '.versions';
@@ -1414,6 +1415,9 @@ mocha.describe('bucketspace namespace_fs - versioning', function() {
14141415
const version_path = path.join(suspended_full_path, '.versions', key_to_delete3 + '_' + latest_dm_version);
14151416
const version_info = await stat_and_get_all(version_path, '');
14161417
assert.equal(version_info.xattr[XATTR_VERSION_ID], NULL_VERSION_ID);
1418+
// check second latest is still non current xattr
1419+
const second_latest_version_path = path.join(suspended_full_path, '.versions', key_to_delete3 + '_' + prev_dm.VersionId);
1420+
await check_non_current_xattr_exists(second_latest_version_path);
14171421
});
14181422
});
14191423

@@ -1450,6 +1454,8 @@ mocha.describe('bucketspace namespace_fs - versioning', function() {
14501454
mocha.it('delete object version id - latest - second latest is null version', async function() {
14511455
const upload_res_arr = await upload_object_versions(account_with_access, delete_object_test_bucket_reg, key1, ['null', 'regular']);
14521456
const cur_version_id1 = await stat_and_get_version_id(full_delete_path, key1);
1457+
const second_latest_version_path = path.join(full_delete_path, '.versions', key1 + '_null');
1458+
await check_non_current_xattr_exists(second_latest_version_path);
14531459

14541460
const delete_res = await account_with_access.deleteObject({
14551461
Bucket: delete_object_test_bucket_reg,
@@ -1460,6 +1466,9 @@ mocha.describe('bucketspace namespace_fs - versioning', function() {
14601466
const cur_version_id2 = await stat_and_get_version_id(full_delete_path, key1);
14611467
assert.notEqual(cur_version_id1, cur_version_id2);
14621468
assert.equal('null', cur_version_id2);
1469+
// check second latest current xattr removed
1470+
const latest_version_path = path.join(full_delete_path, key1);
1471+
await check_non_current_xattr_does_not_exist(latest_version_path);
14631472
await fs_utils.file_must_not_exist(path.join(full_delete_path, key1 + '_' + upload_res_arr[1].VersionId));
14641473
const max_version1 = await find_max_version_past(full_delete_path, key1, '');
14651474
assert.equal(max_version1, undefined);
@@ -3408,9 +3417,18 @@ function _extract_version_info_from_xattr(version_id_str) {
34083417
return { mtimeNsBigint: size_utils.string_to_bigint(arr[0], 36), ino: parseInt(arr[1], 36) };
34093418
}
34103419

3420+
/**
3421+
* version_file_exists returns path of version in .versions
3422+
* @param {String} full_path
3423+
* @param {String} key
3424+
* @param {String} dir
3425+
* @param {String} version_id
3426+
* @returns {Promise<Boolean>}
3427+
*/
34113428
async function version_file_exists(full_path, key, dir, version_id) {
34123429
const version_path = path.join(full_path, dir, '.versions', key + '_' + version_id);
34133430
await fs_utils.file_must_exist(version_path);
3431+
await check_non_current_xattr_exists(version_path, '');
34143432
return true;
34153433
}
34163434

@@ -3427,10 +3445,10 @@ async function get_obj_and_compare_data(s3, bucket_name, key, expected_body) {
34273445
return true;
34283446
}
34293447

3430-
async function is_delete_marker(full_path, dir, key, version) {
3448+
async function is_delete_marker(full_path, dir, key, version, check_non_current_version = true) {
34313449
const version_path = path.join(full_path, dir, '.versions', key + '_' + version);
34323450
const stat = await nb_native().fs.stat(DEFAULT_FS_CONFIG, version_path);
3433-
return stat && stat.xattr[XATTR_DELETE_MARKER];
3451+
return stat && stat.xattr[XATTR_DELETE_MARKER] && (check_non_current_version ? stat.xattr[XATTR_NON_CURRENT_TIMESTASMP] : true);
34343452
}
34353453

34363454
async function stat_and_get_version_id(full_path, key) {
@@ -3508,6 +3526,28 @@ async function create_empty_content_dir(fs_context, bucket_path, key) {
35083526

35093527
}
35103528

3529+
/**
3530+
* check_non_current_xattr_exists checks that the XATTR_NON_CURRENT_TIMESTASMP xattr exists
3531+
* @param {String} full_path
3532+
* @param {String} [key]
3533+
* @returns {Promise<Void>}
3534+
*/
3535+
async function check_non_current_xattr_exists(full_path, key = '') {
3536+
const stat = await stat_and_get_all(full_path, key);
3537+
assert.ok(stat.xattr[XATTR_NON_CURRENT_TIMESTASMP]);
3538+
}
3539+
3540+
/**
3541+
* check_non_current_xattr_does_not_exist checks that the XATTR_NON_CURRENT_TIMESTASMP xattr does not exist
3542+
* @param {String} full_path
3543+
* @param {String} [key]
3544+
* @returns {Promise<Void>}
3545+
*/
3546+
async function check_non_current_xattr_does_not_exist(full_path, key = '') {
3547+
const stat = await stat_and_get_all(full_path, key);
3548+
assert.equal(stat.xattr[XATTR_NON_CURRENT_TIMESTASMP], undefined);
3549+
}
3550+
35113551
async function put_allow_all_bucket_policy(s3_client, bucket) {
35123552
const policy = {
35133553
Version: '2012-10-17',

0 commit comments

Comments
 (0)