zabbix/ui/include/classes/api/services/CSla.php

1495 lines
47 KiB

<?php declare(strict_types = 0);
/*
** 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.
**/
/**
* SLA API implementation.
*/
class CSla extends CApiService {
public const ACCESS_RULES = [
'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
'getsli' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN, 'action' => CRoleHelper::ACTIONS_MANAGE_SLA],
'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN, 'action' => CRoleHelper::ACTIONS_MANAGE_SLA],
'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN, 'action' => CRoleHelper::ACTIONS_MANAGE_SLA]
];
protected $tableName = 'sla';
protected $tableAlias = 'sla';
protected $sortColumns = ['slaid', 'name', 'period', 'slo', 'effective_date', 'timezone', 'status', 'description'];
/**
* @param array $options
*
* @throws APIException
*
* @return array|string
*/
public function get(array $options = []) {
$api_input_rules = ['type' => API_OBJECT, 'fields' => [
// filter
'slaids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null],
'evaltype' => ['type' => API_INT32, 'in' => implode(',', [TAG_EVAL_TYPE_AND_OR, TAG_EVAL_TYPE_OR]), 'default' => TAG_EVAL_TYPE_AND_OR],
'service_tags' => ['type' => API_OBJECTS, 'default' => [], 'fields' => [
'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED],
'value' => ['type' => API_STRING_UTF8],
'operator' => ['type' => API_INT32, 'in' => implode(',', [TAG_OPERATOR_LIKE, TAG_OPERATOR_EQUAL, TAG_OPERATOR_NOT_LIKE, TAG_OPERATOR_NOT_EQUAL, TAG_OPERATOR_EXISTS, TAG_OPERATOR_NOT_EXISTS])]
]],
'serviceids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null],
'filter' => ['type' => API_FILTER, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => ['slaid', 'name', 'period', 'slo', 'effective_date', 'timezone', 'status']],
'search' => ['type' => API_FILTER, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => ['name', 'timezone', 'description']],
'searchByAny' => ['type' => API_BOOLEAN, 'default' => false],
'startSearch' => ['type' => API_FLAG, 'default' => false],
'excludeSearch' => ['type' => API_FLAG, 'default' => false],
'searchWildcardsEnabled' => ['type' => API_BOOLEAN, 'default' => false],
// output
'output' => ['type' => API_OUTPUT, 'in' => implode(',', ['slaid', 'name', 'period', 'slo', 'effective_date', 'timezone', 'status', 'description']), 'default' => API_OUTPUT_EXTEND],
'countOutput' => ['type' => API_FLAG, 'default' => false],
'selectServiceTags' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', ['tag', 'operator', 'value']), 'default' => null],
'selectSchedule' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', ['period_from', 'period_to']), 'default' => null],
'selectExcludedDowntimes' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', ['name', 'period_from', 'period_to']), 'default' => null],
// sort and limit
'sortfield' => ['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', ['slaid', 'name', 'period', 'slo', 'effective_date', 'timezone', 'status', 'description']), 'uniq' => true, 'default' => []],
'sortorder' => ['type' => API_SORTORDER, 'default' => []],
'limit' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_MAX_INT32, 'default' => null],
// flags
'editable' => ['type' => API_BOOLEAN, 'default' => false],
'preservekeys' => ['type' => API_BOOLEAN, 'default' => false]
]];
if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
$accessible_slaids = self::getAccessibleSlaids($options['slaids'], $options['serviceids']);
$count_output = $options['countOutput'];
if ($count_output) {
$options['output'] = ['slaid'];
$options['countOutput'] = false;
}
$resource = DBselect($this->createSelectQuery('sla', $options));
$db_slas = [];
while (($options['limit'] === null || count($db_slas) < $options['limit']) && $row = DBfetch($resource)) {
if ($accessible_slaids !== null && !array_key_exists($row['slaid'], $accessible_slaids)) {
continue;
}
$db_slas[$row['slaid']] = $row;
}
if ($count_output) {
return (string) count($db_slas);
}
if ($db_slas) {
$db_slas = $this->addRelatedObjects($options, $db_slas);
$db_slas = $this->unsetExtraFields($db_slas, ['slaid'], $options['output']);
if (!$options['preservekeys']) {
$db_slas = array_values($db_slas);
}
}
return $db_slas;
}
/**
* @param array $slas
*
* @throws APIException
*
* @return array
*/
public function create(array $slas): array {
self::validateCreate($slas);
$ins_slas = [];
foreach ($slas as $sla) {
unset($sla['service_tags'], $sla['schedule'], $sla['excluded_downtimes']);
$ins_slas[] = $sla;
}
$slaids = DB::insert('sla', $ins_slas);
foreach ($slas as $index => &$sla) {
$sla['slaid'] = $slaids[$index];
}
unset($sla);
self::updateServiceTags($slas);
self::updateSchedule($slas);
self::updateExcludedDowntimes($slas);
self::addAuditLog(CAudit::ACTION_ADD, CAudit::RESOURCE_SLA, $slas);
return ['slaids' => $slaids];
}
/**
* @param array $slas
*
* @throws APIException
*/
private static function validateCreate(array &$slas): void {
$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['name']], 'fields' => [
'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('sla', 'name')],
'period' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_SLA_PERIOD_DAILY, ZBX_SLA_PERIOD_WEEKLY, ZBX_SLA_PERIOD_MONTHLY, ZBX_SLA_PERIOD_QUARTERLY, ZBX_SLA_PERIOD_ANNUALLY])],
'slo' => ['type' => API_FLOAT, 'flags' => API_REQUIRED, 'in' => '0:100'],
'effective_date' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '0:'.ZBX_MAX_DATE],
'timezone' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'in' => implode(',', array_merge([ZBX_DEFAULT_TIMEZONE], array_keys(CTimezoneHelper::getList()))), 'length' => DB::getFieldLength('sla', 'timezone')],
'status' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SLA_STATUS_DISABLED, ZBX_SLA_STATUS_ENABLED])],
'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sla', 'description')],
'service_tags' => ['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'uniq' => [['tag', 'value']], 'fields' => [
'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('sla_service_tag', 'tag')],
'operator' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SLA_SERVICE_TAG_OPERATOR_EQUAL, ZBX_SLA_SERVICE_TAG_OPERATOR_LIKE]), 'default' => DB::getDefault('sla_service_tag', 'operator')],
'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sla_service_tag', 'value'), 'default' => DB::getDefault('sla_service_tag', 'value')]
]],
'schedule' => ['type' => API_OBJECTS, 'uniq' => [['period_from', 'period_to']], 'fields' => [
'period_from' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '0:'.SEC_PER_WEEK],
'period_to' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '0:'.SEC_PER_WEEK]
]],
'excluded_downtimes' => ['type' => API_OBJECTS, 'uniq' => [['period_from', 'period_to']], 'fields' => [
'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('sla_excluded_downtime', 'name')],
'period_from' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '0:'.ZBX_MAX_DATE],
'period_to' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '0:'.ZBX_MAX_DATE]
]]
]];
if (!CApiInputValidator::validate($api_input_rules, $slas, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
self::checkDuplicates($slas);
self::checkSlo($slas);
self::checkSchedule($slas);
self::checkExcludedDowntimes($slas);
}
/**
* @param array $slas
*
* @throws APIException
*
* @return array
*/
public function update(array $slas): array {
self::validateUpdate($slas, $db_slas);
$upd_slas = [];
foreach ($slas as $sla) {
$upd_sla = DB::getUpdatedValues('sla', $sla, $db_slas[$sla['slaid']]);
if ($upd_sla) {
$upd_slas[] = [
'values' => $upd_sla,
'where' => ['slaid' => $sla['slaid']]
];
}
}
if ($upd_slas) {
DB::update('sla', $upd_slas);
}
self::updateServiceTags($slas, $db_slas);
self::updateSchedule($slas, $db_slas);
self::updateExcludedDowntimes($slas, $db_slas);
self::addAuditLog(CAudit::ACTION_UPDATE, CAudit::RESOURCE_SLA, $slas, $db_slas);
return ['slaids' => array_column($slas, 'slaid')];
}
/**
* @param array $slas
* @param array|null $db_slas
*
* @throws APIException
*/
private static function validateUpdate(array &$slas, ?array &$db_slas): void {
$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['slaid'], ['name']], 'fields' => [
'slaid' => ['type' => API_ID, 'flags' => API_REQUIRED],
'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('sla', 'name')],
'period' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SLA_PERIOD_DAILY, ZBX_SLA_PERIOD_WEEKLY, ZBX_SLA_PERIOD_MONTHLY, ZBX_SLA_PERIOD_QUARTERLY, ZBX_SLA_PERIOD_ANNUALLY])],
'slo' => ['type' => API_FLOAT, 'in' => '0:100'],
'effective_date' => ['type' => API_INT32, 'in' => '0:'.ZBX_MAX_DATE],
'timezone' => ['type' => API_STRING_UTF8, 'in' => implode(',', array_merge([ZBX_DEFAULT_TIMEZONE], array_keys(CTimezoneHelper::getList()))), 'length' => DB::getFieldLength('sla', 'timezone')],
'status' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SLA_STATUS_DISABLED, ZBX_SLA_STATUS_ENABLED])],
'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sla', 'description')],
'service_tags' => ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'uniq' => [['tag', 'value']], 'fields' => [
'tag' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('sla_service_tag', 'tag')],
'operator' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SLA_SERVICE_TAG_OPERATOR_EQUAL, ZBX_SLA_SERVICE_TAG_OPERATOR_LIKE]), 'default' => DB::getDefault('sla_service_tag', 'operator')],
'value' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sla_service_tag', 'value'), 'default' => DB::getDefault('sla_service_tag', 'value')]
]],
'schedule' => ['type' => API_OBJECTS, 'uniq' => [['period_from', 'period_to']], 'fields' => [
'period_from' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '0:'.SEC_PER_WEEK],
'period_to' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '0:'.SEC_PER_WEEK]
]],
'excluded_downtimes' => ['type' => API_OBJECTS, 'uniq' => [['period_from', 'period_to']], 'fields' => [
'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('sla_excluded_downtime', 'name')],
'period_from' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '0:'.ZBX_MAX_DATE],
'period_to' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => '0:'.ZBX_MAX_DATE]
]]
]];
if (!CApiInputValidator::validate($api_input_rules, $slas, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
$db_slas = DB::select('sla', [
'output' => ['slaid', 'name', 'period', 'slo', 'timezone', 'status', 'description'],
'slaids' => array_column($slas, 'slaid'),
'preservekeys' => true
]);
if (count($db_slas) != count($slas)) {
self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
}
self::addAffectedObjects($slas, $db_slas);
self::checkDuplicates($slas, $db_slas);
self::checkSlo($slas, $db_slas);
self::checkSchedule($slas, $db_slas);
self::checkExcludedDowntimes($slas, $db_slas);
}
/**
* @param array $slaids
*
* @throws APIException
*
* @return array
*/
public function delete(array $slaids): array {
$this->validateDelete($slaids, $db_slas);
DB::delete('sla', ['slaid' => $slaids]);
self::addAuditLog(CAudit::ACTION_DELETE, CAudit::RESOURCE_SLA, $db_slas);
return ['slaids' => $slaids];
}
/**
* @param array $slaids
* @param array|null $db_slas
*
* @throws APIException
*/
private function validateDelete(array $slaids, ?array &$db_slas): void {
$api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true];
if (!CApiInputValidator::validate($api_input_rules, $slaids, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
$db_slas = $this->get([
'output' => ['slaid', 'name'],
'slaids' => $slaids,
'editable' => true,
'preservekeys' => true
]);
if (count($db_slas) != count($slaids)) {
self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
}
}
/**
* @param string $table_name
* @param string $table_alias
* @param array $options
* @param array $sql_parts
*
* @return array
*/
protected function applyQueryFilterOptions($table_name, $table_alias, array $options, array $sql_parts): array {
$sql_parts = parent::applyQueryFilterOptions($table_name, $table_alias, $options, $sql_parts);
if ($options['service_tags']) {
$sql_parts['where'][] = CApiTagHelper::addWhereCondition($options['service_tags'], $options['evaltype'],
'sla', 'sla_service_tag', 'slaid'
);
}
return $sql_parts;
}
/**
* @param array $options
* @param array $result
*
* @return array
*/
protected function addRelatedObjects(array $options, array $result): array {
$result = parent::addRelatedObjects($options, $result);
self::addRelatedServiceTags($options, $result);
self::addRelatedSchedule($options, $result);
self::addRelatedExcludedDowntimes($options, $result);
return $result;
}
/**
* @param array $options
* @param array $result
*/
private static function addRelatedServiceTags(array $options, array &$result): void {
if ($options['selectServiceTags'] === null) {
return;
}
foreach ($result as &$row) {
$row['service_tags'] = [];
}
unset($row);
if ($options['selectServiceTags'] === API_OUTPUT_COUNT) {
$output = ['sla_service_tagid', 'slaid'];
}
elseif ($options['selectServiceTags'] === API_OUTPUT_EXTEND) {
$output = ['sla_service_tagid', 'slaid', 'tag', 'operator', 'value'];
}
else {
$output = array_unique(array_merge(['sla_service_tagid', 'slaid'], $options['selectServiceTags']));
}
$sql_options = [
'output' => $output,
'filter' => ['slaid' => array_keys($result)]
];
$db_service_tags = DBselect(DB::makeSql('sla_service_tag', $sql_options));
while ($db_service_tag = DBfetch($db_service_tags)) {
$slaid = $db_service_tag['slaid'];
unset($db_service_tag['sla_service_tagid'], $db_service_tag['slaid']);
$result[$slaid]['service_tags'][] = $db_service_tag;
}
if ($options['selectServiceTags'] === API_OUTPUT_COUNT) {
foreach ($result as &$row) {
$row['service_tags'] = (string) count($row['service_tags']);
}
unset($row);
}
}
/**
* @param array $options
* @param array $result
*/
private static function addRelatedSchedule(array $options, array &$result): void {
if ($options['selectSchedule'] === null) {
return;
}
foreach ($result as &$row) {
$row['schedule'] = [];
}
unset($row);
$sql_options = [
'output' => ['sla_scheduleid', 'slaid', 'period_from', 'period_to'],
'filter' => ['slaid' => array_keys($result)]
];
$db_schedule = DBselect(DB::makeSql('sla_schedule', $sql_options));
while ($db_schedule_row = DBfetch($db_schedule)) {
$slaid = $db_schedule_row['slaid'];
unset($db_schedule_row['sla_scheduleid'], $db_schedule_row['slaid']);
$result[$slaid]['schedule'][] = $db_schedule_row;
}
foreach ($result as &$row) {
$row['schedule'] = self::normalizeSchedule($row['schedule']);
if ($options['selectSchedule'] === API_OUTPUT_COUNT) {
$row['schedule'] = (string) count($row['schedule']);
}
elseif ($options['selectSchedule'] !== API_OUTPUT_EXTEND) {
$select_schedule = array_flip($options['selectSchedule']);
foreach ($row['schedule'] as &$schedule_row) {
$schedule_row = array_intersect_key($schedule_row, $select_schedule);
}
unset($schedule_row);
}
}
unset($row);
}
/**
* @param array $options
* @param array $result
*/
private static function addRelatedExcludedDowntimes(array $options, array &$result): void {
if ($options['selectExcludedDowntimes'] === null) {
return;
}
foreach ($result as &$row) {
$row['excluded_downtimes'] = [];
}
unset($row);
if ($options['selectExcludedDowntimes'] === API_OUTPUT_COUNT) {
$output = ['sla_excluded_downtimeid', 'slaid'];
}
elseif ($options['selectExcludedDowntimes'] === API_OUTPUT_EXTEND) {
$output = ['sla_excluded_downtimeid', 'slaid', 'name', 'period_from', 'period_to'];
}
else {
$output = array_unique(array_merge(['sla_excluded_downtimeid', 'slaid'],
$options['selectExcludedDowntimes']
));
}
$sql_options = [
'output' => $output,
'filter' => ['slaid' => array_keys($result)],
'sortfield' => ['period_from'],
'sortorder' => [ZBX_SORT_UP]
];
$db_sla_excluded_downtimes = DBselect(DB::makeSql('sla_excluded_downtime', $sql_options));
while ($db_sla_excluded_downtime = DBfetch($db_sla_excluded_downtimes)) {
$slaid = $db_sla_excluded_downtime['slaid'];
unset($db_sla_excluded_downtime['sla_excluded_downtimeid'], $db_sla_excluded_downtime['slaid']);
$result[$slaid]['excluded_downtimes'][] = $db_sla_excluded_downtime;
}
if ($options['selectExcludedDowntimes'] === API_OUTPUT_COUNT) {
foreach ($result as &$row) {
$row['excluded_downtimes'] = (string) count($row['excluded_downtimes']);
}
unset($row);
}
}
/**
* @param array $slas
* @param array|null $db_slas
*
* @throws APIException
*/
private static function checkDuplicates(array $slas, array $db_slas = null): void {
$names = [];
foreach ($slas as $sla) {
if ($db_slas === null
|| (array_key_exists('name', $sla) && $sla['name'] !== $db_slas[$sla['slaid']]['name'])) {
$names[] = $sla['name'];
}
}
if (!$names) {
return;
}
$duplicate = DBfetch(DBselect(
'SELECT sla.name FROM sla WHERE '.dbConditionString('sla.name', $names), 1
));
if ($duplicate) {
self::exception(ZBX_API_ERROR_PARAMETERS, _s('SLA "%1$s" already exists.', $duplicate['name']));
}
}
/**
* @param array $slas
* @param array|null $db_slas
*
* @throws APIException
*/
private static function checkSlo(array $slas, array $db_slas = null): void {
foreach ($slas as $sla) {
$name = $db_slas !== null ? $db_slas[$sla['slaid']]['name'] : $sla['name'];
if (array_key_exists('slo', $sla) && round($sla['slo'], 4) != $sla['slo']) {
self::exception(ZBX_API_ERROR_PARAMETERS,
_s('SLA "%1$s" SLO must have no more than 4 fractional digits.', $name)
);
}
}
}
/**
* @param array $slas
* @param array|null $db_slas
*
* @throws APIException
*/
private static function checkSchedule(array &$slas, array $db_slas = null): void {
foreach ($slas as &$sla) {
if (!array_key_exists('schedule', $sla)) {
continue;
}
$name = $db_slas !== null ? $db_slas[$sla['slaid']]['name'] : $sla['name'];
foreach ($sla['schedule'] as $schedule_row) {
if ($schedule_row['period_from'] >= $schedule_row['period_to']) {
self::exception(ZBX_API_ERROR_PARAMETERS,
_s('Start time must be less than end time for SLA "%1$s".', $name)
);
}
}
$sla['schedule'] = self::normalizeSchedule($sla['schedule']);
}
unset($sla);
}
/**
* @param array $slas
* @param array|null $db_slas
*
* @throws APIException
*/
private static function checkExcludedDowntimes(array $slas, array $db_slas = null): void {
foreach ($slas as $sla) {
if (!array_key_exists('excluded_downtimes', $sla)) {
continue;
}
$name = $db_slas !== null ? $db_slas[$sla['slaid']]['name'] : $sla['name'];
foreach ($sla['excluded_downtimes'] as $excluded_downtime) {
if ($excluded_downtime['period_from'] >= $excluded_downtime['period_to']) {
self::exception(ZBX_API_ERROR_PARAMETERS, _s(
'Start time must be less than end time for excluded downtime "%2$s" of SLA "%1$s".',
$name, $excluded_downtime['name']
));
}
}
}
}
/**
* @param array $slas
* @param array $db_slas
*/
private static function addAffectedObjects(array $slas, array &$db_slas): void {
self::addAffectedServiceTags($slas, $db_slas);
self::addAffectedSchedule($slas, $db_slas);
self::addAffectedExcludedDowntimes($slas, $db_slas);
}
/**
* @param array $slas
* @param array $db_slas
*/
private static function addAffectedServiceTags(array $slas, array &$db_slas): void {
$affected_slaids = [];
foreach ($slas as $sla) {
if (array_key_exists('service_tags', $sla)) {
$affected_slaids[$sla['slaid']] = true;
$db_slas[$sla['slaid']]['service_tags'] = [];
}
}
$sql_options = [
'output' => ['sla_service_tagid', 'slaid', 'tag', 'operator', 'value'],
'filter' => ['slaid' => array_keys($affected_slaids)]
];
$db_service_tags = DBselect(DB::makeSql('sla_service_tag', $sql_options));
while ($db_service_tag = DBfetch($db_service_tags)) {
$slaid = $db_service_tag['slaid'];
unset($db_service_tag['slaid']);
$db_slas[$slaid]['service_tags'][$db_service_tag['sla_service_tagid']] = $db_service_tag;
}
}
/**
* @param array $slas
* @param array $db_slas
*/
private static function addAffectedSchedule(array $slas, array &$db_slas): void {
$affected_slaids = [];
foreach ($slas as $sla) {
if (array_key_exists('schedule', $sla)) {
$affected_slaids[$sla['slaid']] = true;
$db_slas[$sla['slaid']]['schedule'] = [];
}
}
$sql_options = [
'output' => ['sla_scheduleid', 'slaid', 'period_from', 'period_to'],
'filter' => ['slaid' => array_keys($affected_slaids)]
];
$db_schedule = DBselect(DB::makeSql('sla_schedule', $sql_options));
while ($db_schedule_row = DBfetch($db_schedule)) {
$slaid = $db_schedule_row['slaid'];
unset($db_schedule_row['slaid']);
$db_slas[$slaid]['schedule'][$db_schedule_row['sla_scheduleid']] = $db_schedule_row;
}
}
/**
* @param array $slas
* @param array $db_slas
*/
private static function addAffectedExcludedDowntimes(array $slas, array &$db_slas): void {
$affected_slaids = [];
foreach ($slas as $sla) {
if (array_key_exists('excluded_downtimes', $sla)) {
$affected_slaids[$sla['slaid']] = true;
$db_slas[$sla['slaid']]['excluded_downtimes'] = [];
}
}
$sql_options = [
'output' => ['sla_excluded_downtimeid', 'slaid', 'name', 'period_from', 'period_to'],
'filter' => ['slaid' => array_keys($affected_slaids)]
];
$db_excluded_downtimes = DBselect(DB::makeSql('sla_excluded_downtime', $sql_options));
while ($db_excluded_downtime = DBfetch($db_excluded_downtimes)) {
$slaid = $db_excluded_downtime['slaid'];
unset($db_excluded_downtime['slaid']);
$db_slas[$slaid]['excluded_downtimes'][$db_excluded_downtime['sla_excluded_downtimeid']] =
$db_excluded_downtime;
}
}
/**
* @param array $slas
* @param array|null $db_slas
*/
private static function updateServiceTags(array &$slas, array $db_slas = null): void {
$ins_service_tags = [];
$del_service_tags = [];
foreach ($slas as &$sla) {
if (!array_key_exists('service_tags', $sla)) {
continue;
}
$db_service_tags = [];
if ($db_slas !== null) {
foreach ($db_slas[$sla['slaid']]['service_tags'] as $db_service_tag) {
$db_service_tags[$db_service_tag['tag']][$db_service_tag['operator']][$db_service_tag['value']] =
$db_service_tag['sla_service_tagid'];
$del_service_tags[$db_service_tag['sla_service_tagid']] = true;
}
}
foreach ($sla['service_tags'] as &$service_tag) {
if (array_key_exists($service_tag['tag'], $db_service_tags)
&& array_key_exists($service_tag['operator'], $db_service_tags[$service_tag['tag']])
&& array_key_exists($service_tag['value'],
$db_service_tags[$service_tag['tag']][$service_tag['operator']]
)) {
$service_tag['sla_service_tagid'] =
$db_service_tags[$service_tag['tag']][$service_tag['operator']][$service_tag['value']];
unset($del_service_tags[$service_tag['sla_service_tagid']]);
}
else {
$ins_service_tags[] = ['slaid' => $sla['slaid']] + $service_tag;
}
}
unset($service_tag);
}
unset($sla);
if ($del_service_tags) {
DB::delete('sla_service_tag', ['sla_service_tagid' => array_keys($del_service_tags)]);
}
if ($ins_service_tags) {
$sla_service_tagids = DB::insert('sla_service_tag', $ins_service_tags);
$sla_service_tagids_index = 0;
foreach ($slas as &$sla) {
if (!array_key_exists('service_tags', $sla)) {
continue;
}
foreach ($sla['service_tags'] as &$service_tag) {
if (array_key_exists('sla_service_tagid', $service_tag)) {
continue;
}
$service_tag['sla_service_tagid'] = $sla_service_tagids[$sla_service_tagids_index];
$sla_service_tagids_index++;
}
unset($service_tag);
}
unset($sla);
}
}
/**
* @param array $slas
* @param array|null $db_slas
*/
private static function updateSchedule(array &$slas, array $db_slas = null): void {
$ins_schedule = [];
$del_schedule = [];
foreach ($slas as &$sla) {
if (!array_key_exists('schedule', $sla)) {
continue;
}
$db_schedule = [];
if ($db_slas !== null) {
foreach ($db_slas[$sla['slaid']]['schedule'] as $db_schedule_row) {
$db_schedule[$db_schedule_row['period_from']][$db_schedule_row['period_to']] =
$db_schedule_row['sla_scheduleid'];
$del_schedule[$db_schedule_row['sla_scheduleid']] = true;
}
}
foreach ($sla['schedule'] as &$schedule_row) {
if (array_key_exists($schedule_row['period_from'], $db_schedule)
&& array_key_exists($schedule_row['period_to'], $db_schedule[$schedule_row['period_from']])) {
$schedule_row['sla_scheduleid'] =
$db_schedule[$schedule_row['period_from']][$schedule_row['period_to']];
unset($del_schedule[$schedule_row['sla_scheduleid']]);
}
else {
$ins_schedule[] = ['slaid' => $sla['slaid']] + $schedule_row;
}
}
unset($schedule_row);
}
unset($sla);
if ($del_schedule) {
DB::delete('sla_schedule', ['sla_scheduleid' => array_keys($del_schedule)]);
}
if ($ins_schedule) {
$sla_scheduleids = DB::insert('sla_schedule', $ins_schedule);
$sla_scheduleids_index = 0;
foreach ($slas as &$sla) {
if (!array_key_exists('schedule', $sla)) {
continue;
}
foreach ($sla['schedule'] as &$schedule_row) {
if (array_key_exists('sla_scheduleid', $schedule_row)) {
continue;
}
$schedule_row['sla_scheduleid'] = $sla_scheduleids[$sla_scheduleids_index];
$sla_scheduleids_index++;
}
unset($schedule_row);
}
unset($sla);
}
}
/**
* @param array $slas
* @param array|null $db_slas
*/
private static function updateExcludedDowntimes(array &$slas, array $db_slas = null): void {
$ins_excluded_downtimes = [];
$upd_excluded_downtimes = [];
$del_excluded_downtimes = [];
foreach ($slas as &$sla) {
if (!array_key_exists('excluded_downtimes', $sla)) {
continue;
}
$db_excluded_downtimes = [];
if ($db_slas !== null) {
foreach ($db_slas[$sla['slaid']]['excluded_downtimes'] as $db_excluded_downtime) {
$db_excluded_downtimes[$db_excluded_downtime['period_from']][$db_excluded_downtime['period_to']] =
$db_excluded_downtime;
$del_excluded_downtimes[$db_excluded_downtime['sla_excluded_downtimeid']] = true;
}
}
foreach ($sla['excluded_downtimes'] as &$excluded_downtime) {
if (array_key_exists($excluded_downtime['period_from'], $db_excluded_downtimes)
&& array_key_exists($excluded_downtime['period_to'],
$db_excluded_downtimes[$excluded_downtime['period_from']]
)) {
$excluded_downtime['sla_excluded_downtimeid'] = $db_excluded_downtimes
[$excluded_downtime['period_from']][$excluded_downtime['period_to']]['sla_excluded_downtimeid'];
unset($del_excluded_downtimes[$excluded_downtime['sla_excluded_downtimeid']]);
$upd_excluded_downtime = DB::getUpdatedValues('sla_excluded_downtime', $excluded_downtime,
$db_excluded_downtimes[$excluded_downtime['period_from']][$excluded_downtime['period_to']]
);
if ($upd_excluded_downtime) {
$upd_excluded_downtimes[] = [
'values' => $upd_excluded_downtime,
'where' => ['sla_excluded_downtimeid' => $excluded_downtime['sla_excluded_downtimeid']]
];
}
}
else {
$ins_excluded_downtimes[] = ['slaid' => $sla['slaid']] + $excluded_downtime;
}
}
unset($excluded_downtime);
}
unset($sla);
if ($del_excluded_downtimes) {
DB::delete('sla_excluded_downtime', ['sla_excluded_downtimeid' => array_keys($del_excluded_downtimes)]);
}
if ($ins_excluded_downtimes) {
$sla_excluded_downtimeids = DB::insert('sla_excluded_downtime', $ins_excluded_downtimes);
$sla_excluded_downtimeids_index = 0;
foreach ($slas as &$sla) {
if (!array_key_exists('excluded_downtimes', $sla)) {
continue;
}
foreach ($sla['excluded_downtimes'] as &$excluded_downtime) {
if (array_key_exists('sla_excluded_downtimeid', $excluded_downtime)) {
continue;
}
$excluded_downtime['sla_excluded_downtimeid'] =
$sla_excluded_downtimeids[$sla_excluded_downtimeids_index];
$sla_excluded_downtimeids_index++;
}
unset($excluded_downtime);
}
unset($sla);
}
if ($upd_excluded_downtimes) {
DB::update('sla_excluded_downtime', $upd_excluded_downtimes);
}
}
/**
* Concatenate overlapping periods and sort by starting time of periods.
* Full weekly period is returned as empty array.
*
* @param array $schedule
*
* @return array
*/
private static function normalizeSchedule(array $schedule): array {
$converted_schedule = [];
foreach ($schedule as $schedule_row) {
$period_from = $schedule_row['period_from'];
$period_to = $schedule_row['period_to'];
foreach ($converted_schedule as $converted_schedule_row) {
$is_overlapping = $schedule_row['period_from'] <= $converted_schedule_row['period_to']
&& $schedule_row['period_to'] >= $converted_schedule_row['period_from'];
if ($is_overlapping) {
$period_from = min($period_from, $converted_schedule_row['period_from']);
$period_to = max($period_to, $converted_schedule_row['period_to']);
}
}
foreach ($converted_schedule as $index => $converted_schedule_row) {
if ($converted_schedule_row['period_from'] >= $period_from
&& $converted_schedule_row['period_to'] <= $period_to) {
unset($converted_schedule[$index]);
}
}
$converted_schedule[] = ['period_from' => $period_from, 'period_to' => $period_to];
}
usort($converted_schedule,
static function (array $schedule_row_a, array $schedule_row_b): int {
return $schedule_row_a['period_from'] <=> $schedule_row_b['period_from'];
}
);
if (count($converted_schedule) == 1 && $converted_schedule[0]['period_from'] == 0
&& $converted_schedule[0]['period_to'] == SEC_PER_WEEK) {
return [];
}
return $converted_schedule;
}
/**
* @param array|null $limit_slaids
* @param array|null $limit_serviceids
*
* @throws APIException
*
* @return array|null
*/
private static function getAccessibleSlaids(?array $limit_slaids, ?array $limit_serviceids): ?array {
$role = API::Role()->get([
'output' => [],
'selectRules' => ['services.read.mode', 'services.write.mode', 'actions'],
'roleids' => self::$userData['roleid']
]);
if (!$role) {
return [];
}
if ($limit_serviceids === null) {
$rules = $role[0]['rules'];
$manage_sla_status = 0;
foreach ($rules['actions'] as $action) {
if ($action['name'] === 'manage_sla') {
$manage_sla_status = $action['status'];
break;
}
}
if ($rules['services.read.mode'] == ZBX_ROLE_RULE_SERVICES_ACCESS_ALL
|| $rules['services.write.mode'] == ZBX_ROLE_RULE_SERVICES_ACCESS_ALL
|| $manage_sla_status == 1) {
return null;
}
}
$accessible_services = API::Service()->get([
'output' => [],
'serviceids' => $limit_serviceids,
'preservekeys' => true
]);
if (!$accessible_services) {
return [];
}
$services_slas_resource_sql = 'SELECT DISTINCT st.serviceid,sst.slaid'.
' FROM service_tag st, sla_service_tag sst'.
' WHERE sst.tag=st.tag'.
' AND ('.
'(sst.operator='.ZBX_SLA_SERVICE_TAG_OPERATOR_EQUAL.' AND st.value=sst.value)'.
' OR (sst.operator='.ZBX_SLA_SERVICE_TAG_OPERATOR_LIKE." AND UPPER(st.value) LIKE CONCAT('%', ".
"CONCAT(REPLACE(REPLACE(REPLACE(UPPER(sst.value), '%', '!%'), '_', '!_'), '!', '!!'), '%')".
") ESCAPE '!')".
')'.
($limit_slaids !== null ? ' AND '.dbConditionId('sst.slaid', $limit_slaids) : '').
($limit_serviceids !== null ? ' AND '.dbConditionId('st.serviceid', $limit_serviceids) : '');
$services_slas_resource = DBSelect($services_slas_resource_sql);
$accessible_slaids = [];
while ($db_service_sla = DBfetch($services_slas_resource)) {
if (array_key_exists($db_service_sla['serviceid'], $accessible_services)) {
$accessible_slaids[$db_service_sla['slaid']] = true;
}
}
return $accessible_slaids;
}
/**
* @param array $options
*
* @throws Exception
* @throws APIException
*
* @return array
*/
public function getSli(array $options = []): array {
$api_input_rules = ['type' => API_OBJECT, 'fields' => [
'slaid' => ['type' => API_ID, 'flags' => API_REQUIRED],
'period_from' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '0:'.ZBX_MAX_DATE, 'default' => null],
'period_to' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '0:'.ZBX_MAX_DATE, 'default' => null],
'periods' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_SLA_MAX_REPORTING_PERIODS, 'default' => null],
'serviceids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null]
]];
if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
$db_sla = $this->get([
'output' => ['period', 'slo', 'timezone', 'effective_date'],
'selectSchedule' => ['period_from', 'period_to'],
'selectExcludedDowntimes' => ['name', 'period_from', 'period_to'],
'slaids' => $options['slaid']
]);
if (!$db_sla) {
self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
}
$db_sla = $db_sla[0];
$reporting_periods = self::getReportingPeriods($db_sla, $options);
if ($reporting_periods) {
$db_services = API::Service()->get([
'output' => ['created_at'],
'selectStatusTimeline' => $reporting_periods,
'slaids' => $options['slaid'],
'serviceids' => $options['serviceids'],
'preservekeys' => true
]);
}
else {
$db_services = [];
}
return [
'periods' => $reporting_periods,
'serviceids' => array_keys($db_services),
'sli' => self::calculateSli($db_sla, $reporting_periods, array_values($db_services))
];
}
/**
* @param array $sla
* @param array $options
*
* @throws Exception
*
* @return array
*/
private static function getReportingPeriods(array $sla, array $options): array {
$interval = new DateInterval([
ZBX_SLA_PERIOD_DAILY => 'P1D',
ZBX_SLA_PERIOD_WEEKLY => 'P1W',
ZBX_SLA_PERIOD_MONTHLY => 'P1M',
ZBX_SLA_PERIOD_QUARTERLY => 'P3M',
ZBX_SLA_PERIOD_ANNUALLY => 'P1Y'
][$sla['period']]);
$timezone = new DateTimeZone($sla['timezone'] !== ZBX_DEFAULT_TIMEZONE
? $sla['timezone']
: CTimezoneHelper::getSystemTimezone()
);
$effective_min = (new DateTime('@'.$sla['effective_date']))->setTimezone($timezone);
self::alignDateToPeriodStart($effective_min, (int) $sla['period']);
$effective_max = (new DateTime('now'))->setTimezone($timezone);
self::alignDateToPeriodStart($effective_max, (int) $sla['period']);
$effective_max->add($interval);
if ($options['period_from'] !== null) {
$period_from = (new DateTime('@'.$options['period_from']))->setTimezone($timezone);
self::alignDateToPeriodStart($period_from, (int) $sla['period']);
if ($period_from->getTimestamp() < 0) {
$period_from->add($interval);
}
}
else {
$period_from = null;
}
if ($options['period_to'] !== null) {
$period_to = (new DateTime('@'.$options['period_to']))->setTimezone($timezone);
self::alignDateToPeriodStart($period_to, (int) $sla['period']);
$period_to->add($interval);
if ($period_to->getTimestamp() > ZBX_MAX_DATE) {
$period_to->sub($interval);
}
}
elseif ($period_from === null) {
$period_to = $effective_max;
}
else {
$period_to = null;
}
$reporting_periods = [];
$do_descend = $period_to !== null;
$date = $do_descend ? clone $period_to : clone $period_from;
while (count($reporting_periods) < ZBX_SLA_MAX_REPORTING_PERIODS) {
if ($options['periods'] !== null) {
if (count($reporting_periods) == $options['periods']) {
break;
}
}
else {
if (($period_from === null || $period_to === null)
&& count($reporting_periods) == ZBX_SLA_DEFAULT_REPORTING_PERIODS) {
break;
}
if ($do_descend) {
if ($period_from === null && $date <= $effective_min) {
break;
}
}
elseif ($date >= $effective_max) {
break;
}
}
if ($do_descend && $period_from !== null && $date <= $period_from) {
break;
}
if ($do_descend) {
$to = $date->getTimestamp();
$date->sub($interval);
$from = $date->getTimestamp();
if ($from < 0) {
break;
}
array_unshift($reporting_periods, ['period_from' => $from, 'period_to' => $to]);
}
else {
$from = $date->getTimestamp();
$date->add($interval);
$to = $date->getTimestamp();
if ($to > ZBX_MAX_DATE) {
break;
}
$reporting_periods[] = ['period_from' => $from, 'period_to' => $to];
}
}
return $reporting_periods;
}
/**
* @param DateTime $date
*
* @param int $sla_period
*/
private static function alignDateToPeriodStart(DateTime $date, int $sla_period): void {
$year = (int) $date->format('Y');
$month = (int) $date->format('n');
switch ($sla_period) {
case ZBX_SLA_PERIOD_WEEKLY:
$date
->modify('1 day')
->modify('last Sunday');
break;
case ZBX_SLA_PERIOD_MONTHLY:
$date->setDate($year, $month, 1);
break;
case ZBX_SLA_PERIOD_QUARTERLY:
$date->setDate($year, intdiv($month - 1, 3) * 3 + 1, 1);
break;
case ZBX_SLA_PERIOD_ANNUALLY:
$date->setDate($year, 1, 1);
break;
}
$date->setTime(0, 0);
}
/**
* @param array $db_sla
* @param array $reporting_periods
* @param array $db_services
*
* @throws Exception
*
* @return array
*/
private static function calculateSli(array $db_sla, array $reporting_periods, array $db_services): array {
if (!$reporting_periods || !$db_services) {
return [];
}
$sli = [];
$combined_excluded_downtimes = self::combineExcludedDowntimes($db_sla['excluded_downtimes']);
foreach ($reporting_periods as $reporting_period_index => $reporting_period) {
$scheduled_uptime_periods = self::getScheduledUptimePeriods($db_sla, $reporting_period);
foreach ($db_services as $service_index => $db_service) {
$cell = [
'uptime' => 0,
'downtime' => 0,
'sli' => -1.0,
'error_budget' => 0,
'excluded_downtimes' => []
];
$max_uptime = 0;
foreach ($scheduled_uptime_periods as $scheduled_uptime_period) {
$uptime_period_from = max($db_service['created_at'], $scheduled_uptime_period['period_from']);
$uptime_period_to = $scheduled_uptime_period['period_to'];
$uptime = $uptime_period_to - $uptime_period_from;
if ($uptime <= 0) {
continue;
}
$max_uptime += $uptime;
foreach ($combined_excluded_downtimes as $combined_excluded_downtime) {
$downtime = min($combined_excluded_downtime['period_to'], $uptime_period_to)
- max($combined_excluded_downtime['period_from'], $uptime_period_from);
if ($downtime > 0) {
$max_uptime -= $downtime;
}
}
}
$last_excluded_downtimes = [];
$prev_clock = $reporting_period['period_from'];
$prev_value = $db_service['status_timeline'][$reporting_period_index]['start_value'];
$alarms = $db_service['status_timeline'][$reporting_period_index]['alarms'];
if (!$alarms || $alarms[count($alarms) - 1]['clock'] <= time()) {
$alarms[] = ['clock' => time() + 1, 'value' => null];
}
foreach ($alarms as $alarm) {
foreach ($scheduled_uptime_periods as $scheduled_uptime_period) {
$uptime_period_from = max($db_service['created_at'], $scheduled_uptime_period['period_from'],
$prev_clock
);
$uptime_period_to = min($scheduled_uptime_period['period_to'], $alarm['clock']);
$uptime = $uptime_period_to - $uptime_period_from;
if ($uptime <= 0) {
continue;
}
foreach ($combined_excluded_downtimes as $combined_excluded_downtime) {
$downtime = min($combined_excluded_downtime['period_to'], $uptime_period_to)
- max($combined_excluded_downtime['period_from'], $uptime_period_from);
if ($downtime > 0) {
$uptime -= $downtime;
}
}
if ($prev_value == ZBX_SEVERITY_OK) {
$cell['uptime'] += $uptime;
}
else {
$cell['downtime'] += $uptime;
}
foreach ($db_sla['excluded_downtimes'] as $index => $excluded_downtime) {
$downtime_period_from = max($excluded_downtime['period_from'], $uptime_period_from);
$downtime_period_to = min($excluded_downtime['period_to'], $uptime_period_to);
if ($downtime_period_to > $downtime_period_from) {
if (array_key_exists($index, $last_excluded_downtimes)) {
$cell['excluded_downtimes'][$last_excluded_downtimes[$index]['cell']]['period_to'] =
(int) $downtime_period_to;
}
else {
$cell['excluded_downtimes'][] = [
'name' => $excluded_downtime['name'],
'period_from' => (int) $downtime_period_from,
'period_to' => (int) $downtime_period_to
];
}
$last_excluded_downtimes[$index] = [
'cell' => count($cell['excluded_downtimes']) - 1,
'period_to' => $downtime_period_to
];
}
else {
unset($last_excluded_downtimes[$index]);
}
}
}
$prev_clock = $alarm['clock'];
$prev_value = $alarm['value'];
}
if ($cell['uptime'] + $cell['downtime'] != 0) {
$cell['sli'] = $cell['uptime'] / ($cell['uptime'] + $cell['downtime']) * 100;
}
if ($cell['sli'] != -1) {
$available_uptime = $max_uptime - $cell['uptime'] - $cell['downtime'];
$cell['error_budget'] = $db_sla['slo'] > 0
? min($available_uptime,
(int) ($cell['uptime'] / $db_sla['slo'] * 100) - $cell['uptime'] - $cell['downtime']
)
: $available_uptime;
}
$sli[$reporting_period_index][$service_index] = $cell;
}
}
return $sli;
}
/**
* @param array $excluded_downtimes
*
* @return array
*/
private static function combineExcludedDowntimes(array $excluded_downtimes): array {
$combined_excluded_downtimes = [];
foreach ($excluded_downtimes as $excluded_downtime) {
$period_from = $excluded_downtime['period_from'];
$period_to = $excluded_downtime['period_to'];
foreach ($combined_excluded_downtimes as $combined_excluded_downtime) {
$is_overlapping = $excluded_downtime['period_from'] <= $combined_excluded_downtime['period_to']
&& $excluded_downtime['period_to'] >= $combined_excluded_downtime['period_from'];
if ($is_overlapping) {
$period_from = min($period_from, $combined_excluded_downtime['period_from']);
$period_to = max($period_to, $combined_excluded_downtime['period_to']);
}
}
foreach ($combined_excluded_downtimes as $index => $combined_excluded_downtime) {
if ($combined_excluded_downtime['period_from'] >= $period_from
&& $combined_excluded_downtime['period_to'] <= $period_to) {
unset($combined_excluded_downtimes[$index]);
}
}
$combined_excluded_downtimes[] = ['period_from' => $period_from, 'period_to' => $period_to];
}
return $combined_excluded_downtimes;
}
/**
* @param array $db_sla
* @param array $reporting_period
*
* @throws Exception
*
* @return array
*/
private static function getScheduledUptimePeriods(array $db_sla, array $reporting_period): array {
if (!$db_sla['schedule']) {
return [$reporting_period];
}
$uptime_periods = [];
$reporting_period_from =
(new DateTimeImmutable('@'.$reporting_period['period_from']))
->setTimezone(new DateTimeZone($db_sla['timezone'] !== ZBX_DEFAULT_TIMEZONE
? $db_sla['timezone']
: CTimezoneHelper::getSystemTimezone()
))
->modify('1 day')
->modify('last Sunday');
for ($week = 0;; $week++) {
$week_period_from = $reporting_period_from->modify($week.' week');
foreach ($db_sla['schedule'] as $schedule_row) {
$period_from = $week_period_from
->modify((int) ($schedule_row['period_from'] / SEC_PER_DAY).' day')
->setTime(
(int) ($schedule_row['period_from'] / SEC_PER_HOUR) % 24,
(int) ($schedule_row['period_from'] / SEC_PER_MIN) % 60,
$schedule_row['period_from'] % 60
)
->getTimestamp();
$period_to = $week_period_from
->modify((int) ($schedule_row['period_to'] / SEC_PER_DAY).' day')
->setTime(
(int) ($schedule_row['period_to'] / SEC_PER_HOUR) % 24,
(int) ($schedule_row['period_to'] / SEC_PER_MIN) % 60,
$schedule_row['period_to'] % 60
)
->getTimestamp();
if ($period_from < $reporting_period['period_to'] && $period_to > $reporting_period['period_from']) {
$new_period_from = max($reporting_period['period_from'], $period_from);
$new_period_to = min($reporting_period['period_to'], $period_to);
if ($uptime_periods
&& $uptime_periods[count($uptime_periods) - 1]['period_to'] == $new_period_from) {
$uptime_periods[count($uptime_periods) - 1]['period_to'] = $new_period_to;
}
else {
$uptime_periods[] = ['period_from' => $new_period_from, 'period_to' => $new_period_to];
}
}
if ($period_to >= $reporting_period['period_to']) {
break 2;
}
}
}
return $uptime_periods;
}
}