@@ -12,6 +12,7 @@ const { v4: uuidv4 } = require('uuid');
12
12
const P = require ( '../util/promise' ) ;
13
13
const dbg = require ( '../util/debug_module' ) ( __filename ) ;
14
14
const config = require ( '../../config' ) ;
15
+ const crypto = require ( 'crypto' ) ;
15
16
const s3_utils = require ( '../endpoint/s3/s3_utils' ) ;
16
17
const error_utils = require ( '../util/error_utils' ) ;
17
18
const stream_utils = require ( '../util/stream_utils' ) ;
@@ -293,6 +294,16 @@ function to_fs_xattr(xattr) {
293
294
return _ . mapKeys ( xattr , ( val , key ) => XATTR_USER_PREFIX + key ) ;
294
295
}
295
296
297
+ /**
298
+ * get_random_delay returns a random delay number between base + min and max
299
+ * @param {number } base
300
+ * @param {number } min
301
+ * @param {number } max
302
+ * @returns {number }
303
+ */
304
+ function get_random_delay ( base , min , max ) {
305
+ return base + crypto . randomInt ( min , max ) ;
306
+ }
296
307
297
308
/**
298
309
* @typedef {{
@@ -1391,9 +1402,10 @@ class NamespaceFS {
1391
1402
}
1392
1403
1393
1404
// 1. get latest version_id
1394
- // 2. if versioning is suspended -
1405
+ // 2. if versioning is suspended -
1395
1406
// 2.1 if version ID of the latest version is null -
1396
- // 2.1.1 remove latest version
1407
+ // 2.1.1. if it's POSIX backend - unlink the null version
1408
+ // 2.1.2. if it's GPFS backend - nothing to do, the linkatif will override it
1397
1409
// 2.2 else (version ID of the latest version is unique or there is no latest version) -
1398
1410
// 2.2.1 remove a version (or delete marker) with null version ID from .versions/ (if exists)
1399
1411
// 3. if latest version exists -
@@ -1417,10 +1429,8 @@ class NamespaceFS {
1417
1429
const versioned_path = latest_ver_info && this . _get_version_path ( key , latest_ver_info . version_id_str ) ;
1418
1430
const versioned_info = latest_ver_info && await this . _get_version_info ( fs_context , versioned_path ) ;
1419
1431
1420
- gpfs_options = is_gpfs ?
1421
- await this . _open_files_gpfs ( fs_context , new_ver_tmp_path , latest_ver_path , upload_file ,
1422
- latest_ver_info , open_mode , undefined , versioned_info ) :
1423
- undefined ;
1432
+ gpfs_options = await this . _open_files_gpfs ( fs_context , new_ver_tmp_path , latest_ver_path , upload_file ,
1433
+ latest_ver_info , open_mode , undefined , versioned_info ) ;
1424
1434
const bucket_tmp_dir_path = this . get_bucket_tmpdir_full_path ( ) ;
1425
1435
dbg . log1 ( 'Namespace_fs._move_to_dest_version:' , latest_ver_info , new_ver_info , gpfs_options ) ;
1426
1436
@@ -1439,10 +1449,10 @@ class NamespaceFS {
1439
1449
if ( latest_ver_info &&
1440
1450
( ( this . _is_versioning_enabled ( ) ) ||
1441
1451
( this . _is_versioning_suspended ( ) && latest_ver_info . version_id_str !== NULL_VERSION_ID ) ) ) {
1442
- dbg . log1 ( 'NamespaceFS._move_to_dest_version version ID of the latest version is a unique ID - the file will be moved it to .versions/ directory' ) ;
1452
+ dbg . log1 ( 'NamespaceFS._move_to_dest_version version ID of the latet version is a unique ID - the file will be moved it to .versions/ directory' ) ;
1443
1453
await native_fs_utils . _make_path_dirs ( versioned_path , fs_context ) ;
1444
1454
await native_fs_utils . safe_move ( fs_context , latest_ver_path , versioned_path , latest_ver_info ,
1445
- gpfs_options && gpfs_options . move_to_versions , bucket_tmp_dir_path ) ;
1455
+ gpfs_options ? .move_to_versions , bucket_tmp_dir_path ) ;
1446
1456
}
1447
1457
try {
1448
1458
// move new version to latest_ver_path (key path)
@@ -1460,6 +1470,7 @@ class NamespaceFS {
1460
1470
dbg . warn ( `NamespaceFS._move_to_dest_version retrying retries=${ retries } should_retry=${ should_retry } ` +
1461
1471
` new_ver_tmp_path=${ new_ver_tmp_path } latest_ver_path=${ latest_ver_path } ` , err ) ;
1462
1472
if ( ! should_retry || retries <= 0 ) throw err ;
1473
+ await P . delay ( get_random_delay ( config . NSFS_RANDOM_DELAY_BASE , 0 , 50 ) ) ;
1463
1474
} finally {
1464
1475
if ( gpfs_options ) await this . _close_files_gpfs ( fs_context , gpfs_options . move_to_dst , open_mode ) ;
1465
1476
}
@@ -2680,9 +2691,18 @@ class NamespaceFS {
2680
2691
// if stat failed, undefined will return
2681
2692
}
2682
2693
2683
- // 1. if version exists in .versions/ folder - return its path
2684
- // 2. else if version is latest version - return latest version path
2685
- // 3. throw ENOENT error
2694
+ /**
2695
+ * _find_version_path returns the path of the version
2696
+ * 1. if version_id is not defined, it returns the key file
2697
+ * 2. else,
2698
+ * 2.1. check version format
2699
+ * 2.2. check if latest version exists and it matches the versio_id parameter the latest version path returns
2700
+ * 2.3. else, return the version path under .versions/
2701
+ * @param {import('./nb').NativeFSContext } fs_context
2702
+ * @param {{key: string, version_id?: string} } params
2703
+ * @param {boolean } [return_md_path]
2704
+ * @returns {Promise<string> }
2705
+ */
2686
2706
async _find_version_path ( fs_context , { key, version_id } , return_md_path ) {
2687
2707
const cur_ver_path = return_md_path ? this . _get_file_md_path ( { key } ) : this . _get_file_path ( { key } ) ;
2688
2708
if ( ! version_id ) return cur_ver_path ;
@@ -2754,15 +2774,15 @@ class NamespaceFS {
2754
2774
2755
2775
const deleted_latest = file_path === latest_version_path ;
2756
2776
if ( deleted_latest ) {
2757
- gpfs_options = is_gpfs ?
2758
- await this . _open_files_gpfs ( fs_context , file_path , undefined , undefined , undefined , undefined , true ) :
2759
- undefined ;
2777
+ gpfs_options = await this . _open_files_gpfs ( fs_context , file_path , undefined , undefined , undefined , undefined , true ) ;
2760
2778
const bucket_tmp_dir_path = this . get_bucket_tmpdir_full_path ( ) ;
2761
2779
await native_fs_utils . safe_unlink ( fs_context , file_path , version_info ,
2762
2780
gpfs_options ?. delete_version , bucket_tmp_dir_path ) ;
2781
+ await this . _check_version_moved ( fs_context , key , version_id , file_path ) ;
2763
2782
return { ...version_info , latest : true } ;
2764
2783
} else {
2765
2784
await native_fs_utils . unlink_ignore_enoent ( fs_context , file_path ) ;
2785
+ await this . _check_version_moved ( fs_context , key , version_id , file_path ) ;
2766
2786
}
2767
2787
return version_info ;
2768
2788
} catch ( err ) {
@@ -2776,6 +2796,7 @@ class NamespaceFS {
2776
2796
// 3. concurrent delete of this version - will get ENOENT, doing a retry will return successfully
2777
2797
// after we will see that the version was already deleted
2778
2798
if ( retries <= 0 || ! native_fs_utils . should_retry_link_unlink ( is_gpfs , err ) ) throw err ;
2799
+ await P . delay ( get_random_delay ( config . NSFS_RANDOM_DELAY_BASE , 0 , 50 ) ) ;
2779
2800
} finally {
2780
2801
if ( gpfs_options ) await this . _close_files_gpfs ( fs_context , gpfs_options . delete_version , undefined , true ) ;
2781
2802
}
@@ -2886,6 +2907,7 @@ class NamespaceFS {
2886
2907
}
2887
2908
if ( err . code !== 'ENOENT' ) throw err ;
2888
2909
dbg . warn ( `NamespaceFS: _promote_version_to_latest failed retries=${ retries } ` , err ) ;
2910
+ await P . delay ( get_random_delay ( config . NSFS_RANDOM_DELAY_BASE , 0 , 50 ) ) ;
2889
2911
}
2890
2912
}
2891
2913
}
@@ -2920,19 +2942,17 @@ class NamespaceFS {
2920
2942
2921
2943
dbg . log1 ( 'Namespace_fs._delete_latest_version:' , latest_ver_info , versioned_path , versioned_info ) ;
2922
2944
if ( latest_ver_info ) {
2923
- gpfs_options = is_gpfs ?
2924
- await this . _open_files_gpfs ( fs_context , latest_ver_path ,
2925
- undefined , undefined , undefined , undefined , true , versioned_info ) :
2926
- undefined ;
2945
+ gpfs_options = await this . _open_files_gpfs ( fs_context , latest_ver_path , undefined , undefined , undefined ,
2946
+ undefined , true , versioned_info ) ;
2927
2947
2928
2948
const suspended_and_latest_is_not_null = this . _is_versioning_suspended ( ) &&
2929
2949
latest_ver_info . version_id_str !== NULL_VERSION_ID ;
2930
2950
const bucket_tmp_dir_path = this . get_bucket_tmpdir_full_path ( ) ;
2931
2951
if ( this . _is_versioning_enabled ( ) || suspended_and_latest_is_not_null ) {
2932
2952
await native_fs_utils . _make_path_dirs ( versioned_path , fs_context ) ;
2933
- await native_fs_utils . safe_move ( fs_context , latest_ver_path , versioned_path , latest_ver_info ,
2934
- gpfs_options && gpfs_options . delete_version , bucket_tmp_dir_path ) ;
2935
- if ( suspended_and_latest_is_not_null ) {
2953
+ await native_fs_utils . safe_move ( fs_context , latest_ver_path , versioned_path , latest_ver_info ,
2954
+ gpfs_options ? .delete_version , bucket_tmp_dir_path ) ;
2955
+ if ( suspended_and_latest_is_not_null ) {
2936
2956
// remove a version (or delete marker) with null version ID from .versions/ (if exists)
2937
2957
await this . _delete_null_version_from_versions_directory ( params . key , fs_context ) ;
2938
2958
}
@@ -2948,6 +2968,7 @@ class NamespaceFS {
2948
2968
retries -= 1 ;
2949
2969
if ( retries <= 0 || ! native_fs_utils . should_retry_link_unlink ( is_gpfs , err ) ) throw err ;
2950
2970
dbg . warn ( `NamespaceFS._delete_latest_version: Retrying retries=${ retries } latest_ver_path=${ latest_ver_path } ` , err ) ;
2971
+ await P . delay ( get_random_delay ( config . NSFS_RANDOM_DELAY_BASE , 0 , 50 ) ) ;
2951
2972
} finally {
2952
2973
if ( gpfs_options ) await this . _close_files_gpfs ( fs_context , gpfs_options . delete_version , undefined , true ) ;
2953
2974
}
@@ -2965,29 +2986,29 @@ class NamespaceFS {
2965
2986
// This function removes an object version or delete marker with a null version ID inside .version/ directory
2966
2987
async _delete_null_version_from_versions_directory ( key , fs_context ) {
2967
2988
const is_gpfs = native_fs_utils . _is_gpfs ( fs_context ) ;
2968
- let retries = config . NSFS_RENAME_RETRIES ;
2969
2989
const null_versioned_path = this . _get_version_path ( key , NULL_VERSION_ID ) ;
2970
2990
await this . _check_path_in_bucket_boundaries ( fs_context , null_versioned_path ) ;
2971
-
2991
+ let gpfs_options ;
2992
+ let retries = config . NSFS_RENAME_RETRIES ;
2972
2993
for ( ; ; ) {
2973
2994
try {
2974
2995
const null_versioned_path_info = await this . _get_version_info ( fs_context , null_versioned_path ) ;
2975
2996
dbg . log1 ( 'Namespace_fs._delete_null_version_from_versions_directory:' , null_versioned_path , null_versioned_path_info ) ;
2976
- if ( null_versioned_path_info ) {
2977
- const gpfs_options = is_gpfs ?
2978
- await this . _open_files_gpfs ( fs_context , null_versioned_path , undefined , undefined , undefined , undefined , true ) :
2979
- undefined ;
2980
- const bucket_tmp_dir_path = this . get_bucket_tmpdir_full_path ( ) ;
2981
- await native_fs_utils . safe_unlink ( fs_context , null_versioned_path , null_versioned_path_info ,
2982
- gpfs_options ?. delete_version , bucket_tmp_dir_path ) ;
2997
+ if ( ! null_versioned_path_info ) return ;
2983
2998
2984
- if ( gpfs_options ) await this . _close_files_gpfs ( fs_context , gpfs_options ?. delete_version , undefined , true ) ;
2985
- }
2999
+ gpfs_options = await this . _open_files_gpfs ( fs_context , null_versioned_path , undefined , undefined , undefined ,
3000
+ undefined , true ) ;
3001
+ const bucket_tmp_dir_path = this . get_bucket_tmpdir_full_path ( ) ;
3002
+ await native_fs_utils . safe_unlink ( fs_context , null_versioned_path , null_versioned_path_info ,
3003
+ gpfs_options ?. delete_version , bucket_tmp_dir_path ) ;
2986
3004
break ;
2987
3005
} catch ( err ) {
2988
3006
retries -= 1 ;
2989
3007
if ( retries <= 0 || ! native_fs_utils . should_retry_link_unlink ( is_gpfs , err ) ) throw err ;
2990
3008
dbg . warn ( `NamespaceFS._delete_null_version_from_versions_directory Retrying retries=${ retries } null_versioned_path=${ null_versioned_path } ` , err ) ;
3009
+ await P . delay ( get_random_delay ( config . NSFS_RANDOM_DELAY_BASE , 0 , 50 ) ) ;
3010
+ } finally {
3011
+ if ( gpfs_options ) await this . _close_files_gpfs ( fs_context , gpfs_options . delete_version , undefined , true ) ;
2991
3012
}
2992
3013
}
2993
3014
}
@@ -3025,6 +3046,7 @@ class NamespaceFS {
3025
3046
return delete_marker_version_id ;
3026
3047
}
3027
3048
dbg . warn ( `NamespaceFS: _create_delete_marker failed retries=${ retries } ` , err ) ;
3049
+ await P . delay ( get_random_delay ( config . NSFS_RANDOM_DELAY_BASE , 0 , 50 ) ) ;
3028
3050
} finally {
3029
3051
if ( upload_params ) await this . complete_object_upload_finally ( undefined , undefined , upload_params . target_file , fs_context ) ;
3030
3052
}
@@ -3070,6 +3092,8 @@ class NamespaceFS {
3070
3092
// eslint-disable-next-line max-params
3071
3093
async _open_files_gpfs ( fs_context , src_path , dst_path , upload_or_dir_file , dst_ver_info , open_mode , delete_version , versioned_info ) {
3072
3094
dbg . log1 ( 'Namespace_fs._open_files_gpfs:' , src_path , src_path && path . dirname ( src_path ) , dst_path , upload_or_dir_file , dst_ver_info , open_mode , delete_version , versioned_info ) ;
3095
+ const is_gpfs = native_fs_utils . _is_gpfs ( fs_context ) ;
3096
+ if ( ! is_gpfs ) return ;
3073
3097
3074
3098
let src_file ;
3075
3099
let dst_file ;
@@ -3133,6 +3157,29 @@ class NamespaceFS {
3133
3157
}
3134
3158
}
3135
3159
3160
+ /**
3161
+ * _check_version_moved recieves cur_path and checks if the version moved
3162
+ * 1. if cur_path is in .versions/ - the function will check if the verison moved to latest
3163
+ * 2. if cur_path is the latest version - the function will check if the version moved to .versions/
3164
+ * @param {import('./nb').NativeFSContext } fs_context
3165
+ * @param {string } key
3166
+ * @param {string } version_id
3167
+ * @param {string } cur_path
3168
+ */
3169
+ async _check_version_moved ( fs_context , key , version_id , cur_path ) {
3170
+ const latest_version_path = this . _get_file_path ( { key } ) ;
3171
+ const verisoned_path = this . _get_version_path ( key , version_id ) ;
3172
+ if ( cur_path === latest_version_path ) {
3173
+ const verisoned_path_info = await this . _get_version_info ( fs_context , verisoned_path ) ;
3174
+ if ( verisoned_path_info ) throw error_utils . new_error_code ( 'VERSION_MOVED' , 'version file moved from latest to .versions/, retrying' ) ;
3175
+ } else if ( cur_path === verisoned_path ) {
3176
+ const latest_ver_info = await this . _get_version_info ( fs_context , latest_version_path ) ;
3177
+ if ( latest_ver_info && latest_ver_info . version_id_str === version_id ) throw error_utils . new_error_code ( 'VERSION_MOVED' , 'version file moved from .versions/ to latest, retrying' ) ;
3178
+ } else {
3179
+ throw new RpcError ( 'INTERNAL_ERROR' , `namespace_fs._check_version_moved failed, invalid cur_path=${ cur_path } ` ) ;
3180
+ }
3181
+ }
3182
+
3136
3183
async _throw_if_storage_class_not_supported ( storage_class ) {
3137
3184
if ( ! await this . _is_storage_class_supported ( storage_class ) ) {
3138
3185
throw new S3Error ( S3Error . InvalidStorageClass ) ;
0 commit comments