summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Schwenke <martin@meltin.net>2016-12-11 09:09:44 +1100
committerAmitay Isaacs <amitay@samba.org>2016-12-19 07:58:45 +0100
commit59dc07ee4ab6350a2736a1bd47ba26be7fae67e2 (patch)
tree868b81a884df0c90625680bb22763af96677b49a
parent641b69da80f66e399c6ad78183067ccb4a76e02e (diff)
downloadsamba-59dc07ee4ab6350a2736a1bd47ba26be7fae67e2.tar.gz
ctdb-tests: Remove the python LCP2 simulation
It isn't used anywhere and doesn't contain some of the optimisations that have since gone into the C code. Signed-off-by: Martin Schwenke <martin@meltin.net> Reviewed-by: Amitay Isaacs <amitay@gmail.com> Autobuild-User(master): Amitay Isaacs <amitay@samba.org> Autobuild-Date(master): Mon Dec 19 07:58:45 CET 2016 on sn-devel-144
-rw-r--r--ctdb/tests/takeover/simulation/README6
-rwxr-xr-xctdb/tests/takeover/simulation/ctdb_takeover.py888
-rwxr-xr-xctdb/tests/takeover/simulation/hey_jude.py24
-rwxr-xr-xctdb/tests/takeover/simulation/ip_groups1.py25
-rwxr-xr-xctdb/tests/takeover/simulation/ip_groups2.py20
-rwxr-xr-xctdb/tests/takeover/simulation/ip_groups3.py27
-rwxr-xr-xctdb/tests/takeover/simulation/ip_groups4.py25
-rwxr-xr-xctdb/tests/takeover/simulation/ip_groups5.py23
-rwxr-xr-xctdb/tests/takeover/simulation/mgmt_simple.py22
-rwxr-xr-xctdb/tests/takeover/simulation/node_group.py46
-rwxr-xr-xctdb/tests/takeover/simulation/node_group_extra.py31
-rwxr-xr-xctdb/tests/takeover/simulation/node_group_simple.py26
-rwxr-xr-xctdb/tests/takeover/simulation/nondet_path_01.py25
13 files changed, 0 insertions, 1188 deletions
diff --git a/ctdb/tests/takeover/simulation/README b/ctdb/tests/takeover/simulation/README
deleted file mode 100644
index 4a8267bf6e1..00000000000
--- a/ctdb/tests/takeover/simulation/README
+++ /dev/null
@@ -1,6 +0,0 @@
-This contains a Python simulation of CTDB's IP reallocation algorithm.
-
-It is useful for experimenting with improvements.
-
-To use this on RHEL5 you'll need python2.6 from EPEL
-<http://fedoraproject.org/wiki/EPEL>.
diff --git a/ctdb/tests/takeover/simulation/ctdb_takeover.py b/ctdb/tests/takeover/simulation/ctdb_takeover.py
deleted file mode 100755
index 1aeeaca67e8..00000000000
--- a/ctdb/tests/takeover/simulation/ctdb_takeover.py
+++ /dev/null
@@ -1,888 +0,0 @@
-#!/usr/bin/env python
-
-# ctdb ip takeover code
-
-# Copyright (C) Martin Schwenke, Ronnie Sahlberg 2010, 2011
-
-# Based on original CTDB C code:
-#
-# Copyright (C) Ronnie Sahlberg 2007
-# Copyright (C) Andrew Tridgell 2007
-
-# 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/>.
-
-
-import os
-import sys
-# Use optparse since newer argparse not available in RHEL5/EPEL.
-from optparse import OptionParser
-import copy
-import random
-import itertools
-
-# For parsing IP addresses
-import socket
-import struct
-
-# For external algorithm
-import subprocess
-import re
-
-options = None
-
-def process_args(extra_options=[]):
- global options
-
- parser = OptionParser(option_list=extra_options)
-
- parser.add_option("--nd",
- action="store_false", dest="deterministic_public_ips",
- default=True,
- help="turn off deterministic_public_ips")
- parser.add_option("--ni",
- action="store_true", dest="no_ip_failback", default=False,
- help="turn on no_ip_failback")
- parser.add_option("-L", "--lcp2",
- action="store_true", dest="lcp2", default=False,
- help="use LCP2 IP rebalancing algorithm [default: %default]")
- parser.add_option("-e", "--external",
- action="store_true", dest="external", default=False,
- help="use external test program to implement IP allocation algorithm [default: %default]")
- parser.add_option("-b", "--balance",
- action="store_true", dest="balance", default=False,
- help="show (im)balance information after each event")
- parser.add_option("-d", "--diff",
- action="store_true", dest="diff", default=False,
- help="show IP address movements for each event")
- parser.add_option("-n", "--no-print",
- action="store_false", dest="show", default=True,
- help="don't show IP address layout after each event")
- parser.add_option("-v", "--verbose",
- action="count", dest="verbose", default=0,
- help="print information and actions taken to stdout")
- parser.add_option("-r", "--retries",
- action="store", type="int", dest="retries", default=5,
- help="number of retry loops for rebalancing non-deterministic failback [default: %default]")
- parser.add_option("-i", "--iterations",
- action="store", type="int", dest="iterations",
- default=1000,
- help="number of iterations to run in test [default: %default]")
- parser.add_option("-o", "--odds",
- action="store", type="int", dest="odds", default=4,
- help="make the chances of a failover 1 in ODDS [default: %default]")
- parser.add_option("-A", "--aggressive",
- action="store_true", dest="aggressive", default=False,
- help="apply ODDS to try to flip each node [default: %default]")
-
- def seed_callback(option, opt, value, parser):
- random.seed(value)
- parser.add_option("-s", "--seed",
- action="callback", type="int", callback=seed_callback,
- help="initial random number seed for random events")
-
- parser.add_option("-x", "--exit",
- action="store_true", dest="exit", default=False,
- help="exit on the 1st gratuitous IP move or IP imbalance")
- parser.add_option("-H", "--hard-imbalance-limit",
- action="store", type="int", dest="hard_limit", default=1,
- help="exceeding this limit causes termination [default: %default]")
- parser.add_option("-S", "--soft-imbalance-limit",
- action="store", type="int", dest="soft_limit", default=1,
- help="exceeding this limit increments a counter [default: %default]")
-
- (options, args) = parser.parse_args()
-
- if len(args) != 0:
- parser.error("too many arguments")
-
- # Could use a callback for this or change the default, but
- # laziness is sometimes a virtue. ;-)
- if options.lcp2:
- options.deterministic_public_ips = False
-
-def print_begin(t, delim='='):
- print delim * 40
- print "%s:" % (t)
-
-def print_end():
- print "-" * 40
-
-def verbose_begin(t):
- if options.verbose > 0:
- print_begin(t)
-
-def verbose_end():
- if options.verbose > 0:
- print_end()
-
-def verbose_print(t):
- if options.verbose > 0:
- if not type(t) == list:
- t = [t]
- if t != []:
- print "\n".join([str(i) for i in t])
-
-# more than this and we switch to the logging module... :-)
-def debug_begin(t):
- if options.verbose > 1:
- print_begin(t, '-')
-
-def debug_end():
- if options.verbose > 1:
- print_end()
-
-def debug_print(t):
- if options.verbose > 1:
- if not type(t) == list:
- t = [t]
- if t != []:
- print "\n".join([str(i) for i in t])
-
-def ip_to_list_of_ints(ip):
- # Be lazy... but only expose errors in IPv4 addresses, since
- # they'll be more commonly used. :-)
- try:
- l = socket.inet_pton(socket.AF_INET6, ip)
- except:
- # Pad with leading 0s. This makes IPv4 addresses comparable
- # with IPv6 but reduces the overall effectiveness of the
- # algorithm. The alternative would be to treat these
- # addresses separately while trying to keep all the IPs in
- # overall balance.
- l = "".join(itertools.repeat("\0", 12)) + \
- socket.inet_pton(socket.AF_INET, ip)
-
- return map(lambda x: struct.unpack('B', x)[0], l)
-
-def ip_distance(ip1, ip2):
- """Calculate the distance between 2 IPs.
-
- 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."""
-
- distance = 0
- for (o1, o2) in zip(ip_to_list_of_ints(ip1), ip_to_list_of_ints(ip2)):
- # XOR this pair of octets
- x = o1 ^ o2
- # count number leading zeroes
- if x == 0:
- distance += 8
- else:
- # bin() gives minimal length '0bNNN' string
- distance += (8 - (len(bin(x)) - 2))
- break
-
- return distance
-
-def ip_distance_2_sum(ip, ips):
- """Calculate the IP distance for the given IP relative to IPs.
-
- This could be made more efficient by insering ip_distance_2 into
- the loop in this function. However, that would result in some
- loss of clarity and also will not be necessary in a C
- implemntation."""
-
- sum = 0
- for i in ips:
- sum += ip_distance(ip, i) ** 2
-
- return sum
-
-def imbalance_metric(ips):
- """Return the imbalance metric for a group of IPs.
-
- This is the sum of squares of the IP distances between each pair of IPs."""
- if len(ips) > 1:
- (h, t) = (ips[0], ips[1:])
- return ip_distance_2_sum(h, t) + imbalance_metric(t)
- else:
- return 0
-
-def mean(l):
- return float(sum(l))/len(l)
-
-class Node(object):
- def __init__(self, public_addresses):
- # List of list allows groups of IPs to be passed in. They're
- # not actually used in the algorithm but are just used by
- # calculate_imbalance() for checking the simulation. Note
- # that people can pass in garbage and make this code
- # fail... but we're all friends here in simulation world...
- # :-)
- if type(public_addresses[0]) is str:
- self.public_addresses = set(public_addresses)
- self.ip_groups = []
- else:
- # flatten
- self.public_addresses = set([i for s in public_addresses for i in s])
- self.ip_groups = public_addresses
-
- self.current_addresses = set()
- self.healthy = True
- self.imbalance = -1
-
- def __str__(self):
- return "%s %s%s" % \
- ("*" if len(self.public_addresses) == 0 else \
- (" " if self.healthy else "#"),
- sorted(list(self.current_addresses)),
- " %d" % self.imbalance if options.lcp2 else "")
-
- def can_node_serve_ip(self, ip):
- return ip in self.public_addresses
-
- def node_ip_coverage(self, ips=None):
- return len([a for a in self.current_addresses if ips == None or a in ips])
-
- def set_imbalance(self, imbalance=-1):
- """Set the imbalance metric to the given value. If none given
- then calculate it."""
-
- if imbalance != -1:
- self.imbalance = imbalance
- else:
- self.imbalance = imbalance_metric(list(self.current_addresses))
-
- def get_imbalance(self):
- return self.imbalance
-
-class Cluster(object):
- def __init__(self):
- self.nodes = []
- self.deterministic_public_ips = options.deterministic_public_ips
- self.no_ip_failback = options.no_ip_failback
- self.all_public_ips = set()
-
- # Statistics
- self.ip_moves = []
- self.grat_ip_moves = []
- self.imbalance = []
- self.imbalance_groups = []
- self.imbalance_count = 0
- self.imbalance_groups_count = itertools.repeat(0)
- self.imbalance_metric = []
- self.events = -1
- self.num_unhealthy = []
-
- self.prev = None
-
- def __str__(self):
- return "\n".join(["%2d %s" % (i, n) \
- for (i, n) in enumerate(self.nodes)])
-
- # This is naive. It assumes that IP groups are indicated by the
- # 1st node having IP groups.
- def have_ip_groups(self):
- return (len(self.nodes[0].ip_groups) > 0)
-
- def print_statistics(self):
- print_begin("STATISTICS")
- print "Events: %6d" % self.events
- print "Total IP moves: %6d" % sum(self.ip_moves)
- print "Gratuitous IP moves: %6d" % sum(self.grat_ip_moves)
- print "Max imbalance: %6d" % max(self.imbalance)
- if self.have_ip_groups():
- print "Max group imbalance counts: ", map(max, zip(*self.imbalance_groups))
- print "Mean imbalance: %f" % mean(self.imbalance)
- if self.have_ip_groups():
- print "Mean group imbalances counts: ", map(mean, zip(*self.imbalance_groups))
- print "Final imbalance: %6d" % self.imbalance[-1]
- if self.have_ip_groups():
- print "Final group imbalances: ", self.imbalance_groups[-1]
- if options.lcp2:
- print "Max LCP2 imbalance : %6d" % max(self.imbalance_metric)
- print "Soft imbalance count: %6d" % self.imbalance_count
- if self.have_ip_groups():
- print "Soft imbalance group counts: ", self.imbalance_groups_count
- if options.lcp2:
- print "Final LCP2 imbalance : %6d" % self.imbalance_metric[-1]
- print "Maximum unhealthy: %6d" % max(self.num_unhealthy)
- print_end()
-
- def find_pnn_with_ip(self, ip):
- for (i, n) in enumerate(self.nodes):
- if ip in n.current_addresses:
- return i
- return -1
-
- def quietly_remove_ip(self, ip):
- # Remove address from old node.
- old = self.find_pnn_with_ip(ip)
- if old != -1:
- self.nodes[old].current_addresses.remove(ip)
-
- def add_node(self, node):
- self.nodes.append(node)
- self.all_public_ips |= node.public_addresses
-
- def healthy(self, *pnns):
- verbose_begin("HEALTHY")
-
- for pnn in pnns:
- self.nodes[pnn].healthy = True
- verbose_print(pnn)
-
- verbose_end()
-
- def unhealthy(self, *pnns):
-
- verbose_begin("UNHEALTHY")
-
- for pnn in pnns:
- self.nodes[pnn].healthy = False
- verbose_print(pnn)
-
- verbose_end()
-
- def do_something_random(self):
-
- """Make random node(s) healthy or unhealthy.
-
- If options.aggressive is False then: If all nodes are healthy
- or unhealthy, then invert one of them; otherwise, there's a 1
- in options.odds chance of making another node unhealthy.
-
- If options.aggressive is True then: For each node there is a 1
- in options.odds chance of flipping the state of that node
- between healthy and unhealthy."""
-
- if not options.aggressive:
- num_nodes = len(self.nodes)
- healthy_pnns = [i for (i,n) in enumerate(self.nodes) if n.healthy]
- num_healthy = len(healthy_pnns)
-
- if num_nodes == num_healthy:
- self.unhealthy(random.randint(0, num_nodes-1))
- elif num_healthy == 0:
- self.healthy(random.randint(0, num_nodes-1))
- elif random.randint(1, options.odds) == 1:
- self.unhealthy(random.choice(healthy_pnns))
- else:
- all_pnns = range(num_nodes)
- unhealthy_pnns = sorted(list(set(all_pnns) - set(healthy_pnns)))
- self.healthy(random.choice(unhealthy_pnns))
- else:
- # We need to make at least one change or we retry...x
- changed = False
- while not changed:
- for (pnn, n) in enumerate(self.nodes):
- if random.randint(1, options.odds) == 1:
- changed = True
- if n.healthy:
- self.unhealthy(pnn)
- else:
- self.healthy(pnn)
-
- def random_iterations(self):
- i = 1
- while i <= options.iterations:
- verbose_begin("EVENT %d" % i)
- verbose_end()
- self.do_something_random()
- if self.recover() and options.exit:
- break
- i += 1
-
- self.print_statistics()
-
- def imbalance_for_ips(self, ips):
-
- imbalance = 0
-
- maxnode = -1
- minnode = -1
-
- for ip in ips:
- for (i, n) in enumerate(self.nodes):
-
- if not n.healthy or not n.can_node_serve_ip(ip):
- continue
-
- num = n.node_ip_coverage(ips)
-
- if maxnode == -1 or num > maxnum:
- maxnode = i
- maxnum = num
-
- if minnode == -1 or num < minnum:
- minnode = i
- minnum = num
-
- if maxnode == -1 or minnode == -1:
- continue
-
- i = maxnum - minnum
- #if i < 2:
- # i = 0
- imbalance = max([imbalance, i])
-
- return imbalance
-
-
- def calculate_imbalance(self):
-
- # First, do all the assigned IPs.
- assigned = sorted([ip
- for n in self.nodes
- for ip in n.current_addresses])
-
- i = self.imbalance_for_ips(assigned)
-
- ig = []
- # FIXME? If dealing with IP groups, assume the nodes are all
- # the same.
- for ips in self.nodes[0].ip_groups:
- gi = self.imbalance_for_ips(ips)
- ig.append(gi)
-
- return (i, ig)
-
-
- def diff(self):
- """Calculate differences in IP assignments between self and prev.
-
- Gratuitous IP moves (from a healthy node to a healthy node)
- are prefixed by !!."""
-
- ip_moves = 0
- grat_ip_moves = 0
- details = []
-
- for (new, n) in enumerate(self.nodes):
- for ip in n.current_addresses:
- old = self.prev.find_pnn_with_ip(ip)
- if old != new:
- ip_moves += 1
- if old != -1 and \
- self.prev.nodes[new].healthy and \
- self.nodes[new].healthy and \
- self.nodes[old].healthy and \
- self.prev.nodes[old].healthy:
- prefix = "!!"
- grat_ip_moves += 1
- else:
- prefix = " "
- details.append("%s %s: %d -> %d" %
- (prefix, ip, old, new))
-
- return (ip_moves, grat_ip_moves, details)
-
- def find_takeover_node(self, ip):
-
- pnn = -1
- min = 0
- for (i, n) in enumerate(self.nodes):
- if not n.healthy:
- continue
-
- if not n.can_node_serve_ip(ip):
- continue
-
- num = n.node_ip_coverage()
-
- if (pnn == -1):
- pnn = i
- min = num
- else:
- if num < min:
- pnn = i
- min = num
-
- if pnn == -1:
- verbose_print("Could not find node to take over public address %s" % ip)
- return False
-
- self.nodes[pnn].current_addresses.add(ip)
-
- verbose_print("%s -> %d" % (ip, pnn))
- return True
-
- def basic_allocate_unassigned(self):
-
- assigned = set([ip for n in self.nodes for ip in n.current_addresses])
- unassigned = sorted(list(self.all_public_ips - assigned))
-
- for ip in unassigned:
- self.find_takeover_node(ip)
-
- def basic_failback(self, retries_l):
-
- assigned = sorted([ip
- for n in self.nodes
- for ip in n.current_addresses])
- for ip in assigned:
-
- maxnode = -1
- minnode = -1
- for (i, n) in enumerate(self.nodes):
- if not n.healthy:
- continue
-
- if not n.can_node_serve_ip(ip):
- continue
-
- num = n.node_ip_coverage()
-
- if maxnode == -1:
- maxnode = i
- maxnum = num
- else:
- if num > maxnum:
- maxnode = i
- maxnum = num
- if minnode == -1:
- minnode = i
- minnum = num
- else:
- if num < minnum:
- minnode = i
- minnum = num
-
- if maxnode == -1:
- print "Could not find maxnode. May not be able to serve ip", ip
- continue
-
- #if self.deterministic_public_ips:
- # continue
-
- if maxnum > minnum + 1 and retries_l[0] < options.retries:
- # Remove the 1st ip from maxnode
- t = sorted(list(self.nodes[maxnode].current_addresses))
- realloc = t[0]
- verbose_print("%s <- %d" % (realloc, maxnode))
- self.nodes[maxnode].current_addresses.remove(realloc)
- # Redo the outer loop.
- retries_l[0] += 1
- return True
-
- return False
-
-
- def lcp2_allocate_unassigned(self):
-
- # Assign as many unassigned addresses as possible. Keep
- # selecting the optimal assignment until we don't manage to
- # assign anything.
- assigned = set([ip for n in self.nodes for ip in n.current_addresses])
- unassigned = sorted(list(self.all_public_ips - assigned))
-
- should_loop = True
- while len(unassigned) > 0 and should_loop:
- should_loop = False
-
- debug_begin(" CONSIDERING MOVES (UNASSIGNED)")
-
- minnode = -1
- mindsum = 0
- minip = None
-
- for ip in unassigned:
- for dstnode in range(len(self.nodes)):
- if self.nodes[dstnode].can_node_serve_ip(ip) and \
- self.nodes[dstnode].healthy:
- dstdsum = ip_distance_2_sum(ip, self.nodes[dstnode].current_addresses)
- dstimbl = self.nodes[dstnode].get_imbalance() + dstdsum
- debug_print(" %s -> %d [+%d]" % \
- (ip,
- dstnode,
- dstimbl - self.nodes[dstnode].get_imbalance()))
-
- if (minnode == -1) or (dstdsum < mindsum):
- minnode = dstnode
- minimbl = dstimbl
- mindsum = dstdsum
- minip = ip
- should_loop = True
- debug_end()
-
- if minnode != -1:
- self.nodes[minnode].current_addresses.add(minip)
- self.nodes[minnode].set_imbalance(self.nodes[minnode].get_imbalance() + mindsum)
- verbose_print("%s -> %d [+%d]" % (minip, minnode, mindsum))
- unassigned.remove(minip)
-
- for ip in unassigned:
- verbose_print("Could not find node to take over public address %s" % ip)
-
- def lcp2_failback(self, targets):
-
- # Get the node with the highest imbalance metric.
- srcnode = -1
- maximbl = 0
- for (pnn, n) in enumerate(self.nodes):
- b = n.get_imbalance()
- if (srcnode == -1) or (b > maximbl):
- srcnode = pnn
- maximbl = b
-
- # This means that all nodes had 0 or 1 addresses, so can't
- # be imbalanced.
- if maximbl == 0:
- return False
-
- # We'll need this a few times...
- ips = self.nodes[srcnode].current_addresses
-
- # Find an IP and destination node that best reduces imbalance.
- optimum = None
- debug_begin(" CONSIDERING MOVES FROM %d [%d]" % (srcnode, maximbl))
- for ip in ips:
- # What is this IP address costing the source node?
- srcdsum = ip_distance_2_sum(ip, ips - set([ip]))
- srcimbl = maximbl - 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 in targets:
- if self.nodes[dstnode].can_node_serve_ip(ip) and \
- self.nodes[dstnode].healthy:
- dstdsum = ip_distance_2_sum(ip, self.nodes[dstnode].current_addresses)
- dstimbl = self.nodes[dstnode].get_imbalance() + dstdsum
- debug_print(" %d [%d] -> %s -> %d [+%d]" % \
- (srcnode,
- srcimbl - self.nodes[srcnode].get_imbalance(),
- ip,
- dstnode,
- dstimbl - self.nodes[dstnode].get_imbalance()))
-
- if (dstimbl < maximbl) and (dstdsum < srcdsum):
- if optimum is None:
- optimum = (ip, srcnode, srcimbl, dstnode, dstimbl)
- else:
- (x, sn, si, dn, di) = optimum
- if (srcimbl + dstimbl) < (si + di):
- optimum = (ip, srcnode, srcimbl, dstnode, dstimbl)
- debug_end()
-
- if optimum is not None:
- # We found a move that makes things better...
- (ip, srcnode, srcimbl, dstnode, dstimbl) = optimum
- ini_srcimbl = self.nodes[srcnode].get_imbalance()
- ini_dstimbl = self.nodes[dstnode].get_imbalance()
-
- self.nodes[srcnode].current_addresses.remove(ip)
- self.nodes[srcnode].set_imbalance(srcimbl)
-
- self.nodes[dstnode].current_addresses.add(ip)
- self.nodes[dstnode].set_imbalance(dstimbl)
-
- verbose_print("%d [%d] -> %s -> %d [+%d]" % \
- (srcnode,
- srcimbl - ini_srcimbl,
- ip,
- dstnode,
- dstimbl - ini_dstimbl))
-
- return True
-
- return False
-
- def ctdb_takeover_run_python(self):
-
- # Don't bother with the num_healthy stuff. It is an
- # irrelevant detail.
-
- # We just keep the allocate IPs in the current_addresses field
- # of the node. This needs to readable, not efficient!
-
- if self.deterministic_public_ips:
- # Remap everything.
- addr_list = sorted(list(self.all_public_ips))
- for (i, ip) in enumerate(addr_list):
- self.quietly_remove_ip(ip)
- # Add addresses to new node.
- pnn = i % len(self.nodes)
- self.nodes[pnn].current_addresses.add(ip)
- verbose_print("%s -> %d" % (ip, pnn))
-
- # Remove public addresses from unhealthy nodes.
- for (pnn, n) in enumerate(self.nodes):
- if not n.healthy:
- verbose_print(["%s <- %d" % (ip, pnn)
- for ip in n.current_addresses])
- n.current_addresses = set()
-
- # If a node can't serve an assigned address then remove it.
- for n in self.nodes:
- verbose_print(["%s <- %d" % (ip, pnn)
- for ip in n.current_addresses - n.public_addresses])
- n.current_addresses &= n.public_addresses
-
- if options.lcp2:
- newly_healthy = [pnn for (pnn, n) in enumerate(self.nodes)
- if len(n.current_addresses) == 0 and n.healthy]
- for n in self.nodes:
- n.set_imbalance()
-
- # We'll only retry the balancing act up to options.retries
- # times (for the basic non-deterministic algorithm). This
- # nonsense gives us a reference on the retries count in
- # Python. It will be easier in C. :-)
- # For LCP2 we reassignas many IPs from heavily "loaded" nodes
- # to nodes that are newly healthy, looping until we fail to
- # reassign an IP.
- retries_l = [0]
- should_loop = True
- while should_loop:
- should_loop = False
-
- if options.lcp2:
- self.lcp2_allocate_unassigned()
- else:
- self.basic_allocate_unassigned()
-
- if self.no_ip_failback or self.deterministic_public_ips:
- break
-
- if options.lcp2:
- if len(newly_healthy) == 0:
- break
- should_loop = self.lcp2_failback(newly_healthy)
- else:
- should_loop = self.basic_failback(retries_l)
-
- def ctdb_takeover_run_external(self):
-
- # Written while asleep...
-
- # Convert the cluster state to something that be fed to
- # ctdb_takeover_tests ipalloc ...
-
- in_lines = []
- for ip in sorted(list(self.all_public_ips)):
- allowed = []
- assigned = -1
- for (i, n) in enumerate(self.nodes):
- if n.can_node_serve_ip(ip):
- allowed.append("%s" % i)
- if ip in n.current_addresses:
- assigned = i
- line = "%s\t%d\t%s" % (ip, assigned, ",".join(allowed))
- in_lines.append(line)
-
- nodestates = ",".join(["0" if n.healthy else "1" for n in self.nodes])
-
- if options.lcp2:
- os.environ["CTDB_LCP2"] = "yes"
- if options.verbose > 1:
- os.environ["CTDB_TEST_LOGLEVEL"] = "4"
- elif options.verbose == 1:
- os.environ["CTDB_TEST_LOGLEVEL"] = "3"
- else:
- os.environ["CTDB_TEST_LOGLEVEL"] = "0"
-
- p = subprocess.Popen("../../bin/ctdb_takeover_tests ipalloc %s 2>&1" % nodestates,
- shell=True,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE)
- p.stdin.write("\n".join(in_lines))
- p.stdin.close()
-
- # Flush all of the assigned IPs.
- for n in self.nodes:
- n.current_addresses = set()
-
- # Uses the results to populate the current_addresses for each
- # node.
- for line in p.stdout.read().split("\n"):
- # Some lines are debug, some are the final IP
- # configuration. Let's use a gross hack that assumes any
- # line with 2 words is IP configuration. That will do for
- # now.
- words = re.split("\s+", line)
- if len(words) == 2:
- # Add the IP as current for the specified node.
- self.nodes[int(words[1])].current_addresses.add(words[0])
- else:
- # First 3 words are log date/time, remove them...
- print " ".join(words[3:])
-
- # Now fake up the LCP calculations.
- for n in self.nodes:
- n.set_imbalance()
-
- def ctdb_takeover_run(self):
-
- self.events += 1
-
- if options.external:
- return self.ctdb_takeover_run_external()
- else:
- return self.ctdb_takeover_run_python()
-
- def recover(self):
- verbose_begin("TAKEOVER")
-
- self.ctdb_takeover_run()
-
- verbose_end()
-
- grat_ip_moves = 0
-
- if self.prev is not None:
- (ip_moves, grat_ip_moves, details) = self.diff()
- self.ip_moves.append(ip_moves)
- self.grat_ip_moves.append(grat_ip_moves)
-
- if options.diff:
- print_begin("DIFF")
- print "\n".join(details)
- print_end()
-
- (imbalance, imbalance_groups) = self.calculate_imbalance()
- self.imbalance.append(imbalance)
- self.imbalance_groups.append(imbalance_groups)
-
- if imbalance > options.soft_limit:
- self.imbalance_count += 1
-
- # There must be a cleaner way...
- t = []
- for (c, i) in zip(self.imbalance_groups_count, imbalance_groups):
- if i > options.soft_limit:
- t.append(c + i)
- else:
- t.append(c)
- self.imbalance_groups_count = t
-
- imbalance_metric = max([n.get_imbalance() for n in self.nodes])
- self.imbalance_metric.append(imbalance_metric)
- if options.balance:
- print_begin("IMBALANCE")
- print "ALL IPS:", imbalance
- if self.have_ip_groups():
- print "IP GROUPS:", imbalance_groups
- if options.lcp2:
- print "LCP2 IMBALANCE:", imbalance_metric
- print_end()
-
- num_unhealthy = len(self.nodes) - \
- len([n for n in self.nodes if n.healthy])
- self.num_unhealthy.append(num_unhealthy)
-
- if options.show:
- print_begin("STATE")
- print self
- print_end()
-
- self.prev = None
- self.prev = copy.deepcopy(self)
-
- # True is bad!
- return (grat_ip_moves > 0) or \
- (not self.have_ip_groups() and imbalance > options.hard_limit) or \
- (self.have_ip_groups() and (max(imbalance_groups) > options.hard_limit))
diff --git a/ctdb/tests/takeover/simulation/hey_jude.py b/ctdb/tests/takeover/simulation/hey_jude.py
deleted file mode 100755
index a6b14c5c9b4..00000000000
--- a/ctdb/tests/takeover/simulation/hey_jude.py
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env python
-
-from ctdb_takeover import Cluster, Node, process_args
-
-process_args()
-
-addresses10 = ['10.4.20.%d' % n for n in range(154, 168)]
-addresses172a = ['172.20.106.%d' % n for n in range(110, 124)]
-addresses172b = ['172.20.107.%d' % n for n in range(110, 117)]
-
-c = Cluster()
-
-#for i in range(7):
-# c.add_node(Node([addresses10, addresses172]))
-
-
-for i in range(4):
- c.add_node(Node([addresses172a, addresses172b]))
-for i in range(3):
- c.add_node(Node(addresses10))
-
-c.recover()
-
-c.random_iterations()
diff --git a/ctdb/tests/takeover/simulation/ip_groups1.py b/ctdb/tests/takeover/simulation/ip_groups1.py
deleted file mode 100755
index 0808f466cf1..00000000000
--- a/ctdb/tests/takeover/simulation/ip_groups1.py
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/bin/env python
-
-# 2 IP groups, both on the same 5 nodes, with each group on different
-# interfaces/VLANs. One group has many more addresses to test how
-# well an "imbalanced" configuration will balance...
-
-from ctdb_takeover import Cluster, Node, process_args
-
-process_args()
-
-addresses20 = ['192.168.20.%d' % n for n in range(1, 13)]
-addresses128 = ['192.168.128.%d' % n for n in range(1, 5)]
-
-c = Cluster()
-
-for i in range(5):
- c.add_node(Node([addresses20, addresses128]))
-
-#for i in range(3):
-# c.add_node(Node([addresses20]))
-
-
-c.recover()
-
-c.random_iterations()
diff --git a/ctdb/tests/takeover/simulation/ip_groups2.py b/ctdb/tests/takeover/simulation/ip_groups2.py
deleted file mode 100755
index c6c10266461..00000000000
--- a/ctdb/tests/takeover/simulation/ip_groups2.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env python
-
-# 2 groups of addresses, combined into 1 pool so the checking
-# algorithm doesn't know about the groups, across 2 nodes.
-
-from ctdb_takeover import Cluster, Node, process_args
-
-process_args()
-
-addresses20 = ['192.168.20.%d' % n for n in range(1, 13)]
-addresses21 = ['192.168.21.%d' % n for n in range(1, 5)]
-
-c = Cluster()
-
-for i in range(2):
- c.add_node(Node(addresses20 + addresses21))
-
-c.recover()
-
-c.random_iterations()
diff --git a/ctdb/tests/takeover/simulation/ip_groups3.py b/ctdb/tests/takeover/simulation/ip_groups3.py
deleted file mode 100755
index 149946d72b4..00000000000
--- a/ctdb/tests/takeover/simulation/ip_groups3.py
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-
-# 4 IP groups, across 10 nodes, with each group on different
-# interfaces/VLANs. 80 addresses in total but not evenly balanced, to
-# help check some of the more extreme behaviour.
-
-from ctdb_takeover import Cluster, Node, process_args
-
-process_args()
-
-addresses1 = ['192.168.1.%d' % n for n in range(1, 41)]
-addresses2 = ['192.168.2.%d' % n for n in range(1, 21)]
-addresses3 = ['192.168.3.%d' % n for n in range(1, 11)]
-addresses4 = ['192.168.4.%d' % n for n in range(1, 11)]
-
-# Try detecting imbalance with square root of number of nodes? Or
-# just with a parameter indicating how unbalanced you're willing to
-# accept...
-
-c = Cluster()
-
-for i in range(10):
- c.add_node(Node([addresses1, addresses2, addresses3, addresses4]))
-
-c.recover()
-
-c.random_iterations()
diff --git a/ctdb/tests/takeover/simulation/ip_groups4.py b/ctdb/tests/takeover/simulation/ip_groups4.py
deleted file mode 100755
index fdcef7f0a69..00000000000
--- a/ctdb/tests/takeover/simulation/ip_groups4.py
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/bin/env python
-
-# 2 IP groups, across 2 nodes, with each group on different
-# interfaces. 4 addresses per group. A nice little canonical 2 node
-# configuration.
-
-from ctdb_takeover import Cluster, Node, process_args
-
-process_args()
-
-addresses1 = ['192.168.1.%d' % n for n in range(1, 5)]
-addresses2 = ['192.168.2.%d' % n for n in range(1, 5)]
-
-# Try detecting imbalance with square root of number of nodes? Or
-# just with a parameter indicating how unbalanced you're willing to
-# accept...
-
-c = Cluster()
-
-for i in range(2):
- c.add_node(Node([addresses1, addresses2]))
-
-c.recover()
-
-c.random_iterations()
diff --git a/ctdb/tests/takeover/simulation/ip_groups5.py b/ctdb/tests/takeover/simulation/ip_groups5.py
deleted file mode 100755
index 8c461506389..00000000000
--- a/ctdb/tests/takeover/simulation/ip_groups5.py
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python
-
-# 1 IP group, to test backward compatibility of LCP2 algorithm. 16
-# addresses across 4 nodes.
-
-from ctdb_takeover import Cluster, Node, process_args
-
-process_args()
-
-addresses1 = ['192.168.1.%d' % n for n in range(1, 17)]
-
-# Try detecting imbalance with square root of number of nodes? Or
-# just with a parameter indicating how unbalanced you're willing to
-# accept...
-
-c = Cluster()
-
-for i in range(4):
- c.add_node(Node(addresses1))
-
-c.recover()
-
-c.random_iterations()
diff --git a/ctdb/tests/takeover/simulation/mgmt_simple.py b/ctdb/tests/takeover/simulation/mgmt_simple.py
deleted file mode 100755
index f891199655a..00000000000
--- a/ctdb/tests/takeover/simulation/mgmt_simple.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env python
-
-# This is an example showing a current SONAS configuration with 3
-# interface node and a management node. When run with deterministic
-# IPs there are gratuitous IP reassignments.
-
-from ctdb_takeover import Cluster, Node, process_args
-
-process_args()
-
-addresses = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
-
-c = Cluster()
-
-for i in range(3):
- c.add_node(Node(addresses))
-
-c.add_node(Node([]))
-
-c.recover()
-
-c.random_iterations()
diff --git a/ctdb/tests/takeover/simulation/node_group.py b/ctdb/tests/takeover/simulation/node_group.py
deleted file mode 100755
index bf7de58aa97..00000000000
--- a/ctdb/tests/takeover/simulation/node_group.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env python
-
-# This demonstrates a node group configurations.
-#
-# Node groups can be defined with the syntax "-g N@IP0,IP1-IP2,IP3".
-# This says to create a group of N nodes with IPs IP0, IP1, ..., IP2,
-# IP3. Run it with deterministic IPs causes lots of gratuitous IP
-# reassignments. Running with --nd fixes this.
-
-import ctdb_takeover
-import sys
-from optparse import make_option
-import string
-
-ctdb_takeover.process_args([
- make_option("-g", "--group",
- action="append", type="string", dest="groups",
- help="define a node group using N@IPs syntax"),
- ])
-
-def expand_range(r):
- sr = r.split("-", 1)
- if len(sr) == 2:
- all = string.ascii_uppercase + string.ascii_lowercase
- sr = list(all[all.index(sr[0]):all.index(sr[1])+1])
- return sr
-
-def add_node_group(s):
- (count, ips_str) = s.split("@", 1)
- ips = [i for r in ips_str.split(",") \
- for i in expand_range(r) if r != ""]
- for i in range(int(count)):
- c.add_node(ctdb_takeover.Node(ips))
-
-c = ctdb_takeover.Cluster()
-
-if ctdb_takeover.options.groups is None:
- print "Error: no node groups defined."
- sys.exit(1)
-
-for g in ctdb_takeover.options.groups:
- add_node_group(g)
-
-c.recover()
-
-c.random_iterations()
diff --git a/ctdb/tests/takeover/simulation/node_group_extra.py b/ctdb/tests/takeover/simulation/node_group_extra.py
deleted file mode 100755
index 7e9e518bddd..00000000000
--- a/ctdb/tests/takeover/simulation/node_group_extra.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env python
-
-# This example demonstrates a node group configuration. Is it meant
-# to be the same as node_group_simple.py, but with a couple of nodes
-# added later, so they are listed after the management node.
-
-# When run with deterministic IPs (use "-d" to show the problem) it
-# does many gratuitous IP reassignments.
-
-from ctdb_takeover import Cluster, Node, process_args
-
-process_args()
-
-addresses1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] + ['P', 'Q', 'R', 'S', 'T', 'U']
-addresses2 = ['I', 'J', 'K', 'L']
-
-c = Cluster()
-
-for i in range(4):
- c.add_node(Node(addresses1))
-
-for i in range(3):
- c.add_node(Node(addresses2))
-
-c.add_node(Node([]))
-c.add_node(Node(addresses1))
-c.add_node(Node(addresses2))
-
-c.recover()
-
-c.random_iterations()
diff --git a/ctdb/tests/takeover/simulation/node_group_simple.py b/ctdb/tests/takeover/simulation/node_group_simple.py
deleted file mode 100755
index 3c58ef7314a..00000000000
--- a/ctdb/tests/takeover/simulation/node_group_simple.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python
-
-# This example demonstrates a simple, sensible node group
-# configuration. When run with deterministic IPs (use "-d" to show
-# the problem) it does many gratuitous IP reassignments.
-
-from ctdb_takeover import Cluster, Node, process_args
-
-process_args()
-
-addresses1 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
-addresses2 = ['I', 'J', 'K']
-
-c = Cluster()
-
-for i in range(4):
- c.add_node(Node(addresses1))
-
-for i in range(3):
- c.add_node(Node(addresses2))
-
-c.add_node(Node([]))
-
-c.recover()
-
-c.random_iterations()
diff --git a/ctdb/tests/takeover/simulation/nondet_path_01.py b/ctdb/tests/takeover/simulation/nondet_path_01.py
deleted file mode 100755
index a62847a2163..00000000000
--- a/ctdb/tests/takeover/simulation/nondet_path_01.py
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/bin/env python
-
-# This is a contrived example that makes the balancing algorithm fail
-# for nondeterministic IPs (run with "-dv --nd" to see the failure).
-
-from ctdb_takeover import Cluster, Node, process_args
-
-process_args()
-
-addresses1 = ['A', 'B', 'C', 'D']
-addresses2 = ['B', 'E', 'F']
-
-c = Cluster()
-
-for i in range(2):
- c.add_node(Node(addresses1))
-
-c.add_node(Node(addresses2))
-
-c.recover()
-
-c.unhealthy(1)
-c.recover()
-c.healthy(1)
-c.recover()