Skip to content

Commit a3d32f5

Browse files
committed
NC | Lifecycle | Add lock to lifecycle worker
Signed-off-by: Romy <35330373+romayalon@users.noreply.github.com> (cherry picked from commit 32926c4)
1 parent 60ec881 commit a3d32f5

File tree

8 files changed

+295
-124
lines changed

8 files changed

+295
-124
lines changed

config.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,27 @@ config.NC_DISABLE_HEALTH_ACCESS_CHECK = false;
998998
config.NC_DISABLE_POSIX_MODE_ACCESS_CHECK = true;
999999
config.NC_DISABLE_SCHEMA_CHECK = false;
10001000

1001+
////////// NC LIFECYLE //////////
1002+
1003+
config.LIFECYCLE_LOGS_DIR = '/var/log/noobaa/lifecycle';
1004+
1005+
// NC_LIFECYCLE_RUN_TIME must be of the format hh:mm which specifies
1006+
// when NooBaa should allow running nc lifecycle process
1007+
// NOTE: This will also be in the same timezone as specified in
1008+
// NC_LIFECYCLE_TZ
1009+
config.NC_LIFECYCLE_RUN_TIME = '01:00';
1010+
1011+
// NC_LIFECYCLE_RUN_DELAY_LIMIT_MINS configures the delay
1012+
// tolerance in minutes.
1013+
//
1014+
// eg. If the expiry run time is set to 01:00 and the tolerance is
1015+
// set to be 2 mins then the expiry can trigger till 01:02 (unless
1016+
// already triggered between 01:00 - 01:02
1017+
config.NC_LIFECYCLE_RUN_DELAY_LIMIT_MINS = 2;
1018+
1019+
/** @type {'UTC' | 'LOCAL'} */
1020+
config.NC_LIFECYCLE_TZ = 'LOCAL';
1021+
10011022
////////// GPFS //////////
10021023
config.GPFS_DOWN_DELAY = 1000;
10031024

src/cmd/manage_nsfs.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -803,8 +803,9 @@ async function connection_management(action, user_input) {
803803
* @returns {Promise<void>}
804804
*/
805805
async function lifecycle_management(args) {
806-
const disable_service_validation = args.disable_service_validation === 'true';
807-
await noobaa_cli_lifecycle.run_lifecycle(config_fs, disable_service_validation);
806+
const disable_service_validation = get_boolean_or_string_value(args.disable_service_validation);
807+
const disable_runtime_validation = get_boolean_or_string_value(args.disable_runtime_validation);
808+
await noobaa_cli_lifecycle.run_lifecycle_under_lock(config_fs, disable_service_validation, disable_runtime_validation);
808809
}
809810

810811
exports.main = main;

src/manage_nsfs/manage_nsfs_cli_utils.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,91 @@ async function get_service_status(service_name) {
198198
return service_status;
199199
}
200200

201+
/**
202+
* is_desired_time returns true if the given time matches with
203+
* the desired time or if
204+
* @param {nb.NativeFSContext} fs_context
205+
* @param {Date} current
206+
* @param {string} desire time in format 'hh:mm'
207+
* @param {number} delay_limit_mins
208+
* @param {string} timestamp_file_path
209+
* @param {"UTC" | "LOCAL"} timezone
210+
* @returns {Promise<boolean>}
211+
*/
212+
async function is_desired_time(fs_context, current, desire, delay_limit_mins, timestamp_file_path, timezone) {
213+
const [desired_hour, desired_min] = desire.split(':').map(Number);
214+
if (
215+
isNaN(desired_hour) ||
216+
isNaN(desired_min) ||
217+
(desired_hour < 0 || desired_hour >= 24) ||
218+
(desired_min < 0 || desired_min >= 60)
219+
) {
220+
throw new Error('invalid desired_time - must be hh:mm');
221+
}
222+
223+
const min_time = get_tz_date(desired_hour, desired_min, 0, timezone);
224+
const max_time = get_tz_date(desired_hour, desired_min + delay_limit_mins, 0, timezone);
225+
226+
if (current >= min_time && current <= max_time) {
227+
try {
228+
const { data } = await nb_native().fs.readFile(fs_context, timestamp_file_path);
229+
const lastrun = new Date(data.toString());
230+
231+
// Last run should NOT be in this window
232+
if (lastrun >= min_time && lastrun <= max_time) return false;
233+
} catch (error) {
234+
if (error.code === 'ENOENT') return true;
235+
console.error('failed to read last run timestamp:', error, 'timestamp_file_path:', timestamp_file_path);
236+
237+
throw error;
238+
}
239+
240+
return true;
241+
}
242+
243+
return false;
244+
}
245+
246+
247+
/**
248+
* record_current_time stores the current timestamp in ISO format into
249+
* the given timestamp file
250+
* @param {nb.NativeFSContext} fs_context
251+
* @param {string} timestamp_file_path
252+
*/
253+
async function record_current_time(fs_context, timestamp_file_path) {
254+
await nb_native().fs.writeFile(
255+
fs_context,
256+
timestamp_file_path,
257+
Buffer.from(new Date().toISOString()),
258+
);
259+
}
260+
261+
/**
262+
* @param {number} hours
263+
* @param {number} mins
264+
* @param {number} secs
265+
* @param {'UTC' | 'LOCAL'} tz
266+
* @returns {Date}
267+
*/
268+
function get_tz_date(hours, mins, secs, tz) {
269+
const date = new Date();
270+
271+
if (tz === 'UTC') {
272+
date.setUTCHours(hours);
273+
date.setUTCMinutes(hours);
274+
date.setUTCSeconds(secs);
275+
date.setUTCMilliseconds(0);
276+
} else {
277+
date.setHours(hours);
278+
date.setMinutes(mins);
279+
date.setSeconds(secs);
280+
date.setMilliseconds(0);
281+
}
282+
283+
return date;
284+
}
285+
201286
// EXPORTS
202287
exports.throw_cli_error = throw_cli_error;
203288
exports.write_stdout_response = write_stdout_response;
@@ -212,3 +297,6 @@ exports.is_name_update = is_name_update;
212297
exports.is_access_key_update = is_access_key_update;
213298
exports.get_service_status = get_service_status;
214299
exports.NOOBAA_SERVICE_NAME = NOOBAA_SERVICE_NAME;
300+
exports.is_desired_time = is_desired_time;
301+
exports.record_current_time = record_current_time;
302+
exports.get_tz_date = get_tz_date;

src/manage_nsfs/manage_nsfs_constants.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ const VALID_OPTIONS_CONNECTION = {
9696
'status': new Set(['name', 'decrypt', ...CLI_MUTUAL_OPTIONS]),
9797
};
9898

99-
const VALID_OPTIONS_LIFECYCLE = new Set(['disable_service_validation', ...CLI_MUTUAL_OPTIONS]);
99+
const VALID_OPTIONS_LIFECYCLE = new Set(['disable_service_validation', 'disable_runtime_validation', ...CLI_MUTUAL_OPTIONS]);
100100

101101
const VALID_OPTIONS_WHITELIST = new Set(['ips', ...CLI_MUTUAL_OPTIONS]);
102102

@@ -154,7 +154,8 @@ const OPTION_TYPE = {
154154
custom_upgrade_scripts_dir: 'string',
155155
skip_verification: 'boolean',
156156
// lifecycle options
157-
disable_service_validation: 'string',
157+
disable_service_validation: 'boolean',
158+
disable_runtime_validation: 'boolean',
158159
//connection
159160
notification_protocol: 'string',
160161
agent_request_object: 'string',
@@ -167,7 +168,7 @@ const OPTION_TYPE = {
167168

168169
const BOOLEAN_STRING_VALUES = ['true', 'false'];
169170
const BOOLEAN_STRING_OPTIONS = new Set(['allow_bucket_creation', 'regenerate', 'wide', 'show_secrets', 'force',
170-
'force_md5_etag', 'iam_operate_on_root_account', 'all_account_details', 'all_bucket_details', 'anonymous']);
171+
'force_md5_etag', 'iam_operate_on_root_account', 'all_account_details', 'all_bucket_details', 'anonymous', 'disable_service_validation', 'disable_runtime_validation']);
171172

172173
//options that can be unset using ''
173174
const LIST_UNSETABLE_OPTIONS = ['fs_backend', 's3_policy', 'force_md5_etag'];

src/manage_nsfs/manage_nsfs_glacier.js

Lines changed: 19 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ const config = require('../../config');
77
const nb_native = require('../util/nb_native');
88
const { Glacier } = require('../sdk/glacier');
99
const native_fs_utils = require('../util/native_fs_utils');
10+
const { is_desired_time, record_current_time } = require('./manage_nsfs_cli_utils');
1011

1112
const CLUSTER_LOCK = 'cluster.lock';
1213
const SCAN_LOCK = 'scan.lock';
1314

1415
async function process_migrations() {
1516
const fs_context = native_fs_utils.get_process_fs_context();
1617

17-
await lock_and_run(fs_context, CLUSTER_LOCK, async () => {
18+
const lock_path = path.join(config.NSFS_GLACIER_LOGS_DIR, CLUSTER_LOCK);
19+
await native_fs_utils.lock_and_run(fs_context, lock_path, async () => {
1820
const backend = Glacier.getBackend();
1921

2022
if (
@@ -23,7 +25,8 @@ async function process_migrations() {
2325
await migrate_log_exceeds_threshold()
2426
) {
2527
await run_glacier_migrations(fs_context, backend);
26-
await record_current_time(fs_context, Glacier.MIGRATE_TIMESTAMP_FILE);
28+
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, Glacier.MIGRATE_TIMESTAMP_FILE);
29+
await record_current_time(fs_context, timestamp_file_path);
2730
}
2831
});
2932
}
@@ -41,7 +44,8 @@ async function run_glacier_migrations(fs_context, backend) {
4144
async function process_restores() {
4245
const fs_context = native_fs_utils.get_process_fs_context();
4346

44-
await lock_and_run(fs_context, CLUSTER_LOCK, async () => {
47+
const lock_path = path.join(config.NSFS_GLACIER_LOGS_DIR, CLUSTER_LOCK);
48+
await native_fs_utils.lock_and_run(fs_context, lock_path, async () => {
4549
const backend = Glacier.getBackend();
4650

4751
if (
@@ -51,7 +55,8 @@ async function process_restores() {
5155

5256

5357
await run_glacier_restore(fs_context, backend);
54-
await record_current_time(fs_context, Glacier.RESTORE_TIMESTAMP_FILE);
58+
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, Glacier.RESTORE_TIMESTAMP_FILE);
59+
await record_current_time(fs_context, timestamp_file_path);
5560
});
5661
}
5762

@@ -67,68 +72,27 @@ async function run_glacier_restore(fs_context, backend) {
6772

6873
async function process_expiry() {
6974
const fs_context = native_fs_utils.get_process_fs_context();
70-
71-
await lock_and_run(fs_context, SCAN_LOCK, async () => {
75+
const lock_path = path.join(config.NSFS_GLACIER_LOGS_DIR, SCAN_LOCK);
76+
await native_fs_utils.lock_and_run(fs_context, lock_path, async () => {
7277
const backend = Glacier.getBackend();
78+
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, Glacier.EXPIRY_TIMESTAMP_FILE);
7379
if (
7480
await backend.low_free_space() ||
7581
await is_desired_time(
76-
fs_context,
77-
new Date(),
78-
config.NSFS_GLACIER_EXPIRY_RUN_TIME,
79-
config.NSFS_GLACIER_EXPIRY_RUN_DELAY_LIMIT_MINS,
80-
Glacier.EXPIRY_TIMESTAMP_FILE,
82+
fs_context,
83+
new Date(),
84+
config.NSFS_GLACIER_EXPIRY_RUN_TIME,
85+
config.NSFS_GLACIER_EXPIRY_RUN_DELAY_LIMIT_MINS,
86+
timestamp_file_path,
87+
config.NSFS_GLACIER_EXPIRY_TZ
8188
)
8289
) {
8390
await backend.expiry(fs_context);
84-
await record_current_time(fs_context, Glacier.EXPIRY_TIMESTAMP_FILE);
91+
await record_current_time(fs_context, timestamp_file_path);
8592
}
8693
});
8794
}
8895

89-
/**
90-
* is_desired_time returns true if the given time matches with
91-
* the desired time or if
92-
* @param {nb.NativeFSContext} fs_context
93-
* @param {Date} current
94-
* @param {string} desire time in format 'hh:mm'
95-
* @param {number} delay_limit_mins
96-
* @param {string} timestamp_file
97-
* @returns {Promise<boolean>}
98-
*/
99-
async function is_desired_time(fs_context, current, desire, delay_limit_mins, timestamp_file) {
100-
const [desired_hour, desired_min] = desire.split(':').map(Number);
101-
if (
102-
isNaN(desired_hour) ||
103-
isNaN(desired_min) ||
104-
(desired_hour < 0 || desired_hour >= 24) ||
105-
(desired_min < 0 || desired_min >= 60)
106-
) {
107-
throw new Error('invalid desired_time - must be hh:mm');
108-
}
109-
110-
const min_time = get_tz_date(desired_hour, desired_min, 0, config.NSFS_GLACIER_EXPIRY_TZ);
111-
const max_time = get_tz_date(desired_hour, desired_min + delay_limit_mins, 0, config.NSFS_GLACIER_EXPIRY_TZ);
112-
113-
if (current >= min_time && current <= max_time) {
114-
try {
115-
const { data } = await nb_native().fs.readFile(fs_context, path.join(config.NSFS_GLACIER_LOGS_DIR, timestamp_file));
116-
const lastrun = new Date(data.toString());
117-
118-
// Last run should NOT be in this window
119-
if (lastrun >= min_time && lastrun <= max_time) return false;
120-
} catch (error) {
121-
if (error.code === 'ENOENT') return true;
122-
console.error('failed to read last run timestamp:', error, 'timestamp_file:', timestamp_file);
123-
124-
throw error;
125-
}
126-
127-
return true;
128-
}
129-
130-
return false;
131-
}
13296

13397
/**
13498
* time_exceeded returns true if the time between last run recorded in the given
@@ -154,20 +118,6 @@ async function time_exceeded(fs_context, interval, timestamp_file) {
154118
return false;
155119
}
156120

157-
/**
158-
* record_current_time stores the current timestamp in ISO format into
159-
* the given timestamp file
160-
* @param {nb.NativeFSContext} fs_context
161-
* @param {string} timestamp_file
162-
*/
163-
async function record_current_time(fs_context, timestamp_file) {
164-
await nb_native().fs.writeFile(
165-
fs_context,
166-
path.join(config.NSFS_GLACIER_LOGS_DIR, timestamp_file),
167-
Buffer.from(new Date().toISOString()),
168-
);
169-
}
170-
171121
/**
172122
* migrate_log_exceeds_threshold returns true if the underlying backend
173123
* decides that the migrate log size has exceeded the given size threshold.
@@ -210,49 +160,6 @@ async function run_glacier_operation(fs_context, log_namespace, cb) {
210160
}
211161
}
212162

213-
/**
214-
* @param {number} hours
215-
* @param {number} mins
216-
* @param {number} secs
217-
* @param {'UTC' | 'LOCAL'} tz
218-
* @returns {Date}
219-
*/
220-
function get_tz_date(hours, mins, secs, tz) {
221-
const date = new Date();
222-
223-
if (tz === 'UTC') {
224-
date.setUTCHours(hours);
225-
date.setUTCMinutes(hours);
226-
date.setUTCSeconds(secs);
227-
date.setUTCMilliseconds(0);
228-
} else {
229-
date.setHours(hours);
230-
date.setMinutes(mins);
231-
date.setSeconds(secs);
232-
date.setMilliseconds(0);
233-
}
234-
235-
return date;
236-
}
237-
238-
/**
239-
* lock_and_run acquires a flock and calls the given callback after
240-
* acquiring the lock
241-
* @param {nb.NativeFSContext} fs_context
242-
* @param {string} lockfilename
243-
* @param {Function} cb
244-
*/
245-
async function lock_and_run(fs_context, lockfilename, cb) {
246-
const lockfd = await nb_native().fs.open(fs_context, path.join(config.NSFS_GLACIER_LOGS_DIR, lockfilename), 'w');
247-
248-
try {
249-
await lockfd.fcntllock(fs_context, 'EXCLUSIVE');
250-
await cb();
251-
} finally {
252-
await lockfd.close(fs_context);
253-
}
254-
}
255-
256163
/**
257164
* prepare_galcier_fs_context returns a shallow copy of given
258165
* fs_context with backend set to 'GPFS'.

0 commit comments

Comments
 (0)