r3326 getDevice8021QConfig(): new function to add some vendor neutrality
[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 getDevice8021QConfig ($object_id)
279 {
280 $breed = '';
281 foreach (getAttrValues ($object_id) as $record)
282 {
283 if
284 (
285 $record['name'] == 'SW type' &&
286 strlen ($record['o_value']) &&
287 preg_match ('/^Cisco IOS 12\./', execGMarker ($record['o_value']))
288 )
289 {
290 $breed = 'ios12';
291 break;
292 }
293 if
294 (
295 $record['name'] == 'HW type' &&
296 strlen ($record['o_value']) &&
297 preg_match ('/^Foundry FastIron GS /', execGMarker ($record['o_value']))
298 )
299 {
300 $breed = 'fdry5';
301 break;
302 }
303 }
304 if ($breed == '')
305 throw new RuntimeException ('cannot pick handler for this device');
306 $reader = array
307 (
308 'ios12' => 'iosReadVLANConfig',
309 'fdry5' => 'fdry5ReadVLANConfig',
310 );
311 return $reader[$breed] (dos2unix (gwRetrieveDeviceConfig ($object_id, $breed)));
312 }
313
314 function gwRetrieveDeviceConfig ($object_id, $breed)
315 {
316 $objectInfo = spotEntity ('object', $object_id);
317 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
318 if (count ($endpoints) == 0)
319 throw new RuntimeException ('endpoint not found');
320 if (count ($endpoints) > 1)
321 throw new RuntimeException ('cannot pick endpoint');
322 $endpoint = str_replace (' ', '\ ', str_replace (' ', '+', $endpoints[0]));
323 $tmpfilename = tempnam ('', 'RackTables-deviceconfig-');
324 $outputlines = queryGateway
325 (
326 'deviceconfig',
327 array ("retrieve ${endpoint} ${breed} ${tmpfilename}")
328 );
329 $configtext = file_get_contents ($tmpfilename);
330 unlink ($tmpfilename);
331 if ($outputlines == NULL)
332 throw new RuntimeException ('unknown gateway failure');
333 if (count ($outputlines) != 1)
334 throw new RuntimeException ('gateway protocol violation');
335 if (strpos ($outputlines[0], 'OK!') !== 0)
336 throw new RuntimeException ("gateway failure: ${outputlines[0]}");
337 // Being here means it was alright.
338 return $configtext;
339 }
340
341 ?>