diff --git a/includes/functions.php b/includes/functions.php index 6a708d3a..b04a5f30 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -3414,7 +3414,13 @@ function thold_check_threshold(&$thold_data) { // don't alert for this host if it's selected for maintenance if (api_plugin_is_enabled('maint') || in_array('maint', $plugins, true)) { - include_once($config['base_path'] . '/plugins/maint/functions.php'); + if (file_exists($config['base_path'] . '/plugins/maint/functions.php')) { + require_once($config['base_path'] . '/plugins/maint/functions.php'); + } else if (file_exists($config['base_path'] . '/plugins/maint/includes/functions.php')) { + require_once($config['base_path'] . '/plugins/maint/includes/functions.php'); + } else { + cacti_log('ERROR: cannot include maint functions', false, 'THOLD'); + } } if (api_plugin_is_enabled('maint') && plugin_maint_check_cacti_host($thold_data['host_id'])) { diff --git a/includes/polling.php b/includes/polling.php index f6d8c313..51b68988 100644 --- a/includes/polling.php +++ b/includes/polling.php @@ -465,7 +465,13 @@ function thold_update_host_status() { include_once($config['library_path'] . '/snmp.php'); if (api_plugin_is_enabled('maint')) { - include_once($config['base_path'] . '/plugins/maint/functions.php'); + if (file_exists($config['base_path'] . '/plugins/maint/functions.php')) { + require_once($config['base_path'] . '/plugins/maint/functions.php'); + } else if (file_exists($config['base_path'] . '/plugins/maint/includes/functions.php')) { + require_once($config['base_path'] . '/plugins/maint/includes/functions.php'); + } else { + cacti_log('ERROR: cannot include maint functions', false, 'THOLD'); + } } $ping_failure_count = read_config_option('ping_failure_count'); diff --git a/tests/Fixtures/cacti-root-maint-includes/plugins/maint/includes/functions.php b/tests/Fixtures/cacti-root-maint-includes/plugins/maint/includes/functions.php new file mode 100644 index 00000000..28247f69 --- /dev/null +++ b/tests/Fixtures/cacti-root-maint-includes/plugins/maint/includes/functions.php @@ -0,0 +1,3 @@ + $overrides + * + * @return ThresholdScenario + */ + private function baseline(array $overrides = []) { + return ThresholdScenario::threshold($overrides + [ + 'thold_type' => 1, + 'bl_type' => 0, + 'bl_pct_up' => 10, + 'bl_pct_down' => 10, + 'bl_ref_time_range' => 3600, + 'bl_fail_trigger' => 1, + ]) + ->alertRecipient('ops@example.org') + ->referenceStatistics(100, 100, 100, 100); + } + + /** + * @param string $name + * + * @return string + */ + private function fixtureRoot($name) { + return dirname(__DIR__) . '/Fixtures/' . $name; + } + + /** + * The newer maint layout keeps functions.php under includes/; a device in a + * maintenance window still resolves it and suppresses the notification. + * + * @return void + */ + public function testCheckThresholdResolvesMaintFromIncludesLayout(): void { + $outcome = $this->baseline(['lastread' => 500]) + ->inMaintenance($this->fixtureRoot('cacti-root-maint-includes')) + ->poll(); + + $this->assertSame(0, $outcome->mailCount()); + } + + /** + * With neither maint layout present the include is skipped and the failure + * is logged rather than fatal. + * + * @return void + */ + public function testCheckThresholdLogsWhenMaintFunctionsMissing(): void { + $this->baseline(['lastread' => 500]) + ->inMaintenance($this->fixtureRoot('cacti-root-no-maint')) + ->poll(); + + $this->assertContains('ERROR: cannot include maint functions', CactiStubs::$log); + } + + /** + * Drive thold_update_host_status() far enough to run the maint include + * block. Downstream work needs a live Cacti, so engine messages are + * swallowed and any throw past the block is ignored. + * + * @param string $cactiRoot + * + * @return void + */ + private function runHostStatus($cactiRoot) { + CactiStubs::willAlwaysReturn('api_plugin_is_enabled', true); + CactiStubs::$configOptions['alert_deadnotify'] = 'on'; + $GLOBALS['config']['base_path'] = $cactiRoot; + + set_error_handler(static fn (): bool => true); + ob_start(); + + try { + thold_update_host_status(); + } catch (\Throwable $e) { + // Reaching the maint include block is the point. + } finally { + ob_end_clean(); + restore_error_handler(); + } + } + + /** + * @return void + */ + public function testHostStatusResolvesMaintFromPluginRoot(): void { + $this->runHostStatus($this->fixtureRoot('cacti-root')); + + $this->assertNotContains('ERROR: cannot include maint functions', CactiStubs::$log); + } + + /** + * @return void + */ + public function testHostStatusResolvesMaintFromIncludesLayout(): void { + $this->runHostStatus($this->fixtureRoot('cacti-root-maint-includes')); + + $this->assertNotContains('ERROR: cannot include maint functions', CactiStubs::$log); + } + + /** + * @return void + */ + public function testHostStatusLogsWhenMaintFunctionsMissing(): void { + $this->runHostStatus($this->fixtureRoot('cacti-root-no-maint')); + + $this->assertContains('ERROR: cannot include maint functions', CactiStubs::$log); + } +}