1884540624f56a4c5966f8602e1ba5d13513225a
[racktables] / inc / gateways.php
1 <?php
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
16
17 // This function launches specified gateway with specified
18 // command-line arguments and feeds it with the commands stored
19 // in the second arg as array.
20 // The answers are stored in another array, which is returned
21 // by this function. In the case when a gateway cannot be found,
22 // finishes prematurely or exits with non-zero return code,
23 // a single-item array is returned with the only "ERR" record,
24 // which explains the reason.
25 function queryGateway ($gwname, $questions)
26 {
27 $execpath = "./gateways/{$gwname}/main";
28 $dspec = array
29 (
30 0 => array ("pipe", "r"),
31 1 => array ("pipe", "w"),
32 2 => array ("file", "/dev/null", "a")
33 );
34 $pipes = array();
35 $gateway = proc_open ($execpath, $dspec, $pipes);
36 if (!is_resource ($gateway))
37 return array ('ERR proc_open() failed in ' . __FUNCTION__);
38
39 // Dialogue starts. Send all questions.
40 foreach ($questions as $q)
41 fwrite ($pipes[0], "$q\n");
42 fclose ($pipes[0]);
43
44 // Fetch replies.
45 $answers = array ();
46 while (!feof($pipes[1]))
47 {
48 $a = fgets ($pipes[1]);
49 if (!strlen ($a))
50 continue;
51 // Somehow I got a space appended at the end. Kick it.
52 $answers[] = trim ($a);
53 }
54 fclose($pipes[1]);
55
56 $retval = proc_close ($gateway);
57 if ($retval != 0)
58 return array ("ERR gateway '${gwname}' returned ${retval}");
59 return $answers;
60 }
61
62 // This functions returns an array for VLAN list, and an array for port list (both
63 // form another array themselves) and another one with MAC address list.
64 // The ports in the latter array are marked with either VLAN ID or 'trunk'.
65 // We don't sort the port list, as the gateway is believed to have done this already
66 // (or at least the underlying switch software ought to). This is important, as the
67 // port info is transferred to/from form not by names, but by numbers.
68 function getSwitchVLANs ($object_id = 0)
69 {
70 global $remote_username;
71 if ($object_id <= 0)
72 throw new InvalidArgException('$object_id', $object_id);
73 $objectInfo = spotEntity ('object', $object_id);
74 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
75 if (count ($endpoints) == 0)
76 throw new RuntimeException('Can\'t find any mean to reach current object. Please either set FQDN attribute or assign an IP address to the object.');
77 if (count ($endpoints) > 1)
78 throw new RuntimeException('More than one IP address is assigned to this object, please configure FQDN attribute.');
79 $hwtype = $swtype = 'unknown';
80 foreach (getAttrValues ($object_id) as $record)
81 {
82 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
83 $swtype = str_replace (' ', '+', execGMarker ($record['o_value']));
84 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
85 $hwtype = str_replace (' ', '+', execGMarker ($record['o_value']));
86 }
87 $endpoint = str_replace (' ', '+', $endpoints[0]);
88 $commands = array
89 (
90 "connect ${endpoint} ${hwtype} ${swtype} ${remote_username}",
91 'listvlans',
92 'listports',
93 'listmacs'
94 );
95 $data = queryGateway ('switchvlans', $commands);
96 if ($data == NULL)
97 throw new RuntimeException('Failed to get any response from queryGateway() or the gateway died');
98 if (strpos ($data[0], 'OK!') !== 0)
99 throw new RuntimeException("Gateway failure: ${data[0]}.");
100 if (count ($data) != count ($commands))
101 throw new RuntimeException("Gateway failure: malformed reply.");
102 // Now we have VLAN list in $data[1] and port list in $data[2]. Let's sort this out.
103 $tmp = array_unique (explode (';', substr ($data[1], strlen ('OK!'))));
104 if (count ($tmp) == 0)
105 throw new RuntimeException("Gateway succeeded, but returned no VLAN records.");
106 $vlanlist = array();
107 foreach ($tmp as $record)
108 {
109 list ($vlanid, $vlandescr) = explode ('=', $record);
110 $vlanlist[$vlanid] = $vlandescr;
111 }
112 $portlist = array();
113 foreach (explode (';', substr ($data[2], strlen ('OK!'))) as $pair)
114 {
115 list ($portname, $pair2) = explode ('=', $pair);
116 list ($status, $vlanid) = explode (',', $pair2);
117 $portlist[] = array ('portname' => $portname, 'status' => $status, 'vlanid' => $vlanid);
118 }
119 if (count ($portlist) == 0)
120 throw new RuntimeException("Gateway succeeded, but returned no port records.");
121 $maclist = array();
122 foreach (explode (';', substr ($data[3], strlen ('OK!'))) as $pair)
123 {
124 list ($macaddr, $pair2) = explode ('=', $pair);
125 if (!strlen ($pair2))
126 continue;
127 list ($vlanid, $ifname) = explode ('@', $pair2);
128 $maclist[$ifname][$vlanid][] = $macaddr;
129 }
130 return array ($vlanlist, $portlist, $maclist);
131 }
132
133 function setSwitchVLANs ($object_id = 0, $setcmd)
134 {
135 global $remote_username;
136 if ($object_id <= 0)
137 return oneLiner (160); // invalid arguments
138 $objectInfo = spotEntity ('object', $object_id);
139 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
140 if (count ($endpoints) == 0)
141 return oneLiner (161); // endpoint not found
142 if (count ($endpoints) > 1)
143 return oneLiner (162); // can't pick an address
144 $hwtype = $swtype = 'unknown';
145 foreach (getAttrValues ($object_id) as $record)
146 {
147 if ($record['name'] == 'SW type' && strlen ($record['o_value']))
148 $swtype = strtr (execGMarker ($record['o_value']), ' ', '+');
149 if ($record['name'] == 'HW type' && strlen ($record['o_value']))
150 $hwtype = strtr (execGMarker ($record['o_value']), ' ', '+');
151 }
152 $endpoint = str_replace (' ', '+', $endpoints[0]);
153 $data = queryGateway
154 (
155 'switchvlans',
156 array ("connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", $setcmd)
157 );
158 if ($data == NULL)
159 return oneLiner (163); // unknown gateway failure
160 if (strpos ($data[0], 'OK!') !== 0)
161 return oneLiner (164, array ($data[0])); // gateway failure
162 if (count ($data) != 2)
163 return oneLiner (165); // protocol violation
164 // Finally we can parse the response into message array.
165 $log_m = array();
166 foreach (split (';', substr ($data[1], strlen ('OK!'))) as $text)
167 {
168 if (strpos ($text, 'C!') === 0)
169 {
170 $tmp = split ('!', $text);
171 array_shift ($tmp);
172 $code = array_shift ($tmp);
173 $log_m[] = count ($tmp) ? array ('c' => $code, 'a' => $tmp) : array ('c' => $code); // gateway-encoded message
174 }
175 elseif (strpos ($text, 'I!') === 0)
176 $log_m[] = array ('c' => 62, 'a' => array (substr ($text, 2))); // generic gateway success
177 elseif (strpos ($text, 'W!') === 0)
178 $log_m[] = array ('c' => 202, 'a' => array (substr ($text, 2))); // generic gateway warning
179 else // All improperly formatted messages must be treated as error conditions.
180 $log_m[] = array ('c' => 166, 'a' => array (substr ($text, 2))); // generic gateway error
181 }
182 return $log_m;
183 }
184
185 // Drop a file off RackTables platform. The gateway will catch the file and pass it to the given
186 // installer script.
187 function gwSendFile ($endpoint, $handlername, $filetext = array())
188 {
189 global $remote_username;
190 $tmpnames = array();
191 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
192 $command = "submit ${remote_username} ${endpoint} ${handlername}";
193 foreach ($filetext as $text)
194 {
195 $name = tempnam ('', 'RackTables-sendfile-');
196 $tmpnames[] = $name;
197 $tmpfile = fopen ($name, 'wb');
198 $failed = FALSE === fwrite ($tmpfile, $text);
199 $failed = FALSE === fclose ($tmpfile) or $fail;
200 if ($failed)
201 {
202 foreach ($tmpnames as $name)
203 unlink ($name);
204 return oneLiner (164, array ('file write error')); // gateway failure
205 }
206 $command .= " ${name}";
207 }
208 $outputlines = queryGateway
209 (
210 'sendfile',
211 array ($command)
212 );
213 foreach ($tmpnames as $name)
214 unlink ($name);
215 if ($outputlines == NULL)
216 return oneLiner (163); // unknown gateway failure
217 if (count ($outputlines) != 1)
218 return oneLiner (165); // protocol violation
219 if (strpos ($outputlines[0], 'OK!') !== 0)
220 return oneLiner (164, array ($outputlines[0])); // gateway failure
221 // Being here means having 'OK!' in the response.
222 return oneLiner (66, array ($handlername)); // ignore provided "Ok" text
223 }
224
225 // Query something through a gateway and get some text in return. Return that text.
226 function gwRecvFile ($endpoint, $handlername, &$output)
227 {
228 global $remote_username;
229 $tmpfilename = tempnam ('', 'RackTables-sendfile-');
230 $endpoint = str_replace (' ', '\ ', $endpoint); // the gateway dispatcher uses read (1) to assign arguments
231 $outputlines = queryGateway
232 (
233 'sendfile',
234 array ("submit ${remote_username} ${endpoint} ${handlername} ${tmpfilename}")
235 );
236 $output = file_get_contents ($tmpfilename);
237 unlink ($tmpfilename);
238 if ($outputlines == NULL)
239 return oneLiner (163); // unknown gateway failure
240 if (count ($outputlines) != 1)
241 return oneLiner (165); // protocol violation
242 if (strpos ($outputlines[0], 'OK!') !== 0)
243 return oneLiner (164, array ($outputlines[0])); // gateway failure
244 // Being here means having 'OK!' in the response.
245 return oneLiner (66, array ($handlername)); // ignore provided "Ok" text
246 }
247
248 function gwSendFileToObject ($object_id = 0, $handlername, $filetext = '')
249 {
250 global $remote_username;
251 if ($object_id <= 0 or !strlen ($handlername))
252 return oneLiner (160); // invalid arguments
253 $objectInfo = spotEntity ('object', $object_id);
254 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
255 if (count ($endpoints) == 0)
256 return oneLiner (161); // endpoint not found
257 if (count ($endpoints) > 1)
258 return oneLiner (162); // can't pick an address
259 $endpoint = str_replace (' ', '+', $endpoints[0]);
260 return gwSendFile ($endpoint, $handlername, array ($filetext));
261 }
262
263 function gwRecvFileFromObject ($object_id = 0, $handlername, &$output)
264 {
265 global $remote_username;
266 if ($object_id <= 0 or !strlen ($handlername))
267 return oneLiner (160); // invalid arguments
268 $objectInfo = spotEntity ('object', $object_id);
269 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
270 if (count ($endpoints) == 0)
271 return oneLiner (161); // endpoint not found
272 if (count ($endpoints) > 1)
273 return oneLiner (162); // can't pick an address
274 $endpoint = str_replace (' ', '+', $endpoints[0]);
275 return gwRecvFile ($endpoint, $handlername, $output);
276 }
277
278 function detectDeviceBreed ($object_id)
279 {
280 foreach (getAttrValues ($object_id) as $record)
281 {
282 if
283 (
284 $record['name'] == 'SW type' &&
285 strlen ($record['o_value']) &&
286 preg_match ('/^Cisco IOS 12\./', execGMarker ($record['o_value']))
287 )
288 return 'ios12';
289 if
290 (
291 $record['name'] == 'SW type' &&
292 strlen ($record['o_value']) &&
293 preg_match ('/^Cisco NX-OS 4\./', execGMarker ($record['o_value']))
294 )
295 return 'nxos4';
296 if
297 (
298 $record['name'] == 'HW type' &&
299 strlen ($record['o_value']) &&
300 preg_match ('/^Foundry FastIron GS /', execGMarker ($record['o_value']))
301 )
302 return 'fdry5';
303 if
304 (
305 $record['name'] == 'HW type' &&
306 strlen ($record['o_value']) &&
307 preg_match ('/^Huawei Quidway S53/', execGMarker ($record['o_value']))
308 )
309 return 'vrp53';
310 }
311 return '';
312 }
313
314 function getRunning8021QConfig ($object_id)
315 {
316 if ('' == $breed = detectDeviceBreed ($object_id))
317 throw new RuntimeException ('cannot pick handler for this device');
318 $reader = array
319 (
320 'ios12' => 'ios12ReadVLANConfig',
321 'fdry5' => 'fdry5ReadVLANConfig',
322 'vrp53' => 'vrp53ReadVLANConfig',
323 'nxos4' => 'nxos4Read8021QConfig',
324 );
325 $ret = $reader[$breed] (dos2unix (gwRetrieveDeviceConfig ($object_id, $breed)));
326 // Once there is no default VLAN in the parsed data, it means
327 // something else was parsed instead of config text.
328 if (!in_array (VLAN_DFL_ID, $ret['vlanlist']))
329 throw new RuntimeException ('communication with device failed');
330 return $ret;
331 }
332
333 function setDevice8021QConfig ($object_id, $pseudocode)
334 {
335 if ('' == $breed = detectDeviceBreed ($object_id))
336 throw new RuntimeException ('cannot pick handler for this device');
337 $xlator = array
338 (
339 'ios12' => 'ios12TranslatePushQueue',
340 'fdry5' => 'fdry5TranslatePushQueue',
341 'vrp53' => 'vrp53TranslatePushQueue',
342 'nxos4' => 'ios12TranslatePushQueue', // employ syntax compatibility
343 );
344 gwDeployDeviceConfig ($object_id, $breed, unix2dos ($xlator[$breed] ($pseudocode)));
345 }
346
347 function gwRetrieveDeviceConfig ($object_id, $breed)
348 {
349 $objectInfo = spotEntity ('object', $object_id);
350 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
351 if (count ($endpoints) == 0)
352 throw new RuntimeException ('endpoint not found');
353 if (count ($endpoints) > 1)
354 throw new RuntimeException ('cannot pick endpoint');
355 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
356 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
357 $outputlines = queryGateway
358 (
359 'deviceconfig',
360 array ("retrieve ${endpoint} ${breed} ${tmpfilename}")
361 );
362 $configtext = file_get_contents ($tmpfilename);
363 unlink ($tmpfilename);
364 if ($outputlines == NULL)
365 throw new RuntimeException ('unknown gateway failure');
366 if (count ($outputlines) != 1)
367 throw new RuntimeException ('gateway protocol violation');
368 if (strpos ($outputlines[0], 'OK!') !== 0)
369 throw new RuntimeException ("gateway failure: ${outputlines[0]}");
370 // Being here means it was alright.
371 return $configtext;
372 }
373
374 function gwDeployDeviceConfig ($object_id, $breed, $text)
375 {
376 $objectInfo = spotEntity ('object', $object_id);
377 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
378 if (count ($endpoints) == 0)
379 throw new RuntimeException ('endpoint not found');
380 if (count ($endpoints) > 1)
381 throw new RuntimeException ('cannot pick endpoint');
382 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
383 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
384 if (FALSE === file_put_contents ($tmpfilename, $text))
385 throw new RuntimeException ('failed to write to temporary file');
386 $outputlines = queryGateway
387 (
388 'deviceconfig',
389 array ("deploy ${endpoint} ${breed} ${tmpfilename}")
390 );
391 unlink ($tmpfilename);
392 if ($outputlines == NULL)
393 throw new RuntimeException ('unknown gateway failure');
394 if (count ($outputlines) != 1)
395 throw new RuntimeException ('gateway protocol violation');
396 if (strpos ($outputlines[0], 'OK!') !== 0)
397 throw new RuntimeException ("gateway failure: ${outputlines[0]}");
398 }
399
400 ?>