4 * This file is a library of computational functions for RackTables.
9 $loclist[1] = 'interior';
11 $loclist['front'] = 0;
12 $loclist['interior'] = 1;
14 $template[0] = array (TRUE, TRUE, TRUE);
15 $template[1] = array (TRUE, TRUE, FALSE);
16 $template[2] = array (FALSE, TRUE, TRUE);
17 $template[3] = array (TRUE, FALSE, FALSE);
18 $template[4] = array (FALSE, TRUE, FALSE);
19 $template[5] = array (FALSE, FALSE, TRUE);
20 $templateWidth[0] = 3;
21 $templateWidth[1] = 2;
22 $templateWidth[2] = 2;
23 $templateWidth[3] = 1;
24 $templateWidth[4] = 1;
25 $templateWidth[5] = 1;
27 // Entity type by page number mapping is 1:1 atm, but may change later.
28 $etype_by_pageno = array
30 'ipv4net' => 'ipv4net',
31 'ipv4rspool' => 'ipv4rspool',
39 // Rack thumbnail image width summands: "front", "interior" and "rear" elements w/o surrounding border.
49 32 => '255.255.255.255',
50 31 => '255.255.255.254',
51 30 => '255.255.255.252',
52 29 => '255.255.255.248',
53 28 => '255.255.255.240',
54 27 => '255.255.255.224',
55 26 => '255.255.255.192',
56 25 => '255.255.255.128',
57 24 => '255.255.255.0',
58 23 => '255.255.254.0',
59 22 => '255.255.252.0',
60 21 => '255.255.248.0',
61 20 => '255.255.240.0',
62 19 => '255.255.224.0',
63 18 => '255.255.192.0',
64 17 => '255.255.128.0',
83 $wildcardbylen = array
105 12 => '0.15.255.255',
106 11 => '0.31.255.255',
107 10 => '0.63.255.255',
108 9 => '0.127.255.255',
109 8 => '0.255.255.255',
110 7 => '1.255.255.255',
111 6 => '3.255.255.255',
112 5 => '7.255.255.255',
113 4 => '15.255.255.255',
114 3 => '31.255.255.255',
115 2 => '63.255.255.255',
116 1 => '127.255.255.255'
119 // This function assures that specified argument was passed
120 // and is a number greater than zero.
121 function assertUIntArg ($argname, $allow_zero = FALSE)
123 if (!isset ($_REQUEST[$argname]))
124 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is missing');
125 if (!is_numeric ($_REQUEST[$argname]))
126 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a number');
127 if ($_REQUEST[$argname] < 0)
128 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is less than zero');
129 if (!$allow_zero and $_REQUEST[$argname] == 0)
130 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is zero');
133 // This function assures that specified argument was passed
134 // and is a non-empty string.
135 function assertStringArg ($argname, $ok_if_empty = FALSE)
137 if (!isset ($_REQUEST[$argname]))
138 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is missing');
139 if (!is_string ($_REQUEST[$argname]))
140 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
141 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
142 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
145 function assertBoolArg ($argname, $ok_if_empty = FALSE)
147 if (!isset ($_REQUEST[$argname]))
148 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is missing');
149 if (!is_string ($_REQUEST[$argname]) or $_REQUEST[$argname] != 'on')
150 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
151 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
152 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
155 function assertIPv4Arg ($argname, $ok_if_empty = FALSE)
157 assertStringArg ($argname, $ok_if_empty);
158 if (strlen ($_REQUEST[$argname]) and long2ip (ip2long ($_REQUEST[$argname])) !== $_REQUEST[$argname])
159 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a valid ipv4 address');
162 function assertPCREArg ($argname)
164 assertStringArg ($argname, TRUE); // empty pattern is Ok
165 if (FALSE === preg_match ($_REQUEST[$argname], 'test'))
166 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'PCRE validation failed');
169 // Objects of some types should be explicitly shown as
170 // anonymous (labelless). This function is a single place where the
171 // decision about displayed name is made.
172 function setDisplayedName (&$cell)
174 if ($cell['name'] != '')
175 $cell['dname'] = $cell['name'];
178 $cell['atags'][] = array ('tag' => '$nameless');
179 if (considerConfiguredConstraint ($cell, 'NAMEWARN_LISTSRC'))
180 $cell['dname'] = 'ANONYMOUS ' . decodeObjectType ($cell['objtype_id'], 'o');
182 $cell['dname'] = '[' . decodeObjectType ($cell['objtype_id'], 'o') . ']';
186 // This function finds height of solid rectangle of atoms, which are all
187 // assigned to the same object. Rectangle base is defined by specified
189 function rectHeight ($rackData, $startRow, $template_idx)
192 // The first met object_id is used to match all the folowing IDs.
197 for ($locidx = 0; $locidx < 3; $locidx++
)
199 // At least one value in template is TRUE, but the following block
200 // can meet 'skipped' atoms. Let's ensure we have something after processing
202 if ($template[$template_idx][$locidx])
204 if (isset ($rackData[$startRow - $height][$locidx]['skipped']))
206 if (isset ($rackData[$startRow - $height][$locidx]['rowspan']))
208 if (isset ($rackData[$startRow - $height][$locidx]['colspan']))
210 if ($rackData[$startRow - $height][$locidx]['state'] != 'T')
213 $object_id = $rackData[$startRow - $height][$locidx]['object_id'];
214 if ($object_id != $rackData[$startRow - $height][$locidx]['object_id'])
218 // If the first row can't offer anything, bail out.
219 if ($height == 0 and $object_id == 0)
223 while ($startRow - $height > 0);
224 # echo "for startRow==${startRow} and template==(" . ($template[$template_idx][0] ? 'T' : 'F');
225 # echo ', ' . ($template[$template_idx][1] ? 'T' : 'F') . ', ' . ($template[$template_idx][2] ? 'T' : 'F');
226 # echo ") height==${height}<br>\n";
230 // This function marks atoms to be avoided by rectHeight() and assigns rowspan/colspan
232 function markSpan (&$rackData, $startRow, $maxheight, $template_idx)
234 global $template, $templateWidth;
236 for ($height = 0; $height < $maxheight; $height++
)
238 for ($locidx = 0; $locidx < 3; $locidx++
)
240 if ($template[$template_idx][$locidx])
242 // Add colspan/rowspan to the first row met and mark the following ones to skip.
243 // Explicitly show even single-cell spanned atoms, because rectHeight()
244 // is expeciting this data for correct calculation.
246 $rackData[$startRow - $height][$locidx]['skipped'] = TRUE;
249 $colspan = $templateWidth[$template_idx];
251 $rackData[$startRow - $height][$locidx]['colspan'] = $colspan;
253 $rackData[$startRow - $height][$locidx]['rowspan'] = $maxheight;
261 // This function sets rowspan/solspan/skipped atom attributes for renderRack()
262 // What we actually have to do is to find _all_ possible rectangles for each unit
263 // and then select the widest of those with the maximal square.
264 function markAllSpans (&$rackData = NULL)
266 if ($rackData == NULL)
268 showWarning ('Invalid rackData', __FUNCTION__
);
271 for ($i = $rackData['height']; $i > 0; $i--)
272 while (markBestSpan ($rackData, $i));
275 // Calculate height of 6 possible span templates (array is presorted by width
276 // descending) and mark the best (if any).
277 function markBestSpan (&$rackData, $i)
279 global $template, $templateWidth;
280 for ($j = 0; $j < 6; $j++
)
282 $height[$j] = rectHeight ($rackData, $i, $j);
283 $square[$j] = $height[$j] * $templateWidth[$j];
285 // find the widest rectangle of those with maximal height
286 $maxsquare = max ($square);
289 $best_template_index = 0;
290 for ($j = 0; $j < 6; $j++
)
291 if ($square[$j] == $maxsquare)
293 $best_template_index = $j;
294 $bestheight = $height[$j];
297 // distribute span marks
298 markSpan ($rackData, $i, $bestheight, $best_template_index);
302 // We can mount 'F' atoms and unmount our own 'T' atoms.
303 function applyObjectMountMask (&$rackData, $object_id)
305 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
306 for ($locidx = 0; $locidx < 3; $locidx++
)
307 switch ($rackData[$unit_no][$locidx]['state'])
310 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
313 $rackData[$unit_no][$locidx]['enabled'] = ($rackData[$unit_no][$locidx]['object_id'] == $object_id);
316 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
320 // Design change means transition between 'F' and 'A' and back.
321 function applyRackDesignMask (&$rackData)
323 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
324 for ($locidx = 0; $locidx < 3; $locidx++
)
325 switch ($rackData[$unit_no][$locidx]['state'])
329 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
332 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
336 // The same for 'F' and 'U'.
337 function applyRackProblemMask (&$rackData)
339 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
340 for ($locidx = 0; $locidx < 3; $locidx++
)
341 switch ($rackData[$unit_no][$locidx]['state'])
345 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
348 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
352 // This function highlights specified object (and removes previous highlight).
353 function highlightObject (&$rackData, $object_id)
355 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
356 for ($locidx = 0; $locidx < 3; $locidx++
)
359 $rackData[$unit_no][$locidx]['state'] == 'T' and
360 $rackData[$unit_no][$locidx]['object_id'] == $object_id
362 $rackData[$unit_no][$locidx]['hl'] = 'h';
364 unset ($rackData[$unit_no][$locidx]['hl']);
367 // This function marks atoms to selected or not depending on their current state.
368 function markupAtomGrid (&$data, $checked_state)
370 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
371 for ($locidx = 0; $locidx < 3; $locidx++
)
373 if (!($data[$unit_no][$locidx]['enabled'] === TRUE))
375 if ($data[$unit_no][$locidx]['state'] == $checked_state)
376 $data[$unit_no][$locidx]['checked'] = ' checked';
378 $data[$unit_no][$locidx]['checked'] = '';
382 // This function is almost a clone of processGridForm(), but doesn't save anything to database
383 // Return value is the changed rack data.
384 // Here we assume that correct filter has already been applied, so we just
385 // set or unset checkbox inputs w/o changing atom state.
386 function mergeGridFormToRack (&$rackData)
388 $rack_id = $rackData['id'];
389 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
390 for ($locidx = 0; $locidx < 3; $locidx++
)
392 if ($rackData[$unit_no][$locidx]['enabled'] != TRUE)
394 $inputname = "atom_${rack_id}_${unit_no}_${locidx}";
395 if (isset ($_REQUEST[$inputname]) and $_REQUEST[$inputname] == 'on')
396 $rackData[$unit_no][$locidx]['checked'] = ' checked';
398 $rackData[$unit_no][$locidx]['checked'] = '';
402 // netmask conversion from length to number
403 function binMaskFromDec ($maskL)
405 $map_straight = array (
440 return $map_straight[$maskL];
443 // complementary value
444 function binInvMaskFromDec ($maskL)
481 return $map_compl[$maskL];
484 // This function looks up 'has_problems' flag for 'T' atoms
485 // and modifies 'hl' key. May be, this should be better done
486 // in amplifyCell(). We don't honour 'skipped' key, because
487 // the function is also used for thumb creation.
488 function markupObjectProblems (&$rackData)
490 for ($i = $rackData['height']; $i > 0; $i--)
491 for ($locidx = 0; $locidx < 3; $locidx++
)
492 if ($rackData[$i][$locidx]['state'] == 'T')
494 $object = spotEntity ('object', $rackData[$i][$locidx]['object_id']);
495 if ($object['has_problems'] == 'yes')
497 // Object can be already highlighted.
498 if (isset ($rackData[$i][$locidx]['hl']))
499 $rackData[$i][$locidx]['hl'] = $rackData[$i][$locidx]['hl'] . 'w';
501 $rackData[$i][$locidx]['hl'] = 'w';
506 // Return a uniformly (010203040506 or 0102030405060708) formatted address, if it is present
507 // in the provided string, an empty string for an empty string or NULL for error.
508 function l2addressForDatabase ($string)
510 $string = strtoupper ($string);
513 case ($string == '' or preg_match (RE_L2_SOLID
, $string) or preg_match (RE_L2_WWN_SOLID
, $string)):
515 case (preg_match (RE_L2_IFCFG
, $string) or preg_match (RE_L2_WWN_COLON
, $string)):
516 // reformat output of SunOS ifconfig
518 foreach (explode (':', $string) as $byte)
519 $ret .= (strlen ($byte) == 1 ?
'0' : '') . $byte;
521 case (preg_match (RE_L2_CISCO
, $string)):
522 return str_replace ('.', '', $string);
523 case (preg_match (RE_L2_IPCFG
, $string) or preg_match (RE_L2_WWN_HYPHEN
, $string)):
524 return str_replace ('-', '', $string);
530 function l2addressFromDatabase ($string)
532 switch (strlen ($string))
535 case 16: // FireWire/Fibre Channel
536 $ret = implode (':', str_split ($string, 2));
545 // The following 2 functions return previous and next rack IDs for
546 // a given rack ID. The order of racks is the same as in renderRackspace()
548 function getPrevIDforRack ($row_id = 0, $rack_id = 0)
550 if ($row_id <= 0 or $rack_id <= 0)
552 showWarning ('Invalid arguments passed', __FUNCTION__
);
555 $rackList = listCells ('rack', $row_id);
556 doubleLink ($rackList);
557 if (isset ($rackList[$rack_id]['prev_key']))
558 return $rackList[$rack_id]['prev_key'];
562 function getNextIDforRack ($row_id = 0, $rack_id = 0)
564 if ($row_id <= 0 or $rack_id <= 0)
566 showWarning ('Invalid arguments passed', __FUNCTION__
);
569 $rackList = listCells ('rack', $row_id);
570 doubleLink ($rackList);
571 if (isset ($rackList[$rack_id]['next_key']))
572 return $rackList[$rack_id]['next_key'];
576 // This function finds previous and next array keys for each array key and
577 // modifies its argument accordingly.
578 function doubleLink (&$array)
581 foreach (array_keys ($array) as $key)
585 $array[$key]['prev_key'] = $prev_key;
586 $array[$prev_key]['next_key'] = $key;
592 function sortTokenize ($a, $b)
598 $a = preg_replace('/[^a-zA-Z0-9]/',' ',$a);
599 $a = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$a);
600 $a = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$a);
607 $b = preg_replace('/[^a-zA-Z0-9]/',' ',$b);
608 $b = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$b);
609 $b = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$b);
614 $ar = explode(' ', $a);
615 $br = explode(' ', $b);
616 for ($i=0; $i<count($ar) && $i<count($br); $i++
)
619 if (is_numeric($ar[$i]) and is_numeric($br[$i]))
620 $ret = ($ar[$i]==$br[$i])?
0:($ar[$i]<$br[$i]?
-1:1);
622 $ret = strcasecmp($ar[$i], $br[$i]);
633 function sortByName ($a, $b)
635 $result = sortTokenize ($a['name'], $b['name']);
638 if ($a['iif_id'] != $b['iif_id'])
639 return $a['iif_id'] - $b['iif_id'];
640 $result = strcmp ($a['label'], $b['label']);
643 $result = strcmp ($a['l2address'], $b['l2address']);
646 return $a['id'] - $b['id'];
649 // This function returns an array of single element of object's FQDN attribute,
650 // if FQDN is set. The next choice is object's common name, if it looks like a
651 // hostname. Otherwise an array of all 'regular' IP addresses of the
652 // object is returned (which may appear 0 and more elements long).
653 function findAllEndpoints ($object_id, $fallback = '')
655 foreach (getAttrValues ($object_id) as $record)
656 if ($record['id'] == 3 && strlen ($record['value'])) // FQDN
657 return array ($record['value']);
659 foreach (getObjectIPv4Allocations ($object_id) as $dottedquad => $alloc)
660 if ($alloc['type'] == 'regular')
661 $regular[] = $dottedquad;
662 if (!count ($regular) && strlen ($fallback))
663 return array ($fallback);
667 // Some records in the dictionary may be written as plain text or as Wiki
668 // link in the following syntax:
670 // 2. [[word URL]] // FIXME: this isn't working
671 // 3. [[word word word | URL]]
672 // This function parses the line and returns text suitable for either A
673 // (rendering <A HREF>) or O (for <OPTION>).
674 function parseWikiLink ($line, $which)
676 if (preg_match ('/^\[\[.+\]\]$/', $line) == 0)
678 // always strip the marker for A-data, but let cookOptgroup()
679 // do this later (otherwise it can't sort groups out)
681 return preg_replace ('/^.+%GSKIP%/', '', preg_replace ('/^(.+)%GPASS%/', '\\1 ', $line));
685 $line = preg_replace ('/^\[\[(.+)\]\]$/', '$1', $line);
686 $s = explode ('|', $line);
687 $o_value = trim ($s[0]);
690 $o_value = preg_replace ('/^.+%GSKIP%/', '', preg_replace ('/^(.+)%GPASS%/', '\\1 ', $o_value));
691 $a_value = trim ($s[1]);
692 return "<a href='${a_value}'>${o_value}</a>";
695 // FIXME: should this be saved as "P-data"?
696 function execGMarker ($line)
698 return preg_replace ('/^.+%GSKIP%/', '', preg_replace ('/^(.+)%GPASS%/', '\\1 ', $line));
701 // rackspace usage for a single rack
702 // (T + W + U) / (height * 3 - A)
703 function getRSUforRack ($data = NULL)
707 showWarning ('Invalid argument', __FUNCTION__
);
710 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
711 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
712 for ($locidx = 0; $locidx < 3; $locidx++
)
713 $counter[$data[$unit_no][$locidx]['state']]++
;
714 return ($counter['T'] +
$counter['W'] +
$counter['U']) / ($counter['T'] +
$counter['W'] +
$counter['U'] +
$counter['F']);
718 function getRSUforRackRow ($rowData = NULL)
720 if ($rowData === NULL)
722 showWarning ('Invalid argument', __FUNCTION__
);
725 if (!count ($rowData))
727 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
729 foreach (array_keys ($rowData) as $rack_id)
731 $data = spotEntity ('rack', $rack_id);
733 $total_height +
= $data['height'];
734 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
735 for ($locidx = 0; $locidx < 3; $locidx++
)
736 $counter[$data[$unit_no][$locidx]['state']]++
;
738 return ($counter['T'] +
$counter['W'] +
$counter['U']) / ($counter['T'] +
$counter['W'] +
$counter['U'] +
$counter['F']);
741 // Make sure the string is always wrapped with LF characters
742 function lf_wrap ($str)
744 $ret = trim ($str, "\r\n");
750 // Adopted from Mantis BTS code.
751 function string_insert_hrefs ($s)
753 if (getConfigVar ('DETECT_URLS') != 'yes')
755 # Find any URL in a string and replace it by a clickable link
756 $s = preg_replace( '/(([[:alpha:]][-+.[:alnum:]]*):\/\/(%[[:digit:]A-Fa-f]{2}|[-_.!~*\';\/?%^\\\\:@&={\|}+$#\(\),\[\][:alnum:]])+)/se',
757 "'<a href=\"'.rtrim('\\1','.').'\">\\1</a> [<a href=\"'.rtrim('\\1','.').'\" target=\"_blank\">^</a>]'",
759 $s = preg_replace( '/\b' . email_regex_simple() . '\b/i',
760 '<a href="mailto:\0">\0</a>',
766 function email_regex_simple ()
768 return "(([a-z0-9!#*+\/=?^_{|}~-]+(?:\.[a-z0-9!#*+\/=?^_{|}~-]+)*)" . # recipient
769 "\@((?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?))"; # @domain
772 // Parse AUTOPORTS_CONFIG and return a list of generated pairs (port_type, port_name)
773 // for the requested object_type_id.
774 function getAutoPorts ($type_id)
777 $typemap = explode (';', str_replace (' ', '', getConfigVar ('AUTOPORTS_CONFIG')));
778 foreach ($typemap as $equation)
780 $tmp = explode ('=', $equation);
781 if (count ($tmp) != 2)
783 $objtype_id = $tmp[0];
784 if ($objtype_id != $type_id)
787 foreach (explode ('+', $portlist) as $product)
789 $tmp = explode ('*', $product);
790 if (count ($tmp) != 3)
793 $port_type = $tmp[1];
795 for ($i = 0; $i < $nports; $i++
)
796 $ret[] = array ('type' => $port_type, 'name' => @sprintf
($format, $i));
802 // Use pre-served trace to traverse the tree, then place given node where it belongs.
803 function pokeNode (&$tree, $trace, $key, $value, $threshold = 0)
805 // This function needs the trace to be followed FIFO-way. The fastest
806 // way to do so is to use array_push() for putting values into the
807 // list and array_shift() for getting them out. This exposed up to 11%
808 // performance gain compared to other patterns of array_push/array_unshift/
809 // array_reverse/array_pop/array_shift conjunction.
810 $myid = array_shift ($trace);
811 if (!count ($trace)) // reached the target
813 if (!$threshold or ($threshold and $tree[$myid]['kidc'] +
1 < $threshold))
814 $tree[$myid]['kids'][$key] = $value;
815 // Reset accumulated records once, when the limit is reached, not each time
817 if (++
$tree[$myid]['kidc'] == $threshold)
818 $tree[$myid]['kids'] = array();
822 $self = __FUNCTION__
;
823 $self ($tree[$myid]['kids'], $trace, $key, $value, $threshold);
827 // Likewise traverse the tree with the trace and return the final node.
828 function peekNode ($tree, $trace, $target_id)
830 $self = __FUNCTION__
;
831 if (NULL === ($next = array_shift ($trace))) // warm
833 foreach ($tree as $node)
834 if (array_key_exists ('id', $node) and $node['id'] == $target_id) // hot
839 foreach ($tree as $node)
840 if (array_key_exists ('id', $node) and $node['id'] == $next) // warmer
841 return $self ($node['kids'], $trace, $target_id);
847 // Build a tree from the item list and return it. Input and output data is
848 // indexed by item id (nested items in output are recursively stored in 'kids'
849 // key, which is in turn indexed by id. Functions, which are ready to handle
850 // tree collapsion/expansion themselves, may request non-zero threshold value
851 // for smaller resulting tree.
852 function treeFromList (&$orig_nodelist, $threshold = 0, $return_main_payload = TRUE)
855 $nodelist = $orig_nodelist;
856 // Array equivalent of traceEntity() function.
858 // set kidc and kids only once
859 foreach (array_keys ($nodelist) as $nodeid)
861 $nodelist[$nodeid]['kidc'] = 0;
862 $nodelist[$nodeid]['kids'] = array();
867 foreach (array_keys ($nodelist) as $nodeid)
869 // When adding a node to the working tree, book another
870 // iteration, because the new item could make a way for
871 // others onto the tree. Also remove any item added from
872 // the input list, so iteration base shrinks.
873 // First check if we can assign directly.
874 if ($nodelist[$nodeid]['parent_id'] == NULL)
876 $tree[$nodeid] = $nodelist[$nodeid];
877 $trace[$nodeid] = array(); // Trace to root node is empty
878 unset ($nodelist[$nodeid]);
881 // Now look if it fits somewhere on already built tree.
882 elseif (isset ($trace[$nodelist[$nodeid]['parent_id']]))
884 // Trace to a node is a trace to its parent plus parent id.
885 $trace[$nodeid] = $trace[$nodelist[$nodeid]['parent_id']];
886 $trace[$nodeid][] = $nodelist[$nodeid]['parent_id'];
887 pokeNode ($tree, $trace[$nodeid], $nodeid, $nodelist[$nodeid], $threshold);
888 // path to any other node is made of all parent nodes plus the added node itself
889 unset ($nodelist[$nodeid]);
895 if (!$return_main_payload)
897 // update each input node with its backtrace route
898 foreach ($trace as $nodeid => $route)
899 $orig_nodelist[$nodeid]['trace'] = $route;
903 // Build a tree from the tag list and return everything _except_ the tree.
904 // IOW, return taginfo items, which have parent_id set and pointing outside
905 // of the "normal" tree, which originates from the root.
906 function getOrphanedTags ()
909 return treeFromList ($taglist, 0, FALSE);
912 function serializeTags ($chain, $baseurl = '')
916 foreach ($chain as $taginfo)
919 ($baseurl == '' ?
'' : "<a href='${baseurl}cft[]=${taginfo['id']}'>") .
921 ($baseurl == '' ?
'' : '</a>');
927 // Return the list of missing implicit tags.
928 function getImplicitTags ($oldtags)
932 foreach ($oldtags as $taginfo)
933 $tmp = array_merge ($tmp, $taglist[$taginfo['id']]['trace']);
934 // don't call array_unique here, it is in the function we will call now
935 return buildTagChainFromIds ($tmp);
938 // Minimize the chain: exclude all implicit tags and return the result.
939 // This function makes use of an external cache with a miss/hit ratio
940 // about 3/7 (ticket:255).
941 function getExplicitTagsOnly ($chain)
943 global $taglist, $tagRelCache;
945 foreach (array_keys ($chain) as $keyA) // check each A
947 $tagidA = $chain[$keyA]['id'];
948 // do not include A in result, if A is seen on the trace of any B!=A
949 foreach (array_keys ($chain) as $keyB)
951 $tagidB = $chain[$keyB]['id'];
952 if ($tagidA == $tagidB)
954 if (!isset ($tagRelCache[$tagidA][$tagidB]))
955 $tagRelCache[$tagidA][$tagidB] = in_array ($tagidA, $taglist[$tagidB]['trace']);
956 if ($tagRelCache[$tagidA][$tagidB] === TRUE) // A is ancestor of B
957 continue 2; // skip this A
959 $ret[] = $chain[$keyA];
964 // Universal autotags generator, a complementing function for loadEntityTags().
965 // Bypass key isn't strictly typed, but interpreted depending on the realm.
966 function generateEntityAutoTags ($cell)
969 switch ($cell['realm'])
972 $ret[] = array ('tag' => '$rackid_' . $cell['id']);
973 $ret[] = array ('tag' => '$any_rack');
976 $ret[] = array ('tag' => '$id_' . $cell['id']);
977 $ret[] = array ('tag' => '$typeid_' . $cell['objtype_id']);
978 $ret[] = array ('tag' => '$any_object');
979 if (validTagName ('$cn_' . $cell['name'], TRUE))
980 $ret[] = array ('tag' => '$cn_' . $cell['name']);
981 if (!strlen ($cell['rack_id']))
982 $ret[] = array ('tag' => '$unmounted');
983 if (!$cell['nports'])
984 $ret[] = array ('tag' => '$portless');
985 if ($cell['asset_no'] == '')
986 $ret[] = array ('tag' => '$no_asset_tag');
987 if ($cell['runs8021Q'])
988 $ret[] = array ('tag' => '$runs_8021Q');
991 $ret[] = array ('tag' => '$ip4netid_' . $cell['id']);
992 $ret[] = array ('tag' => '$ip4net-' . str_replace ('.', '-', $cell['ip']) . '-' . $cell['mask']);
993 for ($i = 8; $i < 32; $i++
)
995 // these conditions hit 1 to 3 times per each i
996 if ($cell['mask'] >= $i)
997 $ret[] = array ('tag' => '$masklen_ge_' . $i);
998 if ($cell['mask'] <= $i)
999 $ret[] = array ('tag' => '$masklen_le_' . $i);
1000 if ($cell['mask'] == $i)
1001 $ret[] = array ('tag' => '$masklen_eq_' . $i);
1003 $ret[] = array ('tag' => '$any_ip4net');
1004 $ret[] = array ('tag' => '$any_net');
1007 $ret[] = array ('tag' => '$ipv4vsid_' . $cell['id']);
1008 $ret[] = array ('tag' => '$any_ipv4vs');
1009 $ret[] = array ('tag' => '$any_vs');
1012 $ret[] = array ('tag' => '$ipv4rspid_' . $cell['id']);
1013 $ret[] = array ('tag' => '$any_ipv4rsp');
1014 $ret[] = array ('tag' => '$any_rsp');
1017 // {$username_XXX} autotag is generated always, but {$userid_XXX}
1018 // appears only for accounts, which exist in local database.
1019 $ret[] = array ('tag' => '$username_' . $cell['user_name']);
1020 if (isset ($cell['user_id']))
1021 $ret[] = array ('tag' => '$userid_' . $cell['user_id']);
1024 $ret[] = array ('tag' => '$fileid_' . $cell['id']);
1025 $ret[] = array ('tag' => '$any_file');
1030 // {$tagless} doesn't apply to users
1031 switch ($cell['realm'])
1039 if (!count ($cell['etags']))
1040 $ret[] = array ('tag' => '$untagged');
1048 // Check, if the given tag is present on the chain (will only work
1049 // for regular tags with tag ID set.
1050 function tagOnChain ($taginfo, $tagchain)
1052 if (!isset ($taginfo['id']))
1054 foreach ($tagchain as $test)
1055 if ($test['id'] == $taginfo['id'])
1060 function tagNameOnChain ($tagname, $tagchain)
1062 foreach ($tagchain as $test)
1063 if ($test['tag'] == $tagname)
1068 // Return TRUE, if two tags chains differ (order of tags doesn't matter).
1069 // Assume, that neither of the lists contains duplicates.
1070 // FIXME: a faster, than O(x^2) method is possible for this calculation.
1071 function tagChainCmp ($chain1, $chain2)
1073 if (count ($chain1) != count ($chain2))
1075 foreach ($chain1 as $taginfo1)
1076 if (!tagOnChain ($taginfo1, $chain2))
1081 function redirectIfNecessary ()
1089 'accounts' => 'userlist',
1090 'rspools' => 'ipv4rsplist',
1091 'rspool' => 'ipv4rsp',
1092 'vservices' => 'ipv4vslist',
1093 'vservice' => 'ipv4vs',
1094 'objects' => 'depot',
1095 'objgroup' => 'depot',
1098 $tmap['objects']['newmulti'] = 'addmore';
1099 $tmap['objects']['newobj'] = 'addmore';
1100 $tmap['object']['switchvlans'] = 'livevlans';
1101 $tmap['object']['slb'] = 'editrspvs';
1102 $tmap['object']['portfwrd'] = 'nat4';
1103 $tmap['object']['network'] = 'ipv4';
1104 if (isset ($pmap[$pageno]))
1105 redirectUser ($pmap[$pageno], $tabno);
1106 if (isset ($tmap[$pageno][$tabno]))
1107 redirectUser ($pageno, $tmap[$pageno][$tabno]);
1108 // check if we accidentaly got on a dynamic tab that shouldn't be shown for this object
1111 isset ($trigger[$pageno][$tabno]) and
1112 !strlen (call_user_func ($trigger[$pageno][$tabno]))
1114 redirectUser ($pageno, 'default');
1117 function prepareNavigation()
1123 $pageno = (isset ($_REQUEST['page'])) ?
$_REQUEST['page'] : 'index';
1125 // Special handling of tab number to substitute the "last" index where applicable.
1126 // Always show explicitly requested tab, substitute the last used name in case
1127 // it is awailable, fall back to the default one.
1129 if (isset ($_REQUEST['tab']))
1130 $tabno = $_REQUEST['tab'];
1133 basename($_SERVER['PHP_SELF']) == 'index.php' and
1134 getConfigVar ('SHOW_LAST_TAB') == 'yes' and
1135 isset ($_SESSION['RTLT'][$pageno]) and
1136 permitted ($pageno, $_SESSION['RTLT'][$pageno])
1138 redirectUser ($pageno, $_SESSION['RTLT'][$pageno]);
1143 function fixContext ($target = NULL)
1155 if ($target !== NULL)
1157 $target_given_tags = $target['etags'];
1158 // Don't reset autochain, because auth procedures could push stuff there in.
1159 // Another important point is to ignore 'user' realm, so we don't infuse effective
1160 // context with autotags of the displayed account.
1161 if ($target['realm'] != 'user')
1162 $auto_tags = array_merge ($auto_tags, $target['atags']);
1164 elseif (array_key_exists ($pageno, $etype_by_pageno))
1166 // Each page listed in the map above requires one uint argument.
1167 $target_realm = $etype_by_pageno[$pageno];
1168 assertUIntArg ($page[$pageno]['bypass']);
1169 $target_id = $_REQUEST[$page[$pageno]['bypass']];
1170 $target = spotEntity ($target_realm, $target_id);
1171 $target_given_tags = $target['etags'];
1172 if ($target['realm'] != 'user')
1173 $auto_tags = array_merge ($auto_tags, $target['atags']);
1175 // Explicit and implicit chains should be normally empty at this point, so
1176 // overwrite the contents anyway.
1177 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1178 $impl_tags = getImplicitTags ($expl_tags);
1181 // Take a list of user-supplied tag IDs to build a list of valid taginfo
1182 // records indexed by tag IDs (tag chain).
1183 function buildTagChainFromIds ($tagidlist)
1187 foreach (array_unique ($tagidlist) as $tag_id)
1188 if (isset ($taglist[$tag_id]))
1189 $ret[] = $taglist[$tag_id];
1193 // Process a given tag tree and return only meaningful branches. The resulting
1194 // (sub)tree will have refcnt leaves on every last branch.
1195 function getObjectiveTagTree ($tree, $realm, $preselect)
1197 $self = __FUNCTION__
;
1199 foreach ($tree as $taginfo)
1201 $subsearch = $self ($taginfo['kids'], $realm, $preselect);
1202 // If the current node addresses something, add it to the result
1203 // regardless of how many sub-nodes it features.
1206 isset ($taginfo['refcnt'][$realm]) or
1207 count ($subsearch) > 1 or
1208 in_array ($taginfo['id'], $preselect)
1212 'id' => $taginfo['id'],
1213 'tag' => $taginfo['tag'],
1214 'parent_id' => $taginfo['parent_id'],
1215 'refcnt' => $taginfo['refcnt'],
1216 'kids' => $subsearch
1219 $ret = array_merge ($ret, $subsearch);
1224 // Get taginfo record by tag name, return NULL, if record doesn't exist.
1225 function getTagByName ($target_name)
1228 foreach ($taglist as $taginfo)
1229 if ($taginfo['tag'] == $target_name)
1234 // Merge two chains, filtering dupes out. Return the resulting superset.
1235 function mergeTagChains ($chainA, $chainB)
1238 // Reindex by tag id in any case.
1240 foreach ($chainA as $tag)
1241 $ret[$tag['id']] = $tag;
1242 foreach ($chainB as $tag)
1243 if (!isset ($ret[$tag['id']]))
1244 $ret[$tag['id']] = $tag;
1248 function getCellFilter ()
1252 $staticFilter = getConfigVar ('STATIC_FILTER');
1253 if (isset ($_REQUEST['tagfilter']) and is_array ($_REQUEST['tagfilter']))
1255 $_REQUEST['cft'] = $_REQUEST['tagfilter'];
1256 unset ($_REQUEST['tagfilter']);
1258 //if the page is submitted we get an andor value so we know they are trying to start a new filter or clearing the existing one.
1259 if(isset($_REQUEST['andor']))
1261 unset($_SESSION[$pageno]);
1263 if (isset ($_SESSION[$pageno]['tagfilter']) and is_array ($_SESSION[$pageno]['tagfilter']) and !(isset($_REQUEST['cft'])) and $staticFilter == 'yes')
1265 $_REQUEST['cft'] = $_SESSION[$pageno]['tagfilter'];
1267 if (isset ($_SESSION[$pageno]['cfe']) and !(isset($sic['cfe'])) and $staticFilter == 'yes')
1269 $sic['cfe'] = $_SESSION[$pageno]['cfe'];
1271 if (isset ($_SESSION[$pageno]['andor']) and !(isset($_REQUEST['andor'])) and $staticFilter == 'yes')
1273 $_REQUEST['andor'] = $_SESSION[$pageno]['andor'];
1279 'tagidlist' => array(),
1280 'tnamelist' => array(),
1281 'pnamelist' => array(),
1285 'expression' => array(),
1286 'urlextra' => '', // Just put text here and let makeHref call urlencode().
1290 case (!isset ($_REQUEST['andor'])):
1291 $andor2 = getConfigVar ('FILTER_DEFAULT_ANDOR');
1293 case ($_REQUEST['andor'] == 'and'):
1294 case ($_REQUEST['andor'] == 'or'):
1295 $_SESSION[$pageno]['andor'] = $_REQUEST['andor'];
1296 $ret['andor'] = $andor2 = $_REQUEST['andor'];
1297 $ret['urlextra'] .= '&andor=' . $ret['andor'];
1300 showWarning ('Invalid and/or switch value in submitted form', __FUNCTION__
);
1304 // Both tags and predicates, which don't exist, should be
1305 // handled somehow. Discard them silently for now.
1306 if (isset ($_REQUEST['cft']) and is_array ($_REQUEST['cft']))
1308 $_SESSION[$pageno]['tagfilter'] = $_REQUEST['cft'];
1310 foreach ($_REQUEST['cft'] as $req_id)
1311 if (isset ($taglist[$req_id]))
1313 $ret['tagidlist'][] = $req_id;
1314 $ret['tnamelist'][] = $taglist[$req_id]['tag'];
1315 $ret['text'] .= $andor1 . '{' . $taglist[$req_id]['tag'] . '}';
1316 $andor1 = ' ' . $andor2 . ' ';
1317 $ret['urlextra'] .= '&cft[]=' . $req_id;
1320 if (isset ($_REQUEST['cfp']) and is_array ($_REQUEST['cfp']))
1323 foreach ($_REQUEST['cfp'] as $req_name)
1324 if (isset ($pTable[$req_name]))
1326 $ret['pnamelist'][] = $req_name;
1327 $ret['text'] .= $andor1 . '[' . $req_name . ']';
1328 $andor1 = ' ' . $andor2 . ' ';
1329 $ret['urlextra'] .= '&cfp[]=' . $req_name;
1332 // Extra text comes from TEXTAREA and is easily screwed by standard escaping function.
1333 if (isset ($sic['cfe']))
1335 $_SESSION[$pageno]['cfe'] = $sic['cfe'];
1336 // Only consider extra text, when it is a correct RackCode expression.
1337 $parse = spotPayload ($sic['cfe'], 'SYNT_EXPR');
1338 if ($parse['result'] == 'ACK')
1340 $ret['extratext'] = trim ($sic['cfe']);
1341 $ret['urlextra'] .= '&cfe=' . $ret['extratext'];
1344 $finaltext = array();
1345 if (strlen ($ret['text']))
1346 $finaltext[] = '(' . $ret['text'] . ')';
1347 if (strlen ($ret['extratext']))
1348 $finaltext[] = '(' . $ret['extratext'] . ')';
1349 $finaltext = implode (' ' . $andor2 . ' ', $finaltext);
1350 if (strlen ($finaltext))
1352 $parse = spotPayload ($finaltext, 'SYNT_EXPR');
1353 $ret['expression'] = $parse['result'] == 'ACK' ?
$parse['load'] : NULL;
1354 // It's not quite fair enough to put the blame of the whole text onto
1355 // non-empty "extra" portion of it, but it's the only user-generated portion
1356 // of it, thus the most probable cause of parse error.
1357 if (strlen ($ret['extratext']))
1358 $ret['extraclass'] = $parse['result'] == 'ACK' ?
'validation-success' : 'validation-error';
1363 // Return an empty message log.
1364 function emptyLog ()
1373 // Return a message log consisting of only one message.
1374 function oneLiner ($code, $args = array())
1377 $ret['m'][] = count ($args) ?
array ('c' => $code, 'a' => $args) : array ('c' => $code);
1381 // Merge message payload from two message logs given and return the result.
1382 function mergeLogs ($log1, $log2)
1385 $ret['m'] = array_merge ($log1['m'], $log2['m']);
1389 function validTagName ($s, $allow_autotag = FALSE)
1391 if (1 == preg_match (TAGNAME_REGEXP
, $s))
1393 if ($allow_autotag and 1 == preg_match (AUTOTAGNAME_REGEXP
, $s))
1398 function redirectUser ($p, $t)
1401 $l = "index.php?page=${p}&tab=${t}";
1402 if (isset ($page[$p]['bypass']) and isset ($_REQUEST[$page[$p]['bypass']]))
1403 $l .= '&' . $page[$p]['bypass'] . '=' . $_REQUEST[$page[$p]['bypass']];
1404 header ("Location: " . $l);
1408 function getRackCodeStats ()
1411 $defc = $grantc = $modc = 0;
1412 foreach ($rackCode as $s)
1415 case 'SYNT_DEFINITION':
1429 'Definition sentences' => $defc,
1430 'Grant sentences' => $grantc,
1431 'Context mod sentences' => $modc
1436 function getRackImageWidth ()
1439 return 3 +
$rtwidth[0] +
$rtwidth[1] +
$rtwidth[2] +
3;
1442 function getRackImageHeight ($units)
1444 return 3 +
3 +
$units * 2;
1447 // Perform substitutions and return resulting string
1448 // used solely by buildLVSConfig()
1449 function apply_macros ($macros, $subject)
1452 foreach ($macros as $search => $replace)
1453 $ret = str_replace ($search, $replace, $ret);
1457 function buildLVSConfig ($object_id = 0)
1459 if ($object_id <= 0)
1461 showWarning ('Invalid argument', __FUNCTION__
);
1464 $oInfo = spotEntity ('object', $object_id);
1465 $lbconfig = getSLBConfig ($object_id);
1466 if ($lbconfig === NULL)
1468 showWarning ('getSLBConfig() failed', __FUNCTION__
);
1471 $newconfig = "#\n#\n# This configuration has been generated automatically by RackTables\n";
1472 $newconfig .= "# for object_id == ${object_id}\n# object name: ${oInfo['name']}\n#\n#\n\n\n";
1473 foreach ($lbconfig as $vs_id => $vsinfo)
1475 $newconfig .= "########################################################\n" .
1476 "# VS (id == ${vs_id}): " . (!strlen ($vsinfo['vs_name']) ?
'NO NAME' : $vsinfo['vs_name']) . "\n" .
1477 "# RS pool (id == ${vsinfo['pool_id']}): " . (!strlen ($vsinfo['pool_name']) ?
'ANONYMOUS' : $vsinfo['pool_name']) . "\n" .
1478 "########################################################\n";
1479 # The order of inheritance is: VS -> LB -> pool [ -> RS ]
1482 '%VIP%' => $vsinfo['vip'],
1483 '%VPORT%' => $vsinfo['vport'],
1484 '%PROTO%' => $vsinfo['proto'],
1485 '%VNAME%' => $vsinfo['vs_name'],
1486 '%RSPOOLNAME%' => $vsinfo['pool_name']
1488 $newconfig .= "virtual_server ${vsinfo['vip']} ${vsinfo['vport']} {\n";
1489 $newconfig .= "\tprotocol ${vsinfo['proto']}\n";
1490 $newconfig .= apply_macros
1493 lf_wrap ($vsinfo['vs_vsconfig']) .
1494 lf_wrap ($vsinfo['lb_vsconfig']) .
1495 lf_wrap ($vsinfo['pool_vsconfig'])
1497 foreach ($vsinfo['rslist'] as $rs)
1499 if (!strlen ($rs['rsport']))
1500 $rs['rsport'] = $vsinfo['vport'];
1501 $macros['%RSIP%'] = $rs['rsip'];
1502 $macros['%RSPORT%'] = $rs['rsport'];
1503 $newconfig .= "\treal_server ${rs['rsip']} ${rs['rsport']} {\n";
1504 $newconfig .= apply_macros
1507 lf_wrap ($vsinfo['vs_rsconfig']) .
1508 lf_wrap ($vsinfo['lb_rsconfig']) .
1509 lf_wrap ($vsinfo['pool_rsconfig']) .
1510 lf_wrap ($rs['rs_rsconfig'])
1512 $newconfig .= "\t}\n";
1514 $newconfig .= "}\n\n\n";
1516 // FIXME: deal somehow with Mac-styled text, the below replacement will screw it up
1517 return dos2unix ($newconfig);
1520 // Indicate occupation state of each IP address: none, ordinary or problematic.
1521 function markupIPv4AddrList (&$addrlist)
1523 foreach (array_keys ($addrlist) as $ip_bin)
1527 'shared' => 0, // virtual
1528 'virtual' => 0, // loopback
1529 'regular' => 0, // connected host
1530 'router' => 0 // connected gateway
1532 foreach ($addrlist[$ip_bin]['allocs'] as $a)
1533 $refc[$a['type']]++
;
1534 $nvirtloopback = ($refc['shared'] +
$refc['virtual'] > 0) ?
1 : 0; // modulus of virtual + shared
1535 $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ?
1 : 0; // only one reservation is possible ever
1536 $nrealms = $nreserved +
$nvirtloopback +
$refc['regular'] +
$refc['router']; // latter two are connected and router allocations
1539 $addrlist[$ip_bin]['class'] = 'trbusy';
1540 elseif ($nrealms > 1)
1541 $addrlist[$ip_bin]['class'] = 'trerror';
1543 $addrlist[$ip_bin]['class'] = '';
1547 // Scan the given address list (returned by scanIPv4Space) and return a list of all routers found.
1548 function findRouters ($addrlist)
1551 foreach ($addrlist as $addr)
1552 foreach ($addr['allocs'] as $alloc)
1553 if ($alloc['type'] == 'router')
1556 'id' => $alloc['object_id'],
1557 'iface' => $alloc['name'],
1558 'dname' => $alloc['object_name'],
1559 'addr' => $addr['ip']
1564 // Assist in tag chain sorting.
1565 function taginfoCmp ($tagA, $tagB)
1567 return $tagA['ci'] - $tagB['ci'];
1570 // Compare networks. When sorting a tree, the records on the list will have
1571 // distinct base IP addresses.
1572 // "The comparison function must return an integer less than, equal to, or greater
1573 // than zero if the first argument is considered to be respectively less than,
1574 // equal to, or greater than the second." (c) PHP manual
1575 function IPv4NetworkCmp ($netA, $netB)
1577 // There's a problem just substracting one u32 integer from another,
1578 // because the result may happen big enough to become a negative i32
1579 // integer itself (PHP tries to cast everything it sees to signed int)
1580 // The comparison below must treat positive and negative values of both
1582 // Equal values give instant decision regardless of their [equal] sign.
1583 if ($netA['ip_bin'] == $netB['ip_bin'])
1585 // Same-signed values compete arithmetically within one of i32 contiguous ranges:
1586 // 0x00000001~0x7fffffff 1~2147483647
1587 // 0 doesn't have any sign, and network 0.0.0.0 isn't allowed
1588 // 0x80000000~0xffffffff -2147483648~-1
1589 $signA = $netA['ip_bin'] / abs ($netA['ip_bin']);
1590 $signB = $netB['ip_bin'] / abs ($netB['ip_bin']);
1591 if ($signA == $signB)
1593 if ($netA['ip_bin'] > $netB['ip_bin'])
1598 else // With only one of two values being negative, it... wins!
1600 if ($netA['ip_bin'] < $netB['ip_bin'])
1607 // Modify the given tag tree so, that each level's items are sorted alphabetically.
1608 function sortTree (&$tree, $sortfunc = '')
1610 if (!strlen ($sortfunc))
1612 $self = __FUNCTION__
;
1613 usort ($tree, $sortfunc);
1614 // Don't make a mistake of directly iterating over the items of current level, because this way
1615 // the sorting will be performed on a _copy_ if each item, not the item itself.
1616 foreach (array_keys ($tree) as $tagid)
1617 $self ($tree[$tagid]['kids'], $sortfunc);
1620 function iptree_fill (&$netdata)
1622 if (!isset ($netdata['kids']) or !count ($netdata['kids']))
1624 // If we really have nested prefixes, they must fit into the tree.
1627 'ip_bin' => $netdata['ip_bin'],
1628 'mask' => $netdata['mask']
1630 foreach ($netdata['kids'] as $pfx)
1631 iptree_embed ($worktree, $pfx);
1632 $netdata['kids'] = iptree_construct ($worktree);
1633 $netdata['kidc'] = count ($netdata['kids']);
1636 function iptree_construct ($node)
1638 $self = __FUNCTION__
;
1640 if (!isset ($node['right']))
1642 if (!isset ($node['ip']))
1644 $node['ip'] = long2ip ($node['ip_bin']);
1645 $node['kids'] = array();
1649 return array ($node);
1652 return array_merge ($self ($node['left']), $self ($node['right']));
1655 function iptree_embed (&$node, $pfx)
1657 $self = __FUNCTION__
;
1660 if ($node['ip_bin'] == $pfx['ip_bin'] and $node['mask'] == $pfx['mask'])
1665 if ($node['mask'] == $pfx['mask'])
1666 throw new Exception ('the recurring loop lost control', E_INTERNAL
);
1669 if (!isset ($node['right']))
1671 // Fill in db_first/db_last to make it possible to run scanIPv4Space() on the node.
1672 $node['left']['mask'] = $node['mask'] +
1;
1673 $node['left']['ip_bin'] = $node['ip_bin'];
1674 $node['left']['db_first'] = sprintf ('%u', $node['left']['ip_bin']);
1675 $node['left']['db_last'] = sprintf ('%u', $node['left']['ip_bin'] |
binInvMaskFromDec ($node['left']['mask']));
1677 $node['right']['mask'] = $node['mask'] +
1;
1678 $node['right']['ip_bin'] = $node['ip_bin'] +
binInvMaskFromDec ($node['mask'] +
1) +
1;
1679 $node['right']['db_first'] = sprintf ('%u', $node['right']['ip_bin']);
1680 $node['right']['db_last'] = sprintf ('%u', $node['right']['ip_bin'] |
binInvMaskFromDec ($node['right']['mask']));
1684 if (($node['left']['ip_bin'] & binMaskFromDec ($node['left']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
1685 $self ($node['left'], $pfx);
1686 elseif (($node['right']['ip_bin'] & binMaskFromDec ($node['right']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
1687 $self ($node['right'], $pfx);
1689 throw new Exception ('cannot decide between left and right', E_INTERNAL
);
1692 function treeApplyFunc (&$tree, $func = '', $stopfunc = '')
1694 if (!strlen ($func))
1696 $self = __FUNCTION__
;
1697 foreach (array_keys ($tree) as $key)
1699 $func ($tree[$key]);
1700 if (strlen ($stopfunc) and $stopfunc ($tree[$key]))
1702 $self ($tree[$key]['kids'], $func);
1706 function loadIPv4AddrList (&$netinfo)
1708 loadOwnIPv4Addresses ($netinfo);
1709 markupIPv4AddrList ($netinfo['addrlist']);
1712 function countOwnIPv4Addresses (&$node)
1716 $node['mask_bin'] = binMaskFromDec ($node['mask']);
1717 $node['mask_bin_inv'] = binInvMaskFromDec ($node['mask']);
1718 $node['db_first'] = sprintf ('%u', 0x00000000 +
$node['ip_bin'] & $node['mask_bin']);
1719 $node['db_last'] = sprintf ('%u', 0x00000000 +
$node['ip_bin'] |
($node['mask_bin_inv']));
1720 if (!count ($node['kids']))
1722 $toscan[] = array ('i32_first' => $node['db_first'], 'i32_last' => $node['db_last']);
1723 $node['addrt'] = binInvMaskFromDec ($node['mask']) +
1;
1726 foreach ($node['kids'] as $nested)
1727 if (!isset ($nested['id'])) // spare
1729 $toscan[] = array ('i32_first' => $nested['db_first'], 'i32_last' => $nested['db_last']);
1730 $node['addrt'] +
= binInvMaskFromDec ($nested['mask']) +
1;
1732 // Don't do anything more, because the displaying function will load the addresses anyway.
1734 $node['addrc'] = count (scanIPv4Space ($toscan));
1737 function nodeIsCollapsed ($node)
1739 return $node['symbol'] == 'node-collapsed';
1742 function loadOwnIPv4Addresses (&$node)
1745 if (!isset ($node['kids']) or !count ($node['kids']))
1746 $toscan[] = array ('i32_first' => $node['db_first'], 'i32_last' => $node['db_last']);
1748 foreach ($node['kids'] as $nested)
1749 if (!isset ($nested['id'])) // spare
1750 $toscan[] = array ('i32_first' => $nested['db_first'], 'i32_last' => $nested['db_last']);
1751 $node['addrlist'] = scanIPv4Space ($toscan);
1752 $node['addrc'] = count ($node['addrlist']);
1755 function prepareIPv4Tree ($netlist, $expanded_id = 0)
1757 // treeFromList() requires parent_id to be correct for an item to get onto the tree,
1758 // so perform necessary pre-processing to make orphans belong to root. This trick
1759 // was earlier performed by getIPv4NetworkList().
1760 $netids = array_keys ($netlist);
1761 foreach ($netids as $cid)
1762 if (!in_array ($netlist[$cid]['parent_id'], $netids))
1763 $netlist[$cid]['parent_id'] = NULL;
1764 $tree = treeFromList ($netlist); // medium call
1765 sortTree ($tree, 'IPv4NetworkCmp');
1766 // complement the tree before markup to make the spare networks have "symbol" set
1767 treeApplyFunc ($tree, 'iptree_fill');
1768 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
1769 // count addresses after the markup to skip computation for hidden tree nodes
1770 treeApplyFunc ($tree, 'countOwnIPv4Addresses', 'nodeIsCollapsed');
1774 // Check all items of the tree recursively, until the requested target id is
1775 // found. Mark all items leading to this item as "expanded", collapsing all
1776 // the rest, which exceed the given threshold (if the threshold is given).
1777 function iptree_markup_collapsion (&$tree, $threshold = 1024, $target = 0)
1779 $self = __FUNCTION__
;
1781 foreach (array_keys ($tree) as $key)
1783 $here = ($target === 'ALL' or ($target > 0 and isset ($tree[$key]['id']) and $tree[$key]['id'] == $target));
1784 $below = $self ($tree[$key]['kids'], $threshold, $target);
1785 if (!$tree[$key]['kidc']) // terminal node
1786 $tree[$key]['symbol'] = 'spacer';
1787 elseif ($tree[$key]['kidc'] < $threshold)
1788 $tree[$key]['symbol'] = 'node-expanded-static';
1789 elseif ($here or $below)
1790 $tree[$key]['symbol'] = 'node-expanded';
1792 $tree[$key]['symbol'] = 'node-collapsed';
1793 $ret = ($ret or $here or $below); // parentheses are necessary for this to be computed correctly
1798 // Convert entity name to human-readable value
1799 function formatEntityName ($name) {
1803 return 'IPv4 Network';
1805 return 'IPv4 RS Pool';
1807 return 'IPv4 Virtual Service';
1818 // Take a MySQL or other generic timestamp and make it prettier
1819 function formatTimestamp ($timestamp) {
1820 return date('n/j/y g:iA', strtotime($timestamp));
1823 // Display hrefs for all of a file's parents. If scissors are requested,
1824 // prepend cutting button to each of them.
1825 function serializeFileLinks ($links, $scissors = FALSE)
1829 foreach ($links as $link_id => $li)
1831 switch ($li['entity_type'])
1834 $params = "page=ipv4net&id=";
1837 $params = "page=ipv4rspool&pool_id=";
1840 $params = "page=ipv4vs&vs_id=";
1843 $params = "page=object&object_id=";
1846 $params = "page=rack&rack_id=";
1849 $params = "page=user&user_id=";
1855 $ret .= "<a href='" . makeHrefProcess(array('op'=>'unlinkFile', 'link_id'=>$link_id)) . "'";
1856 $ret .= getImageHREF ('cut') . '</a> ';
1858 $ret .= sprintf("<a href='index.php?%s%s'>%s</a>", $params, $li['entity_id'], $li['name']);
1864 // Convert filesize to appropriate unit and make it human-readable
1865 function formatFileSize ($bytes) {
1867 if($bytes < 1024) // bytes
1868 return "${bytes} bytes";
1871 if ($bytes < 1024000)
1872 return sprintf ("%.1fk", round (($bytes / 1024), 1));
1875 return sprintf ("%.1f MB", round (($bytes / 1024000), 1));
1878 // Reverse of formatFileSize, it converts human-readable value to bytes
1879 function convertToBytes ($value) {
1880 $value = trim($value);
1881 $last = strtolower($value[strlen($value)-1]);
1895 function ip_quad2long ($ip)
1897 return sprintf("%u", ip2long($ip));
1900 function ip_long2quad ($quad)
1902 return long2ip($quad);
1905 function makeHref($params = array())
1907 $ret = 'index.php?';
1909 foreach($params as $key=>$value)
1913 $ret .= urlencode($key).'='.urlencode($value);
1919 function makeHrefProcess($params = array())
1921 global $pageno, $tabno;
1922 $ret = 'process.php?';
1924 if (!isset($params['page']))
1925 $params['page'] = $pageno;
1926 if (!isset($params['tab']))
1927 $params['tab'] = $tabno;
1928 foreach($params as $key=>$value)
1932 $ret .= urlencode($key).'='.urlencode($value);
1938 function makeHrefForHelper ($helper_name, $params = array())
1940 $ret = 'popup.php?helper=' . $helper_name;
1941 foreach($params as $key=>$value)
1942 $ret .= '&'.urlencode($key).'='.urlencode($value);
1946 // Process the given list of records to build data suitable for printNiftySelect()
1947 // (like it was formerly executed by printSelect()). Screen out vendors according
1948 // to VENDOR_SIEVE, if object type ID is provided. However, the OPTGROUP with already
1949 // selected OPTION is protected from being screened.
1950 function cookOptgroups ($recordList, $object_type_id = 0, $existing_value = 0)
1953 // Always keep "other" OPTGROUP at the SELECT bottom.
1955 foreach ($recordList as $dict_key => $dict_value)
1956 if (strpos ($dict_value, '%GSKIP%') !== FALSE)
1958 $tmp = explode ('%GSKIP%', $dict_value, 2);
1959 $ret[$tmp[0]][$dict_key] = $tmp[1];
1961 elseif (strpos ($dict_value, '%GPASS%') !== FALSE)
1963 $tmp = explode ('%GPASS%', $dict_value, 2);
1964 $ret[$tmp[0]][$dict_key] = $tmp[1];
1967 $therest[$dict_key] = $dict_value;
1968 if ($object_type_id != 0)
1970 $screenlist = array();
1971 foreach (explode (';', getConfigVar ('VENDOR_SIEVE')) as $sieve)
1972 if (preg_match ("/^([^@]+)(@${object_type_id})?\$/", trim ($sieve), $regs)){
1973 $screenlist[] = $regs[1];
1975 foreach (array_keys ($ret) as $vendor)
1976 if (in_array ($vendor, $screenlist))
1978 $ok_to_screen = TRUE;
1979 if ($existing_value)
1980 foreach (array_keys ($ret[$vendor]) as $recordkey)
1981 if ($recordkey == $existing_value)
1983 $ok_to_screen = FALSE;
1987 unset ($ret[$vendor]);
1990 $ret['other'] = $therest;
1994 function dos2unix ($text)
1996 return str_replace ("\r\n", "\n", $text);
1999 function unix2dos ($text)
2001 return str_replace ("\n", "\r\n", $text);
2004 function buildPredicateTable ($parsetree)
2007 foreach ($parsetree as $sentence)
2008 if ($sentence['type'] == 'SYNT_DEFINITION')
2009 $ret[$sentence['term']] = $sentence['definition'];
2010 // Now we have predicate table filled in with the latest definitions of each
2011 // particular predicate met. This isn't as chik, as on-the-fly predicate
2012 // overloading during allow/deny scan, but quite sufficient for this task.
2016 // Take a list of records and filter against given RackCode expression. Return
2017 // the original list intact, if there was no filter requested, but return an
2018 // empty list, if there was an error.
2019 function filterCellList ($list_in, $expression = array())
2021 if ($expression === NULL)
2023 if (!count ($expression))
2025 $list_out = array();
2026 foreach ($list_in as $item_key => $item_value)
2027 if (TRUE === judgeCell ($item_value, $expression))
2028 $list_out[$item_key] = $item_value;
2032 // Tell, if the given expression is true for the given entity. Take complete record on input.
2033 function judgeCell ($cell, $expression)
2036 return eval_expression
2050 // Tell, if a constraint from config option permits given record.
2051 function considerConfiguredConstraint ($cell, $varname)
2053 if (!strlen (getConfigVar ($varname)))
2054 return TRUE; // no restriction
2056 if (!isset ($parseCache[$varname]))
2057 // getConfigVar() doesn't re-read the value from DB because of its
2058 // own cache, so there is no race condition here between two calls.
2059 $parseCache[$varname] = spotPayload (getConfigVar ($varname), 'SYNT_EXPR');
2060 if ($parseCache[$varname]['result'] != 'ACK')
2061 return FALSE; // constraint set, but cannot be used due to compilation error
2062 return judgeCell ($cell, $parseCache[$varname]['load']);
2065 // Return list of records in the given realm, which conform to
2066 // the given RackCode expression. If the realm is unknown or text
2067 // doesn't validate as a RackCode expression, return NULL.
2068 // Otherwise (successful scan) return a list of all matched
2069 // records, even if the list is empty (array() !== NULL). If the
2070 // text is an empty string, return all found records in the given
2072 function scanRealmByText ($realm = NULL, $ftext = '')
2082 if (!strlen ($ftext = trim ($ftext)))
2086 $fparse = spotPayload ($ftext, 'SYNT_EXPR');
2087 if ($fparse['result'] != 'ACK')
2089 $fexpr = $fparse['load'];
2091 return filterCellList (listCells ($realm), $fexpr);
2093 throw new InvalidArgException ('$realm', $realm);
2098 function getIPv4VSOptions ()
2101 foreach (listCells ('ipv4vs') as $vsid => $vsinfo)
2102 $ret[$vsid] = $vsinfo['dname'] . (!strlen ($vsinfo['name']) ?
'' : " (${vsinfo['name']})");
2106 function getIPv4RSPoolOptions ()
2109 foreach (listCells ('ipv4rspool') as $pool_id => $poolInfo)
2110 $ret[$pool_id] = $poolInfo['name'];
2114 // Derive a complete cell structure from the given username regardless
2115 // if it is a local account or not.
2116 function constructUserCell ($username)
2118 if (NULL !== ($userid = getUserIDByUsername ($username)))
2119 return spotEntity ('user', $userid);
2123 'user_name' => $username,
2124 'user_realname' => '',
2128 $ret['atags'] = generateEntityAutoTags ($ret);
2132 // Let's have this debug helper here to enable debugging of process.php w/o interface.php.
2133 function dump ($var)
2135 echo '<div align=left><pre>';
2137 echo '</pre></div>';
2140 function getTagChart ($limit = 0, $realm = 'total', $special_tags = array())
2143 // first build top-N chart...
2145 foreach ($taglist as $taginfo)
2146 if (isset ($taginfo['refcnt'][$realm]))
2147 $toplist[$taginfo['id']] = $taginfo['refcnt'][$realm];
2148 arsort ($toplist, SORT_NUMERIC
);
2151 foreach (array_keys ($toplist) as $tag_id)
2153 $ret[$tag_id] = $taglist[$tag_id];
2154 if (++
$done == $limit)
2157 // ...then make sure, that every item of the special list is shown
2158 // (using the same sort order)
2160 foreach ($special_tags as $taginfo)
2161 if (!array_key_exists ($taginfo['id'], $ret))
2162 $extra[$taginfo['id']] = $taglist[$taginfo['id']]['refcnt'][$realm];
2163 arsort ($extra, SORT_NUMERIC
);
2164 foreach (array_keys ($extra) as $tag_id)
2165 $ret[] = $taglist[$tag_id];
2169 function decodeObjectType ($objtype_id, $style = 'r')
2171 static $types = array();
2172 if (!count ($types))
2175 'r' => readChapter (CHAP_OBJTYPE
),
2176 'a' => readChapter (CHAP_OBJTYPE
, 'a'),
2177 'o' => readChapter (CHAP_OBJTYPE
, 'o')
2179 return $types[$style][$objtype_id];
2182 function isolatedPermission ($p, $t, $cell)
2184 // This function is called from both "file" page and a number of other pages,
2185 // which have already fixed security context and authorized the user for it.
2186 // OTOH, it is necessary here to authorize against the current file, which
2187 // means saving the current context and building a new one.
2193 // push current context
2194 $orig_expl_tags = $expl_tags;
2195 $orig_impl_tags = $impl_tags;
2196 $orig_target_given_tags = $target_given_tags;
2197 $orig_auto_tags = $auto_tags;
2200 // remember decision
2201 $ret = permitted ($p, $t);
2203 $expl_tags = $orig_expl_tags;
2204 $impl_tags = $orig_impl_tags;
2205 $target_given_tags = $orig_target_given_tags;
2206 $auto_tags = $orig_auto_tags;
2210 function getPortListPrefs()
2213 if (0 >= ($ret['iif_pick'] = getConfigVar ('DEFAULT_PORT_IIF_ID')))
2214 $ret['iif_pick'] = 1;
2215 $ret['oif_picks'] = array();
2216 foreach (explode (';', getConfigVar ('DEFAULT_PORT_OIF_IDS')) as $tmp)
2218 $tmp = explode ('=', trim ($tmp));
2219 if (count ($tmp) == 2 and $tmp[0] > 0 and $tmp[1] > 0)
2220 $ret['oif_picks'][$tmp[0]] = $tmp[1];
2222 // enforce default value
2223 if (!array_key_exists (1, $ret['oif_picks']))
2224 $ret['oif_picks'][1] = 24;
2225 $ret['selected'] = $ret['iif_pick'] . '-' . $ret['oif_picks'][$ret['iif_pick']];
2229 // Return data for printNiftySelect() with port type options. All OIF options
2230 // for the default IIF will be shown, but only the default OIFs will be present
2231 // for each other IIFs. IIFs, for which there is no default OIF, will not
2233 // This SELECT will be used for the "add new port" form.
2234 function getNewPortTypeOptions()
2237 $prefs = getPortListPrefs();
2238 foreach (getPortInterfaceCompat() as $row)
2240 if ($row['iif_id'] == $prefs['iif_pick'])
2241 $optgroup = $row['iif_name'];
2242 elseif (array_key_exists ($row['iif_id'], $prefs['oif_picks']) and $prefs['oif_picks'][$row['iif_id']] == $row['oif_id'])
2243 $optgroup = 'other';
2246 if (!array_key_exists ($optgroup, $ret))
2247 $ret[$optgroup] = array();
2248 $ret[$optgroup][$row['iif_id'] . '-' . $row['oif_id']] = $row['oif_name'];
2253 // Return a serialized version of VLAN configuration for a port.
2254 // If a native VLAN is defined, print it first. All other VLANs
2255 // are tagged and are listed after a plus sign. When no configuration
2256 // is set for a port, return "default" string.
2257 function serializeVLANPack ($vlanport)
2259 if (!array_key_exists ('mode', $vlanport))
2261 switch ($vlanport['mode'])
2281 foreach ($vlanport['allowed'] as $vlan_id)
2282 if ($vlan_id != $vlanport['native'])
2283 $tagged[] = $vlan_id;
2285 $ret .= $vlanport['native'] ?
$vlanport['native'] : '';
2286 $tagged_bits = array();
2287 $id_from = $id_to = 0;
2288 foreach ($tagged as $next_id)
2292 if ($next_id == $id_to +
1) // merge
2298 $tagged_bits[] = $id_from == $id_to ?
$id_from : "${id_from}-${id_to}";
2300 $id_from = $id_to = $next_id; // start next pair
2304 $tagged_bits[] = $id_from == $id_to ?
$id_from : "${id_from}-${id_to}";
2305 if (count ($tagged))
2306 $ret .= '+' . implode (', ', $tagged_bits);
2307 return strlen ($ret) ?
$ret : 'default';
2310 // Decode VLAN compound key (which is a string formatted DOMAINID-VLANID) and
2311 // return the numbers as an array of two.
2312 function decodeVLANCK ($string)
2315 if (1 != preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $string, $matches))
2316 throw new InvalidArgException ('VLAN compound key', $string);
2317 return array ($matches[1], $matches[2]);
2320 // Return VLAN name formatted for HTML output (note, that input
2321 // argument comes from database unescaped).
2322 function formatVLANName ($vlaninfo, $context = 'markup long')
2327 $ret = $vlaninfo['vlan_id'];
2328 if ($vlaninfo['vlan_descr'] != '')
2329 $ret .= ' ' . niftyString ($vlaninfo['vlan_descr']);
2332 $ret = $vlaninfo['vlan_id'];
2333 if ($vlaninfo['vlan_descr'] != '')
2334 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2337 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2338 if ($vlaninfo['vlan_descr'] != '')
2339 $ret .= ' (' . niftyString ($vlaninfo['vlan_descr']) . ')';
2343 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2344 $ret .= ' @' . niftyString ($vlaninfo['domain_descr']);
2345 if ($vlaninfo['vlan_descr'] != '')
2346 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2351 // map interface name
2352 function ios12ShortenIfName ($ifname)
2354 $ifname = preg_replace ('@^Ethernet(.+)$@', 'e\\1', $ifname);
2355 $ifname = preg_replace ('@^FastEthernet(.+)$@', 'fa\\1', $ifname);
2356 $ifname = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $ifname);
2357 $ifname = preg_replace ('@^TenGigabitEthernet(.+)$@', 'te\\1', $ifname);
2358 $ifname = preg_replace ('@^Port-channel(.+)$@', 'po\\1', $ifname);
2359 $ifname = preg_replace ('@^XGigabitEthernet(.+)$@', 'xg\\1', $ifname);
2363 function iosParseVLANString ($string)
2366 foreach (explode (',', $string) as $item)
2369 $item = trim ($item, ' ');
2370 if (preg_match ('/^([[:digit:]]+)$/', $item, $matches))
2371 $ret[] = $matches[1];
2372 elseif (preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $item, $matches))
2373 $ret = array_merge ($ret, range ($matches[1], $matches[2]));
2378 // Scan given array and return the key, which addresses the first item
2379 // with requested column set to given value (or NULL if there is none such).
2380 // Note that 0 and NULL mean completely different things and thus
2381 // require strict checking (=== and !===).
2382 function scanArrayForItem ($table, $scan_column, $scan_value)
2384 foreach ($table as $key => $row)
2385 if ($row[$scan_column] == $scan_value)
2390 // Return TRUE, if every value of A1 is present in A2 and vice versa,
2391 // regardless of each array's sort order and indexing.
2392 function array_values_same ($a1, $a2)
2394 return !count (array_diff ($a1, $a2)) and !count (array_diff ($a2, $a1));
2397 // Use the VLAN switch template to set VST role for each port of
2398 // the provided list. Return resulting list.
2399 function apply8021QOrder ($vst_id, $portlist)
2401 $vst = getVLANSwitchTemplate ($vst_id);
2402 foreach (array_keys ($portlist) as $port_name)
2404 foreach ($vst['rules'] as $rule)
2405 if (preg_match ($rule['port_pcre'], $port_name))
2407 $portlist[$port_name]['vst_role'] = $rule['port_role'];
2408 $portlist[$port_name]['wrt_vlans'] = buildVLANFilter ($rule['port_role'], $rule['wrt_vlans']);
2411 $portlist[$port_name]['vst_role'] = 'none';
2416 // return a sequence of ranges for given string form and port role
2417 function buildVLANFilter ($role, $string)
2422 case 'access': // 1-4094
2426 case 'trunk': // 2-4094
2429 $min = VLAN_MIN_ID +
1;
2435 if ($string == '') // fast track
2436 return array (array ('from' => $min, 'to' => $max));
2438 $vlanidlist = array();
2439 foreach (iosParseVLANString ($string) as $vlan_id)
2440 if ($min <= $vlan_id and $vlan_id <= $max)
2441 $vlanidlist[] = $vlan_id;
2442 return listToRanges ($vlanidlist);
2445 // pack set of integers into list of integer ranges
2446 // e.g. (1, 2, 3, 5, 6, 7, 9, 11) => ((1, 3), (5, 7), (9, 9), (11, 11))
2447 function listToRanges ($vlanidlist)
2452 foreach ($vlanidlist as $vlan_id)
2454 $from = $to = $vlan_id;
2455 elseif ($to +
1 == $vlan_id)
2459 $ret[] = array ('from' => $from, 'to' => $to);
2460 $from = $to = $vlan_id;
2463 $ret[] = array ('from' => $from, 'to' => $to);
2467 // return TRUE, if given VLAN ID belongs to one of filter's ranges
2468 function matchVLANFilter ($vlan_id, $vfilter)
2470 foreach ($vfilter as $range)
2471 if ($range['from'] <= $vlan_id and $vlan_id <= $range['to'])
2476 function exportSwitch8021QConfig
2484 // only ignore VLANs, which exist and are explicitly shown as "alien"
2485 $old_managed_vlans = array();
2486 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
2487 foreach ($device_vlanlist as $vlan_id)
2490 !array_key_exists ($vlan_id, $domain_vlanlist) or
2491 $domain_vlanlist[$vlan_id]['vlan_type'] != 'alien'
2493 $old_managed_vlans[] = $vlan_id;
2494 $ports_to_do = array();
2496 foreach ($changes as $port_name => $port)
2498 $ports_to_do[$port_name] = array
2500 'old_mode' => $before[$port_name]['mode'],
2501 'old_allowed' => $before[$port_name]['allowed'],
2502 'old_native' => $before[$port_name]['native'],
2503 'new_mode' => $port['mode'],
2504 'new_allowed' => $port['allowed'],
2505 'new_native' => $port['native'],
2507 $after[$port_name] = $port;
2509 // New VLAN table is a union of:
2510 // 1. all compulsory VLANs
2511 // 2. all "current" non-alien allowed VLANs of those ports, which are left
2512 // intact (regardless if a VLAN exists in VLAN domain, but looking,
2513 // if it is present in device's own VLAN table)
2514 // 3. all "new" allowed VLANs of those ports, which we do "push" now
2515 // Like for old_managed_vlans, a VLANs is never listed, only if it
2516 // exists and belongs to "alien" type.
2517 $new_managed_vlans = array();
2519 foreach ($domain_vlanlist as $vlan_id => $vlan)
2520 if ($vlan['vlan_type'] == 'compulsory')
2521 $new_managed_vlans[] = $vlan_id;
2523 foreach ($before as $port_name => $port)
2524 if (!array_key_exists ($port_name, $changes))
2525 foreach ($port['allowed'] as $vlan_id)
2527 if (in_array ($vlan_id, $new_managed_vlans))
2531 array_key_exists ($vlan_id, $domain_vlanlist) and
2532 $domain_vlanlist[$vlan_id]['vlan_type'] == 'alien'
2535 if (in_array ($vlan_id, $device_vlanlist))
2536 $new_managed_vlans[] = $vlan_id;
2539 foreach ($changes as $port)
2540 foreach ($port['allowed'] as $vlan_id)
2543 $domain_vlanlist[$vlan_id]['vlan_type'] == 'ondemand' and
2544 !in_array ($vlan_id, $new_managed_vlans)
2546 $new_managed_vlans[] = $vlan_id;
2548 // Before removing each old VLAN as such it is necessary to unassign
2549 // ports from it (to remove VLAN from each ports' list of "allowed"
2550 // VLANs). This change in turn requires, that a port's "native"
2551 // VLAN isn't set to the one being removed from its "allowed" list.
2552 foreach ($ports_to_do as $port_name => $port)
2553 switch ($port['old_mode'] . '->' . $port['new_mode'])
2555 case 'trunk->trunk':
2556 // "old" native is set and differs from the "new" native
2557 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
2560 'opcode' => 'unset native',
2561 'arg1' => $port_name,
2562 'arg2' => $port['old_native'],
2564 if (count ($tmp = array_diff ($port['old_allowed'], $port['new_allowed'])))
2567 'opcode' => 'rem allowed',
2568 'port' => $port_name,
2572 case 'access->access':
2573 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
2576 'opcode' => 'unset access',
2577 'arg1' => $port_name,
2578 'arg2' => $port['old_native'],
2581 case 'access->trunk':
2584 'opcode' => 'unset access',
2585 'arg1' => $port_name,
2586 'arg2' => $port['old_native'],
2589 case 'trunk->access':
2592 'opcode' => 'unset native',
2593 'arg1' => $port_name,
2594 'arg2' => $port['old_native'],
2596 if (count ($port['old_allowed']))
2599 'opcode' => 'rem allowed',
2600 'port' => $port_name,
2601 'vlans' => $port['old_allowed'],
2605 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure');
2607 // Now it is safe to unconfigure VLANs, which still exist on device,
2608 // but are not present on the "new" list.
2609 // FIXME: put all IDs into one pseudo-command to make it easier
2610 // for translators to create/destroy VLANs in batches, where
2611 // target platform allows them to do.
2612 foreach (array_diff ($old_managed_vlans, $new_managed_vlans) as $vlan_id)
2615 'opcode' => 'destroy VLAN',
2618 // Configure VLANs, which must be present on the device, but are not yet.
2619 foreach (array_diff ($new_managed_vlans, $old_managed_vlans) as $vlan_id)
2622 'opcode' => 'create VLAN',
2625 // Now, when all new VLANs are created (queued), it is safe to assign (queue)
2626 // ports to the new VLANs.
2627 foreach ($ports_to_do as $port_name => $port)
2628 switch ($port['old_mode'] . '->' . $port['new_mode'])
2630 case 'trunk->trunk':
2631 // For each allowed VLAN, which is present on the "new" list and missing from
2632 // the "old" one, queue a command to assign current port to that VLAN.
2633 if (count ($tmp = array_diff ($port['new_allowed'], $port['old_allowed'])))
2636 'opcode' => 'add allowed',
2637 'port' => $port_name,
2640 // One of the "allowed" VLANs for this port may probably be "native".
2641 // "new native" is set and differs from "old native"
2642 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
2645 'opcode' => 'set native',
2646 'arg1' => $port_name,
2647 'arg2' => $port['new_native'],
2650 case 'access->access':
2651 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
2654 'opcode' => 'set access',
2655 'arg1' => $port_name,
2656 'arg2' => $port['new_native'],
2659 case 'access->trunk':
2662 'opcode' => 'set mode',
2663 'arg1' => $port_name,
2664 'arg2' => $port['new_mode'],
2666 if (count ($port['new_allowed']))
2669 'opcode' => 'add allowed',
2670 'port' => $port_name,
2671 'vlans' => $port['new_allowed'],
2675 'opcode' => 'set native',
2676 'arg1' => $port_name,
2677 'arg2' => $port['new_native'],
2680 case 'trunk->access':
2683 'opcode' => 'set mode',
2684 'arg1' => $port_name,
2685 'arg2' => $port['new_mode'],
2689 'opcode' => 'set access',
2690 'arg1' => $port_name,
2691 'arg2' => $port['new_native'],
2695 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure');
2699 array_unshift ($crq, array ('opcode' => 'begin configuration'));
2700 $crq[] = array ('opcode' => 'end configuration');
2701 if (considerConfiguredConstraint (spotEntity ('object', $vswitch['object_id']), '8021Q_WRI_AFTER_CONFT_LISTSRC'))
2702 $crq[] = array ('opcode' => 'save configuration');
2703 setDevice8021QConfig ($vswitch['object_id'], $crq);
2705 return count ($crq);
2708 // filter list of changed ports to cancel changes forbidden by VST and domain
2709 function filter8021QChangeRequests
2712 $before, // current saved configuration of all ports
2713 $changes // changed ports with VST markup
2716 $domain_immune_vlans = array();
2717 foreach ($domain_vlanlist as $vlan_id => $vlan)
2718 if ($vlan['vlan_type'] == 'alien')
2719 $domain_immune_vlans[] = $vlan_id;
2721 foreach ($changes as $port_name => $port)
2723 // find and cancel any changes regarding immune VLANs
2724 switch ($port['vst_role'])
2727 if ($port['mode'] != 'access') // VST violation
2728 continue 2; // ignore change request
2729 foreach ($domain_immune_vlans as $immune)
2730 // Reverting an attempt to set an access port from
2731 // "normal" VLAN to immune one (or vice versa) requires
2732 // special handling, becase the calling function has
2733 // discarded the old contents of 'allowed' for current port.
2736 $before[$port_name]['native'] == $immune or
2737 $port['native'] == $immune
2740 $port['native'] = $before[$port_name]['native'];
2741 $port['allowed'] = array ($port['native']);
2742 // Such reversal happens either once or never for an
2750 if ($port['mode'] != 'trunk')
2752 foreach ($domain_immune_vlans as $immune)
2753 if (in_array ($immune, $before[$port_name]['allowed'])) // was allowed before
2755 if (!in_array ($immune, $port['allowed']))
2756 $port['allowed'][] = $immune; // restore
2757 if ($before[$port_name]['native'] == $immune) // and was native
2758 $port['native'] = $immune; // also restore
2762 if (in_array ($immune, $port['allowed']))
2763 unset ($port['allowed'][array_search ($immune, $port['allowed'])]); // cancel
2764 if ($port['native'] == $immune)
2765 $port['native'] = $before[$port_name]['native'];
2772 $ret[$port_name] = $port;
2777 // take port list with order applied and return uplink ports in the same format
2778 function produceUplinkPorts ($domain_vlanlist, $portlist)
2781 $employed = array();
2782 foreach ($domain_vlanlist as $vlan_id => $vlan)
2783 if ($vlan['vlan_type'] == 'compulsory')
2784 $employed[] = $vlan_id;
2785 foreach ($portlist as $port_name => $port)
2786 if ($port['vst_role'] != 'uplink')
2787 foreach ($port['allowed'] as $vlan_id)
2788 if (!in_array ($vlan_id, $employed))
2789 $employed[] = $vlan_id;
2790 foreach ($portlist as $port_name => $port)
2791 if ($port['vst_role'] == 'uplink')
2793 $employed_here = array();
2794 foreach ($employed as $vlan_id)
2795 if (matchVLANFilter ($vlan_id, $port['wrt_vlans']))
2796 $employed_here[] = $vlan_id;
2797 $ret[$port_name] = array
2799 'vst_role' => 'uplink',
2801 'allowed' => $employed_here,
2808 function same8021QConfigs ($a, $b)
2810 return $a['mode'] == $b['mode'] &&
2811 array_values_same ($a['allowed'], $b['allowed']) &&
2812 $a['native'] == $b['native'];
2817 Relation between desired (D), cached (C) and running (R)
2818 copies of switch ports (P) list.
2822 | P |-----| P |-? +--| P |
2824 | P |-----| P |--+ ?-| P |
2826 | P |-----| P |-------| P |
2828 | P |-----| P |--+ ?-| P |
2830 | P |-----| P |--+ +--| P |
2837 A modified local version of a port in "conflict" state ignores remote
2838 changes until remote change maintains its difference. Once both edits
2839 match, the local copy "locks" on the remote and starts tracking it.
2842 a "o" -- remOte version
2843 l "l" -- Local version
2844 u "b" -- Both versions
2857 0----------------------------------------------> time
2860 function get8021QSyncOptions
2863 $D, // desired config
2864 $C, // cached config
2865 $R // running-config
2868 $default_port = array
2871 'allowed' => array (VLAN_DFL_ID
),
2872 'native' => VLAN_DFL_ID
,
2875 $allports = array();
2876 foreach (array_unique (array_merge (array_keys ($C), array_keys ($R))) as $pn)
2877 $allports[$pn] = array();
2878 foreach (apply8021QOrder ($vswitch['template_id'], $allports) as $pn => $port)
2880 // catch anomalies early
2881 if ($port['vst_role'] == 'none')
2883 if ((!array_key_exists ($pn, $R) or $R[$pn]['mode'] == 'none') and !array_key_exists ($pn, $C))
2884 $ret[$pn] = array ('status' => 'none');
2888 'status' => 'martian_conflict',
2889 'left' => array_key_exists ($pn, $C) ?
$C[$pn] : array ('mode' => 'none'),
2890 'right' => array_key_exists ($pn, $R) ?
$R[$pn] : array ('mode' => 'none'),
2894 elseif ((!array_key_exists ($pn, $R) or $R[$pn]['mode'] == 'none') and array_key_exists ($pn, $C))
2898 'status' => 'martian_conflict',
2899 'left' => array_key_exists ($pn, $C) ?
$C[$pn] : array ('mode' => 'none'),
2900 'right' => array_key_exists ($pn, $R) ?
$R[$pn] : array ('mode' => 'none'),
2904 // (DC_): port missing from device
2905 if (!array_key_exists ($pn, $R))
2907 $ret[$pn] = array ('left' => $D[$pn]);
2908 if (same8021QConfigs ($D[$pn], $default_port))
2909 $ret[$pn]['status'] = 'ok_to_delete';
2912 $ret[$pn]['status'] = 'delete_conflict';
2913 $ret[$pn]['lastseen'] = $C[$pn];
2917 // (__R): port missing from DB
2918 if (!array_key_exists ($pn, $C))
2920 // Allow importing any configuration, which passes basic
2921 // validation. If port mode doesn't match its VST role,
2922 // this will be handled later WRT each port.
2925 'status' => acceptable8021QConfig ($R[$pn]) ?
'ok_to_add' : 'add_conflict',
2930 $D_eq_C = same8021QConfigs ($D[$pn], $C[$pn]);
2931 $C_eq_R = same8021QConfigs ($C[$pn], $R[$pn]);
2932 // (DCR), D = C = R: data in sync
2933 if ($D_eq_C and $C_eq_R) // implies D == R
2937 'status' => 'in_sync',
2942 // (DCR), D = C: no local edit in the way
2946 'status' => 'ok_to_pull',
2950 // (DCR), C = R: no remote edit in the way
2954 'status' => 'ok_to_push',
2958 // (DCR), D = R: end of version conflict, restore tracking
2959 elseif (same8021QConfigs ($D[$pn], $R[$pn]))
2962 'status' => 'ok_to_merge',
2965 else // D != C, C != R, D != R: version conflict
2968 'status' => ($port['vst_role'] == 'access' or $port['vst_role'] == 'trunk') ?
2969 // In case the port is normally updated by user, let him
2970 // resolve the conflict. If the system manages this port,
2971 // arrange the data to let remote version go down.
2972 'merge_conflict' : 'ok_to_push_with_merge',
2980 // return number of records updated successfully of FALSE, if a conflict was in the way
2981 function exec8021QDeploy ($object_id, $do_push)
2984 $nsaved = $npushed = $nsaved_uplinks = 0;
2985 $dbxlink->beginTransaction();
2986 if (NULL === $vswitch = getVLANSwitchInfo ($object_id, 'FOR UPDATE'))
2987 throw new InvalidArgException ('object_id', $object_id, 'VLAN domain is not set for this object');
2988 $D = getStored8021QConfig ($vswitch['object_id'], 'desired');
2989 $C = getStored8021QConfig ($vswitch['object_id'], 'cached');
2992 $R = getRunning8021QConfig ($vswitch['object_id']);
2994 catch (Exception
$e)
2996 usePreparedExecuteBlade
2998 'UPDATE VLANSwitch SET last_errno=?, last_error_ts=NOW() WHERE object_id=?',
2999 array (E_8021Q_PULL_REMOTE_ERROR
, $vswitch['object_id'])
3005 $ok_to_push = array();
3006 foreach (get8021QSyncOptions ($vswitch, $D, $C, $R['portdata']) as $pn => $port)
3008 // always update cache with new data from switch
3009 switch ($port['status'])
3012 // FIXME: this can be logged
3013 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['both']);
3015 case 'ok_to_delete':
3016 $nsaved +
= del8021QPort ($vswitch['object_id'], $pn);
3019 $nsaved +
= add8021QPort ($vswitch['object_id'], $pn, $port['right']);
3021 case 'delete_conflict':
3022 case 'merge_conflict':
3023 case 'add_conflict':
3024 case 'martian_conflict':
3028 // FIXME: this can be logged
3029 upd8021QPort ('desired', $vswitch['object_id'], $pn, $port['right']);
3030 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right']);
3033 case 'ok_to_push_with_merge':
3034 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right']);
3037 $ok_to_push[$pn] = $port['left'];
3041 // redo uplinks unconditionally
3042 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
3043 $Dnew = apply8021QOrder ($vswitch['template_id'], getStored8021QConfig ($vswitch['object_id'], 'desired'));
3044 // Take new "desired" configuration and derive uplink port configuration
3045 // from it. Then cancel changes to immune VLANs and save resulting
3046 // changes (if any left).
3047 $new_uplinks = filter8021QChangeRequests ($domain_vlanlist, $Dnew, produceUplinkPorts ($domain_vlanlist, $Dnew));
3048 $nsaved_uplinks +
= replace8021QPorts ('desired', $vswitch['object_id'], $Dnew, $new_uplinks);
3049 if ($nsaved +
$nsaved_uplinks)
3051 // saved configuration has changed (either "user" ports have changed,
3052 // or uplinks, or both), so bump revision number up)
3053 usePreparedExecuteBlade
3055 'UPDATE VLANSwitch SET mutex_rev=mutex_rev+1, last_change=NOW(), out_of_sync="yes" WHERE object_id=?',
3056 array ($vswitch['object_id'])
3060 usePreparedExecuteBlade
3062 'UPDATE VLANSwitch SET out_of_sync="yes", last_errno=?, last_error_ts=NOW() WHERE object_id=?',
3063 array (E_8021Q_VERSION_CONFLICT
, $vswitch['object_id'])
3067 usePreparedExecuteBlade
3069 'UPDATE VLANSwitch SET last_errno=?, last_error_ts=NOW() WHERE object_id=?',
3070 array (E_8021Q_NOERROR
, $vswitch['object_id'])
3072 // Modified uplinks are very likely to differ from those in R-copy,
3073 // so don't mark device as clean, if this happened. This can cost
3074 // us an additional, empty round of sync, but at least out_of_sync
3075 // won't be mistakenly set to 'no'.
3076 // FIXME: A cleaner way of coupling pull and push operations would
3077 // be to split this function into two.
3078 if (!count ($ok_to_push) and !$nsaved_uplinks)
3079 usePreparedExecuteBlade
3081 'UPDATE VLANSwitch SET out_of_sync="no" WHERE object_id=?',
3082 array ($vswitch['object_id'])
3086 usePreparedExecuteBlade
3088 'UPDATE VLANSwitch SET last_push_started=NOW() WHERE object_id=?',
3089 array ($vswitch['object_id'])
3093 $npushed +
= exportSwitch8021QConfig ($vswitch, $R['vlanlist'], $R['portdata'], $ok_to_push);
3094 // update cache for ports deployed
3095 replace8021QPorts ('cached', $vswitch['object_id'], $R['portdata'], $ok_to_push);
3096 usePreparedExecuteBlade
3098 'UPDATE VLANSwitch SET last_push_finished=NOW(), out_of_sync="no", last_errno=? WHERE object_id=?',
3099 array (E_8021Q_NOERROR
, $vswitch['object_id'])
3102 catch (Exception
$r)
3104 usePreparedExecuteBlade
3106 'UPDATE VLANSwitch SET out_of_sync="yes", last_error_ts=NOW(), last_errno=? WHERE object_id=?',
3107 array (E_8021Q_PUSH_REMOTE_ERROR
, $vswitch['object_id'])
3113 // start downlink work only after unlocking current object to make deadlocks less likely to happen
3114 // TODO: only process changed uplink ports
3115 if ($nsaved_uplinks)
3116 initiateUplinksReverb ($vswitch['object_id'], $new_uplinks);
3117 return $nsaved +
$npushed +
$nsaved_uplinks;
3120 // print part of HTML HEAD block
3121 function printPageHeaders ()
3123 global $pageheaders;
3124 ksort ($pageheaders);
3125 foreach ($pageheaders as $s)
3127 echo "<style type='text/css'>\n";
3128 foreach (array ('F', 'A', 'U', 'T', 'Th', 'Tw', 'Thw') as $statecode)
3130 echo "td.state_${statecode} {\n";
3131 echo "\ttext-align: center;\n";
3132 echo "\tbackground-color: #" . (getConfigVar ('color_' . $statecode)) . ";\n";
3133 echo "\tfont: bold 10px Verdana, sans-serif;\n";
3139 function strerror8021Q ($errno)
3143 case E_8021Q_VERSION_CONFLICT
:
3144 return 'pull failed due to version conflict';
3145 case E_8021Q_PULL_REMOTE_ERROR
:
3146 return 'pull failed due to remote error';
3147 case E_8021Q_PUSH_REMOTE_ERROR
:
3148 return 'push failed due to remote error';
3149 case E_8021Q_SYNC_DISABLED
:
3150 return 'sync disabled by operator';
3152 return "unknown error code ${errno}";
3156 function saveDownlinksReverb ($object_id, $requested_changes)
3160 $dbxlink->beginTransaction();
3161 if (NULL === $vswitch = getVLANSwitchInfo ($object_id, 'FOR UPDATE')) // not configured, bail out
3163 $dbxlink->rollBack();
3166 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
3167 // aplly VST to the smallest set necessary
3168 $requested_changes = apply8021QOrder ($vswitch['template_id'], $requested_changes);
3169 $before = getStored8021QConfig ($object_id, 'desired');
3170 $changes_to_save = array();
3171 // first filter by wrt_vlans constraint
3172 foreach ($requested_changes as $pn => $requested)
3173 if (array_key_exists ($pn, $before) and $requested['vst_role'] == 'downlink')
3177 'vst_role' => 'downlink',
3179 'allowed' => array(),
3183 foreach ($requested['allowed'] as $vlan_id)
3184 if (matchVLANFilter ($vlan_id, $requested['wrt_vlans']))
3185 $negotiated['allowed'][] = $vlan_id;
3186 $changes_to_save[$pn] = $negotiated;
3188 // immune VLANs filter
3189 foreach (filter8021QChangeRequests ($domain_vlanlist, $before, $changes_to_save) as $pn => $finalconfig)
3190 if (!same8021QConfigs ($finalconfig, $before[$pn]))
3191 $nsaved +
= upd8021QPort ('desired', $vswitch['object_id'], $pn, $finalconfig);
3193 usePreparedExecuteBlade
3195 'UPDATE VLANSwitch SET mutex_rev=mutex_rev+1, last_change=NOW(), out_of_sync="yes" WHERE object_id=?',
3196 array ($vswitch['object_id'])
3201 // Use records from Port and Link tables to run a series of tasks on remote
3202 // objects. These device-specific tasks will adjust downlink ports according to
3203 // the current configuration of given uplink ports.
3204 function initiateUplinksReverb ($object_id, $uplink_ports)
3206 $object = spotEntity ('object', $object_id);
3207 amplifyCell ($object);
3208 // Filter and regroup all requests (regardless of how many will succeed)
3209 // to end up with no more, than one execution per remote object.
3210 $upstream_config = array();
3211 foreach ($object['ports'] as $portinfo)
3214 array_key_exists ($portinfo['name'], $uplink_ports) and
3215 $portinfo['remote_object_id'] != '' and
3216 $portinfo['remote_name'] != ''
3218 $upstream_config[$portinfo['remote_object_id']][$portinfo['remote_name']] = $uplink_ports[$portinfo['name']];
3219 // Note that when current object has several Port records inder same name
3220 // (but with unique IIF-OIF pair), these ports can be Link'ed to different
3221 // remote objects (using different media types, perhaps). Such a case can
3222 // be considered as normal, and each remote object will show up on the
3223 // task list (with its actual remote port name, of course).
3224 foreach ($upstream_config as $remote_object_id => $remote_ports)
3225 saveDownlinksReverb ($remote_object_id, $remote_ports);
3228 function detectVLANSwitchQueue ($vswitch)
3230 if ($vswitch['out_of_sync'] == 'no')
3232 switch ($vswitch['last_errno'])
3234 case E_8021Q_NOERROR
:
3235 if ($vswitch['last_change_age_seconds'] > getConfigVar ('8021Q_DEPLOY_MAXAGE'))
3236 return 'sync_ready';
3237 elseif ($vswitch['last_change_age_seconds'] < getConfigVar ('8021Q_DEPLOY_MINAGE'))
3238 return 'sync_aging';
3240 return 'sync_ready';
3241 case E_8021Q_VERSION_CONFLICT
:
3242 if ($vswitch['last_error_age_seconds'] < getConfigVar ('8021Q_DEPLOY_RETRY'))
3243 return 'resync_aging';
3245 return 'resync_ready';
3246 case E_8021Q_PULL_REMOTE_ERROR
:
3247 case E_8021Q_PUSH_REMOTE_ERROR
:
3249 case E_8021Q_SYNC_DISABLED
:
3255 function get8021QDeployQueues()
3259 foreach (array_keys ($dqtitle) as $qcode)
3260 $ret[$qcode] = array();
3261 foreach (getVLANSwitches() as $object_id)
3263 $vswitch = getVLANSwitchInfo ($object_id);
3264 if ('' != $qcode = detectVLANSwitchQueue ($vswitch))
3265 $ret[$qcode][] = $vswitch;
3270 function acceptable8021QConfig ($port)
3272 switch ($port['mode'])
3279 count ($port['allowed']) == 1 and
3280 in_array ($port['native'], $port['allowed'])