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