Skip to content

Commit 5b6ed9f

Browse files
committed
add complete support for tape reclaim
Signed-off-by: Utkarsh Srivastava <[email protected]> add all kind of tests for lifecycle Signed-off-by: Utkarsh Srivastava <[email protected]>
1 parent bfc97e5 commit 5b6ed9f

File tree

9 files changed

+327
-9
lines changed

9 files changed

+327
-9
lines changed

config.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,9 @@ config.NSFS_GLACIER_DMAPI_PMIG_DAYS = config.S3_RESTORE_REQUEST_MAX_DAYS;
936936
// accidental blocking reads from happening.
937937
config.NSFS_GLACIER_DMAPI_FINALIZE_RESTORE_ENABLE = false;
938938

939+
config.NSFS_GLACIER_DMAPI_ENABLE_TAPE_RECLAIM = false;
940+
config.NSFS_GLACIER_RECLAIM_INTERVAL = 15 * 60 * 1000;
941+
939942
config.NSFS_STATFS_CACHE_SIZE = 10000;
940943
config.NSFS_STATFS_CACHE_EXPIRY_MS = 1 * 1000;
941944

@@ -979,7 +982,7 @@ config.NSFS_GLACIER_MIGRATE_LOG_THRESHOLD = 50 * 1024;
979982
config.NSFS_GLACIER_METRICS_STATFS_PATHS = [];
980983
config.NSFS_GLACIER_METRICS_STATFS_INTERVAL = 60 * 1000; // Refresh statfs value every minute
981984

982-
/**
985+
/**
983986
* NSFS_GLACIER_RESERVED_BUCKET_TAGS defines an object of bucket tags which will be reserved
984987
* by the system and PUT operations for them via S3 API would be limited - as in they would be
985988
* mutable only if specified and only under certain conditions.
@@ -990,7 +993,7 @@ config.NSFS_GLACIER_METRICS_STATFS_INTERVAL = 60 * 1000; // Refresh statfs value
990993
* default: any,
991994
* event: boolean
992995
* }>}
993-
*
996+
*
994997
* @example
995998
* {
996999
'deep-archive-copies': {

src/cmd/manage_nsfs.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,9 @@ async function manage_glacier_operations(action, argv) {
879879
case GLACIER_ACTIONS.EXPIRY:
880880
await manage_nsfs_glacier.process_expiry();
881881
break;
882+
case GLACIER_ACTIONS.RECLAIM:
883+
await manage_nsfs_glacier.process_reclaim();
884+
break;
882885
default:
883886
throw_cli_error(ManageCLIError.InvalidGlacierOperation);
884887
}

src/manage_nsfs/manage_nsfs_constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const GLACIER_ACTIONS = Object.freeze({
2626
MIGRATE: 'migrate',
2727
RESTORE: 'restore',
2828
EXPIRY: 'expiry',
29+
RECLAIM: 'reclaim',
2930
});
3031

3132
const DIAGNOSE_ACTIONS = Object.freeze({
@@ -72,6 +73,7 @@ const VALID_OPTIONS_GLACIER = {
7273
'migrate': new Set([ CONFIG_ROOT_FLAG]),
7374
'restore': new Set([ CONFIG_ROOT_FLAG]),
7475
'expiry': new Set([ CONFIG_ROOT_FLAG]),
76+
'reclaim': new Set([ CONFIG_ROOT_FLAG]),
7577
};
7678

7779
const VALID_OPTIONS_DIAGNOSE = {

src/manage_nsfs/manage_nsfs_glacier.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@ async function process_expiry() {
5858
}
5959
}
6060

61+
async function process_reclaim() {
62+
const fs_context = native_fs_utils.get_process_fs_context();
63+
const backend = Glacier.getBackend();
64+
65+
if (
66+
await backend.low_free_space() ||
67+
!(await time_exceeded(fs_context, config.NSFS_GLACIER_RECLAIM_INTERVAL, Glacier.RECLAIM_TIMESTAMP_FILE))
68+
) return;
69+
70+
await backend.perform(prepare_galcier_fs_context(fs_context), "RECLAIM");
71+
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, Glacier.RECLAIM_TIMESTAMP_FILE);
72+
await record_current_time(fs_context, timestamp_file_path);
73+
}
6174

6275
/**
6376
* time_exceeded returns true if the time between last run recorded in the given
@@ -129,3 +142,4 @@ function prepare_galcier_fs_context(fs_context) {
129142
exports.process_migrations = process_migrations;
130143
exports.process_restores = process_restores;
131144
exports.process_expiry = process_expiry;
145+
exports.process_reclaim = process_reclaim;

src/native/fs/fs_napi.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@
5050
#define GPFS_DMAPI_DOT_IBMOBJ_EA "IBMObj"
5151
#define GPFS_DMAPI_DOT_IBMPMIG_EA "IBMPMig"
5252
#define GPFS_DMAPI_DOT_IBMTPS_EA "IBMTPS"
53+
#define GPFS_DMAPI_DOT_IBMUID_EA "IBMUID"
5354
#define GPFS_DMAPI_XATTR_TAPE_INDICATOR GPFS_DMAPI_XATTR_PREFIX "." GPFS_DMAPI_DOT_IBMOBJ_EA
5455
#define GPFS_DMAPI_XATTR_TAPE_PREMIG GPFS_DMAPI_XATTR_PREFIX "." GPFS_DMAPI_DOT_IBMPMIG_EA
5556
#define GPFS_DMAPI_XATTR_TAPE_TPS GPFS_DMAPI_XATTR_PREFIX "." GPFS_DMAPI_DOT_IBMTPS_EA
57+
#define GPFS_DMAPI_XATTR_TAPE_UID GPFS_DMAPI_XATTR_PREFIX "." GPFS_DMAPI_DOT_IBMUID_EA
5658

5759
// This macro should be used after openning a file
5860
// it will autoclose the file using AutoCloser and will throw an error in case of failures
@@ -255,6 +257,7 @@ const static std::vector<std::string> GPFS_DMAPI_XATTRS{
255257
GPFS_DMAPI_XATTR_TAPE_INDICATOR,
256258
GPFS_DMAPI_XATTR_TAPE_PREMIG,
257259
GPFS_DMAPI_XATTR_TAPE_TPS,
260+
GPFS_DMAPI_XATTR_TAPE_UID,
258261
};
259262
const static std::vector<std::string> USER_XATTRS{
260263
"user.content_type",

src/sdk/glacier.js

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Glacier {
2121
static MIGRATE_TIMESTAMP_FILE = 'migrate.timestamp';
2222
static RESTORE_TIMESTAMP_FILE = 'restore.timestamp';
2323
static EXPIRY_TIMESTAMP_FILE = 'expiry.timestamp';
24+
static RECLAIM_TIMESTAMP_FILE = 'reclaim.timestamp';
2425

2526
/**
2627
* XATTR_RESTORE_REQUEST is set to a NUMBER (expiry days) by `restore_object` when
@@ -71,10 +72,21 @@ class Glacier {
7172
*/
7273
static GPFS_DMAPI_XATTR_TAPE_TPS = 'dmapi.IBMTPS';
7374

75+
/**
76+
* GPFS_DMAPI_XATTR_TAPE_UID xattr contains UID which contains the unique ID of the UID
77+
*
78+
* Example: `1284427297506873931-5499940123615166566-1799306066-279655-0` (here 279655 is
79+
* the inode number)
80+
*
81+
* NOTE: If IBMUID EA exists, that means the file is either migrated or premigrated.
82+
*/
83+
static GPFS_DMAPI_XATTR_TAPE_UID = 'dmapi.IBMUID';
84+
7485
static MIGRATE_WAL_NAME = 'migrate';
7586
static MIGRATE_STAGE_WAL_NAME = 'stage.migrate';
7687
static RESTORE_WAL_NAME = 'restore';
7788
static RESTORE_STAGE_WAL_NAME = 'stage.restore';
89+
static RECLAIM_WAL_NAME = 'reclaim';
7890

7991
/** @type {nb.RestoreState} */
8092
static RESTORE_STATUS_CAN_RESTORE = 'CAN_RESTORE';
@@ -86,6 +98,7 @@ class Glacier {
8698
static GLACIER_CLUSTER_LOCK = 'glacier.cluster.lock';
8799
static GLACIER_MIGRATE_CLUSTER_LOCK = 'glacier.cluster.migrate.lock';
88100
static GLACIER_RESTORE_CLUSTER_LOCK = 'glacier.cluster.restore.lock';
101+
static GLACIER_RECLAIM_CLUSTER_LOCK = 'glacier.cluster.reclaim.lock';
89102
static GLACIER_SCAN_LOCK = 'glacier.scan.lock';
90103

91104
/**
@@ -181,6 +194,20 @@ class Glacier {
181194
throw new Error('Unimplementented');
182195
}
183196

197+
/**
198+
* reclaim cleans up inindexed items in the underlying
199+
* glacier storage
200+
*
201+
* NOTE: This needs to be implemented by each backend.
202+
* @param {nb.NativeFSContext} fs_context
203+
* @param {LogFile} log_file log filename
204+
* @param {(entry: string) => Promise<void>} failure_recorder
205+
* @returns {Promise<boolean>}
206+
*/
207+
async reclaim(fs_context, log_file, failure_recorder) {
208+
throw new Error('Unimplementented');
209+
}
210+
184211
/**
185212
* low_free_space must return true if the backend has
186213
* low free space.
@@ -199,7 +226,7 @@ class Glacier {
199226

200227
/**
201228
* @param {nb.NativeFSContext} fs_context
202-
* @param {"MIGRATION" | "RESTORE" | "EXPIRY"} type
229+
* @param {"MIGRATION" | "RESTORE" | "EXPIRY" | "RECLAIM"} type
203230
*/
204231
async perform(fs_context, type) {
205232
const lock_path = lock_file => path.join(config.NSFS_GLACIER_LOGS_DIR, lock_file);
@@ -217,8 +244,8 @@ class Glacier {
217244
* ) => Promise<boolean>} log_cb */
218245

219246
/**
220-
* @param {string} namespace
221-
* @param {log_cb} cb
247+
* @param {string} namespace
248+
* @param {log_cb} cb
222249
*/
223250
const process_glacier_logs = async (namespace, cb) => {
224251
const logs = new PersistentLogger(
@@ -266,6 +293,10 @@ class Glacier {
266293
this.restore.bind(this),
267294
Glacier.GLACIER_RESTORE_CLUSTER_LOCK,
268295
);
296+
} else if (type === 'RECLAIM') {
297+
await native_fs_utils.lock_and_run(fs_context, lock_path(Glacier.GLACIER_RECLAIM_CLUSTER_LOCK), async () => {
298+
await process_glacier_logs(Glacier.RECLAIM_WAL_NAME, this.reclaim.bind(this));
299+
});
269300
}
270301
}
271302

src/sdk/glacier_tapecloud.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ function get_bin_path(bin_name) {
2424
class TapeCloudUtils {
2525
static MIGRATE_SCRIPT = 'migrate';
2626
static RECALL_SCRIPT = 'recall';
27+
static RECLAIM_SCRIPT = 'reclaim';
2728
static TASK_SHOW_SCRIPT = 'task_show';
2829
static PROCESS_EXPIRED_SCRIPT = 'process_expired';
2930
static LOW_FREE_SPACE_SCRIPT = 'low_free_space';
@@ -182,6 +183,29 @@ class TapeCloudUtils {
182183
}
183184
}
184185

186+
/**
187+
* reclaim takes name of a file which contains the list
188+
* of the files to be reclaimed.
189+
*
190+
* reclaim doesn't perform any failure handling and expects the
191+
* underlying scripts to take care of retries.
192+
*
193+
* @param {string} file filename
194+
* @returns {Promise<boolean>} Indicates success if true
195+
*/
196+
static async reclaim(file) {
197+
try {
198+
dbg.log1("Starting reclaim for file", file);
199+
const out = await exec(`${get_bin_path(TapeCloudUtils.RECLAIM_SCRIPT)} ${file}`, { return_stdout: true });
200+
dbg.log4("reclaim finished with:", out);
201+
dbg.log1("Finished reclaim for file", file);
202+
} catch (error) {
203+
dbg.error("Failed to run TapeCloudUtils.reclaim for file:", file, "due to error:", error);
204+
}
205+
206+
return true;
207+
}
208+
185209
static async process_expired() {
186210
dbg.log1("Starting process_expired");
187211
const out = await exec(`${get_bin_path(TapeCloudUtils.PROCESS_EXPIRED_SCRIPT)}`, { return_stdout: true });
@@ -444,6 +468,21 @@ class TapeCloudGlacier extends Glacier {
444468
}
445469
}
446470

471+
/**
472+
*
473+
* @param {nb.NativeFSContext} fs_context
474+
* @param {LogFile} log_file log filename
475+
* @param {(entry: string) => Promise<void>} failure_recorder
476+
* @returns {Promise<boolean>}
477+
*/
478+
async reclaim(fs_context, log_file, failure_recorder) {
479+
try {
480+
return this._reclaim(log_file.log_path);
481+
} catch (error) {
482+
dbg.error('unexpected error occured while running tapecloud.reclaim:', error);
483+
}
484+
}
485+
447486
async low_free_space() {
448487
const result = await exec(get_bin_path(TapeCloudUtils.LOW_FREE_SPACE_SCRIPT), { return_stdout: true });
449488
return result.toLowerCase().trim() === 'true';
@@ -511,6 +550,17 @@ class TapeCloudGlacier extends Glacier {
511550
return TapeCloudUtils.process_expired();
512551
}
513552

553+
/**
554+
* _reclaim should perform object reclaim from tape
555+
*
556+
* NOTE: Must be overwritten for tests
557+
* @param {string} file
558+
* @returns {Promise<boolean>}
559+
*/
560+
async _reclaim(file) {
561+
return TapeCloudUtils.reclaim(file);
562+
}
563+
514564
/**
515565
* finalizes the restore by setting the required EAs
516566
*

src/sdk/namespace_fs.js

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,8 @@ class NamespaceFS {
14071407
const is_disabled_dir_content = this._is_directory_content(file_path, params.key) && this._is_versioning_disabled();
14081408

14091409
const stat = await target_file.stat(fs_context);
1410+
const file_path_stat = config.NSFS_GLACIER_DMAPI_ENABLE_TAPE_RECLAIM &&
1411+
await nb_native().fs.stat(fs_context, file_path).catch(_.noop);
14101412
this._verify_encryption(params.encryption, this._get_encryption_info(stat));
14111413

14121414
const copy_xattr = params.copy_source && params.xattr_copy;
@@ -1455,6 +1457,10 @@ class NamespaceFS {
14551457
dbg.log1('NamespaceFS._finish_upload:', open_mode, file_path, upload_path, fs_xattr);
14561458

14571459
if (!same_inode && !part_upload) {
1460+
if (file_path_stat) {
1461+
await this.append_to_reclaim_wal(fs_context, file_path, file_path_stat);
1462+
}
1463+
14581464
await this._move_to_dest(fs_context, upload_path, file_path, target_file, open_mode, params.key);
14591465
}
14601466

@@ -2126,7 +2132,16 @@ class NamespaceFS {
21262132
if (files) await this._close_files(fs_context, files.delete_version, undefined, true);
21272133
}
21282134
} else {
2129-
await native_fs_utils.unlink_ignore_enoent(fs_context, file_path);
2135+
try {
2136+
const stat = config.NSFS_GLACIER_DMAPI_ENABLE_TAPE_RECLAIM &&
2137+
await nb_native().fs.stat(fs_context, file_path).catch(dbg.warn.bind(this));
2138+
await nb_native().fs.unlink(fs_context, file_path);
2139+
if (stat) {
2140+
await this.append_to_reclaim_wal(fs_context, file_path, stat);
2141+
}
2142+
} catch (err) {
2143+
if (err.code !== 'ENOENT' && err.code !== 'EISDIR') throw err;
2144+
}
21302145
}
21312146

21322147
await this._delete_path_dirs(file_path, fs_context);
@@ -3715,6 +3730,28 @@ class NamespaceFS {
37153730
await NamespaceFS.restore_wal.append(Glacier.getBackend().encode_log(entry));
37163731
}
37173732

3733+
/**
3734+
*
3735+
* @param {nb.NativeFSContext} fs_context
3736+
* @param {string} file_path
3737+
* @param {nb.NativeFSStats} [stat]
3738+
* @returns
3739+
*/
3740+
async append_to_reclaim_wal(fs_context, file_path, stat) {
3741+
if (!config.NSFS_GLACIER_LOGS_ENABLED || !config.NSFS_GLACIER_DMAPI_ENABLE_TAPE_RECLAIM) return;
3742+
3743+
if (!stat) {
3744+
stat = await nb_native().fs.stat(fs_context, file_path);
3745+
}
3746+
3747+
const data = JSON.stringify({
3748+
full_path: file_path,
3749+
logical_size: stat.size,
3750+
ea: stat.xattr,
3751+
});
3752+
await NamespaceFS.reclaim_wal.append(data);
3753+
}
3754+
37183755
static get migrate_wal() {
37193756
if (!NamespaceFS._migrate_wal) {
37203757
NamespaceFS._migrate_wal = new PersistentLogger(config.NSFS_GLACIER_LOGS_DIR, Glacier.MIGRATE_WAL_NAME, {
@@ -3737,6 +3774,17 @@ class NamespaceFS {
37373774
return NamespaceFS._restore_wal;
37383775
}
37393776

3777+
static get reclaim_wal() {
3778+
if (!NamespaceFS._reclaim_wal) {
3779+
NamespaceFS._reclaim_wal = new PersistentLogger(config.NSFS_GLACIER_LOGS_DIR, Glacier.RECLAIM_WAL_NAME, {
3780+
poll_interval: config.NSFS_GLACIER_LOGS_POLL_INTERVAL,
3781+
locking: 'SHARED',
3782+
});
3783+
}
3784+
3785+
return NamespaceFS._reclaim_wal;
3786+
}
3787+
37403788
////////////////////////////
37413789
// LIFECYLE HELPERS //
37423790
////////////////////////////
@@ -3763,6 +3811,9 @@ class NamespaceFS {
37633811
this._check_lifecycle_filter_before_deletion(params, stat);
37643812
const bucket_tmp_dir_path = this.get_bucket_tmpdir_full_path();
37653813
await native_fs_utils.safe_unlink(fs_context, file_path, stat, { dir_file, src_file }, bucket_tmp_dir_path);
3814+
if (!is_dir_content) {
3815+
await this.append_to_reclaim_wal(fs_context, file_path, src_stat).catch(dbg.warn.bind(this));
3816+
}
37663817
} catch (err) {
37673818
dbg.log0('_verify_lifecycle_filter_and_unlink err', err.code, err, file_path);
37683819
if (err.code !== 'ENOENT' && err.code !== 'EISDIR') throw err;
@@ -3809,7 +3860,8 @@ NamespaceFS._migrate_wal = null;
38093860
/** @type {PersistentLogger} */
38103861
NamespaceFS._restore_wal = null;
38113862

3863+
/** @type {PersistentLogger} */
3864+
NamespaceFS._reclaim_wal = null;
3865+
38123866
module.exports = NamespaceFS;
38133867
module.exports.multi_buffer_pool = multi_buffer_pool;
3814-
3815-

0 commit comments

Comments
 (0)