Skip to content

Commit 32926c4

Browse files
committed
NC | Lifecycle | Add lock to lifecycle worker
Signed-off-by: Romy <35330373+romayalon@users.noreply.github.com>
1 parent d908885 commit 32926c4

File tree

8 files changed

+295
-126
lines changed

8 files changed

+295
-126
lines changed

config.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,27 @@ config.NC_DISABLE_HEALTH_ACCESS_CHECK = false;
961961
config.NC_DISABLE_POSIX_MODE_ACCESS_CHECK = true;
962962
config.NC_DISABLE_SCHEMA_CHECK = false;
963963

964+
////////// NC LIFECYLE //////////
965+
966+
config.LIFECYCLE_LOGS_DIR = '/var/log/noobaa/lifecycle';
967+
968+
// NC_LIFECYCLE_RUN_TIME must be of the format hh:mm which specifies
969+
// when NooBaa should allow running nc lifecycle process
970+
// NOTE: This will also be in the same timezone as specified in
971+
// NC_LIFECYCLE_TZ
972+
config.NC_LIFECYCLE_RUN_TIME = '01:00';
973+
974+
// NC_LIFECYCLE_RUN_DELAY_LIMIT_MINS configures the delay
975+
// tolerance in minutes.
976+
//
977+
// eg. If the expiry run time is set to 01:00 and the tolerance is
978+
// set to be 2 mins then the expiry can trigger till 01:02 (unless
979+
// already triggered between 01:00 - 01:02
980+
config.NC_LIFECYCLE_RUN_DELAY_LIMIT_MINS = 2;
981+
982+
/** @type {'UTC' | 'LOCAL'} */
983+
config.NC_LIFECYCLE_TZ = 'LOCAL';
984+
964985
////////// GPFS //////////
965986
config.GPFS_DOWN_DELAY = 1000;
966987

src/cmd/manage_nsfs.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -881,8 +881,9 @@ async function list_connections() {
881881
* @returns {Promise<void>}
882882
*/
883883
async function lifecycle_management(args) {
884-
const disable_service_validation = args.disable_service_validation === 'true';
885-
await noobaa_cli_lifecycle.run_lifecycle(config_fs, disable_service_validation);
884+
const disable_service_validation = get_boolean_or_string_value(args.disable_service_validation);
885+
const disable_runtime_validation = get_boolean_or_string_value(args.disable_runtime_validation);
886+
await noobaa_cli_lifecycle.run_lifecycle_under_lock(config_fs, disable_service_validation, disable_runtime_validation);
886887
}
887888

888889
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

@@ -155,7 +155,8 @@ const OPTION_TYPE = {
155155
custom_upgrade_scripts_dir: 'string',
156156
skip_verification: 'boolean',
157157
// lifecycle options
158-
disable_service_validation: 'string',
158+
disable_service_validation: 'boolean',
159+
disable_runtime_validation: 'boolean',
159160
//connection
160161
notification_protocol: 'string',
161162
agent_request_object: 'string',
@@ -168,7 +169,7 @@ const OPTION_TYPE = {
168169

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

173174
// CLI UNSET VALUES
174175
const CLI_EMPTY_STRING = '';

src/manage_nsfs/manage_nsfs_glacier.js

Lines changed: 19 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,24 @@ const nb_native = require('../util/nb_native');
88
const { GlacierBackend } = require('../sdk/nsfs_glacier_backend/backend');
99
const { getGlacierBackend } = require('../sdk/nsfs_glacier_backend/helper');
1010
const native_fs_utils = require('../util/native_fs_utils');
11+
const { is_desired_time, record_current_time } = require('./manage_nsfs_cli_utils');
1112

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

1516
async function process_migrations() {
1617
const fs_context = native_fs_utils.get_process_fs_context();
17-
18-
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 () => {
1920
const backend = getGlacierBackend();
2021

2122
if (
2223
await backend.low_free_space() ||
2324
await time_exceeded(fs_context, config.NSFS_GLACIER_MIGRATE_INTERVAL, GlacierBackend.MIGRATE_TIMESTAMP_FILE)
2425
) {
2526
await run_glacier_migrations(fs_context, backend);
26-
await record_current_time(fs_context, GlacierBackend.MIGRATE_TIMESTAMP_FILE);
27+
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, GlacierBackend.MIGRATE_TIMESTAMP_FILE);
28+
await record_current_time(fs_context, timestamp_file_path);
2729
}
2830
});
2931
}
@@ -40,8 +42,8 @@ async function run_glacier_migrations(fs_context, backend) {
4042

4143
async function process_restores() {
4244
const fs_context = native_fs_utils.get_process_fs_context();
43-
44-
await lock_and_run(fs_context, CLUSTER_LOCK, async () => {
45+
const lock_path = path.join(config.NSFS_GLACIER_LOGS_DIR, CLUSTER_LOCK);
46+
await native_fs_utils.lock_and_run(fs_context, lock_path, async () => {
4547
const backend = getGlacierBackend();
4648

4749
if (
@@ -51,7 +53,8 @@ async function process_restores() {
5153

5254

5355
await run_glacier_restore(fs_context, backend);
54-
await record_current_time(fs_context, GlacierBackend.RESTORE_TIMESTAMP_FILE);
56+
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, GlacierBackend.RESTORE_TIMESTAMP_FILE);
57+
await record_current_time(fs_context, timestamp_file_path);
5558
});
5659
}
5760

@@ -67,68 +70,27 @@ async function run_glacier_restore(fs_context, backend) {
6770

6871
async function process_expiry() {
6972
const fs_context = native_fs_utils.get_process_fs_context();
70-
71-
await lock_and_run(fs_context, SCAN_LOCK, async () => {
73+
const lock_path = path.join(config.NSFS_GLACIER_LOGS_DIR, SCAN_LOCK);
74+
await native_fs_utils.lock_and_run(fs_context, lock_path, async () => {
7275
const backend = getGlacierBackend();
76+
const timestamp_file_path = path.join(config.NSFS_GLACIER_LOGS_DIR, GlacierBackend.EXPIRY_TIMESTAMP_FILE);
7377
if (
7478
await backend.low_free_space() ||
7579
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-
GlacierBackend.EXPIRY_TIMESTAMP_FILE,
80+
fs_context,
81+
new Date(),
82+
config.NSFS_GLACIER_EXPIRY_RUN_TIME,
83+
config.NSFS_GLACIER_EXPIRY_RUN_DELAY_LIMIT_MINS,
84+
timestamp_file_path,
85+
config.NSFS_GLACIER_EXPIRY_TZ
8186
)
8287
) {
8388
await backend.expiry(fs_context);
84-
await record_current_time(fs_context, GlacierBackend.EXPIRY_TIMESTAMP_FILE);
89+
await record_current_time(fs_context, timestamp_file_path);
8590
}
8691
});
8792
}
8893

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-
}
13294

13395
/**
13496
* time_exceeded returns true if the time between last run recorded in the given
@@ -154,20 +116,6 @@ async function time_exceeded(fs_context, interval, timestamp_file) {
154116
return false;
155117
}
156118

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-
171119
/**
172120
* run_glacier_operations takes a log_namespace and a callback and executes the
173121
* callback on each log file in that namespace. It will also generate a failure
@@ -187,49 +135,6 @@ async function run_glacier_operation(fs_context, log_namespace, cb) {
187135
}
188136
}
189137

190-
/**
191-
* @param {number} hours
192-
* @param {number} mins
193-
* @param {number} secs
194-
* @param {'UTC' | 'LOCAL'} tz
195-
* @returns {Date}
196-
*/
197-
function get_tz_date(hours, mins, secs, tz) {
198-
const date = new Date();
199-
200-
if (tz === 'UTC') {
201-
date.setUTCHours(hours);
202-
date.setUTCMinutes(hours);
203-
date.setUTCSeconds(secs);
204-
date.setUTCMilliseconds(0);
205-
} else {
206-
date.setHours(hours);
207-
date.setMinutes(mins);
208-
date.setSeconds(secs);
209-
date.setMilliseconds(0);
210-
}
211-
212-
return date;
213-
}
214-
215-
/**
216-
* lock_and_run acquires a flock and calls the given callback after
217-
* acquiring the lock
218-
* @param {nb.NativeFSContext} fs_context
219-
* @param {string} lockfilename
220-
* @param {Function} cb
221-
*/
222-
async function lock_and_run(fs_context, lockfilename, cb) {
223-
const lockfd = await nb_native().fs.open(fs_context, path.join(config.NSFS_GLACIER_LOGS_DIR, lockfilename), 'w');
224-
225-
try {
226-
await lockfd.fcntllock(fs_context, 'EXCLUSIVE');
227-
await cb();
228-
} finally {
229-
await lockfd.close(fs_context);
230-
}
231-
}
232-
233138
exports.process_migrations = process_migrations;
234139
exports.process_restores = process_restores;
235140
exports.process_expiry = process_expiry;

0 commit comments

Comments
 (0)