[ '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); } }