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 22class CHostImporter extends CImporter { 23 24 /** 25 * @var array A list of host IDs which were created or updated to create an interface cache for those hosts. 26 */ 27 protected $processedHostIds = []; 28 29 /** 30 * Import hosts. 31 * 32 * @param array $hosts 33 * 34 * @throws Exception 35 */ 36 public function import(array $hosts): void { 37 $hosts_to_create = []; 38 $hosts_to_update = []; 39 $valuemaps = []; 40 $template_linkage = []; 41 $templates_to_clear = []; 42 43 foreach ($hosts as $host) { 44 /* 45 * Save linked templates for 2 purposes: 46 * - save linkages to add in case if 'create new' linkages is checked; 47 * - calculate missing linkages in case if 'delete missing' is checked. 48 */ 49 if (array_key_exists('templates', $host)) { 50 foreach ($host['templates'] as $template) { 51 $templateid = $this->referencer->findTemplateidByHost($template['name']); 52 53 if ($templateid === null) { 54 throw new Exception(_s('Template "%1$s" for host "%2$s" does not exist.', $template['name'], $host['host'])); 55 } 56 57 $template_linkage[$host['host']][] = ['templateid' => $templateid]; 58 } 59 } 60 61 unset($host['templates']); 62 63 $host = $this->resolveHostReferences($host); 64 65 if (array_key_exists('hostid', $host) 66 && ($this->options['hosts']['updateExisting'] || $this->options['process_hosts'])) { 67 $hosts_to_update[] = $host; 68 } 69 elseif ($this->options['hosts']['createMissing']) { 70 if (array_key_exists('hostid', $host)) { 71 throw new Exception(_s('Host "%1$s" already exists.', $host['host'])); 72 } 73 74 $hosts_to_create[] = $host; 75 } 76 77 if (array_key_exists('valuemaps', $host)) { 78 $valuemaps[$host['host']] = $host['valuemaps']; 79 } 80 } 81 82 if ($hosts_to_update) { 83 // Get template linkages to unlink and clear. 84 if ($this->options['templateLinkage']['deleteMissing']) { 85 // Get already linked templates. 86 $db_template_links = API::Host()->get([ 87 'output' => ['hostids'], 88 'selectParentTemplates' => ['hostid'], 89 'hostids' => array_column($hosts_to_update, 'hostid'), 90 'preservekeys' => true 91 ]); 92 93 foreach ($db_template_links as &$db_template_link) { 94 $db_template_link = array_column($db_template_link['parentTemplates'], 'templateid'); 95 } 96 unset($db_template_link); 97 98 foreach ($hosts_to_update as $host) { 99 if (array_key_exists($host['host'], $template_linkage)) { 100 $templates_to_clear[$host['hostid']] = array_diff( 101 $db_template_links[$host['hostid']], 102 array_column($template_linkage[$host['host']], 'templateid') 103 ); 104 } 105 else { 106 $templates_to_clear[$host['hostid']] = $db_template_links[$host['hostid']]; 107 } 108 } 109 } 110 111 if ($this->options['hosts']['updateExisting']) { 112 $hosts_to_update = $this->addInterfaceIds($hosts_to_update); 113 114 API::Host()->update($hosts_to_update); 115 } 116 117 foreach ($hosts_to_update as $host) { 118 $this->processedHostIds[$host['host']] = $host['hostid']; 119 120 // Drop existing template linkages if 'delete missing' selected. 121 if (array_key_exists($host['hostid'], $templates_to_clear) && $templates_to_clear[$host['hostid']]) { 122 API::Host()->massRemove([ 123 'hostids' => [$host['hostid']], 124 'templateids_clear' => $templates_to_clear[$host['hostid']] 125 ]); 126 } 127 128 // Make new template linkages. 129 if ($this->options['templateLinkage']['createMissing'] 130 && array_key_exists($host['host'], $template_linkage)) { 131 API::Template()->massAdd([ 132 'hosts' => $host, 133 'templates' => $template_linkage[$host['host']] 134 ]); 135 } 136 137 $db_valuemaps = API::ValueMap()->get([ 138 'output' => ['valuemapid', 'name'], 139 'hostids' => [$host['hostid']] 140 ]); 141 142 if ($this->options['valueMaps']['createMissing'] && array_key_exists($host['host'], $valuemaps)) { 143 $valuemaps_to_create = []; 144 $valuemap_names = array_column($db_valuemaps, 'name'); 145 146 foreach ($valuemaps[$host['host']] as $valuemap) { 147 if (!in_array($valuemap['name'], $valuemap_names)) { 148 $valuemap['hostid'] = $host['hostid']; 149 $valuemaps_to_create[] = $valuemap; 150 } 151 } 152 153 if ($valuemaps_to_create) { 154 API::ValueMap()->create($valuemaps_to_create); 155 } 156 } 157 158 if ($this->options['valueMaps']['updateExisting'] && array_key_exists($host['host'], $valuemaps)) { 159 $valuemaps_to_update = []; 160 161 foreach ($db_valuemaps as $db_valuemap) { 162 foreach ($valuemaps[$host['host']] as $valuemap) { 163 if ($db_valuemap['name'] === $valuemap['name']) { 164 $valuemap['valuemapid'] = $db_valuemap['valuemapid']; 165 $valuemaps_to_update[] = $valuemap; 166 } 167 } 168 } 169 170 if ($valuemaps_to_update) { 171 API::ValueMap()->update($valuemaps_to_update); 172 } 173 } 174 175 if ($this->options['valueMaps']['deleteMissing'] && $db_valuemaps) { 176 $valuemapids_to_delete = []; 177 178 if (array_key_exists($host['host'], $valuemaps)) { 179 $valuemap_names = array_column($valuemaps[$host['host']], 'name'); 180 181 foreach ($db_valuemaps as $db_valuemap) { 182 if (!in_array($db_valuemap['name'], $valuemap_names)) { 183 $valuemapids_to_delete[] = $db_valuemap['valuemapid']; 184 } 185 } 186 } 187 else { 188 $valuemapids_to_delete = array_column($db_valuemaps, 'valuemapid'); 189 } 190 191 if ($valuemapids_to_delete) { 192 API::ValueMap()->delete($valuemapids_to_delete); 193 } 194 } 195 } 196 } 197 198 if ($this->options['hosts']['createMissing'] && $hosts_to_create) { 199 $created_hosts = API::Host()->create($hosts_to_create); 200 201 foreach ($hosts_to_create as $index => $host) { 202 $hostid = $created_hosts['hostids'][$index]; 203 204 $this->referencer->setDbHost($hostid, $host); 205 $this->processedHostIds[$host['host']] = $hostid; 206 207 if ($this->options['templateLinkage']['createMissing'] 208 && array_key_exists($host['host'], $template_linkage)) { 209 API::Template()->massAdd([ 210 'hosts' => ['hostid' => $hostid], 211 'templates' => $template_linkage[$host['host']] 212 ]); 213 } 214 215 if ($this->options['valueMaps']['createMissing'] && array_key_exists($host['host'], $valuemaps)) { 216 $valuemaps_to_create = []; 217 218 foreach ($valuemaps[$host['host']] as $valuemap) { 219 $valuemap['hostid'] = $hostid; 220 $valuemaps_to_create[] = $valuemap; 221 } 222 223 if ($valuemaps_to_create) { 224 API::ValueMap()->create($valuemaps_to_create); 225 } 226 } 227 } 228 } 229 230 // create interfaces cache interface_ref->interfaceid 231 $db_interfaces = API::HostInterface()->get([ 232 'output' => API_OUTPUT_EXTEND, 233 'hostids' => $this->processedHostIds 234 ]); 235 236 foreach ($hosts as $host) { 237 if (array_key_exists($host['host'], $this->processedHostIds)) { 238 foreach ($host['interfaces'] as $interface) { 239 $hostid = $this->processedHostIds[$host['host']]; 240 241 if (!array_key_exists($hostid, $this->referencer->interfaces_cache)) { 242 $this->referencer->interfaces_cache[$hostid] = []; 243 } 244 245 foreach ($db_interfaces as $db_interface) { 246 if ($db_interface['hostid'] == $hostid 247 && $db_interface['ip'] === $interface['ip'] 248 && $db_interface['dns'] === $interface['dns'] 249 && $db_interface['useip'] == $interface['useip'] 250 && $db_interface['port'] == $interface['port'] 251 && $db_interface['type'] == $interface['type'] 252 && $db_interface['main'] == $interface['main']) { 253 254 // Check SNMP additional fields. 255 if ($db_interface['type'] == INTERFACE_TYPE_SNMP) { 256 // Get fields that we can compare. 257 $array_diff = array_intersect_key($db_interface['details'], $interface['details']); 258 259 foreach (array_keys($array_diff) as $key) { 260 // Check field equality. 261 if ($db_interface['details'][$key] != $interface['details'][$key]) { 262 continue 2; 263 } 264 } 265 } 266 267 $this->referencer->interfaces_cache[$hostid][$interface['interface_ref']] 268 = $db_interface['interfaceid']; 269 } 270 } 271 } 272 } 273 } 274 } 275 276 /** 277 * Get a list of created or updated host IDs. 278 * 279 * @return array 280 */ 281 public function getProcessedHostIds(): array { 282 return $this->processedHostIds; 283 } 284 285 /** 286 * Change all references in host to database ids. 287 * 288 * @param array $host 289 * 290 * @return array 291 * 292 * @throws Exception 293 */ 294 protected function resolveHostReferences(array $host): array { 295 foreach ($host['groups'] as $index => $group) { 296 $groupid = $this->referencer->findGroupidByName($group['name']); 297 298 if ($groupid === null) { 299 throw new Exception(_s('Group "%1$s" for host "%2$s" does not exist.', $group['name'], $host['host'])); 300 } 301 302 $host['groups'][$index] = ['groupid' => $groupid]; 303 } 304 305 if (array_key_exists('proxy', $host)) { 306 if (!$host['proxy']) { 307 $proxyid = 0; 308 } 309 else { 310 $proxyid = $this->referencer->findProxyidByHost($host['proxy']['name']); 311 312 if ($proxyid === null) { 313 throw new Exception(_s('Proxy "%1$s" for host "%2$s" does not exist.', $host['proxy']['name'], $host['host'])); 314 } 315 } 316 317 $host['proxy_hostid'] = $proxyid; 318 } 319 320 $hostid = $this->referencer->findHostidByHost($host['host']); 321 322 if ($hostid !== null) { 323 $host['hostid'] = $hostid; 324 325 if (array_key_exists('macros', $host)) { 326 foreach ($host['macros'] as &$macro) { 327 $hostmacroid = $this->referencer->findHostMacroid($hostid, $macro['macro']); 328 329 if ($hostmacroid !== null) { 330 $macro['hostmacroid'] = $hostmacroid; 331 } 332 } 333 unset($macro); 334 } 335 } 336 337 return $host; 338 } 339 340 /** 341 * For existing hosts we need to set an interfaceid for existing interfaces or they will be added. 342 * 343 * @param array $hosts Hosts from XML for which interfaces will be added. 344 * 345 * @return array 346 */ 347 protected function addInterfaceIds(array $hosts): array { 348 $db_interfaces = API::HostInterface()->get([ 349 'output' => API_OUTPUT_EXTEND, 350 'hostids' => array_column($hosts, 'hostid'), 351 'preservekeys' => true 352 ]); 353 354 // build lookup maps for: 355 // - interfaces per host 356 // - default (primary) interface ids per host per interface type 357 $db_host_interfaces = []; 358 $db_host_main_interfaceids = []; 359 360 foreach ($db_interfaces as $db_interface) { 361 $hostid = $db_interface['hostid']; 362 363 $db_host_interfaces[$hostid][] = $db_interface; 364 if ($db_interface['main'] == INTERFACE_PRIMARY) { 365 $db_host_main_interfaceids[$hostid][$db_interface['type']] = $db_interface['interfaceid']; 366 } 367 } 368 369 foreach ($hosts as &$host) { 370 // If interfaces in XML are non-existent or empty, delete the interfaces on host. 371 372 $hostid = $host['hostid']; 373 374 $main_interfaceids = array_key_exists($hostid, $db_host_main_interfaceids) 375 ? $db_host_main_interfaceids[$hostid] 376 : []; 377 378 $reused_interfaceids = []; 379 380 foreach ($host['interfaces'] as &$interface) { 381 // check if an existing interfaceid from current host can be reused 382 // in case there is default (primary) interface in current host with same type 383 if ($interface['main'] == INTERFACE_PRIMARY 384 && array_key_exists($interface['type'], $main_interfaceids)) { 385 $db_interfaceid = $main_interfaceids[$interface['type']]; 386 387 $interface['interfaceid'] = $db_interfaceid; 388 $reused_interfaceids[$db_interfaceid] = true; 389 } 390 } 391 unset($interface); 392 393 // loop through all interfaces of current host and take interfaceids from ones that 394 // match completely, ignoring hosts from XML with set interfaceids and ignoring hosts 395 // from DB with reused interfaceids 396 foreach ($host['interfaces'] as &$interface) { 397 if (!array_key_exists($hostid, $db_host_interfaces)) { 398 continue; 399 } 400 401 foreach ($db_host_interfaces[$hostid] as $db_host_interface) { 402 $db_interfaceid = $db_host_interface['interfaceid']; 403 404 if (!array_key_exists('interfaceid', $interface) 405 && !array_key_exists($db_interfaceid, $reused_interfaceids) 406 && $db_host_interface['ip'] == $interface['ip'] 407 && $db_host_interface['dns'] == $interface['dns'] 408 && $db_host_interface['useip'] == $interface['useip'] 409 && $db_host_interface['port'] == $interface['port'] 410 && $db_host_interface['type'] == $interface['type']) { 411 $interface['interfaceid'] = $db_interfaceid; 412 $reused_interfaceids[$db_interfaceid] = true; 413 break; 414 } 415 } 416 } 417 unset($interface); 418 } 419 unset($host); 420 421 return $hosts; 422 } 423} 424