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.
zabbix/ui/tests/integration/testEventsCauseAndSymptoms.php

603 lines
17 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 testing changing rank of trigger-based events/problems
*
* @required-components server
* @backup hosts,items,triggers,task,task_data,acknowledges,event_symptom,events,problem
*/
class testEventsCauseAndSymptoms extends CIntegrationTest {
private static $hostid;
private static $trigger_ids = [];
private static $event_ids = [];
private static $cause_events_test_scriptid;
private static $symptom_events_test_scriptid;
const HOST_NAME = 'host cause and symptoms';
const TRAPPER_ITEM_NAME_PREFIX = 'Trapper item ';
const TRAPPER_ITEM_KEY_PREFIX = 'trap';
const EVENT_CAUSE_MACRO_TEMPLATE = 'Macros to test: {EVENT.NAME}|{EVENT.ID}|{EVENT.CAUSE.NAME}|{EVENT.CAUSE.ID}|{EVENT.CAUSE.SOURCE}|{EVENT.CAUSE.OBJECT}|{EVENT.CAUSE.VALUE}';
const EVENT_SYMPTOMS_MACRO_TEMPLATE = '{EVENT.SYMPTOMS}';
const TEST_CAUSE_EVENTS_SCRIPT_NAME = 'script test cause events';
const TEST_SYMPTOM_EVENTS_SCRIPT_NAME = 'script test symptom events';
const EVENT_COUNT = 5;
const EVENT_START = 1;
private function expandMacros($in_str, $event_n, $cause_n = null) {
$replacements = [
'{EVENT.NAME}' => 'Trigger trap '.$event_n,
'{EVENT.ID}' => $this->eventNumToId($event_n)
];
if (null != $cause_n) {
$replacements = array_merge($replacements, [
'{EVENT.CAUSE.NAME}' => 'Trigger trap '.$cause_n,
'{EVENT.CAUSE.ID}' => $this->eventNumToId($cause_n),
'{EVENT.CAUSE.SOURCE}' => EVENT_SOURCE_TRIGGERS,
'{EVENT.CAUSE.OBJECT}' => EVENT_OBJECT_TRIGGER,
'{EVENT.CAUSE.VALUE}' => self::EVENT_START
]);
}
$from = [];
$to = [];
foreach ($replacements as $key => $value) {
$from[] = $key;
$to[] = $value;
}
return str_replace($from, $to, $in_str);
}
private function eventNumToId($num) {
if (!is_int($num) && !is_array($num)) {
throw new Exception("Test error: eventNumToId() arguments must be int or array, got '%s'",
gettype($num));
}
if (is_array($num)) {
$a = [];
foreach ($num as $n)
$a[] = $this->eventNumToId($n);
return $a;
}
if (0 == $num)
return 0;
return self::$event_ids[$num];
}
private function markAsSymptoms($rank_as_symptom_requests) {
foreach ($rank_as_symptom_requests as $request) {
$event_ids = $this->eventNumToId($request['event_nums']);
$cause_event_ids = $this->eventNumToId($request['cause_event_num']);
$response = $this->call('event.acknowledge', [
'eventids' => $event_ids,
'action' => ZBX_PROBLEM_UPDATE_RANK_TO_SYMPTOM,
'cause_eventid' => $cause_event_ids
]);
$this->assertArrayHasKey('result', $response);
$this->assertArrayHasKey('eventids', $response['result']);
if (is_array($event_ids))
$this->assertCount(count($event_ids), $response['result']['eventids']);
else
$this->assertArrayHasKey(0, $response['result']['eventids']);
}
}
private function checkSymptomsUntilSuccessOrTimeout($expected_events) {
$max_attempts = 10;
$sleep_time = 1;
for ($i = 0; $i < $max_attempts - 1; $i++) {
try {
$this->checkSymptoms($expected_events);
return;
} catch (Exception $e) {
sleep($sleep_time);
}
}
$this->checkSymptoms($expected_events);
}
private function checkSymptoms($expected_events) {
foreach (['problem.get', 'event.get'] as $request_type) {
// get events/problems
$response = $this->call($request_type, [
'output' => [
'eventid',
'cause_eventid'
],
'objectids' => self::$trigger_ids,
'source' => EVENT_SOURCE_TRIGGERS,
'object' => EVENT_OBJECT_TRIGGER
]);
$this->assertArrayHasKey('result', $response);
$this->assertCount(self::EVENT_COUNT, $response['result']);
$events = [];
foreach ($response['result'] as $event) {
$this->assertArrayHasKey('eventid', $event);
$this->assertArrayHasKey('cause_eventid', $event);
$events[$event['eventid']] = $event;
}
// check if actual cause eventid matches the expected cause eventid
foreach ($expected_events as $expected_event) {
$expected_event_id = $this->eventNumToId($expected_event['event_num']);
$expected_cause_eventid = $this->eventNumToId($expected_event['cause_event_num']);
$this->assertEquals($expected_cause_eventid,
$events[$expected_event_id]['cause_eventid']);
}
}
}
private function checkEventsStartUntilSuccessOrTimeout() {
$max_attempts = 5;
$sleep_time = 1;
for ($i = 0; $i < $max_attempts - 1; $i++) {
try {
return $this->checkEventsStart();
} catch (Exception $e) {
sleep($sleep_time);
}
}
return $this->checkEventsStart();
}
private function checkEventsStart() {
foreach (['problem.get', 'event.get'] as $request_type) {
// get events/problems
$response = $this->callUntilDataIsPresent($request_type, [
'output' => [
'eventid',
'objectid',
'cause_eventid'
],
'objectids' => self::$trigger_ids,
'source' => EVENT_SOURCE_TRIGGERS,
'object' => EVENT_OBJECT_TRIGGER
]);
$this->assertArrayHasKey('result', $response);
$this->assertCount(self::EVENT_COUNT, $response['result']);
$events = [];
foreach ($response['result'] as $event) {
$this->assertArrayHasKey('eventid', $event);
$this->assertArrayHasKey('objectid', $event);
$this->assertArrayHasKey('cause_eventid', $event);
$this->assertEquals(0, $event['cause_eventid']);
$events[$event['objectid']] = $event;
}
// make sure an event is started for each trigger and it is cause with no symptoms
foreach (self::$trigger_ids as $trigger_id) {
$this->assertArrayHasKey($trigger_id, $events);
}
}
return $events;
}
private function checkEventCauseMacros($expected_events) {
foreach ($expected_events as $expected_event) {
$eventid = $this->eventNumToId($expected_event['event_num']);
$expected_value = $this->expandMacros(self::EVENT_CAUSE_MACRO_TEMPLATE, $expected_event['event_num'],
$expected_event['cause_event_num']);
$response = $this->callUntilDataIsPresent('script.execute', [
'scriptid' => self::$cause_events_test_scriptid,
'eventid' => $eventid
], 10, 1);
$this->assertArrayHasKey('result', $response);
$this->assertArrayHasKey('value', $response['result']);
$this->assertEquals($expected_value, $response['result']['value']);
}
}
private function checkEventSymptomMacros($expected_events) {
$expected_symptoms = [];
foreach ($expected_events as $expected_event) {
$cause_event_num = $expected_event['cause_event_num'];
if (0 != $cause_event_num) {
if (!array_key_exists($cause_event_num, $expected_symptoms))
$expected_symptoms[$cause_event_num] = [];
$symptom_event_num = $expected_event['event_num'];
$expected_symptoms[$cause_event_num][] = $symptom_event_num;
}
}
foreach ($expected_symptoms as $cause_event_num => $symptom_events_nums) {
$response = $this->callUntilDataIsPresent('script.execute', [
'scriptid' => self::$symptom_events_test_scriptid,
'eventid' => $this->eventNumToId($cause_event_num)
], 10, 1);
$this->assertArrayHasKey('result', $response);
$this->assertArrayHasKey('value', $response['result']);
$expanded_macro = $response['result']['value'];
$lines = explode("\n", $expanded_macro);
$this->assertCount(count($symptom_events_nums), $lines);
// for all expected symptoms
for ($i = 0; $i < count($symptom_events_nums); $i++) {
$match_found = false;
$needle = sprintf("Host: %s Problem name: %s Severity: Not classified Age:",
self::HOST_NAME, 'Trigger trap '.(string)$symptom_events_nums[$i]);
// search for the expected symptom in the expanded macro line by line
foreach ($lines as $haystack) {
$cmp_result = substr_compare($haystack, $needle, 0, strlen($needle));
if (0 == $cmp_result) {
$match_found = true;
break;
}
}
$this->assertTrue($match_found, sprintf("A symptom which starts with:\n'%s'\nis not found in the expanded macro '%s':\n\n%s\n",
$needle, self::EVENT_SYMPTOMS_MACRO_TEMPLATE, $expanded_macro));
}
}
}
/**
* @inheritdoc
*/
public function prepareData() {
// create host
$response = $this->call('host.create', [
'host' => self::HOST_NAME,
'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('result', $response);
$this->assertArrayHasKey('hostids', $response['result']);
$this->assertArrayHasKey(0, $response['result']['hostids']);
self::$hostid = $response['result']['hostids'][0];
for ($i = 1; $i <= self::EVENT_COUNT; $i++) {
$item_name = self::TRAPPER_ITEM_NAME_PREFIX . (string) $i;
$item_key = self::TRAPPER_ITEM_KEY_PREFIX . (string) $i;
// create trapper item
$response = $this->call('item.create', [
'hostid' => self::$hostid,
'name' => $item_name,
'key_' => $item_key,
'type' => ITEM_TYPE_TRAPPER,
'value_type' => ITEM_VALUE_TYPE_UINT64
]);
$this->assertArrayHasKey('result', $response);
$this->assertArrayHasKey('itemids', $response['result']);
$this->assertEquals(1, count($response['result']['itemids']));
// create trigger
$response = $this->call('trigger.create', [
'description' => 'Trigger trap '.(string) $i,
'expression' => 'last(/'.self::HOST_NAME.'/'.$item_key.')='.self::EVENT_START
]);
$this->assertArrayHasKey('result', $response);
$this->assertArrayHasKey('triggerids', $response['result']);
$this->assertEquals(1, count($response['result']['triggerids']));
$trigger_id = $response['result']['triggerids'][0];
self::$trigger_ids[$i] = $trigger_id;
}
// create a script for testing cause events
$response = $this->call('script.create', [
'name' => self::TEST_CAUSE_EVENTS_SCRIPT_NAME,
'command' => sprintf('echo -n "%s"', self::EVENT_CAUSE_MACRO_TEMPLATE),
'execute_on' => ZBX_SCRIPT_EXECUTE_ON_SERVER,
'scope' => ZBX_SCRIPT_SCOPE_EVENT,
'type' => ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT
]);
$this->assertArrayHasKey('scriptids', $response['result']);
self::$cause_events_test_scriptid = $response['result']['scriptids'][0];
// create a script for testing symptom events
$response = $this->call('script.create', [
'name' => self::TEST_SYMPTOM_EVENTS_SCRIPT_NAME,
'command' => sprintf('echo -n "%s"', self::EVENT_SYMPTOMS_MACRO_TEMPLATE),
'execute_on' => ZBX_SCRIPT_EXECUTE_ON_SERVER,
'scope' => ZBX_SCRIPT_SCOPE_EVENT,
'type' => ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT
]);
$this->assertArrayHasKey('scriptids', $response['result']);
self::$symptom_events_test_scriptid = $response['result']['scriptids'][0];
}
/**
* Start 5 events/problems. All events/problems are expected to be causes with no symptoms.
*
* Expected result:
*
* [C] (1)
* [C] (2)
* [C] (3)
* [C] (4)
* [C] (5)
*/
public function testEventsCauseAndSymptoms_startEvents()
{
// start events/problems
for ($i = 1; $i <= self::EVENT_COUNT ; $i++) {
$item_key = self::TRAPPER_ITEM_KEY_PREFIX . (string) $i;
$this->sendSenderValue(self::HOST_NAME, $item_key, self::EVENT_START);
}
$events = $this->checkEventsStartUntilSuccessOrTimeout();
// save event ids
for ($i = 1; $i <= self::EVENT_COUNT; $i++) {
$trigger_id = self::$trigger_ids[$i];
self::$event_ids[$i] = $events[$trigger_id]['eventid'];
}
}
/**
* Rank events 2, 3 as symptoms of event 1. Rank event 5 as symptom of event 4.
*
* Initial position:
*
* [C] (1)
* [C] (2)
* [C] (3)
* [C] (4)
* [C] (5)
*
* Expected result:
*
* [C] (1)
* |----[S] (2)
* |----[S] (3)
*
* [C] (4)
* |----[S] (5)
*
* @depends testEventsCauseAndSymptoms_startEvents
*/
public function testEventsCauseAndSymptoms_rankAsSymptom() {
$this->markAsSymptoms([
['event_nums' => [2, 3], 'cause_event_num' => 1],
['event_nums' => 5, 'cause_event_num' => 4]
]);
$expected = [
['event_num' => 1, 'cause_event_num' => 0],
['event_num' => 2, 'cause_event_num' => 1],
['event_num' => 3, 'cause_event_num' => 1],
['event_num' => 4, 'cause_event_num' => 0],
['event_num' => 5, 'cause_event_num' => 4]
];
$this->checkSymptomsUntilSuccessOrTimeout($expected);
$this->checkEventCauseMacros($expected);
$this->checkEventSymptomMacros($expected);
}
/**
* Swap cause and symptom: rank cause to be it's symptom's symptom
*
* Initial position:
*
* [C] (1)
* |----[S] (2)
* |----[S] (3)
*
* [C] (4)
* |----[S] (5)
*
* Expected result:
*
* [C] (3) <-- swap is expected here
* |----[S] (1) <--
* |----[S] (2)
*
* [C] (4) <-- no change is expected here
* |----[S] (5)
*
* @depends testEventsCauseAndSymptoms_rankAsSymptom
*
*/
public function testEventsCauseAndSymptoms_swapCauseAndSymptom() {
$this->markAsSymptoms([
['event_nums' => 1, 'cause_event_num' => 3]
]);
$expected = [
['event_num' => 1, 'cause_event_num' => 3],
['event_num' => 2, 'cause_event_num' => 3],
['event_num' => 3, 'cause_event_num' => 0],
['event_num' => 4, 'cause_event_num' => 0],
['event_num' => 5, 'cause_event_num' => 4]
];
$this->checkSymptomsUntilSuccessOrTimeout($expected);
$this->checkEventCauseMacros($expected);
$this->checkEventSymptomMacros($expected);
}
/**
* Rank cause event 4 (which has one symptom 5) as a symptom of a symptom 2. Events 4 and 5 are expected to
* become symptoms of event's 2 cause 3.
*
* Initial position:
*
* [C] (3)
* |----[S] (1)
* |----[S] (2)
*
* [C] (4)
* |----[S] (5)
*
* Expected result:
*
* [C] (3)
* |----[S] (1)
* |----[S] (2)
* |----[S] (4)
* |----[S] (5)
*
* @depends testEventsCauseAndSymptoms_swapCauseAndSymptom
*
*/
public function testEventsCauseAndSymptoms_rankCauseAsSymptomOfSymptom() {
$this->markAsSymptoms([
['event_nums' => 4, 'cause_event_num' => 2]
]);
$expected = [
['event_num' => 1, 'cause_event_num' => 3],
['event_num' => 2, 'cause_event_num' => 3],
['event_num' => 3, 'cause_event_num' => 0],
['event_num' => 4, 'cause_event_num' => 3],
['event_num' => 5, 'cause_event_num' => 3]
];
$this->checkSymptomsUntilSuccessOrTimeout($expected);
$this->checkEventCauseMacros($expected);
$this->checkEventSymptomMacros($expected);
}
/**
* Rank symptom events as causes.
*
* Initial position:
*
* [C] (3)
* |----[S] (1)
* |----[S] (2)
* |----[S] (4)
* |----[S] (5)
*
* Expected result:
*
* [C] (1)
* [C] (2)
*
* [C] (3)
* |----[S] (4)
* |----[S] (5)
*
* @depends testEventsCauseAndSymptoms_rankCauseAsSymptomOfSymptom
*
*/
public function testEventsCauseAndSymptoms_rankAsCause1() {
// request event/problem ranking
$response = $this->call('event.acknowledge', [
'eventids' => $this->eventNumToId([1, 2]),
'action' => ZBX_PROBLEM_UPDATE_RANK_TO_CAUSE
]);
$this->assertArrayHasKey('result', $response);
$this->assertArrayHasKey('eventids', $response['result']);
$this->assertCount(2, $response['result']['eventids']);
$expected = [
['event_num' => 1, 'cause_event_num' => 0],
['event_num' => 2, 'cause_event_num' => 0],
['event_num' => 3, 'cause_event_num' => 0],
['event_num' => 4, 'cause_event_num' => 3],
['event_num' => 5, 'cause_event_num' => 3]
];
$this->checkSymptomsUntilSuccessOrTimeout($expected);
$this->checkEventCauseMacros($expected);
$this->checkEventSymptomMacros($expected);
}
/**
* Rank symptom events as causes.
*
* Initial position:
*
* [C] (1)
* [C] (2)
*
* [C] (3)
* |----[S] (4)
* |----[S] (5)
*
* Expected result:
*
* [C] (1)
* [C] (2)
* [C] (3)
* [C] (4)
* [C] (5)
*
* @depends testEventsCauseAndSymptoms_rankAsCause1
*
*/
public function testEventsCauseAndSymptoms_rankAsCause2() {
// request event/problem ranking
$response = $this->call('event.acknowledge', [
'eventids' => $this->eventNumToId([4, 5]),
'action' => ZBX_PROBLEM_UPDATE_RANK_TO_CAUSE
]);
$this->assertArrayHasKey('result', $response);
$this->assertArrayHasKey('eventids', $response['result']);
$this->assertCount(2, $response['result']['eventids']);
$expected = [
['event_num' => 1, 'cause_event_num' => 0],
['event_num' => 2, 'cause_event_num' => 0],
['event_num' => 3, 'cause_event_num' => 0],
['event_num' => 4, 'cause_event_num' => 0],
['event_num' => 5, 'cause_event_num' => 0]
];
$this->checkSymptomsUntilSuccessOrTimeout($expected);
$this->checkEventCauseMacros($expected);
}
}