r1789 + parseWikiLink() learns optgroup name skipping
[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: returned code ${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 $log = array();
161 if ($object_id <= 0)
162 return array (array ('code' => 'error', 'message' => __FUNCTION__ . ': Invalid object_id'));
163 $objectInfo = getObjectInfo ($object_id);
164 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
165 if (count ($endpoints) == 0)
166 return array (array ('code' => 'error', 'message' => 'Can\'t find any mean to reach current object. Please either set FQDN attribute or assign an IP address to the object.'));
167 if (count ($endpoints) > 1)
168 return array (array ('code' => 'error', 'message' => 'More than one IP address is assigned to this object, please configure FQDN attribute.'));
169 $hwtype = $swtype = 'unknown';
170 foreach (getAttrValues ($object_id, TRUE) as $record)
171 {
172 if ($record['name'] == 'SW type' && !empty ($record['value']))
173 $swtype = strtr ($record['value'], ' ', '+');
174 if ($record['name'] == 'HW type' && !empty ($record['value']))
175 $hwtype = strtr ($record['value'], ' ', '+');
176 }
177 $endpoint = str_replace (' ', '+', $endpoints[0]);
178 $data = queryGateway
179 (
180 'switchvlans',
181 array ("connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", $setcmd)
182 );
183 if ($data == NULL)
184 return array (array ('code' => 'error', 'message' => __FUNCTION__ . ': Failed to get any response from queryGateway() or the gateway died'));
185 if (strpos ($data[0], 'OK!') !== 0)
186 return array (array ('code' => 'error', 'message' => "Gateway failure: returned code ${data[0]}."));
187 if (count ($data) != 2)
188 return array (array ('code' => 'error', 'message' => 'Gateway failure: malformed reply.'));
189 // Finally we can parse the response into message array.
190 $ret = array();
191 foreach (split (';', substr ($data[1], strlen ('OK!'))) as $text)
192 {
193 if (strpos ($text, 'I!') === 0)
194 $code = 'success';
195 elseif (strpos ($text, 'W!') === 0)
196 $code = 'warning';
197 else // All improperly formatted messages must be treated as error conditions.
198 $code = 'error';
199 $ret[] = array ('code' => $code, 'message' => substr ($text, 2));
200 }
201 return $ret;
202 }
203
204 // FIXME: shouldn't the common code be made into some helper?
205 function activateSLBConfig ($object_id = 0, $configtext = '')
206 {
207 global $remote_username;
208 $log = array();
209 if ($object_id <= 0 or empty ($configtext))
210 return array (array ('code' => 'error', 'message' => __FUNCTION__ . ': Invalid arguments'));
211 $objectInfo = getObjectInfo ($object_id);
212 $endpoints = findAllEndpoints ($object_id, $objectInfo['name']);
213 if (count ($endpoints) == 0)
214 return array (array ('code' => 'error', 'message' => 'Can\'t find any mean to reach current object. Please either set FQDN attribute or assign an IP address to the object.'));
215 if (count ($endpoints) > 1)
216 return array (array ('code' => 'error', 'message' => 'More than one IP address is assigned to this object, please configure FQDN attribute.'));
217 $hwtype = $swtype = 'unknown';
218 $endpoint = str_replace (' ', '+', $endpoints[0]);
219 $tmpfilename = tempnam ('', 'RackTables-slbconfig-');
220 $tmpfile = fopen ($tmpfilename, 'wb');
221 fwrite ($tmpfile, str_replace ("\r", '', $configtext));
222 fclose ($tmpfile);
223 $data = queryGateway
224 (
225 'slbconfig',
226 array ("connect ${endpoint} ${hwtype} ${swtype} ${remote_username}", "activate ${tmpfilename}")
227 );
228 unlink ($tmpfilename);
229 if ($data == NULL)
230 return array (array ('code' => 'error', 'message' => __FUNCTION__ . ': Failed to get any response from queryGateway() or the gateway died'));
231 if (strpos ($data[0], 'OK!') !== 0)
232 return array (array ('code' => 'error', 'message' => "Gateway failure: returned code ${data[0]}."));
233 if (count ($data) != 2)
234 return array (array ('code' => 'error', 'message' => 'Gateway failure: malformed reply.'));
235 // Finally we can parse the response into message array.
236 $ret = array();
237 $codemap['ERR'] = 'error';
238 $codemap['OK'] = 'success';
239 list ($code, $text) = split ('!', $data[1]);
240 $ret[] = array ('code' => $codemap[$code], 'message' => $text);
241 return $ret;
242 }
243
244 ?>