Skip to content

Commit 5d8f505

Browse files
committed
NC | Lifecycle | Health
Signed-off-by: Romy <35330373+romayalon@users.noreply.github.com> (cherry picked from commit b331051)
1 parent bea5a98 commit 5d8f505

File tree

4 files changed

+161
-13
lines changed

4 files changed

+161
-13
lines changed

src/manage_nsfs/health.js

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
const dbg = require('../util/debug_module')(__filename);
55
const _ = require('lodash');
6+
const path = require('path');
67
const P = require('../util/promise');
78
const config = require('../../config');
89
const os_util = require('../util/os_utils');
@@ -104,6 +105,7 @@ class NSFSHealth {
104105
this.all_bucket_details = options.all_bucket_details;
105106
this.all_connection_details = options.all_connection_details;
106107
this.notif_storage_threshold = options.notif_storage_threshold;
108+
this.lifecycle = options.lifecycle;
107109
this.config_fs = options.config_fs;
108110
}
109111

@@ -133,6 +135,7 @@ class NSFSHealth {
133135
let account_details;
134136
let connection_details;
135137
let notif_storage_threshold_details;
138+
let latest_lifecycle_run_status;
136139
const endpoint_response_code = (endpoint_state && endpoint_state.response?.response_code) || 'UNKNOWN_ERROR';
137140
const health_check_params = { service_status, pid, endpoint_response_code, config_directory_status };
138141
const service_health = this._calc_health_status(health_check_params);
@@ -141,6 +144,8 @@ class NSFSHealth {
141144
if (this.all_account_details) account_details = await this.get_account_status();
142145
if (this.all_connection_details) connection_details = await this.get_connection_status();
143146
if (this.notif_storage_threshold) notif_storage_threshold_details = this.get_notif_storage_threshold_status();
147+
if (this.lifecycle) latest_lifecycle_run_status = await this.get_lifecycle_health_status();
148+
144149
const health = {
145150
service_name: NOOBAA_SERVICE_NAME,
146151
status: service_health,
@@ -164,7 +169,8 @@ class NSFSHealth {
164169
error_type: health_errors_tyes.PERSISTENT,
165170
},
166171
connections_status: connection_details,
167-
notif_storage_threshold_details
172+
notif_storage_threshold_details,
173+
latest_lifecycle_run_status
168174
}
169175
};
170176
if (!this.all_account_details) delete health.checks.accounts_status;
@@ -333,10 +339,10 @@ class NSFSHealth {
333339
};
334340
}
335341

336-
async validate_config_dir_exists(path, type) {
337-
const config_root_type_exists = await this.config_fs.validate_config_dir_exists(path);
342+
async validate_config_dir_exists(config_dir_path, type) {
343+
const config_root_type_exists = await this.config_fs.validate_config_dir_exists(config_dir_path);
338344
if (!config_root_type_exists) {
339-
dbg.log1(`Config directory type - ${type} is missing, ${path}`);
345+
dbg.log1(`Config directory type - ${type} is missing, ${config_dir_path}`);
340346
return {
341347
invalid_storages: [],
342348
valid_storages: []
@@ -446,6 +452,48 @@ class NSFSHealth {
446452
return res;
447453
}
448454

455+
/////////////////////////////
456+
// LIFECYCLE HEALTH STATUS //
457+
/////////////////////////////
458+
459+
/**
460+
* get_lifecycle_health_status returns the lifecycle rules status based on the status of the latest lifecycle wroker run
461+
* on the same host
462+
* @returns {Promise<object>}
463+
*/
464+
async get_lifecycle_health_status() {
465+
const latest_lifecycle_run_status = await this.get_latest_lifecycle_run_status({ silent_if_missing: true });
466+
if (!latest_lifecycle_run_status) return {};
467+
return {
468+
total_stats: latest_lifecycle_run_status.total_stats,
469+
lifecycle_run_times: latest_lifecycle_run_status.lifecycle_run_times,
470+
errors: latest_lifecycle_run_status.errors
471+
};
472+
}
473+
474+
475+
/**
476+
* get_latest_lifecycle_run_status returns the latest lifecycle run status
477+
* latest run can be found by maxing the lifecycle log entry names, log entry name is the lifecycle_run_{timestamp}.json of the run
478+
* @params {{silent_if_missing: boolean}} options
479+
* @returns {Promise<object | undefined >}
480+
*/
481+
async get_latest_lifecycle_run_status(options) {
482+
const { silent_if_missing = false } = options;
483+
try {
484+
const lifecycle_log_entries = await nb_native().fs.readdir(this.config_fs.fs_context, config.NC_LIFECYCLE_LOGS_DIR);
485+
const latest_lifecycle_run = _.maxBy(lifecycle_log_entries, entry => entry.name);
486+
const latest_lifecycle_run_status_path = path.join(config.NC_LIFECYCLE_LOGS_DIR, latest_lifecycle_run.name);
487+
const latest_lifecycle_run_status = await this.config_fs.get_config_data(latest_lifecycle_run_status_path, options);
488+
return latest_lifecycle_run_status;
489+
} catch (err) {
490+
if (err.code === 'ENOENT' && silent_if_missing) {
491+
return;
492+
}
493+
throw err;
494+
}
495+
}
496+
449497
/**
450498
* get_config_file_data_or_error_object return an object containing config_data or err_obj if error occurred
451499
* @param {string} type
@@ -613,10 +661,11 @@ async function get_health_status(argv, config_fs) {
613661
const all_bucket_details = get_boolean_or_string_value(argv.all_bucket_details);
614662
const all_connection_details = get_boolean_or_string_value(argv.all_connection_details);
615663
const notif_storage_threshold = get_boolean_or_string_value(argv.notif_storage_threshold);
664+
const lifecycle = get_boolean_or_string_value(argv.lifecycle);
616665

617666
if (deployment_type === 'nc') {
618667
const health = new NSFSHealth({ https_port,
619-
all_account_details, all_bucket_details, all_connection_details, notif_storage_threshold, config_fs });
668+
all_account_details, all_bucket_details, all_connection_details, notif_storage_threshold, lifecycle, config_fs });
620669
const health_status = await health.nc_nsfs_health();
621670
write_stdout_response(ManageCLIResponse.HealthStatus, health_status);
622671
} else {

src/manage_nsfs/manage_nsfs_constants.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ const VALID_OPTIONS_GLACIER = {
7575
};
7676

7777
const VALID_OPTIONS_DIAGNOSE = {
78-
'health': new Set([ 'https_port', 'deployment_type', 'all_account_details', 'all_bucket_details', 'all_connection_details', 'notif_storage_threshold', ...CLI_MUTUAL_OPTIONS]),
78+
'health': new Set([ 'https_port', 'deployment_type', 'all_account_details', 'all_bucket_details', 'all_connection_details', 'notif_storage_threshold', 'lifecycle', ...CLI_MUTUAL_OPTIONS]),
7979
'gather-logs': new Set([ CONFIG_ROOT_FLAG]),
8080
'metrics': new Set([CONFIG_ROOT_FLAG])
8181
};
@@ -150,6 +150,7 @@ const OPTION_TYPE = {
150150
notif_storage_threshold: 'boolean',
151151
https_port: 'number',
152152
debug: 'number',
153+
lifecycle: 'boolean',
153154
// upgrade options
154155
expected_version: 'string',
155156
expected_hosts: 'string',

src/manage_nsfs/manage_nsfs_help_utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ Flags:
371371
--all_account_details <boolean> (optional) Set a flag for returning all account details.
372372
--all_bucket_details <boolean> (optional) Set a flag for returning all bucket details.
373373
--all_connection_details <boolean> (optional) Set a flag for returning all connection details.
374+
--lifecycle <boolean> (optional) Set a flag for returning lifecycle details on the current host.
374375
--debug <number> (optional) Use for increasing the log verbosity of health cli commands.
375376
--config_root <string> (optional) Set Configuration files path for Noobaa standalon NSFS. (default config.NSFS_NC_DEFAULT_CONF_DIR)
376377

src/test/unit_tests/test_nc_health.js

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
'use strict';
55

66
const path = require('path');
7+
const _ = require('lodash');
78
const mocha = require('mocha');
89
const sinon = require('sinon');
910
const assert = require('assert');
@@ -19,7 +20,7 @@ const NSFSHealth = require('../../manage_nsfs/health').NSFSHealth;
1920
const { get_process_fs_context } = require('../../util/native_fs_utils');
2021
const { ManageCLIError } = require('../../manage_nsfs/manage_nsfs_cli_errors');
2122
const { TYPES, DIAGNOSE_ACTIONS, ACTIONS } = require('../../manage_nsfs/manage_nsfs_constants');
22-
const { TMP_PATH, create_fs_user_by_platform, delete_fs_user_by_platform, exec_manage_cli } = require('../system_tests/test_utils');
23+
const { TMP_PATH, create_fs_user_by_platform, delete_fs_user_by_platform, exec_manage_cli, TEST_TIMEOUT } = require('../system_tests/test_utils');
2324
const { CONFIG_DIR_PHASES } = require('../../sdk/config_fs');
2425

2526
const tmp_fs_path = path.join(TMP_PATH, 'test_nc_health');
@@ -56,12 +57,13 @@ const connection_from_file = {
5657
name: "http_notif",
5758
};
5859

59-
mocha.describe('nsfs nc health', function() {
60+
const config_root = path.join(tmp_fs_path, 'config_root_nsfs_health');
61+
const config_fs = new ConfigFS(config_root);
62+
const root_path = path.join(tmp_fs_path, 'root_path_nsfs_health/');
63+
const config_root_invalid = path.join(tmp_fs_path, 'config_root_nsfs_health_invalid');
64+
const tmp_lifecycle_logs_dir_path = path.join(TMP_PATH, 'test_lifecycle_logs');
6065

61-
const config_root = path.join(tmp_fs_path, 'config_root_nsfs_health');
62-
const config_fs = new ConfigFS(config_root);
63-
const root_path = path.join(tmp_fs_path, 'root_path_nsfs_health/');
64-
const config_root_invalid = path.join(tmp_fs_path, 'config_root_nsfs_health_invalid');
66+
mocha.describe('nsfs nc health', function() {
6567
let Health;
6668

6769
mocha.before(async () => {
@@ -693,6 +695,85 @@ mocha.describe('nsfs nc health', function() {
693695
});
694696
});
695697

698+
699+
mocha.describe('health - lifecycle', function() {
700+
const Health = new NSFSHealth({ config_root, config_fs, lifecycle: true });
701+
const orig_lifecycle_logs_dir = config.NC_LIFECYCLE_LOGS_DIR;
702+
703+
mocha.before(async () => {
704+
await fs_utils.create_fresh_path(config_root);
705+
config.NC_LIFECYCLE_LOGS_DIR = tmp_lifecycle_logs_dir_path;
706+
});
707+
708+
mocha.after(async () => {
709+
fs_utils.folder_delete(config_root);
710+
config.NC_LIFECYCLE_LOGS_DIR = orig_lifecycle_logs_dir;
711+
});
712+
713+
mocha.beforeEach(async () => {
714+
await config_fs.create_config_json_file(JSON.stringify({ NC_LIFECYCLE_LOGS_DIR: tmp_lifecycle_logs_dir_path }));
715+
});
716+
717+
mocha.afterEach(async () => {
718+
fs_utils.folder_delete(tmp_lifecycle_logs_dir_path);
719+
await config_fs.delete_config_json_file();
720+
});
721+
722+
mocha.it('Health lifecycle - lifecycle worker never ran on host', async function() {
723+
try {
724+
await nb_native().fs.readdir(config_fs.fs_context, tmp_lifecycle_logs_dir_path);
725+
assert.fail('tmp_lifecycle_logs_dir_path should not exist');
726+
} catch (err) {
727+
assert.equal(err.code, 'ENOENT');
728+
}
729+
const health_status = await Health.nc_nsfs_health();
730+
const empty_lifecycle_health_status = {};
731+
assert_lifecycle_status(health_status, empty_lifecycle_health_status, false);
732+
});
733+
734+
mocha.it('Health lifecycle - after 1 run of the lifecycle worker', async function() {
735+
await exec_manage_cli(TYPES.LIFECYCLE, '', { config_root, disable_service_validation: true, disable_runtime_validation: true }, true);
736+
const lifecycle_log_entries = await nb_native().fs.readdir(config_fs.fs_context, tmp_lifecycle_logs_dir_path);
737+
assert.strictEqual(lifecycle_log_entries.length, 1);
738+
const log_file_path = path.join(tmp_lifecycle_logs_dir_path, lifecycle_log_entries[0].name);
739+
const lifecycle_log_json = await config_fs.get_config_data(log_file_path, { silent_if_missing: true });
740+
const health_status = await Health.nc_nsfs_health();
741+
assert_lifecycle_status(health_status, lifecycle_log_json, false);
742+
});
743+
744+
mocha.it('Health lifecycle - should report on timeout error', async function() {
745+
await config_fs.update_config_json_file(JSON.stringify({
746+
NC_LIFECYCLE_TIMEOUT_MS: 1,
747+
NC_LIFECYCLE_LOGS_DIR: tmp_lifecycle_logs_dir_path
748+
}));
749+
await exec_manage_cli(TYPES.LIFECYCLE, '', { config_root, disable_service_validation: true, disable_runtime_validation: true }, true);
750+
const lifecycle_log_entries = await nb_native().fs.readdir(config_fs.fs_context, tmp_lifecycle_logs_dir_path);
751+
assert.strictEqual(lifecycle_log_entries.length, 1);
752+
const log_file_path = path.join(tmp_lifecycle_logs_dir_path, lifecycle_log_entries[0].name);
753+
const lifecycle_log_json = await config_fs.get_config_data(log_file_path, {silent_if_missing: true});
754+
const health_status = await Health.nc_nsfs_health();
755+
assert_lifecycle_status(health_status, lifecycle_log_json, true);
756+
});
757+
758+
mocha.it('Health lifecycle - run lifecycle 3 times - should report on latest run', async function() {
759+
this.timeout(TEST_TIMEOUT);// eslint-disable-line no-invalid-this
760+
let latest_lifecycle;
761+
for (let i = 0; i < 3; i++) {
762+
latest_lifecycle = await exec_manage_cli(TYPES.LIFECYCLE, '', { config_root, disable_service_validation: true, disable_runtime_validation: true }, true);
763+
}
764+
const parsed_res_latest_lifecycle = JSON.parse(latest_lifecycle);
765+
const lifecycle_log_entries = await nb_native().fs.readdir(config_fs.fs_context, tmp_lifecycle_logs_dir_path);
766+
assert.strictEqual(lifecycle_log_entries.length, 3);
767+
const latest_log_file_path = _.maxBy(lifecycle_log_entries, entry => entry.name);
768+
const is_latest = latest_log_file_path.name.endsWith(parsed_res_latest_lifecycle.response.reply.lifecycle_run_times.run_lifecycle_start_time + '.json');
769+
assert.equal(is_latest, true);
770+
const log_file_path = path.join(tmp_lifecycle_logs_dir_path, latest_log_file_path.name);
771+
const lifecycle_log_json = await config_fs.get_config_data(log_file_path, { silent_if_missing: true });
772+
const health_status = await Health.nc_nsfs_health();
773+
assert_lifecycle_status(health_status, lifecycle_log_json, false);
774+
});
775+
});
776+
696777
/**
697778
* assert_config_dir_status asserts config directory status
698779
* @param {Object} health_status
@@ -723,6 +804,21 @@ function assert_upgrade_status(actual_upgrade_status, expected_upgrade_status) {
723804
assert.deepStrictEqual(actual_upgrade_status_res, expected_upgrade_status_res);
724805
}
725806

807+
/**
808+
* assert_lifecycle_status asserts the lifecycle status
809+
* @param {Object} health_status
810+
* @param {Object} lifecycle_log_json
811+
* @param {Boolean} is_error_expected
812+
* @returns {Void}
813+
*/
814+
function assert_lifecycle_status(health_status, lifecycle_log_json, is_error_expected = false) {
815+
assert.deepStrictEqual(health_status.checks.latest_lifecycle_run_status.total_stats, lifecycle_log_json.total_stats);
816+
assert.deepStrictEqual(health_status.checks.latest_lifecycle_run_status.lifecycle_run_times,
817+
lifecycle_log_json.lifecycle_run_times);
818+
const err_value = is_error_expected ? lifecycle_log_json.errors : undefined;
819+
assert.deepStrictEqual(health_status.checks.latest_lifecycle_run_status.errors, err_value);
820+
}
821+
726822
/**
727823
* restore_health_if_needed restores health obj functions if needed
728824
* @param {*} health_obj
@@ -740,7 +836,8 @@ function restore_health_if_needed(health_obj) {
740836
* the value is an array of responses by the order of their call
741837
* @param {Object} Health
742838
* @param {{get_endpoint_response?: Object[], get_service_state?: Object[],
743-
* get_system_config_file?: Object[], get_service_memory_usage?: Object[]}} mock_function_responses
839+
* get_system_config_file?: Object[], get_service_memory_usage?: Object[],
840+
* get_lifecycle_health_status?: Object, get_latest_lifecycle_run_status?: Object}} mock_function_responses
744841
*/
745842
function set_mock_functions(Health, mock_function_responses) {
746843
for (const mock_function_name of Object.keys(mock_function_responses)) {

0 commit comments

Comments
 (0)