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 22/** 23 * Class containing methods for operations with tasks. 24 */ 25class CTask extends CApiService { 26 27 public const ACCESS_RULES = [ 28 'get' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 29 'create' => ['min_user_type' => USER_TYPE_SUPER_ADMIN] 30 ]; 31 32 protected $tableName = 'task'; 33 protected $tableAlias = 't'; 34 protected $sortColumns = ['taskid']; 35 36 const RESULT_STATUS_ERROR = -1; 37 /** 38 * Get results of requested ZBX_TM_TASK_DATA task. 39 * 40 * @param array $options 41 * @param string|array $options['output'] 42 * @param string|array $options['taskids'] Task IDs to select data about. 43 * @param bool $options['preservekeys'] Use IDs as keys in the resulting array. 44 * 45 * @return array | boolean 46 */ 47 public function get(array $options): array { 48 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 49 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 50 } 51 52 $output_fields = ['taskid', 'type', 'status', 'clock', 'ttl', 'proxy_hostid', 'request', 'result']; 53 54 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 55 // filter 56 'taskids' => ['type' => API_IDS, 'flags' => API_NORMALIZE | API_ALLOW_NULL, 'default' => null], 57 // output 58 'output' => ['type' => API_OUTPUT, 'in' => implode(',', $output_fields), 'default' => API_OUTPUT_EXTEND], 59 // flags 60 'preservekeys' => ['type' => API_BOOLEAN, 'default' => false] 61 ]]; 62 63 if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) { 64 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 65 } 66 67 $options += [ 68 'sortfield' => 'taskid', 69 'sortorder' => ZBX_SORT_DOWN, 70 'limit' => CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT) 71 ]; 72 73 $sql_parts = [ 74 'select' => ['task' => 't.taskid'], 75 'from' => ['task' => 'task t'], 76 'where' => [ 77 'type' => 't.type='.ZBX_TM_TASK_DATA 78 ], 79 'order' => [], 80 'group' => [] 81 ]; 82 83 if ($options['taskids'] !== null) { 84 $sql_parts['where']['taskid'] = dbConditionInt('t.taskid', $options['taskids']); 85 } 86 87 $db_tasks = []; 88 89 $sql_parts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts); 90 $sql_parts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts); 91 92 $result = DBselect($this->createSelectQueryFromParts($sql_parts), $options['limit']); 93 94 while ($row = DBfetch($result, false)) { 95 if ($this->outputIsRequested('request', $options['output'])) { 96 $row['request'] = json_decode($row['request_data']); 97 unset($row['request_data']); 98 } 99 100 if ($this->outputIsRequested('result', $options['output'])) { 101 if ($row['result_status'] === null) { 102 $row['result'] = null; 103 } 104 else { 105 if ($row['result_status'] == self::RESULT_STATUS_ERROR) { 106 $result_data = $row['result_info']; 107 } 108 else { 109 $result_data = $row['result_info'] ? json_decode($row['result_info']) : []; 110 } 111 112 $row['result'] = [ 113 'data' => $result_data, 114 'status' => $row['result_status'] 115 ]; 116 } 117 118 unset($row['result_info'], $row['result_status']); 119 } 120 121 $db_tasks[$row['taskid']] = $row; 122 } 123 124 if ($db_tasks) { 125 $db_tasks = $this->unsetExtraFields($db_tasks, ['taskid'], $options['output']); 126 127 if (!$options['preservekeys']) { 128 $db_tasks = array_values($db_tasks); 129 } 130 } 131 132 return $db_tasks; 133 } 134 135 /** 136 * Create tasks. 137 * 138 * @param array $tasks Tasks to create. 139 * @param string|array $tasks[]['type'] Type of task. 140 * @param string $tasks[]['request']['itemid'] Must be set for ZBX_TM_TASK_CHECK_NOW task. 141 * @param array $tasks[]['request']['historycache'] (optional) object of history cache data request. 142 * @param array $tasks[]['request']['valuecache'] (optional) object of value cache data request. 143 * @param array $tasks[]['request']['preprocessing'] (optional) object of preprocessing data request. 144 * @param array $tasks[]['request']['alerting'] (optional) object of alerting data request. 145 * @param array $tasks[]['request']['lld'] (optional) object of lld cache data request. 146 * @param array $tasks[]['proxy_hostid'] (optional) Proxy to get diagnostic data about. 147 * 148 * @return array 149 */ 150 public function create(array $tasks): array { 151 $this->validateCreate($tasks); 152 153 $tasks_by_types = [ 154 ZBX_TM_DATA_TYPE_CHECK_NOW => [], 155 ZBX_TM_DATA_TYPE_DIAGINFO => [] 156 ]; 157 158 foreach ($tasks as $index => $task) { 159 $tasks_by_types[$task['type']][$index] = $task; 160 } 161 162 $return = $this->createTasksCheckNow($tasks_by_types[ZBX_TM_DATA_TYPE_CHECK_NOW]); 163 $return += $this->createTasksDiagInfo($tasks_by_types[ZBX_TM_DATA_TYPE_DIAGINFO]); 164 165 ksort($return); 166 167 return ['taskids' => array_values($return)]; 168 } 169 170 /** 171 * Validates the input for create method. 172 * 173 * @param array $tasks Tasks to validate. 174 * 175 * @throws APIException if the input is invalid. 176 */ 177 protected function validateCreate(array &$tasks) { 178 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'fields' => [ 179 'type' => ['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_TM_DATA_TYPE_DIAGINFO, ZBX_TM_DATA_TYPE_CHECK_NOW])], 180 'request' => ['type' => API_MULTIPLE, 'flags' => API_REQUIRED, 'rules' => [ 181 ['if' => ['field' => 'type', 'in' => ZBX_TM_DATA_TYPE_DIAGINFO], 'type' => API_OBJECT, 'fields' => [ 182 'historycache' => ['type' => API_OBJECT, 'fields' => [ 183 'stats' => ['type' => API_OUTPUT, 'in' => implode(',', ['items', 'values', 'memory', 'memory.data', 'memory.index']), 'default' => API_OUTPUT_EXTEND], 184 'top' => ['type' => API_OBJECT, 'fields' => [ 185 'values' => ['type' => API_INT32] 186 ]] 187 ]], 188 'valuecache' => ['type' => API_OBJECT, 'fields' => [ 189 'stats' => ['type' => API_OUTPUT, 'in' => implode(',', ['items', 'values', 'memory', 'mode']), 'default' => API_OUTPUT_EXTEND], 190 'top' => ['type' => API_OBJECT, 'fields' => [ 191 'values' => ['type' => API_INT32], 192 'request.values' => ['type' => API_INT32] 193 ]] 194 ]], 195 'preprocessing' => ['type' => API_OBJECT, 'fields' => [ 196 'stats' => ['type' => API_OUTPUT, 'in' => implode(',', ['values', 'preproc.values']), 'default' => API_OUTPUT_EXTEND], 197 'top' => ['type' => API_OBJECT, 'fields' => [ 198 'values' => ['type' => API_INT32] 199 ]] 200 ]], 201 'alerting' => ['type' => API_OBJECT, 'fields' => [ 202 'stats' => ['type' => API_OUTPUT, 'in' => 'alerts', 'default' => API_OUTPUT_EXTEND], 203 'top' => ['type' => API_OBJECT, 'fields' => [ 204 'media.alerts' => ['type' => API_INT32], 205 'source.alerts' => ['type' => API_INT32] 206 ]] 207 ]], 208 'lld' => ['type' => API_OBJECT, 'fields' => [ 209 'stats' => ['type' => API_OUTPUT, 'in' => implode(',', ['rules', 'values']), 'default' => API_OUTPUT_EXTEND], 210 'top' => ['type' => API_OBJECT, 'fields' => [ 211 'values' => ['type' => API_INT32] 212 ]] 213 ]] 214 ]], 215 ['if' => ['field' => 'type', 'in' => ZBX_TM_DATA_TYPE_CHECK_NOW], 'type' => API_OBJECT, 'fields' => [ 216 'itemid' => ['type' => API_ID, 'flags' => API_REQUIRED | API_NOT_EMPTY] 217 ]] 218 ]], 219 'proxy_hostid' => ['type' => API_ID, 'default' => 0] 220 ]]; 221 222 if (!CApiInputValidator::validate($api_input_rules, $tasks, '/', $error)) { 223 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 224 } 225 226 $min_permissions = USER_TYPE_ZABBIX_ADMIN; 227 $itemids_editable = []; 228 $proxy_hostids = []; 229 230 foreach ($tasks as $task) { 231 switch ($task['type']) { 232 case ZBX_TM_DATA_TYPE_DIAGINFO: 233 $min_permissions = USER_TYPE_SUPER_ADMIN; 234 235 $proxy_hostids[$task['proxy_hostid']] = true; 236 break; 237 238 case ZBX_TM_DATA_TYPE_CHECK_NOW: 239 $itemids_editable[$task['request']['itemid']] = true; 240 break; 241 } 242 } 243 244 unset($proxy_hostids[0]); 245 246 if (self::$userData['type'] < $min_permissions) { 247 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 248 } 249 250 $this->checkProxyHostids(array_keys($proxy_hostids)); 251 $this->checkEditableItems(array_keys($itemids_editable)); 252 } 253 254 /** 255 * Create ZBX_TM_TASK_CHECK_NOW tasks. 256 * 257 * @param array $tasks Request object for tasks to create. 258 * @param string|array $tasks[]['request']['itemid'] Item or LLD rule IDs to create tasks for. 259 * 260 * @throws APIException 261 * 262 * @return array 263 */ 264 protected function createTasksCheckNow(array $tasks): array { 265 if (!$tasks) { 266 return []; 267 } 268 269 $itemids = []; 270 $return = []; 271 272 foreach ($tasks as $index => $task) { 273 $itemids[$index] = $task['request']['itemid']; 274 } 275 276 // Check if tasks for items and LLD rules already exist. 277 $db_tasks = DBselect( 278 'SELECT t.taskid, tcn.itemid'. 279 ' FROM task t, task_check_now tcn'. 280 ' WHERE t.taskid=tcn.taskid'. 281 ' AND t.type='.ZBX_TM_TASK_CHECK_NOW. 282 ' AND t.status='.ZBX_TM_STATUS_NEW. 283 ' AND '.dbConditionId('tcn.itemid', $itemids) 284 ); 285 286 while ($db_task = DBfetch($db_tasks)) { 287 foreach (array_keys($itemids, $db_task['itemid']) as $index) { 288 $return[$index] = $db_task['taskid']; 289 unset($itemids[$index]); 290 } 291 } 292 293 // Create new tasks. 294 if ($itemids) { 295 $taskid = DB::reserveIds('task', count($itemids)); 296 $task_rows = []; 297 $task_check_now_rows = []; 298 $time = time(); 299 300 foreach ($itemids as $index => $itemid) { 301 $task_rows[] = [ 302 'taskid' => $taskid, 303 'type' => ZBX_TM_TASK_CHECK_NOW, 304 'status' => ZBX_TM_STATUS_NEW, 305 'clock' => $time, 306 'ttl' => SEC_PER_HOUR 307 ]; 308 $task_check_now_rows[] = [ 309 'taskid' => $taskid, 310 'itemid' => $itemid, 311 'parent_taskid' => $taskid 312 ]; 313 314 $return[$index] = $taskid; 315 $taskid = bcadd($taskid, 1, 0); 316 } 317 318 DB::insertBatch('task', $task_rows, false); 319 DB::insertBatch('task_check_now', $task_check_now_rows, false); 320 } 321 322 return $return; 323 } 324 325 /** 326 * Create ZBX_TM_DATA_TYPE_DIAGINFO tasks. 327 * 328 * @param array $tasks[] 329 * @param array $tasks[]['request']['historycache'] (optional) object of history cache data request. 330 * @param array $tasks[]['request']['valuecache'] (optional) object of value cache data request. 331 * @param array $tasks[]['request']['preprocessing'] (optional) object of preprocessing data request. 332 * @param array $tasks[]['request']['alerting'] (optional) object of alerting data request. 333 * @param array $tasks[]['request']['lld'] (optional) object of lld cache data request. 334 * @param array $tasks[]['proxy_hostid'] Proxy to get diagnostic data about. 335 * 336 * @throws APIException 337 * 338 * @return array 339 */ 340 protected function createTasksDiagInfo(array $tasks): array { 341 $task_rows = []; 342 $task_data_rows = []; 343 $return = []; 344 $taskid = DB::reserveIds('task', count($tasks)); 345 346 foreach ($tasks as $index => $task) { 347 $task_rows[] = [ 348 'taskid' => $taskid, 349 'type' => ZBX_TM_TASK_DATA, 350 'status' => ZBX_TM_STATUS_NEW, 351 'clock' => time(), 352 'ttl' => SEC_PER_HOUR, 353 'proxy_hostid' => $task['proxy_hostid'] 354 ]; 355 356 $task_data_rows[] = [ 357 'taskid' => $taskid, 358 'type' => $task['type'], 359 'data' => json_encode($task['request']), 360 'parent_taskid' => $taskid 361 ]; 362 363 $return[$index] = $taskid; 364 $taskid = bcadd($taskid, 1, 0); 365 } 366 367 DB::insertBatch('task', $task_rows, false); 368 DB::insertBatch('task_data', $task_data_rows, false); 369 370 return $return; 371 } 372 373 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sql_parts) { 374 $sql_parts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sql_parts); 375 376 if ($this->outputIsRequested('request', $options['output'])) { 377 $sql_parts['left_join'][] = ['alias' => 'req', 'table' => 'task_data', 'using' => 'parent_taskid']; 378 $sql_parts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName()]; 379 380 $sql_parts = $this->addQuerySelect('req.data AS request_data', $sql_parts); 381 } 382 383 if ($this->outputIsRequested('result', $options['output'])) { 384 $sql_parts['left_join'][] = ['alias' => 'resp', 'table' => 'task_result', 'using' => 'parent_taskid']; 385 $sql_parts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName()]; 386 387 $sql_parts = $this->addQuerySelect('resp.info AS result_info', $sql_parts); 388 $sql_parts = $this->addQuerySelect('resp.status AS result_status', $sql_parts); 389 } 390 391 return $sql_parts; 392 } 393 394 /** 395 * Validate user permissions to items and LLD rules; 396 * Check if requested items are allowed to make 'check now' operation; 397 * Check if items are monitored and they belong to monitored hosts. 398 * 399 * @param array $itemids 400 * 401 * @throws Exception 402 */ 403 protected function checkEditableItems(array $itemids): void { 404 if (!$itemids) { 405 return; 406 } 407 408 // Check permissions. 409 $items = API::Item()->get([ 410 'output' => ['name', 'type', 'status', 'flags'], 411 'selectHosts' => ['name', 'status'], 412 'itemids' => $itemids, 413 'editable' => true, 414 'preservekeys' => true 415 ]); 416 417 $itemids_cnt = count($itemids); 418 419 if (count($items) != $itemids_cnt) { 420 $items += API::DiscoveryRule()->get([ 421 'output' => ['name', 'type', 'status', 'flags'], 422 'selectHosts' => ['name', 'status'], 423 'itemids' => $itemids, 424 'editable' => true, 425 'preservekeys' => true 426 ]); 427 428 if (count($items) != $itemids_cnt) { 429 self::exception(ZBX_API_ERROR_PERMISSIONS, 430 _('No permissions to referred object or it does not exist!') 431 ); 432 } 433 } 434 435 // Validate item and LLD rule type and status. 436 $allowed_types = checkNowAllowedTypes(); 437 438 foreach ($items as $item) { 439 if (!in_array($item['type'], $allowed_types)) { 440 self::exception(ZBX_API_ERROR_PARAMETERS, 441 _s('Cannot send request: %1$s.', 442 ($item['flags'] == ZBX_FLAG_DISCOVERY_RULE) 443 ? _('wrong discovery rule type') 444 : _('wrong item type') 445 ) 446 ); 447 } 448 449 if ($item['status'] != ITEM_STATUS_ACTIVE || $item['hosts'][0]['status'] != HOST_STATUS_MONITORED) { 450 $host_name = $item['hosts'][0]['name']; 451 $problem = ($item['flags'] == ZBX_FLAG_DISCOVERY_RULE) 452 ? _s('discovery rule "%1$s" on host "%2$s" is not monitored', $item['name'], $host_name) 453 : _s('item "%1$s" on host "%2$s" is not monitored', $item['name'], $host_name); 454 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Cannot send request: %1$s.', $problem)); 455 } 456 } 457 } 458 459 /** 460 * Function to check if specified proxies exists. 461 * 462 * @param array $proxy_hostids Proxy IDs to check. 463 * 464 * @throws Exception if proxy doesn't exist. 465 */ 466 protected function checkProxyHostids(array $proxy_hostids): void { 467 if (!$proxy_hostids) { 468 return; 469 } 470 471 $proxies = API::Proxy()->get([ 472 'countOutput' => true, 473 'proxyids' => $proxy_hostids 474 ]); 475 476 if ($proxies != count($proxy_hostids)) { 477 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 478 } 479 } 480} 481