r2083 + setSwitchVLANs(): use oneLiner(), handle "C" messages
[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 (empty ($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 {
73 showError ('Invalid object_id', __FUNCTION__);
74 return;
75 }
76 $objectInfo = getObjectInfo ($object_id);
77 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
78 if (count ($endpoints) == 0)
79 {
80 showError ('Can\'t find any mean to reach current object. Please either set FQDN attribute or assign an IP address to the object.', __FUNCTION__);
81 return NULL;
82 }
83 if (count ($endpoints) > 1)
84 {
85 showError ('More than one IP address is assigned to this object, please configure FQDN attribute.', __FUNCTION__);
86 return NULL;
87 }
88 $hwtype = $swtype = 'unknown';
89 foreach (getAttrValues ($object_id, TRUE) as $record)
90 {
91 if ($record['name'] == 'SW type' && !empty ($record['value']))
92 $swtype = str_replace (' ', '+', $record['value']);
93 if ($record['name'] == 'HW type' && !empty ($record['value']))
94 $hwtype = str_replace (' ', '+', $record['value']);
95 }
96 $endpoint = str_replace (' ', '+', $endpoints[0]);
97 $commands = array
98 (
99 "connect ${endpoint} ${hwtype} ${swtype} ${remote_username}",
100 'listvlans',
101 'listports',
102 'listmacs'
103 );
104 $data = queryGateway ('switchvlans', $commands);
105 if ($data == NULL)
106 {
107 showError ('Failed to get any response from queryGateway() or the gateway died', __FUNCTION__);
108 return NULL;
109 }
110 if (strpos ($data[0], 'OK!') !== 0)
111 {
112 showError ("Gateway failure: ${data[0]}.", __FUNCTION__);
113 return NULL;
114 }
115 if (count ($data) != count ($commands))
116 {
117 showError ("Gateway failure: malformed reply.", __FUNCTION__);
118 return NULL;
119 }
120 // Now we have VLAN list in $data[1] and port list in $data[2]. Let's sort this out.
121 $tmp = array_unique (explode (';', substr ($data[1], strlen ('OK!'))));
122 if (count ($tmp) == 0)
123 {
124 showError ("Gateway succeeded, but returned no VLAN records.", __FUNCTION__);
125 return NULL;
126 }
127 $vlanlist = array();
128 foreach ($tmp as $record)
129 {
130 list ($vlanid, $vlandescr) = explode ('=', $record);
131 $vlanlist[$vlanid] = $vlandescr;
132 }
133 $portlist = array();
134 foreach (explode (';', substr ($data[2], strlen ('OK!'))) as $pair)
135 {
136 list ($portname, $pair2) = explode ('=', $pair);
137 list ($status, $vlanid) = explode (',', $pair2);
138 $portlist[] = array ('portname' => $portname, 'status' => $status, 'vlanid' => $vlanid);
139 }
140 if (count ($portlist) == 0)
141 {
142 showError ("Gateway succeeded, but returned no port records.", __FUNCTION__);
143 return NULL;
144 }
145 $maclist = array();
146 foreach (explode (';', substr ($data[3], strlen ('OK!'))) as $pair)
147 {
148 list ($macaddr, $pair2) = explode ('=', $pair);
149 if (empty ($pair2))
150 continue;
151 list ($vlanid, $ifname) = explode ('@', $pair2);
152 $maclist[$ifname][$vlanid][] = $macaddr;
153 }
154 return array ($vlanlist, $portlist, $maclist);
155 }
156
157 function setSwitchVLANs ($object_id = 0, $setcmd)
158 {
159 global $remote_username;
160 if ($object_id <= 0)
161 return oneLiner (160); // invalid arguments
162 $objectInfo = getObjectInfo ($object_id);
163 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
164 if (count ($endpoints) == 0)
165 return oneLiner (161); // endpoint not found
166 if (count ($endpoints) > 1)
167 return oneLiner (162); // can't pick an address
168 $hwtype = $swtype = 'unknown';
169 foreach (getAttrValues ($object_id, TRUE) as $record)
170 {
171 if ($record['name'] == 'SW type' && !empty ($record['value']))
172 $swtype = strtr ($record['value'], ' ', '+');
173 if ($record['name'] == 'HW type' && !empty ($record['value']))
174 $hwtype = strtr ($record['value'], ' ', '+');
175 }
176 $endpoint = str_replace (' ', '+', $endpoints[0]);
177 $data = queryGateway
178 (
179 'switchvlans',
180 array ("connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", $setcmd)
181 );
182 if ($data == NULL)
183 return oneLiner (163); // unknown gateway failure
184 if (strpos ($data[0], 'OK!') !== 0)
185 return oneLiner (164, array ($data[0])); // gateway failure
186 if (count ($data) != 2)
187 return oneLiner (165); // protocol violation
188 // Finally we can parse the response into message array.
189 $log_m = array();
190 foreach (split (';', substr ($data[1], strlen ('OK!'))) as $text)
191 {
192 if (strpos ($text, 'C!') === 0)
193 {
194 $tmp = split ('!', $text);
195 array_shift ($tmp);
196 $code = array_shift ($tmp);
197 $log_m[] = count ($tmp) ? array ('c' => $code, 'a' => $tmp) : array ('c' => $code); // gateway-encoded message
198 }
199 elseif (strpos ($text, 'I!') === 0)
200 $log_m[] = array ('c' => 62, 'a' => array (substr ($text, 2))); // generic gateway success
201 elseif (strpos ($text, 'W!') === 0)
202 $log_m[] = array ('c' => 202, 'a' => array (substr ($text, 2))); // generic gateway warning
203 else // All improperly formatted messages must be treated as error conditions.
204 $log_m[] = array ('c' => 166, 'a' => array (substr ($text, 2))); // generic gateway error
205 }
206 return $log_m;
207 }
208
209 // FIXME: shouldn't the common code be made into some helper?
210 function activateSLBConfig ($object_id = 0, $configtext = '')
211 {
212 global $remote_username;
213 if ($object_id <= 0 or empty ($configtext))
214 return oneLiner (160); // invalid arguments
215 $objectInfo = getObjectInfo ($object_id);
216 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
217 if (count ($endpoints) == 0)
218 return oneLiner (161); // endpoint not found
219 if (count ($endpoints) > 1)
220 return oneLiner (162); // can't pick an address
221 $hwtype = $swtype = 'unknown';
222 $endpoint = str_replace (' ', '+', $endpoints[0]);
223 $tmpfilename = tempnam ('', 'RackTables-slbconfig-');
224 $tmpfile = fopen ($tmpfilename, 'wb');
225 fwrite ($tmpfile, str_replace ("\r", '', $configtext));
226 fclose ($tmpfile);
227 $data = queryGateway
228 (
229 'slbconfig',
230 array ("connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", "activate ${tmpfilename}")
231 );
232 unlink ($tmpfilename);
233 if ($data == NULL)
234 return oneLiner (163); // unknown gateway failure
235 if (strpos ($data[0], 'OK!') !== 0)
236 return oneLiner (164, array ($data[0])); // gateway failure
237 if (count ($data) != 2)
238 return oneLiner (165); // protocol violation
239 // Finally we can parse the response into message array.
240 $log = array ('v' => 2);
241 $codemap['ERR'] = 166; // generic gateway error
242 $codemap['OK'] = 62; // generic gateway success
243 list ($code, $text) = split ('!', $data[1]);
244 $log['m'][] = array ('c' => $codemap[$code], 'a' => array ($text));
245 return $log;
246 }
247
248 ?>