<?php /* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** 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 Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ require_once dirname(__FILE__).'/../include/CIntegrationTest.php'; /** * Test suite for High availability * * @backup hosts, proxy, host_rtdata, proxy_rtdata, proxy_group, host_proxy */ class testProxyHa extends CIntegrationTest { private static $proxy_groupid; private static $proxyid1; private static $proxyid2; private static $hostid1; private static $hostid2; private static $assigned_proxyids; const PG_NAME = "Proxy group 1"; const PROXY1_HOSTNAME = "P1"; const PROXY1_HA_HOSTNAME = "P2"; /** * Component configuration provider for test case 'host is being synced to proxies after reassignment'. * * @return array */ public function reassignmentConfigurationProvider() { return [ self::COMPONENT_SERVER => [ 'DebugLevel' => 5, 'LogFileSize' => 0, 'UnavailableDelay' => 5, 'UnreachableDelay' => 1 ], self::COMPONENT_PROXY => [ 'Hostname' => self::PROXY1_HOSTNAME, 'DebugLevel' => 5, 'LogFileSize' => 0, 'Server' => '127.0.0.1:'.self::getConfigurationValue(self::COMPONENT_SERVER, 'ListenPort') ] ]; } /** * Component configuration provider. * * @return array */ public function configurationProvider() { return [ self::COMPONENT_SERVER => [ 'DebugLevel' => 5, 'LogFileSize' => 0, 'UnavailableDelay' => 5, 'UnreachableDelay' => 1 ], self::COMPONENT_PROXY => [ 'DebugLevel' => 5, 'LogFileSize' => 0, 'Hostname' => self::PROXY1_HOSTNAME, 'Server' => '127.0.0.1:'.self::getConfigurationValue(self::COMPONENT_SERVER, 'ListenPort') ], self::COMPONENT_PROXY_HANODE1 => [ 'DebugLevel' => 5, 'LogFileSize' => 0, 'Hostname' => self::PROXY1_HA_HOSTNAME, 'Server' => '127.0.0.1:'.self::getConfigurationValue(self::COMPONENT_SERVER, 'ListenPort') ] ]; } /** * @required-components server, proxy, proxy_ha1 * @inheritdoc */ public function prepareData() { $socketDir = $this->getConfigurationValue(self::COMPONENT_PROXY_HANODE1, 'SocketDir'); if (file_exists($socketDir) === false) { mkdir($socketDir); } $response = $this->call('proxygroup.create', [ 'name' => 'Proxy group 1', 'failover_delay' => '10', 'min_online' => '1' ]); $this->assertArrayHasKey('proxy_groupids', $response['result']); $this->assertCount(1, $response['result']['proxy_groupids']); self::$proxy_groupid = $response['result']['proxy_groupids'][0]; $response = $this->call('host.create', [ 'host' => 'host1', 'interfaces' => [ 'type' => 1, 'main' => 1, 'useip' => 1, 'ip' => '127.0.0.1', 'dns' => '', 'port' => $this->getConfigurationValue(self::COMPONENT_AGENT, 'ListenPort') ], 'monitored_by' => ZBX_MONITORED_BY_PROXY_GROUP, 'proxy_groupid' => self::$proxy_groupid, 'groups' => ['groupid' => 4] ]); $this->assertArrayHasKey('hostids', $response['result']); $this->assertArrayHasKey(0, $response['result']['hostids']); self::$hostid1 = $response['result']['hostids'][0]; $response = $this->call('host.create', [ 'host' => 'host2', 'interfaces' => [ 'type' => 1, 'main' => 1, 'useip' => 1, 'ip' => '127.0.0.2', 'dns' => '', 'port' => $this->getConfigurationValue(self::COMPONENT_AGENT, 'ListenPort') ], 'monitored_by' => ZBX_MONITORED_BY_PROXY_GROUP, 'proxy_groupid' => self::$proxy_groupid, 'groups' => ['groupid' => 4] ]); $this->assertArrayHasKey('hostids', $response['result']); $this->assertArrayHasKey(0, $response['result']['hostids']); self::$hostid2 = $response['result']['hostids'][0]; $response = $this->call('proxy.create', [ 'name' => self::PROXY1_HOSTNAME, 'operating_mode' => PROXY_OPERATING_MODE_ACTIVE, 'local_address' => '127.0.0.1', 'local_port' => $this->getConfigurationValue(self::COMPONENT_PROXY, 'ListenPort'), 'proxy_groupid' => self::$proxy_groupid ]); $this->assertArrayHasKey("proxyids", $response['result']); self::$proxyid1 = $response['result']['proxyids'][0]; $response = $this->call('proxy.create', [ 'name' => self::PROXY1_HA_HOSTNAME, 'operating_mode' => PROXY_OPERATING_MODE_ACTIVE, 'local_address' => '127.0.0.1', 'local_port' => $this->getConfigurationValue(self::COMPONENT_PROXY_HANODE1, 'ListenPort'), 'proxy_groupid' => self::$proxy_groupid ]); $this->assertArrayHasKey("proxyids", $response['result']); self::$proxyid2 = $response['result']['proxyids'][0]; return true; } /** * Test unassigned group assignment * * @configurationDataProvider configurationProvider * @required-components server, proxy, proxy_ha1 */ public function testProxyHa_tc1() { $pg_logline = 'Proxy group "' . self::PG_NAME . '" changed state from \b[a-z]+\b to online'; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $pg_logline, true, 90, 1, true); $assign_logline = '\bassigned hostid (' . self::$hostid1 . '|' . self::$hostid2 . ') to proxyid'; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $assign_logline, true, 90, 1, true); $response = $this->call('host.get', [ 'output' => ['hostid', 'assigned_proxyid'], 'hostids' => [self::$hostid1, self::$hostid2], 'preservekeys' => true ]); $this->assertCount(2, $response['result']); foreach ($response['result'] as $host) { $this->assertArrayHasKey('hostid', $host); $this->assertArrayHasKey('assigned_proxyid', $host); $this->assertNotEquals('0', $host['assigned_proxyid']); self::$assigned_proxyids[$host['hostid']] = $host['assigned_proxyid']; } return true; } /** * Test if hosts are not reassigned after restart * * @configurationDataProvider configurationProvider * @required-components server, proxy, proxy_ha1 */ public function testProxyHa_tc2() { $this->stopComponent(self::COMPONENT_SERVER); $this->startComponent(self::COMPONENT_SERVER); $pg_logline = 'Proxy group "' . self::PG_NAME . '" changed state from \b[a-z]+\b to online'; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $pg_logline, true, 90, 1, true); $this->reloadConfigurationCache(self::COMPONENT_SERVER); $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, "End of zbx_dc_sync_configuration()", true, 120, 1, true); $response = $this->call('host.get', [ 'output' => ['hostid', 'assigned_proxyid'], 'hostids' => [self::$hostid1, self::$hostid2], 'preservekeys' => true ]); $this->assertCount(2, $response['result']); foreach ($response['result'] as $host) { $this->assertArrayHasKey('hostid', $host); $this->assertArrayHasKey('assigned_proxyid', $host); $this->assertEquals($host['assigned_proxyid'], self::$assigned_proxyids[$host['hostid']]); } return true; } /** * Test if host is reassigned from offline proxy * * @configurationDataProvider configurationProvider * @required-components server, proxy, proxy_ha1 */ public function testProxyHa_tc3() { $this->stopComponent(self::COMPONENT_PROXY); $monitored_host = null; foreach (self::$assigned_proxyids as $hostid => $proxyid) { if ($proxyid == self::$proxyid1) { $monitored_host = [ 'hostid' => $hostid, 'initial_proxyid' => $proxyid ]; break; } } $this->assertNotNull($monitored_host); $px_logline = 'Proxy "' . $this->getConfigurationValue(self::COMPONENT_PROXY, 'Hostname') . '" changed state from \b[a-z]+\b to offline'; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $px_logline, true, 90, 1, true); $assign_logline = 're-assigned hostid ' . $monitored_host['hostid'] . ' to proxyid ' . self::$proxyid2; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $assign_logline, true, 90, 1, true); $response = $this->call('host.get', [ 'output' => ['hostid', 'assigned_proxyid'], 'hostids' => [self::$hostid1, self::$hostid2], 'preservekeys' => true ]); $this->assertCount(2, $response['result']); foreach ($response['result'] as $host) { $this->assertEquals(self::$proxyid2, $host['assigned_proxyid']); } $this->startComponent(self::COMPONENT_PROXY); $px_logline = 'Proxy "' . $this->getConfigurationValue(self::COMPONENT_PROXY, 'Hostname') . '" changed state from offline to online'; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $px_logline, true, 90, 1, true); $assign_logline = 're-assigned hostid ' . $monitored_host['hostid'] . ' to proxyid ' . $monitored_host['initial_proxyid']; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $assign_logline, true, 250, 2, true); $response = $this->call('host.get', [ 'output' => ['assigned_proxyid'], 'hostids' => [$monitored_host['hostid']], 'preservekeys' => true ]); $this->assertCount(1, $response['result']); $this->assertArrayHasKey('assigned_proxyid', $response['result'][$monitored_host['hostid']]); $this->assertEquals(self::$proxyid1, $response['result'][$monitored_host['hostid']]['assigned_proxyid']); return true; } /** * Test if host is reassigned from proxy when proxy is removed from group * * @configurationDataProvider configurationProvider * @required-components server, proxy, proxy_ha1 */ public function testProxyHa_tc4() { $response = $this->call('proxy.update', [ 'proxyid' => self::$proxyid1, 'proxy_groupid' => 0 ]); $this->assertArrayHasKey("proxyids", $response['result']); $this->assertCount(1, $response['result']['proxyids']); $this->reloadConfigurationCache(self::COMPONENT_SERVER); $response = $this->call('host.get', [ 'output' => ['assigned_proxyid'], 'hostids' => [self::$hostid1, self::$hostid2], 'assigned_proxyid' => self::$proxyid2, 'preservekeys' => true ]); $this->assertCount(2, $response['result']); $assign_logline = '\bassigned hostid (' . self::$hostid1 . '|' . self::$hostid2 . ') to proxyid ' . self::$proxyid2; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $assign_logline, true, 120, 2, true); return true; } /** * Test if host is assigned to proxy when proxy is added to group * * @configurationDataProvider configurationProvider * @required-components server, proxy, proxy_ha1 */ public function testProxyHa_tc5() { $response = $this->call('proxy.update', [ 'proxyid' => self::$proxyid1, 'proxy_groupid' => self::$proxy_groupid, 'local_address' => '127.0.0.1', 'local_port' => $this->getConfigurationValue(self::COMPONENT_PROXY, 'ListenPort') ]); $this->assertArrayHasKey("proxyids", $response['result']); $this->assertCount(1, $response['result']['proxyids']); $this->reloadConfigurationCache(self::COMPONENT_SERVER); $response = $this->call('host.get', [ 'output' => ['assigned_proxyid'], 'hostids' => [self::$hostid1, self::$hostid2], 'assigned_proxyid' => self::$proxyid2, 'preservekeys' => true ]); $this->assertCount(2, $response['result']); $assign_logline = '\bassigned hostid (' . self::$hostid1 . '|' . self::$hostid2 . ') to proxyid ' . self::$proxyid1; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $assign_logline, true, 120, 2, true); return true; } /** * Test if host is unassigned from proxy when removed from proxy group * * @configurationDataProvider configurationProvider * @required-components server, proxy, proxy_ha1 */ public function testProxyHa_tc6() { $response = $this->call('host.update', [ 'hostid' => self::$hostid1, 'monitored_by' => ZBX_MONITORED_BY_SERVER, 'proxyid' => 0 ]); $this->assertArrayHasKey("hostids", $response['result']); $this->assertCount(1, $response['result']['hostids']); $this->reloadConfigurationCache(self::COMPONENT_SERVER); $response = $this->call('host.get', [ 'output' => ['assigned_proxyid'], 'hostids' => [self::$hostid1, self::$hostid2], 'assigned_proxyid' => self::$proxyid2, 'preservekeys' => true ]); $this->assertCount(2, $response['result']); $assign_logline = 'unassigned hostid ' . self::$hostid1; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $assign_logline, true, 120, 2, true); return true; } /** * Test if host is assigned to proxy when added to proxy group * * @configurationDataProvider configurationProvider * @required-components server, proxy, proxy_ha1 */ public function testProxyHa_tc7() { $response = $this->call('host.update', [ 'hostid' => self::$hostid1, 'monitored_by' => ZBX_MONITORED_BY_PROXY_GROUP, 'proxy_groupid' => self::$proxy_groupid ]); $this->assertArrayHasKey("hostids", $response['result']); $this->assertCount(1, $response['result']['hostids']); $this->reloadConfigurationCache(self::COMPONENT_SERVER); $response = $this->call('host.get', [ 'output' => ['assigned_proxyid'], 'hostids' => [self::$hostid1, self::$hostid2], 'assigned_proxyid' => self::$proxyid2, 'preservekeys' => true ]); $this->assertCount(2, $response['result']); $assign_logline = '\bassigned hostid ' . self::$hostid1; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $assign_logline, true, 120, 2, true); return true; } /** * Test redistribution of discovered hosts * * @configurationDataProvider configurationProvider * @required-components server, proxy, proxy_ha1 */ public function testProxyHa_tc8() { $this->stopComponent(self::COMPONENT_SERVER); $this->stopComponent(self::COMPONENT_PROXY); $this->stopComponent(self::COMPONENT_PROXY_HANODE1); $xml = <<<HEREDOC <?xml version="1.0" encoding="UTF-8"?> <zabbix_export> <version>7.0</version> <host_groups> <host_group> <uuid>6f6799aa69e844b4b3918f779f2abf08</uuid> <name>Zabbix servers</name> </host_group> </host_groups> <hosts> <host> <host>Zabbix1</host> <name>Zabbix1</name> <monitored_by>PROXY_GROUP</monitored_by> <proxy_group> <name>Proxy group 1</name> </proxy_group> <groups> <group> <name>Zabbix servers</name> </group> </groups> <discovery_rules> <discovery_rule> <name>lld</name> <type>SCRIPT</type> <key>lld</key> <delay>5s</delay> <params>hosts = []; for (var i = 1; i <= 10; i++) { hosts.push({ '{#HOSTNAME}': i }); } return JSON.stringify(hosts);</params> <host_prototypes> <host_prototype> <host>host {#HOSTNAME}</host> <name>host {#HOSTNAME}</name> <inventory_mode>DISABLED</inventory_mode> <group_links> <group_link> <group> <name>Zabbix servers</name> </group> </group_link> </group_links> <tags> <tag> <tag>test</tag> <value>proxyha</value> </tag> </tags> </host_prototype> </host_prototypes> </discovery_rule> </discovery_rules> <inventory_mode>DISABLED</inventory_mode> </host> </hosts> </zabbix_export> HEREDOC; $response = $this->call('configuration.import', [ 'format' => 'xml', 'source' => $xml, 'rules' => [ 'host_groups' => [ 'updateExisting' => true, 'createMissing' => true ], 'hosts' => [ 'updateExisting' => true, 'createMissing' => true ], 'valueMaps' => [ 'updateExisting' => true, 'createMissing' => true, 'deleteMissing' => false ], 'templateLinkage' => [ 'createMissing' => true, 'deleteMissing' => false ], 'items' => [ 'updateExisting' => true, 'createMissing' => true, 'deleteMissing' => false ], 'discoveryRules' => [ 'updateExisting' => true, 'createMissing' => true, 'deleteMissing' => false ], 'triggers' => [ 'updateExisting' => true, 'createMissing' => true, 'deleteMissing' => false ], 'graphs' => [ 'updateExisting' => true, 'createMissing' => true, 'deleteMissing' => false ], 'httptests' => [ 'updateExisting' => true, 'createMissing' => true, 'deleteMissing' => false ] ] ]); $this->startComponent(self::COMPONENT_SERVER); $this->startComponent(self::COMPONENT_PROXY); $this->startComponent(self::COMPONENT_PROXY_HANODE1); $pg_logline = 'Proxy group "' . self::PG_NAME . '" changed state from \b[a-z]+\b to online'; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $pg_logline, false, 90, 1, true); $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'assigned hostid', false, 90, 1); $this->reloadConfigurationCache(self::COMPONENT_PROXY); $this->reloadConfigurationCache(self::COMPONENT_PROXY_HANODE1); $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'End of lld_update_hosts', true, 120, 1, true); $lld_host_count = 10; $response = $this->callUntilDataIsPresent('host.get', [ 'output' => ['hostid'], 'preservekeys' => 'true', 'tags' => [ [ 'tag' => 'test', 'value' => 'proxyha', 'operator' => 1 ] ] ], 30, 1); $hostids = array_keys($response['result']); $this->assertEquals(count($hostids), $lld_host_count); $this->reloadConfigurationCache(self::COMPONENT_SERVER); sleep(1); $proxy1_hostcount = CDBHelper::getCount( 'SELECT NULL FROM host_proxy ' . 'WHERE proxyid=' . self::$proxyid1 . ' AND hostid IN (' . CDBHelper::escape($hostids) . ')' ); $proxy2_hostcount = CDBHelper::getCount( 'SELECT NULL FROM host_proxy ' . 'WHERE proxyid=' . self::$proxyid2 . ' AND hostid IN (' . CDBHelper::escape($hostids) . ')' ); $this->assertEquals($lld_host_count, $proxy1_hostcount + $proxy2_hostcount); $this->assertTrue($proxy1_hostcount >= floor($lld_host_count / 2.5)); $this->assertTrue($proxy2_hostcount >= floor($lld_host_count / 2.5)); } /** * Test if host is being synced to proxies after reassignment * * @configurationDataProvider reassignmentConfigurationProvider * @required-components server, proxy */ public function testProxyHa_tc9() { $response = $this->call('host.get', []); $hostids = []; foreach ($response['result'] as $host) { $hostids[] = $host['hostid']; } $this->call('host.delete', $hostids); $response = $this->call('proxy.get', []); $proxyids = []; foreach ($response['result'] as $proxy) { $proxyids[] = $proxy['proxyid']; } $this->call('proxy.delete', $proxyids); $response = $this->call('proxygroup.delete', [ self::$proxy_groupid ]); $this->assertArrayHasKey("proxy_groupids", $response['result']); $this->assertCount(1, $response['result']['proxy_groupids']); $this->stopComponent(self::COMPONENT_SERVER); $this->stopComponent(self::COMPONENT_PROXY); $this->stopComponent(self::COMPONENT_PROXY_HANODE1); $response = $this->call('proxygroup.create', [ 'name' => 'Proxy group X', 'failover_delay' => '10', 'min_online' => '1' ]); $this->assertArrayHasKey('proxy_groupids', $response['result']); $this->assertCount(1, $response['result']['proxy_groupids']); $proxy_groupid = $response['result']['proxy_groupids'][0]; $response = $this->call('host.create', [ 'host' => 'host3', 'interfaces' => [ 'type' => 1, 'main' => 1, 'useip' => 1, 'ip' => '127.0.0.1', 'dns' => '', 'port' => $this->getConfigurationValue(self::COMPONENT_AGENT, 'ListenPort') ], 'groups' => ['groupid' => 4] ]); $this->assertArrayHasKey('hostids', $response['result']); $this->assertArrayHasKey(0, $response['result']['hostids']); $hostid_trapper = $response['result']['hostids'][0]; $response = $this->call('item.create', [ [ 'name' => 'trap', 'key_' => 'trap', 'type' => ITEM_TYPE_TRAPPER, 'hostid' => $hostid_trapper, 'value_type' => ITEM_VALUE_TYPE_UINT64 ] ]); $this->assertArrayHasKey('itemids', $response['result']); $this->assertEquals(1, count($response['result']['itemids'])); $itemid = $response['result']['itemids'][0]; $response = $this->call('proxy.create', [ 'name' => self::PROXY1_HOSTNAME, 'operating_mode' => PROXY_OPERATING_MODE_ACTIVE, "local_address" => "127.0.0.1", 'local_port' => $this->getConfigurationValue(self::COMPONENT_PROXY, 'ListenPort'), 'proxy_groupid' => $proxy_groupid ]); $this->assertArrayHasKey("proxyids", $response['result']); $proxyid = $response['result']['proxyids'][0]; $this->startComponent(self::COMPONENT_SERVER); $this->startComponent(self::COMPONENT_PROXY); $pg_logline = 'Proxy group "Proxy group X" changed state from \b[a-z]+\b to online'; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $pg_logline, true, 90, 1, true); $this->sendSenderValue('host3', 'trap', 333); $response = $this->callUntilDataIsPresent('history.get', [ 'output' => ['itemid', 'value', 'clock', 'ns'], 'history' => ITEM_VALUE_TYPE_UINT64, 'itemids' => [$itemid], 'sortfield' => 'clock', 'sortorder' => 'DESC', 'limit' => 1 ], 30, 2); $this->assertCount(1, $response['result']); $response = $this->call('host.update', [ 'hostid' => $hostid_trapper, 'monitored_by' => ZBX_MONITORED_BY_PROXY_GROUP, 'proxy_groupid' => $proxy_groupid ]); $this->assertArrayHasKey("hostids", $response['result']); $this->assertCount(1, $response['result']['hostids']); $this->reloadConfigurationCache(self::COMPONENT_SERVER); $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, "End of zbx_dc_sync_configuration()", true, 120, 1, true); $px_logline = 'Proxy "' . self::PROXY1_HOSTNAME . '" changed state from unknown to online'; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $px_logline, false, 120, 2, true); $assign_logline = '\bassigned hostid ' . $hostid_trapper . ' to proxyid ' . $proxyid; $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, $assign_logline, false, 120, 2, true); $this->reloadConfigurationCache(self::COMPONENT_SERVER); $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, "End of zbx_dc_sync_configuration()", true, 120, 1, true); $this->reloadConfigurationCache(self::COMPONENT_PROXY); $this->waitForLogLineToBePresent(self::COMPONENT_SERVER, "End of zbx_dc_sync_configuration()", true, 120, 1, true); $this->sendSenderValue('host3', 'trap', 1000, self::COMPONENT_PROXY); $response = $this->callUntilDataIsPresent('history.get', [ 'output' => ['itemid', 'value', 'clock', 'ns'], 'history' => ITEM_VALUE_TYPE_UINT64, 'itemids' => [$itemid] ], 60, 2); $this->assertCount(2, $response['result']); return true; } }