Skip to content

Commit 0bff063

Browse files
committed
add support for DMAPI xattr based GLACIER storage class
Signed-off-by: Utkarsh Srivastava <[email protected]> add tests for tape info parsing Signed-off-by: Utkarsh Srivastava <[email protected]> remove auto-code formatting Signed-off-by: Utkarsh Srivastava <[email protected]>
1 parent f9ea301 commit 0bff063

File tree

11 files changed

+491
-166
lines changed

11 files changed

+491
-166
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: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ config.DENY_UPLOAD_TO_STORAGE_CLASS_STANDARD = false;
191191
// of days an object can be restored using `restore-object` call.
192192
config.S3_RESTORE_REQUEST_MAX_DAYS = 30;
193193

194+
// NSFS_GLACIER_DMAPI_PMIG_DAYS controls the "virtual"/fake expiry
195+
// days that will be shown if we detect a glacier object whose life-
196+
// cycle NSFS doesn't controls
197+
//
198+
// This is initialized to be the same as S3_RESTORE_REQUEST_MAX_DAYS
199+
// but can be overridden to any numberical value
200+
config.NSFS_GLACIER_DMAPI_PMIG_DAYS = config.S3_RESTORE_REQUEST_MAX_DAYS;
201+
202+
194203
/**
195204
* S3_RESTORE_MAX_DAYS_BEHAVIOUR controls whether to truncate the
196205
* requested number of days in restore request or whether to deny the request.
@@ -873,6 +882,21 @@ config.NSFS_GLACIER_EXPIRY_TZ = 'LOCAL';
873882
// the request will be used
874883
config.NSFS_GLACIER_EXPIRY_TIME_OF_DAY = '';
875884

885+
// If set to true then NooBaa will consider DMAPI extended attributes
886+
// in conjuction with NooBaa's `user.storage_class` extended attribute
887+
// to determine state of an object.
888+
config.NSFS_GLACIER_USE_DMAPI = false;
889+
890+
// NSFS_GLACIER_DMAPI_ALLOW_NOOBAA_TAKEOVER allows NooBaa to take over lifecycle
891+
// management of an object which was originally NOT managed by NooBaa.
892+
config.NSFS_GLACIER_DMAPI_ALLOW_NOOBAA_TAKEOVER = false;
893+
894+
// NSFS_GLACIER_DMAPI_TPS_HTTP_HEADER if enabled will add additional HTTP headers
895+
// `x-tape-meta-copy-n` based on `dmapi.IBMTPS` EA.
896+
//
897+
// For this to work, NSFS_GLACIER_USE_DMAPI must be set to `true`.
898+
config.NSFS_GLACIER_DMAPI_TPS_HTTP_HEADER = config.NSFS_GLACIER_USE_DMAPI || false;
899+
876900
config.NSFS_STATFS_CACHE_SIZE = 10000;
877901
config.NSFS_STATFS_CACHE_EXPIRY_MS = 1 * 1000;
878902

src/endpoint/s3/s3_utils.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,12 @@ function set_response_object_md(res, object_md) {
325325

326326
res.setHeader('x-amz-restore', restore);
327327
}
328+
if (storage_class === STORAGE_CLASS_GLACIER) {
329+
object_md.restore_status?.backend_meta?.forEach?.((meta, idx) => {
330+
const header = Object.keys(meta).map(key => `${key}=${meta[key]}`).join('&');
331+
res.setHeader(`x-tape-meta-copy-${idx}`, header);
332+
});
333+
}
328334
}
329335

330336
/** set_response_headers_get_object_attributes is based on set_response_object_md

src/manage_nsfs/manage_nsfs_glacier.js

Lines changed: 40 additions & 17 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() {
69-
const fs_context = native_fs_utils.get_process_fs_context();
68+
const fs_context = force_gpfs_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 = force_gpfs_fs_context(fs_context);
181182
try {
182183
await log.process(async (entry, failure_recorder) => cb(fs_context, entry, failure_recorder));
183184
} catch (error) {
@@ -212,6 +213,28 @@ function get_tz_date(hours, mins, secs, tz) {
212213
return date;
213214
}
214215

216+
/**
217+
* force_gpfs_fs_context returns a shallow copy of given
218+
* fs_context with backend set to 'GPFS'.
219+
*
220+
* NOTE: The function will throw error if it detects that GPFS
221+
* DL isn't loaded.
222+
*
223+
* @param {nb.NativeFSContext} fs_context
224+
* @returns {nb.NativeFSContext}
225+
*/
226+
function force_gpfs_fs_context(fs_context) {
227+
if (config.NSFS_GLACIER_USE_DMAPI) {
228+
if (!nb_native().fs.gpfs) {
229+
throw new Error('cannot use DMAPI EA: gpfs dl not loaded');
230+
}
231+
232+
return { ...fs_context, backend: 'GPFS', use_dmapi: true };
233+
}
234+
235+
return { ...fs_context };
236+
}
237+
215238
/**
216239
* lock_and_run acquires a flock and calls the given callback after
217240
* acquiring the lock

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)