1<?php 2 3# This file is a part of RackTables, a datacenter and server room management 4# framework. See accompanying file "COPYING" for the full copyright and 5# licensing information. 6 7/* 8* 9* This file is a library of database access functions for RackTables. 10* 11*/ 12 13$SQLSchema = array 14( 15 'object' => array 16 ( 17 'table' => 'RackObject', 18 'columns' => array 19 ( 20 'id' => 'id', 21 'name' => 'name', 22 'label' => 'label', 23 'asset_no' => 'asset_no', 24 'objtype_id' => 'objtype_id', 25 'rack_id' => '(SELECT MIN(rack_id) FROM RackSpace WHERE object_id = RackObject.id)', 26 'rack_id_2' => "(SELECT MIN(parent_entity_id) FROM EntityLink WHERE child_entity_type='object' AND child_entity_id = RackObject.id AND parent_entity_type = 'rack')", 27 'container_id' => "(SELECT MIN(parent_entity_id) FROM EntityLink WHERE child_entity_type='object' AND child_entity_id = RackObject.id AND parent_entity_type = 'object')", 28 'container_name' => '(SELECT name FROM RackObject WHERE id = container_id)', 29 'container_objtype_id' => '(SELECT objtype_id FROM RackObject WHERE id = container_id)', 30 'has_problems' => 'has_problems', 31 'comment' => 'comment', 32 'nports' => '(SELECT COUNT(*) FROM Port WHERE object_id = RackObject.id)', 33 '8021q_domain_id' => '(SELECT domain_id FROM VLANSwitch WHERE object_id = id LIMIT 1)', 34 '8021q_template_id' => '(SELECT template_id FROM VLANSwitch WHERE object_id = id LIMIT 1)', 35 ), 36 'keycolumn' => 'id', 37 'ordcolumns' => array ('RackObject.name'), 38 ), 39 'user' => array 40 ( 41 'table' => 'UserAccount', 42 'columns' => array 43 ( 44 'user_id' => 'user_id', 45 'user_name' => 'user_name', 46 'user_password_hash' => 'user_password_hash', 47 'user_realname' => 'user_realname', 48 ), 49 'keycolumn' => 'user_id', 50 'ordcolumns' => array ('UserAccount.user_name'), 51 ), 52 'ipv4net' => array 53 ( 54 'table' => 'IPv4Network', 55 'columns' => array 56 ( 57 'id' => 'id', 58 'ip_bin' => 'ip', 59 'mask' => 'mask', 60 'name' => 'name', 61 'comment' => 'comment', 62 '8021q' => '(SELECT GROUP_CONCAT(CONCAT(domain_id, "-", vlan_id) ORDER BY domain_id, vlan_id) FROM VLANIPv4 WHERE ipv4net_id = id)', 63 ), 64 'keycolumn' => 'id', 65 'ordcolumns' => array ('IPv4Network.ip', 'IPv4Network.mask'), 66 ), 67 'ipv6net' => array 68 ( 69 'table' => 'IPv6Network', 70 'columns' => array 71 ( 72 'id' => 'id', 73 'ip_bin' => 'ip', 74 'mask' => 'mask', 75 'name' => 'name', 76 'comment' => 'comment', 77 '8021q' => '(SELECT GROUP_CONCAT(CONCAT(domain_id, "-", vlan_id) ORDER BY domain_id, vlan_id) FROM VLANIPv6 WHERE ipv6net_id = id)', 78 ), 79 'keycolumn' => 'id', 80 'ordcolumns' => array ('IPv6Network.ip', 'IPv6Network.mask'), 81 ), 82 'file' => array 83 ( 84 'table' => 'File', 85 'columns' => array 86 ( 87 'id' => 'id', 88 'name' => 'name', 89 'type' => 'type', 90 'size' => 'size', 91 'ctime' => 'ctime', 92 'mtime' => 'mtime', 93 'atime' => 'atime', 94 'comment' => 'comment', 95 ), 96 'keycolumn' => 'id', 97 'ordcolumns' => array ('File.name'), 98 ), 99 'ipv4vs' => array 100 ( 101 'table' => 'IPv4VS', 102 'columns' => array 103 ( 104 'id' => 'id', 105 'vip_bin' => 'vip', 106 'vport' => 'vport', 107 'proto' => 'proto', 108 'name' => 'name', 109 'vsconfig' => 'vsconfig', 110 'rsconfig' => 'rsconfig', 111 'refcnt' => '(select count(vs_id) from IPv4LB where vs_id = id)', 112 //'vip' => 113 //'dname' => 114 ), 115 'keycolumn' => 'id', 116 'ordcolumns' => array ('IPv4VS.vip', 'IPv4VS.proto', 'IPv4VS.vport'), 117 ), 118 'ipvs' => array 119 ( 120 'table' => 'VS', 121 'columns' => array 122 ( 123 'id' => 'id', 124 'name' => 'name', 125 'vsconfig' => 'vsconfig', 126 'rsconfig' => 'rsconfig', 127 ), 128 'keycolumn' => 'id', 129 ), 130 'ipv4rspool' => array 131 ( 132 'table' => 'IPv4RSPool', 133 'columns' => array 134 ( 135 'id' => 'id', 136 'name' => 'name', 137 'refcnt' => '(select count(rspool_id) from IPv4LB where rspool_id = id)', 138 'rscount' => '(select count(rspool_id) from IPv4RS where rspool_id = IPv4RSPool.id)', 139 'vsconfig' => 'vsconfig', 140 'rsconfig' => 'rsconfig', 141 ), 142 'keycolumn' => 'id', 143 'ordcolumns' => array ('IPv4RSPool.name', 'IPv4RSPool.id'), 144 ), 145 'rack' => array 146 ( 147 'table' => 'Rack', 148 'columns' => array 149 ( 150 'id' => 'id', 151 'name' => 'name', 152 'height' => 'height', 153 'asset_no' => 'asset_no', 154 'has_problems' => 'has_problems', 155 'comment' => 'comment', 156 'row_id' => 'row_id', 157 'row_name' => 'row_name', 158 'location_id' => 'location_id', 159 'location_name' => 'location_name', 160 ), 161 'keycolumn' => 'id', 162 'ordcolumns' => array ('location_name', 'row_name', 'sort_order', 'Rack.name'), 163 'pidcolumn' => 'row_id', 164 ), 165 'row' => array 166 ( 167 'table' => '`Row`', 168 'columns' => array 169 ( 170 'id' => 'id', 171 'name' => 'name', 172 'location_id' => 'location_id', 173 'location_name' => 'location_name', 174 'rackc' => '(select count(Rack.id) from Rack where row_id = Row.id)', 175 ), 176 'keycolumn' => 'id', 177 'ordcolumns' => array ('location_name', 'name'), 178 ), 179 'location' => array 180 ( 181 'table' => 'Location', 182 'columns' => array 183 ( 184 'id' => 'id', 185 'name' => 'name', 186 'has_problems' => 'has_problems', 187 'comment' => 'comment', 188 'parent_id' => 'parent_id', 189 'parent_name' => 'parent_name', 190 'refcnt' => "(SELECT COUNT(child_entity_id) FROM EntityLink EL WHERE EL.parent_entity_type = 'location' AND EL.parent_entity_id = Location.id)", 191 ), 192 'keycolumn' => 'id', 193 'ordcolumns' => array ('name'), 194 ), 195 'vst' => array 196 ( 197 'table' => 'VLANSwitchTemplate', 198 'columns' => array 199 ( 200 'id' => 'id', 201 'description' => 'description', 202 'mutex_rev' => 'mutex_rev', 203 'saved_by' => 'saved_by', 204 'switchc' => '(SELECT COUNT(object_id) FROM VLANSwitch WHERE template_id = id)', 205 'rulec' => '(SELECT COUNT(rule_no) FROM VLANSTRule WHERE vst_id = id)', 206 ), 207 'keycolumn' => 'id', 208 'ordcolumns' => array ('description'), 209 ), 210); 211 212$searchfunc = array 213( 214 'object' => array 215 ( 216 'by_port' => 'getPortSearchResults', 217 'by_attr' => 'getObjectAttrsSearchResults', 218 'by_iface' => 'getObjectIfacesSearchResults', 219 'by_nat' => 'getObjectNATSearchResults', 220 'by_cableid' => 'searchCableIDs', 221 ), 222); 223 224$tablemap_8021q = array 225( 226 'desired' => array 227 ( 228 'pvm' => 'PortVLANMode', 229 'pav' => 'PortAllowedVLAN', 230 'pnv' => 'PortNativeVLAN', 231 ), 232 'cached' => array 233 ( 234 'pvm' => 'CachedPVM', 235 'pav' => 'CachedPAV', 236 'pnv' => 'CachedPNV', 237 ), 238); 239 240// VST roles 241$port_role_options = array 242( 243 'none' => 'none', 244 'access' => 'user: access only', 245 'trunk' => 'user: trunk only', 246 'anymode' => 'user: any mode', 247 'uplink' => 'system: uplink trunk', 248 'downlink' => 'system: downlink trunk', 249); 250 251// flags to pass to scanIPSpace, scanIPv4Space, scanIPv6Space 252define ('IPSCAN_DO_ADDR', 1 << 0); 253define ('IPSCAN_DO_ALLOCS', 1 << 1); 254define ('IPSCAN_DO_VS', 1 << 2); 255define ('IPSCAN_DO_RS', 1 << 3); 256define ('IPSCAN_DO_NAT', 1 << 4); 257define ('IPSCAN_DO_LOG', 1 << 5); 258define ('IPSCAN_RTR_ONLY', 1 << 6); 259 260define ('IPSCAN_ANY', -1 ^ IPSCAN_RTR_ONLY); 261define ('IPSCAN_DO_SLB', IPSCAN_DO_VS | IPSCAN_DO_RS); 262 263$object_attribute_cache = array(); 264 265// Return list of locations directly under a specified location 266function getLocations ($location_id) 267{ 268 $result = usePreparedSelectBlade 269 ( 270 'SELECT id, name FROM Location WHERE parent_id = ? ORDER BY name', 271 array ($location_id) 272 ); 273 return reduceSubarraysToColumn (reindexById ($result->fetchAll (PDO::FETCH_ASSOC)), 'name'); 274} 275 276// Return detailed information about one rack row. 277function getRowInfo ($row_id) 278{ 279 $query = 280 "SELECT `Row`.id AS id, `Row`.name AS name, COUNT(Rack.id) AS count, " . 281 "IF(ISNULL(SUM(Rack.height)),0,SUM(Rack.height)) AS sum, " . 282 "Location.id AS location_id, Location.name AS location " . 283 "FROM `Row` LEFT JOIN Rack ON Rack.row_id = `Row`.id " . 284 "LEFT OUTER JOIN Location ON `Row`.location_id = Location.id " . 285 "WHERE `Row`.id = ? " . 286 "GROUP BY `Row`.id, Location.id"; 287 $result = usePreparedSelectBlade ($query, array ($row_id)); 288 if ($row = $result->fetch (PDO::FETCH_ASSOC)) 289 return $row; 290 throw new EntityNotFoundException ('rackrow', $row_id); 291} 292 293// TODO: deprecated function. delete it 294function getAllRows () 295{ 296 return listCells ('row'); 297} 298 299// Return list of rows directly under a specified location 300function getRows ($location_id) 301{ 302 $result = usePreparedSelectBlade 303 ( 304 'SELECT R.id, R.name FROM `Row` R ' . 305 'INNER JOIN EntityLink EL ON ' . 306 "EL.parent_entity_type = 'location' " . 307 "AND EL.child_entity_type = 'row' " . 308 "AND EL.child_entity_id = R.id " . 309 'WHERE EL.parent_entity_id = ? ' . 310 'ORDER BY R.name', 311 array ($location_id) 312 ); 313 return reduceSubarraysToColumn (reindexById ($result->fetchAll (PDO::FETCH_ASSOC)), 'name'); 314} 315 316function getRacks ($row_id) 317{ 318 $result = usePreparedSelectBlade 319 ( 320 'SELECT id, name, asset_no, height, sort_order, comment, row_name FROM Rack WHERE row_id = ? ORDER BY sort_order', 321 array ($row_id) 322 ); 323 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC)); 324} 325 326# Return rack and row details for those objects on the list that have 327# at least one rackspace atom allocated to them. 328function getMountInfo ($object_ids) 329{ 330 if (! count ($object_ids)) 331 return array(); 332 # In theory number of involved racks can be equal or even greater than the 333 # number of objects, but in practice it will often be tens times less. Hence 334 # the scope of the 1st pass is to tell IDs of all involved racks without 335 # fetching lots of duplicate data. 336 $result = usePreparedSelectBlade 337 ( 338 'SELECT object_id, rack_id ' . 339 'FROM RackSpace ' . 340 'WHERE object_id IN(' . questionMarks (count ($object_ids)) . ') ' . 341 'GROUP BY object_id, rack_id ' . 342 'UNION ' . 343 'SELECT child_entity_id AS object_id, parent_entity_id AS rack_id ' . 344 'FROM EntityLink ' . 345 'WHERE child_entity_id IN(' . questionMarks (count ($object_ids)) . ') ' . 346 "AND parent_entity_type = 'rack' AND child_entity_type = 'object' " . 347 'ORDER BY rack_id ASC', 348 array_merge($object_ids, $object_ids) 349 ); 350 $rackidlist = $objectlist = array(); 351 foreach ($result as $row) 352 { 353 $objectlist[$row['object_id']][] = $row['rack_id']; 354 $rackidlist[] = $row['rack_id']; 355 } 356 unset ($result); 357 # short-cut to exit in case no object had rackspace allocated 358 if (! count ($rackidlist)) 359 { 360 $ret = array(); 361 foreach ($object_ids as $object_id) 362 $ret[$object_id] = array(); 363 return $ret; 364 } 365 # Pass 2. Fetch shorter, but better extra data about the rows and racks, 366 # set displayed names for both. 367 $result = usePreparedSelectBlade 368 ( 369 'SELECT Rack_.id as rack_id, Rack_.name AS rack_name, Rack_.label as rack_label, ' . 370 'parent_entity_id AS row_id, Row_.name AS row_name ' . 371 'FROM Object Rack_ ' . 372 "LEFT JOIN EntityLink ON (Rack_.id = child_entity_id AND parent_entity_type = 'row' AND child_entity_type = 'rack') " . 373 'LEFT JOIN Object Row_ ON parent_entity_id = Row_.id ' . 374 'WHERE Rack_.id IN(' . questionMarks (count ($rackidlist)) . ') ', 375 $rackidlist 376 ); 377 $rackinfo = array(); 378 foreach ($result as $row) 379 { 380 $rackinfo[$row['rack_id']] = array 381 ( 382 'rack_id' => $row['rack_id'], 383 'row_id' => $row['row_id'], 384 ); 385 if ('' != $row['rack_name']) 386 $rackinfo[$row['rack_id']]['rack_name'] = $row['rack_name']; 387 elseif ('' != $row['rack_label']) 388 $rackinfo[$row['rack_id']]['rack_name'] = $row['rack_label']; 389 else 390 $rackinfo[$row['rack_id']]['rack_name'] = 'rack#' . $row['rack_id']; 391 if ('' != $row['row_name']) 392 $rackinfo[$row['rack_id']]['row_name'] = $row['row_name']; 393 else 394 $rackinfo[$row['rack_id']]['row_name'] = 'row#' . $row['row_id']; 395 } 396 unset ($result); 397 # Pass 3. Combine retrieved data into returned array. 398 $ret = array(); 399 foreach ($objectlist as $object_id => $racklist) 400 foreach ($racklist as $rack_id) 401 $ret[$object_id][] = $rackinfo[$rack_id]; 402 return $ret; 403} 404 405# Return container details for a list of objects 406function getContainerInfo ($object_ids) 407{ 408 if (! count ($object_ids)) 409 return array (); 410 $result = usePreparedSelectBlade 411 ( 412 'SELECT EL.child_entity_id, EL.parent_entity_id, RO.name, RO.objtype_id ' . 413 'FROM EntityLink EL ' . 414 'LEFT JOIN RackObject RO ON EL.parent_entity_id = RO.id ' . 415 'WHERE EL.child_entity_id IN (' . questionMarks (count ($object_ids)) . ') ' . 416 "AND EL.parent_entity_type = 'object' " . 417 "AND EL.child_entity_type = 'object' " . 418 'ORDER BY RO.name', 419 $object_ids 420 ); 421 $ret = array (); 422 foreach ($result as $row) 423 $ret[$row['child_entity_id']][] = array 424 ( 425 'container_id' => $row['parent_entity_id'], 426 'container_dname' => formatObjectDisplayedName ($row['name'], $row['objtype_id']) 427 ); 428 unset ($result); 429 return $ret; 430} 431 432// For a given realm return a list of entity records, each with 433// enough information for judgeCell() to execute. 434function listCells ($realm, $parent_id = 0) 435{ 436 if (!$parent_id) 437 { 438 global $entityCache; 439 if (isset ($entityCache['complete'][$realm])) 440 return $entityCache['complete'][$realm]; 441 } 442 global $SQLSchema; 443 if (!isset ($SQLSchema[$realm])) 444 throw new InvalidArgException ('realm', $realm); 445 $SQLinfo = $SQLSchema[$realm]; 446 $qparams = array (); 447 $query = 'SELECT '; 448 foreach ($SQLinfo['columns'] as $alias => $expression) 449 // Automatically prepend table name to each single column, but leave all others intact. 450 $query .= ($alias == $expression ? "${SQLinfo['table']}.${alias}" : "${expression} as ${alias}") . ', '; 451 $query = trim($query, ', '); 452 $query .= " FROM ${SQLinfo['table']}"; 453 if (isset ($SQLinfo['pidcolumn']) && $parent_id) 454 { 455 $query .= " WHERE ${SQLinfo['table']}.${SQLinfo['pidcolumn']} = ?"; 456 $qparams[] = $parent_id; 457 } 458 if (isset ($SQLinfo['ordcolumns'])) 459 { 460 $query .= " ORDER BY "; 461 foreach ($SQLinfo['ordcolumns'] as $oc) 462 $query .= "${oc}, "; 463 $query = trim($query, ', '); 464 } 465 $result = usePreparedSelectBlade ($query, $qparams); 466 $ret = array(); 467 // Index returned result by the value of key column. 468 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 469 { 470 $entity_id = $row[$SQLinfo['keycolumn']]; 471 $ret[$entity_id] = array ('realm' => $realm); 472 $ret[$entity_id]['etags'] = array(); 473 foreach (array_keys ($SQLinfo['columns']) as $alias) 474 $ret[$entity_id][$alias] = $row[$alias]; 475 // use the temporary rack_id_2 key and remove this key from the result array 476 if ($realm == 'object') 477 { 478 if (! isset ($ret[$entity_id]['rack_id'])) 479 $ret[$entity_id]['rack_id'] = $ret[$entity_id]['rack_id_2']; 480 unset ($ret[$entity_id]['rack_id_2']); 481 } 482 } 483 unset($result); 484 485 // select tags and link them to previosly fetched entities 486 $query = 'SELECT entity_id, tag_id, user AS tag_user, UNIX_TIMESTAMP(date) AS tag_time FROM TagStorage WHERE entity_realm = ?'; 487 $result = usePreparedSelectBlade ($query, array($realm)); 488 global $taglist; 489 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 490 { 491 $tag_id = $row['tag_id']; 492 if (array_key_exists($row['entity_id'], $ret)) 493 $ret[$row['entity_id']]['etags'][$tag_id] = array 494 ( 495 'id' => $tag_id, 496 'tag' => $taglist[$tag_id]['tag'], 497 'parent_id' => $taglist[$tag_id]['parent_id'], 498 'color' => $taglist[$tag_id]['color'], 499 'description' => $taglist[$tag_id]['description'], 500 'user' => $row['tag_user'], 501 'time' => $row['tag_time'], 502 ); 503 } 504 unset($result); 505 // Add necessary finish to the list before returning it. Maintain caches. 506 if (!$parent_id) 507 unset ($entityCache['partial'][$realm]); 508 if ($realm == 'object') // cache dict attributes of all objects to speed up autotags calculation 509 cacheDictAttrValues(); 510 foreach ($ret as $entity_id => &$entity) 511 { 512 sortEntityTags ($entity); // changes ['etags'] and ['itags'] 513 switch ($realm) 514 { 515 case 'object': 516 setDisplayedName ($entity); // set $entity['dname'] 517 break; 518 case 'ipv4net': 519 $entity = array_merge ($entity, constructIPRange (ip4_int2bin ($entity['ip_bin']), $entity['mask'])); 520 processIPNetVlans ($entity); 521 $entity['spare_ranges'] = array(); 522 $entity['kidc'] = 0; 523 break; 524 case 'ipv6net': 525 $entity = array_merge ($entity, constructIPRange ($entity['ip_bin'], $entity['mask'])); 526 processIPNetVlans ($entity); 527 $entity['spare_ranges'] = array(); 528 $entity['kidc'] = 0; 529 break; 530 case 'ipv4vs': 531 $entity['vip'] = ip_format ($entity['vip_bin']); 532 setDisplayedName ($entity); // set $entity['dname'] 533 $entity['vsconfig'] = dos2unix ($entity['vsconfig']); 534 $entity['rsconfig'] = dos2unix ($entity['rsconfig']); 535 break; 536 case 'ipv4rspool': 537 $entity['vsconfig'] = dos2unix ($entity['vsconfig']); 538 $entity['rsconfig'] = dos2unix ($entity['rsconfig']); 539 break; 540 case 'ipvs': 541 $entity['vsconfig'] = dos2unix ($entity['vsconfig']); 542 $entity['rsconfig'] = dos2unix ($entity['rsconfig']); 543 break; 544 default: 545 break; 546 } 547 } 548 if ($realm == 'ipv4net' || $realm == 'ipv6net') 549 fillIPNetsCorrelation ($ret); 550 551 foreach (array_keys ($ret) as $entity_id) 552 { 553 $entity = &$ret[$entity_id]; 554 $entity['atags'] = callHook ('generateEntityAutoTags', $entity); 555 if (!$parent_id) 556 $entityCache['complete'][$realm][$entity_id] = $entity; 557 else 558 $entityCache['partial'][$realm][$entity_id] = $entity; 559 } 560 561 return $ret; 562} 563 564// Very much like listCells(), but return only one record requested 565// throws an exception if entity not exists 566function spotEntity ($realm, $id, $ignore_cache = FALSE) 567{ 568 if (! $ignore_cache) 569 { 570 global $entityCache; 571 if (isset ($entityCache['complete'][$realm])) 572 { 573 if (isset ($entityCache['complete'][$realm][$id])) 574 return $entityCache['complete'][$realm][$id]; 575 } 576 elseif (isset ($entityCache['partial'][$realm][$id])) 577 return $entityCache['partial'][$realm][$id]; 578 } 579 global $SQLSchema; 580 if (!isset ($SQLSchema[$realm])) 581 throw new InvalidArgException ('realm', $realm); 582 $SQLinfo = $SQLSchema[$realm]; 583 $query = 'SELECT tag_id, TagStorage.user as tag_user, UNIX_TIMESTAMP(TagStorage.date) AS tag_time'; 584 foreach ($SQLinfo['columns'] as $alias => $expression) 585 // Automatically prepend table name to each single column, but leave all others intact. 586 $query .= ', ' . ($alias == $expression ? "${SQLinfo['table']}.${alias}" : "${expression} as ${alias}"); 587 $query .= " FROM ${SQLinfo['table']} LEFT JOIN TagStorage on entity_realm = ? and entity_id = ${SQLinfo['table']}.${SQLinfo['keycolumn']}"; 588 $query .= " WHERE ${SQLinfo['table']}.${SQLinfo['keycolumn']} = ?"; 589 $result = usePreparedSelectBlade ($query, array ($realm, $id)); 590 $ret = array(); 591 global $taglist; 592 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 593 if (!isset ($ret['realm'])) 594 { 595 $ret = array ('realm' => $realm); 596 foreach (array_keys ($SQLinfo['columns']) as $alias) 597 $ret[$alias] = $row[$alias]; 598 // use the temporary rack_id_2 key and remove this key from the result array 599 if ($realm == 'object') 600 { 601 if (! isset ($ret['rack_id'])) 602 $ret['rack_id'] = $ret['rack_id_2']; 603 unset ($ret['rack_id_2']); 604 } 605 $ret['etags'] = array(); 606 if ($row['tag_id'] != NULL && isset ($taglist[$row['tag_id']])) 607 $ret['etags'][$row['tag_id']] = array 608 ( 609 'id' => $row['tag_id'], 610 'tag' => $taglist[$row['tag_id']]['tag'], 611 'parent_id' => $taglist[$row['tag_id']]['parent_id'], 612 'color' => $taglist[$row['tag_id']]['color'], 613 'description' => $taglist[$row['tag_id']]['description'], 614 'user' => $row['tag_user'], 615 'time' => $row['tag_time'], 616 ); 617 } 618 elseif (isset ($taglist[$row['tag_id']])) 619 $ret['etags'][$row['tag_id']] = array 620 ( 621 'id' => $row['tag_id'], 622 'tag' => $taglist[$row['tag_id']]['tag'], 623 'parent_id' => $taglist[$row['tag_id']]['parent_id'], 624 'color' => $taglist[$row['tag_id']]['color'], 625 'description' => $taglist[$row['tag_id']]['description'], 626 'user' => $row['tag_user'], 627 'time' => $row['tag_time'], 628 ); 629 unset ($result); 630 if (!isset ($ret['realm'])) // no rows were returned 631 throw new EntityNotFoundException ($realm, $id); 632 sortEntityTags ($ret); // changes ['etags'] and ['itags'] 633 switch ($realm) 634 { 635 case 'object': 636 setDisplayedName ($ret); // set $ret['dname'] 637 break; 638 case 'ipv4net': 639 processIPNetVlans ($ret); 640 $ret = array_merge ($ret, constructIPRange (ip4_int2bin ($ret['ip_bin']), $ret['mask'])); 641 if (! fillNetKids ($ret)) 642 { 643 $ret['spare_ranges'] = array(); 644 $ret['kidc'] = 0; 645 } 646 break; 647 case 'ipv6net': 648 processIPNetVlans ($ret); 649 $ret = array_merge ($ret, constructIPRange ($ret['ip_bin'], $ret['mask'])); 650 if (! fillNetKids ($ret)) 651 { 652 $ret['spare_ranges'] = array(); 653 $ret['kidc'] = 0; 654 } 655 break; 656 case 'ipv4vs': 657 $ret['vip'] = ip_format ($ret['vip_bin']); 658 setDisplayedName ($ret); // set $ret['dname'] 659 $ret['vsconfig'] = dos2unix ($ret['vsconfig']); 660 $ret['rsconfig'] = dos2unix ($ret['rsconfig']); 661 break; 662 case 'ipv4rspool': 663 $ret['vsconfig'] = dos2unix ($ret['vsconfig']); 664 $ret['rsconfig'] = dos2unix ($ret['rsconfig']); 665 break; 666 case 'ipvs': 667 $ret['vsconfig'] = dos2unix ($ret['vsconfig']); 668 $ret['rsconfig'] = dos2unix ($ret['rsconfig']); 669 break; 670 default: 671 break; 672 } 673 674 $ret['atags'] = generateEntityAutoTags ($ret); 675 if (! $ignore_cache) 676 $entityCache['partial'][$realm][$id] = $ret; 677 return $ret; 678} 679 680function fillNetKids (&$net_cell) 681{ 682 if ($net_cell['realm'] == 'ipv6net') 683 { 684 $table = 'IPv6Network'; 685 $ip_first = $net_cell['ip_bin']; 686 $ip_last = ip_last ($net_cell); 687 } 688 else 689 { 690 $table = 'IPv4Network'; 691 $ip_first = ip4_bin2db ($net_cell['ip_bin']); 692 $ip_last = ip4_bin2db (ip_last ($net_cell)); 693 } 694 695 $result = usePreparedSelectBlade (" 696SELECT id, ip as ip_bin, mask FROM $table 697WHERE ip BETWEEN ? AND ? AND mask >= ? 698ORDER BY ip, mask 699", array ($ip_first, $ip_last, $net_cell['mask'])); 700 $nets = array(); 701 while ($net_row = $result->fetch (PDO::FETCH_ASSOC)) 702 { 703 $ip_bin = $net_row['ip_bin']; 704 if ($net_cell['realm'] == 'ipv4net') 705 $ip_bin = ip4_int2bin ($ip_bin); 706 $nets[] = constructIPRange ($ip_bin, $net_row['mask']) + 707 array( 708 'id' => $net_row['id'], 709 'spare_ranges' => array(), 710 'kidc' => 0, 711 ); 712 } 713 unset ($result); 714 715 fillIPNetsCorrelation ($nets, 1); 716 if (is_array ($nets[0]) && $nets[0]['id'] == $net_cell['id']) 717 { 718 $net_cell['spare_ranges'] = $nets[0]['spare_ranges']; 719 $net_cell['kidc'] = $nets[0]['kidc']; 720 return TRUE; 721 } 722 return FALSE; 723} 724 725// This function can be used with array_walk(). 726function amplifyCell (&$record, $dummy = NULL) 727{ 728 switch ($record['realm']) 729 { 730 case 'object': 731 $record['ports'] = getObjectPortsAndLinks ($record['id']); 732 $record['ipv4'] = getObjectIPv4Allocations ($record['id']); 733 $record['ipv6'] = getObjectIPv6Allocations ($record['id']); 734 $record['nat4'] = getNATv4ForObject ($record['id']); 735 $record['files'] = getFilesOfEntity ($record['realm'], $record['id']); 736 break; 737 case 'file': 738 $record['links'] = getFileLinks ($record['id']); 739 break; 740 case 'location': 741 $record['locations'] = getLocations ($record['id']); 742 $record['rows'] = getRows ($record['id']); 743 break; 744 case 'row': 745 $record['racks'] = getRacks ($record['id']); 746 break; 747 case 'rack': 748 // start with default rackspace 749 for ($i = $record['height']; $i > 0; $i--) 750 for ($locidx = 0; $locidx < 3; $locidx++) 751 $record[$i][$locidx]['state'] = 'F'; 752 // load difference 753 $query = 754 "select unit_no, atom, state, object_id, has_problems " . 755 "from RackSpace LEFT JOIN Object ON Object.id = object_id where rack_id = ? and " . 756 "unit_no between 1 and ? order by unit_no"; 757 $result = usePreparedSelectBlade ($query, array ($record['id'], $record['height'])); 758 global $loclist; 759 $mounted_objects = array(); 760 // fetch Zero-U mounted objects 761 foreach (getChildren ($record, 'object') as $child) 762 $mounted_objects[$child['id']] = TRUE; 763 764 $rows = $result->fetchAll (PDO::FETCH_ASSOC); 765 unset ($result); 766 foreach ($rows as $row) 767 { 768 $record[$row['unit_no']][$loclist[$row['atom']]]['state'] = $row['state']; 769 $record[$row['unit_no']][$loclist[$row['atom']]]['object_id'] = $row['object_id']; 770 $record[$row['unit_no']][$loclist[$row['atom']]]['hl'] = $row['has_problems'] == 'yes' ? 'w' : ''; 771 if ($row['state'] == 'T' && $row['object_id'] != NULL) 772 $mounted_objects[$row['object_id']] = TRUE; 773 } 774 775 $record['isDeletable'] = (count ($rows) || count ($mounted_objects)) ? FALSE : TRUE; 776 $record['mountedObjects'] = array_keys ($mounted_objects); 777 break; 778 case 'vst': 779 $record['rules'] = array(); 780 $record['switches'] = array(); 781 $result = usePreparedSelectBlade 782 ( 783 'SELECT rule_no, port_pcre, port_role, wrt_vlans, description ' . 784 'FROM VLANSTRule WHERE vst_id = ? ORDER BY rule_no', 785 array ($record['id']) 786 ); 787 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 788 $record['rules'][$row['rule_no']] = $row; 789 unset ($result); 790 $result = usePreparedSelectBlade ('SELECT object_id, domain_id FROM VLANSwitch WHERE template_id = ?', array ($record['id'])); 791 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 792 $record['switches'][$row['object_id']] = $row; 793 break; 794 case 'ipvs': 795 $record['ports'] = array(); 796 $record['vips'] = array(); 797 $result = usePreparedSelectBlade ("SELECT proto, vport, vsconfig, rsconfig FROM VSPorts WHERE vs_id = ?", array ($record['id'])); 798 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 799 { 800 $row['vsconfig'] = dos2unix ($row['vsconfig']); 801 $row['rsconfig'] = dos2unix ($row['rsconfig']); 802 $record['ports'][] = $row; 803 } 804 unset ($result); 805 $result = usePreparedSelectBlade ("SELECT vip, vsconfig, rsconfig FROM VSIPs WHERE vs_id = ?", array ($record['id'])); 806 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 807 { 808 $row['vsconfig'] = dos2unix ($row['vsconfig']); 809 $row['rsconfig'] = dos2unix ($row['rsconfig']); 810 $record['vips'][] = $row; 811 } 812 unset ($result); 813 break; 814 default: 815 } 816} 817 818// is called by spotEntity and listCells. 819// replaces ['8021q'] text value in cell by an array with 'domain_id' and 'vlan_id' subkeys 820// also sets ['vlanc'] cell key to the binded vlans count. 821function processIPNetVlans (&$cell) 822{ 823 if (empty ($cell['8021q'])) 824 $cell['8021q'] = array(); 825 else 826 { 827 $ck_list = explode (',', $cell['8021q']); 828 $cell['8021q'] = array(); 829 foreach ($ck_list as $vlan_ck) 830 { 831 list ($domain_id, $vlan_id) = decodeVLANCK ($vlan_ck); 832 $cell['8021q'][] = array ('domain_id' => $domain_id, 'vlan_id' => $vlan_id); 833 } 834 } 835 $cell['vlanc'] = count ($cell['8021q']); 836} 837 838function fetchPortList ($sql_where_clause, $query_params = array()) 839{ 840 // Heredoc, not nowdoc! 841 $query = <<<"END" 842SELECT 843 Port.id, 844 Port.name, 845 Port.object_id, 846 Object.name AS object_name, 847 Port.l2address, 848 Port.label, 849 Port.reservation_comment, 850 Port.iif_id, 851 Port.type AS oif_id, 852 (SELECT PortInnerInterface.iif_name FROM PortInnerInterface WHERE PortInnerInterface.id = Port.iif_id) AS iif_name, 853 (SELECT PortOuterInterface.oif_name FROM PortOuterInterface WHERE PortOuterInterface.id = Port.type) AS oif_name, 854 IF(la.porta, la.cable, lb.cable) AS cableid, 855 IF(la.porta, pa.id, pb.id) AS remote_id, 856 IF(la.porta, pa.name, pb.name) AS remote_name, 857 IF(la.porta, pa.object_id, pb.object_id) AS remote_object_id, 858 IF(la.porta, oa.name, ob.name) AS remote_object_name, 859 IF(la.porta, oa.objtype_id, ob.objtype_id) AS remote_object_tid, 860 (SELECT COUNT(*) FROM PortLog WHERE PortLog.port_id = Port.id) AS log_count, 861 PortLog.user, 862 UNIX_TIMESTAMP(PortLog.date) as time 863FROM 864 Port 865 INNER JOIN Object ON Port.object_id = Object.id 866 LEFT JOIN Link AS la ON la.porta = Port.id 867 LEFT JOIN Port AS pa ON pa.id = la.portb 868 LEFT JOIN Object AS oa ON pa.object_id = oa.id 869 LEFT JOIN Link AS lb on lb.portb = Port.id 870 LEFT JOIN Port AS pb ON pb.id = lb.porta 871 LEFT JOIN Object AS ob ON pb.object_id = ob.id 872 LEFT JOIN PortLog ON PortLog.id = (SELECT id FROM PortLog WHERE PortLog.port_id = Port.id ORDER BY date DESC LIMIT 1) 873WHERE 874 $sql_where_clause 875END; 876 877 $result = usePreparedSelectBlade ($query, $query_params); 878 $ret = array(); 879 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 880 { 881 $row['l2address'] = l2addressFromDatabase ($row['l2address']); 882 $row['linked'] = isset ($row['remote_id']) ? 1 : 0; 883 884 // last changed log 885 $row['last_log'] = array(); 886 if ($row['log_count']) 887 { 888 $row['last_log']['user'] = $row['user']; 889 $row['last_log']['time'] = $row['time']; 890 } 891 unset ($row['user']); 892 unset ($row['time']); 893 894 $ret[] = $row; 895 } 896 return $ret; 897} 898 899function getObjectPortsAndLinks ($object_id, $sorted = TRUE) 900{ 901 $ret = fetchPortList ("Port.object_id = ?", array ($object_id)); 902 if ($sorted) 903 $ret = sortPortList ($ret, TRUE); 904 return $ret; 905} 906 907// This function provides data for replaceObjectPorts() and requires only two tables locked. 908function getObjectPortsAndLinksTerse ($object_id) 909{ 910 $result = usePreparedSelectBlade 911 ( 912 'SELECT id, name, iif_id, type AS oif_id, label, l2address, reservation_comment, ' . 913 '(SELECT COUNT(*) FROM Link WHERE porta = Port.id OR portb = Port.id) AS link_count ' . 914 'FROM Port WHERE object_id = ?', 915 array ($object_id) 916 ); 917 return $result->fetchAll (PDO::FETCH_ASSOC); 918} 919 920// Fetch the object type via SQL. 921// spotEntity cannot be used because it references RackObject, which doesn't suit Racks, Rows, or Locations. 922function getObjectType ($object_id) 923{ 924 $result = usePreparedSelectBlade ('SELECT objtype_id from Object WHERE id = ?', array ($object_id)); 925 return $result->fetchColumn (); 926} 927 928// If the given name is used by any object other than the current object, 929// raise an exception. Validation is bypassed for certain object types 930// where duplicates are acceptable. 931// NOTE: This could be enforced more strictly at the database level using triggers. 932function checkObjectNameUniqueness ($name, $type_id, $object_id = 0) 933{ 934 // Some object types do not need unique names 935 // 1560 - Rack 936 // 1561 - Row 937 $dupes_allowed = array (1560, 1561); 938 if (in_array ($type_id, $dupes_allowed)) 939 return; 940 941 $result = usePreparedSelectBlade 942 ( 943 'SELECT COUNT(*) FROM Object WHERE name = ? AND id != ?', 944 array ($name, $object_id) 945 ); 946 $row = $result->fetch (PDO::FETCH_NUM); 947 if ($row[0] != 0) 948 throw new InvalidRequestArgException ('name', $name, 'An object with that name already exists'); 949} 950 951function commitAddObject ($new_name, $new_label, $new_type_id, $new_asset_no, $taglist = array()) 952{ 953 checkObjectNameUniqueness ($new_name, $new_type_id); 954 usePreparedInsertBlade 955 ( 956 'Object', 957 array 958 ( 959 'name' => nullIfEmptyStr ($new_name), 960 'label' => nullIfEmptyStr ($new_label), 961 'objtype_id' => $new_type_id, 962 'asset_no' => nullIfEmptyStr ($new_asset_no), 963 ) 964 ); 965 $object_id = lastInsertID(); 966 switch ($new_type_id) 967 { 968 case 1560: 969 $realm = 'rack'; 970 break; 971 case 1561: 972 $realm = 'row'; 973 break; 974 case 1562: 975 $realm = 'location'; 976 break; 977 default: 978 $realm = 'object'; 979 } 980 lastCreated ($realm, $object_id); 981 982 // Store any tags before executeAutoPorts() calls spotEntity() and populates the cache. 983 produceTagsForNewRecord ($realm, $taglist, $object_id); 984 // Do AutoPorts magic 985 if ($realm == 'object') 986 executeAutoPorts ($object_id); 987 recordObjectHistory ($object_id); 988 return $object_id; 989} 990 991function commitRenameObject ($object_id, $new_name) 992{ 993 $type_id = getObjectType ($object_id); 994 checkObjectNameUniqueness ($new_name, $type_id, $object_id); 995 usePreparedUpdateBlade 996 ( 997 'Object', 998 array 999 ( 1000 'name' => nullIfEmptyStr ($new_name), 1001 ), 1002 array 1003 ( 1004 'id' => $object_id 1005 ) 1006 ); 1007 recordObjectHistory ($object_id); 1008} 1009 1010function commitUpdateObject ($object_id, $new_name, $new_label, $new_has_problems, $new_asset_no, $new_comment) 1011{ 1012 $set_columns = array 1013 ( 1014 'name' => nullIfEmptyStr ($new_name), 1015 'label' => nullIfEmptyStr ($new_label), 1016 'has_problems' => $new_has_problems == '' ? 'no' : $new_has_problems, 1017 'asset_no' => nullIfEmptyStr ($new_asset_no), 1018 'comment' => nullIfEmptyStr ($new_comment), 1019 ); 1020 $override = callHook('commitUpdateObjectBefore_hook', $object_id, $set_columns); 1021 if (is_array ($override)) 1022 $set_columns = $override; 1023 $type_id = getObjectType ($object_id); 1024 checkObjectNameUniqueness ($new_name, $type_id, $object_id); 1025 usePreparedUpdateBlade 1026 ( 1027 'Object', 1028 $set_columns, 1029 array 1030 ( 1031 'id' => $object_id 1032 ) 1033 ); 1034 recordObjectHistory ($object_id); 1035 callHook ('commitUpdateObjectAfter_hook', $object_id); 1036} 1037 1038function compare_name ($a, $b) 1039{ 1040 return strnatcmp($a['name'], $b['name']); 1041} 1042 1043// find either parents or children of a record 1044function getEntityRelatives ($type, $entity_type, $entity_id) 1045{ 1046 $ret = array(); 1047 1048 if ($type == 'parents') 1049 { 1050 // searching for parents 1051 $sql = 1052 'SELECT id, parent_entity_type AS entity_type, parent_entity_id AS entity_id FROM EntityLink ' . 1053 'WHERE child_entity_type = ? AND child_entity_id = ?'; 1054 } 1055 else 1056 { 1057 // searching for children 1058 $sql = 1059 'SELECT id, child_entity_type AS entity_type, child_entity_id AS entity_id FROM EntityLink ' . 1060 'WHERE parent_entity_type = ? AND parent_entity_id = ?'; 1061 } 1062 1063 $result = usePreparedSelectBlade ($sql, array ($entity_type, $entity_id)); 1064 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 1065 $ret[$row['id']] = array( 1066 'entity_type' => $row['entity_type'], 1067 'entity_id' => $row['entity_id'], 1068 ); 1069 1070 return $ret; 1071} 1072 1073function getParents ($entity, $result_realm = NULL) 1074{ 1075 return getRelatives ($entity, 'parents', $result_realm); 1076} 1077 1078function getChildren ($entity, $result_realm = NULL) 1079{ 1080 return getRelatives ($entity, 'children', $result_realm); 1081} 1082 1083function getRelatives ($entity, $type, $result_realm = NULL) 1084{ 1085 $ret = array(); 1086 foreach (getEntityRelatives ($type, $entity['realm'], $entity['id']) as $link_id => $struct) 1087 if (! isset ($result_realm) || $result_realm == $struct['entity_type']) 1088 $ret[$link_id] = spotEntity ($struct['entity_type'], $struct['entity_id']); 1089 return $ret; 1090} 1091 1092// This function is recursive and returns only object IDs. 1093function getObjectContentsList ($object_id, $children = array ()) 1094{ 1095 $self = __FUNCTION__; 1096 $result = usePreparedSelectBlade 1097 ( 1098 'SELECT child_entity_id FROM EntityLink ' . 1099 'WHERE parent_entity_type = "object" AND child_entity_type = "object" AND parent_entity_id = ?', 1100 array ($object_id) 1101 ); 1102 $rows = $result->fetchAll (PDO::FETCH_ASSOC); 1103 unset ($result); 1104 foreach ($rows as $row) 1105 { 1106 if (in_array ($row['child_entity_id'], $children)) 1107 throw new RackTablesError ("Circular reference for object ${object_id}", RackTablesError::INTERNAL); 1108 $children[] = $row['child_entity_id']; 1109 $children = array_unique (array_merge ($children, $self ($row['child_entity_id'], $children))); 1110 } 1111 return $children; 1112} 1113 1114// This function is recursive and returns only location IDs. 1115function getLocationChildrenList ($location_id, $children = array ()) 1116{ 1117 $self = __FUNCTION__; 1118 $result = usePreparedSelectBlade ('SELECT id FROM Location WHERE parent_id = ?', array ($location_id)); 1119 $rows = $result->fetchAll (PDO::FETCH_ASSOC); 1120 unset ($result); 1121 foreach ($rows as $row) 1122 { 1123 if (in_array ($row['id'], $children)) 1124 throw new RackTablesError ("Circular reference for location ${location_id}", RackTablesError::INTERNAL); 1125 $children[] = $row['id']; 1126 $children = array_unique (array_merge ($children, $self ($row['id'], $children))); 1127 } 1128 return $children; 1129} 1130 1131// DEPRECATED: use getTagDescendents() instead 1132// This function is recursive and returns only tag IDs. 1133function getTagChildrenList ($tag_id, $children = array ()) 1134{ 1135 $self = __FUNCTION__; 1136 $result = usePreparedSelectBlade ('SELECT id FROM TagTree WHERE parent_id = ?', array ($tag_id)); 1137 $rows = $result->fetchAll (PDO::FETCH_ASSOC); 1138 unset ($result); 1139 foreach ($rows as $row) 1140 { 1141 if (in_array ($row['id'], $children)) 1142 throw new RackTablesError ("Circular reference for tag ${tag_id}", RackTablesError::INTERNAL); 1143 $children[] = $row['id']; 1144 $children = array_unique (array_merge ($children, $self ($row['id'], $children))); 1145 } 1146 return $children; 1147} 1148 1149function commitLinkEntities ($parent_entity_type, $parent_entity_id, $child_entity_type, $child_entity_id) 1150{ 1151 // a location's parent may not be one of its children 1152 if 1153 ( 1154 $parent_entity_type == 'location' && 1155 $child_entity_type == 'location' && 1156 in_array ($parent_entity_id, getLocationChildrenList ($child_entity_id)) 1157 ) 1158 throw new RackTablesError ("Circular reference for location ${parent_entity_id}", RackTablesError::INTERNAL); 1159 1160 // an object's container may not be one of its contained objects 1161 if 1162 ( 1163 $parent_entity_type == 'object' && 1164 $child_entity_type == 'object' && 1165 in_array ($parent_entity_id, getObjectContentsList ($child_entity_id)) 1166 ) 1167 throw new RackTablesError ("Circular reference for object ${parent_entity_id}", RackTablesError::INTERNAL); 1168 1169 usePreparedInsertBlade 1170 ( 1171 'EntityLink', 1172 array 1173 ( 1174 'parent_entity_type' => $parent_entity_type, 1175 'parent_entity_id' => $parent_entity_id, 1176 'child_entity_type' => $child_entity_type, 1177 'child_entity_id' => $child_entity_id, 1178 ) 1179 ); 1180} 1181 1182function commitUpdateEntityLink 1183( 1184 $old_parent_entity_type, $old_parent_entity_id, $old_child_entity_type, $old_child_entity_id, 1185 $new_parent_entity_type, $new_parent_entity_id, $new_child_entity_type, $new_child_entity_id 1186) 1187{ 1188 // a location's parent may not be one of its children 1189 if 1190 ( 1191 $new_parent_entity_type == 'location' && 1192 $new_child_entity_type == 'location' && 1193 in_array ($new_parent_entity_id, getLocationChildrenList ($new_child_entity_id)) 1194 ) 1195 throw new RackTablesError ("Circular reference for location ${new_parent_entity_id}", RackTablesError::INTERNAL); 1196 1197 // an object's container may not be one of its contained objects 1198 if 1199 ( 1200 $new_parent_entity_type == 'object' && 1201 $new_child_entity_type == 'object' && 1202 in_array ($new_parent_entity_id, getObjectContentsList ($new_child_entity_id)) 1203 ) 1204 throw new RackTablesError ("Circular reference for object ${new_parent_entity_id}", RackTablesError::INTERNAL); 1205 1206 usePreparedUpdateBlade 1207 ( 1208 'EntityLink', 1209 array 1210 ( 1211 'parent_entity_type' => $new_parent_entity_type, 1212 'parent_entity_id' => $new_parent_entity_id, 1213 'child_entity_type' => $new_child_entity_type, 1214 'child_entity_id' => $new_child_entity_id 1215 ), 1216 array 1217 ( 1218 'parent_entity_type' => $old_parent_entity_type, 1219 'parent_entity_id' => $old_parent_entity_id, 1220 'child_entity_type' => $old_child_entity_type, 1221 'child_entity_id' => $old_child_entity_id 1222 ) 1223 ); 1224} 1225 1226function commitUnlinkEntities ($parent_entity_type, $parent_entity_id, $child_entity_type, $child_entity_id) 1227{ 1228 usePreparedDeleteBlade 1229 ( 1230 'EntityLink', 1231 array 1232 ( 1233 'parent_entity_type' => $parent_entity_type, 1234 'parent_entity_id' => $parent_entity_id, 1235 'child_entity_type' => $child_entity_type, 1236 'child_entity_id' => $child_entity_id 1237 ) 1238 ); 1239} 1240 1241function commitUnlinkEntitiesByLinkID ($link_id) 1242{ 1243 usePreparedDeleteBlade ('EntityLink', array ('id' => $link_id)); 1244} 1245 1246// return VM clusters and corresponding stats 1247// - number of hypervisors 1248// - number of resource pools 1249// - number of VMs whose parent is the cluster itself 1250// - number of VMs whose parent is one of the resource pools in the cluster 1251function getVMClusterSummary () 1252{ 1253 $query = <<<'END' 1254SELECT 1255 O.id, 1256 O.name, 1257 (SELECT COUNT(*) FROM EntityLink EL 1258 LEFT JOIN Object O_H ON EL.child_entity_id = O_H.id 1259 LEFT JOIN AttributeValue AV ON O_H.id = AV.object_id 1260 WHERE EL.parent_entity_type = 'object' 1261 AND EL.child_entity_type = 'object' 1262 AND EL.parent_entity_id = O.id 1263 AND O_H.objtype_id = 4 1264 AND AV.attr_id = 26 1265 AND AV.uint_value = 1501) AS hypervisors, 1266 (SELECT COUNT(*) FROM EntityLink EL 1267 LEFT JOIN Object O_RP ON EL.child_entity_id = O_RP.id 1268 WHERE EL.parent_entity_type = 'object' 1269 AND EL.child_entity_type = 'object' 1270 AND EL.parent_entity_id = O.id 1271 AND O_RP.objtype_id = 1506) AS resource_pools, 1272 (SELECT COUNT(*) FROM EntityLink EL 1273 LEFT JOIN Object O_C_VM ON EL.child_entity_id = O_C_VM.id 1274 WHERE EL.parent_entity_type = 'object' 1275 AND EL.child_entity_type = 'object' 1276 AND EL.parent_entity_id = O.id 1277 AND O_C_VM.objtype_id = 1504) AS cluster_vms, 1278 (SELECT COUNT(*) FROM EntityLink EL 1279 LEFT JOIN Object O_RP_VM ON EL.child_entity_id = O_RP_VM.id 1280 WHERE EL.parent_entity_type = 'object' 1281 AND EL.child_entity_type = 'object' 1282 AND EL.parent_entity_id IN 1283 (SELECT child_entity_id FROM EntityLink EL 1284 LEFT JOIN Object O_RP ON EL.child_entity_id = O_RP.id 1285 WHERE EL.parent_entity_type = 'object' 1286 AND EL.child_entity_type = 'object' 1287 AND EL.parent_entity_id = O.id 1288 AND O_RP.objtype_id = 1506) 1289 AND O_RP_VM.objtype_id = 1504) AS resource_pool_vms 1290FROM Object O 1291WHERE O.objtype_id = 1505 1292ORDER BY O.name 1293END; 1294 $result = usePreparedSelectBlade ($query); 1295 return $result->fetchAll (PDO::FETCH_ASSOC); 1296} 1297 1298function getVMResourcePoolSummary () 1299{ 1300 $result = usePreparedSelectBlade 1301 ( 1302 "SELECT O.id, O.name, " . 1303 "(SELECT O_C.id " . 1304 "FROM EntityLink EL " . 1305 "LEFT JOIN Object O_C ON EL.parent_entity_id = O_C.id " . 1306 "WHERE EL.child_entity_id = O.id " . 1307 "AND EL.parent_entity_type = 'object' " . 1308 "AND EL.child_entity_type = 'object' " . 1309 "AND O_C.objtype_id = 1505 LIMIT 1) AS cluster_id, " . 1310 "(SELECT O_C.name " . 1311 "FROM EntityLink EL " . 1312 "LEFT JOIN Object O_C ON EL.parent_entity_id = O_C.id " . 1313 "WHERE EL.child_entity_id = O.id " . 1314 "AND EL.parent_entity_type = 'object' " . 1315 "AND EL.child_entity_type = 'object' " . 1316 "AND O_C.objtype_id = 1505 LIMIT 1) AS cluster_name, " . 1317 "(SELECT COUNT(*) FROM EntityLink EL " . 1318 "LEFT JOIN Object O_VM ON EL.child_entity_id = O_VM.id " . 1319 "WHERE EL.parent_entity_type = 'object' " . 1320 "AND EL.child_entity_type = 'object' " . 1321 "AND EL.parent_entity_id = O.id " . 1322 "AND O_VM.objtype_id = 1504) AS VMs " . 1323 "FROM Object O " . 1324 "WHERE O.objtype_id = 1506 " . 1325 "ORDER BY O.name" 1326 ); 1327 return $result->fetchAll (PDO::FETCH_ASSOC); 1328} 1329 1330function getVMHypervisorSummary () 1331{ 1332 $result = usePreparedSelectBlade 1333 ( 1334 "SELECT O.id, O.name, " . 1335 "(SELECT O_C.id " . 1336 "FROM EntityLink EL " . 1337 "LEFT JOIN Object O_C ON EL.parent_entity_id = O_C.id " . 1338 "WHERE EL.child_entity_id = O.id " . 1339 "AND EL.parent_entity_type = 'object' " . 1340 "AND EL.child_entity_type = 'object' " . 1341 "AND O_C.objtype_id = 1505 LIMIT 1) AS cluster_id, " . 1342 "(SELECT O_C.name " . 1343 "FROM EntityLink EL " . 1344 "LEFT JOIN Object O_C ON EL.parent_entity_id = O_C.id " . 1345 "WHERE EL.child_entity_id = O.id " . 1346 "AND EL.parent_entity_type = 'object' " . 1347 "AND EL.child_entity_type = 'object' " . 1348 "AND O_C.objtype_id = 1505 LIMIT 1) AS cluster_name, " . 1349 "(SELECT COUNT(*) FROM EntityLink EL " . 1350 "LEFT JOIN Object O_VM ON EL.child_entity_id = O_VM.id " . 1351 "WHERE EL.parent_entity_type = 'object' " . 1352 "AND EL.child_entity_type = 'object' " . 1353 "AND EL.parent_entity_id = O.id " . 1354 "AND O_VM.objtype_id = 1504) AS VMs " . 1355 "FROM Object O " . 1356 "LEFT JOIN AttributeValue AV ON O.id = AV.object_id " . 1357 "WHERE O.objtype_id = 4 " . 1358 "AND AV.attr_id = 26 " . 1359 "AND AV.uint_value = 1501 " . 1360 "ORDER BY O.name" 1361 ); 1362 return $result->fetchAll (PDO::FETCH_ASSOC); 1363} 1364 1365function getVMSwitchSummary () 1366{ 1367 $result = usePreparedSelectBlade 1368 ( 1369 "SELECT O.id, O.name " . 1370 "FROM Object O " . 1371 "WHERE O.objtype_id = 1507 " . 1372 "ORDER BY O.name" 1373 ); 1374 return $result->fetchAll (PDO::FETCH_ASSOC); 1375} 1376 1377// Remove file links related to the entity, but leave the entity and file(s) intact. 1378function releaseFiles ($entity_realm, $entity_id) 1379{ 1380 usePreparedDeleteBlade ('FileLink', array ('entity_type' => $entity_realm, 'entity_id' => $entity_id)); 1381} 1382 1383// There are times when you want to delete all traces of an object 1384function commitDeleteObject ($object_id) 1385{ 1386 // Reset most of stuff 1387 commitResetObject ($object_id); 1388 // Object itself 1389 usePreparedDeleteBlade ('Object', array ('id' => $object_id)); 1390 // Dangling links 1391 usePreparedExecuteBlade 1392 ( 1393 'DELETE FROM EntityLink WHERE ' . 1394 "(parent_entity_type IN ('rack', 'row', 'location') AND parent_entity_id = ?) OR " . 1395 "(child_entity_type IN ('rack', 'row', 'location') AND child_entity_id = ?)", 1396 array ($object_id, $object_id) 1397 ); 1398} 1399 1400function commitResetObject ($object_id) 1401{ 1402 releaseFiles ('object', $object_id); 1403 destroyTagsForEntity ('object', $object_id); 1404 usePreparedDeleteBlade ('IPv4LB', array ('object_id' => $object_id)); 1405 usePreparedDeleteBlade ('IPv4Allocation', array ('object_id' => $object_id)); 1406 usePreparedDeleteBlade ('IPv6Allocation', array ('object_id' => $object_id)); 1407 usePreparedDeleteBlade ('IPv4NAT', array ('object_id' => $object_id)); 1408 // Parent-child relationships 1409 usePreparedExecuteBlade 1410 ( 1411 'DELETE FROM EntityLink WHERE ' . 1412 "(parent_entity_type = 'object' AND parent_entity_id = ?) OR (child_entity_type = 'object' AND child_entity_id = ?)", 1413 array ($object_id, $object_id) 1414 ); 1415 // Rack space 1416 usePreparedExecuteBlade ('DELETE FROM Atom WHERE molecule_id IN (SELECT new_molecule_id FROM MountOperation WHERE object_id = ?)', array ($object_id)); 1417 usePreparedExecuteBlade ('DELETE FROM Molecule WHERE id IN (SELECT new_molecule_id FROM MountOperation WHERE object_id = ?)', array ($object_id)); 1418 usePreparedDeleteBlade ('MountOperation', array ('object_id' => $object_id)); 1419 usePreparedDeleteBlade ('RackSpace', array ('object_id' => $object_id)); 1420 // 802.1Q 1421 usePreparedDeleteBlade ('PortVLANMode', array ('object_id' => $object_id)); 1422 usePreparedDeleteBlade ('PortNativeVLAN', array ('object_id' => $object_id)); 1423 usePreparedDeleteBlade ('PortAllowedVLAN', array ('object_id' => $object_id)); 1424 usePreparedDeleteBlade ('CachedPVM', array ('object_id' => $object_id)); 1425 usePreparedDeleteBlade ('VLANSwitch', array ('object_id' => $object_id)); 1426 // SLB 1427 usePreparedDeleteBlade ('IPv4LB', array ('object_id' => $object_id)); 1428 usePreparedDeleteBlade ('VSEnabledIPs', array ('object_id' => $object_id)); 1429 usePreparedDeleteBlade ('VSEnabledPorts', array ('object_id' => $object_id)); 1430 // Ports & links 1431 usePreparedDeleteBlade ('Port', array ('object_id' => $object_id)); 1432 // CN 1433 usePreparedUpdateBlade ('Object', array ('name' => NULL, 'label' => ''), array ('id' => $object_id)); 1434 // FQDN 1435 commitUpdateAttrValue ($object_id, 3, ""); 1436 // log history 1437 recordObjectHistory ($object_id); 1438 # Do an additional reset if needed 1439 callHook ('resetObject_hook', $object_id); 1440} 1441 1442function commitUpdateRack ($rack_id, $new_row_id, $new_name, $new_height, $new_has_problems, $new_asset_no, $new_comment) 1443{ 1444 // Can't shrink a rack if rows being deleted contain mounted objects 1445 $check_result = usePreparedSelectBlade ('SELECT COUNT(*) AS count FROM RackSpace WHERE rack_id = ? AND unit_no > ?', array ($rack_id, $new_height)); 1446 $check_row = $check_result->fetch (PDO::FETCH_ASSOC); 1447 unset ($check_result); 1448 if ($check_row['count'] > 0) 1449 throw new InvalidArgException ('new_height', $new_height, 'Cannot shrink rack, objects are still mounted there'); 1450 1451 // Determine if the row changed 1452 $old_rack = spotEntity ('rack', $rack_id); 1453 $old_row_id = $old_rack['row_id']; 1454 if ($old_row_id != $new_row_id) 1455 { 1456 // Move it to the specified row 1457 usePreparedUpdateBlade 1458 ( 1459 'EntityLink', 1460 array ('parent_entity_id' => $new_row_id), 1461 array ('child_entity_type' => 'rack', 'child_entity_id' => $rack_id) 1462 ); 1463 1464 // Set the sort_order attribute so it's placed at the end of the new row 1465 $rowInfo = getRowInfo ($new_row_id); 1466 usePreparedUpdateBlade 1467 ( 1468 'AttributeValue', 1469 array ('uint_value' => $rowInfo['count']), 1470 array ('object_id' => $rack_id, 'attr_id' => 29) 1471 ); 1472 1473 // Reset the sort order of the old row 1474 resetRackSortOrder ($old_row_id); 1475 } 1476 1477 // Update the height 1478 commitUpdateAttrValue ($rack_id, 27, $new_height); 1479 1480 // Update the rack 1481 commitUpdateObject ($rack_id, $new_name, NULL, $new_has_problems, $new_asset_no, $new_comment); 1482} 1483 1484// Unmount all objects from the rack 1485function commitCleanRack ($rack_id) 1486{ 1487 $rack = spotEntity ('rack', $rack_id); 1488 foreach (getChildren ($rack, 'object') as $child) 1489 commitUnlinkEntities ('rack', $rack_id, 'object', $child['id']); 1490 usePreparedDeleteBlade ('RackSpace', array ('rack_id' => $rack_id)); 1491 usePreparedDeleteBlade ('RackThumbnail', array ('rack_id' => $rack_id)); 1492} 1493 1494// Drop the rack 1495function commitDeleteRack ($rack_id) 1496{ 1497 $rack = spotEntity ('rack', $rack_id); 1498 releaseFiles ('rack', $rack_id); 1499 destroyTagsForEntity ('rack', $rack_id); 1500 usePreparedDeleteBlade ('RackSpace', array ('rack_id' => $rack_id)); 1501 commitDeleteObject ($rack_id); 1502 resetRackSortOrder ($rack['row_id']); 1503} 1504 1505// Drop the row with all racks inside 1506function commitDeleteRow ($row_id) 1507{ 1508 $racks = getRacks ($row_id); 1509 foreach ($racks as $rack) 1510 commitDeleteRack ($rack['id']); 1511 commitDeleteObject ($row_id); 1512} 1513 1514// Returns mounted devices count in all racks inside the specified row 1515function getRowMountsCount ($row_id) 1516{ 1517 $query =<<<'END' 1518SELECT COUNT(*) FROM ( 1519 SELECT object_id FROM RackSpace rs LEFT JOIN EntityLink el ON (rs.rack_id = el.child_entity_id) 1520 WHERE 1521 rs.object_id IS NOT NULL AND 1522 el.parent_entity_id = ? AND el.parent_entity_type = "row" AND el.child_entity_type = "rack" 1523 UNION 1524 SELECT el1.child_entity_id object_id FROM EntityLink el1 LEFT JOIN EntityLink el2 ON (el1.parent_entity_id = el2.child_entity_id) 1525 WHERE 1526 el1.parent_entity_type = "rack" AND el1.child_entity_type = "object" AND 1527 el2.parent_entity_id = ? AND el2.parent_entity_type = "row" AND el2.child_entity_type = "rack" 1528) x 1529END; 1530 $result = usePreparedSelectBlade ($query, array ($row_id, $row_id)); 1531 return $result->fetchColumn(); 1532} 1533 1534// Returns mounted devices count in specified rack 1535function getRackMountsCount ($rack_id) 1536{ 1537 $query =<<<'END' 1538SELECT COUNT(*) FROM ( 1539 SELECT object_id FROM RackSpace WHERE object_id IS NOT NULL AND rack_id = ? 1540 UNION 1541 SELECT child_entity_id object_id FROM EntityLink WHERE 1542 parent_entity_id = ? AND parent_entity_type = "rack" AND child_entity_type = "object" 1543) x 1544END; 1545 $result = usePreparedSelectBlade ($query, array ($rack_id, $rack_id)); 1546 return $result->fetchColumn(); 1547} 1548 1549// Used when sort order is manually changed, and when a rack is moved or deleted 1550// Input is expected to be a pre-sorted array of rack IDs 1551function updateRackSortOrder ($racks) 1552{ 1553 for ($i = 0; $i<count($racks); $i++) 1554 { 1555 usePreparedUpdateBlade 1556 ( 1557 'AttributeValue', 1558 array ('uint_value' => $i+1), 1559 array ('object_id' => $racks[$i], 'attr_id' => 29) 1560 ); 1561 } 1562} 1563 1564function resetRackSortOrder ($row_id) 1565{ 1566 // Re-order the row's racks 1567 $racks = getRacks($row_id); 1568 $rack_ids = array (); 1569 foreach ($racks as $rack_id => $rackDetails) 1570 $rack_ids[] = $rack_id; 1571 updateRackSortOrder ($rack_ids); 1572} 1573 1574// This function builds a list of rack-unit-atom records assigned to 1575// the requested object. 1576function getMoleculeForObject ($object_id) 1577{ 1578 $result = usePreparedSelectBlade 1579 ( 1580 'SELECT rack_id, unit_no, atom FROM RackSpace ' . 1581 'WHERE state = "T" AND object_id = ? ORDER BY rack_id, unit_no, atom', 1582 array ($object_id) 1583 ); 1584 return $result->fetchAll (PDO::FETCH_ASSOC); 1585} 1586 1587// This function builds a list of rack-unit-atom records for requested molecule. 1588function getMolecule ($mid) 1589{ 1590 $result = usePreparedSelectBlade ('SELECT rack_id, unit_no, atom FROM Atom WHERE molecule_id = ?', array ($mid)); 1591 return $result->fetchAll (PDO::FETCH_ASSOC); 1592} 1593 1594// returns exactly what is's named after 1595function lastInsertID () 1596{ 1597 $result = usePreparedSelectBlade ('select last_insert_id()'); 1598 return $result->fetchColumn(); 1599} 1600 1601// This function creates a new record in Molecule and number of linked 1602// R-U-A records in Atom. 1603function createMolecule ($molData) 1604{ 1605 usePreparedExecuteBlade ('INSERT INTO Molecule VALUES()'); 1606 $molecule_id = lastInsertID(); 1607 foreach ($molData as $rua) 1608 usePreparedInsertBlade 1609 ( 1610 'Atom', 1611 array 1612 ( 1613 'molecule_id' => $molecule_id, 1614 'rack_id' => $rua['rack_id'], 1615 'unit_no' => $rua['unit_no'], 1616 'atom' => $rua['atom'], 1617 ) 1618 ); 1619 return $molecule_id; 1620} 1621 1622// History logger. This function assumes certain table naming convention and 1623// column design: 1624// - History table must have the same row set (w/o keys) plus one row named 1625// 'ctime' of type 'timestamp'. 1626function recordObjectHistory ($object_id) 1627{ 1628 global $remote_username; 1629 usePreparedExecuteBlade 1630 ( 1631 'INSERT INTO ObjectHistory ' . 1632 '(id, name, label, objtype_id, asset_no, has_problems, comment, ctime, user_name) ' . 1633 'SELECT id, name, label, objtype_id, asset_no, has_problems, comment, ' . 1634 'CURRENT_TIMESTAMP(), ? FROM Object WHERE id=?', 1635 array ($remote_username, $object_id) 1636 ); 1637} 1638 1639function getRackspaceHistory () 1640{ 1641 $result = usePreparedSelectBlade 1642 ( 1643 "SELECT id as mo_id, object_id as ro_id, ctime, comment, user_name FROM " . 1644 "MountOperation ORDER BY ctime DESC" 1645 ); 1646 return $result->fetchAll (PDO::FETCH_ASSOC); 1647} 1648 1649// This function is used in renderRackspaceHistory() 1650function getOperationMolecules ($op_id) 1651{ 1652 $result = usePreparedSelectBlade ('SELECT old_molecule_id, new_molecule_id FROM MountOperation WHERE id = ?', array ($op_id)); 1653 // The result is a single row. 1654 $row = $result->fetch (PDO::FETCH_ASSOC); 1655 return array ($row['old_molecule_id'], $row['new_molecule_id']); 1656} 1657 1658function getResidentRackIDs ($object_id) 1659{ 1660 $result = usePreparedSelectBlade 1661 ( 1662 // Include racks that the object is directly mounted in 1663 "SELECT rack_id FROM RackSpace WHERE object_id = ? " . 1664 "UNION " . 1665 // Include racks that its parent is mounted in 1666 "SELECT RS.rack_id FROM RackSpace RS INNER JOIN EntityLink EL ON RS.object_id = EL.parent_entity_id AND EL.parent_entity_type = 'object' WHERE EL.child_entity_id = ? AND EL.child_entity_type = 'object' " . 1667 "UNION " . 1668 // and racks that it is 'Zero-U' mounted in 1669 "SELECT parent_entity_id AS rack_id FROM EntityLink WHERE parent_entity_type = 'rack' AND child_entity_type = 'object' AND child_entity_id = ? " . 1670 'ORDER BY rack_id', array ($object_id, $object_id, $object_id) 1671 ); 1672 return $result->fetchAll (PDO::FETCH_COLUMN); 1673} 1674 1675function commitAddPort ($object_id, $port_name, $port_type_id, $port_label, $port_l2address) 1676{ 1677 global $dbxlink; 1678 $db_l2address = l2addressForDatabase ($port_l2address); 1679 list ($iif_id, $oif_id) = parsePortIIFOIF ($port_type_id); 1680 // The conditional table locking is less relevant now due to replaceObjectPorts(). 1681 if ($db_l2address != '') 1682 $dbxlink->exec ('LOCK TABLES Port WRITE'); 1683 try 1684 { 1685 assertUniqueL2Addresses (array ($db_l2address), $object_id); 1686 $ret = commitAddPortReal ($object_id, $port_name, $iif_id, $oif_id, $port_label, $db_l2address); 1687 } 1688 catch (Exception $e) 1689 { 1690 if ($db_l2address != '') 1691 $dbxlink->exec ('UNLOCK TABLES'); 1692 throw $e; 1693 } 1694 if ($db_l2address != '') 1695 $dbxlink->exec ('UNLOCK TABLES'); 1696 return $ret; 1697} 1698 1699// Having the call to assertUniqueL2Addresses() in this function would break things because 1700// if the constraint check fails for any port the whole "transaction" needs to be rolled 1701// back. Thus the calling function must call assertUniqueL2Addresses() for all involved ports 1702// first and only then start making any calls to this function. 1703function commitAddPortReal ($object_id, $port_name, $iif_id, $oif_id, $port_label, $db_l2address) 1704{ 1705 usePreparedInsertBlade 1706 ( 1707 'Port', 1708 array 1709 ( 1710 'name' => $port_name, 1711 'object_id' => $object_id, 1712 'label' => nullIfEmptyStr ($port_label), 1713 'iif_id' => $iif_id, 1714 'type' => $oif_id, 1715 'l2address' => nullIfEmptyStr ($db_l2address), 1716 ) 1717 ); 1718 lastCreated ('port', lastInsertID()); 1719 return lastInsertID(); 1720} 1721 1722function getPortReservationComment ($port_id, $extrasql = '') 1723{ 1724 $result = usePreparedSelectBlade ("SELECT reservation_comment FROM Port WHERE id = ? $extrasql", array ($port_id)); 1725 return $result->fetchColumn(); 1726} 1727 1728function commitUpdatePort ($object_id, $port_id, $port_name, $port_type_id, $port_label, $port_l2address, $port_reservation_comment) 1729{ 1730 global $dbxlink; 1731 $db_l2address = l2addressForDatabase ($port_l2address); 1732 list ($iif_id, $oif_id) = parsePortIIFOIF ($port_type_id); 1733 if ($db_l2address != '') 1734 $dbxlink->exec ('LOCK TABLES Port WRITE, PortLog WRITE'); 1735 try 1736 { 1737 assertUniqueL2Addresses (array ($db_l2address), $object_id); 1738 commitUpdatePortReal ($object_id, $port_id, $port_name, $iif_id, $oif_id, $port_label, $db_l2address, $port_reservation_comment); 1739 } 1740 catch (Exception $e) 1741 { 1742 if ($db_l2address != '') 1743 $dbxlink->exec ('UNLOCK TABLES'); 1744 throw $e; 1745 } 1746 if ($db_l2address != '') 1747 $dbxlink->exec ('UNLOCK TABLES'); 1748} 1749 1750// The comment about commitAddPortReal() also applies here. 1751function commitUpdatePortReal ($object_id, $port_id, $port_name, $iif_id, $oif_id, $port_label, $db_l2address, $port_reservation_comment) 1752{ 1753 $old_reservation_comment = getPortReservationComment ($port_id); 1754 $port_reservation_comment = nullIfEmptyStr ($port_reservation_comment); 1755 usePreparedUpdateBlade 1756 ( 1757 'Port', 1758 array 1759 ( 1760 'name' => $port_name, 1761 'iif_id' => $iif_id, 1762 'type' => $oif_id, 1763 'label' => nullIfEmptyStr ($port_label), 1764 'reservation_comment' => $port_reservation_comment, 1765 'l2address' => nullIfEmptyStr ($db_l2address), 1766 ), 1767 array 1768 ( 1769 'id' => $port_id, 1770 'object_id' => $object_id 1771 ) 1772 ); 1773 if ($old_reservation_comment !== $port_reservation_comment) 1774 addPortLogEntry ($port_id, sprintf ("Reservation changed from '%s' to '%s'", $old_reservation_comment, $port_reservation_comment)); 1775} 1776 1777function commitUpdatePortComment ($port_id, $port_reservation_comment) 1778{ 1779 global $dbxlink; 1780 $dbxlink->beginTransaction(); 1781 $prev_comment = getPortReservationComment ($port_id, 'FOR UPDATE'); 1782 $reservation_comment = nullIfEmptyStr ($port_reservation_comment); 1783 usePreparedUpdateBlade 1784 ( 1785 'Port', 1786 array 1787 ( 1788 'reservation_comment' => $reservation_comment, 1789 ), 1790 array 1791 ( 1792 'id' => $port_id, 1793 ) 1794 ); 1795 if ($prev_comment !== $reservation_comment) 1796 addPortLogEntry ($port_id, sprintf ("Reservation changed from '%s' to '%s'", $prev_comment, $reservation_comment)); 1797 $dbxlink->commit(); 1798} 1799 1800function commitUpdatePortOIF ($port_id, $port_type_id) 1801{ 1802 usePreparedUpdateBlade 1803 ( 1804 'Port', 1805 array ('type' => $port_type_id), 1806 array ('id' => $port_id) 1807 ); 1808} 1809 1810function getAllIPv4Allocations () 1811{ 1812 $result = usePreparedSelectBlade 1813 ( 1814 'SELECT object_id AS object_id, Object.name AS object_name, objtype_id, ' . 1815 'IPv4Allocation.name AS name, IPv4Allocation.type AS type, INET_NTOA(ip) AS ip '. 1816 'FROM IPv4Allocation JOIN Object ON id = object_id' 1817 ); 1818 return $result->fetchAll (PDO::FETCH_ASSOC); 1819} 1820 1821function linkPorts ($porta, $portb, $cable = NULL) 1822{ 1823 if ($porta == $portb) 1824 throw new InvalidArgException ('porta/portb', $porta, "Ports can't be the same"); 1825 1826 $result = usePreparedSelectBlade 1827 ( 1828 'SELECT COUNT(*) FROM Link WHERE porta IN (?,?) OR portb IN (?,?)', 1829 array ($porta, $portb, $porta, $portb) 1830 ); 1831 if ($result->fetchColumn () != 0) 1832 throw new RTDatabaseError ("Port ${porta} or ${portb} is already linked"); 1833 unset ($result); 1834 1835 $ret = usePreparedInsertBlade 1836 ( 1837 'Link', 1838 array 1839 ( 1840 'porta' => $porta, 1841 'portb' => $portb, 1842 'cable' => nullIfEmptyStr ($cable), 1843 ) 1844 ); 1845 usePreparedUpdateBlade ('Port', array ('reservation_comment' => NULL), array ('id' => array ($porta, $portb))); 1846 1847 // log new links 1848 $result = usePreparedSelectBlade 1849 ( 1850 "SELECT Port.id, Port.name as port_name, Object.name as obj_name FROM Port " . 1851 "INNER JOIN Object ON Port.object_id = Object.id WHERE Port.id IN (?, ?)", 1852 array ($porta, $portb) 1853 ); 1854 $rows = $result->fetchAll (PDO::FETCH_ASSOC); 1855 unset ($result); 1856 foreach ($rows as $row) 1857 { 1858 $pair_id = ($row['id'] == $porta ? $portb : $porta); 1859 addPortLogEntry ($pair_id, sprintf ("linked to %s %s", $row['obj_name'], $row['port_name'])); 1860 } 1861 return $ret; 1862} 1863 1864function commitUpdatePortLink ($port_id, $cable = NULL) 1865{ 1866 return usePreparedUpdateBlade 1867 ( 1868 'Link', 1869 array ('cable' => nullIfEmptyStr ($cable)), 1870 array ('porta' => $port_id, 'portb' => $port_id), 1871 'OR' 1872 ); 1873} 1874 1875function commitUnlinkPort ($port_id) 1876{ 1877 // fetch and log existing link 1878 $result = usePreparedSelectBlade 1879 ( 1880 "SELECT pa.id AS id_a, pa.name AS port_name_a, oa.name AS obj_name_a, " . 1881 "pb.id AS id_b, pb.name AS port_name_b, ob.name AS obj_name_b " . 1882 "FROM " . 1883 "Link INNER JOIN Port pa ON pa.id = Link.porta " . 1884 "INNER JOIN Port pb ON pb.id = Link.portb " . 1885 "INNER JOIN RackObject oa ON pa.object_id = oa.id " . 1886 "INNER JOIN RackObject ob ON pb.object_id = ob.id " . 1887 "WHERE " . 1888 "Link.porta = ? OR Link.portb = ?", 1889 array ($port_id, $port_id) 1890 ); 1891 $rows = $result->fetchAll (PDO::FETCH_ASSOC); 1892 unset ($result); 1893 foreach ($rows as $row) 1894 { 1895 addPortLogEntry ($row['id_a'], sprintf ("unlinked from %s %s", $row['obj_name_b'], $row['port_name_b'])); 1896 addPortLogEntry ($row['id_b'], sprintf ("unlinked from %s %s", $row['obj_name_a'], $row['port_name_a'])); 1897 } 1898 1899 // remove existing link 1900 return usePreparedDeleteBlade ('Link', array ('porta' => $port_id, 'portb' => $port_id), 'OR'); 1901} 1902 1903function addPortLogEntry ($port_id, $message) 1904{ 1905 global $disable_logging; 1906 if (isset ($disable_logging) && $disable_logging) 1907 return; 1908 global $remote_username; 1909 usePreparedExecuteBlade 1910 ( 1911 "INSERT INTO PortLog (port_id, user, date, message) VALUES (?, ?, NOW(), ?)", 1912 array ($port_id, $remote_username, $message) 1913 ); 1914} 1915 1916function addIPLogEntry ($ip_bin, $message) 1917{ 1918 switch (strlen ($ip_bin)) 1919 { 1920 case 4: return addIPv4LogEntry ($ip_bin, $message); 1921 case 16: return addIPv6LogEntry ($ip_bin, $message); 1922 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 1923 } 1924} 1925 1926function addIPv4LogEntry ($ip_bin, $message) 1927{ 1928 global $disable_logging; 1929 if (isset ($disable_logging) && $disable_logging) 1930 return; 1931 global $remote_username; 1932 usePreparedExecuteBlade 1933 ( 1934 "INSERT INTO IPv4Log (ip, date, user, message) VALUES (?, NOW(), ?, ?)", 1935 array (ip4_bin2db ($ip_bin), $remote_username, $message) 1936 ); 1937} 1938 1939function addIPv6LogEntry ($ip_bin, $message) 1940{ 1941 global $disable_logging; 1942 if (isset ($disable_logging) && $disable_logging) 1943 return; 1944 global $remote_username; 1945 usePreparedExecuteBlade 1946 ( 1947 "INSERT INTO IPv6Log (ip, date, user, message) VALUES (?, NOW(), ?, ?)", 1948 array ($ip_bin, $remote_username, $message) 1949 ); 1950} 1951 1952function fetchIPLogEntry ($ip_bin) 1953{ 1954 switch (strlen ($ip_bin)) 1955 { 1956 case 4: return fetchIPv4LogEntry ($ip_bin); 1957 case 16: return fetchIPv6LogEntry ($ip_bin); 1958 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 1959 } 1960} 1961 1962function fetchIPv4LogEntry ($ip_bin) 1963{ 1964 $result = usePreparedSelectBlade 1965 ( 1966 "SELECT date, user, message FROM IPv4Log WHERE ip = ? ORDER BY date ASC", 1967 array (ip4_bin2db ($ip_bin)) 1968 ); 1969 return $result->fetchAll (PDO::FETCH_ASSOC); 1970} 1971 1972function fetchIPv6LogEntry ($ip_bin) 1973{ 1974 $result = usePreparedSelectBlade 1975 ( 1976 "SELECT date, user, message FROM IPv6Log WHERE ip = ? ORDER BY date ASC", 1977 array ($ip_bin) 1978 ); 1979 return $result->fetchAll (PDO::FETCH_ASSOC); 1980} 1981 1982// wrapper around getObjectIPv4AllocationList and getObjectIPv6AllocationList 1983function getObjectIPAllocationList ($object_id) 1984{ 1985 return 1986 getObjectIPv4AllocationList ($object_id) + 1987 getObjectIPv6AllocationList ($object_id); 1988} 1989 1990// Returns all IPv4 addresses allocated to object, but does not attach detailed info about address 1991// Used instead of getObjectIPv4Allocations if you need perfomance but 'addrinfo' value 1992function getObjectIPv4AllocationList ($object_id) 1993{ 1994 $ret = array(); 1995 $result = usePreparedSelectBlade 1996 ( 1997 'SELECT name AS osif, type, ip FROM IPv4Allocation ' . 1998 'WHERE object_id = ?', 1999 array ($object_id) 2000 ); 2001 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2002 $ret[ip4_int2bin ($row['ip'])] = array ('osif' => $row['osif'], 'type' => $row['type']); 2003 return $ret; 2004} 2005 2006// Returns all IPv6 addresses allocated to object, but does not attach detailed info about address 2007// Used instead of getObjectIPv6Allocations if you need perfomance but 'addrinfo' value 2008function getObjectIPv6AllocationList ($object_id) 2009{ 2010 $ret = array(); 2011 $result = usePreparedSelectBlade 2012 ( 2013 'SELECT name AS osif, type, ip AS ip FROM IPv6Allocation ' . 2014 'WHERE object_id = ?', 2015 array ($object_id) 2016 ); 2017 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2018 $ret[$row['ip']] = array ('osif' => $row['osif'], 'type' => $row['type']); 2019 return $ret; 2020} 2021 2022// Return all IP addresses allocated to the object sorted by allocation name. 2023// Attach detailed info about address to each alocation records. 2024// Index result by binary ip 2025function getObjectIPAllocations ($object_id) 2026{ 2027 return amplifyAllocationList (getObjectIPAllocationList ($object_id)); 2028} 2029function getObjectIPv4Allocations ($object_id) 2030{ 2031 return amplifyAllocationList (getObjectIPv4AllocationList ($object_id)); 2032} 2033function getObjectIPv6Allocations ($object_id) 2034{ 2035 return amplifyAllocationList (getObjectIPv6AllocationList ($object_id)); 2036} 2037 2038function amplifyAllocationList ($alloc_list) 2039{ 2040 $ret = array(); 2041 $sorted = array(); 2042 foreach ($alloc_list as $ip_bin => $alloc) 2043 $sorted[$alloc['osif']][$ip_bin] = $alloc; 2044 foreach (sortPortList ($sorted) as $osif => $subarray) 2045 foreach ($subarray as $ip_bin => $alloc) 2046 { 2047 $alloc['addrinfo'] = getIPAddress ($ip_bin); 2048 $ret[$ip_bin] = $alloc; 2049 } 2050 return $ret; 2051} 2052 2053function scanIPNet ($net_info, $filter_flags = IPSCAN_ANY) 2054{ 2055 $pairlist = array( 2056 array('first' => $net_info['ip_bin'], 'last' => ip_last ($net_info)) 2057 ); 2058 return scanIPSpace ($pairlist, $filter_flags); 2059} 2060 2061function scanIPSpace ($pairlist, $filter_flags = IPSCAN_ANY) 2062{ 2063 $v4_pairs = array(); 2064 $v6_pairs = array(); 2065 foreach ($pairlist as $pair) 2066 { 2067 if (strlen ($pair['first']) == 4) 2068 $v4_pairs[] = $pair; 2069 elseif (strlen ($pair['first']) == 16) 2070 $v6_pairs[] = $pair; 2071 } 2072 return 2073 scanIPv4Space ($v4_pairs, $filter_flags) + 2074 scanIPv6Space ($v6_pairs, $filter_flags); 2075} 2076 2077// Check the range requested for meaningful IPv4 records, build them 2078// into a list and return. Return an empty list if nothing matched. 2079// Both arguments are expected in 4-byte binary string form. The resulting list 2080// is keyed by 4-byte binary IPs, items aren't sorted. 2081// LATER: accept a list of pairs and build WHERE sub-expression accordingly 2082function scanIPv4Space ($pairlist, $filter_flags = IPSCAN_ANY) 2083{ 2084 $ret = array(); 2085 if (!count ($pairlist)) // this is normal for a network completely divided into smaller parts 2086 return $ret; 2087 $pairlist = reduceIPPairList ($pairlist); 2088 // FIXME: this is a copy-and-paste prototype 2089 $or = ''; 2090 $whereexpr1 = '('; 2091 $whereexpr2 = '('; 2092 $whereexpr3a = '('; 2093 $whereexpr3b = '('; 2094 $whereexpr4 = '('; 2095 $whereexpr5a = '('; 2096 $whereexpr5b = '('; 2097 $whereexpr6a = '('; 2098 $whereexpr6b = '('; 2099 $qparams = array(); 2100 $qparams_bin = array(); 2101 foreach ($pairlist as $tmp) 2102 { 2103 $whereexpr1 .= $or . "ip between ? and ?"; 2104 $whereexpr2 .= $or . "ip between ? and ?"; 2105 $whereexpr3a .= $or . "vip between ? and ?"; 2106 $whereexpr3b .= $or . "vip between ? and ?"; 2107 $whereexpr4 .= $or . "rsip between ? and ?"; 2108 $whereexpr5a .= $or . "remoteip between ? and ?"; 2109 $whereexpr5b .= $or . "localip between ? and ?"; 2110 $whereexpr6a .= $or . "ip between ? and ?"; 2111 $whereexpr6b .= $or . "l.ip between ? and ?"; 2112 $or = ' or '; 2113 $qparams[] = ip4_bin2db ($tmp['first']); 2114 $qparams[] = ip4_bin2db ($tmp['last']); 2115 $qparams_bin[] = $tmp['first']; 2116 $qparams_bin[] = $tmp['last']; 2117 } 2118 $whereexpr1 .= ')'; 2119 $whereexpr2 .= ')'; 2120 $whereexpr3a .= ')'; 2121 $whereexpr3b .= ')'; 2122 $whereexpr4 .= ')'; 2123 $whereexpr5a .= ')'; 2124 $whereexpr5b .= ')'; 2125 $whereexpr6a .= ')'; 2126 $whereexpr6b .= ')'; 2127 2128 // 1. collect labels and reservations 2129 if ($filter_flags & IPSCAN_DO_ADDR) 2130 { 2131 $query = "select ip, name, comment, reserved from IPv4Address ". 2132 "where ${whereexpr1} and (reserved = 'yes' or name != '' or comment != '')"; 2133 $result = usePreparedSelectBlade ($query, $qparams); 2134 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2135 { 2136 $ip_bin = ip4_int2bin ($row['ip']); 2137 if (!isset ($ret[$ip_bin])) 2138 $ret[$ip_bin] = constructIPAddress ($ip_bin); 2139 $ret[$ip_bin]['name'] = $row['name']; 2140 $ret[$ip_bin]['comment'] = $row['comment']; 2141 $ret[$ip_bin]['reserved'] = $row['reserved']; 2142 } 2143 unset ($result); 2144 } 2145 2146 // 2. check for allocations 2147 if ($filter_flags & IPSCAN_DO_ALLOCS) 2148 { 2149 if ($filter_flags & IPSCAN_RTR_ONLY) 2150 $whereexpr2 .= " AND ( ia.type = 'router' OR ia.type = 'sharedrouter')"; 2151 $query = 2152 "select ia.ip, ia.object_id, ia.name, ia.type, Object.name as object_name " . 2153 "from IPv4Allocation AS ia INNER JOIN Object ON ia.object_id = Object.id where ${whereexpr2} order by ia.type"; 2154 $result = usePreparedSelectBlade ($query, $qparams); 2155 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2156 { 2157 $ip_bin = ip4_int2bin ($row['ip']); 2158 if (!isset ($ret[$ip_bin])) 2159 $ret[$ip_bin] = constructIPAddress ($ip_bin); 2160 $ret[$ip_bin]['allocs'][] = array 2161 ( 2162 'type' => $row['type'], 2163 'name' => $row['name'], 2164 'object_id' => $row['object_id'], 2165 'object_name' => $row['object_name'], 2166 ); 2167 } 2168 unset ($result); 2169 } 2170 2171 // 3a. look for virtual services 2172 if ($filter_flags & IPSCAN_DO_VS) 2173 { 2174 $query = "select id, vip from IPv4VS where ${whereexpr3a}"; 2175 $result = usePreparedSelectBlade ($query, $qparams_bin); 2176 $allRows = $result->fetchAll (PDO::FETCH_ASSOC); 2177 unset ($result); 2178 foreach ($allRows as $row) 2179 { 2180 $ip_bin = $row['vip']; 2181 if (!isset ($ret[$ip_bin])) 2182 $ret[$ip_bin] = constructIPAddress ($ip_bin); 2183 $ret[$ip_bin]['vslist'][] = $row['id']; 2184 } 2185 2186 // 3b. look for virtual service groups 2187 $query = "select vs_id, vip from VSIPs where ${whereexpr3b}"; 2188 $result = usePreparedSelectBlade ($query, $qparams_bin); 2189 $allRows = $result->fetchAll (PDO::FETCH_ASSOC); 2190 unset ($result); 2191 foreach ($allRows as $row) 2192 { 2193 $ip_bin = $row['vip']; 2194 if (!isset ($ret[$ip_bin])) 2195 $ret[$ip_bin] = constructIPAddress ($ip_bin); 2196 $ret[$ip_bin]['vsglist'][] = $row['vs_id']; 2197 } 2198 } 2199 2200 // 4. don't forget about real servers along with pools 2201 if ($filter_flags & IPSCAN_DO_RS) 2202 { 2203 $query = "select rsip, rspool_id from IPv4RS where ${whereexpr4}"; 2204 $result = usePreparedSelectBlade ($query, $qparams_bin); 2205 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2206 { 2207 $ip_bin = $row['rsip']; 2208 if (!isset ($ret[$ip_bin])) 2209 $ret[$ip_bin] = constructIPAddress ($ip_bin); 2210 $ret[$ip_bin]['rsplist'][] = $row['rspool_id']; 2211 } 2212 unset ($result); 2213 } 2214 2215 // 5. add NAT rules, remote ip 2216 if ($filter_flags & IPSCAN_DO_NAT) 2217 { 2218 $query = 2219 "select " . 2220 "proto, " . 2221 "localip, " . 2222 "localport, " . 2223 "remoteip, " . 2224 "remoteport, " . 2225 "description " . 2226 "from IPv4NAT " . 2227 "where ${whereexpr5a} " . 2228 "order by localip, localport, remoteip, remoteport, proto"; 2229 $result = usePreparedSelectBlade ($query, $qparams); 2230 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2231 { 2232 $ip_bin_local = ip4_int2bin ($row['localip']); 2233 $ip_bin_remote = ip4_int2bin ($row['remoteip']); 2234 $row['localip_bin'] = $ip_bin_local; 2235 $row['remoteip_bin'] = $ip_bin_remote; 2236 $row['localip'] = ip_format ($ip_bin_local); 2237 $row['remoteip'] = ip_format ($ip_bin_remote); 2238 if (!isset ($ret[$ip_bin_remote])) 2239 $ret[$ip_bin_remote] = constructIPAddress ($ip_bin_remote); 2240 $ret[$ip_bin_remote]['inpf'][] = $row; 2241 } 2242 unset ($result); 2243 // 5. add NAT rules, local ip 2244 $query = 2245 "select " . 2246 "proto, " . 2247 "localip, " . 2248 "localport, " . 2249 "remoteip, " . 2250 "remoteport, " . 2251 "description " . 2252 "from IPv4NAT " . 2253 "where ${whereexpr5b} " . 2254 "order by localip, localport, remoteip, remoteport, proto"; 2255 $result = usePreparedSelectBlade ($query, $qparams); 2256 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2257 { 2258 $ip_bin_local = ip4_int2bin ($row['localip']); 2259 $ip_bin_remote = ip4_int2bin ($row['remoteip']); 2260 $row['localip_bin'] = $ip_bin_local; 2261 $row['remoteip_bin'] = $ip_bin_remote; 2262 $row['localip'] = ip_format ($ip_bin_local); 2263 $row['remoteip'] = ip_format ($ip_bin_remote); 2264 if (!isset ($ret[$ip_bin_local])) 2265 $ret[$ip_bin_local] = constructIPAddress ($ip_bin_local); 2266 $ret[$ip_bin_local]['outpf'][] = $row; 2267 } 2268 unset ($result); 2269 } 2270 2271 // 6. collect last log message 2272 if ($filter_flags & IPSCAN_DO_LOG) 2273 { 2274 $query = "select l.ip, l.user, UNIX_TIMESTAMP(l.date) AS time from IPv4Log l INNER JOIN " . 2275 " (SELECT MAX(id) as id FROM IPv4Log WHERE ${whereexpr6a} GROUP BY ip) v USING (id) WHERE ${whereexpr6b}"; 2276 $result = usePreparedSelectBlade ($query, array_merge ($qparams, $qparams)); 2277 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2278 { 2279 $ip_bin = ip4_int2bin ($row['ip']); 2280 if (isset ($ret[$ip_bin])) 2281 $ret[$ip_bin]['last_log'] = array 2282 ( 2283 'user' => $row['user'], 2284 'time' => $row['time'], 2285 ); 2286 } 2287 unset ($result); 2288 } 2289 2290 $override = callHook ('scanIPSpace_hook', $ret, $pairlist, $filter_flags); 2291 if (isset ($override)) 2292 $ret = $override; 2293 2294 return $ret; 2295} 2296 2297// Check the range requested for meaningful IPv6 records, build them 2298// into a list and return. Return an empty list if nothing matched. 2299// Both arguments are expected as 16-byte binary IPs. The resulting list 2300// is keyed by 16-byte bynary IPs, items aren't sorted. 2301function scanIPv6Space ($pairlist, $filter_flags = IPSCAN_ANY) 2302{ 2303 $ret = array(); 2304 if (!count ($pairlist)) // this is normal for a network completely divided into smaller parts 2305 return $ret; 2306 $pairlist = reduceIPPairList ($pairlist); 2307 2308 $or = ''; 2309 $whereexpr1 = '('; 2310 $whereexpr2 = '('; 2311 $whereexpr3a = '('; 2312 $whereexpr3b = '('; 2313 $whereexpr4 = '('; 2314 $whereexpr6a = '('; 2315 $whereexpr6b = '('; 2316 $qparams = array(); 2317 foreach ($pairlist as $tmp) 2318 { 2319 $whereexpr1 .= $or . "ip between ? and ?"; 2320 $whereexpr2 .= $or . "ip between ? and ?"; 2321 $whereexpr3a .= $or . "vip between ? and ?"; 2322 $whereexpr3b .= $or . "vip between ? and ?"; 2323 $whereexpr4 .= $or . "rsip between ? and ?"; 2324 $whereexpr6a .= $or . "ip between ? and ?"; 2325 $whereexpr6b .= $or . "l.ip between ? and ?"; 2326 $or = ' or '; 2327 $qparams[] = $tmp['first']; 2328 $qparams[] = $tmp['last']; 2329 } 2330 $whereexpr1 .= ')'; 2331 $whereexpr2 .= ')'; 2332 $whereexpr3a .= ')'; 2333 $whereexpr3b .= ')'; 2334 $whereexpr4 .= ')'; 2335 $whereexpr6a .= ')'; 2336 $whereexpr6b .= ')'; 2337 2338 // 1. collect labels and reservations 2339 if ($filter_flags & IPSCAN_DO_ADDR) 2340 { 2341 $query = "select ip, name, comment, reserved from IPv6Address ". 2342 "where ${whereexpr1} and (reserved = 'yes' or name != '' or comment != '')"; 2343 $result = usePreparedSelectBlade ($query, $qparams); 2344 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2345 { 2346 $ip_bin = $row['ip']; 2347 if (!isset ($ret[$ip_bin])) 2348 $ret[$ip_bin] = constructIPAddress ($ip_bin); 2349 $ret[$ip_bin]['name'] = $row['name']; 2350 $ret[$ip_bin]['comment'] = $row['comment']; 2351 $ret[$ip_bin]['reserved'] = $row['reserved']; 2352 } 2353 unset ($result); 2354 } 2355 2356 // 2. check for allocations 2357 if ($filter_flags & IPSCAN_DO_ALLOCS) 2358 { 2359 if ($filter_flags & IPSCAN_RTR_ONLY) 2360 $whereexpr2 .= " AND (ia.type = 'router' OR ia.type = 'sharedrouter')"; 2361 $query = 2362 "select ia.ip, ia.object_id, ia.name, ia.type, Object.name as object_name " . 2363 "from IPv6Allocation AS ia INNER JOIN Object ON ia.object_id = Object.id where ${whereexpr2} order by ia.type"; 2364 $result = usePreparedSelectBlade ($query, $qparams); 2365 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2366 { 2367 $ip_bin = $row['ip']; 2368 if (!isset ($ret[$ip_bin])) 2369 $ret[$ip_bin] = constructIPAddress ($ip_bin); 2370 $ret[$ip_bin]['allocs'][] = array 2371 ( 2372 'type' => $row['type'], 2373 'name' => $row['name'], 2374 'object_id' => $row['object_id'], 2375 'object_name' => $row['object_name'], 2376 ); 2377 } 2378 unset ($result); 2379 } 2380 2381 // 3a. look for virtual services 2382 if ($filter_flags & IPSCAN_DO_VS) 2383 { 2384 $query = "select id, vip from IPv4VS where ${whereexpr3a}"; 2385 $result = usePreparedSelectBlade ($query, $qparams); 2386 $allRows = $result->fetchAll (PDO::FETCH_ASSOC); 2387 unset ($result); 2388 foreach ($allRows as $row) 2389 { 2390 $ip_bin = $row['vip']; 2391 if (!isset ($ret[$ip_bin])) 2392 $ret[$ip_bin] = constructIPAddress ($ip_bin); 2393 $ret[$ip_bin]['vslist'][] = $row['id']; 2394 } 2395 2396 // 3b. look for virtual service groups 2397 $query = "select vs_id, vip from VSIPs where ${whereexpr3b}"; 2398 $result = usePreparedSelectBlade ($query, $qparams); 2399 $allRows = $result->fetchAll (PDO::FETCH_ASSOC); 2400 unset ($result); 2401 foreach ($allRows as $row) 2402 { 2403 $ip_bin = $row['vip']; 2404 if (!isset ($ret[$ip_bin])) 2405 $ret[$ip_bin] = constructIPAddress ($ip_bin); 2406 $ret[$ip_bin]['vsglist'][] = $row['vs_id']; 2407 } 2408 } 2409 2410 // 4. don't forget about real servers along with pools 2411 if ($filter_flags & IPSCAN_DO_RS) 2412 { 2413 $query = "select rsip, rspool_id from IPv4RS where ${whereexpr4}"; 2414 $result = usePreparedSelectBlade ($query, $qparams); 2415 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2416 { 2417 $ip_bin = $row['rsip']; 2418 if (!isset ($ret[$ip_bin])) 2419 $ret[$ip_bin] = constructIPAddress ($ip_bin); 2420 $ret[$ip_bin]['rsplist'][] = $row['rspool_id']; 2421 } 2422 unset ($result); 2423 } 2424 2425 // 6. collect last log message 2426 if ($filter_flags & IPSCAN_DO_LOG) 2427 { 2428 $query = "select l.ip, l.user, UNIX_TIMESTAMP(l.date) AS time from IPv6Log l INNER JOIN " . 2429 " (SELECT MAX(id) as id FROM IPv6Log WHERE ${whereexpr6a} GROUP BY ip) v USING (id) WHERE ${whereexpr6b}"; 2430 $result = usePreparedSelectBlade ($query, array_merge ($qparams, $qparams)); 2431 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2432 { 2433 $ip_bin = $row['ip']; 2434 if (isset ($ret[$ip_bin])) 2435 $ret[$ip_bin]['last_log'] = array 2436 ( 2437 'user' => $row['user'], 2438 'time' => $row['time'], 2439 ); 2440 } 2441 unset ($result); 2442 } 2443 2444 $override = callHook ('scanIPSpace_hook', $ret, $pairlist, $filter_flags); 2445 if (isset ($override)) 2446 $ret = $override; 2447 2448 return $ret; 2449} 2450 2451function bindIPToObject ($ip_bin, $object_id = 0, $name = '', $type = '') 2452{ 2453 switch (strlen ($ip_bin)) 2454 { 2455 case 4: 2456 $db_ip = ip4_bin2db ($ip_bin); 2457 $table = 'IPv4Allocation'; 2458 $table2 = 'IPv4Address'; 2459 break; 2460 case 16: 2461 $db_ip = $ip_bin; 2462 $table = 'IPv6Allocation'; 2463 $table2 = 'IPv6Address'; 2464 break; 2465 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2466 } 2467 2468 // release IP reservation and/or comment if configured 2469 $release = getConfigVar ('IPV4_AUTO_RELEASE'); 2470 if ($release >= 2) 2471 usePreparedExecuteBlade ("DELETE FROM $table2 WHERE ip = ?", array ($db_ip)); 2472 elseif ($release >= 1) 2473 usePreparedExecuteBlade ("UPDATE $table2 SET reserved = 'no' WHERE ip = ?", array ($db_ip)); 2474 2475 usePreparedInsertBlade 2476 ( 2477 $table, 2478 array ('ip' => $db_ip, 'object_id' => $object_id, 'name' => $name, 'type' => $type) 2479 ); 2480 // store history line 2481 $cell = spotEntity ('object', $object_id); 2482 setDisplayedName ($cell); 2483 addIPLogEntry ($ip_bin, "Binded with ${cell['dname']}, ifname=$name"); 2484} 2485 2486function bindIPv4ToObject ($ip_bin, $object_id = 0, $name = '', $type = '') 2487{ 2488 if (strlen ($ip_bin) != 4) 2489 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2490 return bindIPToObject ($ip_bin, $object_id, $name, $type); 2491} 2492 2493function bindIPv6ToObject ($ip_bin, $object_id = 0, $name = '', $type = '') 2494{ 2495 if (strlen ($ip_bin) != 16) 2496 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2497 return bindIPToObject ($ip_bin, $object_id, $name, $type); 2498} 2499 2500// Universal v4/v6 wrapper around getIPv4AddressNetworkId and getIPv6AddressNetworkId. 2501// Return the id of the smallest IP network containing the given IP address 2502// or NULL, if nothing was found. When finding the covering network for 2503// another network, it is important to filter out matched records with longer 2504// masks (they aren't going to be the right pick). 2505function getIPAddressNetworkId ($ip_bin, $masklen = NULL) 2506{ 2507 switch (strlen ($ip_bin)) 2508 { 2509 case 4: return getIPv4AddressNetworkId ($ip_bin, isset ($masklen) ? $masklen : 32); 2510 case 16: return getIPv6AddressNetworkId ($ip_bin, isset ($masklen) ? $masklen : 128); 2511 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2512 } 2513} 2514 2515// Returns ipv4net or ipv6net entity, or NULL if no spanning network found. 2516// Throws an exception if $ip_bin is not valid binary address; 2517function spotNetworkByIP ($ip_bin, $masklen = NULL) 2518{ 2519 $net_id = getIPAddressNetworkId ($ip_bin, $masklen); 2520 if (! $net_id) 2521 return NULL; 2522 switch (strlen ($ip_bin)) 2523 { 2524 case 4: return spotEntity ('ipv4net', $net_id); 2525 case 16: return spotEntity ('ipv6net', $net_id); 2526 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2527 } 2528} 2529 2530// Return the id of the smallest IPv4 network containing the given IPv4 address 2531// or NULL, if nothing was found. When finding the covering network for 2532// another network, it is important to filter out matched records with longer 2533// masks (they aren't going to be the right pick). 2534function getIPv4AddressNetworkId ($ip_bin, $masklen = 32) 2535{ 2536 $row = callHook ('fetchIPv4AddressNetworkRow', $ip_bin, $masklen); 2537 return $row === NULL ? NULL : $row['id']; 2538} 2539 2540function fetchIPAddressNetworkRow ($ip_bin, $masklen = NULL) 2541{ 2542 switch (strlen ($ip_bin)) 2543 { 2544 case 4: 2545 return callHook ('fetchIPv4AddressNetworkRow', $ip_bin, isset ($masklen) ? $masklen : 32); 2546 case 16: 2547 return callHook ('fetchIPv6AddressNetworkRow', $ip_bin, isset ($masklen) ? $masklen : 128); 2548 default: 2549 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary address"); 2550 } 2551} 2552 2553function fetchIPv4AddressNetworkRow ($ip_bin, $masklen = 32) 2554{ 2555 $ip_db = ip4_bin2db ($ip_bin); 2556 $result = usePreparedSelectBlade 2557 ( 2558 'SELECT id, ip, mask, name, comment FROM IPv4Network ' . 2559 'WHERE ? & (4294967295 >> (32 - mask)) << (32 - mask) = ip AND ip <= ? AND mask < ? ' . 2560 'ORDER BY mask DESC LIMIT 1', 2561 array ($ip_db, $ip_db, $masklen) 2562 ); 2563 return nullIfFalse ($result->fetch (PDO::FETCH_ASSOC)); 2564} 2565 2566// Return the id of the smallest IPv6 network containing the given IPv6 address 2567// ($ip is an instance of IPv4Address class) or NULL, if nothing was found. 2568function getIPv6AddressNetworkId ($ip_bin, $masklen = 128) 2569{ 2570 $row = callHook ('fetchIPv6AddressNetworkRow', $ip_bin, $masklen); 2571 return $row === NULL ? NULL : $row['id']; 2572} 2573 2574function fetchIPv6AddressNetworkRow ($ip_bin, $masklen = 128) 2575{ 2576 $result = usePreparedSelectBlade 2577 ( 2578 'SELECT id, ip, mask, last_ip, name, comment FROM IPv6Network ' . 2579 'WHERE ip <= ? AND last_ip >= ? AND mask < ? ' . 2580 'ORDER BY mask DESC LIMIT 1', 2581 array ($ip_bin, $ip_bin, $masklen) 2582 ); 2583 return nullIfFalse ($result->fetch (PDO::FETCH_ASSOC)); 2584} 2585 2586// This function is actually used not only to update, but also to create records, 2587// that's why ON DUPLICATE KEY UPDATE was replaced by DELETE-INSERT pair 2588// (MySQL 4.0 workaround). 2589function updateAddress ($ip_bin, $name = '', $reserved = 'no', $comment = '') 2590{ 2591 switch (strlen ($ip_bin)) 2592 { 2593 case 4: 2594 $table = 'IPv4Address'; 2595 $db_ip = ip4_bin2db ($ip_bin); 2596 break; 2597 case 16: 2598 $table = 'IPv6Address'; 2599 $db_ip = $ip_bin; 2600 break; 2601 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2602 } 2603 2604 // compute update log message 2605 $result = usePreparedSelectBlade ("SELECT name, comment, reserved FROM $table WHERE ip = ?", array ($db_ip)); 2606 $old_name = ''; 2607 $old_comment = ''; 2608 if ($row = $result->fetch (PDO::FETCH_ASSOC)) 2609 { 2610 $old_name = $row['name']; 2611 $old_comment = $row['comment']; 2612 } 2613 2614 // If the 'comment' argument was specified when this function was called, use it. 2615 // If not, retain the old value. 2616 $comment = (func_num_args () == 4 ) ? $comment : $old_comment; 2617 $new_row = array ('name' => $name, 'comment' => $comment, 'reserved' => $reserved); 2618 $new_row_empty = $name == '' && $comment == '' && $reserved == 'no'; 2619 2620 unset ($result); 2621 $messages = array (); 2622 if ($name != $old_name) 2623 { 2624 if ($name == '') 2625 $messages[] = "name '$old_name' removed"; 2626 elseif ($old_name == '') 2627 $messages[] = "name set to '$name'"; 2628 else 2629 $messages[] = "name changed from '$old_name' to '$name'"; 2630 } 2631 if ($comment != $old_comment) 2632 { 2633 if ($comment == '') 2634 $messages[] = "comment '$old_comment' removed"; 2635 elseif ($old_name == '') 2636 $messages[] = "comment set to '$comment'"; 2637 else 2638 $messages[] = "comment changed from '$old_comment' to '$comment'"; 2639 } 2640 2641 if ($row && ! $new_row_empty && $row == $new_row) 2642 return; 2643 if ($row) 2644 usePreparedDeleteBlade ($table, array ('ip' => $db_ip)); 2645 // INSERT may appear not necessary. 2646 if (! $new_row_empty) 2647 usePreparedInsertBlade 2648 ( 2649 $table, 2650 array ('name' => $name, 'comment' => $comment, 'reserved' => $reserved, 'ip' => $db_ip) 2651 ); 2652 // store history line 2653 if ($messages) 2654 addIPLogEntry ($ip_bin, ucfirst (implode (', ', $messages))); 2655} 2656 2657function updateV4Address ($ip_bin, $name = '', $reserved = 'no', $comment = '') 2658{ 2659 if (strlen ($ip_bin) != 4) 2660 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2661 return updateAddress ($ip_bin, $name, $reserved, $comment); 2662} 2663 2664function updateV6Address ($ip_bin, $name = '', $reserved = 'no', $comment = '') 2665{ 2666 if (strlen ($ip_bin) != 16) 2667 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2668 return updateAddress ($ip_bin, $name, $reserved, $comment); 2669} 2670 2671function updateIPBond ($ip_bin, $object_id=0, $name='', $type='') 2672{ 2673 switch (strlen ($ip_bin)) 2674 { 2675 case 4: return updateIPv4Bond ($ip_bin, $object_id, $name, $type); 2676 case 16: return updateIPv6Bond ($ip_bin, $object_id, $name, $type); 2677 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2678 } 2679} 2680 2681function updateIPv4Bond ($ip_bin, $object_id=0, $name='', $type='') 2682{ 2683 usePreparedUpdateBlade 2684 ( 2685 'IPv4Allocation', 2686 array 2687 ( 2688 'name' => $name, 2689 'type' => $type, 2690 ), 2691 array 2692 ( 2693 'ip' => ip4_bin2db ($ip_bin), 2694 'object_id' => $object_id, 2695 ) 2696 ); 2697} 2698 2699function updateIPv6Bond ($ip_bin, $object_id=0, $name='', $type='') 2700{ 2701 usePreparedUpdateBlade 2702 ( 2703 'IPv6Allocation', 2704 array 2705 ( 2706 'name' => $name, 2707 'type' => $type, 2708 ), 2709 array 2710 ( 2711 'ip' => $ip_bin, 2712 'object_id' => $object_id, 2713 ) 2714 ); 2715} 2716 2717function unbindIPFromObject ($ip_bin, $object_id) 2718{ 2719 switch (strlen ($ip_bin)) 2720 { 2721 case 4: 2722 $table = 'IPv4Allocation'; 2723 $db_ip = ip4_bin2db ($ip_bin); 2724 break; 2725 case 16: 2726 $table = 'IPv6Allocation'; 2727 $db_ip = $ip_bin; 2728 break; 2729 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2730 } 2731 2732 $n_deleted = usePreparedDeleteBlade 2733 ( 2734 $table, 2735 array ('ip' => $db_ip, 'object_id' => $object_id) 2736 ); 2737 if ($n_deleted) 2738 { 2739 // store history line 2740 $cell = spotEntity ('object', $object_id); 2741 setDisplayedName ($cell); 2742 addIPLogEntry ($ip_bin, "Removed from ${cell['dname']}"); 2743 } 2744} 2745 2746function unbindIPv4FromObject ($ip_bin, $object_id) 2747{ 2748 if (strlen ($ip_bin) != 4) 2749 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2750 return unbindIPFromObject ($ip_bin, $object_id); 2751} 2752 2753function unbindIPv6FromObject ($ip_bin, $object_id) 2754{ 2755 if (strlen ($ip_bin) != 16) 2756 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2757 return unbindIPFromObject ($ip_bin, $object_id); 2758} 2759 2760function getIPv4PrefixSearchResult ($terms) 2761{ 2762 $ret = array(); 2763 foreach (array ('name', 'comment') as $column) 2764 { 2765 $tmp = getSearchResultByField 2766 ( 2767 'IPv4Network', 2768 array ('id'), 2769 $column, 2770 $terms, 2771 'ip' 2772 ); 2773 foreach ($tmp as $row) 2774 $ret[$row['id']] = spotEntity ('ipv4net', $row['id']); 2775 } 2776 return $ret; 2777} 2778 2779function getIPv6PrefixSearchResult ($terms) 2780{ 2781 $ret = array(); 2782 foreach (array ('name', 'comment') as $column) 2783 { 2784 $tmp = getSearchResultByField 2785 ( 2786 'IPv6Network', 2787 array ('id'), 2788 $column, 2789 $terms, 2790 'ip' 2791 ); 2792 foreach ($tmp as $row) 2793 $ret[$row['id']] = spotEntity ('ipv6net', $row['id']); 2794 } 2795 return $ret; 2796} 2797 2798function getIPv4AddressSearchResult ($terms) 2799{ 2800 $query = "select ip, name, comment from IPv4Address where "; 2801 $or = ''; 2802 $qparams = array(); 2803 foreach (explode (' ', $terms) as $term) 2804 { 2805 $query .= $or . "name like ? or comment like ?"; 2806 $or = ' or '; 2807 $qparams[] = "%${term}%"; 2808 $qparams[] = "%${term}%"; 2809 } 2810 $result = usePreparedSelectBlade ($query, $qparams); 2811 $ret = array(); 2812 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 2813 { 2814 $ip_bin = ip4_int2bin ($row['ip']); 2815 $row['ip'] = $ip_bin; 2816 $ret[$ip_bin] = $row; 2817 } 2818 return $ret; 2819} 2820 2821function getIPv6AddressSearchResult ($terms) 2822{ 2823 $query = "select ip, name, comment from IPv6Address where "; 2824 $or = ''; 2825 $qparams = array(); 2826 foreach (explode (' ', $terms) as $term) 2827 { 2828 $query .= $or . "name like ? or comment like ?"; 2829 $or = ' or '; 2830 $qparams[] = "%${term}%"; 2831 $qparams[] = "%${term}%"; 2832 } 2833 $result = usePreparedSelectBlade ($query, $qparams); 2834 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC), 'ip'); 2835} 2836 2837function getIPv4RSPoolSearchResult ($terms) 2838{ 2839 $byname = getSearchResultByField 2840 ( 2841 'IPv4RSPool', 2842 array ('id'), 2843 'name', 2844 $terms, 2845 'name' 2846 ); 2847 $ret = array(); 2848 foreach ($byname as $row) 2849 $ret[$row['id']] = spotEntity ('ipv4rspool', $row['id']); 2850 return $ret; 2851} 2852 2853function getIPv4VServiceSearchResult ($terms) 2854{ 2855 $byname = getSearchResultByField 2856 ( 2857 'IPv4VS', 2858 array ('id'), 2859 'name', 2860 $terms, 2861 'vip' 2862 ); 2863 $ret = array(); 2864 foreach ($byname as $row) 2865 $ret[$row['id']] = spotEntity ('ipv4vs', $row['id']); 2866 return $ret; 2867} 2868 2869function getVServiceSearchResult ($terms) 2870{ 2871 $byname = getSearchResultByField 2872 ( 2873 'VS', 2874 array ('id'), 2875 'name', 2876 $terms, 2877 'name' 2878 ); 2879 $ret = array(); 2880 foreach ($byname as $row) 2881 $ret[$row['id']] = spotEntity ('ipvs', $row['id']); 2882 return $ret; 2883} 2884 2885function getAccountSearchResult ($terms) 2886{ 2887 $byUsername = getSearchResultByField 2888 ( 2889 'UserAccount', 2890 array ('user_id'), 2891 'user_name', 2892 $terms, 2893 'user_name' 2894 ); 2895 $byRealname = getSearchResultByField 2896 ( 2897 'UserAccount', 2898 array ('user_id'), 2899 'user_realname', 2900 $terms, 2901 'user_name' 2902 ); 2903 // Merge it together, if duplicates persist, byUsername wins 2904 $ret = array(); 2905 foreach (array ($byRealname, $byUsername) as $array) 2906 foreach ($array as $user) 2907 $ret[$user['user_id']] = spotEntity ('user', $user['user_id']); 2908 return $ret; 2909} 2910 2911function getFileSearchResult ($terms) 2912{ 2913 $byName = getSearchResultByField 2914 ( 2915 'File', 2916 array ('id'), 2917 'name', 2918 $terms, 2919 'name' 2920 ); 2921 $byComment = getSearchResultByField 2922 ( 2923 'File', 2924 array ('id'), 2925 'comment', 2926 $terms, 2927 'name' 2928 ); 2929 // Filter out dupes. 2930 foreach ($byName as $res1) 2931 foreach (array_keys ($byComment) as $key2) 2932 if ($res1['id'] == $byComment[$key2]['id']) 2933 { 2934 unset ($byComment[$key2]); 2935 continue 2; 2936 } 2937 $ret = array(); 2938 foreach (array_merge ($byName, $byComment) as $row) 2939 $ret[$row['id']] = spotEntity ('file', $row['id']); 2940 return $ret; 2941} 2942 2943function getRackSearchResult ($terms) 2944{ 2945 $byName = getSearchResultByField 2946 ( 2947 'Rack', 2948 array ('id'), 2949 'name', 2950 $terms, 2951 'name' 2952 ); 2953 $byComment = getSearchResultByField 2954 ( 2955 'Rack', 2956 array ('id'), 2957 'comment', 2958 $terms, 2959 'name' 2960 ); 2961 $byAssetNo = getSearchResultByField 2962 ( 2963 'Rack', 2964 array ('id'), 2965 'asset_no', 2966 $terms, 2967 'name' 2968 ); 2969 $bySticker = getStickerSearchResults ('Rack', $terms); 2970 // Filter out dupes. 2971 foreach ($byName as $res1) 2972 { 2973 foreach (array_keys ($byComment) as $key2) 2974 if ($res1['id'] == $byComment[$key2]['id']) 2975 unset ($byComment[$key2]); 2976 foreach (array_keys ($byAssetNo) as $key3) 2977 if ($res1['id'] == $byAssetNo[$key3]['id']) 2978 unset ($byAssetNo[$key3]); 2979 foreach (array_keys ($bySticker) as $key4) 2980 if ($res1['id'] == $bySticker[$key4]['id']) 2981 unset ($bySticker[$key4]); 2982 } 2983 $ret = array(); 2984 foreach (array_merge ($byName, $byComment, $byAssetNo, $bySticker) as $row) 2985 $ret[$row['id']] = spotEntity ('rack', $row['id']); 2986 return $ret; 2987} 2988 2989function getRowSearchResult ($terms) 2990{ 2991 $byName = getSearchResultByField 2992 ( 2993 '`Row`', 2994 array ('id'), 2995 'name', 2996 $terms, 2997 'name' 2998 ); 2999 3000 $ret = array(); 3001 foreach ($byName as $row) 3002 $ret[$row['id']] = spotEntity ('row', $row['id']); 3003 return $ret; 3004} 3005 3006function getLocationSearchResult ($terms) 3007{ 3008 $byName = getSearchResultByField 3009 ( 3010 'Location', 3011 array ('id'), 3012 'name', 3013 $terms, 3014 'name' 3015 ); 3016 $byComment = getSearchResultByField 3017 ( 3018 'Location', 3019 array ('id'), 3020 'comment', 3021 $terms, 3022 'name' 3023 ); 3024 $bySticker = getStickerSearchResults ('Location', $terms); 3025 // Filter out dupes. 3026 foreach ($byName as $res1) 3027 { 3028 foreach (array_keys ($byComment) as $key2) 3029 if ($res1['id'] == $byComment[$key2]['id']) 3030 unset ($byComment[$key2]); 3031 foreach (array_keys ($bySticker) as $key3) 3032 if ($res1['id'] == $bySticker[$key3]['id']) 3033 unset ($bySticker[$key3]); 3034 } 3035 $ret = array(); 3036 foreach (array_merge ($byName, $byComment, $bySticker) as $location) 3037 $ret[$location['id']] = spotEntity ('location', $location['id']); 3038 return $ret; 3039} 3040 3041function getVLANSearchResult ($terms) 3042{ 3043 $ret = array(); 3044 $byDescr = getSearchResultByField 3045 ( 3046 'VLANDescription', 3047 array ('domain_id', 'vlan_id'), 3048 'vlan_descr', 3049 $terms 3050 ); 3051 foreach ($byDescr as $row) 3052 { 3053 $vlan_ck = $row['domain_id'] . '-' . $row['vlan_id']; 3054 $row['id'] = $vlan_ck; 3055 $ret[$vlan_ck] = $row; 3056 } 3057 return $ret; 3058} 3059 3060function getSearchResultByField ($tablename, $retcolumns, $scancolumn, $terms, $ordercolumn = '', $exactness = 0) 3061{ 3062 $query = 'SELECT ' . implode (', ', $retcolumns) . " FROM ${tablename} WHERE "; 3063 $qparams = array(); 3064 $pfx = ''; 3065 $pterms = $exactness == 3 ? explode (' ', $terms) : parseSearchTerms ($terms); 3066 foreach ($pterms as $term) 3067 { 3068 switch ($exactness) 3069 { 3070 case 3: 3071 $query .= $pfx . "${scancolumn} REGEXP ?"; 3072 $qparams[] = $term; 3073 break; 3074 case 2: // does this work as expected? 3075 $query .= $pfx . "BINARY ${scancolumn} = ?"; 3076 $qparams[] = $term; 3077 break; 3078 case 1: 3079 $query .= $pfx . "${scancolumn} = ?"; 3080 $qparams[] = $term; 3081 break; 3082 default: 3083 $query .= $pfx . "${scancolumn} LIKE ?"; 3084 $qparams[] = "%${term}%"; 3085 break; 3086 } 3087 $pfx = ' OR '; 3088 } 3089 if ($ordercolumn != '') 3090 $query .= " ORDER BY ${ordercolumn}"; 3091 $result = usePreparedSelectBlade ($query, $qparams); 3092 return $result->fetchAll (PDO::FETCH_ASSOC); 3093} 3094 3095function getObjectSearchResults ($what) 3096{ 3097 $ret = array(); 3098 global $searchfunc; 3099 foreach ($searchfunc['object'] as $method => $func) 3100 foreach ($func ($what) as $objRecord) 3101 { 3102 $ret[$objRecord['id']]['id'] = $objRecord['id']; 3103 $ret[$objRecord['id']][$method] = $objRecord[$method]; 3104 } 3105 foreach (getStickerSearchResults ('RackObject', $what) as $objRecord) 3106 { 3107 $ret[$objRecord['id']]['id'] = $objRecord['id']; 3108 $ret[$objRecord['id']]['by_sticker'] = $objRecord['by_sticker']; 3109 } 3110 return $ret; 3111} 3112 3113function getObjectAttrsSearchResults ($what) 3114{ 3115 $ret = array(); 3116 foreach (array ('name', 'label', 'asset_no', 'comment') as $column) 3117 { 3118 $tmp = getSearchResultByField 3119 ( 3120 'RackObject', 3121 array ('id'), 3122 $column, 3123 $what, 3124 $column 3125 ); 3126 foreach ($tmp as $row) 3127 { 3128 $ret[$row['id']]['id'] = $row['id']; 3129 $ret[$row['id']]['by_attr'][] = $column; 3130 } 3131 } 3132 return $ret; 3133} 3134 3135// Search stickers and return a list of pairs "object_id-attribute_id" 3136// that matched. A partilar object_id could be returned more than once, if it has 3137// multiple matching stickers. Search is only performed on "string" or "dict" attributes. 3138function getStickerSearchResults ($tablename, $what) 3139{ 3140 $attr_types = array(); 3141 $result = usePreparedSelectBlade 3142 ( 3143 'SELECT AV.object_id, AV.attr_id FROM AttributeValue AV ' . 3144 "INNER JOIN ${tablename} O ON AV.object_id = O.id " . 3145 'INNER JOIN Attribute A ON AV.attr_id = A.id ' . 3146 'LEFT JOIN AttributeMap AM ON A.type = "dict" AND AV.object_tid = AM.objtype_id AND AV.attr_id = AM.attr_id ' . 3147 'LEFT JOIN Dictionary D ON AM.chapter_id = D.chapter_id AND AV.uint_value = D.dict_key ' . 3148 'WHERE string_value LIKE ? ' . 3149 'OR (A.type = "dict" AND dict_value LIKE ?) ORDER BY object_id', 3150 array ("%${what}%", "%${what}%") 3151 ); 3152 $ret = array (); 3153 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 3154 { 3155 if (! array_key_exists ($row['attr_id'], $attr_types)) 3156 $attr_types[$row['attr_id']] = getAttrType ($row['attr_id']); 3157 if (in_array ($attr_types[$row['attr_id']], array ('string', 'dict'))) 3158 { 3159 $ret[$row['object_id']]['id'] = $row['object_id']; 3160 $ret[$row['object_id']]['by_sticker'][] = $row['attr_id']; 3161 } 3162 } 3163 return $ret; 3164} 3165 3166// search in port "reservation comment", "label" and "L2 address" columns 3167function getPortSearchResults ($what) 3168{ 3169 $ret = array(); 3170 $ports = getSearchResultByField 3171 ( 3172 'Port', 3173 array ('object_id', 'id', 'reservation_comment'), 3174 'reservation_comment', 3175 $what, 3176 'object_id', 3177 0 3178 ); 3179 foreach ($ports as $port) 3180 { 3181 $ret[$port['object_id']]['id'] = $port['object_id']; 3182 $ret[$port['object_id']]['by_port'][$port['id']] = $port['reservation_comment']; 3183 } 3184 $ports = getSearchResultByField 3185 ( 3186 'Port', 3187 array ('object_id', 'id', 'label'), 3188 'label', 3189 $what, 3190 'object_id', 3191 0 3192 ); 3193 foreach ($ports as $port) 3194 { 3195 $ret[$port['object_id']]['id'] = $port['object_id']; 3196 $ret[$port['object_id']]['by_port'][$port['id']] = $port['label']; 3197 } 3198 try 3199 { 3200 $db_l2address = l2addressForDatabase ($what); 3201 } 3202 catch (InvalidArgException $e) 3203 { 3204 return $ret; 3205 } 3206 $ports = getSearchResultByField 3207 ( 3208 'Port', 3209 array ('object_id', 'id', 'l2address'), 3210 'l2address', 3211 $db_l2address, 3212 'object_id', 3213 2 3214 ); 3215 foreach ($ports as $port) 3216 { 3217 $ret[$port['object_id']]['id'] = $port['object_id']; 3218 $ret[$port['object_id']]['by_port'][$port['id']] = $port['l2address']; 3219 } 3220 return $ret; 3221} 3222 3223// search in IPv4 allocations 3224function getObjectIfacesSearchResults ($what) 3225{ 3226 $ret = array(); 3227 $ifaces4 = getSearchResultByField 3228 ( 3229 'IPv4Allocation', 3230 array ('object_id', 'name'), 3231 'name', 3232 $what, 3233 'object_id' 3234 ); 3235 $ifaces6 = getSearchResultByField 3236 ( 3237 'IPv6Allocation', 3238 array ('object_id', 'name'), 3239 'name', 3240 $what, 3241 'object_id' 3242 ); 3243 foreach (array_merge ($ifaces4, $ifaces6) as $row) 3244 { 3245 $ret[$row['object_id']]['id'] = $row['object_id']; 3246 $ret[$row['object_id']]['by_iface'][] = $row['name']; 3247 } 3248 return $ret; 3249} 3250 3251function getObjectNATSearchResults ($what) 3252{ 3253 $ret = array(); 3254 $ifaces = getSearchResultByField 3255 ( 3256 'IPv4NAT', 3257 array ('object_id', 'description'), 3258 'description', 3259 $what, 3260 'object_id' 3261 ); 3262 foreach ($ifaces as $row) 3263 { 3264 $ret[$row['object_id']]['id'] = $row['object_id']; 3265 $ret[$row['object_id']]['by_nat'][] = $row['description']; 3266 } 3267 return $ret; 3268} 3269 3270function searchCableIDs ($what) 3271{ 3272 $ret = array(); 3273 $result = usePreparedSelectBlade 3274 ( 3275 'SELECT object_id, cable ' . 3276 'FROM Link INNER JOIN Port ON porta = Port.id OR portb = Port.id ' . 3277 'WHERE cable LIKE ? ORDER BY object_id', 3278 array ("%${what}%") 3279 ); 3280 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 3281 { 3282 $ret[$row['object_id']]['id'] = $row['object_id']; 3283 $ret[$row['object_id']]['by_cableid'][] = $row['cable']; 3284 } 3285 return $ret; 3286} 3287 3288// This function returns either port ID or NULL for specified arguments. 3289function getPortIDs ($object_id, $port_name) 3290{ 3291 $result = usePreparedSelectBlade ('SELECT id FROM Port WHERE object_id = ? AND name = ?', array ($object_id, $port_name)); 3292 return reduceSubarraysToColumn ($result->fetchAll (PDO::FETCH_ASSOC), 'id'); 3293} 3294 3295// Search in "FQDN" attribute only, and return object ID, when there is exactly 3296// one result found (and NULL in any other case). 3297function searchByMgmtHostname ($string) 3298{ 3299 $result = usePreparedSelectBlade ('SELECT object_id FROM AttributeValue WHERE attr_id = 3 AND string_value = ? LIMIT 2', array ($string)); 3300 $rows = $result->fetchAll (PDO::FETCH_NUM); 3301 if (count ($rows) == 1) 3302 return $rows[0][0]; 3303 unset ($result); 3304 3305 // second attempt: search for FQDN part, separated by dot. 3306 $result = usePreparedSelectBlade ('SELECT object_id FROM AttributeValue WHERE attr_id = 3 AND string_value LIKE ? LIMIT 2', array ("$string.%")); 3307 $rows = $result->fetchAll (PDO::FETCH_NUM); 3308 return count ($rows) == 1 ? $rows[0][0] : NULL; 3309} 3310 3311// returns an array of object ids 3312function searchByAttrValue ($attr_id, $value) 3313{ 3314 $type = getAttrType ($attr_id); 3315 if (! isset ($type)) 3316 throw new InvalidArgException ('attr_id', $attr_id, "No such attribute"); 3317 3318 switch ($type) 3319 { 3320 case 'string': 3321 $field = 'string_value'; 3322 break; 3323 case 'float': 3324 $field = 'float_value'; 3325 break; 3326 case 'uint': 3327 case 'dict': 3328 case 'date': 3329 $field = 'uint_value'; 3330 break; 3331 default: 3332 throw new InvalidArgException ('type', $type); 3333 } 3334 3335 $result = usePreparedSelectBlade (" 3336SELECT object_id FROM AttributeValue 3337WHERE 3338 attr_id = ? 3339 AND $field = ? 3340", array ($attr_id, $value) 3341); 3342 return $result->fetchAll (PDO::FETCH_COLUMN, 0); 3343} 3344 3345// returns user_id 3346// throws an exception if error occured 3347function commitCreateUserAccount ($username, $realname, $password_hash) 3348{ 3349 if (! preg_match ('/^[0-9a-f]{40}$/', $password_hash)) 3350 throw new InvalidArgException ('password_hash', $password_hash, 'not a 20-octet hex string'); 3351 usePreparedInsertBlade 3352 ( 3353 'UserAccount', 3354 array 3355 ( 3356 'user_name' => $username, 3357 'user_realname' => nullIfEmptyStr ($realname), 3358 'user_password_hash' => $password_hash, 3359 ) 3360 ); 3361 $user_id = lastInsertID(); 3362 lastCreated ('user', $user_id); 3363 return $user_id; 3364} 3365 3366// Update the password only if it is provided. 3367function commitUpdateUserAccount ($id, $new_username, $new_realname, $new_password_hash = '') 3368{ 3369 $set_columns = array 3370 ( 3371 'user_name' => $new_username, 3372 'user_realname' => nullIfEmptyStr ($new_realname), 3373 ); 3374 if ($new_password_hash != '') 3375 { 3376 if (! preg_match ('/^[0-9a-f]{40}$/', $new_password_hash)) 3377 throw new InvalidArgException ('new_password_hash', $new_password_hash, 'not a 20-octet hex string'); 3378 $set_columns['user_password_hash'] = $new_password_hash; 3379 } 3380 $userinfo = spotEntity ('user', $id); 3381 usePreparedUpdateBlade ('UserAccount', $set_columns, array ('user_id' => $id)); 3382 // There is no FK to do this update as user-specific configuration is always local, 3383 // but authentication may be not (the rows in UserConfig may have no matching 3384 // rows in UserAccount). 3385 if ($userinfo['user_name'] != $new_username) 3386 usePreparedUpdateBlade ('UserConfig', array ('user' => $new_username), array('user' => $userinfo['user_name'])); 3387} 3388 3389function commitDeleteUserAccount ($id) 3390{ 3391 if ($id == 1) 3392 throw new InvalidArgException ('id', $id, 'belongs to the administrator account'); 3393 $userinfo = spotEntity ('user', $id); 3394 destroyTagsForEntity ('user', $id); 3395 usePreparedDeleteBlade ('UserConfig', array ('user' => $userinfo['user_name'])); 3396 usePreparedDeleteBlade ('UserAccount', array ('user_id' => $id)); 3397} 3398 3399// This function returns an array of all port type pairs from PortCompat table. 3400function getPortOIFCompat ($ignore_cache = FALSE) 3401{ 3402 static $cache = NULL; 3403 if (! $ignore_cache && isset ($cache)) 3404 return $cache; 3405 3406 $query = 3407 "SELECT type1, type2, POI1.oif_name AS type1name, POI2.oif_name AS type2name FROM " . 3408 "PortCompat AS pc INNER JOIN PortOuterInterface AS POI1 ON pc.type1 = POI1.id " . 3409 "INNER JOIN PortOuterInterface AS POI2 ON pc.type2 = POI2.id " . 3410 'ORDER BY type1name, type2name'; 3411 $result = usePreparedSelectBlade ($query); 3412 $cache = $result->fetchAll (PDO::FETCH_ASSOC); 3413 return $cache; 3414} 3415 3416function addPortOIFCompat ($type1, $type2) 3417{ 3418 return usePreparedExecuteBlade ("INSERT IGNORE INTO PortCompat (type1, type2) VALUES (?, ?),(?, ?)", array ($type1, $type2, $type2, $type1)); 3419} 3420 3421function deletePortOIFCompat ($type1, $type2) 3422{ 3423 return usePreparedExecuteBlade ("DELETE FROM PortCompat WHERE (type1 = ? AND type2 = ?) OR (type1 = ? AND type2 = ?)", array ($type1, $type2, $type2, $type1)); 3424} 3425 3426// Returns an array of all object type pairs from the ObjectParentCompat table. 3427function getObjectParentCompat () 3428{ 3429 $result = usePreparedSelectBlade 3430 ( 3431 'SELECT parent_objtype_id, child_objtype_id, d1.dict_value AS parent_name, d2.dict_value AS child_name, ' . 3432 '(SELECT COUNT(*) FROM EntityLink EL ' . 3433 "LEFT JOIN Object PO ON (EL.parent_entity_id = PO.id AND EL.parent_entity_type = 'object') " . 3434 "LEFT JOIN Object CO ON (EL.child_entity_id = CO.id AND EL.child_entity_type = 'object') " . 3435 'WHERE PO.objtype_id = parent_objtype_id AND CO.objtype_id = child_objtype_id) AS count ' . 3436 'FROM ObjectParentCompat AS pc INNER JOIN Dictionary AS d1 ON pc.parent_objtype_id = d1.dict_key ' . 3437 'INNER JOIN Dictionary AS d2 ON pc.child_objtype_id = d2.dict_key ' . 3438 'ORDER BY parent_name, child_name' 3439 ); 3440 return $result->fetchAll (PDO::FETCH_ASSOC); 3441} 3442 3443// Used to determine if a type of object may have a parent or not 3444function objectTypeMayHaveParent ($objtype_id) 3445{ 3446 $result = usePreparedSelectBlade ('SELECT COUNT(*) FROM ObjectParentCompat WHERE child_objtype_id = ?', array ($objtype_id)); 3447 return $result->fetchColumn() > 0; 3448} 3449 3450// Add a pair to the ObjectParentCompat table. 3451function commitSupplementOPC ($parent_objtype_id, $child_objtype_id) 3452{ 3453 if ($parent_objtype_id <= 0) 3454 throw new InvalidArgException ('parent_objtype_id', $parent_objtype_id); 3455 if ($child_objtype_id <= 0) 3456 throw new InvalidArgException ('child_objtype_id', $child_objtype_id); 3457 usePreparedInsertBlade 3458 ( 3459 'ObjectParentCompat', 3460 array ('parent_objtype_id' => $parent_objtype_id, 'child_objtype_id' => $child_objtype_id) 3461 ); 3462} 3463 3464// Remove a pair from the ObjectParentCompat table. 3465function commitReduceOPC ($parent_objtype_id, $child_objtype_id) 3466{ 3467 usePreparedDeleteBlade ('ObjectParentCompat', array ('parent_objtype_id' => $parent_objtype_id, 'child_objtype_id' => $child_objtype_id)); 3468} 3469 3470function getDictStats () 3471{ 3472 $result = usePreparedSelectBlade 3473 ( 3474 "select Chapter.id as chapter_no, Chapter.name as chapter_name, count(dict_key) as wc from " . 3475 "Chapter left join Dictionary on Chapter.id = Dictionary.chapter_id group by Chapter.id" 3476 ); 3477 $tc = $tw = $uc = $uw = 0; 3478 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 3479 { 3480 $tc++; 3481 $tw += $row['wc'];; 3482 if ($row['chapter_no'] < 10000) 3483 continue; 3484 $uc++; 3485 $uw += $row['wc'];; 3486 } 3487 unset ($result); 3488 $result = usePreparedSelectBlade 3489 ( 3490 "select count(object_id) as attrc from RackObject as ro left join " . 3491 "AttributeValue as av on ro.id = av.object_id group by ro.id" 3492 ); 3493 $to = $ta = $so = 0; 3494 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 3495 { 3496 $to++; 3497 if ($row['attrc'] != 0) 3498 { 3499 $so++; 3500 $ta += $row['attrc']; 3501 } 3502 } 3503 unset ($result); 3504 $ret = array(); 3505 $ret['Total chapters in dictionary'] = $tc; 3506 $ret['Total words in dictionary'] = $tw; 3507 $ret['User chapters'] = $uc; 3508 $ret['Words in user chapters'] = $uw; 3509 $ret['Total objects'] = $to; 3510 $ret['Objects with stickers'] = $so; 3511 $ret['Total stickers attached'] = $ta; 3512 return $ret; 3513} 3514 3515function getIPv4Stats () 3516{ 3517 $ret = array(); 3518 $subject = array(); 3519 $subject[] = array ('q' => 'select count(id) from IPv4Network', 'txt' => 'Networks'); 3520 $subject[] = array ('q' => 'select count(ip) from IPv4Address', 'txt' => 'Addresses commented/reserved'); 3521 $subject[] = array ('q' => 'select count(ip) from IPv4Allocation', 'txt' => 'Addresses allocated'); 3522 $subject[] = array ('q' => 'select count(*) from IPv4NAT', 'txt' => 'NAT rules'); 3523 $subject[] = array ('q' => 'select count(id) from IPv4VS', 'txt' => 'Virtual services'); 3524 $subject[] = array ('q' => 'select count(id) from IPv4RSPool', 'txt' => 'Real server pools'); 3525 $subject[] = array ('q' => 'select count(id) from IPv4RS', 'txt' => 'Real servers'); 3526 $subject[] = array ('q' => 'select count(distinct object_id) from IPv4LB', 'txt' => 'Load balancers'); 3527 3528 foreach ($subject as $item) 3529 { 3530 $result = usePreparedSelectBlade ($item['q']); 3531 $ret[$item['txt']] = $result->fetchColumn(); 3532 unset ($result); 3533 } 3534 return $ret; 3535} 3536 3537function getIPv6Stats () 3538{ 3539 $ret = array(); 3540 $subject = array(); 3541 $subject[] = array ('q' => 'select count(id) from IPv6Network', 'txt' => 'Networks'); 3542 $subject[] = array ('q' => 'select count(ip) from IPv6Address', 'txt' => 'Addresses commented/reserved'); 3543 $subject[] = array ('q' => 'select count(ip) from IPv6Allocation', 'txt' => 'Addresses allocated'); 3544 3545 foreach ($subject as $item) 3546 { 3547 $result = usePreparedSelectBlade ($item['q']); 3548 $ret[$item['txt']] = $result->fetchColumn(); 3549 unset ($result); 3550 } 3551 return $ret; 3552} 3553 3554function getRackspaceStats () 3555{ 3556 $ret = array(); 3557 $subject = array(); 3558 $subject[] = array ('q' => 'select count(*) from `Row`', 'txt' => 'Rows'); 3559 $subject[] = array ('q' => 'select count(*) from Rack', 'txt' => 'Racks'); 3560 $subject[] = array ('q' => 'select avg(height) from Rack', 'txt' => 'Average rack height'); 3561 $subject[] = array ('q' => 'select sum(height) from Rack', 'txt' => 'Total rack units in field'); 3562 3563 foreach ($subject as $item) 3564 { 3565 $result = usePreparedSelectBlade ($item['q']); 3566 $tmp = $result->fetchColumn(); 3567 $ret[$item['txt']] = $tmp == '' ? 0 : $tmp; 3568 unset ($result); 3569 } 3570 return $ret; 3571} 3572 3573function getPortsCount ($object_id) 3574{ 3575 $result = usePreparedSelectBlade ("SELECT COUNT(id) FROM Port WHERE object_id = ?", array($object_id)); 3576 return $result->fetchColumn(); 3577} 3578 3579# FIXME: this function is not used any more 3580function commitDeleteChapter ($chapter_no) 3581{ 3582 usePreparedDeleteBlade ('Chapter', array ('id' => $chapter_no, 'sticky' => 'no')); 3583} 3584 3585function readChapter ($chapter_id, $style = '') 3586{ 3587 $result = usePreparedSelectBlade ('SELECT id FROM Chapter WHERE id = ?', array ($chapter_id)); 3588 if (FALSE === $result->fetchColumn()) 3589 throw new EntityNotFoundException ('chapter', $chapter_id); 3590 unset ($result); 3591 $result = usePreparedSelectBlade 3592 ( 3593 'SELECT dict_key, dict_value AS value FROM Dictionary WHERE chapter_id = ?', 3594 array ($chapter_id) 3595 ); 3596 $chapter = array(); 3597 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 3598 { 3599 if ($style == 'r') 3600 $value = $row['value']; 3601 else 3602 { 3603 parseWikiLink ($row); 3604 $value = ($style == 'a' ? $row['a_value'] : $row['o_value']); 3605 } 3606 $chapter[$row['dict_key']] = $value; 3607 } 3608 // SQL ORDER BY would make no sense in the query above because the rows need 3609 // to be sorted after the wiki link parsing, not before. 3610 // Try to sort after the parsing in the same way as ORDER BY would do. 3611 asort ($chapter, SORT_STRING | SORT_FLAG_CASE); 3612 return $chapter; 3613} 3614 3615// Return refcounters for all given keys of the given chapter. 3616function getChapterRefc ($chapter_id, $keylist) 3617{ 3618 $ret = array_fill_keys ($keylist, 0); 3619 switch ($chapter_id) 3620 { 3621 case CHAP_OBJTYPE: 3622 // ObjectType chapter is referenced by AttributeMap and Object tables 3623 $query = 'SELECT dict_key AS uint_value, (SELECT COUNT(*) FROM AttributeMap WHERE objtype_id = dict_key) + ' . 3624 '(SELECT COUNT(*) FROM Object WHERE objtype_id = dict_key) AS refcnt FROM Dictionary WHERE chapter_id = ?'; 3625 break; 3626 default: 3627 // Find the list of all assigned values of dictionary-addressed attributes, each with 3628 // chapter/word keyed reference counters. 3629 $query = 'SELECT uint_value, count(object_id) AS refcnt 3630 FROM AttributeMap am 3631 INNER JOIN AttributeValue av ON am.attr_id = av.attr_id 3632 INNER JOIN Object o ON o.id = av.object_id 3633 WHERE am.chapter_id = ? AND o.objtype_id = am.objtype_id 3634 GROUP BY uint_value'; 3635 break; 3636 } 3637 $result = usePreparedSelectBlade ($query, array ($chapter_id)); 3638 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 3639 $ret[$row['uint_value']] = $row['refcnt']; 3640 return $ret; 3641} 3642 3643// Return references counter for each of the given OIF IDs. This includes not 3644// only PortCompat and PortInterfaceCompat but also Port even though the latter 3645// is not based on PortOuterInterface directly. 3646function getPortOIFRefc() 3647{ 3648 $result = usePreparedSelectBlade 3649 ( 3650 'SELECT POI.id, (' . 3651 '(SELECT COUNT(*) FROM PortCompat WHERE type1 = id) + ' . 3652 '(SELECT COUNT(*) FROM Port WHERE type = POI.id) + ' . 3653 '(SELECT COUNT(*) FROM PortInterfaceCompat WHERE oif_id = POI.id)' . 3654 ') AS refcnt FROM PortOuterInterface AS POI' 3655 ); 3656 return reduceSubarraysToColumn (reindexById ($result->fetchAll (PDO::FETCH_ASSOC)), 'refcnt'); 3657} 3658 3659function getAttrType ($attr_id) 3660{ 3661 $result = usePreparedSelectBlade ('SELECT type FROM Attribute WHERE id = ?' , array ($attr_id)); 3662 return $result->fetchColumn(); 3663} 3664 3665function getObjTypeAttrMap ($objtype_id) 3666{ 3667 $result = usePreparedSelectBlade ('SELECT id, type, name, chapter_id, sticky FROM Attribute INNER JOIN AttributeMap ON id = attr_id WHERE objtype_id = ?' , array ($objtype_id)); 3668 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC)); 3669} 3670 3671// Return a list of all stickers with sticker map applied. Each sticker records will 3672// list all its ways on the map with refcnt set. 3673// The function is pretty heavy, uses temporary tables and scans many rows, 3674// so try to not use it unless it is really necessary 3675function getAttrMap () 3676{ 3677 static $cached_result = NULL; 3678 if (isset ($cached_result)) 3679 return $cached_result; 3680 3681 $result = usePreparedSelectBlade 3682 ( 3683 'SELECT id, type, name, chapter_id, sticky, (SELECT dict_value FROM Dictionary WHERE dict_key = objtype_id) '. 3684 'AS objtype_name, (SELECT name FROM Chapter WHERE id = chapter_id) ' . 3685 'AS chapter_name, objtype_id, (SELECT COUNT(object_id) FROM AttributeValue AS av INNER JOIN Object AS o ' . 3686 'ON av.object_id = o.id WHERE av.attr_id = Attribute.id AND o.objtype_id = AttributeMap.objtype_id) ' . 3687 'AS refcnt FROM Attribute LEFT JOIN AttributeMap ON id = attr_id ORDER BY Attribute.name, objtype_name' 3688 ); 3689 $ret = array(); 3690 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 3691 { 3692 if (!isset ($ret[$row['id']])) 3693 $ret[$row['id']] = array 3694 ( 3695 'id' => $row['id'], 3696 'type' => $row['type'], 3697 'name' => $row['name'], 3698 'application' => array(), 3699 ); 3700 if ($row['objtype_id'] == '') 3701 continue; 3702 $application = array 3703 ( 3704 'objtype_id' => $row['objtype_id'], 3705 'sticky' => $row['sticky'], 3706 'refcnt' => $row['refcnt'], 3707 ); 3708 if ($row['type'] == 'dict') 3709 { 3710 $application['chapter_no'] = $row['chapter_id']; 3711 $application['chapter_name'] = $row['chapter_name']; 3712 } 3713 $ret[$row['id']]['application'][] = $application; 3714 } 3715 $cached_result = $ret; 3716 return $ret; 3717} 3718 3719function commitSupplementAttrMap ($attr_id, $objtype_id, $chapter_no = NULL) 3720{ 3721 if ($objtype_id <= 0) 3722 throw new InvalidArgException ('objtype_id', $objtype_id); 3723 if (getAttrType ($attr_id) != 'dict') 3724 $chapter_no = NULL; 3725 elseif ($chapter_no === NULL) 3726 throw new InvalidArgException ('chapter_no', '(NULL)', 'must not be NULL for a [D] attribute'); 3727 3728 usePreparedInsertBlade 3729 ( 3730 'AttributeMap', 3731 array 3732 ( 3733 'attr_id' => $attr_id, 3734 'objtype_id' => $objtype_id, 3735 'chapter_id' => $chapter_no 3736 ) 3737 ); 3738} 3739 3740function cacheAllObjectsAttributes() 3741{ 3742 global $object_attribute_cache; 3743 $object_attribute_cache = fetchAttrsForObjects(); 3744} 3745 3746// Fetches a list of attributes for each object in $object_set array. 3747// If $object_set is not set, returns attributes for all objects in DB 3748// Returns an array with object_id keys 3749function fetchAttrsForObjects ($object_set = array()) 3750{ 3751 $ret = array(); 3752 $query = 3753 "select AM.attr_id, A.name as attr_name, A.type as attr_type, C.name as chapter_name, " . 3754 "C.id as chapter_id, AV.uint_value, AV.float_value, AV.string_value, D.dict_value, O.id as object_id from " . 3755 "Object as O left join AttributeMap as AM on O.objtype_id = AM.objtype_id " . 3756 "left join Attribute as A on AM.attr_id = A.id " . 3757 "left join AttributeValue as AV on AV.attr_id = AM.attr_id and AV.object_id = O.id " . 3758 "left join Dictionary as D on D.dict_key = AV.uint_value and AM.chapter_id = D.chapter_id " . 3759 "left join Chapter as C on AM.chapter_id = C.id"; 3760 if (count ($object_set)) 3761 $query .= ' WHERE O.id IN (' . questionMarks (count ($object_set)) . ')'; 3762 3763 $result = usePreparedSelectBlade ($query, $object_set); 3764 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 3765 { 3766 $object_id = $row['object_id']; 3767 if (!array_key_exists ($object_id, $ret)) 3768 $ret[$object_id] = array(); 3769 # Objects with zero attributes also matter due to the LEFT JOIN. Create 3770 # keys for them too to enable negative caching. 3771 if ($row['attr_id'] == NULL) 3772 continue; 3773 3774 $record = array(); 3775 $record['id'] = $row['attr_id']; 3776 $record['name'] = $row['attr_name']; 3777 $record['type'] = $row['attr_type']; 3778 switch ($row['attr_type']) 3779 { 3780 case 'dict': 3781 $record['chapter_id'] = $row['chapter_id']; 3782 $record['chapter_name'] = $row['chapter_name']; 3783 $record['key'] = $row['uint_value']; 3784 // fall through 3785 case 'uint': 3786 case 'float': 3787 case 'string': 3788 $record['value'] = $row[$row['attr_type'] . '_value']; 3789 parseWikiLink ($record); 3790 break; 3791 case 'date': 3792 $record['value'] = $row['uint_value']; 3793 break; 3794 default: 3795 throw new RackTablesError ("unexpected attribute type '${row['attr_type']}'", RackTablesError::INTERNAL); 3796 } 3797 $ret[$object_id][$row['attr_id']] = $record; 3798 } 3799 return $ret; 3800} 3801 3802// This function returns all optional attributes for requested object 3803// as an array of records. 3804// Empty array is returned, if there are no attributes found. 3805function getAttrValues ($object_id) 3806{ 3807 global $object_attribute_cache; 3808 $ret = array(); 3809 if (isset ($object_attribute_cache[$object_id])) 3810 return $object_attribute_cache[$object_id]; 3811 3812 $tmp = fetchAttrsForObjects (array ($object_id)); 3813 if (isset ($tmp[$object_id])) 3814 $ret = $object_attribute_cache[$object_id] = $tmp[$object_id]; 3815 return $ret; 3816} 3817 3818// returns the same data as getAttrValues, but sorts the result array 3819// by the attr_name using SQL server's collation 3820function getAttrValuesSorted ($object_id) 3821{ 3822 static $attr_order = NULL; 3823 if (! isset ($attr_order)) 3824 { 3825 $attr_order = array(); 3826 $result = usePreparedSelectBlade ("SELECT id FROM Attribute ORDER by name"); 3827 $i = 0; 3828 foreach ($result->fetchAll (PDO::FETCH_COLUMN, 0) as $attr_id) 3829 $attr_order[$attr_id] = $i++; 3830 unset ($result); 3831 } 3832 return customKsort (getAttrValues ($object_id), $attr_order); 3833} 3834 3835// FIXME: This function causes RTDatabaseError if the attribute is not 3836// enabled for the given object in AttributeMap. It would be better to detect 3837// the mismatch here and throw InvalidArgException instead. 3838function commitUpdateAttrValue ($object_id, $attr_id, $value = '') 3839{ 3840 global $object_attribute_cache; 3841 if (isset ($object_attribute_cache[$object_id])) 3842 unset ($object_attribute_cache[$object_id]); 3843 $result = usePreparedSelectBlade 3844 ( 3845 'SELECT A.type AS attr_type, AV.attr_id, AV.uint_value, AV.float_value, AV.string_value ' . 3846 'FROM Attribute AS A ' . 3847 'LEFT JOIN AttributeValue AS AV ON A.id = AV.attr_id AND AV.object_id = ? ' . 3848 'WHERE A.id = ?', 3849 array ($object_id, $attr_id) 3850 ); 3851 if (! $row = $result->fetch (PDO::FETCH_ASSOC)) 3852 throw new InvalidArgException ('attr_id', $attr_id, 'No such attribute'); 3853 $attr_type = $row['attr_type']; 3854 unset ($result); 3855 switch ($attr_type) 3856 { 3857 case 'uint': 3858 case 'float': 3859 case 'string': 3860 $column = $attr_type . '_value'; 3861 break; 3862 case 'dict': 3863 case 'date': 3864 $column = 'uint_value'; 3865 break; 3866 default: 3867 throw new RackTablesError ("Unknown attribute type '${attr_type}' for object_id ${object_id} attr_id ${attr_id}", RackTablesError::INTERNAL); 3868 } 3869 if (isset ($row['attr_id'])) 3870 { 3871 // AttributeValue row present in table 3872 $where = array ('object_id' => $object_id, 'attr_id' => $attr_id); 3873 if ($value == '') 3874 usePreparedDeleteBlade ('AttributeValue', $where); 3875 elseif ($row[$column] !== $value) 3876 usePreparedUpdateBlade ('AttributeValue', array ($column => $value), $where); 3877 } 3878 elseif ($value != '') 3879 usePreparedInsertBlade 3880 ( 3881 'AttributeValue', 3882 array 3883 ( 3884 $column => $value, 3885 'object_id' => $object_id, 3886 'object_tid' => getObjectType ($object_id), 3887 'attr_id' => $attr_id, 3888 ) 3889 ); 3890} 3891 3892function convertPDOException ($e) 3893{ 3894 switch ($e->getCode() . '-' . $e->errorInfo[1]) 3895 { 3896 case '23000-1062': 3897 case '23000-1205': 3898 $text = 'such record already exists'; 3899 break; 3900 case '23000-1451': 3901 case '23000-1452': 3902 $text = 'foreign key violation'; 3903 break; 3904 case 'HY000-1205': 3905 $text = 'lock wait timeout'; 3906 break; 3907 case '42000-1142': 3908 return new RTDBTableAccessDenied ($e->getMessage()); 3909 default: 3910 return $e; 3911 } 3912 return new RTDatabaseError ($text); 3913} 3914 3915function assertListOfColumnNames ($column_names) 3916{ 3917 if (! is_array ($column_names)) 3918 throw new InvalidArgException ('column_names', $column_names, 'is not an array'); 3919 if (! count ($column_names)) 3920 throw new InvalidArgException ('column_names', '(empty array)', 'must not be empty'); 3921 foreach ($column_names as $each) 3922 if (! is_string ($each)) 3923 throw new InvalidArgException ('column_names', '(array)', 'contains an item that is not a string'); 3924 elseif ($each == '') 3925 throw new InvalidArgException ('column_names', '(array)', 'contains an empty string'); 3926} 3927 3928// This is a swiss-knife blade to insert a record into a table. 3929// The first argument is table name. 3930// The second argument is an array of "name" => "value" pairs. 3931// returns integer - affected rows count. Throws exception on error 3932function usePreparedInsertBlade ($tablename, $columns) 3933{ 3934 global $dbxlink; 3935 $query = "INSERT INTO `${tablename}` SET " . makeSetSQL (array_keys ($columns)); 3936 // INSERT INTO `table` SET `c1` = ?, `c2` = ?, `c3` = ? 3937 try 3938 { 3939 $prepared = $dbxlink->prepare ($query); 3940 $prepared->execute (array_values ($columns)); 3941 return $prepared->rowCount(); 3942 } 3943 catch (PDOException $e) 3944 { 3945 throw convertPDOException ($e); 3946 } 3947} 3948 3949function makeSetSQL ($column_names) 3950{ 3951 assertListOfColumnNames ($column_names); 3952 $tmp = array(); 3953 // Same syntax works for NULL as well. 3954 foreach ($column_names as $each) 3955 $tmp[] = "`${each}` = ?"; 3956 return implode (', ', $tmp); 3957} 3958 3959function makeWhereSQL ($where_columns, $conjunction, &$params) 3960{ 3961 if (! in_array (strtoupper ($conjunction), array ('AND', '&&', 'OR', '||', 'XOR'))) 3962 throw new InvalidArgException ('conjunction', $conjunction, 'invalid operator'); 3963 assertListOfColumnNames (array_keys ($where_columns)); 3964 $params = array(); 3965 $tmp = array(); 3966 foreach ($where_columns as $colname => $colvalue) 3967 if ($colvalue === NULL) 3968 $tmp[] = "`${colname}` IS NULL"; 3969 elseif (is_array ($colvalue)) 3970 { 3971 // Suppress any string keys to keep array_merge() from overwriting. 3972 $params = array_merge ($params, array_values ($colvalue)); 3973 $tmp[] = sprintf ('`%s` IN(%s)', $colname, questionMarks (count ($colvalue))); 3974 } 3975 else 3976 { 3977 $tmp[] = "`${colname}` = ?"; 3978 $params[] = $colvalue; 3979 } 3980 return implode (" ${conjunction} ", $tmp); 3981} 3982 3983// This swiss-knife blade deletes any number of records from the specified table 3984// using the specified key names and values. 3985// returns integer - affected rows count. Throws exception on error 3986function usePreparedDeleteBlade ($tablename, $columns, $conjunction = 'AND') 3987{ 3988 global $dbxlink; 3989 if (! is_array ($columns) || ! count ($columns)) 3990 throw new InvalidArgException ('columns', '(empty array)', 'in this function DELETE must have WHERE'); 3991 $query = "DELETE FROM `${tablename}` WHERE " . makeWhereSQL ($columns, $conjunction, $where_values); 3992 try 3993 { 3994 $prepared = $dbxlink->prepare ($query); 3995 $prepared->execute ($where_values); 3996 return $prepared->rowCount(); 3997 } 3998 catch (PDOException $e) 3999 { 4000 throw convertPDOException ($e); 4001 } 4002} 4003 4004function usePreparedSelectBlade ($query, $args = array()) 4005{ 4006 global $dbxlink; 4007 try 4008 { 4009 $prepared = $dbxlink->prepare ($query); 4010 $prepared->execute ($args); 4011 return $prepared; 4012 } 4013 catch (PDOException $e) 4014 { 4015 throw convertPDOException ($e); 4016 } 4017} 4018 4019// returns integer - affected rows count. Throws exception on error 4020function usePreparedUpdateBlade ($tablename, $set_columns, $where_columns, $conjunction = 'AND') 4021{ 4022 global $dbxlink; 4023 if (! is_array ($set_columns) || ! count ($set_columns)) 4024 throw new InvalidArgException ('set_columns', '(empty array)', 'UPDATE must have SET'); 4025 if (! is_array ($where_columns) || ! count ($where_columns)) 4026 throw new InvalidArgException ('where_columns', '(empty array)', 'in this function UPDATE must have WHERE'); 4027 $query = "UPDATE `${tablename}` SET " . makeSetSQL (array_keys ($set_columns)); 4028 $query .= ' WHERE ' . makeWhereSQL ($where_columns, $conjunction, $where_values); 4029 try 4030 { 4031 $prepared = $dbxlink->prepare ($query); 4032 $prepared->execute (array_merge (array_values ($set_columns), $where_values)); 4033 return $prepared->rowCount(); 4034 } 4035 catch (PDOException $e) 4036 { 4037 throw convertPDOException ($e); 4038 } 4039} 4040 4041// Prepare and execute the statement with parameters 4042// returns integer - affected rows count. Throws exception on error 4043function usePreparedExecuteBlade ($query, $args = array()) 4044{ 4045 global $dbxlink; 4046 try 4047 { 4048 $prepared = $dbxlink->prepare ($query); 4049 $prepared->execute ($args); 4050 return $prepared->rowCount(); 4051 } 4052 catch (PDOException $e) 4053 { 4054 throw convertPDOException ($e); 4055 } 4056} 4057 4058function loadConfigCache () 4059{ 4060 $result = usePreparedSelectBlade ('SELECT varname, varvalue, vartype, is_hidden, emptyok, description, is_userdefined FROM Config ORDER BY varname'); 4061 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC), 'varname'); 4062} 4063 4064function loadUserConfigCache ($username) 4065{ 4066 if ($username == '') 4067 throw new InvalidArgException ('username', $username, 'must not be empty'); 4068 $result = usePreparedSelectBlade ('SELECT varname, varvalue FROM UserConfig WHERE user = ?', array ($username)); 4069 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC), 'varname'); 4070} 4071 4072function loadRackThumbCache ($rack_id) 4073{ 4074 $result = usePreparedSelectBlade ('SELECT thumb_data FROM RackThumbnail WHERE rack_id = ?', array ($rack_id)); 4075 $row = $result->fetch (PDO::FETCH_ASSOC); 4076 return $row ? $row['thumb_data'] : NULL; 4077} 4078 4079function saveRackThumbCache ($rack_id, $thumb_data) 4080{ 4081 global $dbxlink; 4082 try 4083 { 4084 $query = $dbxlink->prepare ('REPLACE INTO RackThumbnail SET rack_id = ?, thumb_data = ?'); 4085 $query->bindParam (1, $rack_id); 4086 $query->bindParam (2, $thumb_data, PDO::PARAM_LOB); 4087 return $query->execute(); 4088 } 4089 catch (PDOException $e) 4090 { 4091 throw convertPDOException ($e); 4092 } 4093} 4094 4095function executeAutoPorts ($object_id) 4096{ 4097 foreach (getAutoPorts (spotEntity ('object', $object_id)) as $autoport) 4098 commitAddPort ($object_id, $autoport['name'], $autoport['type'], '', ''); 4099} 4100 4101// Return only explicitly listed tags, the rest of the chain will be 4102// generated/deducted later at higher levels. 4103// Result is a chain: randomly indexed taginfo list. 4104function loadEntityTags ($entity_realm, $entity_id) 4105{ 4106 $result = usePreparedSelectBlade 4107 ( 4108 "SELECT tt.id, tag FROM " . 4109 "TagStorage AS ts INNER JOIN TagTree AS tt ON ts.tag_id = tt.id " . 4110 "WHERE entity_realm = ? AND entity_id = ? " . 4111 "ORDER BY tt.tag", 4112 array ($entity_realm, $entity_id) 4113 ); 4114 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC)); 4115} 4116 4117function cacheDictAttrValues() 4118{ 4119 global $dict_attr_cache; 4120 $dict_attr_cache = array(); 4121 $result = usePreparedSelectBlade (" 4122SELECT 4123 AV.attr_id, 4124 AV.uint_value, 4125 AV.object_id 4126FROM 4127 AttributeValue as AV 4128 JOIN Attribute as A ON AV.attr_id = A.id 4129WHERE 4130 A.type = 'dict' 4131 AND uint_value IS NOT NULL 4132"); 4133 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 4134 $dict_attr_cache[$row['object_id']][$row['attr_id']] = $row['uint_value']; 4135} 4136 4137# Universal autotags generator, a complementing function for loadEntityTags(). 4138# Bypass key isn't strictly typed, but interpreted depending on the realm. 4139function generateEntityAutoTags ($cell) 4140{ 4141 global $dict_attr_cache; 4142 $ret = array(); 4143 if (! array_key_exists ('realm', $cell)) 4144 throw new InvalidArgException ('cell', '(array)', 'malformed structure'); 4145 switch ($cell['realm']) 4146 { 4147 case 'location': 4148 $ret[] = array ('tag' => '$locationid_' . $cell['id']); 4149 $ret[] = array ('tag' => '$any_location'); 4150 break; 4151 case 'row': 4152 $ret[] = array ('tag' => '$rowid_' . $cell['id']); 4153 $ret[] = array ('tag' => '$any_row'); 4154 break; 4155 case 'rack': 4156 $ret[] = array ('tag' => '$rackid_' . $cell['id']); 4157 $ret[] = array ('tag' => '$any_rack'); 4158 break; 4159 case 'object': 4160 $ret[] = array ('tag' => '$id_' . $cell['id']); 4161 $ret[] = array ('tag' => '$typeid_' . $cell['objtype_id']); 4162 $ret[] = array ('tag' => '$any_object'); 4163 if ($cell['name'] == '') 4164 $ret[] = array ('tag' => '$nameless'); 4165 if (validTagName ('$cn_' . $cell['name'], TRUE)) 4166 $ret[] = array ('tag' => '$cn_' . $cell['name']); 4167 if ($cell['rack_id'] == '' && $cell['container_id'] == '') 4168 $ret[] = array ('tag' => '$unmounted'); 4169 if (!$cell['nports']) 4170 $ret[] = array ('tag' => '$portless'); 4171 if ($cell['asset_no'] == '') 4172 $ret[] = array ('tag' => '$no_asset_tag'); 4173 if (isset ($cell['8021q_domain_id'])) 4174 { 4175 $ret[] = array ('tag' => '$runs_8021Q'); 4176 $ret[] = array ('tag' => '$8021Q_domain_' . $cell['8021q_domain_id']); 4177 if (isset ($cell['8021q_template_id'])) 4178 $ret[] = array ('tag' => '$8021Q_tpl_' . $cell['8021q_template_id']); 4179 } 4180 4181 # dictionary attribute autotags '$attr_X_Y' 4182 $dict_attrs = array(); 4183 if (isset ($dict_attr_cache)) 4184 { 4185 if (isset ($dict_attr_cache[$cell['id']])) 4186 $dict_attrs = $dict_attr_cache[$cell['id']]; 4187 } 4188 else 4189 { 4190 foreach (getAttrValues($cell['id']) as $attr_id => $attr_record) 4191 if (isset ($attr_record['key'])) 4192 $dict_attrs[$attr_id] = $attr_record['key']; 4193 } 4194 foreach ($dict_attrs as $attr_id => $key) 4195 $ret[] = array ('tag' => "\$attr_{$attr_id}_{$key}"); 4196 break; 4197 case 'ipv4net': 4198 // v4-only rules 4199 $ret[] = array ('tag' => '$ip4net-' . str_replace ('.', '-', $cell['ip']) . '-' . $cell['mask']); 4200 case 'ipv6net': 4201 // common (v4 & v6) rules 4202 $ver = $cell['realm'] == 'ipv4net' ? 4 : 6; 4203 $ret[] = array ('tag' => "\$ip${ver}netid_" . $cell['id']); 4204 $ret[] = array ('tag' => "\$any_ip${ver}net"); 4205 $ret[] = array ('tag' => '$any_net'); 4206 4207 $ret[] = array ('tag' => '$masklen_eq_' . $cell['mask']); 4208 4209 if ($cell['vlanc']) 4210 $ret[] = array ('tag' => '$runs_8021Q'); 4211 4212 foreach ($cell['8021q'] as $vlan_info) 4213 $ret[] = array ('tag' => '$vlan_' . $vlan_info['vlan_id']); 4214 4215 foreach (array_keys ($cell['spare_ranges']) as $mask) 4216 $ret[] = array ('tag' => '$spare_' . $mask); 4217 4218 if ($cell['kidc'] > 0) 4219 $ret[] = array ('tag' => '$aggregate'); 4220 break; 4221 case 'ipv4vs': 4222 $ret[] = array ('tag' => '$ipvsid_' . $cell['id']); 4223 if (strlen ($cell['vip_bin']) == 16) 4224 $ret[] = array ('tag' => '$any_ipv6vs'); 4225 else 4226 $ret[] = array ('tag' => '$any_ipv4vs'); 4227 $ret[] = array ('tag' => '$any_vs'); 4228 if ($cell['refcnt'] == 0) 4229 $ret[] = array ('tag' => '$unused'); 4230 $ret[] = array ('tag' => '$type_' . strtolower ($cell['proto'])); // $type_tcp, $type_udp or $type_mark 4231 break; 4232 case 'ipvs': 4233 $ret[] = array ('tag' => '$ipvsid_' . $cell['id']); 4234 $ret[] = array ('tag' => '$any_vs'); 4235 break; 4236 case 'ipv4rspool': 4237 $ret[] = array ('tag' => '$ipv4rspid_' . $cell['id']); 4238 $ret[] = array ('tag' => '$any_ipv4rsp'); 4239 $ret[] = array ('tag' => '$any_rsp'); 4240 if ($cell['refcnt'] == 0) 4241 $ret[] = array ('tag' => '$unused'); 4242 break; 4243 case 'user': 4244 # {$username_XXX} autotag is generated always, but {$userid_XXX} 4245 # appears only for accounts that exist in local database. 4246 $ret[] = array ('tag' => '$username_' . $cell['user_name']); 4247 if (isset ($cell['user_id'])) 4248 $ret[] = array ('tag' => '$userid_' . $cell['user_id']); 4249 break; 4250 case 'file': 4251 $ret[] = array ('tag' => '$fileid_' . $cell['id']); 4252 $ret[] = array ('tag' => '$any_file'); 4253 break; 4254 case 'vst': 4255 $ret[] = array ('tag' => '$vstid_' . $cell['id']); 4256 $ret[] = array ('tag' => '$any_vst'); 4257 break; 4258 default: 4259 throw new InvalidArgException ('cell', '(array)', 'this input does not belong here'); 4260 break; 4261 } 4262 # {$untagged} doesn't apply to users 4263 switch ($cell['realm']) 4264 { 4265 case 'rack': 4266 case 'object': 4267 case 'ipv4net': 4268 case 'ipv6net': 4269 case 'ipv4vs': 4270 case 'ipv4rspool': 4271 case 'file': 4272 case 'vst': 4273 if (!count ($cell['etags'])) 4274 $ret[] = array ('tag' => '$untagged'); 4275 break; 4276 default: 4277 break; 4278 } 4279 return $ret; 4280} 4281 4282// Return a tag chain with all DB tags on it. ORDER BY is important as it 4283// enables treeFromList() and sortTree() to do their jobs properly. Doing 4284// the same sorting in PHP is possible but complicated and may deliver 4285// results different from MySQL. 4286function getTagList ($extra_sql = '') 4287{ 4288 $result = usePreparedSelectBlade 4289 ( 4290 'SELECT id, parent_id, is_assignable, tag, LPAD(HEX(color), 6, "0") AS color, description ' . 4291 "FROM TagTree ORDER BY tag ${extra_sql}" 4292 ); 4293 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC)); 4294} 4295 4296function getTagUsage ($ignore_cache = FALSE) 4297{ 4298 global $taglist; 4299 static $ret = NULL; 4300 if (isset ($ret) && ! $ignore_cache) 4301 return $ret; 4302 $ret = array(); 4303 4304 foreach ($taglist as $id => $taginfo) 4305 $ret[$id] = $taginfo + array('refcnt' => array('total' => 0)); 4306 4307 $result = usePreparedSelectBlade ("SELECT entity_realm AS realm, tag_id AS id, count(*) AS refcnt FROM TagStorage GROUP BY tag_id, entity_realm"); 4308 while ($row = $result->fetch(PDO::FETCH_ASSOC)) 4309 { 4310 $ret[$row['id']]['refcnt'][$row['realm']] = $row['refcnt']; 4311 $ret[$row['id']]['refcnt']['total'] += $row['refcnt']; 4312 // introduce the 'pseudo'-realm 'ipnet' which combines 'ipv4net' and 'ipv6net' realms. 4313 if ($row['realm'] == 'ipv4net' || $row['realm'] == 'ipv6net') 4314 if (isset ($ret[$row['id']]['refcnt']['ipnet'])) 4315 $ret[$row['id']]['refcnt']['ipnet'] += $row['refcnt']; 4316 else 4317 $ret[$row['id']]['refcnt']['ipnet'] = $row['refcnt']; 4318 } 4319 return $ret; 4320} 4321 4322// Drop the whole chain stored. 4323function destroyTagsForEntity ($entity_realm, $entity_id) 4324{ 4325 usePreparedDeleteBlade ('TagStorage', array ('entity_realm' => $entity_realm, 'entity_id' => $entity_id)); 4326 if ($entity_realm == 'rack') 4327 usePreparedDeleteBlade ('RackThumbnail', array ('rack_id' => $entity_id)); 4328 elseif ($entity_realm == 'object' && count ($rack_ids = getResidentRackIDs ($entity_id))) 4329 usePreparedDeleteBlade ('RackThumbnail', array ('rack_id' => $rack_ids)); 4330} 4331 4332// Drop only one record. This operation doesn't involve retossing other tags, unlike when adding. 4333// FIXME: this function could be used by 3rd-party scripts, dismiss it at some later point, 4334// but not now. 4335function deleteTagForEntity ($entity_realm, $entity_id, $tag_id) 4336{ 4337 return usePreparedDeleteBlade ('TagStorage', array ('entity_realm' => $entity_realm, 'entity_id' => $entity_id, 'tag_id' => $tag_id)); 4338} 4339 4340// A tag's parent may not be one of its children, the tag itself or a tag 4341// that does not belong to the forest of rooted trees because of a cycle. 4342function commitUpdateTag ($tag_id, $tag_name, $parent_id, $is_assignable, $color = NULL) 4343{ 4344 global $dbxlink; 4345 global $taglist; 4346 $dbxlink->beginTransaction(); 4347 try 4348 { 4349 // Use the copy from within the transaction. 4350 $local_taglist = addTraceToNodes (getTagList ('FOR UPDATE')); 4351 assertValidParentId ($local_taglist, $tag_id, $parent_id); 4352 $db_color = HTMLColorForDatabase ($color); 4353 usePreparedUpdateBlade 4354 ( 4355 'TagTree', 4356 array 4357 ( 4358 'tag' => $tag_name, 4359 'parent_id' => nullIfZero ($parent_id), 4360 'is_assignable' => $is_assignable, 4361 'color' => $db_color 4362 ), 4363 array ('id' => $tag_id) 4364 ); 4365 if ($db_color !== HTMLColorForDatabase ($local_taglist[$tag_id]['color'])) 4366 // Remove only those rack thumbnails that include any objects tagged with 4367 // the current tag (this does not include zero-U associations) or that 4368 // represent racks tagged with the current tag. 4369 usePreparedExecuteBlade 4370 ( 4371 'DELETE FROM RackThumbnail WHERE ' . 4372 'rack_id IN(SELECT DISTINCT rack_id FROM TagStorage INNER JOIN RackSpace ON entity_id = object_id ' . 4373 ' WHERE entity_realm = "object" AND tag_id = ?)' . 4374 'OR rack_id IN(SELECT entity_id FROM TagStorage WHERE entity_realm = "rack" AND tag_id = ?)', 4375 array ($tag_id, $tag_id) 4376 ); 4377 $taglist = addTraceToNodes (getTagList()); 4378 $dbxlink->commit(); 4379 } 4380 catch (PDOException $pe) 4381 { 4382 $dbxlink->rollBack(); 4383 throw convertPDOException ($pe); 4384 } 4385 catch (Exception $e) 4386 { 4387 $dbxlink->rollBack(); 4388 throw $e; 4389 } 4390} 4391 4392// Push a record into TagStorage unconditionally. 4393function addTagForEntity ($realm, $entity_id, $tag_id) 4394{ 4395 global $SQLSchema; 4396 global $remote_username; 4397 if (! array_key_exists ($realm, $SQLSchema)) 4398 throw new InvalidArgException ('realm', $realm); 4399 // spotEntity ($realm, $entity_id) would be a more expensive way 4400 // to validate two parameters 4401 usePreparedExecuteBlade 4402 ( 4403 'INSERT INTO TagStorage (entity_realm, entity_id, tag_id, user, date) VALUES (?, ?, ?, ?, NOW())', 4404 array 4405 ( 4406 $realm, 4407 $entity_id, 4408 $tag_id, 4409 $remote_username, 4410 ) 4411 ); 4412} 4413 4414// When $replace == FALSE: 4415// Add records into TagStorage if this makes sense (IOW, they don't appear 4416// on the implicit list already). Then remove any other records that 4417// appear on the "implicit" side of the chain. This will make sure 4418// that both the tag base is still minimal and all requested tags appear on 4419// the resulting tag chain. 4420// 4421// When $replace == TRUE: 4422// Replace the current tag chain with the given tag chain (same implicit tag 4423// suppression applies as above). 4424// 4425// Return TRUE if any changes were committed. 4426function rebuildTagChainForEntity ($realm, $entity_id, $extrachain = array(), $replace = FALSE) 4427{ 4428 // Put the current explicit sub-chain into a buffer and merge all tags from 4429 // the extra chain that aren't there yet. 4430 $oldchain = array(); 4431 $newchain = array(); 4432 foreach (loadEntityTags ($realm, $entity_id) as $oldtag) 4433 $oldchain[$oldtag['id']] = $oldtag; 4434 $tmpchain = $replace ? array() : $oldchain; 4435 foreach ($extrachain as $extratag) 4436 $tmpchain[$extratag['id']] = $extratag; 4437 // minimize the working buffer 4438 foreach (getExplicitTagsOnly ($tmpchain) as $taginfo) 4439 $newchain[$taginfo['id']] = $taginfo; 4440 4441 if (array_values_same (array_keys ($oldchain), array_keys ($newchain))) 4442 return FALSE; 4443 foreach (array_diff (array_keys($oldchain), array_keys ($newchain)) as $tag_id) 4444 deleteTagForEntity ($realm, $entity_id, $tag_id); 4445 foreach (array_diff (array_keys($newchain), array_keys ($oldchain)) as $tag_id) 4446 addTagForEntity ($realm, $entity_id, $tag_id); 4447 4448 // remove Rack thumbnail if Rack or Object tag changes 4449 if ($realm == 'rack') 4450 usePreparedDeleteBlade ('RackThumbnail', array ('rack_id' => $entity_id)); 4451 elseif ($realm == 'object' && count ($rack_ids = getResidentRackIDs ($entity_id))) 4452 usePreparedDeleteBlade ('RackThumbnail', array ('rack_id' => $rack_ids)); 4453 4454 return TRUE; 4455} 4456 4457// Replace any tags already assigned to the target entity. 4458function produceTagsForNewRecord ($realm, $tagidlist, $record_id) 4459{ 4460 rebuildTagChainForEntity ($realm, $record_id, buildTagChainFromIds ($tagidlist), TRUE); 4461} 4462 4463function createIPv4Prefix ($range = '', $name = '', $is_connected = FALSE, $taglist = array(), $vlan_ck = NULL) 4464{ 4465 // $range is in x.x.x.x/x format, split into ip/mask vars 4466 $rangeArray = explode('/', $range); 4467 if (count ($rangeArray) != 2) 4468 throw new InvalidArgException ('range', $range, 'Invalid IPv4 prefix'); 4469 $ip = $rangeArray[0]; 4470 $mask = $rangeArray[1]; 4471 $forbidden_ranges = array 4472 ( 4473 constructIPRange ("\0\0\0\0", 8), # 0.0.0.0/8 4474 constructIPRange ("\xF0\0\0\0", 4), # 240.0.0.0/4 4475 ); 4476 $net = constructIPRange (ip4_parse ($ip), $mask); 4477 foreach ($forbidden_ranges as $invalid_net) 4478 if (IPNetContainsOrEqual ($invalid_net, $net)) 4479 throw new InvalidArgException ('range', $range, 'Reserved IPv4 network'); 4480 4481 usePreparedInsertBlade 4482 ( 4483 'IPv4Network', 4484 array 4485 ( 4486 'ip' => ip4_bin2db ($net['ip_bin']), 4487 'mask' => $mask, 4488 'name' => $name 4489 ) 4490 ); 4491 $network_id = lastInsertID(); 4492 lastCreated ('ipv4net', $network_id); 4493 4494 if ($is_connected && $mask < 31) 4495 { 4496 updateV4Address ($net['ip_bin'], 'network', 'yes'); 4497 updateV4Address (ip_last ($net), 'broadcast', 'yes'); 4498 } 4499 produceTagsForNewRecord ('ipv4net', $taglist, $network_id); 4500 if ($vlan_ck != NULL) 4501 commitSupplementVLANIPv4 ($vlan_ck, $network_id); 4502 return $network_id; 4503} 4504 4505function createIPv6Prefix ($range = '', $name = '', $is_connected = FALSE, $taglist = array(), $vlan_ck = NULL) 4506{ 4507 // $range is in aaa0:b::c:d/x format, split into ip/mask vars 4508 $rangeArray = explode ('/', $range); 4509 if (count ($rangeArray) != 2) 4510 throw new InvalidArgException ('range', $range, 'Invalid IPv6 prefix'); 4511 $ip = $rangeArray[0]; 4512 $mask = $rangeArray[1]; 4513 $net = constructIPRange (ip6_parse ($ip), $mask); 4514 usePreparedInsertBlade 4515 ( 4516 'IPv6Network', 4517 array 4518 ( 4519 'ip' => $net['ip_bin'], 4520 'last_ip' => ip_last ($net), 4521 'mask' => $mask, 4522 'name' => $name 4523 ) 4524 ); 4525 $network_id = lastInsertID(); 4526 lastCreated ('ipv6net', lastInsertID()); 4527 4528 # RFC3513 2.6.1 - Subnet-Router anycast 4529 if ($is_connected) 4530 updateV6Address ($net['ip_bin'], 'Subnet-Router anycast', 'yes'); 4531 produceTagsForNewRecord ('ipv6net', $taglist, $network_id); 4532 if ($vlan_ck != NULL) 4533 commitSupplementVLANIPv6 ($vlan_ck, $network_id); 4534 return $network_id; 4535} 4536 4537// FIXME: This function doesn't wipe relevant records from IPv4Address table. 4538function destroyIPv4Prefix ($id) 4539{ 4540 releaseFiles ('ipv4net', $id); 4541 usePreparedDeleteBlade ('IPv4Network', array ('id' => $id)); 4542 destroyTagsForEntity ('ipv4net', $id); 4543} 4544 4545// FIXME: This function doesn't wipe relevant records from IPv6Address table. 4546function destroyIPv6Prefix ($id) 4547{ 4548 releaseFiles ('ipv6net', $id); 4549 usePreparedDeleteBlade ('IPv6Network', array ('id' => $id)); 4550 destroyTagsForEntity ('ipv6net', $id); 4551} 4552 4553function loadScript ($name) 4554{ 4555 $result = usePreparedSelectBlade ("SELECT script_text FROM Script WHERE script_name = ?", array ($name)); 4556 return nullIfFalse ($result->fetchColumn()); 4557} 4558 4559function saveScript ($name, $text) 4560{ 4561 if ($name == '') 4562 throw new InvalidArgException ('name', $name, 'must not be empty'); 4563 if (!isset ($text)) 4564 return deleteScript ($name); 4565 return usePreparedExecuteBlade 4566 ( 4567 'INSERT INTO Script (script_name, script_text) VALUES (?, ?) ' . 4568 'ON DUPLICATE KEY UPDATE script_text=?', 4569 array ($name, $text, $text) 4570 ); 4571} 4572 4573function deleteScript ($name) 4574{ 4575 if ($name == '') 4576 throw new InvalidArgException ('name', $name); 4577 return usePreparedDeleteBlade ('Script', array ('script_name' => $name)); 4578} 4579 4580function newPortForwarding ($object_id, $localip_bin, $localport, $remoteip_bin, $remoteport, $proto, $description) 4581{ 4582 if (NULL === getIPv4AddressNetworkId ($localip_bin)) 4583 throw new InvalidArgException ('localip_bin', ip4_format ($localip_bin), 'address does not belong to a known network'); 4584 if (NULL === getIPv4AddressNetworkId ($remoteip_bin)) 4585 throw new InvalidArgException ('remoteip_bin', ip4_format ($remoteip_bin), 'address does not belong to a known network'); 4586 if ( $proto == "ALL" ) 4587 { 4588 $localport = 0; 4589 $remoteport = 0; 4590 } 4591 else 4592 { 4593 if ($localport <= 0 || $localport >= 65536) 4594 throw new InvalidArgException ('localport', $localport, 'Invaild port'); 4595 if ($remoteport <= 0 || $remoteport >= 65536) 4596 throw new InvalidArgException ('remoteport', $remoteport, 'Invaild port'); 4597 } 4598 4599 return usePreparedInsertBlade 4600 ( 4601 'IPv4NAT', 4602 array 4603 ( 4604 'object_id' => $object_id, 4605 'localip' => ip4_bin2db ($localip_bin), 4606 'localport' => $localport, 4607 'remoteip' => ip4_bin2db ($remoteip_bin), 4608 'remoteport' => $remoteport, 4609 'proto' => $proto, 4610 'description' => $description, 4611 ) 4612 ); 4613} 4614 4615function deletePortForwarding ($object_id, $localip_bin, $localport, $remoteip_bin, $remoteport, $proto) 4616{ 4617 return usePreparedDeleteBlade 4618 ( 4619 'IPv4NAT', 4620 array 4621 ( 4622 'object_id' => $object_id, 4623 'localip' => ip4_bin2db ($localip_bin), 4624 'localport' => $localport, 4625 'remoteip' => ip4_bin2db ($remoteip_bin), 4626 'remoteport' => $remoteport, 4627 'proto' => $proto, 4628 ) 4629 ); 4630} 4631 4632function updatePortForwarding ($object_id, $localip_bin, $localport, $remoteip_bin, $remoteport, $proto, $description) 4633{ 4634 return usePreparedUpdateBlade 4635 ( 4636 'IPv4NAT', 4637 array ('description' => $description), 4638 array 4639 ( 4640 'object_id' => $object_id, 4641 'localip' => ip4_bin2db ($localip_bin), 4642 'localport' => $localport, 4643 'remoteip' => ip4_bin2db ($remoteip_bin), 4644 'remoteport' => $remoteport, 4645 'proto' => $proto, 4646 ) 4647 ); 4648} 4649 4650function getNATv4CountForObject ($object_id) 4651{ 4652 $result = usePreparedSelectBlade ('SELECT COUNT(*) FROM IPv4NAT WHERE object_id = ?', array ($object_id)); 4653 return $result->fetchColumn(); 4654} 4655 4656function getNATv4ForObject ($object_id) 4657{ 4658 $ret = array(); 4659 $ret['out'] = array(); 4660 $ret['in'] = array(); 4661 $result = usePreparedSelectBlade 4662 ( 4663 "select ". 4664 "proto, ". 4665 "localip, ". 4666 "localport, ". 4667 "remoteip, ". 4668 "remoteport, ". 4669 "ipa1.name as local_addr_name, " . 4670 "ipa2.name as remote_addr_name, " . 4671 "description ". 4672 "from IPv4NAT ". 4673 "left join IPv4Address as ipa1 on IPv4NAT.localip = ipa1.ip " . 4674 "left join IPv4Address as ipa2 on IPv4NAT.remoteip = ipa2.ip " . 4675 "where object_id=? ". 4676 "order by localip, localport, proto, remoteip, remoteport", 4677 array ($object_id) 4678 ); 4679 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 4680 { 4681 $row['localip_bin'] = ip4_int2bin ($row['localip']); 4682 $row['localip'] = ip_format ($row['localip_bin']); 4683 $row['remoteip_bin'] = ip4_int2bin ($row['remoteip']); 4684 $row['remoteip'] = ip_format ($row['remoteip_bin']); 4685 $ret['out'][] = $row; 4686 } 4687 unset ($result); 4688 4689 $result = usePreparedSelectBlade 4690 ( 4691 "select ". 4692 "proto, ". 4693 "localip, ". 4694 "localport, ". 4695 "remoteip, ". 4696 "remoteport, ". 4697 "IPv4NAT.object_id as object_id, ". 4698 "Object.name as object_name, ". 4699 "description ". 4700 "from ((IPv4NAT join IPv4Allocation on remoteip=IPv4Allocation.ip) join Object on IPv4NAT.object_id=Object.id) ". 4701 "where IPv4Allocation.object_id=? ". 4702 "order by remoteip, remoteport, proto, localip, localport", 4703 array ($object_id) 4704 ); 4705 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 4706 { 4707 $row['localip_bin'] = ip4_int2bin ($row['localip']); 4708 $row['localip'] = ip_format ($row['localip_bin']); 4709 $row['remoteip_bin'] = ip4_int2bin ($row['remoteip']); 4710 $row['remoteip'] = ip_format ($row['remoteip_bin']); 4711 $ret['in'][] = $row; 4712 } 4713 return $ret; 4714} 4715 4716// Return a list of files that are not linked to the specified record. This list 4717// will be used by printSelect(). 4718function getAllUnlinkedFiles ($entity_type, $entity_id) 4719{ 4720 $result = usePreparedSelectBlade 4721 ( 4722 'SELECT id, name, SUBSTR(comment FROM 1 FOR 128) AS comment FROM File ' . 4723 'WHERE id NOT IN (SELECT file_id FROM FileLink WHERE entity_type = ? AND entity_id = ?) ' . 4724 'ORDER BY name, id', 4725 array ($entity_type, $entity_id) 4726 ); 4727 return $result->fetchAll (PDO::FETCH_ASSOC); 4728} 4729 4730// FIXME: return a standard cell list, so upper layer can iterate over 4731// it conveniently. 4732function getFilesOfEntity ($entity_type, $entity_id) 4733{ 4734 $result = usePreparedSelectBlade 4735 ( 4736 'SELECT FileLink.file_id as id, FileLink.id AS link_id, name, type, size, ctime, mtime, atime, comment ' . 4737 'FROM FileLink LEFT JOIN File ON FileLink.file_id = File.id ' . 4738 'WHERE FileLink.entity_type = ? AND FileLink.entity_id = ? ORDER BY name', 4739 array ($entity_type, $entity_id) 4740 ); 4741 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC)); 4742} 4743 4744function getFile ($file_id) 4745{ 4746 $result = usePreparedSelectBlade 4747 ( 4748 'SELECT id, name, type, size, ctime, mtime, atime, contents, comment ' . 4749 'FROM File WHERE id = ?', 4750 array ($file_id) 4751 ); 4752 if (($row = $result->fetch (PDO::FETCH_ASSOC)) == NULL) 4753 // FIXME: isn't this repeating the code already in spotEntity()? 4754 throw new EntityNotFoundException ('file', $file_id); 4755 return $row; 4756} 4757 4758function getFileCache ($file_id) 4759{ 4760 $result = usePreparedSelectBlade 4761 ( 4762 'SELECT File.thumbnail FROM File ' . 4763 'WHERE File.id = ? and File.thumbnail IS NOT NULL', 4764 array ($file_id) 4765 ); 4766 return $result->fetchColumn(); 4767} 4768 4769function commitAddFileCache ($file_id, $contents) 4770{ 4771 global $dbxlink; 4772 try 4773 { 4774 $query = $dbxlink->prepare ('UPDATE File SET thumbnail = ? WHERE id = ?'); 4775 $query->bindParam (1, $contents, PDO::PARAM_LOB); 4776 $query->bindParam (2, $file_id); 4777 return $query->execute(); 4778 } 4779 catch (PDOException $e) 4780 { 4781 throw convertPDOException ($e); 4782 } 4783} 4784 4785function getFileLinks ($file_id) 4786{ 4787 $result = usePreparedSelectBlade 4788 ( 4789 'SELECT id, entity_type, entity_id FROM FileLink ' . 4790 'WHERE file_id = ? ORDER BY entity_type, entity_id', 4791 array ($file_id) 4792 ); 4793 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC)); 4794} 4795 4796function getFileStats () 4797{ 4798 $result = usePreparedSelectBlade ('SELECT entity_type, COUNT(*) AS count FROM FileLink GROUP BY entity_type'); 4799 $ret = array(); 4800 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 4801 if ($row['count'] > 0) 4802 $ret["Links in realm '${row['entity_type']}'"] = $row['count']; 4803 unset ($result); 4804 4805 // Find number of files without any linkage 4806 $result = usePreparedSelectBlade 4807 ( 4808 'SELECT COUNT(*) ' . 4809 'FROM File ' . 4810 'WHERE id NOT IN (SELECT file_id FROM FileLink)' 4811 ); 4812 $ret["Unlinked files"] = $result->fetchColumn (); 4813 unset ($result); 4814 4815 // Find total number of files 4816 $result = usePreparedSelectBlade ('SELECT COUNT(*) FROM File'); 4817 $ret["Total files"] = $result->fetchColumn (); 4818 4819 return $ret; 4820} 4821 4822// returns file id 4823// throws an exception if error occured 4824function commitAddFile ($name, $type, $contents, $comment) 4825{ 4826 global $dbxlink; 4827 switch ($type) 4828 { 4829 case 'image/x-png': 4830 $type = 'image/png'; 4831 break; 4832 case 'image/pjpeg': 4833 $type = 'image/jpeg'; 4834 break; 4835 default: 4836 } 4837 try 4838 { 4839 # File.size has no default value, set to 0 with MySQL strict mode in mind. 4840 $query = $dbxlink->prepare ('INSERT INTO File (name, type, size, ctime, mtime, atime, contents, comment) VALUES (?, ?, 0, NOW(), NOW(), NOW(), ?, ?)'); 4841 $query->bindParam (1, $name); 4842 $query->bindParam (2, $type); 4843 $query->bindParam (3, $contents, PDO::PARAM_LOB); 4844 $query->bindParam (4, $comment); 4845 $query->execute(); 4846 $file_id = lastInsertID(); 4847 usePreparedExecuteBlade ('UPDATE File SET size = LENGTH(contents) WHERE id = ?', array ($file_id)); 4848 return $file_id; 4849 } 4850 catch (PDOException $e) 4851 { 4852 throw convertPDOException ($e); 4853 } 4854} 4855 4856function commitReplaceFile ($file_id = 0, $contents) 4857{ 4858 global $dbxlink; 4859 $query = $dbxlink->prepare('UPDATE File SET mtime = NOW(), contents = ?, size = LENGTH(contents), thumbnail = NULL WHERE id = ?'); 4860 $query->bindParam(1, $contents, PDO::PARAM_LOB); 4861 $query->bindParam(2, $file_id); 4862 4863 try 4864 { 4865 return $query->execute(); 4866 } 4867 catch (PDOException $e) 4868 { 4869 throw convertPDOException ($e); 4870 } 4871} 4872 4873function commitUnlinkFile ($link_id) 4874{ 4875 usePreparedDeleteBlade ('FileLink', array ('id' => $link_id)); 4876} 4877 4878function commitDeleteFile ($file_id) 4879{ 4880 destroyTagsForEntity ('file', $file_id); 4881 usePreparedDeleteBlade ('File', array ('id' => $file_id)); 4882} 4883 4884function getChapterList () 4885{ 4886 $result = usePreparedSelectBlade 4887 ( 4888 'SELECT id, sticky, name, count(chapter_id) as wordc ' . 4889 'FROM Chapter LEFT JOIN Dictionary ON Chapter.id = chapter_id ' . 4890 'GROUP BY id ORDER BY name' 4891 ); 4892 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC)); 4893} 4894 4895// Return file id by file name. 4896function findFileByName ($filename) 4897{ 4898 $result = usePreparedSelectBlade ('SELECT id FROM File WHERE name = ?', array ($filename)); 4899 return nullIfFalse ($result->fetchColumn()); 4900} 4901 4902function fetchLDAPCacheRow ($username, $extrasql = '') 4903{ 4904 $result = usePreparedSelectBlade 4905 ( 4906 'SELECT TIMESTAMPDIFF(SECOND, first_success, now()) AS success_age, ' . 4907 'TIMESTAMPDIFF(SECOND, last_retry, now()) AS retry_age, displayed_name, memberof, successful_hash ' . 4908 'FROM LDAPCache WHERE presented_username = ? ' . $extrasql, 4909 array ($username) 4910 ); 4911 $row = $result->fetch (PDO::FETCH_ASSOC); 4912 if ($row) 4913 { 4914 $members = unserialize (base64_decode ($row['memberof'])); 4915 $row['memberof'] = is_array ($members) ? $members : array(); 4916 } 4917 return $row; 4918} 4919 4920// locks LDAPCache row for given username or throws an exception 4921function acquireLDAPCache ($username, $max_tries = 2) 4922{ 4923 $self = __FUNCTION__; 4924 global $dbxlink; 4925 4926 // guarantee there is a row to lock 4927 usePreparedExecuteBlade ("INSERT IGNORE INTO LDAPCache (presented_username) VALUES (?)", array ($username)); 4928 $dbxlink->beginTransaction(); 4929 4930 if ($row = fetchLDAPCacheRow ($username, 'FOR UPDATE')) 4931 return $row; 4932 4933 // Maybe another instance deleted the row before this instance had locked it. Try again. 4934 if ($max_tries > 0) 4935 return $self ($username, $max_tries - 1); 4936 4937 // the problem still persists after retries, throw an exception 4938 throw new RackTablesError ("Unable to acquire lock on LDAPCache", RackTablesError::INTERNAL); 4939} 4940 4941function releaseLDAPCache () 4942{ 4943 global $dbxlink; 4944 $dbxlink->commit(); 4945} 4946 4947// This actually changes only last_retry. 4948function touchLDAPCacheRecord ($username) 4949{ 4950 usePreparedExecuteBlade ('UPDATE LDAPCache SET last_retry=NOW() WHERE presented_username=?', array ($username)); 4951} 4952 4953function replaceLDAPCacheRecord ($username, $password_hash, $dname, $memberof) 4954{ 4955 usePreparedDeleteBlade ('LDAPCache', array ('presented_username' => $username)); 4956 usePreparedInsertBlade ('LDAPCache', 4957 array 4958 ( 4959 'presented_username' => $username, 4960 'successful_hash' => $password_hash, 4961 'displayed_name' => $dname, 4962 'memberof' => base64_encode (serialize ($memberof)), 4963 ) 4964 ); 4965} 4966 4967function deleteLDAPCacheRecord ($username) 4968{ 4969 usePreparedDeleteBlade ('LDAPCache', array ('presented_username' => $username)); 4970} 4971 4972// Purge all records older than the threshold, as well as any records made in future. 4973// Calling this function w/o argument purges the whole LDAP cache. 4974function discardLDAPCache ($maxseconds = 0) 4975{ 4976 usePreparedExecuteBlade ('DELETE FROM LDAPCache WHERE TIMESTAMPDIFF(SECOND, first_success, NOW()) >= ? OR NOW() < first_success', array ($maxseconds)); 4977} 4978 4979function getUserIDByUsername ($username) 4980{ 4981 $result = usePreparedSelectBlade ('SELECT user_id FROM UserAccount WHERE user_name = ?', array ($username)); 4982 return nullIfFalse ($result->fetchColumn()); 4983} 4984 4985# Derive a complete cell structure from the given username regardless 4986# if it is a local account or not. 4987function constructUserCell ($username) 4988{ 4989 if (NULL !== ($userid = getUserIDByUsername ($username))) 4990 return spotEntity ('user', $userid); 4991 $ret = array 4992 ( 4993 'realm' => 'user', 4994 'user_name' => $username, 4995 'user_realname' => '', 4996 'etags' => array(), 4997 'itags' => array(), 4998 ); 4999 $ret['atags'] = generateEntityAutoTags ($ret); 5000 return $ret; 5001} 5002 5003// DEPRECATED but snmpgeneric.php uses it, remove in 0.21.0. 5004function alreadyUsedL2Address ($address, $my_object_id) 5005{ 5006 try 5007 { 5008 assertUniqueL2Addresses (array ($address), $my_object_id); 5009 return FALSE; 5010 } 5011 catch (InvalidArgException $iae) 5012 { 5013 return TRUE; 5014 } 5015} 5016 5017// Raise an exception if any of the given MAC/WWN addresses (less empty strings) 5018// belongs to a port with an object ID other than the given. This constraint makes 5019// it possible to reuse L2 addresses within one object's set of ports and to keep 5020// them universally unique otherwise. Every L2 address on the input list must have 5021// been conditioned with l2addressForDatabase(). 5022function assertUniqueL2Addresses ($db_l2addresses, $my_object_id) 5023{ 5024 // Reindex the array such that array_merge() below works as expected. 5025 $db_l2addresses = array_values (array_unique (array_filter ($db_l2addresses, 'strlen'))); 5026 if (0 == count ($db_l2addresses)) 5027 return; 5028 $qm = questionMarks (count ($db_l2addresses)); 5029 // BINARY in the second comparison is what the query is actually looking for but without 5030 // the first (non-BINARY) comparison the table index does not work as expected. 5031 $query = 'SELECT l2address, object_id, name FROM Port ' . 5032 "WHERE l2address IN(${qm}) AND BINARY l2address IN(${qm}) AND object_id != ? LIMIT 1"; 5033 $params = array_merge ($db_l2addresses, $db_l2addresses, array ($my_object_id)); 5034 $result = usePreparedSelectBlade ($query, $params); 5035 if ($row = $result->fetch (PDO::FETCH_ASSOC)) 5036 throw new InvalidArgException ('L2 address', $row['l2address'], "already used by object#{$row['object_id']} port '{$row['name']}'"); 5037} 5038 5039function getPortInterfaceCompat() 5040{ 5041 $result = usePreparedSelectBlade 5042 ( 5043 'SELECT iif_id, iif_name, oif_id, oif_name ' . 5044 'FROM PortInterfaceCompat INNER JOIN PortInnerInterface AS PII ON PII.id = iif_id ' . 5045 'INNER JOIN PortOuterInterface AS POI ON POI.id = oif_id ' . 5046 'ORDER BY iif_name, oif_name' 5047 ); 5048 return $result->fetchAll (PDO::FETCH_ASSOC); 5049} 5050 5051// Return a set of options for a plain SELECT. These options include the current 5052// OIF of the given port and all OIFs of its permanent IIF. 5053// If given port is already linked, returns only types compatible with the remote port's type 5054function getExistingPortTypeOptions ($portinfo) 5055{ 5056 $remote_type = NULL; 5057 if ($portinfo['linked']) 5058 { 5059 $remote_portinfo = getPortInfo ($portinfo['remote_id']); 5060 $result = usePreparedSelectBlade (" 5061SELECT oif_id, oif_name 5062FROM PortInterfaceCompat 5063INNER JOIN PortOuterInterface ON oif_id = id 5064INNER JOIN PortCompat pc ON pc.type1 = oif_id AND pc.type2 = ? 5065WHERE iif_id = ? 5066ORDER BY oif_name 5067", array ($remote_portinfo['oif_id'], $portinfo['iif_id']) 5068 ); 5069 } 5070 else 5071 { 5072 $result = usePreparedSelectBlade (" 5073SELECT oif_id, oif_name 5074FROM PortInterfaceCompat 5075INNER JOIN PortOuterInterface ON oif_id = id 5076WHERE iif_id = ? 5077ORDER BY oif_name 5078", array ($portinfo['iif_id']) 5079 ); 5080 } 5081 5082 return reduceSubarraysToColumn (reindexById ($result->fetchAll (PDO::FETCH_ASSOC), 'oif_id'), 'oif_name'); 5083} 5084 5085function getPortTypeUsageStatistics() 5086{ 5087 $result = usePreparedSelectBlade 5088 ( 5089 'SELECT p.iif_id, p.type, COUNT(p.id) AS count FROM Port p INNER JOIN Link l '. 5090 'ON (p.id = l.porta or p.id = l.portb) WHERE p.type <> 0 GROUP BY iif_id, type' 5091 ); 5092 $ret = array(); 5093 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 5094 $ret[$row['iif_id'] . '-' . $row['type']] = $row['count']; 5095 return $ret; 5096} 5097 5098function getPortIIFOptions() 5099{ 5100 $result = usePreparedSelectBlade ('SELECT id, iif_name FROM PortInnerInterface ORDER BY iif_name'); 5101 return reduceSubarraysToColumn (reindexById ($result->fetchAll (PDO::FETCH_ASSOC)), 'iif_name'); 5102} 5103 5104function getPortOIFOptions() 5105{ 5106 $result = usePreparedSelectBlade ('SELECT id, oif_name FROM PortOuterInterface ORDER BY oif_name'); 5107 return reduceSubarraysToColumn (reindexById ($result->fetchAll (PDO::FETCH_ASSOC)), 'oif_name'); 5108} 5109 5110function commitSupplementPIC ($iif_id, $oif_id) 5111{ 5112 usePreparedInsertBlade 5113 ( 5114 'PortInterfaceCompat', 5115 array ('iif_id' => $iif_id, 'oif_id' => $oif_id) 5116 ); 5117} 5118 5119function getPortIIFStats ($iif_id) 5120{ 5121 $result = usePreparedSelectBlade 5122 ( 5123 'SELECT oif_name AS title, COUNT(Port.id) AS max, ' . 5124 'COUNT(reservation_comment) + ' . 5125 'COUNT(la.porta) + COUNT(lb.portb) AS current ' . 5126 'FROM Port INNER JOIN PortOuterInterface AS POI ON type = POI.id ' . 5127 'LEFT JOIN Link AS la ON la.porta = Port.id ' . 5128 'LEFT JOIN Link AS lb ON lb.portb = Port.id ' . 5129 'WHERE iif_id = ? GROUP BY type', 5130 array ($iif_id) 5131 ); 5132 return $result->fetchAll (PDO::FETCH_ASSOC); 5133} 5134 5135function getPortInfo ($port_id) 5136{ 5137 $result = fetchPortList ('Port.id = ?', array ($port_id)); 5138 return count ($result) ? $result[0] : NULL; 5139} 5140 5141function getVLANDomainStats () 5142{ 5143 $result = usePreparedSelectBlade 5144 ( 5145 'SELECT id, group_id, description, ' . 5146 '(SELECT COUNT(vd.id) FROM VLANDomain vd WHERE vd.group_id = VLANDomain.id) as subdomc, ' . 5147 '(SELECT COUNT(vlan_id) FROM VLANDescription WHERE domain_id = id) AS vlanc, ' . 5148 '(SELECT COUNT(ipv4net_id) FROM VLANIPv4 WHERE domain_id = id) AS ipv4netc, ' . 5149 '(SELECT COUNT(object_id) FROM VLANSwitch WHERE domain_id = id) AS switchc, ' . 5150 '(SELECT COUNT(port_name) FROM VLANSwitch AS VS INNER JOIN PortVLANMode AS PVM ON VS.object_id = PVM.object_id WHERE domain_id = id) AS portc ' . 5151 'FROM VLANDomain ORDER BY description' 5152 ); 5153 $ret = reindexById ($result->fetchAll (PDO::FETCH_ASSOC)); 5154 foreach ($ret as $vdom_id => $domain) 5155 if ($domain['group_id']) 5156 { 5157 // sum only vlans/nets because subdomains have switches/ports of their own 5158 $ret[$domain['group_id']]['vlanc'] += $domain['vlanc']; 5159 $ret[$domain['group_id']]['ipv4netc'] += $domain['ipv4netc']; 5160 } 5161 return $ret; 5162} 5163 5164function getVLANDomainOptions() 5165{ 5166 $result = usePreparedSelectBlade ('SELECT id, description FROM VLANDomain ORDER BY description'); 5167 return reduceSubarraysToColumn (reindexById ($result->fetchAll (PDO::FETCH_ASSOC)), 'description'); 5168} 5169 5170function getVLANDomain ($vdid) 5171{ 5172 $result = usePreparedSelectBlade 5173 ( 5174 'SELECT id, group_id, description, ' . 5175 '(SELECT COUNT(vd.id) FROM VLANDomain vd WHERE vd.group_id = VLANDomain.id) as subdomc ' . 5176 'FROM VLANDomain WHERE id = ?', 5177 array ($vdid) 5178 ); 5179 if (!$ret = $result->fetch (PDO::FETCH_ASSOC)) 5180 throw new EntityNotFoundException ('VLAN domain', $vdid); 5181 unset ($result); 5182 $ret['vlanlist'] = getDomainVLANList ($vdid); 5183 $ret['switchlist'] = array(); 5184 $result = usePreparedSelectBlade 5185 ( 5186 'SELECT object_id, template_id, last_errno, out_of_sync, ' . 5187 'TIMESTAMPDIFF(SECOND, last_change, NOW()) AS age_seconds ' . 5188 'FROM VLANSwitch WHERE domain_id = ? ORDER BY object_id', 5189 array ($vdid) 5190 ); 5191 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 5192 $ret['switchlist'][$row['object_id']] = $row; 5193 return $ret; 5194} 5195 5196function getDomainGroupMembers ($vdom_group_id) 5197{ 5198 $result = usePreparedSelectBlade ("SELECT id FROM VLANDomain WHERE group_id = ?", array ($vdom_group_id)); 5199 return $result->fetchAll (PDO::FETCH_COLUMN, 0); 5200} 5201 5202// This function is pretty heavy. Consider use of getDomainVLANList instead 5203// If $strict is FALSE, returns VLANs belonging to the domain or group. 5204// Otherwise the vlans of group subdomains are not returned. 5205function getDomainVLANs ($vdom_id, $strict = FALSE) 5206{ 5207 $self = __FUNCTION__; 5208 if (! $strict && $members = getDomainGroupMembers ($vdom_id)) 5209 { 5210 $ret = $self ($vdom_id, TRUE); 5211 foreach ($members as $member_vdom_id) 5212 foreach ($self ($member_vdom_id, TRUE) as $vid => $vlan_info) 5213 if (! isset ($ret[$vid])) 5214 $ret[$vid] = $vlan_info; 5215 else 5216 { 5217 $ret[$vid]['netc'] += $vlan_info['netc']; 5218 $ret[$vid]['portc'] += $vlan_info['portc']; 5219 } 5220 ksort ($ret, SORT_NUMERIC); 5221 return $ret; 5222 } 5223 5224 $result = usePreparedSelectBlade 5225 (<<<'END' 5226SELECT 5227 vlan_id, 5228 vlan_type, 5229 vlan_descr, 5230 (SELECT COUNT(ipv4net_id) FROM VLANIPv4 AS VI WHERE VI.domain_id = VD.domain_id and VI.vlan_id = VD.vlan_id) + 5231 (SELECT COUNT(ipv6net_id) FROM VLANIPv6 AS VI WHERE VI.domain_id = VD.domain_id and VI.vlan_id = VD.vlan_id) AS netc, 5232 s2.portc 5233FROM 5234 VLANDescription VD LEFT JOIN 5235 ( 5236 SELECT 5237 PAV.vlan_id as vid, 5238 COUNT(PAV.port_name) as portc 5239 FROM 5240 VLANSwitch VS 5241 INNER JOIN VLANDescription USING (domain_id) 5242 INNER JOIN PortAllowedVLAN PAV ON PAV.object_id = VS.object_id AND VLANDescription.vlan_id = PAV.vlan_id 5243 WHERE VS.domain_id = ? 5244 GROUP BY PAV.vlan_id 5245 ) AS s2 ON vlan_id = s2.vid 5246WHERE domain_id = ? 5247ORDER BY vlan_id 5248 5249END 5250 , array ($vdom_id, $vdom_id) 5251 ); 5252 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC), 'vlan_id'); 5253} 5254 5255// faster than getDomainVLANs, but w/o statistics. 5256// If $strict is FALSE, returns VLANs belonging to the domain or group. 5257// Otherwise the vlans of group subdomains are not returned. 5258function getDomainVLANList ($vdom_id, $strict = FALSE) 5259{ 5260 if (! $strict && $members = getDomainGroupMembers ($vdom_id)) 5261 { 5262 $self = __FUNCTION__; 5263 $ret = $self ($vdom_id, TRUE); 5264 foreach ($members as $member_vdom_id) 5265 $ret += $self ($member_vdom_id, TRUE); 5266 return $ret; 5267 } 5268 5269 $result = usePreparedSelectBlade 5270 (<<<'END' 5271SELECT 5272 vlan_id, 5273 vlan_type, 5274 vlan_descr 5275FROM 5276 VLANDescription AS VD 5277WHERE domain_id = ? 5278ORDER BY vlan_id 5279END 5280 , array ($vdom_id) 5281 ); 5282 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC), 'vlan_id'); 5283} 5284 5285function getVLANSwitches() 5286{ 5287 $result = usePreparedSelectBlade ('SELECT object_id FROM VLANSwitch'); 5288 return reduceSubarraysToColumn ($result->fetchAll (PDO::FETCH_ASSOC), 'object_id'); 5289} 5290 5291function getVLANSwitchInfo ($object_id, $extrasql = '') 5292{ 5293 return array_first (getVLANSwitchInfoRows (array ('object_id' => $object_id), $extrasql)); 5294} 5295 5296function getVLANSwitchInfoRows ($filter = array(), $extrasql = '') 5297{ 5298 $query = 5299 'SELECT object_id, domain_id, template_id, mutex_rev, out_of_sync, last_errno, ' . 5300 'UNIX_TIMESTAMP(last_change) as last_change, ' . 5301 'UNIX_TIMESTAMP(last_push_started) as last_push_started, ' . 5302 'UNIX_TIMESTAMP(last_push_finished) as last_push_finished, ' . 5303 'UNIX_TIMESTAMP(last_error_ts) as last_error_ts ' . 5304 'FROM VLANSwitch'; 5305 $params = array(); 5306 if ($filter) 5307 $query .= ' WHERE ' . makeWhereSQL ($filter, 'AND', $params); 5308 $query .= ' ' . $extrasql; 5309 $result = usePreparedSelectBlade ($query, $params); 5310 return $result->fetchAll (PDO::FETCH_ASSOC); 5311} 5312 5313// Reads the per-port VLAN configuration for a given object. 5314// $instance could be either 'desired' or 'cached'. 5315// $port_names is a filter enumerating names of ports to select. If empty, selects all ports. 5316function getStored8021QConfig ($object_id, $instance = 'desired', $port_names = array()) 5317{ 5318 global $tablemap_8021q; 5319 if (!array_key_exists ($instance, $tablemap_8021q)) 5320 throw new InvalidArgException ('instance', $instance); 5321 $sql_filter = ' WHERE object_id = ?'; 5322 $sql_params = array ($object_id); 5323 if (count ($port_names) == 1) 5324 { 5325 $sql_filter .= ' AND port_name = ?'; 5326 $sql_params[] = array_first ($port_names); 5327 } 5328 else if (count ($port_names) > 1) 5329 { 5330 $sql_filter .= ' AND port_name IN(' . questionMarks (count ($port_names)) . ')'; 5331 $sql_params = array_merge ($sql_params, $port_names); 5332 } 5333 5334 $ret = array(); 5335 $result = usePreparedSelectBlade 5336 ( 5337 'SELECT port_name, vlan_mode FROM ' . $tablemap_8021q[$instance]['pvm'] . $sql_filter, 5338 $sql_params 5339 ); 5340 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 5341 $ret[$row['port_name']] = array 5342 ( 5343 'mode' => $row['vlan_mode'], 5344 'allowed' => array(), 5345 'native' => 0, 5346 ); 5347 unset ($result); 5348 $result = usePreparedSelectBlade 5349 ( 5350 'SELECT port_name, vlan_id FROM ' . $tablemap_8021q[$instance]['pav'] . $sql_filter, 5351 $sql_params 5352 ); 5353 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 5354 $ret[$row['port_name']]['allowed'][] = $row['vlan_id']; 5355 unset ($result); 5356 $result = usePreparedSelectBlade 5357 ( 5358 'SELECT port_name, vlan_id FROM ' . $tablemap_8021q[$instance]['pnv'] . $sql_filter, 5359 $sql_params 5360 ); 5361 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 5362 $ret[$row['port_name']]['native'] = $row['vlan_id']; 5363 return $ret; 5364} 5365 5366function getVlanRow ($vlan_ck) 5367{ 5368 list ($vdom_id, $vlan_id) = decodeVLANCK ($vlan_ck); 5369 $query = 'SELECT domain_id, vlan_id, vlan_type AS vlan_prop, vlan_descr, ' . 5370 '(SELECT description FROM VLANDomain WHERE id = domain_id) AS domain_descr, ' . 5371 '(SELECT group_id FROM VLANDomain WHERE id = domain_id) AS domain_group_id ' . 5372 'FROM VLANDescription WHERE domain_id = ? AND vlan_id = ?'; 5373 $result = usePreparedSelectBlade ($query, array ($vdom_id, $vlan_id)); 5374 if (FALSE === $ret = $result->fetch (PDO::FETCH_ASSOC)) 5375 throw new EntityNotFoundException ('VLAN', $vlan_ck); 5376 $ret['vlan_ck'] = $vlan_ck; 5377 return $ret; 5378} 5379 5380function getVLANInfo ($vlan_ck) 5381{ 5382 list ($vdom_id, $vlan_id) = decodeVLANCK ($vlan_ck); 5383 $ret = getVlanRow ($vlan_ck); 5384 5385 $result = usePreparedSelectBlade 5386 ( 5387 'SELECT ipv4net_id FROM VLANIPv4 WHERE domain_id = ? AND vlan_id = ? ORDER BY ipv4net_id', 5388 array ($vdom_id, $vlan_id) 5389 ); 5390 $ret['ipv4nets'] = reduceSubarraysToColumn ($result->fetchAll (PDO::FETCH_ASSOC), 'ipv4net_id'); 5391 unset ($result); 5392 $result = usePreparedSelectBlade 5393 ( 5394 'SELECT ipv6net_id FROM VLANIPv6 WHERE domain_id = ? AND vlan_id = ? ORDER BY ipv6net_id', 5395 array ($vdom_id, $vlan_id) 5396 ); 5397 $ret['ipv6nets'] = reduceSubarraysToColumn ($result->fetchAll (PDO::FETCH_ASSOC), 'ipv6net_id'); 5398 return $ret; 5399} 5400 5401// return list of network IDs that are not bound to the given VLAN domain 5402function getVLANIPv4Options ($except_vdid) 5403{ 5404 $prepared = usePreparedSelectBlade 5405 ( 5406 'SELECT id FROM IPv4Network WHERE id NOT IN ' . 5407 '(SELECT ipv4net_id FROM VLANIPv4 WHERE domain_id = ?)' . 5408 'ORDER BY ip, mask', 5409 array ($except_vdid) 5410 ); 5411 return reduceSubarraysToColumn ($prepared->fetchAll (PDO::FETCH_ASSOC), 'id'); 5412} 5413 5414// return list of network IDs that are not bound to the given VLAN domain 5415function getVLANIPv6Options ($except_vdid) 5416{ 5417 $prepared = usePreparedSelectBlade 5418 ( 5419 'SELECT id FROM IPv6Network WHERE id NOT IN ' . 5420 '(SELECT ipv6net_id FROM VLANIPv6 WHERE domain_id = ?)' . 5421 'ORDER BY ip, mask', 5422 array ($except_vdid) 5423 ); 5424 return reduceSubarraysToColumn ($prepared->fetchAll (PDO::FETCH_ASSOC), 'id'); 5425} 5426 5427function commitSupplementVLANIPv4 ($vlan_ck, $ipv4net_id) 5428{ 5429 list ($vdom_id, $vlan_id) = decodeVLANCK ($vlan_ck); 5430 usePreparedInsertBlade 5431 ( 5432 'VLANIPv4', 5433 array 5434 ( 5435 'domain_id' => $vdom_id, 5436 'vlan_id' => $vlan_id, 5437 'ipv4net_id' => $ipv4net_id, 5438 ) 5439 ); 5440} 5441 5442function commitSupplementVLANIPv6 ($vlan_ck, $ipv6net_id) 5443{ 5444 list ($vdom_id, $vlan_id) = decodeVLANCK ($vlan_ck); 5445 usePreparedInsertBlade 5446 ( 5447 'VLANIPv6', 5448 array 5449 ( 5450 'domain_id' => $vdom_id, 5451 'vlan_id' => $vlan_id, 5452 'ipv6net_id' => $ipv6net_id, 5453 ) 5454 ); 5455} 5456 5457function commitReduceVLANIPv4 ($vlan_ck, $ipv4net_id) 5458{ 5459 list ($vdom_id, $vlan_id) = decodeVLANCK ($vlan_ck); 5460 usePreparedDeleteBlade 5461 ( 5462 'VLANIPv4', 5463 array 5464 ( 5465 'domain_id' => $vdom_id, 5466 'vlan_id' => $vlan_id, 5467 'ipv4net_id' => $ipv4net_id, 5468 ) 5469 ); 5470} 5471 5472function commitReduceVLANIPv6 ($vlan_ck, $ipv6net_id) 5473{ 5474 list ($vdom_id, $vlan_id) = decodeVLANCK ($vlan_ck); 5475 usePreparedDeleteBlade 5476 ( 5477 'VLANIPv6', 5478 array 5479 ( 5480 'domain_id' => $vdom_id, 5481 'vlan_id' => $vlan_id, 5482 'ipv6net_id' => $ipv6net_id, 5483 ) 5484 ); 5485} 5486 5487// Return a list of switches that have specific VLAN configured on 5488// any port (each switch with the list of such ports). 5489function getVLANConfiguredPorts ($vlan_ck) 5490{ 5491 $result = usePreparedSelectBlade 5492 ( 5493 'SELECT PAV.object_id, PAV.port_name ' . 5494 'FROM PortAllowedVLAN AS PAV ' . 5495 'INNER JOIN VLANSwitch AS VS ON PAV.object_id = VS.object_id ' . 5496 'WHERE domain_id = ? AND vlan_id = ? ' . 5497 'ORDER BY PAV.object_id, PAV.port_name', 5498 decodeVLANCK ($vlan_ck) 5499 ); 5500 $ret = array(); 5501 while ($row = $result->fetch (PDO::FETCH_ASSOC)) 5502 $ret[$row['object_id']][] = $row['port_name']; 5503 return $ret; 5504} 5505 5506function add8021QPort ($object_id, $port_name, $port) 5507{ 5508 global $tablemap_8021q; 5509 $changed = 0; 5510 $changed += usePreparedInsertBlade 5511 ( 5512 $tablemap_8021q['cached']['pvm'], 5513 array ('object_id' => $object_id, 'port_name' => $port_name, 'vlan_mode' => $port['mode']) 5514 ); 5515 $changed += usePreparedInsertBlade 5516 ( 5517 $tablemap_8021q['desired']['pvm'], 5518 array ('object_id' => $object_id, 'port_name' => $port_name, 'vlan_mode' => $port['mode']) 5519 ); 5520 $changed += upd8021QPort ('cached', $object_id, $port_name, $port, NULL); 5521 $changed += upd8021QPort ('desired', $object_id, $port_name, $port, NULL); 5522 return $changed ? 1 : 0; 5523} 5524 5525function del8021QPort ($object_id, $port_name) 5526{ 5527 // rely on ON DELETE CASCADE for PortAllowedVLAN and PortNativeVLAN 5528 global $tablemap_8021q; 5529 $changed = 0; 5530 $changed += usePreparedDeleteBlade 5531 ( 5532 $tablemap_8021q['desired']['pvm'], 5533 array ('object_id' => $object_id, 'port_name' => $port_name) 5534 ); 5535 $changed += usePreparedDeleteBlade 5536 ( 5537 $tablemap_8021q['cached']['pvm'], 5538 array ('object_id' => $object_id, 'port_name' => $port_name) 5539 ); 5540 5541 callHook ('portConfChanged', $object_id, $port_name, NULL); 5542 return $changed ? 1 : 0; 5543} 5544 5545// Returns list of tuples ("where_text" params_array) that covers 5546// all the VLANs in $vlan_list. 5547// aggregates sparse VLANs into single IN() condition 5548// each range larger than 5 is returned as separate BETWEEN condition 5549function makeVlanListWhere ($db_field_name, $vlan_list) 5550{ 5551 $ret = array(); 5552 $in_list = array(); 5553 foreach (listToRanges ($vlan_list) as $range) 5554 { 5555 if ($range['from'] == $range['to']) 5556 $in_list[] = $range['from']; 5557 elseif ($range['to'] - $range['from'] < 5) 5558 for ($i = $range['from']; $i <= $range['to']; ++$i) 5559 $in_list[] = $i; 5560 else 5561 $ret[] = array ("$db_field_name BETWEEN ? AND ?", array ($range['from'], $range['to'])); 5562 } 5563 if ($in_list) 5564 $ret[] = array ("$db_field_name IN(" . questionMarks(count ($in_list)) . ')', $in_list); 5565 return $ret; 5566} 5567 5568function upd8021QPort ($instance = 'desired', $object_id, $port_name, $port, $before) 5569{ 5570 global $tablemap_8021q; 5571 if (!array_key_exists ($instance, $tablemap_8021q)) 5572 throw new InvalidArgException ('instance', $instance); 5573 // Replace current port configuration with the provided one. If the new 5574 // native VLAN ID doesn't belong to the allowed list, don't issue 5575 // INSERT query, which would always trigger an FK exception. 5576 // This function indicates an error, but doesn't revert it, so it is 5577 // assummed that the calling function performs necessary transaction wrapping. 5578 // A record on a port with none VLANs allowed makes no sense regardless of port mode. 5579 if ($port['mode'] != 'trunk' && ! count ($port['allowed'])) 5580 return 0; 5581 $changed = 0; 5582 if (! isset ($before) || $before['mode'] != $port['mode']) 5583 $changed += usePreparedUpdateBlade 5584 ( 5585 $tablemap_8021q[$instance]['pvm'], 5586 array ('vlan_mode' => $port['mode']), 5587 array ('object_id' => $object_id, 'port_name' => $port_name) 5588 ); 5589 5590 if (isset ($before)) 5591 { 5592 $add_list = array_diff ($port['allowed'], $before['allowed']); 5593 $del_list = array_diff ($before['allowed'], $port['allowed']); 5594 foreach (makeVlanListWhere ('vlan_id', $del_list) as $where) 5595 $changed += usePreparedExecuteBlade 5596 ( 5597 'DELETE FROM ' . $tablemap_8021q[$instance]['pav'] . 5598 ' WHERE object_id = ? AND port_name = ? AND ' . $where[0], 5599 array_merge (array ($object_id, $port_name), $where[1]) 5600 ); 5601 } 5602 else 5603 { 5604 $add_list = $port['allowed']; 5605 $changed += usePreparedDeleteBlade ( 5606 $tablemap_8021q[$instance]['pav'], 5607 array ('object_id' => $object_id, 'port_name' => $port_name) 5608 ); 5609 } 5610 5611 // The goal is to INSERT as many rows as there are values in 'allowed' list 5612 // without wrapping each row with own INSERT (otherwise the SQL connection 5613 // instantly becomes the bottleneck). 5614 foreach (makeVlanListWhere ('vlan_id', $add_list) as $where) 5615 $changed += usePreparedExecuteBlade 5616 ( 5617 'INSERT INTO ' . $tablemap_8021q[$instance]['pav'] . ' (object_id, port_name, vlan_id) ' . 5618 'SELECT ?, ?, vlan_id FROM VLANValidID WHERE ' . $where[0], 5619 array_merge (array ($object_id, $port_name), $where[1]) 5620 ); 5621 5622 if (! $port['native'] && (! isset ($before) || in_array ($before['native'], $port['allowed']))) 5623 $changed += usePreparedDeleteBlade 5624 ( 5625 $tablemap_8021q[$instance]['pnv'], 5626 array ('object_id' => $object_id, 'port_name' => $port_name) 5627 ); 5628 elseif ($port['native'] && (! isset ($before) || $before['native'] != $port['native'])) 5629 $changed += usePreparedExecuteBlade 5630 ( 5631 'REPLACE INTO ' . $tablemap_8021q[$instance]['pnv'] . 5632 ' (object_id, port_name, vlan_id) VALUES (?, ?, ?)', 5633 array ($object_id, $port_name, $port['native']) 5634 ); 5635 5636 if ($instance == 'desired' && $changed) 5637 callHook ('portConfChanged', $object_id, $port_name, $port); 5638 return $changed ? 1 : 0; 5639} 5640 5641function replace8021QPorts ($instance = 'desired', $object_id, $before, $changes) 5642{ 5643 $done = 0; 5644 foreach ($changes as $pn => $port) 5645 $done += upd8021QPort ($instance, $object_id, $pn, $port, array_fetch ($before, $pn, NULL)); 5646 return $done; 5647} 5648 5649function commitUpdateVSTRules ($vst_id, $mutex_rev, $rules) 5650{ 5651 global $dbxlink, $remote_username; 5652 $dbxlink->beginTransaction(); 5653 $result = usePreparedSelectBlade 5654 ( 5655 'SELECT mutex_rev, saved_by FROM VLANSwitchTemplate ' . 5656 'WHERE id = ? FOR UPDATE', 5657 array ($vst_id) 5658 ); 5659 $vst = $result->fetch (PDO::FETCH_ASSOC); 5660 unset ($result); 5661 if ($vst['mutex_rev'] != $mutex_rev) 5662 throw new InvalidRequestArgException ('mutex_rev', $mutex_rev, "already saved by ${vst['saved_by']}"); 5663 usePreparedDeleteBlade ('VLANSTRule', array ('vst_id' => $vst_id)); 5664 foreach ($rules as $rule) 5665 usePreparedInsertBlade ('VLANSTRule', array_merge (array ('vst_id' => $vst_id), $rule)); 5666 usePreparedExecuteBlade ('UPDATE VLANSwitchTemplate SET mutex_rev=mutex_rev+1, saved_by=? WHERE id=?', array ($remote_username, $vst_id)); 5667 $dbxlink->commit(); 5668} 5669 5670// Return entity ID, if its 'name' column equals to provided string, or NULL otherwise (nothing 5671// found or more than one row returned by query due to some odd reason). 5672function lookupEntityByString ($realm, $value, $column = 'name') 5673{ 5674 global $SQLSchema; 5675 if (!isset ($SQLSchema[$realm])) 5676 throw new InvalidArgException ('realm', $realm); 5677 $SQLinfo = $SQLSchema[$realm]; 5678 $query = "SELECT ${SQLinfo['keycolumn']} AS id FROM ${SQLinfo['table']} WHERE ${SQLinfo['table']}.${column}=? LIMIT 2"; 5679 $result = usePreparedSelectBlade ($query, array ($value)); 5680 $rows = $result->fetchAll (PDO::FETCH_ASSOC); 5681 return count ($rows) == 1 ? $rows[0]['id'] : NULL; 5682} 5683 5684// Returns an array of attribute IDs that are mapped to the specified chapter. 5685function getChapterAttributes($chapter_id) 5686{ 5687 $prepared = usePreparedSelectBlade ('SELECT DISTINCT attr_id FROM AttributeMap WHERE chapter_id = ?', array ($chapter_id)); 5688 $rows = $prepared->fetchAll (PDO::FETCH_COLUMN); 5689 return is_array($rows) ? $rows : array(); 5690} 5691 5692function getLogRecordsForObject ($object_id) 5693{ 5694 $result = usePreparedSelectBlade ('SELECT id, content, date, user FROM ObjectLog WHERE object_id = ? ORDER BY date DESC', array ($object_id)); 5695 return $result->fetchAll (PDO::FETCH_ASSOC); 5696} 5697 5698function getLogRecords() 5699{ 5700 $result = usePreparedSelectBlade 5701 ( 5702 'SELECT OL.id AS log_id, O.objtype_id, O.name, OL.content, OL.date, OL.user, O.id AS object_id ' . 5703 'FROM ObjectLog OL LEFT JOIN Object O ON OL.object_id = O.id ' . 5704 'ORDER BY OL.date DESC' 5705 ); 5706 return $result->fetchAll (PDO::FETCH_ASSOC); 5707} 5708 5709// plugins may install their own settings 5710function addConfigVar ($varname, $varvalue, $vartype, $emptyok, $is_hidden, $is_userdefined, $description) 5711{ 5712 usePreparedInsertBlade 5713 ( 5714 'Config', 5715 array 5716 ( 5717 'varname' => $varname, 5718 'varvalue' => $varvalue, 5719 'vartype' => $vartype, 5720 'emptyok' => $emptyok, 5721 'is_hidden' => $is_hidden, 5722 'is_userdefined' => $is_userdefined, 5723 'description' => $description 5724 ) 5725 ); 5726 global $configCache; 5727 $configCache = loadConfigDefaults (); 5728} 5729 5730// used when uninstalling a plugin 5731function deleteConfigVar ($varname) 5732{ 5733 global $configCache; 5734 if (! isset ($configCache)) 5735 throw new RackTablesError ('configuration cache is unavailable', RackTablesError::INTERNAL); 5736 unset ($configCache[$varname]); 5737 usePreparedDeleteBlade ('Config', array ('varname' => $varname)); 5738} 5739 5740function setConfigVar ($varname, $varvalue) 5741{ 5742 global $configCache; 5743 if (!isset ($configCache)) 5744 throw new RackTablesError ('configuration cache is unavailable', RackTablesError::INTERNAL); 5745 if (NULL === $var = array_fetch ($configCache, $varname, NULL)) 5746 throw new InvalidArgException ('varname', $varname, 'unknown variable'); 5747 if ($var['is_hidden'] != 'no') 5748 throw new InvalidArgException ('varname', $varname, 'a hidden variable cannot be changed'); 5749 if ($varvalue == '' && $var['emptyok'] != 'yes') 5750 throw new InvalidArgException ('varvalue', $varvalue, "'${varname}' must have a non-empty value"); 5751 if ($varvalue != '' && $var['vartype'] == 'uint' && ! isUnsignedInteger ($varvalue)) 5752 throw new InvalidArgException ('varvalue', $varvalue, "'${varname}' must be an unsigned integer"); 5753 // Update cache only if the changes went into DB. 5754 usePreparedUpdateBlade ('Config', array ('varvalue' => $varvalue), array ('varname' => $varname)); 5755 $configCache[$varname]['varvalue'] = $varvalue; 5756 $configCache[$varname]['defaultvalue'] = $varvalue; 5757 alterConfigWithUserPreferences(); 5758} 5759 5760function setUserConfigVar ($varname, $varvalue) 5761{ 5762 global $configCache; 5763 global $remote_username; 5764 if (!isset ($configCache)) 5765 throw new RackTablesError ('configuration cache is unavailable', RackTablesError::INTERNAL); 5766 if (NULL === $var = array_fetch ($configCache, $varname, NULL)) 5767 throw new InvalidArgException ('varname', $varname, 'unknown variable'); 5768 if ($var['is_userdefined'] != 'yes') 5769 throw new InvalidArgException ('varname', $varname, 'a system-wide setting cannot be changed by user'); 5770 if ($var['is_hidden'] != 'no') 5771 throw new InvalidArgException ('varname', $varname, 'a hidden variable cannot be changed'); 5772 if ($varvalue == '' && $var['emptyok'] != 'yes') 5773 throw new InvalidArgException ('varvalue', $varvalue, "'${varname}' must have a non-empty value"); 5774 if ($varvalue != '' && $var['vartype'] == 'uint' && ! isUnsignedInteger ($varvalue)) 5775 throw new InvalidArgException ('varvalue', $varvalue, "'${varname}' must be an unsigned integer"); 5776 // Update cache only if the changes went into DB. 5777 usePreparedExecuteBlade 5778 ( 5779 'REPLACE INTO UserConfig SET varvalue=?, varname=?, user=?', 5780 array ($varvalue, $varname, $remote_username) 5781 ); 5782 $configCache[$varname]['varvalue'] = $varvalue; 5783 $configCache[$varname]['is_altered'] = 'yes'; 5784} 5785 5786function resetUserConfigVar ($varname) 5787{ 5788 global $configCache; 5789 global $remote_username; 5790 if (!isset ($configCache)) 5791 throw new RackTablesError ('configuration cache is unavailable', RackTablesError::INTERNAL); 5792 if (NULL === $var = array_fetch ($configCache, $varname, NULL)) 5793 throw new InvalidArgException ('varname', $varname, 'unknown variable'); 5794 if ($var['is_hidden'] != 'no') 5795 throw new InvalidArgException ('varname', $varname, 'a hidden variable cannot be changed'); 5796 // Update cache only if the changes went into DB. 5797 if (! array_key_exists ('is_altered', $var) || $var['is_altered'] != 'yes') 5798 return; 5799 usePreparedDeleteBlade ('UserConfig', array ('varname' => $varname, 'user' => $remote_username)); 5800 $configCache[$varname]['varvalue'] = $configCache[$varname]['defaultvalue']; 5801 unset ($configCache[$varname]['is_altered']); 5802} 5803 5804// parses QUICK_LINK_PAGES config var and returns array with ('href'=>..., 'title'=>...) items 5805function getConfiguredQuickLinks() 5806{ 5807 $ret = array(); 5808 foreach (explode (',', getConfigVar('QUICK_LINK_PAGES')) as $page_code) 5809 if ($page_code != '') 5810 if ('' != $title = getPageName ($page_code)) 5811 $ret[] = array ('href' => makeHref (array ('page' => $page_code)), 'title' => $title); 5812 return $ret; 5813} 5814 5815function touchVLANSwitch ($switch_id) 5816{ 5817 return usePreparedExecuteBlade 5818 ( 5819 'UPDATE VLANSwitch SET mutex_rev=mutex_rev+1, last_change=NOW(), out_of_sync="yes" WHERE object_id=?', 5820 array ($switch_id) 5821 ); 5822} 5823 5824function detouchVLANSwitch ($switch_id, $mutex_rev) 5825{ 5826 return usePreparedExecuteBlade 5827 ( 5828 'UPDATE VLANSwitch SET last_change=NOW(), out_of_sync="no" WHERE object_id=? AND mutex_rev = ?', 5829 array ($switch_id, $mutex_rev) 5830 ); 5831} 5832 5833function setVLANSwitchError ($object_id, $errno) 5834{ 5835 return usePreparedExecuteBlade 5836 ( 5837 'UPDATE VLANSwitch SET last_errno=?, last_error_ts=NOW() WHERE object_id=?', 5838 array ($errno, $object_id) 5839 ); 5840} 5841 5842function setVLANSwitchTimestamp ($object_id, $field_name) 5843{ 5844 return usePreparedExecuteBlade 5845 ( 5846 "UPDATE VLANSwitch SET `$field_name`=NOW() WHERE object_id=?", 5847 array ($object_id) 5848 ); 5849} 5850 5851# Return list of rows for objects that have the date stored in the given 5852# attribute belonging to the given range (relative to today's date). 5853function scanAttrRelativeDays ($attr_id, $not_before_days, $not_after_days) 5854{ 5855 if (getAttrType ($attr_id) != 'date') 5856 throw new InvalidArgException ('attr_id', $attr_id, 'attribute cannot store dates'); 5857 $result = usePreparedSelectBlade 5858 ( 5859 'SELECT uint_value, object_id FROM AttributeValue ' . 5860 'WHERE attr_id=? and FROM_UNIXTIME(uint_value) BETWEEN '. 5861 'DATE_ADD(curdate(), INTERVAL ? DAY) and DATE_ADD(curdate(), INTERVAL ? DAY)', 5862 array ($attr_id, $not_before_days, $not_after_days) 5863 ); 5864 return $result->fetchAll (PDO::FETCH_ASSOC); 5865} 5866 5867function isTransactionActive() 5868{ 5869 global $dbxlink; 5870 try 5871 { 5872 if ($dbxlink->beginTransaction()) 5873 { 5874 $dbxlink->rollBack(); 5875 return FALSE; 5876 } 5877 throw new RackTablesError ("beginTransaction() returned FALSE instead of throwing exception", RackTablesError::INTERNAL); 5878 } 5879 catch (PDOException $e) 5880 { 5881 return TRUE; 5882 } 5883} 5884 5885function getRowsCount ($table) 5886{ 5887 $result = usePreparedSelectBlade ("SELECT COUNT(*) FROM `$table`"); 5888 return $result->fetchColumn(); 5889} 5890 5891function getEntitiesCount ($realm) 5892{ 5893 global $SQLSchema; 5894 if (!isset ($SQLSchema[$realm])) 5895 throw new InvalidArgException ('realm', $realm); 5896 return getRowsCount ($SQLSchema[$realm]['table']); 5897} 5898 5899function getPatchCableConnectorList() 5900{ 5901 $result = usePreparedSelectBlade 5902 ( 5903 'SELECT id, origin, connector, ' . 5904 '(SELECT COUNT(*) FROM PatchCableConnectorCompat WHERE connector_id = id) AS refc ' . 5905 'FROM PatchCableConnector ORDER BY connector' 5906 ); 5907 return $result->fetchAll (PDO::FETCH_ASSOC); 5908} 5909 5910function getPatchCableConnectorOptions() 5911{ 5912 $ret = array(); 5913 foreach (getPatchCableConnectorList() as $item) 5914 $ret[$item['id']] = $item['connector'] . ($item['origin'] == 'custom' ? ' (custom)' : ''); 5915 return $ret; 5916} 5917 5918function getPatchCableTypeList() 5919{ 5920 $result = usePreparedSelectBlade 5921 ( 5922 'SELECT id, origin, pctype, ' . 5923 '(SELECT COUNT(*) FROM PatchCableConnectorCompat WHERE pctype_id = PatchCableType.id) + ' . 5924 '(SELECT COUNT(*) FROM PatchCableOIFCompat WHERE pctype_id = PatchCableType.id) AS refc ' . 5925 'FROM PatchCableType ORDER BY pctype' 5926 ); 5927 return $result->fetchAll (PDO::FETCH_ASSOC); 5928} 5929 5930function getPatchCableTypeOptions() 5931{ 5932 $ret = array(); 5933 foreach (getPatchCableTypeList() as $item) 5934 $ret[$item['id']] = $item['pctype'] . ($item['origin'] == 'custom' ? ' (custom)' : ''); 5935 return $ret; 5936} 5937 5938function getPatchCableHeapSummary() 5939{ 5940 $result = usePreparedSelectBlade 5941 ( 5942 'SELECT PCH.id, end1_conn_id, PCC1.connector AS end1_connector, pctype_id, pctype, ' . 5943 'end2_conn_id, PCC2.connector AS end2_connector, amount, length, description, ' . 5944 'COUNT(PCHL.id) AS logc FROM PatchCableHeap AS PCH ' . 5945 'INNER JOIN PatchCableType AS PCT ON PCH.pctype_id = PCT.id ' . 5946 'INNER JOIN PatchCableConnector AS PCC1 ON end1_conn_id = PCC1.id ' . 5947 'INNER JOIN PatchCableConnector AS PCC2 ON end2_conn_id = PCC2.id ' . 5948 'LEFT JOIN PatchCableHeapLog AS PCHL ON PCH.id = PCHL.heap_id ' . 5949 'GROUP BY PCH.id ' . 5950 'ORDER BY pctype, end1_connector, end2_connector, description, id ' 5951 ); 5952 return reindexById ($result->fetchAll (PDO::FETCH_ASSOC)); 5953} 5954 5955function getPatchCableHeapOptionsForOIF ($oif_id) 5956{ 5957 $result = usePreparedSelectBlade ('SELECT pctype_id FROM PatchCableOIFCompat WHERE oif_id = ?', array ($oif_id)); 5958 $pctypes = reduceSubarraysToColumn ($result->fetchAll (PDO::FETCH_ASSOC), 'pctype_id'); 5959 unset ($result); 5960 $ret = array(); 5961 foreach (getPatchCableHeapSummary() as $item) 5962 if ($item['amount'] > 0 && in_array ($item['pctype_id'], $pctypes)) 5963 $ret[$item['id']] = formatPatchCableHeapAsPlainText ($item); 5964 return $ret; 5965} 5966 5967function getPatchCableConnectorCompat() 5968{ 5969 $result = usePreparedSelectBlade 5970 ( 5971 'SELECT pctype_id, pctype, connector_id, connector FROM PatchCableConnectorCompat ' . 5972 'INNER JOIN PatchCableType AS PCT ON pctype_id = PCT.id ' . 5973 'INNER JOIN PatchCableConnector AS PCC ON connector_id = PCC.id ' . 5974 'ORDER BY pctype, connector' 5975 ); 5976 return $result->fetchAll (PDO::FETCH_ASSOC); 5977} 5978 5979function getPatchCableOIFCompat() 5980{ 5981 $result = usePreparedSelectBlade 5982 ( 5983 'SELECT pctype_id, pctype, oif_id, oif_name FROM PatchCableOIFCompat ' . 5984 'INNER JOIN PatchCableType AS PCT ON pctype_id = PCT.id ' . 5985 'INNER JOIN PortOuterInterface AS POI ON oif_id = POI.id ' . 5986 'ORDER BY pctype, oif_name' 5987 ); 5988 return $result->fetchAll (PDO::FETCH_ASSOC); 5989} 5990 5991function commitModifyPatchCableAmount ($heap_id, $by_amount) 5992{ 5993 global $dbxlink; 5994 $dbxlink->beginTransaction(); 5995 usePreparedExecuteBlade 5996 ( 5997 'UPDATE PatchCableHeap SET amount = amount + ? WHERE id = ? AND amount + ? >= 0', 5998 array ($by_amount, $heap_id, $by_amount) 5999 ); 6000 addPatchCableHeapLogEntry ($heap_id, "amount adjusted by ${by_amount}"); 6001 return $dbxlink->commit(); 6002} 6003 6004function commitSetPatchCableAmount ($heap_id, $new_amount) 6005{ 6006 global $dbxlink; 6007 $dbxlink->beginTransaction(); 6008 usePreparedUpdateBlade 6009 ( 6010 'PatchCableHeap', 6011 array ('amount' => $new_amount), 6012 array ('id' => $heap_id) 6013 ); 6014 addPatchCableHeapLogEntry ($heap_id, "amount set to ${new_amount}"); 6015 return $dbxlink->commit(); 6016} 6017 6018function getPatchCableHeapLogEntries ($heap_id) 6019{ 6020 $result = usePreparedSelectBlade 6021 ( 6022 'SELECT date, user, message FROM PatchCableHeapLog WHERE heap_id = ? ORDER BY date DESC', 6023 array ($heap_id) 6024 ); 6025 return $result->fetchAll (PDO::FETCH_ASSOC); 6026} 6027 6028function addPatchCableHeapLogEntry ($heap_id, $message) 6029{ 6030 global $disable_logging; 6031 if (isset ($disable_logging) && $disable_logging) 6032 return; 6033 global $remote_username; 6034 usePreparedExecuteBlade 6035 ( 6036 "INSERT INTO PatchCableHeapLog (heap_id, date, user, message) VALUES (?, NOW(), ?, ?)", 6037 array ($heap_id, $remote_username, $message) 6038 ); 6039} 6040 6041function selectRackOrder ($row_id) 6042{ 6043 $result = usePreparedSelectBlade ("SELECT id FROM Rack WHERE row_id = ? ORDER BY sort_order, name", array($row_id)); 6044 return $result->fetchAll (PDO::FETCH_COLUMN, 0); 6045} 6046 6047function getDBName() 6048{ 6049 global $pdo_dsn; 6050 if (preg_match ('/\bdbname=(.+?)(;|$)/', $pdo_dsn, $m)) 6051 return $m[1]; 6052 throw new RackTablesError ('failed to spot "dbname" in $pdo_dsn', RackTablesError::INTERNAL); 6053} 6054 6055// Sets exclusive server-global named lock. 6056// Returns TRUE or throws an exceptions. 6057// A lock is implicitly released on any subsequent call to setDBMutex in the same connection 6058function setDBMutex ($name, $timeout = 5) 6059{ 6060 $fullname = getDBName() . '.' . $name; 6061 $result = usePreparedSelectBlade ('SELECT GET_LOCK(?, ?)', array ($fullname, $timeout)); 6062 $row = $result->fetchColumn(); 6063 if ($row === NULL) 6064 throw new RTDatabaseError ("error occured when executing GET_LOCK on $fullname"); 6065 if ($row !== '1') 6066 throw new RTDatabaseError ("lock wait timeout for $fullname"); 6067 return TRUE; 6068} 6069 6070function tryDBMutex ($name, $timeout = 0) 6071{ 6072 try 6073 { 6074 return setDBMutex ($name, $timeout); 6075 } 6076 catch (RTDatabaseError $e) 6077 { 6078 return FALSE; 6079 } 6080} 6081 6082function releaseDBMutex ($name) 6083{ 6084 $result = usePreparedSelectBlade ('SELECT RELEASE_LOCK(?)', array (getDBName() . '.' . $name)); 6085 $row = $result->fetchColumn(); 6086 return $row === '1'; 6087} 6088 6089// valid states: disabled, enabled, not_installed 6090// if no state is specified, return all 6091function getPlugins ($state = NULL) 6092{ 6093 // installed 6094 $result = usePreparedSelectBlade ('SELECT name, longname, version, home_url, state FROM Plugin ORDER BY name'); 6095 $in_db = array (); 6096 foreach ($result as $row) 6097 if (! $state or $state == $row['state']) 6098 $in_db[$row['name']] = array 6099 ( 6100 'longname' => $row['longname'], 6101 'code_version' => 'N/A', 6102 'db_version' => $row['version'], 6103 'home_url' => $row['home_url'], 6104 'state' => $row['state'] 6105 ); 6106 6107 // available 6108 global $racktables_plugins_dir; 6109 $plugin_dirs = glob ("${racktables_plugins_dir}/*", GLOB_ONLYDIR); 6110 $in_code = array (); 6111 foreach ($plugin_dirs as $plugin_dir) 6112 { 6113 $plugin = basename ($plugin_dir); 6114 if (! file_exists ("${plugin_dir}/plugin.php")) 6115 continue; 6116 6117 require_once "${plugin_dir}/plugin.php"; 6118 if (! function_exists ("plugin_${plugin}_info")) 6119 continue; 6120 6121 $info = call_user_func ("plugin_${plugin}_info"); 6122 $in_code[$info['name']] = array 6123 ( 6124 'longname' => $info['longname'], 6125 'code_version' => $info['version'], 6126 'db_version' => (array_key_exists ($plugin, $in_db) ? $in_db[$info['name']]['db_version'] : 'N/A'), 6127 'home_url' => $info['home_url'], 6128 'state' => (array_key_exists ($plugin, $in_db) ? $in_db[$info['name']]['state'] : 'not_installed') 6129 ); 6130 } 6131 6132 // merge plugin lists and filter them, if needed 6133 $ret = array_replace_recursive ($in_db, $in_code); 6134 if ($state) 6135 foreach ($ret as $name => $info) 6136 if ($info['state'] != $state) 6137 unset ($ret[$name]); 6138 ksort ($ret); 6139 return $ret; 6140} 6141 6142function getPlugin ($name) 6143{ 6144 $result = usePreparedSelectBlade 6145 ( 6146 'SELECT name, longname, version, home_url, state FROM Plugin WHERE name = ?', 6147 array ($name) 6148 ); 6149 $db_info = $result->fetch (PDO::FETCH_ASSOC); 6150 $in_db = array (); 6151 if (is_array ($db_info)) 6152 { 6153 $in_db['name'] = $db_info['name']; 6154 $in_db['longname'] = $db_info['longname']; 6155 $in_db['code_version'] = 'N/A'; 6156 $in_db['db_version'] = $db_info['version']; 6157 $in_db['home_url'] = $db_info['home_url']; 6158 $in_db['state'] = $db_info['state']; 6159 } 6160 6161 global $racktables_plugins_dir; 6162 $in_code = array (); 6163 if (file_exists ("$racktables_plugins_dir/${name}/plugin.php")) 6164 { 6165 require_once "${racktables_plugins_dir}/${name}/plugin.php"; 6166 if (! function_exists ("plugin_${name}_info")) 6167 return FALSE; 6168 6169 $code_info = call_user_func ("plugin_${name}_info"); 6170 $in_code['name'] = $code_info['name']; 6171 $in_code['longname'] = $code_info['longname']; 6172 $in_code['code_version'] = $code_info['version']; 6173 $in_code['db_version'] = (isset ($in_db['db_version']) ? $in_db['db_version'] : 'N/A'); 6174 $in_code['home_url'] = $code_info['home_url']; 6175 $in_code['state'] = (isset ($in_db['state']) ? $in_db['state'] : 'not_installed'); 6176 } 6177 return array_replace ($in_db, $in_code); 6178} 6179 6180function commitInstallPlugin ($name, $longname, $version, $home_url) 6181{ 6182 usePreparedInsertBlade 6183 ( 6184 'Plugin', 6185 array 6186 ( 6187 'name' => $name, 6188 'longname' => $longname, 6189 'version' => $version, 6190 'home_url' => $home_url, 6191 'state' => 'enabled' 6192 ) 6193 ); 6194} 6195 6196function commitUninstallPlugin ($name) 6197{ 6198 usePreparedDeleteBlade ('Plugin', array ('name' => $name)); 6199} 6200