From 5a043b434b19ab995f756d616769e500e2bc9c22 Mon Sep 17 00:00:00 2001 From: Marcin Haba Date: Sun, 12 May 2024 03:35:56 +0200 Subject: [PATCH] Add endpoint to run health self-test --- API/Modules/SelfTest.php | 354 +++++++++++++++++++++++++++++++++ API/Modules/SelfTestResult.php | 138 +++++++++++++ API/Pages/API/SelfTestRun.php | 33 +++ API/Pages/API/config.xml | 1 + API/Pages/API/endpoints.xml | 1 + 5 files changed, 527 insertions(+) create mode 100644 API/Modules/SelfTest.php create mode 100644 API/Modules/SelfTestResult.php create mode 100644 API/Pages/API/SelfTestRun.php diff --git a/API/Modules/SelfTest.php b/API/Modules/SelfTest.php new file mode 100644 index 0000000..d20435e --- /dev/null +++ b/API/Modules/SelfTest.php @@ -0,0 +1,354 @@ + + * @category Module + */ +class SelfTest extends APIModule +{ + private function configModuleTest(): array + { + $result = []; + $misc = $this->getModule('misc'); + $api_config = $this->getModule('api_config'); + $json_tools = $this->getModule('json_tools'); + $components = $misc->getComponents(); + $section = 'Bacula configuration'; + + // check if Bacula config function is enabled + $test = new SelfTestResult(); + $name = 'Bacula configuration feature is enabled.'; + $test->setName($name); + $test->setSection($section); + $test->setType('boolean'); + $function_enabled = $api_config->isJSONToolsEnabled(); + $test->setResult($function_enabled); + $state = !$function_enabled ? SelfTestResult::TEST_RESULT_STATE_DISABLED : SelfTestResult::TEST_RESULT_STATE_INFO; + $test->setState($state); + $description = SelfTestResult::TEST_RESULT_DESC_OK; + if (!$function_enabled) { + $description = sprintf( + '%s. If you plan to configure Bacula using the Bacularis web interface, Bacula configuration function needs to be enabled in the API.', + SelfTestResult::TEST_RESULT_DESC_DISABLED + ); + } + $test->setDescription($description); + $result[] = $test->toArray(); + + for ($i = 0; $i < count($components); $i++) { + $comp = $misc->getComponentFullName($components[$i]); + + // check if component config is configured + $test = new SelfTestResult(); + $name = sprintf('%s configuration function is configured.', $comp); + $test->setName($name); + $test->setSection($section); + $test->setType('boolean'); + $result_configured = $api_config->isJSONToolConfigured($components[$i]); + $test->setResult($result_configured); + $state_configured = !$function_enabled || !$result_configured ? SelfTestResult::TEST_RESULT_STATE_DISABLED : SelfTestResult::TEST_RESULT_STATE_INFO; + $test->setState($state_configured); + $description = SelfTestResult::TEST_RESULT_DESC_OK; + if (!$function_enabled || !$result_configured) { + $description = sprintf( + '%s. If you plan to configure Bacula %s using the Bacularis web interface, Bacula %s configuration function needs to be configured in the API.', + SelfTestResult::TEST_RESULT_DESC_DISABLED, + $comp, + $comp + ); + } + $test->setDescription($description); + $result[] = $test->toArray(); + + // check if config is readable + $test = new SelfTestResult(); + $name = sprintf('%s configuration is readable.', $comp); + $test->setName($name); + $test->setSection($section); + $test->setType('boolean'); + $state_read = ''; + $description = ''; + if ($function_enabled && $result_configured) { + $ret = $json_tools->execCommand( + $components[$i] + ); + $result_read = ($ret['exitcode'] === 0); + $test->setResult($result_read); + $state_read = SelfTestResult::TEST_RESULT_STATE_INFO; + $description = SelfTestResult::TEST_RESULT_DESC_OK; + if (!$result_read) { + $state_read = SelfTestResult::TEST_RESULT_STATE_ERROR; + $description = sprintf( + '%s. Bacula %s configuration is not possible to read.', + SelfTestResult::TEST_RESULT_DESC_ERROR, + $comp + ); + } + } else { + $test->setResult(true); + $state_read = SelfTestResult::TEST_RESULT_STATE_DISABLED; + $description = sprintf( + '%s. Test skipped.', + SelfTestResult::TEST_RESULT_DESC_DISABLED + ); + } + $test->setState($state_read); + $test->setDescription($description); + $result[] = $test->toArray(); + + // check if config is writeable + $test = new SelfTestResult(); + $name = sprintf('%s configuration is writeable.', $comp); + $test->setName($name); + $test->setSection($section); + $test->setType('boolean'); + $state_write = ''; + $description = ''; + if ($function_enabled && $result_configured) { + $ret = $api_config->getJSONToolConfig( + $components[$i] + ); + $result_write = is_writeable($ret['cfg']); + $test->setResult($result_write); + $state_write = SelfTestResult::TEST_RESULT_STATE_INFO; + $description = SelfTestResult::TEST_RESULT_DESC_OK; + if (!$result_write) { + $state_write = SelfTestResult::TEST_RESULT_STATE_ERROR; + $description = sprintf( + '%s. Bacula %s configuration is not possible to write.', + SelfTestResult::TEST_RESULT_DESC_ERROR, + $comp + ); + } + } else { + $test->setResult(true); + $state_write = SelfTestResult::TEST_RESULT_STATE_DISABLED; + $description = sprintf( + '%s. Test skipped.', + SelfTestResult::TEST_RESULT_DESC_DISABLED + ); + } + $test->setState($state_write); + $test->setDescription($description); + $result[] = $test->toArray(); + + } + return $result; + } + + private function bconsoleTest(): array + { + $result = []; + $bconsole = $this->getModule('bconsole'); + $api_config = $this->getModule('api_config'); + $config = $api_config->getConfig('bconsole'); + $section = 'Bconsole'; + + // check if Bacula console is enabled + $test = new SelfTestResult(); + $name = 'Bconsole feature is enabled.'; + $test->setName($name); + $test->setSection($section); + $test->setType('boolean'); + $result_enabled = $config['enabled'] === '1'; + $test->setResult($result_enabled); + $state_enabled = $result_enabled ? SelfTestResult::TEST_RESULT_STATE_INFO : SelfTestResult::TEST_RESULT_STATE_DISABLED; + $test->setState($state_enabled); + $description = SelfTestResult::TEST_RESULT_DESC_OK; + if (!$result_enabled) { + $description = SelfTestResult::TEST_RESULT_DESC_DISABLED; + } + $test->setDescription($description); + $result[] = $test->toArray(); + + // check if Bacula console is accessible + $test = new SelfTestResult(); + $name = 'Bconsole is accessible.'; + $test->setName($name); + $test->setSection($section); + $test->setType('boolean'); + $sudo = [ + 'use_sudo' => $config['use_sudo'], + 'user' => $config['sudo_user'] ?? '', + 'group' => $config['sudo_group'] ?? '' + ]; + $ret = $bconsole->testBconsoleCommand(['version'], $config['bin_path'], $config['cfg_path'], $sudo); + $result_accessible = ($ret->exitcode === 0); + $test->setResult($result_accessible); + if ($result_enabled) { + if ($result_accessible) { + $state_accessible = SelfTestResult::TEST_RESULT_STATE_INFO; + $description = SelfTestResult::TEST_RESULT_DESC_OK; + } else { + $state_accessible = SelfTestResult::TEST_RESULT_STATE_ERROR; + $description = sprintf( + '%s. Bconsole is enabled but not accessible.', + SelfTestResult::TEST_RESULT_DESC_ERROR + ); + } + } else { + $state_accessible = SelfTestResult::TEST_RESULT_STATE_DISABLED; + $description = sprintf( + '%s. Test skipped.', + SelfTestResult::TEST_RESULT_DESC_DISABLED + ); + } + $test->setState($state_accessible); + $test->setDescription($description); + $result[] = $test->toArray(); + + return $result; + } + + private function catalogTest(): array + { + $result = []; + $db = $this->getModule('db'); + $api_config = $this->getModule('api_config'); + $config = $api_config->getConfig('db'); + $section = 'Catalog'; + + // check if Catalog is enabled + $test = new SelfTestResult(); + $name = 'Catalog feature is enabled.'; + $test->setName($name); + $test->setSection($section); + $test->setType('boolean'); + $result_enabled = $config['enabled'] === '1'; + $test->setResult($result_enabled); + $state_enabled = SelfTestResult::TEST_RESULT_STATE_INFO; + $test->setState($state_enabled); + if ($result_enabled) { + $description = SelfTestResult::TEST_RESULT_DESC_OK; + } else { + $description = SelfTestResult::TEST_RESULT_DESC_DISABLED; + } + $test->setDescription($description); + $result[] = $test->toArray(); + + // check if Catalog is accessible + $test = new SelfTestResult(); + $name = 'Catalog feature is accessible.'; + $test->setName($name); + $test->setSection($section); + $test->setType('boolean'); + $result_accessible = false; + $state_accessible = $description = ''; + if ($result_enabled) { + try { + $result_accessible = $db->testCatalog(); + } catch (BCatalogException $e) { + $result_accessible = false; + } + if ($result_accessible) { + $state_accessible = SelfTestResult::TEST_RESULT_STATE_INFO; + $description = SelfTestResult::TEST_RESULT_DESC_OK; + } else { + $state_accessible = SelfTestResult::TEST_RESULT_STATE_ERROR; + $description = sprintf( + '%s. Catalog database is not accessible', + SelfTestResult::TEST_RESULT_DESC_ERROR + ); + } + } else { + $state_accessible = SelfTestResult::TEST_RESULT_STATE_DISABLED; + $description = sprintf( + '%s. Test skipped', + SelfTestResult::TEST_RESULT_DESC_DISABLED + ); + } + $test->setResult($result_accessible); + $test->setState($state_accessible); + $test->setDescription($description); + $result[] = $test->toArray(); + + return $result; + } + + private function actionsTest(): array + { + $result = []; + $bconsole = $this->getModule('bconsole'); + $api_config = $this->getModule('api_config'); + $section = 'Actions'; + + // Check if actions are enabled + $test = new SelfTestResult(); + $name = 'Actions feature is enabled.'; + $test->setName($name); + $test->setSection($section); + $test->setType('boolean'); + $result_enabled = $api_config->isActionsEnabled(); + $test->setResult($result_enabled); + if ($result_enabled) { + $state_enabled = SelfTestResult::TEST_RESULT_STATE_INFO; + $description = SelfTestResult::TEST_RESULT_DESC_OK; + } else { + $state_enabled = SelfTestResult::TEST_RESULT_STATE_DISABLED; + $description = SelfTestResult::TEST_RESULT_DESC_DISABLED; + } + $test->setState($state_enabled); + $test->setDescription($description); + $result[] = $test->toArray(); + + return $result; + } + + private function softwareManagementTest(): array + { + $result = []; + $bconsole = $this->getModule('bconsole'); + $api_config = $this->getModule('api_config'); + $section = 'Software management'; + + // Check if software management is enabled + $test = new SelfTestResult(); + $name = 'Software management feature is enabled.'; + $test->setName($name); + $test->setSection($section); + $test->setType('boolean'); + $result_enabled = $api_config->isSoftwareManagementEnabled(); + $test->setResult($result_enabled); + if ($result_enabled) { + $state_enabled = SelfTestResult::TEST_RESULT_STATE_INFO; + $description = SelfTestResult::TEST_RESULT_DESC_OK; + } else { + $state_enabled = SelfTestResult::TEST_RESULT_STATE_DISABLED; + $description = SelfTestResult::TEST_RESULT_DESC_DISABLED; + } + $test->setState($state_enabled); + $test->setDescription($description); + $result[] = $test->toArray(); + + return $result; + } + + public function getTestResults(): array + { + $db = $this->catalogTest(); + $config = $this->configModuleTest(); + $bconsole = $this->bconsoleTest(); + $actions = $this->actionsTest(); + $software_mgmt = $this->softwareManagementTest(); + return array_merge($db, $bconsole, $config, $actions, $software_mgmt); + } +} diff --git a/API/Modules/SelfTestResult.php b/API/Modules/SelfTestResult.php new file mode 100644 index 0000000..14fa7e9 --- /dev/null +++ b/API/Modules/SelfTestResult.php @@ -0,0 +1,138 @@ + + * @category Module + */ +class SelfTestResult extends APIModule +{ + public const TEST_RESULT_STATE_INFO = 'info'; + public const TEST_RESULT_STATE_WARNING = 'warning'; + public const TEST_RESULT_STATE_ERROR = 'error'; + public const TEST_RESULT_STATE_DISABLED = 'disabled'; + + + public const TEST_RESULT_DESC_OK = 'OK'; + public const TEST_RESULT_DESC_ERROR = 'Error'; + public const TEST_RESULT_DESC_DISABLED = 'Disabled'; + /** + * Test short name. + */ + private $name; + + /** + * Test section/category. + */ + private $section; + + /** + * Result type (boolean, string, integer...etc) + */ + private $type; + + /** + * Test result + */ + private $result; + + /** + * Test result state (info, warning, error). + */ + private $state; + + /** + * Test description + */ + private $description; + + /** + * Set test short name. + * + * @param string $name test name + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * Set test section/category. + * Ex. 'Director configuration' or 'Console access' + * + * @param string $section test section + */ + public function setSection(string $section): void + { + $this->section = $section; + } + + /** + * Set test return value type. + * Used are long type names (string, integer) instead of short names (str, int) + * + * @param string $type return value type + */ + public function setType(string $type): void + { + $this->type = $type; + } + + /** + * Set test result. + * + * @param mixed $result test result + */ + public function setResult($result): void + { + $this->result = $result; + } + + /** + * Set test result state (info, warning, error). + * + * @param string $state test result state + */ + public function setState($state): void + { + $this->state = $state; + } + + /** + * Set test description. + * + * @param string $description test description + */ + public function setDescription(string $description): void + { + $this->description = $description; + } + + public function toArray() + { + return [ + 'name' => $this->name, + 'section' => $this->section, + 'type' => $this->type, + 'result' => $this->result, + 'state' => $this->state, + 'description' => $this->description + ]; + } +} diff --git a/API/Pages/API/SelfTestRun.php b/API/Pages/API/SelfTestRun.php new file mode 100644 index 0000000..736a966 --- /dev/null +++ b/API/Pages/API/SelfTestRun.php @@ -0,0 +1,33 @@ + + * @category API + */ +class SelfTestRun extends BaculumAPIServer +{ + public function get() + { + $self_test = $this->getModule('self_test'); + $this->output = $self_test->getTestResults(); + $this->error = GenericError::ERROR_NO_ERRORS; + } +} diff --git a/API/Pages/API/config.xml b/API/Pages/API/config.xml index 39ab146..88821ae 100644 --- a/API/Pages/API/config.xml +++ b/API/Pages/API/config.xml @@ -33,6 +33,7 @@ + diff --git a/API/Pages/API/endpoints.xml b/API/Pages/API/endpoints.xml index 80959ed..67ed2d9 100644 --- a/API/Pages/API/endpoints.xml +++ b/API/Pages/API/endpoints.xml @@ -114,6 +114,7 @@ +