You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

293 lines
9.7 KiB

<?php
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
require_once dirname(__FILE__).'/../include/CIntegrationTest.php';
/**
* Test suite for High availability
*
* @backup ha_node
*/
class testHighAvailability extends CIntegrationTest {
const STANDALONE_NAME = '<standalone server>';
const NODE1_NAME = 'node1';
const NODE2_NAME = 'node2';
/**
* @required-components server, server_ha1
* @inheritdoc
*/
public function prepareData() {
$socketDir = $this->getConfigurationValue(self::COMPONENT_SERVER_HANODE1, 'SocketDir');
if (file_exists($socketDir) === false) {
mkdir($socketDir);
}
return true;
}
/**
* Component configuration provider for standalone mode
* Used to test if server quits gracefully when cache size is too low
*
* @return array
*/
public function serverConfigurationProvider_cacheSize() {
return [
self::COMPONENT_SERVER => [
'HANodeName' => self::NODE1_NAME,
'CacheSize' => '128K',
'ListenPort' => PHPUNIT_PORT_PREFIX.self::SERVER_HANODE1_PORT_SUFFIX
]
];
}
/**
* Component configuration provider for 2 nodes (Active + standby)
*
* @return array
*/
public function serverConfigurationProvider_ha() {
return [
self::COMPONENT_SERVER => [
'HANodeName' => self::NODE1_NAME,
'ListenPort' => PHPUNIT_PORT_PREFIX.self::SERVER_HANODE1_PORT_SUFFIX
],
self::COMPONENT_SERVER_HANODE1 => [
'HANodeName' => self::NODE2_NAME,
'NodeAddress' => 'localhost:'.self::getConfigurationValue(self::COMPONENT_SERVER_HANODE1, 'ListenPort')
]
];
}
/**
* Launching Zabbix server in stand-alone mode
*
* @required-components server_ha1
*/
public function testHighAvailability_checkStandaloneModeStartup() {
$this->assertFalse($this->isLogLinePresent(self::COMPONENT_SERVER_HANODE1, '"'.self::NODE1_NAME.'" node started in "active" mode'));
return true;
}
/**
* Launching High availability cluster with 2 nodes (Active + standby)
*
* @required-components server, server_ha1
* @configurationDataProvider serverConfigurationProvider_ha
*/
public function testHighAvailability_checkHaStartup() {
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, '"'.self::NODE1_NAME.'" node started in "active" mode', true, 3, 3);
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER_HANODE1, '"'.self::NODE2_NAME.'" node started in "standby" mode', true, 3, 3);
return true;
}
/**
* Stopping the active node (Standby node should take over)
* Starting stopped node (when another node is active and when there are no active nodes)
* Stopping a stand-by node
*
* @required-components server, server_ha1
* @configurationDataProvider serverConfigurationProvider_ha
*/
public function testHighAvailability_checkModeSwitching() {
$this->stopComponent(self::COMPONENT_SERVER);
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER_HANODE1, '"'.self::NODE2_NAME.'" node switched to "active" mode', true, 5, 15);
$this->startComponent(self::COMPONENT_SERVER, "HA manager started");
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, '"'.self::NODE1_NAME.'" node started in "standby" mode', true, 5, 15);
$this->stopComponent(self::COMPONENT_SERVER_HANODE1);
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, '"'.self::NODE1_NAME.'" node switched to "active" mode', true, 5, 15);
return true;
}
/**
* Stopping the active node (Standby node should take over)
* Starting stopped node (when another node is active and when there are no active nodes)
* Stopping a stand-by node
*
* @required-components server, server_ha1
* @configurationDataProvider serverConfigurationProvider_ha
*/
public function testHighAvailability_checkModeSwitching2() {
$this->stopComponent(self::COMPONENT_SERVER);
$this->startComponent(self::COMPONENT_SERVER, "HA manager started");
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER_HANODE1, '"node2" node switched to "active" mode', true, 20, 3);
$this->stopComponent(self::COMPONENT_SERVER_HANODE1);
$this->stopComponent(self::COMPONENT_SERVER);
return true;
}
private function verifyNodesStatus($expected_nodes) {
$this->executeRuntimeControlCommand(self::COMPONENT_SERVER, 'ha_status');
foreach ($expected_nodes as $node) {
$re = $node["nodename"].".*".$node["expected_status"];
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $re, true, 20, 3, true);
}
return true;
}
/**
* Retrieving ha cluster info via ha_status runtime command
*
* @required-components server, server_ha1
* @configurationDataProvider serverConfigurationProvider_ha
*/
public function testHighAvailability_haStatus() {
$expected_nodes = [
[
"nodename" => self::NODE1_NAME,
"expected_status" => "active"
],
[
"nodename" => self::NODE2_NAME,
"expected_status" => "standby"
]
];
$this->assertTrue($this->verifyNodesStatus($expected_nodes));
}
/**
* Remove node
*
* @required-components server, server_ha1
* @configurationDataProvider serverConfigurationProvider_ha
*/
public function testHighAvailability_removeNode() {
$this->stopComponent(self::COMPONENT_SERVER_HANODE1);
$this->executeRuntimeControlCommand(self::COMPONENT_SERVER, 'ha_remove_node=node2');
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, "removed node", true, 3, 5);
$response = $this->call('hanode.get', [
'output' => 'extend',
'filter' => [
'name' => self::STANDALONE_NAME
]
]);
$this->assertEmpty($response['result']);
}
/**
* Updating the failover delay via ha_set_failover_delay runtime command
*
* @required-components server, server_ha1
* @configurationDataProvider serverConfigurationProvider_ha
*/
public function testHighAvailability_failover() {
$this->executeRuntimeControlCommand(self::COMPONENT_SERVER, 'ha_set_failover_delay=10s');
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'HA failover delay set to 10s');
$this->stopComponent(self::COMPONENT_SERVER);
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER_HANODE1, '"'.self::NODE2_NAME.'" node switched to "active" mode');
$this->startComponent(self::COMPONENT_SERVER, 'HA manager started in standby mode');
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER_HANODE1, 'started [trigger housekeeper');
sleep(1);
self::killComponent(self::COMPONENT_SERVER_HANODE1);
$response = $this->callUntilDataIsPresent('hanode.get', [
'output' => 'extend',
'filter' => [
'name' => self::NODE2_NAME,
'status' => ZBX_NODE_STATUS_UNAVAILABLE
]
], 15, 2);
$this->assertCount(1, $response['result']);
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, '"'.self::NODE1_NAME.'" node switched to "active" mode');
}
/**
* Check graceful stop of server if cacheSize is too low
*
* @required-components server
* @configurationDataProvider serverConfigurationProvider_ha
*/
public function testHighAvailability_cacheSize() {
$this->stopComponent(self::COMPONENT_SERVER);
$newConfig = [
'server' => array_merge(self::$case_configuration[self::COMPONENT_SERVER], $this->serverConfigurationProvider_cacheSize()[self::COMPONENT_SERVER])
];
self::prepareComponentConfiguration(self::COMPONENT_SERVER, $newConfig);
$this->startComponent(self::COMPONENT_SERVER, 'Zabbix Server stopped', true);
$this->assertTrue(true); // Ignore warning for risky test, checks are performed in nested funcs and exceptions can be thrown
}
/**
* Check for static socket on mode switching
*
* @required-components server, server_ha1
* @configurationDataProvider serverConfigurationProvider_ha
*/
public function testHighAvailability_checkRtc() {
$this->stopComponent(self::COMPONENT_SERVER);
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER_HANODE1, '"'.self::NODE2_NAME.'" node switched to "active" mode', true, 5, 15);
$this->startComponent(self::COMPONENT_SERVER, "HA manager started");
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, '"'.self::NODE1_NAME.'" node started in "standby" mode', true, 5, 15);
$this->stopComponent(self::COMPONENT_SERVER_HANODE1);
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, '"'.self::NODE1_NAME.'" node switched to "active" mode', true, 5, 15);
$expected_nodes = [
[
"nodename" => self::NODE1_NAME,
"expected_status" => "active"
],
[
"nodename" => self::NODE2_NAME,
"expected_status" => "stopped"
]
];
$this->assertTrue($this->verifyNodesStatus($expected_nodes));
$commands = [
'config_cache_reload' => 'forced reloading of the configuration cache',
'secrets_reload' => 'forced reloading of the secrets',
'service_cache_reload' => 'forced reloading of the service manager cache',
'housekeeper_execute' => 'forced execution of the housekeeper',
'diaginfo=locks' => '== locks diagnostic information =='
];
foreach ($commands as $cmd => $exp) {
$this->executeRuntimeControlCommand(self::COMPONENT_SERVER, $cmd);
$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $exp, true, 20, 3);
}
$this->stopComponent(self::COMPONENT_SERVER);
$this->stopComponent(self::COMPONENT_SERVER_HANODE1);
}
}