1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22function serviceAlgorithm($algorithm = null) { 23 $algorithms = [ 24 SERVICE_ALGORITHM_MAX => _('Problem, if at least one child has a problem'), 25 SERVICE_ALGORITHM_MIN => _('Problem, if all children have problems'), 26 SERVICE_ALGORITHM_NONE => _('Do not calculate') 27 ]; 28 29 if ($algorithm === null) { 30 return $algorithms; 31 } 32 elseif (isset($algorithms[$algorithm])) { 33 return $algorithms[$algorithm]; 34 } 35 else { 36 return false; 37 } 38} 39 40function get_service_children($serviceid, $soft = 0) { 41 $children = []; 42 43 $result = DBselect( 44 'SELECT sl.servicedownid'. 45 ' FROM services_links sl'. 46 ' WHERE sl.serviceupid='.zbx_dbstr($serviceid). 47 ($soft ? '' : ' AND sl.soft=0') 48 ); 49 while ($row = DBfetch($result)) { 50 $children[] = $row['servicedownid']; 51 $children = array_merge($children, get_service_children($row['servicedownid'])); 52 } 53 return $children; 54} 55 56/** 57 * Creates nodes that can be used to display the service configuration tree using the CTree class. 58 * 59 * @see CTree 60 * 61 * @param array $services 62 * @param array $parentService 63 * @param array $service 64 * @param array $dependency 65 * @param array $tree 66 */ 67function createServiceConfigurationTree(array $services, &$tree, array $parentService = [], array $service = [], array $dependency = []) { 68 if (!$service) { 69 $serviceNode = [ 70 'id' => 0, 71 'parentid' => 0, 72 'caption' => _('root'), 73 'trigger' => [], 74 'action' => new CHorList([ 75 (new CLink(_('Add child'), 'services.php?form=1&parentname='._('root'))) 76 ->addClass(ZBX_STYLE_LINK_ACTION) 77 ]), 78 'algorithm' => SPACE, 79 'description' => SPACE 80 ]; 81 82 $service = $serviceNode; 83 $service['serviceid'] = 0; 84 $service['dependencies'] = []; 85 $service['trigger'] = []; 86 87 // add all top level services as children of "root" 88 foreach ($services as $topService) { 89 if (!$topService['parent']) { 90 $service['dependencies'][] = [ 91 'servicedownid' => $topService['serviceid'], 92 'soft' => 0, 93 'linkid' => 0 94 ]; 95 } 96 } 97 98 $tree = [$serviceNode]; 99 } 100 else { 101 // service is deletable only if it has no hard dependency 102 $deletable = true; 103 foreach ($service['dependencies'] as $dep) { 104 if ($dep['soft'] == 0) { 105 $deletable = false; 106 break; 107 } 108 } 109 110 $serviceNode = [ 111 'id' => $service['serviceid'], 112 'caption' => new CLink($service['name'], 'services.php?form=1&serviceid='.$service['serviceid']), 113 'action' => new CHorList([ 114 (new CLink(_('Add child'), 115 'services.php?form=1&parentid='.$service['serviceid'].'&parentname='.urlencode($service['name']) 116 ))->addClass(ZBX_STYLE_LINK_ACTION), 117 $deletable 118 ? (new CLink(_('Delete'), 'services.php?delete=1&serviceid='.$service['serviceid'])) 119 ->addClass(ZBX_STYLE_LINK_ACTION) 120 ->addConfirmation(_s('Delete service "%1$s"?', $service['name'])) 121 ->addSID() 122 : null 123 ]), 124 'description' => $service['trigger'] ? $service['trigger']['description'] : '', 125 'parentid' => $parentService ? $parentService['serviceid'] : 0, 126 'algorithm' => serviceAlgorithm($service['algorithm']) 127 ]; 128 } 129 130 if (!$dependency || !$dependency['soft']) { 131 $tree[$serviceNode['id']] = $serviceNode; 132 133 foreach ($service['dependencies'] as $dependency) { 134 $childService = $services[$dependency['servicedownid']]; 135 createServiceConfigurationTree($services, $tree, $service, $childService, $dependency); 136 } 137 } 138 else { 139 $serviceNode['caption'] = (new CSpan($serviceNode['caption']))->addClass('service-caption-soft'); 140 141 $tree[$serviceNode['id'].'.'.$dependency['linkid']] = $serviceNode; 142 } 143} 144 145/** 146 * Creates nodes that can be used to display the SLA report tree using the CTree class. 147 * 148 * @see CTree 149 * 150 * @param array $services an array of services to display in the tree 151 * @param array $slaData sla report data, see CService::getSla() 152 * @param $period 153 * @param array $parentService 154 * @param array $service 155 * @param array $dependency 156 * @param array $tree 157 */ 158function createServiceMonitoringTree(array $services, array $slaData, $period, &$tree, array $parentService = [], array $service = [], array $dependency = []) { 159 // if no parent service is given, start from the root 160 if (!$service) { 161 $serviceNode = [ 162 'id' => 0, 163 'caption' => _('root'), 164 'reason' => '', 165 'sla' => '', 166 'sla2' => '', 167 'sla3' => '', 168 'parentid' => 0, 169 'status' => '' 170 ]; 171 172 $service = $serviceNode; 173 $service['serviceid'] = 0; 174 $service['dependencies'] = []; 175 $service['trigger'] = []; 176 177 // add all top level services as children of "root" 178 foreach ($services as $topService) { 179 if (!$topService['parent']) { 180 $service['dependencies'][] = [ 181 'servicedownid' => $topService['serviceid'], 182 'soft' => 0, 183 'linkid' => 0 184 ]; 185 } 186 } 187 188 $tree = [$serviceNode]; 189 } 190 // create a not from the given service 191 else { 192 $serviceSla = $slaData[$service['serviceid']]; 193 $slaValues = reset($serviceSla['sla']); 194 195 // caption 196 // remember the selected time period when following the bar link 197 $periods = [ 198 'today' => 'daily', 199 'week' => 'weekly', 200 'month' => 'monthly', 201 'year' => 'yearly', 202 24 => 'daily', 203 24 * 7 => 'weekly', 204 24 * 30 => 'monthly', 205 24 * DAY_IN_YEAR => 'yearly' 206 ]; 207 208 $caption = new CLink($service['name'], 209 'zabbix.php?action=report.services'.'&serviceid='.$service['serviceid'].'&period='.$periods[$period] 210 ); 211 212 $trigger = $service['trigger']; 213 if ($trigger) { 214 $caption = [ 215 $caption, 216 ' - ', 217 new CLink($trigger['description'], 218 (new CUrl('zabbix.php')) 219 ->setArgument('action', 'problem.view') 220 ->setArgument('filter_triggerids[]', $trigger['triggerid']) 221 ->setArgument('filter_set', '1') 222 ) 223 ]; 224 } 225 226 // reason 227 $reason = []; 228 foreach ($serviceSla['problems'] as $problemTrigger) { 229 if ($reason) { 230 $reason[] = ', '; 231 } 232 $reason[] = new CLink($problemTrigger['description'], 233 (new CUrl('zabbix.php')) 234 ->setArgument('action', 'problem.view') 235 ->setArgument('filter_triggerids[]', $problemTrigger['triggerid']) 236 ->setArgument('filter_set', '1') 237 ); 238 } 239 240 // sla 241 $sla = ''; 242 $sla2 = ''; 243 $sla3 = ''; 244 if ($service['showsla'] && $slaValues['sla'] !== null) { 245 $sla_good = $slaValues['sla']; 246 $sla_bad = 100 - $slaValues['sla']; 247 248 $width = 160; 249 $width_red = $width * min($sla_bad, 20) / 20; 250 $width_green = $width - $width_red; 251 252 $sla = (new CDiv( 253 new CLink([ 254 (new CSpan([new CSpan('80%'), new CSpan('100%')]))->addClass(ZBX_STYLE_PROGRESS_BAR_LABEL), 255 $width_green > 0 256 ? (new CSpan(' ')) 257 ->addClass(ZBX_STYLE_PROGRESS_BAR_BG) 258 ->addClass(ZBX_STYLE_GREEN_BG) 259 ->setAttribute('style', 'width: '.$width_green.'px;') 260 : null, 261 $width_red > 0 262 ? (new CSpan(' ')) 263 ->addClass(ZBX_STYLE_PROGRESS_BAR_BG) 264 ->addClass(ZBX_STYLE_RED_BG) 265 ->setAttribute('style', 'width: '.$width_red.'px;') 266 : null 267 ], 'srv_status.php?serviceid='.$service['serviceid'].'&showgraph=1'.url_param('path')) 268 )) 269 ->addClass(ZBX_STYLE_PROGRESS_BAR_CONTAINER) 270 ->setTitle(_s('Only the last 20%% of the indicator is displayed.')); 271 272 $sla2 = (new CSpan(sprintf('%.4f', $sla_bad))) 273 ->addClass($service['goodsla'] > $sla_good ? ZBX_STYLE_RED : ZBX_STYLE_GREEN); 274 275 $sla3 = [ 276 (new CSpan(sprintf('%.4f', $sla_good))) 277 ->addClass($service['goodsla'] > $sla_good ? ZBX_STYLE_RED : ZBX_STYLE_GREEN), 278 ' / ', 279 sprintf('%.4f', $service['goodsla']) 280 ]; 281 } 282 283 $serviceNode = [ 284 'id' => $service['serviceid'], 285 'caption' => $caption, 286 'reason' => $reason, 287 'sla' => $sla, 288 'sla2' => $sla2, 289 'sla3' => $sla3, 290 'parentid' => ($parentService) ? $parentService['serviceid'] : 0, 291 'status' => ($serviceSla['status'] !== null) ? $serviceSla['status'] : '' 292 ]; 293 } 294 295 // hard dependencies and dependencies for the "root" node 296 if (!$dependency || $dependency['soft'] == 0) { 297 $tree[$serviceNode['id']] = $serviceNode; 298 299 foreach ($service['dependencies'] as $dependency) { 300 $childService = $services[$dependency['servicedownid']]; 301 createServiceMonitoringTree($services, $slaData, $period, $tree, $service, $childService, $dependency); 302 } 303 } 304 // soft dependencies 305 else { 306 $serviceNode['caption'] = (new CSpan($serviceNode['caption']))->addClass('service-caption-soft'); 307 308 $tree[$serviceNode['id'].'.'.$dependency['linkid']] = $serviceNode; 309 } 310} 311 312/** 313 * Calculates the current service status based on it's child services. 314 * 315 * The new statuses are written to the $services array in the "newStatus" property. 316 * 317 * @param string $rootServiceId id of the service to start calculation from 318 * @param array $servicesLinks array with service IDs as keys and arrays of child service IDs as values 319 * @param array $services array of services with IDs as keys 320 * @param array $triggers array of triggers with trigger IDs as keys 321 */ 322function calculateItServiceStatus($rootServiceId, array $servicesLinks, array &$services, array $triggers) { 323 $service = &$services[$rootServiceId]; 324 325 // don't calculate a thread if it is already calculated 326 // it can be with soft links 327 if (isset($service['newStatus'])) { 328 return; 329 } 330 331 $newStatus = SERVICE_STATUS_OK; 332 333 // leaf service with a trigger 334 if ($service['triggerid'] != 0) { 335 if ($service['algorithm'] != SERVICE_ALGORITHM_NONE) { 336 $trigger = $triggers[$service['triggerid']]; 337 $newStatus = calculateItServiceStatusByTrigger($trigger['status'], $trigger['value'], $trigger['priority']); 338 } 339 } 340 elseif (isset($servicesLinks[$rootServiceId])) { 341 // calculate status depending on children status 342 $statuses = []; 343 344 foreach ($servicesLinks[$rootServiceId] as $rootServiceId) { 345 calculateItServiceStatus($rootServiceId, $servicesLinks, $services, $triggers); 346 $statuses[] = $services[$rootServiceId]['newStatus']; 347 } 348 349 if ($statuses && $service['algorithm'] != SERVICE_ALGORITHM_NONE) { 350 $maxSeverity = max($statuses); 351 352 // always return the maximum status of child services 353 if ($service['algorithm'] == SERVICE_ALGORITHM_MAX && $maxSeverity != SERVICE_STATUS_OK) { 354 $newStatus = $maxSeverity; 355 } 356 elseif (min($statuses) != SERVICE_STATUS_OK) { 357 $newStatus = $maxSeverity; 358 } 359 } 360 } 361 362 $service['newStatus'] = $newStatus; 363} 364 365/** 366 * Checks the status of the trigger and returns the corresponding service status. 367 * 368 * @param int $triggerStatus 369 * @param int $triggerValue 370 * @param int $triggerPriority 371 * 372 * @return int 373 */ 374function calculateItServiceStatusByTrigger($triggerStatus, $triggerValue, $triggerPriority) { 375 if ($triggerStatus == TRIGGER_STATUS_DISABLED || $triggerValue == TRIGGER_VALUE_FALSE) { 376 return SERVICE_STATUS_OK; 377 } 378 379 return $triggerPriority; 380} 381 382/** 383 * Updates the status of all services 384 */ 385function updateItServices() { 386 $servicesLinks = []; 387 $services = []; 388 $rootServiceIds = []; 389 $triggers = []; 390 391 // auxiliary arrays 392 $triggerIds = []; 393 $servicesLinksDown = []; 394 395 $result = DBselect('SELECT sl.serviceupid,sl.servicedownid FROM services_links sl'); 396 397 while ($row = DBfetch($result)) { 398 $servicesLinks[$row['serviceupid']][] = $row['servicedownid']; 399 $servicesLinksDown[$row['servicedownid']] = true; 400 } 401 402 $result = DBselect('SELECT s.serviceid,s.algorithm,s.triggerid,s.status FROM services s ORDER BY s.serviceid'); 403 404 while ($row = DBfetch($result)) { 405 $services[$row['serviceid']] = [ 406 'serviceid' => $row['serviceid'], 407 'algorithm' => $row['algorithm'], 408 'triggerid' => $row['triggerid'], 409 'status' => $row['status'] 410 ]; 411 412 if (!isset($servicesLinksDown[$row['serviceid']])) { 413 $rootServiceIds[] = $row['serviceid']; 414 } 415 416 if ($row['triggerid'] != 0) { 417 $triggerIds[$row['triggerid']] = true; 418 } 419 } 420 421 if ($triggerIds) { 422 $result = DBselect( 423 'SELECT t.triggerid,t.priority,t.status,t.value'. 424 ' FROM triggers t'. 425 ' WHERE '.dbConditionInt('t.triggerid', array_keys($triggerIds)) 426 ); 427 428 while ($row = DBfetch($result)) { 429 $triggers[$row['triggerid']] = [ 430 'priority' => $row['priority'], 431 'status' => $row['status'], 432 'value' => $row['value'] 433 ]; 434 } 435 } 436 437 // clearing auxiliary variables 438 unset($triggerIds, $servicesLinksDown); 439 440 // calculating data 441 foreach ($rootServiceIds as $rootServiceId) { 442 calculateItServiceStatus($rootServiceId, $servicesLinks, $services, $triggers); 443 } 444 445 // updating changed data 446 $updates = []; 447 $inserts = []; 448 $clock = time(); 449 450 foreach ($services as $service) { 451 if ($service['newStatus'] != $service['status']) { 452 $updates[] = [ 453 'values' => ['status' => $service['newStatus']], 454 'where' => ['serviceid' => $service['serviceid']] 455 ]; 456 $inserts[] = [ 457 'serviceid' => $service['serviceid'], 458 'clock' => $clock, 459 'value' => $service['newStatus'] 460 ]; 461 } 462 } 463 464 if ($updates) { 465 DB::update('services', $updates); 466 DB::insert('service_alarms', $inserts); 467 } 468} 469 470/** 471 * Validate the new service time. Validation is implemented as a separate function to be available directly from the 472 * frontend. 473 * 474 * @throws APIException if the given service time is invalid 475 * 476 * @param array $serviceTime 477 */ 478function checkServiceTime(array $serviceTime) { 479 // type validation 480 $serviceTypes = [ 481 SERVICE_TIME_TYPE_DOWNTIME, 482 SERVICE_TIME_TYPE_ONETIME_DOWNTIME, 483 SERVICE_TIME_TYPE_UPTIME 484 ]; 485 if (!isset($serviceTime['type']) || !in_array($serviceTime['type'], $serviceTypes)) { 486 throw new APIException(ZBX_API_ERROR_PARAMETERS, _('Incorrect service time type.')); 487 } 488 489 // one-time downtime validation 490 if ($serviceTime['type'] == SERVICE_TIME_TYPE_ONETIME_DOWNTIME) { 491 if (!isset($serviceTime['ts_from']) || !validateUnixTime($serviceTime['ts_from'])) { 492 throw new APIException(ZBX_API_ERROR_PARAMETERS, _('Incorrect service start time.')); 493 } 494 if (!isset($serviceTime['ts_to']) || !validateUnixTime($serviceTime['ts_to'])) { 495 throw new APIException(ZBX_API_ERROR_PARAMETERS, _('Incorrect service end time.')); 496 } 497 } 498 // recurring downtime validation 499 else { 500 if (!isset($serviceTime['ts_from']) || !zbx_is_int($serviceTime['ts_from']) || $serviceTime['ts_from'] < 0 || $serviceTime['ts_from'] > SEC_PER_WEEK) { 501 throw new APIException(ZBX_API_ERROR_PARAMETERS, _('Incorrect service start time.')); 502 } 503 if (!isset($serviceTime['ts_to']) || !zbx_is_int($serviceTime['ts_to']) || $serviceTime['ts_to'] < 0 || $serviceTime['ts_to'] > SEC_PER_WEEK) { 504 throw new APIException(ZBX_API_ERROR_PARAMETERS, _('Incorrect service end time.')); 505 } 506 } 507 508 if ($serviceTime['ts_from'] >= $serviceTime['ts_to']) { 509 throw new APIException(ZBX_API_ERROR_PARAMETERS, _('Service start time must be less than end time.')); 510 } 511} 512 513/** 514 * Method to sort list of Services by 'sortorder' field and then by 'name' field if more entries has same 'sortorder' 515 * value. Separate method is needed because entries make multilevel hierarchy and branches also must be sorted according 516 * fields 'sortorder' and 'name'. 517 * 518 * @param array $services 519 * 520 * @return void 521 */ 522function sortServices(array &$services) { 523 $sort_options = [ 524 ['field' => 'sortorder', 'order' => ZBX_SORT_UP], 525 ['field' => 'name', 'order' => ZBX_SORT_UP] 526 ]; 527 528 // Sort first level entries. 529 CArrayHelper::sort($services, $sort_options); 530 531 // Sort dependencies. 532 foreach ($services as &$service) { 533 if ($service['dependencies']) { 534 foreach ($service['dependencies'] as &$dependent_item) { 535 $dependent_item['name'] = $services[$dependent_item['serviceid']]['name']; 536 $dependent_item['sortorder'] = $services[$dependent_item['serviceid']]['sortorder']; 537 } 538 unset($dependent_item); 539 540 CArrayHelper::sort($service['dependencies'], $sort_options); 541 } 542 } 543 unset($service); 544} 545