1
1
/* Copyright (C) 2024 NooBaa */
2
2
'use strict' ;
3
3
4
+ const path = require ( 'path' ) ;
4
5
const nb_native = require ( '../util/nb_native' ) ;
5
6
const s3_utils = require ( '../endpoint/s3/s3_utils' ) ;
6
7
const { round_up_to_next_time_of_day } = require ( '../util/time_utils' ) ;
7
8
const dbg = require ( '../util/debug_module' ) ( __filename ) ;
8
9
const config = require ( '../../config' ) ;
10
+ const { PersistentLogger } = require ( '../util/persistent_logger' ) ;
11
+ const native_fs_utils = require ( '../util/native_fs_utils' ) ;
12
+
13
+ /** @import {LogFile} from "../util/persistent_logger" */
9
14
10
15
class Glacier {
11
16
// These names start with the word 'timestamp' so as to assure
@@ -41,6 +46,8 @@ class Glacier {
41
46
42
47
static STORAGE_CLASS_XATTR = 'user.storage_class' ;
43
48
49
+ static XATTR_STAGE_MIGRATE = 'user.noobaa.migrate.staged' ;
50
+
44
51
/**
45
52
* GPFS_DMAPI_XATTR_TAPE_INDICATOR if set on a file indicates that the file is on tape.
46
53
*
@@ -65,7 +72,9 @@ class Glacier {
65
72
static GPFS_DMAPI_XATTR_TAPE_TPS = 'dmapi.IBMTPS' ;
66
73
67
74
static MIGRATE_WAL_NAME = 'migrate' ;
75
+ static MIGRATE_STAGE_WAL_NAME = 'stage.migrate' ;
68
76
static RESTORE_WAL_NAME = 'restore' ;
77
+ static RESTORE_STAGE_WAL_NAME = 'stage.restore' ;
69
78
70
79
/** @type {nb.RestoreState } */
71
80
static RESTORE_STATUS_CAN_RESTORE = 'CAN_RESTORE' ;
@@ -74,17 +83,46 @@ class Glacier {
74
83
/** @type {nb.RestoreState } */
75
84
static RESTORE_STATUS_RESTORED = 'RESTORED' ;
76
85
86
+ static GLACIER_CLUSTER_LOCK = 'glacier.cluster.lock' ;
87
+ static GLACIER_MIGRATE_CLUSTER_LOCK = 'glacier.cluster.migrate.lock' ;
88
+ static GLACIER_RESTORE_CLUSTER_LOCK = 'glacier.cluster.restore.lock' ;
89
+ static GLACIER_SCAN_LOCK = 'glacier.scan.lock' ;
90
+
91
+ /**
92
+ * stage_migrate must take a file name (this should from the
93
+ * `GLACIER.MIGRATE_WAL_NAME` namespace) which will have
94
+ * newline seperated entries of filenames which needs to be
95
+ * migrated to GLACIER and should stage the files for migration.
96
+ *
97
+ * The function should return false if it needs the log file to be
98
+ * preserved.
99
+ * @param {nb.NativeFSContext } fs_context
100
+ * @param {LogFile } log_file log filename
101
+ * @param {(entry: string) => Promise<void> } failure_recorder
102
+ * @returns {Promise<boolean> }
103
+ */
104
+ async stage_migrate ( fs_context , log_file , failure_recorder ) {
105
+ try {
106
+ await log_file . collect ( Glacier . MIGRATE_STAGE_WAL_NAME , async ( entry , batch_recorder ) => batch_recorder ( entry ) ) ;
107
+ return true ;
108
+ } catch ( error ) {
109
+ dbg . error ( 'Glacier.stage_migrate error:' , error ) ;
110
+ throw error ;
111
+ }
112
+ }
113
+
77
114
/**
78
- * migrate must take a file name which will have newline seperated
79
- * entries of filenames which needs to be migrated to GLACIER and
80
- * should perform migration of those files if feasible.
115
+ * migrate must take a file name (this should from the
116
+ * `GLACIER.MIGRATE_STAGE_WAL_NAME` namespace) which will have newline
117
+ * separated entries of filenames which needs to be migrated to GLACIER
118
+ * and should perform migration of those files if feasible.
81
119
*
82
120
* The function should return false if it needs the log file to be
83
121
* preserved.
84
122
*
85
123
* NOTE: This needs to be implemented by each backend.
86
124
* @param {nb.NativeFSContext } fs_context
87
- * @param {string } log_file log filename
125
+ * @param {LogFile } log_file log filename
88
126
* @param {(entry: string) => Promise<void> } failure_recorder
89
127
* @returns {Promise<boolean> }
90
128
*/
@@ -93,16 +131,39 @@ class Glacier {
93
131
}
94
132
95
133
/**
96
- * restore must take a file name which will have newline seperated
97
- * entries of filenames which needs to be restored from GLACIER and
98
- * should perform restore of those files if feasible
134
+ * stage_restore must take a log file (from `Glacier.RESTORE_STAGE_WAL_NAME`)
135
+ * which will have newline seperated entries of filenames which needs to be
136
+ * migrated to GLACIER and should stage the files for migration.
137
+ *
138
+ * The function should return false if it needs the log file to be
139
+ * preserved.
140
+ * @param {nb.NativeFSContext } fs_context
141
+ * @param {LogFile } log_file log filename
142
+ * @param {(entry: string) => Promise<void> } failure_recorder
143
+ * @returns {Promise<boolean> }
144
+ */
145
+ async stage_restore ( fs_context , log_file , failure_recorder ) {
146
+ try {
147
+ await log_file . collect ( Glacier . RESTORE_STAGE_WAL_NAME , async ( entry , batch_recorder ) => batch_recorder ( entry ) ) ;
148
+ return true ;
149
+ } catch ( error ) {
150
+ dbg . error ( 'Glacier.stage_restore error:' , error ) ;
151
+ throw error ;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * restore must take a log file (from `Glacier.RESTORE_WAL_NAME`) which will
157
+ * have newline seperated entries of filenames which needs to be
158
+ * restored from GLACIER and should perform restore of those files if
159
+ * feasible
99
160
*
100
161
* The function should return false if it needs the log file to be
101
162
* preserved.
102
163
*
103
164
* NOTE: This needs to be implemented by each backend.
104
165
* @param {nb.NativeFSContext } fs_context
105
- * @param {string } log_file log filename
166
+ * @param {LogFile } log_file log filename
106
167
* @param {(entry: string) => Promise<void> } failure_recorder
107
168
* @returns {Promise<boolean> }
108
169
*/
@@ -136,6 +197,78 @@ class Glacier {
136
197
throw new Error ( 'Unimplementented' ) ;
137
198
}
138
199
200
+ /**
201
+ * @param {nb.NativeFSContext } fs_context
202
+ * @param {"MIGRATION" | "RESTORE" | "EXPIRY" } type
203
+ */
204
+ async perform ( fs_context , type ) {
205
+ const lock_path = lock_file => path . join ( config . NSFS_GLACIER_LOGS_DIR , lock_file ) ;
206
+
207
+ if ( type === 'EXPIRY' ) {
208
+ await native_fs_utils . lock_and_run ( fs_context , lock_path ( Glacier . GLACIER_SCAN_LOCK ) , async ( ) => {
209
+ await this . expiry ( fs_context ) ;
210
+ } ) ;
211
+ }
212
+
213
+ /** @typedef {(
214
+ * fs_context: nb.NativeFSContext,
215
+ * file: LogFile,
216
+ * failure_recorder: (entry: string) => Promise<void>
217
+ * ) => Promise<boolean>} log_cb */
218
+
219
+ /**
220
+ * @param {string } namespace
221
+ * @param {log_cb } cb
222
+ */
223
+ const process_glacier_logs = async ( namespace , cb ) => {
224
+ const logs = new PersistentLogger (
225
+ config . NSFS_GLACIER_LOGS_DIR ,
226
+ namespace , { locking : 'EXCLUSIVE' } ,
227
+ ) ;
228
+ await logs . process ( async ( entry , failure_recorder ) => cb ( fs_context , entry , failure_recorder ) ) ;
229
+ } ;
230
+
231
+ /**
232
+ *
233
+ * @param {string } primary_log_ns
234
+ * @param {string } staged_log_ns
235
+ * @param {log_cb } process_staged_fn
236
+ * @param {log_cb } process_primary_fn
237
+ * @param {string } stage_lock_file
238
+ */
239
+ const run_operation = async ( primary_log_ns , staged_log_ns , process_staged_fn , process_primary_fn , stage_lock_file ) => {
240
+ // Acquire a cluster wide lock for all the operations for staging
241
+ await native_fs_utils . lock_and_run ( fs_context , lock_path ( Glacier . GLACIER_CLUSTER_LOCK ) , async ( ) => {
242
+ await process_glacier_logs ( primary_log_ns , process_staged_fn ) ;
243
+ } ) ;
244
+
245
+ // Acquire a type specific lock to consume staged logs
246
+ await native_fs_utils . lock_and_run (
247
+ fs_context , lock_path ( stage_lock_file ) , async ( ) => {
248
+ await process_glacier_logs ( staged_log_ns , process_primary_fn ) ;
249
+ }
250
+ ) ;
251
+ } ;
252
+
253
+ if ( type === 'MIGRATION' ) {
254
+ await run_operation (
255
+ Glacier . MIGRATE_WAL_NAME ,
256
+ Glacier . MIGRATE_STAGE_WAL_NAME ,
257
+ this . stage_migrate . bind ( this ) ,
258
+ this . migrate . bind ( this ) ,
259
+ Glacier . GLACIER_MIGRATE_CLUSTER_LOCK ,
260
+ ) ;
261
+ } else if ( type === 'RESTORE' ) {
262
+ await run_operation (
263
+ Glacier . RESTORE_WAL_NAME ,
264
+ Glacier . RESTORE_STAGE_WAL_NAME ,
265
+ this . stage_restore . bind ( this ) ,
266
+ this . restore . bind ( this ) ,
267
+ Glacier . GLACIER_RESTORE_CLUSTER_LOCK ,
268
+ ) ;
269
+ }
270
+ }
271
+
139
272
/**
140
273
* should_migrate returns true if the given file must be migrated
141
274
*
@@ -319,6 +452,7 @@ class Glacier {
319
452
xattr_get_keys : [
320
453
Glacier . XATTR_RESTORE_REQUEST ,
321
454
Glacier . STORAGE_CLASS_XATTR ,
455
+ Glacier . XATTR_STAGE_MIGRATE ,
322
456
] ,
323
457
} ) ;
324
458
}
0 commit comments