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 histories. 24 */ 25class CHistory extends CApiService { 26 27 public const ACCESS_RULES = [ 28 'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER] 29 ]; 30 31 protected $tableName; 32 protected $tableAlias = 'h'; 33 protected $sortColumns = ['itemid', 'clock']; 34 35 public function __construct() { 36 // considering the quirky nature of the history API, 37 // the parent::__construct() method should not be called. 38 } 39 40 /** 41 * Get history data. 42 * 43 * @param array $options 44 * @param int $options['history'] History object type to return. 45 * @param array $options['hostids'] Return only history from the given hosts. 46 * @param array $options['itemids'] Return only history from the given items. 47 * @param int $options['time_from'] Return only values that have been received after or at 48 * the given time. 49 * @param int $options['time_till'] Return only values that have been received before or at 50 * the given time. 51 * @param array $options['filter'] Return only those results that exactly match the given 52 * filter. 53 * @param int $options['filter']['itemid'] 54 * @param int $options['filter']['clock'] 55 * @param mixed $options['filter']['value'] 56 * @param int $options['filter']['ns'] 57 * @param array $options['search'] Return results that match the given wildcard search 58 * (case-insensitive). 59 * @param string $options['search']['value'] 60 * @param bool $options['searchByAny'] If set to true return results that match any of the 61 * criteria given in the filter or search parameter instead 62 * of all of them. 63 * @param bool $options['startSearch'] Return results that match the given wildcard search 64 * (case-insensitive). 65 * @param bool $options['excludeSearch'] Return results that do not match the criteria given in 66 * the search parameter. 67 * @param bool $options['searchWildcardsEnabled'] If set to true enables the use of "*" as a wildcard 68 * character in the search parameter. 69 * @param array $options['output'] Object properties to be returned. 70 * @param bool $options['countOutput'] Return the number of records in the result instead of the 71 * actual data. 72 * @param array $options['sortfield'] Sort the result by the given properties. Refer to a 73 * specific API get method description for a list of 74 * properties that can be used for sorting. Macros are not 75 * expanded before sorting. 76 * @param array $options['sortorder'] Order of sorting. If an array is passed, each value will 77 * be matched to the corresponding property given in the 78 * sortfield parameter. 79 * @param int $options['limit'] Limit the number of records returned. 80 * @param bool $options['editable'] If set to true return only objects that the user has 81 * write permissions to. 82 * 83 * @throws Exception 84 * @return array|int Data array or number of rows. 85 */ 86 public function get($options = []) { 87 $value_types = [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_UINT64, 88 ITEM_VALUE_TYPE_TEXT 89 ]; 90 $common_value_types = [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_UINT64, 91 ITEM_VALUE_TYPE_TEXT 92 ]; 93 94 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 95 // filter 96 'history' => ['type' => API_INT32, 'in' => implode(',', $value_types), 'default' => ITEM_VALUE_TYPE_UINT64], 97 'hostids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 98 'itemids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 99 'time_from' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'default' => null], 100 'time_till' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'default' => null], 101 'filter' => ['type' => API_MULTIPLE, 'default' => null, 'rules' => [ 102 ['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_LOG])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [ 103 'itemid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 104 'clock' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 105 'timestamp' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 106 'source' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 107 'severity' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 108 'logeventid' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 109 'ns' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE] 110 ]], 111 ['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_TEXT])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [ 112 'itemid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 113 'clock' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 114 'ns' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE] 115 ]], 116 ['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_UINT64])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [ 117 'itemid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 118 'clock' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 119 'ns' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 120 'value' => ['type' => API_UINTS64, 'flags' => API_ALLOW_NULL | API_NORMALIZE] 121 ]], 122 ['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [ 123 'itemid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 124 'clock' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 125 'ns' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 126 'value' => ['type' => API_FLOATS, 'flags' => API_ALLOW_NULL | API_NORMALIZE] 127 ]] 128 ]], 129 'search' => ['type' => API_MULTIPLE, 'default' => null, 'rules' => [ 130 ['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_LOG])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [ 131 'source' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 132 'value' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE] 133 ]], 134 ['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_TEXT])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => [ 135 'value' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE] 136 ]], 137 ['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_UINT64])], 'type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'fields' => []] 138 ]], 139 'searchByAny' => ['type' => API_BOOLEAN, 'default' => false], 140 'startSearch' => ['type' => API_FLAG, 'default' => false], 141 'excludeSearch' => ['type' => API_FLAG, 'default' => false], 142 'searchWildcardsEnabled' => ['type' => API_BOOLEAN, 'default' => false], 143 // output 144 'output' => ['type' => API_MULTIPLE, 'default' => API_OUTPUT_EXTEND, 'rules' => [ 145 ['if' => ['field' => 'history', 'in' => implode(',', [ITEM_VALUE_TYPE_LOG])], 'type' => API_OUTPUT, 'in' => implode(',', ['itemid', 'clock', 'timestamp', 'source', 'severity', 'value', 'logeventid', 'ns'])], 146 ['if' => ['field' => 'history', 'in' => implode(',', $common_value_types)], 'type' => API_OUTPUT, 'in' => implode(',', ['itemid', 'clock', 'value', 'ns'])] 147 ]], 148 'countOutput' => ['type' => API_FLAG, 'default' => false], 149 // sort and limit 150 'sortfield' => ['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', $this->sortColumns), 'uniq' => true, 'default' => []], 151 'sortorder' => ['type' => API_SORTORDER, 'default' => []], 152 'limit' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_MAX_INT32, 'default' => null], 153 // flags 154 'editable' => ['type' => API_BOOLEAN, 'default' => false] 155 ]]; 156 157 if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) { 158 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 159 } 160 161 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN || $options['hostids'] !== null) { 162 $items = API::Item()->get([ 163 'output' => ['itemid'], 164 'itemids' => $options['itemids'], 165 'hostids' => $options['hostids'], 166 'editable' => $options['editable'], 167 'webitems' => true, 168 'preservekeys' => true 169 ]); 170 $options['itemids'] = array_keys($items); 171 } 172 173 $this->tableName = CHistoryManager::getTableName($options['history']); 174 175 switch (CHistoryManager::getDataSourceType($options['history'])) { 176 case ZBX_HISTORY_SOURCE_ELASTIC: 177 return $this->getFromElasticsearch($options); 178 179 default: 180 return $this->getFromSql($options); 181 } 182 } 183 184 /** 185 * SQL specific implementation of get. 186 * 187 * @see CHistory::get 188 */ 189 private function getFromSql($options) { 190 $result = []; 191 $sql_parts = [ 192 'select' => ['history' => 'h.itemid'], 193 'from' => [ 194 'history' => $this->tableName.' h' 195 ], 196 'where' => [], 197 'group' => [], 198 'order' => [] 199 ]; 200 201 // itemids 202 if ($options['itemids'] !== null) { 203 $sql_parts['where']['itemid'] = dbConditionInt('h.itemid', $options['itemids']); 204 } 205 206 // time_from 207 if ($options['time_from'] !== null) { 208 $sql_parts['where']['clock_from'] = 'h.clock>='.$options['time_from']; 209 } 210 211 // time_till 212 if ($options['time_till'] !== null) { 213 $sql_parts['where']['clock_till'] = 'h.clock<='.$options['time_till']; 214 } 215 216 // filter 217 if ($options['filter'] !== null) { 218 $this->dbFilter($sql_parts['from']['history'], $options, $sql_parts); 219 } 220 221 // search 222 if ($options['search'] !== null) { 223 zbx_db_search($sql_parts['from']['history'], $options, $sql_parts); 224 } 225 226 $sql_parts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts); 227 $sql_parts = $this->applyQuerySortOptions($this->tableName, $this->tableAlias(), $options, $sql_parts); 228 229 $db_res = DBselect(self::createSelectQueryFromParts($sql_parts), $options['limit']); 230 231 while ($data = DBfetch($db_res)) { 232 if ($options['countOutput']) { 233 $result = $data['rowscount']; 234 } 235 else { 236 $result[] = $data; 237 } 238 } 239 240 return $result; 241 } 242 243 /** 244 * Elasticsearch specific implementation of get. 245 * 246 * @see CHistory::get 247 */ 248 private function getFromElasticsearch($options) { 249 $query = []; 250 $schema = DB::getSchema($this->tableName); 251 252 // itemids 253 if ($options['itemids'] !== null) { 254 $query['query']['bool']['must'][] = [ 255 'terms' => [ 256 'itemid' => $options['itemids'] 257 ] 258 ]; 259 } 260 261 // time_from 262 if ($options['time_from'] !== null) { 263 $query['query']['bool']['must'][] = [ 264 'range' => [ 265 'clock' => [ 266 'gte' => $options['time_from'] 267 ] 268 ] 269 ]; 270 } 271 272 // time_till 273 if ($options['time_till'] !== null) { 274 $query['query']['bool']['must'][] = [ 275 'range' => [ 276 'clock' => [ 277 'lte' => $options['time_till'] 278 ] 279 ] 280 ]; 281 } 282 283 // filter 284 if ($options['filter'] !== null) { 285 $query = CElasticsearchHelper::addFilter(DB::getSchema($this->tableName), $query, $options); 286 } 287 288 // search 289 if ($options['search'] !== null) { 290 $query = CElasticsearchHelper::addSearch($schema, $query, $options); 291 } 292 293 // output 294 if ($options['countOutput'] === false && $options['output'] !== API_OUTPUT_EXTEND) { 295 $query['_source'] = $options['output']; 296 } 297 298 // sorting 299 if ($options['sortfield']) { 300 $query = CElasticsearchHelper::addSort($query, $options); 301 } 302 303 // limit 304 if ($options['limit'] !== null) { 305 $query['size'] = $options['limit']; 306 } 307 308 $endpoints = CHistoryManager::getElasticsearchEndpoints($options['history']); 309 if ($endpoints) { 310 return CElasticsearchHelper::query('POST', reset($endpoints), $query); 311 } 312 313 return null; 314 } 315} 316