mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
7dbf833697
All quite obvious. For the LCP2 one, we're not actually counting so use a bool instead of an int. Signed-off-by: Martin Schwenke <martin@meltin.net> Reviewed-by: Amitay Isaacs <amitay@gmail.com>
523 lines
14 KiB
C
523 lines
14 KiB
C
/*
|
|
ctdb ip takeover code
|
|
|
|
Copyright (C) Ronnie Sahlberg 2007
|
|
Copyright (C) Andrew Tridgell 2007
|
|
Copyright (C) Martin Schwenke 2011
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "replace.h"
|
|
#include "system/network.h"
|
|
|
|
#include "lib/util/debug.h"
|
|
#include "common/logging.h"
|
|
|
|
#include "protocol/protocol_util.h"
|
|
|
|
#include "server/ipalloc_private.h"
|
|
|
|
/*
|
|
* This is the length of the longtest common prefix between the IPs.
|
|
* It is calculated by XOR-ing the 2 IPs together and counting the
|
|
* number of leading zeroes. The implementation means that all
|
|
* addresses end up being 128 bits long.
|
|
*
|
|
* FIXME? Should we consider IPv4 and IPv6 separately given that the
|
|
* 12 bytes of 0 prefix padding will hurt the algorithm if there are
|
|
* lots of nodes and IP addresses?
|
|
*/
|
|
static uint32_t ip_distance(ctdb_sock_addr *ip1, ctdb_sock_addr *ip2)
|
|
{
|
|
uint32_t ip1_k[IP_KEYLEN];
|
|
uint32_t *t;
|
|
int i;
|
|
uint32_t x;
|
|
|
|
uint32_t distance = 0;
|
|
|
|
memcpy(ip1_k, ip_key(ip1), sizeof(ip1_k));
|
|
t = ip_key(ip2);
|
|
for (i=0; i<IP_KEYLEN; i++) {
|
|
x = ip1_k[i] ^ t[i];
|
|
if (x == 0) {
|
|
distance += 32;
|
|
} else {
|
|
/* Count number of leading zeroes.
|
|
* FIXME? This could be optimised...
|
|
*/
|
|
while ((x & (1 << 31)) == 0) {
|
|
x <<= 1;
|
|
distance += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return distance;
|
|
}
|
|
|
|
/* Calculate the IP distance for the given IP relative to IPs on the
|
|
given node. The ips argument is generally the all_ips variable
|
|
used in the main part of the algorithm.
|
|
*/
|
|
static uint32_t ip_distance_2_sum(ctdb_sock_addr *ip,
|
|
struct public_ip_list *ips,
|
|
int pnn)
|
|
{
|
|
struct public_ip_list *t;
|
|
uint32_t d;
|
|
|
|
uint32_t sum = 0;
|
|
|
|
for (t = ips; t != NULL; t = t->next) {
|
|
if (t->pnn != pnn) {
|
|
continue;
|
|
}
|
|
|
|
/* Optimisation: We never calculate the distance
|
|
* between an address and itself. This allows us to
|
|
* calculate the effect of removing an address from a
|
|
* node by simply calculating the distance between
|
|
* that address and all of the exitsing addresses.
|
|
* Moreover, we assume that we're only ever dealing
|
|
* with addresses from all_ips so we can identify an
|
|
* address via a pointer rather than doing a more
|
|
* expensive address comparison. */
|
|
if (&(t->addr) == ip) {
|
|
continue;
|
|
}
|
|
|
|
d = ip_distance(ip, &(t->addr));
|
|
sum += d * d; /* Cheaper than pulling in math.h :-) */
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
/* Return the LCP2 imbalance metric for addresses currently assigned
|
|
to the given node.
|
|
*/
|
|
static uint32_t lcp2_imbalance(struct public_ip_list * all_ips, int pnn)
|
|
{
|
|
struct public_ip_list *t;
|
|
|
|
uint32_t imbalance = 0;
|
|
|
|
for (t = all_ips; t != NULL; t = t->next) {
|
|
if (t->pnn != pnn) {
|
|
continue;
|
|
}
|
|
/* Pass the rest of the IPs rather than the whole
|
|
all_ips input list.
|
|
*/
|
|
imbalance += ip_distance_2_sum(&(t->addr), t->next, pnn);
|
|
}
|
|
|
|
return imbalance;
|
|
}
|
|
|
|
static bool lcp2_init(struct ipalloc_state *ipalloc_state,
|
|
uint32_t **lcp2_imbalances,
|
|
bool **rebalance_candidates)
|
|
{
|
|
int i, numnodes;
|
|
struct public_ip_list *t;
|
|
|
|
numnodes = ipalloc_state->num;
|
|
|
|
*rebalance_candidates = talloc_array(ipalloc_state, bool, numnodes);
|
|
if (*rebalance_candidates == NULL) {
|
|
DEBUG(DEBUG_ERR, (__location__ " out of memory\n"));
|
|
return false;
|
|
}
|
|
*lcp2_imbalances = talloc_array(ipalloc_state, uint32_t, numnodes);
|
|
if (*lcp2_imbalances == NULL) {
|
|
DEBUG(DEBUG_ERR, (__location__ " out of memory\n"));
|
|
return false;
|
|
}
|
|
|
|
for (i=0; i<numnodes; i++) {
|
|
(*lcp2_imbalances)[i] =
|
|
lcp2_imbalance(ipalloc_state->all_ips, i);
|
|
/* First step: assume all nodes are candidates */
|
|
(*rebalance_candidates)[i] = true;
|
|
}
|
|
|
|
/* 2nd step: if a node has IPs assigned then it must have been
|
|
* healthy before, so we remove it from consideration. This
|
|
* is overkill but is all we have because we don't maintain
|
|
* state between takeover runs. An alternative would be to
|
|
* keep state and invalidate it every time the recovery master
|
|
* changes.
|
|
*/
|
|
for (t = ipalloc_state->all_ips; t != NULL; t = t->next) {
|
|
if (t->pnn != -1) {
|
|
(*rebalance_candidates)[t->pnn] = false;
|
|
}
|
|
}
|
|
|
|
/* 3rd step: if a node is forced to re-balance then
|
|
we allow failback onto the node */
|
|
if (ipalloc_state->force_rebalance_nodes == NULL) {
|
|
return true;
|
|
}
|
|
for (i = 0;
|
|
i < talloc_array_length(ipalloc_state->force_rebalance_nodes);
|
|
i++) {
|
|
uint32_t pnn = ipalloc_state->force_rebalance_nodes[i];
|
|
if (pnn >= numnodes) {
|
|
DEBUG(DEBUG_ERR,
|
|
(__location__ "unknown node %u\n", pnn));
|
|
continue;
|
|
}
|
|
|
|
DEBUG(DEBUG_NOTICE,
|
|
("Forcing rebalancing of IPs to node %u\n", pnn));
|
|
(*rebalance_candidates)[pnn] = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Allocate any unassigned addresses using the LCP2 algorithm to find
|
|
* the IP/node combination that will cost the least.
|
|
*/
|
|
static void lcp2_allocate_unassigned(struct ipalloc_state *ipalloc_state,
|
|
uint32_t *lcp2_imbalances)
|
|
{
|
|
struct public_ip_list *t;
|
|
int dstnode, numnodes;
|
|
|
|
int minnode;
|
|
uint32_t mindsum, dstdsum, dstimbl;
|
|
uint32_t minimbl = 0;
|
|
struct public_ip_list *minip;
|
|
|
|
bool should_loop = true;
|
|
bool have_unassigned = true;
|
|
|
|
numnodes = ipalloc_state->num;
|
|
|
|
while (have_unassigned && should_loop) {
|
|
should_loop = false;
|
|
|
|
DEBUG(DEBUG_DEBUG,(" ----------------------------------------\n"));
|
|
DEBUG(DEBUG_DEBUG,(" CONSIDERING MOVES (UNASSIGNED)\n"));
|
|
|
|
minnode = -1;
|
|
mindsum = 0;
|
|
minip = NULL;
|
|
|
|
/* loop over each unassigned ip. */
|
|
for (t = ipalloc_state->all_ips; t != NULL ; t = t->next) {
|
|
if (t->pnn != -1) {
|
|
continue;
|
|
}
|
|
|
|
for (dstnode = 0; dstnode < numnodes; dstnode++) {
|
|
/* only check nodes that can actually takeover this ip */
|
|
if (!can_node_takeover_ip(ipalloc_state,
|
|
dstnode,
|
|
t)) {
|
|
/* no it couldnt so skip to the next node */
|
|
continue;
|
|
}
|
|
|
|
dstdsum = ip_distance_2_sum(&(t->addr),
|
|
ipalloc_state->all_ips,
|
|
dstnode);
|
|
dstimbl = lcp2_imbalances[dstnode] + dstdsum;
|
|
DEBUG(DEBUG_DEBUG,
|
|
(" %s -> %d [+%d]\n",
|
|
ctdb_sock_addr_to_string(ipalloc_state,
|
|
&(t->addr),
|
|
false),
|
|
dstnode,
|
|
dstimbl - lcp2_imbalances[dstnode]));
|
|
|
|
|
|
if ((minnode == -1) || (dstdsum < mindsum)) {
|
|
minnode = dstnode;
|
|
minimbl = dstimbl;
|
|
mindsum = dstdsum;
|
|
minip = t;
|
|
should_loop = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG(DEBUG_DEBUG,(" ----------------------------------------\n"));
|
|
|
|
/* If we found one then assign it to the given node. */
|
|
if (minnode != -1) {
|
|
minip->pnn = minnode;
|
|
lcp2_imbalances[minnode] = minimbl;
|
|
DEBUG(DEBUG_INFO,(" %s -> %d [+%d]\n",
|
|
ctdb_sock_addr_to_string(
|
|
ipalloc_state,
|
|
&(minip->addr), false),
|
|
minnode,
|
|
mindsum));
|
|
}
|
|
|
|
/* There might be a better way but at least this is clear. */
|
|
have_unassigned = false;
|
|
for (t = ipalloc_state->all_ips; t != NULL; t = t->next) {
|
|
if (t->pnn == -1) {
|
|
have_unassigned = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We know if we have an unassigned addresses so we might as
|
|
* well optimise.
|
|
*/
|
|
if (have_unassigned) {
|
|
for (t = ipalloc_state->all_ips; t != NULL; t = t->next) {
|
|
if (t->pnn == -1) {
|
|
DEBUG(DEBUG_WARNING,
|
|
("Failed to find node to cover ip %s\n",
|
|
ctdb_sock_addr_to_string(ipalloc_state,
|
|
&t->addr,
|
|
false)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* LCP2 algorithm for rebalancing the cluster. Given a candidate node
|
|
* to move IPs from, determines the best IP/destination node
|
|
* combination to move from the source node.
|
|
*/
|
|
static bool lcp2_failback_candidate(struct ipalloc_state *ipalloc_state,
|
|
int srcnode,
|
|
uint32_t *lcp2_imbalances,
|
|
bool *rebalance_candidates)
|
|
{
|
|
int dstnode, mindstnode, numnodes;
|
|
uint32_t srcimbl, srcdsum, dstimbl, dstdsum;
|
|
uint32_t minsrcimbl, mindstimbl;
|
|
struct public_ip_list *minip;
|
|
struct public_ip_list *t;
|
|
|
|
/* Find an IP and destination node that best reduces imbalance. */
|
|
srcimbl = 0;
|
|
minip = NULL;
|
|
minsrcimbl = 0;
|
|
mindstnode = -1;
|
|
mindstimbl = 0;
|
|
|
|
numnodes = ipalloc_state->num;
|
|
|
|
DEBUG(DEBUG_DEBUG,(" ----------------------------------------\n"));
|
|
DEBUG(DEBUG_DEBUG,(" CONSIDERING MOVES FROM %d [%d]\n",
|
|
srcnode, lcp2_imbalances[srcnode]));
|
|
|
|
for (t = ipalloc_state->all_ips; t != NULL; t = t->next) {
|
|
/* Only consider addresses on srcnode. */
|
|
if (t->pnn != srcnode) {
|
|
continue;
|
|
}
|
|
|
|
/* What is this IP address costing the source node? */
|
|
srcdsum = ip_distance_2_sum(&(t->addr),
|
|
ipalloc_state->all_ips,
|
|
srcnode);
|
|
srcimbl = lcp2_imbalances[srcnode] - srcdsum;
|
|
|
|
/* Consider this IP address would cost each potential
|
|
* destination node. Destination nodes are limited to
|
|
* those that are newly healthy, since we don't want
|
|
* to do gratuitous failover of IPs just to make minor
|
|
* balance improvements.
|
|
*/
|
|
for (dstnode = 0; dstnode < numnodes; dstnode++) {
|
|
if (!rebalance_candidates[dstnode]) {
|
|
continue;
|
|
}
|
|
|
|
/* only check nodes that can actually takeover this ip */
|
|
if (!can_node_takeover_ip(ipalloc_state, dstnode,
|
|
t)) {
|
|
/* no it couldnt so skip to the next node */
|
|
continue;
|
|
}
|
|
|
|
dstdsum = ip_distance_2_sum(&(t->addr),
|
|
ipalloc_state->all_ips,
|
|
dstnode);
|
|
dstimbl = lcp2_imbalances[dstnode] + dstdsum;
|
|
DEBUG(DEBUG_DEBUG,(" %d [%d] -> %s -> %d [+%d]\n",
|
|
srcnode, -srcdsum,
|
|
ctdb_sock_addr_to_string(
|
|
ipalloc_state,
|
|
&(t->addr), false),
|
|
dstnode, dstdsum));
|
|
|
|
if ((dstimbl < lcp2_imbalances[srcnode]) &&
|
|
(dstdsum < srcdsum) && \
|
|
((mindstnode == -1) || \
|
|
((srcimbl + dstimbl) < (minsrcimbl + mindstimbl)))) {
|
|
|
|
minip = t;
|
|
minsrcimbl = srcimbl;
|
|
mindstnode = dstnode;
|
|
mindstimbl = dstimbl;
|
|
}
|
|
}
|
|
}
|
|
DEBUG(DEBUG_DEBUG,(" ----------------------------------------\n"));
|
|
|
|
if (mindstnode != -1) {
|
|
/* We found a move that makes things better... */
|
|
DEBUG(DEBUG_INFO,
|
|
("%d [%d] -> %s -> %d [+%d]\n",
|
|
srcnode, minsrcimbl - lcp2_imbalances[srcnode],
|
|
ctdb_sock_addr_to_string(ipalloc_state,
|
|
&(minip->addr), false),
|
|
mindstnode, mindstimbl - lcp2_imbalances[mindstnode]));
|
|
|
|
|
|
lcp2_imbalances[srcnode] = minsrcimbl;
|
|
lcp2_imbalances[mindstnode] = mindstimbl;
|
|
minip->pnn = mindstnode;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
struct lcp2_imbalance_pnn {
|
|
uint32_t imbalance;
|
|
int pnn;
|
|
};
|
|
|
|
static int lcp2_cmp_imbalance_pnn(const void * a, const void * b)
|
|
{
|
|
const struct lcp2_imbalance_pnn * lipa = (const struct lcp2_imbalance_pnn *) a;
|
|
const struct lcp2_imbalance_pnn * lipb = (const struct lcp2_imbalance_pnn *) b;
|
|
|
|
if (lipa->imbalance > lipb->imbalance) {
|
|
return -1;
|
|
} else if (lipa->imbalance == lipb->imbalance) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* LCP2 algorithm for rebalancing the cluster. This finds the source
|
|
* node with the highest LCP2 imbalance, and then determines the best
|
|
* IP/destination node combination to move from the source node.
|
|
*/
|
|
static void lcp2_failback(struct ipalloc_state *ipalloc_state,
|
|
uint32_t *lcp2_imbalances,
|
|
bool *rebalance_candidates)
|
|
{
|
|
int i, numnodes;
|
|
struct lcp2_imbalance_pnn * lips;
|
|
bool again;
|
|
|
|
numnodes = ipalloc_state->num;
|
|
|
|
try_again:
|
|
/* Put the imbalances and nodes into an array, sort them and
|
|
* iterate through candidates. Usually the 1st one will be
|
|
* used, so this doesn't cost much...
|
|
*/
|
|
DEBUG(DEBUG_DEBUG,("+++++++++++++++++++++++++++++++++++++++++\n"));
|
|
DEBUG(DEBUG_DEBUG,("Selecting most imbalanced node from:\n"));
|
|
lips = talloc_array(ipalloc_state, struct lcp2_imbalance_pnn, numnodes);
|
|
for (i = 0; i < numnodes; i++) {
|
|
lips[i].imbalance = lcp2_imbalances[i];
|
|
lips[i].pnn = i;
|
|
DEBUG(DEBUG_DEBUG,(" %d [%d]\n", i, lcp2_imbalances[i]));
|
|
}
|
|
qsort(lips, numnodes, sizeof(struct lcp2_imbalance_pnn),
|
|
lcp2_cmp_imbalance_pnn);
|
|
|
|
again = false;
|
|
for (i = 0; i < numnodes; i++) {
|
|
/* This means that all nodes had 0 or 1 addresses, so
|
|
* can't be imbalanced.
|
|
*/
|
|
if (lips[i].imbalance == 0) {
|
|
break;
|
|
}
|
|
|
|
if (lcp2_failback_candidate(ipalloc_state,
|
|
lips[i].pnn,
|
|
lcp2_imbalances,
|
|
rebalance_candidates)) {
|
|
again = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
talloc_free(lips);
|
|
if (again) {
|
|
goto try_again;
|
|
}
|
|
}
|
|
|
|
bool ipalloc_lcp2(struct ipalloc_state *ipalloc_state)
|
|
{
|
|
uint32_t *lcp2_imbalances;
|
|
bool *rebalance_candidates;
|
|
int numnodes, i;
|
|
bool have_rebalance_candidates;
|
|
bool ret = true;
|
|
|
|
unassign_unsuitable_ips(ipalloc_state);
|
|
|
|
if (!lcp2_init(ipalloc_state,
|
|
&lcp2_imbalances, &rebalance_candidates)) {
|
|
ret = false;
|
|
goto finished;
|
|
}
|
|
|
|
lcp2_allocate_unassigned(ipalloc_state, lcp2_imbalances);
|
|
|
|
/* If we don't want IPs to fail back then don't rebalance IPs. */
|
|
if (ipalloc_state->no_ip_failback) {
|
|
goto finished;
|
|
}
|
|
|
|
/* It is only worth continuing if we have suitable target
|
|
* nodes to transfer IPs to. This check is much cheaper than
|
|
* continuing on...
|
|
*/
|
|
numnodes = ipalloc_state->num;
|
|
have_rebalance_candidates = false;
|
|
for (i=0; i<numnodes; i++) {
|
|
if (rebalance_candidates[i]) {
|
|
have_rebalance_candidates = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!have_rebalance_candidates) {
|
|
goto finished;
|
|
}
|
|
|
|
/* Now, try to make sure the ip adresses are evenly distributed
|
|
across the nodes.
|
|
*/
|
|
lcp2_failback(ipalloc_state, lcp2_imbalances, rebalance_candidates);
|
|
|
|
finished:
|
|
return ret;
|
|
}
|