summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRonnie Sahlberg <ronniesahlberg@gmail.com>2012-03-22 15:39:59 +1100
committerRonnie Sahlberg <ronniesahlberg@gmail.com>2012-03-22 15:39:59 +1100
commit6bb0115ce5df8a5cffc89784837ac0b80dd5783d (patch)
treed16a552574bbea24ae6aac72a1943bbffe7a696e
parentfbe64dec018afec3ca56b3fd90eb4dc4e3d53d15 (diff)
parent8d328920dbe7faa907248e39e846ef9612ee669f (diff)
downloadsamba-6bb0115ce5df8a5cffc89784837ac0b80dd5783d.tar.gz
samba-6bb0115ce5df8a5cffc89784837ac0b80dd5783d.tar.xz
samba-6bb0115ce5df8a5cffc89784837ac0b80dd5783d.zip
Merge remote branch 'martins/policy_routing'
(This used to be ctdb commit 7293762046e25a9c96e6417e34e18c214ebc6e55)
-rw-r--r--ctdb/.gitignore2
-rwxr-xr-xctdb/Makefile.in1
-rw-r--r--ctdb/config/README6
-rwxr-xr-xctdb/config/events.d/13.per_ip_routing662
-rwxr-xr-xctdb/config/functions197
-rwxr-xr-xctdb/config/interface_modify.sh142
-rw-r--r--ctdb/packaging/RPM/ctdb.spec.in1
-rw-r--r--ctdb/tests/eventscripts/common.sh188
-rw-r--r--ctdb/tests/eventscripts/etc-ctdb/public_addresses5
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.001.sh16
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.002.sh14
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.003.sh24
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.004.sh25
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.005.sh38
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.006.sh38
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.007.sh40
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.008.sh42
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.009.sh44
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.010.sh52
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.011.sh41
-rwxr-xr-xctdb/tests/eventscripts/multievent/13.per_ip_routing.012.sh48
-rwxr-xr-xctdb/tests/eventscripts/run_tests.sh13
-rwxr-xr-xctdb/tests/eventscripts/simple/10.interface.monitor.015.sh2
-rwxr-xr-xctdb/tests/eventscripts/simple/10.interface.takeip.003.sh6
-rwxr-xr-xctdb/tests/eventscripts/stubs/ctdb208
-rwxr-xr-xctdb/tests/eventscripts/stubs/ip598
26 files changed, 1508 insertions, 945 deletions
diff --git a/ctdb/.gitignore b/ctdb/.gitignore
index ef8ae6ad3c..77ffc3b793 100644
--- a/ctdb/.gitignore
+++ b/ctdb/.gitignore
@@ -26,3 +26,5 @@ tests/events.d/00.ctdb_test_trigger
tests/var
tests/takeover/ctdb_takeover.pyc
tests/eventscripts/var
+tests/eventscripts/etc/iproute2
+tests/eventscripts/etc-ctdb/policy_routing
diff --git a/ctdb/Makefile.in b/ctdb/Makefile.in
index 9055a8f64d..90d7b085cc 100755
--- a/ctdb/Makefile.in
+++ b/ctdb/Makefile.in
@@ -315,7 +315,6 @@ install: all $(PMDA_INSTALL)
${INSTALLCMD} -m 644 include/ctdb_typesafe_cb.h $(DESTDIR)$(includedir)
${INSTALLCMD} -m 644 config/functions $(DESTDIR)$(etcdir)/ctdb
${INSTALLCMD} -m 755 config/statd-callout $(DESTDIR)$(etcdir)/ctdb
- ${INSTALLCMD} -m 755 config/interface_modify.sh $(DESTDIR)$(etcdir)/ctdb
${INSTALLCMD} -m 644 config/events.d/README $(DESTDIR)$(docdir)/ctdb/README.eventscripts
${INSTALLCMD} -m 644 doc/recovery-process.txt $(DESTDIR)$(docdir)/ctdb/recovery-process.txt
${INSTALLCMD} -m 755 config/events.d/00.ctdb $(DESTDIR)$(etcdir)/ctdb/events.d
diff --git a/ctdb/config/README b/ctdb/config/README
index f2457a7618..ffbeb0e690 100644
--- a/ctdb/config/README
+++ b/ctdb/config/README
@@ -14,12 +14,6 @@ Selected highlights:
Support functions, sourced by eventscripts and other scripts.
- interface_modify.sh
-
- Script to support add/remove IPs and other funky stuff. Not sure
- why this is separate... but it certainly allows easy wrapping by
- flock.
-
statd-callout
rpc.statd high-availability callout to support lock migration on
diff --git a/ctdb/config/events.d/13.per_ip_routing b/ctdb/config/events.d/13.per_ip_routing
index d8a064493e..f51d5edf5f 100755
--- a/ctdb/config/events.d/13.per_ip_routing
+++ b/ctdb/config/events.d/13.per_ip_routing
@@ -3,492 +3,350 @@
. $CTDB_BASE/functions
loadconfig
-ctdb_setup_service_state_dir "per_ip_routing"
+# Do nothing if unconfigured
+[ -n "$CTDB_PER_IP_ROUTING_CONF" ] || exit 0
-[ -z "$CTDB_PER_IP_ROUTING_STATE" ] && {
- CTDB_PER_IP_ROUTING_STATE="$service_state_dir"
-}
+table_id_prefix="ctdb."
-AUTO_LINK_LOCAL="no"
-
-case "$CTDB_PER_IP_ROUTING_CONF" in
- __auto_link_local__)
- AUTO_LINK_LOCAL="yes"
- CTDB_PER_IP_ROUTING_CONF="$CTDB_PER_IP_ROUTING_STATE/auto_link_local.conf"
- ;;
- *)
- [ -z "$CTDB_PER_IP_ROUTING_CONF" ] && {
- #echo "No config file found. Nothing to do for 13.per_ip_routing"
- exit 0;
- }
- ;;
-esac
+[ -n "$CTDB_PER_IP_ROUTING_RULE_PREF" ] || \
+ die "error: CTDB_PER_IP_ROUTING_RULE_PREF not configured"
-_low=$CTDB_PER_IP_ROUTING_TABLE_ID_LOW
-_high=$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH
+[ "$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" -lt "$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" ] 2>/dev/null || \
+ die "error: CTDB_PER_IP_ROUTING_TABLE_ID_LOW[$CTDB_PER_IP_ROUTING_TABLE_ID_LOW] and/or CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH] improperly configured"
-test -z "$_low" && {
- echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_LOW not configured";
- exit 1;
-}
-test -z "$_high" && {
- echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_HIGH not configured";
- exit 1;
-}
-test "$_low" -ge "$_high" && {
- echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_LOW[$_low] needs to be below CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[$_high]";
- exit 1;
-}
+######################################################################
-test -z "$CTDB_PER_IP_ROUTING_RULE_PREF" && {
- echo "$0: CTDB_PER_IP_ROUTING_RULE_PREF not configured";
- exit 1;
-}
-
-locknesting=0
-lock_root="$CTDB_PER_IP_ROUTING_STATE"
-host=`hostname`
-
-lock_debug()
+ipv4_is_valid_addr()
{
- echo -n ""
-}
-
-############################
-# grab a lock file. Not atomic, but close :)
-# tries to cope with NFS
-lock_file() {
- if [ -z "$lock_root" ]; then
- lock_root=`pwd`;
- fi
- lckf="$lock_root/$1"
- machine=`cat "$lckf" 2> /dev/null | cut -d: -f1`
- pid=`cat "$lckf" 2> /dev/null | cut -d: -f2`
-
- if [ "$pid" = "$$" ]; then
- locknesting=`expr $locknesting + 1`
- lock_debug "lock nesting now $locknesting"
- return 0
+ _ip="$1"
+
+ _count=0
+ # Get the shell to break up the address into 1 word per octet
+ for _o in $(export IFS="." ; echo $_ip) ; do
+ # The 2>/dev/null stops output from failures where an "octet"
+ # is not numeric. The test will still fail.
+ if ! [ 0 -le $_o -a $_o -le 255 ] 2>/dev/null ; then
+ return 1
fi
+ _count=$(($_count + 1))
+ done
- if test -f "$lckf"; then
- test $machine = $host || {
- lock_debug "lock file $lckf is valid for other machine $machine"
- stat -c%y "$lckf"
- return 1
- }
- kill -0 $pid && {
- lock_debug "lock file $lckf is valid for process $pid"
- stat -c%y "$lckf"
- return 1
- }
- lock_debug "stale lock file $lckf for $machine:$pid"
- cat "$lckf"
- rm -f "$lckf"
- fi
- echo "$host:$$" > "$lckf"
- return 0
+ # A valid IPv4 address has 4 octets
+ [ $_count -eq 4 ]
}
-############################
-# unlock a lock file
-unlock_file() {
- if [ -z "$lock_root" ]; then
- lock_root=`pwd`;
- fi
- if [ "$locknesting" != "0" ]; then
- locknesting=`expr $locknesting - 1`
- lock_debug "lock nesting now $locknesting"
- else
- lckf="$lock_root/$1"
- rm -f "$lckf"
- fi
-}
-
-generate_table_id () {
- local _ip=$1
- local _ipsdir="$CTDB_PER_IP_ROUTING_STATE/ips"
- local _ipdir="$_ipsdir/$_ip"
+ensure_ipv4_is_valid_addr ()
+{
+ _event="$1"
+ _ip="$2"
- mkdir -p $_ipdir
+ ipv4_is_valid_addr "$_ip" || {
+ echo "$0: $_event not an ipv4 address skipping IP:$_ip"
+ exit 0
+ }
+}
- #echo "generate_table_id $_ip"
+ipv4_host_addr_to_net ()
+{
+ _host="$1"
+ _maskbits="$2"
+
+ # Convert the host address to an unsigned long by splitting out
+ # the octets and doing the math.
+ _host_ul=0
+ for _o in $(export IFS="." ; echo $_host) ; do
+ _host_ul=$(( ($_host_ul << 8) + $_o)) # work around Emacs color bug
+ done
+
+ # Calculate the mask and apply it.
+ _mask_ul=$(( 0xffffffff << (32 - $_maskbits) ))
+ _net_ul=$(( $_host_ul & $_mask_ul ))
+
+ # Now convert to a network address one byte at a time.
+ _net=""
+ for _o in $(seq 1 4) ; do
+ _net="$(($_net_ul & 255))${_net:+.}${_net}"
+ _net_ul=$(($_net_ul >> 8))
+ done
+
+ echo "${_net}/${_maskbits}"
+}
- local _id=`cat $_ipdir/table_id 2>/dev/null| xargs`
- test -n "$_id" && {
- #echo "IP: $_ip => OLD TABLE: $_id"
- table_id=$_id
- return 0;
- }
+######################################################################
- local _low="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW"
- local _high="$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH"
-
- local _newid=""
- for _id in `seq $_low $_high | xargs`; do
- local _table_lck="table_id_$_id.lock"
- lock_file $_table_lck 2>/dev/null || {
- continue;
- }
- local _taken=`grep "^$_id$" $_ipsdir/*/table_id 2>/dev/null| wc -l | xargs`
- test x"$_taken" != x"0" && {
- unlock_file $_table_lck
- #echo "tableid: $_id taken"
- continue
- }
- _newid=$_id;
- echo "$_newid" > $_ipdir/table_id
- unlock_file $_table_lck
- break;
+# Setup a table id to use for the given IP. We don't need to know it,
+# it just needs to exist in /etc/iproute2/rt_tables. Fail if no free
+# table id could be found in the configured range.
+ensure_table_id_for_ip ()
+{
+ _ip=$1
+
+ _f="$CTDB_ETCDIR/iproute2/rt_tables"
+ # This file should always exist, but...
+ if [ ! -f "$_f" ] ; then
+ mkdir -p $(dirname "$_f")
+ touch "$_f"
+ fi
+
+ # Maintain a table id for each IP address we've ever seen in
+ # rt_tables. We use a "ctdb." prefix on the label.
+ _label="${table_id_prefix}${_ip}"
+
+ # This finds either the table id corresponding to the label or a
+ # new unused one (that is greater than all the used ones in the
+ # range).
+ (
+ # Note that die() just gets us out of the subshell...
+ flock --timeout 30 0 || \
+ die "ensure_table_id_for_ip: failed to lock file $_f"
+
+ _new=$CTDB_PER_IP_ROUTING_TABLE_ID_LOW
+ while read _t _l ; do
+ # Skip comments
+ case "$_t" in
+ \#*) continue ;;
+ esac
+ # Found existing: done
+ if [ "$_l" = "$_label" ] ; then
+ return 0
+ fi
+ # Potentially update the new table id to be used. The
+ # redirect stops error spam for a non-numeric value.
+ if [ $_new -le $_t -a \
+ $_t -le $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH ] 2>/dev/null ; then
+ _new=$(($_t + 1))
+ fi
done
- test -z "$_newid" && {
- echo "generate_table_id: out of table ids: $_low - $_high"
- exit 1;
- }
-
- #echo "IP: $_ip => NEW TABLE: $_newid"
- table_id=$_newid
- return 0;
+ # If the new table id is legal then add it to the file and
+ # print it.
+ if [ $_new -le $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH ] ; then
+ printf "%d\t%s\n" "$_new" "$_label" >>"$_f"
+ return 0
+ else
+ return 1
+ fi
+ ) <"$_f"
}
-run_release_script_once()
+# Clean up all the table ids that we might own.
+clean_up_table_ids ()
{
- local _script=$1
-
- #echo "run_release_script_once[$_script]"
-
- test -x "$_script" && {
- #echo "run it: start"
- $_script || {
- echo "release_script: $_script - failed $?"
- return $?;
- }
- #echo "run it: end"
- }
-
- echo '#!/bin/sh' > $_script
- echo '#' >> $_script
- echo >> $_script
+ _f="$CTDB_ETCDIR/iproute2/rt_tables"
+ # Even if this didn't exist on the system, adding a route will
+ # have created it. What if we startup and immediately shutdown?
+ if [ ! -f "$_f" ] ; then
+ mkdir -p $(basename "$_f")
+ touch "$_f"
+ fi
+
+ (
+ # Note that die() just gets us out of the subshell...
+ flock --timeout 30 0 || \
+ die "clean_up_table_ids: failed to lock file $_f"
+
+ # Delete any items from the file that have a table id in our
+ # range or a label matching our label. Preserve comments.
+ _tmp="${_f}.$$.ctdb"
+ awk -v min="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW" \
+ -v max="$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH" \
+ -v pre="$table_id_prefix" \
+ '/^#/ || \
+ !(min <= $1 && $1 <= max) && \
+ !(index($2, pre) == 1) \
+ { print $0 }' "$_f" >"$_tmp"
+
+ mv "$_tmp" "$_f"
+ # The lock is gone - don't do anything else here
+ ) <"$_f"
+}
- chmod +x $_script
+######################################################################
- return 0;
+# This prints the config for an IP, which is either relevant entries
+# from the config file or, if set to the magic link local value, some
+# link local routing config for the IP.
+# NOTE: non-zero return indicates missing configuration file
+get_config_for_ip ()
+{
+ _ip="$1"
+
+ if [ "$CTDB_PER_IP_ROUTING_CONF" = "__auto_link_local__" ] ; then
+ # When parsing public_addresses also split on '/'. This means
+ # that we get the maskbits as item #2 without further parsing.
+ while IFS="/$IFS" read _i _maskbits _x ; do
+ if [ "$_ip" = "$_i" ] ; then
+ echo -n "$_ip "; ipv4_host_addr_to_net "$_ip" "$_maskbits"
+ fi
+ done <"${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE:-/dev/null}${CTDB:+/public_addresses}}"
+ else
+ [ -f "$CTDB_PER_IP_ROUTING_CONF" ] || return 1
+
+ while read _i _rest ; do
+ if [ "$_ip" = "$_i" ] ; then
+ printf "%s\t%s\n" "$_ip" "$_rest"
+ fi
+ done <"$CTDB_PER_IP_ROUTING_CONF"
+ fi
}
-generate_auto_link_local()
+ip_has_configuration ()
{
- local _ip=$1
- local _maskbits=$2
-
- #echo "generate_auto_link_local $_ip $_maskbits"
-
- local _netip=`ipv4_host_addr_to_net_addr $_ip $_maskbits`
-
- local _line="$_ip $_netip/$_maskbits"
-
- local _lockfile="$CTDB_PER_IP_ROUTING_CONF.lock"
- local _script="$CTDB_PER_IP_ROUTING_CONF.$$.sh"
-
- echo "#!/bin/sh" > $_script
- echo "#" >> $_script
- echo "" >> $_script
- echo "_config=\`cat $CTDB_PER_IP_ROUTING_CONF 2>/dev/null\`" >> $_script
- echo "_exact=\`echo -n \"\$_config\" | grep \"^$_line\$\" | wc -l | xargs\`" >> $_script
- echo "" >> $_script
-
- echo "test x\"\$_exact\" = x\"1\" && {" >> $_script
- echo " exit 0;" >> $_script
- echo "}" >> $_script
- echo "" >> $_script
-
- echo "_tmp=\"$CTDB_PER_IP_ROUTING_CONF.$$.tmp\"" >> $_script
- echo "echo -n \"\$_config\" | grep -v \"^$_ip \" | cat > \$_tmp || {" >> $_script
- echo " echo \"echo -n \\\"\$_config\\\" | grep -v \\\"^$_ip \\\" > \$_tmp - failed\"" >> $_script
- echo " exit 1;" >> $_script
- echo "}" >> $_script
- echo "echo \"$_line\" >> \$_tmp || {" >> $_script
- echo " echo \"echo \\\"$_line\\\" >> \$_tmp - failed\"" >> $_script
- echo " exit 1;" >> $_script
- echo "}" >> $_script
- echo "" >> $_script
-
- echo "mv \$_tmp $CTDB_PER_IP_ROUTING_CONF || {" >> $_script
- echo " echo \"mv \$_tmp $CTDB_PER_IP_ROUTING_CONF - failed\"" >> $_script
- echo " exit 1;" >> $_script
- echo "}" >> $_script
- echo "" >> $_script
-
- echo "echo \"Added '$_line' to $CTDB_PER_IP_ROUTING_CONF\"">> $_script
- echo "exit 0" >> $_script
-
- chmod +x $_script
-
- test -f $_lockfile || {
- touch $_lockfile
- }
+ _ip="$1"
- flock --timeout 30 $_lockfile $_script
- ret=$?
- rm $_script
- return $ret
+ _config="$(get_config_for_ip $_ip)" || \
+ die "error: CTDB_PER_IP_ROUTING_CONF=$CTDB_PER_IP_ROUTING_CONF file not found"
+ [ -n "$_config" ]
}
-generate_per_ip_routing()
+add_routing_for_ip ()
{
- local _ip=$1
- local _maskbits=$2
- local _iface=$3
- local _readonly=$4
- local _ipdir="$CTDB_PER_IP_ROUTING_STATE/ips/$_ip"
-
- table_id=""
- release_script="$_ipdir/per_ip_routing_release.sh"
- setup_script="$_ipdir/per_ip_routing_setup.sh"
-
- test x"$_readonly" = x"yes" && {
- test -d $_ipdir || {
- return 1;
- }
- return 0;
- }
+ _iface="$1"
+ _ip="$2"
- mkdir -p $_ipdir || {
- echo "mkdir -p $_ipdir failed"
- return 1;
- }
- echo "$_ip" > $_ipdir/ip
+ # Do nothing if no config for this IP.
+ ip_has_configuration "$_ip" || return 0
- generate_table_id $_ip
+ ensure_table_id_for_ip "$_ip" || \
+ die "add_routing_for_ip: out of table ids in range $CTDB_PER_IP_ROUTING_TABLE_ID_LOW - $CTDB_PER_IP_ROUTING_TABLE_ID_HIGH"
- test x"$AUTO_LINK_LOCAL" = x"yes" && {
- generate_auto_link_local $_ip $_maskbits
- }
+ _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
+ _table_id="${table_id_prefix}${_ip}"
- run_release_script_once $release_script
+ del_routing_for_ip "$_ip"
- echo '#!/bin/sh' > $setup_script
- echo '#' >> $setup_script
- echo >> $setup_script
- chmod +x $setup_script
+ ip rule add from "$_ip" pref "$_pref" table "$_table_id" || \
+ die "add_routing_for_ip: failed to add rule for $_ip"
- return 0;
+ # Add routes to table for any lines matching the IP.
+ get_config_for_ip "$_ip" |
+ while read _i _dest _gw ; do
+ _r="$_dest ${_gw:+via} $_gw dev $_iface table $_table_id"
+ ip route add $_r || \
+ die "add_routing_for_ip: failed to add route: $_r"
+ done
}
-setup_per_ip_routing()
+del_routing_for_ip ()
{
- local _ip=$1
- local _iface=$2
- local _table_id=$3
- local _release_script=$4
- local _setup_script=$5
-
- local _config=`cat $CTDB_PER_IP_ROUTING_CONF`
- local _lines=`echo -n "$_config" | grep -n "^$_ip " | cut -d ':' -f1 | xargs`
+ _ip="$1"
- local _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
+ _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
+ _table_id="${table_id_prefix}${_ip}"
- test -n "$_lines" && {
- echo "ip rule del from $_ip pref $_pref table $_table_id" >> $_release_script
- echo "ip route flush table $_table_id 2>/dev/null" >> $_release_script
-
- cmd="ip rule del from $_ip pref $_pref 2>/dev/null"
- echo "$cmd" >> $_setup_script
+ # Do this unconditionally since we own any matching table ids...
+ ip rule del from $_ip pref $_pref table $_table_id 2>/dev/null
+ ip route flush table $_table_id 2>/dev/null
+}
- cmd="ip route flush table $_table_id 2>/dev/null"
- echo "$cmd" >> $_setup_script
+######################################################################
- cmd="ip rule add from $_ip pref $_pref table $_table_id"
- echo "$cmd || {" >> $_setup_script
- echo " echo \"$cmd - failed \$ret\"" >> $_setup_script
- echo " exit \$ret" >> $_setup_script
- echo "}" >> $_setup_script
- }
- local _l
- for _l in $_lines; do
- local _line=`echo -n "$_config" | head -n $_l | tail -n 1`
- local _dest=`echo -n "$_line" | cut -d ' ' -f 2`
- local _gw=`echo -n "$_line" | cut -d ' ' -f 3`
-
- local _via=""
- test -n "$_gw" && {
- _via="via $_gw"
- }
-
- cmd="ip route add $_dest $_via dev $_iface table $_table_id"
- echo "$cmd || {" >> $_setup_script
- echo " echo \"$cmd - failed \$ret\"" >> $_setup_script
- echo " exit \$ret" >> $_setup_script
- echo "}" >> $_setup_script
+flush_rules_and_routes ()
+{
+ ip rule show |
+ while read _p _x _i _x _t ; do
+ # Remove trailing colon after priority/preference.
+ _p="${_p%:}"
+ # Only remove rules that match our priority/preference.
+ [ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
+
+ echo "Removing ip rule for public address $_i for routing table $_t"
+ ip rule del from "$_i" table "$_t" pref "$_p"
+ ip route flush table "$_t" 2>/dev/null
done
+}
- $_setup_script
- return $?;
+# Add any missing routes. Some might have gone missing if, for
+# example, all IPs on the network were removed (possibly if the
+# primary was removed).
+add_missing_routes ()
+{
+ ctdb ip -v -Y | {
+ read _x # skip header line
+
+ # Read the rest of the lines. We're only interested in the
+ # "IP" and "ActiveInterface" columns. The latter is only set
+ # for addresses local to this node, making it easy to skip
+ # non-local addresses. For each IP local address we check if
+ # the relevant routing table is populated and populate it if
+ # not.
+ while IFS=":" read _x _ip _x _iface _x ; do
+ [ -n "$_iface" ] || continue
+
+ _table_id="${table_id_prefix}${_ip}"
+ if [ -z "$(ip route show table $_table_id 2>/dev/null)" ] ; then
+ add_routing_for_ip "$_iface" "$_ip"
+ fi
+ done
+ } || exit $?
}
+######################################################################
+
ctdb_check_args "$@"
case "$1" in
- #############################
- # called when ctdbd starts up
- startup)
- # cleanup old rules
- pref=$CTDB_PER_IP_ROUTING_RULE_PREF
- rules=`ip rule show | grep "^$pref:" | sed -e 's/.*from \([^ ][^ ]*\) lookup \([^ ][^ ]*\)/\2;\1/' | xargs`
- for r in $rules; do
- table_id=`echo -n "$r" | cut -d ';' -f1`
- ip=`echo -n "$r" | cut -d ';' -f2-`
-
- echo "Removing ip rule for public address $ip for routing table $table_id"
- cmd="ip rule del from $ip table $table_id pref $pref"
- #echo $cmd
- eval $cmd
- cmd="ip route flush table $table_id"
- #echo $cmd
- eval $cmd 2>/dev/null
- done
+ startup)
+ flush_rules_and_routes
- # make sure that we only respond to ARP messages from the NIC where
- # a particular ip address is associated.
- [ -f /proc/sys/net/ipv4/conf/all/arp_filter ] && {
- echo 1 > /proc/sys/net/ipv4/conf/all/arp_filter
+ # make sure that we only respond to ARP messages from the NIC
+ # where a particular ip address is associated.
+ get_proc sys/net/ipv4/conf/all/arp_filter >/dev/null 2>&1 && {
+ set_proc sys/net/ipv4/conf/all/arp_filter 1
}
-
- mkdir -p $CTDB_PER_IP_ROUTING_STATE
-
;;
- shutdown)
-
- for s in $CTDB_PER_IP_ROUTING_STATE/ips/*/per_ip_routing_release.sh; do
- run_release_script_once "$s"
- done
- rm -rf $CTDB_PER_IP_ROUTING_STATE
-
+ shutdown)
+ flush_rules_and_routes
+ clean_up_table_ids
;;
- ################################################
- # called when ctdbd wants to claim an IP address
- takeip)
+ takeip)
iface=$2
ip=$3
maskbits=$4
- ipv4_is_valid_addr $ip || {
- echo "$0: $1 not an ipv4 address skipping IP:$ip"
- exit 0;
- }
-
- [ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
- echo "$0: $1 No state directory found, waiting for startup."
- exit 0;
- }
-
- generate_per_ip_routing $ip $maskbits $iface "no" || {
- echo "$0: $1: generate_per_ip_routing $ip $maskbits $iface no - failed"
- exit 1;
- }
-
- setup_per_ip_routing $ip $iface $table_id $release_script $setup_script || {
- echo "$0: $1: setup_per_ip_routing $ip $iface $table_id $release_script $setup_script - failed"
- exit 1;
- }
-
- setup_iface_ip_readd_script $iface $ip $maskbits $setup_script || {
- echo "$0: $1: setup_iface_ip_readd_script $iface $ip $maskbits $setup_script - failed"
- exit 1;
- }
+ ensure_ipv4_is_valid_addr "$1" "$ip"
+ add_routing_for_ip "$iface" "$ip"
# flush our route cache
- echo 1 > /proc/sys/net/ipv4/route/flush
- ctdb gratiousarp $ip $iface
+ set_proc sys/net/ipv4/route/flush 1
+ ctdb gratiousarp "$ip" "$iface"
;;
- ################################################
- # called when ctdbd wants to claim an IP address
- updateip)
+ updateip)
oiface=$2
niface=$3
ip=$4
maskbits=$5
- ipv4_is_valid_addr $ip || {
- echo "$0: $1 not an ipv4 address skipping IP:$ip"
- exit 0;
- }
-
- [ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
- echo "$0: $1 No state directory found, waiting for startup."
- exit 0;
- }
-
- generate_per_ip_routing $ip $maskbits $niface "no" || {
- echo "$0: $1: generate_per_ip_routing $ip $maskbits $niface no - failed"
- exit 1;
- }
-
- setup_per_ip_routing $ip $niface $table_id $release_script $setup_script || {
- echo "$0: $1: setup_per_ip_routing $ip $niface $table_id $release_script $setup_script - failed"
- exit 1;
- }
-
- setup_iface_ip_readd_script $niface $ip $maskbits $setup_script || {
- echo "$0: $1: setup_iface_ip_readd_script $niface $ip $maskbits $setup_script - failed"
- exit 1;
- }
+ ensure_ipv4_is_valid_addr "$1" "$ip"
+ add_routing_for_ip "$niface" "$ip"
# flush our route cache
- echo 1 > /proc/sys/net/ipv4/route/flush
-
- ctdb gratiousarp $ip $niface
- tickle_tcp_connections $ip
+ set_proc sys/net/ipv4/route/flush 1
+ ctdb gratiousarp "$ip" "$niface"
+ tickle_tcp_connections "$ip"
;;
- ##################################################
- # called when ctdbd wants to release an IP address
- releaseip)
+ releaseip)
iface=$2
ip=$3
maskbits=$4
- ipv4_is_valid_addr $ip || {
- echo "$0: $1 not an ipv4 address skipping IP:$ip"
- exit 0;
- }
-
- [ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
- echo "$0: $1 No state directory found, waiting for startup."
- exit 0;
- }
-
- generate_per_ip_routing $ip $maskbits $iface "yes" || {
- echo "$0: $1: generate_per_ip_routing $ip $maskbits $iface yes - failed"
- exit 1;
- }
-
- run_release_script_once "$release_script"
-
+ ensure_ipv4_is_valid_addr "$1" "$ip"
+ del_routing_for_ip "$ip"
;;
-
- ###########################################
- # called when ctdbd has finished a recovery
- recovered)
- ;;
-
- ####################################
- # called when ctdbd is shutting down
- shutdown)
+ ipreallocated)
+ add_missing_routes
;;
- monitor)
- ;;
*)
ctdb_standard_event_handler "$@"
;;
esac
exit 0
-
diff --git a/ctdb/config/functions b/ctdb/config/functions
index 7c5c1c245d..ca89c38379 100755
--- a/ctdb/config/functions
+++ b/ctdb/config/functions
@@ -86,6 +86,15 @@ debug ()
fi
}
+die ()
+{
+ _msg="$1"
+ _rc="${2:-1}"
+
+ echo "$_msg"
+ exit $_rc
+}
+
##############################################################
# check number of args for different events
ctdb_check_args ()
@@ -770,72 +779,88 @@ startstop_nfslock() {
add_ip_to_iface()
{
- local _iface=$1
- local _ip=$2
- local _maskbits=$3
- local _state_dir="$CTDB_VARDIR/state/interface_modify"
- local _lockfile="$_state_dir/$_iface.flock"
- local _readd_base="$_state_dir/$_iface.readd.d"
-
- mkdir -p $_state_dir || {
- ret=$?
- echo "Failed to mkdir -p $_state_dir - $ret"
- return $ret
- }
+ _iface=$1
+ _ip=$2
+ _maskbits=$3
- test -f $_lockfile || {
- touch $_lockfile
- }
+ _lockfile="${CTDB_VARDIR}/state/interface_modify_${_iface}.flock"
+ [ -f "$_lockfile" ] || touch "$_lockfile"
- flock --timeout 30 $_lockfile $CTDB_BASE/interface_modify.sh add "$_iface" "$_ip" "$_maskbits" "$_readd_base"
- return $?
+ (
+ # Note: use of return/exit/die() below only gets us out of the
+ # sub-shell, which is actually what we want. That is, the
+ # function should just return non-zero.
+
+ flock --timeout 30 0 || \
+ die "add_ip_to_iface: unable to get lock for ${_iface}"
+
+ # Ensure interface is up
+ ip link set "$_iface" up || \
+ die "Failed to bringup interface $_iface"
+
+ ip addr add "$_ip/$_maskbits" brd + dev "$_iface" || \
+ die "Failed to add $_ip/$_maskbits on dev $_iface"
+ ) <"$_lockfile"
+
+ # Do nothing here - return above only gets us out of the subshell
+ # and doing anything here will affect the return code.
}
delete_ip_from_iface()
{
- local _iface=$1
- local _ip=$2
- local _maskbits=$3
- local _state_dir="$CTDB_VARDIR/state/interface_modify"
- local _lockfile="$_state_dir/$_iface.flock"
- local _readd_base="$_state_dir/$_iface.readd.d"
-
- mkdir -p $_state_dir || {
- ret=$?
- echo "Failed to mkdir -p $_state_dir - $ret"
- return $ret
- }
+ _iface=$1
+ _ip=$2
+ _maskbits=$3
- test -f $_lockfile || {
- touch $_lockfile
- }
+ _lockfile="${CTDB_VARDIR}/state/interface_modify_${_iface}.flock"
+ [ -f "$_lockfile" ] || touch "$_lockfile"
- flock --timeout 30 $_lockfile $CTDB_BASE/interface_modify.sh delete "$_iface" "$_ip" "$_maskbits" "$_readd_base"
- return $?
-}
+ (
+ # Note: use of return/exit/die() below only gets us out of the
+ # sub-shell, which is actually what we want. That is, the
+ # function should just return non-zero.
-setup_iface_ip_readd_script()
-{
- local _iface=$1
- local _ip=$2
- local _maskbits=$3
- local _readd_script=$4
- local _state_dir="$CTDB_VARDIR/state/interface_modify"
- local _lockfile="$_state_dir/$_iface.flock"
- local _readd_base="$_state_dir/$_iface.readd.d"
-
- mkdir -p $_state_dir || {
- ret=$?
- echo "Failed to mkdir -p $_state_dir - $ret"
- return $ret
- }
+ flock --timeout 30 0 || \
+ die "delete_ip_from_iface: unable to get lock for ${_iface}"
- test -f $_lockfile || {
- touch $_lockfile
+ _im="$_ip/$_maskbits" # shorthand for readability
+
+ # "ip addr del" will delete all secondary IPs if this is the
+ # primary. To work around this _very_ annoying behaviour we
+ # have to keep a record of the secondaries and re-add them
+ # afterwards. Yuck!
+
+ _secondaries=""
+ if ip addr list dev "$_iface" primary | grep -Fq "inet $_im " ; then
+ _secondaries=$(ip addr list dev "$_iface" secondary | \
+ awk '$1 == "inet" { print $2 }')
+ fi
+
+ local _rc=0
+ ip addr del "$_im" dev "$_iface" || {
+ echo "Failed to del $_ip on dev $_iface"
+ _rc=1
}
- flock --timeout 30 $_lockfile $CTDB_BASE/interface_modify.sh readd_script "$_iface" "$_ip" "$_maskbits" "$_readd_base" "$_readd_script"
- return $?
+ if [ -n "$_secondaries" ] ; then
+ for _i in $_secondaries; do
+ if ip addr list dev "$_iface" | grep -Fq "inet $_i" ; then
+ echo "Kept secondary $_i on dev $_iface"
+ else
+ echo "Re-adding secondary address $_i to dev $_iface"
+ ip addr add $_i brd + dev $_iface || {
+ echo "Failed to re-add address $_i to dev $_iface"
+ _rc=1
+ }
+ fi
+ done
+ fi
+
+ return $_rc
+ ) <"$_lockfile"
+
+ # Do nothing here - return above only gets us out of the subshell
+ # and doing anything here will affect the return code.
}
########################################################
@@ -1278,70 +1303,6 @@ ctdb_standard_event_handler ()
esac
}
-ipv4_host_addr_to_net_addr()
-{
- local HOST=$1
- local MASKBITS=$2
-
- local HOST0=$(echo $HOST | awk -F . '{print $4}')
- local HOST1=$(echo $HOST | awk -F . '{print $3}')
- local HOST2=$(echo $HOST | awk -F . '{print $2}')
- local HOST3=$(echo $HOST | awk -F . '{print $1}')
-
- local HOST_NUM=$(( $HOST0 + $HOST1 * 256 + $HOST2 * (256 ** 2) + $HOST3 * (256 ** 3) ))
-
- local MASK_NUM=$(( ( (2**32 - 1) * (2**(32 - $MASKBITS)) ) & (2**32 - 1) ))
-
- local NET_NUM=$(( $HOST_NUM & $MASK_NUM))
-
- local NET0=$(( $NET_NUM & 255 ))
- local NET1=$(( ($NET_NUM & (255 * 256)) / 256 ))
- local NET2=$(( ($NET_NUM & (255 * 256**2)) / 256**2 ))
- local NET3=$(( ($NET_NUM & (255 * 256**3)) / 256**3 ))
-
- echo "$NET3.$NET2.$NET1.$NET0"
-}
-
-ipv4_maskbits_to_net_mask()
-{
- local MASKBITS=$1
-
- local MASK_NUM=$(( ( (2**32 - 1) * (2**(32 - $MASKBITS)) ) & (2**32 - 1) ))
-
- local MASK0=$(( $MASK_NUM & 255 ))
- local MASK1=$(( ($MASK_NUM & (255 * 256)) / 256 ))
- local MASK2=$(( ($MASK_NUM & (255 * 256**2)) / 256**2 ))
- local MASK3=$(( ($MASK_NUM & (255 * 256**3)) / 256**3 ))
-
- echo "$MASK3.$MASK2.$MASK1.$MASK0"
-}
-
-ipv4_is_valid_addr()
-{
- local ADDR=$1
- local fail=0
-
- local N=`echo $ADDR | sed -e 's/[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*//'`
- test -n "$N" && fail=1
-
- local ADDR0=$(echo $ADDR | awk -F . '{print $4}')
- local ADDR1=$(echo $ADDR | awk -F . '{print $3}')
- local ADDR2=$(echo $ADDR | awk -F . '{print $2}')
- local ADDR3=$(echo $ADDR | awk -F . '{print $1}')
-
- test "$ADDR0" -gt 255 && fail=1
- test "$ADDR1" -gt 255 && fail=1
- test "$ADDR2" -gt 255 && fail=1
- test "$ADDR3" -gt 255 && fail=1
-
- test x"$fail" != x"0" && {
- #echo "IPv4: '$ADDR' is not a valid address"
- return 1;
- }
-
- return 0;
-}
-
# iptables doesn't like being re-entered, so flock-wrap it.
iptables()
{
diff --git a/ctdb/config/interface_modify.sh b/ctdb/config/interface_modify.sh
deleted file mode 100755
index 041637650f..0000000000
--- a/ctdb/config/interface_modify.sh
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/bin/sh
-#
-
-OP=$1
-IFACE=$2
-IP=$3
-MASKBITS=$4
-READD_BASE=$5
-READD_SCRIPT=$6
-
-add_ip_to_iface()
-{
- local _iface=$1
- local _ip=$2
- local _maskbits=$3
- local _readd_base=$4
- local _script_dir="$_readd_base/$_ip.$_maskbits"
-
- # we make sure the interface is up first
- ip link set $_iface up || {
- echo "Failed to bringup interface $_iface"
- return 1;
- }
- ip addr add $_ip/$_maskbits brd + dev $_iface || {
- echo "Failed to add $_ip/$_maskbits on dev $_iface"
- return 1;
- }
-
- mkdir -p $_script_dir || {
- echo "Failed to mkdir -p $_script_dir"
- return 1;
- }
-
- rm -f $_script_dir/*
-
- return 0;
-}
-
-delete_ip_from_iface()
-{
- local _iface=$1
- local _ip=$2
- local _maskbits=$3
- local _readd_base=$4
- local _script_dir="$_readd_base/$_ip.$_maskbits"
-
- # the ip tool will delete all secondary IPs if this is the primary. To work around
- # this _very_ annoying behaviour we have to keep a record of the secondaries and re-add
- # them afterwards. yuck
- local _secondaries=""
- if ip addr list dev $_iface primary | grep -q "inet $_ip/$_maskbits " ; then
- _secondaries=`ip addr list dev $_iface secondary | grep " inet " | awk '{print $2}'`
- fi
- local _failed=0
- ip addr del $_ip/$_maskbits dev $_iface || _failed=1
- [ -z "$_secondaries" ] || {
- local _i=""
- for _i in $_secondaries; do
- if ip addr list dev $_iface | grep -q "inet $_i" ; then
- echo "kept secondary $_i on dev $_iface"
- else
- echo "re-adding secondary address $_i to dev $_iface"
- ip addr add $_i brd + dev $_iface || _failed=1
- fi
- local _s_ip=`echo "$_i" | cut -d '/' -f1`
- local _s_maskbits=`echo "$_i" | cut -d '/' -f2`
- local _s_script_dir="$_readd_base/$_s_ip.$_s_maskbits"
-
- local _s_script=""
- for _s_script in $_s_script_dir/*; do
- test -x "$_s_script" || {
- continue
- }
- echo "call $_s_script '$_iface' '$_s_ip' '$_s_maskbits'"
- $_s_script "$_iface" "$_s_ip" "$_s_maskbits" || {
- ret=$?
- echo "$_s_script '$_iface' '$_s_ip' '$_s_maskbits' - failed - $ret"
- _failed=1
- }
- done
-
- done
- }
-
- test -d $_script_dir && {
- rm -f $_script_dir/*
- }
-
- [ $_failed = 0 ] || {
- echo "Failed to del $_ip on dev $_iface"
- return 1;
- }
- return 0;
-}
-
-setup_iface_ip_readd_script()
-{
- local _iface=$1
- local _ip=$2
- local _maskbits=$3
- local _readd_base=$4
- local _readd_script=$5
- local _script_dir="$_readd_base/$_ip.$_maskbits"
-
- test -x "$_readd_script" || {
- echo "Script '$_readd_script' isn't executable"
- return 1;
- }
-
- local _readd_basename=`basename $_readd_script`
- local _readd_final="$_script_dir/$_readd_basename"
-
- mkdir -p $_script_dir || {
- echo "Failed to mkdir -p $_script_dir"
- return 1;
- }
-
- cp -a $_readd_script $_readd_final || {
- echo "Failed to - cp -a $_readd_script $_readd_final"
- return 1;
- }
-
- return 0
-}
-
-case "$OP" in
- add)
- add_ip_to_iface $IFACE $IP $MASKBITS $READD_BASE
- exit $?
- ;;
- delete)
- delete_ip_from_iface $IFACE $IP $MASKBITS $READD_BASE
- exit $?
- ;;
- readd_script)
- setup_iface_ip_readd_script $IFACE $IP $MASKBITS $READD_BASE $READD_SCRIPT
- exit $?
- ;;
-esac
-
-echo "$0: unknown operation[$OP]"
-exit 1
diff --git a/ctdb/packaging/RPM/ctdb.spec.in b/ctdb/packaging/RPM/ctdb.spec.in
index 1d6f5702b7..f9a5cefb93 100644
--- a/ctdb/packaging/RPM/ctdb.spec.in
+++ b/ctdb/packaging/RPM/ctdb.spec.in
@@ -112,7 +112,6 @@ rm -rf $RPM_BUILD_ROOT
%{_sysconfdir}/ctdb/events.d/70.iscsi
%{_sysconfdir}/ctdb/events.d/91.lvs
%{_sysconfdir}/ctdb/statd-callout
-%{_sysconfdir}/ctdb/interface_modify.sh
%{_sbindir}/ctdbd
%{_bindir}/ctdb
%{_bindir}/smnotify
diff --git a/ctdb/tests/eventscripts/common.sh b/ctdb/tests/eventscripts/common.sh
index df9656b831..44c975e034 100644
--- a/ctdb/tests/eventscripts/common.sh
+++ b/ctdb/tests/eventscripts/common.sh
@@ -183,6 +183,50 @@ setup_nmap_output_filter ()
OUT_FILTER="-e 's@^(DEBUG: # Nmap 5.21 scan initiated) .+ (as:)@\1 DATE \2@' -e 's@^(DEBUG: # Nmap done at) .+ (--)@\1 DATE \2@'"
}
+dump_routes ()
+{
+ echo "# ip rule show"
+ ip rule show
+
+ ip rule show |
+ while read _p _x _i _x _t ; do
+ # Remove trailing colon after priority/preference.
+ _p="${_p%:}"
+ # Only remove rules that match our priority/preference.
+ [ "$CTDB_PER_IP_ROUTING_RULE_PREF" = "$_p" ] || continue
+
+ echo "# ip route show table $_t"
+ ip route show table "$_t"
+ done
+}
+
+# Copied from 13.per_ip_routing for now... so this is lazy testing :-(
+ipv4_host_addr_to_net ()
+{
+ _host="$1"
+ _maskbits="$2"
+
+ # Convert the host address to an unsigned long by splitting out
+ # the octets and doing the math.
+ _host_ul=0
+ for _o in $(export IFS="." ; echo $_host) ; do
+ _host_ul=$(( ($_host_ul << 8) + $_o)) # work around Emacs color bug
+ done
+
+ # Calculate the mask and apply it.
+ _mask_ul=$(( 0xffffffff << (32 - $_maskbits) ))
+ _net_ul=$(( $_host_ul & $_mask_ul ))
+
+ # Now convert to a network address one byte at a time.
+ _net=""
+ for _o in $(seq 1 4) ; do
+ _net="$(($_net_ul & 255))${_net:+.}${_net}"
+ _net_ul=$(($_net_ul >> 8))
+ done
+
+ echo "${_net}/${_maskbits}"
+}
+
######################################################################
# CTDB fakery
@@ -231,35 +275,17 @@ setup_ctdb ()
eventscripts_test_add_cleanup "rm -f $CTDB_PUBLIC_ADDRESSES"
fi
- export FAKE_CTDB_IFACES_DOWN="$EVENTSCRIPTS_TESTS_VAR_DIR/fake-ctdb/ifaces-down"
+ export FAKE_CTDB_STATE="$EVENTSCRIPTS_TESTS_VAR_DIR/fake-ctdb"
+
+ export FAKE_CTDB_IFACES_DOWN="$FAKE_CTDB_STATE/ifaces-down"
mkdir -p "$FAKE_CTDB_IFACES_DOWN"
rm -f "$FAKE_CTDB_IFACES_DOWN"/*
- export FAKE_CTDB_NODES_DOWN="$EVENTSCRIPTS_TESTS_VAR_DIR/nodes-down"
- mkdir -p "$FAKE_CTDB_NODES_DOWN"
- rm -f "$FAKE_CTDB_NODES_DOWN"/*
-
- export FAKE_CTDB_SCRIPTSTATUS="$EVENTSCRIPTS_TESTS_VAR_DIR/scriptstatus"
+ export FAKE_CTDB_SCRIPTSTATUS="$FAKE_CTDB_STATE/scriptstatus"
mkdir -p "$FAKE_CTDB_SCRIPTSTATUS"
rm -f "$FAKE_CTDB_SCRIPTSTATUS"/*
}
-
-
-ctdb_nodes_up ()
-{
- for _i ; do
- rm -f "${FAKE_CTDB_NODES_DOWN}/${_i}"
- done
-}
-
-ctdb_nodes_down ()
-{
- for _i ; do
- touch "${FAKE_CTDB_NODES_DOWN}/${_i}"
- done
-}
-
ctdb_get_interfaces ()
{
# The echo/subshell forces all the output onto 1 line.
@@ -272,22 +298,40 @@ ctdb_get_1_interface ()
echo ${_t%% *}
}
-ctdb_get_public_addresses ()
+# Print all public addresses as: interface IP maskbits
+# Each line is suitable for passing to takeip/releaseip
+ctdb_get_all_public_addresses ()
{
_f="${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE}/public_addresses}"
- echo $(if [ -r "$_f" ] ; then
- while read _ip _iface ; do
- echo "${_ip}@${_iface}"
- done <"$_f"
- fi)
+ while IFS="/$IFS" read _ip _maskbits _ifaces ; do
+ echo "$_ifaces $_ip $_maskbits"
+ done <"$_f"
+}
+
+# Print public addresses on this node as: interface IP maskbits
+# Each line is suitable for passing to takeip/releaseip
+ctdb_get_my_public_addresses ()
+{
+ ctdb ip -v -Y | {
+ read _x # skip header line
+
+ while IFS=":" read _x _ip _x _iface _x ; do
+ [ -n "$_iface" ] || continue
+ while IFS="/$IFS" read _i _maskbits _x ; do
+ if [ "$_ip" = "$_i" ] ; then
+ echo $_iface $_ip $_maskbits
+ break
+ fi
+ done <"${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE}/public_addresses}"
+ done
+ }
}
# Prints the 1st public address as: interface IP maskbits
-# This is suitable for passing to 10.interfaces takeip/releaseip
+# This is suitable for passing to takeip/releaseip
ctdb_get_1_public_address ()
{
- _addrs=$(ctdb_get_public_addresses)
- echo "${_addrs%% *}" | sed -r -e 's#(.*)/(.*)@(.*)#\3 \1 \2#g'
+ ctdb_get_my_public_addresses | head -n 1
}
ctdb_not_implemented ()
@@ -309,6 +353,17 @@ ctdb_fake_scriptstatus ()
echo "$_code $_status $_err_out" >"$FAKE_CTDB_SCRIPTSTATUS/$script"
}
+setup_ctdb_policy_routing ()
+{
+ export CTDB_PER_IP_ROUTING_CONF="$CTDB_BASE/policy_routing"
+ export CTDB_PER_IP_ROUTING_RULE_PREF=100
+ export CTDB_PER_IP_ROUTING_TABLE_ID_LOW=1000
+ export CTDB_PER_IP_ROUTING_TABLE_ID_HIGH=2000
+
+ # Tests need to create and populate this file
+ rm -f "$CTDB_PER_IP_ROUTING_CONF"
+}
+
######################################################################
# Samba fakery
@@ -633,7 +688,7 @@ define_test ()
die "Internal error - unknown testcase filename format"
esac
- printf "%-14s %-10s %-4s - %s\n\n" "$script" "$event" "$_num" "$desc"
+ printf "%-17s %-10s %-4s - %s\n\n" "$script" "$event" "$_num" "$desc"
}
# Set the required result for a test.
@@ -690,8 +745,8 @@ cat <<EOF
--------------------------------------------------
Output (Exit status: ${_rc}):
--------------------------------------------------
-$_out
EOF
+ echo "$_out" | cat $EVENTSCRIPT_TESTS_CAT_RESULTS_OPTS
fi
if ! $_passed ; then
@@ -699,8 +754,24 @@ EOF
--------------------------------------------------
Required output (Exit status: ${required_rc}):
--------------------------------------------------
-$required_output
EOF
+ echo "$required_output" | cat $EVENTSCRIPT_TESTS_CAT_RESULTS_OPTS
+
+ if $EVENTSCRIPT_TESTS_DIFF_RESULTS ; then
+ _outr=$(mktemp)
+ echo "$required_output" >"$_outr"
+
+ _outf=$(mktemp)
+ echo "$_out" >"$_outf"
+
+ cat <<EOF
+--------------------------------------------------
+Diff:
+--------------------------------------------------
+EOF
+ diff -u "$_outr" "$_outf" | cat -A
+ rm "$_outr" "$_outf"
+ fi
fi
}
@@ -729,22 +800,8 @@ EOF
fi
}
-# Run an eventscript once. The test passes if the return code and
-# output match those required.
-
-# Any args are passed to the eventscript.
-
-# Eventscript tracing can be done by setting:
-# EVENTSCRIPTS_TESTS_TRACE="sh -x"
-
-# or similar. This will almost certainly make a test fail but is
-# useful for debugging.
-simple_test ()
+result_check ()
{
- [ -n "$event" ] || die 'simple_test: $event not set'
-
- echo "Running \"$script $event${1:+ }$*\""
- _out=$($EVENTSCRIPTS_TESTS_TRACE "${CTDB_BASE}/events.d/$script" "$event" "$@" 2>&1)
_rc=$?
if [ -n "$OUT_FILTER" ] ; then
@@ -763,6 +820,26 @@ simple_test ()
result_footer "$_passed"
}
+# Run an eventscript once. The test passes if the return code and
+# output match those required.
+
+# Any args are passed to the eventscript.
+
+# Eventscript tracing can be done by setting:
+# EVENTSCRIPTS_TESTS_TRACE="sh -x"
+
+# or similar. This will almost certainly make a test fail but is
+# useful for debugging.
+simple_test ()
+{
+ [ -n "$event" ] || die 'simple_test: $event not set'
+
+ echo "Running eventscript \"$script $event${1:+ }$*\""
+ _out=$($EVENTSCRIPTS_TESTS_TRACE "${CTDB_BASE}/events.d/$script" "$event" "$@" 2>&1)
+
+ result_check
+}
+
simple_test_event ()
{
# If something has previously failed then don't continue.
@@ -774,6 +851,19 @@ simple_test_event ()
simple_test "$@"
}
+simple_test_command ()
+{
+ # If something has previously failed then don't continue.
+ : ${_passed:=true}
+ $_passed || return 1
+
+ echo "##################################################"
+ echo "Running command \"$*\""
+ _out=$("$@" 2>&1)
+
+ result_check
+}
+
# Run an eventscript iteratively.
# - 1st argument is the number of iterations.
# - 2nd argument is something to eval to do setup for every iteration.
diff --git a/ctdb/tests/eventscripts/etc-ctdb/public_addresses b/ctdb/tests/eventscripts/etc-ctdb/public_addresses
index 5421cd781d..cd2f6be4e1 100644
--- a/ctdb/tests/eventscripts/etc-ctdb/public_addresses
+++ b/ctdb/tests/eventscripts/etc-ctdb/public_addresses
@@ -1,4 +1,9 @@
10.0.0.1/24 dev123
10.0.0.2/24 dev123
+10.0.0.3/24 dev123
+10.0.0.4/24 dev123
+10.0.0.5/24 dev123
+10.0.0.6/24 dev123
10.0.1.1/24 dev456
10.0.1.2/24 dev456
+10.0.1.3/24 dev456
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.001.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.001.sh
new file mode 100755
index 0000000000..0bcf8e3055
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.001.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "not configured"
+
+setup_ctdb
+
+ok <<EOF
+# ip rule show
+0: from all lookup local
+32766: from all lookup main
+32767: from all lookup default
+EOF
+
+simple_test_command dump_routes
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.002.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.002.sh
new file mode 100755
index 0000000000..9a32df09f1
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.002.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "missing config, no takeip, ipreallocated"
+
+setup_ctdb
+setup_ctdb_policy_routing
+
+required_result 1 <<EOF
+error: CTDB_PER_IP_ROUTING_CONF=/home/martins/samba/ctdb/tests/eventscripts/etc-ctdb/policy_routing file not found
+EOF
+
+simple_test_event "ipreallocated"
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.003.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.003.sh
new file mode 100755
index 0000000000..832101f91d
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.003.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "empty config, ipreallocated"
+
+setup_ctdb
+setup_ctdb_policy_routing
+
+touch "$CTDB_PER_IP_ROUTING_CONF"
+
+ok_null
+
+# ipreallocated should add any missing routes
+simple_test_event "ipreallocated"
+
+ok <<EOF
+# ip rule show
+0: from all lookup local
+32766: from all lookup main
+32767: from all lookup default
+EOF
+
+simple_test_command dump_routes
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.004.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.004.sh
new file mode 100755
index 0000000000..49d2b4efa1
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.004.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "empty config, takeip"
+
+setup_ctdb
+setup_ctdb_policy_routing
+
+touch "$CTDB_PER_IP_ROUTING_CONF"
+
+public_address=$(ctdb_get_1_public_address)
+
+ok_null
+
+simple_test_event "takeip" $public_address
+
+ok <<EOF
+# ip rule show
+0: from all lookup local
+32766: from all lookup main
+32767: from all lookup default
+EOF
+
+simple_test_command dump_routes
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.005.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.005.sh
new file mode 100755
index 0000000000..3ebacbedbb
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.005.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "1 IP configured, takeip"
+
+setup_ctdb
+setup_ctdb_policy_routing
+
+ctdb_get_1_public_address |
+{
+ read dev ip bits
+
+ net=$(ipv4_host_addr_to_net "$ip" "$bits")
+ gw="${net%.*}.1" # a dumb, calculated default
+
+ cat >"$CTDB_PER_IP_ROUTING_CONF" <<EOF
+$ip $net
+$ip 0.0.0.0/0 $gw
+EOF
+
+ ok_null
+
+ simple_test_event "takeip" $dev $ip $bits
+
+ ok <<EOF
+# ip rule show
+0: from all lookup local
+${CTDB_PER_IP_ROUTING_RULE_PREF}: from $ip lookup ctdb.$ip
+32766: from all lookup main
+32767: from all lookup default
+# ip route show table ctdb.$ip
+$net dev $dev scope link
+default via $gw dev $dev
+EOF
+
+ simple_test_command dump_routes
+}
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.006.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.006.sh
new file mode 100755
index 0000000000..d458556372
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.006.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "1 IP configured, takeip, releaseip"
+
+setup_ctdb
+setup_ctdb_policy_routing
+
+ctdb_get_1_public_address |
+{
+ read dev ip bits
+
+ net=$(ipv4_host_addr_to_net "$ip" "$bits")
+ gw="${net%.*}.1" # a dumb, calculated default
+
+ cat >"$CTDB_PER_IP_ROUTING_CONF" <<EOF
+$ip $net
+$ip 0.0.0.0/0 $gw
+EOF
+
+ ok_null
+
+ simple_test_event "takeip" $dev $ip $bits
+
+ ok_null
+
+ simple_test_event "releaseip" $dev $ip $bits
+
+ ok <<EOF
+# ip rule show
+0: from all lookup local
+32766: from all lookup main
+32767: from all lookup default
+EOF
+
+ simple_test_command dump_routes
+}
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.007.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.007.sh
new file mode 100755
index 0000000000..53e9d3866a
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.007.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "1 IP configured, ipreallocated"
+
+setup_ctdb
+setup_ctdb_policy_routing
+
+ctdb_get_1_public_address |
+{
+ read dev ip bits
+
+ net=$(ipv4_host_addr_to_net "$ip" "$bits")
+ gw="${net%.*}.1" # a dumb, calculated default
+
+ cat >"$CTDB_PER_IP_ROUTING_CONF" <<EOF
+$ip $net
+$ip 0.0.0.0/0 $gw
+EOF
+
+ ok_null
+
+ # ipreallocated should add any missing routes
+ simple_test_event "ipreallocated"
+
+ ok <<EOF
+# ip rule show
+0: from all lookup local
+${CTDB_PER_IP_ROUTING_RULE_PREF}: from $ip lookup ctdb.$ip
+32766: from all lookup main
+32767: from all lookup default
+# ip route show table ctdb.$ip
+$net dev $dev scope link
+default via $gw dev $dev
+EOF
+
+ simple_test_command dump_routes
+}
+
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.008.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.008.sh
new file mode 100755
index 0000000000..282ec07364
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.008.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "1 IP configured, takeip twice"
+
+setup_ctdb
+setup_ctdb_policy_routing
+
+ctdb_get_1_public_address |
+{
+ read dev ip bits
+
+ net=$(ipv4_host_addr_to_net "$ip" "$bits")
+ gw="${net%.*}.1" # a dumb, calculated default
+
+ cat >"$CTDB_PER_IP_ROUTING_CONF" <<EOF
+$ip $net
+$ip 0.0.0.0/0 $gw
+EOF
+
+ ok_null
+
+ simple_test_event "takeip" $dev $ip $bits
+
+ ok_null
+
+ simple_test_event "takeip" $dev $ip $bits
+
+ ok <<EOF
+# ip rule show
+0: from all lookup local
+${CTDB_PER_IP_ROUTING_RULE_PREF}: from $ip lookup ctdb.$ip
+32766: from all lookup main
+32767: from all lookup default
+# ip route show table ctdb.$ip
+$net dev $dev scope link
+default via $gw dev $dev
+EOF
+
+ simple_test_command dump_routes
+}
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.009.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.009.sh
new file mode 100755
index 0000000000..771651e94a
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.009.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "All IPs configured, takeip"
+
+setup_ctdb
+setup_ctdb_policy_routing
+
+ctdb_get_all_public_addresses |
+while read dev ip bits ; do
+ net=$(ipv4_host_addr_to_net "$ip" "$bits")
+ gw="${net%.*}.1" # a dumb, calculated default
+
+ cat <<EOF
+$ip $net
+$ip 0.0.0.0/0 $gw
+EOF
+done >"$CTDB_PER_IP_ROUTING_CONF"
+
+ctdb_get_1_public_address |
+{
+ read dev ip bits
+
+ net=$(ipv4_host_addr_to_net "$ip" "$bits")
+ gw="${net%.*}.1" # a dumb, calculated default
+
+ ok_null
+
+ simple_test_event "takeip" $dev $ip $bits
+
+ ok <<EOF
+# ip rule show
+0: from all lookup local
+${CTDB_PER_IP_ROUTING_RULE_PREF}: from $ip lookup ctdb.$ip
+32766: from all lookup main
+32767: from all lookup default
+# ip route show table ctdb.$ip
+$net dev $dev scope link
+default via $gw dev $dev
+EOF
+
+ simple_test_command dump_routes
+}
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.010.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.010.sh
new file mode 100755
index 0000000000..f7caf1c98d
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.010.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "All IPs configured, takeip all on node"
+
+setup_ctdb
+setup_ctdb_policy_routing
+
+# First setup the policy routing config for all possible IPs
+ctdb_get_all_public_addresses |
+while read dev ip bits ; do
+ net=$(ipv4_host_addr_to_net "$ip" "$bits")
+ gw="${net%.*}.1" # a dumb, calculated default
+
+ cat <<EOF
+$ip $net
+$ip 0.0.0.0/0 $gw
+EOF
+done >"$CTDB_PER_IP_ROUTING_CONF"
+
+# Now do a takeip for each IP on the "current" node
+ctdb_get_my_public_addresses |
+{
+ policy_rules=""
+ policy_routes=""
+ while read dev ip bits ; do
+
+ net=$(ipv4_host_addr_to_net "$ip" "$bits")
+ gw="${net%.*}.1" # a dumb, calculated default
+
+ ok_null
+
+ simple_test_event "takeip" $dev $ip $bits
+
+ policy_rules="${policy_rules}
+${CTDB_PER_IP_ROUTING_RULE_PREF}: from $ip lookup ctdb.$ip "
+ policy_routes="${policy_routes}
+# ip route show table ctdb.$ip
+$net dev $dev scope link
+default via $gw dev $dev "
+ done
+
+ ok <<EOF
+# ip rule show
+0: from all lookup local ${policy_rules}
+32766: from all lookup main
+32767: from all lookup default ${policy_routes}
+EOF
+
+ simple_test_command dump_routes
+}
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.011.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.011.sh
new file mode 100755
index 0000000000..18b51d4b78
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.011.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "__auto_link_local__, takeip all on node"
+
+setup_ctdb
+setup_ctdb_policy_routing
+# Override to do link local fu
+CTDB_PER_IP_ROUTING_CONF="__auto_link_local__"
+
+# Do a takeip for each IP on the "current" node
+ctdb_get_my_public_addresses |
+{
+ policy_rules=""
+ policy_routes=""
+ while read dev ip bits ; do
+
+ net=$(ipv4_host_addr_to_net "$ip" "$bits")
+ gw="${net%.*}.1" # a dumb, calculated default
+
+ ok_null
+
+ simple_test_event "takeip" $dev $ip $bits
+
+ policy_rules="${policy_rules}
+${CTDB_PER_IP_ROUTING_RULE_PREF}: from $ip lookup ctdb.$ip "
+ policy_routes="${policy_routes}
+# ip route show table ctdb.$ip
+$net dev $dev scope link "
+ done
+
+ ok <<EOF
+# ip rule show
+0: from all lookup local ${policy_rules}
+32766: from all lookup main
+32767: from all lookup default ${policy_routes}
+EOF
+
+ simple_test_command dump_routes
+}
diff --git a/ctdb/tests/eventscripts/multievent/13.per_ip_routing.012.sh b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.012.sh
new file mode 100755
index 0000000000..c2720646d6
--- /dev/null
+++ b/ctdb/tests/eventscripts/multievent/13.per_ip_routing.012.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+. "${EVENTSCRIPTS_TESTS_DIR}/common.sh"
+
+define_test "1 IP configured, takeip, releaseip, ipreallocated"
+
+# This partly tests the test infrastructure. If the (stub) "ctdb
+# moveip" doesn't do anything then the IP being released will still be
+# on the node and the ipreallocated event will add the routes back.
+
+setup_ctdb
+setup_ctdb_policy_routing
+
+ctdb_get_1_public_address |
+{
+ read dev ip bits
+
+ net=$(ipv4_host_addr_to_net "$ip" "$bits")
+ gw="${net%.*}.1" # a dumb, calculated default
+
+ cat >"$CTDB_PER_IP_ROUTING_CONF" <<EOF
+$ip $net
+$ip 0.0.0.0/0 $gw
+EOF
+
+ ok_null
+
+ simple_test_event "takeip" $dev $ip $bits
+
+ ok_null
+
+ ctdb moveip $ip 1
+ simple_test_event "releaseip" $dev $ip $bits
+
+ ok_null
+
+ # This will cause any
+ simple_test_event "ipreallocated"
+
+ ok <<EOF
+# ip rule show
+0: from all lookup local
+32766: from all lookup main
+32767: from all lookup default
+EOF
+
+ simple_test_command dump_routes
+}
diff --git a/ctdb/tests/eventscripts/run_tests.sh b/ctdb/tests/eventscripts/run_tests.sh
index ec17b5f7a7..406689a0d6 100755
--- a/ctdb/tests/eventscripts/run_tests.sh
+++ b/ctdb/tests/eventscripts/run_tests.sh
@@ -7,6 +7,9 @@ export EVENTSCRIPTS_TESTS_DIR=$(pwd)
test_dir=$(dirname "$EVENTSCRIPTS_TESTS_DIR")
+export EVENTSCRIPT_TESTS_CAT_RESULTS_OPTS=""
+export EVENTSCRIPT_TESTS_DIFF_RESULTS=false
+
opts="-d"
for i ; do
@@ -21,6 +24,16 @@ for i ; do
export EVENTSCRIPTS_TESTS_TRACE="sh -x"
shift
;;
+ -A)
+ # Useful for detecting whitespace differences in results
+ export EVENTSCRIPT_TESTS_CAT_RESULTS_OPTS="-A"
+ shift
+ ;;
+ -D)
+ # Useful for detecting whitespace differences in results
+ export EVENTSCRIPT_TESTS_DIFF_RESULTS=true
+ shift
+ ;;
-*)
opts="$opts $i"
shift
diff --git a/ctdb/tests/eventscripts/simple/10.interface.monitor.015.sh b/ctdb/tests/eventscripts/simple/10.interface.monitor.015.sh
index 88ce593b15..6810733b54 100755
--- a/ctdb/tests/eventscripts/simple/10.interface.monitor.015.sh
+++ b/ctdb/tests/eventscripts/simple/10.interface.monitor.015.sh
@@ -15,7 +15,7 @@ export CTDB_DELETE_UNEXPECTED_IPS="yes"
ok <<EOF
WARNING: Removing unmanaged IP address 192.168.253.253/24 from interface dev123
-re-adding secondary address 192.168.254.254/24 to dev dev123
+Re-adding secondary address 192.168.254.254/24 to dev dev123
WARNING: Removing unmanaged IP address 192.168.254.254/24 from interface dev123
EOF
diff --git a/ctdb/tests/eventscripts/simple/10.interface.takeip.003.sh b/ctdb/tests/eventscripts/simple/10.interface.takeip.003.sh
index 8997d71d76..ce78be90b0 100755
--- a/ctdb/tests/eventscripts/simple/10.interface.takeip.003.sh
+++ b/ctdb/tests/eventscripts/simple/10.interface.takeip.003.sh
@@ -7,6 +7,10 @@ define_test "error - add same IP twice"
setup_ctdb
public_address=$(ctdb_get_1_public_address)
+dev="${public_address%% *}"
+t="${public_address#* }"
+ip="${t% *}"
+bits="${t#* }"
# This is a bit gross and contrived. The method of quoting the error
# message so it makes it to required_result() is horrible. Hopefully
@@ -14,7 +18,7 @@ public_address=$(ctdb_get_1_public_address)
err2="\
RTNETLINK answers: File exists
-Failed to add 10.0.0.1/24 on dev dev123"
+Failed to add $ip/$bits on dev $dev"
#EVENTSCRIPTS_TESTS_TRACE="sh -x"
iterate_test -- $public_address -- 2 "ok_null" \
diff --git a/ctdb/tests/eventscripts/stubs/ctdb b/ctdb/tests/eventscripts/stubs/ctdb
index 155f518ddc..81d275af70 100755
--- a/ctdb/tests/eventscripts/stubs/ctdb
+++ b/ctdb/tests/eventscripts/stubs/ctdb
@@ -22,18 +22,22 @@ not_implemented ()
}
# Don't set $POSIXLY_CORRECT here.
-_temp=$(getopt -n "$prog" -o "Yvh" -l help -- "$@") || \
+_temp=$(getopt -n "$prog" -o "Yvhn:" -l help -- "$@") || \
usage
eval set -- "$_temp"
verbose=false
machine_readable=false
+nodespec=""
+
+args="$*"
while true ; do
case "$1" in
-Y) machine_readable=true ; shift ;;
-v) verbose=true ; shift ;;
+ -n) nodespec="$2" ; shift 2 ;;
--) shift ; break ;;
-h|--help|*) usage ;; # * shouldn't happen, so this is reasonable.
esac
@@ -55,6 +59,18 @@ setup_pstore ()
mkdir -p "$pstore_dir"
}
+parse_nodespec ()
+{
+ if [ "$nodespec" = "all" ] ; then
+ nodes="$(seq 0 $((FAKE_CTDB_NUMNODES - 1)) )"
+ elif [ -n "$nodespec" ] ; then
+ nodes="$(echo $nodespec | sed -e 's@,@ @g')"
+ else
+ _t=$(ctdb_pnn)
+ nodes="${_t#PNN:}"
+ fi
+}
+
# For testing backward compatibility...
for i in $CTDB_NOT_IMPLEMENTED ; do
if [ "$i" = "$1" ] ; then
@@ -62,58 +78,145 @@ for i in $CTDB_NOT_IMPLEMENTED ; do
fi
done
-case "$1" in
- ip)
- # NOTE: all nodes share the same public addresses file.
+ctdb_pnn ()
+{
+ # Defaults to 0
+ echo "PNN:${FAKE_CTDB_PNN:-0}"
+}
- # This is completely stateless and IPs move unnecessarily.
- _f="${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE}/public_addresses}"
- if [ -f "$_f" ] ; then
- if $verbose ; then
- echo ":Public IP:Node:ActiveInterface:AvailableInterfaces:ConfiguredInterfaces:"
+######################################################################
+
+FAKE_CTDB_NODE_STATE="$FAKE_CTDB_STATE/node-state"
+FAKE_CTDB_NODES_DISABLED="$FAKE_CTDB_NODE_STATE/0x4"
+
+######################################################################
+
+# NOTE: all nodes share $CTDB_PUBLIC_ADDRESSES
+
+FAKE_CTDB_IP_LAYOUT="$FAKE_CTDB_STATE/ip-layout"
+
+ip_reallocate ()
+{
+ touch "$FAKE_CTDB_IP_LAYOUT"
+ (
+ flock 0
+
+ _pa="${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE}/public_addresses}"
+
+ if [ ! -s "$FAKE_CTDB_IP_LAYOUT" ] ; then
+ sed -n -e 's@^\([^#][^/]*\)/.*@\1 -1@p' \
+ "$_pa" >"$FAKE_CTDB_IP_LAYOUT"
+ fi
+
+ _t="${FAKE_CTDB_IP_LAYOUT}.new"
+
+ _flags=""
+ for _i in $(seq 0 $((FAKE_CTDB_NUMNODES - 1)) ) ; do
+ if ls "$FAKE_CTDB_STATE/node-state/"*"/$_i" >/dev/null 2>&1 ; then
+ # Have non-zero flags
+ _this=0
+ for _j in "$FAKE_CTDB_STATE/node-state/"*"/$_i" ; do
+ _t="${_j%/*}" # dirname
+ _f="${_t%/*}" # basename
+ _this=$(( $_this | $_f ))
+ done
else
- echo ":Public IP:Node:"
+ _this="0"
fi
- # Here IPs are distributed across nodes in a stupid way...
- _n=0
- while read _ip _ifaces ; do
- case "_$ip" in
- \#*) : ;;
- *)
- # Find a node that is up... but don't loop forever.
- _orig=$_n
- while [ -f "${FAKE_CTDB_NODES_DOWN}/${_n}" ] ; do
- _n=$(($_n + 1))
- if [ _n -eq $FAKE_CTDB_NUMNODES ] ; then
- _n=0
- fi
- if [ $_n -eq $_orig ] ; then
- _n=-1 # Never down! :-)
- fi
- done
- if $verbose ; then
- # If more than 1 interface, assume all
- # addresses are on the 1st.
- _first_iface="${_ifaces%%,*}"
- # Only show interface if address is on
- # this node.
- _my_iface=""
- if [ "PNN:$_n" = $(ctdb -Y pnn) ]; then
- _my_iface="$_first_iface"
- fi
- echo ":${_ip}:${_n}:${_my_iface}:${_first_iface}:${_ifaces}:"
- else
- echo ":${_ip}:${_n}:"
- fi
- esac
- done <"$_f"
+ _flags="${_flags}${_flags:+,}${_this}"
+ done
+ "$(dirname ${EVENTSCRIPTS_TESTS_DIR})/bin/ctdb_takeover_tests" \
+ "ctdb_takeover_run_core" "$_flags" <"$FAKE_CTDB_IP_LAYOUT" |
+ sort >"$_t"
+ mv "$_t" "$FAKE_CTDB_IP_LAYOUT"
+ ) <"$FAKE_CTDB_IP_LAYOUT"
+}
+
+ctdb_ip ()
+{
+ # If nobody has done any IP-fu then generate a layout.
+ [ -f "$FAKE_CTDB_IP_LAYOUT" ] || ip_reallocate
+
+ if $verbose ; then
+ echo ":Public IP:Node:ActiveInterface:AvailableInterfaces:ConfiguredInterfaces:"
+ else
+ echo ":Public IP:Node:"
+ fi
+
+ _mypnn=$(ctdb_pnn | sed -e 's@PNN:@@')
+
+ # Join public addresses file with $FAKE_CTDB_IP_LAYOUT, and
+ # process output line by line...
+ _pa="${CTDB_PUBLIC_ADDRESSES:-${CTDB_BASE}/public_addresses}"
+ sed -e 's@/@ @' "$_pa" | sort | join - "$FAKE_CTDB_IP_LAYOUT" |
+ while read _ip _bit _ifaces _pnn ; do
+ if $verbose ; then
+ # If more than 1 interface, assume all addresses are on the 1st.
+ _first_iface="${_ifaces%%,*}"
+ # Only show interface if address is on this node.
+ _my_iface=""
+ if [ "$_pnn" = "$_mypnn" ]; then
+ _my_iface="$_first_iface"
+ fi
+ echo ":${_ip}:${_pnn}:${_my_iface}:${_first_iface}:${_ifaces}:"
+ else
+ echo ":${_ip}:${_pnn}:"
fi
- ;;
- pnn|xpnn)
- # Defaults to 0
- echo "PNN:${FAKE_CTDB_PNN:-0}"
- ;;
+ done
+}
+
+ctdb_moveip ()
+{
+ _ip="$1"
+ _target="$2"
+
+ ip_reallocate # should be harmless and ensures we have good state
+
+ (
+ flock 0
+
+ _t="${FAKE_CTDB_IP_LAYOUT}.new"
+
+ while read _i _pnn ; do
+ if [ "$_ip" = "$_i" ] ; then
+ echo "$_ip $_target"
+ else
+ echo "$_ip $_pnn"
+ fi
+ done | sort >"$_t"
+ mv "$_t" "$FAKE_CTDB_IP_LAYOUT"
+ ) <"$FAKE_CTDB_IP_LAYOUT"
+}
+
+######################################################################
+
+ctdb_enable ()
+{
+ parse_nodespec
+
+ for _i in $nodes ; do
+ rm -f "${FAKE_CTDB_NODES_DISABLED}/${_i}"
+ done
+
+ ip_reallocate
+}
+
+ctdb_disable ()
+{
+ parse_nodespec
+
+ for _i in $nodes ; do
+ mkdir -p "$FAKE_CTDB_NODES_DISABLED"
+ touch "${FAKE_CTDB_NODES_DISABLED}/${_i}"
+ done
+
+ ip_reallocate
+}
+
+######################################################################
+
+case "$1" in
gettickles)
setup_tickles
echo ":source ip:port:destination ip:port:"
@@ -222,6 +325,11 @@ EOF
echo ":${2:-monitor}:${_b}:${_code}:${_status}:${_d1}:${_d2}:${_err_out}:"
done
;;
- *)
- not_implemented "$1"
+ gratiousarp) : ;; # Do nothing for now
+ ip) ctdb_ip "$@" ;;
+ pnn|xpnn) ctdb_pnn ;;
+ enable) ctdb_enable "$@";;
+ disable) ctdb_disable "$@";;
+ moveip) ctdb_moveip "$@";;
+ *) not_implemented "$1" ;;
esac
diff --git a/ctdb/tests/eventscripts/stubs/ip b/ctdb/tests/eventscripts/stubs/ip
index d14b06a530..c9b8eb11c7 100755
--- a/ctdb/tests/eventscripts/stubs/ip
+++ b/ctdb/tests/eventscripts/stubs/ip
@@ -8,198 +8,448 @@ not_implemented ()
exit 127
}
+######################################################################
-case "$1" in
- link)
- case "$2" in
- set)
- iface="$3"
- case "$4" in
- up)
- rm -f "${FAKE_IP_STATE}/interfaces-down/${iface}"
- ;;
- down)
- mkdir -p "${FAKE_IP_STATE}/interfaces-down"
- touch "${FAKE_IP_STATE}/interfaces-down/${iface}"
- ;;
- *)
- not_implemented "$*"
- esac
+ip_link ()
+{
+ case "$2" in
+ set)
+ # iface="$3"
+ case "$4" in
+ up) ip_link_set_up "$@" ;;
+ down) ip_link_down_up "$@" ;;
+ *) not_implemented "$*" ;;
+ esac
+ ;;
+ *) not_implemented "$*" ;;
+ esac
+}
+
+ip_link_set_up ()
+{
+ rm -f "${FAKE_IP_STATE}/interfaces-down/$3"
+}
+
+ip_link_set_down ()
+{
+ mkdir -p "${FAKE_IP_STATE}/interfaces-down"
+ touch "${FAKE_IP_STATE}/interfaces-down/$3"
+}
+
+######################################################################
+
+ip_addr ()
+{
+ case "$2" in
+ show|list|"") ip_addr_show "$@" ;;
+ add*) ip_addr_add "$@" ;;
+ del*) ip_addr_del "$@" ;;
+ *) not_implemented "$*" ;;
+ esac
+}
+
+ip_addr_show ()
+{
+ _args="$*"
+
+ shift 2
+ dev=""
+ primary=true
+ secondary=true
+ while [ -n "$1" ] ; do
+ case "$1" in
+ dev)
+ dev="$2" ; shift 2
+ ;;
+ # Do stupid things and stupid things will happen!
+ primary)
+ primary=true ; secondary=false ; shift
+ ;;
+ secondary)
+ secondary=true ; primary=false ; shift
;;
*)
- not_implemented "$1 $2"
+ # Assume an interface name
+ dev="$1" ; shift 1
esac
-
- ;;
- addr)
- case "$2" in
- add)
- shift 2
- local=""
- dev=""
- brd=""
- while [ -n "$1" ] ; do
- case "$1" in
- *.*.*.*/*)
- local="$1" ; shift
- ;;
- local)
- local="$2" ; shift 2
- ;;
- broadcast|brd)
- # For now assume this is always '+'.
- if [ "$2" != "+" ] ; then
- not_implemented "addr add ... brd $2 ..."
- fi
- shift 2
- ;;
- dev)
- dev="$2" ; shift 2
- ;;
- *)
- not_implemented "addr add ... $1 ..."
- esac
- done
- if [ -z "$dev" ] ; then
- not_implemented "addr add (without dev)"
- fi
- mkdir -p "${FAKE_IP_STATE}/addresses"
- pf="${FAKE_IP_STATE}/addresses/${dev}-primary"
- sf="${FAKE_IP_STATE}/addresses/${dev}-secondary"
- # We could lock here... but we should be the only ones
- # playing around here with these stubs.
- if [ ! -f "$pf" ] ; then
- echo "$local" >"$pf"
- elif grep -Fq "$local" "$pf" ; then
- echo "RTNETLINK answers: File exists" >&2
- exit 254
- elif [ -f "$sf" ] && grep -Fq "$local" "$sf" ; then
- echo "RTNETLINK answers: File exists" >&2
- exit 254
- else
- echo "$local" >>"$sf"
- fi
- ;;
- delete|del)
- shift 2
- local=""
- dev=""
- while [ -n "$1" ] ; do
- case "$1" in
- *.*.*.*/*)
- local="$1" ; shift
- ;;
- local)
- local="$2" ; shift 2
- ;;
- dev)
- dev="$2" ; shift 2
- ;;
- *)
- not_implemented "addr del ... $1 ..."
- esac
- done
- if [ -z "$dev" ] ; then
- not_implemented "addr del (without dev)"
- fi
- mkdir -p "${FAKE_IP_STATE}/addresses"
- pf="${FAKE_IP_STATE}/addresses/${dev}-primary"
- sf="${FAKE_IP_STATE}/addresses/${dev}-secondary"
- # We could lock here... but we should be the only ones
- # playing around here with these stubs.
- if [ ! -f "$pf" ] ; then
- echo "RTNETLINK answers: Cannot assign requested address" >&2
- exit 254
- elif grep -Fq "$local" "$pf" ; then
- # Remove primaries AND SECONDARIES.
- rm -f "$pf" "$sf"
- elif [ -f "$sf" ] && grep -Fq "$local" "$sf" ; then
- grep -Fv "$local" "$sf" >"${sf}.new"
- mv "${sf}.new" "$sf"
- else
- echo "RTNETLINK answers: Cannot assign requested address" >&2
- exit 254
- fi
+ done
+ devices="$dev"
+ if [ -z "$devices" ] ; then
+ # No device specified? Get all the primaries...
+ devices=$(ls "${FAKE_IP_STATE}/addresses/"*-primary 2>/dev/null | \
+ sed -e 's@.*/@@' -e 's@-primary$@@')
+ fi
+ calc_brd ()
+ {
+ case "${local#*/}" in
+ 24)
+ brd="${local%.*}.255"
;;
- show|list)
- shift 2
- dev=""
- primary=true
- secondary=true
- while [ -n "$1" ] ; do
- case "$1" in
- dev)
- dev="$2" ; shift 2
- ;;
- # Do stupid things and stupid things will happen!
- primary)
- primary=true ; secondary=false ; shift
- ;;
- secondary)
- secondary=true ; primary=false ; shift
- ;;
- *)
- # Assume an interface name
- dev="$1" ; shift 1
- esac
- done
- devices="$dev"
- if [ -z "$devices" ] ; then
- # No device specified? Get all the primaries...
- devices=$(ls "${FAKE_IP_STATE}/addresses/"*-primary 2>/dev/null | \
- sed -e 's@.*/@@' -e 's@-primary$@@')
- fi
- calc_brd ()
- {
- case "${local#*/}" in
- 24)
- brd="${local%.*}.255"
- ;;
- *)
- not_implemented "list ... fake bits other than 24: ${local#*/}"
- esac
- }
- show_iface()
- {
- pf="${FAKE_IP_STATE}/addresses/${dev}-primary"
- sf="${FAKE_IP_STATE}/addresses/${dev}-secondary"
- mac=$(echo $dev | md5sum | sed -r -e 's@(..)(..)(..)(..)(..)(..).*@\1:\2:\3:\4:\5:\6@')
- cat <<EOF
+ *)
+ not_implemented "list ... fake bits other than 24: ${local#*/}"
+ esac
+ }
+ show_iface()
+ {
+ pf="${FAKE_IP_STATE}/addresses/${dev}-primary"
+ sf="${FAKE_IP_STATE}/addresses/${dev}-secondary"
+ mac=$(echo $dev | md5sum | sed -r -e 's@(..)(..)(..)(..)(..)(..).*@\1:\2:\3:\4:\5:\6@')
+ cat <<EOF
${n}: ${dev}: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether ${mac} brd ff:ff:ff:ff:ff:ff
EOF
- if $primary && [ -r "$pf" ] ; then
- read local <"$pf"
- calc_brd
-cat <<EOF
+ if $primary && [ -r "$pf" ] ; then
+ read local <"$pf"
+ calc_brd
+ cat <<EOF
inet ${local} brd ${brd} scope global ${dev}
EOF
- fi
- if $secondary && [ -r "$sf" ] ; then
- while read local ; do
- calc_brd
-cat <<EOF
+ fi
+ if $secondary && [ -r "$sf" ] ; then
+ while read local ; do
+ calc_brd
+ cat <<EOF
inet ${local} brd ${brd} scope global secondary ${dev}
EOF
- done <"$sf"
- fi
-cat <<EOF
+ done <"$sf"
+ fi
+ cat <<EOF
valid_lft forever preferred_lft forever
EOF
+ }
+ n=1
+ for dev in $devices ; do
+ show_iface
+ n=$(($n + 1))
+ done
+}
+
+ip_addr_add ()
+{
+ _args="$*"
- }
- n=1
- for dev in $devices ; do
- show_iface
- n=$(($n + 1))
- done
+ shift 2
+ local=""
+ dev=""
+ brd=""
+ while [ -n "$1" ] ; do
+ case "$1" in
+ *.*.*.*/*)
+ local="$1" ; shift
+ ;;
+ local)
+ local="$2" ; shift 2
+ ;;
+ broadcast|brd)
+ # For now assume this is always '+'.
+ if [ "$2" != "+" ] ; then
+ not_implemented "addr add ... brd $2 ..."
+ fi
+ shift 2
+ ;;
+ dev)
+ dev="$2" ; shift 2
;;
*)
- not_implemented "$1 $2"
+ not_implemented "$@"
esac
- ;;
- *)
- not_implemented "$1"
+ done
+ if [ -z "$dev" ] ; then
+ not_implemented "addr add (without dev)"
+ fi
+ mkdir -p "${FAKE_IP_STATE}/addresses"
+ pf="${FAKE_IP_STATE}/addresses/${dev}-primary"
+ sf="${FAKE_IP_STATE}/addresses/${dev}-secondary"
+ # We could lock here... but we should be the only ones playing
+ # around here with these stubs.
+ if [ ! -f "$pf" ] ; then
+ echo "$local" >"$pf"
+ elif grep -Fq "$local" "$pf" ; then
+ echo "RTNETLINK answers: File exists" >&2
+ exit 254
+ elif [ -f "$sf" ] && grep -Fq "$local" "$sf" ; then
+ echo "RTNETLINK answers: File exists" >&2
+ exit 254
+ else
+ echo "$local" >>"$sf"
+ fi
+}
+
+ip_addr_del ()
+{
+ shift 2
+ local=""
+ dev=""
+ while [ -n "$1" ] ; do
+ case "$1" in
+ *.*.*.*/*)
+ local="$1" ; shift
+ ;;
+ local)
+ local="$2" ; shift 2
+ ;;
+ dev)
+ dev="$2" ; shift 2
+ ;;
+ *)
+ not_implemented "addr del ... $1 ..."
+ esac
+ done
+ if [ -z "$dev" ] ; then
+ not_implemented "addr del (without dev)"
+ fi
+ mkdir -p "${FAKE_IP_STATE}/addresses"
+ pf="${FAKE_IP_STATE}/addresses/${dev}-primary"
+ sf="${FAKE_IP_STATE}/addresses/${dev}-secondary"
+ # We could lock here... but we should be the only ones playing
+ # around here with these stubs.
+ if [ ! -f "$pf" ] ; then
+ echo "RTNETLINK answers: Cannot assign requested address" >&2
+ exit 254
+ elif grep -Fq "$local" "$pf" ; then
+ # Remove primaries AND SECONDARIES.
+ rm -f "$pf" "$sf"
+ elif [ -f "$sf" ] && grep -Fq "$local" "$sf" ; then
+ grep -Fv "$local" "$sf" >"${sf}.new"
+ mv "${sf}.new" "$sf"
+ else
+ echo "RTNETLINK answers: Cannot assign requested address" >&2
+ exit 254
+ fi
+}
+
+######################################################################
+
+ip_rule ()
+{
+ case "$2" in
+ show|list|"") ip_rule_show "$@" ;;
+ add*) ip_rule_add "$@" ;;
+ del*) ip_rule_del "$@" ;;
+ *) not_implemented "$2 in \"$*\"" ;;
+ esac
+
+}
+
+# All non-default rules are in $FAKE_IP_STATE_RULES/rules. As with
+# the real version, rules can be repeated. Deleting just deletes the
+# 1st match.
+
+ip_rule_show ()
+{
+ ip_rule_show_1 ()
+ {
+ _pre="$1"
+ _table="$2"
+ _selectors="$3"
+ # potentially more options
+
+ printf "%d:\t%s lookup %s \n" $_pre "$_selectors" "$_table"
+ }
+
+ ip_rule_show_some ()
+ {
+ _min="$1"
+ _max="$2"
+
+ [ -f "${FAKE_IP_STATE}/rules" ] || return
+
+ while read _pre _table _selectors ; do
+ # Only print those in range
+ [ $_min -le $_pre -a $_pre -le $_max ] || continue
+
+ ip_rule_show_1 $_pre "$_table" "$_selectors"
+ done <"${FAKE_IP_STATE}/rules"
+ }
+
+ ip_rule_show_1 0 "local" "from all"
+
+ ip_rule_show_some 1 32765
+
+ ip_rule_show_1 32766 "main" "from all"
+ ip_rule_show_1 32767 "default" "from all"
+
+ ip_rule_show_some 32768 2147483648
+}
+
+ip_rule_common ()
+{
+ _args="$*"
+
+ shift 2
+ _from=""
+ _pre=""
+ _table=""
+ while [ -n "$1" ] ; do
+ case "$1" in
+ from) _from="$2" ; shift 2 ;;
+ pref) _pre="$2" ; shift 2 ;;
+ table) _table="$2" ; shift 2 ;;
+ *) not_implemented "$1 in \"$_args\"" ;;
+ esac
+ done
+
+ [ -n "$_pre" ] || not_implemented "ip rule without \"pref\""
+ [ -n "$_table" ] || not_implemented "ip rule without \"table\""
+ # Relax this if more selectors added later...
+ [ -n "$_from" ] || not_implemented "ip rule without \"from\""
+}
+
+ip_rule_add ()
+{
+ ip_rule_common "$@"
+
+ _f="${FAKE_IP_STATE}/rules"
+ touch "$_f"
+ (
+ flock 0
+ # Filter order must be consistent with the comparison in ip_rule_del()
+ echo "$_pre $_table${_from:+ from }$_from" >>"$_f"
+ ) <"$_f"
+}
+
+ip_rule_del ()
+{
+ ip_rule_common "$@"
+
+ _f="${FAKE_IP_STATE}/rules"
+ touch "$_f"
+ (
+ flock 0
+ _tmp="$(mktemp)"
+ _found=false
+ while read _p _t _s ; do
+ if ! $_found && \
+ [ "$_p" = "$_pre" -a "$_t" = "$_table" -a \
+ "$_s" = "${_from:+from }$_from" ] ; then
+ # Found. Skip this one but not future ones.
+ _found=true
+ else
+ echo "$_p $_t $_s" >>"$_tmp"
+ fi
+ done
+ if cmp -s "$_tmp" "$_f" ; then
+ # No changes, must not have found what we wanted to delete
+ echo "RTNETLINK answers: No such file or directory" >&2
+ rm -f "$_tmp"
+ exit 2
+ else
+ mv "$_tmp" "$_f"
+ fi
+ ) <"$_f" || exit $?
+}
+
+######################################################################
+
+ip_route ()
+{
+ case "$2" in
+ show|list) ip_route_show "$@" ;;
+ flush) ip_route_flush "$@" ;;
+ add) ip_route_add "$@" ;;
+ *) not_implemented "$2 in \"$*\"" ;;
+ esac
+}
+
+ip_route_check_table ()
+{
+ [ -n "$_table" ] || not_implemented "ip rule without \"table\""
+
+ # Only allow tables names from 13.per_ip_routing. This is a cheap
+ # way of avoiding implementing the default/main/local tables.
+ case "$_table" in
+ ctdb.*) : ;;
+ *) not_implemented "table=${_table} in ${_args}" ;;
+ esac
+}
+
+ip_route_common ()
+{
+ _args="$*"
+ shift 2
+ [ "$1" = table ] || not_implemented "$1 in \"$_args\""
+ _table="$2"
+
+ ip_route_check_table
+}
+
+# Routes are in a file per table in the directory
+# $FAKE_IP_STATE/routes. These routes just use the table ID
+# that is passed and don't do any lookup. This could be "improved" if
+# necessary.
+
+ip_route_show ()
+{
+ ip_route_common "$@"
+
+ # Missing file is just an empty table
+ cat "$FAKE_IP_STATE/routes/${_table}" 2>/dev/null || true
+}
+
+ip_route_flush ()
+{
+ ip_route_common "$@"
+
+ rm -f "$FAKE_IP_STATE/routes/${_table}"
+}
+
+ip_route_add ()
+{
+ _args="$*"
+
+ shift 2
+
+ _prefix=""
+ _dev=""
+ _gw=""
+ _table=""
+
+ while [ -n "$1" ] ; do
+ case "$1" in
+ *.*.*.*/*|*.*.*.*) _prefix="$1" ; shift 1 ;;
+ local) _prefix="$2" ; shift 2 ;;
+ dev) _dev="$2" ; shift 2 ;;
+ via) _gw="$2" ; shift 2 ;;
+ table) _table="$2" ; shift 2 ;;
+ *) not_implemented "$1 in \"$_args\"" ;;
+ esac
+ done
+
+ ip_route_check_table
+ [ -n "$_prefix" ] || not_implemented "ip route without inet prefix in \"$_args\""
+ [ -n "$_dev" ] || not_implemented "ip route without \"dev\" in \"$_args\""
+
+ # Alias or add missing bits
+ case "$_prefix" in
+ 0.0.0.0/0) _prefix="default" ;;
+ */*) : ;;
+ *) _prefix="${_prefix}/32" ;;
+ esac
+
+ _f="$FAKE_IP_STATE/routes/${_table}"
+ mkdir -p "$FAKE_IP_STATE/routes"
+ touch "$_f"
+
+ (
+ flock 0
+
+ if [ -n "$_gw" ] ; then
+ echo "${_prefix} via ${_gw} dev ${_dev} "
+ else
+ echo "${_prefix} dev ${_dev} scope link "
+ fi >>"$_f"
+ ) <"$_f"
+}
+
+
+######################################################################
+
+case "$1" in
+ link) ip_link "$@" ;;
+ addr*) ip_addr "$@" ;;
+ rule) ip_rule "$@" ;;
+ route) ip_route "$@" ;;
+ *) not_implemented "$1" ;;
esac
exit 0