new Huawei dict items
[racktables] / wwwroot / inc / interface-reports.php
CommitLineData
069689de
DO
1<?php
2
004c2fd2
DO
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
069689de
DO
7function renderSystemReports ()
8{
9 $tmp = array
10 (
11 array
12 (
13 'title' => 'Dictionary/objects',
14 'type' => 'counters',
15 'func' => 'getDictStats'
16 ),
17 array
18 (
19 'title' => 'Rackspace',
20 'type' => 'counters',
21 'func' => 'getRackspaceStats'
22 ),
23 array
24 (
25 'title' => 'Files',
26 'type' => 'counters',
27 'func' => 'getFileStats'
28 ),
29 array
30 (
31 'title' => 'Tags top list',
32 'type' => 'custom',
33 'func' => 'renderTagStats'
34 ),
35 );
36 renderReports ($tmp);
37}
38
39function renderLocalReports ()
40{
41 global $localreports;
42 renderReports ($localreports);
43}
44
45function renderRackCodeReports ()
46{
47 $tmp = array
48 (
49 array
50 (
51 'title' => 'Stats',
52 'type' => 'counters',
53 'func' => 'getRackCodeStats',
069689de
DO
54 ),
55 array
56 (
57 'title' => 'Warnings',
58 'type' => 'messages',
59 'func' => 'getRackCodeWarnings',
069689de
DO
60 ),
61 );
62 renderReports ($tmp);
63}
64
65function renderIPv4Reports ()
66{
67 $tmp = array
68 (
69 array
70 (
71 'title' => 'Stats',
72 'type' => 'counters',
73 'func' => 'getIPv4Stats'
74 ),
75 );
76 renderReports ($tmp);
77}
78
79function renderIPv6Reports ()
80{
81 $tmp = array
82 (
83 array
84 (
85 'title' => 'Stats',
86 'type' => 'counters',
87 'func' => 'getIPv6Stats'
88 ),
89 );
90 renderReports ($tmp);
91}
92
93function renderPortsReport ()
94{
95 $tmp = array();
96 foreach (getPortIIFOptions() as $iif_id => $iif_name)
97 if (count (getPortIIFStats ($iif_id)))
98 $tmp[] = array
99 (
100 'title' => $iif_name,
101 'type' => 'meters',
102 'func' => 'getPortIIFStats',
103 'args' => $iif_id,
104 );
105 renderReports ($tmp);
106}
107
108function render8021QReport ()
109{
110 if (!count ($domains = getVLANDomainOptions()))
111 {
112 echo '<center><h3>(no VLAN configuration exists)</h3></center>';
113 return;
114 }
115 $vlanstats = array();
116 for ($i = VLAN_MIN_ID; $i <= VLAN_MAX_ID; $i++)
117 $vlanstats[$i] = array();
118 $header = '<tr><th>&nbsp;</th>';
119 foreach ($domains as $domain_id => $domain_name)
120 {
121 foreach (getDomainVLANList ($domain_id) as $vlan_id => $vlan_info)
122 $vlanstats[$vlan_id][$domain_id] = $vlan_info;
123 $header .= '<th>' . mkA ($domain_name, 'vlandomain', $domain_id) . '</th>';
124 }
125 $header .= '</tr>';
126 $output = $available = array();
127 for ($i = VLAN_MIN_ID; $i <= VLAN_MAX_ID; $i++)
128 if (!count ($vlanstats[$i]))
129 $available[] = $i;
130 else
131 $output[$i] = FALSE;
132 foreach (listToRanges ($available) as $span)
133 {
134 if ($span['to'] - $span['from'] < 4)
135 for ($i = $span['from']; $i <= $span['to']; $i++)
136 $output[$i] = FALSE;
137 else
138 {
139 $output[$span['from']] = TRUE;
140 $output[$span['to']] = FALSE;
141 }
142 }
143 ksort ($output, SORT_NUMERIC);
144 $header_delay = 0;
145 startPortlet ('VLAN existence per domain');
146 echo '<table border=1 cellspacing=0 cellpadding=5 align=center class=rackspace>';
147 foreach ($output as $vlan_id => $tbc)
148 {
149 if (--$header_delay <= 0)
150 {
151 echo $header;
152 $header_delay = 25;
153 }
154 echo '<tr class="state_' . (count ($vlanstats[$vlan_id]) ? 'T' : 'F');
155 echo '"><th class=tdright>' . $vlan_id . '</th>';
156 foreach (array_keys ($domains) as $domain_id)
157 {
158 echo '<td class=tdcenter>';
159 if (array_key_exists ($domain_id, $vlanstats[$vlan_id]))
160 echo mkA ('&exist;', 'vlan', "${domain_id}-${vlan_id}");
161 else
162 echo '&nbsp;';
163 echo '</td>';
164 }
165 echo '</tr>';
166 if ($tbc)
167 echo '<tr class="state_A"><th>...</th><td colspan=' . count ($domains) . '>&nbsp;</td></tr>';
168 }
169 echo '</table>';
170 finishPortlet();
171}
172
173function renderReports ($what)
174{
175 if (!count ($what))
176 return;
177 echo "<table align=center>\n";
178 foreach ($what as $item)
179 {
069689de
DO
180 echo "<tr><th colspan=2><h3>${item['title']}</h3></th></tr>\n";
181 switch ($item['type'])
182 {
183 case 'counters':
184 if (array_key_exists ('args', $item))
185 $data = $item['func'] ($item['args']);
186 else
187 $data = $item['func'] ();
188 foreach ($data as $header => $data)
189 echo "<tr><td class=tdright>${header}:</td><td class=tdleft>${data}</td></tr>\n";
190 break;
191 case 'messages':
192 if (array_key_exists ('args', $item))
193 $data = $item['func'] ($item['args']);
194 else
195 $data = $item['func'] ();
196 foreach ($data as $msg)
197 echo "<tr class='msg_${msg['class']}'><td class=tdright>${msg['header']}:</td><td class=tdleft>${msg['text']}</td></tr>\n";
198 break;
199 case 'meters':
200 if (array_key_exists ('args', $item))
201 $data = $item['func'] ($item['args']);
202 else
203 $data = $item['func'] ();
204 foreach ($data as $meter)
205 {
206 echo "<tr><td class=tdright>${meter['title']}:</td><td class=tdcenter>";
207 renderProgressBar ($meter['max'] ? $meter['current'] / $meter['max'] : 0);
208 echo '<br><small>' . ($meter['max'] ? $meter['current'] . '/' . $meter['max'] : '0') . '</small></td></tr>';
209 }
210 break;
211 case 'custom':
212 echo "<tr><td colspan=2>";
213 $item['func'] ();
214 echo "</td></tr>\n";
215 break;
216 default:
217 throw new InvalidArgException ('type', $item['type']);
218 }
219 echo "<tr><td colspan=2><hr></td></tr>\n";
220 }
221 echo "</table>\n";
222}
223
224function renderTagStats ()
225{
226 global $taglist;
227 echo '<table border=1><tr><th>tag</th><th>total</th><th>objects</th><th>IPv4 nets</th><th>IPv6 nets</th>';
228 echo '<th>racks</th><th>IPv4 VS</th><th>IPv4 RS pools</th><th>users</th><th>files</th></tr>';
229 $pagebyrealm = array
230 (
231 'file' => 'files&tab=default',
232 'ipv4net' => 'ipv4space&tab=default',
233 'ipv6net' => 'ipv6space&tab=default',
234 'ipv4vs' => 'ipv4slb&tab=default',
235 'ipv4rspool' => 'ipv4slb&tab=rspools',
236 'object' => 'depot&tab=default',
237 'rack' => 'rackspace&tab=default',
238 'user' => 'userlist&tab=default'
239 );
240 foreach (getTagChart (getConfigVar ('TAGS_TOPLIST_SIZE')) as $taginfo)
241 {
242 echo "<tr><td>${taginfo['tag']}</td><td>" . $taginfo['refcnt']['total'] . "</td>";
243 foreach (array ('object', 'ipv4net', 'ipv6net', 'rack', 'ipv4vs', 'ipv4rspool', 'user', 'file') as $realm)
244 {
245 echo '<td>';
246 if (!isset ($taginfo['refcnt'][$realm]))
247 echo '&nbsp;';
248 else
249 {
250 echo "<a href='index.php?page=" . $pagebyrealm[$realm] . "&cft[]=${taginfo['id']}'>";
251 echo $taginfo['refcnt'][$realm] . '</a>';
252 }
253 echo '</td>';
254 }
255 echo '</tr>';
256 }
257 echo '</table>';
258}
259
330f20d8
DO
260function renderExpirations ()
261{
262 global $nextorder, $expirations;
263 $attrmap = getAttrMap();
264 foreach ($expirations as $attr_id => $sections)
265 {
266 startPortlet ($attrmap[$attr_id]['name']);
267 foreach ($sections as $section)
268 {
269 $count = 1;
270 $order = 'odd';
271 $result = scanAttrRelativeDays ($attr_id, $section['from'], $section['to']);
272
273 echo '<table align=center width=60% border=0 cellpadding=5 cellspacing=0 align=center class=cooltable>';
274 echo "<caption>${section['title']}</caption>\n";
275
276 if (! count ($result))
277 {
278 echo "<tr><td colspan=4>(none)</td></tr></table><br>\n";
279 continue;
280 }
281 echo '<tr valign=top><th align=center>Count</th><th align=center>Name</th>';
282 echo "<th align=center>Asset Tag</th><th align=center>OEM S/N 1</th><th align=center>Date Warranty <br> Expires</th></tr>\n";
283 foreach ($result as $row)
284 {
285 $date_value = datetimestrFromTimestamp ($row['uint_value']);
286
287 $object = spotEntity ('object', $row['object_id']);
288 $attributes = getAttrValues ($object['id']);
289 $oem_sn_1 = array_key_exists (1, $attributes) ? $attributes[1]['a_value'] : '&nbsp;';
290 echo '<tr class=' . $section['class'] . $order . ' valign=top>';
ef08756a
DO
291 echo "<td class=tdright>${count}</td>";
292 echo '<td class=tdleft>' . mkCellA ($object) . '</td>';
293 echo "<td class=tdleft>${object['asset_no']}</td>";
294 echo "<td class=tdleft>${oem_sn_1}</td>";
330f20d8
DO
295 echo "<td>${date_value}</td>";
296 echo "</tr>\n";
297 $order = $nextorder[$order];
298 $count++;
299 }
300 echo "</table><br>\n";
301 }
302 finishPortlet ();
303 }
304}
305
306// The validity of some data cannot be guaranteed using foreign keys.
307// Display any invalid rows that have crept in.
308// Possible enhancements:
309// - check for IP addresses whose subnet does not exist in IPvXNetwork (X = 4 or 6)
310// - IPvXAddress, IPvXAllocation, IPvXLog, IPvXRS, IPvXVS
311// - provide links/buttons to delete invalid rows
312// - verify that the current DDL is correct for each DB element
313// - columns, indexes, character sets
314function renderDataIntegrityReport ()
315{
330f20d8
DO
316 $violations = FALSE;
317
318 // check 1: EntityLink rows referencing not-existent relatives
319 // check 1.1: children
320 $realms = array
321 (
322 'location' => 'Location',
323 'object' => 'RackObject',
324 'rack' => 'Rack',
325 'row' => 'Row'
326 );
327 $orphans = array ();
328 foreach ($realms as $realm => $table)
329 {
330 $result = usePreparedSelectBlade
331 (
332 'SELECT EL.* FROM EntityLink EL ' .
333 "LEFT JOIN ${table} ON EL.child_entity_id = ${table}.id " .
334 "WHERE EL.child_entity_type = ? AND ${table}.id IS NULL",
335 array ($realm)
336 );
337 $rows = $result->fetchAll (PDO::FETCH_ASSOC);
338 unset ($result);
339 $orphans = array_merge ($orphans, $rows);
340 }
341 if (count ($orphans))
342 {
343 $violations = TRUE;
344 startPortlet ('EntityLink: Missing Children (' . count ($orphans) . ')');
3b15479f 345 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 346 echo "<tr><th>Parent</th><th>Child Type</th><th>Child ID</th></tr>\n";
330f20d8
DO
347 foreach ($orphans as $orphan)
348 {
349 $realm_name = formatRealmName ($orphan['parent_entity_type']);
350 try
351 {
352 $parent = spotEntity ($orphan['parent_entity_type'], $orphan['parent_entity_id']);
353 $parent_name = $parent['name'];
354 }
355 catch (EntityNotFoundException $e)
356 {
357 $parent_name = 'missing from DB';
358 }
3b15479f 359 echo '<tr>';
330f20d8
DO
360 echo "<td>${realm_name}: ${parent_name}</td>";
361 echo "<td>${orphan['child_entity_type']}</td>";
362 echo "<td>${orphan['child_entity_id']}</td>";
363 echo "</tr>\n";
330f20d8
DO
364 }
365 echo "</table>\n";
366 finishPortLet ();
367 }
368
369 // check 1.2: parents
370 $orphans = array ();
371 foreach ($realms as $realm => $table)
372 {
373 $result = usePreparedSelectBlade
374 (
375 'SELECT EL.* FROM EntityLink EL ' .
376 "LEFT JOIN ${table} ON EL.parent_entity_id = ${table}.id " .
377 "WHERE EL.parent_entity_type = ? AND ${table}.id IS NULL",
378 array ($realm)
379 );
380 $rows = $result->fetchAll (PDO::FETCH_ASSOC);
381 unset ($result);
382 $orphans = array_merge ($orphans, $rows);
383 }
384 if (count ($orphans))
385 {
386 $violations = TRUE;
387 startPortlet ('EntityLink: Missing Parents (' . count ($orphans) . ')');
3b15479f 388 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 389 echo "<tr><th>Child</th><th>Parent Type</th><th>Parent ID</th></tr>\n";
330f20d8
DO
390 foreach ($orphans as $orphan)
391 {
392 $realm_name = formatRealmName ($orphan['child_entity_type']);
393 try
394 {
395 $child = spotEntity ($orphan['child_entity_type'], $orphan['child_entity_id']);
396 $child_name = $child['name'];
397 }
398 catch (EntityNotFoundException $e)
399 {
400 $child_name = 'missing from DB';
401 }
3b15479f 402 echo '<tr>';
330f20d8
DO
403 echo "<td>${realm_name}: ${child_name}</td>";
404 echo "<td>${orphan['parent_entity_type']}</td>";
405 echo "<td>${orphan['parent_entity_id']}</td>";
406 echo "</tr>\n";
330f20d8
DO
407 }
408 echo "</table>\n";
409 finishPortLet ();
410 }
411
412 // check 3: multiple tables referencing non-existent dictionary entries
413 // check 3.1: AttributeMap
414 $orphans = array ();
415 $result = usePreparedSelectBlade
416 (
417 'SELECT AM.*, A.name AS attr_name, C.name AS chapter_name ' .
418 'FROM AttributeMap AM ' .
419 'LEFT JOIN Attribute A ON AM.attr_id = A.id ' .
420 'LEFT JOIN Chapter C ON AM.chapter_id = C.id ' .
421 'LEFT JOIN Dictionary D ON AM.objtype_id = D.dict_key ' .
422 'WHERE D.dict_key IS NULL'
423 );
424 $orphans = $result->fetchAll (PDO::FETCH_ASSOC);
425 unset ($result);
426 if (count ($orphans))
427 {
428 $violations = TRUE;
429 startPortlet ('AttributeMap: Invalid Mappings (' . count ($orphans) . ')');
3b15479f 430 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 431 echo "<tr><th>Attribute</th><th>Chapter</th><th>Object TypeID</th></tr>\n";
330f20d8
DO
432 foreach ($orphans as $orphan)
433 {
3b15479f 434 echo '<tr>';
330f20d8
DO
435 echo "<td>${orphan['attr_name']}</td>";
436 echo "<td>${orphan['chapter_name']}</td>";
437 echo "<td>${orphan['objtype_id']}</td>";
438 echo "</tr>\n";
330f20d8
DO
439 }
440 echo "</table>\n";
441 finishPortLet ();
442 }
443
444 // check 3.2: Object
445 $orphans = array ();
446 $result = usePreparedSelectBlade
447 (
448 'SELECT O.* FROM Object O ' .
449 'LEFT JOIN Dictionary D ON O.objtype_id = D.dict_key ' .
450 'WHERE D.dict_key IS NULL'
451 );
452 $orphans = $result->fetchAll (PDO::FETCH_ASSOC);
453 unset ($result);
454 if (count ($orphans))
455 {
456 $violations = TRUE;
457 startPortlet ('Object: Invalid Types (' . count ($orphans) . ')');
3b15479f 458 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 459 echo "<tr><th>ID</th><th>Name</th><th>Type ID</th></tr>\n";
330f20d8
DO
460 foreach ($orphans as $orphan)
461 {
3b15479f 462 echo '<tr>';
330f20d8
DO
463 echo "<td>${orphan['id']}</td>";
464 echo "<td>${orphan['name']}</td>";
465 echo "<td>${orphan['objtype_id']}</td>";
466 echo "</tr>\n";
330f20d8
DO
467 }
468 echo "</table>\n";
469 finishPortLet ();
470 }
471
472 // check 3.3: ObjectHistory
473 $orphans = array ();
474 $result = usePreparedSelectBlade
475 (
476 'SELECT OH.* FROM ObjectHistory OH ' .
477 'LEFT JOIN Dictionary D ON OH.objtype_id = D.dict_key ' .
478 'WHERE D.dict_key IS NULL'
479 );
480 $orphans = $result->fetchAll (PDO::FETCH_ASSOC);
481 unset ($result);
482 if (count ($orphans))
483 {
484 $violations = TRUE;
485 startPortlet ('ObjectHistory: Invalid Types (' . count ($orphans) . ')');
3b15479f 486 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 487 echo "<tr><th>ID</th><th>Name</th><th>Type ID</th></tr>\n";
330f20d8
DO
488 foreach ($orphans as $orphan)
489 {
3b15479f 490 echo '<tr>';
330f20d8
DO
491 echo "<td>${orphan['id']}</td>";
492 echo "<td>${orphan['name']}</td>";
493 echo "<td>${orphan['objtype_id']}</td>";
494 echo "</tr>\n";
330f20d8
DO
495 }
496 echo "</table>\n";
497 finishPortLet ();
498 }
499
500 // check 3.4: ObjectParentCompat
501 $orphans = array ();
502 $result = usePreparedSelectBlade
503 (
504 'SELECT OPC.*, PD.dict_value AS parent_name, CD.dict_value AS child_name '.
505 'FROM ObjectParentCompat OPC ' .
506 'LEFT JOIN Dictionary PD ON OPC.parent_objtype_id = PD.dict_key ' .
507 'LEFT JOIN Dictionary CD ON OPC.child_objtype_id = CD.dict_key ' .
508 'WHERE PD.dict_key IS NULL OR CD.dict_key IS NULL'
509 );
510 $orphans = $result->fetchAll (PDO::FETCH_ASSOC);
511 unset ($result);
512 if (count ($orphans))
513 {
514 $violations = TRUE;
515 startPortlet ('Object Container Compatibility rules: Invalid Parent or Child Type (' . count ($orphans) . ')');
3b15479f 516 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 517 echo "<tr><th>Parent</th><th>Parent Type ID</th><th>Child</th><th>Child Type ID</th></tr>\n";
330f20d8
DO
518 foreach ($orphans as $orphan)
519 {
3b15479f 520 echo '<tr>';
330f20d8
DO
521 echo "<td>${orphan['parent_name']}</td>";
522 echo "<td>${orphan['parent_objtype_id']}</td>";
523 echo "<td>${orphan['child_name']}</td>";
524 echo "<td>${orphan['child_objtype_id']}</td>";
525 echo "</tr>\n";
330f20d8
DO
526 }
527 echo "</table>\n";
528 finishPortLet ();
529 }
530
531 // check 4: relationships that violate ObjectParentCompat Rules
532 $invalids = array ();
533 $result = usePreparedSelectBlade
534 (
535 'SELECT CO.id AS child_id, CO.objtype_id AS child_type_id, CD.dict_value AS child_type, CO.name AS child_name, ' .
536 'PO.id AS parent_id, PO.objtype_id AS parent_type_id, PD.dict_value AS parent_type, PO.name AS parent_name ' .
537 'FROM Object CO ' .
538 'LEFT JOIN EntityLink EL ON CO.id = EL.child_entity_id ' .
539 'LEFT JOIN Object PO ON EL.parent_entity_id = PO.id ' .
540 'LEFT JOIN ObjectParentCompat OPC ON PO.objtype_id = OPC.parent_objtype_id ' .
541 'LEFT JOIN Dictionary PD ON PO.objtype_id = PD.dict_key ' .
542 'LEFT JOIN Dictionary CD ON CO.objtype_id = CD.dict_key ' .
543 "WHERE EL.parent_entity_type = 'object' AND EL.child_entity_type = 'object' " .
544 'AND OPC.parent_objtype_id IS NULL'
545 );
546 $invalids = $result->fetchAll (PDO::FETCH_ASSOC);
547 unset ($result);
548 if (count ($invalids))
549 {
550 $violations = TRUE;
551 startPortlet ('Objects: Violate Object Container Compatibility rules (' . count ($invalids) . ')');
3b15479f 552 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 553 echo "<tr><th>Contained Obj Name</th><th>Contained Obj Type</th><th>Container Obj Name</th><th>Container Obj Type</th></tr>\n";
330f20d8
DO
554 foreach ($invalids as $invalid)
555 {
3b15479f 556 echo '<tr>';
330f20d8
DO
557 echo "<td>${invalid['child_name']}</td>";
558 echo "<td>${invalid['child_type']}</td>";
559 echo "<td>${invalid['parent_name']}</td>";
560 echo "<td>${invalid['parent_type']}</td>";
561 echo "</tr>\n";
330f20d8
DO
562 }
563 echo "</table>\n";
564 finishPortLet ();
565 }
566
567 // check 5: Links that violate PortCompat Rules
568 $invalids = array ();
569 $result = usePreparedSelectBlade
570 (
571 'SELECT OA.id AS obja_id, OA.name AS obja_name, L.porta AS porta_id, PA.name AS porta_name, POIA.oif_name AS porta_type, ' .
572 'OB.id AS objb_id, OB.name AS objb_name, L.portb AS portb_id, PB.name AS portb_name, POIB.oif_name AS portb_type ' .
573 'FROM Link L ' .
574 'LEFT JOIN Port PA ON L.porta = PA.id ' .
575 'LEFT JOIN Object OA ON PA.object_id = OA.id ' .
576 'LEFT JOIN PortOuterInterface POIA ON PA.type = POIA.id ' .
577 'LEFT JOIN Port PB ON L.portb = PB.id ' .
578 'LEFT JOIN Object OB ON PB.object_id = OB.id ' .
579 'LEFT JOIN PortOuterInterface POIB ON PB.type = POIB.id ' .
580 'LEFT JOIN PortCompat PC on PA.type = PC.type1 AND PB.type = PC.type2 ' .
581 'WHERE PC.type1 IS NULL OR PC.type2 IS NULL'
582 );
583 $invalids = $result->fetchAll (PDO::FETCH_ASSOC);
584 unset ($result);
585 if (count ($invalids))
586 {
587 $violations = TRUE;
588 startPortlet ('Port Links: Violate Port Compatibility Rules (' . count ($invalids) . ')');
3b15479f 589 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 590 echo "<tr><th>Object A</th><th>Port A Name</th><th>Port A Type</th><th>Object B</th><th>Port B Name</th><th>Port B Type</th></tr>\n";
330f20d8
DO
591 foreach ($invalids as $invalid)
592 {
3b15479f 593 echo '<tr>';
330f20d8
DO
594 echo "<td>${invalid['obja_name']}</td>";
595 echo "<td>${invalid['porta_name']}</td>";
596 echo "<td>${invalid['porta_type']}</td>";
597 echo "<td>${invalid['objb_name']}</td>";
598 echo "<td>${invalid['portb_name']}</td>";
599 echo "<td>${invalid['portb_type']}</td>";
600 echo "</tr>\n";
330f20d8
DO
601 }
602 echo "</table>\n";
603 finishPortLet ();
604 }
605
606 // check 6: TagStorage rows referencing non-existent parents
607 $realms = array
608 (
609 'file' => array ('table' => 'File', 'column' => 'id'),
610 'ipv4net' => array ('table' => 'IPv4Network', 'column' => 'id'),
611 'ipv4rspool' => array ('table' => 'IPv4RSPool', 'column' => 'id'),
612 'ipv4vs' => array ('table' => 'IPv4VS', 'column' => 'id'),
613 'ipv6net' => array ('table' => 'IPv6Network', 'column' => 'id'),
614 'ipvs' => array ('table' => 'VS', 'column' => 'id'),
615 'location' => array ('table' => 'Location', 'column' => 'id'),
616 'object' => array ('table' => 'RackObject', 'column' => 'id'),
617 'rack' => array ('table' => 'Rack', 'column' => 'id'),
618 'user' => array ('table' => 'UserAccount', 'column' => 'user_id'),
619 'vst' => array ('table' => 'VLANSwitchTemplate', 'column' => 'id'),
620 );
621 $orphans = array ();
622 foreach ($realms as $realm => $details)
623 {
624 $result = usePreparedSelectBlade
625 (
626 'SELECT TS.*, TT.tag FROM TagStorage TS ' .
627 'LEFT JOIN TagTree TT ON TS.tag_id = TT.id ' .
628 "LEFT JOIN ${details['table']} ON TS.entity_id = ${details['table']}.${details['column']} " .
629 "WHERE TS.entity_realm = ? AND ${details['table']}.${details['column']} IS NULL",
630 array ($realm)
631 );
632 $rows = $result->fetchAll (PDO::FETCH_ASSOC);
633 unset ($result);
634 $orphans = array_merge ($orphans, $rows);
635 }
636 if (count ($orphans))
637 {
638 $violations = TRUE;
639 startPortlet ('TagStorage: Missing Parents (' . count ($orphans) . ')');
3b15479f 640 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 641 echo "<tr><th>Tag</th><th>Parent Type</th><th>Parent ID</th></tr>\n";
330f20d8
DO
642 foreach ($orphans as $orphan)
643 {
644 $realm_name = formatRealmName ($orphan['entity_realm']);
3b15479f 645 echo '<tr>';
330f20d8
DO
646 echo "<td>${orphan['tag']}</td>";
647 echo "<td>${realm_name}</td>";
648 echo "<td>${orphan['entity_id']}</td>";
649 echo "</tr>\n";
330f20d8
DO
650 }
651 echo "</table>\n";
652 finishPortLet ();
653 }
654
655 // check 7: FileLink rows referencing non-existent parents
656 // re-use the realms list from the TagStorage check, with a few mods
657 unset ($realms['file'], $realms['vst']);
658 $realms['row'] = array ('table' => 'Row', 'column' => 'id');
659 $orphans = array ();
660 foreach ($realms as $realm => $details)
661 {
662 $result = usePreparedSelectBlade
663 (
664 'SELECT FL.*, F.name FROM FileLink FL ' .
665 'LEFT JOIN File F ON FL.file_id = F.id ' .
666 "LEFT JOIN ${details['table']} ON FL.entity_id = ${details['table']}.${details['column']} " .
667 "WHERE FL.entity_type = ? AND ${details['table']}.${details['column']} IS NULL",
668 array ($realm)
669 );
670 $rows = $result->fetchAll (PDO::FETCH_ASSOC);
671 unset ($result);
672 $orphans = array_merge ($orphans, $rows);
673 }
674 if (count ($orphans))
675 {
676 $violations = TRUE;
677 startPortlet ('FileLink: Missing Parents (' . count ($orphans) . ')');
3b15479f 678 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 679 echo "<tr><th>File</th><th>Parent Type</th><th>Parent ID</th></tr>\n";
330f20d8
DO
680 foreach ($orphans as $orphan)
681 {
682 $realm_name = formatRealmName ($orphan['entity_type']);
3b15479f 683 echo '<tr>';
330f20d8
DO
684 echo "<td>${orphan['name']}</td>";
685 echo "<td>${realm_name}</td>";
686 echo "<td>${orphan['entity_id']}</td>";
687 echo "</tr>\n";
330f20d8
DO
688 }
689 echo "</table>\n";
690 finishPortLet ();
691 }
692
693 // check 8: missing triggers
694 $triggers= array
695 (
696 'Link-before-insert' => 'Link',
697 'Link-before-update' => 'Link'
698 );
699 $result = usePreparedSelectBlade
700 (
701 'SELECT TRIGGER_NAME, EVENT_OBJECT_TABLE ' .
702 'FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = SCHEMA()'
703 );
704 $rows = $result->fetchAll (PDO::FETCH_ASSOC);
705 unset ($result);
706 $existing_triggers = $missing_triggers = array ();
707 foreach ($rows as $row)
708 $existing_triggers[$row['TRIGGER_NAME']] = $row['EVENT_OBJECT_TABLE'];
709 foreach ($triggers as $trigger => $table)
710 if (! array_key_exists ($trigger, $existing_triggers))
711 $missing_triggers[$trigger] = $table;
712 if (count ($missing_triggers))
713 {
714 $violations = TRUE;
715 startPortlet ('Missing Triggers (' . count ($missing_triggers) . ')');
3b15479f 716 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 717 echo "<tr><th>Table</th><th>Trigger</th></tr>\n";
330f20d8
DO
718 foreach ($missing_triggers as $trigger => $table)
719 {
3b15479f 720 echo '<tr>';
330f20d8
DO
721 echo "<td>${table}</td>";
722 echo "<td>${trigger}</td>";
723 echo "</tr>\n";
330f20d8
DO
724 }
725 echo "</table>\n";
726 finishPortLet ();
727 }
728
729 // check 9: missing foreign keys
730 $fkeys= array
731 (
732 'Atom-FK-molecule_id' => 'Atom',
733 'Atom-FK-rack_id' => 'Atom',
734 'AttributeMap-FK-chapter_id' => 'AttributeMap',
735 'AttributeMap-FK-attr_id' => 'AttributeMap',
736 'AttributeValue-FK-map' => 'AttributeValue',
737 'AttributeValue-FK-object' => 'AttributeValue',
738 'CachedPAV-FK-object-port' => 'CachedPAV',
739 'CachedPAV-FK-vlan_id' => 'CachedPAV',
740 'CachedPNV-FK-compound' => 'CachedPNV',
741 'CachedPVM-FK-object_id' => 'CachedPVM',
742 'CactiGraph-FK-server_id' => 'CactiGraph',
743 'CactiGraph-FK-server_id' => 'CactiGraph',
744 'Dictionary-FK-chapter_id' => 'Dictionary',
745 'FileLink-File_fkey' => 'FileLink',
746 'IPv4Allocation-FK-object_id' => 'IPv4Allocation',
747 'IPv4LB-FK-vs_id' => 'IPv4LB',
748 'IPv4LB-FK-object_id' => 'IPv4LB',
749 'IPv4LB-FK-rspool_id' => 'IPv4LB',
750 'IPv4NAT-FK-object_id' => 'IPv4NAT',
751 'IPv4RS-FK' => 'IPv4RS',
752 'IPv6Allocation-FK-object_id' => 'IPv6Allocation',
753 'Link-FK-a' => 'Link',
754 'Link-FK-b' => 'Link',
755 'MountOperation-FK-object_id' => 'MountOperation',
756 'MountOperation-FK-old_molecule_id' => 'MountOperation',
757 'MountOperation-FK-new_molecule_id' => 'MountOperation',
758 'MuninGraph-FK-server_id' => 'MuninGraph',
759 'MuninGraph-FK-server_id' => 'MuninGraph',
760 'ObjectHistory-FK-object_id' => 'ObjectHistory',
761 'ObjectLog-FK-object_id' => 'ObjectLog',
762 'PatchCableConnectorCompat-FK-connector_id' => 'PatchCableConnectorCompat',
763 'PatchCableConnectorCompat-FK-pctype_id' => 'PatchCableConnectorCompat',
764 'PatchCableHeap-FK-compat1' => 'PatchCableHeap',
765 'PatchCableHeap-FK-compat2' => 'PatchCableHeap',
766 'PatchCableHeapLog-FK-heap_id' => 'PatchCableHeapLog',
767 'PatchCableOIFCompat-FK-oif_id' => 'PatchCableOIFCompat',
768 'PatchCableOIFCompat-FK-pctype_id' => 'PatchCableOIFCompat',
769 'Port-FK-iif-oif' => 'Port',
770 'Port-FK-object_id' => 'Port',
771 'PortAllowedVLAN-FK-object-port' => 'PortAllowedVLAN',
772 'PortAllowedVLAN-FK-vlan_id' => 'PortAllowedVLAN',
773 'PortCompat-FK-oif_id1' => 'PortCompat',
774 'PortCompat-FK-oif_id2' => 'PortCompat',
775 'PortInterfaceCompat-FK-iif_id' => 'PortInterfaceCompat',
776 'PortInterfaceCompat-FK-oif_id' => 'PortInterfaceCompat',
777 'PortLog_ibfk_1' => 'PortLog',
778 'PortNativeVLAN-FK-compound' => 'PortNativeVLAN',
779 'PortVLANMode-FK-object-port' => 'PortVLANMode',
780 'RackSpace-FK-rack_id' => 'RackSpace',
781 'RackSpace-FK-object_id' => 'RackSpace',
782 'TagStorage-FK-TagTree' => 'TagStorage',
783 'TagTree-K-parent_id' => 'TagTree',
784 'UserConfig-FK-varname' => 'UserConfig',
785 'VLANDescription-FK-domain_id' => 'VLANDescription',
786 'VLANDescription-FK-vlan_id' => 'VLANDescription',
787 'VLANIPv4-FK-compound' => 'VLANIPv4',
788 'VLANIPv4-FK-ipv4net_id' => 'VLANIPv4',
789 'VLANIPv6-FK-compound' => 'VLANIPv6',
790 'VLANIPv6-FK-ipv6net_id' => 'VLANIPv6',
791 'VLANSTRule-FK-vst_id' => 'VLANSTRule',
792 'VLANSwitch-FK-domain_id' => 'VLANSwitch',
793 'VLANSwitch-FK-object_id' => 'VLANSwitch',
794 'VLANSwitch-FK-template_id' => 'VLANSwitch',
795 'VSEnabledIPs-FK-object_id' => 'VSEnabledIPs',
796 'VSEnabledIPs-FK-rspool_id' => 'VSEnabledIPs',
797 'VSEnabledIPs-FK-vs_id-vip' => 'VSEnabledIPs',
798 'VSEnabledPorts-FK-object_id' => 'VSEnabledPorts',
799 'VSEnabledPorts-FK-rspool_id' => 'VSEnabledPorts',
800 'VSEnabledPorts-FK-vs_id-proto-vport' => 'VSEnabledPorts',
801 'VSIPs-vs_id' => 'VSIPs',
802 'VS-vs_id' => 'VSPorts'
803 );
804 $result = usePreparedSelectBlade
805 (
806 'SELECT CONSTRAINT_NAME, TABLE_NAME ' .
807 'FROM information_schema.TABLE_CONSTRAINTS ' .
808 "WHERE CONSTRAINT_SCHEMA = SCHEMA() AND CONSTRAINT_TYPE = 'FOREIGN KEY'"
809 );
810 $rows = $result->fetchAll (PDO::FETCH_ASSOC);
811 unset ($result);
812 $existing_fkeys = $missing_fkeys = array ();
813 foreach ($rows as $row)
814 $existing_fkeys[$row['CONSTRAINT_NAME']] = $row['TABLE_NAME'];
815 foreach ($fkeys as $fkey => $table)
816 if (! array_key_exists ($fkey, $existing_fkeys))
817 $missing_fkeys[$fkey] = $table;
818 if (count ($missing_fkeys))
819 {
820 $violations = TRUE;
821 startPortlet ('Missing Foreign Keys (' . count ($missing_fkeys) . ')');
3b15479f 822 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 823 echo "<tr><th>Table</th><th>Key</th></tr>\n";
330f20d8
DO
824 foreach ($missing_fkeys as $fkey => $table)
825 {
3b15479f 826 echo '<tr>';
330f20d8
DO
827 echo "<td>${table}</td>";
828 echo "<td>${fkey}</td>";
829 echo "</tr>\n";
330f20d8
DO
830 }
831 echo "</table>\n";
832 finishPortLet ();
833 }
834
835 // check 10: circular references
836 // - all affected members of the tree are displayed
837 // - it would be beneficial to only display the offending records
838 // check 10.1: locations
839 $invalids = array ();
840 $locations = listCells ('location');
841 foreach ($locations as $location)
842 {
843 try
844 {
845 $children = getLocationChildrenList ($location['id']);
846 }
847 catch (RackTablesError $e)
848 {
849 $invalids[] = $location;
850 }
851 }
852 if (count ($invalids))
853 {
854 $violations = TRUE;
855 startPortlet ('Locations: Tree Contains Circular References (' . count ($invalids) . ')');
3b15479f 856 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 857 echo "<tr><th>Child ID</th><th>Child Location</th><th>Parent ID</th><th>Parent Location</th></tr>\n";
330f20d8
DO
858 foreach ($invalids as $invalid)
859 {
3b15479f 860 echo '<tr>';
330f20d8
DO
861 echo "<td>${invalid['id']}</td>";
862 echo "<td>${invalid['name']}</td>";
863 echo "<td>${invalid['parent_id']}</td>";
864 echo "<td>${invalid['parent_name']}</td>";
865 echo "</tr>\n";
330f20d8
DO
866 }
867 echo "</table>\n";
868 finishPortLet ();
869 }
870
871 // check 10.2: objects
872 $invalids = array ();
873 $objects = listCells ('object');
874 foreach ($objects as $object)
875 {
876 try
877 {
878 $children = getObjectContentsList ($object['id']);
879 }
880 catch (RackTablesError $e)
881 {
882 $invalids[] = $object;
883 }
884 }
885 if (count ($invalids))
886 {
887 $violations = TRUE;
888 startPortlet ('Objects: Tree Contains Circular References (' . count ($invalids) . ')');
3b15479f 889 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 890 echo "<tr><th>Contained ID</th><th>Contained Object</th><th>Container ID</th><th>Container Object</th></tr>\n";
330f20d8
DO
891 foreach ($invalids as $invalid)
892 {
3b15479f 893 echo '<tr>';
330f20d8
DO
894 echo "<td>${invalid['id']}</td>";
895 echo "<td>${invalid['name']}</td>";
896 echo "<td>${invalid['container_id']}</td>";
897 echo "<td>${invalid['container_name']}</td>";
898 echo "</tr>\n";
330f20d8
DO
899 }
900 echo "</table>\n";
901 finishPortLet ();
902 }
903
904 // check 10.3: tags
a04b65ce 905 global $taglist;
b46bd2c6 906 $invalids = getInvalidNodes ($taglist);
330f20d8
DO
907 if (count ($invalids))
908 {
909 $violations = TRUE;
910 startPortlet ('Tags: Tree Contains Circular References (' . count ($invalids) . ')');
3b15479f 911 echo "<table cellpadding=5 cellspacing=0 align=center class='cooltable zebra'>\n";
330f20d8 912 echo "<tr><th>Child ID</th><th>Child Tag</th><th>Parent ID</th><th>Parent Tag</th></tr>\n";
330f20d8
DO
913 foreach ($invalids as $invalid)
914 {
3b15479f 915 echo '<tr>';
330f20d8
DO
916 echo "<td>${invalid['id']}</td>";
917 echo "<td>${invalid['tag']}</td>";
918 echo "<td>${invalid['parent_id']}</td>";
a04b65ce 919 printf('<td>%s</td>', $taglist[$invalid['parent_id']]['tag']);
330f20d8 920 echo "</tr>\n";
330f20d8
DO
921 }
922 echo "</table>\n";
923 finishPortLet ();
924 }
925
926 if (! $violations)
7941b2e1 927 echo '<h2 class=centered>No integrity violations found</h2>';
330f20d8
DO
928}
929
70bc004f 930?>