Release of grains plugin to racktables-contribs
[racktables-contribs] / local_portlinker.php
1 <?php
2 //
3 // This extension for RackTables is used to mass connect ports (usefull for connecting back ends of patch panels)
4 //
5 // Version 2.0
6 //
7 // Created by Jeroen Benda
8 // 04-2010
9 // http://racktablescode.j-tools.net
10 //
11 // Please do not remove the credentials if you alter this file.
12 //
13
14 /*
15 Port linker allows you to connect the back ends of a patch panel to each other. That means you have created 2 ports per port on your
16 patch panel. One for the actual port and one for the cable coming out at the back.
17 The port type of the cable coming out at the back has to be in the array $portLinkerPortTypes to work (see below). So what needs to be done is
18 the setup of these backend cable types. So far I came up with 3 different cable types that I use
19 - utpLink
20 - fiberLinkSingleMode
21 - fiberLinkMultiMode
22 There is no real need for the difference between the last 2 but from the network team wanted this difference since the backend is different
23
24 After you've done that you need to set up a naming convention for your patch panels (if you have not already done so). With any object in
25 RackTables, the common name has to be unique. This extension uses the common name to identify which panels have to be connected so it cannot
26 be left blank. For example our naming convention is <rackname>-<rownumber where panel is mounted>.
27
28 You can use the Port Generator extension (can be found at racktablescode.j-tools.net) to generate the ports for a patch panel, both front and
29 back. Afterwards you can connect the end cables with this Port Linker.
30
31 To tell this extension which ends to connect you use the label field. If none of the link ports have been connected yet and there are link
32 ports defined, the port linker tab will show up.
33
34 Different checks are done before the link to connect the ports is actually shown. You have to make sure that the number of ports match,
35 the types match etc.
36
37 So now for some examples:
38 Let's say you have panel S1-42 with 24 ports and a panel N1-37 with 24 ports that need to be connected. You should create the ports on both
39 panels (or generate them) so you have a front port and a cable port per port on your patch panel:
40 S1-42 eth1
41 S1-42 eth1link
42 S1-42 eth2
43 S1-42 eth2link
44 ....
45 N1-37 eth1
46 N1-37 eth1link
47 N2-37 eth2
48 N2-37 eth2link
49 .....
50 You than tell the port linker which panels to link. You do this by putting in the label field the destination of the patch panel. In this example
51 the label for S1-42 will be N1-37 AND the label for N1-37 will be S1-42.
52 Common name Label
53 S1-42 N1-37
54 N1-37 S1-42
55
56 If you select the tab port linker, you get an overview of the ports
57 that will be linked and a link to confirm that you actually want to connect them. If you connect on the link, they will be linked.
58
59 A little more difficult example:
60 Let's say you have a panel S1-42 with 24 ports and 2 panels, N1-37 and N2-37 both with 12 ports (a server rack patch panel going to 2 network
61 racks). The labels will be as followed:
62 Common name Label
63 N1-37 S1-42
64 N2-37 S1-42
65 S1-42 N1-37 N2-37
66
67 Notice that the label for S1-42 has 2 names seperated by a space. This means that the ports of S1-42 will be split evenly. Because there are
68 2 destinations, the number of ports will be divided by 2. Half the number of ports (12) will go to one and the other will go to other. In this
69 example, the FIRST half of ports will go to N1-37 and the SECOND half of ports will go to N2-37
70 For the network racks all ports will go to S1-42 so no need for anything else in the label.
71 When you click on port linker now, you will get a list that looks like this:
72
73 S1-42 eth1link N1-37 eth1link
74 S1-42 eth2link N1-37 eth2link
75 ...
76 S1-42 eth13link N2-37 eth1link
77 ...
78 S1-42 eth24link N2-37 eth12link
79
80 Finally an even more complex (but normal) example:
81 You have 2 server racks S1 and S2 which both have a patch panel at the top. Half of the 24 ports go to one network N1 and the other go to another
82 network rack N2. In this example, the network racks also have 24 ports patch panels and on each row their receive patch panels from 2 server racks.
83 Let's say that in this case the panel in the network racks at row 37 connects to S1 and S2, the first 12 ports of the patch panel in each network
84 rack go to S1 and the second 12 to S2.
85 This will result in the following labels:
86 Common name Label
87 S1-42 N1-37 N2-37
88 S2-42 N1-37 N2-37
89 N1-37 S1-42 S2-42
90 N2-37 S1-42 S2-42
91
92 The label is always written from the perspective of the patch panel. In this example the ports for S1 go to N1 and N2 in that order. The same is
93 true for the ports of S2 so the labels are the same. In the network racks you see the same thing from the other side. Even though the labels are
94 the same, this will not result in a connecting conflict because port linker looks from both ends to make sure the cables are connected properly.
95 If we just look at ports 1 and 13 in this example you get:
96
97 S1-42 eth1link N1-37 eth1link
98 S1-42 eth13link N2-37 eth1link
99 S2-42 eth1link N1-37 eth13link
100 S2-42 eth13link N2-37 eth13link
101
102 */
103
104 //
105 // This php file depends on 2 variables that have to be set before this file is included:
106 //
107 // $portLinkerPortTypes contains port types that can be connected (these are the types that represent the cables at the back end
108 // of the patch panel)
109 // $portLinkerObjectTypes contains object types that can be connected (this is usually the patch panel and maybe a self created ODF panel)
110 //
111
112 //
113 // Set up the RackTables variables that create the tab
114 // The tab only shows up if there are ports defined and no ports already linked
115 //
116 // The functions referred to in the handlers and trigger are all in this php file
117 //
118 $tab['object']['portlinker'] = 'Port linker';
119 $trigger['object']['portlinker'] = 'localtrigger_PortLinker';
120 $tabhandler['object']['portlinker'] = 'localfunc_PortLinker';
121 $ophandler['object']['default']['linknow'] = 'execute_PortLinker';
122
123 //
124 // Check whether the variables are set or otherwise set the default values.
125 // This is for this extension rather useless because you need extra port types that represent the cables at the back of the patch panel
126 //------------------------
127 //Version 1.2
128 //Revised by Jorge Sanchez
129 //04-2011
130 //------------------------
131 //Changes
132 //------------------------
133 //Only one change has been added to this file. The $result variable is no longer equal to useSelectBlade($q,__FUNCTION__); but to usePreparedSelectBlade($q);
134 //This is due to the changes since 0.17.x to 0.18.7 to the rendering of objects
135 //
136
137 global $portLinkerPortTypes;
138 if (!isset($portLinkerPortTypes)) {
139 $portLinkerPortTypes = array(50015);
140 }
141
142
143 global $portLinkerObjectTypes;
144 if (!isset($portLinkerObjectTypes)) {
145 $portLinkerObjectTypes = array(9);
146 }
147
148
149 function determinePortSplit ($object_label,$portCount) {
150 $labels = explode(" ",$object_label);
151 $splits = array();
152 if (count($labels)==0) {
153 } else if (count($labels)==1) {
154 $splits[$labels[0]] = array();
155 $splits[$labels[0]]['start'] = 1;
156 $splits[$labels[0]]['count'] = $portCount;
157 $splits[$labels[0]]['end'] = $portCount;
158 } else {
159 $sum = 0;
160 foreach($labels as $aLabel) {
161 $splits[$aLabel] = array();
162 $splits[$aLabel]['start'] = $sum+1;
163 $splits[$aLabel]['count'] = floor($portCount/count($labels));
164 $splits[$aLabel]['end'] = $sum+floor($portCount/count($labels));
165 $sum += $splits[$aLabel]['count'];
166 }
167 if ($sum<$portCount) {
168 $temp = array_keys($splits);
169 end($temp);
170 $splits[current($temp)]['count'] += $portCount-$sum;
171 $splits[current($temp)]['end'] = $portCount;
172 }
173 }
174 return $splits;
175 }
176
177 //
178 // This function returns the number of ports that have a type in the array $portLinkerPortTypes
179 //
180 function countPorts ($object_id) {
181 global $portLinkerPortTypes;
182 $record = getObjectPortsAndLinks($object_id);
183 $count = 0;
184 foreach ($record as $aPort) {
185 if (in_array($aPort['oif_id'],$portLinkerPortTypes)) {
186 $count++;
187 }
188 }
189 return $count;
190 }
191
192 //
193 // This function returns the integer found in a string. It keeps the first digits found.
194 // Example: eth3link => 3
195 // fbr15link => 15
196 // fbr1link2 => 1
197 // 12a => 12
198 //
199 function str2int ($xIn) {
200 $theNumber = "";
201 $allowedToAdd = true;
202 $inNumber = false;
203 $i = 0;
204 while ($i<strlen($xIn)) {
205 if ($allowedToAdd) {
206 if (is_numeric($xIn{$i})) {
207 $inNumber = true;
208 $theNumber .= $xIn{$i};
209 } else {
210 if ($inNumber) {
211 $inNumber = false;
212 $allowedToAdd = false;
213 }
214 }
215 }
216 $i++;
217 }
218 return intval($theNumber);
219 }
220
221 //
222 // This function returns an array of all available ports of a type that is listed in the array $portLinkerPortTypes
223 // of an object where the port number (as defined by parsing the name through str2int) is between $portStart and ($portStart+$portCount)
224 //
225 function getPorts ($object_id, $portCount, $portStart) {
226 global $portLinkerPortTypes;
227 $record = getObjectPortsAndLinks($object_id);
228 $foundPorts = array();
229 foreach ($record as $aPort) {
230 if (in_array($aPort['oif_id'],$portLinkerPortTypes) && strlen($aPort['reservation_comment'])==0 && $aPort['remote_id']==0) {
231 $num = str2int($aPort['name']);
232 if ($num>=$portStart && $num<$portStart+$portCount) {
233 $foundPorts[$num] = array();
234 $foundPorts[$num]['id'] = $aPort['id'];
235 $foundPorts[$num]['name'] = $aPort['name'];
236 }
237 }
238 }
239 ksort($foundPorts);
240 return $foundPorts;
241 }
242
243 //
244 // This is the function that actually executes the linking of the ports
245 // It uses determine_PortLinker function to see if it is allowed to link
246 //
247 $msgcode['execute_PortLinker']['OK'] = 0;
248 $msgcode['execute_PortLinker']['ERR'] = 100;
249 function execute_PortLinker () {
250 global $localSplit, $remoteSplit;
251 $errorText = determine_PortLinker();
252 $object = spotEntity ('object', $_REQUEST['object_id']);
253 if (strlen($errorText)==0) {
254 $count = 0;
255 // loop through all remote objects as defined
256 foreach ($localSplit as $aKey=>$aValue) {
257 $numAdd = $remoteSplit[$aKey][$object['name']]['start']-$aValue['start'];
258 foreach ($aValue['ports'] as $aPortNum=>$aPort) {
259 linkPorts($aPort['id'],$aValue['remote_ports'][$aPortNum+$numAdd]['id']);
260 $count++;
261 }
262 }
263 }
264 if (strlen($errorText)==0) {
265 return buildRedirectURL (__FUNCTION__, 'OK', array("{$count} ports successfully linked"));
266 } else {
267 return buildRedirectURL (__FUNCTION__, 'ERR', array($errorText));
268 }
269 }
270
271 function determine_PortLinker() {
272 global $localSplit, $remoteSplit, $portLinkerObjectTypes;
273 $errorText = "";
274 assertUIntArg ('object_id', __FUNCTION__);
275 $object = spotEntity ('object', $_REQUEST['object_id']);
276 $linkok = localpretrigger_PortLinker();
277 if ($linkok==2) {
278 if (in_array($object['objtype_id'],$portLinkerObjectTypes)) {
279 $localPortCount = countPorts($object['id']);
280 $remotePortCount = array();
281 $remoteObject = array();
282 $remoteSplit = array();
283 $localSplit = determinePortSplit($object['label'],$localPortCount);
284 $current = 1;
285 foreach ($localSplit as $aKey=>$aValue) {
286 if (strlen($errorText)==0) {
287 $q = "SELECT id FROM RackObject WHERE name='{$aKey}' ";
288 $result = usePreparedSelectBlade ($q);
289 if ($result==NULL) { print_r($dbxlink->errorInfo()); die(); }
290 if ($row = $result->fetch (PDO::FETCH_NUM)) {
291 $remotePortCount[$aKey] = countPorts($row[0]);
292 $remoteObject[$aKey] = spotEntity('object',$row[0]);
293 $remoteSplit[$aKey] = determinePortSplit($remoteObject[$aKey]['label'],$remotePortCount[$aKey]);
294 } else {
295 $errorText = "Could not find object <b>{$aKey}</b>";
296 }
297 }
298 }
299 if (strlen($errorText)==0) {
300 foreach ($localSplit as $aKey=>$aValue) {
301 if (strlen($errorText)==0 && !isset($remoteSplit[$aKey][$object['name']]['count'])) {
302 $errorText = "Object <b>{$aKey}</b> does not list this object in the label field as a remote panel";
303 }
304 if (strlen($errorText)==0 && $remoteSplit[$aKey][$object['name']]['count']!=$aValue['count']) {
305 $errorText = "Port count does not match for object <b>{$aKey}</b>";
306 }
307 }
308 if (strlen($errorText)==0) {
309 foreach ($localSplit as $aKey=>$aValue) {
310 if (strlen($errorText)==0) {
311 $localSplit[$aKey]['ports'] = getPorts($object['id'],$aValue['count'],$aValue['start']);
312 if (count($localSplit[$aKey]['ports'])!=$aValue['count']) {
313 $errorText = "Not all ports available on this object";
314 }
315 }
316 if (strlen($errorText)==0) {
317 $localSplit[$aKey]['remote_ports'] = getPorts($remoteObject[$aKey]['id'],$remoteSplit[$aKey][$object['name']]['count'],
318 $remoteSplit[$aKey][$object['name']]['start']);
319 if (count($localSplit[$aKey]['ports'])!=$remoteSplit[$aKey][$object['name']]['count']) {
320 $errorText = "Not all ports available on this object";
321 }
322 }
323 }
324 }
325 }
326 } else {
327 $errorText = "Object type should be PatchPanel or ODFPanel";
328 }
329 } else {
330 switch ($linkok) {
331 case "-1" : $errorText = "There are no ports configured yet, so nothing to link to."; break;
332 case "0" : $errorText = "Some link ports are already linked to another port."; break;
333 case "1" : $errorText = "No ports found that end in link."; break;
334 default : $errorText = "Unknown error.";
335 }
336 }
337 return $errorText;
338 }
339
340 function localfunc_PortLinker()
341 {
342 global $localSplit, $remoteSplit;
343 startPortlet("Port linker");
344 print "<center>";
345 $errorText = determine_PortLinker();
346 $object = spotEntity ('object', $_REQUEST['object_id']);
347 if (strlen($errorText)>0) {
348 print "Trying to link this object to : {$object['label']}<br><br>\n";
349 print $errorText;
350 } else {
351 echo "<a href='".
352 makeHrefProcess(array('op'=>'linknow','page'=>'object','tab'=>'default','object_id'=>$_REQUEST['object_id'])).
353 "'>Connect the following ports now:</a><p>\n";
354
355 print "<table cellspacing=0 cellpadding='5' align='center' class='widetable'>";
356 print "<tr><th>Object name</th><th>Port name</th><th>&nbsp;&nbsp;&nbsp;</th><th>Remote object name</th><th>Remote port name</th></tr>\n";
357 foreach ($localSplit as $aKey=>$aValue) {
358 $numAdd = $remoteSplit[$aKey][$object['name']]['start']-$aValue['start'];
359 foreach ($aValue['ports'] as $aPortNum=>$aPort) {
360 print "<tr><td>{$object['name']}</td><td>{$aPort['name']}</td><td>&nbsp;</td><td>{$aKey}</td><td>";
361 print $aValue['remote_ports'][$aPortNum+$numAdd]['name']."</td></tr>\n";
362 }
363 }
364 print "</table>";
365 }
366 print "</center>";
367 finishPortlet();
368 }
369
370 function localpretrigger_PortLinker() {
371 global $portLinkerPortTypes;
372 $record = getObjectPortsAndLinks ($_REQUEST['object_id']);
373 if (count($record)>0) {
374 $linkok = 1;
375 foreach ($record as $aPort) {
376 if (in_array($aPort['oif_id'],$portLinkerPortTypes)) {
377 if ($linkok==1) {
378 $linkok = 2;
379 }
380 if (strlen($aPort['remote_id'])>0) {
381 $linkok = 0;
382 }
383 }
384 }
385 } else {
386 $linkok = -1;
387 }
388 return $linkok;
389 }
390
391
392 function localtrigger_PortLinker()
393 {
394 global $portLinkerObjectTypes;
395 assertUIntArg ('object_id', __FUNCTION__);
396 $object = spotEntity ('object', $_REQUEST['object_id']);
397 if (in_array($object['objtype_id'],$portLinkerObjectTypes)) {
398 $linkok = localpretrigger_PortLinker();
399 } else {
400 $linkok = 0;
401 }
402 if ($linkok==2)
403 return 1;
404 else
405 {
406 return '';
407 }
408 }
409
410 function test_debug()
411 {
412 global $portLinkerObjectTypes;
413 print $portLinkerObjectTypes;
414 }
415 ?>