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.

428 lines
12 KiB

1 year ago
<?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 items state change verification.
*
* @required-components server, agent
* @configurationDataProvider serverConfigurationProvider
* @hosts test_host
* @backup history
*/
class testItemState extends CIntegrationTest {
const REFRESH_ACT_CHKS_INTERVAL = 60;
const PROCESS_ACT_CHKS_DELAY = 60;
const LOG_LINE_WAIT_TIME = 30;
const PSV_FILE_NAME = '/tmp/some_temp_file_psv';
const ACT_FILE_NAME = '/tmp/some_temp_file_act';
private static $hostid;
private static $interfaceid;
private static $items = [
'zbx_psv_01' => [
'key' => 'vfs.file.contents['.self::PSV_FILE_NAME.']',
'type' => ITEM_TYPE_ZABBIX
],
'zbx_act_01' => [
'key' => 'vfs.file.contents['.self::ACT_FILE_NAME.']',
'type' => ITEM_TYPE_ZABBIX_ACTIVE
]
];
private static $scenarios = [
[
'name' => 'zbx_psv_01',
'delay_s' => 5
],
[
'name' => 'zbx_act_01',
'delay_s' => 5
]
];
/**
* @inheritdoc
*/
public function prepareData() {
// Create host "test_host"
$response = $this->call('host.create', [
[
'host' => 'test_host',
'interfaces' => [
'type' => 1,
'main' => 1,
'useip' => 1,
'ip' => '127.0.0.1',
'dns' => '',
'port' => $this->getConfigurationValue(self::COMPONENT_AGENT, 'ListenPort')
],
'groups' => [['groupid' => 4]],
'status' => HOST_STATUS_NOT_MONITORED
]
]);
$this->assertArrayHasKey('hostids', $response['result']);
$this->assertArrayHasKey(0, $response['result']['hostids']);
self::$hostid = $response['result']['hostids'][0];
// Get host interface ids.
$response = $this->call('host.get', [
'output' => ['host'],
'hostids' => [self::$hostid],
'selectInterfaces' => ['interfaceid']
]);
$this->assertArrayHasKey(0, $response['result']);
$this->assertArrayHasKey('interfaces', $response['result'][0]);
$this->assertArrayHasKey(0, $response['result'][0]['interfaces']);
self::$interfaceid = $response['result'][0]['interfaces'][0]['interfaceid'];
// Create items
foreach (self::$items as $key => $item) {
$new_item = [
'name' => $key,
'key_' => $item['key'],
'type' => $item['type'],
'hostid' => self::$hostid,
'interfaceid' => self::$interfaceid,
'value_type' => ITEM_VALUE_TYPE_UINT64,
'delay' => '1s',
'status' => ITEM_STATUS_DISABLED
];
if ($new_item['type'] == ITEM_TYPE_ZABBIX_ACTIVE) {
$new_item['interfaceid'] = 0;
} else {
$new_item['interfaceid'] = self::$interfaceid;
}
$items[] = $new_item;
}
$response = $this->call('item.create', $items);
$this->assertArrayHasKey('itemids', $response['result']);
$this->assertEquals(count($items), count($response['result']['itemids']));
$itemids = $response['result']['itemids'];
$id = 0;
foreach (self::$items as &$item) {
$item['itemid'] = $itemids[$id++];
}
$this->assertTrue(@file_put_contents(self::PSV_FILE_NAME, '1') !== false);
$this->assertTrue(@file_put_contents(self::ACT_FILE_NAME, '1') !== false);
return true;
}
/**
* Component configuration provider for agent related tests.
*
* @return array
*/
public function serverConfigurationProvider() {
return [
self::COMPONENT_SERVER => [
'DebugLevel' => 4,
'LogFileSize' => 20,
'ListenPort' => self::getConfigurationValue(self::COMPONENT_SERVER, 'ListenPort', 10051)
],
self::COMPONENT_AGENT => [
'Hostname' => 'test_host',
'ServerActive' => '127.0.0.1:'.self::getConfigurationValue(self::COMPONENT_SERVER, 'ListenPort', 10051),
'RefreshActiveChecks' => self::REFRESH_ACT_CHKS_INTERVAL,
'BufferSend' => 1
]
];
}
/**
* Get timestamp of log last line.
*
* @param string $line log line
*
* @return integer|false
*/
protected function getTimestamp($line) {
$matches = [];
$regex = '/\d+:(\d+:\d+.\d+)/';
if (preg_match($regex, $line, $matches) === 1) {
if ($matches[1]) {
$ts = DateTime::createFromFormat('Ymd:Gis.u', $matches[1]);
return $ts->format('U');
}
}
return false;
}
/**
* Wait until line is present in log.
*
* @param string $component name of the component
* @param string|array $lines line(s) to look for
* @param integer $iterations iteration count
*
* @return integer
*
* @throws Exception on failed wait or if not able to retrieve timestamp
*/
protected function getLogLineTimestamp($component, $lines, $iterations = null) {
if ($iterations === null) {
$iterations = self::LOG_LINE_WAIT_TIME;
}
for ($r = 0; $r < $iterations; $r++) {
$log_content = CLogHelper::readLogUntil(self::getLogPath($component), $lines);
if ($log_content !== null) {
$log_content = $this->getTimestamp(strrchr(rtrim($log_content, "\n"), "\n"));
if ($log_content === false) {
throw new Exception('Failed to get timestamp of the log line');
}
return $log_content;
}
sleep(1);
}
if (is_array($lines)) {
$quoted = [];
foreach ($lines as $line) {
$quoted[] = '"'.$line.'"';
}
$description = 'any of the lines ['.implode(', ', $quoted).']';
}
else {
$description = 'line "'.$lines.'"';
}
throw new Exception('Failed to wait for '.$description.' to be present in '.$component.' log file.');
}
/**
* Routine to prepare item.
*/
protected function prepareItem($itemid, $delay) {
// Disable all items
foreach (self::$items as $item) {
if ($item['itemid'] == $itemid) {
$items[] = [
'itemid' => $itemid,
'status' => ITEM_STATUS_ACTIVE,
'delay' => $delay
];
} else {
$items[] = [
'itemid' => $item['itemid'],
'status' => ITEM_STATUS_DISABLED
];
}
}
$response = $this->call('item.update', $items);
$this->assertArrayHasKey('itemids', $response['result']);
$this->assertEquals(count($items), count($response['result']['itemids']));
$this->reloadConfigurationCache();
// Clear log
$this->clearLog(self::COMPONENT_SERVER);
}
/**
* Routine to check item state and intervals.
*/
protected function checkItemStatePassive($scenario, $state) {
$delay = $scenario['delay_s'];
$wait = $delay + self::LOG_LINE_WAIT_TIME;
$key = self::$items[$scenario['name']]['key'];
// Wait for item to be checked
$first_check = $this->getLogLineTimestamp(self::COMPONENT_SERVER, ["In process_async_result() key:'".$key."'"], $wait);
// Wait for item state to be flushed (once per second in preprocessing manager and in poller)
sleep(2);
$response = $this->call('item.get', [
'itemids' => self::$items[$scenario['name']]['itemid'],
'output' => ['state']
]);
$this->assertEquals($state, $response['result'][0]['state'], 'Unexpected item state='.
$response['result'][0]['state'].' (expected='.$state.')'
);
// Verify item checks intervals
$check = $this->getLogLineTimestamp(self::COMPONENT_SERVER, ["In process_async_result() key:'".$key."'"], $wait);
$this->assertTrue($check <= $first_check + $delay + 1);
$next_check = $this->getLogLineTimestamp(self::COMPONENT_SERVER, ["In process_async_result() key:'".$key."'"], $wait);
$this->assertTrue($next_check <= $check + $delay + 1 && $next_check >= $check + $delay - 1);
}
/**
* Routine to check item state and intervals (active agent items).
*/
protected function checkItemStateActive($scenario, $state, &$refresh) {
$wait = max($scenario['delay_s'], self::REFRESH_ACT_CHKS_INTERVAL) + self::PROCESS_ACT_CHKS_DELAY
+ self::LOG_LINE_WAIT_TIME;
$key = self::$items[$scenario['name']]['key'];
// Wait for item to be checked
$check = $this->getLogLineTimestamp(self::COMPONENT_SERVER,
[',"data":[{"host":"test_host","key":"'.$key.'","value":"'], $wait
);
// Update last refresh timestamp
while ($check > $refresh + self::REFRESH_ACT_CHKS_INTERVAL) {
$refresh += self::REFRESH_ACT_CHKS_INTERVAL;
}
// Check item state and read update interval
sleep(1);
$response = $this->call('item.get', [
'itemids' => self::$items[$scenario['name']]['itemid'],
'output' => ['state']
]);
$this->assertEquals($state, $response['result'][0]['state'],
'Unexpected item state='.$response['result'][0]['state'].' (expected='.$state.')'
);
// Verify item checks intervals
$next_check = $this->getLogLineTimestamp(self::COMPONENT_SERVER,
[',"data":[{"host":"test_host","key":"'.$key.'","value":"'], $wait
);
while ($next_check > $refresh + self::REFRESH_ACT_CHKS_INTERVAL) {
$refresh += self::REFRESH_ACT_CHKS_INTERVAL;
}
$this->assertTrue($next_check <= $check + $scenario['delay_s'] + 1
&& $next_check >= $check + $scenario['delay_s'] - 1
);
return $refresh;
}
/**
* Function to get scenarios by type.
*
* @param integer $type type
*
* @return array
*/
protected function getScenariosByType($type) {
$scenarios = [];
foreach (self::$scenarios as $scenario) {
if (self::$items[$scenario['name']]['type'] === $type) {
$scenarios[] = [$scenario];
}
}
return $scenarios;
}
/**
* Data provider (passive checks).
*
* @return array
*/
public function getDataPassive() {
return $this->getScenariosByType(ITEM_TYPE_ZABBIX);
}
/**
* Data provider (active checks).
*
* @return array
*/
public function getDataActive() {
return $this->getScenariosByType(ITEM_TYPE_ZABBIX_ACTIVE);
}
/**
* Test if item becomes supported/not supported within expected time span (passive checks).
*
* @dataProvider getDataPassive
*/
public function testItemState_checkPassive($data) {
// Prepare item
$this->prepareItem(self::$items[$data['name']]['itemid'], $data['delay_s'].'s');
// Check item state and intervals
$this->checkItemStatePassive($data, ITEM_STATE_NORMAL);
// Make item not supported
$this->assertTrue(@unlink(self::PSV_FILE_NAME) !== false);
// Check item state and intervals
$this->checkItemStatePassive($data, ITEM_STATE_NOTSUPPORTED);
// Make item supported
$this->assertTrue(@file_put_contents(self::PSV_FILE_NAME, '1') !== false);
// Check item state and intervals
$this->checkItemStatePassive($data, ITEM_STATE_NORMAL);
}
/**
* Test if item becomes supported/not supported within expected time span (active checks).
*
* @dataProvider getDataActive
*/
public function testItemState_checkActive($data) {
// Prepare item
$this->prepareItem(self::$items[$data['name']]['itemid'], $data['delay_s'].'s');
// Wait for the refresh active checks
$refresh_active = $this->getLogLineTimestamp(self::COMPONENT_SERVER,
['trapper got \'{"request":"active checks","host":"test_host"'],
self::REFRESH_ACT_CHKS_INTERVAL + self::LOG_LINE_WAIT_TIME
);
// Check item state and intervals
$this->checkItemStateActive($data, ITEM_STATE_NORMAL, $refresh_active);
// Make item not supported
$this->assertTrue(@unlink(self::ACT_FILE_NAME) !== false);
// Check item state and intervals
$this->checkItemStateActive($data, ITEM_STATE_NOTSUPPORTED, $refresh_active);
// Make item supported
$this->assertTrue(@file_put_contents(self::ACT_FILE_NAME, '1') !== false);
// Check item state and intervals
$this->checkItemStateActive($data, ITEM_STATE_NORMAL, $refresh_active);
}
}