update: display parent locations when browsing rackspace (#845)
update: display parent and child location(s) on View Location page (#847)
new feature: Rackcode filter for RDP-managed objects (#819)
+ new feature: grouping of Virtual services, generating of
+ virtual_server_group-aware keepalived configs
0.20.4 2013-04-15
bugfix: %GPASS% dictionary markers were ignored in selectboxes
bugfix: 802.1Q: better Force10 switches support
* *
*******************************************************
+*** Upgrading to 0.20.5 ***
+
+This release introduces the VS groups feature. VS groups is a new way to store
+and display virtual services configuration. New realm 'ipvs' (VS group) is created.
+All the existing VS configuration is kept and displayed as-is, but user is free to convert
+it to the new format, which displays it in more natural way and allows to generate
+virtual_server_group keepalived configs. To convert a virtual servise to the new format,
+you need to manually create the vs group object and assign IP addresses to it. Then, if you
+have the old-style VSes configured, the Migrate tab will be displayed on the particular VS group's
+page. After successfull migration, you can remove the old-style VS objects.
+
+Old-style VS configuration becomes DEPRECATED. Its support will be removed in one of the following
+major releases. So it is strongly recommended to convert it to the new format.
+
*** Upgrading to 0.20.4 ***
Please note that some dictionary items of Cisco Catalyst 2960 series switches
border: 1px solid #000000;
background: #FFFFFF;
padding: 0px 5px;
+ z-index: 999;
}
/*popup div with MAC list*/
margin: 0 0;
font-size: 90%;
}
+
+.slb-checks {
+ margin: 0px;
+ padding: 0px 2em;
+ list-style: square;
+}
+
+.slb-checks li {
+ margin: 5px 0px;
+ white-space: pre;
+}
+
+.slb-checks li.disabled {
+ color: #999999;
+ list-style-image: url(?module=chrome&uri=pix/unchecked.png)
+}
+
+.slb-checks li.disabled * {
+ color: #999999;
+}
+
+.slb-checks li.enabled {
+ list-style-image: url(?module=chrome&uri=pix/checked.png);
+}
+
+.slb-checks.editable li:hover {
+ list-style-image: url(?module=chrome&uri=pix/pencil-icon.png);
+ cursor: pointer;
+}
+
+.slb-checks.editable li a {
+ cursor: pointer;
+}
+
+.slb-checks.editable li * {
+ cursor: auto;
+}
+
+.slb-checks li form {
+ display: none;
+}
+
+.slb-prio {
+ border: 1px solid #aaaaaa;
+ padding: 0px 2px;
+ margin-left: 1em;
+}
+
+.slbconf-btn {
+ border: 1px solid #aaaaaa;
+ font-size: 12px;
+ font-weight: bold;
+ height: 10px;
+ line-height: 6px;
+
+ display: inline-block;
+ padding: 0px 2px;
+ margin-left: 1em;
+ position: relative;
+ top: -3px;
+}
+
+.popup-box h1 {
+ background-color: #3C78B5;
+ color: white;
+ text-align: center;
+ font-family: verdana, tahoma;
+ font-weight: normal;
+ padding: 0px;
+ margin: 0px -5px;
+ margin-top: 0.5em;
+ font-size: 12px;
+ line-height: 1.5em;
+}
'keycolumn' => 'id',
'ordcolumns' => array ('IPv4VS.vip', 'IPv4VS.proto', 'IPv4VS.vport'),
),
+ 'ipvs' => array
+ (
+ 'table' => 'VS',
+ 'columns' => array
+ (
+ 'id' => 'id',
+ 'name' => 'name',
+ 'vsconfig' => 'vsconfig',
+ 'rsconfig' => 'rsconfig',
+ ),
+ 'keycolumn' => 'id',
+ ),
'ipv4rspool' => array
(
'table' => 'IPv4RSPool',
$query .= " WHERE ${SQLinfo['table']}.${SQLinfo['pidcolumn']} = ?";
$qparams[] = $parent_id;
}
- $query .= " ORDER BY ";
- foreach ($SQLinfo['ordcolumns'] as $oc)
- $query .= "${oc}, ";
- $query = trim($query, ', ');
+ if (isset ($SQLinfo['ordcolumns']))
+ {
+ $query .= " ORDER BY ";
+ foreach ($SQLinfo['ordcolumns'] as $oc)
+ $query .= "${oc}, ";
+ $query = trim($query, ', ');
+ }
$result = usePreparedSelectBlade ($query, $qparams);
$ret = array();
// Index returned result by the value of key column.
case 'ipv4vs':
$entity['vip'] = ip_format ($entity['vip_bin']);
setDisplayedName ($entity); // set $entity['dname']
+ $entity['vsconfig'] = dos2unix ($entity['vsconfig']);
+ $entity['rsconfig'] = dos2unix ($entity['rsconfig']);
+ break;
+ case 'ipv4rspool':
+ $entity['vsconfig'] = dos2unix ($entity['vsconfig']);
+ $entity['rsconfig'] = dos2unix ($entity['rsconfig']);
+ break;
+ case 'ipvs':
+ $entity['vsconfig'] = dos2unix ($entity['vsconfig']);
+ $entity['rsconfig'] = dos2unix ($entity['rsconfig']);
break;
default:
break;
case 'ipv4vs':
$ret['vip'] = ip_format ($ret['vip_bin']);
setDisplayedName ($ret); // set $ret['dname']
+ $ret['vsconfig'] = dos2unix ($ret['vsconfig']);
+ $ret['rsconfig'] = dos2unix ($ret['rsconfig']);
+ break;
+ case 'ipv4rspool':
+ $ret['vsconfig'] = dos2unix ($ret['vsconfig']);
+ $ret['rsconfig'] = dos2unix ($ret['rsconfig']);
+ break;
+ case 'ipvs':
+ $ret['vsconfig'] = dos2unix ($ret['vsconfig']);
+ $ret['rsconfig'] = dos2unix ($ret['rsconfig']);
break;
default:
break;
while ($row = $result->fetch (PDO::FETCH_ASSOC))
$record['switches'][$row['object_id']] = $row;
break;
+ case 'ipvs':
+ $result = usePreparedSelectBlade ("SELECT proto, vport, vsconfig, rsconfig FROM VSPorts WHERE vs_id = ?", array ($record['id']));
+ while ($row = $result->fetch (PDO::FETCH_ASSOC))
+ {
+ $row['vsconfig'] = dos2unix ($row['vsconfig']);
+ $row['rsconfig'] = dos2unix ($row['rsconfig']);
+ $record['ports'][] = $row;
+ }
+ unset ($result);
+ $result = usePreparedSelectBlade ("SELECT vip, vsconfig, rsconfig FROM VSIPs WHERE vs_id = ?", array ($record['id']));
+ while ($row = $result->fetch (PDO::FETCH_ASSOC))
+ {
+ $row['vsconfig'] = dos2unix ($row['vsconfig']);
+ $row['rsconfig'] = dos2unix ($row['rsconfig']);
+ $record['vips'][] = $row;
+ }
+ unset ($result);
+ break;
default:
}
}
$or = '';
$whereexpr1 = '(';
$whereexpr2 = '(';
- $whereexpr3 = '(';
+ $whereexpr3a = '(';
+ $whereexpr3b = '(';
$whereexpr4 = '(';
$whereexpr5a = '(';
$whereexpr5b = '(';
{
$whereexpr1 .= $or . "ip between ? and ?";
$whereexpr2 .= $or . "ip between ? and ?";
- $whereexpr3 .= $or . "vip between ? and ?";
+ $whereexpr3a .= $or . "vip between ? and ?";
+ $whereexpr3b .= $or . "vip between ? and ?";
$whereexpr4 .= $or . "rsip between ? and ?";
$whereexpr5a .= $or . "remoteip between ? and ?";
$whereexpr5b .= $or . "localip between ? and ?";
}
$whereexpr1 .= ')';
$whereexpr2 .= ')';
- $whereexpr3 .= ')';
+ $whereexpr3a .= ')';
+ $whereexpr3b .= ')';
$whereexpr4 .= ')';
$whereexpr5a .= ')';
$whereexpr5b .= ')';
);
}
- // 3. look for virtual services
- $query = "select id, vip from IPv4VS where ${whereexpr3}";
+ // 3a. look for virtual services
+ $query = "select id, vip from IPv4VS where ${whereexpr3a}";
$result = usePreparedSelectBlade ($query, $qparams_bin);
$allRows = $result->fetchAll (PDO::FETCH_ASSOC);
unset ($result);
$ret[$ip_bin]['vslist'][] = $row['id'];
}
+ // 3b. look for virtual service groups
+ $query = "select vs_id, vip from VSIPs where ${whereexpr3b}";
+ $result = usePreparedSelectBlade ($query, $qparams_bin);
+ $allRows = $result->fetchAll (PDO::FETCH_ASSOC);
+ unset ($result);
+ foreach ($allRows as $row)
+ {
+ $ip_bin = $row['vip'];
+ if (!isset ($ret[$ip_bin]))
+ $ret[$ip_bin] = constructIPAddress ($ip_bin);
+ $ret[$ip_bin]['vsglist'][] = $row['vs_id'];
+ }
+
// 4. don't forget about real servers along with pools
$query = "select rsip, rspool_id from IPv4RS where ${whereexpr4}";
$result = usePreparedSelectBlade ($query, $qparams_bin);
$or = '';
$whereexpr1 = '(';
$whereexpr2 = '(';
- $whereexpr3 = '(';
+ $whereexpr3a = '(';
+ $whereexpr3b = '(';
$whereexpr4 = '(';
$whereexpr6 = '(';
$qparams = array();
{
$whereexpr1 .= $or . "ip between ? and ?";
$whereexpr2 .= $or . "ip between ? and ?";
- $whereexpr3 .= $or . "vip between ? and ?";
+ $whereexpr3a .= $or . "vip between ? and ?";
+ $whereexpr3b .= $or . "vip between ? and ?";
$whereexpr4 .= $or . "rsip between ? and ?";
$whereexpr6 .= $or . "l.ip between ? and ?";
$or = ' or ';
}
$whereexpr1 .= ')';
$whereexpr2 .= ')';
- $whereexpr3 .= ')';
+ $whereexpr3a .= ')';
+ $whereexpr3b .= ')';
$whereexpr4 .= ')';
$whereexpr6 .= ')';
);
}
- // 3. look for virtual services
- $query = "select id, vip from IPv4VS where ${whereexpr3}";
+ // 3a. look for virtual services
+ $query = "select id, vip from IPv4VS where ${whereexpr3a}";
$result = usePreparedSelectBlade ($query, $qparams);
$allRows = $result->fetchAll (PDO::FETCH_ASSOC);
unset ($result);
$ret[$ip_bin]['vslist'][] = $row['id'];
}
+ // 3b. look for virtual service groups
+ $query = "select vs_id, vip from VSIPs where ${whereexpr3b}";
+ $result = usePreparedSelectBlade ($query, $qparams);
+ $allRows = $result->fetchAll (PDO::FETCH_ASSOC);
+ unset ($result);
+ foreach ($allRows as $row)
+ {
+ $ip_bin = $row['vip'];
+ if (!isset ($ret[$ip_bin]))
+ $ret[$ip_bin] = constructIPAddress ($ip_bin);
+ $ret[$ip_bin]['vsglist'][] = $row['vs_id'];
+ }
+
// 4. don't forget about real servers along with pools
$query = "select rsip, rspool_id from IPv4RS where ${whereexpr4}";
$result = usePreparedSelectBlade ($query, $qparams);
$ret[] = array ('tag' => '$unused');
$ret[] = array ('tag' => '$type_' . strtolower ($cell['proto'])); // $type_tcp, $type_udp or $type_mark
break;
+ case 'ipvs':
+ $ret[] = array ('tag' => '$ipvsid_' . $cell['id']);
+ $ret[] = array ('tag' => '$any_vs');
+ break;
case 'ipv4rspool':
$ret[] = array ('tag' => '$ipv4rspid_' . $cell['id']);
$ret[] = array ('tag' => '$any_ipv4rsp');
'ipv6net' => 'ipv6net',
'ipv4rspool' => 'ipv4rspool',
'ipv4vs' => 'ipv4vs',
+ 'ipvs' => 'ipvs',
'object' => 'object',
'rack' => 'rack',
'row' => 'row',
'reserved' => 'no',
'allocs' => array(),
'vslist' => array(),
+ 'vsglist' => array(),
'rsplist' => array(),
);
return 'IPv4 RS Pool';
case 'ipv4vs':
return 'IPv4 Virtual Service';
+ case 'ipvs':
+ return 'IP Virtual Service';
case 'object':
return 'Object';
case 'rack':
return $single[0];
}
+// returns array of key-value pairs from array $a such that keys are not present in $b
+function array_sub ($a, $b)
+{
+ $ret = array();
+ foreach ($a as $key => $value)
+ if (! array_key_exists($key, $b))
+ $ret[$key] = $value;
+ return $ret;
+}
+
// Registers additional ophandler on page-tab-opname triplet.
// Valid $method values are 'before' and 'after'.
// 'before' puts your ophandler in the beginning of the list (and thus before the default)
case 'ipv4vs':
$ret[$entity['id']] = $entity['name'] . (strlen ($entity['name']) ? ' ' : '') . '(' . $entity['dname'] . ')';
break;
+ case 'ipvs':
+ $ret[$entity['id']] = $entity['name'];
+ break;
case 'ipv4rspool':
$ret[$entity['id']] = $entity['name'];
break;
return FALSE;
}
+function nullEmptyStr ($str)
+{
+ return strlen ($str) ? $str : NULL;
+}
+
?>
require_once 'remote.php';
require_once 'caching.php';
require_once 'slb.php';
+require_once 'slbv2.php';
// secret.php may be missing, in which case this is a special fatal error
if (! fileSearchExists ($path_to_secret_php))
CREATE TABLE `EntityLink` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `parent_entity_type` enum('ipv4net','ipv4rspool','ipv4vs','ipv6net','location','object','rack','row','user') NOT NULL,
+ `parent_entity_type` enum('ipv4net','ipv4rspool','ipv4vs','ipvs','ipv6net','location','object','rack','row','user') NOT NULL,
`parent_entity_id` int(10) unsigned NOT NULL,
`child_entity_type` enum('file','location','object','rack','row') NOT NULL,
`child_entity_id` int(10) unsigned NOT NULL,
CREATE TABLE `FileLink` (
`id` int(10) unsigned NOT NULL auto_increment,
`file_id` int(10) unsigned NOT NULL,
- `entity_type` enum('ipv4net','ipv4rspool','ipv4vs','ipv6net','location','object','rack','row','user') NOT NULL default 'object',
+ `entity_type` enum('ipv4net','ipv4rspool','ipv4vs','ipvs','ipv6net','location','object','rack','row','user') NOT NULL default 'object',
`entity_id` int(10) NOT NULL,
PRIMARY KEY (`id`),
KEY `FileLink-file_id` (`file_id`),
) ENGINE=InnoDB;
CREATE TABLE `TagStorage` (
- `entity_realm` enum('file','ipv4net','ipv4rspool','ipv4vs','ipv6net','location','object','rack','user','vst') NOT NULL default 'object',
+ `entity_realm` enum('file','ipv4net','ipv4rspool','ipv4vs','ipvs','ipv6net','location','object','rack','user','vst') NOT NULL default 'object',
`entity_id` int(10) unsigned NOT NULL,
`tag_id` int(10) unsigned NOT NULL default '0',
`tag_is_assignable` enum('yes','no') NOT NULL DEFAULT 'yes',
PRIMARY KEY (`vlan_id`)
) ENGINE=InnoDB;
+CREATE TABLE `VS` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` char(255) DEFAULT NULL,
+ `vsconfig` text,
+ `rsconfig` text,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB;
+
+CREATE TABLE `VSIPs` (
+ `vs_id` int(10) unsigned NOT NULL,
+ `vip` varbinary(16) NOT NULL,
+ `vsconfig` text,
+ `rsconfig` text,
+ PRIMARY KEY (`vs_id`,`vip`),
+ KEY `vip` (`vip`),
+ CONSTRAINT `VSIPs-vs_id` FOREIGN KEY (`vs_id`) REFERENCES `VS` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+CREATE TABLE `VSPorts` (
+ `vs_id` int(10) unsigned NOT NULL,
+ `proto` enum('TCP','UDP','MARK') CHARACTER SET latin1 NOT NULL,
+ `vport` int(10) unsigned NOT NULL,
+ `vsconfig` text,
+ `rsconfig` text,
+ PRIMARY KEY (`vs_id`,`proto`,`vport`),
+ CONSTRAINT `VS-vs_id` FOREIGN KEY (`vs_id`) REFERENCES `VS` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+CREATE TABLE `VSEnabledIPs` (
+ `object_id` int(10) unsigned NOT NULL,
+ `vs_id` int(10) unsigned NOT NULL,
+ `vip` varbinary(16) NOT NULL,
+ `rspool_id` int(10) unsigned NOT NULL,
+ `prio` varchar(255) DEFAULT NULL,
+ `vsconfig` text,
+ `rsconfig` text,
+ PRIMARY KEY (`object_id`,`vs_id`,`vip`),
+ KEY `vip` (`vip`),
+ KEY `VSEnabledIPs-FK-vs_id-vip` (`vs_id`,`vip`),
+ KEY `VSEnabledIPs-FK-rspool_id` (`rspool_id`),
+ CONSTRAINT `VSEnabledIPs-FK-rspool_id` FOREIGN KEY (`rspool_id`) REFERENCES `IPv4RSPool` (`id`) ON DELETE CASCADE,
+ CONSTRAINT `VSEnabledIPs-FK-vs_id-vip` FOREIGN KEY (`vs_id`, `vip`) REFERENCES `VSIPs` (`vs_id`, `vip`) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+CREATE TABLE `VSEnabledPorts` (
+ `object_id` int(10) unsigned NOT NULL,
+ `vs_id` int(10) unsigned NOT NULL,
+ `proto` enum('TCP','UDP','MARK') CHARACTER SET latin1 NOT NULL,
+ `vport` int(10) unsigned NOT NULL,
+ `rspool_id` int(10) unsigned NOT NULL,
+ `vsconfig` text,
+ `rsconfig` text,
+ PRIMARY KEY (`object_id`,`vs_id`,`proto`,`vport`),
+ KEY `VSEnabledPorts-FK-vs_id-proto-vport` (`vs_id`,`proto`,`vport`),
+ KEY `VSEnabledPorts-FK-rspool_id` (`rspool_id`),
+ CONSTRAINT `VSEnabledPorts-FK-object_id` FOREIGN KEY (`object_id`) REFERENCES `Object` (`id`) ON DELETE CASCADE,
+ CONSTRAINT `VSEnabledPorts-FK-rspool_id` FOREIGN KEY (`rspool_id`) REFERENCES `IPv4RSPool` (`id`) ON DELETE CASCADE,
+ CONSTRAINT `VSEnabledPorts-FK-vs_id-proto-vport` FOREIGN KEY (`vs_id`, `proto`, `vport`) REFERENCES `VSPorts` (`vs_id`, `proto`, `vport`) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
CREATE VIEW `Location` AS SELECT O.id, O.name, O.has_problems, O.comment, P.id AS parent_id, P.name AS parent_name
FROM `Object` O
LEFT JOIN (
finishPortlet();
}
+ renderSLBTriplets2 ($info);
renderSLBTriplets ($info);
echo "</td>\n";
echo $delim . mkA ("${vs['name']}:${vs['vport']}/${vs['proto']}", 'ipv4vs', $vs['id']) . '→';
$delim = '<br>';
}
+ foreach ($addr['vsglist'] as $vs_id)
+ {
+ $vs = spotEntity ('ipvs', $vs_id);
+ echo $delim . mkA ($vs['name'], 'ipvs', $vs['id']) . '→';
+ $delim = '<br>';
+ }
foreach ($addr['rsplist'] as $rsp_id)
{
$rsp = spotEntity ('ipv4rspool', $rsp_id);
echo $delim . mkA ("${vs['name']}:${vs['vport']}/${vs['proto']}", 'ipv4vs', $vs['id']) . '→';
$delim = '<br>';
}
+ foreach ($addr['vsglist'] as $vs_id)
+ {
+ $vs = spotEntity ('ipvs', $vs_id);
+ echo $delim . mkA ($vs['name'], 'ipvs', $vs['id']) . '→';
+ $delim = '<br>';
+ }
foreach ($addr['rsplist'] as $rsp_id)
{
$rsp = spotEntity ('ipv4rspool', $rsp_id);
renderEntitySummary ($address, 'summary', $summary);
// render SLB portlet
- if (! empty ($address['vslist']) or ! empty ($address['rsplist']))
+ if (! empty ($address['vslist']) or ! empty ($address['vsglist']) or ! empty ($address['rsplist']))
{
startPortlet ("");
+ if (! empty ($address['vsglist']))
+ {
+ printf ("<h2>virtual service groups (%d):</h2>", count ($address['vsglist']));
+ foreach ($address['vsglist'] as $vsg_id)
+ renderSLBEntityCell (spotEntity ('ipvs', $vsg_id));
+ }
+
if (! empty ($address['vslist']))
{
printf ("<h2>virtual services (%d):</h2>", count ($address['vslist']));
finishPortlet();
}
- if (! empty ($address['vslist']) or ! empty ($address['rsplist']))
+ if (! empty ($address['rsplist']))
+ {
+ startPortlet ("RS pools:");
+ foreach ($address['rsplist'] as $rsp_id)
+ {
+ renderSLBEntityCell (spotEntity ('ipv4rspool', $rsp_id));
+ echo '<br>';
+ }
+ finishPortlet();
+ }
+
+ if (! empty ($address['vsglist']))
+ foreach ($address['vsglist'] as $vsg_id)
+ renderSLBTriplets2 (spotEntity ('ipvs', $vsg_id), FALSE, $ip_bin);
+
+ if (! empty ($address['vslist']))
renderSLBTriplets ($address);
foreach (array ('outpf' => 'departing NAT rules', 'inpf' => 'arriving NAT rules') as $key => $title)
echo "</td></tr></table>";
break;
case 'ipv4vs':
+ case 'ipvs':
case 'ipv4rspool':
renderSLBEntityCell ($cell);
break;
$self = __FUNCTION__;
global $page;
$path = array();
+ $page_name = preg_replace ('/:.*/', '', $targetno);
// Recursion breaks at first parentless page.
- if ($targetno == 'ipaddress')
+ if ($page_name == 'ipaddress')
{
// case ipaddress is a universal v4/v6 page, it has two parents and requires special handling
$ip_bin = ip_parse ($_REQUEST['ip']);
$path = $self ($parent);
$path[] = $targetno;
}
- elseif (!isset ($page[$targetno]['parent']))
+ elseif (!isset ($page[$page_name]['parent']))
$path = array ($targetno);
else
{
- $path = $self ($page[$targetno]['parent']);
+ $path = $self ($page[$page_name]['parent']);
$path[] = $targetno;
}
return $path;
}
- global $page;
+ global $page, $tab;
// Path.
$path = getPath ($pageno);
$items = array();
foreach (array_reverse ($path) as $no)
{
- if (isset ($page[$no]['title']))
+ if (preg_match ('/(.*):(.*)/', $no, $m) && isset ($tab[$m[1]][$m[2]]))
+ $title = array
+ (
+ 'name' => $tab[$m[1]][$m[2]],
+ 'params' => array('page' => $m[1], 'tab' => $m[2]),
+ );
+ elseif (isset ($page[$no]['title']))
$title = array
(
'name' => $page[$no]['title'],
'name' => $vs_info['dname'],
'params' => array ('vs_id' => $vs_info['id'])
);
+ case 'ipvs':
+ $vs_info = spotEntity ('ipvs', assertUIntArg ('vs_id'));
+ return array
+ (
+ 'name' => $vs_info['name'],
+ 'params' => array ('vs_id' => $vs_info['id'])
+ );
case 'object':
$object = spotEntity ('object', assertUIntArg ('object_id'));
return array
$tab['object']['livecdp'] = 'Live CDP';
$tab['object']['livelldp'] = 'Live LLDP';
$tab['object']['snmpportfinder'] = 'SNMP sync';
-$tab['object']['editrspvs'] = 'RS pools';
+$tab['object']['editrspvs'] = 'VS links';
+$tab['object']['editvslinks'] = 'VSG links';
$tab['object']['lvsconfig'] = 'keepalived.conf';
$tab['object']['autoports'] = 'AutoPorts';
$tab['object']['tags'] = 'Tags';
$tabhandler['object']['tags'] = 'renderEntityTags';
$tabhandler['object']['files'] = 'renderFilesForEntity';
$tabhandler['object']['editrspvs'] = 'renderSLBEditTab';
+$tabhandler['object']['editvslinks'] = 'renderTripletForm';
$tabhandler['object']['8021qorder'] = 'render8021QOrderForm';
$tabhandler['object']['8021qports'] = 'renderObject8021QPorts';
$tabhandler['object']['8021qsync'] = 'renderObject8021QSync';
$trigger['object']['livelldp'] = 'trigger_LiveLLDP';
$trigger['object']['snmpportfinder'] = 'trigger_snmpportfinder';
$trigger['object']['editrspvs'] = 'trigger_isloadbalancer';
+$trigger['object']['editvslinks'] = 'trigger_isloadbalancer';
$trigger['object']['lvsconfig'] = 'trigger_isloadbalancer';
$trigger['object']['autoports'] = 'trigger_autoports';
$trigger['object']['tags'] = 'trigger_tags';
$ophandler['object']['editrspvs']['addLB'] = 'addLoadBalancer';
$ophandler['object']['editrspvs']['delLB'] = 'tableHandler';
$ophandler['object']['editrspvs']['updLB'] = 'tableHandler';
+$ophandler['object']['editvslinks']['addLink'] = 'createTriplet';
+$ophandler['object']['editvslinks']['del'] = 'removeTriplet';
+$ophandler['object']['editvslinks']['updPort'] = 'updateTripletConfig';
+$ophandler['object']['editvslinks']['updIp'] = 'updateTripletConfig';
$ophandler['object']['lvsconfig']['submitSLBConfig'] = 'submitSLBConfig';
$ophandler['object']['snmpportfinder']['querySNMPData'] = 'querySNMPData';
$ophandler['object']['8021qorder']['add'] = 'add8021QOrder';
$page['ipv4slb']['title'] = 'IP SLB';
$page['ipv4slb']['parent'] = 'index';
$tab['ipv4slb']['default'] = 'Virtual services';
+$tab['ipv4slb']['vs'] = 'VS groups';
$tab['ipv4slb']['lbs'] = 'Load balancers';
-$tab['ipv4slb']['rspools'] = 'Real server pools';
+$tab['ipv4slb']['rspools'] = 'RS pools';
$tab['ipv4slb']['rservers'] = 'Real servers';
$tab['ipv4slb']['defconfig'] = 'Default configs';
$tab['ipv4slb']['new_vs'] = 'new VS';
$tab['ipv4slb']['new_rs'] = 'new RS pool';
$tabhandler['ipv4slb']['default'] = 'renderVSList';
+$tabhandler['ipv4slb']['vs'] = 'renderVSGList';
$tabhandler['ipv4slb']['lbs'] = 'renderLBList';
$tabhandler['ipv4slb']['rspools'] = 'renderRSPoolList';
$tabhandler['ipv4slb']['rservers'] = 'renderRealServerList';
$ophandler['ipv4slb']['new_rs']['add'] = 'addRSPool';
$ophandler['ipv4slb']['defconfig']['save'] = 'updateSLBDefConfig';
-$page['ipv4vs']['parent'] = 'ipv4slb';
+$page['ipv4vs']['parent'] = 'ipv4slb:default';
$page['ipv4vs']['bypass'] = 'vs_id';
$page['ipv4vs']['bypass_type'] = 'uint';
$tab['ipv4vs']['default'] = 'View';
$tab['ipv4vs']['edit'] = 'Edit';
-$tab['ipv4vs']['editlblist'] = 'Load balancers';
+$tab['ipv4vs']['editlblist'] = 'VSG links';
$tab['ipv4vs']['tags'] = 'Tags';
$tab['ipv4vs']['files'] = 'Files';
$tabhandler['ipv4vs']['default'] = 'renderVirtualService';
$ophandler['ipv4vs']['files']['linkFile'] = 'linkFileToEntity';
$ophandler['ipv4vs']['files']['unlinkFile'] = 'unlinkFile';
-$page['ipv4rspool']['parent'] = 'ipv4slb';
+$page['ipvs']['parent'] = 'ipv4slb:vs';
+$page['ipvs']['bypass'] = 'vs_id';
+$page['ipvs']['bypass_type'] = 'uint';
+$tab['ipvs']['default'] = 'View';
+$tab['ipvs']['edit'] = 'Edit';
+$tab['ipvs']['editvslinks'] = 'VSG links';
+$tab['ipvs']['tags'] = 'Tags';
+$tab['ipvs']['files'] = 'Files';
+$tab['ipvs']['convert'] = 'Migrate';
+$tabhandler['ipvs']['default'] = 'renderVS';
+$tabhandler['ipvs']['edit'] = 'renderEditVS';
+$tabhandler['ipvs']['editvslinks'] = 'renderTripletForm';
+$tabhandler['ipvs']['tags'] = 'renderEntityTags';
+$tabhandler['ipvs']['files'] = 'renderFilesForEntity';
+$tabhandler['ipvs']['convert'] = 'renderIPVSConvert';
+$ophandler['ipvs']['edit']['updVS'] = 'updateVS';
+$ophandler['ipvs']['edit']['addIP'] = 'addIPToVS';
+$ophandler['ipvs']['edit']['addPort'] = 'addPortToVS';
+$ophandler['ipvs']['edit']['updIP'] = 'updateIPInVS';
+$ophandler['ipvs']['edit']['updPort'] = 'updatePortInVS';
+$ophandler['ipvs']['edit']['delIP'] = 'removeIPFromVS';
+$ophandler['ipvs']['edit']['delPort'] = 'removePortFromVS';
+$ophandler['ipvs']['editvslinks']['addLink'] = 'createTriplet';
+$ophandler['ipvs']['editvslinks']['del'] = 'removeTriplet';
+$ophandler['ipvs']['editvslinks']['updPort'] = 'updateTripletConfig';
+$ophandler['ipvs']['editvslinks']['updIp'] = 'updateTripletConfig';
+$ophandler['ipvs']['tags']['saveTags'] = 'saveEntityTags';
+$ophandler['ipvs']['files']['addFile'] = 'addFileToEntity';
+$ophandler['ipvs']['files']['linkFile'] = 'linkFileToEntity';
+$ophandler['ipvs']['files']['unlinkFile'] = 'unlinkFile';
+$ophandler['ipvs']['convert']['convert'] = 'doVSMigrate';
+$trigger['ipvs']['tags'] = 'trigger_tags';
+$trigger['ipvs']['convert'] = 'trigger_ipvs_convert';
+
+$page['ipv4rspool']['parent'] = 'ipv4slb:rspools';
$page['ipv4rspool']['bypass'] = 'pool_id';
$page['ipv4rspool']['bypass_type'] = 'uint';
$tab['ipv4rspool']['default'] = 'View';
$tab['ipv4rspool']['edit'] = 'Edit';
-$tab['ipv4rspool']['editlblist'] = 'Load balancers';
+$tab['ipv4rspool']['editlblist'] = 'VS links';
+$tab['ipv4rspool']['editvslinks'] = 'VSG links';
$tab['ipv4rspool']['editrslist'] = 'RS list';
$tab['ipv4rspool']['tags'] = 'Tags';
$tab['ipv4rspool']['files'] = 'Files';
$tabhandler['ipv4rspool']['default'] = 'renderRSPool';
$tabhandler['ipv4rspool']['edit'] = 'renderEditRSPool';
$tabhandler['ipv4rspool']['editrslist'] = 'renderRSPoolServerForm';
+$tabhandler['ipv4rspool']['editvslinks'] = 'renderTripletForm';
$tabhandler['ipv4rspool']['editlblist'] = 'renderSLBEditTab';
$tabhandler['ipv4rspool']['tags'] = 'renderEntityTags';
$tabhandler['ipv4rspool']['files'] = 'renderFilesForEntity';
$ophandler['ipv4rspool']['editlblist']['addLB'] = 'addLoadBalancer';
$ophandler['ipv4rspool']['editlblist']['delLB'] = 'tableHandler';
$ophandler['ipv4rspool']['editlblist']['updLB'] = 'tableHandler';
+$ophandler['ipv4rspool']['editvslinks']['addLink'] = 'createTriplet';
+$ophandler['ipv4rspool']['editvslinks']['del'] = 'removeTriplet';
+$ophandler['ipv4rspool']['editvslinks']['updPort'] = 'updateTripletConfig';
+$ophandler['ipv4rspool']['editvslinks']['updIp'] = 'updateTripletConfig';
$ophandler['ipv4rspool']['tags']['saveTags'] = 'saveEntityTags';
$ophandler['ipv4rspool']['files']['addFile'] = 'addFileToEntity';
$ophandler['ipv4rspool']['files']['linkFile'] = 'linkFileToEntity';
$ajaxhandler['upd-reservation-port'] = 'updatePortRsvAJAX';
$ajaxhandler['upd-reservation-cable'] = 'updateCableIdAJAX';
$ajaxhandler['net-usage'] = 'getNetUsageAJAX';
+$ajaxhandler['get-slb-form'] = 'renderSLBFormAJAX';
?>
return showFuncMessage (__FUNCTION__, 'OK');
}
+function updateVS ()
+{
+ $vs_id = assertUIntArg ('vs_id');
+ $name = assertStringArg ('name');
+ $vsconfig = nullEmptyStr (assertStringArg ('vsconfig', TRUE));
+ $rsconfig = nullEmptyStr (assertStringArg ('rsconfig', TRUE));
+
+ usePreparedUpdateBlade ('VS', array ('name' => $name, 'vsconfig' => $vsconfig, 'rsconfig' => $rsconfig), array ('id' => $vs_id));
+ showSuccess ("Service updated successfully");
+}
+
+function addIPToVS()
+{
+ $ip_bin = assertIPArg ('ip');
+ $vsinfo = spotEntity ('ipvs', assertUIntArg ('vs_id'));
+ amplifyCell ($vsinfo);
+ $row = array ('vs_id' => $vsinfo['id'], 'vip' => $ip_bin, 'vsconfig' => NULL, 'rsconfig' => NULL);
+ if ($vip = isVIPEnabled ($row, $vsinfo['vips']))
+ return showError ("Service already contains IP " . formatVSIP ($vip));
+ usePreparedInsertBlade ('VSIPs', $row);
+ showSuccess ("IP addded");
+}
+
+function addPortToVS()
+{
+ global $vs_proto;
+ $proto = assertStringArg ('proto');
+ if (! in_array ($proto, $vs_proto))
+ throw new InvalidRequestArgException ('proto', "Invalid VS protocol");
+ $vport = assertUIntArg ('port', TRUE);
+ if ($proto == 'MARK')
+ {
+ if ($vport > 0xFFFFFFFF)
+ return showError ("fwmark value is too large");
+ }
+ else
+ if ($vport == 0 || $vport >= 0xFFFF)
+ return showError ("Invalid $proto port value");
+
+ $vsinfo = spotEntity ('ipvs', assertUIntArg ('vs_id'));
+ amplifyCell ($vsinfo);
+ $row = array ('vs_id' => $vsinfo['id'], 'proto' => $proto, 'vport' => $vport, 'vsconfig' => NULL, 'rsconfig' => NULL);
+ if ($port = isPortEnabled ($row, $vsinfo['ports']))
+ return showError ("Service already contains port " . formatVSPort ($port));
+ usePreparedInsertBlade ('VSPorts', $row);
+ showSuccess ("port addded");
+}
+
+function updateIPInVS()
+{
+ $vs_id = assertUIntArg ('vs_id');
+ $ip_bin = assertIPArg ('ip');
+ $vsconfig = nullEmptyStr (assertStringArg ('vsconfig', TRUE));
+ $rsconfig = nullEmptyStr (assertStringArg ('rsconfig', TRUE));
+ if (usePreparedUpdateBlade ('VSIPs', array ('vsconfig' => $vsconfig, 'rsconfig' => $rsconfig), array ('vs_id' => $vs_id, 'vip' => $ip_bin)))
+ showSuccess ("IP configuration updated");
+ else
+ showNotice ("Nothing changed");
+}
+
+function updatePortInVS()
+{
+ $vs_id = assertUIntArg ('vs_id');
+ $proto = assertStringArg ('proto');
+ $vport = assertUIntArg ('port', TRUE);
+ $vsconfig = nullEmptyStr (assertStringArg ('vsconfig', TRUE));
+ $rsconfig = nullEmptyStr (assertStringArg ('rsconfig', TRUE));
+ if (usePreparedUpdateBlade ('VSPorts', array ('vsconfig' => $vsconfig, 'rsconfig' => $rsconfig), array ('vs_id' => $vs_id, 'proto' => $proto, 'vport' => $vport)))
+ showSuccess ("Port configuration updated");
+ else
+ showNotice ("Nothing changed");
+}
+
+function removeIPFromVS()
+{
+ $vip = array ('vip' => assertIPArg ('ip'));
+ $vsinfo = spotEntity ('ipvs', assertUIntArg ('vs_id'));
+ amplifyCell ($vsinfo);
+ $used = 0;
+ foreach (getTriplets ($vsinfo) as $triplet)
+ if (isVIPEnabled ($vip, $triplet['vips']))
+ $used++;
+ if (usePreparedDeleteBlade ('VSIPs', array ('vs_id' => $vsinfo['id']) + $vip))
+ showSuccess ("IP removed" . ($used ? ", it was binded with $used SLBs" : ''));
+ else
+ showNotice ("Nothing changed");
+}
+
+function removePortFromVS()
+{
+ $port = array ('proto' => assertStringArg ('proto'), 'vport' => assertUIntArg ('port', TRUE));
+ $vsinfo = spotEntity ('ipvs', assertUIntArg ('vs_id'));
+ amplifyCell ($vsinfo);
+ $used = 0;
+ foreach (getTriplets ($vsinfo) as $triplet)
+ if (isPortEnabled ($port, $triplet['ports']))
+ $used++;
+ if (usePreparedDeleteBlade ('VSPorts', array ('vs_id' => $vsinfo['id']) + $port))
+ showSuccess ("Port removed" . ($used ? ", it was binded with $used SLBs" : ''));
+ else
+ showNotice ("Nothing changed");
+}
+
+function updateTripletConfig()
+{
+ $key_fields = array
+ (
+ 'object_id' => assertUIntArg ('object_id'),
+ 'vs_id' => assertUIntArg ('vs_id'),
+ 'rspool_id' => assertUIntArg ('rspool_id'),
+ );
+ $config_fields = array
+ (
+ 'vsconfig' => nullEmptyStr (assertStringArg ('vsconfig', TRUE)),
+ 'rsconfig' => nullEmptyStr (assertStringArg ('rsconfig', TRUE)),
+ );
+
+ $vsinfo = spotEntity ('ipvs', $key_fields['vs_id']);
+ amplifyCell ($vsinfo);
+ $found = FALSE;
+
+ if ($_REQUEST['op'] == 'updPort')
+ {
+ $table = 'VSEnabledPorts';
+ $proto = assertStringArg ('proto');
+ $vport = assertUIntArg ('port', TRUE);
+ $key_fields['proto'] = $proto;
+ $key_fields['vport'] = $vport;
+ $key = "Port $proto-$vport";
+ // check if such port exists in VS
+ foreach ($vsinfo['ports'] as $vs_port)
+ if ($vs_port['proto'] == $proto && $vs_port['vport'] == $vport)
+ {
+ $found = TRUE;
+ break;
+ }
+ }
+ else
+ {
+ $table = 'VSEnabledIPs';
+ $vip = assertIPArg ('vip');
+ $config_fields['prio'] = nullEmptyStr (assertStringArg ('prio', TRUE));
+ $key_fields['vip'] = $vip;
+ $key = "IP " . ip_format ($vip);
+ // check if such VIP exists in VS
+ foreach ($vsinfo['vips'] as $vs_vip)
+ if ($vs_vip['vip'] === $vip)
+ {
+ $found = TRUE;
+ break;
+ }
+ }
+ if (! $found)
+ return showError ("$key not found in VS");
+
+ $nchanged = 0;
+ if (! isCheckSet ('enabled'))
+ {
+ if ($nchanged += usePreparedDeleteBlade ($table, $key_fields))
+ return showSuccess ("$key disabled");
+ }
+ else
+ {
+ global $dbxlink;
+ $dbxlink->beginTransaction();
+ $q = "SELECT * FROM $table WHERE";
+ $sep = '';
+ $params = array();
+ foreach ($key_fields as $field => $value)
+ {
+ $q .= " $sep $field = ?";
+ $params[] = $value;
+ $sep = 'AND';
+ }
+ $result = usePreparedSelectBlade ("$q FOR UPDATE", $params);
+ $row = $result->fetch (PDO::FETCH_ASSOC);
+ unset ($result);
+ if ($row)
+ {
+ if ($nchanged += usePreparedUpdateBlade ($table, $config_fields, $key_fields))
+ showSuccess ("$key config updated");
+ }
+ else
+ {
+ $constraint_failed = FALSE;
+ // search VIP in some other triplet on this balancer
+ if (isset ($vip))
+ {
+ $result = usePreparedSelectBlade ("SELECT * FROM $table WHERE object_id = ? AND vip = ?", array ($key_fields['object_id'], $vip));
+ if ($result->fetch (PDO::FETCH_ASSOC))
+ {
+ $constraint_failed = TRUE;
+ showError ("$key is already used on this server");
+ }
+ }
+ if (! $constraint_failed)
+ if ($nchanged += usePreparedInsertBlade ($table, $key_fields + $config_fields))
+ showSuccess ("$key enabled");
+ }
+ $dbxlink->commit();
+ }
+ if (! $nchanged)
+ showNotice ("No changes made");
+}
+
+function removeTriplet()
+{
+ $key_fields = array
+ (
+ 'object_id' => assertUIntArg ('object_id'),
+ 'vs_id' => assertUIntArg ('vs_id'),
+ 'rspool_id' => assertUIntArg ('rspool_id'),
+ );
+
+ global $dbxlink;
+ $dbxlink->beginTransaction();
+ usePreparedDeleteBlade ('VSEnabledIPs', $key_fields);
+ usePreparedDeleteBlade ('VSEnabledPorts', $key_fields);
+ $dbxlink->commit();
+ showSuccess ('Triplet deleted');
+}
+
+function createTriplet()
+{
+ $object_id = assertUIntArg ('object_id');
+ $vs_id = assertUIntArg ('vs_id');
+ $rspool_id = assertUIntArg ('rspool_id');
+ $vips = array_key_exists ('enabled_vips', $_REQUEST) ? genericAssertion ('enabled_vips', 'array') : array();
+ $ports = array_key_exists ('enabled_ports', $_REQUEST) ? genericAssertion ('enabled_ports', 'array') : array();
+ if (getTriplet ($object_id, $vs_id, $rspool_id))
+ return showError ("SLB triplet already exists");
+
+ $vsinfo = spotEntity ('ipvs', $vs_id);
+ amplifyCell ($vsinfo);
+ foreach ($vsinfo['vips'] as $vip)
+ if (in_array (ip_format ($vip['vip']), $vips))
+ usePreparedInsertBlade ('VSEnabledIPs', array ('object_id' => $object_id, 'vs_id' => $vs_id, 'rspool_id' => $rspool_id, 'vip' => $vip['vip']));
+ foreach ($vsinfo['ports'] as $port)
+ if (in_array($port['proto'] . '-' . $port['vport'], $ports))
+ usePreparedInsertBlade ('VSEnabledPorts', array ('object_id' => $object_id, 'vs_id' => $vs_id, 'rspool_id' => $rspool_id, 'proto' => $port['proto'], 'vport' => $port['vport']));
+ showSuccess ("SLB triplet created");
+}
+
$msgcode['addLoadBalancer']['OK'] = 48;
function addLoadBalancer ()
{
return buildRedirectURL ('ipv4rspool', 'default', array ('pool_id' => $new_id));
}
+function doVSMigrate()
+{
+ global $dbxlink;
+ $vs_id = assertUIntArg ('vs_id');
+ $vs_cell = spotEntity ('ipvs', $vs_id);
+ amplifyCell ($vs_cell);
+ $tag_ids = genericAssertion ('taglist', 'array');
+ $old_vs_list = genericAssertion ('vs_list', 'array');
+ $plan = callHook ('buildVSMigratePlan', $vs_id, $old_vs_list);
+
+ $dbxlink->beginTransaction();
+
+ // remove all triplets
+ usePreparedDeleteBlade ('VSEnabledIPs', array ('vs_id' => $vs_id));
+ usePreparedDeleteBlade ('VSEnabledPorts', array ('vs_id' => $vs_id));
+
+ // remove all VIPs and ports which are in $plan,and create new ones
+ foreach ($plan['vips'] as $vip)
+ {
+ usePreparedDeleteBlade ('VSIPs', array ('vs_id' => $vs_id, 'vip' => $vip['vip']));
+ usePreparedInsertBlade ('VSIPs', array ('vs_id' => $vs_id) + $vip);
+ }
+ foreach ($plan['ports'] as $port)
+ {
+ usePreparedDeleteBlade ('VSPorts', array ('vs_id' => $vs_id, 'proto' => $port['proto'], 'vport' => $port['vport']));
+ usePreparedInsertBlade ('VSPorts', array ('vs_id' => $vs_id) + $port);
+ }
+
+ // create triplets
+ foreach ($plan['triplets'] as $triplet)
+ {
+ $tr_key = array
+ (
+ 'vs_id' => $triplet['vs_id'],
+ 'object_id' => $triplet['object_id'],
+ 'rspool_id' => $triplet['rspool_id'],
+ );
+
+ foreach ($triplet['ports'] as $port)
+ usePreparedInsertBlade ('VSEnabledPorts', $tr_key + $port);
+ foreach ($triplet['vips'] as $vip)
+ usePreparedInsertBlade ('VSEnabledIPs', $tr_key + $vip);
+ }
+
+ // update configs
+ usePreparedUpdateBlade ('VS', $plan['properties'], array ('id' => $vs_id));
+
+ // replace tags
+ global $taglist;
+ $chain = array();
+ foreach ($tag_ids as $tid)
+ if (! isset ($taglist[$tid]))
+ {
+ $dbxlink->rollback();
+ showError ("Unknown tag id $tid");
+ }
+ else
+ $chain[] = $taglist[$tid];
+ rebuildTagChainForEntity ('ipvs', $vs_id, $chain, TRUE);
+
+ $dbxlink->commit();
+ showSuccess ("old VS configs were copied to VS group");
+ return buildRedirectURL (NULL, 'default');
+}
+
# validate user input and produce SQL columns per the opspec descriptor
function buildOpspecColumns ($opspec, $listname)
{
# framework. See accompanying file "COPYING" for the full copyright and
# licensing information.
+require_once 'slb2-interface.php';
+
function renderSLBDefConfig()
{
$defaults = getSLBDefaults();
echo $cell['dname'] . "</a></td></tr><tr><td>";
echo $cell['name'] . '</td></tr>';
break;
+ case 'ipvs':
+ echo "<tr><td rowspan=3 width='5%'>";
+ printImageHREF ('VS');
+ echo "</td><td>";
+ echo "<a class='$a_class' href='index.php?page=ipvs&vs_id=${cell['id']}'>";
+ echo $cell['name'] . "</a></td></tr>";
+ break;
case 'ipv4rspool':
echo "<tr><td>";
echo "<a class='$a_class' href='index.php?page=ipv4rspool&pool_id=${cell['id']}'>";
}
echo "</td><td class=pcright>\n";
+ renderSLBTriplets2 ($poolInfo);
renderSLBTriplets ($poolInfo);
echo "</td></tr><tr><td colspan=2>\n";
renderFilesPortlet ('ipv4rspool', $pool_id);
global $triplet_class;
foreach ($rows as $row)
{
+ $row['vsconfig'] = dos2unix ($row['vsconfig']);
+ $row['rsconfig'] = dos2unix ($row['rsconfig']);
$triplet = new $triplet_class ($row['object_id'], $row['vs_id'], $row['rspool_id'], $row);
$triplet->display_cells = $display_cells;
$ret[] = $triplet;
$gl_parser->addMacro ('GLOBAL_VS_CONF', dos2unix ($defaults['vsconfig']));
$gl_parser->addMacro ('GLOBAL_RS_CONF', dos2unix ($defaults['rsconfig']));
$gl_parser->addMacro ('RSPORT', '%VPORT%');
+ $gl_parser->addMacro ('VS_PREPEND',
+"# LB (id == %LB_ID%): %LB_NAME%
+# VS (id == %VS_ID%): %VS_NAME%
+# RS (id == %RSP_ID%): %RSP_NAME%");
// group triplets by object_id, vs_id
$grouped = array();
$rs_parser->addMacro ('SLB_RS_CONF', dos2unix ($triplet->slb['rsconfig']));
$ret .= $rs_parser->expand ("
-# LB (id == %LB_ID%): %LB_NAME%
-# VS (id == %VS_ID%): %VS_NAME%
-# RS (id == %RSP_ID%): %RSP_NAME%
+%VS_PREPEND%
virtual_server %VS_HEADER% {
protocol %PROTO%
%GLOBAL_VS_CONF%
function buildEntityLVSConfig ($cell)
{
$ret = "#\n#\n# This configuration has been generated automatically by RackTables\n#\n#\n";
- $ret .= generateSLBConfig (SLBTriplet::getTriplets ($cell));
+ // slbv2
+ if ($cell['realm'] != 'ipv4vs')
+ $ret .= generateSLBConfig2 (getTriplets ($cell));
+ // slbv1
+ if ($cell['realm'] != 'ipvs')
+ $ret .= generateSLBConfig (SLBTriplet::getTriplets ($cell));
return $ret;
}
while ($row = $result->fetch (PDO::FETCH_ASSOC))
{
$row['rsip'] = ip_format ($row['rsip_bin']);
+ $row['rsconfig'] = dos2unix ($row['rsconfig']);
foreach (array ('inservice', 'rsip_bin', 'rsip', 'rsport', 'rspool_id', 'rsconfig') as $cname)
$ret[$row['id']][$cname] = $row[$cname];
}
--- /dev/null
+<?php
+
+# This file is a part of RackTables, a datacenter and server room management
+# framework. See accompanying file "COPYING" for the full copyright and
+# licensing information.
+
+function renderVSGList ()
+{
+ renderCellList ('ipvs', 'VS groups');
+}
+
+function formatVSPort ($port, $plain_text = FALSE)
+{
+ if ($port['proto'] == 'MARK')
+ return 'fwmark ' . $port['vport'];
+ $proto = strtolower ($port['proto']);
+ $name = $port['vport'] . '/' . $proto;
+ if (!$plain_text && NULL !== ($srv = getservbyport ($port['vport'], $proto)))
+ return '<span title="' . $name . '">' . $srv . '</span>';
+ else
+ return $name;
+}
+
+function formatVSIP ($vip, $plain_text = FALSE)
+{
+ $fmt_ip = ip_format ($vip['vip']);
+ if ($plain_text)
+ return $fmt_ip;
+ $ret = '<a href="' . makeHref (array ('page' => 'ipaddress', 'ip' => $fmt_ip)) . '">' . $fmt_ip . '</a>';
+ return $ret;
+}
+
+function renderVS ($vsid)
+{
+ $vsinfo = spotEntity ('ipvs', $vsid);
+ amplifyCell ($vsinfo);
+
+ echo '<table border=0 class=objectview cellspacing=0 cellpadding=0>';
+ if (strlen ($vsinfo['name']))
+ echo "<tr><td colspan=2 align=center><h1>${vsinfo['name']}</h1></td></tr>\n";
+ echo '<tr>';
+
+ echo '<td class=pcleft>';
+ $summary = array();
+ $summary['Name'] = $vsinfo['name'] . getPopupSLBConfig ($vsinfo);
+ $summary['tags'] = '';
+
+ $ips = '<ul class="slb-checks">';
+ foreach ($vsinfo['vips'] as $vip)
+ $ips .= '<li>' . formatVSIP ($vip) . getPopupSLBConfig ($vip) . '</li>';
+ $ips .= '</ul>';
+ $summary['IPs'] = $ips;
+
+ $ports = '<ul class="slb-checks">';
+ foreach ($vsinfo['ports'] as $port)
+ $ports .= '<li>' . formatVSPort ($port) . getPopupSLBConfig ($port) . '</li>';
+ $ports .= '</ul>';
+ $summary['Ports'] = $ports;
+
+ renderEntitySummary ($vsinfo, 'Summary', $summary);
+ echo '</td>';
+
+ echo '<td class=pcright>';
+ renderSLBTriplets2 ($vsinfo);
+ echo '</td></tr><tr><td colspan=2>';
+ renderFilesPortlet ('ipvs', $vsid);
+ echo '</tr><table>';
+}
+
+function renderTripletForm ($bypass_id)
+{
+ global $pageno, $etype_by_pageno;
+ $cell = spotEntity ($etype_by_pageno[$pageno], $bypass_id);
+ renderSLBTriplets2 ($cell, TRUE);
+}
+
+// either $port of $vip argument should be NULL
+function renderPopupTripletForm ($triplet, $port, $vip, $row)
+{
+ printOpFormIntro (isset ($port) ? 'updPort' : 'updIp');
+ echo '<input type=hidden name=object_id value="' . htmlspecialchars ($triplet['object_id'], ENT_QUOTES) . '">';
+ echo '<input type=hidden name=vs_id value="' . htmlspecialchars ($triplet['vs_id'], ENT_QUOTES) . '">';
+ echo '<input type=hidden name=rspool_id value="' . htmlspecialchars ($triplet['rspool_id'], ENT_QUOTES) . '">';
+ echo '<p><label><input type=checkbox name=enabled' . (is_array ($row) ? ' checked' : '') . '> ' . (isset ($port) ? 'Enable port' : 'Enable IP') . '</label>';
+ if (isset ($port))
+ {
+ echo '<input type=hidden name=proto value="' . htmlspecialchars ($port['proto'], ENT_QUOTES) . '">';
+ echo '<input type=hidden name=port value="' . htmlspecialchars ($port['vport'], ENT_QUOTES) . '">';
+ }
+ else
+ {
+ echo '<input type=hidden name=vip value="' . htmlspecialchars (ip_format ($vip['vip']), ENT_QUOTES) . '">';
+ echo '<p><label>Priority:<br><input type=text name=prio value="' . htmlspecialchars (is_array ($row) ? $row['prio'] : '', ENT_QUOTES) . '"></label>';
+ }
+ echo '<p><label>VS config:<br>';
+ echo '<textarea name=vsconfig rows=3 cols=80>' . htmlspecialchars (is_array ($row) ? $row['vsconfig'] : '') . '</textarea></label>';
+ echo '<p><label>RS config:<br>';
+ echo '<textarea name=rsconfig rows=3 cols=80>' . htmlspecialchars (is_array ($row) ? $row['rsconfig'] : '') . '</textarea></label>';
+ echo '<p align=center>' . getImageHREF ('SAVE', 'Save changes', TRUE);
+ echo '</form>';
+}
+
+function renderPopupVSPortForm ($port, $used = 0)
+{
+ $keys = array ('proto' => $port['proto'], 'port' => $port['vport']);
+ $title = 'remove port ' . formatVSPort ($port) . ($used ? " (used $used times)" : '');
+ printOpFormIntro ('updPort', $keys);
+ echo '<p align=center>';
+ echo getOpLink (array ('op' => 'delPort') + $keys, $title, 'destroy', '', ($used ? 'del-used-slb' : '') );
+ echo '<p><label>VS config:<br>';
+ echo '<textarea name=vsconfig rows=3 cols=80>' . htmlspecialchars ($port['vsconfig']) . '</textarea></label>';
+ echo '<p><label>RS config:<br>';
+ echo '<textarea name=rsconfig rows=3 cols=80>' . htmlspecialchars ($port['rsconfig']) . '</textarea></label>';
+ echo '<p align=center>' . getImageHREF ('SAVE', 'Save changes', TRUE);
+ echo '</form>';
+}
+
+function renderPopupVSVIPForm ($vip, $used = 0)
+{
+ $fmt_ip = ip_format ($vip['vip']);
+ $title = 'remove IP ' . formatVSIP ($vip) . ($used ? " (used $used times)" : '');
+ printOpFormIntro ('updIP', array ('ip' => $fmt_ip));
+ echo '<p align=center>';
+ echo getOpLink (array ('op' => 'delIP', 'ip' => $fmt_ip), $title, 'destroy', '', ($used ? 'del-used-slb' : '') );
+ echo '<p><label>VS config:<br>';
+ echo '<textarea name=vsconfig rows=3 cols=80>' . htmlspecialchars ($vip['vsconfig']) . '</textarea></label>';
+ echo '<p><label>RS config:<br>';
+ echo '<textarea name=rsconfig rows=3 cols=80>' . htmlspecialchars ($vip['rsconfig']) . '</textarea></label>';
+ echo '<p align=center>' . getImageHREF ('SAVE', 'Save changes', TRUE);
+ echo '</form>';
+}
+
+function renderEditVS ($vs_id)
+{
+ global $vs_proto;
+ $vsinfo = spotEntity ('ipvs', $vs_id);
+ amplifyCell ($vsinfo);
+ $triplets = getTriplets ($vsinfo);
+
+ // first form - common VS settings
+ printOpFormIntro ('updVS');
+ echo '<table border=0 align=center>';
+ echo '<tr><th class=tdright>Name:</th><td class=tdleft><input type=text name=name value="' . htmlspecialchars ($vsinfo['name'], ENT_QUOTES) . '"></td></tr>';
+ echo '<tr><th class=tdright>VS config:</th><td class=tdleft><textarea name=vsconfig rows=3 cols=80>' . htmlspecialchars ($vsinfo['vsconfig']) . '</textarea></td></tr>';
+ echo '<tr><th class=tdright>RS config:</th><td class=tdleft><textarea name=rsconfig rows=3 cols=80>' . htmlspecialchars ($vsinfo['rsconfig']) . '</textarea></td></tr>';
+ echo '<tr><th></th><th>';
+ printImageHREF ('SAVE', 'Save changes', TRUE);
+ echo '</th></tr>';
+ echo '</table></form>';
+
+ addJS ('js/jquery.thumbhover.js');
+ addJS ('js/slb_editor.js');
+
+ // second form - ports and IPs settings
+ echo '<p>'; // vertical indentation
+ echo '<table width=50% border=0 align=center>';
+
+ echo '<tr><th style="white-space:nowrap">';
+ printOpFormIntro ('addPort');
+ echo 'Add new port:<br>';
+ echo getSelect ($vs_proto, array ('name' => 'proto'));
+ echo ' <input name=port size=5> ';
+ echo getImageHREF ('add', 'Add port', TRUE);
+ echo '</form></th>';
+
+ echo '<td width=99%></td>';
+
+ echo '<th style="white-space:nowrap">';
+ printOpFormIntro ('addIP');
+ echo 'Add new IP:<br>';
+ echo '<input name=ip size=14> ';
+ echo getImageHREF ('add', 'Add IP', TRUE);
+ echo '</form></th></tr>';
+
+ echo '<tr><td valign=top class=tdleft><ul class="slb-checks editable">';
+ foreach ($vsinfo['ports'] as $port)
+ {
+ $used = 0;
+ foreach ($triplets as $triplet)
+ if (isPortEnabled ($port, $triplet['ports']))
+ $used++;
+ echo '<li class="enabled">';
+ echo formatVSPort ($port) . getPopupSLBConfig ($port);
+ renderPopupVSPortForm ($port, $used);
+ echo '</li>';
+ }
+ echo '</ul></td>';
+
+ echo '<td width=99%></td>';
+
+ echo '<td valign=top class=tdleft><ul class="slb-checks editable">';
+ foreach ($vsinfo['vips'] as $vip)
+ {
+ $used = 0;
+ foreach ($triplets as $triplet)
+ if (isVIPEnabled ($vip, $triplet['vips']))
+ $used++;
+ echo '<li class="enabled">';
+ echo formatVSIP ($vip) . getPopupSLBConfig ($vip);
+ renderPopupVSVIPForm ($vip, $used);
+ echo '</li>';
+ }
+ echo '</ul></td>';
+
+ echo '</tr></table>';
+}
+
+// supports object, ipvs, ipv4rspool cell types
+function renderSLBTriplets2 ($cell, $editable = FALSE, $hl_ip = NULL)
+{
+ list ($realm1, $realm2) = array_values (array_diff (array ('object', 'ipvs', 'ipv4rspool'), array ($cell['realm'])));
+ if ($editable && getConfigVar ('ADDNEW_AT_TOP') == 'yes')
+ callHook ('renderNewTripletForm', $realm1, $realm2);
+
+ $fields = array
+ (
+ 'ipvs' => 'vs_id',
+ 'object' => 'object_id',
+ 'ipv4rspool' => 'rspool_id',
+ );
+
+ $headers = array
+ (
+ 'ipvs' => 'VS',
+ 'object' => 'LB',
+ 'ipv4rspool' => 'RS pool',
+ );
+
+ $triplets = getTriplets ($cell);
+
+ // render table header
+ if (count ($triplets))
+ {
+ startPortlet ('VS group instances (' . count ($triplets) . ')');
+ echo "<table cellspacing=0 cellpadding=5 align=center class=widetable><tr>";
+ foreach ($headers as $realm => $header)
+ if ($realm != $cell['realm'])
+ echo "<th>$header</th>";
+ echo '<th>Ports</th>';
+ echo '<th>VIPs</th>';
+ echo "</tr>";
+ }
+
+ $class = 'slb-checks';
+ if ($editable)
+ {
+ addJS ('js/jquery.thumbhover.js');
+ addJS ('js/slb_editor.js');
+ $class .= ' editable';
+ }
+
+ // render table rows
+ global $nextorder;
+ $order = 'odd';
+ foreach ($triplets as $slb)
+ {
+ $vs_cell = spotEntity ('ipvs', $slb['vs_id']);
+ amplifyCell ($vs_cell);
+ echo "<tr valign=top class='row_${order} triplet-row'>";
+ foreach (array_keys ($headers) as $realm)
+ {
+ if ($realm == $cell['realm'])
+ continue;
+ echo "<td class=tdleft>";
+ $slb_cell = spotEntity ($realm, $slb[$fields[$realm]]);
+ renderSLBEntityCell ($slb_cell);
+ echo "</td>";
+ }
+ // render ports
+ echo "<td class=tdleft><ul class='$class'>";
+ foreach ($vs_cell['ports'] as $port)
+ {
+ echo '<li class="' . (($row = isPortEnabled ($port, $slb['ports'])) ? 'enabled' : 'disabled') . '">';
+ echo formatVSPort ($port) . getPopupSLBConfig ($row);
+ if ($editable)
+ renderPopupTripletForm ($slb, $port, NULL, $row);
+ echo '</li>';
+ }
+ echo '</ul></td>';
+
+ // render VIPs
+ echo "<td class=tdleft><ul class='$class'>";
+ foreach ($vs_cell['vips'] as $vip)
+ {
+ $li_class = isVIPEnabled ($vip, $slb['vips']) ? 'enabled' : 'disabled';
+ if ($vip['vip'] === $hl_ip && $li_class == 'enabled')
+ $li_class .= ' highlight';
+ echo "<li class='$li_class'>";
+ echo formatVSIP ($vip);
+ if (is_array ($row) && !empty ($row['prio']))
+ {
+ $prio_class = 'slb-prio slb-prio-' . preg_replace ('/\s.*/', '', $row['prio']);
+ echo '<span class="' . htmlspecialchars ($prio_class, ENT_QUOTES) . '">' . htmlspecialchars($row['prio']) . '</span>';
+ }
+ echo getPopupSLBConfig ($row);
+ if ($editable)
+ renderPopupTripletForm ($slb, NULL, $vip, $row);
+ echo '</li>';
+ }
+ echo '<ul></td>';
+
+ if ($editable)
+ {
+ echo '<td valign=middle>';
+ printOpFormIntro ('del', array (
+ 'object_id' => $slb['object_id'],
+ 'vs_id' => $slb['vs_id'],
+ 'rspool_id' => $slb['rspool_id'],
+ ));
+ printImageHREF ('DELETE', 'Remove triplet', TRUE);
+ echo '</form></td>';
+ }
+
+ echo "</tr>\n";
+ $order = $nextorder[$order];
+ }
+ if (count ($triplets))
+ {
+ echo "</table>\n";
+ finishPortlet();
+ }
+
+ if ($editable && getConfigVar ('ADDNEW_AT_TOP') != 'yes')
+ callHook ('renderNewTripletForm', $realm1, $realm2);
+
+}
+
+function renderSLBFormAJAX()
+{
+ global $pageno, $tabno;
+ parse_str (assertStringArg ('form'), $orig_request);
+ parse_str (ltrim (assertStringArg ('action'), '?'), $action);
+ $pageno = $action['page'];
+ $tabno = $action['tab'];
+ printOpFormIntro ($action['op'], $orig_request);
+
+ $realm_list = array_diff (array ('ipvs', 'object', 'ipv4rspool'), array ($pageno));
+ echo '<table align=center><tr class="tdleft">';
+ foreach ($realm_list as $realm)
+ {
+ switch ($realm)
+ {
+ case 'object':
+ $slb_cell = spotEntity ('object', $orig_request['object_id']);
+ break;
+ case 'ipv4rspool':
+ $slb_cell = spotEntity ('ipv4rspool', $orig_request['rspool_id']);
+ break;
+ case 'ipvs':
+ $slb_cell = spotEntity ('ipvs', $orig_request['vs_id']);
+ break;
+ }
+ echo '<td>';
+ renderSLBEntityCell ($slb_cell);
+ echo '</td>';
+ }
+
+ $vsinfo = spotEntity ('ipvs', $orig_request['vs_id']);
+ amplifyCell ($vsinfo);
+
+ echo '<td><ul style="list-style: none">';
+ foreach ($vsinfo['ports'] as $port)
+ {
+ $key = $port['proto'] . '-' . $port['vport'];
+ echo '<li><label><input type=checkbox name="enabled_ports[]" value="' . htmlspecialchars ($key, ENT_QUOTES) . '" checked>' . formatVSPort ($port) . '</label></li>';
+ }
+ echo '</ul></td>';
+
+ echo '<td><ul style="list-style: none">';
+ foreach ($vsinfo['vips'] as $vip)
+ {
+ $key = ip_format ($vip['vip']);
+ echo '<li><label><input type=checkbox name="enabled_vips[]" value="' . htmlspecialchars ($key, ENT_QUOTES) . '" checked>' . $key . '</label></li>';
+ }
+ echo '</ul></td>';
+ echo '<td>';
+ printImageHREF ('ADD', 'Configure LB', TRUE);
+ echo '</td>';
+ echo '</tr></table>';
+ echo '</form>';
+}
+
+function renderNewTripletForm ($realm1, $realm2)
+{
+ function get_realm_data ($realm)
+ {
+ $name = NULL;
+ $list = array();
+ $options = array();
+ switch ($realm)
+ {
+ case 'object':
+ $name = 'Load balancer';
+ //FIXME: remove Y-specific optimizer
+ // $list = getNarrowObjectList ('IPV4LB_LISTSRC');
+ $list = formatEntityList (quickListTaggedBy ($realm, 58));
+ $options = array ('name' => 'object_id', 'tabindex' => 100);
+ break;
+ case 'ipvs':
+ $name = 'Virtual service';
+ $list = formatEntityList (listCells ('ipvs'));
+ $options = array ('name' => 'vs_id', 'tabindex' => 101);
+ break;
+ case 'ipv4rspool':
+ $name = 'RS pool';
+ $list = formatEntityList (listCells ('ipv4rspool'));
+ $options = array ('name' => 'rspool_id', 'tabindex' => 102);
+ break;
+ default:
+ throw new InvalidArgException('realm', $realm);
+ }
+ return array ('name' => $name, 'list' => $list, 'options' => $options);
+ }
+
+ $realm1_data = get_realm_data ($realm1);
+ $realm2_data = get_realm_data ($realm2);
+ startPortlet ('Add new VS group');
+ if (count ($realm1_data['list']) && count ($realm2_data['list']))
+ printOpFormIntro ('addLink');
+ echo "<table cellspacing=0 cellpadding=5 align=center>";
+ echo "<tr valign=top><th class=tdright>{$realm1_data['name']}</th><td class=tdleft>";
+ printSelect ($realm1_data['list'], $realm1_data['options']);
+ echo '</td><td class=tdcenter valign=middle rowspan=2>';
+ if (count ($realm1_data['list']) && count ($realm2_data['list']))
+ printImageHREF ('ADD', 'Configure LB', TRUE, 120);
+ else
+ {
+ $names = array();
+ if (! count ($realm1_data['list']))
+ $names[] = 'a ' . $realm1_data['name'];
+ if (! count ($realm2_data['list']))
+ $names[] = 'a ' . $realm2_data['name'];
+ $message = 'Please create ' . (implode (' and ', $names)) . '.';
+ showNotice ($message);
+ printImageHREF ('DENIED', $message, FALSE);
+ }
+ echo "<tr valign=top><th class=tdright>{$realm2_data['name']}</th><td class=tdleft>";
+ printSelect ($realm2_data['list'], $realm2_data['options']);
+ echo "</td></tr>\n";
+ echo "</table></form>\n";
+ finishPortlet();
+}
+
+function getPopupSLBConfig ($row)
+{
+ $do_vs = (isset ($row) && isset ($row['vsconfig']) && strlen ($row['vsconfig']));
+ $do_rs = (isset ($row) && isset ($row['rsconfig']) && strlen ($row['rsconfig']));
+ if (!$do_vs && !$do_rs)
+ return;
+
+ $ret = '';
+ $ret .= '<div class="slbconf-btn">…</div>';
+ $ret .= '<div class="slbconf popup-box">';
+ if ($do_vs)
+ {
+ $ret .= '<h1>VS config:</h1>';
+ $ret .= $row['vsconfig'];
+ }
+ if ($do_rs)
+ {
+ $ret .= '<h1>RS config:</h1>';
+ $ret .= $row['rsconfig'];
+ }
+ $ret .= '</div>';
+
+ static $js_added = FALSE;
+ if (! $js_added)
+ {
+ addJS ('js/jquery.thumbhover.js');
+ addJS (<<<END
+$(document).ready (function () {
+ $('.slbconf-btn').each (function () {
+ $(this).thumbPopup($(this).siblings('.slbconf.popup-box'), { showFreezeHint: false });
+ });
+});
+END
+ , TRUE);
+ }
+ return $ret;
+}
+
+function trigger_ipvs_convert ()
+{
+ return count (callHook ('getVSIDsByGroup', getBypassValue())) ? 'std' : '';
+}
+
+function renderIPVSConvert ($vs_id)
+{
+ $old_vs_list = callHook ('getVSIDsByGroup', $vs_id);
+
+ $grouped = array();
+ $used_tags = array();
+ foreach ($old_vs_list as $old_vs_id)
+ {
+ $vsinfo = spotEntity ('ipv4vs', $old_vs_id);
+ foreach ($vsinfo['etags'] as $taginfo)
+ $used_tags[$taginfo['id']] = $taginfo;
+ $port_key = $vsinfo['proto'] . '-' . $vsinfo['vport'];
+ $grouped[$port_key][] = $vsinfo;
+ }
+
+ startPortlet ("Found " . count ($old_vs_list) . " matching VS");
+ printOpFormIntro ('convert');
+ if (count ($used_tags))
+ {
+ echo '<p>Assign these tags to VS group:</p>';
+ foreach ($used_tags as $taginfo)
+ echo '<p><label><input type=checkbox checked name="taglist[]" value="' . htmlspecialchars ($taginfo['id'], ENT_QUOTES) . '""> ' . serializeTags (array ($taginfo)) . '<label>';
+ }
+
+ echo '<p>Import settings of these VS:</p>';
+ echo '<table align=center><tr>';
+ foreach ($grouped as $port_key => $list)
+ echo '<th>' . $port_key . '</th>';
+ echo '</tr><tr>';
+ foreach ($grouped as $port_key => $list)
+ {
+ echo '<td><table>';
+ foreach ($list as $vsinfo)
+ {
+ echo '<tr><td><input type=checkbox name="vs_list[]" checked value="' . htmlspecialchars ($vsinfo['id'], ENT_QUOTES) . '"></td><td>';
+ renderSLBEntityCell ($vsinfo);
+ echo '</td></tr>';
+ }
+ echo '</table></td>';
+ }
+ echo '</tr></table>';
+ printImageHREF ('next', "Import settings of the selected services", TRUE);
+ echo '</form>';
+ finishPortlet();
+}
--- /dev/null
+<?php
+
+# This file is a part of RackTables, a datacenter and server room management
+# framework. See accompanying file "COPYING" for the full copyright and
+# licensing information.
+
+// ********************* Config-generating functions *********************
+
+$parser_class = 'MacroParser';
+
+// Returns array of triplets:
+//triplet = array
+//(
+// 'ports' => $db_port_row,
+// 'vips' => $db_ip_row,
+// 'object_id' => $object_id,
+// 'vs_id' => $vs_id,
+// 'rspool_id' => $rspool_id,
+//)
+function getTriplets ($cell)
+{
+ $filter_fields = array();
+ $order_fields = array();
+ switch ($cell['realm'])
+ {
+ case 'object':
+ $filter_fields['object_id'] = $cell['id'];
+ $order_fields[] = 'vs_id';
+ break;
+ case 'ipvs':
+ $filter_fields['vs_id'] = $cell['id'];
+ $order_fields[] = 'rspool_id';
+ break;
+ case 'ipv4rspool':
+ $filter_fields['rspool_id'] = $cell['id'];
+ $order_fields[] = 'vs_id';
+ break;
+ default:
+ throw new InvalidArgException ('realm', $cell['realm']);
+ }
+ return fetchTripletRows ($filter_fields, $order_fields);
+}
+
+function fetchTripletRows ($filter_fields, $order_fields = array())
+{
+ $order = count ($order_fields) ? "ORDER BY " . implode (',', $order_fields) : '';
+ $filter = 'TRUE';
+ $params = array();
+ foreach ($filter_fields as $key => $value)
+ {
+ $filter .= " AND `$key` = ?";
+ $params[] = $value;
+ }
+
+ $triplets = array();
+ foreach (array ('ports' => 'VSEnabledPorts', 'vips' => 'VSEnabledIPs') as $key => $table)
+ {
+ $result = usePreparedSelectBlade ("SELECT * FROM $table WHERE $filter $order", $params);
+ while ($row = $result->fetch (PDO::FETCH_ASSOC))
+ {
+ $data = $row;
+ unset ($data['object_id']);
+ unset ($data['vs_id']);
+ unset ($data['rspool_id']);
+ $triplet_key = implode ('-', array ($row['vs_id'], $row['rspool_id'], $row['object_id']));
+ if (! isset ($triplets[$triplet_key]))
+ $triplets[$triplet_key] = array
+ (
+ 'vips' => array(),
+ 'ports' => array(),
+ );
+ $triplets[$triplet_key][$key][] = $data;
+ $triplets[$triplet_key]['vs_id'] = $row['vs_id'];
+ $triplets[$triplet_key]['object_id'] = $row['object_id'];
+ $triplets[$triplet_key]['rspool_id'] = $row['rspool_id'];
+ }
+ unset ($result);
+ }
+ return $triplets;
+}
+
+function getTriplet ($object_id, $vs_id, $rspool_id)
+{
+ return array_first (fetchTripletRows (
+ array ('object_id' => $object_id, 'vs_id' => $vs_id, 'rspool_id' => $rspool_id)
+ ));
+}
+
+function generateVSSection ($rspool_id, $vs_parser)
+{
+ $ret = $vs_parser->expand (
+" protocol %PROTO%
+ %GLOBAL_VS_CONF%
+ %RSP_VS_CONF%
+ %VS_VS_CONF%
+ %PORT_VS_CONF%
+ %VIP_VS_CONF%
+ %SLB_PORT_VS_CONF%
+ %SLB_VIP_VS_CONF%
+");
+ $vip_bin = ip_checkparse ($vs_parser->expandMacro ('VIP'));
+ if ($vip_bin === FALSE)
+ $family_length = 4;
+ else
+ $family_length = strlen ($vip_bin);
+
+ foreach (getRSListInPool ($rspool_id) as $rs)
+ {
+ if ($rs['inservice'] != 'yes')
+ continue;
+ $parser = clone $vs_parser;
+ $parser->addMacro ('RSIP', $rs['rsip']);
+ $parser->addMacro ('RS_COMMENT', $rs['comment']);
+ $parser->addMacro ('RS_RS_CONF', $rs['rsconfig']);
+
+ // do not add v6 reals into v4 service and vice versa
+ $rsip_bin = ip_checkparse ($parser->expandMacro ('RSIP'));
+ if ($rsip_bin !== FALSE && strlen ($rsip_bin) == $family_length)
+ foreach (explode (',', $parser->expandMacro ('RSPORT')) as $rsp_token)
+ {
+ $port_range = explode ('-', $rsp_token);
+ if (count ($port_range) < 1)
+ throw new InvalidArgException ('RSPORT', $rsp_token, "invalid RS port range");
+ if (count ($port_range) < 2)
+ $port_range[] = $port_range[0];
+ if ($port_range[0] > $port_range[1])
+ throw new InvalidArgException ('RSPORT', $rsp_token, "invalid RS port range");
+
+ for ($rsport = $port_range[0]; $rsport <= $port_range[1]; $rsport++)
+ {
+ $rs_parser = clone $parser;
+ $rs_parser->addMacro ('RSPORT', $rsport);
+ $ret .= $rs_parser->expand ("
+ %RS_PREPEND%
+ real_server %RS_HEADER% {
+ %GLOBAL_RS_CONF%
+ %VS_RS_CONF%
+ %RSP_RS_CONF%
+ %VIP_RS_CONF%
+ %PORT_RS_CONF%
+ %SLB_VIP_RS_CONF%
+ %SLB_PORT_RS_CONF%
+ %RS_RS_CONF%
+ }
+");
+ }
+ }
+ }
+ return $ret;
+}
+
+function generateSLBConfig2 ($triplet_list)
+{
+ $ret = '';
+
+ global $parser_class;
+ $gl_parser = new $parser_class;
+ $defaults = getSLBDefaults (TRUE);
+ $gl_parser->addMacro ('GLOBAL_VS_CONF', dos2unix ($defaults['vsconfig']));
+ $gl_parser->addMacro ('GLOBAL_RS_CONF', dos2unix ($defaults['rsconfig']));
+ $gl_parser->addMacro ('RSPORT', '%VPORT%');
+ $gl_parser->addMacro ('VS_PREPEND',
+"# LB (id == %LB_ID%): %LB_NAME%
+# VSG (id == %VSG_ID%): %VS_NAME%
+# RS (id == %RSP_ID%): %RSP_NAME%");
+
+ // group triplets by object_id, vs_id
+ $grouped = array();
+ foreach ($triplet_list as $triplet)
+ $grouped[$triplet['object_id']][$triplet['vs_id']][] = $triplet;
+
+ foreach ($grouped as $object_id => $subarr)
+ {
+ $seen_vs_groups = array();
+ $lb_parser = clone $gl_parser;
+ $lb_cell = spotEntity ('object', $object_id);
+ $lb_parser->addMacro ('LB_ID', $lb_cell['id']);
+ $lb_parser->addMacro ('LB_NAME', $lb_cell['name']);
+
+ foreach ($subarr as $vs_id => $triplets)
+ {
+ $vs_parser = clone $lb_parser;
+ $vs_cell = spotEntity ('ipvs', $vs_id);
+ if (! isset ($vs_cell['ports']) || ! isset ($vs_cell['vips']))
+ amplifyCell ($vs_cell);
+ $vs_parser->addMacro ('VS_ID', $vs_cell['id']);
+ $vs_parser->addMacro ('VSG_ID', $vs_cell['id']);
+ $vs_parser->addMacro ('VS_NAME', $vs_cell['name']);
+ $vs_parser->addMacro ('VS_RS_CONF', dos2unix ($vs_cell['rsconfig']));
+
+ $virtual_services = array();
+ foreach ($triplets as $triplet)
+ {
+ $tr_parser = clone $vs_parser;
+ $rs_cell = spotEntity ('ipv4rspool', $triplet['rspool_id']);
+ $tr_parser->addMacro ('RSP_ID', $rs_cell['id']);
+ $tr_parser->addMacro ('RSP_NAME', $rs_cell['name']);
+ $tr_parser->addMacro ('RSP_VS_CONF', dos2unix ($rs_cell['vsconfig']));
+ $tr_parser->addMacro ('RSP_RS_CONF', dos2unix ($rs_cell['rsconfig']));
+ $tr_parser->addMacro ('VS_VS_CONF', dos2unix ($vs_cell['vsconfig'])); // VS-driven vsconfig has higher priority than RSP-driven
+
+ foreach ($triplet['ports'] as $port_row)
+ {
+ $is_mark = ($port_row['proto'] == 'MARK');
+ $p_parser = clone $tr_parser;
+ $p_parser->addMacro ('VS_HEADER', $is_mark ? 'fwmark %MARK%' : '%VIP% %VPORT%');
+ $p_parser->addMacro ('PROTO', $is_mark ? 'TCP' : $port_row['proto']);
+ $p_parser->addMacro ($is_mark ? 'MARK' : 'VPORT', $port_row['vport']);
+ foreach ($vs_cell['ports'] as $vport)
+ if ($vport['vport'] == $port_row['vport'] && $vport['proto'] == $port_row['proto'])
+ {
+ $p_parser->addMacro ('PORT_VS_CONF', dos2unix ($vport['vsconfig']));
+ $p_parser->addMacro ('PORT_RS_CONF', dos2unix ($vport['rsconfig']));
+ break;
+ }
+ $p_parser->addMacro ('SLB_PORT_VS_CONF', dos2unix ($port_row['vsconfig']));
+ $p_parser->addMacro ('SLB_PORT_RS_CONF', dos2unix ($port_row['rsconfig']));
+
+ if ($is_mark)
+ {
+ $p_parser->addMacro ('RS_HEADER', '%RSIP%');
+ $virtual_services[$parser->expandMacro ('VS_HEADER')] = generateVSSection ($triplet['rspool_id'], $p_parser);
+ }
+ else
+ {
+ $p_parser->addMacro ('RS_HEADER', '%RSIP% %RSPORT%');
+ foreach ($triplet['vips'] as $ip_row)
+ {
+ $ip_parser = clone $p_parser;
+ $ip_parser->addMacro ('VIP', ip_format ($ip_row['vip']));
+ $ip_parser->addMacro ('IP_VER', (strlen ($ip_row['vip']) == 16) ? 6 : 4);
+ $ip_parser->addMacro ('PRIO', $ip_row['prio']);
+ foreach ($vs_cell['vips'] as $vip)
+ if ($vip['vip'] === $ip_row['vip'])
+ {
+ $ip_parser->addMacro ('VIP_VS_CONF', dos2unix ($vip['vsconfig']));
+ $ip_parser->addMacro ('VIP_RS_CONF', dos2unix ($vip['rsconfig']));
+ break;
+ }
+ $ip_parser->addMacro ('SLB_VIP_VS_CONF', dos2unix ($ip_row['vsconfig']));
+ $ip_parser->addMacro ('SLB_VIP_RS_CONF', dos2unix ($ip_row['rsconfig']));
+ $virtual_services[$ip_parser->expandMacro ('VS_HEADER')] = generateVSSection ($triplet['rspool_id'], $ip_parser);
+ }
+ }
+ }
+
+ // group multiple virtual_services into vs_groups
+ $groups = array();
+ foreach ($virtual_services as $key => $content)
+ $groups[$content][] = $key;
+ $gid = 1;
+ foreach ($groups as $content => $keys)
+ {
+ $ret .= $tr_parser->expand ("\n%VS_PREPEND%\n");
+ if (count ($keys) == 1)
+ $ret .= "virtual_server " . array_first ($keys) . " {\n" . $content . "}\n";
+ else
+ {
+ // come up with the name for new VS group
+ $vsg_name = makeUniqueVSGName ($seen_vs_groups, $keys, $vs_cell);
+ $seen_vs_groups[$vsg_name] = 1;
+ $tr_parser->addMacro ('VSG_NAME', $vsg_name);
+
+ $ret .= $tr_parser->expand ("virtual_server_group %VSG_NAME% {\n");
+ foreach ($keys as $vs_header)
+ $ret .= "\t$vs_header\n";
+ $ret .= "}\n";
+ $ret .= $tr_parser->expand ("virtual_server group %VSG_NAME% {\n");
+ $ret .= $content . "}\n";
+ }
+ }
+ }
+ }
+ }
+ return $ret;
+}
+
+function makeUniqueVSGName ($seen_names, $keys, $vs_cell)
+{
+ $seen_ports = array();
+ $seen_marks = array();
+ sort ($keys);
+ foreach ($keys as $key)
+ if (preg_match('/^fwmark\s+(\d+)$/', $key, $m))
+ $seen_marks[$m[1]] = $m[1];
+ elseif (preg_match('/^[0-9a-fA-F:.]+\s+(\d+)$/', $key, $m))
+ $seen_ports[$m[1]] = $m[1];
+ $base_name = preg_replace('/\s+/', '_', $vs_cell['name']);
+
+ $vsg_name = NULL;
+ if (! isset ($vsg_name) && count ($seen_ports) == 1)
+ {
+ $cname = $base_name . '_' . array_first ($seen_ports);
+ if (! array_key_exists ($cname, $seen_names))
+ $vsg_name = $cname;
+ }
+ if (! isset ($vsg_name) && count ($seen_marks))
+ {
+ $cname = $base_name . '_fwm' . implode('_fwm', $seen_marks);
+ if (! array_key_exists ($cname, $seen_names))
+ $vsg_name = $cname;
+ }
+ if (! isset ($vsg_name))
+ {
+ $cname = $base_name;
+ if (count ($seen_ports))
+ $cname .= '_' . implode('_', $seen_ports);
+ if (count ($seen_marks))
+ $cname .= '_fwm' . implode('_fwm', $seen_marks);
+ if (! array_key_exists ($cname, $seen_names))
+ $vsg_name = $cname;
+ }
+ if (! isset ($vsg_name))
+ {
+ $cname = $basename . '_' . substr (sha1 (serialize ($vsg['keys'])), 0, 6);
+ if (! array_key_exists ($cname, $seen_names))
+ $vsg_name = $cname;
+ }
+ if (! isset ($vsg_name))
+ throw new RackTablesError ("Could not produce unique VS group name for ${vs_cell['name']}", RackTablesError::INTERNAL);
+ return $vsg_name;
+}
+
+// $vs_list is array of VS text configs, indexed by VS headers
+// function returns a list of groups. Services with equal configs are grouped together.
+// Each group has one or more VS headers in 'keys' subarray, and 'content' field.
+
+function groupVS ($vs_list)
+{
+ $ret = array();
+ $tmp = array();
+ foreach ($vs_list as $key => $content)
+ $tmp[$content][] = $key;
+ foreach ($tmp as $content => $keys)
+ $ret[] = array ('keys' => $keys, 'content' => $content);
+ return $ret;
+}
+
+// returns list item or FALSE
+function isPortEnabled ($port, $port_list)
+{
+ foreach ($port_list as $i_port)
+ if ($i_port['proto'] == $port['proto'] && $i_port['vport'] == $port['vport'])
+ return $i_port;
+ return FALSE;
+}
+
+// returns list item or FALSE
+function isVIPEnabled ($vip, $vip_list)
+{
+ foreach ($vip_list as $i_vip)
+ if ($i_vip['vip'] === $vip['vip'])
+ return $i_vip;
+ return FALSE;
+}
+
+// returns list of ipv4vs ids which have one of the IPs or fwmarks of group $group_id
+function getVSIDsByGroup ($group_id)
+{
+ $ret = array();
+ $vsinfo = spotEntity ('ipvs', $group_id);
+ amplifyCell ($vsinfo);
+ if (count ($vsinfo['vips']))
+ {
+ $ips = reduceSubarraysToColumn ($vsinfo['vips'], 'vip');
+ $qm = questionMarks (count ($ips));
+ $result = usePreparedSelectBlade ("SELECT id FROM IPv4VS WHERE vip IN ($qm) ORDER BY vip", $ips);
+ $ret = array_merge ($ret, $result->fetchAll (PDO::FETCH_COLUMN, 0));
+ unset ($result);
+ }
+
+ $bin_marks = array();
+ foreach ($vsinfo['ports'] as $port)
+ if ($port['proto'] == 'MARK')
+ $bin_marks[] = pack ('N', $port['vport']);
+ if (count ($bin_marks))
+ {
+ $qm = questionMarks (count ($bin_marks));
+ $result = usePreparedSelectBlade ("SELECT id FROM IPv4VS WHERE proto = 'MARK' AND vip IN ($qm) ORDER BY vip", $bin_marks);
+ $ret = array_merge ($ret, $result->fetchAll (PDO::FETCH_COLUMN, 0));
+ }
+
+ return $ret;
+}
+
+function concatConfig (&$config, $line)
+{
+ if (strlen ($config))
+ $config .= "\n";
+ $config .= $line;
+}
+
+// splits $text to configuration directives (lines)
+// each macro declaration is one configuration directive
+// empty lines are skipped, trailing spaces are cut.
+function tokenizeConfig ($text)
+{
+ $ret = array();
+ $pos = 0;
+ $len = strlen ($text);
+ $state = 0;
+ while ($pos < $len || $state != 0)
+ switch ($state)
+ {
+ case 0:
+ if (preg_match ('/[?:]?=`?|\n/s', $text, $m, PREG_OFFSET_CAPTURE, $pos))
+ {
+ if ($m[0][0] == "\n")
+ {
+ $ret[] = substr ($text, $pos, $m[0][1] - $pos);
+ $pos = $m[0][1] + 1; // skip \n
+ }
+ else
+ {
+ $macro_name = substr ($text, $pos, $m[0][1] - $pos);
+ if (preg_match('/^[a-zA-Z0-9_]+$/', $macro_name))
+ {
+ $assignment_start = $pos;
+ $pos = $m[0][1] + 1;
+ $state = $m[0][0][strlen ($m[0][0]) - 1] == '`' ? 1 : 2;
+ }
+ }
+ }
+ else
+ {
+ $ret[] = substr ($text, $pos);
+ break 2;
+ }
+ break;
+ case 1:
+ if (FALSE === ($i = strpos ($text, "'", $pos)))
+ break 2;
+ else
+ {
+ $ret[$macro_name] = substr ($text, $assignment_start, $i - $assignment_start + 1);
+ $pos = $i + 1;
+ $state = 0;
+ }
+ break;
+ case 2:
+ if (FALSE === ($i = strpos ($text, "\n", $pos)))
+ {
+ $ret[$macro_name] = substr ($text, $assignment_start);
+ break 2;
+ }
+ else
+ {
+ $ret[$macro_name] = substr ($text, $assignment_start, $i - $assignment_start);
+ $pos = $i + 1;
+ $state = 0;
+ }
+ break;
+ }
+
+ return array_diff (array_map ('rtrim', $ret), array (''));
+}
+
+// returns array with keys: 'properties' 'ports', 'vips', 'triplets'
+function buildVSMigratePlan ($new_vs_id, $vs_id_list)
+{
+ $ret = array
+ (
+ 'properties' => array ('vsconfig' => '', 'rsconfig' => ''),
+ 'ports' => array(),
+ 'vips' => array(),
+ 'triplets' => array(),
+ 'messages' => array(), // keys are $old_tr_key
+ );
+ $config_stat = array('vsconfig' => array(), 'rsconfig' => array());
+ $gt = array(); // grouped triplets
+
+ foreach ($vs_id_list as $vs_id)
+ {
+ $vsinfo = spotEntity ('ipv4vs', $vs_id);
+
+ // create nesessary vips and ports
+ if ($vsinfo['proto'] != 'MARK')
+ {
+ $vip_key = $vsinfo['vip_bin'];
+ $port_key = $vsinfo['proto'] . '-' . $vsinfo['vport'];
+ $ret['vips'][$vip_key] = array ('vip' => $vsinfo['vip_bin'], 'vsconfig' => '', 'rsconfig' => '');
+ $ret['ports'][$port_key] = array ('proto' => $vsinfo['proto'], 'vport' => $vsinfo['vport'], 'vsconfig' => '', 'rsconfig' => '');
+ }
+ else
+ {
+ $vip_key = '';
+ $mark = implode('', unpack ('N', $vsinfo['vip_bin']));
+ $port_key = $vsinfo['proto'] . '-' . $mark;
+ $ret['ports'][$port_key] = array ('proto' => $vsinfo['proto'], 'vport' => $mark, 'vsconfig' => '', 'rsconfig' => '');
+ }
+
+ // fill triplets
+ foreach (SLBTriplet::getTriplets ($vsinfo) as $triplet)
+ {
+ $tr_key = $triplet->lb['id'] . '-' . $triplet->rs['id'];
+ if (! isset ($ret['triplets'][$tr_key]))
+ $ret['triplets'][$tr_key] = array
+ (
+ 'object_id' => $triplet->lb['id'],
+ 'rspool_id' => $triplet->rs['id'],
+ 'vs_id' => $new_vs_id,
+ 'vips' => array(),
+ 'ports' => array(),
+ );
+
+ $configs = array
+ (
+ 'vsconfig' => tokenizeConfig ($triplet->vs['vsconfig'] . "\n" . $triplet->slb['vsconfig']),
+ 'rsconfig' => tokenizeConfig ($triplet->vs['rsconfig'] . "\n" . $triplet->slb['rsconfig']),
+ );
+
+ if ($vsinfo['proto'] != 'MARK')
+ {
+ if (! isset ($ret['triplets'][$tr_key]['ports'][$port_key]))
+ $ret['triplets'][$tr_key]['ports'][$port_key] = array ('proto' => $vsinfo['proto'], 'vport' => $vsinfo['vport'], 'vsconfig' => '', 'rsconfig' => '');
+ if (! isset ($ret['triplets'][$tr_key]['vips'][$vip_key]))
+ $ret['triplets'][$tr_key]['vips'][$vip_key] = array ('vip' => $vsinfo['vip_bin'], 'prio' => NULL, 'vsconfig' => '', 'rsconfig' => '');
+ if ('' != $triplet->slb['prio'])
+ $ret['triplets'][$tr_key]['vips'][$vip_key]['prio'] = $triplet->slb['prio'];
+ }
+ else
+ $ret['triplets'][$tr_key]['ports'][$port_key] = array ('proto' => $vsinfo['proto'], 'vport' => $mark, 'vsconfig' => '', 'rsconfig' => '');
+
+ $old_tr_key = $tr_key . '-' . $vip_key . '-' . $port_key;
+ $gt['all'][$old_tr_key] = $triplet;
+ $gt['ports'][$port_key][$old_tr_key] = $triplet;
+ $gt['vips'][$vip_key][$old_tr_key] = $triplet;
+ $gt['vip_links'][$tr_key][$vip_key][$old_tr_key] = $triplet;
+ $gt['port_links'][$tr_key][$port_key][$old_tr_key] = $triplet;
+
+ foreach ($configs as $conf_type => $list)
+ foreach ($list as $line)
+ $config_stat[$conf_type][$line][$old_tr_key] = $triplet;
+ }
+ }
+
+ // reduce common config lines and move them from $config_stat into $ret
+ foreach ($config_stat as $conf_type => $stat)
+ foreach ($stat as $line => $used_in_triplets)
+ {
+ $added_to_triplets = array();
+ $wrong_triplets = array();
+
+ if (! array_sub ($gt['all'], $used_in_triplets))
+ {
+ // line used in all triplets
+ concatConfig ($ret['properties'][$conf_type], $line);
+ continue;
+ }
+
+ foreach ($gt['ports'] as $port_key => $port_triplets)
+ {
+ $diff = array_sub ($port_triplets, $used_in_triplets);
+ if (count ($diff) < count ($port_triplets) / 2)
+ {
+ // line used in most triplets of this port
+ $added_to_triplets += $port_triplets;
+ $wrong_triplets += $diff;
+ concatConfig ($ret['ports'][$port_key][$conf_type], $line);
+ }
+ }
+
+ foreach ($gt['vips'] as $vip_key => $vip_triplets)
+ if (! array_sub ($vip_triplets, $used_in_triplets))
+ if (count ($vip_triplets) == count (array_sub ($vip_triplets, $added_to_triplets)))
+ {
+ // if none of the $vip_triplets are in $added_to_triplets,
+ // line used in all triplets of this vip
+ $added_to_triplets += $vip_triplets;
+ concatConfig ($ret['vips'][$vip_key][$conf_type], $line);
+ }
+
+ foreach ($used_in_triplets as $old_tr_key => $triplet)
+ {
+ if (isset ($added_to_triplets[$old_tr_key]))
+ continue;
+ $tr_key = $triplet->lb['id'] . '-' . $triplet->rs['id'];
+ if ($triplet->vs['proto'] != 'MARK')
+ {
+ $vip_key = $triplet->vs['vip_bin'];
+ $port_key = $triplet->vs['proto'] . '-' . $triplet->vs['vport'];
+ }
+ else
+ {
+ $vip_key = '';
+ $port_key = $triplet->vs['proto'] . '-' . implode ('', unpack ('N', $triplet->vs['vip_bin']));
+ }
+ // if all the triplets for a given port contain line, add it to the ports' config
+ if (! array_sub ($gt['port_links'][$tr_key][$port_key], $used_in_triplets))
+ if (count ($gt['port_links'][$tr_key][$port_key]) == count (array_sub ($gt['port_links'][$tr_key][$port_key], $added_to_triplets)))
+ {
+ $added_to_triplets += $gt['port_links'][$tr_key][$port_key];
+ concatConfig ($ret['triplets'][$tr_key]['ports'][$port_key][$conf_type], $line);
+ }
+
+ // if all the triplets for a given vip contain line, add it to the vips' config
+ if ($vip_key != '')
+ if (! array_sub ($gt['vip_links'][$tr_key][$vip_key], $used_in_triplets))
+ if (count ($gt['vip_links'][$tr_key][$vip_key]) == count (array_sub ($gt['vip_links'][$tr_key][$vip_key], $added_to_triplets)))
+ {
+ $added_to_triplets += $gt['vip_links'][$tr_key][$vip_key];
+ concatConfig ($ret['triplets'][$tr_key]['vips'][$vip_key][$conf_type], $line);
+ }
+ }
+
+ // check for failed-to-insert lines
+ foreach (array_sub ($used_in_triplets, $added_to_triplets) as $old_tr_key => $unused_triplet)
+ $ret['messages'][$old_tr_key][] = "Failed to add $conf_type line '$line'";
+ foreach ($wrong_triplets as $old_tr_key => $triplet)
+ $ret['messages'][$old_tr_key][] = "Added $conf_type line '$line'";
+ } // for $line
+
+ return $ret;
+}
*/
+require_once 'slb-interface.php';
+
define ('RE_STATIC_URI', '#^([[:alpha:]]+)/(?:[[:alpha:]]+/)*[[:alnum:]\._-]+\.([[:alpha:]]+)$#');
function dispatchImageRequest()
[1] http://php.net/manual/en/function.strftime.php
ENDOFTEXT
,
+
+ '0.20.5' => <<<ENDOFTEXT
+This release introduces the VS groups feature. VS groups is a new way to store
+and display virtual services configuration. New realm 'ipvs' (VS group) is created.
+All the existing VS configuration is kept and displayed as-is, but user is free to convert
+it to the new format, which displays it in more natural way and allows to generate
+virtual_server_group keepalived configs. To convert a virtual servise to the new format,
+you need to manually create the vs group object and assign IP addresses to it. Then, if you
+have the old-style VSes configured, the Migrate tab will be displayed on the particular VS group's
+page. After successfull migration, you can remove the old-style VS objects.
+
+Old-style VS configuration becomes DEPRECATED. Its support will be removed in one of the following
+major releases. So it is strongly recommended to convert it to the new format.
+ENDOFTEXT
+,
+
);
// At the moment we assume, that for any two releases we can
$query[] = "UPDATE AttributeMap SET sticky = 'yes' WHERE objtype_id = 1787 AND attr_id = 30"; // Management interface -> Mgmt type
$query[] = "INSERT INTO `Config` (varname, varvalue, vartype, emptyok, is_hidden, is_userdefined, description) VALUES ('RDP_OBJS_LISTSRC','false','string','yes','no','yes','Rackcode filter for RDP-managed objects')";
+
+ // SLB v2 tables
+ $query[] = "
+CREATE TABLE `VS` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` char(255) DEFAULT NULL,
+ `vsconfig` text,
+ `rsconfig` text,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB
+";
+ $query[] = "
+CREATE TABLE `VSIPs` (
+ `vs_id` int(10) unsigned NOT NULL,
+ `vip` varbinary(16) NOT NULL,
+ `vsconfig` text,
+ `rsconfig` text,
+ PRIMARY KEY (`vs_id`,`vip`),
+ KEY `vip` (`vip`),
+ CONSTRAINT `VSIPs-vs_id` FOREIGN KEY (`vs_id`) REFERENCES `VS` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB
+";
+ $query[] = "
+CREATE TABLE `VSPorts` (
+ `vs_id` int(10) unsigned NOT NULL,
+ `proto` enum('TCP','UDP','MARK') CHARACTER SET latin1 NOT NULL,
+ `vport` int(10) unsigned NOT NULL,
+ `vsconfig` text,
+ `rsconfig` text,
+ PRIMARY KEY (`vs_id`,`proto`,`vport`),
+ CONSTRAINT `VS-vs_id` FOREIGN KEY (`vs_id`) REFERENCES `VS` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB
+";
+ $query[] = "
+CREATE TABLE `VSEnabledIPs` (
+ `object_id` int(10) unsigned NOT NULL,
+ `vs_id` int(10) unsigned NOT NULL,
+ `vip` varbinary(16) NOT NULL,
+ `rspool_id` int(10) unsigned NOT NULL,
+ `prio` varchar(255) DEFAULT NULL,
+ `vsconfig` text,
+ `rsconfig` text,
+ PRIMARY KEY (`object_id`,`vs_id`,`vip`),
+ KEY `vip` (`vip`),
+ KEY `VSEnabledIPs-FK-vs_id-vip` (`vs_id`,`vip`),
+ KEY `VSEnabledIPs-FK-rspool_id` (`rspool_id`),
+ CONSTRAINT `VSEnabledIPs-FK-rspool_id` FOREIGN KEY (`rspool_id`) REFERENCES `IPv4RSPool` (`id`) ON DELETE CASCADE,
+ CONSTRAINT `VSEnabledIPs-FK-vs_id-vip` FOREIGN KEY (`vs_id`, `vip`) REFERENCES `VSIPs` (`vs_id`, `vip`) ON DELETE CASCADE
+) ENGINE=InnoDB
+";
+ $query[] = "
+CREATE TABLE `VSEnabledPorts` (
+ `object_id` int(10) unsigned NOT NULL,
+ `vs_id` int(10) unsigned NOT NULL,
+ `proto` enum('TCP','UDP','MARK') CHARACTER SET latin1 NOT NULL,
+ `vport` int(10) unsigned NOT NULL,
+ `rspool_id` int(10) unsigned NOT NULL,
+ `vsconfig` text,
+ `rsconfig` text,
+ PRIMARY KEY (`object_id`,`vs_id`,`proto`,`vport`),
+ KEY `VSEnabledPorts-FK-vs_id-proto-vport` (`vs_id`,`proto`,`vport`),
+ KEY `VSEnabledPorts-FK-rspool_id` (`rspool_id`),
+ CONSTRAINT `VSEnabledPorts-FK-object_id` FOREIGN KEY (`object_id`) REFERENCES `Object` (`id`) ON DELETE CASCADE,
+ CONSTRAINT `VSEnabledPorts-FK-rspool_id` FOREIGN KEY (`rspool_id`) REFERENCES `IPv4RSPool` (`id`) ON DELETE CASCADE,
+ CONSTRAINT `VSEnabledPorts-FK-vs_id-proto-vport` FOREIGN KEY (`vs_id`, `proto`, `vport`) REFERENCES `VSPorts` (`vs_id`, `proto`, `vport`) ON DELETE CASCADE
+) ENGINE=InnoDB
+";
$query[] = "UPDATE Config SET varvalue = '0.20.5' WHERE varname = 'DB_VERSION'";
break;
case 'dictionary':
}
if (popup.data("sticked"))
return;
+ var left;
if (windowSize.width + windowSize.scrollLeft < event.pageX + popupSize.width + settings.cursorLeftOffset){
- $(popup).css("left", event.pageX - popupSize.width - settings.cursorLeftOffset);
+ left = event.pageX - popupSize.width - settings.cursorLeftOffset;
} else {
- $(popup).css("left", event.pageX + settings.cursorLeftOffset);
+ left = event.pageX + settings.cursorLeftOffset;
}
+ // center pop-up if it does not fit entirely into window
+ if (
+ left < windowSize.scrollLeft ||
+ left + popupSize.width > windowSize.scrollLeft + windowSize.width
+ ) {
+ left = (windowSize.width - popupSize.width) / 2;
+ }
+ $(popup).css("left", left);
+
if (bPopupShownAbove) {
$(popup).css("top", event.pageY - popupSize.height - settings.cursorTopOffset);
} else {
--- /dev/null
+$(document).ready (function () {
+ // popup form for port/ip config update
+ $('.slb-checks.editable li').click (function (e) {
+ if (e.target.tagName != 'LI')
+ return true;
+ var form = $('<div>').addClass ('popup-box').appendTo ('body').append ($(this).find('form').clone());
+ // confirmation box on port/vip deletion
+ $('a.del-used-slb').click (function (e) {
+ return confirm ('This entity is used. Please confirm the deletion');
+ });
+
+ $(this).thumbPopup (form, { event: e });
+ return false;
+ });
+
+ // confirmation box on triplet deletion
+ $('form#del input').click (function (e) {
+ return confirm ('Please confirm the deletion of triplet');
+ });
+
+ // new triplet form stage1 handler
+ var forms = $('form#addLink');
+ forms.submit (function() { return false });
+ forms.find('input[name="submit"]').click (function (e) {
+ var form = $(this).parents('form');
+ $.ajax ({
+ type: 'POST',
+ url: 'index.php',
+ data: {
+ 'module': 'ajax',
+ 'ac': 'get-slb-form',
+ 'form': form.serialize(),
+ 'action': form.attr('action')
+ },
+ 'success': function (data) {
+ form.replaceWith (data);
+ }
+ });
+
+ return false;
+ });
+});