Commit | Line | Data |
---|---|---|
b325120a | 1 | <?php |
d33645ff DO |
2 | /* |
3 | * | |
4 | * This file contains gateway functions for RackTables. | |
5 | * A gateway is an external executable, which provides | |
6 | * read-only or read-write access to some external entities. | |
7 | * Each gateway accepts its own list of command-line args | |
8 | * and then reads its stdin for requests. Each request consists | |
9 | * of one line and results in exactly one line of reply. | |
10 | * The replies must have the following syntax: | |
11 | * OK<space>any text up to the end of the line | |
12 | * ERR<space>any text up to the end of the line | |
13 | * | |
14 | */ | |
15 | ||
ad541266 DO |
16 | // translating functions maps |
17 | $gwrxlator = array(); | |
ffd829af DO |
18 | $gwrxlator['getcdpstatus'] = array |
19 | ( | |
20 | 'ios12' => 'ios12ReadCDPStatus', | |
21 | 'nxos4' => 'ios12ReadCDPStatus', | |
22 | ); | |
50636557 DO |
23 | $gwrxlator['getlldpstatus'] = array |
24 | ( | |
1ebbf889 | 25 | 'ios12' => 'ios12ReadLLDPStatus', |
50636557 DO |
26 | 'xos12' => 'xos12ReadLLDPStatus', |
27 | 'vrp53' => 'vrp53ReadLLDPStatus', | |
e11d4936 | 28 | 'vrp55' => 'vrp55ReadLLDPStatus', |
50636557 | 29 | ); |
db64180c | 30 | $gwrxlator['get8021q'] = array |
ad541266 DO |
31 | ( |
32 | 'ios12' => 'ios12ReadVLANConfig', | |
33 | 'fdry5' => 'fdry5ReadVLANConfig', | |
34 | 'vrp53' => 'vrp53ReadVLANConfig', | |
ec523868 | 35 | 'vrp55' => 'vrp55Read8021QConfig', |
ad541266 | 36 | 'nxos4' => 'nxos4Read8021QConfig', |
1aa648ca | 37 | 'xos12' => 'xos12Read8021QConfig', |
ad541266 | 38 | ); |
65e557dd AA |
39 | $gwrxlator['getportstatus'] = array |
40 | ( | |
41 | 'ios12' => 'ciscoReadInterfaceStatus', | |
42 | 'vrp53' => 'vrpReadInterfaceStatus', | |
43 | 'vrp55' => 'vrpReadInterfaceStatus', | |
44 | 'nxos4' => 'ciscoReadInterfaceStatus', | |
45 | ); | |
46 | $gwrxlator['getmaclist'] = array | |
47 | ( | |
48 | 'ios12' => 'ios12ReadMacList', | |
49 | 'vrp53' => 'vrp53ReadMacList', | |
50 | 'vrp55' => 'vrp55ReadMacList', | |
51 | 'nxos4' => 'nxos4ReadMacList', | |
52 | ); | |
d33645ff | 53 | |
0328f6d6 DO |
54 | $gwrxlator['gethndp']['vrp53'] = 'vrp53ReadHNDPStatus'; |
55 | ||
50520b40 DO |
56 | $gwpushxlator = array |
57 | ( | |
58 | 'ios12' => 'ios12TranslatePushQueue', | |
59 | 'fdry5' => 'fdry5TranslatePushQueue', | |
60 | 'vrp53' => 'vrp53TranslatePushQueue', | |
ec523868 | 61 | 'vrp55' => 'vrp55TranslatePushQueue', |
50520b40 DO |
62 | 'nxos4' => 'ios12TranslatePushQueue', // employ syntax compatibility |
63 | 'xos12' => 'xos12TranslatePushQueue', | |
64 | ); | |
65 | ||
d33645ff DO |
66 | // This function launches specified gateway with specified |
67 | // command-line arguments and feeds it with the commands stored | |
68 | // in the second arg as array. | |
69 | // The answers are stored in another array, which is returned | |
70 | // by this function. In the case when a gateway cannot be found, | |
71 | // finishes prematurely or exits with non-zero return code, | |
72 | // a single-item array is returned with the only "ERR" record, | |
73 | // which explains the reason. | |
2da7c9b0 | 74 | function queryGateway ($gwname, $questions) |
d33645ff DO |
75 | { |
76 | $execpath = "./gateways/{$gwname}/main"; | |
d33645ff DO |
77 | $dspec = array |
78 | ( | |
79 | 0 => array ("pipe", "r"), | |
80 | 1 => array ("pipe", "w"), | |
81 | 2 => array ("file", "/dev/null", "a") | |
82 | ); | |
83 | $pipes = array(); | |
2da7c9b0 | 84 | $gateway = proc_open ($execpath, $dspec, $pipes); |
d33645ff | 85 | if (!is_resource ($gateway)) |
4d2e93f2 | 86 | return array ('ERR proc_open() failed in ' . __FUNCTION__); |
d33645ff DO |
87 | |
88 | // Dialogue starts. Send all questions. | |
89 | foreach ($questions as $q) | |
90 | fwrite ($pipes[0], "$q\n"); | |
91 | fclose ($pipes[0]); | |
92 | ||
93 | // Fetch replies. | |
2da7c9b0 | 94 | $answers = array (); |
d33645ff DO |
95 | while (!feof($pipes[1])) |
96 | { | |
97 | $a = fgets ($pipes[1]); | |
59a83bd8 | 98 | if (!strlen ($a)) |
d33645ff | 99 | continue; |
075aeb88 DO |
100 | // Somehow I got a space appended at the end. Kick it. |
101 | $answers[] = trim ($a); | |
d33645ff DO |
102 | } |
103 | fclose($pipes[1]); | |
104 | ||
105 | $retval = proc_close ($gateway); | |
106 | if ($retval != 0) | |
3a089a44 | 107 | throw new RTGatewayError ("gateway failed with code ${retval}"); |
7a60526f | 108 | if (!count ($answers)) |
3a089a44 | 109 | throw new RTGatewayError ('no response from gateway'); |
7a60526f | 110 | if (count ($answers) != count ($questions)) |
3a089a44 | 111 | throw new RTGatewayError ('protocol violation'); |
7a60526f | 112 | foreach ($answers as $a) |
793ba0cc | 113 | if (strpos ($a, 'OK!') !== 0) |
3a089a44 | 114 | throw new RTGatewayError ("subcommand failed with status: ${a}"); |
d33645ff DO |
115 | return $answers; |
116 | } | |
117 | ||
0a1b1268 | 118 | // This functions returns an array for VLAN list, and an array for port list (both |
18cb9495 | 119 | // form another array themselves) and another one with MAC address list. |
0a1b1268 DO |
120 | // The ports in the latter array are marked with either VLAN ID or 'trunk'. |
121 | // We don't sort the port list, as the gateway is believed to have done this already | |
122 | // (or at least the underlying switch software ought to). This is important, as the | |
123 | // port info is transferred to/from form not by names, but by numbers. | |
124 | function getSwitchVLANs ($object_id = 0) | |
125 | { | |
126 | global $remote_username; | |
6297d584 | 127 | $objectInfo = spotEntity ('object', $object_id); |
0a1b1268 DO |
128 | $endpoints = findAllEndpoints ($object_id, $objectInfo['name']); |
129 | if (count ($endpoints) == 0) | |
3a089a44 | 130 | throw new RTGatewayError ('no management address set'); |
0a1b1268 | 131 | if (count ($endpoints) > 1) |
3a089a44 | 132 | throw new RTGatewayError ('cannot pick management address'); |
0a1b1268 | 133 | $hwtype = $swtype = 'unknown'; |
7fa7047a | 134 | foreach (getAttrValues ($object_id) as $record) |
0a1b1268 | 135 | { |
e609f5c8 | 136 | if ($record['name'] == 'SW type' && strlen ($record['o_value'])) |
c9edf725 | 137 | $swtype = str_replace (' ', '+', execGMarker ($record['o_value'])); |
e609f5c8 | 138 | if ($record['name'] == 'HW type' && strlen ($record['o_value'])) |
c9edf725 | 139 | $hwtype = str_replace (' ', '+', execGMarker ($record['o_value'])); |
0a1b1268 | 140 | } |
ee2174dd | 141 | $endpoint = str_replace (' ', '+', $endpoints[0]); |
18cb9495 | 142 | $commands = array |
0a1b1268 | 143 | ( |
ee2174dd | 144 | "connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", |
18cb9495 DO |
145 | 'listvlans', |
146 | 'listports', | |
147 | 'listmacs' | |
0a1b1268 | 148 | ); |
18cb9495 | 149 | $data = queryGateway ('switchvlans', $commands); |
0a1b1268 | 150 | if (strpos ($data[0], 'OK!') !== 0) |
3a089a44 | 151 | throw new RTGatewayError ("gateway failed with status: ${data[0]}."); |
0a1b1268 DO |
152 | // Now we have VLAN list in $data[1] and port list in $data[2]. Let's sort this out. |
153 | $tmp = array_unique (explode (';', substr ($data[1], strlen ('OK!')))); | |
154 | if (count ($tmp) == 0) | |
3a089a44 | 155 | throw new RTGatewayError ('gateway returned no records'); |
0a1b1268 DO |
156 | $vlanlist = array(); |
157 | foreach ($tmp as $record) | |
158 | { | |
159 | list ($vlanid, $vlandescr) = explode ('=', $record); | |
160 | $vlanlist[$vlanid] = $vlandescr; | |
161 | } | |
162 | $portlist = array(); | |
0a73c02f | 163 | foreach (explode (';', substr ($data[2], strlen ('OK!'))) as $pair) |
0a1b1268 | 164 | { |
0a73c02f DO |
165 | list ($portname, $pair2) = explode ('=', $pair); |
166 | list ($status, $vlanid) = explode (',', $pair2); | |
167 | $portlist[] = array ('portname' => $portname, 'status' => $status, 'vlanid' => $vlanid); | |
0a1b1268 DO |
168 | } |
169 | if (count ($portlist) == 0) | |
3a089a44 | 170 | throw new RTGatewayError ('gateway returned no records'); |
18cb9495 DO |
171 | $maclist = array(); |
172 | foreach (explode (';', substr ($data[3], strlen ('OK!'))) as $pair) | |
173 | { | |
174 | list ($macaddr, $pair2) = explode ('=', $pair); | |
59a83bd8 | 175 | if (!strlen ($pair2)) |
18cb9495 DO |
176 | continue; |
177 | list ($vlanid, $ifname) = explode ('@', $pair2); | |
178 | $maclist[$ifname][$vlanid][] = $macaddr; | |
179 | } | |
180 | return array ($vlanlist, $portlist, $maclist); | |
0a1b1268 DO |
181 | } |
182 | ||
183 | function setSwitchVLANs ($object_id = 0, $setcmd) | |
184 | { | |
185 | global $remote_username; | |
6297d584 | 186 | $objectInfo = spotEntity ('object', $object_id); |
0a1b1268 DO |
187 | $endpoints = findAllEndpoints ($object_id, $objectInfo['name']); |
188 | if (count ($endpoints) == 0) | |
3f052a67 | 189 | throw new RTGatewayError ('no management address set'); |
0a1b1268 | 190 | if (count ($endpoints) > 1) |
3f052a67 | 191 | throw new RTGatewayError ('cannot pick management address'); |
0a1b1268 | 192 | $hwtype = $swtype = 'unknown'; |
7fa7047a | 193 | foreach (getAttrValues ($object_id) as $record) |
0a1b1268 | 194 | { |
e609f5c8 | 195 | if ($record['name'] == 'SW type' && strlen ($record['o_value'])) |
c9edf725 | 196 | $swtype = strtr (execGMarker ($record['o_value']), ' ', '+'); |
e609f5c8 | 197 | if ($record['name'] == 'HW type' && strlen ($record['o_value'])) |
c9edf725 | 198 | $hwtype = strtr (execGMarker ($record['o_value']), ' ', '+'); |
0a1b1268 | 199 | } |
15bb23dc | 200 | $endpoint = str_replace (' ', '+', $endpoints[0]); |
0a1b1268 DO |
201 | $data = queryGateway |
202 | ( | |
203 | 'switchvlans', | |
15bb23dc | 204 | array ("connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", $setcmd) |
0a1b1268 | 205 | ); |
0a1b1268 | 206 | // Finally we can parse the response into message array. |
c0042a75 | 207 | $log = emptyLog(); |
b591ac21 | 208 | foreach (explode (';', substr ($data[1], strlen ('OK!'))) as $text) |
0a1b1268 | 209 | { |
24dcb9d8 DO |
210 | if (strpos ($text, 'C!') === 0) |
211 | { | |
c0042a75 | 212 | // gateway-encoded message |
b591ac21 | 213 | $tmp = explode ('!', $text); |
f0ff4930 DO |
214 | array_shift ($tmp); |
215 | $code = array_shift ($tmp); | |
c0042a75 | 216 | $log = mergeLogs ($log, oneLiner ($code, $tmp)); |
24dcb9d8 DO |
217 | } |
218 | elseif (strpos ($text, 'I!') === 0) | |
c0042a75 | 219 | $log = mergeLogs ($log, oneLiner (62, array (substr ($text, 2)))); // generic gateway success |
0a1b1268 | 220 | elseif (strpos ($text, 'W!') === 0) |
c0042a75 | 221 | $log = mergeLogs ($log, oneLiner (202, array (substr ($text, 2)))); // generic gateway warning |
0a1b1268 | 222 | else // All improperly formatted messages must be treated as error conditions. |
c0042a75 | 223 | $log = mergeLogs ($log, oneLiner (166, array (substr ($text, 2)))); // generic gateway error |
0a1b1268 | 224 | } |
c0042a75 | 225 | return $log; |
0a1b1268 DO |
226 | } |
227 | ||
e77d763c DO |
228 | // Drop a file off RackTables platform. The gateway will catch the file and pass it to the given |
229 | // installer script. | |
1ee5488d | 230 | function gwSendFile ($endpoint, $handlername, $filetext = array()) |
c030232f | 231 | { |
b063cc8e DO |
232 | if (! is_array ($filetext)) |
233 | throw new InvalidArgException ('filetext', '(suppressed)', 'is not an array'); | |
c030232f | 234 | global $remote_username; |
1ee5488d | 235 | $tmpnames = array(); |
f3d274bf | 236 | $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments |
1ee5488d DO |
237 | $command = "submit ${remote_username} ${endpoint} ${handlername}"; |
238 | foreach ($filetext as $text) | |
239 | { | |
240 | $name = tempnam ('', 'RackTables-sendfile-'); | |
241 | $tmpnames[] = $name; | |
01af4445 | 242 | if (FALSE === $name or FALSE === file_put_contents ($name, $text)) |
1d34465d DO |
243 | { |
244 | foreach ($tmpnames as $name) | |
245 | unlink ($name); | |
3a089a44 | 246 | throw new RTGatewayError ('failed to write to temporary file'); |
1d34465d | 247 | } |
1ee5488d DO |
248 | $command .= " ${name}"; |
249 | } | |
e11d4936 DO |
250 | try |
251 | { | |
252 | queryGateway ('sendfile', array ($command)); | |
253 | foreach ($tmpnames as $name) | |
254 | unlink ($name); | |
255 | } | |
256 | catch (RTGatewayError $e) | |
257 | { | |
258 | foreach ($tmpnames as $name) | |
259 | unlink ($name); | |
260 | throw $e; | |
261 | } | |
f3d274bf | 262 | } |
1b494df6 | 263 | |
9bc28e53 DO |
264 | // Query something through a gateway and get some text in return. Return that text. |
265 | function gwRecvFile ($endpoint, $handlername, &$output) | |
266 | { | |
267 | global $remote_username; | |
268 | $tmpfilename = tempnam ('', 'RackTables-sendfile-'); | |
269 | $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments | |
e11d4936 DO |
270 | try |
271 | { | |
272 | queryGateway ('sendfile', array ("submit ${remote_username} ${endpoint} ${handlername} ${tmpfilename}")); | |
273 | $output = file_get_contents ($tmpfilename); | |
274 | unlink ($tmpfilename); | |
275 | } | |
276 | catch (RTGatewayError $e) | |
277 | { | |
278 | unlink ($tmpfilename); | |
279 | throw $e; | |
280 | } | |
ec523868 DO |
281 | if ($output === FALSE) |
282 | throw new RTGatewayError ('failed to read temporary file'); | |
9bc28e53 DO |
283 | } |
284 | ||
3f052a67 | 285 | function gwSendFileToObject ($object_id, $handlername, $filetext = '') |
f3d274bf | 286 | { |
3f052a67 DO |
287 | if (!mb_strlen ($handlername)) |
288 | throw new InvalidArgException ('$handlername'); | |
6297d584 | 289 | $objectInfo = spotEntity ('object', $object_id); |
f3d274bf DO |
290 | $endpoints = findAllEndpoints ($object_id, $objectInfo['name']); |
291 | if (count ($endpoints) == 0) | |
3a089a44 | 292 | throw new RTGatewayError ('no management address set'); |
f3d274bf | 293 | if (count ($endpoints) > 1) |
3a089a44 | 294 | throw new RTGatewayError ('cannot pick management address'); |
01af4445 | 295 | gwSendFile (str_replace (' ', '+', $endpoints[0]), $handlername, array ($filetext)); |
c030232f DO |
296 | } |
297 | ||
3f052a67 | 298 | function gwRecvFileFromObject ($object_id, $handlername, &$output) |
9bc28e53 | 299 | { |
3f052a67 DO |
300 | if (!mb_strlen ($handlername)) |
301 | throw new InvalidArgException ('$handlername'); | |
6297d584 | 302 | $objectInfo = spotEntity ('object', $object_id); |
9bc28e53 DO |
303 | $endpoints = findAllEndpoints ($object_id, $objectInfo['name']); |
304 | if (count ($endpoints) == 0) | |
3f052a67 | 305 | throw new RTGatewayError ('no management address set'); |
9bc28e53 | 306 | if (count ($endpoints) > 1) |
3f052a67 DO |
307 | throw new RTGatewayError ('cannot pick management address'); |
308 | gwRecvFile (str_replace (' ', '+', $endpoints[0]), $handlername, $output); | |
9bc28e53 DO |
309 | } |
310 | ||
9dd73255 | 311 | function detectDeviceBreed ($object_id) |
aff10dee | 312 | { |
ec523868 DO |
313 | $breed_by_swcode = array |
314 | ( | |
315 | 251 => 'ios12', | |
316 | 252 => 'ios12', | |
317 | 254 => 'ios12', | |
318 | 963 => 'nxos4', | |
319 | 964 => 'nxos4', | |
3c6595fa | 320 | 1365 => 'nxos4', |
ec523868 DO |
321 | 1352 => 'xos12', |
322 | 1360 => 'vrp53', | |
323 | 1361 => 'vrp55', | |
908c2e7d | 324 | 1369 => 'vrp55', // VRP versions 5.5 and 5.7 seem to be compatible |
ec523868 DO |
325 | 1363 => 'fdry5', |
326 | ); | |
aff10dee | 327 | foreach (getAttrValues ($object_id) as $record) |
ec523868 DO |
328 | if ($record['id'] == 4 and array_key_exists ($record['key'], $breed_by_swcode)) |
329 | return $breed_by_swcode[$record['key']]; | |
9dd73255 DO |
330 | return ''; |
331 | } | |
332 | ||
74d3f7da | 333 | function getRunning8021QConfig ($object_id) |
9dd73255 | 334 | { |
db64180c | 335 | $ret = gwRetrieveDeviceConfig ($object_id, 'get8021q'); |
f740bd2f DO |
336 | // Once there is no default VLAN in the parsed data, it means |
337 | // something else was parsed instead of config text. | |
338 | if (!in_array (VLAN_DFL_ID, $ret['vlanlist'])) | |
3a089a44 | 339 | throw new RTGatewayError ('communication with device failed'); |
f740bd2f | 340 | return $ret; |
dbc00990 DO |
341 | } |
342 | ||
9dd73255 DO |
343 | function setDevice8021QConfig ($object_id, $pseudocode) |
344 | { | |
2d8fd72f | 345 | require_once 'deviceconfig.php'; |
9dd73255 | 346 | if ('' == $breed = detectDeviceBreed ($object_id)) |
3a089a44 | 347 | throw new RTGatewayError ('device breed unknown'); |
50520b40 | 348 | global $gwpushxlator; |
f82a94da | 349 | // FIXME: this is a perfect place to log intended changes |
5017142e | 350 | gwDeployDeviceConfig ($object_id, $breed, unix2dos ($gwpushxlator[$breed] ($pseudocode))); |
9dd73255 DO |
351 | } |
352 | ||
793ba0cc | 353 | function gwRetrieveDeviceConfig ($object_id, $command) |
dbc00990 | 354 | { |
b519e37e | 355 | require_once 'deviceconfig.php'; |
ad541266 DO |
356 | global $gwrxlator; |
357 | if (!array_key_exists ($command, $gwrxlator)) | |
3a089a44 | 358 | throw new RTGatewayError ('command unknown'); |
ad541266 DO |
359 | $breed = detectDeviceBreed ($object_id); |
360 | if (!array_key_exists ($breed, $gwrxlator[$command])) | |
3a089a44 | 361 | throw new RTGatewayError ('device breed unknown'); |
dbc00990 DO |
362 | $objectInfo = spotEntity ('object', $object_id); |
363 | $endpoints = findAllEndpoints ($object_id, $objectInfo['name']); | |
364 | if (count ($endpoints) == 0) | |
3a089a44 | 365 | throw new RTGatewayError ('no management address set'); |
dbc00990 | 366 | if (count ($endpoints) > 1) |
3a089a44 | 367 | throw new RTGatewayError ('cannot pick management address'); |
dbc00990 DO |
368 | $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0])); |
369 | $tmpfilename = tempnam ('', 'RackTables-deviceconfig-'); | |
e11d4936 DO |
370 | try |
371 | { | |
372 | queryGateway ('deviceconfig', array ("${command} ${endpoint} ${breed} ${tmpfilename}")); | |
373 | $configtext = file_get_contents ($tmpfilename); | |
374 | unlink ($tmpfilename); | |
375 | } | |
376 | catch (RTGatewayError $e) | |
377 | { | |
378 | unlink ($tmpfilename); | |
379 | throw $e; | |
380 | } | |
ec523868 DO |
381 | if ($configtext === FALSE) |
382 | throw new RTGatewayError ('failed to read temporary file'); | |
0f86f02c | 383 | // Being here means it was alright. |
ec523868 | 384 | return $gwrxlator[$command][$breed] (dos2unix ($configtext)); |
aff10dee DO |
385 | } |
386 | ||
9dd73255 DO |
387 | function gwDeployDeviceConfig ($object_id, $breed, $text) |
388 | { | |
f82a94da DO |
389 | if ($text == '') |
390 | throw new InvalidArgException ('text', '', 'deploy text is empty'); | |
9dd73255 DO |
391 | $objectInfo = spotEntity ('object', $object_id); |
392 | $endpoints = findAllEndpoints ($object_id, $objectInfo['name']); | |
393 | if (count ($endpoints) == 0) | |
3a089a44 | 394 | throw new RTGatewayError ('no management address set'); |
9dd73255 | 395 | if (count ($endpoints) > 1) |
3a089a44 | 396 | throw new RTGatewayError ('cannot pick management address'); |
9dd73255 DO |
397 | $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0])); |
398 | $tmpfilename = tempnam ('', 'RackTables-deviceconfig-'); | |
399 | if (FALSE === file_put_contents ($tmpfilename, $text)) | |
01af4445 DO |
400 | { |
401 | unlink ($tmpfilename); | |
3a089a44 | 402 | throw new RTGatewayError ('failed to write to temporary file'); |
01af4445 | 403 | } |
e11d4936 DO |
404 | try |
405 | { | |
406 | queryGateway ('deviceconfig', array ("deploy ${endpoint} ${breed} ${tmpfilename}")); | |
407 | unlink ($tmpfilename); | |
408 | } | |
409 | catch (RTGatewayError $e) | |
410 | { | |
411 | unlink ($tmpfilename); | |
412 | throw $e; | |
413 | } | |
9dd73255 DO |
414 | } |
415 | ||
d33645ff | 416 | ?> |