Added some code cleanup.
[racktables] / wwwroot / inc / slb.php
CommitLineData
defd92d8
AA
1<?php
2
cddbb9fd
DO
3# This file is a part of RackTables, a datacenter and server room management
4# framework. See accompanying file "COPYING" for the full copyright and
5# licensing information.
6
ed38c73c
AA
7$vs_proto = array (
8 'TCP' => 'TCP',
9 'UDP' => 'UDP',
10 'MARK' => 'MARK',
11);
12
defd92d8
AA
13// ********************* Config-generating functions *********************
14
15// you may override the class name to make using your own triplet class
16$triplet_class = 'SLBTriplet';
17
18class SLBTriplet
19{
20 public $lb;
21 public $vs;
22 public $rs;
23 public $slb;
24 public $display_cells;
25
26 function __construct ($lb_id, $vs_id, $rs_id, $db_row = NULL)
27 {
28 $this->lb = spotEntity ('object', $lb_id);
29 $this->vs = spotEntity ('ipv4vs', $vs_id);
30 $this->rs = spotEntity ('ipv4rspool', $rs_id);
31 $this->display_cells = array ('lb', 'vs', 'rs');
32 if (isset ($db_row))
33 $this->slb = $db_row;
34 else
35 {
36 $result = usePreparedSelectBlade
37 (
38 "SELECT prio, vsconfig, rsconfig FROM IPv4LB WHERE object_id = ? AND vs_id = ? AND rspool_id = ?",
39 array ($lb_id, $vs_id, $rs_id)
40 );
41 if ($row = $result->fetch (PDO::FETCH_ASSOC))
42 $this->slb = $row;
43 else
44 throw new RackTablesError ("SLB triplet not found in the DB");
45 }
46 }
47
48 public static function getTriplets ($cell)
49 {
71066ef1
AA
50 if (isset ($cell['ip_bin']) and isset ($cell['vslist']))
51 // cell is IPAddress
52 return self::getTripletsByIP ($cell['ip_bin']);
defd92d8
AA
53 $ret = array();
54 switch ($cell['realm'])
55 {
56 case 'object':
57 $db_field = 'object_id';
58 $order_fields = 'vs_id';
59 $display_cells = array('vs', 'rs');
60 break;
61 case 'ipv4vs':
62 $db_field = 'vs_id';
63 $order_fields = 'rspool_id';
64 $display_cells = array('rs', 'lb');
65 break;
66 case 'ipv4rspool':
67 $db_field = 'rspool_id';
68 $order_fields = 'vs_id';
69 $display_cells = array('vs', 'lb');
70 break;
71 default:
62c607f3 72 throw new InvalidArgException ('realm', $cell['realm']);
defd92d8
AA
73 }
74 $result = usePreparedSelectBlade
75 (
76 "SELECT * FROM IPv4LB WHERE `$db_field` = ? ORDER BY $order_fields",
77 array ($cell['id'])
78 );
79 $rows = $result->fetchAll (PDO::FETCH_ASSOC);
80 unset ($result);
81 global $triplet_class;
82 foreach ($rows as $row)
83 {
84 $triplet = new $triplet_class ($row['object_id'], $row['vs_id'], $row['rspool_id'], $row);
85 $triplet->display_cells = $display_cells;
86 $ret[] = $triplet;
87 }
88 return $ret;
89 }
90
71066ef1 91 private static function getTripletsByIP ($ip_bin)
defd92d8
AA
92 {
93 $ret = array();
defd92d8 94 $result = usePreparedSelectBlade ("
dec748f6
MH
95SELECT DISTINCT IPv4LB.*
96FROM
defd92d8
AA
97 IPv4LB INNER JOIN IPv4VS ON IPv4VS.id = IPv4LB.vs_id
98 LEFT JOIN IPv4RS USING (rspool_id)
99WHERE
100 rsip = ? OR vip = ?
101ORDER BY
102 vs_id
71066ef1 103 ", array ($ip_bin, $ip_bin)
defd92d8
AA
104 );
105 $rows = $result->fetchAll (PDO::FETCH_ASSOC);
106 unset ($result);
107 global $triplet_class;
108 foreach ($rows as $row)
109 {
110 $triplet = new $triplet_class ($row['object_id'], $row['vs_id'], $row['rspool_id'], $row);
111 $triplet->display_cells = array ('vs', 'lb', 'rs');
112 $ret[] = $triplet;
113 }
114 return $ret;
115 }
116
58a8fe61 117 protected function createParser ($triplet)
defd92d8
AA
118 {
119 return new MacroParser();
120 }
121
122 function generateConfig()
123 {
124 // fill the predefined macros
58a8fe61 125 $parser = $this->createParser ($this);
defd92d8
AA
126 $parser->addMacro ('LB_ID', $this->lb['id']);
127 $parser->addMacro ('LB_NAME', $this->lb['name']);
128 $parser->addMacro ('VS_ID', $this->vs['id']);
fa688d0e 129 $parser->addMacro ('VS_NAME', $this->vs['name']);
defd92d8 130 $parser->addMacro ('RSP_ID', $this->rs['id']);
fa688d0e 131 $parser->addMacro ('RSP_NAME', $this->rs['name']);
defd92d8
AA
132 $parser->addMacro ('VIP', $this->vs['vip']);
133 $parser->addMacro ('VPORT', $this->vs['vport']);
defd92d8 134 $parser->addMacro ('PRIO', $this->slb['prio']);
71066ef1 135 $parser->addMacro ('IP_VER', (strlen ($this->vs['vip_bin']) == 16) ? 6 : 4);
defd92d8 136
ed38c73c
AA
137 if ($this->vs['proto'] == 'MARK')
138 {
139 $parser->addMacro ('PROTO', 'TCP');
71066ef1 140 $mark = implode ('', unpack ('N', substr ($this->vs['vip_bin'], 0, 4)));
ed38c73c
AA
141 $parser->addMacro ('MARK', $mark);
142 $parser->addMacro ('VS_HEADER', "fwmark $mark");
143 }
144 else
145 {
146 $parser->addMacro ('VS_HEADER', $this->vs['vip'] . ' ' . $this->vs['vport']);
147 $parser->addMacro ('PROTO', $this->vs['proto']);
148 }
149
defd92d8
AA
150 $defaults = getSLBDefaults (TRUE);
151 $parser->addMacro ('GLOBAL_VS_CONF', dos2unix ($defaults['vs']));
defd92d8 152 $parser->addMacro ('RSP_VS_CONF', dos2unix ($this->rs['vsconfig']));
55b55c4c 153 $parser->addMacro ('VS_VS_CONF', dos2unix ($this->vs['vsconfig']));
defd92d8
AA
154 $parser->addMacro ('SLB_VS_CONF', dos2unix ($this->slb['vsconfig']));
155
156 // return the expanded VS template using prepared $macros array
157 $ret = $parser->expand ("
158# LB (id == %LB_ID%): %LB_NAME%
159# VS (id == %VS_ID%): %VS_NAME%
160# RS (id == %RSP_ID%): %RSP_NAME%
ed38c73c 161virtual_server %VS_HEADER% {
defd92d8 162 protocol %PROTO%
55b55c4c 163 %GLOBAL_VS_CONF%
defd92d8 164 %RSP_VS_CONF%
f3670996 165 %VS_VS_CONF%
defd92d8
AA
166 %SLB_VS_CONF%
167");
168 foreach (getRSListInPool ($this->rs['id']) as $rs)
169 {
71066ef1
AA
170 // do not add v6 reals into v4 service and vice versa
171 if (strlen ($rs['rsip_bin']) != strlen ($this->vs['vip_bin']))
172 continue;
defd92d8
AA
173 if ($rs['inservice'] != 'yes')
174 continue;
175 $parser->pushdefs(); // backup macros
0e42841e 176 $parser->addMacro ('RS_HEADER', ($this->vs['proto'] == 'MARK' ? '%RSIP%' : '%RSIP% %RSPORT%'));
defd92d8
AA
177 $parser->addMacro ('RSIP', $rs['rsip']);
178 $parser->addMacro ('RSPORT', isset ($rs['rsport']) ? $rs['rsport'] : $this->vs['vport']); // VS port is a default value for RS port
ac726238 179 $parser->addMacro ('RS_COMMENT', $rs['comment']);
defd92d8
AA
180
181 $parser->addMacro ('GLOBAL_RS_CONF', dos2unix ($defaults['rs']));
182 $parser->addMacro ('VS_RS_CONF', dos2unix ($this->vs['rsconfig']));
183 $parser->addMacro ('RSP_RS_CONF', dos2unix ($this->rs['rsconfig']));
184 $parser->addMacro ('SLB_RS_CONF', dos2unix ($this->slb['rsconfig']));
185 $parser->addMacro ('RS_RS_CONF', $rs['rsconfig']);
186
88a55b7c
AA
187 foreach (explode (',', $parser->expandMacro ('RSPORT')) as $rsport) {
188 $parser->pushdefs();
189 $parser->addMacro ('RSPORT', $rsport);
190
191 $ret .= $parser->expand ("
ac726238 192 %RS_PREPEND%
0e42841e 193 real_server %RS_HEADER% {
55b55c4c 194 %GLOBAL_RS_CONF%
defd92d8
AA
195 %VS_RS_CONF%
196 %RSP_RS_CONF%
defd92d8 197 %SLB_RS_CONF%
f3670996 198 %RS_RS_CONF%
defd92d8
AA
199 }
200");
88a55b7c
AA
201 $parser->popdefs();
202 }
defd92d8
AA
203 $parser->popdefs(); // restore original (VS-driven) macros
204 }
205 $ret .= "}\n";
206 return $ret;
207 }
208}
209
210class MacroParser
211{
1bd597e1
AA
212 protected $macros; // current macro context
213 protected $stack; // macro contexts saved by pushdefs()
214 protected $trace; // recursive macro expansion path
defd92d8
AA
215
216 function __construct()
217 {
218 $this->macros = array();
219 $this->stack = array();
1bd597e1 220 $this->trace = array();
defd92d8
AA
221 }
222
223 function pushdefs()
224 {
225 $this->stack[] = $this->macros;
226 }
227
228 function popdefs()
229 {
230 $this->macros = array_pop ($this->stack);
231 }
232
233 // cuts the subsequent defines from $value and stores the $name-$value define
234 // if $value is unset, returns immediately
235 public function addMacro ($name, $value)
236 {
237 if (! isset ($value))
238 return;
239 $new_value = ''; // value without defines
240 $macro_deep = 0;
241 foreach (explode ("\n", $value) as $line)
242 {
243 if (! $macro_deep)
244 {
245 if (preg_match ('/^([A-Za-z_0-9]+)=(.*)/', $line, $m))
246 {
247 // found macro definition
248 $mname = $m[1];
249 $mvalue = ltrim ($m[2]);
250 if (substr ($mvalue, 0, 1) == '`') // quoted define value
251 {
252 $line = substr ($mvalue, 1);
253 $mvalue = '';
254 $macro_deep++;
255 }
256 else
257 {
258 $this->addMacro ($mname, rtrim ($mvalue));
259 }
260 }
261 else
262 $new_value .= $line . "\n";
263 }
264
265 if ($macro_deep)
266 {
267 for ($i = 0; $i < strlen ($line); $i++)
268 {
dec748f6 269 $c = $line[$i];
defd92d8
AA
270 if ($c == "'" and 0 == --$macro_deep)
271 {
272 $this->addMacro ($mname, $mvalue);
273 $rest = substr ($line, $i + 1);
274 if (preg_match ('/\S/', $rest))
275 $new_value .= $rest . "\n";
276 break;
277 }
278 elseif ($c == "`")
279 $macro_deep++;
280 $mvalue .= $c;
281 }
282 if ($macro_deep)
283 $mvalue .= "\n";
284 }
285 }
286 $this->macros[$name] = substr ($new_value, 0, -1); // trim last \n
287 }
288
289 // replaces all macro expansions (like %THIS%) by the results of expandMacro calls
290 // Has some formatting logic:
291 // * indent each line of expansion if the expanding line contains only the macro reference
292 // * do not add empty line to the output if the expanding line contains only the macro reference and expands to empty string
293 // * trim last newline of expansion if there already is newline in source string after the macro reference
294 public function expand ($text)
295 {
defd92d8
AA
296 $ret = '';
297 foreach (explode ("\n", $text) as $line)
298 {
299 $line .= "\n";
300 $prev = '';
301 while (preg_match ('/(.*?)%([A-Za-z_0-9]+)%(.*)/s', $line, $m))
302 {
303 $prev .= $m[1];
304 $mname = $m[2];
305 $line = $m[3];
306 $mvalue = $this->expandMacro ($mname);
307 $before_empty = preg_match ('/^\s*$/', $prev);
308 $after_empty = preg_match ('/^[\s\n]*$/s', $line);
309 $macro_empty = preg_match ('/^[\s\n]*$/s', $mvalue);
310 $prev .= $mvalue;
311 if ($before_empty and $after_empty)
312 {
313 if ($macro_empty)
314 {
315 $line = '';
316 break;
317 }
318 // indent every line in $mvalue by $m[1]
319 $mvalue = preg_replace ('/^/m', $m[1], $mvalue);
320 $m[1] = '';
321 }
322 if ($after_empty and substr ($mvalue, -1, 1) == "\n")
323 $mvalue = substr ($mvalue, 0, -1);
defd92d8
AA
324 $ret .= $m[1] . $mvalue;
325 }
326 $ret .= $line;
327 }
defd92d8
AA
328 return substr ($ret, 0, -1); // trim last \n
329 }
330
331 // returns the result of expanding the named define, or '' if unset
332 public function expandMacro ($name)
333 {
1bd597e1 334 array_push ($this->trace, $name);
defd92d8 335 if (isset ($this->macros[$name]))
1bd597e1 336 $ret = $this->expand ($this->macros[$name]);
defd92d8 337 else
1bd597e1
AA
338 $ret = '';
339 array_pop ($this->trace);
340 return $ret;
defd92d8
AA
341 }
342}
343
344function buildEntityLVSConfig ($cell)
345{
346 $newconfig = "#\n#\n# This configuration has been generated automatically by RackTables\n#\n#\n";
347 foreach (SLBTriplet::getTriplets ($cell) as $slb)
348 $newconfig .= $slb->generateConfig();
349 return $newconfig;
350}
351
352function buildLVSConfig ($object_id)
353{
354 return buildEntityLVSConfig (spotEntity ('object', $object_id));
355}
356
357// ********************* Database functions *********************
358
4318ced5 359function addRStoRSPool ($pool_id, $rsip_bin, $rsport = 0, $inservice = 'no', $rsconfig = '', $comment = '')
defd92d8 360{
4318ced5 361 return usePreparedInsertBlade
defd92d8 362 (
4318ced5 363 'IPv4RS',
defd92d8
AA
364 array
365 (
4318ced5 366 'rspool_id' => $pool_id,
71066ef1 367 'rsip' => $rsip_bin,
4318ced5 368 'rsport' => (!strlen ($rsport) or $rsport === 0) ? NULL : $rsport,
dec748f6 369 'inservice' => $inservice == 'yes' ? 'yes' : 'no',
4318ced5
AA
370 'rsconfig' => !strlen ($rsconfig) ? NULL : $rsconfig,
371 'comment' => !strlen ($comment) ? NULL : $comment,
defd92d8
AA
372 )
373 );
374}
375
376function addLBtoRSPool ($pool_id = 0, $object_id = 0, $vs_id = 0, $vsconfig = '', $rsconfig = '', $prio = '')
377{
378 usePreparedInsertBlade
379 (
380 'IPv4LB',
381 array
382 (
383 'object_id' => $object_id,
384 'rspool_id' => $pool_id,
385 'vs_id' => $vs_id,
386 'vsconfig' => (!strlen ($vsconfig) ? NULL : $vsconfig),
387 'rsconfig' => (!strlen ($rsconfig) ? NULL : $rsconfig),
388 'prio' => (!strlen ($prio) ? NULL : $prio),
389 )
390 );
391}
392
393function commitDeleteVS ($id = 0)
394{
395 releaseFiles ('ipv4vs', $id);
396 destroyTagsForEntity ('ipv4vs', $id);
397 usePreparedDeleteBlade ('IPv4VS', array ('id' => $id));
398}
399
4318ced5 400function commitUpdateRS ($rsid, $rsip_bin, $rsport = 0, $inservice = 'yes', $rsconfig = '', $comment = '')
defd92d8 401{
defd92d8
AA
402 usePreparedExecuteBlade
403 (
4318ced5 404 'UPDATE IPv4RS SET rsip=?, rsport=?, inservice=?, rsconfig=?, comment=? WHERE id=?',
defd92d8
AA
405 array
406 (
71066ef1 407 $rsip_bin,
defd92d8
AA
408 (!strlen ($rsport) or $rsport === 0) ? NULL : $rsport,
409 $inservice,
410 !strlen ($rsconfig) ? NULL : $rsconfig,
4ed2310e 411 !strlen ($comment) ? NULL : $comment,
defd92d8
AA
412 $rsid,
413 )
414 );
415}
416
9e0596b3 417// $vport is ignored if $proto == 'MARK'
4318ced5 418function commitUpdateVS ($vsid, $vip_bin, $vport = 0, $proto = '', $name = '', $vsconfig = '', $rsconfig = '')
defd92d8 419{
9e0596b3 420 if ($proto != 'MARK' && $vport <= 0)
71066ef1 421 throw new InvalidArgException ('vport', $vport);
defd92d8 422 if (!strlen ($proto))
71066ef1 423 throw new InvalidArgException ('proto', $proto);
4318ced5 424 return usePreparedUpdateBlade
defd92d8 425 (
4318ced5 426 'IPv4VS',
defd92d8
AA
427 array
428 (
71066ef1 429 'vip' => $vip_bin,
9e0596b3 430 'vport' => ($proto == 'MARK' ? NULL : $vport),
4318ced5
AA
431 'proto' => $proto,
432 'name' => !strlen ($name) ? NULL : $name,
433 'vsconfig' => !strlen ($vsconfig) ? NULL : $vsconfig,
434 'rsconfig' => !strlen ($rsconfig) ? NULL : $rsconfig,
435 ),
436 array ('id' => $vsid)
defd92d8
AA
437 );
438}
439
f4cd3f04 440function commitCreateRSPool ($name = '', $vsconfig = '', $rsconfig = '', $tagidlist = array())
defd92d8 441{
2e9fbb2f
AA
442 $new_pool_id = FALSE;
443 if (usePreparedInsertBlade
defd92d8
AA
444 (
445 'IPv4RSPool',
446 array
447 (
448 'name' => (!strlen ($name) ? NULL : $name),
449 'vsconfig' => (!strlen ($vsconfig) ? NULL : $vsconfig),
450 'rsconfig' => (!strlen ($rsconfig) ? NULL : $rsconfig)
451 )
2e9fbb2f
AA
452 ))
453 $new_pool_id = lastInsertID();
4318ced5 454 produceTagsForNewRecord ('ipv4rspool', $tagidlist, $new_pool_id);
2e9fbb2f 455 return $new_pool_id;
defd92d8
AA
456}
457
458function commitDeleteRSPool ($pool_id = 0)
459{
460 releaseFiles ('ipv4rspool', $pool_id);
461 destroyTagsForEntity ('ipv4rspool', $pool_id);
462 usePreparedDeleteBlade ('IPv4RSPool', array ('id' => $pool_id));
463}
464
465function commitUpdateSLBDefConf ($data)
466{
467 saveScript('DefaultVSConfig', $data['vs']);
468 saveScript('DefaultRSConfig', $data['rs']);
469}
470
471function getSLBDefaults ($do_cache_result = FALSE) {
472 static $ret = array();
473
474 if (! $do_cache_result)
475 $ret = array();
476 elseif (! empty ($ret))
477 return $ret;
478
479 $ret['vs'] = loadScript('DefaultVSConfig');
480 $ret['rs'] = loadScript('DefaultRSConfig');
481 return $ret;
482}
483
484// Return the list of all currently configured load balancers with their pool count.
485function getLBList ()
486{
487 $result = usePreparedSelectBlade
488 (
489 "select object_id, count(rspool_id) as poolcount " .
490 "from IPv4LB group by object_id order by object_id"
491 );
492 $ret = array ();
493 while ($row = $result->fetch (PDO::FETCH_ASSOC))
494 $ret[$row['object_id']] = $row['poolcount'];
495 return $ret;
496}
497
498function getRSList ()
499{
500 $result = usePreparedSelectBlade
501 (
71066ef1 502 "select id, inservice, rsip as rsip_bin, rsport, rspool_id, rsconfig " .
defd92d8
AA
503 "from IPv4RS order by rspool_id, IPv4RS.rsip, rsport"
504 );
505 $ret = array ();
506 while ($row = $result->fetch (PDO::FETCH_ASSOC))
71066ef1
AA
507 {
508 $row['rsip'] = ip_format ($row['rsip_bin']);
509 foreach (array ('inservice', 'rsip_bin', 'rsip', 'rsport', 'rspool_id', 'rsconfig') as $cname)
defd92d8 510 $ret[$row['id']][$cname] = $row[$cname];
71066ef1 511 }
defd92d8
AA
512 return $ret;
513}
514
515function getRSListInPool ($rspool_id)
516{
517 $ret = array();
71066ef1 518 $query = "select id, inservice, rsip as rsip_bin, rsport, rsconfig, comment from " .
defd92d8
AA
519 "IPv4RS where rspool_id = ? order by IPv4RS.rsip, rsport";
520 $result = usePreparedSelectBlade ($query, array ($rspool_id));
521 while ($row = $result->fetch (PDO::FETCH_ASSOC))
71066ef1
AA
522 {
523 $row['rsip'] = ip_format ($row['rsip_bin']);
4ed2310e 524 $ret[$row['id']] = $row;
71066ef1 525 }
defd92d8
AA
526 return $ret;
527}
528
529?>