Skip to content

Commit d747791

Browse files
committed
add complete support for tape reclaim
Signed-off-by: Utkarsh Srivastava <srivastavautkarsh8097@gmail.com>
1 parent 69d598a commit d747791

File tree

8 files changed

+167
-8
lines changed

8 files changed

+167
-8
lines changed

config.js

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

931+
config.NSFS_GLACIER_DMAPI_ENABLE_TAPE_RECLAIM = false;
932+
config.NSFS_GLACIER_RECLAIM_INTERVAL = 15 * 60 * 1000;
933+
931934
config.NSFS_STATFS_CACHE_SIZE = 10000;
932935
config.NSFS_STATFS_CACHE_EXPIRY_MS = 1 * 1000;
933936

@@ -971,7 +974,7 @@ config.NSFS_GLACIER_MIGRATE_LOG_THRESHOLD = 50 * 1024;
971974
config.NSFS_GLACIER_METRICS_STATFS_PATHS = [];
972975
config.NSFS_GLACIER_METRICS_STATFS_INTERVAL = 60 * 1000; // Refresh statfs value every minute
973976

974-
/**
977+
/**
975978
* NSFS_GLACIER_RESERVED_BUCKET_TAGS defines an object of bucket tags which will be reserved
976979
* by the system and PUT operations for them via S3 API would be limited - as in they would be
977980
* mutable only if specified and only under certain conditions.
@@ -982,7 +985,7 @@ config.NSFS_GLACIER_METRICS_STATFS_INTERVAL = 60 * 1000; // Refresh statfs value
982985
* default: any,
983986
* event: boolean
984987
* }>}
985-
*
988+
*
986989
* @example
987990
* {
988991
'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._process_reclaimed(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+
* _process_reclaimed should perform reclaimed
555+
*
556+
* NOTE: Must be overwritten for tests
557+
* @param {string} file
558+
* @returns {Promise<boolean>}
559+
*/
560+
async _process_reclaimed(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: 56 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,11 @@ 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 the target file is already on tape then this is a candidate for tape reclaim
1461+
if (file_path_stat && file_path_stat.xattr[Glacier.GPFS_DMAPI_XATTR_TAPE_INDICATOR]) {
1462+
await this.append_to_reclaim_wal(fs_context, upload_path, file_path_stat);
1463+
}
1464+
14581465
await this._move_to_dest(fs_context, upload_path, file_path, target_file, open_mode, params.key);
14591466
}
14601467

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

21322148
await this._delete_path_dirs(file_path, fs_context);
@@ -3711,6 +3727,28 @@ class NamespaceFS {
37113727
await NamespaceFS.restore_wal.append(Glacier.getBackend().encode_log(entry));
37123728
}
37133729

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

3774+
static get reclaim_wal() {
3775+
if (!NamespaceFS._reclaim_wal) {
3776+
NamespaceFS._reclaim_wal = new PersistentLogger(config.NSFS_GLACIER_LOGS_DIR, Glacier.RECLAIM_WAL_NAME, {
3777+
poll_interval: config.NSFS_GLACIER_LOGS_POLL_INTERVAL,
3778+
locking: 'SHARED',
3779+
});
3780+
}
3781+
3782+
return NamespaceFS._reclaim_wal;
3783+
}
3784+
37363785
////////////////////////////
37373786
// LIFECYLE HELPERS //
37383787
////////////////////////////
@@ -3759,6 +3808,9 @@ class NamespaceFS {
37593808
this._check_lifecycle_filter_before_deletion(params, stat);
37603809
const bucket_tmp_dir_path = this.get_bucket_tmpdir_full_path();
37613810
await native_fs_utils.safe_unlink(fs_context, file_path, stat, { dir_file, src_file }, bucket_tmp_dir_path);
3811+
if (!is_empty_directory_content) {
3812+
await this.append_to_reclaim_wal(fs_context, file_path, src_stat).catch(dbg.warn.bind(this));
3813+
}
37623814
} catch (err) {
37633815
dbg.log0('_verify_lifecycle_filter_and_unlink err', err.code, err, file_path);
37643816
if (err.code !== 'ENOENT' && err.code !== 'EISDIR') throw err;
@@ -3805,7 +3857,8 @@ NamespaceFS._migrate_wal = null;
38053857
/** @type {PersistentLogger} */
38063858
NamespaceFS._restore_wal = null;
38073859

3860+
/** @type {PersistentLogger} */
3861+
NamespaceFS._reclaim_wal = null;
3862+
38083863
module.exports = NamespaceFS;
38093864
module.exports.multi_buffer_pool = multi_buffer_pool;
3810-
3811-

0 commit comments

Comments
 (0)