Skip to content

Commit 4de4be3

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> (cherry picked from commit 4abe534)
1 parent 65045b6 commit 4de4be3

File tree

11 files changed

+533
-164
lines changed

11 files changed

+533
-164
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
@@ -869,6 +869,44 @@ config.NSFS_GLACIER_EXPIRY_TZ = 'LOCAL';
869869
// the request will be used
870870
config.NSFS_GLACIER_EXPIRY_TIME_OF_DAY = '';
871871

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

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 & 16 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

1211
const CLUSTER_LOCK = 'cluster.lock';
@@ -16,14 +15,14 @@ async function process_migrations() {
1615
const fs_context = native_fs_utils.get_process_fs_context();
1716

1817
await lock_and_run(fs_context, CLUSTER_LOCK, async () => {
19-
const backend = getGlacierBackend();
18+
const backend = Glacier.getBackend();
2019

2120
if (
2221
await backend.low_free_space() ||
23-
await time_exceeded(fs_context, config.NSFS_GLACIER_MIGRATE_INTERVAL, GlacierBackend.MIGRATE_TIMESTAMP_FILE)
22+
await time_exceeded(fs_context, config.NSFS_GLACIER_MIGRATE_INTERVAL, Glacier.MIGRATE_TIMESTAMP_FILE)
2423
) {
2524
await run_glacier_migrations(fs_context, backend);
26-
await record_current_time(fs_context, GlacierBackend.MIGRATE_TIMESTAMP_FILE);
25+
await record_current_time(fs_context, Glacier.MIGRATE_TIMESTAMP_FILE);
2726
}
2827
});
2928
}
@@ -32,56 +31,56 @@ async function process_migrations() {
3231
* run_tape_migrations reads the migration WALs and attempts to migrate the
3332
* files mentioned in the WAL.
3433
* @param {nb.NativeFSContext} fs_context
35-
* @param {import('../sdk/nsfs_glacier_backend/backend').GlacierBackend} backend
34+
* @param {import('../sdk/glacier').Glacier} backend
3635
*/
3736
async function run_glacier_migrations(fs_context, backend) {
38-
await run_glacier_operation(fs_context, GlacierBackend.MIGRATE_WAL_NAME, backend.migrate.bind(backend));
37+
await run_glacier_operation(fs_context, Glacier.MIGRATE_WAL_NAME, backend.migrate.bind(backend));
3938
}
4039

4140
async function process_restores() {
4241
const fs_context = native_fs_utils.get_process_fs_context();
4342

4443
await lock_and_run(fs_context, CLUSTER_LOCK, async () => {
45-
const backend = getGlacierBackend();
44+
const backend = Glacier.getBackend();
4645

4746
if (
4847
await backend.low_free_space() ||
49-
!(await time_exceeded(fs_context, config.NSFS_GLACIER_RESTORE_INTERVAL, GlacierBackend.RESTORE_TIMESTAMP_FILE))
48+
!(await time_exceeded(fs_context, config.NSFS_GLACIER_RESTORE_INTERVAL, Glacier.RESTORE_TIMESTAMP_FILE))
5049
) return;
5150

5251

5352
await run_glacier_restore(fs_context, backend);
54-
await record_current_time(fs_context, GlacierBackend.RESTORE_TIMESTAMP_FILE);
53+
await record_current_time(fs_context, Glacier.RESTORE_TIMESTAMP_FILE);
5554
});
5655
}
5756

5857
/**
5958
* run_tape_restore reads the restore WALs and attempts to restore the
6059
* files mentioned in the WAL.
61-
* @param {nb.NativeFSContext} fs_context
62-
* @param {import('../sdk/nsfs_glacier_backend/backend').GlacierBackend} backend
60+
* @param {nb.NativeFSContext} fs_context
61+
* @param {import('../sdk/glacier').Glacier} backend
6362
*/
6463
async function run_glacier_restore(fs_context, backend) {
65-
await run_glacier_operation(fs_context, GlacierBackend.RESTORE_WAL_NAME, backend.restore.bind(backend));
64+
await run_glacier_operation(fs_context, Glacier.RESTORE_WAL_NAME, backend.restore.bind(backend));
6665
}
6766

6867
async function process_expiry() {
6968
const fs_context = native_fs_utils.get_process_fs_context();
7069

7170
await lock_and_run(fs_context, SCAN_LOCK, async () => {
72-
const backend = getGlacierBackend();
71+
const backend = Glacier.getBackend();
7372
if (
7473
await backend.low_free_space() ||
7574
await is_desired_time(
7675
fs_context,
7776
new Date(),
7877
config.NSFS_GLACIER_EXPIRY_RUN_TIME,
7978
config.NSFS_GLACIER_EXPIRY_RUN_DELAY_LIMIT_MINS,
80-
GlacierBackend.EXPIRY_TIMESTAMP_FILE,
79+
Glacier.EXPIRY_TIMESTAMP_FILE,
8180
)
8281
) {
8382
await backend.expiry(fs_context);
84-
await record_current_time(fs_context, GlacierBackend.EXPIRY_TIMESTAMP_FILE);
83+
await record_current_time(fs_context, Glacier.EXPIRY_TIMESTAMP_FILE);
8584
}
8685
});
8786
}
@@ -178,6 +177,8 @@ async function record_current_time(fs_context, timestamp_file) {
178177
*/
179178
async function run_glacier_operation(fs_context, log_namespace, cb) {
180179
const log = new PersistentLogger(config.NSFS_GLACIER_LOGS_DIR, log_namespace, { locking: 'EXCLUSIVE' });
180+
181+
fs_context = prepare_galcier_fs_context(fs_context);
181182
try {
182183
await log.process(async (entry, failure_recorder) => cb(fs_context, entry, failure_recorder));
183184
} catch (error) {
@@ -230,6 +231,27 @@ async function lock_and_run(fs_context, lockfilename, cb) {
230231
}
231232
}
232233

234+
/** prepare_galcier_fs_context returns a shallow copy of given
235+
* fs_context with backend set to 'GPFS'.
236+
*
237+
* NOTE: The function will throw error if it detects that libgfs
238+
* isn't loaded.
239+
*
240+
* @param {nb.NativeFSContext} fs_context
241+
* @returns {nb.NativeFSContext}
242+
*/
243+
function prepare_galcier_fs_context(fs_context) {
244+
if (config.NSFS_GLACIER_DMAPI_ENABLE) {
245+
if (!nb_native().fs.gpfs) {
246+
throw new Error('cannot use DMAPI xattrs: libgpfs not loaded');
247+
}
248+
249+
return { ...fs_context, backend: 'GPFS', use_dmapi: true };
250+
}
251+
252+
return { ...fs_context };
253+
}
254+
233255
exports.process_migrations = process_migrations;
234256
exports.process_restores = process_restores;
235257
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)
@@ -793,7 +814,7 @@ struct Stat : public FSWorker
793814
if (!_use_lstat) {
794815
SYSCALL_OR_RETURN(get_fd_xattr(fd, _xattr, _xattr_get_keys));
795816
if (use_gpfs_lib()) {
796-
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error));
817+
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error, _use_dmapi));
797818
}
798819
}
799820

@@ -1221,7 +1242,7 @@ struct Readfile : public FSWorker
12211242
if (_read_xattr) {
12221243
SYSCALL_OR_RETURN(get_fd_xattr(fd, _xattr, _xattr_get_keys));
12231244
if (use_gpfs_lib()) {
1224-
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error));
1245+
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error, _use_dmapi));
12251246
}
12261247
}
12271248

@@ -1752,7 +1773,7 @@ struct FileStat : public FSWrapWorker<FileWrap>
17521773
SYSCALL_OR_RETURN(fstat(fd, &_stat_res));
17531774
SYSCALL_OR_RETURN(get_fd_xattr(fd, _xattr, _xattr_get_keys));
17541775
if (use_gpfs_lib()) {
1755-
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error));
1776+
GPFS_FCNTL_OR_RETURN(get_fd_gpfs_xattr(fd, _xattr, gpfs_error, _use_dmapi));
17561777
}
17571778

17581779
if (_do_ctime_check) {

0 commit comments

Comments
 (0)