Skip to content

Commit 4abe534

Browse files
committed
add support for DMAPI xattr based GLACIER storage class
Signed-off-by: Utkarsh Srivastava <srivastavautkarsh8097@gmail.com> add tests for tape info parsing Signed-off-by: Utkarsh Srivastava <srivastavautkarsh8097@gmail.com> remove auto-code formatting Signed-off-by: Utkarsh Srivastava <srivastavautkarsh8097@gmail.com> use querystring Signed-off-by: Utkarsh Srivastava <srivastavautkarsh8097@gmail.com> fix sending HTTP headers on all the status Signed-off-by: Utkarsh Srivastava <srivastavautkarsh8097@gmail.com> address PR comments Signed-off-by: Utkarsh Srivastava <srivastavautkarsh8097@gmail.com> address PR comments Signed-off-by: Utkarsh Srivastava <srivastavautkarsh8097@gmail.com> Separate out implicit restore status from implicit storage class Signed-off-by: Utkarsh Srivastava <srivastavautkarsh8097@gmail.com> update tests Signed-off-by: Utkarsh Srivastava <srivastavautkarsh8097@gmail.com>
1 parent fc141c8 commit 4abe534

File tree

11 files changed

+533
-163
lines changed

11 files changed

+533
-163
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ config-local.js
2323
*.sublime*
2424
.DS_Store
2525
heapdump-*
26+
.cache
27+
.clangd
2628

2729
## PRIVATE
2830
*.pem

config.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,44 @@ config.NSFS_GLACIER_EXPIRY_TZ = 'LOCAL';
880880
// the request will be used
881881
config.NSFS_GLACIER_EXPIRY_TIME_OF_DAY = '';
882882

883+
// If set to to true, NooBaa will attempt to read DMAPI
884+
// xattrs
885+
config.NSFS_GLACIER_DMAPI_ENABLE = false;
886+
887+
// NSFS_GLACIER_DMAPI_IMPLICIT_RESTORE_STATUS if enabled then
888+
// NooBaa will derive restore status of the files based on DMAPI
889+
// xattr IF there are no explicit restore status attributes on
890+
// the file.
891+
config.NSFS_GLACIER_DMAPI_IMPLICIT_RESTORE_STATUS = false;
892+
893+
// If set to true then NooBaa will consider DMAPI extended attributes
894+
// in conjuction with NooBaa's `user.storage_class` extended attribute
895+
// to determine state of an object.
896+
//
897+
// NOTE:NSFS_GLACIER_DMAPI_ENABLE should be enabled to use this.
898+
config.NSFS_GLACIER_DMAPI_IMPLICIT_SC = false;
899+
900+
// NSFS_GLACIER_DMAPI_ALLOW_NOOBAA_TAKEOVER allows NooBaa to take over lifecycle
901+
// management of an object which was originally NOT managed by NooBaa.
902+
//
903+
// NOTE:NSFS_GLACIER_DMAPI_ENABLE and NSFS_GLACIER_USE_DMAPI should be enabled to use this.
904+
config.NSFS_GLACIER_DMAPI_ALLOW_NOOBAA_TAKEOVER = false;
905+
906+
// NSFS_GLACIER_DMAPI_TPS_HTTP_HEADER_ENABLE if true will add additional HTTP headers
907+
// `config.NSFS_GLACIER_DMAPI_TPS_HTTP_HEADER` based on `dmapi.IBMTPS` EA.
908+
//
909+
// NOTE:NSFS_GLACIER_DMAPI_ENABLE should be enabled to use this.
910+
config.NSFS_GLACIER_DMAPI_TPS_HTTP_HEADER_ENABLE = false;
911+
config.NSFS_GLACIER_DMAPI_TPS_HTTP_HEADER = 'x-tape-meta-copy';
912+
913+
// NSFS_GLACIER_DMAPI_PMIG_DAYS controls the "virtual"/fake expiry
914+
// days that will be shown if we detect a glacier object whose life-
915+
// cycle NSFS doesn't controls
916+
//
917+
// This is initialized to be the same as S3_RESTORE_REQUEST_MAX_DAYS
918+
// but can be overridden to any numberical value
919+
config.NSFS_GLACIER_DMAPI_PMIG_DAYS = config.S3_RESTORE_REQUEST_MAX_DAYS;
920+
883921
config.NSFS_STATFS_CACHE_SIZE = 10000;
884922
config.NSFS_STATFS_CACHE_EXPIRY_MS = 1 * 1000;
885923

src/endpoint/s3/s3_utils.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
'use strict';
33

44
const _ = require('lodash');
5+
const querystring = require('querystring');
56

67
const dbg = require('../../util/debug_module')(__filename);
78
const S3Error = require('./s3_errors').S3Error;
@@ -325,6 +326,13 @@ function set_response_object_md(res, object_md) {
325326

326327
res.setHeader('x-amz-restore', restore);
327328
}
329+
if (config.NSFS_GLACIER_DMAPI_TPS_HTTP_HEADER_ENABLE) {
330+
object_md.restore_status?.tape_info?.forEach?.((meta, idx) => {
331+
// @ts-ignore - For some TS check doesn't like "meta" being passed to querystring
332+
const header = querystring.stringify(meta);
333+
res.setHeader(`${config.NSFS_GLACIER_DMAPI_TPS_HTTP_HEADER}-${idx}`, header);
334+
});
335+
}
328336
}
329337

330338
/** set_response_headers_get_object_attributes is based on set_response_object_md

src/manage_nsfs/manage_nsfs_glacier.js

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ const path = require('path');
55
const { PersistentLogger } = require('../util/persistent_logger');
66
const config = require('../../config');
77
const nb_native = require('../util/nb_native');
8-
const { GlacierBackend } = require('../sdk/nsfs_glacier_backend/backend');
9-
const { getGlacierBackend } = require('../sdk/nsfs_glacier_backend/helper');
8+
const { Glacier } = require('../sdk/glacier');
109
const native_fs_utils = require('../util/native_fs_utils');
1110
const { is_desired_time, record_current_time } = require('./manage_nsfs_cli_utils');
1211

@@ -17,14 +16,14 @@ async function process_migrations() {
1716
const fs_context = native_fs_utils.get_process_fs_context();
1817
const lock_path = path.join(config.NSFS_GLACIER_LOGS_DIR, CLUSTER_LOCK);
1918
await native_fs_utils.lock_and_run(fs_context, lock_path, async () => {
20-
const backend = getGlacierBackend();
19+
const backend = Glacier.getBackend();
2120

2221
if (
2322
await backend.low_free_space() ||
24-
await time_exceeded(fs_context, config.NSFS_GLACIER_MIGRATE_INTERVAL, GlacierBackend.MIGRATE_TIMESTAMP_FILE)
23+
await time_exceeded(fs_context, config.NSFS_GLACIER_MIGRATE_INTERVAL, Glacier.MIGRATE_TIMESTAMP_FILE)
2524
) {
2625
await run_glacier_migrations(fs_context, backend);
27-
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, GlacierBackend.MIGRATE_TIMESTAMP_FILE);
26+
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, Glacier.MIGRATE_TIMESTAMP_FILE);
2827
await record_current_time(fs_context, timestamp_file_path);
2928
}
3029
});
@@ -34,46 +33,46 @@ async function process_migrations() {
3433
* run_tape_migrations reads the migration WALs and attempts to migrate the
3534
* files mentioned in the WAL.
3635
* @param {nb.NativeFSContext} fs_context
37-
* @param {import('../sdk/nsfs_glacier_backend/backend').GlacierBackend} backend
36+
* @param {import('../sdk/glacier').Glacier} backend
3837
*/
3938
async function run_glacier_migrations(fs_context, backend) {
40-
await run_glacier_operation(fs_context, GlacierBackend.MIGRATE_WAL_NAME, backend.migrate.bind(backend));
39+
await run_glacier_operation(fs_context, Glacier.MIGRATE_WAL_NAME, backend.migrate.bind(backend));
4140
}
4241

4342
async function process_restores() {
4443
const fs_context = native_fs_utils.get_process_fs_context();
4544
const lock_path = path.join(config.NSFS_GLACIER_LOGS_DIR, CLUSTER_LOCK);
4645
await native_fs_utils.lock_and_run(fs_context, lock_path, async () => {
47-
const backend = getGlacierBackend();
46+
const backend = Glacier.getBackend();
4847

4948
if (
5049
await backend.low_free_space() ||
51-
!(await time_exceeded(fs_context, config.NSFS_GLACIER_RESTORE_INTERVAL, GlacierBackend.RESTORE_TIMESTAMP_FILE))
50+
!(await time_exceeded(fs_context, config.NSFS_GLACIER_RESTORE_INTERVAL, Glacier.RESTORE_TIMESTAMP_FILE))
5251
) return;
5352

5453

5554
await run_glacier_restore(fs_context, backend);
56-
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, GlacierBackend.RESTORE_TIMESTAMP_FILE);
55+
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, Glacier.RESTORE_TIMESTAMP_FILE);
5756
await record_current_time(fs_context, timestamp_file_path);
5857
});
5958
}
6059

6160
/**
6261
* run_tape_restore reads the restore WALs and attempts to restore the
6362
* files mentioned in the WAL.
64-
* @param {nb.NativeFSContext} fs_context
65-
* @param {import('../sdk/nsfs_glacier_backend/backend').GlacierBackend} backend
63+
* @param {nb.NativeFSContext} fs_context
64+
* @param {import('../sdk/glacier').Glacier} backend
6665
*/
6766
async function run_glacier_restore(fs_context, backend) {
68-
await run_glacier_operation(fs_context, GlacierBackend.RESTORE_WAL_NAME, backend.restore.bind(backend));
67+
await run_glacier_operation(fs_context, Glacier.RESTORE_WAL_NAME, backend.restore.bind(backend));
6968
}
7069

7170
async function process_expiry() {
7271
const fs_context = native_fs_utils.get_process_fs_context();
7372
const lock_path = path.join(config.NSFS_GLACIER_LOGS_DIR, SCAN_LOCK);
7473
await native_fs_utils.lock_and_run(fs_context, lock_path, async () => {
75-
const backend = getGlacierBackend();
76-
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, GlacierBackend.EXPIRY_TIMESTAMP_FILE);
74+
const backend = Glacier.getBackend();
75+
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, Glacier.EXPIRY_TIMESTAMP_FILE);
7776
if (
7877
await backend.low_free_space() ||
7978
await is_desired_time(
@@ -126,6 +125,8 @@ async function time_exceeded(fs_context, interval, timestamp_file) {
126125
*/
127126
async function run_glacier_operation(fs_context, log_namespace, cb) {
128127
const log = new PersistentLogger(config.NSFS_GLACIER_LOGS_DIR, log_namespace, { locking: 'EXCLUSIVE' });
128+
129+
fs_context = prepare_galcier_fs_context(fs_context);
129130
try {
130131
await log.process(async (entry, failure_recorder) => cb(fs_context, entry, failure_recorder));
131132
} catch (error) {
@@ -135,6 +136,28 @@ async function run_glacier_operation(fs_context, log_namespace, cb) {
135136
}
136137
}
137138

139+
/**
140+
* prepare_galcier_fs_context returns a shallow copy of given
141+
* fs_context with backend set to 'GPFS'.
142+
*
143+
* NOTE: The function will throw error if it detects that libgfs
144+
* isn't loaded.
145+
*
146+
* @param {nb.NativeFSContext} fs_context
147+
* @returns {nb.NativeFSContext}
148+
*/
149+
function prepare_galcier_fs_context(fs_context) {
150+
if (config.NSFS_GLACIER_DMAPI_ENABLE) {
151+
if (!nb_native().fs.gpfs) {
152+
throw new Error('cannot use DMAPI xattrs: libgpfs not loaded');
153+
}
154+
155+
return { ...fs_context, backend: 'GPFS', use_dmapi: true };
156+
}
157+
158+
return { ...fs_context };
159+
}
160+
138161
exports.process_migrations = process_migrations;
139162
exports.process_restores = process_restores;
140163
exports.process_expiry = process_expiry;

src/native/fs/fs_napi.cpp

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@
4646
#define GPFS_XATTR_PREFIX "gpfs"
4747
#define GPFS_DOT_ENCRYPTION_EA "Encryption"
4848
#define GPFS_ENCRYPTION_XATTR_NAME GPFS_XATTR_PREFIX "." GPFS_DOT_ENCRYPTION_EA
49+
#define GPFS_DMAPI_XATTR_PREFIX "dmapi"
50+
#define GPFS_DMAPI_DOT_IBMOBJ_EA "IBMObj"
51+
#define GPFS_DMAPI_DOT_IBMPMIG_EA "IBMPMig"
52+
#define GPFS_DMAPI_DOT_IBMTPS_EA "IBMTPS"
53+
#define GPFS_DMAPI_XATTR_TAPE_INDICATOR GPFS_DMAPI_XATTR_PREFIX "." GPFS_DMAPI_DOT_IBMOBJ_EA
54+
#define GPFS_DMAPI_XATTR_TAPE_PREMIG GPFS_DMAPI_XATTR_PREFIX "." GPFS_DMAPI_DOT_IBMPMIG_EA
55+
#define GPFS_DMAPI_XATTR_TAPE_TPS GPFS_DMAPI_XATTR_PREFIX "." GPFS_DMAPI_DOT_IBMTPS_EA
4956

5057
// This macro should be used after openning a file
5158
// it will autoclose the file using AutoCloser and will throw an error in case of failures
@@ -244,6 +251,11 @@ parse_open_flags(std::string flags)
244251
}
245252

246253
const static std::vector<std::string> GPFS_XATTRS{ GPFS_ENCRYPTION_XATTR_NAME };
254+
const static std::vector<std::string> GPFS_DMAPI_XATTRS{
255+
GPFS_DMAPI_XATTR_TAPE_INDICATOR,
256+
GPFS_DMAPI_XATTR_TAPE_PREMIG,
257+
GPFS_DMAPI_XATTR_TAPE_TPS,
258+
};
247259
const static std::vector<std::string> USER_XATTRS{
248260
"user.content_type",
249261
"user.content_md5",
@@ -461,9 +473,14 @@ get_fd_xattr(int fd, XattrMap& xattr, const std::vector<std::string>& xattr_keys
461473
}
462474

463475
static int
464-
get_fd_gpfs_xattr(int fd, XattrMap& xattr, int& gpfs_error)
476+
get_fd_gpfs_xattr(int fd, XattrMap& xattr, int& gpfs_error, bool use_dmapi)
465477
{
466-
for (auto const& key : GPFS_XATTRS) {
478+
auto gpfs_xattrs { GPFS_XATTRS };
479+
if (use_dmapi) {
480+
gpfs_xattrs.insert(gpfs_xattrs.end(), GPFS_DMAPI_XATTRS.begin(), GPFS_DMAPI_XATTRS.end());
481+
}
482+
483+
for (auto const& key : gpfs_xattrs) {
467484
gpfsRequest_t gpfsGetXattrRequest;
468485
build_gpfs_get_ea_request(&gpfsGetXattrRequest, key);
469486
int r = dlsym_gpfs_fcntl(fd, &gpfsGetXattrRequest);
@@ -603,6 +620,8 @@ struct FSWorker : public Napi::AsyncWorker
603620
// NOTE: If _do_ctime_check = false, then some functions will fallback to using mtime check
604621
bool _do_ctime_check;
605622

623+
bool _use_dmapi;
624+
606625
FSWorker(const Napi::CallbackInfo& info)
607626
: AsyncWorker(info.Env())
608627
, _deferred(Napi::Promise::Deferred::New(info.Env()))
@@ -616,6 +635,7 @@ struct FSWorker : public Napi::AsyncWorker
616635
, _should_add_thread_capabilities(false)
617636
, _supplemental_groups()
618637
, _do_ctime_check(false)
638+
, _use_dmapi(false)
619639
{
620640
for (int i = 0; i < (int)info.Length(); ++i) _args_ref.Set(i, info[i]);
621641
if (info[0].ToBoolean()) {
@@ -635,6 +655,7 @@ struct FSWorker : public Napi::AsyncWorker
635655
_report_fs_stats = Napi::Persistent(fs_context.Get("report_fs_stats").As<Napi::Function>());
636656
}
637657
_do_ctime_check = fs_context.Get("do_ctime_check").ToBoolean();
658+
_use_dmapi = fs_context.Get("use_dmapi").ToBoolean();
638659
}
639660
}
640661
void Begin(std::string desc)
@@ -798,7 +819,7 @@ struct Stat : public FSWorker
798819
if (!_use_lstat) {
799820
SYSCALL_OR_RETURN(get_fd_xattr(fd, _xattr, _xattr_get_keys));
800821
if (use_gpfs_lib()) {
801-
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error));
822+
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error, _use_dmapi));
802823
}
803824
}
804825

@@ -1226,7 +1247,7 @@ struct Readfile : public FSWorker
12261247
if (_read_xattr) {
12271248
SYSCALL_OR_RETURN(get_fd_xattr(fd, _xattr, _xattr_get_keys));
12281249
if (use_gpfs_lib()) {
1229-
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error));
1250+
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error, _use_dmapi));
12301251
}
12311252
}
12321253

@@ -1757,7 +1778,7 @@ struct FileStat : public FSWrapWorker<FileWrap>
17571778
SYSCALL_OR_RETURN(fstat(fd, &_stat_res));
17581779
SYSCALL_OR_RETURN(get_fd_xattr(fd, _xattr, _xattr_get_keys));
17591780
if (use_gpfs_lib()) {
1760-
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error));
1781+
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error, _use_dmapi));
17611782
}
17621783

17631784
if (_do_ctime_check) {

0 commit comments

Comments
 (0)