r4985 NX-OS 6.0 gateways support
[racktables] / wwwroot / inc / gateways.php
CommitLineData
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 16// translating functions maps
b2148be4 17$breedfunc = array
ffd829af 18(
fc787218 19 'ios12-getcdpstatus-main' => 'ios12ReadCDPStatus',
b2148be4 20 'ios12-getlldpstatus-main' => 'ios12ReadLLDPStatus',
fc787218
DO
21 'ios12-get8021q-main' => 'ios12ReadVLANConfig',
22 'ios12-get8021q-top' => 'ios12ScanTopLevel',
23 'ios12-get8021q-readport' => 'ios12PickSwitchportCommand',
24 'ios12-get8021q-readvlan' => 'ios12PickVLANCommand',
b2148be4 25 'ios12-getportstatus-main' => 'ciscoReadInterfaceStatus',
fc787218
DO
26 'ios12-getmaclist-main' => 'ios12ReadMacList',
27 'ios12-xlatepushq-main' => 'ios12TranslatePushQueue',
eb8a4c05 28 'ios12-getallconf-main' => 'ios12SpotConfigText',
fc787218
DO
29 'fdry5-get8021q-main' => 'fdry5ReadVLANConfig',
30 'fdry5-get8021q-top' => 'fdry5ScanTopLevel',
31 'fdry5-get8021q-readvlan' => 'fdry5PickVLANSubcommand',
32 'fdry5-get8021q-readport' => 'fdry5PickInterfaceSubcommand',
33 'fdry5-xlatepushq-main' => 'fdry5TranslatePushQueue',
eb8a4c05 34 'fdry5-getallconf-main' => 'fdry5SpotConfigText',
b2148be4 35 'vrp53-getlldpstatus-main' => 'vrp5xReadLLDPStatus',
fc787218
DO
36 'vrp53-get8021q-main' => 'vrp53ReadVLANConfig',
37 'vrp53-get8021q-top' => 'vrp53ScanTopLevel',
38 'vrp53-get8021q-readport' => 'vrp53PickInterfaceSubcommand',
b2148be4 39 'vrp53-getportstatus-main' => 'vrpReadInterfaceStatus',
fc787218 40 'vrp53-getmaclist-main' => 'vrp53ReadMacList',
fc787218 41 'vrp53-xlatepushq-main' => 'vrp53TranslatePushQueue',
eb8a4c05 42 'vrp53-getallconf-main' => 'vrp5xSpotConfigText',
b2148be4 43 'vrp55-getlldpstatus-main' => 'vrp5xReadLLDPStatus',
fc787218 44 'vrp55-get8021q-main' => 'vrp55Read8021QConfig',
b2148be4 45 'vrp55-getportstatus-main' => 'vrpReadInterfaceStatus',
fc787218
DO
46 'vrp55-getmaclist-main' => 'vrp55ReadMacList',
47 'vrp55-xlatepushq-main' => 'vrp55TranslatePushQueue',
eb8a4c05 48 'vrp55-getallconf-main' => 'vrp5xSpotConfigText',
fc787218 49 'nxos4-getcdpstatus-main' => 'ios12ReadCDPStatus',
c56449ed 50 'nxos4-getlldpstatus-main' => 'nxos4ReadLLDPStatus',
fc787218
DO
51 'nxos4-get8021q-main' => 'nxos4Read8021QConfig',
52 'nxos4-get8021q-top' => 'nxos4ScanTopLevel',
53 'nxos4-get8021q-readport' => 'nxos4PickSwitchportCommand',
b2148be4 54 'nxos4-getportstatus-main' => 'ciscoReadInterfaceStatus',
fc787218 55 'nxos4-getmaclist-main' => 'nxos4ReadMacList',
c56449ed 56 'nxos4-xlatepushq-main' => 'nxos4TranslatePushQueue',
eb8a4c05 57 'nxos4-getallconf-main' => 'nxos4SpotConfigText',
b2148be4 58 'xos12-getlldpstatus-main' => 'xos12ReadLLDPStatus',
fc787218
DO
59 'xos12-get8021q-main' => 'xos12Read8021QConfig',
60 'xos12-xlatepushq-main' => 'xos12TranslatePushQueue',
eb8a4c05 61 'xos12-getallconf-main' => 'xos12SpotConfigText',
fc787218
DO
62 'jun10-get8021q-main' => 'jun10Read8021QConfig',
63 'jun10-xlatepushq-main' => 'jun10TranslatePushQueue',
eb8a4c05 64 'jun10-getallconf-main' => 'jun10SpotConfigText',
4cd9fe27
DO
65 'ftos8-xlatepushq-main' => 'ftos8TranslatePushQueue',
66 'ftos8-getlldpstatus-main' => 'ftos8ReadLLDPStatus',
dcc34f52
AA
67 'ftos8-getmaclist-main' => 'ftos8ReadMacList',
68 'ftos8-getportstatus-main' => 'ftos8ReadInterfaceStatus',
69 'ftos8-get8021q-main' => 'ftos8Read8021QConfig',
eb8a4c05 70 'ftos8-getallconf-main' => 'ftos8SpotConfigText',
50520b40
DO
71);
72
d33645ff
DO
73// This function launches specified gateway with specified
74// command-line arguments and feeds it with the commands stored
75// in the second arg as array.
76// The answers are stored in another array, which is returned
77// by this function. In the case when a gateway cannot be found,
78// finishes prematurely or exits with non-zero return code,
79// a single-item array is returned with the only "ERR" record,
80// which explains the reason.
2da7c9b0 81function queryGateway ($gwname, $questions)
d33645ff 82{
a86888d8 83 global $racktables_gwdir;
62cb8727 84 $execpath = "${racktables_gwdir}/{$gwname}/main";
d33645ff
DO
85 $dspec = array
86 (
87 0 => array ("pipe", "r"),
88 1 => array ("pipe", "w"),
89 2 => array ("file", "/dev/null", "a")
90 );
91 $pipes = array();
2da7c9b0 92 $gateway = proc_open ($execpath, $dspec, $pipes);
d33645ff 93 if (!is_resource ($gateway))
4d2e93f2 94 return array ('ERR proc_open() failed in ' . __FUNCTION__);
d33645ff
DO
95
96// Dialogue starts. Send all questions.
97 foreach ($questions as $q)
98 fwrite ($pipes[0], "$q\n");
99 fclose ($pipes[0]);
100
101// Fetch replies.
2da7c9b0 102 $answers = array ();
d33645ff
DO
103 while (!feof($pipes[1]))
104 {
105 $a = fgets ($pipes[1]);
59a83bd8 106 if (!strlen ($a))
d33645ff 107 continue;
075aeb88
DO
108 // Somehow I got a space appended at the end. Kick it.
109 $answers[] = trim ($a);
d33645ff
DO
110 }
111 fclose($pipes[1]);
112
113 $retval = proc_close ($gateway);
114 if ($retval != 0)
3a089a44 115 throw new RTGatewayError ("gateway failed with code ${retval}");
7a60526f 116 if (!count ($answers))
3a089a44 117 throw new RTGatewayError ('no response from gateway');
7a60526f 118 if (count ($answers) != count ($questions))
3a089a44 119 throw new RTGatewayError ('protocol violation');
7a60526f 120 foreach ($answers as $a)
793ba0cc 121 if (strpos ($a, 'OK!') !== 0)
3a089a44 122 throw new RTGatewayError ("subcommand failed with status: ${a}");
d33645ff
DO
123 return $answers;
124}
125
0a1b1268 126// This functions returns an array for VLAN list, and an array for port list (both
18cb9495 127// form another array themselves) and another one with MAC address list.
0a1b1268
DO
128// The ports in the latter array are marked with either VLAN ID or 'trunk'.
129// We don't sort the port list, as the gateway is believed to have done this already
130// (or at least the underlying switch software ought to). This is important, as the
131// port info is transferred to/from form not by names, but by numbers.
132function getSwitchVLANs ($object_id = 0)
133{
134 global $remote_username;
6297d584 135 $objectInfo = spotEntity ('object', $object_id);
0a1b1268
DO
136 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
137 if (count ($endpoints) == 0)
3a089a44 138 throw new RTGatewayError ('no management address set');
0a1b1268 139 if (count ($endpoints) > 1)
3a089a44 140 throw new RTGatewayError ('cannot pick management address');
0a1b1268 141 $hwtype = $swtype = 'unknown';
7fa7047a 142 foreach (getAttrValues ($object_id) as $record)
0a1b1268 143 {
e609f5c8 144 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
c9edf725 145 $swtype = str_replace (' ', '+', execGMarker ($record['o_value']));
e609f5c8 146 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
c9edf725 147 $hwtype = str_replace (' ', '+', execGMarker ($record['o_value']));
0a1b1268 148 }
ee2174dd 149 $endpoint = str_replace (' ', '+', $endpoints[0]);
18cb9495 150 $commands = array
0a1b1268 151 (
ee2174dd 152 "connect ${endpoint} ${hwtype} ${swtype} ${remote_username}",
18cb9495
DO
153 'listvlans',
154 'listports',
155 'listmacs'
0a1b1268 156 );
18cb9495 157 $data = queryGateway ('switchvlans', $commands);
0a1b1268 158 if (strpos ($data[0], 'OK!') !== 0)
3a089a44 159 throw new RTGatewayError ("gateway failed with status: ${data[0]}.");
0a1b1268
DO
160 // Now we have VLAN list in $data[1] and port list in $data[2]. Let's sort this out.
161 $tmp = array_unique (explode (';', substr ($data[1], strlen ('OK!'))));
162 if (count ($tmp) == 0)
3a089a44 163 throw new RTGatewayError ('gateway returned no records');
0a1b1268
DO
164 $vlanlist = array();
165 foreach ($tmp as $record)
166 {
167 list ($vlanid, $vlandescr) = explode ('=', $record);
168 $vlanlist[$vlanid] = $vlandescr;
169 }
170 $portlist = array();
0a73c02f 171 foreach (explode (';', substr ($data[2], strlen ('OK!'))) as $pair)
0a1b1268 172 {
0a73c02f
DO
173 list ($portname, $pair2) = explode ('=', $pair);
174 list ($status, $vlanid) = explode (',', $pair2);
175 $portlist[] = array ('portname' => $portname, 'status' => $status, 'vlanid' => $vlanid);
0a1b1268
DO
176 }
177 if (count ($portlist) == 0)
3a089a44 178 throw new RTGatewayError ('gateway returned no records');
18cb9495
DO
179 $maclist = array();
180 foreach (explode (';', substr ($data[3], strlen ('OK!'))) as $pair)
96265135
AA
181 if (preg_match ('/^([^=]+)=(.+)/', $pair, $m))
182 {
183 $macaddr = $m[1];
184 list ($vlanid, $ifname) = explode ('@', $m[2]);
185 $maclist[$ifname][$vlanid][] = $macaddr;
186 }
18cb9495 187 return array ($vlanlist, $portlist, $maclist);
0a1b1268
DO
188}
189
190function setSwitchVLANs ($object_id = 0, $setcmd)
191{
192 global $remote_username;
6297d584 193 $objectInfo = spotEntity ('object', $object_id);
0a1b1268
DO
194 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
195 if (count ($endpoints) == 0)
3f052a67 196 throw new RTGatewayError ('no management address set');
0a1b1268 197 if (count ($endpoints) > 1)
3f052a67 198 throw new RTGatewayError ('cannot pick management address');
0a1b1268 199 $hwtype = $swtype = 'unknown';
7fa7047a 200 foreach (getAttrValues ($object_id) as $record)
0a1b1268 201 {
e609f5c8 202 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
c9edf725 203 $swtype = strtr (execGMarker ($record['o_value']), ' ', '+');
e609f5c8 204 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
c9edf725 205 $hwtype = strtr (execGMarker ($record['o_value']), ' ', '+');
0a1b1268 206 }
15bb23dc 207 $endpoint = str_replace (' ', '+', $endpoints[0]);
0a1b1268
DO
208 $data = queryGateway
209 (
210 'switchvlans',
15bb23dc 211 array ("connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", $setcmd)
0a1b1268 212 );
0a1b1268 213 // Finally we can parse the response into message array.
b591ac21 214 foreach (explode (';', substr ($data[1], strlen ('OK!'))) as $text)
0a1b1268 215 {
9bf2cae5
AA
216 $message = 'gw: ' . substr ($text, 2);
217 if (strpos ($text, 'I!') === 0)
218 showSuccess ($message); // generic gateway success
0a1b1268 219 elseif (strpos ($text, 'W!') === 0)
9bf2cae5
AA
220 showWarning ($message); // generic gateway warning
221 elseif (strpos ($text, 'E!') === 0)
222 showError ($message); // generic gateway error
0a1b1268 223 else // All improperly formatted messages must be treated as error conditions.
9bf2cae5 224 showError ('unexpected line from gw: ' . $text);
0a1b1268 225 }
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.
c6a761d5
AA
230// On success returns the text string printed by sendfile handler
231// On failure throws an exception
1ee5488d 232function gwSendFile ($endpoint, $handlername, $filetext = array())
c030232f 233{
c6a761d5 234 $result = '';
b063cc8e
DO
235 if (! is_array ($filetext))
236 throw new InvalidArgException ('filetext', '(suppressed)', 'is not an array');
c030232f 237 global $remote_username;
1ee5488d 238 $tmpnames = array();
f3d274bf 239 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
1ee5488d
DO
240 $command = "submit ${remote_username} ${endpoint} ${handlername}";
241 foreach ($filetext as $text)
242 {
243 $name = tempnam ('', 'RackTables-sendfile-');
244 $tmpnames[] = $name;
01af4445 245 if (FALSE === $name or FALSE === file_put_contents ($name, $text))
1d34465d
DO
246 {
247 foreach ($tmpnames as $name)
248 unlink ($name);
3a089a44 249 throw new RTGatewayError ('failed to write to temporary file');
1d34465d 250 }
1ee5488d
DO
251 $command .= " ${name}";
252 }
e11d4936
DO
253 try
254 {
c6a761d5
AA
255 $answers = queryGateway ('sendfile', array ($command));
256 $result = preg_replace ('/^OK!\s*/', '', array_shift ($answers));
e11d4936
DO
257 foreach ($tmpnames as $name)
258 unlink ($name);
259 }
260 catch (RTGatewayError $e)
261 {
262 foreach ($tmpnames as $name)
263 unlink ($name);
c6a761d5 264 throw new RTGatewayError ("Sending $handlername to $endpoint: " . $e->getMessage());
e11d4936 265 }
c6a761d5 266 return $result;
f3d274bf 267}
1b494df6 268
9bc28e53
DO
269// Query something through a gateway and get some text in return. Return that text.
270function gwRecvFile ($endpoint, $handlername, &$output)
271{
272 global $remote_username;
273 $tmpfilename = tempnam ('', 'RackTables-sendfile-');
274 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
e11d4936
DO
275 try
276 {
277 queryGateway ('sendfile', array ("submit ${remote_username} ${endpoint} ${handlername} ${tmpfilename}"));
278 $output = file_get_contents ($tmpfilename);
279 unlink ($tmpfilename);
280 }
281 catch (RTGatewayError $e)
282 {
283 unlink ($tmpfilename);
284 throw $e;
285 }
ec523868
DO
286 if ($output === FALSE)
287 throw new RTGatewayError ('failed to read temporary file');
9bc28e53
DO
288}
289
3f052a67 290function gwSendFileToObject ($object_id, $handlername, $filetext = '')
f3d274bf 291{
3f052a67
DO
292 if (!mb_strlen ($handlername))
293 throw new InvalidArgException ('$handlername');
6297d584 294 $objectInfo = spotEntity ('object', $object_id);
f3d274bf
DO
295 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
296 if (count ($endpoints) == 0)
3a089a44 297 throw new RTGatewayError ('no management address set');
f3d274bf 298 if (count ($endpoints) > 1)
3a089a44 299 throw new RTGatewayError ('cannot pick management address');
c6a761d5 300 return gwSendFile (str_replace (' ', '+', $endpoints[0]), $handlername, array ($filetext));
c030232f
DO
301}
302
3f052a67 303function gwRecvFileFromObject ($object_id, $handlername, &$output)
9bc28e53 304{
3f052a67
DO
305 if (!mb_strlen ($handlername))
306 throw new InvalidArgException ('$handlername');
6297d584 307 $objectInfo = spotEntity ('object', $object_id);
9bc28e53
DO
308 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
309 if (count ($endpoints) == 0)
3f052a67 310 throw new RTGatewayError ('no management address set');
9bc28e53 311 if (count ($endpoints) > 1)
3f052a67
DO
312 throw new RTGatewayError ('cannot pick management address');
313 gwRecvFile (str_replace (' ', '+', $endpoints[0]), $handlername, $output);
9bc28e53
DO
314}
315
9dd73255 316function detectDeviceBreed ($object_id)
aff10dee 317{
ec523868
DO
318 $breed_by_swcode = array
319 (
320 251 => 'ios12',
321 252 => 'ios12',
322 254 => 'ios12',
5e53489d
AA
323 963 => 'nxos4', // NX-OS 4.0
324 964 => 'nxos4', // NX-OS 4.1
325 1365 => 'nxos4', // NX-OS 4.2
326 1410 => 'nxos4', // NX-OS 5.0, seems compatible
327 1411 => 'nxos4', // NX-OS 5.1
b4e5f241 328 1643 => 'nxos4', // NX-OS 6.0
ec523868
DO
329 1352 => 'xos12',
330 1360 => 'vrp53',
331 1361 => 'vrp55',
908c2e7d 332 1369 => 'vrp55', // VRP versions 5.5 and 5.7 seem to be compatible
ec523868 333 1363 => 'fdry5',
74a868cc
DO
334 1367 => 'jun10', # 10S
335 1597 => 'jun10', # 10R
336 1598 => 'jun10', # 11R
4cd9fe27 337 1594 => 'ftos8',
ec523868 338 );
aff10dee 339 foreach (getAttrValues ($object_id) as $record)
ec523868
DO
340 if ($record['id'] == 4 and array_key_exists ($record['key'], $breed_by_swcode))
341 return $breed_by_swcode[$record['key']];
9dd73255
DO
342 return '';
343}
344
a948ad0b
DO
345function validBreedFunction ($breed, $command)
346{
b2148be4
DO
347 global $breedfunc;
348 return array_key_exists ("${breed}-${command}-main", $breedfunc);
a948ad0b
DO
349}
350
87b33149 351function assertBreedFunction ($breed, $command)
a2f665fb 352{
b2148be4
DO
353 global $breedfunc;
354 if (! array_key_exists ("${breed}-${command}-main", $breedfunc))
355 throw new RTGatewayError ('unsupported command for this breed');
a2f665fb
DO
356}
357
793ba0cc 358function gwRetrieveDeviceConfig ($object_id, $command)
dbc00990 359{
b519e37e 360 require_once 'deviceconfig.php';
b2148be4 361 global $breedfunc;
ad541266 362 $breed = detectDeviceBreed ($object_id);
b2148be4 363 assertBreedFunction ($breed, $command);
dbc00990
DO
364 $objectInfo = spotEntity ('object', $object_id);
365 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
366 if (count ($endpoints) == 0)
3a089a44 367 throw new RTGatewayError ('no management address set');
dbc00990 368 if (count ($endpoints) > 1)
3a089a44 369 throw new RTGatewayError ('cannot pick management address');
dbc00990
DO
370 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
371 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
e11d4936
DO
372 try
373 {
374 queryGateway ('deviceconfig', array ("${command} ${endpoint} ${breed} ${tmpfilename}"));
375 $configtext = file_get_contents ($tmpfilename);
376 unlink ($tmpfilename);
377 }
378 catch (RTGatewayError $e)
379 {
380 unlink ($tmpfilename);
381 throw $e;
382 }
ec523868
DO
383 if ($configtext === FALSE)
384 throw new RTGatewayError ('failed to read temporary file');
0f86f02c 385 // Being here means it was alright.
b2148be4 386 return $breedfunc["${breed}-${command}-main"] (dos2unix ($configtext));
aff10dee
DO
387}
388
9dd73255
DO
389function gwDeployDeviceConfig ($object_id, $breed, $text)
390{
f82a94da
DO
391 if ($text == '')
392 throw new InvalidArgException ('text', '', 'deploy text is empty');
9dd73255
DO
393 $objectInfo = spotEntity ('object', $object_id);
394 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
395 if (count ($endpoints) == 0)
3a089a44 396 throw new RTGatewayError ('no management address set');
9dd73255 397 if (count ($endpoints) > 1)
3a089a44 398 throw new RTGatewayError ('cannot pick management address');
9dd73255
DO
399 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
400 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
401 if (FALSE === file_put_contents ($tmpfilename, $text))
01af4445
DO
402 {
403 unlink ($tmpfilename);
3a089a44 404 throw new RTGatewayError ('failed to write to temporary file');
01af4445 405 }
e11d4936
DO
406 try
407 {
408 queryGateway ('deviceconfig', array ("deploy ${endpoint} ${breed} ${tmpfilename}"));
409 unlink ($tmpfilename);
410 }
411 catch (RTGatewayError $e)
412 {
413 unlink ($tmpfilename);
414 throw $e;
415 }
9dd73255
DO
416}
417
d33645ff 418?>