diff options
Diffstat (limited to 'ctdb/tests')
775 files changed, 62656 insertions, 0 deletions
diff --git a/ctdb/tests/CLUSTER/complex/11_ctdb_delip_removes_ip.sh b/ctdb/tests/CLUSTER/complex/11_ctdb_delip_removes_ip.sh new file mode 100755 index 0000000..072780a --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/11_ctdb_delip_removes_ip.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Verify that a node's public IP address can be deleted using 'ctdb deleteip'. + +# This is an extended version of simple/17_ctdb_config_delete_ip.sh + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +test_node_has_test_ip() +{ + # $test_node and $test_ip set by select_test_node_and_ips() + # shellcheck disable=SC2154 + try_command_on_node "$test_node" "ip addr show to ${test_ip}" + [ -n "$out" ] +} + +ctdb_test_init + +select_test_node_and_ips + +# $test_node and $test_ip set by select_test_node_and_ips() +# shellcheck disable=SC2154 +echo "Checking that node ${test_node} hosts ${test_ip}..." +test_node_has_test_ip + +echo "Attempting to remove ${test_ip} from node ${test_node}." +ctdb_onnode "$test_node" "delip ${test_ip}" +ctdb_onnode "$test_node" "ipreallocate" +wait_until_ips_are_on_node '!' "$test_node" "$test_ip" + +echo "Waiting for ${test_ip} to disappear from node ${test_node}..." +wait_until 60/5 '!' test_node_has_test_ip + +echo "GOOD: IP was successfully removed!" diff --git a/ctdb/tests/CLUSTER/complex/18_ctdb_reloadips.sh b/ctdb/tests/CLUSTER/complex/18_ctdb_reloadips.sh new file mode 100755 index 0000000..150aeea --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/18_ctdb_reloadips.sh @@ -0,0 +1,257 @@ +#!/bin/bash + +# Verify that adding/deleting IPs using 'ctdb reloadips' works + +# Checks that when IPs are added to and deleted from a single node then +# those IPs are actually assigned and unassigned from the specified +# interface. + +# Prerequisites: + +# * An active CTDB cluster with public IP addresses configured + +# Expected results: + +# * When IPs are added to a single node then they are assigned to an +# interface. + +# * When IPs are deleted from a single node then they disappear from an +# interface. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +#################### + +# Search for an unused 10.B.1.0/24 network on which to add public IP +# addresses. + +# The initial search is for a 10.B.0.0/16 network since some +# configurations may use a whole class B for the private network. +# Check that there are no public IP addresses (as reported by "ctdb ip +# all") or other IP addresses (as reported by "ip addr show") with +# the provided prefix. Note that this is an IPv4-specific test. + +echo "Getting public IP information from CTDB..." +ctdb_onnode "$test_node" "ip -X -v all" +ctdb_ip_info=$(awk -F'|' 'NR > 1 { print $2, $3, $5 }' "$outfile") + +echo "Getting IP information from interfaces..." +try_command_on_node all "ip addr show" +ip_addr_info=$(awk '$1 == "inet" { ip = $2; sub(/\/.*/, "", ip); print ip }' \ + "$outfile") + +prefix="" +for b in $(seq 0 255) ; do + prefix="10.${b}" + + # Does the prefix match any IP address returned by "ip addr info"? + while read ip ; do + if [ "${ip#${prefix}.}" != "$ip" ] ; then + prefix="" + continue 2 + fi + done <<<"$ip_addr_info" + + # Does the prefix match any public IP address "ctdb ip all"? + while read ip pnn iface ; do + if [ "${ip#${prefix}.}" != "$ip" ] ; then + prefix="" + continue 2 + fi + done <<<"$ctdb_ip_info" + + # Got through the IPs without matching prefix - done! + break +done + +[ -n "$prefix" ] || die "Unable to find a usable IP address prefix" + +# We really want a class C: 10.B.1.0/24 +prefix="${prefix}.1" + +#################### + +iface=$(echo "$ctdb_ip_info" | awk -v pnn=$test_node '$2 == pnn { print $3 ; exit }') + +#################### + +# This needs to be set only on the recmaster. All nodes should do the trick. +new_takeover_timeout=90 +echo "Setting TakeoverTimeout=${new_takeover_timeout} to avoid potential bans" +try_command_on_node all "$CTDB setvar TakeoverTimeout ${new_takeover_timeout}" + +#################### + +try_command_on_node $test_node $CTDB_TEST_WRAPPER ctdb_base_show +addresses="${out}/public_addresses" +echo "Public addresses file on node $test_node is \"$addresses\"" +backup="${addresses}.$$" + +backup_public_addresses () +{ + try_command_on_node $test_node "cp -a $addresses $backup" +} + +restore_public_addresses () +{ + try_command_on_node $test_node "mv $backup $addresses >/dev/null 2>&1 || true" +} +ctdb_test_exit_hook_add restore_public_addresses + +# Now create that backup +backup_public_addresses + +#################### + +add_ips_to_original_config () +{ + local test_node="$1" + local addresses="$2" + local iface="$3" + local prefix="$4" + local first="$5" + local last="$6" + + echo "Adding new public IPs to original config on node ${test_node}..." + echo "IPs will be ${prefix}.${first}/24..${prefix}.${last}/24" + + # Implement this by completely rebuilding the public_addresses + # file. This is easier than deleting entries on a remote node. + restore_public_addresses + backup_public_addresses + + # Note that tee is a safe way of creating a file on a remote node. + # This avoids potential fragility with quoting or redirection. + for i in $(seq $first $last) ; do + echo "${prefix}.${i}/24 ${iface}" + done | + try_command_on_node -i $test_node "tee -a $addresses" +} + +check_ips () +{ + local test_node="$1" + local iface="$2" + local prefix="$3" + local first="$4" + local last="$5" + + # If just 0 specified then this is an empty range + local public_ips_file=$(mktemp) + if [ "$first" = 0 -a -z "$last" ] ; then + echo "Checking that there are no IPs in ${prefix}.0/24" + else + local prefix_regexp="inet *${prefix//./\.}" + + echo "Checking IPs in range ${prefix}.${first}/24..${prefix}.${last}/24" + + local i + for i in $(seq $first $last) ; do + echo "${prefix}.${i}" + done | sort >"$public_ips_file" + fi + + try_command_on_node $test_node "ip addr show dev ${iface}" + local ip_addrs_file=$(mktemp) + cat "$outfile" | \ + sed -n -e "s@.*inet * \(${prefix//./\.}\.[0-9]*\)/.*@\1@p" | \ + sort >"$ip_addrs_file" + + local diffs=$(diff "$public_ips_file" "$ip_addrs_file") || true + rm -f "$ip_addrs_file" "$public_ips_file" + + if [ -z "$diffs" ] ; then + echo "GOOD: IP addresses are as expected" + else + echo "BAD: IP addresses are incorrect:" + echo "$diffs" + exit 1 + fi +} + +# ctdb reloadips will fail if it can't disable takover runs. The most +# likely reason for this is that there is already a takeover run in +# progress. We can't predict when this will happen, so retry if this +# occurs. +do_ctdb_reloadips () +{ + local retry_max=10 + local retry_count=0 + while : ; do + if try_command_on_node "$test_node" "$CTDB reloadips" ; then + return 0 + fi + + if [ "$out" != "Failed to disable takeover runs" ] ; then + return 1 + fi + + if [ $retry_count -ge $retry_max ] ; then + return 1 + fi + + retry_count=$((retry_count + 1)) + echo "Retrying..." + sleep_for 1 + done +} + +#################### + +new_ip_max=100 + +#################### + +add_ips_to_original_config \ + $test_node "$addresses" "$iface" "$prefix" 1 $new_ip_max + +do_ctdb_reloadips + +check_ips $test_node "$iface" "$prefix" 1 $new_ip_max + +ctdb_onnode "$test_node" sync + +#################### + +# This should be the primary. Ensure that no other IPs are lost +echo "Using 'ctdb reloadips' to remove the 1st address just added..." + +add_ips_to_original_config \ + $test_node "$addresses" "$iface" "$prefix" 2 $new_ip_max + +do_ctdb_reloadips + +check_ips $test_node "$iface" "$prefix" 2 $new_ip_max + +ctdb_onnode "$test_node" sync + +#################### + +# Get rid of about 1/2 the IPs +start=$(($new_ip_max / 2 + 1)) +echo "Updating to include only about 1/2 of the new IPs..." + +add_ips_to_original_config \ + $test_node "$addresses" "$iface" "$prefix" $start $new_ip_max + +do_ctdb_reloadips + +check_ips $test_node "$iface" "$prefix" $start $new_ip_max + +ctdb_onnode "$test_node" sync + +#################### + +# Delete the rest +echo "Restoring original IP configuration..." +restore_public_addresses + +do_ctdb_reloadips + +check_ips $test_node "$iface" "$prefix" 0 diff --git a/ctdb/tests/CLUSTER/complex/30_nfs_tickle_killtcp.sh b/ctdb/tests/CLUSTER/complex/30_nfs_tickle_killtcp.sh new file mode 100755 index 0000000..4d8f617 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/30_nfs_tickle_killtcp.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Verify that NFS connections are monitored and that NFS tickles are sent. + +# Create a connection to the NFS server on a node. Then disable the +# relevant NFS server node and ensure that it sends an appropriate reset +# packet. The packet must come from the releasing node. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# * Cluster nodes must be listening on the NFS TCP port (2049). + +# Expected results: + +# * CTDB on the releasing node should correctly send a reset packet when +# the node is disabled. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +test_port=2049 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with netcat..." + +sleep 30 | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +echo "Getting MAC address associated with ${test_ip}..." +releasing_mac=$(ip neigh show $test_prefix | awk '$4 == "lladdr" {print $5}') +[ -n "$releasing_mac" ] || die "Couldn't get MAC address for ${test_prefix}" +echo "MAC address is: ${releasing_mac}" + +tcptickle_sniff_start $src_socket "${test_ip}:${test_port}" + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +# Only look for a reset from the releasing node +tcptickle_sniff_wait_show "$releasing_mac" diff --git a/ctdb/tests/CLUSTER/complex/31_nfs_tickle.sh b/ctdb/tests/CLUSTER/complex/31_nfs_tickle.sh new file mode 100755 index 0000000..e3f1540 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/31_nfs_tickle.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# Verify that NFS connections are monitored and that NFS tickles are sent. + +# We create a connection to the NFS server on a node and confirm that +# this connection is registered in the nfs-tickles/ subdirectory in +# shared storage. Then kill ctdbd on the relevant NFS server node and +# ensure that the takeover node sends an appropriate reset packet. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# * Cluster nodes must be listening on the NFS TCP port (2049). + +# Expected results: + +# * CTDB should correctly record the socket and on failover the takeover +# node should send a reset packet. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips +try_command_on_node $test_node "$CTDB listnodes | wc -l" +numnodes="$out" + +# We need this for later, so we know how long to run nc for. +ctdb_onnode "$test_node" "getvar MonitorInterval" +monitor_interval="${out#*= }" + +test_port=2049 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with netcat..." + +sleep $((monitor_interval * 4)) | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +wait_for_monitor_event $test_node + +echo "Wait until NFS connection is tracked by CTDB on test node ..." +wait_until 10 check_tickles $test_node $test_ip $test_port $src_socket + +echo "Getting TicklesUpdateInterval..." +try_command_on_node $test_node $CTDB getvar TickleUpdateInterval +update_interval="$out" + +echo "Wait until NFS connection is tracked by CTDB on all nodes..." +wait_until $(($update_interval * 2)) \ + check_tickles_all $numnodes $test_ip $test_port $src_socket + +tcptickle_sniff_start $src_socket "${test_ip}:${test_port}" + +# We need to be nasty to make that the node being failed out doesn't +# get a chance to send any tickles and confuse our sniff. IPs also +# need to be dropped because we're simulating a dead node rather than +# a CTDB failure. To properly handle a CTDB failure we would need a +# watchdog to drop the IPs when CTDB disappears. +echo "Killing ctdbd on ${test_node}..." +try_command_on_node -v $test_node "killall -9 ctdbd ; $CTDB_TEST_WRAPPER drop_ips ${test_node_ips}" + +wait_until_node_has_status $test_node disconnected + +tcptickle_sniff_wait_show diff --git a/ctdb/tests/CLUSTER/complex/32_cifs_tickle.sh b/ctdb/tests/CLUSTER/complex/32_cifs_tickle.sh new file mode 100755 index 0000000..78b8948 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/32_cifs_tickle.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Verify that CIFS connections are monitored and that CIFS tickles are sent. + +# We create a connection to the CIFS server on a node and confirm that +# this connection is registered by CTDB. Then disable the relevant CIFS +# server node and ensure that the takeover node sends an appropriate +# reset packet. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# * Clustered Samba must be listening on TCP port 445. + +# Expected results: + +# * CTDB should correctly record the connection and the takeover node +# should send a reset packet. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +# We need this for later, so we know how long to sleep. +try_command_on_node 0 $CTDB getvar MonitorInterval +monitor_interval="${out#*= }" +#echo "Monitor interval on node $test_node is $monitor_interval seconds." + +select_test_node_and_ips + +test_port=445 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with netcat..." + +sleep $((monitor_interval * 4)) | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +# This should happen as soon as connection is up... but unless we wait +# we sometimes beat the registration. +echo "Checking if CIFS connection is tracked by CTDB on test node..." +wait_until 10 check_tickles $test_node $test_ip $test_port $src_socket + +# This is almost immediate. However, it is sent between nodes +# asynchronously, so it is worth checking... +echo "Wait until CIFS connection is tracked by CTDB on all nodes..." +try_command_on_node $test_node "$CTDB listnodes | wc -l" +numnodes="$out" +wait_until 5 \ + check_tickles_all $numnodes $test_ip $test_port $src_socket +tcptickle_sniff_start $src_socket "${test_ip}:${test_port}" + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +tcptickle_sniff_wait_show diff --git a/ctdb/tests/CLUSTER/complex/33_gratuitous_arp.sh b/ctdb/tests/CLUSTER/complex/33_gratuitous_arp.sh new file mode 100755 index 0000000..7a0944f --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/33_gratuitous_arp.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# Verify that a gratuitous ARP is sent when a node is failed out. + +# We ping a public IP and lookup the MAC address in the ARP table. We +# then disable the node and check the ARP table again - the MAC address +# should have changed. This test does NOT test connectivity after the +# failover. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. Remove any entries for the chosen address from the ARP table. +# 4. Send a single ping request packet to the selected public address. +# 5. Determine the MAC address corresponding to the public address by +# checking the ARP table. +# 6. Disable the selected node. +# 7. Check the ARP table and check the MAC associated with the public +# address. + +# Expected results: + +# * When a node is disabled the MAC address associated with public +# addresses on that node should change. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Removing ${test_ip} from the local ARP table..." +ip neigh flush "$test_prefix" >/dev/null 2>&1 || true + +echo "Pinging ${test_ip}..." +ping_wrapper -q -n -c 1 $test_ip + +echo "Getting MAC address associated with ${test_ip}..." +original_mac=$(ip neigh show $test_prefix | awk '$4 == "lladdr" {print $5}') +[ -n "$original_mac" ] || die "Couldn't get MAC address for ${test_prefix}" + +echo "MAC address is: ${original_mac}" + +gratarp_sniff_start + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +gratarp_sniff_wait_show + +echo "Getting MAC address associated with ${test_ip} again..." +new_mac=$(ip neigh show $test_prefix | awk '$4 == "lladdr" {print $5}') +[ -n "$new_mac" ] || die "Couldn't get MAC address for ${test_prefix}" + +echo "MAC address is: ${new_mac}" + +if [ "$original_mac" != "$new_mac" ] ; then + echo "GOOD: MAC address changed" +else + die "BAD: MAC address did not change" +fi diff --git a/ctdb/tests/CLUSTER/complex/34_nfs_tickle_restart.sh b/ctdb/tests/CLUSTER/complex/34_nfs_tickle_restart.sh new file mode 100755 index 0000000..b81510d --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/34_nfs_tickle_restart.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# Verify that a newly started CTDB node gets updated tickle details + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Cluster nodes must be listening on the NFS TCP port (2049). + +# Steps: + +# As with 31_nfs_tickle.sh but restart a node after the tickle is +# registered. + +# Expected results: + +# * CTDB should correctly communicated tickles to new CTDB instances as +# they join the cluster. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips +try_command_on_node $test_node "$CTDB listnodes -X" +listnodes_output="$out" +numnodes=$(wc -l <<<"$listnodes_output") + +test_port=2049 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with netcat..." + +sleep 600 | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +wait_for_monitor_event $test_node + +echo "Wait until NFS connection is tracked by CTDB on test node ..." +wait_until 10 check_tickles $test_node $test_ip $test_port $src_socket + +echo "Select a node to restart ctdbd" +rn=$(awk -F'|' -v test_node=$test_node \ + '$2 != test_node { print $2 ; exit }' <<<"$listnodes_output") + +echo "Restarting CTDB on node ${rn}" +ctdb_nodes_restart "$rn" + +# In some theoretical world this is racy. In practice, the node will +# take quite a while to become healthy, so this will beat any +# assignment of IPs to the node. +echo "Setting NoIPTakeover on node ${rn}" +try_command_on_node $rn $CTDB setvar NoIPTakeover 1 + +wait_until_ready + +echo "Getting TickleUpdateInterval..." +try_command_on_node $test_node $CTDB getvar TickleUpdateInterval +update_interval="$out" + +echo "Wait until NFS connection is tracked by CTDB on all nodes..." +if ! wait_until $(($update_interval * 2)) \ + check_tickles_all $numnodes $test_ip $test_port $src_socket ; then + echo "BAD: connection not tracked on all nodes:" + echo "$out" + exit 1 +fi + +# We could go on to test whether the tickle ACK gets sent. However, +# this is tested in previous tests and the use of NoIPTakeover +# complicates things on a 2 node cluster. diff --git a/ctdb/tests/CLUSTER/complex/36_smb_reset_server.sh b/ctdb/tests/CLUSTER/complex/36_smb_reset_server.sh new file mode 100755 index 0000000..d0f3d08 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/36_smb_reset_server.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Verify that the server end of an SMB connection is correctly reset + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# * Clustered Samba must be listening on TCP port 445. + +# Expected results: + +# * CTDB should correctly record the connection and the releasing node +# should reset the server end of the connection. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +# We need this for later, so we know how long to sleep. +try_command_on_node 0 $CTDB getvar MonitorInterval +monitor_interval="${out#*= }" + +select_test_node_and_ips + +test_port=445 + +echo "Set NoIPTakeover=1 on all nodes" +try_command_on_node all $CTDB setvar NoIPTakeover 1 + +echo "Give the recovery daemon some time to reload tunables" +sleep_for 5 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with nc..." + +sleep $((monitor_interval * 4)) | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +# This should happen as soon as connection is up... but unless we wait +# we sometimes beat the registration. +echo "Waiting until SMB connection is tracked by CTDB on test node..." +wait_until 10 check_tickles $test_node $test_ip $test_port $src_socket + +# It would be nice if ss consistently used local/peer instead of src/dst +ss_filter="src ${test_ip}:${test_port} dst ${src_socket}" + +try_command_on_node $test_node \ + "ss -tn state established '${ss_filter}' | tail -n +2" +if [ -z "$out" ] ; then + echo "BAD: ss did not list the socket" + exit 1 +fi +echo "GOOD: ss lists the socket:" +cat "$outfile" + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +try_command_on_node $test_node \ + "ss -tn state established '${ss_filter}' | tail -n +2" +if [ -n "$out" ] ; then + echo "BAD: ss listed the socket after failover" + exit 1 +fi +echo "GOOD: ss no longer lists the socket" diff --git a/ctdb/tests/CLUSTER/complex/37_nfs_reset_server.sh b/ctdb/tests/CLUSTER/complex/37_nfs_reset_server.sh new file mode 100755 index 0000000..3e249f9 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/37_nfs_reset_server.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Verify that the server end of an NFS connection is correctly reset + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# * Cluster nodes must be listening on the NFS TCP port (2049). + +# Expected results: + +# * CTDB should correctly record the connection and the releasing node +# should reset the server end of the connection. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +# We need this for later, so we know how long to sleep. +try_command_on_node 0 $CTDB getvar MonitorInterval +monitor_interval="${out#*= }" + +select_test_node_and_ips + +test_port=2049 + +echo "Set NoIPTakeover=1 on all nodes" +try_command_on_node all $CTDB setvar NoIPTakeover 1 + +echo "Give the recovery daemon some time to reload tunables" +sleep_for 5 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with nc..." + +sleep $((monitor_interval * 4)) | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +echo "Wait until NFS connection is tracked by CTDB on test node ..." +wait_until $((monitor_interval * 2)) \ + check_tickles $test_node $test_ip $test_port $src_socket +cat "$outfile" + +# It would be nice if ss consistently used local/peer instead of src/dst +ss_filter="src ${test_ip}:${test_port} dst ${src_socket}" + +try_command_on_node $test_node \ + "ss -tn state established '${ss_filter}' | tail -n +2" +if [ -z "$out" ] ; then + echo "BAD: ss did not list the socket" + exit 1 +fi +echo "GOOD: ss lists the socket:" +cat "$outfile" + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +try_command_on_node $test_node \ + "ss -tn state established '${ss_filter}' | tail -n +2" +if [ -n "$out" ] ; then + echo "BAD: ss listed the socket after failover" + exit 1 +fi +echo "GOOD: ss no longer lists the socket" diff --git a/ctdb/tests/CLUSTER/complex/41_failover_ping_discrete.sh b/ctdb/tests/CLUSTER/complex/41_failover_ping_discrete.sh new file mode 100755 index 0000000..539d25e --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/41_failover_ping_discrete.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Verify that it is possible to ping a public address after disabling a node. + +# We ping a public IP, disable the node hosting it and then ping the +# public IP again. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. Send a single ping request packet to the selected public address. +# 4. Disable the selected node. +# 5. Send another single ping request packet to the selected public address. + +# Expected results: + +# * When a node is disabled the public address fails over and the +# address is still pingable. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Removing ${test_ip} from the local neighbor table..." +ip neigh flush "$test_prefix" >/dev/null 2>&1 || true + +echo "Pinging ${test_ip}..." +ping_wrapper -q -n -c 1 $test_ip + +gratarp_sniff_start + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +gratarp_sniff_wait_show + +echo "Removing ${test_ip} from the local neighbor table again..." +ip neigh flush "$test_prefix" >/dev/null 2>&1 || true + +echo "Pinging ${test_ip} again..." +ping_wrapper -q -n -c 1 $test_ip diff --git a/ctdb/tests/CLUSTER/complex/42_failover_ssh_hostname.sh b/ctdb/tests/CLUSTER/complex/42_failover_ssh_hostname.sh new file mode 100755 index 0000000..233819b --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/42_failover_ssh_hostname.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# Verify that it is possible to SSH to a public address after disabling a node. + +# We SSH to a public IP and check the hostname, disable the node hosting +# it and then SSH again to confirm that the hostname has changed. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. SSH to the selected public address and run hostname. +# 4. Disable the selected node. +# 5. SSH to the selected public address again and run hostname. + +# Expected results: + +# * When a node is disabled the public address fails over and it is +# still possible to SSH to the node. The hostname should change. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Removing ${test_ip} from the local neighbor table..." +ip neigh flush "$test_prefix" >/dev/null 2>&1 || true + +echo "SSHing to ${test_ip} and running hostname..." +if ! original_hostname=$(ssh -o "StrictHostKeyChecking no" $test_ip hostname) ; then + die "Failed to get original hostname via SSH..." +fi + +echo "Hostname is: ${original_hostname}" + +gratarp_sniff_start + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +gratarp_sniff_wait_show + +echo "SSHing to ${test_ip} and running hostname (again)..." +if ! new_hostname=$(ssh -o "StrictHostKeyChecking no" $test_ip hostname) ; then + echo "Failed to get new hostname via SSH..." + echo "DEBUG:" + ip neigh show + exit 1 +fi + +echo "Hostname is: ${new_hostname}" + +if [ "$original_hostname" != "$new_hostname" ] ; then + echo "GOOD: hostname changed" +else + die "BAD: hostname did not change" +fi diff --git a/ctdb/tests/CLUSTER/complex/43_failover_nfs_basic.sh b/ctdb/tests/CLUSTER/complex/43_failover_nfs_basic.sh new file mode 100755 index 0000000..ac2cafd --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/43_failover_nfs_basic.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# Verify that a mounted NFS share is still operational after failover. + +# We mount an NFS share from a node, write a file via NFS and then +# confirm that we can correctly read the file after a failover. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. Select the 1st NFS share exported on the node. +# 4. Mount the selected NFS share. +# 5. Create a file in the NFS mount and calculate its checksum. +# 6. Disable the selected node. +# 7. Read the file and calculate its checksum. +# 8. Compare the checksums. + +# Expected results: + +# * When a node is disabled the public address fails over and it is +# possible to correctly read a file over NFS. The checksums should be +# the same before and after. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +nfs_test_setup + +echo "Create file containing random data..." +dd if=/dev/urandom of=$nfs_local_file bs=1k count=1 +original_sum=$(sum $nfs_local_file) +[ $? -eq 0 ] + +gratarp_sniff_start + +echo "Disabling node $test_node" +try_command_on_node 0 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +gratarp_sniff_wait_show + +new_sum=$(sum $nfs_local_file) +[ $? -eq 0 ] + +if [ "$original_md5" = "$new_md5" ] ; then + echo "GOOD: file contents unchanged after failover" +else + die "BAD: file contents are different after failover" +fi diff --git a/ctdb/tests/CLUSTER/complex/44_failover_nfs_oneway.sh b/ctdb/tests/CLUSTER/complex/44_failover_nfs_oneway.sh new file mode 100755 index 0000000..5c8324c --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/44_failover_nfs_oneway.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# Verify that a file created on a node is readable via NFS after a failover. + +# We write a file into an exported directory on a node, mount the NFS +# share from a node, verify that we can read the file via NFS and that +# we can still read it after a failover. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. Select the 1st NFS share exported on the node. +# 4. Write a file into exported directory on the node and calculate its +# checksum. +# 5. Mount the selected NFS share. +# 6. Read the file via the NFS mount and calculate its checksum. +# 7. Compare checksums. +# 8. Disable the selected node. +# 9. Read the file via NFS and calculate its checksum. +# 10. Compare the checksums. + +# Expected results: + +# * Checksums for the file on all 3 occasions should be the same. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +nfs_test_setup + +echo "Create file containing random data..." +local_f=$(mktemp) +ctdb_test_exit_hook_add rm -f "$local_f" +dd if=/dev/urandom of=$local_f bs=1k count=1 +local_sum=$(sum $local_f) + +scp -p "$local_f" "[${test_ip}]:${nfs_remote_file}" +try_command_on_node $test_node "chmod 644 $nfs_remote_file" + +nfs_sum=$(sum $nfs_local_file) + +if [ "$local_sum" = "$nfs_sum" ] ; then + echo "GOOD: file contents read correctly via NFS" +else + echo "BAD: file contents are different over NFS" + echo " original file: $local_sum" + echo " NFS file: $nfs_sum" + exit 1 +fi + +gratarp_sniff_start + +echo "Disabling node $test_node" +try_command_on_node 0 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +gratarp_sniff_wait_show + +new_sum=$(sum $nfs_local_file) +[ $? -eq 0 ] + +if [ "$nfs_sum" = "$new_sum" ] ; then + echo "GOOD: file contents unchanged after failover" +else + echo "BAD: file contents are different after failover" + echo " original file: $nfs_sum" + echo " NFS file: $new_sum" + exit 1 +fi diff --git a/ctdb/tests/CLUSTER/complex/45_failover_nfs_kill.sh b/ctdb/tests/CLUSTER/complex/45_failover_nfs_kill.sh new file mode 100755 index 0000000..2d15748 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/45_failover_nfs_kill.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Verify that a mounted NFS share is still operational after failover. + +# We mount an NFS share from a node, write a file via NFS and then +# confirm that we can correctly read the file after a failover. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. Select the 1st NFS share exported on the node. +# 4. Mount the selected NFS share. +# 5. Create a file in the NFS mount and calculate its checksum. +# 6. Kill CTDB on the selected node. +# 7. Read the file and calculate its checksum. +# 8. Compare the checksums. + +# Expected results: + +# * When a node is disabled the public address fails over and it is +# possible to correctly read a file over NFS. The checksums should be +# the same before and after. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +nfs_test_setup + +echo "Create file containing random data..." +dd if=/dev/urandom of=$nfs_local_file bs=1k count=1 +original_sum=$(sum $nfs_local_file) +[ $? -eq 0 ] + +gratarp_sniff_start + +echo "Killing node $test_node" +try_command_on_node $test_node $CTDB getpid +pid=${out#*:} +# We need to be nasty to make that the node being failed out doesn't +# get a chance to send any tickles or doing anything else clever. IPs +# also need to be dropped because we're simulating a dead node rather +# than a CTDB failure. To properly handle a CTDB failure we would +# need a watchdog to drop the IPs when CTDB disappears. +try_command_on_node -v $test_node "kill -9 $pid ; $CTDB_TEST_WRAPPER drop_ips ${test_node_ips}" +wait_until_node_has_status $test_node disconnected + +gratarp_sniff_wait_show + +new_sum=$(sum $nfs_local_file) +[ $? -eq 0 ] + +if [ "$original_md5" = "$new_md5" ] ; then + echo "GOOD: file contents unchanged after failover" +else + die "BAD: file contents are different after failover" +fi diff --git a/ctdb/tests/CLUSTER/complex/60_rogueip_releaseip.sh b/ctdb/tests/CLUSTER/complex/60_rogueip_releaseip.sh new file mode 100755 index 0000000..efa9ef2 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/60_rogueip_releaseip.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Verify that the recovery daemon correctly handles a rogue IP + +# It should be released... + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Using $test_ip, which is onnode $test_node" + +# This test depends on being able to assign a duplicate address on a +# 2nd node. However, IPv6 guards against this and causes the test to +# fail. +case "$test_ip" in +*:*) ctdb_test_skip "This test is not supported for IPv6 addresses" ;; +esac + +get_test_ip_mask_and_iface + +echo "Finding another node that knows about $test_ip" +ctdb_get_all_pnns +other_node="" +for i in $all_pnns ; do + if [ "$i" = "$test_node" ] ; then + continue + fi + try_command_on_node $i "$CTDB ip" + n=$(awk -v ip="$test_ip" '$1 == ip { print }' "$outfile") + if [ -n "$n" ] ; then + other_node="$i" + break + fi +done +if [ -z "$other_node" ] ; then + die "Unable to find another node that knows about $test_ip" +fi + +echo "Adding $test_ip on node $other_node" +try_command_on_node $other_node "ip addr add ${test_ip}/${mask} dev ${iface}" + +rogue_ip_is_gone () +{ + local pnn="$1" + local test_ip="$2" + try_command_on_node $pnn $CTDB_TEST_WRAPPER ip_maskbits_iface $test_ip + [ -z "$out" ] +} + +echo "Waiting until rogue IP is no longer assigned..." +wait_until 30 rogue_ip_is_gone $other_node $test_ip diff --git a/ctdb/tests/CLUSTER/complex/61_rogueip_takeip.sh b/ctdb/tests/CLUSTER/complex/61_rogueip_takeip.sh new file mode 100755 index 0000000..5ee4e54 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/61_rogueip_takeip.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Verify that TAKE_IP will work for an IP that is already on an interface + +# This is a variation of simple/60_recoverd_missing_ip.sh + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Running test against node $test_node and IP $test_ip" + +# This test puts an address on an interface and then needs to quickly +# configure that address and cause an IP takeover. However, an IPv6 +# address will be tentative for a while so "quickly" is not possible". +# When ctdb_control_takeover_ip() calls ctdb_sys_have_ip() it will +# decide that the address is not present. It then attempts a takeip, +# which can fail if the address is suddenly present because it is no +# longer tentative. +case "$test_ip" in +*:*) ctdb_test_skip "This test is not supported for IPv6 addresses" ;; +esac + +get_test_ip_mask_and_iface + +echo "Deleting IP $test_ip from all nodes" +delete_ip_from_all_nodes $test_ip +try_command_on_node -v $test_node $CTDB ipreallocate +wait_until_ips_are_on_node ! $test_node $test_ip + +try_command_on_node -v all $CTDB ip + +# The window here needs to small, to try to avoid the address being +# released. The test will still pass either way but if the first IP +# takeover run does a release then this doesn't test the code path we +# expect it to... +echo "Adding IP $test_ip to $iface and CTDB on node $test_node" +ip_cmd="ip addr add $test_ip/$mask dev $iface" +ctdb_cmd="$CTDB addip $test_ip/$mask $iface && $CTDB ipreallocate" +try_command_on_node $test_node "$ip_cmd && $ctdb_cmd" + +wait_until_ips_are_on_node $test_node $test_ip diff --git a/ctdb/tests/CLUSTER/complex/README b/ctdb/tests/CLUSTER/complex/README new file mode 100644 index 0000000..72de396 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/README @@ -0,0 +1,2 @@ +Complex integration tests. These need a real or virtual cluster. +That is, they can not be run against local daemons. diff --git a/ctdb/tests/CLUSTER/complex/scripts/local.bash b/ctdb/tests/CLUSTER/complex/scripts/local.bash new file mode 100644 index 0000000..0ef5c0a --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/scripts/local.bash @@ -0,0 +1,289 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +# Thanks/blame to Stephen Rothwell for suggesting that this can be +# done in the shell. ;-) +ipv6_to_hex () +{ + local addr="$1" + + # Replace "::" by something special. + local foo="${addr/::/:@:}" + + # Join the groups of digits together, 0-padding each group of + # digits out to 4 digits, and count the number of (non-@) groups + local out="" + local count=0 + local i + for i in $(IFS=":" ; echo $foo ) ; do + if [ "$i" = "@" ] ; then + out="${out}@" + else + out="${out}$(printf '%04x' 0x${i})" + count=$(($count + 4)) + fi + done + + # Replace '@' with correct number of zeroes + local zeroes=$(printf "%0$((32 - $count))x" 0) + echo "${out/@/${zeroes}}" +} + +####################################### + +get_src_socket () +{ + local proto="$1" + local dst_socket="$2" + local pid="$3" + local prog="$4" + + local pat="^${proto}6?[[:space:]]+[[:digit:]]+[[:space:]]+[[:digit:]]+[[:space:]]+[^[:space:]]+[[:space:]]+${dst_socket//./\\.}[[:space:]]+ESTABLISHED[[:space:]]+${pid}/${prog}[[:space:]]*\$" + out=$(netstat -tanp | + grep -E "$pat" | + awk '{ print $4 }') + + [ -n "$out" ] +} + +wait_until_get_src_socket () +{ + local proto="$1" + local dst_socket="$2" + local pid="$3" + local prog="$4" + + echo "Waiting for ${prog} to establish connection to ${dst_socket}..." + + wait_until 5 get_src_socket "$@" +} + +####################################### + +check_tickles () +{ + local node="$1" + local test_ip="$2" + local test_port="$3" + local src_socket="$4" + try_command_on_node $node ctdb gettickles $test_ip $test_port + # SRC: 10.0.2.45:49091 DST: 10.0.2.143:445 + grep -Fq "SRC: ${src_socket} " "$outfile" +} + +check_tickles_all () +{ + local numnodes="$1" + local test_ip="$2" + local test_port="$3" + local src_socket="$4" + + try_command_on_node all ctdb gettickles $test_ip $test_port + # SRC: 10.0.2.45:49091 DST: 10.0.2.143:445 + local count=$(grep -Fc "SRC: ${src_socket} " "$outfile" || true) + [ $count -eq $numnodes ] +} + + + +####################################### + +# filename will be in $tcpdump_filename, pid in $tcpdump_pid +tcpdump_start () +{ + tcpdump_filter="$1" # global + + echo "Running tcpdump..." + tcpdump_filename=$(mktemp) + ctdb_test_exit_hook_add "rm -f $tcpdump_filename" + + # The only way of being sure that tcpdump is listening is to send + # some packets that it will see. So we use dummy pings - the -U + # option to tcpdump ensures that packets are flushed to the file + # as they are captured. + local dummy_addr="127.3.2.1" + local dummy="icmp and dst host ${dummy_addr} and icmp[icmptype] == icmp-echo" + tcpdump -n -p -s 0 -e -U -w $tcpdump_filename -i any "($tcpdump_filter) or ($dummy)" & + ctdb_test_exit_hook_add "kill $! >/dev/null 2>&1" + + echo "Waiting for tcpdump output file to be ready..." + ping -q "$dummy_addr" >/dev/null 2>&1 & + ctdb_test_exit_hook_add "kill $! >/dev/null 2>&1" + + tcpdump_listen_for_dummy () + { + tcpdump -n -r $tcpdump_filename -c 1 "$dummy" >/dev/null 2>&1 + } + + wait_until 10 tcpdump_listen_for_dummy +} + +# By default, wait for 1 matching packet. +tcpdump_wait () +{ + local count="${1:-1}" + local filter="${2:-${tcpdump_filter}}" + + tcpdump_check () + { + # It would be much nicer to add "ether src + # $releasing_mac" to the filter. However, tcpdump + # does not allow MAC filtering unless an ethernet + # interface is specified with -i. It doesn't work + # with "-i any" and it doesn't work when reading from + # a file. :-( + local found + if [ -n "$releasing_mac" ] ; then + found=$(tcpdump -n -e -r "$tcpdump_filename" \ + "$filter" 2>/dev/null | + grep -c "In ${releasing_mac}") + else + found=$(tcpdump -n -e -r "$tcpdump_filename" \ + "$filter" 2>/dev/null | + wc -l) + fi + + [ $found -ge $count ] + } + + echo "Waiting for tcpdump to capture some packets..." + if ! wait_until 30 tcpdump_check ; then + echo "DEBUG AT $(date '+%F %T'):" + local i + for i in "onnode -q 0 $CTDB status" \ + "netstat -tanp" \ + "tcpdump -n -e -r $tcpdump_filename" ; do + echo "$i" + $i || true + done + return 1 + fi +} + +tcpdump_show () +{ + local filter="${1:-${tcpdump_filter}}" + + tcpdump -n -e -vv -XX -r $tcpdump_filename "$filter" 2>/dev/null +} + +tcp4tickle_sniff_start () +{ + local src="$1" + local dst="$2" + + local in="src host ${dst%:*} and tcp src port ${dst##*:} and dst host ${src%:*} and tcp dst port ${src##*:}" + local out="src host ${src%:*} and tcp src port ${src##*:} and dst host ${dst%:*} and tcp dst port ${dst##*:}" + local tickle_ack="${in} and (tcp[tcpflags] & tcp-ack != 0) and (tcp[14:2] == 1234)" # win == 1234 + local ack_ack="${out} and (tcp[tcpflags] & tcp-ack != 0)" + tcptickle_reset="${in} and tcp[tcpflags] & tcp-rst != 0" + local filter="(${tickle_ack}) or (${ack_ack}) or (${tcptickle_reset})" + + tcpdump_start "$filter" +} + +# tcp[] does not work for IPv6 (in some versions of tcpdump) +tcp6tickle_sniff_start () +{ + local src="$1" + local dst="$2" + + local in="src host ${dst%:*} and tcp src port ${dst##*:} and dst host ${src%:*} and tcp dst port ${src##*:}" + local out="src host ${src%:*} and tcp src port ${src##*:} and dst host ${dst%:*} and tcp dst port ${dst##*:}" + local tickle_ack="${in} and (ip6[53] & tcp-ack != 0) and (ip6[54:2] == 1234)" # win == 1234 + local ack_ack="${out} and (ip6[53] & tcp-ack != 0)" + tcptickle_reset="${in} and ip6[53] & tcp-rst != 0" + local filter="(${tickle_ack}) or (${ack_ack}) or (${tcptickle_reset})" + + tcpdump_start "$filter" +} + +tcptickle_sniff_start () +{ + local src="$1" + local dst="$2" + + case "${dst%:*}" in + *:*) tcp6tickle_sniff_start "$src" "$dst" ;; + *) tcp4tickle_sniff_start "$src" "$dst" ;; + esac +} + +tcptickle_sniff_wait_show () +{ + local releasing_mac="$1" # optional, used by tcpdump_wait() + + tcpdump_wait 1 "$tcptickle_reset" + + echo "GOOD: here are some TCP tickle packets:" + tcpdump_show +} + +gratarp4_sniff_start () +{ + tcpdump_start "arp host ${test_ip}" +} + +gratarp6_sniff_start () +{ + local neighbor_advertisement="icmp6 and ip6[40] == 136" + local hex=$(ipv6_to_hex "$test_ip") + local match_target="ip6[48:4] == 0x${hex:0:8} and ip6[52:4] == 0x${hex:8:8} and ip6[56:4] == 0x${hex:16:8} and ip6[60:4] == 0x${hex:24:8}" + + tcpdump_start "${neighbor_advertisement} and ${match_target}" +} + +gratarp_sniff_start () +{ + case "$test_ip" in + *:*) gratarp6_sniff_start ;; + *) gratarp4_sniff_start ;; + esac +} + +gratarp_sniff_wait_show () +{ + tcpdump_wait 2 + + echo "GOOD: this should be the some gratuitous ARPs:" + tcpdump_show +} + +ping_wrapper () +{ + case "$*" in + *:*) ping6 "$@" ;; + *) ping "$@" ;; + esac +} + +####################################### + +nfs_test_setup () +{ + select_test_node_and_ips + + nfs_first_export=$(showmount -e $test_ip | sed -n -e '2s/ .*//p') + + echo "Creating test subdirectory..." + try_command_on_node $test_node "TMPDIR=$nfs_first_export mktemp -d" + nfs_test_dir="$out" + try_command_on_node $test_node "chmod 777 $nfs_test_dir" + + nfs_mnt_d=$(mktemp -d) + nfs_local_file="${nfs_mnt_d}/${nfs_test_dir##*/}/TEST_FILE" + nfs_remote_file="${nfs_test_dir}/TEST_FILE" + + ctdb_test_exit_hook_add nfs_test_cleanup + + echo "Mounting ${test_ip}:${nfs_first_export} on ${nfs_mnt_d} ..." + mount -o timeo=1,hard,intr,vers=3 \ + "[${test_ip}]:${nfs_first_export}" ${nfs_mnt_d} +} + +nfs_test_cleanup () +{ + rm -f "$nfs_local_file" + umount -f "$nfs_mnt_d" + rmdir "$nfs_mnt_d" + onnode -q $test_node rmdir "$nfs_test_dir" +} diff --git a/ctdb/tests/INTEGRATION/database/basics.001.attach.sh b/ctdb/tests/INTEGRATION/database/basics.001.attach.sh new file mode 100755 index 0000000..1fbffc5 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/basics.001.attach.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb getdbmap' operates as expected + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node + +# test_node set by select_test_node() above +# shellcheck disable=SC2154 +ctdb_onnode -v "$test_node" getdbmap + +dbid='dbid:0x[[:xdigit:]]+' +name='name:[^[:space:]]+' +path='path:[^[:space:]]+' +opts='( (PERSISTENT|STICKY|READONLY|REPLICATED|UNHEALTHY))*' +line="${dbid} ${name} ${path}${opts}" +dbmap_pattern="^(Number of databases:[[:digit:]]+|${line})\$" + +# outfile set by ctdb_onnode() above +# shellcheck disable=SC2154 +num_db_init=$(sed -n -e '1s/.*://p' "$outfile") + +sanity_check_output $(($num_db_init + 1)) "$dbmap_pattern" + +for i in $(seq 1 5) ; do + f="attach_test_${i}.tdb" + echo "Creating test database: $f" + ctdb_onnode "$test_node" "attach ${f}" + + ctdb_onnode "$test_node" getdbmap + sanity_check_output $((num_db_init + 1)) "$dbmap_pattern" + num=$(sed -n -e '1s/^.*://p' "$outfile") + if [ "$num" = $((num_db_init + i)) ] ; then + echo "OK: correct number of additional databases" + else + ctdb_test_fail "BAD: no additional database" + fi + if awk '{print $2}' "$outfile" | grep -Fqx "name:$f" ; then + echo "OK: getdbmap knows about \"$f\"" + else + ctdb_test_fail "BAD: getdbmap does not know about \"$f\"" + fi +done diff --git a/ctdb/tests/INTEGRATION/database/basics.002.attach.sh b/ctdb/tests/INTEGRATION/database/basics.002.attach.sh new file mode 100755 index 0000000..6a5c812 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/basics.002.attach.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +# Verify that databases are attached a node joins the cluster: +# 1. Shut down CTDB on one node +# 2. Attach test databases +# 3. Check that databases are attached on all up nodes +# 4. Start CTDB on the node where it is shut down +# 5. Verify that the test databases are attached on this node +# 6. Restart one of the nodes +# 7. Verify that the test databases are attached on this node + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +###################################################################### + +try_command_on_node 0 "$CTDB listnodes -X | wc -l" +numnodes="$out" +lastnode=$(( numnodes - 1 )) + +###################################################################### + +# Confirm that the database is attached with appropriate flags +check_db_once () +{ + local pnn="$1" + local db="$2" + + try_command_on_node "$pnn" $CTDB getdbmap + if grep -qF "name:${db}" "$outfile" >/dev/null ; then + return 0 + else + return 1 + fi +} + +check_db () +{ + local pnn="$1" + local db="$2" + local flag="$3" + + local flags + + echo "Waiting until database ${db} is attached on node ${pnn}" + wait_until 10 check_db_once "$pnn" "$db" + + flags=$(awk -v db="$db" '$2 == "name:" db {print $4}' "$outfile") + if [ "$flags" = "$flag" ]; then + echo "GOOD: db ${db} attached on node ${pnn} with flag $flag" + else + echo "BAD: db ${db} attached on node ${pnn} with wrong flag" + cat "$outfile" + exit 1 + fi +} + +###################################################################### + +testdb1="test_volatile.tdb" +testdb2="test_persistent.tdb" +testdb3="test_replicated.tdb" + +test_node="0" + +echo "Shutting down node $test_node" +ctdb_nodes_stop "$test_node" +sleep 1 +wait_until_node_has_status 1 recovered +try_command_on_node -v 1 $CTDB status + +echo "Create test databases" +try_command_on_node 1 $CTDB attach "$testdb1" +try_command_on_node 1 $CTDB attach "$testdb2" persistent +try_command_on_node 1 $CTDB attach "$testdb3" replicated + +echo +echo "Checking if database is attached with correct flags" +for node in $(seq 0 $lastnode) ; do + if [ $node -ne $test_node ] ; then + check_db $node $testdb1 "" + check_db $node $testdb2 PERSISTENT + check_db $node $testdb3 REPLICATED + fi +done + +###################################################################### + +echo +echo "Start node $test_node" +ctdb_nodes_start "$test_node" +sleep 1 +wait_until_ready + +echo +echo "Checking if database is attached with correct flags" +check_db $test_node $testdb1 "" +check_db $test_node $testdb2 PERSISTENT +check_db $test_node $testdb3 REPLICATED + +###################################################################### + +echo +echo "Restarting node $test_node" +ctdb_nodes_restart "$test_node" +sleep 1 +wait_until_ready + +echo +echo "Checking if database is attached with correct flags" +check_db $test_node $testdb1 "" +check_db $test_node $testdb2 PERSISTENT +check_db $test_node $testdb3 REPLICATED diff --git a/ctdb/tests/INTEGRATION/database/basics.003.detach.sh b/ctdb/tests/INTEGRATION/database/basics.003.detach.sh new file mode 100755 index 0000000..cb44955 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/basics.003.detach.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb detach' works as expected: +# 1. Attach test databases +# 2. Detach test databases +# 3. Confirm test databases are not attached + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +###################################################################### + +try_command_on_node 0 "$CTDB listnodes -X | wc -l" +numnodes="$out" + +###################################################################### + +# Confirm that the database is attached +check_db_once () +{ + local db="$1" + + local num_db + + try_command_on_node all "$CTDB getdbmap" + num_db=$(grep -cF "name:${db}" "$outfile") || true + if [ "$num_db" -eq "$numnodes" ]; then + return 0 + else + return 1 + fi +} + +check_db () +{ + local db="$1" + + echo "Waiting until database ${db} is attached on all nodes" + wait_until 10 check_db_once "$db" +} + +# Confirm that no nodes have databases attached +check_no_db_once () +{ + local db="$1" + + local num_db + + try_command_on_node all "$CTDB getdbmap" + num_db=$(grep -cF "name:${db}" "$outfile") || true + if [ "$num_db" -eq 0 ]; then + return 0 + else + return 1 + fi +} + +check_no_db () +{ + local db="$1" + + echo "Waiting until database ${db} is detached on all nodes" + wait_until 10 check_no_db_once "$db" +} + +###################################################################### + +testdb1="detach_test1.tdb" +testdb2="detach_test2.tdb" +testdb3="detach_test3.tdb" +testdb4="detach_test4.tdb" + +echo "Create test databases" +for db in "$testdb1" "$testdb2" "$testdb3" "$testdb4" ; do + echo " $db" + try_command_on_node 0 $CTDB attach "$db" +done + +for db in "$testdb1" "$testdb2" "$testdb3" "$testdb4" ; do + check_db "$db" +done + +###################################################################### + +echo +echo "Ensuring AllowClientDBAttach=1 on all nodes" +try_command_on_node all $CTDB setvar AllowClientDBAttach 1 + +echo "Check failure detaching single test database $testdb1" +try_command_on_node 1 "! $CTDB detach $testdb1" +check_db "$testdb1" + +echo +echo "Setting AllowClientDBAttach=0 on node 0" +try_command_on_node 0 $CTDB setvar AllowClientDBAttach 0 + +echo "Check failure detaching single test database $testdb1" +try_command_on_node 1 "! $CTDB detach $testdb1" +check_db "$testdb1" + +echo +echo "Setting AllowClientDBAttach=0 on all nodes" +try_command_on_node all $CTDB setvar AllowClientDBAttach 0 + +echo "Check detaching single test database $testdb1" +try_command_on_node 1 "$CTDB detach $testdb1" +check_no_db "$testdb1" + +###################################################################### + +echo +echo "Detach multiple test databases" +echo " $testdb2, $testdb3, $testdb4" +try_command_on_node 0 $CTDB detach $testdb2 $testdb3 $testdb4 + +for db in "$testdb2" "$testdb3" "$testdb4" ; do + check_no_db "$db" +done + +###################################################################### + +echo +echo "Attach a single test database" +try_command_on_node all $CTDB setvar AllowClientDBAttach 1 +try_command_on_node 0 $CTDB attach $testdb1 +check_db "$testdb1" + +echo +echo "Write a key to database" +try_command_on_node 0 $CTDB writekey $testdb1 foo bar +try_command_on_node 0 $CTDB catdb $testdb1 +num_keys=$(sed -n -e 's/Dumped \([0-9]*\) records/\1/p' "$outfile") || true +if [ -n "$num_keys" -a $num_keys -eq 1 ]; then + echo "GOOD: Key added to database" +else + echo "BAD: Key did not get added to database" + cat "$outfile" + exit 1 +fi + +echo +echo "Detach test database" +try_command_on_node all $CTDB setvar AllowClientDBAttach 0 +try_command_on_node 0 $CTDB detach $testdb1 +check_no_db "$testdb1" + +echo +echo "Re-attach test database" +try_command_on_node all $CTDB setvar AllowClientDBAttach 1 +try_command_on_node 0 $CTDB attach $testdb1 +check_db "$testdb1" + +echo +echo "Check if the database is empty" +try_command_on_node 0 $CTDB catdb $testdb1 +num_keys=$(sed -n -e 's/Dumped \([0-9]*\) records/\1/p' "$outfile") || true +if [ -n "$num_keys" -a $num_keys -eq 0 ]; then + echo "GOOD: Database $testdb1 is empty" +else + echo "BAD: Database $testdb1 is not empty" + cat "$outfile" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/basics.004.wipe.sh b/ctdb/tests/INTEGRATION/database/basics.004.wipe.sh new file mode 100755 index 0000000..115d64c --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/basics.004.wipe.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb wipedb' can clear a persistent database: +# 1. Verify that the status on all of the ctdb nodes is 'OK'. +# 2. Create a persistent test database +# 3. Add some records to node 0 and node 1 +# 4. Run wipedb on node 0 +# 5. verify the database is empty on both node 0 and 1 + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +# 2. +test_db="persistent_test.tdb" +echo "Create persistent test database \"$test_db\"" +try_command_on_node 0 $CTDB attach "$test_db" persistent + +# 3. +# add one record to node 0 key==ABC data==ABC +echo "Store key(ABC) data(ABC) on node 0" +db_ctdb_tstore 0 "$test_db" "ABC" "ABC" + +# add one record to node 1 key==DEF data==DEF +echo "Store key(DEF) data(DEF) on node 1" +db_ctdb_tstore 1 "$test_db" "DEF" "DEF" + +# 4. +echo "Wipe database" +try_command_on_node 0 $CTDB wipedb "$test_db" + +# check that the database is wiped +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "0" ] ; then + echo "OK: Database was wiped" +else + echo "BAD: We did not end up with an empty database" + exit 1 +fi + +echo "Force a recovery" +try_command_on_node 0 $CTDB recover + +# check that the database is wiped +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "0" ] ; then + echo "OK: Database was wiped" +else + echo "BAD: We did not end up with an empty database" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/basics.010.backup_restore.sh b/ctdb/tests/INTEGRATION/database/basics.010.backup_restore.sh new file mode 100755 index 0000000..8c469d4 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/basics.010.backup_restore.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash + +# Confirm that 'ctdb restoredb' works correctly: +# 1. Create a persistent test database +# 2. Add some records to test database +# 3. Backup database +# 4. Wipe database and verify the database is empty on all nodes +# 5. Restore database and make sure all the records are restored +# 6. Make sure no recovery has been triggered + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 $CTDB status +generation=$(sed -n -e 's/^Generation:\([0-9]*\)/\1/p' "$outfile") + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +# 2. +test_db="restoredb_test.tdb" +test_dump=$(mktemp) +echo $test_dump +echo "Create persistent test database \"$test_db\"" +try_command_on_node 0 $CTDB attach "$test_db" persistent +try_command_on_node 0 $CTDB wipedb "$test_db" + +# 3. +# add 10,000 records to database +echo "Adding 10000 records to database" +( +for i in $(seq 1 10000) ; do + echo "\"key$i\" \"value$i\"" +done +) | try_command_on_node -i 0 $CTDB ptrans "$test_db" + +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "10000" ] ; then + echo "OK: Records added" +else + echo "BAD: We did not end up with 10000 records" + echo "num records = $num_records" + exit 1 +fi + +ctdb_test_exit_hook_add "rm -f $test_dump" + +# 4. +echo "Backup database" +try_command_on_node 0 $CTDB backupdb "$test_db" "$test_dump" + +# 5. +echo "Wipe database" +try_command_on_node 0 $CTDB wipedb "$test_db" + +# check that the database is restored +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "0" ] ; then + echo "OK: Database was wiped" +else + echo "BAD: We did not end up with an empty database" + echo "num records = $num_records" + exit 1 +fi + +# 6. +echo "Restore database" +try_command_on_node 0 $CTDB restoredb "$test_dump" "$test_db" + +# check that the database is restored +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "10000" ] ; then + echo "OK: Database was restored" +else + echo "BAD: We did not end up with 10000 records" + echo "num records = $num_records" + exit 1 +fi + +# 7. +wait_until_ready + +try_command_on_node 0 $CTDB status +new_generation=$(sed -n -e 's/^Generation:\([0-9]*\)/\1/p' "$outfile") + +echo "Old generation = $generation" +echo "New generation = $new_generation" + +if [ "$generation" = "$new_generation" ]; then + echo "OK: Database recovery not triggered." +else + echo "BAD: Database recovery triggered." + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/fetch.001.ring.sh b/ctdb/tests/INTEGRATION/database/fetch.001.ring.sh new file mode 100755 index 0000000..4d7d392 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/fetch.001.ring.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Run the fetch_ring test and sanity check the output + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +echo "Running fetch_ring on all $num_nodes nodes." +testprog_onnode -v -p all \ + fetch_ring -n "$num_nodes" -D "fetch_ring.tdb" -k "testkey" + +pat='^(Waiting for cluster|Fetch\[[[:digit:]]+\]: [[:digit:]]+(\.[[:digit:]]+)? msgs/sec)$' +sanity_check_output 1 "$pat" + +# Get the last line of output. +last=$(tail -n 1 "$outfile") + +# $last should look like this: +# Fetch[1]: 10670.93 msgs/sec +stuff="${last##*Fetch\[*\]: }" +mps="${stuff% msgs/sec*}" + +if [ ${mps%.*} -ge 10 ] ; then + echo "OK: $mps msgs/sec >= 10 msgs/sec" +else + echo "BAD: $mps msgs/sec < 10 msgs/sec" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/fetch.002.ring-hotkeys.sh b/ctdb/tests/INTEGRATION/database/fetch.002.ring-hotkeys.sh new file mode 100755 index 0000000..6d44253 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/fetch.002.ring-hotkeys.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash + +# Run the fetch_ring test, sanity check the output and check hot keys +# statistics + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +testdb="fetch_ring.tdb" + +ctdb_get_all_pnns +# $all_pnns is set above +# shellcheck disable=SC2154 +num_nodes=$(echo "$all_pnns" | wc -w | tr -d '[:space:]') +first=$(echo "$all_pnns" | sed -n -e '1p') + +get_key () +{ + _n="$1" + + echo "testkey${_n}" +} + +run_fetch_ring () +{ + _timelimit="$1" + _key_num="$2" + + _key=$(get_key "$_key_num") + _base_cmd="fetch_ring -n ${num_nodes} -D ${testdb}" + _cmd="${_base_cmd} -t ${_timelimit} -k ${_key}" + echo "Running \"${_cmd}\" on all $num_nodes nodes." + testprog_onnode -v -p all "$_cmd" + + _pat='^(Waiting for cluster|Fetch\[[[:digit:]]+\]: [[:digit:]]+(\.[[:digit:]]+)? msgs/sec)$' + sanity_check_output 1 "$_pat" + + # Get the last line of output. + # $outfile is set above by testprog_onnode() + # shellcheck disable=SC2154 + _last=$(tail -n 1 "$outfile") + + # $last should look like this: + # Fetch[1]: 10670.93 msgs/sec + _stuff="${_last##*Fetch\[*\]: }" + _mps="${_stuff% msgs/sec*}" + + if [ "${_mps%.*}" -ge 10 ] ; then + echo "OK: ${_mps} msgs/sec >= 10 msgs/sec" + else + ctdb_test_fail "BAD: ${_mps} msgs/sec < 10 msgs/sec" + fi +} + +check_hot_keys () +{ + _pnn="$1" + _first_key="$2" + _num_keys="$3" + + echo + echo "Checking hot keys on node ${_pnn}" + + ctdb_onnode "$_pnn" dbstatistics "$testdb" + + # Get hot keys with a non-empty key + _hotkeys=$(grep -Ex '[[:space:]]+Count:[[:digit:]]+ Key:[[:xdigit:]]+' \ + "$outfile") || true + + # Check that there are the right number of non-empty slots + if [ -z "$_hotkeys" ] ; then + _num=0 + else + _num=$(echo "$_hotkeys" | wc -l | tr -d '[:space:]') + fi + _msg="hot key slots in use = ${_num}" + if [ "$_num_keys" -ne "$_num" ] ; then + echo + cat "$outfile" + ctdb_test_fail "BAD: ${_msg} (expected ${_num_keys})" + fi + echo "GOOD: ${_msg}" + + # No hot keys? Done... + if [ "$_num" = 0 ] ; then + return + fi + + # Check that hot key counts are correctly sorted + # + # Try to be as POSIX as possible + # shellcheck disable=SC2001 + _counts=$(echo "$_hotkeys" | \ + sed -e 's|.*Count:\([[:digit:]][[:digit:]]*\).*|\1|') + _counts_sorted=$(echo "$_counts" | sort -n) + if [ "$_counts" != "$_counts_sorted" ] ; then + echo + cat "$outfile" + ctdb_test_fail "BAD: hot keys not sorted" + fi + echo "GOOD: hot key counts are correctly sorted" + + # Check that all keys are considered hot + for _j in $(seq "$_first_key" $((_first_key + _num_keys - 1))) ; do + _key=$(get_key "$_j") + _key_hex=$(printf '%s' "$_key" | \ + od -A n -t x1 | \ + tr -d '[:space:]') + if ! echo "$_hotkeys" | grep -q "Key:${_key_hex}\$" ; then + echo + cat "$outfile" + ctdb_test_fail "BAD: key \"${_key}\" is not a hot key" + fi + done + echo "GOOD: all keys are listed as hot keys" +} + +# Run fetch_ring for each of 10 keys. After each run confirm that all +# keys used so far are considered hot keys (and do other hot key +# sanity checks) on all nodes. +for i in $(seq 1 10) ; do + run_fetch_ring 5 "$i" + + for pnn in $all_pnns ; do + check_hot_keys "$pnn" 1 "$i" + done + + echo +done + +echo +echo "Resetting statistics on node ${first}" +ctdb_onnode "$first" statisticsreset + +# Ensure that only node $first has had statistics reset +for pnn in $all_pnns ; do + if [ "$pnn" = "$first" ] ; then + check_hot_keys "$pnn" 1 0 + else + check_hot_keys "$pnn" 1 10 + fi +done + +echo + +# Run fetch_ring for each of 3 new keys. After each run confirm that +# the new keys used so far are considered hot keys (and do other hot +# key sanity checks) on node $first. +# +# Note that nothing can be said about hot keys on other nodes, since +# they may be an arbitrary blend of old and new keys. +for i in $(seq 1 3) ; do + run_fetch_ring 5 $((100 + i)) + + check_hot_keys 0 101 "$i" + + echo +done diff --git a/ctdb/tests/INTEGRATION/database/readonly.001.basic.sh b/ctdb/tests/INTEGRATION/database/readonly.001.basic.sh new file mode 100755 index 0000000..aeb9740 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/readonly.001.basic.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash + +# Test support for read-only records + +# Read-only records can be activated at runtime using a ctdb command. +# If read-only records are not activated, then any attempt to fetch a +# read-only copy should be automatically upgraded to a read-write +# fetch_locked(). + +# If read-only delegations are present, then any attempt to acquire a +# read-write fetch_lock will trigger revocation of all delegations +# before the fetch_locked(). + +# 1. Create a test database and some records +# 2. Try to fetch read-only records, this should not result in any delegations +# 3. Activate read-only support +# 4. Try to fetch read-only records, this should result in delegations +# 5. Do a fetchlock and the delegations should be revoked +# 6. Try to fetch read-only records, this should result in delegations + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +###################################################################### + +# Confirm that no nodes have databases with read-only delegations +check_no_readonly () +{ + try_command_on_node all $CTDB cattdb $testdb + local ro_flags="RO_HAVE_READONLY|RO_HAVE_DELEGATIONS" + local numreadonly=$(grep -c -E "$ro_flags" "$outfile") || true + if [ $numreadonly -eq 0 ] ; then + echo "GOOD: no read-only delegations" + else + echo "BAD: there are read-only delegations" + cat "$outfile" + exit 1 + fi +} + +# Check that the test record has the correct read-only flags on the +# given nodes. The first node is the dmaster, which should know there +# are delegations but should not be flagged as having a read-only +# copy. Subsequent nodes should have a read-only copy but not know +# about any (other) delegations. +check_readonly () +{ + local dmaster="$1" ; shift + local others="$*" + + local count + + try_command_on_node $dmaster $CTDB cattdb $testdb + count=$(grep -c -E "RO_HAVE_DELEGATIONS" "$outfile") || true + if [ $count -eq 1 ] ; then + echo "GOOD: dmaster ${dmaster} has read-only delegations" + else + echo "BAD: dmaster ${dmaster} has no read-only delegations" + cat "$outfile" + exit 1 + fi + count=$(grep -c -E "RO_HAVE_READONLY" "$outfile") || true + if [ $count -ne 0 ] ; then + echo "BAD: dmaster ${dmaster} has a read-only copy" + cat "$outfile" + exit 1 + fi + + local o + for o in $others ; do + try_command_on_node $o $CTDB cattdb $testdb + count=$(grep -c -E "RO_HAVE_READONLY" "$outfile") || true + if [ $count -eq 1 ] ; then + echo "GOOD: node ${o} has a read-only copy" + else + echo "BAD: node ${o} has no read-only copy" + cat "$outfile" + exit 1 + fi + count=$(grep -c -E "RO_HAVE_DELEGATIONS" "$outfile") || true + if [ $count -ne 0 ] ; then + echo "BAD: other node ${o} has read-only delegations" + cat "$outfile" + exit 1 + fi + done +} + +###################################################################### + +echo "Get list of nodes..." +ctdb_onnode 0 "-X listnodes" +all_nodes=$(awk -F'|' '{print $2}' "$outfile") + +###################################################################### + +testdb="test.tdb" +echo "Create test database \"${testdb}\"" +try_command_on_node 0 $CTDB attach $testdb + +echo "Create some records..." +try_command_on_node all $CTDB_TEST_WRAPPER $VALGRIND update_record \ + -D ${testdb} -k testkey + +###################################################################### + +echo "Try some readonly fetches, these should all be upgraded to full fetchlocks..." +try_command_on_node all $CTDB_TEST_WRAPPER $VALGRIND fetch_readonly \ + -D ${testdb} -k testkey + +check_no_readonly + +###################################################################### + +echo "Activate read-only record support for \"$testdb\"..." +try_command_on_node all $CTDB setdbreadonly $testdb + +# Database should be tagged as READONLY +try_command_on_node 0 $CTDB getdbmap +db_details=$(awk -v db="$testdb" '$2 == foo="name:" db { print }' "$outfile") +if grep -q "READONLY" <<<"$db_details" ; then + echo "GOOD: read-only record support is enabled" +else + echo "BAD: could not activate read-only support" + echo "$db_details" + exit 1 +fi + +###################################################################### + +echo "Create 1 read-only delegation ..." +# dmaster=1 +try_command_on_node 1 $CTDB_TEST_WRAPPER $VALGRIND update_record \ + -D ${testdb} -k testkey + +# Fetch read-only to node 0 +try_command_on_node 0 $CTDB_TEST_WRAPPER $VALGRIND fetch_readonly \ + -D ${testdb} -k testkey + +check_readonly 1 0 + +###################################################################### + +echo "Verify that a fetchlock revokes read-only delegations..." +# Node 1 becomes dmaster +try_command_on_node 1 $CTDB_TEST_WRAPPER $VALGRIND update_record \ + -D ${testdb} -k testkey + +check_no_readonly + +###################################################################### + +echo "Create more read-only delegations..." +dmaster=1 +try_command_on_node $dmaster $CTDB_TEST_WRAPPER $VALGRIND update_record \ + -D ${testdb} -k testkey + +others="" +for n in $all_nodes ; do + if [ "$n" != "$dmaster" ] ; then + # Fetch read-only copy to this node + try_command_on_node $n $CTDB_TEST_WRAPPER $VALGRIND fetch_readonly \ + -D ${testdb} -k testkey + others="${others} ${n}" + fi +done + +check_readonly $dmaster $others + +###################################################################### + +echo "Verify that a recovery will revoke the delegations..." +try_command_on_node 0 $CTDB recover + +check_no_readonly diff --git a/ctdb/tests/INTEGRATION/database/recovery.001.volatile.sh b/ctdb/tests/INTEGRATION/database/recovery.001.volatile.sh new file mode 100755 index 0000000..d7aaa3b --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/recovery.001.volatile.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash + +# Test that recovery correctly handles RSNs + +# Recovery can under certain circumstances lead to old record copies +# resurrecting: Recovery selects the newest record copy purely by RSN. At +# the end of the recovery, the leader is the dmaster for all +# records in all (non-persistent) databases. And the other nodes locally +# hold the complete copy of the databases. The bug is that the recovery +# process does not increment the RSN on the leader at the end of +# the recovery. Now clients acting directly on the leader will +# directly change a record's content on the leader without migration +# and hence without RSN bump. So a subsequent recovery can not tell that +# the leader's copy is newer than the copies on the other nodes, since +# their RSN is the same. Hence, if the leader is not node 0 (or more +# precisely not the active node with the lowest node number), the recovery +# will choose copies from nodes with lower number and stick to these. + +# 1. Create a test database +# 2. Add a record with value value1 on leader +# 3. Force a recovery +# 4. Update the record with value value2 on leader +# 5. Force a recovery +# 6. Confirm that the value is value2 + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# +# Main test +# +TESTDB="rec_test.tdb" + +status=0 + +# Make sure node 0 is not the leader +echo "find out which node is leader" +ctdb_onnode 0 leader +leader="$out" +if [ "$leader" = "0" ]; then + echo "node 0 is leader, disable leader role on node 0" + # + # Note: + # It should be sufficient to run "ctdb setleaderrole off" + # on node 0 and wait for election and recovery to finish. + # But there were problems related to this in this automatic + # test, so for now use "ctdb stop" and "ctdb continue". + # + echo "stop node 0" + try_command_on_node 0 $CTDB stop + wait_until_node_has_status 0 stopped + echo "continue node 0" + try_command_on_node 0 $CTDB continue + wait_until_node_has_status 0 notstopped + + ctdb_onnode 0 leader + leader="$out" + if [ "$leader" = "0" ]; then + echo "failed to move leader to different node" + exit 1 + fi +fi + +echo "Leader:${leader}" + +# Create a temporary non-persistent database to test with +echo "create test database $TESTDB" +ctdb_onnode "$leader" attach "$TESTDB" + +# Wipe Test database +echo "wipe test database" +ctdb_onnode "$leader" wipedb "$TESTDB" + +# Add a record key=test1 data=value1 +echo "store key(test1) data(value1)" +ctdb_onnode "$leader" writekey "$TESTDB" test1 value1 + +# Fetch a record key=test1 +echo "read key(test1)" +ctdb_onnode "$leader" readkey "$TESTDB" test1 +cat "$outfile" + +# Do a recovery +echo "force recovery" +ctdb_onnode "$leader" recover + +wait_until_node_has_status "$leader" recovered + +# Add a record key=test1 data=value2 +echo "store key(test1) data(value2)" +ctdb_onnode "$leader" writekey "$TESTDB" test1 value2 + +# Fetch a record key=test1 +echo "read key(test1)" +ctdb_onnode "$leader" readkey "$TESTDB" test1 +cat "$outfile" + +# Do a recovery +echo "force recovery" +ctdb_onnode "$leader" recover + +wait_until_node_has_status "$leader" recovered + +# Verify record key=test1 +echo "read key(test1)" +ctdb_onnode "$leader" readkey "$TESTDB" test1 +cat "$outfile" +if [ "$out" = "Data: size:6 ptr:[value2]" ]; then + echo "GOOD: Recovery did not corrupt database" +else + echo "BAD: Recovery corrupted database" + status=1 +fi + +exit $status diff --git a/ctdb/tests/INTEGRATION/database/recovery.002.large.sh b/ctdb/tests/INTEGRATION/database/recovery.002.large.sh new file mode 100755 index 0000000..4736071 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/recovery.002.large.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +# Test recovery of large volatile and persistent databases + +# Recovery now uses DB_PULL and DB_PUSH_START/DB_PUSH_CONFIRM +# controls. This sends the records in batches of ~RecBufferSizeLimit +# in size at a time. Test that large databases are re-assembled +# correctly. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# +# Main test +# +TEST1DB="large_persistent_db.tdb" +TEST2DB="large_volatile_db.tdb" +RECDATA=$(onnode 0 mktemp) + +# Create a persistent database to test +echo "create persistent test database $TEST1DB" +try_command_on_node 0 $CTDB attach $TEST1DB persistent + +# Wipe Test database +echo "wipe test database $TEST1DB" +try_command_on_node 0 $CTDB wipedb $TEST1DB + +# Create dummy record data +echo "creating dummy record data" +onnode 0 dd if=/dev/urandom of=$RECDATA bs=10K count=1 + +# Add 345 records +echo "Adding 345 records" +for i in $(seq 1 345) ; do + try_command_on_node 0 $CTDB pstore $TEST1DB record$i $RECDATA || exit 1 +done + +num_records=$(db_ctdb_cattdb_count_records 0 $TEST1DB) +if [ $num_records = "345" ] ; then + echo "OK: records added correctly" +else + echo "BAD: persistent database has $num_records of 345 records" + try_command_on_node -v 0 "$CTDB cattdb $TEST1DB | tail -n 1" + exit 1 +fi + +# Create a volatile database to test +echo "create volatile test database $TEST2DB" +try_command_on_node 0 $CTDB attach $TEST2DB + +# Wipe Test database +echo "wipe test database $TEST2DB" +try_command_on_node 0 $CTDB wipedb $TEST2DB + +# Create dummy record data +v1="1234567890" +v2="$v1$v1$v1$v1$v1$v1$v1$v1$v1$v1" +v3="$v2$v2$v2$v2$v2$v2$v2$v2$v2$v2" + +# Add 1234 records +echo "Adding 1234 records" +for i in $(seq 1 1234) ; do + try_command_on_node 0 $CTDB writekey $TEST2DB record$i $v3 || exit 1 +done + +num_records=$(db_ctdb_cattdb_count_records 0 $TEST2DB) +if [ $num_records = "1234" ] ; then + echo "OK: records added correctly" +else + echo "BAD: volatile database has $num_records of 1234 records" + try_command_on_node -v 0 "$CTDB cattdb $TEST2DB | tail -n 1" + exit 1 +fi + +echo +leader_get 0 +# Set RecBufferSizeLimit to 10000 +ctdb_onnode "$leader" setvar RecBufferSizeLimit 10000 + +# Do a recovery +echo "force recovery" +try_command_on_node 0 $CTDB recover + +wait_until_node_has_status 0 recovered 30 + +# check that there are correct number of records +num_records=$(db_ctdb_cattdb_count_records 0 $TEST1DB) +if [ $num_records = "345" ] ; then + echo "OK: persistent database recovered correctly" +else + echo "BAD: persistent database has $num_records of 345 records" + try_command_on_node -v 0 "$CTDB cattdb $TEST1DB | tail -n 1" + exit 1 +fi + +num_records=$(db_ctdb_cattdb_count_records 0 $TEST2DB) +if [ $num_records = "1234" ] ; then + echo "OK: volatile database recovered correctly" +else + echo "BAD: volatile database has $num_records of 1234 records" + try_command_on_node -v 0 "$CTDB cattdb $TEST2DB | tail -n 1" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/recovery.003.no_resurrect.sh b/ctdb/tests/INTEGRATION/database/recovery.003.no_resurrect.sh new file mode 100755 index 0000000..b314d4d --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/recovery.003.no_resurrect.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +# Ensure recovery doesn't resurrect deleted records from recently +# inactive nodes + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +testdb="rec_test.tdb" + +echo "Getting list of nodes..." +ctdb_get_all_pnns + +first=$(echo "$all_pnns" | sed -n -e '1p') +second=$(echo "$all_pnns" | sed -n -e '2p') +notfirst=$(echo "$all_pnns" | tail -n +2) + +echo "Create/wipe test database ${testdb}" +try_command_on_node $first $CTDB attach "$testdb" +try_command_on_node $first $CTDB wipedb "$testdb" + +echo "store key(test1) data(value1)" +try_command_on_node $first $CTDB writekey "$testdb" test1 value1 + +echo "Migrate key(test1) to all nodes" +try_command_on_node all $CTDB readkey "$testdb" test1 + +echo "Stop node ${first}" +try_command_on_node $first $CTDB stop +wait_until_node_has_status $first stopped + +echo "Delete key(test1)" +try_command_on_node $second $CTDB deletekey "$testdb" test1 + +database_has_zero_records () +{ + # shellcheck disable=SC2086 + # $notfirst can be multi-word + check_cattdb_num_records "$testdb" 0 "$notfirst" +} + +echo "Trigger a recovery" +try_command_on_node "$second" $CTDB recover + +echo "Checking that database has 0 records" +database_has_zero_records + +echo "Continue node ${first}" +try_command_on_node $first $CTDB continue +wait_until_node_has_status $first notstopped + +echo "Get database contents" +try_command_on_node -v $first $CTDB catdb "$testdb" + +if grep -q '^key(' "$outfile" ; then + echo "BAD: Deleted record has been resurrected" + exit 1 +fi + +echo "GOOD: Deleted record is still gone" diff --git a/ctdb/tests/INTEGRATION/database/recovery.010.persistent.sh b/ctdb/tests/INTEGRATION/database/recovery.010.persistent.sh new file mode 100755 index 0000000..d13a9a5 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/recovery.010.persistent.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash + +# Ensure that persistent databases are correctly recovered by database +# sequence number +# +# 1. Create and wipe a persistent test database +# 2. Directly add a single record to the database on each node +# 3. Trigger a recover +# 4. Ensure that the database contains only a single record +# +# Repeat but with sequence numbers set by hand on each node + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +add_record_per_node () +{ + _i=0 + while [ $_i -lt $num_nodes ] ; do + _k="KEY${_i}" + _d="DATA${_i}" + echo "Store key(${_k}) data(${_d}) on node ${_i}" + db_ctdb_tstore $_i "$test_db" "$_k" "$_d" + _i=$(($_i + 1)) + done +} + +test_db="persistent_test.tdb" +echo "Create persistent test database \"$test_db\"" +try_command_on_node 0 $CTDB attach "$test_db" persistent + +# 3, +# If no __db_sequence_number__ recover whole database +# + +echo +echo "Test that no __db_sequence_number__ does not blend the database during recovery" + +# wipe database +echo "Wipe the test database" +try_command_on_node 0 $CTDB wipedb "$test_db" + +add_record_per_node + +# force a recovery +echo force a recovery +try_command_on_node 0 $CTDB recover + +# Check that we now have 1 record on node 0 +num_records=$(db_ctdb_cattdb_count_records 0 "$test_db") +if [ $num_records = "1" ] ; then + echo "OK: databases were not blended" +else + echo "BAD: we did not end up with the expected single record after the recovery" + exit 1 +fi + + +# 4, +# If __db_sequence_number__ recover whole database +# + +echo +echo test that __db_sequence_number__ does not blend the database during recovery + +# wipe database +echo wipe the test database +try_command_on_node 0 $CTDB wipedb persistent_test.tdb + +add_record_per_node + +echo "Add __db_sequence_number__==5 record to all nodes" +pnn=0 +while [ $pnn -lt $num_nodes ] ; do + db_ctdb_tstore_dbseqnum $pnn "$test_db" 5 + pnn=$(($pnn + 1)) +done + +echo "Set __db_sequence_number__ to 7 on node 0" +db_ctdb_tstore_dbseqnum 0 "$test_db" 7 + +echo "Set __db_sequence_number__ to 8 on node 1" +db_ctdb_tstore_dbseqnum 1 "$test_db" 8 + + +# force a recovery +echo force a recovery +try_command_on_node 0 $CTDB recover + +# check that we now have both records on node 0 +num_records=$(db_ctdb_cattdb_count_records 0 "$test_db") +if [ $num_records = "1" ] ; then + echo "OK: databases were not blended" +else + echo "BAD: we did not end up with the expected single record after the recovery" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/recovery.011.continue.sh b/ctdb/tests/INTEGRATION/database/recovery.011.continue.sh new file mode 100755 index 0000000..995b282 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/recovery.011.continue.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +# Confirm that the deleted records are not resurrected after recovery +# +# 1. Create a persistent database +# 2. Add a record and update it few times. +# 3. Delete the record +# 4. Use "ctdb stop" to stop one of the nodes +# 5. Add a record with same key. +# 6. Continue on the stopped node +# 7. Confirm that the record still exists + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +do_test() +{ +# Wipe Test database +echo "wipe test database" +try_command_on_node 0 $CTDB wipedb $TESTDB + +# Add a record key=test1 data=value1 +# and update values +for value in value1 value2 value3 value4 value5 ; do + echo "store key(test1) data($value)" + echo "\"test1\" \"$value\"" | try_command_on_node -i 0 $CTDB ptrans "$TESTDB" +done + +# Delete record +echo "delete key(test1)" +try_command_on_node 0 $CTDB pdelete $TESTDB test1 + +# Stop a node +echo "stop node 1" +try_command_on_node 1 $CTDB stop + +wait_until_node_has_status 1 stopped + +# Add a record key=test1 data=value2 +echo "store key(test1) data(newvalue1)" +echo '"test1" "newvalue1"' | try_command_on_node -i 0 $CTDB ptrans "$TESTDB" + +# Continue node +echo "continue node 1" +try_command_on_node 1 $CTDB continue + +wait_until_node_has_status 1 notstopped + +} + +# +# Main test +# +TESTDB="persistent_test.tdb" + +status=0 + +# Create a temporary persistent database to test with +echo "create persistent test database $TESTDB" +try_command_on_node 0 $CTDB attach $TESTDB persistent + +do_test +if try_command_on_node 0 $CTDB pfetch $TESTDB test1 ; then + echo "GOOD: Record was not deleted (recovery by sequence number worked)" +else + echo "BAD: Record was deleted" + status=1 +fi + +exit $status diff --git a/ctdb/tests/INTEGRATION/database/scripts/local.bash b/ctdb/tests/INTEGRATION/database/scripts/local.bash new file mode 100644 index 0000000..ae2e0d5 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/scripts/local.bash @@ -0,0 +1,116 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +check_cattdb_num_records () +{ + local db="$1" + local num="$2" + local nodes="$3" + + # $nodes has embedded newlines - put list on 1 line for printing + local t + t=$(echo "$nodes" | xargs) + echo "Confirm that ${db} has ${num} record(s) on node(s): ${t}" + + local ret=0 + local node + for node in $nodes ; do + local num_found + + num_found=$(db_ctdb_cattdb_count_records "$node" "$db") + if [ "$num_found" = "$num" ] ; then + continue + fi + + printf 'BAD: %s on node %d has %d record(s), expected %d\n' \ + "$db" "$node" "$num_found" "$num" + ctdb_onnode -v "$node" "cattdb $db" + ret=1 + done + + return $ret +} + +_key_dmaster_check () +{ + local node="$1" + local db="$2" + local key="$3" + local dmaster="${4:-${node}}" + + testprog_onnode "$node" "ctdb-db-test local-read ${db} ${key}" + + # shellcheck disable=SC2154 + # $outfile is set above by try_command_on_node() + grep -Fqx "dmaster: ${dmaster}" "$outfile" +} + +_key_dmaster_fail () +{ + local dmaster="$1" + + echo "BAD: node ${dmaster} is not dmaster" + # shellcheck disable=SC2154 + # $outfile is set by the caller via _key_dmaster_check() + cat "$outfile" + ctdb_test_fail +} + +vacuum_test_key_dmaster () +{ + local node="$1" + local db="$2" + local key="$3" + local dmaster="${4:-${node}}" + + if ! _key_dmaster_check "$node" "$db" "$key" "$dmaster" ; then + _key_dmaster_fail "$dmaster" + fi +} + +vacuum_test_wait_key_dmaster () +{ + local node="$1" + local db="$2" + local key="$3" + local dmaster="${4:-${node}}" + + if ! wait_until 30 \ + _key_dmaster_check "$node" "$db" "$key" "$dmaster" ; then + _key_dmaster_fail "$dmaster" + fi +} + +vacuum_confirm_key_empty_dmaster () +{ + local node="$1" + local db="$2" + local key="$3" + local dmaster="${4:-${node}}" + + echo "Confirm record key=\"${key}\" is empty and dmaster=${dmaster}" + + vacuum_test_key_dmaster "$node" "$db" "$key" "$dmaster" + + if ! grep -Fqx 'data(0) = ""' "$outfile" ; then + echo "BAD: record not empty" + cat "$outfile" + ctdb_test_fail + fi +} + +db_confirm_key_has_value () +{ + local node="$1" + local db="$2" + local key="$3" + local val="$4" + + local out + + ctdb_onnode "$node" "readkey ${db} ${key}" + outv=$(echo "$out" | sed -n 's|^Data: size:.* ptr:\[\(.*\)\]$|\1|p') + if [ "$val" != "$outv" ] ; then + ctdb_test_fail \ + "BAD: value for \"${key}\"=\"${outv}\" (not \"${val}\")" + fi +} diff --git a/ctdb/tests/INTEGRATION/database/transaction.001.ptrans.sh b/ctdb/tests/INTEGRATION/database/transaction.001.ptrans.sh new file mode 100755 index 0000000..556e523 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/transaction.001.ptrans.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash + +# Verify that the 'ctdb ptrans' works as expected +# +# Pipe some operation to ctdb ptrans and validate the TDB contents +# with ctdb catdb + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +TESTDB="ptrans_test.tdb" + +# Create a temporary persistent database to test with +echo "create persistent test database $TESTDB" +try_command_on_node 0 $CTDB attach $TESTDB persistent + +# Wipe Test database +echo "wipe test database" +try_command_on_node 0 $CTDB wipedb $TESTDB + +########## + +echo "Adding 3 records" + +items=' +"key1" "value1" +"key2" "value1" +"key3" "value1"' + +echo "$items" | try_command_on_node -i 0 $CTDB ptrans "$TESTDB" + +try_command_on_node 0 $CTDB catdb "$TESTDB" + +n=$(grep -c '^key.*= "key.*"' "$outfile" || true) + +if [ $n -ne 3 ] ; then + echo "BAD: expected 3 keys in..." + cat "$outfile" + exit 1 +else + echo "GOOD: 3 records were inserted" +fi + +########## + +echo "Deleting 1 record, updating 1, adding 1 new record, 1 bogus input line" + +items=' +"key1" "" +"key2" "value2" +"key3" +"key4" "value1"' + +echo "$items" | try_command_on_node -i 0 $CTDB ptrans "$TESTDB" + +try_command_on_node 0 $CTDB catdb "$TESTDB" + +n=$(grep -c '^key.*= "key.*"' "$outfile" || true) + +if [ $n -ne 3 ] ; then + echo "BAD: expected 3 keys in..." + cat "$outfile" + exit 1 +else + echo "GOOD: 3 records found" +fi + +########## + +echo "Verifying records" + +while read key value ; do + try_command_on_node 0 $CTDB pfetch "$TESTDB" "$key" + if [ "$value" != "$out" ] ; then + echo "BAD: for key \"$key\" expected \"$value\" but got \"$out\"" + exit 1 + else + echo "GOOD: for key \"$key\" got \"$out\"" + fi +done <<EOF +key2 value2 +key3 value1 +key4 value1 +EOF + +########## + +echo "Deleting all records" + +items=' +"key2" "" +"key3" "" +"key4" ""' + +echo "$items" | try_command_on_node -i 0 $CTDB ptrans "$TESTDB" + +try_command_on_node 0 $CTDB catdb "$TESTDB" + +n=$(grep -c '^key.*= "key.*"' "$outfile" || true) + +if [ $n -ne 0 ] ; then + echo "BAD: expected 0 keys in..." + cat "$outfile" + exit 1 +else + echo "GOOD: 0 records found" +fi diff --git a/ctdb/tests/INTEGRATION/database/transaction.002.loop.sh b/ctdb/tests/INTEGRATION/database/transaction.002.loop.sh new file mode 100755 index 0000000..d633c7c --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/transaction.002.loop.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# Verify that the transaction_loop test succeeds + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +TESTDB="persistent_trans.tdb" + +try_command_on_node 0 "$CTDB attach $TESTDB persistent" +try_command_on_node 0 "$CTDB wipedb $TESTDB" + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +if [ -z "$CTDB_TEST_TIMELIMIT" ] ; then + CTDB_TEST_TIMELIMIT=30 +fi + +t="$CTDB_TEST_WRAPPER $VALGRIND transaction_loop \ + -n ${num_nodes} -t ${CTDB_TEST_TIMELIMIT} \ + -D ${TESTDB} -T persistent -k testkey" + +echo "Running transaction_loop on all $num_nodes nodes." +try_command_on_node -v -p all "$t" diff --git a/ctdb/tests/INTEGRATION/database/transaction.003.loop_recovery.sh b/ctdb/tests/INTEGRATION/database/transaction.003.loop_recovery.sh new file mode 100755 index 0000000..05aadba --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/transaction.003.loop_recovery.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +# Verify that the transaction_loop test succeeds with recoveries. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +recovery_loop() +{ + local COUNT=1 + + while true ; do + echo Recovery $COUNT + try_command_on_node 0 $CTDB recover + sleep 2 + COUNT=$((COUNT + 1)) + done +} + +recovery_loop_start() +{ + recovery_loop >/dev/null & + RECLOOP_PID=$! + ctdb_test_exit_hook_add "kill $RECLOOP_PID >/dev/null 2>&1" +} + +TESTDB="persistent_trans.tdb" + +try_command_on_node 0 "$CTDB attach $TESTDB persistent" +try_command_on_node 0 "$CTDB wipedb $TESTDB" + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +if [ -z "$CTDB_TEST_TIMELIMIT" ] ; then + CTDB_TEST_TIMELIMIT=30 +fi + +t="$CTDB_TEST_WRAPPER $VALGRIND transaction_loop \ + -n ${num_nodes} -t ${CTDB_TEST_TIMELIMIT} \ + -D ${TESTDB} -T persistent -k testkey" + +echo "Starting recovery loop" +recovery_loop_start + +echo "Running transaction_loop on all $num_nodes nodes." +try_command_on_node -v -p all "$t" diff --git a/ctdb/tests/INTEGRATION/database/transaction.004.update_record.sh b/ctdb/tests/INTEGRATION/database/transaction.004.update_record.sh new file mode 100755 index 0000000..528303a --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/transaction.004.update_record.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# Verify that "ctdb update_record_persistent" creates new records and +# updates existing records in a persistent database +# +# 1. Create and wipe a persistent test database +# 2. Do a recovery +# 3. Confirm that the database is empty +# 4. Create a new record using "ctdb update_record_persistent" +# 5. Confirm the record exists in the database using "ctdb cattdb" +# 6. Update the record's value using "ctdb update_record_persistent" +# 7. Confirm that the original value no longer exists using "ctdb cattdb" + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +test_db="persistent_test.tdb" + +# create a temporary persistent database to test with +echo "Create persistent test database \"$test_db\"" +try_command_on_node 0 $CTDB attach "$test_db" persistent + + +# 3. +echo "Wipe the persistent test database" +try_command_on_node 0 $CTDB wipedb "$test_db" +echo "Force a recovery" +try_command_on_node 0 $CTDB recover + +# check that the database is wiped +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "0" ] ; then + echo "OK: database was wiped" +else + echo "BAD: we did not end up with an empty database" + exit 1 +fi + +# 4. +echo "Create a new record in the persistent database using UPDATE_RECORD" +try_command_on_node 0 $CTDB_TEST_WRAPPER $VALGRIND update_record_persistent \ + -D "$test_db" -k "Update_Record_Persistent" -v "FirstValue" + +try_command_on_node 0 "$CTDB cattdb "$test_db" | grep 'FirstValue' | wc -l" +if [ "$out" = 1 ] ; then + echo "GOOD: we did not find the record after the create/update" +else + echo "BAD: we did find the record after the create/update" + exit 1 +fi + +# 5. +echo Modify an existing record in the persistent database using UPDATE_RECORD +try_command_on_node 0 $CTDB_TEST_WRAPPER $VALGRIND update_record_persistent \ + -D "$test_db" -k "Update_Record_Persistent" -v "SecondValue" + +try_command_on_node 0 "$CTDB cattdb "$test_db" | grep 'FirstValue' | wc -l" +if [ "$out" = 0 ] ; then + echo "GOOD: did not find old record after the modify/update" +else + echo "BAD: we still found the old record after the modify/update" + exit 1 +fi + +try_command_on_node 0 "$CTDB cattdb "$test_db" | grep 'SecondValue' | wc -l" +if [ "$out" = 1 ] ; then + echo "GOOD: found the record after the modify/update" +else + echo "BAD: could not find the record after the modify/update" + exit 1 +fi + +echo "Wipe the persistent test databases and clean up" +try_command_on_node 0 $CTDB wipedb "$test_db" diff --git a/ctdb/tests/INTEGRATION/database/transaction.010.loop_recovery.sh b/ctdb/tests/INTEGRATION/database/transaction.010.loop_recovery.sh new file mode 100755 index 0000000..9de6c34 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/transaction.010.loop_recovery.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +# Verify that the transaction_loop test succeeds with recoveries for +# replicated databases + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +recovery_loop() +{ + local COUNT=1 + + while true ; do + echo Recovery $COUNT + try_command_on_node 0 $CTDB recover + sleep 2 + COUNT=$((COUNT + 1)) + done +} + +recovery_loop_start() +{ + recovery_loop >/dev/null & + RECLOOP_PID=$! + ctdb_test_exit_hook_add "kill $RECLOOP_PID >/dev/null 2>&1" +} + +TESTDB="replicated_trans.tdb" + +try_command_on_node 0 "$CTDB attach $TESTDB replicated" +try_command_on_node 0 "$CTDB wipedb $TESTDB" + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +if [ -z "$CTDB_TEST_TIMELIMIT" ] ; then + CTDB_TEST_TIMELIMIT=30 +fi + +t="$CTDB_TEST_WRAPPER $VALGRIND transaction_loop \ + -n ${num_nodes} -t ${CTDB_TEST_TIMELIMIT} \ + -D ${TESTDB} -T replicated -k testkey" + +echo "Starting recovery loop" +recovery_loop_start + +echo "Running transaction_loop on all $num_nodes nodes." +try_command_on_node -v -p all "$t" diff --git a/ctdb/tests/INTEGRATION/database/traverse.001.one.sh b/ctdb/tests/INTEGRATION/database/traverse.001.one.sh new file mode 100755 index 0000000..1b3b7c2 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/traverse.001.one.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +# Confirm that traverses of volatile databases work as expected + +# This is a very simple example. It writes a single record, updates it +# on another node and then confirms that the correct value is found when +# traversing. It then repeats this after removing the LMASTER role from +# the node where the value is updated. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# +# Main test +# +TESTDB="traverse_db.tdb" + +echo "create volatile test database $TESTDB" +try_command_on_node 0 $CTDB attach "$TESTDB" + +echo "wipe test database $TESTDB" +try_command_on_node 0 $CTDB wipedb "$TESTDB" + +echo "write foo=bar0 on node 0" +try_command_on_node 0 $CTDB writekey "$TESTDB" "foo" "bar0" + +echo "write foo=bar1 on node 1" +try_command_on_node 1 $CTDB writekey "$TESTDB" "foo" "bar1" + +echo + +check_db_num_records () +{ + local node="$1" + local db="$2" + local n="$3" + + echo "Checking on node ${node} to ensure ${db} has ${n} records..." + try_command_on_node "$node" "${CTDB} catdb ${db}" + + num=$(sed -n -e 's|^Dumped \(.*\) records$|\1|p' "$outfile") + if [ "$num" = "$n" ] ; then + echo "OK: Number of records=${num}" + echo + else + echo "BAD: There were ${num} (!= ${n}) records" + cat "$outfile" + exit 1 + fi +} + +check_db_num_records 0 "$TESTDB" 1 +check_db_num_records 1 "$TESTDB" 1 + +cat <<EOF + +Again, this time with 10 records, rewriting 5 of them on the 2nd node + +EOF + +echo "wipe test database $TESTDB" +try_command_on_node 0 $CTDB wipedb "$TESTDB" + +for i in $(seq 0 9) ; do + k="foo${i}" + v="bar${i}@0" + echo "write ${k}=${v} on node 0" + try_command_on_node 0 "${CTDB} writekey ${TESTDB} ${k} ${v}" +done + +for i in $(seq 1 5) ; do + k="foo${i}" + v="bar${i}@1" + echo "write ${k}=${v} on node 1" + try_command_on_node 1 "${CTDB} writekey ${TESTDB} ${k} ${v}" +done + +check_db_num_records 0 "$TESTDB" 10 +check_db_num_records 1 "$TESTDB" 10 + +cat <<EOF + +Again, this time with lmaster role off on node 1 + +EOF + +echo "wipe test database $TESTDB" +try_command_on_node 0 $CTDB wipedb "$TESTDB" + +echo "switching off lmaster role on node 1" +try_command_on_node 1 $CTDB setlmasterrole off + +try_command_on_node -v 1 $CTDB getcapabilities + +wait_until_node_has_status 1 notlmaster 10 0 + +echo "write foo=bar0 on node 0" +try_command_on_node 0 $CTDB writekey "$TESTDB" "foo" "bar0" + +echo "write foo=bar1 on node 1" +try_command_on_node 1 $CTDB writekey "$TESTDB" "foo" "bar1" + +echo + +check_db_num_records 0 "$TESTDB" 1 +check_db_num_records 1 "$TESTDB" 1 + +if grep -q "^data(4) = \"bar1\"\$" "$outfile" ; then + echo "OK: Data from node 1 was returned" +else + echo "BAD: Data from node 1 was not returned" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/traverse.002.many.sh b/ctdb/tests/INTEGRATION/database/traverse.002.many.sh new file mode 100755 index 0000000..fb0dc98 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/traverse.002.many.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +# Test cluster wide traverse code +# +# 1. Create a volatile test database +# 2. Add records on different nodes +# 3. Use "ctdb catdb" to confirm that all added records are present + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes" +num_nodes=$(echo "$out" | wc -l) + +num_records=1000 + +TESTDB="traverse_test.tdb" + +echo "create test database $TESTDB" +try_command_on_node 0 $CTDB attach $TESTDB + +echo "wipe test database $TESTDB" +try_command_on_node 0 $CTDB wipedb $TESTDB + +echo "Add $num_records records to database" +i=0 +while [ $i -lt $num_records ]; do + key=$(printf "key-%04x" $i) + value="value-$i" + + n=$[ $i % $num_nodes ] + try_command_on_node $n $CTDB writekey $TESTDB $key $value + + i=$[ $i + 1 ] +done + +echo "Start a traverse and collect records" +try_command_on_node 0 $CTDB catdb $TESTDB + +num_read=$(tail -n 1 "$outfile" | cut -d\ -f2) +if [ $num_read -eq $num_records ]; then + echo "GOOD: All $num_records records retrieved" + status=0 +else + echo "BAD: Only $num_read/$num_records records retrieved" + status=1 +fi + +exit $status diff --git a/ctdb/tests/INTEGRATION/database/vacuum.001.fast.sh b/ctdb/tests/INTEGRATION/database/vacuum.001.fast.sh new file mode 100755 index 0000000..27a2225 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/vacuum.001.fast.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash + +# Ensure that vacuuming deletes records on all nodes + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +vacuum_test () +{ + local db="$1" + local num_records="$2" + local delete_from_lmaster="${3:-false}" + + local t + if "$delete_from_lmaster" ; then + t="lmaster" + else + t="non-lmaster" + fi + + echo + echo '............................................................' + printf 'Creating %d record(s)\n' "$num_records" + printf 'Testing vacuuming of 1 record deleted from %s\n' "$t" + echo '............................................................' + + echo + echo "Stall vacuuming on all nodes" + ctdb_onnode -p all "setvar VacuumInterval 99999" + + echo + echo "Getting list of nodes..." + local all_pnns + ctdb_get_all_pnns + + local first + first=$(echo "$all_pnns" | sed -n -e '1p') + + echo + echo "Create/wipe test database ${db}" + ctdb_onnode "$first" "attach ${db}" + ctdb_onnode "$first" "wipedb ${db}" + + echo + echo "Write ${num_records} records to ${db}" + local i + for i in $(seq 1 "$num_records") ; do + ctdb_onnode "$first" "writekey ${db} test${i} value${i}" + done + + echo + echo "Migrate record(s) to all nodes" + for i in $(seq 1 "$num_records") ; do + ctdb_onnode all "readkey ${db} test${i}" + done + + echo + echo "Confirm that all nodes have all the records" + check_cattdb_num_records "$db" "$num_records" "$all_pnns" + + local key="test1" + echo + echo "Delete key ${key}" + + echo " Find lmaster for key \"${key}\"" + testprog_onnode "$first" "ctdb-db-test get-lmaster ${key}" + # out is set above + # shellcheck disable=SC2154 + lmaster="$out" + echo " lmaster=${lmaster}" + + if "$delete_from_lmaster" ; then + echo " Delete key ${key} on lmaster node ${lmaster}" + dnode="$lmaster" + else + for i in $all_pnns ; do + if [ "$i" != "$lmaster" ] ; then + dnode="$i" + break + fi + done + echo " Delete key ${key} on non-lmaster node ${dnode}" + fi + ctdb_onnode "$dnode" "deletekey ${db} ${key}" + + echo + vacuum_confirm_key_empty_dmaster "$dnode" "$db" "$key" + + echo + echo "Confirm all records still exist on all nodes" + check_cattdb_num_records "$db" "$num_records" "$all_pnns" + + if ! "$delete_from_lmaster" ; then + # Ask the lmaster to fetch the deleted record + echo + echo "Vacuum on non-lmaster node ${dnode}" + testprog_onnode "$dnode" "ctdb-db-test vacuum ${db}" + + echo + vacuum_confirm_key_empty_dmaster "$dnode" "$db" "$key" + + # Fetch the record and put it in the delete queue in + # the main daemon for processing in next vacuuming run + # on the lmaster + echo + echo "Vacuum on lmaster node ${lmaster}" + testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + + echo + echo "Confirm all records still exist on all node nodes" + check_cattdb_num_records "$db" "$num_records" "$all_pnns" + + echo + vacuum_confirm_key_empty_dmaster "$lmaster" "$db" "$key" + fi + + echo + # In the delete-from-lmaster case, the record is already in + # the lmaster's delete-queue so only a single run is needed + echo "Vacuum on lmaster node ${lmaster}" + testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + + echo + echo "Confirm a record has been deleted on all nodes" + local n=$((num_records - 1)) + check_cattdb_num_records "$db" "$n" "$all_pnns" + + echo + echo "Confirm all other records still exist with expected values" + local i + for i in $(seq 1 "$num_records") ; do + local k="test${i}" + local v="value${i}" + + if [ "$k" = "$key" ] ; then + continue + fi + + db_confirm_key_has_value "$first" "$db" "$k" "$v" + done + echo "GOOD" +} + +testdb="vacuum_test.tdb" + +# 1 record, delete from non-lmaster +vacuum_test "$testdb" 1 false + +# 10 records, delete from non-lmaster +vacuum_test "$testdb" 10 false + +# 1 record, delete from lmaster +vacuum_test "$testdb" 1 true + +# 10 records, delete from lmaster +vacuum_test "$testdb" 10 true diff --git a/ctdb/tests/INTEGRATION/database/vacuum.002.full.sh b/ctdb/tests/INTEGRATION/database/vacuum.002.full.sh new file mode 100755 index 0000000..0dc8372 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/vacuum.002.full.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +# Ensure a full vacuuming run deletes records + +# Create some records, delete some of them on their lmaster (with a +# test tool that doesn't do SCHEDULE_FOR_DELETION), run some fast +# vacuuming runs (to ensure they don't delete records that haven't +# been added to the delete queue) and then try a full vacuuming run, +# which will actually do a traverse of the database to find empty +# records and delete them. Confirm that records that haven't been +# deleted are still there, with expected values. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +db="vacuum_test.tdb" + +echo "Stall vacuuming on all nodes" +ctdb_onnode -p all "setvar VacuumInterval 99999" + +echo +echo "Getting list of nodes..." +ctdb_get_all_pnns + +# all_pnns is set above by ctdb_get_all_pnns() +# shellcheck disable=SC2154 +first=$(echo "$all_pnns" | sed -n -e '1p') + +echo +echo "Create/wipe test database ${db}" +ctdb_onnode "$first" "attach ${db}" +ctdb_onnode "$first" "wipedb ${db}" + +echo +echo "Create records in ${db}" +for i in $(seq 1 10) ; do + ctdb_onnode "$first" "writekey ${db} delete${i} value${i}" + ctdb_onnode "$first" "writekey ${db} keep${i} value${i}" +done + +echo +echo "Migrate record(s) to all nodes" +for i in $(seq 1 10) ; do + ctdb_onnode all "readkey ${db} delete${i}" + ctdb_onnode all "readkey ${db} keep${i}" +done + +echo +echo "Confirm that all nodes have all the records" +check_cattdb_num_records "$db" 20 "$all_pnns" + +echo +echo "Delete all 10 records from their lmaster node" +for i in $(seq 1 10) ; do + key="delete${i}" + + testprog_onnode "$first" "ctdb-db-test get-lmaster ${key}" + # $out is set above by testprog_onnode() + # shellcheck disable=SC2154 + lmaster="$out" + + echo + echo "Delete ${key} from lmaster node ${lmaster}" + testprog_onnode "$lmaster" \ + "ctdb-db-test fetch-local-delete $db ${key}" + + vacuum_confirm_key_empty_dmaster "$lmaster" "$db" "$key" +done + +echo "Do fast vacuuming run on all nodes" +testprog_onnode "all" "ctdb-db-test vacuum ${db}" + +echo +echo "Confirm all records still exist on all nodes" +check_cattdb_num_records "$db" 20 "$all_pnns" + +echo +echo "Do full vacuuming run on all nodes" +testprog_onnode "all" "ctdb-db-test vacuum ${db} full" + +echo +echo "Confirm 10 records exist on all nodes" +check_cattdb_num_records "$db" 10 "$all_pnns" + +echo +echo "Confirm that remaining records still exist with expected values" +for i in $(seq 1 10) ; do + k="keep${i}" + v="value${i}" + + db_confirm_key_has_value "$first" "$db" "$k" "$v" +done +echo "GOOD" diff --git a/ctdb/tests/INTEGRATION/database/vacuum.003.recreate.sh b/ctdb/tests/INTEGRATION/database/vacuum.003.recreate.sh new file mode 100755 index 0000000..acb7b13 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/vacuum.003.recreate.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash + +# Ensure that vacuuming does not delete a record that is recreated +# before vacuuming completes. This needs at least 3 nodes. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +db="vacuum_test.tdb" + +echo "Stall vacuuming on all nodes" +ctdb_onnode -p all "setvar VacuumInterval 99999" + +echo +echo "Getting list of nodes..." +ctdb_get_all_pnns + +# all_pnns is set above by ctdb_get_all_pnns() +# shellcheck disable=SC2154 +first=$(echo "$all_pnns" | sed -n -e '1p') + +echo +echo "Create/wipe test database ${db}" +ctdb_onnode "$first" "attach ${db}" +ctdb_onnode "$first" "wipedb ${db}" + +echo +echo "Create a record in ${db}" +ctdb_onnode "$first" "writekey ${db} key value1" + +echo +echo "Migrate record to all nodes" +ctdb_onnode all "readkey ${db} key" + +echo +echo "Confirm that all nodes have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo +echo "Determine lmaster node for key" +testprog_onnode "$first" "ctdb-db-test get-lmaster key" +# $out is set above by testprog_onnode() +# shellcheck disable=SC2154 +lmaster="$out" +echo "lmaster=${lmaster}" + +non_lmaster="" +# Find a non-lmaster node +for i in $all_pnns ; do + if [ "$i" != "$lmaster" ] ; then + non_lmaster="$i" + break + fi +done +if [ -z "$non_lmaster" ] ; then + ctdb_test_fail "Could not find non-lmaster node for key" +fi + +another_non_lmaster="" +# Find another non-lmaster node +for i in $all_pnns ; do + if [ "$i" != "$lmaster" ] && [ "$i" != "$non_lmaster" ] ; then + another_non_lmaster="$i" + break + fi +done +if [ -z "$another_non_lmaster" ] ; then + ctdb_test_fail "Could not find another non-lmaster node for key" +fi + +vacuum_test () +{ + local db="$1" + local key="$2" + local val="$3" + local dnode="$4" + local rnode="$5" + local rrun="$6" + + echo + echo '............................................................' + printf 'Delete key %s on node %d\n' "$key" "$dnode" + printf 'Recreate on node %d after %d vacuuming run(s)\n' \ + "$rnode" "$rrun" + echo '............................................................' + + echo + echo "Delete key \"${key}\" from node ${dnode}" + ctdb_onnode "$dnode" "deletekey ${db} ${key}" + + if [ "$rrun" -eq 0 ] ; then + echo "Recreate record on node ${rnode}" + ctdb_onnode "$rnode" "writekey ${db} ${key} ${val}" + fi + + echo "Do a fast vacuuming run on node ${dnode}" + testprog_onnode "$dnode" "ctdb-db-test vacuum ${db}" + + if [ "$rrun" -eq 1 ] ; then + echo "Recreate record on node ${rnode}" + ctdb_onnode "$rnode" "writekey ${db} ${key} ${val}" + fi + + echo "Do a fast vacuuming run on lmaster node ${lmaster}" + testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + + if [ "$rrun" -eq 2 ] ; then + echo "Recreate record on node ${rnode}" + ctdb_onnode "$rnode" "writekey ${db} ${key} ${val}" + fi + + echo "Do a fast vacuuming run on lmaster node ${lmaster}" + testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + + echo + echo "Confirm the record still exists on all nodes" + check_cattdb_num_records "$db" 1 "$all_pnns" + + echo + echo "Confirm the record contains correct value" + db_confirm_key_has_value "$first" "$db" "$key" "$val" +} + +vacuum_test "$db" "key" "value01" "$non_lmaster" "$non_lmaster" 0 +vacuum_test "$db" "key" "value02" "$non_lmaster" "$another_non_lmaster" 0 +vacuum_test "$db" "key" "value03" "$non_lmaster" "$lmaster" 0 +vacuum_test "$db" "key" "value04" "$lmaster" "$non_lmaster" 0 +vacuum_test "$db" "key" "value05" "$lmaster" "$lmaster" 0 + +vacuum_test "$db" "key" "value06" "$non_lmaster" "$non_lmaster" 1 +vacuum_test "$db" "key" "value07" "$non_lmaster" "$lmaster" 1 +vacuum_test "$db" "key" "value08" "$non_lmaster" "$another_non_lmaster" 1 + +vacuum_test "$db" "key" "value09" "$non_lmaster" "$non_lmaster" 2 +vacuum_test "$db" "key" "value10" "$non_lmaster" "$lmaster" 2 +vacuum_test "$db" "key" "value11" "$non_lmaster" "$another_non_lmaster" 2 diff --git a/ctdb/tests/INTEGRATION/database/vacuum.030.locked.sh b/ctdb/tests/INTEGRATION/database/vacuum.030.locked.sh new file mode 100755 index 0000000..3862526 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/vacuum.030.locked.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash + +# Confirm that a record is not vacuumed if it is locked when the 1st +# fast vacuuming run occurs on the node on which it was deleted, but +# is dropped from the delete queue + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +db="vacuum_test.tdb" +key="key" + +echo "Stall vacuuming on all nodes" +ctdb_onnode -p all "setvar VacuumInterval 99999" + +echo +echo "Getting list of nodes..." +ctdb_get_all_pnns + +# all_pnns is set above by ctdb_get_all_pnns() +# shellcheck disable=SC2154 +first=$(echo "$all_pnns" | sed -n -e '1p') + +echo +echo "Determine lmaster node for key" +testprog_onnode "$first" "ctdb-db-test get-lmaster key" +# $out is set above by testprog_onnode() +# shellcheck disable=SC2154 +lmaster="$out" +echo "lmaster=${lmaster}" + +non_lmaster="" +# Find a non-lmaster node +for i in $all_pnns ; do + if [ "$i" != "$lmaster" ] ; then + non_lmaster="$i" + break + fi +done +if [ -z "$non_lmaster" ] ; then + ctdb_test_fail "Could not find non-lmaster node for key" +fi + +echo "............................................................" +echo "Delete key ${key} on non-lmaster node ${non_lmaster}" +echo "Lock on node ${non_lmaster} during 1st vacuuming run" +echo "............................................................" + +echo + +echo "Create/wipe test database ${db}" +ctdb_onnode "$first" "attach ${db}" +ctdb_onnode "$first" "wipedb ${db}" + +echo "Create a record in ${db}" +ctdb_onnode "$first" "writekey ${db} ${key} value1" + +echo "Migrate record to all nodes" +ctdb_onnode all "readkey ${db} ${key}" + +echo "Confirm that all nodes have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo + +echo "Delete key \"${key}\" from node ${non_lmaster}" +ctdb_onnode "$non_lmaster" "deletekey $db ${key}" + +echo "Lock record on node ${non_lmaster}" +testprog_onnode "$non_lmaster" "ctdb-db-test local-lock ${db} ${key}" +pid="${out#OK }" +ctdb_test_cleanup_pid_set "$non_lmaster" "$pid" + +echo "Do a fast vacuuming run on node ${non_lmaster}" +testprog_onnode "$non_lmaster" "ctdb-db-test vacuum ${db}" + +echo "Kill lock process ${pid} on node ${non_lmaster}" +try_command_on_node "$non_lmaster" "kill ${pid}" +ctdb_test_cleanup_pid_clear + +echo + +# If the record is still in the delete queue then this will process it +echo "Do a fast vacuuming run on node ${non_lmaster}" +testprog_onnode "$non_lmaster" "ctdb-db-test vacuum ${db}" + +echo "Do a fast vacuuming run on lmaster node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo "Do a fast vacuuming run on lmaster node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo + +echo "Confirm the record still exists on all nodes" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo +vacuum_confirm_key_empty_dmaster "$non_lmaster" "$db" "$key" diff --git a/ctdb/tests/INTEGRATION/database/vacuum.031.locked.sh b/ctdb/tests/INTEGRATION/database/vacuum.031.locked.sh new file mode 100755 index 0000000..d16482e --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/vacuum.031.locked.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash + +# Confirm that a record is vacuumed if it is locked on the deleting +# node when the 2nd fast vacuuming run occurs, but vacuuming is +# delayed until the lock is released + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +db="vacuum_test.tdb" +key="key" + +echo "Stall vacuuming on all nodes" +ctdb_onnode -p all "setvar VacuumInterval 99999" + +echo +echo "Getting list of nodes..." +ctdb_get_all_pnns + +# all_pnns is set above by ctdb_get_all_pnns() +# shellcheck disable=SC2154 +first=$(echo "$all_pnns" | sed -n -e '1p') + +echo +echo "Determine lmaster node for key" +testprog_onnode "$first" "ctdb-db-test get-lmaster key" +# $out is set above by testprog_onnode() +# shellcheck disable=SC2154 +lmaster="$out" +echo "lmaster=${lmaster}" + +non_lmaster="" +# Find a non-lmaster node +for i in $all_pnns ; do + if [ "$i" != "$lmaster" ] ; then + non_lmaster="$i" + break + fi +done +if [ -z "$non_lmaster" ] ; then + ctdb_test_fail "Could not find non-lmaster node for key" +fi + +echo "............................................................" +echo "Delete key ${key} on node ${non_lmaster}" +echo "Lock on non-lmaster node ${non_lmaster} during 2nd vacuuming run" +echo "............................................................" + +echo + +echo "Create/wipe test database ${db}" +ctdb_onnode "$first" "attach ${db}" +ctdb_onnode "$first" "wipedb ${db}" + +echo "Create a record in ${db}" +ctdb_onnode "$first" "writekey ${db} ${key} value1" + +echo "Migrate record to all nodes" +ctdb_onnode all "readkey ${db} ${key}" + +echo "Confirm that all nodes have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo + +echo "Delete key \"${key}\" from node ${non_lmaster}" +ctdb_onnode "$non_lmaster" "deletekey $db ${key}" + +echo +echo "Do a fast vacuuming run on node ${non_lmaster}" +testprog_onnode "$non_lmaster" "ctdb-db-test vacuum ${db}" + +echo +echo "Confirm that all nodes still have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo +echo "Lock record on non-lmaster node ${non_lmaster}" +testprog_onnode "$non_lmaster" "ctdb-db-test local-lock ${db} ${key}" +pid="${out#OK }" +ctdb_test_cleanup_pid_set "$non_lmaster" "$pid" + +echo +echo "Do a fast vacuuming run on lmaster node ${lmaster} - THIS WILL FAIL" +status=0 +testprog_onnode "$lmaster" "ctdb-db-test -t 10 vacuum ${db}" || status=$? + +if [ $status -ne 110 ] ; then + ctdb_test_fail "$out" +fi + +echo "Confirm record key=\"${key}\" has dmaster=${non_lmaster}" +vacuum_test_key_dmaster "$lmaster" "$db" "$key" "$non_lmaster" + +echo "Kill lock process ${pid} on node ${non_lmaster}" +try_command_on_node "$non_lmaster" "kill ${pid}" +ctdb_test_cleanup_pid_clear + +echo "Wait until record is migrated to lmaster node ${lmaster}" +vacuum_test_wait_key_dmaster "$lmaster" "$db" "$key" + +echo +echo "Confirm that all nodes still have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo "Do a fast vacuuming run on node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo +echo "Confirm that the record is gone from all nodes" +check_cattdb_num_records "$db" 0 "$all_pnns" diff --git a/ctdb/tests/INTEGRATION/database/vacuum.032.locked.sh b/ctdb/tests/INTEGRATION/database/vacuum.032.locked.sh new file mode 100755 index 0000000..481d1d4 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/vacuum.032.locked.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash + +# Confirm that a record is not vacuumed if it is locked on the lmaster +# when the 3rd fast vacuuming run occurs, but is dropped from the +# lmaster delete queue + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +db="vacuum_test.tdb" +key="key" + +echo "Stall vacuuming on all nodes" +ctdb_onnode -p all "setvar VacuumInterval 99999" + +echo +echo "Getting list of nodes..." +ctdb_get_all_pnns + +# all_pnns is set above by ctdb_get_all_pnns() +# shellcheck disable=SC2154 +first=$(echo "$all_pnns" | sed -n -e '1p') + +echo +echo "Determine lmaster node for key" +testprog_onnode "$first" "ctdb-db-test get-lmaster key" +# $out is set above by testprog_onnode() +# shellcheck disable=SC2154 +lmaster="$out" +echo "lmaster=${lmaster}" + +non_lmaster="" +# Find a non-lmaster node +for i in $all_pnns ; do + if [ "$i" != "$lmaster" ] ; then + non_lmaster="$i" + break + fi +done +if [ -z "$non_lmaster" ] ; then + ctdb_test_fail "Could not find non-lmaster node for key" +fi + +echo "............................................................" +echo "Delete key ${key} on node ${non_lmaster}" +echo "Lock on lmaster node ${lmaster} during 3rd vacuuming run" +echo "............................................................" + +echo + +echo "Create/wipe test database ${db}" +ctdb_onnode "$first" "attach ${db}" +ctdb_onnode "$first" "wipedb ${db}" + +echo "Create a record in ${db}" +ctdb_onnode "$first" "writekey ${db} ${key} value1" + +echo "Migrate record to all nodes" +ctdb_onnode all "readkey ${db} ${key}" + +echo "Confirm that all nodes have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo + +echo "Delete key \"${key}\" from node ${non_lmaster}" +ctdb_onnode "$non_lmaster" "deletekey $db ${key}" + +echo "Do a fast vacuuming run on node ${non_lmaster}" +testprog_onnode "$non_lmaster" "ctdb-db-test vacuum ${db}" + +echo "Do a fast vacuuming run on lmaster node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo "Lock record on lmaster node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test local-lock ${db} ${key}" +pid="${out#OK }" +ctdb_test_cleanup_pid_set "$lmaster" "$pid" + +echo "Do a fast vacuuming run on node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo "Kill lock process ${pid} on node ${lmaster}" +try_command_on_node "$lmaster" "kill ${pid}" +ctdb_test_cleanup_pid_clear + +echo + +# If the record is still in the delete queue then this will process it +echo "Do a fast vacuuming run on lmaster node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo + +echo "Confirm the record still exists on all nodes" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo +vacuum_confirm_key_empty_dmaster "$lmaster" "$db" "$key" diff --git a/ctdb/tests/INTEGRATION/database/vacuum.033.locked.sh b/ctdb/tests/INTEGRATION/database/vacuum.033.locked.sh new file mode 100755 index 0000000..63d7d1f --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/vacuum.033.locked.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +# Confirm that a record is not vacuumed if it is locked on the +# deleting node when the 3rd fast vacuuming run occurs, but is dropped +# from the lmaster delete list + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +db="vacuum_test.tdb" +key="key" + +echo "Stall vacuuming on all nodes" +ctdb_onnode -p all "setvar VacuumInterval 99999" + +echo +echo "Getting list of nodes..." +ctdb_get_all_pnns + +# all_pnns is set above by ctdb_get_all_pnns() +# shellcheck disable=SC2154 +first=$(echo "$all_pnns" | sed -n -e '1p') + +echo +echo "Determine lmaster node for key" +testprog_onnode "$first" "ctdb-db-test get-lmaster key" +# $out is set above by testprog_onnode() +# shellcheck disable=SC2154 +lmaster="$out" +echo "lmaster=${lmaster}" + +non_lmaster="" +# Find a non-lmaster node +for i in $all_pnns ; do + if [ "$i" != "$lmaster" ] ; then + non_lmaster="$i" + break + fi +done +if [ -z "$non_lmaster" ] ; then + ctdb_test_fail "Could not find non-lmaster node for key" +fi + +echo "............................................................" +echo "Delete key ${key} on node ${non_lmaster}" +echo "Lock on non-lmaster node ${non_lmaster} during 3rd vacuuming run" +echo "............................................................" + +echo + +echo "Create/wipe test database ${db}" +ctdb_onnode "$first" "attach ${db}" +ctdb_onnode "$first" "wipedb ${db}" + +echo "Create a record in ${db}" +ctdb_onnode "$first" "writekey ${db} ${key} value1" + +echo "Migrate record to all nodes" +ctdb_onnode all "readkey ${db} ${key}" + +echo "Confirm that all nodes have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo + +echo "Delete key \"${key}\" from node ${non_lmaster}" +ctdb_onnode "$non_lmaster" "deletekey $db ${key}" + +echo +echo "Do a fast vacuuming run on node ${non_lmaster}" +testprog_onnode "$non_lmaster" "ctdb-db-test vacuum ${db}" + +echo +echo "Confirm that all nodes still have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo +echo "Do a fast vacuuming run on lmaster node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo +echo "Confirm that all nodes still have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo +echo "Lock record on non-lmaster node ${non_lmaster}" +testprog_onnode "$non_lmaster" "ctdb-db-test local-lock ${db} ${key}" +pid="${out#OK }" +ctdb_test_cleanup_pid_set "$non_lmaster" "$pid" + +echo "Do a fast vacuuming run on node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo "Kill lock process ${pid} on node ${non_lmaster}" +try_command_on_node "$non_lmaster" "kill ${pid}" +ctdb_test_cleanup_pid_clear + +echo +echo "Confirm that nodes ${lmaster} and ${non_lmaster} still have the record" +check_cattdb_num_records "$db" 1 "${lmaster} ${non_lmaster}" + +vacuum_confirm_key_empty_dmaster "$lmaster" "$db" "$key" + +echo + +# Record has been dropped from the delete list so this will not pick it up +echo "Do a fast vacuuming run on lmaster node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo +echo "Confirm that nodes ${lmaster} and ${non_lmaster} still have the record" +check_cattdb_num_records "$db" 1 "${lmaster} ${non_lmaster}" + +vacuum_confirm_key_empty_dmaster "$lmaster" "$db" "$key" diff --git a/ctdb/tests/INTEGRATION/database/vacuum.034.locked.sh b/ctdb/tests/INTEGRATION/database/vacuum.034.locked.sh new file mode 100755 index 0000000..7f37ada --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/vacuum.034.locked.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash + +# Confirm that a record is not vacuumed if it is locked on another +# (non-lmaster, non-deleting) node when the 3rd fast vacuuming run +# occurs, but is dropped from the lmaster delete tree + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +db="vacuum_test.tdb" +key="key" + +echo "Stall vacuuming on all nodes" +ctdb_onnode -p all "setvar VacuumInterval 99999" + +echo +echo "Getting list of nodes..." +ctdb_get_all_pnns + +# all_pnns is set above by ctdb_get_all_pnns() +# shellcheck disable=SC2154 +first=$(echo "$all_pnns" | sed -n -e '1p') + +echo +echo "Determine lmaster node for key" +testprog_onnode "$first" "ctdb-db-test get-lmaster key" +# $out is set above by testprog_onnode() +# shellcheck disable=SC2154 +lmaster="$out" +echo "lmaster=${lmaster}" + +non_lmaster="" +# Find a non-lmaster node +for i in $all_pnns ; do + if [ "$i" != "$lmaster" ] ; then + non_lmaster="$i" + break + fi +done +if [ -z "$non_lmaster" ] ; then + ctdb_test_fail "Could not find non-lmaster node for key" +fi + +another_node="" +# Find another node +for i in $all_pnns ; do + if [ "$i" != "$lmaster" ] && [ "$i" != "$non_lmaster" ] ; then + another_node="$i" + break + fi +done +if [ -z "$another_node" ] ; then + ctdb_test_fail "Could not find another non-lmaster node for key" +fi + +echo "............................................................" +echo "Delete key ${key} on node ${non_lmaster}" +echo "Lock on non-lmaster node ${non_lmaster} during 3rd vacuuming run" +echo "............................................................" + +echo + +echo "Create/wipe test database ${db}" +ctdb_onnode "$first" "attach ${db}" +ctdb_onnode "$first" "wipedb ${db}" + +echo "Create a record in ${db}" +ctdb_onnode "$first" "writekey ${db} ${key} value1" + +echo "Migrate record to all nodes" +ctdb_onnode all "readkey ${db} ${key}" + +echo "Confirm that all nodes have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo + +echo "Delete key \"${key}\" from node ${non_lmaster}" +ctdb_onnode "$non_lmaster" "deletekey $db ${key}" + +echo +echo "Do a fast vacuuming run on node ${non_lmaster}" +testprog_onnode "$non_lmaster" "ctdb-db-test vacuum ${db}" + +echo +echo "Confirm that all nodes still have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo +echo "Do a fast vacuuming run on lmaster node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo +echo "Confirm that all nodes still have the record" +check_cattdb_num_records "$db" 1 "$all_pnns" + +echo +echo "Lock record on non-lmaster node ${another_node}" +testprog_onnode "$another_node" "ctdb-db-test local-lock ${db} ${key}" +pid="${out#OK }" +ctdb_test_cleanup_pid_set "$another_node" "$pid" + +echo "Do a fast vacuuming run on node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo "Kill lock process ${pid} on node ${another_node}" +try_command_on_node "$another_node" "kill ${pid}" +ctdb_test_cleanup_pid_clear + +echo +echo "Confirm that nodes ${lmaster} and ${another_node} still have the record" +check_cattdb_num_records "$db" 1 "${lmaster} ${another_node}" + +vacuum_confirm_key_empty_dmaster "$lmaster" "$db" "$key" + +echo + +# Record has been dropped from the delete list so this will not pick it up +echo "Do a fast vacuuming run on lmaster node ${lmaster}" +testprog_onnode "$lmaster" "ctdb-db-test vacuum ${db}" + +echo +echo "Confirm that nodes ${lmaster} and ${another_node} still have the record" +check_cattdb_num_records "$db" 1 "${lmaster} ${another_node}" + +vacuum_confirm_key_empty_dmaster "$lmaster" "$db" "$key" diff --git a/ctdb/tests/INTEGRATION/failover/pubips.001.list.sh b/ctdb/tests/INTEGRATION/failover/pubips.001.list.sh new file mode 100755 index 0000000..2fc75b7 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.001.list.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb ip' shows the correct output + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +echo "Getting list of public IPs..." +try_command_on_node -v 1 "$CTDB ip all | tail -n +2" +ips=$(sed \ + -e 's@ node\[@ @' \ + -e 's@\].*$@@' \ + "$outfile") +machineout=$(sed -r \ + -e 's@^| |$@\|@g' \ + -e 's@[[:alpha:]]+\[@@g' \ + -e 's@\]@@g' \ + "$outfile") + +if ctdb_test_on_cluster ; then + while read ip pnn ; do + try_command_on_node $pnn "ip addr show to ${ip}" + if [ -n "$out" ] ; then + echo "GOOD: node $pnn appears to have $ip assigned" + else + die "BAD: node $pnn does not appear to have $ip assigned" + fi + done <<<"$ips" # bashism to avoid problem setting variable in pipeline. +fi + +echo "Looks good!" + +cmd="$CTDB -X ip all | tail -n +2" +echo "Checking that \"$cmd\" produces expected output..." + +try_command_on_node 1 "$cmd" +if [ "$out" = "$machineout" ] ; then + echo "Yep, looks good!" +else + echo "Nope, it looks like this:" + echo "$out" + echo "Should be like this:" + echo "$machineout" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/failover/pubips.010.addip.sh b/ctdb/tests/INTEGRATION/failover/pubips.010.addip.sh new file mode 100755 index 0000000..aba85dd --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.010.addip.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# Verify that an IP address can be added to a node using 'ctdb addip' + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips +get_test_ip_mask_and_iface + +echo "Deleting IP $test_ip from all nodes" +delete_ip_from_all_nodes $test_ip +try_command_on_node -v $test_node $CTDB ipreallocate +wait_until_ips_are_on_node '!' $test_node $test_ip + +# Debugging... +try_command_on_node -v all $CTDB ip + +echo "Adding IP ${test_ip}/${mask} on ${iface}, node ${test_node}" +try_command_on_node $test_node $CTDB addip ${test_ip}/${mask} $iface +try_command_on_node $test_node $CTDB ipreallocate +wait_until_ips_are_on_node $test_node $test_ip diff --git a/ctdb/tests/INTEGRATION/failover/pubips.011.delip.sh b/ctdb/tests/INTEGRATION/failover/pubips.011.delip.sh new file mode 100755 index 0000000..5235a9d --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.011.delip.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Verify that a node's public IP address can be deleted using 'ctdb deleteip' + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Deleting IP ${test_ip} from node ${test_node}" +try_command_on_node $test_node $CTDB delip $test_ip +try_command_on_node $test_node $CTDB ipreallocate +wait_until_ips_are_on_node '!' $test_node $test_ip diff --git a/ctdb/tests/INTEGRATION/failover/pubips.012.reloadips.sh b/ctdb/tests/INTEGRATION/failover/pubips.012.reloadips.sh new file mode 100755 index 0000000..a3bb3af --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.012.reloadips.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +# Verify that IPs can be reconfigured using 'ctdb reloadips' + +# Various sub-tests that remove addresses from the public_addresses file +# on a node or delete the entire contents of the public_addresses file. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +try_command_on_node $test_node $CTDB_TEST_WRAPPER ctdb_base_show +addresses="${out}/public_addresses" +echo "Public addresses file on node $test_node is \"$addresses\"" +backup="${addresses}.$$" + +restore_public_addresses () +{ + try_command_on_node $test_node "mv $backup $addresses >/dev/null 2>&1 || true" +} +ctdb_test_exit_hook_add restore_public_addresses + +# ctdb reloadips will fail if it can't disable takover runs. The most +# likely reason for this is that there is already a takeover run in +# progress. We can't predict when this will happen, so retry if this +# occurs. +do_ctdb_reloadips () +{ + local retry_max=10 + local retry_count=0 + while : ; do + if ctdb_onnode "$test_node" "reloadips all" ; then + return 0 + fi + + if [ "$out" != "Failed to disable takeover runs" ] ; then + return 1 + fi + + if [ $retry_count -ge $retry_max ] ; then + return 1 + fi + + retry_count=$((retry_count + 1)) + echo "Retrying..." + sleep_for 1 + done +} + + +echo "Removing IP $test_ip from node $test_node" + +try_command_on_node $test_node "mv $addresses $backup && grep -v '^${test_ip}/' $backup >$addresses" + +do_ctdb_reloadips + +try_command_on_node $test_node $CTDB ip + +if grep "^${test_ip} " <<<"$out" ; then + cat <<EOF +BAD: node $test_node can still host IP $test_ip: +$out +EOF + exit 1 +fi + +cat <<EOF +GOOD: node $test_node is no longer hosting IP $test_ip: +$out +EOF + +ctdb_onnode "$test_node" sync + + +echo "Restoring addresses" +restore_public_addresses + +do_ctdb_reloadips + +echo "Getting list of public IPs on node $test_node" +try_command_on_node $test_node "$CTDB ip | tail -n +2" + +if [ -z "$out" ] ; then + echo "BAD: node $test_node has no ips" + exit 1 +fi + +cat <<EOF +GOOD: node $test_node has these addresses: +$out +EOF + +ctdb_onnode "$test_node" sync + + +echo "Emptying public addresses file on $test_node" + +try_command_on_node $test_node "mv $addresses $backup && touch $addresses" + +do_ctdb_reloadips + +echo "Getting list of public IPs on node $test_node" +try_command_on_node $test_node "$CTDB ip | tail -n +2" + +if [ -n "$out" ] ; then + cat <<EOF +BAD: node $test_node still has ips: +$out +EOF + exit 1 +fi + +echo "GOOD: no IPs left on node $test_node" diff --git a/ctdb/tests/INTEGRATION/failover/pubips.013.failover_noop.sh b/ctdb/tests/INTEGRATION/failover/pubips.013.failover_noop.sh new file mode 100755 index 0000000..77f9a63 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.013.failover_noop.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# Check that CTDB operates correctly if: + +# * failover is disabled; or +# * there are 0 public IPs configured + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +echo "Starting CTDB with failover disabled..." +ctdb_nodes_start_custom -F + +select_test_node + +echo "Getting IP allocation..." + +# $test_node set above by select_test_node() +# shellcheck disable=SC2154 +try_command_on_node -v "$test_node" "$CTDB ip all | tail -n +2" + +while read ip pnn ; do + if [ "$pnn" != "-1" ] ; then + die "BAD: IP address ${ip} is assigned to node ${pnn}" + fi +done <"$outfile" + +echo "GOOD: All IP addresses are unassigned" + +echo "----------------------------------------" + +echo "Starting CTDB with an empty public addresses configuration..." +ctdb_nodes_start_custom -P /dev/null + +echo "Trying explicit ipreallocate..." +ctdb_onnode "$test_node" ipreallocate + +echo "Good, that seems to work!" +echo diff --git a/ctdb/tests/INTEGRATION/failover/pubips.014.iface_gc.sh b/ctdb/tests/INTEGRATION/failover/pubips.014.iface_gc.sh new file mode 100755 index 0000000..845b4b5 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.014.iface_gc.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +# Verify that an interface is deleted when all IPs on it are deleted + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +# Find interfaces on test node +try_command_on_node $test_node "$CTDB ifaces -X" +ifaces=$(awk -F'|' 'NR > 1 { print $2 }' "$outfile") +echo "Node ${test_node} has interfaces: ${ifaces}" + +# Delete all IPs on each interface... deleting IPs from one interface +# can cause other interfaces to disappear, so we need to be careful... +for i in $ifaces ; do + try_command_on_node $test_node "$CTDB ifaces -X" + info=$(awk -F'|' -v iface="$i" '$2 == iface { print $0 }' "$outfile") + + if [ -z "$info" ] ; then + echo "Interface ${i} missing... assuming already deleted!" + continue + fi + + echo "Deleting IPs on interface ${i}, with this information:" + echo " $info" + + try_command_on_node $test_node "$CTDB ip -v -X | tail -n +2" + awk -F'|' -v i="$i" \ + '$6 == i { print $2 }' "$outfile" | + while read ip ; do + echo " $ip" + try_command_on_node $test_node "$CTDB delip $ip" + done + try_command_on_node $test_node "$CTDB ipreallocate" + + try_command_on_node $test_node "$CTDB ifaces -X" + info=$(awk -F'|' -v iface="$i" '$2 == iface { print $0 }' "$outfile") + + if [ -z "$info" ] ; then + echo "GOOD: Interface ${i} has been garbage collected" + else + echo "BAD: Interface ${i} still exists" + echo "$out" + exit 1 + fi +done diff --git a/ctdb/tests/INTEGRATION/failover/pubips.020.moveip.sh b/ctdb/tests/INTEGRATION/failover/pubips.020.moveip.sh new file mode 100755 index 0000000..8daf3f5 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.020.moveip.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb moveip' allows movement of public IPs between nodes + +# This test does not do any network level checks to make sure IP +# addresses are actually on interfaces. It just consults "ctdb ip". + +# To work, this test ensures that IPAllocAlgorithm is not set to 0 +# (Deterministic IPs) and sets NoIPFailback. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +sanity_check_ips () +{ + echo "Sanity checking IPs..." + + local x ipp prev + prev="" + while read x ipp ; do + [ "$ipp" = "-1" ] && break + if [ -n "$prev" -a "$ipp" != "$prev" ] ; then + echo "OK" + return 0 + fi + prev="$ipp" + done <"$outfile" + + echo "BAD: a node was -1 or IPs are only assigned to one node:" + cat "$outfile" + echo "Are you running an old version of CTDB?" + return 1 +} + +sanity_check_ips + +# Find a target node - it must be willing to host $test_ip + +# $test_node set above by select_test_node_and_ips() +# shellcheck disable=SC2154 +try_command_on_node "$test_node" "$CTDB listnodes | wc -l" +num_nodes="$out" +to_node="" +for i in $(seq 0 $(($num_nodes - 1)) ) ; do + [ $i -ne $test_node ] || continue + all_ips_on_node $i + while read ip x ; do + if [ "$ip" = "$test_ip" ] ; then + to_node="$i" + break 2 + fi + done <"$outfile" +done + +if [ -z "$to_node" ] ; then + echo "Unable to find target node" + exit 1 +fi + +echo "Target node is ${to_node}" + +echo "Setting IPAllocAlgorithm=2 to avoid Deterministic IPs..." +try_command_on_node -q all $CTDB setvar IPAllocAlgorithm 2 + +echo "Turning on NoIPFailback..." +try_command_on_node -q all $CTDB setvar NoIPFailback 1 + +echo "Attempting to move ${test_ip} from node ${test_node} to node ${to_node}" +try_command_on_node $test_node $CTDB moveip $test_ip $to_node +wait_until_ips_are_on_node '!' $test_node $test_ip +wait_until_ips_are_on_node $to_node $test_ip diff --git a/ctdb/tests/INTEGRATION/failover/pubips.030.disable_enable.sh b/ctdb/tests/INTEGRATION/failover/pubips.030.disable_enable.sh new file mode 100755 index 0000000..3f40097 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.030.disable_enable.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Verify the operation of "ctdb disable" and "ctdb enable" + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +######################################## + +select_test_node_and_ips + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled 30 all +wait_until_node_has_no_ips "$test_node" + +echo "Re-enabling node $test_node" +try_command_on_node 1 $CTDB enable -n $test_node +wait_until_node_has_status $test_node enabled 30 all +wait_until_node_has_some_ips "$test_node" diff --git a/ctdb/tests/INTEGRATION/failover/pubips.032.stop_continue.sh b/ctdb/tests/INTEGRATION/failover/pubips.032.stop_continue.sh new file mode 100755 index 0000000..f5936b0 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.032.stop_continue.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# Verify the operation of "ctdb stop" and "ctdb continue" + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Stopping node ${test_node}..." +try_command_on_node 1 $CTDB stop -n $test_node +wait_until_node_has_status $test_node stopped +wait_until_node_has_no_ips "$test_node" + +echo "Continuing node $test_node" +try_command_on_node 1 $CTDB continue -n $test_node +wait_until_node_has_status $test_node notstopped +wait_until_node_has_some_ips "$test_node" diff --git a/ctdb/tests/INTEGRATION/failover/pubips.040.NoIPTakeover.sh b/ctdb/tests/INTEGRATION/failover/pubips.040.NoIPTakeover.sh new file mode 100755 index 0000000..e99a265 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.040.NoIPTakeover.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb setvar NoIPTakeover 1' stops IP addresses being taken over + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +ctdb_get_all_pnns +# out is set above +# shellcheck disable=SC2154 +num_nodes=$(echo "$out" | wc -l | tr -d '[:space:]') +echo "There are $num_nodes nodes..." + +if [ "$num_nodes" -lt 2 ] ; then + echo "Less than 2 nodes!" + exit 1 +fi + +select_test_node_and_ips + + +# sets: num +count_ips_on_node () +{ + local node="$1" + + ctdb_onnode "$node" ip + # outfile is set by ctdb_onnode() above + # shellcheck disable=SC2154,SC2126 + # * || true is needed to avoid command failure when there are no matches + # * Using "wc -l | tr -d '[:space:]'" is our standard + # pattern... and "grep -c" requires handling of special case + # for no match + num=$(grep -v 'Public' "$outfile" | \ + grep " ${node}\$" | \ + wc -l | \ + tr -d '[:space:]') + echo "Number of addresses on node ${node}: ${num}" +} + + +# test_node is set by select_test_node_and_ips() above +# shellcheck disable=SC2154 +count_ips_on_node "$test_node" + +echo "Turning on NoIPTakeover on all nodes" +ctdb_onnode all "setvar NoIPTakeover 1" +ctdb_onnode "$test_node" ipreallocate + +echo "Disable node ${test_node}" +ctdb_onnode "$test_node" disable + +count_ips_on_node "$test_node" +if [ "$num" != "0" ] ; then + test_fail "BAD: node 1 still hosts IP addresses" +fi + + +echo "Enable node 1 again" +ctdb_onnode "$test_node" enable + +count_ips_on_node "$test_node" +if [ "$num" != "0" ] ; then + test_fail "BAD: node 1 took over IP addresses" +fi + + +echo "OK: IP addresses were not taken over" diff --git a/ctdb/tests/INTEGRATION/failover/pubips.050.missing_ip.sh b/ctdb/tests/INTEGRATION/failover/pubips.050.missing_ip.sh new file mode 100755 index 0000000..543f9a9 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.050.missing_ip.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +# Verify that the recovery daemon handles unhosted IPs properly + +# This test does not do any network level checks to make sure the IP +# address is actually on an interface. It just consults "ctdb ip". + +# This is a variation of the "addip" test. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Running test against node $test_node and IP $test_ip" + +get_test_ip_mask_and_iface + +echo "Deleting IP $test_ip from all nodes" +delete_ip_from_all_nodes $test_ip +try_command_on_node -v $test_node $CTDB ipreallocate +wait_until_ips_are_on_node ! $test_node $test_ip + +try_command_on_node -v all $CTDB ip + +my_exit_hook () +{ + if ctdb_test_on_cluster ; then + onnode -q all $CTDB event script enable legacy "10.interface" + fi +} + +ctdb_test_exit_hook_add my_exit_hook + +# This forces us to wait until the ipreallocated associated with the +# delips is complete. +try_command_on_node $test_node $CTDB sync + +# Wait for a monitor event. Then the next steps are unlikely to occur +# in the middle of a monitor event and will have the expected effect. +wait_for_monitor_event $test_node + +if ctdb_test_on_cluster ; then + # Stop monitor events from bringing up the link status of an interface + try_command_on_node $test_node $CTDB event script disable legacy 10.interface +fi + +echo "Marking interface $iface down on node $test_node" +try_command_on_node $test_node $CTDB setifacelink $iface down + +echo "Adding IP $test_ip to node $test_node" +try_command_on_node $test_node $CTDB addip $test_ip/$mask $iface +try_command_on_node $test_node $CTDB ipreallocate + +echo "Wait long enough for IP verification to have taken place" +sleep_for 15 + +echo "Ensuring that IP ${test_ip} is not hosted on node ${test_node} when interface is down" +if ips_are_on_node '!' $test_node $test_ip; then + echo "GOOD: the IP has not been hosted while the interface is down" +else + echo "BAD: the IP is hosted but the interface is down" + exit 1 +fi + +echo "Marking interface $iface up on node $test_node" +try_command_on_node $test_node $CTDB setifacelink $iface up +wait_until_ips_are_on_node $test_node $test_ip diff --git a/ctdb/tests/INTEGRATION/simple/README b/ctdb/tests/INTEGRATION/simple/README new file mode 100644 index 0000000..3ac738d --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/README @@ -0,0 +1,2 @@ +Simple integration tests. These can be run against a pool of CTDB +daemons running on the local machine - aka "local daemons". diff --git a/ctdb/tests/INTEGRATION/simple/basics.000.onnode.sh b/ctdb/tests/INTEGRATION/simple/basics.000.onnode.sh new file mode 100755 index 0000000..4ca6e46 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.000.onnode.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# Use 'onnode' to confirm connectivity between all cluster nodes + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +echo "Checking connectivity between nodes..." +onnode all onnode -p all hostname diff --git a/ctdb/tests/INTEGRATION/simple/basics.001.listnodes.sh b/ctdb/tests/INTEGRATION/simple/basics.001.listnodes.sh new file mode 100755 index 0000000..aafe27e --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.001.listnodes.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb listnodes' shows the list of nodes + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node -v 0 "$CTDB listnodes" + +num_nodes=$(wc -l <"$outfile") + +# Each line should look like an IP address. +ipv4_pat='[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+' +ipv6_pat='[[:xdigit:]]+:[[:xdigit:]:]+[[:xdigit:]]+' +sanity_check_output \ + 2 \ + "^${ipv4_pat}|${ipv6_pat}\$" + +out_0="$out" + +echo "Checking other nodes..." + +n=1 +while [ $n -lt $num_nodes ] ; do + echo -n "Node ${n}: " + try_command_on_node $n "$CTDB listnodes" + if [ "$out_0" = "$out" ] ; then + echo "OK" + else + echo "DIFFERs from node 0:" + echo "$out" + exit 1 + fi + n=$(($n + 1)) +done diff --git a/ctdb/tests/INTEGRATION/simple/basics.002.tunables.sh b/ctdb/tests/INTEGRATION/simple/basics.002.tunables.sh new file mode 100755 index 0000000..6f362c6 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.002.tunables.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +# Verify the operation of "ctdb listvars", "ctdb getvar", "ctdb setvar" + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node -v 0 "$CTDB listvars" + +sanity_check_output \ + 5 \ + '^[[:alpha:]][[:alnum:]]+[[:space:]]*=[[:space:]]*[[:digit:]]+$' + +echo "Verifying all variable values using \"ctdb getvar\"..." + +while read var x val ; do + try_command_on_node 0 "$CTDB getvar $var" + + val2="${out#*= }" + + if [ "$val" != "$val2" ] ; then + echo "MISMATCH on $var: $val != $val2" + exit 1 + fi +done <"$outfile" + +echo "GOOD: all tunables match" + +var="RecoverTimeout" + +try_command_on_node -v 0 $CTDB getvar $var + +val="${out#*= }" + +echo "Going to try incrementing it..." + +incr=$(($val + 1)) + +try_command_on_node 0 $CTDB setvar $var $incr + +echo "That seemed to work, let's check the value..." + +try_command_on_node -v 0 $CTDB getvar $var + +newval="${out#*= }" + +if [ "$incr" != "$newval" ] ; then + echo "Nope, that didn't work..." + exit 1 +fi + +echo "Look's good! Now verifying with \"ctdb listvars\"" +try_command_on_node -v 0 "$CTDB listvars | grep '^$var'" + +check="${out#*= }" + +if [ "$incr" != "$check" ] ; then + echo "Nope, that didn't work..." + exit 1 +fi + +echo "Look's good! Putting the old value back..." +cmd="$CTDB setvar $var $val" +try_command_on_node 0 $cmd diff --git a/ctdb/tests/INTEGRATION/simple/basics.003.ping.sh b/ctdb/tests/INTEGRATION/simple/basics.003.ping.sh new file mode 100755 index 0000000..8071762 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.003.ping.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Verify the operation of the 'ctdb ping' command +# +# 1. Run the 'ctdb ping' command on one of the nodes and verify that it +# shows valid and expected output. +# 2. Shutdown one of the cluster nodes, using the 'ctdb shutdown' +# command. +# 3. Run the 'ctdb ping -n <node>' command from another node to this +# node. +# 4. Verify that the command is not successful since th ctdb daemon is +# not running on the node. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node -v 0 "$CTDB ping -n 1" + +sanity_check_output \ + 1 \ + '^response from 1 time=-?[.0-9]+ sec[[:space:]]+\([[:digit:]]+ clients\)$' + +ctdb_onnode -v 1 "shutdown" + +wait_until_node_has_status 1 disconnected 30 0 + +try_command_on_node -v 0 "! $CTDB ping -n 1" + +sanity_check_output \ + 1 \ + "(: ctdb_control error: ('ctdb_control to disconnected node'|'node is disconnected')|Unable to get ping response from node 1|Node 1 is DISCONNECTED|ctdb_control for getpnn failed|: Can not access node. Node is not operational\.|Node 1 has status DISCONNECTED\|UNHEALTHY\|INACTIVE$)" diff --git a/ctdb/tests/INTEGRATION/simple/basics.004.getpid.sh b/ctdb/tests/INTEGRATION/simple/basics.004.getpid.sh new file mode 100755 index 0000000..27025df --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.004.getpid.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb getpid' works as expected + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" +echo "There are $num_nodes nodes..." + +# Call getpid a few different ways and make sure the answer is always the same. + +try_command_on_node -v 0 "onnode -q all $CTDB getpid" +pids_onnode="$out" + +cmd="" +n=0 +while [ $n -lt $num_nodes ] ; do + cmd="${cmd}${cmd:+; }$CTDB getpid -n $n" + n=$(($n + 1)) +done +try_command_on_node -v 0 "( $cmd )" +pids_getpid_n="$out" + +if [ "$pids_onnode" = "$pids_getpid_n" ] ; then + echo "They're the same... cool!" +else + die "Error: they differ." +fi + +echo "Checking each PID for validity" + +n=0 +while [ $n -lt $num_nodes ] ; do + read pid + try_command_on_node $n "ls -l /proc/${pid}/exe | sed -e 's@.*/@@'" + echo -n "Node ${n}, PID ${pid} looks to be running \"$out\" - " + case "$out" in + ctdbd) : ;; + memcheck*) + if [ -z "$VALGRIND" ] ; then + die "BAD" + fi + ;; + *) die "BAD" + esac + + echo "GOOD!" + + n=$(($n + 1)) +done <<<"$pids_onnode" diff --git a/ctdb/tests/INTEGRATION/simple/basics.005.process_exists.sh b/ctdb/tests/INTEGRATION/simple/basics.005.process_exists.sh new file mode 100755 index 0000000..c6212fd --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.005.process_exists.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb process-exists' shows correct information + +# The implementation is creative about how it gets PIDs for existing and +# non-existing processes. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +test_node=1 +srvid=0xAE00000012345678 + +# Execute a ctdb client on $test_node that will last for 60 seconds. +# It should still be there when we check. +try_command_on_node -v $test_node \ + "$CTDB_TEST_WRAPPER exec dummy_client -n 10 -S ${srvid} >/dev/null 2>&1 & echo \$!" +client_pid="$out" + +cleanup () +{ + if [ -n "$client_pid" ] ; then + onnode $test_node kill -9 "$client_pid" + fi +} + +ctdb_test_exit_hook_add cleanup + +echo "Waiting until PID $client_pid is registered on node $test_node" +status=0 +wait_until 30 try_command_on_node $test_node \ + "$CTDB process-exists ${client_pid}" || status=$? +echo "$out" + +if [ $status -eq 0 ] ; then + echo "OK" +else + die "BAD" +fi + +echo "Checking for PID $client_pid with SRVID $srvid on node $test_node" +status=0 +try_command_on_node $test_node \ + "$CTDB process-exists ${client_pid} ${srvid}" || status=$? +echo "$out" + +if [ $status -eq 0 ] ; then + echo "OK" +else + die "BAD" +fi + +echo "Checking for PID $client_pid with SRVID $client_pid on node $test_node" +try_command_on_node -v $test_node \ + "! $CTDB process-exists ${client_pid} ${client_pid}" + +# Now just echo the PID of the ctdb daemon on test node. +# This is not a ctdb client and process-exists should return error. +try_command_on_node $test_node "ctdb getpid" +pid="$out" + +echo "Checking for PID $pid on node $test_node" +try_command_on_node -v $test_node "! $CTDB process-exists ${pid}" diff --git a/ctdb/tests/INTEGRATION/simple/basics.010.statistics.sh b/ctdb/tests/INTEGRATION/simple/basics.010.statistics.sh new file mode 100755 index 0000000..d97e035 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.010.statistics.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb statistics' works as expected + +# This is pretty superficial and could do more validation. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +pattern='^(CTDB version 1|Current time of statistics[[:space:]]*:.*|Statistics collected since[[:space:]]*:.*|Gathered statistics for [[:digit:]]+ nodes|[[:space:]]+[[:alpha:]_]+[[:space:]]+[[:digit:]]+|[[:space:]]+(node|client|timeouts|locks)|[[:space:]]+([[:alpha:]_]+_latency|max_reclock_[[:alpha:]]+)[[:space:]]+[[:digit:]-]+\.[[:digit:]]+[[:space:]]sec|[[:space:]]*(locks_latency|reclock_ctdbd|reclock_recd|call_latency|lockwait_latency|childwrite_latency)[[:space:]]+MIN/AVG/MAX[[:space:]]+[-.[:digit:]]+/[-.[:digit:]]+/[-.[:digit:]]+ sec out of [[:digit:]]+|[[:space:]]+(hop_count_buckets|lock_buckets):[[:space:][:digit:]]+)$' + +try_command_on_node -v 1 "$CTDB statistics" + +sanity_check_output 40 "$pattern" diff --git a/ctdb/tests/INTEGRATION/simple/basics.011.statistics_reset.sh b/ctdb/tests/INTEGRATION/simple/basics.011.statistics_reset.sh new file mode 100755 index 0000000..51f34d9 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.011.statistics_reset.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb statisticsreset' works as expected + +# This is pretty superficial. It just checks that a few particular +# items reduce. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +get_stat () +{ + local label="$1" + + cat "$outfile" | + sed -rn -e "s@^[[:space:]]+${label}[[:space:]]+([[:digit:]])@\1@p" | + head -1 +} + +check_reduced () +{ + local label="$1" + local before="$2" + local after="$3" + + if [ $after -lt $before ] ; then + echo "GOOD: ${label} reduced from ${before} to ${after}" + else + die "BAD: ${label} did not reduce from ${before} to ${after}" + fi +} + +n=0 +while [ $n -lt $num_nodes ] ; do + echo "Getting initial statistics for node ${n}..." + + try_command_on_node -v $n $CTDB statistics + + before_req_control=$(get_stat "req_control") + before_reply_control=$(get_stat "reply_control") + before_node_packets_recv=$(get_stat "node_packets_recv") + + try_command_on_node $n $CTDB statisticsreset + + try_command_on_node -v $n $CTDB statistics + + after_req_control=$(get_stat "req_control") + after_reply_control=$(get_stat "reply_control") + after_node_packets_recv=$(get_stat "node_packets_recv") + + check_reduced "req_control" "$before_req_control" "$after_req_control" + check_reduced "reply_control" "$before_reply_control" "$after_reply_control" + check_reduced "node_packets_recv" "$before_node_packets_recv" "$after_node_packets_recv" + + n=$(($n + 1)) +done diff --git a/ctdb/tests/INTEGRATION/simple/cluster.001.stop_leader_yield.sh b/ctdb/tests/INTEGRATION/simple/cluster.001.stop_leader_yield.sh new file mode 100755 index 0000000..180b4ae --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.001.stop_leader_yield.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb stop' causes a node to yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Stopping leader ${leader}..." +ctdb_onnode "$test_node" stop -n "$leader" + +wait_until_node_has_status "$leader" stopped + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.002.ban_leader_yield.sh b/ctdb/tests/INTEGRATION/simple/cluster.002.ban_leader_yield.sh new file mode 100755 index 0000000..234869c --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.002.ban_leader_yield.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb ban' causes a node to yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Banning leader ${leader}..." +ctdb_onnode "$test_node" ban 300 -n "$leader" + +wait_until_node_has_status "$leader" banned + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.003.capability_leader_yield.sh b/ctdb/tests/INTEGRATION/simple/cluster.003.capability_leader_yield.sh new file mode 100755 index 0000000..94bcf27 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.003.capability_leader_yield.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb ban' causes a node to yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Removing leader capability from leader ${leader}..." +ctdb_onnode "$test_node" setleaderrole off -n "$leader" + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.006.stop_leader_yield_no_lock.sh b/ctdb/tests/INTEGRATION/simple/cluster.006.stop_leader_yield_no_lock.sh new file mode 100755 index 0000000..95f522d --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.006.stop_leader_yield_no_lock.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb stop' causes a node to yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +ctdb_nodes_start_custom -C "cluster lock" + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Stopping leader ${leader}..." +ctdb_onnode "$test_node" stop -n "$leader" + +wait_until_node_has_status "$leader" stopped + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.007.ban_leader_yield_no_lock.sh b/ctdb/tests/INTEGRATION/simple/cluster.007.ban_leader_yield_no_lock.sh new file mode 100755 index 0000000..0ef4e2b --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.007.ban_leader_yield_no_lock.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb ban' causes a node to yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +ctdb_nodes_start_custom -C "cluster lock" + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Banning leader ${leader}..." +ctdb_onnode "$test_node" ban 300 -n "$leader" + +wait_until_node_has_status "$leader" banned + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.008.capability_leader_yield_no_lock.sh b/ctdb/tests/INTEGRATION/simple/cluster.008.capability_leader_yield_no_lock.sh new file mode 100755 index 0000000..4489bc5 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.008.capability_leader_yield_no_lock.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# Verify that removing the the leader capability causes a node to +# yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +ctdb_nodes_start_custom -C "cluster lock" + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Removing leader capability from leader ${leader}..." +ctdb_onnode "$test_node" setleaderrole off -n "$leader" + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.010.getrelock.sh b/ctdb/tests/INTEGRATION/simple/cluster.010.getrelock.sh new file mode 100755 index 0000000..3a76654 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.010.getrelock.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Verify that "ctdb getreclock" gets the recovery lock correctly + +# Make sure the recovery lock is consistent across all nodes. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +echo "Check that recovery lock is set the same on all nodes..." +ctdb_onnode all getreclock + +# outfile is set above by ctdb_onnode +# shellcheck disable=SC2154 +n=$(sort -u "$outfile" | wc -l | tr -d '[:space:]') + +case "$n" in +0) echo "GOOD: Recovery lock is unset on all nodes" ;; +1) echo "GOOD: All nodes have the same recovery lock setting" ;; +*) ctdb_test_fail "BAD: Recovery lock setting differs across nodes" ;; +esac diff --git a/ctdb/tests/INTEGRATION/simple/cluster.012.reclock_command.sh b/ctdb/tests/INTEGRATION/simple/cluster.012.reclock_command.sh new file mode 100755 index 0000000..d043c7e --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.012.reclock_command.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# Check that CTDB operates correctly if the recovery lock is configured +# as a command. + +# This test works only with local daemons. On a real cluster it has +# no way of updating configuration. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +echo "Starting CTDB with recovery lock command configured..." +ctdb_nodes_start_custom -R + +echo "Good, that seems to work!" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.015.reclock_remove_lock.sh b/ctdb/tests/INTEGRATION/simple/cluster.015.reclock_remove_lock.sh new file mode 100755 index 0000000..2bb058c --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.015.reclock_remove_lock.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# Verify that the cluster recovers if the recovery lock is removed. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +echo "Starting CTDB with cluster lock recheck interval set to 5s..." +ctdb_nodes_start_custom -r 5 + +generation_has_changed () +{ + local node="$1" + local generation_init="$2" + + # Leak this so it can be printed by test + generation_new="" + + ctdb_onnode "$node" status + # shellcheck disable=SC2154 + # $outfile set by ctdb_onnode() above + generation_new=$(sed -n -e 's/^Generation:\([0-9]*\)/\1/p' "$outfile") + + [ "$generation_new" != "$generation_init" ] +} + +select_test_node + +echo "Get recovery lock setting" +# shellcheck disable=SC2154 +# $test_node set by select_test_node() above +ctdb_onnode "$test_node" getreclock +# shellcheck disable=SC2154 +# $out set by ctdb_onnode() above +reclock_setting="$out" + +if [ -z "$reclock_setting" ] ; then + ctdb_test_skip "Recovery lock is not set" +fi + +t="${reclock_setting% 5}" +reclock="${t##* }" + +if [ ! -f "$reclock" ] ; then + ctdb_test_error "Recovery lock file \"${reclock}\" is missing" +fi + +echo "Recovery lock setting is \"${reclock_setting}\"" +echo "Recovery lock file is \"${reclock}\"" +echo + +leader_get "$test_node" + +generation_get + +echo "Remove recovery lock" +rm "$reclock" +echo + +# This will mean an election has taken place and a recovery has occurred +wait_until_generation_has_changed "$test_node" + +# shellcheck disable=SC2154 +# $leader set by leader_get() above +leader_old="$leader" + +leader_get "$test_node" + +if [ "$leader" != "$leader_old" ] ; then + echo "OK: Leader has changed to node ${leader_new}" +fi +echo "GOOD: Leader is still node ${leader}" +echo + +cluster_is_healthy diff --git a/ctdb/tests/INTEGRATION/simple/cluster.016.reclock_move_lock_dir.sh b/ctdb/tests/INTEGRATION/simple/cluster.016.reclock_move_lock_dir.sh new file mode 100755 index 0000000..147547d --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.016.reclock_move_lock_dir.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +# Verify that if the directory containing the cluster lock is moved +# then the current cluster leader no longer claims to be leader, and +# no other node claims to be leader. Confirm that if the directory is +# moved back then a node will become leader. + +# This simulates the cluster filesystem containing the cluster lock +# being unmounted and remounted. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +echo "Starting CTDB with cluster lock recheck interval set to 5s..." +ctdb_nodes_start_custom -r 5 + +select_test_node + +echo "Get cluster lock setting" +# shellcheck disable=SC2154 +# $test_node set by select_test_node() above +ctdb_onnode "$test_node" getreclock +# shellcheck disable=SC2154 +# $out set by ctdb_onnode() above +reclock_setting="$out" + +if [ -z "$reclock_setting" ] ; then + ctdb_test_skip "Cluster lock is not set" +fi + +t="${reclock_setting% 5}" +reclock="${t##* }" + +if [ ! -f "$reclock" ] ; then + ctdb_test_error "Cluster lock file \"${reclock}\" is missing" +fi + +echo "Cluster lock setting is \"${reclock_setting}\"" +echo "Cluster lock file is \"${reclock}\"" +echo + +leader_get "$test_node" + +dir=$(dirname "$reclock") + +echo "Rename cluster lock directory" +mv "$dir" "${dir}.$$" + +wait_until_leader_has_changed "$test_node" +echo + +# shellcheck disable=SC2154 +# $leader set by leader_get() & wait_until_leader_has_changed(), above +if [ "$leader" != "UNKNOWN" ]; then + test_fail "BAD: leader is ${leader}" +fi + +echo "OK: leader is UNKNOWN" +echo + +echo 'Get "leader timeout":' +conf_tool="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb-config" +# shellcheck disable=SC2154 +# $test_node set by select_test_node() above +try_command_on_node "$test_node" "${conf_tool} get cluster 'leader timeout'" +# shellcheck disable=SC2154 +# $out set by ctdb_onnode() above +leader_timeout="$out" +echo "Leader timeout is ${leader_timeout}s" +echo + +sleep_time=$((2 * leader_timeout)) +echo "Waiting for ${sleep_time}s to confirm leader stays UNKNOWN" +sleep_for $sleep_time + +leader_get "$test_node" +if [ "$leader" = "UNKNOWN" ]; then + echo "OK: leader is UNKNOWN" + echo +else + test_fail "BAD: leader is ${leader}" +fi + +echo "Restore cluster lock directory" +mv "${dir}.$$" "$dir" + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.020.message_ring.sh b/ctdb/tests/INTEGRATION/simple/cluster.020.message_ring.sh new file mode 100755 index 0000000..b841f5b --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.020.message_ring.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# Run the message_ring test and sanity check the output + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +echo "Running message_ring on all $num_nodes nodes." +try_command_on_node -v -p all $CTDB_TEST_WRAPPER $VALGRIND message_ring -n $num_nodes + +# Get the last line of output. +last=$(tail -n 1 "$outfile") + +pat='^(Waiting for cluster|Ring\[[[:digit:]]+\]: [[:digit:]]+(\.[[:digit:]]+)? msgs/sec \(\+ve=[[:digit:]]+ -ve=[[:digit:]]+\))$' +sanity_check_output 1 "$pat" + +# $last should look like this: +# Ring[1]: 10670.93 msgs/sec (+ve=53391 -ve=53373) +stuff="${last##Ring\[*\]: }" +mps="${stuff% msgs/sec*}" + +if [ ${mps%.*} -ge 10 ] ; then + echo "OK: $mps msgs/sec >= 10 msgs/sec" +else + echo "BAD: $mps msgs/sec < 10 msgs/sec" + exit 1 +fi + +stuff="${stuff#*msgs/sec (+ve=}" +positive="${stuff%% *}" + +if [ $positive -ge 10 ] ; then + echo "OK: +ive ($positive) >= 10" +else + echo "BAD: +ive ($positive) < 10" + exit 1 +fi + +stuff="${stuff#*-ve=}" +negative="${stuff%)}" + +if [ $negative -ge 10 ] ; then + echo "OK: -ive ($negative) >= 10" +else + echo "BAD: -ive ($negative) < 10" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/simple/cluster.021.tunnel_ring.sh b/ctdb/tests/INTEGRATION/simple/cluster.021.tunnel_ring.sh new file mode 100755 index 0000000..f86d080 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.021.tunnel_ring.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Run tunnel_test and sanity check the output + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +echo "Running tunnel_test on all $num_nodes nodes." +try_command_on_node -v -p all $CTDB_TEST_WRAPPER $VALGRIND \ + tunnel_test -t 30 -n $num_nodes + +# Get the last line of output. +last=$(tail -n 1 "$outfile") + +pat='^(Waiting for cluster|pnn\[[[:digit:]]+\] [[:digit:]]+(\.[[:digit:]]+)? msgs/sec)$' +sanity_check_output 1 "$pat" + +# $last should look like this: +# pnn[2] count=85400 +stuff="${last##pnn\[*\] }" +mps="${stuff% msgs/sec}" + +if [ ${mps%.*} -ge 10 ] ; then + echo "OK: $mps msgs/sec >= 10 msgs/sec" +else + echo "BAD: $mps msgs/sec < 10 msgs/sec" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/simple/cluster.030.node_stall_leader_timeout.sh b/ctdb/tests/INTEGRATION/simple/cluster.030.node_stall_leader_timeout.sh new file mode 100755 index 0000000..7bca58c --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.030.node_stall_leader_timeout.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# Verify that nothing bad occurs if a node stalls and the leader +# broadcast timeout triggers + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node +echo + +echo 'Get "leader timeout":' +conf_tool="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb-config" +# shellcheck disable=SC2154 +# $test_node set by select_test_node() above +try_command_on_node "$test_node" "${conf_tool} get cluster 'leader timeout'" +# shellcheck disable=SC2154 +# $out set by ctdb_onnode() above +leader_timeout="$out" +echo "Leader timeout is ${leader_timeout} seconds" +echo + +# Assume leader timeout is reasonable and doesn't cause node to be +# disconnected +stall_time=$((leader_timeout * 2)) + +generation_get "$test_node" + +echo "Get ctdbd PID on node ${test_node}..." +ctdb_onnode -v "$test_node" "getpid" +ctdbd_pid="$out" +echo + +echo "Sending SIGSTOP to ctdbd on ${test_node}" +try_command_on_node "$test_node" "kill -STOP ${ctdbd_pid}" + +sleep_for "$stall_time" + +echo "Sending SIGCONT to ctdbd on ${test_node}" +try_command_on_node "$test_node" "kill -CONT ${ctdbd_pid}" +echo + +wait_until_generation_has_changed "$test_node" + +cluster_is_healthy diff --git a/ctdb/tests/INTEGRATION/simple/cluster.090.unreachable.sh b/ctdb/tests/INTEGRATION/simple/cluster.090.unreachable.sh new file mode 100755 index 0000000..1410a12 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.090.unreachable.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# Verify an error occurs if a ctdb command is run against a node +# without a ctdbd + +# That is, check that an error message is printed if an attempt is made +# to execute a ctdb command against a node that is not running ctdbd. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +test_node=1 + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" +echo "There are $num_nodes nodes." + +echo "Shutting down node ${test_node}..." +try_command_on_node $test_node $CTDB shutdown + +wait_until_node_has_status $test_node disconnected 30 0 + +wait_until_node_has_status 0 recovered 30 0 + +pat="ctdb_control error: 'ctdb_control to disconnected node'|ctdb_control error: 'node is disconnected'|Node $test_node is DISCONNECTED|Node $test_node has status DISCONNECTED\|UNHEALTHY\|INACTIVE" + +for i in ip disable enable "ban 0" unban listvars ; do + try_command_on_node -v 0 ! $CTDB $i -n $test_node + + if grep -Eq "$pat" "$outfile" ; then + echo "OK: \"ctdb ${i}\" fails with expected \"disconnected node\" message" + else + echo "BAD: \"ctdb ${i}\" does not fail with expected \"disconnected node\" message" + exit 1 + fi +done diff --git a/ctdb/tests/INTEGRATION/simple/cluster.091.version_check.sh b/ctdb/tests/INTEGRATION/simple/cluster.091.version_check.sh new file mode 100755 index 0000000..be71750 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.091.version_check.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Check that the CTDB version consistency checking operates correctly + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init + +select_test_node + +try_command_on_node -v "$test_node" ctdb version +version="$out" + +major="${version%%.*}" +rest="${version#*.}" +minor="${rest%%.*}" + +echo "Node ${test_node} has version ${major}.${minor}" + +# Unchanged version - this should work +export CTDB_TEST_SAMBA_VERSION=$(( (major << 16) | minor )) +printf '\nRestarting node %d with CTDB_TEST_SAMBA_VERSION=0x%08x\n' \ + "$test_node" \ + "$CTDB_TEST_SAMBA_VERSION" +ctdb_nodes_restart "$test_node" +wait_until_ready +echo "GOOD: ctdbd restarted successfully on node ${test_node}" + +d="$CTDB_SCRIPTS_HELPER_BINDIR" +try_command_on_node "$test_node" "${d}/ctdb-path" "pidfile" "ctdbd" +pidfile="$out" + +# Changed major version - this should fail +export CTDB_TEST_SAMBA_VERSION=$(( ((major + 1) << 16) | minor )) +printf '\nRestarting node %d with CTDB_TEST_SAMBA_VERSION=0x%08x\n' \ + "$test_node" \ + "$CTDB_TEST_SAMBA_VERSION" +ctdb_nodes_restart "$test_node" +echo "Will use PID file ${pidfile} to check for ctdbd exit" +wait_until 30 ! test -f "$pidfile" +echo "GOOD: ctdbd exited early on node ${test_node}" + +# Changed minor version - this should fail +export CTDB_TEST_SAMBA_VERSION=$(( (major << 16) | (minor + 1) )) +printf '\nRestarting node %d with CTDB_TEST_SAMBA_VERSION=0x%08x\n' \ + "$test_node" \ + "$CTDB_TEST_SAMBA_VERSION" +ctdb_nodes_start "$test_node" +echo "Will use PID file ${pidfile} to check for ctdbd exit" +wait_until 30 ! test -f "$pidfile" +echo "GOOD: ctdbd exited early on node ${test_node}" diff --git a/ctdb/tests/INTEGRATION/simple/debug.001.getdebug.sh b/ctdb/tests/INTEGRATION/simple/debug.001.getdebug.sh new file mode 100755 index 0000000..2220a20 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/debug.001.getdebug.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb getdebug' works as expected + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +try_command_on_node -v 1 "onnode -q all $CTDB getdebug" +getdebug_onnode="$out" + +sanity_check_output \ + $num_nodes \ + '^(ERROR|WARNING|NOTICE|INFO|DEBUG)$' + +cmd="" +n=0 +while [ $n -lt $num_nodes ] ; do + cmd="${cmd}${cmd:+; }$CTDB getdebug -n $n" + n=$(($n + 1)) +done +try_command_on_node -v 1 "$cmd" +getdebug_n="$out" + +if [ "$getdebug_onnode" = "$getdebug_n" ] ; then + echo "They're the same... cool!" +else + die "Error: they differ." +fi + +seps="" +nl=" +" +while read line ; do + t=$(echo "$line" | sed -r -e 's@Node [[:digit:]]+ is at debug level ([[:alpha:]]+) \((-?[[:digit:]]+)\)$@\|\1\|\2|@') + seps="${seps}${seps:+${nl}}|Name|Level|${nl}${t}" +done <<<"$getdebug_onnode" diff --git a/ctdb/tests/INTEGRATION/simple/debug.002.setdebug.sh b/ctdb/tests/INTEGRATION/simple/debug.002.setdebug.sh new file mode 100755 index 0000000..dd5949e --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/debug.002.setdebug.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb setdebug' works as expected. + +# This is a little superficial. It checks that CTDB thinks the debug +# level has been changed but doesn't actually check that logging occurs +# at the new level. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node + +get_debug () +{ + # Sets: check_debug + local node="$1" + + local out + + try_command_on_node -v $node "$CTDB getdebug" + check_debug="$out" +} + +set_and_check_debug () +{ + local node="$1" + local level="$2" + local levelstr="${3:-$level}" + + echo "Setting debug level on node ${node} to ${level}." + try_command_on_node $node "$CTDB setdebug ${level}" + + local check_debug + get_debug $node + + if [ "$levelstr" != "$check_debug" ] ; then + die "BAD: Debug level \"$levelstr\" != \"$check_debug\"." + fi +} + +get_debug $test_node +initial_debug="$check_debug" + +levels="ERROR WARNING NOTICE INFO DEBUG" + +for new_debug in $levels ; do + [ "$initial_debug" != "$new_debug" ] || continue + + echo + set_and_check_debug $test_node "$new_debug" +done + +while read new_debug i ; do + [ "$initial_debug" != "$i" ] || continue + + echo + set_and_check_debug $test_node "$i" "$new_debug" +done <<EOF +ERROR 0 +WARNING 1 +WARNING 2 +NOTICE 3 +NOTICE 4 +INFO 5 +INFO 6 +INFO 7 +INFO 8 +INFO 9 +DEBUG 10 +EOF diff --git a/ctdb/tests/INTEGRATION/simple/debug.003.dumpmemory.sh b/ctdb/tests/INTEGRATION/simple/debug.003.dumpmemory.sh new file mode 100755 index 0000000..6205c27 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/debug.003.dumpmemory.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb dumpmemory' shows expected output + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +pat='^([[:space:]].+[[:space:]]+contains[[:space:]]+[[:digit:]]+ bytes in[[:space:]]+[[:digit:]]+ blocks \(ref [[:digit:]]+\)[[:space:]]+0x[[:xdigit:]]+|[[:space:]]+reference to: .+|full talloc report on .+ \(total[[:space:]]+[[:digit:]]+ bytes in [[:digit:]]+ blocks\))$' + +try_command_on_node -v 0 "$CTDB dumpmemory" +sanity_check_output 10 "$pat" + +echo +try_command_on_node -v 0 "$CTDB rddumpmemory" +sanity_check_output 10 "$pat" diff --git a/ctdb/tests/INTEGRATION/simple/eventscripts.001.zero_scripts.sh b/ctdb/tests/INTEGRATION/simple/eventscripts.001.zero_scripts.sh new file mode 100755 index 0000000..4fdf61c --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/eventscripts.001.zero_scripts.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Check that CTDB operates correctly if there are 0 event scripts + + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +ctdb_nodes_start_custom --no-event-scripts + +echo "Good, that seems to work!" diff --git a/ctdb/tests/INTEGRATION/simple/eventscripts.090.debug_hung.sh b/ctdb/tests/INTEGRATION/simple/eventscripts.090.debug_hung.sh new file mode 100755 index 0000000..046989c --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/eventscripts.090.debug_hung.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +# Verify CTDB's debugging of timed out eventscripts + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init + +select_test_node + +#################### + +echo "Setting monitor events to time out..." +try_command_on_node $test_node 'echo $CTDB_BASE' +ctdb_base="$out" +script_options="${ctdb_base}/script.options" +ctdb_test_exit_hook_add "onnode $test_node rm -f $script_options" + +debug_output="${ctdb_base}/debug-hung-script.log" +ctdb_test_exit_hook_add "onnode $test_node rm -f $debug_output" + +try_command_on_node -i "$test_node" tee "$script_options" <<EOF +CTDB_RUN_TIMEOUT_MONITOR=yes +CTDB_DEBUG_HUNG_SCRIPT_LOGFILE='$debug_output' +CTDB_DEBUG_HUNG_SCRIPT_STACKPAT='exportfs|rpcinfo|sleep' +CTDB_SCRIPT_VARDIR='$ctdb_base' +EOF + +#################### + +wait_for_monitor_event $test_node + +echo "Waiting for debugging output to appear..." +# Use test -s because the file is created above using mktemp +wait_until 60 test -s "$debug_output" + +echo +echo "Debugging output:" +cat "$debug_output" +echo + +echo "Checking output of hung script debugging..." + +# Can we actually read kernel stacks +if try_command_on_node $test_node "cat /proc/$$/stack >/dev/null 2>&1" ; then + stackpat=' +---- Stack trace of interesting process [0-9]*\\[sleep\\] ---- +[<[0-9a-f]*>] .*sleep+.* +' +else + stackpat='' +fi + +while IFS="" read pattern ; do + [ -n "$pattern" ] || continue + if grep -q -- "^${pattern}\$" "$debug_output" ; then + printf 'GOOD: output contains "%s"\n' "$pattern" + else + printf 'BAD: output does not contain "%s"\n' "$pattern" + exit 1 + fi +done <<EOF +===== Start of hung script debug for PID=".*", event="monitor" ===== +===== End of hung script debug for PID=".*", event="monitor" ===== +pstree -p -a .*: +00\\\\.test\\\\.script,.* + *\`-sleep,.* +${stackpat} +---- ctdb scriptstatus monitor: ---- +00\\.test *TIMEDOUT.* + *OUTPUT: Sleeping for [0-9]* seconds\\\\.\\\\.\\\\. +EOF diff --git a/ctdb/tests/README b/ctdb/tests/README new file mode 100644 index 0000000..80f3311 --- /dev/null +++ b/ctdb/tests/README @@ -0,0 +1,145 @@ +Introduction +------------ + +For a developer, the simplest way of running most tests on a local +machine from within the git repository is: + + make test + +This runs all UNIT and INTEGRATION tests. + +tests/run_tests.sh +------------------ + +This script can be used to manually run all tests or selected tests, +with a variety of options. For usage, run: + + tests/run_tests.sh -h + +If no tests are specified this runs all of the UNIT and INTEGRATION +tests. + +By default: + +* INTEGRATION tests are run against 3 local daemons + +* When testing is complete, a summary showing a list is printed + showing the tests run and their results + +Tests can be selected in various ways: + +* tests/run_tests.sh UNIT INTEGRATION + + runs all UNIT and INTEGRATION tests, and is like specifying no tests + +* tests/run_tests.sh UNIT/tool + + runs all of the "tool" UNIT tests + +* tests/run_tests.sh tests/UNIT/eventscripts/00.ctdb.setup.001.sh + tests/run_tests.sh tests/INTEGRATION/simple/basics.001.listnodes.sh + + each runs a single specified test case + +* tests/run_tests.sh UNIT/eventscripts UNIT/tool tests/UNIT/onnode/0001.sh + + runs a combination of UNIT test suites and a single UNIT test + +Testing on a cluster +-------------------- + +INTEGRATION and CLUSTER tests can be run on a real or virtual cluster +using tests/run_cluster_tests.sh (or "tests/run_tests.sh -c"). The +test code needs to be available on all cluster nodes, as well as the +test client node. The test client node needs to have a nodes file +where the onnode(1) command will find it. + +If the all of the cluster nodes have the CTDB git tree in the same +location as on the test client then no special action is necessary. +The simplest way of doing this is to share the tree to cluster nodes +and test clients via NFS. + +Alternatively, the tests can be installed on all nodes. One technique +is to build a package containing the tests and install it on all +nodes. CTDB developers do a lot of testing this way using the +provided sample packaging, which produces a ctdb-tests RPM package. + +Finally, if the test code is installed in a different place on the +cluster nodes, then CTDB_TEST_REMOTE_DIR can be set on the test client +node to point to a directory that contains the test_wrap script on the +cluster nodes. + +Running tests under valgrind +---------------------------- + +The easiest way of doing this is something like: + + VALGRIND="valgrind -q" tests/run_tests ... + +This can be used to cause all invocations of the ctdb tool, test +programs and, with local daemons, the ctdbd daemons themselves to run +under valgrind. + +How is the ctdb tool invoked? +----------------------------- + +$CTDB determines how to invoke the ctdb client. If not already set +and if $VALGRIND is set, this is set to "$VALGRIND ctdb". If this is +not already set but $VALGRIND is not set, this is simply set to "ctdb" + +Test and debugging variable options +----------------------------------- + + CTDB_TEST_MODE + + Set this environment variable to enable test mode. + + This enables daemons and tools to locate their socket and + PID file relative to CTDB_BASE. + + When testing with multiple local daemons on a single + machine this does 3 extra things: + + * Disables checks related to public IP addresses + + * Speeds up the initial recovery during startup at the + expense of some consistency checking + + * Disables real-time scheduling + + CTDB_DEBUG_HUNG_SCRIPT_LOGFILE=FILENAME + FILENAME specifies where log messages should go when + debugging hung eventscripts. This is a testing option. See + also CTDB_DEBUG_HUNG_SCRIPT. + + No default. Messages go to stdout/stderr and are logged to + the same place as other CTDB log messages. + + CTDB_SYS_ETCDIR=DIRECTORY + DIRECTORY containing system configuration files. This is + used to provide alternate configuration when testing and + should not need to be changed from the default. + + Default is /etc. + + CTDB_RUN_TIMEOUT_MONITOR=yes|no + Whether CTDB should simulate timing out monitor + events in local daemon tests. + + Default is no. + + CTDB_TEST_SAMBA_VERSION=VERSION + + VERSION is a 32-bit number containing the Samba major + version in the most significant 16 bits and the minor + version in the least significant 16 bits. This can be + used to test CTDB's checking of incompatible versions + without installing an incompatible version. This is + probably best set like this: + + export CTDB_TEST_SAMBA_VERSION=$(( (4 << 16) | 12 )) + + CTDB_VARDIR=DIRECTORY + DIRECTORY containing CTDB files that are modified at runtime. + + Defaults to /usr/local/var/lib/ctdb. diff --git a/ctdb/tests/TODO b/ctdb/tests/TODO new file mode 100644 index 0000000..be471cc --- /dev/null +++ b/ctdb/tests/TODO @@ -0,0 +1,4 @@ +* Make tests know about IPv6. +* Tests that write to database. +* Tests that check actual network connectivity on failover. +* Handle interrupting tests better. diff --git a/ctdb/tests/UNIT/cunit/cluster_mutex_001.sh b/ctdb/tests/UNIT/cunit/cluster_mutex_001.sh new file mode 100755 index 0000000..7976143 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/cluster_mutex_001.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +# This tests the fcntl helper, configured via a lock file + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +t="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb_mutex_fcntl_helper" +export CTDB_CLUSTER_MUTEX_HELPER="$t" + +lockfile="${CTDB_TEST_TMP_DIR}/cluster_mutex.lockfile" +trap 'rm -f ${lockfile}' 0 + +test_case "No contention: lock, unlock" +ok <<EOF +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-unlock "$lockfile" + +test_case "Contention: lock, lock, unlock" +ok <<EOF +LOCK +CONTENTION +NOLOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-lock-unlock "$lockfile" + +test_case "No contention: lock, unlock, lock, unlock" +ok <<EOF +LOCK +UNLOCK +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-unlock-lock-unlock "$lockfile" + +test_case "Cancelled: unlock while lock still in progress" +ok <<EOF +CANCEL +NOLOCK +EOF +unit_test cluster_mutex_test lock-cancel-check "$lockfile" + +test_case "Cancelled: unlock while lock still in progress, unlock again" +ok <<EOF +CANCEL +UNLOCK +EOF +unit_test cluster_mutex_test lock-cancel-unlock "$lockfile" + +test_case "PPID doesn't go away: lock, wait, unlock" +ok <<EOF +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-wait-unlock "$lockfile" + +test_case "PPID goes away: lock, wait, lock, unlock" +ok <<EOF +LOCK +parent gone +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-ppid-gone-lock-unlock "$lockfile" diff --git a/ctdb/tests/UNIT/cunit/cluster_mutex_002.sh b/ctdb/tests/UNIT/cunit/cluster_mutex_002.sh new file mode 100755 index 0000000..c672eaf --- /dev/null +++ b/ctdb/tests/UNIT/cunit/cluster_mutex_002.sh @@ -0,0 +1,132 @@ +#!/bin/sh + +# This tests the fcntl helper, externally configured via ! + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +export CTDB_CLUSTER_MUTEX_HELPER="/bin/false" + +lockfile="${CTDB_TEST_TMP_DIR}/cluster_mutex.lockfile" +trap 'rm ${lockfile}' 0 + +t="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb_mutex_fcntl_helper" +helper="!${t} ${lockfile}" + +test_case "No contention: lock, unlock" +ok <<EOF +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-unlock "$helper" + +test_case "Contention: lock, lock, unlock" +ok <<EOF +LOCK +CONTENTION +NOLOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-lock-unlock "$helper" + +test_case "No contention: lock, unlock, lock, unlock" +ok <<EOF +LOCK +UNLOCK +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-unlock-lock-unlock "$helper" + +test_case "Cancelled: unlock while lock still in progress" +ok <<EOF +CANCEL +NOLOCK +EOF +unit_test cluster_mutex_test lock-cancel-check "$helper" + +test_case "Cancelled: unlock while lock still in progress, unlock again" +ok <<EOF +CANCEL +UNLOCK +EOF +unit_test cluster_mutex_test lock-cancel-unlock "$helper" + +test_case "PPID doesn't go away: lock, wait, unlock" +ok <<EOF +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-wait-unlock "$helper" + +test_case "PPID goes away: lock, wait, lock, unlock" +ok <<EOF +LOCK +parent gone +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-ppid-gone-lock-unlock "$helper" + +test_case "Recheck off, lock file removed" +ok <<EOF +LOCK +LOCK +UNLOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-file-removed-no-recheck \ + "$helper 0" "$lockfile" + +test_case "Recheck on, lock file not removed" +ok <<EOF +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-file-wait-recheck-unlock \ + "$helper 5" 10 + +test_case "Recheck on, lock file removed" +ok <<EOF +LOCK +ctdb_mutex_fcntl_helper: lock lost - lock file "${lockfile}" open failed (ret=2) +LOST +EOF +unit_test cluster_mutex_test lock-file-removed "$helper 5" "$lockfile" + +test_case "Recheck on, lock file replaced" +ok <<EOF +LOCK +ctdb_mutex_fcntl_helper: lock lost - lock file "${lockfile}" inode changed +LOST +EOF +unit_test cluster_mutex_test lock-file-changed "$helper 10" "$lockfile" + +test_case "Recheck on, ping on, child isn't blocked" +ok <<EOF +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-io-timeout "$helper 5 7" "$lockfile" 0 0 + +test_case "Recheck on, ping on, child waits, child isn't blocked" +ok <<EOF +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-io-timeout "$helper 5 3" "$lockfile" 7 0 + +test_case "Recheck on, ping on, child waits, child blocks for short time" +ok <<EOF +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-io-timeout "$helper 5 7" "$lockfile" 1 2 + + +test_case "Recheck on, ping on, child waits, child blocks causing ping timeout" +ok <<EOF +LOCK +ctdb_mutex_fcntl_helper: ping timeout from lock test child +LOST +EOF +unit_test cluster_mutex_test lock-io-timeout "$helper 5 3" "$lockfile" 1 7 diff --git a/ctdb/tests/UNIT/cunit/cluster_mutex_003.sh b/ctdb/tests/UNIT/cunit/cluster_mutex_003.sh new file mode 100755 index 0000000..57319bd --- /dev/null +++ b/ctdb/tests/UNIT/cunit/cluster_mutex_003.sh @@ -0,0 +1,75 @@ +#!/bin/sh + +# This tests a helper, externally configured via ! + +# By default this is the fcntl helper, so this is a subset of test 002. +# However, other helps can be tested by setting CTDB_TEST_MUTEX_HELPER. + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +export CTDB_CLUSTER_MUTEX_HELPER="/bin/false" + +lockfile="${CTDB_TEST_TMP_DIR}/cluster_mutex.lockfile" +trap 'rm ${lockfile}' 0 + +if [ -n "$CTDB_TEST_MUTEX_HELPER" ] ; then + helper="$CTDB_TEST_MUTEX_HELPER" +else + t="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb_mutex_fcntl_helper" + helper="!${t} ${lockfile}" +fi + +test_case "No contention: lock, unlock" +ok <<EOF +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-unlock "$helper" + +test_case "Contention: lock, lock, unlock" +ok <<EOF +LOCK +CONTENTION +NOLOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-lock-unlock "$helper" + +test_case "No contention: lock, unlock, lock, unlock" +ok <<EOF +LOCK +UNLOCK +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-unlock-lock-unlock "$helper" + +test_case "Cancelled: unlock while lock still in progress" +ok <<EOF +CANCEL +NOLOCK +EOF +unit_test cluster_mutex_test lock-cancel-check "$helper" + +test_case "Cancelled: unlock while lock still in progress, unlock again" +ok <<EOF +CANCEL +UNLOCK +EOF +unit_test cluster_mutex_test lock-cancel-unlock "$helper" + +test_case "PPID doesn't go away: lock, wait, unlock" +ok <<EOF +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-wait-unlock "$helper" + +test_case "PPID goes away: lock, wait, lock, unlock" +ok <<EOF +LOCK +parent gone +LOCK +UNLOCK +EOF +unit_test cluster_mutex_test lock-ppid-gone-lock-unlock "$helper" diff --git a/ctdb/tests/UNIT/cunit/cmdline_test_001.sh b/ctdb/tests/UNIT/cunit/cmdline_test_001.sh new file mode 100755 index 0000000..e959000 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/cmdline_test_001.sh @@ -0,0 +1,98 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null +unit_test cmdline_test 1 + +ok <<EOF +Command 'nofunc' has no implementation function +Command 'nohelp' has no help msg +Command 'really really long command with lots of words' is too long (85) +Command 'longhelp' help too long (90) +EOF +unit_test cmdline_test 2 + +ok <<EOF +Option has no long name +Option 'debug' has unsupported type +Option 'debug' has invalid arg +EOF +unit_test cmdline_test 3 + +ok <<EOF +Usage: test4 [<options>] <command> [<args>] + +Help Options: + -h, --help Show this help message + +Options: + -c, --count=INT Option help of length thirty. + -v, --value=Value help of length 23 Short description + +Commands: + A really really long command <a long arguments message> This is a really long help message + short command <short arg msg> short msg for short command +Usage: test4 [-h] [-h|--help] [-c|--count=INT] + [-v|--value=Value help of length 23] <command> [<args>] + + short command <short arg msg> short msg for short command +EOF +unit_test cmdline_test 4 + +ok <<EOF +Usage: test5 [<options>] <command> [<args>] + +Help Options: + -h, --help Show this help message + +Action Commands: + action one action one help + action two action two help +Usage: test5 [<options>] <command> [<args>] + +Help Options: + -h, --help Show this help message + +Action Commands: + action one action one help + action two action two help +Usage: test5 [<options>] <command> [<args>] + +Help Options: + -h, --help Show this help message + +Action Commands: + action one action one help + action two action two help +EOF +unit_test cmdline_test 5 + +ok <<EOF +arg1 +EOF +unit_test cmdline_test 6 + +ok <<EOF +Usage: test7 [<options>] <command> [<args>] + +Help Options: + -h, --help Show this help message + +Basic Commands: + cmd1 command one help + cmd2 command two help + +Advanced Commands: + cmd3 command three help + cmd4 command four help + +Ultimate Commands: + cmd5 command five help + cmd6 command six help + +one +three +six +EOF +unit_test cmdline_test 7 diff --git a/ctdb/tests/UNIT/cunit/comm_test_001.sh b/ctdb/tests/UNIT/cunit/comm_test_001.sh new file mode 100755 index 0000000..ac09f5c --- /dev/null +++ b/ctdb/tests/UNIT/cunit/comm_test_001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + + +ok_null +unit_test comm_test 1 + +ok_null +unit_test comm_test 2 + +ok "100 2048 500 4096 1024 8192 200 16384 300 32768 400 65536 1048576 " +unit_test comm_test 3 diff --git a/ctdb/tests/UNIT/cunit/comm_test_002.sh b/ctdb/tests/UNIT/cunit/comm_test_002.sh new file mode 100755 index 0000000..a2fbf51 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/comm_test_002.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +socket="${CTDB_TEST_TMP_DIR}/test_sock.$$" +num_clients=10 + +remove_socket () +{ + rm -f "$socket" +} + +test_cleanup remove_socket + +ok_null + +unit_test comm_server_test "$socket" $num_clients & +pid=$! + +for i in $(seq 1 $num_clients) ; do + unit_test comm_client_test "$socket" +done + +wait $pid diff --git a/ctdb/tests/UNIT/cunit/conf_test_001.sh b/ctdb/tests/UNIT/cunit/conf_test_001.sh new file mode 100755 index 0000000..188964e --- /dev/null +++ b/ctdb/tests/UNIT/cunit/conf_test_001.sh @@ -0,0 +1,196 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +conffile="${CTDB_TEST_TMP_DIR}/config.$$" + +remove_files () +{ + rm -f "$conffile" +} + +test_cleanup remove_files + +ok_null +unit_test conf_test 1 + +ok <<EOF +conf: unknown section [section1] +EOF +unit_test conf_test 2 + +ok <<EOF +conf: option "key1" already exists +EOF +unit_test conf_test 3 + +ok <<EOF +conf: option "key1" already exists +EOF +unit_test conf_test 4 + +ok_null +unit_test conf_test 5 + +ok <<EOF +[section1] + key1 = foobar # temporary + key2 = 20 # temporary + key3 = false # temporary +EOF +unit_test conf_test 6 + +ok <<EOF +conf: validation for option "key1" failed +conf: validation for option "key2" failed +conf: validation for option "key3" failed +EOF +unit_test conf_test 7 + +cat > "$conffile" <<EOF +[section1] +EOF + +required_error EINVAL <<EOF +conf: validation for section [section1] failed +[section1] + # key1 = default +EOF +unit_test conf_test 8 "$conffile" + +cat > "$conffile" <<EOF +[section1] + key1 = unknown +EOF + +required_error EINVAL <<EOF +conf: validation for section [section1] failed +[section1] + # key1 = default +EOF +unit_test conf_test 8 "$conffile" + +cat > "$conffile" <<EOF +[section1] + key1 = +EOF + +required_error EINVAL <<EOF +conf: empty value [section1] -> "key1" +[section1] + # key1 = value1 + # key2 = 10 + key3 = false # temporary +EOF +unit_test conf_test 9 "$conffile" + +cat > "$conffile" <<EOF +[section1] + key3 = +EOF + +required_error EINVAL <<EOF +conf: empty value [section1] -> "key3" +[section1] + # key1 = value1 + # key2 = 10 + key3 = false # temporary +EOF +unit_test conf_test 9 "$conffile" + +cat > "$conffile" <<EOF + +[section1] + key1 = value2 + key2 = 20 # comment +key3 = false +EOF + +ok <<EOF +[section1] + key1 = value2 + key2 = 20 + # key3 = true +EOF +unit_test conf_test 9 "$conffile" + +cat > "$conffile" <<EOF +[section1] +key1 = value2 +EOF + +ok <<EOF +[section1] + key1 = value2 + # key2 = 10 + # key3 = true +EOF +unit_test conf_test 9 "$conffile" + +cat > "$conffile" <<EOF +[section2] + foo = bar +EOF + +required_error EINVAL <<EOF +conf: unknown section [section2] +conf: unknown section for option "foo" +[section1] + # key1 = value1 + # key2 = 10 + key3 = false # temporary +EOF +unit_test conf_test 10 "$conffile" + +cat > "$conffile" <<EOF +[section1] + key1 = value2 + foo = bar + key2 = 20 +EOF + +required_error EINVAL <<EOF +conf: unknown option [section1] -> "foo" +[section1] + # key1 = value1 + # key2 = 10 + key3 = false # temporary +EOF +unit_test conf_test 10 "$conffile" + +cat > "$conffile" <<EOF +[section1] + key1 = value2 + key2 = 20 + key3 = false +EOF + +touch "${conffile}.reload" + +ok <<EOF +[section1] + # key1 = value1 + # key2 = 10 + # key3 = true +EOF +unit_test conf_test 11 "$conffile" + +cat > "$conffile" <<EOF +[section1] + key1 = value2 + key2 = 20 + key3 = false +EOF + +cat > "${conffile}.reload" <<EOF +[section1] + key1 = value3 +EOF + +ok <<EOF +[section1] + key1 = value3 + # key2 = 10 + # key3 = true +EOF +unit_test conf_test 11 "$conffile" diff --git a/ctdb/tests/UNIT/cunit/config_test_001.sh b/ctdb/tests/UNIT/cunit/config_test_001.sh new file mode 100755 index 0000000..70bf77f --- /dev/null +++ b/ctdb/tests/UNIT/cunit/config_test_001.sh @@ -0,0 +1,115 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +PATH="$PATH:$CTDB_SCRIPTS_TOOLS_HELPER_DIR" + +setup_ctdb_base "${CTDB_TEST_TMP_DIR}" "ctdb-etc" + +conffile="${CTDB_BASE}/ctdb.conf" + +remove_files () +{ + rm -f "$conffile" +} + +test_cleanup remove_files + +# Get the default values that are dependent on install prefix +logging_location=$(ctdb-config get "logging" "location") +database_volatile_dbdir=$(ctdb-config get \ + "database" \ + "volatile database directory") +database_persistent_dbdir=$(ctdb-config get \ + "database" \ + "persistent database directory") +database_state_dbdir=$(ctdb-config get \ + "database" \ + "state database directory") + +ok <<EOF +[logging] + # location = ${logging_location} + # log level = NOTICE +[cluster] + # transport = tcp + # node address = + # cluster lock = + # recovery lock = + # leader timeout = 5 + # leader capability = true +[database] + # volatile database directory = ${database_volatile_dbdir} + # persistent database directory = ${database_persistent_dbdir} + # state database directory = ${database_state_dbdir} + # lock debug script = + # tdb mutexes = true +[event] + # debug script = +[failover] + # disabled = false +[legacy] + # realtime scheduling = true + # lmaster capability = true + # start as stopped = false + # start as disabled = false + # script log level = ERROR +EOF +unit_test ctdb-config dump + +required_result 2 <<EOF +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +EOF + +ok_null +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +[foobar] +EOF + +required_result 22 <<EOF +conf: unknown section [foobar] +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +foobar = cat +EOF + +required_result 22 <<EOF +conf: unknown section for option "foobar" +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +required_result 2 <<EOF +Configuration option [section] -> "key" not defined +EOF +unit_test ctdb-config get section key + +# Confirm that an unknown key doesn't stop the rest of the file from +# loading +cat > "$conffile" <<EOF +[database] + unknown key = 123 + +[logging] + log level = debug +EOF + +required_error EINVAL <<EOF +conf: unknown option [database] -> "unknown key" +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +ok <<EOF +debug +EOF +unit_test ctdb-config get "logging" "log level" diff --git a/ctdb/tests/UNIT/cunit/config_test_002.sh b/ctdb/tests/UNIT/cunit/config_test_002.sh new file mode 100755 index 0000000..23b0863 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/config_test_002.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +PATH="$PATH:$CTDB_SCRIPTS_TOOLS_HELPER_DIR" + +setup_ctdb_base "${CTDB_TEST_TMP_DIR}" "ctdb-etc" + +conffile="${CTDB_BASE}/ctdb.conf" + +remove_files () +{ + rm -f "$conffile" +} + +test_cleanup remove_files + +cat > "$conffile" <<EOF +EOF + +ok <<EOF +NOTICE +EOF +unit_test ctdb-config get "logging" "log level" + +cat > "$conffile" <<EOF +[logging] + location = syslog:magic +EOF + +required_result 22 <<EOF +conf: validation for option "location" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +[logging] + log level = high +EOF + +required_result 22 <<EOF +conf: validation for option "log level" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +[logging] + location = syslog + log level = notice +EOF + +ok_null +unit_test ctdb-config validate + +ok <<EOF +syslog +EOF +unit_test ctdb-config get "logging" "location" + +ok <<EOF +notice +EOF +unit_test ctdb-config get "logging" "log level" diff --git a/ctdb/tests/UNIT/cunit/config_test_003.sh b/ctdb/tests/UNIT/cunit/config_test_003.sh new file mode 100755 index 0000000..4e8d553 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/config_test_003.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +PATH="$PATH:$CTDB_SCRIPTS_TOOLS_HELPER_DIR" + +setup_ctdb_base "${CTDB_TEST_TMP_DIR}" "ctdb-etc" + +conffile="${CTDB_BASE}/ctdb.conf" +scriptfile="${CTDB_BASE}/debug-hung-script.sh" + +remove_files () +{ + rm -f "$conffile" +} + +test_cleanup remove_files + +cat > "$conffile" <<EOF +EOF + +ok <<EOF +EOF +unit_test ctdb-config get "event" "debug script" + +cat > "$conffile" <<EOF +[event] + debug script = debug-hung-script.sh +EOF + +touch "$scriptfile" + +required_result 22 <<EOF +debug script $scriptfile is not executable +conf: validation for option "debug script" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +chmod +x "$scriptfile" + +ok_null +unit_test ctdb-config validate + +rm -f "$scriptfile" + +required_result 22 <<EOF +debug script $scriptfile does not exist +conf: validation for option "debug script" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate diff --git a/ctdb/tests/UNIT/cunit/config_test_004.sh b/ctdb/tests/UNIT/cunit/config_test_004.sh new file mode 100755 index 0000000..ebbc05b --- /dev/null +++ b/ctdb/tests/UNIT/cunit/config_test_004.sh @@ -0,0 +1,144 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +PATH="$PATH:$CTDB_SCRIPTS_TOOLS_HELPER_DIR" + +setup_ctdb_base "${CTDB_TEST_TMP_DIR}" "ctdb-etc" + +conffile="$CTDB_BASE/ctdb.conf" + +remove_files () +{ + rm -f "$conffile" +} + +test_cleanup remove_files + +cat > "$conffile" <<EOF +EOF + +ok <<EOF +tcp +EOF +unit_test ctdb-config get "cluster" "transport" + +ok <<EOF +EOF +unit_test ctdb-config get "cluster" "node address" + +ok <<EOF +EOF +unit_test ctdb-config get "cluster" "cluster lock" + +ok <<EOF +5 +EOF +unit_test ctdb-config get "cluster" "leader timeout" + +ok <<EOF +true +EOF +unit_test ctdb-config get "cluster" "leader capability" + +cat > "$conffile" <<EOF +[cluster] + transport = invalid +EOF + +required_result 22 <<EOF +Invalid value for [cluster] -> transport = invalid +conf: validation for option "transport" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +[cluster] + node address = 10.1.2.3 +EOF + +ok <<EOF +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +[cluster] + node address = fc00:10:1:2::123 +EOF + +ok <<EOF +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +[cluster] + node address = 10.1.2.3:123 +EOF + +required_result 22 <<EOF +Invalid value for [cluster] -> node address = 10.1.2.3:123 +conf: validation for option "node address" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +[cluster] + cluster lock = /foo/bar +EOF + +required_result 0 <<EOF +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +[cluster] + recovery lock = /foo/bar +EOF + +required_result 0 <<EOF +Configuration option [cluster] -> recovery lock is deprecated +EOF +unit_test ctdb-config -d WARNING validate + +cat > "$conffile" <<EOF +[cluster] + leader timeout = 10 +EOF + +required_result 0 <<EOF +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +[cluster] + leader timeout = 0 +EOF + +required_result 22 <<EOF +Invalid value for [cluster] -> leader timeout = 0 +conf: validation for option "leader timeout" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +[cluster] + leader timeout = -5 +EOF + +required_result 22 <<EOF +conf: invalid value [cluster] -> "leader timeout" = "-5" +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" <<EOF +[cluster] + leader capability = false +EOF + +required_result 0 <<EOF +EOF +unit_test ctdb-config validate diff --git a/ctdb/tests/UNIT/cunit/config_test_005.sh b/ctdb/tests/UNIT/cunit/config_test_005.sh new file mode 100755 index 0000000..c16a43f --- /dev/null +++ b/ctdb/tests/UNIT/cunit/config_test_005.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +PATH="$PATH:$CTDB_SCRIPTS_HELPER_BINDIR" + +setup_ctdb_base "${CTDB_TEST_TMP_DIR}" "ctdb-etc" + +conffile="${CTDB_BASE}/ctdb.conf" +scriptfile="${CTDB_BASE}/debug_locks.sh" +dbdir="${CTDB_BASE}/dbdir" +dbdir_volatile="${dbdir}/volatile" +dbdir_persistent="${dbdir}/persistent" +dbdir_state="${dbdir}/state" + +remove_files () +{ + rm -f "$conffile" "$scriptfile" +} + +test_cleanup remove_files + +cat > "$conffile" <<EOF +[database] + volatile database directory = ${dbdir_volatile} + persistent database directory = ${dbdir_persistent} + state database directory = ${dbdir_state} +EOF + +required_result 22 <<EOF +volatile database directory "${dbdir_volatile}" does not exist +conf: validation for option "volatile database directory" failed +persistent database directory "${dbdir_persistent}" does not exist +conf: validation for option "persistent database directory" failed +state database directory "${dbdir_state}" does not exist +conf: validation for option "state database directory" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +mkdir -p "$dbdir_volatile" + +required_result 22 <<EOF +persistent database directory "${dbdir_persistent}" does not exist +conf: validation for option "persistent database directory" failed +state database directory "${dbdir_state}" does not exist +conf: validation for option "state database directory" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +mkdir -p "$dbdir_persistent" + +required_result 22 <<EOF +state database directory "${dbdir_state}" does not exist +conf: validation for option "state database directory" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +mkdir -p "$dbdir_state" + +required_result 0 <<EOF +EOF +unit_test ctdb-config validate + +ok <<EOF +EOF +unit_test ctdb-config get "database" "lock debug script" + +cat > "$conffile" <<EOF +[database] + lock debug script = $scriptfile +EOF + +touch "$scriptfile" + +required_result 22 <<EOF +lock debug script $scriptfile is not executable +conf: validation for option "lock debug script" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +chmod +x "$scriptfile" + +ok_null +unit_test ctdb-config validate + +rm -f "$scriptfile" + +required_result 22 <<EOF +lock debug script $scriptfile does not exist +conf: validation for option "lock debug script" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate diff --git a/ctdb/tests/UNIT/cunit/config_test_006.sh b/ctdb/tests/UNIT/cunit/config_test_006.sh new file mode 100755 index 0000000..622fb66 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/config_test_006.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +PATH="$PATH:$CTDB_SCRIPTS_HELPER_BINDIR" + +setup_ctdb_base "${CTDB_TEST_TMP_DIR}" "ctdb-etc" + +conffile="${CTDB_BASE}/ctdb.conf" + +remove_files () +{ + rm -f "$conffile" +} + +test_cleanup remove_files + +cat > "$conffile" <<EOF +EOF + +ok <<EOF +true +EOF +unit_test ctdb-config get "legacy" "realtime scheduling" + +ok <<EOF +true +EOF +unit_test ctdb-config get "legacy" "lmaster capability" + +ok <<EOF +false +EOF +unit_test ctdb-config get "legacy" "start as stopped" + +ok <<EOF +false +EOF +unit_test ctdb-config get "legacy" "start as disabled" + +ok <<EOF +ERROR +EOF +unit_test ctdb-config get "legacy" "script log level" + +cat > "$conffile" <<EOF +[legacy] + script log level = INVALID +EOF + +required_result 22 <<EOF +Invalid value for [legacy] -> script log level = INVALID +conf: validation for option "script log level" failed +Failed to load config file ${conffile} +EOF +unit_test ctdb-config validate diff --git a/ctdb/tests/UNIT/cunit/config_test_007.sh b/ctdb/tests/UNIT/cunit/config_test_007.sh new file mode 100755 index 0000000..8804448 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/config_test_007.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +PATH="$PATH:$CTDB_SCRIPTS_HELPER_BINDIR" + +setup_ctdb_base "${CTDB_TEST_TMP_DIR}" "ctdb-etc" + +conffile="${CTDB_BASE}/ctdb.conf" + +remove_files () +{ + rm -f "$conffile" +} + +test_cleanup remove_files + +cat > "$conffile" <<EOF +EOF + +ok <<EOF +false +EOF +unit_test ctdb-config get "failover" "disabled" diff --git a/ctdb/tests/UNIT/cunit/ctdb_io_test_001.sh b/ctdb/tests/UNIT/cunit/ctdb_io_test_001.sh new file mode 100755 index 0000000..b6d3bce --- /dev/null +++ b/ctdb/tests/UNIT/cunit/ctdb_io_test_001.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test ctdb_io_test 1 +unit_test ctdb_io_test 2 +unit_test ctdb_io_test 3 +unit_test ctdb_io_test 4 diff --git a/ctdb/tests/UNIT/cunit/db_hash_test_001.sh b/ctdb/tests/UNIT/cunit/db_hash_test_001.sh new file mode 100755 index 0000000..76c38fe --- /dev/null +++ b/ctdb/tests/UNIT/cunit/db_hash_test_001.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test db_hash_test diff --git a/ctdb/tests/UNIT/cunit/event_protocol_test_001.sh b/ctdb/tests/UNIT/cunit/event_protocol_test_001.sh new file mode 100755 index 0000000..8d5f932 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/event_protocol_test_001.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test event_protocol_test 1 100 diff --git a/ctdb/tests/UNIT/cunit/event_script_test_001.sh b/ctdb/tests/UNIT/cunit/event_script_test_001.sh new file mode 100755 index 0000000..0d6a38e --- /dev/null +++ b/ctdb/tests/UNIT/cunit/event_script_test_001.sh @@ -0,0 +1,127 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +scriptdir="${CTDB_TEST_TMP_DIR}/scriptdir" +mkdir -p "${scriptdir}" + +scriptdir=$(cd "$scriptdir" && echo "$PWD") + +test_cleanup "rm -rf ${scriptdir}" + +# Invalid path +invalid="${scriptdir}/notfound" +ok <<EOF +Script list ${invalid} failed with result=$(errcode ENOENT) +EOF +unit_test event_script_test list "${invalid}" + +# Empty directory +ok <<EOF +No scripts found +EOF +unit_test event_script_test list "$scriptdir" + +# Invalid script, doesn't end in ".script" +touch "${scriptdir}/prog" + +ok <<EOF +No scripts found +EOF +unit_test event_script_test list "$scriptdir" + +# Is not found because enabling "prog" actually looks for "prog.script" +ok <<EOF +Script enable ${scriptdir} prog completed with result=$(errcode ENOENT) +EOF +unit_test event_script_test enable "$scriptdir" "prog" + +required_result 1 <<EOF +EOF +unit_test test -x "${scriptdir}/prog" + +# Is not found because enabling "prog" actually looks for "prog.script" +ok <<EOF +Script disable ${scriptdir} prog completed with result=$(errcode ENOENT) +EOF +unit_test event_script_test disable "$scriptdir" "prog" + +# Valid script +touch "$scriptdir/11.foo.script" + +ok <<EOF +11.foo +EOF +unit_test event_script_test list "$scriptdir" + +ok <<EOF +Script enable ${scriptdir} 11.foo completed with result=0 +EOF +unit_test event_script_test enable "$scriptdir" "11.foo" + +ok <<EOF +EOF +unit_test test -x "${scriptdir}/11.foo.script" + +ok <<EOF +Script disable ${scriptdir} 11.foo.script completed with result=0 +EOF +unit_test event_script_test disable "$scriptdir" "11.foo.script" + +required_result 1 <<EOF +EOF +unit_test test -x "${scriptdir}/11.foo.script" + +# Multiple scripts +touch "${scriptdir}/22.bar.script" + +ok <<EOF +11.foo +22.bar +EOF +unit_test event_script_test list "$scriptdir" + +# Symlink to existing file +ln -s "${scriptdir}/prog" "${scriptdir}/33.link.script" + +ok <<EOF +11.foo +22.bar +33.link +EOF +unit_test event_script_test list "$scriptdir" + +ok <<EOF +Script enable ${scriptdir} 33.link completed with result=$(errcode EINVAL) +EOF +unit_test event_script_test enable "$scriptdir" "33.link" + + +ok <<EOF +Script disable ${scriptdir} 33.link.script completed with result=$(errcode EINVAL) +EOF +unit_test event_script_test disable "$scriptdir" "33.link.script" + +# Dangling symlink +rm "${scriptdir}/33.link.script" +ln -s "${scriptdir}/nosuchfile" "${scriptdir}/33.link.script" + +ok <<EOF +11.foo +22.bar +33.link +EOF +unit_test event_script_test list "$scriptdir" + +ok <<EOF +Script enable ${scriptdir} 33.link completed with result=$(errcode ENOENT) +EOF +unit_test event_script_test enable "$scriptdir" "33.link" + + +ok <<EOF +Script disable ${scriptdir} 33.link.script completed with result=$(errcode ENOENT) +EOF +unit_test event_script_test disable "$scriptdir" "33.link.script" + +exit 0 diff --git a/ctdb/tests/UNIT/cunit/hash_count_test_001.sh b/ctdb/tests/UNIT/cunit/hash_count_test_001.sh new file mode 100755 index 0000000..3958706 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/hash_count_test_001.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test hash_count_test diff --git a/ctdb/tests/UNIT/cunit/line_test_001.sh b/ctdb/tests/UNIT/cunit/line_test_001.sh new file mode 100755 index 0000000..5676893 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/line_test_001.sh @@ -0,0 +1,90 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +tfile="${CTDB_TEST_TMP_DIR}/line.$$" + +remove_files () +{ + rm -f "$tfile" +} + +test_cleanup remove_files + +> "$tfile" + +ok_null +unit_test line_test "$tfile" + +printf "\0" > "$tfile" + +required_result 1 <<EOF + +EOF + +unit_test line_test "$tfile" + +echo -n "hello" > "$tfile" + +ok_null +unit_test line_test "$tfile" + +cat <<EOF > "$tfile" +hello +world +EOF + +required_result 2 << EOF +hello +world +EOF +unit_test line_test "$tfile" + +required_result 2 << EOF +hello +world +EOF +unit_test line_test "$tfile" + +cat <<EOF > "$tfile" +This is a really long long line full of random words and hopefully it will be read properly by the line test program and identified as a single line +EOF + +required_result 1 <<EOF +This is a really long long line full of random words and hopefully it will be read properly by the line test program and identified as a single line +EOF +unit_test line_test "$tfile" + +cat <<EOF > "$tfile" +line number one +line number two +line number one +line number two +line number one +EOF + +required_result 5 <<EOF +line number one +line number two +line number one +line number two +line number one +EOF +unit_test line_test "$tfile" 64 + +cat <<EOF > "$tfile" +this is line number one +this is line number two +this is line number three +this is line number four +this is line number five +EOF + +required_result 5 <<EOF +this is line number one +this is line number two +this is line number three +this is line number four +this is line number five +EOF +unit_test line_test "$tfile" 64 diff --git a/ctdb/tests/UNIT/cunit/path_tests_001.sh b/ctdb/tests/UNIT/cunit/path_tests_001.sh new file mode 100755 index 0000000..5713fc8 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/path_tests_001.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +PATH="$PATH:$CTDB_SCRIPTS_TOOLS_HELPER_DIR" + +setup_ctdb_base "${CTDB_TEST_TMP_DIR}" "ctdb-etc" + +ok <<EOF +$CTDB_BASE/ctdb.conf +EOF +unit_test ctdb-path config + +ok <<EOF +$CTDB_BASE/run/foobar.pid +EOF +unit_test ctdb-path pidfile foobar + +ok <<EOF +$CTDB_BASE/run/foobar.socket +EOF +unit_test ctdb-path socket foobar + +ok <<EOF +$CTDB_BASE/share +EOF +unit_test ctdb-path datadir + +ok <<EOF +$CTDB_BASE +EOF +unit_test ctdb-path etcdir + +ok <<EOF +$CTDB_BASE/run +EOF +unit_test ctdb-path rundir + +ok <<EOF +$CTDB_BASE/var +EOF +unit_test ctdb-path vardir + +ok <<EOF +$CTDB_BASE/share/foobar +EOF +unit_test ctdb-path datadir append foobar + +ok <<EOF +$CTDB_BASE/foobar +EOF +unit_test ctdb-path etcdir append foobar + +ok <<EOF +$CTDB_BASE/run/foobar +EOF +unit_test ctdb-path rundir append foobar + +ok <<EOF +$CTDB_BASE/var/foobar +EOF +unit_test ctdb-path vardir append foobar diff --git a/ctdb/tests/UNIT/cunit/pidfile_test_001.sh b/ctdb/tests/UNIT/cunit/pidfile_test_001.sh new file mode 100755 index 0000000..cf48403 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/pidfile_test_001.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +pidfile=$(TMPDIR="$CTDB_TEST_TMP_DIR" mktemp) + +ok_null +unit_test pidfile_test $pidfile diff --git a/ctdb/tests/UNIT/cunit/pkt_read_001.sh b/ctdb/tests/UNIT/cunit/pkt_read_001.sh new file mode 100755 index 0000000..c951f39 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/pkt_read_001.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test pkt_read_test diff --git a/ctdb/tests/UNIT/cunit/pkt_write_001.sh b/ctdb/tests/UNIT/cunit/pkt_write_001.sh new file mode 100755 index 0000000..131af05 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/pkt_write_001.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test pkt_write_test diff --git a/ctdb/tests/UNIT/cunit/porting_tests_001.sh b/ctdb/tests/UNIT/cunit/porting_tests_001.sh new file mode 100755 index 0000000..bdb7fc5 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/porting_tests_001.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +socket="${CTDB_TEST_TMP_DIR}/test_sock.$$" + +remove_socket () +{ + rm -f "$socket" +} + +test_cleanup remove_socket + +ok_null +unit_test porting_tests --socket="$socket" diff --git a/ctdb/tests/UNIT/cunit/protocol_test_001.sh b/ctdb/tests/UNIT/cunit/protocol_test_001.sh new file mode 100755 index 0000000..7f68c48 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/protocol_test_001.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test protocol_basic_test 1 1000 diff --git a/ctdb/tests/UNIT/cunit/protocol_test_002.sh b/ctdb/tests/UNIT/cunit/protocol_test_002.sh new file mode 100755 index 0000000..51e0513 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/protocol_test_002.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test protocol_types_test 1 1000 diff --git a/ctdb/tests/UNIT/cunit/protocol_test_012.sh b/ctdb/tests/UNIT/cunit/protocol_test_012.sh new file mode 100755 index 0000000..b9fd492 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/protocol_test_012.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test protocol_types_compat_test 1 1000 diff --git a/ctdb/tests/UNIT/cunit/protocol_test_101.sh b/ctdb/tests/UNIT/cunit/protocol_test_101.sh new file mode 100755 index 0000000..f944c6b --- /dev/null +++ b/ctdb/tests/UNIT/cunit/protocol_test_101.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test protocol_ctdb_test 1 100 diff --git a/ctdb/tests/UNIT/cunit/protocol_test_111.sh b/ctdb/tests/UNIT/cunit/protocol_test_111.sh new file mode 100755 index 0000000..28d190c --- /dev/null +++ b/ctdb/tests/UNIT/cunit/protocol_test_111.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test protocol_ctdb_compat_test 1 100 diff --git a/ctdb/tests/UNIT/cunit/protocol_test_201.sh b/ctdb/tests/UNIT/cunit/protocol_test_201.sh new file mode 100755 index 0000000..012db90 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/protocol_test_201.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null +unit_test protocol_util_test diff --git a/ctdb/tests/UNIT/cunit/rb_test_001.sh b/ctdb/tests/UNIT/cunit/rb_test_001.sh new file mode 100755 index 0000000..25d3ceb --- /dev/null +++ b/ctdb/tests/UNIT/cunit/rb_test_001.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +output="\ +testing trbt_insertarray32_callback +traverse data:3 +traverse data:2 +traverse data:1 + +deleting key4 +traverse data:3 +traverse data:2 +traverse data:1 + +deleting key2 +traverse data:3 +traverse data:1 + +deleting key3 +traverse data:3 + +deleting key1 + +run random insert and delete for 60 seconds + +deleting all entries" + +ok "$output" + +unit_test rb_test diff --git a/ctdb/tests/UNIT/cunit/reqid_test_001.sh b/ctdb/tests/UNIT/cunit/reqid_test_001.sh new file mode 100755 index 0000000..06259ba --- /dev/null +++ b/ctdb/tests/UNIT/cunit/reqid_test_001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +output=$( +for i in $(seq 0 1023) ; do + echo "WARNING: attempt to remove unset id $i in idtree" +done +) + +ok "$output" + +unit_test reqid_test diff --git a/ctdb/tests/UNIT/cunit/run_event_001.sh b/ctdb/tests/UNIT/cunit/run_event_001.sh new file mode 100755 index 0000000..4df3b4b --- /dev/null +++ b/ctdb/tests/UNIT/cunit/run_event_001.sh @@ -0,0 +1,137 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +# Invalid path +required_result 1 <<EOF +run_event_init() failed, ret=2 +EOF +unit_test run_event_test /a/b/c list + +scriptdir=$(TMPDIR="$CTDB_TEST_TMP_DIR" mktemp -d) + +# Empty directory +ok <<EOF +No event scripts found +EOF +unit_test run_event_test "$scriptdir" list + +cat > "$scriptdir/prog" <<EOF +#!/bin/sh + +echo hello +EOF + +# Invalid script, doesn't end in ".script" +ok <<EOF +No event scripts found +EOF +unit_test run_event_test "$scriptdir" list + +# Is not found because enabling "prog" actually looks for "prog.script" +ok <<EOF +Script enable prog completed with result=2 +EOF +unit_test run_event_test "$scriptdir" enable prog + +required_result 1 <<EOF +EOF +unit_test test -x "${scriptdir}/prog" + +cat > "$scriptdir/11.foo.script" <<EOF +#!/bin/sh + +echo hello +EOF + +# Valid script +ok <<EOF +11.foo +EOF +unit_test run_event_test "$scriptdir" list + +ok <<EOF +Script enable 11.foo completed with result=0 +EOF +unit_test run_event_test "$scriptdir" enable 11.foo + +ok <<EOF +EOF +unit_test test -x "${scriptdir}/11.foo.script" + +ok <<EOF +11.foo: hello +Event monitor completed with result=0 +11.foo result=0 +EOF +unit_test run_event_test "$scriptdir" run 10 monitor + +cat > "$scriptdir/22.bar.script" <<EOF +#!/bin/sh + +exit 1 +EOF + +# Multiple scripts +ok <<EOF +11.foo +22.bar +EOF +unit_test run_event_test "$scriptdir" list + +ok <<EOF +Script enable 22.bar completed with result=0 +EOF +unit_test run_event_test "$scriptdir" enable 22.bar + +ok <<EOF +11.foo: hello +Event monitor completed with result=1 +11.foo result=0 +22.bar result=1 +EOF +unit_test run_event_test "$scriptdir" run 10 monitor + +# Disable script +ok <<EOF +Script disable 22.bar completed with result=0 +EOF +unit_test run_event_test "$scriptdir" disable 22.bar + +required_result 1 <<EOF +EOF +unit_test test -x "${scriptdir}/22.bar.script" + +ok <<EOF +11.foo: hello +Event monitor completed with result=0 +11.foo result=0 +22.bar result=-$(errcode ENOEXEC) +EOF +unit_test run_event_test "$scriptdir" run 10 monitor + +cat > "$scriptdir/22.bar.script" <<EOF +#!/bin/sh + +echo before sleep +sleep 10 +echo after sleep +EOF + +# Timed out script +ok <<EOF +Script enable 22.bar completed with result=0 +EOF +unit_test run_event_test "$scriptdir" enable 22.bar + +ok <<EOF +11.foo: hello +22.bar: before sleep +Event monitor completed with result=-$(errcode ETIMEDOUT) +11.foo result=0 +22.bar result=-$(errcode ETIMEDOUT) +EOF +unit_test run_event_test "$scriptdir" run 5 monitor + +rm -rf "$scriptdir" +exit 0 diff --git a/ctdb/tests/UNIT/cunit/run_proc_001.sh b/ctdb/tests/UNIT/cunit/run_proc_001.sh new file mode 100755 index 0000000..3f48885 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/run_proc_001.sh @@ -0,0 +1,159 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +# Invalid path +ok <<EOF +Process exited with error $(errcode ENOENT) +EOF +unit_test run_proc_test 0 -1 /a/b/c + +# Non-executable path +prog=$(TMPDIR="$CTDB_TEST_TMP_DIR" mktemp) +cat > "$prog" <<EOF +echo hello +EOF + +ok <<EOF +Process exited with error $(errcode EACCES) +EOF +unit_test run_proc_test 0 -1 "$prog" + +# Executable path +chmod +x "$prog" + +ok <<EOF +Process exited with error $(errcode ENOEXEC) +EOF +unit_test run_proc_test 0 -1 "$prog" + +# Capture output +cat > "$prog" <<EOF +#!/bin/sh +echo hello +EOF + +ok <<EOF +Process exited with status 0 +Output = (hello +) +EOF +unit_test run_proc_test 0 -1 "$prog" + +# Specify timeout +ok <<EOF +Process exited with status 0 +Output = (hello +) +EOF +unit_test run_proc_test 5 -1 "$prog" + +# Redirected output +output=$(TMPDIR="$CTDB_TEST_TMP_DIR" mktemp) +cat > "$prog" <<EOF +#!/bin/sh +exec >"$output" 2>&1 +echo hello +EOF + +ok <<EOF +Process exited with status 0 +EOF +unit_test run_proc_test 0 -1 "$prog" + +ok <<EOF +hello +EOF +unit_test cat "$output" + +# Exit with error +cat > "$prog" <<EOF +#!/bin/sh +exit 1 +EOF + +ok <<EOF +Process exited with status 1 +EOF +unit_test run_proc_test 0 -1 "$prog" + +# Exit with signal +cat > "$prog" <<EOF +#!/bin/sh +kill \$$ +EOF + +ok <<EOF +Process exited with signal 15 +EOF +unit_test run_proc_test 0 -1 "$prog" + +# Exit with timeout +cat > "$prog" <<EOF +#!/bin/sh +echo "Sleeping for 5 seconds" +sleep 5 +EOF + +result_filter () +{ + _pid="[0-9][0-9]*" + sed -e "s|= ${_pid}|= PID|" +} + +ok <<EOF +Process exited with error $(errcode ETIMEDOUT) +Child = PID +Output = (Sleeping for 5 seconds +) +EOF +unit_test run_proc_test 1 -1 "$prog" + +# No zombie processes +pidfile=$(TMPDIR="$CTDB_TEST_TMP_DIR" mktemp) + +cat > "$prog" <<EOF +#!/bin/sh +echo \$$ > "$pidfile" +sleep 10 +EOF + +ok <<EOF +Process exited with error $(errcode ETIMEDOUT) +Child = PID +EOF +unit_test run_proc_test 1 -1 "$prog" + +result_filter () +{ + _header=" *PID *TTY *TIME *CMD" + _header2=" *PID *TT *STAT *TIME *COMMAND" + sed -e "s|^${_header}|HEADER|" -e "s|^${_header2}|HEADER|" +} + +pid=$(cat "$pidfile") +required_result 1 <<EOF +HEADER +EOF +unit_test ps -p "$pid" + +# Redirect stdin +cat > "$prog" <<EOF +#!/bin/sh +cat - +EOF + +cat > "$output" <<EOF +this is sample input +EOF + +ok <<EOF +Process exited with status 0 +Output = (this is sample input +) +EOF +(unit_test run_proc_test 0 4 "$prog") 4<"$output" + +rm -f "$pidfile" +rm -f "$output" +rm -f "$prog" diff --git a/ctdb/tests/UNIT/cunit/sock_daemon_test_001.sh b/ctdb/tests/UNIT/cunit/sock_daemon_test_001.sh new file mode 100755 index 0000000..6f360f7 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/sock_daemon_test_001.sh @@ -0,0 +1,135 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +pidfile="${CTDB_TEST_TMP_DIR}/sock_daemon_test.pid.$$" +sockpath="${CTDB_TEST_TMP_DIR}/sock_daemon_test.sock.$$" + +remove_files () +{ + rm -f "$pidfile" + rm -f "$sockpath" +} + +test_cleanup remove_files + +result_filter () +{ + _pid="[0-9][0-9]*" + sed -e "s|pid=${_pid}|pid=PID|" \ + -e "s|PID ${_pid}|PID PID|" +} + + +ok <<EOF +daemon started, pid=PID +startup failed, ret=1 +daemon started, pid=PID +startup failed, ret=2 +daemon started, pid=PID +startup completed successfully +listening on $sockpath +Shutting down +EOF +unit_test sock_daemon_test "$pidfile" "$sockpath" 1 + +ok <<EOF +daemon started, pid=PID +startup completed successfully +listening on $sockpath +Received signal $(sigcode SIGUSR1) +reconfigure failed, ret=1 +Received signal $(sigcode SIGUSR1) +reconfigure completed successfully +Received signal 1 +reopen logs, ret=1 +Received signal 1 +reopen logs completed successfully +Received signal $(sigcode SIGTERM) +Shutting down +daemon started, pid=PID +startup completed successfully +listening on $sockpath +Received signal $(sigcode SIGUSR1) +reconfigure failed, ret=2 +Received signal $(sigcode SIGUSR1) +reconfigure completed successfully +Received signal 1 +reopen logs failed, ret=2 +Received signal 1 +reopen logs completed successfully +Received signal $(sigcode SIGTERM) +Shutting down +EOF +unit_test sock_daemon_test "$pidfile" "$sockpath" 2 + +ok <<EOF +daemon started, pid=PID +listening on $sockpath +PID PID gone away, exiting +Shutting down +EOF +unit_test sock_daemon_test "$pidfile" "$sockpath" 3 + +ok <<EOF +daemon started, pid=PID +Shutting down +EOF +unit_test sock_daemon_test "$pidfile" "$sockpath" 4 + +ok <<EOF +daemon started, pid=PID +listening on $sockpath +Received signal $(sigcode SIGTERM) +Shutting down +EOF +unit_test sock_daemon_test "$pidfile" "$sockpath" 5 + +ok <<EOF +daemon started, pid=PID +listening on $sockpath +Shutting down +EOF +unit_test sock_daemon_test "$pidfile" "$sockpath" 6 + +ok <<EOF +daemon started, pid=PID +startup completed successfully +Received signal $(sigcode SIGTERM) +Shutting down +EOF +unit_test sock_daemon_test "$pidfile" "$sockpath" 7 + +ok <<EOF +daemon started, pid=PID +startup completed successfully +Received signal $(sigcode SIGTERM) +Shutting down +daemon started, pid=PID +startup completed successfully +Received signal $(sigcode SIGTERM) +Shutting down +EOF +unit_test sock_daemon_test "$pidfile" "$sockpath" 8 + +ok <<EOF +daemon started, pid=PID +startup completed successfully +Received signal $(sigcode SIGTERM) +Shutting down +daemon started, pid=PID +startup completed successfully +Received signal $(sigcode SIGTERM) +Shutting down +EOF +unit_test sock_daemon_test "$pidfile" "$sockpath" 9 + +ok <<EOF +daemon started, pid=PID +listening on $sockpath +daemon started, pid=PID +listening on $sockpath +Received signal $(sigcode SIGTERM) +Shutting down +EOF +unit_test sock_daemon_test "$pidfile" "$sockpath" 10 diff --git a/ctdb/tests/UNIT/cunit/sock_io_test_001.sh b/ctdb/tests/UNIT/cunit/sock_io_test_001.sh new file mode 100755 index 0000000..09a280c --- /dev/null +++ b/ctdb/tests/UNIT/cunit/sock_io_test_001.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +sockpath="${CTDB_TEST_TMP_DIR}/sock_daemon_test.sock.$$" + +ok_null + +unit_test sock_io_test "$sockpath" diff --git a/ctdb/tests/UNIT/cunit/srvid_test_001.sh b/ctdb/tests/UNIT/cunit/srvid_test_001.sh new file mode 100755 index 0000000..ed09535 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/srvid_test_001.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null + +unit_test srvid_test diff --git a/ctdb/tests/UNIT/cunit/system_socket_test_001.sh b/ctdb/tests/UNIT/cunit/system_socket_test_001.sh new file mode 100755 index 0000000..389cec6 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/system_socket_test_001.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ok_null +unit_test system_socket_test types diff --git a/ctdb/tests/UNIT/cunit/system_socket_test_002.sh b/ctdb/tests/UNIT/cunit/system_socket_test_002.sh new file mode 100755 index 0000000..c20bcfe --- /dev/null +++ b/ctdb/tests/UNIT/cunit/system_socket_test_002.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +tcp_test () +{ + unit_test system_socket_test tcp "$@" +} + +test_case "ACK, IPv4, seq# 0, ack# 0" +ok <<EOF +000000 45 00 00 08 00 00 00 00 ff 06 00 00 c0 a8 01 19 +000010 c0 a8 02 4b 01 bd d4 31 00 00 00 00 00 00 00 00 +000020 50 10 04 d2 50 5f 00 00 +000028 +EOF +tcp_test "192.168.1.25:445" "192.168.2.75:54321" 0 0 0 + +test_case "RST, IPv4, seq# 0, ack# 0" +ok <<EOF +000000 45 00 00 08 00 00 00 00 ff 06 00 00 c0 a8 01 19 +000010 c0 a8 02 4b 01 bd d4 31 00 00 00 00 00 00 00 00 +000020 50 14 04 d2 50 5b 00 00 +000028 +EOF +tcp_test "192.168.1.25:445" "192.168.2.75:54321" 0 0 1 + +test_case "RST, IPv4, seq# 12345, ack# 23456" +ok <<EOF +000000 45 00 00 08 00 00 00 00 ff 06 00 00 c0 a8 01 19 +000010 c0 a8 02 4b 01 bd d4 31 39 30 00 00 a0 5b 00 00 +000020 50 14 04 d2 76 cf 00 00 +000028 +EOF +tcp_test "192.168.1.25:445" "192.168.2.75:54321" 12345 23456 1 + +test_case "ACK, IPv6, seq# 0, ack# 0" +ok <<EOF +000000 60 00 00 00 00 14 06 40 fe 80 00 00 00 00 00 00 +000010 6a f7 28 ff fe fa d1 36 fe 80 00 00 00 00 00 00 +000020 6a f7 28 ff fe fb d1 37 01 bd d4 31 00 00 00 00 +000030 00 00 00 00 50 10 04 d2 0f c0 00 00 +00003c +EOF +tcp_test "[fe80::6af7:28ff:fefa:d136]:445" \ + "[fe80::6af7:28ff:fefb:d137]:54321" 0 0 0 + +test_case "RST, IPv6, seq# 0, ack# 0" +ok <<EOF +000000 60 00 00 00 00 14 06 40 fe 80 00 00 00 00 00 00 +000010 6a f7 28 ff fe fa d1 36 fe 80 00 00 00 00 00 00 +000020 6a f7 28 ff fe fb d1 37 01 bd d4 31 00 00 00 00 +000030 00 00 00 00 50 14 04 d2 0f bc 00 00 +00003c +EOF +tcp_test "[fe80::6af7:28ff:fefa:d136]:445" \ + "[fe80::6af7:28ff:fefb:d137]:54321" 0 0 1 + +test_case "RST, IPv6, seq# 12345, ack# 23456" +ok <<EOF +000000 60 00 00 00 00 14 06 40 fe 80 00 00 00 00 00 00 +000010 6a f7 28 ff fe fa d1 36 fe 80 00 00 00 00 00 00 +000020 6a f7 28 ff fe fb d1 37 01 bd d4 31 39 30 00 00 +000030 a0 5b 00 00 50 14 04 d2 36 30 00 00 +00003c +EOF +tcp_test "[fe80::6af7:28ff:fefa:d136]:445" \ + "[fe80::6af7:28ff:fefb:d137]:54321" 12345 23456 1 diff --git a/ctdb/tests/UNIT/cunit/system_socket_test_003.sh b/ctdb/tests/UNIT/cunit/system_socket_test_003.sh new file mode 100755 index 0000000..c94ac30 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/system_socket_test_003.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +ctdb_test_check_supported_OS "Linux" + +arp_test () +{ + unit_test system_socket_test arp "$@" +} + +test_case "IPv4 ARP send" +ok <<EOF +000000 ff ff ff ff ff ff 12 34 56 78 9a bc 08 06 00 01 +000010 08 00 06 04 00 01 12 34 56 78 9a bc c0 a8 01 19 +000020 00 00 00 00 00 00 c0 a8 01 19 00 00 00 00 00 00 +000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +000040 +EOF +arp_test "192.168.1.25" "12:34:56:78:9a:bc" + +test_case "IPv4 ARP reply" +ok <<EOF +000000 ff ff ff ff ff ff 12 34 56 78 9a bc 08 06 00 01 +000010 08 00 06 04 00 02 12 34 56 78 9a bc c0 a8 01 19 +000020 12 34 56 78 9a bc c0 a8 01 19 00 00 00 00 00 00 +000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +000040 +EOF +arp_test "192.168.1.25" "12:34:56:78:9a:bc" reply + +test_case "IPv6 neighbor advertisement" +ok <<EOF +000000 33 33 00 00 00 01 12 34 56 78 9a bc 86 dd 60 00 +000010 00 00 00 20 3a ff fe 80 00 00 00 00 00 00 6a f7 +000020 28 ff fe fa d1 36 ff 02 00 00 00 00 00 00 00 00 +000030 00 00 00 00 00 01 88 00 8d e4 20 00 00 00 fe 80 +000040 00 00 00 00 00 00 6a f7 28 ff fe fa d1 36 02 01 +000050 12 34 56 78 9a bc +000056 +EOF +arp_test "fe80::6af7:28ff:fefa:d136" "12:34:56:78:9a:bc" diff --git a/ctdb/tests/UNIT/cunit/tmon_test_001.sh b/ctdb/tests/UNIT/cunit/tmon_test_001.sh new file mode 100755 index 0000000..96f706c --- /dev/null +++ b/ctdb/tests/UNIT/cunit/tmon_test_001.sh @@ -0,0 +1,195 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +epipe=$(errcode EPIPE) +eio=$(errcode EIO) +etimedout=$(errcode ETIMEDOUT) + +test_case "No pings, only child monitors, so gets EPIPE" +ok <<EOF +parent: async wait start 5 +child: async wait start 10 +parent: async wait end +child: pipe closed +EOF +unit_test tmon_ping_test false 0 5 0 0 false 0 10 0 "$epipe" + +test_case "No pings, only parent monitors, so gets EPIPE" +ok <<EOF +parent: async wait start 10 +child: async wait start 5 +child: async wait end +parent: pipe closed +EOF +unit_test tmon_ping_test false 0 10 0 "$epipe" false 0 5 0 0 + +test_case "No pings, Child exits first, parent notices" +ok <<EOF +parent: async wait start 10 +child: async wait start 1 +child: async wait end +parent: pipe closed +EOF +unit_test tmon_ping_test false 0 10 0 "$epipe" false 0 1 0 0 + +test_case "No pings, parent exits first, child notices" +ok <<EOF +parent: async wait start 1 +child: async wait start 10 +parent: async wait end +child: pipe closed +EOF +unit_test tmon_ping_test false 0 1 0 0 false 0 10 0 "$epipe" + +test_case "Parent pings, child doesn't expect them, EIO" +ok <<EOF +parent: async wait start 5 +child: async wait start 5 +child: error ($eio) +parent: pipe closed +EOF +unit_test tmon_ping_test true 0 5 0 "$epipe" false 0 5 0 "$eio" + +test_case "Child pings, parent doesn't expect them, EIO" +ok <<EOF +parent: async wait start 5 +child: async wait start 5 +parent: error ($eio) +child: pipe closed +EOF +unit_test tmon_ping_test false 0 5 0 "$eio" true 0 5 0 "$epipe" + +test_case "Both ping, child doesn't expect them, EIO" +ok <<EOF +parent: async wait start 5 +child: async wait start 5 +child: error ($eio) +parent: pipe closed +EOF +unit_test tmon_ping_test true 3 5 0 "$epipe" true 0 5 0 "$eio" + +test_case "Both ping, parent doesn't expect them, EIO" +ok <<EOF +parent: async wait start 5 +child: async wait start 5 +parent: error ($eio) +child: pipe closed +EOF +unit_test tmon_ping_test true 0 5 0 "$eio" true 3 5 0 "$epipe" + +test_case "Child pings, no ping timeout error, child exits first" +ok <<EOF +parent: async wait start 10 +child: async wait start 5 +child: async wait end +parent: pipe closed +EOF +unit_test tmon_ping_test false 3 10 0 "$epipe" true 0 5 0 0 + +test_case "Parent pings, no ping timeout error, parent exits first" +ok <<EOF +parent: async wait start 5 +child: async wait start 10 +parent: async wait end +child: pipe closed +EOF +unit_test tmon_ping_test true 0 5 0 0 false 3 10 0 "$epipe" + +test_case "Both ping, no ping timeout error, parent exits first" +ok <<EOF +parent: async wait start 5 +child: async wait start 10 +parent: async wait end +child: pipe closed +EOF +unit_test tmon_ping_test true 3 5 0 0 true 3 10 0 "$epipe" + +test_case "Both ping, no ping timeout error, child exits first" +ok <<EOF +parent: async wait start 10 +child: async wait start 5 +child: async wait end +parent: pipe closed +EOF +unit_test tmon_ping_test true 3 10 0 "$epipe" true 3 5 0 0 + +test_case "Both ping, child blocks, parent ping timeout error" +ok <<EOF +parent: async wait start 20 +child: blocking sleep start 7 +parent: ping timeout +child: blocking sleep end +EOF +unit_test tmon_ping_test true 3 20 0 "$etimedout" true 3 0 7 0 + +test_case "Both ping, parent blocks, child ping timeout error" +ok <<EOF +parent: blocking sleep start 7 +child: async wait start 20 +child: ping timeout +parent: blocking sleep end +EOF +unit_test tmon_ping_test true 3 0 7 0 true 3 20 0 "$etimedout" + +test_case "Both ping, child waits, child blocks, parent ping timeout error" +ok <<EOF +parent: async wait start 20 +child: async wait start 2 +child: async wait end +child: blocking sleep start 7 +parent: ping timeout +child: blocking sleep end +EOF +unit_test tmon_ping_test true 3 20 0 "$etimedout" true 3 2 7 0 + +test_case "Both ping, parent waits, parent blocks, child ping timeout error" +ok <<EOF +parent: async wait start 2 +child: async wait start 20 +parent: async wait end +parent: blocking sleep start 7 +child: ping timeout +parent: blocking sleep end +EOF +unit_test tmon_ping_test true 3 2 7 0 true 3 20 0 "$etimedout" + +test_case "Both ping, child blocks for less than ping timeout" +ok <<EOF +parent: async wait start 20 +child: blocking sleep start 3 +child: blocking sleep end +parent: pipe closed +EOF +unit_test tmon_ping_test true 7 20 0 "$epipe" true 7 0 3 0 + +test_case "Both ping, parent blocks for less than ping timeout" +ok <<EOF +parent: blocking sleep start 3 +child: async wait start 20 +parent: blocking sleep end +child: pipe closed +EOF +unit_test tmon_ping_test true 7 0 3 0 true 7 20 3 "$epipe" + +test_case "Both ping, child waits, child blocks for less than ping timeout" +ok <<EOF +parent: async wait start 20 +child: async wait start 2 +child: async wait end +child: blocking sleep start 3 +child: blocking sleep end +parent: pipe closed +EOF +unit_test tmon_ping_test true 7 20 0 "$epipe" true 7 2 3 0 + +test_case "Both ping, parent waits, parent blocks for less than ping timeout" +ok <<EOF +parent: async wait start 2 +child: async wait start 20 +parent: async wait end +parent: blocking sleep start 3 +parent: blocking sleep end +child: pipe closed +EOF +unit_test tmon_ping_test true 7 2 3 0 true 7 20 0 "$epipe" diff --git a/ctdb/tests/UNIT/cunit/tmon_test_002.sh b/ctdb/tests/UNIT/cunit/tmon_test_002.sh new file mode 100755 index 0000000..e4118a3 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/tmon_test_002.sh @@ -0,0 +1,142 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +epipe=$(errcode EPIPE) +etimedout=$(errcode ETIMEDOUT) +edom=$(errcode EDOM) + +test_cases() +{ + test_case "no packets, sender exits, 3s timeout" + ok <<EOF +WRITER OK +READER ERR=$epipe +EOF + unit_test tmon_test "" false 3 false + + test_case "no packets, sender exits, 3s timeout, close ok" + ok <<EOF +WRITER OK +READER OK +EOF + unit_test tmon_test "" true 3 false + + test_case "Exit packet @ 1s, no timeout" + ok <<EOF +READER OK +WRITER OK +EOF + unit_test tmon_test "0" false 0 false + + test_case "errno 7 packet @ 1s, no timeout" + ok <<EOF +READER ERR=7 +WRITER OK +EOF + unit_test tmon_test "7" false 0 false + + test_case "errno 110 packet @ 1s, no timeout" + ok <<EOF +READER ERR=110 +WRITER OK +EOF + unit_test tmon_test "#110" false 0 false + + test_case "errno 0 error causes EDOM @ 1s, no timeout" + ok <<EOF +WRITER ERR=$edom +READER ERR=$epipe +EOF + unit_test tmon_test "#0;" false 0 false + + test_case "errno -1 error causes EDOM @ 1s, no timeout" + ok <<EOF +WRITER ERR=$edom +READER ERR=$epipe +EOF + unit_test tmon_test "#-1;" false 0 false + + test_case "errno 70000 error causes EDOM @ 1s, no timeout" + ok <<EOF +WRITER ERR=$edom +READER ERR=$epipe +EOF + unit_test tmon_test "#70000;!0" false 0 false + + test_case "Exit packet @ 3s, no timeout" + ok <<EOF +READER OK +WRITER OK +EOF + unit_test tmon_test "..0" false 0 false + + test_case "errno 7 packet @ 3s, no timeout" + ok <<EOF +READER ERR=7 +WRITER OK +EOF + unit_test tmon_test "..7" false 0 false + + test_case "no packets for 5s, 3s timeout" + ok <<EOF +READER ERR=$etimedout +WRITER OK +EOF + unit_test tmon_test "....." false 3 false + + test_case "no packets for 5s, 3s timeout, timeout ok" + ok <<EOF +READER OK +WRITER OK +EOF + unit_test tmon_test "....." false 3 true + + test_case "4 pings then exit, 3s timeout" + ok <<EOF +PING +PING +PING +PING +READER OK +WRITER OK +EOF + unit_test tmon_test "!!!!0" false 3 false + + test_case "ASCII Hello, errno 7, 3s timeout" + ok <<EOF +ASCII H +ASCII e +ASCII l +ASCII l +ASCII o +READER ERR=7 +WRITER OK +EOF + unit_test tmon_test "Hello7" false 3 false + + test_case "Hi there! 3s timeout" + ok <<EOF +ASCII H +ASCII i +CUSTOM 0x20 +ASCII t +ASCII h +ASCII e +ASCII r +ASCII e +PING +WRITER OK +READER ERR=$epipe +EOF + unit_test tmon_test "Hi there!" false 3 false +} + +echo "PASS #1: Run test cases in default mode" +test_cases + +echo +echo "==================================================" + +echo "PASS #2: Run test cases in write-skip mode" +CTDB_TEST_TMON_WRITE_SKIP_MODE=1 test_cases diff --git a/ctdb/tests/UNIT/cunit/tunable_test_001.sh b/ctdb/tests/UNIT/cunit/tunable_test_001.sh new file mode 100755 index 0000000..c68cd69 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/tunable_test_001.sh @@ -0,0 +1,312 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +tfile="${CTDB_TEST_TMP_DIR}/tunable.$$" + +remove_files () +{ + rm -f "$tfile" +} +test_cleanup remove_files + +defaults="\ +SeqnumInterval=1000 +ControlTimeout=60 +TraverseTimeout=20 +KeepaliveInterval=5 +KeepaliveLimit=5 +RecoverTimeout=30 +RecoverInterval=1 +ElectionTimeout=3 +TakeoverTimeout=9 +MonitorInterval=15 +TickleUpdateInterval=20 +EventScriptTimeout=30 +MonitorTimeoutCount=20 +RecoveryGracePeriod=120 +RecoveryBanPeriod=300 +DatabaseHashSize=100001 +DatabaseMaxDead=5 +RerecoveryTimeout=10 +EnableBans=1 +NoIPFailback=0 +VerboseMemoryNames=0 +RecdPingTimeout=60 +RecdFailCount=10 +LogLatencyMs=0 +RecLockLatencyMs=1000 +RecoveryDropAllIPs=120 +VacuumInterval=10 +VacuumMaxRunTime=120 +RepackLimit=10000 +VacuumFastPathCount=60 +MaxQueueDropMsg=1000000 +AllowUnhealthyDBRead=0 +StatHistoryInterval=1 +DeferredAttachTO=120 +AllowClientDBAttach=1 +FetchCollapse=1 +HopcountMakeSticky=50 +StickyDuration=600 +StickyPindown=200 +NoIPTakeover=0 +DBRecordCountWarn=100000 +DBRecordSizeWarn=10000000 +DBSizeWarn=100000000 +PullDBPreallocation=10485760 +LockProcessesPerDB=200 +RecBufferSizeLimit=1000000 +QueueBufferSize=1024 +IPAllocAlgorithm=2 +AllowMixedVersions=0 +" + +ok_tunable_defaults () +{ + ok "$defaults" +} + +# Set required output to a version of $defaults where values for +# tunables specified in $tfile replace the default values +ok_tunable () +{ + # Construct a version of $defaults prepended with a lowercase + # version of the tunable variable, to allow case-insensitive + # matching. This would be easier with the GNU sed + # case-insensitivity flag, but that is less portable. The $0 + # condition in awk causes empty lines to be skipped, in case + # there are trailing empty lines in $defaults. + _map=$(echo "$defaults" | + awk -F= '$0 { printf "%s:%s=%s\n", tolower($1), $1, $2 }') + + # Replace values for tunables set in $tfile + while IFS='= ' read -r _var _val ; do + case "$_var" in + \#* | "") continue ;; + esac + _decval=$((_val)) + _vl=$(echo "$_var" | tr '[:upper:]' '[:lower:]') + _map=$(echo "$_map" | + sed -e "s|^\\(${_vl}:.*=\\).*\$|\\1${_decval}|") + done <"$tfile" + + # Set result, stripping off lowercase tunable prefix + ok "$(echo "$_map" | awk -F: '{ print $2 }')" +} + +test_case "Unreadable file" +: >"$tfile" +chmod a-r "$tfile" +uid=$(id -u) +# root can read unreadable files +if [ "$uid" = 0 ]; then + ok_tunable_defaults +else + required_error EINVAL <<EOF +ctdb_tunable_load_file: Failed to open ${tfile} +EOF +fi +unit_test tunable_test "$tfile" +rm -f "$tfile" + +test_case "Invalid file, contains 1 word" +echo "Hello" >"$tfile" +required_error EINVAL <<EOF +ctdb_tunable_load_file: Invalid line containing "Hello" +EOF +unit_test tunable_test "$tfile" + +test_case "Invalid file, contains multiple words" +echo "Hello world!" >"$tfile" +required_error EINVAL <<EOF +ctdb_tunable_load_file: Invalid line containing "Hello world!" +EOF +unit_test tunable_test "$tfile" + +test_case "Invalid file, missing value" +echo "EnableBans=" >"$tfile" +required_error EINVAL <<EOF +ctdb_tunable_load_file: Invalid line containing "EnableBans" +EOF +unit_test tunable_test "$tfile" + +test_case "Invalid file, invalid value (not a number)" +echo "EnableBans=value" >"$tfile" +required_error EINVAL <<EOF +ctdb_tunable_load_file: Invalid value "value" for tunable "EnableBans" +EOF +unit_test tunable_test "$tfile" + +test_case "Invalid file, missing key" +echo "=123" >"$tfile" +required_error EINVAL <<EOF +ctdb_tunable_load_file: Syntax error +EOF +unit_test tunable_test "$tfile" + +test_case "Invalid file, missing key but space before =" +cat >"$tfile" <<EOF + =0 +EOF +required_error EINVAL <<EOF +ctdb_tunable_load_file: Syntax error +EOF +unit_test tunable_test "$tfile" + +test_case "Invalid file, unknown tunable" +echo "HelloWorld=123" >"$tfile" +required_error EINVAL <<EOF +ctdb_tunable_load_file: Unknown tunable "HelloWorld" +EOF +unit_test tunable_test "$tfile" + +test_case "Invalid file, obsolete tunable" +echo "MaxRedirectCount=123" >"$tfile" +required_error EINVAL <<EOF +ctdb_tunable_load_file: Obsolete tunable "MaxRedirectCount" +EOF +unit_test tunable_test "$tfile" + +test_case "Invalid file, trailing non-whitespace garbage" +echo "EnableBans=0xgg" >"$tfile" +required_error EINVAL <<EOF +ctdb_tunable_load_file: Invalid value "0xgg" for tunable "EnableBans" +EOF +unit_test tunable_test "$tfile" + +test_case "Invalid file, multiple errors" +cat >"$tfile" <<EOF +EnableBans= +EnableBans=value +=123 +HelloWorld=123 +MaxRedirectCount =123 +EOF +required_error EINVAL <<EOF +ctdb_tunable_load_file: Invalid line containing "EnableBans" +ctdb_tunable_load_file: Invalid value "value" for tunable "EnableBans" +ctdb_tunable_load_file: Syntax error +EOF +unit_test tunable_test "$tfile" + +test_case "Invalid file, errors followed by valid" +cat >"$tfile" <<EOF +HelloWorld=123 +EnableBans=value +EnableBans=0 +EOF +required_error EINVAL <<EOF +ctdb_tunable_load_file: Unknown tunable "HelloWorld" +ctdb_tunable_load_file: Invalid value "value" for tunable "EnableBans" +EOF +unit_test tunable_test "$tfile" + +test_case "OK, missing file" +rm -f "$tfile" +ok_tunable_defaults +unit_test tunable_test "$tfile" + +test_case "OK, empty file" +: >"$tfile" +ok_tunable_defaults +unit_test tunable_test "$tfile" + +test_case "OK, comments and blanks only" +cat >"$tfile" <<EOF +# This is a comment + +# There are also some blank lines + + +EOF +ok_tunable_defaults +unit_test tunable_test "$tfile" + +test_case "OK, 1 tunable" +cat >"$tfile" <<EOF +EnableBans=0 +EOF +ok_tunable +unit_test tunable_test "$tfile" + +test_case "OK, 1 tunable, hex" +cat >"$tfile" <<EOF +EnableBans=0xf +EOF +ok_tunable +unit_test tunable_test "$tfile" + +test_case "OK, 1 tunable, octal" +cat >"$tfile" <<EOF +EnableBans=072 +EOF +ok_tunable +unit_test tunable_test "$tfile" + +test_case "OK, 1 tunable, tab before =" +cat >"$tfile" <<EOF +EnableBans =0 +EOF +ok_tunable +unit_test tunable_test "$tfile" + +test_case "OK, 1 tunable, space after =" +cat >"$tfile" <<EOF +EnableBans= 0 +EOF +ok_tunable +unit_test tunable_test "$tfile" + +test_case "OK, 2 tunables, multiple spaces around =" +cat >"$tfile" <<EOF +EnableBans = 0 +RecoverInterval = 10 +EOF +ok_tunable +unit_test tunable_test "$tfile" + +test_case "OK, 2 tunables, whitespace everywhere" +cat >"$tfile" <<EOF + EnableBans = 0 + RecoverInterval = 10 +EOF +ok_tunable +unit_test tunable_test "$tfile" + +test_case "OK, several tunables" +cat >"$tfile" <<EOF +EnableBans=0 +RecoverInterval=10 +ElectionTimeout=5 +EOF +ok_tunable +unit_test tunable_test "$tfile" + +test_case "OK, several tunables, varying case" +cat >"$tfile" <<EOF +enablebans=0 +ReCoVerInTeRvAl=10 +ELECTIONTIMEOUT=5 +EOF +ok_tunable +unit_test tunable_test "$tfile" + +test_case "OK, miscellaneous..." +cat >"$tfile" <<EOF +# Leading comment +enablebans=0 +ReCoVerInTeRvAl = 10 + +# Intermediate comment after a blank line + ELECTIONTIMEOUT=25 + + +# Final comment among blanks lines + + + + +EOF +ok_tunable +unit_test tunable_test "$tfile" diff --git a/ctdb/tests/UNIT/eventd/README b/ctdb/tests/UNIT/eventd/README new file mode 100644 index 0000000..742b2c5 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/README @@ -0,0 +1 @@ +Unit tests for event daemon diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/ctdb.conf b/ctdb/tests/UNIT/eventd/etc-ctdb/ctdb.conf new file mode 100644 index 0000000..59bc9bb --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/ctdb.conf @@ -0,0 +1,6 @@ +[logging] + location = file: + log level = DEBUG + +[event] + debug script = debug-script.sh diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/debug-script.sh b/ctdb/tests/UNIT/eventd/etc-ctdb/debug-script.sh new file mode 100755 index 0000000..d54de7e --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/debug-script.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +log="${CTDB_BASE}/debug_script.log" + +case "$2" in +"timeout") + echo "args: $*" > "$log" + ;; + +"verbosetimeout") + (ctdb-event status random $2) > "$log" + ;; + +"verbosetimeout2") + exec > "$log" 2>&1 + ctdb-event status random $2 + ;; + +*) + ;; + +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/03.notalink.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/03.notalink.script new file mode 100644 index 0000000..039e4d0 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/03.notalink.script @@ -0,0 +1,2 @@ +#!/bin/sh +exit 0 diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/README b/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/README new file mode 100644 index 0000000..f38a189 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/README @@ -0,0 +1 @@ +initially empty event scripts directory diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/empty/README b/ctdb/tests/UNIT/eventd/etc-ctdb/events/empty/README new file mode 100644 index 0000000..a5614a9 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/empty/README @@ -0,0 +1 @@ +empty event scripts directory diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/01.test.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/01.test.script new file mode 100755 index 0000000..d16f0de --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/01.test.script @@ -0,0 +1,11 @@ +#!/bin/sh + +case "$1" in +"startup") sleep 5; exit 0 ;; +"monitor") sleep 5; exit 0 ;; +"event1") sleep 1; exit 0 ;; +"event2") sleep 1; exit 0 ;; +"event3") exit 3 ;; +"timeout1") sleep 99 ;; +*) exit 0 ;; +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/02.test.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/02.test.script new file mode 100755 index 0000000..5c841aa --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/02.test.script @@ -0,0 +1,9 @@ +#!/bin/sh + +case "$1" in +"monitor") sleep 1; exit 0 ;; +"event1") exit 1 ;; +"event2") sleep 1; exit 0 ;; +"timeout2") sleep 99 ;; +*) exit 0 ;; +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/03.test.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/03.test.script new file mode 100755 index 0000000..b48b68c --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/03.test.script @@ -0,0 +1,9 @@ +#!/bin/sh + +case "$1" in +"monitor") sleep 1; exit 0 ;; +"event1") sleep 1; exit 0 ;; +"event2") exit 2 ;; +"timeout3") sleep 99 ;; +*) exit 0 ;; +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/01.disabled.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/01.disabled.script new file mode 100644 index 0000000..c52d3c2 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/01.disabled.script @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 0 diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/02.enabled.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/02.enabled.script new file mode 100755 index 0000000..ace80fd --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/02.enabled.script @@ -0,0 +1,49 @@ +#!/bin/sh + +case "$1" in +"monitor") exit 0 ;; +"failure") exit 1 ;; +"timeout") sleep 99 ;; +"verbose") echo "Running event $1" ; exit 0 ;; +"verbosemultiline") + cat <<EOF +Running event $1 +There are multiple output lines + +^^^ including blank lines... + +EOF + exit 0 + ;; +"verbosemultilinenonl") + cat <<EOF +Running event $1 +Multiple output lines + +EOF + printf 'No trailing newline' + exit 0 + ;; +"verbosenewlinesonly") + cat <<EOF + + + +EOF + exit 0 + ;; +"verbosefailure") echo "args: $*"; exit 1 ;; +"verbosemultilinefailure") + cat <<EOF +Failing event $1 +There are multiple output lines + +args: $* + +EOF + exit 2 + ;; +"verbosetimeout") echo "Sleeping for 99 seconds"; sleep 99 ;; +"verbosetimeout2") echo "Sleeping for 99 seconds"; sleep 99 ;; +*) exit 0 ;; +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/README.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/README.script new file mode 100644 index 0000000..9086add --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/README.script @@ -0,0 +1 @@ +Random collection of files and event scripts diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/a.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/a.script new file mode 100755 index 0000000..2bb8d86 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/a.script @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 1 diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/data/01.dummy.script b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/data/01.dummy.script new file mode 100755 index 0000000..9c56f5b --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/data/01.dummy.script @@ -0,0 +1,6 @@ +#!/bin/sh + +case "$1" in +"failure") exit 1 ;; +*) exit 0 ;; +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/data/02.disabled.script b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/data/02.disabled.script new file mode 100755 index 0000000..9c56f5b --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/data/02.disabled.script @@ -0,0 +1,6 @@ +#!/bin/sh + +case "$1" in +"failure") exit 1 ;; +*) exit 0 ;; +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/empty/README b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/empty/README new file mode 100644 index 0000000..a5614a9 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/empty/README @@ -0,0 +1 @@ +empty event scripts directory diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/01.disabled.script b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/01.disabled.script new file mode 100644 index 0000000..c52d3c2 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/01.disabled.script @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 0 diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/02.enabled.script b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/02.enabled.script new file mode 100755 index 0000000..f25e724 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/02.enabled.script @@ -0,0 +1,12 @@ +#!/bin/sh + +case "$1" in +"monitor") exit 0 ;; +"failure") exit 1 ;; +"timeout") sleep 99 ;; +"verbose") echo "Running event $1" ; exit 0 ;; +"verbosefailure") echo "args: $*"; exit 1 ;; +"verbosetimeout") echo "Sleeping for 99 seconds"; sleep 99 ;; +"verbosetimeout2") echo "Sleeping for 99 seconds"; sleep 99 ;; +*) exit 0 ;; +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/README.script b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/README.script new file mode 100644 index 0000000..9086add --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/README.script @@ -0,0 +1 @@ +Random collection of files and event scripts diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/a.script b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/a.script new file mode 100755 index 0000000..2bb8d86 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/a.script @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 1 diff --git a/ctdb/tests/UNIT/eventd/eventd_001.sh b/ctdb/tests/UNIT/eventd/eventd_001.sh new file mode 100755 index 0000000..7d4ee9e --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_001.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "non-existent eventscript directory" + +setup_eventd + +required_error ENOENT <<EOF +Event dir for foobar does not exist +EOF +simple_test status foobar monitor + +required_error ENOENT <<EOF +Event dir for foobar does not exist +EOF +simple_test run 10 foobar monitor + +required_error ENOENT <<EOF +Script 01.test does not exist in foobar +EOF +simple_test script enable foobar 01.test + +required_error ENOENT <<EOF +Command script list finished with result=$(errcode ENOENT) +EOF +simple_test script list foobar diff --git a/ctdb/tests/UNIT/eventd/eventd_002.sh b/ctdb/tests/UNIT/eventd/eventd_002.sh new file mode 100755 index 0000000..f964adf --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_002.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "empty eventscript directory" + +setup_eventd + +required_error EINVAL <<EOF +Event monitor has never run in empty +EOF +simple_test status empty monitor + +ok_null +simple_test run 10 empty monitor + +ok_null +simple_test status empty monitor + +ok_null +simple_test script list empty diff --git a/ctdb/tests/UNIT/eventd/eventd_003.sh b/ctdb/tests/UNIT/eventd/eventd_003.sh new file mode 100755 index 0000000..8625057 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_003.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "eventscript directory with random files" + +setup_eventd + +required_error EINVAL <<EOF +Script README is invalid in random +EOF +simple_test script enable random README + +required_error EINVAL <<EOF +Script a is invalid in random +EOF +simple_test script disable random a + +required_error ENOENT <<EOF +Script 00.foobar does not exist in random +EOF +simple_test script enable random 00.foobar + +required_error EINVAL <<EOF +Event monitor has never run in random +EOF +simple_test status random monitor + +ok_null +simple_test run 10 random monitor + +ok <<EOF +01.disabled DISABLED +02.enabled OK DURATION DATETIME +EOF +simple_test status random monitor + +ok <<EOF + 01.disabled + 02.enabled + + 01.disabled +* 02.enabled +EOF +simple_test script list random diff --git a/ctdb/tests/UNIT/eventd/eventd_004.sh b/ctdb/tests/UNIT/eventd/eventd_004.sh new file mode 100755 index 0000000..fe69d1d --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_004.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "disabled event script" + +setup_eventd + +ok_null +simple_test script disable random 01.disabled + +ok_null +simple_test script disable random 01.disabled + +ok_null +simple_test script enable random 01.disabled + +ok_null +simple_test script disable random 01.disabled + +required_error EINVAL <<EOF +Event monitor has never run in random +EOF +simple_test status random monitor + +ok_null +simple_test run 10 random monitor + +ok <<EOF +01.disabled DISABLED +02.enabled OK DURATION DATETIME +EOF +simple_test status random monitor diff --git a/ctdb/tests/UNIT/eventd/eventd_005.sh b/ctdb/tests/UNIT/eventd/eventd_005.sh new file mode 100755 index 0000000..28f4935 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_005.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "enabled event script" + +setup_eventd + +ok_null +simple_test script enable random 02.enabled + +ok_null +simple_test script enable random 02.enabled + +ok_null +simple_test run 10 random monitor + +ok <<EOF +01.disabled DISABLED +02.enabled OK DURATION DATETIME +EOF +simple_test status random monitor + +ok_null +simple_test script enable random 01.disabled + +ok_null +simple_test run 10 random monitor + +ok <<EOF +01.disabled OK DURATION DATETIME +02.enabled OK DURATION DATETIME +EOF +simple_test status random monitor diff --git a/ctdb/tests/UNIT/eventd/eventd_006.sh b/ctdb/tests/UNIT/eventd/eventd_006.sh new file mode 100755 index 0000000..a7a2d41 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_006.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "failing event script" + +setup_eventd + +required_error ENOEXEC <<EOF +Event failure in random failed +EOF +simple_test run 10 random failure + +required_result 1 <<EOF +01.disabled DISABLED +02.enabled ERROR DURATION DATETIME + OUTPUT: +EOF +simple_test status random failure diff --git a/ctdb/tests/UNIT/eventd/eventd_007.sh b/ctdb/tests/UNIT/eventd/eventd_007.sh new file mode 100755 index 0000000..e8ee403 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_007.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "timing out event script" + +setup_eventd + +required_error ETIMEDOUT <<EOF +Event timeout in random timed out +EOF +simple_test run 5 random timeout + +required_error ETIMEDOUT <<EOF +01.disabled DISABLED +02.enabled TIMEDOUT DATETIME + OUTPUT: +EOF +simple_test status random timeout diff --git a/ctdb/tests/UNIT/eventd/eventd_008.sh b/ctdb/tests/UNIT/eventd/eventd_008.sh new file mode 100755 index 0000000..bd0fc50 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_008.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "capture event script output" + +setup_eventd + +required_result 8 <<EOF +Event verbosefailure in random failed +EOF +simple_test run 10 random verbosefailure with some args + +required_result 1 <<EOF +01.disabled DISABLED +02.enabled ERROR DURATION DATETIME + OUTPUT: args: verbosefailure with some args +EOF +simple_test status random verbosefailure + +ok_null +simple_test run 10 random verbose + +ok <<EOF +01.disabled DISABLED +02.enabled OK DURATION DATETIME + OUTPUT: Running event verbose +EOF +simple_test status random verbose + +ok_null +simple_test run 10 random verbosemultiline + +ok <<EOF +01.disabled DISABLED +02.enabled OK DURATION DATETIME + OUTPUT: + Running event verbosemultiline + There are multiple output lines + + ^^^ including blank lines... +EOF +simple_test status random verbosemultiline + +ok_null +simple_test run 10 random verbosemultilinenonl + +ok <<EOF +01.disabled DISABLED +02.enabled OK DURATION DATETIME + OUTPUT: + Running event verbosemultilinenonl + Multiple output lines + + No trailing newline +EOF +simple_test status random verbosemultilinenonl + +ok_null +simple_test run 10 random verbosenewlinesonly + +ok <<EOF +01.disabled DISABLED +02.enabled OK DURATION DATETIME + OUTPUT: +EOF +simple_test status random verbosenewlinesonly + +required_result 8 <<EOF +Event verbosemultilinefailure in random failed +EOF +simple_test run 10 random verbosemultilinefailure with some args + +required_result 2 <<EOF +01.disabled DISABLED +02.enabled ERROR DURATION DATETIME + OUTPUT: + Failing event verbosemultilinefailure + There are multiple output lines + + args: verbosemultilinefailure with some args +EOF +simple_test status random verbosemultilinefailure diff --git a/ctdb/tests/UNIT/eventd/eventd_009.sh b/ctdb/tests/UNIT/eventd/eventd_009.sh new file mode 100755 index 0000000..39e5cd6 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_009.sh @@ -0,0 +1,155 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "eventscript directory with links" + +setup_eventd + +ok <<EOF + 01.dummy + 02.disabled + + 03.notalink +EOF +simple_test script list data + +# Should be a no-op +ok_null +simple_test script disable data 03.notalink + +ok_null +simple_test run 10 data failure + +ok_null +simple_test script enable data 01.dummy + +required_result 8 <<EOF +Event failure in data failed +EOF +simple_test run 10 data failure + +ok <<EOF +* 01.dummy + 02.disabled + + 03.notalink +EOF +simple_test script list data + +required_result 1 <<EOF +01.dummy ERROR DURATION DATETIME + OUTPUT: +EOF +simple_test status data failure + +ok_null +simple_test run 10 data monitor + +ok <<EOF +01.dummy OK DURATION DATETIME +03.notalink DISABLED +EOF +simple_test status data monitor + +ok_null +simple_test script enable data 03.notalink + +ok <<EOF +* 01.dummy + 02.disabled + +* 03.notalink +EOF +simple_test script list data + +# Local/3rd-party link, not enabled +touch "${CTDB_BASE}/foo" +chmod 644 "${CTDB_BASE}/foo" +abs_base=$(cd "$CTDB_BASE" && echo "$PWD") +ln -s "${abs_base}/foo" "${CTDB_BASE}/events/data/04.locallink.script" + +ok <<EOF +* 01.dummy + 02.disabled + +* 03.notalink + 04.locallink +EOF +simple_test script list data + +ok_null +simple_test script enable data 04.locallink + +required_result 1 "" +unit_test test -x "${CTDB_BASE}/foo" + +ok_null +simple_test script disable data 04.locallink + +ok_null +unit_test test -f "${CTDB_BASE}/foo" + +ok <<EOF +* 01.dummy + 02.disabled + +* 03.notalink +EOF +simple_test script list data + +# Local/3rd-party link, enabled +chmod +x "${CTDB_BASE}/foo" +ln -s "${abs_base}/foo" "${CTDB_BASE}/events/data/04.locallink.script" + +ok <<EOF +* 01.dummy + 02.disabled + +* 03.notalink +* 04.locallink +EOF +simple_test script list data + +ok_null +simple_test script disable data 01.dummy + +ok_null +simple_test script disable data 04.locallink + +ok_null +unit_test test -f "${CTDB_BASE}/foo" + +ok <<EOF + 01.dummy + 02.disabled + +* 03.notalink +EOF +simple_test script list data + +ok_null +simple_test run 10 data failure + +# Local/3rd-party link, dangling +ln -s "${CTDB_BASE}/doesnotexist" "${CTDB_BASE}/events/data/04.locallink.script" + +ok <<EOF + 01.dummy + 02.disabled + +* 03.notalink + 04.locallink +EOF +simple_test script list data + +ok_null +simple_test script disable data 04.locallink + +ok <<EOF + 01.dummy + 02.disabled + +* 03.notalink +EOF +simple_test script list data diff --git a/ctdb/tests/UNIT/eventd/eventd_011.sh b/ctdb/tests/UNIT/eventd/eventd_011.sh new file mode 100755 index 0000000..ce75613 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_011.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "multiple events" + +setup_eventd + +ok_null +simple_test run 10 random monitor + +ok <<EOF +01.disabled DISABLED +02.enabled OK DURATION DATETIME +EOF +simple_test status random monitor + +required_error ENOEXEC <<EOF +Event failure in random failed +EOF +simple_test run 10 random failure + +required_result 1 <<EOF +01.disabled DISABLED +02.enabled ERROR DURATION DATETIME + OUTPUT: +EOF +simple_test status random failure + +required_error ENOEXEC <<EOF +Event verbosefailure in random failed +EOF +simple_test run 10 random verbosefailure + +required_result 1 <<EOF +01.disabled DISABLED +02.enabled ERROR DURATION DATETIME + OUTPUT: args: verbosefailure +EOF +simple_test status random verbosefailure diff --git a/ctdb/tests/UNIT/eventd/eventd_012.sh b/ctdb/tests/UNIT/eventd/eventd_012.sh new file mode 100755 index 0000000..5e6857b --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_012.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "cancel new monitor event" + +setup_eventd + +ok_null +simple_test_background run 10 multi startup + +required_error ECANCELED <<EOF +Event monitor in multi got cancelled +EOF +simple_test run 10 multi monitor + +ok <<EOF +01.test OK DURATION DATETIME +02.test OK DURATION DATETIME +03.test OK DURATION DATETIME +EOF +simple_test status multi startup + +required_error EINVAL <<EOF +Event monitor has never run in multi +EOF +simple_test status multi monitor diff --git a/ctdb/tests/UNIT/eventd/eventd_013.sh b/ctdb/tests/UNIT/eventd/eventd_013.sh new file mode 100755 index 0000000..5bbb4dc --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_013.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "cancel running monitor event" + +setup_eventd + +required_error ECANCELED <<EOF +Event monitor in multi got cancelled +EOF +simple_test_background run 10 multi monitor + +ok_null +simple_test run 10 multi startup + +ok <<EOF +01.test OK DURATION DATETIME +02.test OK DURATION DATETIME +03.test OK DURATION DATETIME +EOF +simple_test status multi startup + +required_error EINVAL <<EOF +Event monitor has never run in multi +EOF +simple_test status multi monitor diff --git a/ctdb/tests/UNIT/eventd/eventd_014.sh b/ctdb/tests/UNIT/eventd/eventd_014.sh new file mode 100755 index 0000000..63b34b4 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_014.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "queue events" + +setup_eventd + +ok_null +simple_test_background run 10 multi queue1 + +ok_null +simple_test run 10 multi queue2 + +ok <<EOF +01.test OK DURATION DATETIME +02.test OK DURATION DATETIME +03.test OK DURATION DATETIME +EOF +simple_test status multi queue1 + +ok <<EOF +01.test OK DURATION DATETIME +02.test OK DURATION DATETIME +03.test OK DURATION DATETIME +EOF +simple_test status multi queue2 diff --git a/ctdb/tests/UNIT/eventd/eventd_021.sh b/ctdb/tests/UNIT/eventd/eventd_021.sh new file mode 100755 index 0000000..935373a --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_021.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "debug script" + +setup_eventd + +result_filter () +{ + _pid="[0-9][0-9]*" + sed -e "s| ${_pid}| PID|" +} + +required_error ETIMEDOUT <<EOF +Event timeout in random timed out +EOF +simple_test run 5 random timeout + +# wait for debug hung script +sleep 5 + +ok <<EOF +args: PID timeout +EOF +unit_test cat "${CTDB_BASE}/debug_script.log" diff --git a/ctdb/tests/UNIT/eventd/eventd_022.sh b/ctdb/tests/UNIT/eventd/eventd_022.sh new file mode 100755 index 0000000..3f1c4f6 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_022.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "status output in debug script" + +setup_eventd + +required_error ETIMEDOUT <<EOF +Event verbosetimeout in random timed out +EOF +simple_test run 5 random verbosetimeout + +# wait for debug hung script +sleep 5 + +ok <<EOF +01.disabled DISABLED +02.enabled TIMEDOUT DATETIME + OUTPUT: Sleeping for 99 seconds +EOF +unit_test cat "${CTDB_BASE}/debug_script.log" diff --git a/ctdb/tests/UNIT/eventd/eventd_023.sh b/ctdb/tests/UNIT/eventd/eventd_023.sh new file mode 100755 index 0000000..8914218 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_023.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "redirected status output in debug script" + +setup_eventd + +required_error ETIMEDOUT <<EOF +Event verbosetimeout2 in random timed out +EOF +simple_test run 5 random verbosetimeout2 + +# wait for debug hung script +sleep 5 + +ok <<EOF +01.disabled DISABLED +02.enabled TIMEDOUT DATETIME + OUTPUT: Sleeping for 99 seconds +EOF +unit_test cat "${CTDB_BASE}/debug_script.log" diff --git a/ctdb/tests/UNIT/eventd/eventd_024.sh b/ctdb/tests/UNIT/eventd/eventd_024.sh new file mode 100755 index 0000000..db68d01 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_024.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "process terminated after debug" + +setup_eventd + +result_filter() +{ + _pid="[0-9][0-9]*" + sed -e "s|${_pid}|PID|" +} + +required_error ETIMEDOUT <<EOF +Event timeout in random timed out +EOF +simple_test run 5 random timeout + +# wait for debug hung script +sleep 5 + +ok <<EOF +args: PID timeout +EOF +unit_test cat "${CTDB_BASE}/debug_script.log" + +pid=$(cat "${CTDB_BASE}/debug_script.log" | awk '{print $2}') + +ok_null +unit_test pstree "$pid" diff --git a/ctdb/tests/UNIT/eventd/eventd_031.sh b/ctdb/tests/UNIT/eventd/eventd_031.sh new file mode 100755 index 0000000..07efa80 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_031.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "multiple scripts" + +setup_eventd + +ok_null +simple_test run 30 multi monitor + +ok <<EOF +01.test OK DURATION DATETIME +02.test OK DURATION DATETIME +03.test OK DURATION DATETIME +EOF +simple_test status multi monitor diff --git a/ctdb/tests/UNIT/eventd/eventd_032.sh b/ctdb/tests/UNIT/eventd/eventd_032.sh new file mode 100755 index 0000000..778acdb --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_032.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "failures with multiple scripts" + +setup_eventd + +required_error ENOEXEC <<EOF +Event event1 in multi failed +EOF +simple_test run 10 multi event1 + +required_result 1 <<EOF +01.test OK DURATION DATETIME +02.test ERROR DURATION DATETIME + OUTPUT: +EOF +simple_test status multi event1 + +required_error ENOEXEC <<EOF +Event event2 in multi failed +EOF +simple_test run 10 multi event2 + +required_result 2 <<EOF +01.test OK DURATION DATETIME +02.test OK DURATION DATETIME +03.test ERROR DURATION DATETIME + OUTPUT: +EOF +simple_test status multi event2 + +required_error ENOEXEC <<EOF +Event event3 in multi failed +EOF +simple_test run 10 multi event3 + +required_result 3 <<EOF +01.test ERROR DURATION DATETIME + OUTPUT: +EOF +simple_test status multi event3 diff --git a/ctdb/tests/UNIT/eventd/eventd_033.sh b/ctdb/tests/UNIT/eventd/eventd_033.sh new file mode 100755 index 0000000..ba99b11 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_033.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "timeouts with multiple scripts" + +setup_eventd + +required_error ETIMEDOUT <<EOF +Event timeout1 in multi timed out +EOF +simple_test run 5 multi timeout1 + +required_error ETIMEDOUT <<EOF +01.test TIMEDOUT DATETIME + OUTPUT: +EOF +simple_test status multi timeout1 + +required_error ETIMEDOUT <<EOF +Event timeout2 in multi timed out +EOF +simple_test run 5 multi timeout2 + +required_error ETIMEDOUT <<EOF +01.test OK DURATION DATETIME +02.test TIMEDOUT DATETIME + OUTPUT: +EOF +simple_test status multi timeout2 + +required_error ETIMEDOUT <<EOF +Event timeout3 in multi timed out +EOF +simple_test run 5 multi timeout3 + +required_error ETIMEDOUT <<EOF +01.test OK DURATION DATETIME +02.test OK DURATION DATETIME +03.test TIMEDOUT DATETIME + OUTPUT: +EOF +simple_test status multi timeout3 diff --git a/ctdb/tests/UNIT/eventd/eventd_041.sh b/ctdb/tests/UNIT/eventd/eventd_041.sh new file mode 100755 index 0000000..ca4a99c --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_041.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "multiple components" + +setup_eventd + +ok_null +simple_test_background run 10 multi monitor + +ok_null +simple_test run 10 random monitor + +ok <<EOF +01.test OK DURATION DATETIME +02.test OK DURATION DATETIME +03.test OK DURATION DATETIME +EOF +simple_test status multi monitor + +ok <<EOF +01.disabled DISABLED +02.enabled OK DURATION DATETIME +EOF +simple_test status random monitor diff --git a/ctdb/tests/UNIT/eventd/eventd_042.sh b/ctdb/tests/UNIT/eventd/eventd_042.sh new file mode 100755 index 0000000..862cf6c --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_042.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "multiple components with failure" + +setup_eventd + +ok_null +simple_test_background run 10 multi monitor + +required_error ENOEXEC <<EOF +Event failure in random failed +EOF +simple_test run 10 random failure + +ok <<EOF +01.test OK DURATION DATETIME +02.test OK DURATION DATETIME +03.test OK DURATION DATETIME +EOF +simple_test status multi monitor + +required_result 1 <<EOF +01.disabled DISABLED +02.enabled ERROR DURATION DATETIME + OUTPUT: +EOF +simple_test status random failure diff --git a/ctdb/tests/UNIT/eventd/eventd_043.sh b/ctdb/tests/UNIT/eventd/eventd_043.sh new file mode 100755 index 0000000..2304d23 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_043.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "multiple components with timeout" + +setup_eventd + +ok_null +simple_test_background run 10 multi monitor + +required_error ETIMEDOUT <<EOF +Event timeout in random timed out +EOF +simple_test run 10 random timeout + +ok <<EOF +01.test OK DURATION DATETIME +02.test OK DURATION DATETIME +03.test OK DURATION DATETIME +EOF +simple_test status multi monitor + +required_error ETIMEDOUT <<EOF +01.disabled DISABLED +02.enabled TIMEDOUT DATETIME + OUTPUT: +EOF +simple_test status random timeout diff --git a/ctdb/tests/UNIT/eventd/eventd_044.sh b/ctdb/tests/UNIT/eventd/eventd_044.sh new file mode 100755 index 0000000..8c0e931 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_044.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "new component" + +setup_eventd + +ok_null +mkdir "${eventd_scriptdir}/foobar" + +ok_null +cp "${eventd_scriptdir}/random/01.disabled.script" "${eventd_scriptdir}/foobar" + +required_result 22 <<EOF +Event monitor has never run in foobar +EOF +simple_test status foobar monitor + +ok_null +simple_test run 10 foobar monitor + +ok <<EOF +01.disabled DISABLED +EOF +simple_test status foobar monitor + +ok_null +simple_test script enable foobar 01.disabled + +ok_null +simple_test run 10 foobar monitor + +ok <<EOF +01.disabled OK DURATION DATETIME +EOF +simple_test status foobar monitor diff --git a/ctdb/tests/UNIT/eventd/eventd_051.sh b/ctdb/tests/UNIT/eventd/eventd_051.sh new file mode 100755 index 0000000..c00cb2e --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_051.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "logging check" + +setup_eventd + +ok_null +simple_test run 10 random verbose + +ok <<EOF +02.enabled: Running event verbose +EOF +unit_test grep "02.enabled:" "$eventd_logfile" diff --git a/ctdb/tests/UNIT/eventd/eventd_052.sh b/ctdb/tests/UNIT/eventd/eventd_052.sh new file mode 100755 index 0000000..75f9572 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/eventd_052.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "run through failure" + +setup_eventd + +export CTDB_EVENT_RUN_ALL=1 + +required_error ENOEXEC <<EOF +Event event1 in multi failed +EOF +simple_test run 10 multi event1 + +required_result 1 <<EOF +01.test OK DURATION DATETIME +02.test ERROR DURATION DATETIME + OUTPUT: +03.test OK DURATION DATETIME +EOF +simple_test status multi event1 + +required_error ENOEXEC <<EOF +Event event2 in multi failed +EOF +simple_test run 10 multi event2 + +required_result 2 <<EOF +01.test OK DURATION DATETIME +02.test OK DURATION DATETIME +03.test ERROR DURATION DATETIME + OUTPUT: +EOF +simple_test status multi event2 diff --git a/ctdb/tests/UNIT/eventd/scripts/local.sh b/ctdb/tests/UNIT/eventd/scripts/local.sh new file mode 100644 index 0000000..04cce63 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/scripts/local.sh @@ -0,0 +1,122 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +. "${TEST_SCRIPTS_DIR}/script_install_paths.sh" + +PATH="$PATH:$CTDB_SCRIPTS_TOOLS_HELPER_DIR" + +if "$CTDB_TEST_VERBOSE" ; then + debug () { echo "$@" ; } +else + debug () { : ; } +fi + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "ctdb-etc" + +ctdb_config=$(ctdb-path config) +eventd_socket=$(ctdb-path socket eventd) +eventd_pidfile=$(ctdb-path pidfile eventd) +eventd_scriptdir=$(ctdb-path etcdir append events) +eventd_logfile="${CTDB_BASE}/eventd.log" + +define_test () +{ + _f=$(basename "$0" ".sh") + + printf "%-28s - %s\n" "$_f" "$1" +} + +cleanup_eventd () +{ + debug "Cleaning up eventd" + + pid=$(cat "$eventd_pidfile" 2>/dev/null || echo) + if [ -n "$pid" ] ; then + kill $pid || true + fi +} + +setup_eventd () +{ + echo "Setting up eventd" + + $VALGRIND ctdb-eventd 2>&1 | tee "$eventd_logfile" & + # Wait till eventd is running + wait_until 10 test -S "$eventd_socket" || \ + die "ctdb_eventd failed to start" + + test_cleanup cleanup_eventd +} + +simple_test_background () +{ + background_log="${CTDB_BASE}/background.log" + background_status="${CTDB_BASE}/background.status" + background_running=1 + + ( + (unit_test ctdb-event "$@") > "$background_log" 2>&1 + echo $? > "$background_status" + ) & + background_pid=$! +} + +background_wait () +{ + [ -n "$background_running" ] || return + + count=0 + while [ ! -s "$background_status" -a $count -lt 30 ] ; do + count=$(( $count + 1 )) + sleep 1 + done + + if [ ! -s "$background_status" ] ; then + kill -9 "$background_pid" + echo TIMEOUT > "$background_status" + fi +} + +background_output () +{ + [ -n "$background_running" ] || return + + bg_status=$(cat "$background_status") + rm -f "$background_status" + echo "--- Background ---" + if [ "$bg_status" = "TIMEOUT" ] ; then + echo "Background process did not complete" + bg_status=1 + else + cat "$background_log" + rm -f "$background_log" + fi + echo "--- Background ---" + unset background_running + [ $bg_status -eq 0 ] || exit $bg_status +} + +simple_test () +{ + (unit_test ctdb-event "$@") + status=$? + + background_wait + background_output + + [ $status -eq 0 ] || exit $status +} + +result_filter () +{ + _duration="\<[0-9][0-9]*\.[0-9][0-9][0-9]\>" + _day="[FMSTW][aehoru][deintu]" + _month="[ADFJMNOS][aceopu][bcglnprtvy]" + _date="[ 0-9][0-9]" + _time="[0-9][0-9]:[0-9][0-9]:[0-9][0-9]" + _year="[0-9][0-9][0-9][0-9]" + _datetime="${_day} ${_month} ${_date} ${_time} ${_year}" + _pid="[0-9][0-9]*" + sed -e "s#${_duration}#DURATION#" \ + -e "s#${_datetime}#DATETIME#" \ + -e "s#,${_pid}#,PID#" +} diff --git a/ctdb/tests/UNIT/eventscripts/00.ctdb.init.001.sh b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.001.sh new file mode 100755 index 0000000..807f3ef --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "TDB check, tdbtool supports check" + +setup + +FAKE_TDBTOOL_SUPPORTS_CHECK="yes" + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/00.ctdb.init.002.sh b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.002.sh new file mode 100755 index 0000000..7ff5385 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.002.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "TDB check, tdbtool does no support check" + +setup + +FAKE_TDBTOOL_SUPPORTS_CHECK="no" + +ok <<EOF +WARNING: The installed 'tdbtool' does not offer the 'check' subcommand. + Using 'tdbdump' for database checks. + Consider updating 'tdbtool' for better checks! +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/00.ctdb.init.003.sh b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.003.sh new file mode 100755 index 0000000..2d1fb0d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.003.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "TDB check, tdbtool supports check, good TDB" + +setup + +FAKE_TDBTOOL_SUPPORTS_CHECK="yes" + +touch "${CTDB_DBDIR}/foo.tdb.0" +FAKE_TDB_IS_OK="yes" + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/00.ctdb.init.004.sh b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.004.sh new file mode 100755 index 0000000..196d7c2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.004.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "TDB check, tdbtool supports check, bad TDB" + +setup + +db="${CTDB_DBDIR}/foo.tdb.0" +touch "$db" +FAKE_TDB_IS_OK="no" + +ok <<EOF +WARNING: database ${db} is corrupted. + Moving to backup ${db}.DATE.TIME.corrupt for later analysis. +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/00.ctdb.init.005.sh b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.005.sh new file mode 100755 index 0000000..3f85de4 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.005.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "TDB check, tdbtool does not support check, good TDB" + +setup + +FAKE_TDBTOOL_SUPPORTS_CHECK="no" + +touch "${CTDB_DBDIR}/foo.tdb.0" +FAKE_TDB_IS_OK="yes" + +ok <<EOF +WARNING: The installed 'tdbtool' does not offer the 'check' subcommand. + Using 'tdbdump' for database checks. + Consider updating 'tdbtool' for better checks! +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/00.ctdb.init.006.sh b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.006.sh new file mode 100755 index 0000000..29794d7 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.006.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "TDB check, tdbtool does not support check, bad TDB" + +setup + +FAKE_TDBTOOL_SUPPORTS_CHECK="no" + +db="${CTDB_DBDIR}/foo.tdb.0" +touch "$db" +FAKE_TDB_IS_OK="no" + +ok <<EOF +WARNING: The installed 'tdbtool' does not offer the 'check' subcommand. + Using 'tdbdump' for database checks. + Consider updating 'tdbtool' for better checks! +WARNING: database ${db} is corrupted. + Moving to backup ${db}.DATE.TIME.corrupt for later analysis. +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/00.ctdb.init.007.sh b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.007.sh new file mode 100755 index 0000000..5121513 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.007.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "TDB check, tdbtool supports check, good persistent TDB" + +setup + +FAKE_TDBTOOL_SUPPORTS_CHECK="yes" + +touch "${CTDB_DBDIR_PERSISTENT}/foo.tdb.0" +FAKE_TDB_IS_OK="yes" + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/00.ctdb.init.008.sh b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.008.sh new file mode 100755 index 0000000..120aefc --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.008.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "TDB check, tdbtool supports check, bad persistent TDB" + +setup + +FAKE_TDBTOOL_SUPPORTS_CHECK="yes" + +db="${CTDB_DBDIR_PERSISTENT}/foo.tdb.0" +touch "$db" +FAKE_TDB_IS_OK="no" + +required_result 1 <<EOF +Persistent database ${db} is corrupted! CTDB will not start. +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/00.ctdb.init.009.sh b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.009.sh new file mode 100755 index 0000000..92a0e25 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.009.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "TDB check, bad TDB multiple times" + +setup + +db="${CTDB_DBDIR}/foo.tdb.0" +FAKE_TDB_IS_OK="no" + +required_result_tdbcheck () +{ + ok <<EOF +WARNING: database ${db} is corrupted. + Moving to backup ${db}.DATE.TIME.corrupt for later analysis. +EOF +} + +# List the corrupt databases +test_num_corrupt () +{ + (cd "$CTDB_DBDIR" && ls foo.tdb.0.*.corrupt) +} + +# Required result is a list of up to 10 corrupt databases +required_result_num_corrupt () +{ + _num="$1" + + if [ "$_num" -gt 10 ] ; then + _num=10 + fi + + _t="" + for _x in $(seq 1 $_num) ; do + _t="${_t:+${_t} +}foo.tdb.0.DATE.TIME.corrupt" + done + + ok "$_t" +} + +for i in $(seq 1 15) ; do + FAKE_SLEEP_REALLY=yes sleep 1 + touch "$db" + required_result_tdbcheck + simple_test + required_result_num_corrupt "$i" + simple_test_command test_num_corrupt +done diff --git a/ctdb/tests/UNIT/eventscripts/01.reclock.init.001.sh b/ctdb/tests/UNIT/eventscripts/01.reclock.init.001.sh new file mode 100755 index 0000000..c495a47 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/01.reclock.init.001.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "unset, check no-op" + +setup "" + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/01.reclock.init.002.sh b/ctdb/tests/UNIT/eventscripts/01.reclock.init.002.sh new file mode 100755 index 0000000..1bd409c --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/01.reclock.init.002.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set to use helper, check no-op" + +setup "!/bin/false" + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/01.reclock.init.003.sh b/ctdb/tests/UNIT/eventscripts/01.reclock.init.003.sh new file mode 100755 index 0000000..a8b6abd --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/01.reclock.init.003.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set to default lock file, directory is created" + +setup + +dir=$(dirname "$CTDB_RECOVERY_LOCK") + +# Ensure directory doesn't exist before +required_result 1 "" +unit_test test -d "$dir" + +ok_null +simple_test + +# Ensure directory exists after +ok_null +unit_test test -d "$dir" diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.001.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.001.sh new file mode 100755 index 0000000..4171f3d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Filesystem use check, error situation, default checks enabled" + +setup + +set_fs_usage 100 +ok <<EOF +WARNING: Filesystem ${CTDB_DBDIR_BASE} utilization 100% >= threshold 90% +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.002.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.002.sh new file mode 100755 index 0000000..4e78a56 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.002.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Filesystem use check, good situation, 1 error check enabled" + +setup + +setup_script_options <<EOF +CTDB_MONITOR_FILESYSTEM_USAGE="/var::80" +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.003.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.003.sh new file mode 100755 index 0000000..41fd914 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.003.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Filesystem use check, error situation, 1 error check enabled" + +setup + +setup_script_options <<EOF +CTDB_MONITOR_FILESYSTEM_USAGE="/var::80" +EOF + +set_fs_usage 90 +required_result 1 <<EOF +ERROR: Filesystem /var utilization 90% >= threshold 80% +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.004.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.004.sh new file mode 100755 index 0000000..3400393 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.004.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Filesystem use check, warn situation, only error check enabled" + +setup + +setup_script_options <<EOF +CTDB_MONITOR_FILESYSTEM_USAGE="/var::80" +EOF + +set_fs_usage 70 +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.005.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.005.sh new file mode 100755 index 0000000..7e1a953 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.005.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Filesystem use check, warn situation, both checks enabled" + +setup + +setup_script_options <<EOF +CTDB_MONITOR_FILESYSTEM_USAGE="/var:80:90" +EOF + +set_fs_usage 85 +ok <<EOF +WARNING: Filesystem /var utilization 85% >= threshold 80% +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.006.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.006.sh new file mode 100755 index 0000000..48008d9 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.006.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Filesystem use check, error situation, both checks enabled" + +setup + +setup_script_options <<EOF +CTDB_MONITOR_FILESYSTEM_USAGE="/var:80:90" +EOF + +set_fs_usage 95 +required_result 1 <<EOF +ERROR: Filesystem /var utilization 95% >= threshold 90% +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.007.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.007.sh new file mode 100755 index 0000000..68b99cf --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.007.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Filesystem use check, good situation, both checks enabled, multiple filesystems" + +setup + +setup_script_options <<EOF +CTDB_MONITOR_FILESYSTEM_USAGE="/var:80:90 /:90:95" +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.011.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.011.sh new file mode 100755 index 0000000..6cd1dab --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.011.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Memory check (default), warning situation" + +setup + +set_mem_usage 100 100 +ok <<EOF +WARNING: System memory utilization 100% >= threshold 80% +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.012.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.012.sh new file mode 100755 index 0000000..9e84056 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.012.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Memory check (custom, both), good situation" + +setup + +setup_script_options <<EOF +CTDB_MONITOR_MEMORY_USAGE="80:90" +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.014.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.014.sh new file mode 100755 index 0000000..9e2b21c --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.014.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Memory check (custom, warning only), warning situation" + +setup + +setup_script_options <<EOF +CTDB_MONITOR_MEMORY_USAGE="85:" +EOF + +set_mem_usage 90 90 +ok <<EOF +WARNING: System memory utilization 90% >= threshold 85% +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.015.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.015.sh new file mode 100755 index 0000000..76b73a3 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.015.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Memory check (custom, error only), error situation" + +setup + +setup_script_options <<EOF +CTDB_MONITOR_MEMORY_USAGE=":85" +EOF + +set_mem_usage 90 90 +required_result 1 <<EOF +ERROR: System memory utilization 90% >= threshold 85% +$FAKE_PROC_MEMINFO +$(ps auxfww) +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.017.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.017.sh new file mode 100755 index 0000000..b2e5029 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.017.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Memory check (custom, both), error situation" + +setup + +setup_script_options <<EOF +CTDB_MONITOR_MEMORY_USAGE="70:80" +EOF + +set_mem_usage 87 87 +required_result 1 <<EOF +ERROR: System memory utilization 87% >= threshold 80% +$FAKE_PROC_MEMINFO +$(ps auxfww) +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/05.system.monitor.018.sh b/ctdb/tests/UNIT/eventscripts/05.system.monitor.018.sh new file mode 100755 index 0000000..427adc6 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/05.system.monitor.018.sh @@ -0,0 +1,82 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Memory check (custom, both), check throttling of warnings" + +setup + +setup_script_options <<EOF +CTDB_MONITOR_MEMORY_USAGE="70:80" +EOF + +# Below threshold, nothing logged +set_mem_usage 67 67 +ok_null +simple_test + +set_mem_usage 71 71 +ok "WARNING: System memory utilization 71% >= threshold 70%" +simple_test + +# 2nd time at same level, nothing logged +set_mem_usage 71 71 +ok_null +simple_test + +set_mem_usage 73 73 +ok "WARNING: System memory utilization 73% >= threshold 70%" +simple_test + +# 2nd time at same level, nothing logged +set_mem_usage 73 73 +ok_null +simple_test + +set_mem_usage 79 79 +ok "WARNING: System memory utilization 79% >= threshold 70%" +simple_test + +set_mem_usage 80 80 +required_result 1 <<EOF +ERROR: System memory utilization 80% >= threshold 80% +$FAKE_PROC_MEMINFO +$(ps auxfww) +EOF +simple_test + +# Fall back into warning at same level as last warning... should log +set_mem_usage 79 79 +ok "WARNING: System memory utilization 79% >= threshold 70%" +simple_test + +# Below threshold, notice +set_mem_usage 69 69 +ok <<EOF +NOTICE: System memory utilization 69% < threshold 70% +EOF +simple_test + +# Further reduction, nothing logged +set_mem_usage 68 68 +ok_null +simple_test + +# Back up into warning at same level as last warning... should log +set_mem_usage 79 79 +ok "WARNING: System memory utilization 79% >= threshold 70%" +simple_test + +# Back up above critical threshold... unhealthy +set_mem_usage 81 81 +required_result 1 <<EOF +ERROR: System memory utilization 81% >= threshold 80% +$FAKE_PROC_MEMINFO +$(ps auxfww) +EOF +simple_test + +# Straight back down to a good level... notice +set_mem_usage 65 65 +ok "NOTICE: System memory utilization 65% < threshold 70%" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/06.nfs.releaseip.001.sh b/ctdb/tests/UNIT/eventscripts/06.nfs.releaseip.001.sh new file mode 100755 index 0000000..0546863 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/06.nfs.releaseip.001.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout succeeds" + +setup + +setup_nfs_callout + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/06.nfs.releaseip.002.sh b/ctdb/tests/UNIT/eventscripts/06.nfs.releaseip.002.sh new file mode 100755 index 0000000..dc44d2d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/06.nfs.releaseip.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout causes releaseip-pre to fail" + +setup + +setup_nfs_callout "releaseip-pre" + +required_result 1 "releaseip-pre" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/06.nfs.takeip.001.sh b/ctdb/tests/UNIT/eventscripts/06.nfs.takeip.001.sh new file mode 100755 index 0000000..0546863 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/06.nfs.takeip.001.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout succeeds" + +setup + +setup_nfs_callout + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/06.nfs.takeip.002.sh b/ctdb/tests/UNIT/eventscripts/06.nfs.takeip.002.sh new file mode 100755 index 0000000..c9f3db9 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/06.nfs.takeip.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout causes takeip-pre to fail" + +setup + +setup_nfs_callout "takeip-pre" + +required_result 1 "takeip-pre" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.010.sh b/ctdb/tests/UNIT/eventscripts/10.interface.010.sh new file mode 100755 index 0000000..171a697 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.010.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Release 1 IP, 10 connections killed OK" + +setup + +ctdb_get_1_public_address | +while read dev ip bits ; do + ok_null + simple_test_event "takeip" $dev $ip $bits + + count=10 + setup_tcp_connections $count \ + "$ip" 445 10.254.254.0 12300 + + ok <<EOF +Killed ${count}/${count} TCP connections to released IP $ip +EOF + + simple_test_event "releaseip" $dev $ip $bits +done diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.011.sh b/ctdb/tests/UNIT/eventscripts/10.interface.011.sh new file mode 100755 index 0000000..7f4302d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.011.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Release 1 IP, 10 connections killed, 1 fails" + +setup + +ctdb_get_1_public_address | +while read dev ip bits ; do + ok_null + simple_test_event "takeip" $dev $ip $bits + + count=10 + setup_tcp_connections $count \ + "$ip" 445 10.254.254.0 12300 + + setup_tcp_connections_unkillable 1 \ + "$ip" 445 10.254.254.0 43210 + + ok <<EOF +Killed 10/11 TCP connections to released IP ${ip} +Remaining connections: + ${ip}:445 10.254.254.1:43211 +EOF + + simple_test_event "releaseip" $dev $ip $bits +done diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.012.sh b/ctdb/tests/UNIT/eventscripts/10.interface.012.sh new file mode 100755 index 0000000..2ef0fe6 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.012.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Release 1 IP, 10 connections killed, 3 fail" + +setup + +ctdb_get_1_public_address | +while read dev ip bits ; do + ok_null + simple_test_event "takeip" $dev $ip $bits + + count=10 + + setup_tcp_connections $count \ + "$ip" 445 10.254.254.0 12300 + + setup_tcp_connections_unkillable 3 \ + "$ip" 445 10.254.254.0 43210 + + ok <<EOF +Killed 10/13 TCP connections to released IP ${ip} +Remaining connections: + ${ip}:445 10.254.254.1:43211 + ${ip}:445 10.254.254.2:43212 + ${ip}:445 10.254.254.3:43213 +EOF + + simple_test_event "releaseip" $dev $ip $bits +done diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.013.sh b/ctdb/tests/UNIT/eventscripts/10.interface.013.sh new file mode 100755 index 0000000..e9a4c30 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.013.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Release 1 IP, all 10 connections kills fail" + +setup + +ctdb_get_1_public_address | +while read dev ip bits ; do + ok_null + simple_test_event "takeip" $dev $ip $bits + + setup_tcp_connections 0 + + count=10 + setup_tcp_connections_unkillable $count \ + "$ip" 445 10.254.254.0 43210 + + ok <<EOF +Killed 0/$count TCP connections to released IP ${ip} +Remaining connections: + ${ip}:445 10.254.254.1:43211 + ${ip}:445 10.254.254.2:43212 + ${ip}:445 10.254.254.3:43213 + ${ip}:445 10.254.254.4:43214 + ${ip}:445 10.254.254.5:43215 + ${ip}:445 10.254.254.6:43216 + ${ip}:445 10.254.254.7:43217 + ${ip}:445 10.254.254.8:43218 + ${ip}:445 10.254.254.9:43219 + ${ip}:445 10.254.254.10:43220 +EOF + + simple_test_event "releaseip" $dev $ip $bits +done diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.init.001.sh b/ctdb/tests/UNIT/eventscripts/10.interface.init.001.sh new file mode 100755 index 0000000..7f370b2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.init.001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "no public addresses" + +setup + +rm -f "${CTDB_BASE}/public_addresses" + +ok "No public addresses file found" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.init.002.sh b/ctdb/tests/UNIT/eventscripts/10.interface.init.002.sh new file mode 100755 index 0000000..1862eac --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.init.002.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all interfaces up" + +setup + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.init.021.sh b/ctdb/tests/UNIT/eventscripts/10.interface.init.021.sh new file mode 100755 index 0000000..fd89c87 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.init.021.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Check public IP dropping, none assigned" + +setup + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.init.022.sh b/ctdb/tests/UNIT/eventscripts/10.interface.init.022.sh new file mode 100755 index 0000000..ee7fa14 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.init.022.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Check public IP dropping, 1 assigned" + +setup + +ctdb_get_1_public_address | +while read dev ip bits ; do + ip addr add "${ip}/${bits}" dev "$dev" + + ok <<EOF +Removing public address ${ip}/${bits} from device ${dev} +EOF + + simple_test +done diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.init.023.sh b/ctdb/tests/UNIT/eventscripts/10.interface.init.023.sh new file mode 100755 index 0000000..b39b67a --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.init.023.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Check public IP dropping, all assigned" + +setup + +nl=" +" +ctdb_get_my_public_addresses | { + out="" + while read dev ip bits ; do + ip addr add "${ip}/${bits}" dev "$dev" + + msg="Removing public address ${ip}/${bits} from device ${dev}" + out="${out}${out:+${nl}}${msg}" + done + + ok "$out" + + simple_test +} diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.001.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.001.sh new file mode 100755 index 0000000..c829efc --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "no public addresses" + +setup + +rm -f "${CTDB_BASE}/public_addresses" + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.002.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.002.sh new file mode 100755 index 0000000..1862eac --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.002.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all interfaces up" + +setup + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.003.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.003.sh new file mode 100755 index 0000000..db1b2c6 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.003.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 interface down" + +setup + +iface=$(ctdb_get_1_interface) + +ethtool_interfaces_down $iface + +required_result 1 "ERROR: No link on the public network interface $iface" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.004.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.004.sh new file mode 100755 index 0000000..3f20fdc --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.004.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all interfaces up, 1 is a bond" + +setup + +iface=$(ctdb_get_1_interface) + +setup_bond $iface + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.005.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.005.sh new file mode 100755 index 0000000..1042d15 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.005.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 bond, no active slaves" + +setup + +iface=$(ctdb_get_1_interface) + +setup_bond $iface "None" + +required_result 1 "ERROR: No active slaves for bond device $iface" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.006.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.006.sh new file mode 100755 index 0000000..5facf08 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.006.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 bond, active slaves, link down" + +setup + +iface=$(ctdb_get_1_interface) + +setup_bond $iface "" "down" + +required_result 1 "ERROR: public network interface $iface is down" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.009.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.009.sh new file mode 100755 index 0000000..93ed68b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.009.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "CTDB_PARTIALLY_ONLINE_INTERFACES, 1 down" + +setup + +iface=$(ctdb_get_1_interface) + +setup_script_options <<EOF +CTDB_PARTIALLY_ONLINE_INTERFACES=yes +EOF + +ethtool_interfaces_down "$iface" + +ok "ERROR: No link on the public network interface $iface" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.010.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.010.sh new file mode 100755 index 0000000..5287893 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.010.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "CTDB_PARTIALLY_ONLINE_INTERFACES, all down" + +setup + +ifaces=$(ctdb_get_interfaces) + +setup_script_options <<EOF +CTDB_PARTIALLY_ONLINE_INTERFACES=yes +EOF + +ethtool_interfaces_down $ifaces + +msg=$( + for i in $ifaces ; do + echo "ERROR: No link on the public network interface $i" + done + ) + +required_result 1 "$msg" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.011.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.011.sh new file mode 100755 index 0000000..824bd32 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.011.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "CTDB_PARTIALLY_ONLINE_INTERFACES, 1 bond down" + +setup + +iface=$(ctdb_get_1_interface) + +setup_bond $iface "None" + +setup_script_options <<EOF +CTDB_PARTIALLY_ONLINE_INTERFACES=yes +EOF + +ethtool_interfaces_down "$iface" + +ok "ERROR: No active slaves for bond device $iface" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.012.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.012.sh new file mode 100755 index 0000000..1315980 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.012.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "CTDB_PARTIALLY_ONLINE_INTERFACES, 1 bond down" + +setup + +ifaces=$(ctdb_get_interfaces) + +for i in $ifaces ; do + setup_bond $i "None" +done + +setup_script_options <<EOF +CTDB_PARTIALLY_ONLINE_INTERFACES=yes +EOF + +ethtool_interfaces_down $ifaces + +msg=$( + for i in $ifaces ; do + echo "ERROR: No active slaves for bond device $i" + done + ) + +required_result 1 "$msg" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.013.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.013.sh new file mode 100755 index 0000000..2aa0a8e --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.013.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 bond, active slaves, link down" + +setup + +iface=$(ctdb_get_1_interface) + +setup_bond $iface "" "up" "down" + +required_result 1 "ERROR: No active slaves for 802.ad bond device $iface" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.014.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.014.sh new file mode 100755 index 0000000..1dd8ff0 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.014.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "spurious addresses on interface, no action" + +setup + +iface=$(ctdb_get_1_interface) + +ip addr add 192.168.253.253/24 dev $iface +ip addr add 192.168.254.254/24 dev $iface + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.015.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.015.sh new file mode 100755 index 0000000..b7b4787 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.015.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Missing interface, fail" + +setup + +iface=$(ctdb_get_1_interface) +ip link delete "$iface" + +required_result 1 <<EOF +ERROR: Monitored interface dev123 does not exist +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.016.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.016.sh new file mode 100755 index 0000000..bd7f302 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.016.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Missing interface, CTDB_PARTIALLY_ONLINE_INTERFACES=yes, warn" + +setup + +setup_script_options <<EOF +CTDB_PARTIALLY_ONLINE_INTERFACES=yes +EOF + +iface=$(ctdb_get_1_interface) +ip link delete "$iface" + +ok <<EOF +ERROR: Monitored interface dev123 does not exist +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.017.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.017.sh new file mode 100755 index 0000000..bae0886 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.017.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 VLAN, link down" + +setup + +iface=$(ctdb_get_1_interface) + +ethtool_interfaces_down "$iface" + +# This just exercises the VLAN checking code, which will allow us to +# determine that real0 is not a bond. +realiface="real0" +ip link add link "$realiface" name "$iface" type vlan id 11 +ip link set "${iface}@${realiface}" up + +required_result 1 "ERROR: No link on the public network interface ${iface}" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.monitor.018.sh b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.018.sh new file mode 100755 index 0000000..8006d92 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.monitor.018.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "VLAN on bond, active slaves, link down" + +setup + +iface=$(ctdb_get_1_interface) + +bond="bond0" + +setup_bond "$bond" "" "down" + +ip link add link "$bond" name "$iface" type vlan id 11 +ip link set "${iface}@${bond}" up + +required_result 1 "ERROR: public network interface ${bond} is down" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.multi.001.sh b/ctdb/tests/UNIT/eventscripts/10.interface.multi.001.sh new file mode 100755 index 0000000..867cc24 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.multi.001.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "takeip, removeip" + +setup + +public_address=$(ctdb_get_1_public_address) + +ok_null + +simple_test_event "takeip" $public_address +simple_test_event "releaseip" $public_address diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.releaseip.001.sh b/ctdb/tests/UNIT/eventscripts/10.interface.releaseip.001.sh new file mode 100755 index 0000000..2ac0b86 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.releaseip.001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "error - no args given" + +setup + +iface=$(ctdb_get_1_interface) + +required_result 1 "ERROR: must supply interface, IP and maskbits" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.releaseip.002.sh b/ctdb/tests/UNIT/eventscripts/10.interface.releaseip.002.sh new file mode 100755 index 0000000..a60adbd --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.releaseip.002.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "error - remove a non-existent ip" + +setup + +public_address=$(ctdb_get_1_public_address) +ip="${public_address% *}" ; ip="${ip#* }" + +required_result 1 "ERROR: Unable to determine interface for IP ${ip}" + +simple_test $public_address diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.startup.001.sh b/ctdb/tests/UNIT/eventscripts/10.interface.startup.001.sh new file mode 100755 index 0000000..c829efc --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.startup.001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "no public addresses" + +setup + +rm -f "${CTDB_BASE}/public_addresses" + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.startup.002.sh b/ctdb/tests/UNIT/eventscripts/10.interface.startup.002.sh new file mode 100755 index 0000000..1862eac --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.startup.002.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all interfaces up" + +setup + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.takeip.001.sh b/ctdb/tests/UNIT/eventscripts/10.interface.takeip.001.sh new file mode 100755 index 0000000..2ac0b86 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.takeip.001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "error - no args given" + +setup + +iface=$(ctdb_get_1_interface) + +required_result 1 "ERROR: must supply interface, IP and maskbits" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.takeip.002.sh b/ctdb/tests/UNIT/eventscripts/10.interface.takeip.002.sh new file mode 100755 index 0000000..e267f16 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.takeip.002.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "add an ip" + +setup + +public_address=$(ctdb_get_1_public_address) + +ok_null + +simple_test $public_address diff --git a/ctdb/tests/UNIT/eventscripts/10.interface.takeip.003.sh b/ctdb/tests/UNIT/eventscripts/10.interface.takeip.003.sh new file mode 100755 index 0000000..acb9b04 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/10.interface.takeip.003.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "error - add same IP twice" + +setup + +public_address=$(ctdb_get_1_public_address) +dev="${public_address%% *}" +t="${public_address#* }" +ip="${t% *}" +bits="${t#* }" + +ok_null +simple_test $public_address + +required_result 1 <<EOF +RTNETLINK answers: File exists +Failed to add $ip/$bits on dev $dev +EOF +simple_test $public_address diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.001.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.001.sh new file mode 100755 index 0000000..06b2cd3 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.001.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "not configured" + +setup + +ok_null +simple_test_event "ipreallocate" + +check_routes 0 diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.002.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.002.sh new file mode 100755 index 0000000..90b1399 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.002.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "missing config file" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +rm -f "$CTDB_NATGW_NODES" + +required_result 1 <<EOF +error: CTDB_NATGW_NODES=${CTDB_NATGW_NODES} unreadable +EOF + +for i in "startup" "ipreallocated" ; do + simple_test_event "$i" +done diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.003.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.003.sh new file mode 100755 index 0000000..370c10d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.003.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "CTDB_NATGW_PUBLIC_IFACE unset, not follower-only" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +setup_script_options <<EOF +CTDB_NATGW_PUBLIC_IFACE="" +EOF + +required_result 1 "Invalid configuration: CTDB_NATGW_PUBLIC_IFACE not set" + +for i in "startup" "ipreallocated" ; do + simple_test_event "$i" +done diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.004.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.004.sh new file mode 100755 index 0000000..0f06be1 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.004.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "CTDB_NATGW_PUBLIC_IP unset, not follower-only" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +setup_script_options <<EOF +CTDB_NATGW_PUBLIC_IP="" +EOF + +required_result 1 "Invalid configuration: CTDB_NATGW_PUBLIC_IP not set" + +for i in "startup" "ipreallocated" ; do + simple_test_event "$i" +done diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.011.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.011.sh new file mode 100755 index 0000000..407f049 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.011.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "leader node, basic configuration" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +ok_null +simple_test_event "ipreallocated" + +ok "default via ${CTDB_NATGW_DEFAULT_GATEWAY} dev ethXXX metric 10 " +simple_test_command ip route show + +ok_natgw_leader_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.012.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.012.sh new file mode 100755 index 0000000..fdec8ee --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.012.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "follower node, basic configuration" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 +192.168.1.22 leader +192.168.1.23 +192.168.1.24 +EOF + +ok_null +simple_test_event "ipreallocated" + +ok "default via ${FAKE_CTDB_NATGW_LEADER} dev ethXXX metric 10 " +simple_test_command ip route show + +ok_natgw_follower_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.013.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.013.sh new file mode 100755 index 0000000..cb9af46 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.013.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "leader node, no gateway" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +setup_script_options <<EOF +CTDB_NATGW_DEFAULT_GATEWAY="" +EOF + +ok_null +simple_test_event "ipreallocated" + +ok_null +simple_test_command ip route show + +ok_natgw_leader_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.014.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.014.sh new file mode 100755 index 0000000..0fc3ccc --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.014.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "follower node, no gateway" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 +192.168.1.22 leader +192.168.1.23 +192.168.1.24 +EOF + +setup_script_options <<EOF +CTDB_NATGW_DEFAULT_GATEWAY="" +EOF + +ok_null +simple_test_event "ipreallocated" + +ok "default via ${FAKE_CTDB_NATGW_LEADER} dev ethXXX metric 10 " +simple_test_command ip route show + +ok_natgw_follower_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.015.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.015.sh new file mode 100755 index 0000000..84cc17b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.015.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "basic configuration, multiple transitions" + +setup + +echo "*** Leader node..." + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +ok_null +simple_test_event "ipreallocated" + +ok "default via ${CTDB_NATGW_DEFAULT_GATEWAY} dev ethXXX metric 10 " +simple_test_command ip route show + +ok_natgw_leader_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" + +echo "*** Follower node..." + +setup_ctdb_natgw <<EOF +192.168.1.21 +192.168.1.22 leader +192.168.1.23 +192.168.1.24 +EOF + +ok_null +simple_test_event "ipreallocated" + +ok "default via ${FAKE_CTDB_NATGW_LEADER} dev ethXXX metric 10 " +simple_test_command ip route show + +ok_natgw_follower_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" + +echo "*** Leader node again..." + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +ok_null +simple_test_event "ipreallocated" + +ok "default via ${CTDB_NATGW_DEFAULT_GATEWAY} dev ethXXX metric 10 " +simple_test_command ip route show + +ok_natgw_leader_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.021.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.021.sh new file mode 100755 index 0000000..7d73c37 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.021.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "leader node, static routes" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +setup_script_options<<EOF +CTDB_NATGW_STATIC_ROUTES="10.1.1.0/24 10.1.2.0/24" +EOF + +ok_null +simple_test_event "ipreallocated" + +ok_natgw_leader_static_routes +simple_test_command ip route show + +ok_natgw_leader_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.022.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.022.sh new file mode 100755 index 0000000..2a4dd47 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.022.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "follower node, static routes" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 +192.168.1.22 leader +192.168.1.23 +192.168.1.24 +EOF + +setup_script_options <<EOF +CTDB_NATGW_STATIC_ROUTES="10.1.1.0/24 10.1.2.0/24" +EOF + +ok_null +simple_test_event "ipreallocated" + +ok_natgw_follower_static_routes +simple_test_command ip route show + +ok_natgw_follower_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.023.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.023.sh new file mode 100755 index 0000000..9fdf734 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.023.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "leader node, static routes, custom gateway" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +setup_script_options <<EOF +CTDB_NATGW_STATIC_ROUTES="10.1.1.0/24 10.1.2.0/24@10.1.1.253" +EOF + +ok_null +simple_test_event "ipreallocated" + +ok_natgw_leader_static_routes +simple_test_command ip route show + +ok_natgw_leader_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.024.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.024.sh new file mode 100755 index 0000000..24f677d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.024.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "follower node, static routes, custom gateway" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 +192.168.1.22 leader +192.168.1.23 +192.168.1.24 +EOF + +setup_script_options <<EOF +CTDB_NATGW_STATIC_ROUTES="10.1.1.0/24 10.1.2.0/24@10.1.1.253" +EOF + +ok_null +simple_test_event "ipreallocated" + +ok_natgw_follower_static_routes +simple_test_command ip route show + +ok_natgw_follower_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.025.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.025.sh new file mode 100755 index 0000000..d4221c2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.025.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "static routes, custom gateway, multiple transitions" + +setup + +setup_script_options <<EOF +CTDB_NATGW_STATIC_ROUTES="10.1.1.0/24 10.1.2.0/24@10.1.1.253" +EOF + +echo "*** Leader node..." + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +ok_null +simple_test_event "ipreallocated" + +ok_natgw_leader_static_routes +simple_test_command ip route show + +ok_natgw_leader_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" + +echo "*** Follower node..." + +setup_ctdb_natgw <<EOF +192.168.1.21 +192.168.1.22 leader +192.168.1.23 +192.168.1.24 +EOF + +ok_null +simple_test_event "ipreallocated" + +ok_natgw_follower_static_routes +simple_test_command ip route show + +ok_natgw_follower_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" + +echo "*** Leader node again..." + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +ok_null +simple_test_event "ipreallocated" + +ok_natgw_leader_static_routes +simple_test_command ip route show + +ok_natgw_leader_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.031.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.031.sh new file mode 100755 index 0000000..6a5bcad --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.031.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "leader node, static routes, custom gateway, config change" + +setup + +setup_script_options <<EOF +CTDB_NATGW_STATIC_ROUTES="10.1.1.0/24 10.1.2.0/24@10.1.1.253" +EOF + +echo "##################################################" +echo "Static routes..." + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +ok_null +simple_test_event "ipreallocated" + +ok_natgw_leader_static_routes +simple_test_command ip route show + +ok_natgw_leader_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" + +echo "##################################################" +echo "Default routes..." + +setup_script_options <<EOF +CTDB_NATGW_STATIC_ROUTES="" +EOF + +ok "NAT gateway configuration has changed" +simple_test_event "ipreallocated" + +ok "default via ${CTDB_NATGW_DEFAULT_GATEWAY} dev ethXXX metric 10 " +simple_test_command ip route show + +ok_natgw_leader_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" + +echo "##################################################" +echo "Static routes again..." + +setup_script_options <<EOF +CTDB_NATGW_STATIC_ROUTES="10.1.3.0/24 10.1.4.4/32 10.1.2.0/24@10.1.1.252" +EOF + +ok "NAT gateway configuration has changed" +simple_test_event "ipreallocated" + +ok_natgw_leader_static_routes +simple_test_command ip route show + +ok_natgw_leader_ip_addr_show +simple_test_command ip addr show "$CTDB_NATGW_PUBLIC_IFACE" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.041.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.041.sh new file mode 100755 index 0000000..1cbe5b3 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.041.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "follower-only, CTDB_NATGW_PUBLIC_IFACE unset" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 follower-only +192.168.1.22 leader +192.168.1.23 +192.168.1.24 +EOF + +setup_script_options <<EOF +CTDB_NATGW_PUBLIC_IFACE="" +EOF + +ok_null +simple_test_event "ipreallocated" + +ok "default via ${FAKE_CTDB_NATGW_LEADER} dev ethXXX metric 10 " +simple_test_command ip route show diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.042.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.042.sh new file mode 100755 index 0000000..b643fd3 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.042.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "follower-only, CTDB_NATGW_PUBLIC_IP unset" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 follower-only +192.168.1.22 leader +192.168.1.23 +192.168.1.24 +EOF + +setup_script_options <<EOF +CTDB_NATGW_PUBLIC_IFACE="" +CTDB_NATGW_PUBLIC_IP="" +EOF + +ok_null +simple_test_event "ipreallocated" + +ok "default via ${FAKE_CTDB_NATGW_LEADER} dev ethXXX metric 10 " +simple_test_command ip route show diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.051.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.051.sh new file mode 100755 index 0000000..6c711c0 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.051.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Monitor CTDB_NATGW_PUBLIC_IFACE, follower, up" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 +192.168.1.22 leader +192.168.1.23 +192.168.1.24 +EOF + +ok_null +simple_test_event "monitor" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.052.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.052.sh new file mode 100755 index 0000000..ad02003 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.052.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Monitor CTDB_NATGW_PUBLIC_IFACE, follower, down" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 +192.168.1.22 leader +192.168.1.23 +192.168.1.24 +EOF + +ethtool_interfaces_down "$CTDB_NATGW_PUBLIC_IFACE" + +required_result 1 <<EOF +ERROR: No link on the public network interface ${CTDB_NATGW_PUBLIC_IFACE} +EOF +simple_test_event "monitor" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.053.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.053.sh new file mode 100755 index 0000000..e9bded1 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.053.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Monitor CTDB_NATGW_PUBLIC_IFACE, leader, up" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +ok_null +simple_test_event "monitor" diff --git a/ctdb/tests/UNIT/eventscripts/11.natgw.054.sh b/ctdb/tests/UNIT/eventscripts/11.natgw.054.sh new file mode 100755 index 0000000..2a79cde --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/11.natgw.054.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Monitor CTDB_NATGW_PUBLIC_IFACE, leader, down" + +setup + +setup_ctdb_natgw <<EOF +192.168.1.21 leader +192.168.1.22 +192.168.1.23 +192.168.1.24 +EOF + +ethtool_interfaces_down "$CTDB_NATGW_PUBLIC_IFACE" + +required_result 1 <<EOF +ERROR: No link on the public network interface ${CTDB_NATGW_PUBLIC_IFACE} +EOF +simple_test_event "monitor" diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.001.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.001.sh new file mode 100755 index 0000000..55c8c64 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.001.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "not configured" + +setup + +setup_script_options <<EOF +CTDB_PER_IP_ROUTING_CONF="" +EOF + +ok_null +simple_test_event "takeip" + +ok_null +simple_test_event "ipreallocate" + +check_routes 0 diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.002.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.002.sh new file mode 100755 index 0000000..6925983 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.002.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "missing config file" + +setup + +# Error because policy routing is configured but the configuration +# file is missing. +required_result 1 <<EOF +error: CTDB_PER_IP_ROUTING_CONF=${CTDB_BASE}/policy_routing file not found +EOF + +for i in "startup" "ipreallocated" "monitor" ; do + simple_test_event "$i" +done + diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.003.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.003.sh new file mode 100755 index 0000000..4eac963 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.003.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "empty config, ipreallocated" + +setup + +create_policy_routing_config 0 + +# ipreallocated should silently add any missing routes +ok_null +simple_test_event "ipreallocated" + +# empty configuration file should mean there are no routes +check_routes 0 diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.004.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.004.sh new file mode 100755 index 0000000..3724de0 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.004.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "empty config, takeip" + +setup + +create_policy_routing_config 0 + +public_address=$(ctdb_get_1_public_address) + +ok_null +simple_test_event "takeip" $public_address + +# empty configuration file should mean there are no routes +check_routes 0 diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.005.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.005.sh new file mode 100755 index 0000000..baafbbb --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.005.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 IP configured, takeip" + +setup + +# Configuration for 1 IP +create_policy_routing_config 1 default + +# takeip should add routes for the given address +ctdb_get_1_public_address | +while read dev ip bits ; do + ok_null + simple_test_event "takeip" $dev $ip $bits +done + +# Should have routes for 1 IP +check_routes 1 default diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.006.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.006.sh new file mode 100755 index 0000000..6c4d686 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.006.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 IP configured, takeip, releaseip" + +setup + +# create config for 1 IP +create_policy_routing_config 1 default + +ctdb_get_1_public_address | +while read dev ip bits ; do + # takeip adds routes + ok_null + simple_test_event "takeip" $dev $ip $bits + + # releaseip removes routes + ok_null + simple_test_event "releaseip" $dev $ip $bits +done + +# should have no routes +check_routes 0 diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.007.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.007.sh new file mode 100755 index 0000000..4cf46e6 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.007.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 IP configured, ipreallocated" + +setup + +# create config for 1 IP +create_policy_routing_config 1 default + +# no takeip, but ipreallocated should add any missing routes +ok_null +simple_test_event "ipreallocated" + +# should have routes for 1 IP +check_routes 1 default diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.008.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.008.sh new file mode 100755 index 0000000..889b4c4 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.008.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 IP configured, takeip twice" + +setup + +# create config for 1 IP +create_policy_routing_config 1 default + +ctdb_get_1_public_address | +while read dev ip bits ; do + ok_null + simple_test_event "takeip" $dev $ip $bits + + # 2nd takeip event for the same IP should be a no-op + ok_null + simple_test_event "takeip" $dev $ip $bits +done + +# should be routes for 1 IP +check_routes 1 default diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.009.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.009.sh new file mode 100755 index 0000000..c887feb --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.009.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "All IPs configured, takeip 1 address" + +setup + +# configure all addresses +create_policy_routing_config all default + +# add routes for all 1 IP +ctdb_get_1_public_address | +while read dev ip bits ; do + ok_null + simple_test_event "takeip" $dev $ip $bits +done + +# for 1 IP +check_routes 1 default diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.010.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.010.sh new file mode 100755 index 0000000..7297f96 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.010.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "All IPs configured, takeip on all nodes" + +setup + +# create config for all IPs +create_policy_routing_config all default + +ctdb_get_my_public_addresses | +while read dev ip bits ; do + ok_null + simple_test_event "takeip" $dev $ip $bits +done + +# should have routes for all IPs +check_routes all default diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.011.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.011.sh new file mode 100755 index 0000000..8d96c8d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.011.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "__auto_link_local__, takeip all on node" + +setup + +# do link local fu instead of creating configuration +setup_script_options <<EOF +CTDB_PER_IP_ROUTING_CONF="__auto_link_local__" +EOF + +# add routes for all addresses +ctdb_get_my_public_addresses | +while read dev ip bits ; do + ok_null + simple_test_event "takeip" $dev $ip $bits +done + +check_routes all diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.012.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.012.sh new file mode 100755 index 0000000..48aab21 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.012.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.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 + +create_policy_routing_config 1 default + +ctdb_get_1_public_address | +while read dev ip bits ; do + ok_null + simple_test_event "takeip" $dev $ip $bits + + ok_null + ctdb moveip $ip 1 + simple_test_event "releaseip" $dev $ip $bits + + ok_null + simple_test_event "ipreallocated" +done + +# all routes should have been removed and not added back +check_routes 0 diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.013.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.013.sh new file mode 100755 index 0000000..2262083 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.013.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 IP configured, releaseip of unassigned" + +setup + +create_policy_routing_config 1 default + +ctdb_get_1_public_address | +while read dev ip bits ; do + ok <<EOF +WARNING: Failed to delete policy routing rule + Command "ip rule del from $ip pref $CTDB_PER_IP_ROUTING_RULE_PREF table ctdb.$ip" failed: + RTNETLINK answers: No such file or directory +EOF + + simple_test_event "releaseip" $dev $ip $bits +done + +# there should be no routes +check_routes 0 diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.014.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.014.sh new file mode 100755 index 0000000..a63e134 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.014.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 IP configured, takeip, moveip, ipreallocated" + +# We move the IP to another node but don't run releaseip. +# ipreallocated should remove the bogus routes. + +setup + +create_policy_routing_config 1 default + +ctdb_get_1_public_address | +while read dev ip bits ; do + ok_null + # Set up the routes for an IP that we have + simple_test_event "takeip" $dev $ip $bits + + # Now move that IPs but don't run the associated "releaseip" + ctdb moveip $ip 1 + + # This should handle removal of the routes + ok "Removing ip rule/routes for unhosted public address $ip" + simple_test_event "ipreallocated" +done + +# no routes left +check_routes 0 diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.015.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.015.sh new file mode 100755 index 0000000..742cfd4 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.015.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 IP configured, releaseip of unassigned" + +setup + +export IP_ROUTE_BAD_TABLE_ID=true + +create_policy_routing_config 1 default + +ctdb_get_1_public_address | +{ + read dev ip bits + + ok <<EOF +WARNING: Failed to delete policy routing rule + Command "ip rule del from $ip pref $CTDB_PER_IP_ROUTING_RULE_PREF table ctdb.$ip" failed: + Error: argument ctdb.$ip is wrong: invalid table ID + Error: argument ctdb.$ip is wrong: table id value is invalid +EOF + + simple_test_event "releaseip" $dev $ip $bits +} + + +# there should be no routes +check_routes 0 diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.016.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.016.sh new file mode 100755 index 0000000..4856ba5 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.016.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "empty config, reconfigure, NOOP" + +setup + +create_policy_routing_config 0 + +ok "Reconfiguring service \"${service_name}\"..." +simple_test_event "reconfigure" + +check_routes 0 diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.017.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.017.sh new file mode 100755 index 0000000..d26ab9c --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.017.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 IP configured, reconfigure" + +setup + +create_policy_routing_config 1 default + +# no takeip, but reconfigure should add any missing routes +ok "Reconfiguring service \"${service_name}\"..." +simple_test_event "reconfigure" + +check_routes 1 default diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.018.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.018.sh new file mode 100755 index 0000000..4d89dc2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.018.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 IP configured, ipreallocated, more routes, reconfigure" + +setup + +create_policy_routing_config 1 + +# no takeip, but ipreallocated should add any missing routes +ok_null +simple_test_event "ipreallocated" + +create_policy_routing_config 1 default + +# reconfigure should update routes even though rules are unchanged +ok "Reconfiguring service \"${service_name}\"..." +simple_test_event "reconfigure" + +check_routes 1 default diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.019.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.019.sh new file mode 100755 index 0000000..7575466 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.019.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 IP configured, ipreallocated, less routes, reconfigure" + +setup + +create_policy_routing_config 1 default + +# no takeip, but ipreallocated should add any missing routes +ok_null +simple_test_event "ipreallocated" + +# rewrite the configuration to take out the default routes, as per the +# above change to $args +create_policy_routing_config 1 + +# reconfigure should update routes even though rules are unchanged +ok "Reconfiguring service \""${service_name}\""..." +simple_test_event "reconfigure" + +check_routes 1 diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.021.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.021.sh new file mode 100755 index 0000000..876b600 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.021.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Invalid table ID range - includes system tables" + +setup + +setup_script_options <<EOF +CTDB_PER_IP_ROUTING_TABLE_ID_LOW=100 +CTDB_PER_IP_ROUTING_TABLE_ID_HIGH=500 +EOF + +required_result 1 "error: range CTDB_PER_IP_ROUTING_TABLE_ID_LOW[${CTDB_PER_IP_ROUTING_TABLE_ID_LOW}]..CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[${CTDB_PER_IP_ROUTING_TABLE_ID_HIGH}] must not include 253-255" +simple_test_event "ipreallocated" diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.022.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.022.sh new file mode 100755 index 0000000..6f0638e --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.022.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Invalid table ID range - reversed" + +setup + +setup_script_options <<EOF +CTDB_PER_IP_ROUTING_TABLE_ID_LOW=9000 +CTDB_PER_IP_ROUTING_TABLE_ID_HIGH=1000 +EOF + +required_result 1 "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" +simple_test_event "ipreallocated" diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.023.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.023.sh new file mode 100755 index 0000000..a94b58b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.023.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "1 IP configured, broken configuration, takeip" + +setup + +# Configuration for 1 IP +create_policy_routing_config 1 default + +# takeip should add routes for the given address +ctdb_get_1_public_address | +while read dev ip bits ; do + # Now add configuration breakage by changing default route into a + # link local route with a gateway + net=$(ipv4_host_addr_to_net "$ip" "$bits") + sed -i -e "s@0\.0\.0\.0/0@${net}@" "$CTDB_PER_IP_ROUTING_CONF" + + ok <<EOF +RTNETLINK answers: File exists +add_routing_for_ip: failed to add route: ${net} via ${net%.*}.254 dev ${dev} table ctdb.${ip} +EOF + simple_test_event "takeip" $dev $ip $bits +done diff --git a/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.024.sh b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.024.sh new file mode 100755 index 0000000..7b1af37 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/13.per_ip_routing.024.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Single IP, restores original rt_tables" + +setup + +create_policy_routing_config 1 default + +_rt_tables="$CTDB_SYS_ETCDIR/iproute2/rt_tables" +_rt_orig=$(TMPDIR="$CTDB_TEST_TMP_DIR" mktemp) +cp "$_rt_tables" "$_rt_orig" + +ctdb_get_1_public_address | { + read dev ip bits + + ok_null + simple_test_event "takeip" $dev $ip $bits + + ok <<EOF +Removing ip rule for public address ${ip} for routing table ctdb.${ip} +EOF + simple_test_event "shutdown" +} + +ok_null +simple_test_command diff -u "$_rt_orig" "$_rt_tables" + +check_routes 0 diff --git a/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.001.sh b/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.001.sh new file mode 100755 index 0000000..4991765 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.001.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "No multipath devices configure to check" + +setup + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.002.sh b/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.002.sh new file mode 100755 index 0000000..f57f476 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.002.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 multipath devices configure to check, all up" + +setup "mpatha" "mpathb" "mpathc" + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.003.sh b/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.003.sh new file mode 100755 index 0000000..0d768a0 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.003.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 multipath devices configure to check, one down" + +setup "mpatha" "!mpathb" "mpathc" + +required_result 1 <<EOF +ERROR: multipath device "mpathb" has no active paths +multipath monitoring failed +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.004.sh b/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.004.sh new file mode 100755 index 0000000..a655b83 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.004.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 multipath devices configure to check, multipath hangs" + +setup "mpatha" "!mpathb" "mpathc" +export FAKE_MULTIPATH_HANG="yes" + +required_result 1 <<EOF +ERROR: callout to multipath checks hung +multipath monitoring failed +EOF + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/31.clamd.monitor.002.sh b/ctdb/tests/UNIT/eventscripts/31.clamd.monitor.002.sh new file mode 100755 index 0000000..48d3cbf --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/31.clamd.monitor.002.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Managed, clamd not listening" + +setup + +setup_script_options <<EOF +CTDB_CLAMD_SOCKET="/var/run/clamd.sock" +EOF + +required_result 1 <<EOF +ERROR: clamd not listening on $CTDB_CLAMD_SOCKET +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/31.clamd.monitor.003.sh b/ctdb/tests/UNIT/eventscripts/31.clamd.monitor.003.sh new file mode 100755 index 0000000..f4e37d2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/31.clamd.monitor.003.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Managed, clamd listening" + +setup + +setup_script_options <<EOF +CTDB_CLAMD_SOCKET="/var/run/clamd.sock" +EOF + +unix_socket_listening "$CTDB_CLAMD_SOCKET" + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/40.vsftpd.monitor.002.sh b/ctdb/tests/UNIT/eventscripts/40.vsftpd.monitor.002.sh new file mode 100755 index 0000000..f825be4 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/40.vsftpd.monitor.002.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "up once, down with recovery" + +setup "up" + +ok_null +simple_test + +setup "down" + +ok <<EOF +vsftpd not listening on TCP port 21 +WARNING: vsftpd listening on TCP port 21: fail count 1 >= threshold 1 +EOF +simple_test + +setup "up" + +ok <<EOF +NOTICE: vsftpd listening on TCP port 21: no longer failing +EOF +simple_test + +setup "down" + +ok <<EOF +vsftpd not listening on TCP port 21 +WARNING: vsftpd listening on TCP port 21: fail count 1 >= threshold 1 +EOF +simple_test + +required_result 1 <<EOF +vsftpd not listening on TCP port 21 +ERROR: vsftpd listening on TCP port 21: fail count 2 >= threshold 2 +EOF +simple_test + +required_result 1 <<EOF +vsftpd not listening on TCP port 21 +ERROR: vsftpd listening on TCP port 21: fail count 3 >= threshold 2 +EOF +simple_test + +setup "up" + +ok <<EOF +NOTICE: vsftpd listening on TCP port 21: no longer failing +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/40.vsftpd.shutdown.002.sh b/ctdb/tests/UNIT/eventscripts/40.vsftpd.shutdown.002.sh new file mode 100755 index 0000000..fe65278 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/40.vsftpd.shutdown.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "managed" + +setup "up" + +ok <<EOF +Stopping vsftpd: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/40.vsftpd.startup.002.sh b/ctdb/tests/UNIT/eventscripts/40.vsftpd.startup.002.sh new file mode 100755 index 0000000..dd39860 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/40.vsftpd.startup.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "managed" + +setup "down" + +ok <<EOF +Starting vsftpd: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/41.httpd.monitor.002.sh b/ctdb/tests/UNIT/eventscripts/41.httpd.monitor.002.sh new file mode 100755 index 0000000..383040c --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/41.httpd.monitor.002.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "managed, down - 5 times" + +setup "down" + +ok_null +simple_test + +ok <<EOF +HTTPD is not running. Trying to restart HTTPD. +service: can't stop httpd - not running +Starting httpd: OK +EOF +simple_test + +ok_null +simple_test + +ok_null +simple_test + +required_result 1 <<EOF +HTTPD is not running. Trying to restart HTTPD. +Stopping httpd: OK +Starting httpd: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/41.httpd.shutdown.002.sh b/ctdb/tests/UNIT/eventscripts/41.httpd.shutdown.002.sh new file mode 100755 index 0000000..4e342fb --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/41.httpd.shutdown.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "managed" + +setup "up" + +ok <<EOF +Stopping httpd: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/41.httpd.startup.002.sh b/ctdb/tests/UNIT/eventscripts/41.httpd.startup.002.sh new file mode 100755 index 0000000..1722785 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/41.httpd.startup.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "managed" + +setup "down" + +ok <<EOF +Starting httpd: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/48.netbios.shutdown.011.sh b/ctdb/tests/UNIT/eventscripts/48.netbios.shutdown.011.sh new file mode 100755 index 0000000..0649813 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/48.netbios.shutdown.011.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "shutdown, Debian init style" + +setup + +export EVENTSCRIPT_TESTS_INIT_STYLE="debian" + +ok <<EOF +Stopping nmbd: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/48.netbios.startup.011.sh b/ctdb/tests/UNIT/eventscripts/48.netbios.startup.011.sh new file mode 100755 index 0000000..40b90a1 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/48.netbios.startup.011.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "shutdown, Debian init style" + +setup + +export EVENTSCRIPT_TESTS_INIT_STYLE="debian" + +ok <<EOF +Starting nmbd: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/49.winbind.monitor.101.sh b/ctdb/tests/UNIT/eventscripts/49.winbind.monitor.101.sh new file mode 100755 index 0000000..3884a33 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/49.winbind.monitor.101.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all OK" + +setup + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/49.winbind.monitor.102.sh b/ctdb/tests/UNIT/eventscripts/49.winbind.monitor.102.sh new file mode 100755 index 0000000..24e4ed2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/49.winbind.monitor.102.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "winbind down" + +setup + +wbinfo_down + +required_result 1 "ERROR: wbinfo -p returned error" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/49.winbind.shutdown.002.sh b/ctdb/tests/UNIT/eventscripts/49.winbind.shutdown.002.sh new file mode 100755 index 0000000..dc6f160 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/49.winbind.shutdown.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "managed" + +setup "up" + +ok <<EOF +Stopping winbind: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/49.winbind.startup.002.sh b/ctdb/tests/UNIT/eventscripts/49.winbind.startup.002.sh new file mode 100755 index 0000000..dd0c1ad --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/49.winbind.startup.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "managed" + +setup "down" + +ok <<EOF +Starting winbind: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.monitor.101.sh b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.101.sh new file mode 100755 index 0000000..3884a33 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.101.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all OK" + +setup + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.monitor.103.sh b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.103.sh new file mode 100755 index 0000000..e9232a6 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.103.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "port 445 down" + +setup + +tcp_port_down 445 + +required_result 1 "samba not listening on TCP port 445" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.monitor.104.sh b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.104.sh new file mode 100755 index 0000000..8e9d789 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.104.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "port 139 down" + +setup + +tcp_port_down 139 + +required_result 1 "samba not listening on TCP port 139" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.monitor.105.sh b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.105.sh new file mode 100755 index 0000000..7208aca --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.105.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "non-existent share path" + +setup + +out=$(shares_missing "samba" 2) + +required_result 1 "$out" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.monitor.106.sh b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.106.sh new file mode 100755 index 0000000..80bccef --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.106.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "non-existent share - not checked" + +setup + +setup_script_options <<EOF +CTDB_SAMBA_SKIP_SHARE_CHECK="yes" +EOF + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.monitor.110.sh b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.110.sh new file mode 100755 index 0000000..9645c5a --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.110.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "testparm fails" + +setup + +export FAKE_TESTPARM_FAIL="yes" +required_result 1 <<EOF +ERROR: smb.conf cache create failed - testparm failed with: +Load smb config files from ${CTDB_SYS_ETCDIR}/samba/smb.conf +rlimit_max: increasing rlimit_max (2048) to minimum Windows limit (16384) +Processing section "[share1]" +Processing section "[share2]" +Processing section "[share3]" +Loaded services file OK. +WARNING: 'workgroup' and 'netbios name' must differ. +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.monitor.111.sh b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.111.sh new file mode 100755 index 0000000..d72855f --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.111.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "testparm fails on 2nd time through" + +setup + +ok_null +simple_test + +export FAKE_TESTPARM_FAIL="yes" +ok <<EOF +WARNING: smb.conf cache update failed - using old cache file +Load smb config files from ${CTDB_SYS_ETCDIR}/samba/smb.conf +rlimit_max: increasing rlimit_max (2048) to minimum Windows limit (16384) +Processing section "[share1]" +Processing section "[share2]" +Processing section "[share3]" +Loaded services file OK. +WARNING: 'workgroup' and 'netbios name' must differ. +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.monitor.112.sh b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.112.sh new file mode 100755 index 0000000..f714472 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.112.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "testparm times out" + +setup + +export FAKE_TIMEOUT="yes" +required_result 1 <<EOF +ERROR: smb.conf cache create failed - testparm command timed out +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.monitor.113.sh b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.113.sh new file mode 100755 index 0000000..faadda1 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.monitor.113.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "testparm times out on 2nd time through" + +setup + +ok_null +simple_test + +export FAKE_TIMEOUT="yes" +ok <<EOF +WARNING: smb.conf cache update timed out - using old cache file +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.shutdown.001.sh b/ctdb/tests/UNIT/eventscripts/50.samba.shutdown.001.sh new file mode 100755 index 0000000..76ac985 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.shutdown.001.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "shutdown, simple" + +setup + +ok <<EOF +Stopping smb: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.shutdown.002.sh b/ctdb/tests/UNIT/eventscripts/50.samba.shutdown.002.sh new file mode 100755 index 0000000..f692026 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.shutdown.002.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "shutdown, simple" + +setup + +samba_setup_fake_threads 1 2 3 4 5 6 + +ok <<EOF +Stopping smb: OK +$SAMBA_STACK_TRACES +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.shutdown.011.sh b/ctdb/tests/UNIT/eventscripts/50.samba.shutdown.011.sh new file mode 100755 index 0000000..94867e0 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.shutdown.011.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "shutdown, Debian init style" + +setup + +export EVENTSCRIPT_TESTS_INIT_STYLE="debian" + +ok <<EOF +Stopping smbd: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/50.samba.startup.011.sh b/ctdb/tests/UNIT/eventscripts/50.samba.startup.011.sh new file mode 100755 index 0000000..8c4699d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/50.samba.startup.011.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "shutdown, Debian init style" + +setup + +export EVENTSCRIPT_TESTS_INIT_STYLE="debian" + +ok <<EOF +Starting smbd: OK +EOF +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.101.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.101.sh new file mode 100755 index 0000000..293b724 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.101.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all services available" + +setup + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.102.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.102.sh new file mode 100755 index 0000000..2f83b2c --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.102.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all services available, check nfsd thread count, count matches" + +setup + +RPCNFSDCOUNT=8 +nfs_setup_fake_threads "nfsd" 1 2 3 4 5 6 7 8 + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.103.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.103.sh new file mode 100755 index 0000000..c0bb305 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.103.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all services available, not enough nfsd threads" + +setup + +RPCNFSDCOUNT=8 +nfs_setup_fake_threads "nfsd" 1 2 3 4 5 + +ok "Attempting to correct number of nfsd threads from 5 to 8" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.104.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.104.sh new file mode 100755 index 0000000..d568892 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.104.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +# Add this extra test to catch a design change where we only ever +# increase the number of threads. That is, this test would need to be +# consciously removed. +define_test "all services available, check nfsd thread count, too many threads" + +setup + +RPCNFSDCOUNT=4 +nfs_setup_fake_threads "nfsd" 1 2 3 4 5 6 + +ok "Attempting to correct number of nfsd threads from 6 to 4" + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.105.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.105.sh new file mode 100755 index 0000000..e83ead8 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.105.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all services available, 10 iterations with ok_null" + +setup + +ok_null +nfs_iterate_test 10 diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.106.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.106.sh new file mode 100755 index 0000000..43d6b2f --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.106.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "portmapper down, 2 iterations" + +setup + +rpc_services_down "portmapper" + +nfs_iterate_test 2 "portmapper" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.107.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.107.sh new file mode 100755 index 0000000..8bf0fa2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.107.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout is 'true'" + +setup + +setup_script_options <<EOF +CTDB_NFS_CALLOUT="true" +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.108.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.108.sh new file mode 100755 index 0000000..39aba84 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.108.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout causes monitor-pre to fail" + +setup + +setup_nfs_callout "monitor-pre" + +required_result 1 "monitor-pre" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.109.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.109.sh new file mode 100755 index 0000000..36572e9 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.109.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout causes monitor-post to fail" + +setup + +setup_nfs_callout "monitor-post" + +required_result 1 "monitor-post" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.111.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.111.sh new file mode 100755 index 0000000..2bbda96 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.111.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "knfsd down, 1 iteration" + +setup + +rpc_services_down "nfs" + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.112.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.112.sh new file mode 100755 index 0000000..4000b5d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.112.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "knfsd down, 10 iterations" + +# knfsd fails and attempts to restart it fail. + +setup + +rpc_services_down "nfs" + +nfs_iterate_test 10 "nfs" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.113.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.113.sh new file mode 100755 index 0000000..744966c --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.113.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "knfsd down, 10 iterations, no hung threads" + +# knfsd fails and attempts to restart it fail. +setup + +rpc_services_down "nfs" + +nfs_setup_fake_threads "nfsd" + +nfs_iterate_test 10 "nfs" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.114.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.114.sh new file mode 100755 index 0000000..7170fff --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.114.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "knfsd down, 10 iterations, 3 hung threads" + +# knfsd fails and attempts to restart it fail. +setup + +rpc_services_down "nfs" + +nfs_setup_fake_threads "nfsd" 1001 1002 1003 + +nfs_iterate_test 10 "nfs" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.121.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.121.sh new file mode 100755 index 0000000..1cda276 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.121.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "lockd down, 7 iterations" + +# This simulates an ongoing failure in the eventscript's automated +# attempts to restart the service. That is, the eventscript is unable +# to restart the service. + +setup + +rpc_services_down "nlockmgr" + +nfs_iterate_test 7 "nlockmgr" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.122.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.122.sh new file mode 100755 index 0000000..eae7ca0 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.122.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "lockd down, 7 iterations, back up after 2" + +# This simulates a success the eventscript's automated attempts to +# restart the service. + +setup + +rpc_services_down "nlockmgr" + +# Iteration 2 should try to restart rpc.lockd. However, our test +# stub rpc.lockd does nothing, so we have to explicitly flag it as up. + +nfs_iterate_test 7 "nlockmgr" \ + 3 "rpc_services_up nlockmgr" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.131.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.131.sh new file mode 100755 index 0000000..33e1cf4 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.131.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "rquotad down, 7 iterations" + +setup + +rpc_services_down "rquotad" + +nfs_iterate_test 7 "rquotad" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.132.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.132.sh new file mode 100755 index 0000000..207d872 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.132.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "rquotad down, 7 iterations, back up after 2" + +# rquotad fails once but then comes back after restart after 2nd +# failure. + +setup + +rpc_services_down "rquotad" + +nfs_iterate_test 7 "rquotad" \ + 3 'rpc_services_up "rquotad"' diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.141.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.141.sh new file mode 100755 index 0000000..5a8c5ce --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.141.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "statd down, 7 iterations" + +# statd fails and attempts to restart it fail. + +setup + +rpc_services_down "status" + +nfs_iterate_test 7 "status" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.142.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.142.sh new file mode 100755 index 0000000..694bf92 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.142.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "statd down, 7 iterations, back up after 2" + +# statd fails and the first attempt to restart it succeeds. + +setup + +rpc_services_down "status" + +nfs_iterate_test 7 "status" \ + 3 'rpc_services_up "status"' diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.143.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.143.sh new file mode 100755 index 0000000..d17277e --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.143.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "statd down, 2 iterations, stuck process" + +# statd fails and the first attempt to restart it succeeds. + +setup + +rpc_services_down "status" +nfs_setup_fake_threads "rpc.status" 1001 + +nfs_iterate_test 2 "status" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.144.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.144.sh new file mode 100755 index 0000000..5a8c5ce --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.144.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "statd down, 7 iterations" + +# statd fails and attempts to restart it fail. + +setup + +rpc_services_down "status" + +nfs_iterate_test 7 "status" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.151.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.151.sh new file mode 100755 index 0000000..9ab1807 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.151.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "mountd down, 1 iteration" + +setup + +rpc_services_down "mountd" + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.152.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.152.sh new file mode 100755 index 0000000..c3a6b8b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.152.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "mountd down, 7 iterations" + +# This simulates an ongoing failure in the eventscript's automated +# attempts to restart the service. That is, the eventscript is unable +# to restart the service. + +setup + +rpc_services_down "mountd" + +nfs_iterate_test 7 "mountd" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.153.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.153.sh new file mode 100755 index 0000000..a09315b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.153.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "mountd down, 7 iterations, back up after 2" + +setup + +rpc_services_down "mountd" + +# Iteration 2 should try to restart rpc.mountd. However, our test +# stub rpc.mountd does nothing, so we have to explicitly flag it as +# up. +nfs_iterate_test 7 "mountd" \ + 3 "rpc_services_up mountd" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.161.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.161.sh new file mode 100755 index 0000000..1fa73bb --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.161.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "2nd share missing" + +setup + +out=$(shares_missing "nfs" 2) + +required_result 1 "$out" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.162.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.162.sh new file mode 100755 index 0000000..9e3438f --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.monitor.162.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "2nd share missing, skipping share checks" + +setup + +setup_script_options <<EOF +CTDB_NFS_SKIP_SHARE_CHECK="yes" +EOF + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.multi.001.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.multi.001.sh new file mode 100755 index 0000000..baa5701 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.multi.001.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "takeip, ipreallocated -> reconfigure" + +setup + +public_address=$(ctdb_get_1_public_address) + +ok_null + +simple_test_event "takeip" $public_address + +ok <<EOF +Reconfiguring service "nfs"... +EOF + +simple_test_event "ipreallocated" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.multi.002.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.multi.002.sh new file mode 100755 index 0000000..846380f --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.multi.002.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "takeip, monitor -> no reconfigure" + +setup + +public_address=$(ctdb_get_1_public_address) + +ok_null + +simple_test_event "takeip" $public_address + +ok_null + +simple_test_event "monitor" diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.releaseip.001.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.releaseip.001.sh new file mode 100755 index 0000000..8bf0fa2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.releaseip.001.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout is 'true'" + +setup + +setup_script_options <<EOF +CTDB_NFS_CALLOUT="true" +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.releaseip.002.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.releaseip.002.sh new file mode 100755 index 0000000..998c3ba --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.releaseip.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout causes releaseip to fail" + +setup + +setup_nfs_callout "releaseip" + +required_result 1 "releaseip" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.shutdown.001.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.shutdown.001.sh new file mode 100755 index 0000000..8bf0fa2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.shutdown.001.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout is 'true'" + +setup + +setup_script_options <<EOF +CTDB_NFS_CALLOUT="true" +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.shutdown.002.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.shutdown.002.sh new file mode 100755 index 0000000..9db0656 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.shutdown.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout causes shutdown to fail" + +setup + +setup_nfs_callout "shutdown" + +required_result 1 "shutdown" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.startup.001.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.startup.001.sh new file mode 100755 index 0000000..8bf0fa2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.startup.001.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout is 'true'" + +setup + +setup_script_options <<EOF +CTDB_NFS_CALLOUT="true" +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.startup.002.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.startup.002.sh new file mode 100755 index 0000000..bf881d9 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.startup.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout causes startup to fail" + +setup + +setup_nfs_callout "startup" + +required_result 1 "startup" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.takeip.001.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.takeip.001.sh new file mode 100755 index 0000000..8bf0fa2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.takeip.001.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout is 'true'" + +setup + +setup_script_options <<EOF +CTDB_NFS_CALLOUT="true" +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/60.nfs.takeip.002.sh b/ctdb/tests/UNIT/eventscripts/60.nfs.takeip.002.sh new file mode 100755 index 0000000..3f247ff --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/60.nfs.takeip.002.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "callout causes takeip to fail" + +setup + +setup_nfs_callout "takeip" + +required_result 1 "takeip" +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.001.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.001.sh new file mode 100755 index 0000000..9fc8d02 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.001.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "sanity check ipvsadm stub" + +setup<<EOF +EOF + +check_ipvsadm NULL + +ipvsadm -A -u 10.1.1.201 -s lc -p 1999999 +ipvsadm -a -u 10.1.1.201 -r 192.168.1.3 -g +ipvsadm -a -u 10.1.1.201 -r 192.168.1.1 -g +ipvsadm -a -u 10.1.1.201 -r 192.168.1.2:0 -g +ipvsadm -a -u 10.1.1.201 -r 127.0.0.1 + +check_ipvsadm <<EOF +UDP 10.1.1.201:0 lc persistent 1999999 + -> 127.0.0.1:0 Local 1 0 0 + -> 192.168.1.1:0 Route 1 0 0 + -> 192.168.1.2:0 Route 1 0 0 + -> 192.168.1.3:0 Route 1 0 0 +EOF + +ipvsadm -A -t 10.1.1.201 -s lc -p 1999999 +ipvsadm -a -t 10.1.1.201 -r 192.168.1.3 -g +ipvsadm -a -t 10.1.1.201 -r 192.168.1.1 -g +ipvsadm -a -t 10.1.1.201 -r 192.168.1.2:0 -g + +check_ipvsadm <<EOF +TCP 10.1.1.201:0 lc persistent 1999999 + -> 192.168.1.1:0 Route 1 0 0 + -> 192.168.1.2:0 Route 1 0 0 + -> 192.168.1.3:0 Route 1 0 0 +UDP 10.1.1.201:0 lc persistent 1999999 + -> 127.0.0.1:0 Local 1 0 0 + -> 192.168.1.1:0 Route 1 0 0 + -> 192.168.1.2:0 Route 1 0 0 + -> 192.168.1.3:0 Route 1 0 0 +EOF + +ipvsadm -D -u 10.1.1.201 + +check_ipvsadm <<EOF +TCP 10.1.1.201:0 lc persistent 1999999 + -> 192.168.1.1:0 Route 1 0 0 + -> 192.168.1.2:0 Route 1 0 0 + -> 192.168.1.3:0 Route 1 0 0 +EOF + +ipvsadm -D -t 10.1.1.201 + +check_ipvsadm NULL diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.011.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.011.sh new file mode 100755 index 0000000..6866047 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.011.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "configured, no nodes in config" + +setup "10.1.1.201" "eth0" <<EOF +EOF + +ok_null +simple_test + +check_ipvsadm NULL +check_lvs_ip host diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.012.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.012.sh new file mode 100755 index 0000000..15328ef --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.012.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "nodes in config, no leader (e.g. all inactive)" + +setup "10.1.1.201" "eth0" <<EOF +192.168.1.1 +192.168.1.2 +192.168.1.3 +EOF + +ok_null +simple_test + +check_ipvsadm NULL +check_lvs_ip host diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.013.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.013.sh new file mode 100755 index 0000000..918b18d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.013.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "nodes in config, other node is leader" + +setup "10.1.1.201" "eth0" <<EOF +192.168.1.1 +192.168.1.2 leader +192.168.1.3 +EOF + +ok_null +simple_test + +check_ipvsadm NULL +check_lvs_ip host diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.014.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.014.sh new file mode 100755 index 0000000..8af31d7 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.014.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "nodes in config, this is leader" + +setup "10.1.1.201" "eth0" <<EOF +192.168.1.1 leader +192.168.1.2 +192.168.1.3 +EOF + +ok_null +simple_test + +check_ipvsadm <<EOF +TCP 10.1.1.201:0 lc persistent 1999999 + -> 127.0.0.1:0 Local 1 0 0 + -> 192.168.1.2:0 Route 1 0 0 + -> 192.168.1.3:0 Route 1 0 0 +UDP 10.1.1.201:0 lc persistent 1999999 + -> 127.0.0.1:0 Local 1 0 0 + -> 192.168.1.2:0 Route 1 0 0 + -> 192.168.1.3:0 Route 1 0 0 +EOF + +check_lvs_ip global diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.monitor.001.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.monitor.001.sh new file mode 100755 index 0000000..42831fb --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.monitor.001.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "not configured" + +setup <<EOF +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.monitor.002.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.monitor.002.sh new file mode 100755 index 0000000..a808017 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.monitor.002.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "configured, interface up" + +setup "10.1.1.201" "eth0" <<EOF +192.168.1.1 +192.168.1.2 +192.168.1.3 +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.monitor.003.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.monitor.003.sh new file mode 100755 index 0000000..89f443e --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.monitor.003.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "configured, interface up" + +setup "10.1.1.201" "eth0" <<EOF +192.168.1.1 +192.168.1.2 +192.168.1.3 +EOF + +ethtool_interfaces_down "$CTDB_LVS_PUBLIC_IFACE" + +required_result 1 <<EOF +ERROR: No link on the public network interface ${CTDB_LVS_PUBLIC_IFACE} +EOF +simple_test + diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.shutdown.001.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.shutdown.001.sh new file mode 100755 index 0000000..42831fb --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.shutdown.001.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "not configured" + +setup <<EOF +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.shutdown.002.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.shutdown.002.sh new file mode 100755 index 0000000..61c7f96 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.shutdown.002.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "configured" + +setup "10.1.1.201" "eth0" <<EOF +EOF + +ipvsadm -A -t "$CTDB_LVS_PUBLIC_IP" -s lc -p 1999999 +ipvsadm -A -u "$CTDB_LVS_PUBLIC_IP" -s lc -p 1999999 +ip addr add $CTDB_LVS_PUBLIC_IP/32 dev lo + +ok_null +simple_test + +check_ipvsadm NULL +check_lvs_ip NULL diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.startup.001.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.startup.001.sh new file mode 100755 index 0000000..42831fb --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.startup.001.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "not configured" + +setup <<EOF +EOF + +ok_null +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/91.lvs.startup.002.sh b/ctdb/tests/UNIT/eventscripts/91.lvs.startup.002.sh new file mode 100755 index 0000000..e4c5e8d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/91.lvs.startup.002.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "configured" + +setup "10.1.1.201" "eth0" <<EOF +EOF + +ok_null +simple_test + +check_ipvsadm NULL +check_lvs_ip "host" diff --git a/ctdb/tests/UNIT/eventscripts/README b/ctdb/tests/UNIT/eventscripts/README new file mode 100644 index 0000000..304cdba --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/README @@ -0,0 +1,46 @@ +eventscript unit tests +====================== + +This directory contains some eventscript unit tests for CTDB. These +tests can be run as a non-privileged user. There are a lot of stub +implementations of commands (located in stubs/) used to make the +eventscripts think they're running against a real system. + +Test case filenames look like: + + <eventscript>.<event>.NNN.sh + +The test helper functions will run <eventscript> with specified +options. If using the simple_test() helper function then the 1st +<event> argument is automatically passed. When simple_test_event() is +used the event name must be explicitly passed as the 1st argument - +this is more flexible and supports multiple events per test. + +Examples: + +* ../run_tests.sh . + + Run all tests, displaying minimal output. + +* ../run_tests.sh -s . + + Run all tests, displaying minimal output and a summary. + +* ../run_tests.sh -s ./10.interface.*.sh + + Run all the tests against the 10.interface eventscript. + +* ../run_tests.sh -v -s . + + Run all tests, displaying extra output and a summary. + +* ../run_tests.sh -sq . + + Run all tests, displaying only a summary. + +* ../run_tests.sh -X ./10.interface.startup.002.sh + + Run a test and have the eventscript itself run with "sh -x". This + will usually make a test fail because the (undesirable) trace output + will be included with the output of the eventscript. However, this + is useful for finding out why a test might be failing. diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.001.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.001.sh new file mode 100755 index 0000000..8f10200 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.001.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "DB S+ DB" + +setup + +do_test "DB" "S+" "DB" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.002.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.002.sh new file mode 100755 index 0000000..31ae3df --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.002.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "DB D. DB" + +setup + +do_test "DB" "D." "DB" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.003.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.003.sh new file mode 100755 index 0000000..89ab2f1 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.003.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "RECORD S+ DB" + +setup + +do_test "RECORD" "S+" "DB" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.004.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.004.sh new file mode 100755 index 0000000..35500cb --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.004.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "RECORD D. DB" + +setup + +do_test "RECORD" "D." "DB" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.005.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.005.sh new file mode 100755 index 0000000..10cbade --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.005.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "DB S+ RECORD" + +setup + +do_test "DB" "S+" "RECORD" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.006.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.006.sh new file mode 100755 index 0000000..c4988b7 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.006.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "DB D. RECORD" + +setup + +do_test "DB" "D." "RECORD" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.007.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.007.sh new file mode 100755 index 0000000..b186d20 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.007.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "RECORD S+ RECORD" + +setup + +do_test "RECORD" "S+" "RECORD" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.008.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.008.sh new file mode 100755 index 0000000..7b7ac9b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.008.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "RECORD D. RECORD" + +setup + +do_test "RECORD" "D." "RECORD" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.021.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.021.sh new file mode 100755 index 0000000..f324803 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.021.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "DB S+ DB MUTEX" + +setup + +do_test "DB" "S+" "DB" "MUTEX" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.022.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.022.sh new file mode 100755 index 0000000..0e70771 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.022.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "DB D. DB MUTEX" + +setup + +do_test "DB" "D." "DB" "MUTEX" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.023.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.023.sh new file mode 100755 index 0000000..de84c81 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.023.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "RECORD S+ DB MUTEX" + +setup + +do_test "RECORD" "S+" "DB" "MUTEX" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.024.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.024.sh new file mode 100755 index 0000000..30ad6bd --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.024.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "RECORD D. DB MUTEX" + +setup + +do_test "RECORD" "D." "DB" "MUTEX" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.025.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.025.sh new file mode 100755 index 0000000..f259db5 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.025.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "DB S+ RECORD MUTEX" + +setup + +do_test "DB" "S+" "RECORD" "MUTEX" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.026.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.026.sh new file mode 100755 index 0000000..9e057af --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.026.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "DB D. RECORD MUTEX" + +setup + +do_test "DB" "D." "RECORD" "MUTEX" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.027.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.027.sh new file mode 100755 index 0000000..d70e7b7 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.027.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "RECORD S+ RECORD MUTEX" + +setup + +do_test "RECORD" "S+" "RECORD" "MUTEX" diff --git a/ctdb/tests/UNIT/eventscripts/debug_locks.sh.028.sh b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.028.sh new file mode 100755 index 0000000..7199035 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/debug_locks.sh.028.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "RECORD D. RECORD MUTEX" + +setup + +do_test "RECORD" "D." "RECORD" "MUTEX" diff --git a/ctdb/tests/UNIT/eventscripts/etc-ctdb/public_addresses b/ctdb/tests/UNIT/eventscripts/etc-ctdb/public_addresses new file mode 100644 index 0000000..cd2f6be --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/etc-ctdb/public_addresses @@ -0,0 +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/UNIT/eventscripts/etc-ctdb/rc.local b/ctdb/tests/UNIT/eventscripts/etc-ctdb/rc.local new file mode 100755 index 0000000..777aeaf --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/etc-ctdb/rc.local @@ -0,0 +1,56 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +# Always use stub version of service command +service () +{ + "${CTDB_HELPER_BINDIR}/service" "$@" +} + +nice_service () +{ + nice "${CTDB_HELPER_BINDIR}/service" "$@" +} + +# Always succeeds +set_proc () { : ; } +set_proc_maybe () { : ; } + +get_proc () +{ + case "$1" in + net/bonding/*) + cat "$FAKE_PROC_NET_BONDING/${1##*/}" + ;; + sys/net/ipv4/conf/all/arp_filter) + echo 1 + ;; + sys/net/ipv4/conf/all/promote_secondaries) + echo 1 + ;; + fs/nfsd/threads) + echo "$FAKE_NFSD_THREAD_PIDS" | wc -w + ;; + */stack) + echo "[<ffffffff87654321>] fake_stack_trace_for_pid_${1}+0x0/0xff" + ;; + meminfo) + echo "$FAKE_PROC_MEMINFO" + ;; + locks) + echo "$FAKE_PROC_LOCKS" + ;; + *) + echo "get_proc: \"$1\" not implemented" + exit 1 + esac +} + +# Do not actually background - we want to see the output +background_with_logging () +{ + "$@" 2>&1 </dev/null | sed -e 's@^@\&@' +} + +if [ -n "$EVENTSCRIPT_TESTS_INIT_STYLE" ]; then + CTDB_INIT_STYLE="$EVENTSCRIPT_TESTS_INIT_STYLE" +fi diff --git a/ctdb/tests/UNIT/eventscripts/etc/init.d/nfs b/ctdb/tests/UNIT/eventscripts/etc/init.d/nfs new file mode 100755 index 0000000..43eb308 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/etc/init.d/nfs @@ -0,0 +1,7 @@ +#!/bin/sh + +# This is not used. The fake "service" script is used instead. This +# is only needed to shut up functions like startstop_nfs(), which look +# for this script. + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/etc/init.d/nfslock b/ctdb/tests/UNIT/eventscripts/etc/init.d/nfslock new file mode 100755 index 0000000..43eb308 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/etc/init.d/nfslock @@ -0,0 +1,7 @@ +#!/bin/sh + +# This is not used. The fake "service" script is used instead. This +# is only needed to shut up functions like startstop_nfs(), which look +# for this script. + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/etc/os-release b/ctdb/tests/UNIT/eventscripts/etc/os-release new file mode 100644 index 0000000..f0057cc --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/etc/os-release @@ -0,0 +1,2 @@ +ID="rocky" +ID_LIKE="rhel centos fedora" diff --git a/ctdb/tests/UNIT/eventscripts/etc/samba/smb.conf b/ctdb/tests/UNIT/eventscripts/etc/samba/smb.conf new file mode 100644 index 0000000..45976cd --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/etc/samba/smb.conf @@ -0,0 +1,43 @@ +[global] + # enable clustering + clustering=yes + ctdb:registry.tdb=yes + + security = ADS + auth methods = guest sam winbind + + netbios name = cluster1 + workgroup = CLUSTER1 + realm = CLUSTER1.COM + server string = "Clustered Samba" + disable netbios = yes + disable spoolss = yes + fileid:mapping = fsname + use mmap = yes + gpfs:sharemodes = yes + gpfs:leases = yes + passdb backend = tdbsam + preferred master = no + kernel oplocks = yes + syslog = 1 + host msdfs = no + notify:inotify = no + vfs objects = shadow_copy2 syncops gpfs fileid + shadow:snapdir = .snapshots + shadow:fixinodes = yes + wide links = no + smbd:backgroundqueue = False + read only = no + use sendfile = yes + strict locking = yes + posix locking = yes + large readwrite = yes + force unknown acl user = yes + nfs4:mode = special + nfs4:chown = yes + nfs4:acedup = merge + nfs4:sidmap = /etc/samba/sidmap.tdb + map readonly = no + ea support = yes + dmapi support = no + smb ports = 445 139 diff --git a/ctdb/tests/UNIT/eventscripts/etc/sysconfig/nfs b/ctdb/tests/UNIT/eventscripts/etc/sysconfig/nfs new file mode 100644 index 0000000..090d786 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/etc/sysconfig/nfs @@ -0,0 +1,2 @@ +NFS_HOSTNAME="cluster1" +STATD_HOSTNAME="$NFS_HOSTNAME -H /etc/ctdb/statd-callout " diff --git a/ctdb/tests/UNIT/eventscripts/scripts/00.ctdb.sh b/ctdb/tests/UNIT/eventscripts/scripts/00.ctdb.sh new file mode 100644 index 0000000..a192e05 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/00.ctdb.sh @@ -0,0 +1,24 @@ +setup() +{ + setup_dbdir + setup_date + + export FAKE_TDBTOOL_SUPPORTS_CHECK="yes" + export FAKE_TDB_IS_OK="yes" + + export FAKE_CTDB_TUNABLES_OK=" + MonitorInterval + DatabaseHashSize + " + export FAKE_CTDB_TUNABLES_OBSOLETE=" + EventScriptUnhealthyOnTimeout + " +} + +result_filter() +{ + _date="[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]" + _time="[0-9][0-9][0-9][0-9][0-9][0-9]" + _date_time="${_date}\.${_time}" + sed -e "s|\.${_date_time}\.|.DATE.TIME.|" +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/01.reclock.sh b/ctdb/tests/UNIT/eventscripts/scripts/01.reclock.sh new file mode 100644 index 0000000..7365dd8 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/01.reclock.sh @@ -0,0 +1,16 @@ +setup() +{ + if [ $# -eq 1 ]; then + reclock="$1" + else + reclock="${CTDB_TEST_TMP_DIR}/reclock_subdir/rec.lock" + fi + CTDB_RECOVERY_LOCK="$reclock" + + if [ -n "$CTDB_RECOVERY_LOCK" ]; then + cat >>"${CTDB_BASE}/ctdb.conf" <<EOF +[cluster] + recovery lock = $CTDB_RECOVERY_LOCK +EOF + fi +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/05.system.sh b/ctdb/tests/UNIT/eventscripts/scripts/05.system.sh new file mode 100644 index 0000000..0191e55 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/05.system.sh @@ -0,0 +1,48 @@ +# shellcheck disable=SC2120 +# Arguments used in testcases +set_mem_usage() +{ + _mem_usage="${1:-10}" # Default is 10% + _swap_usage="${2:-0}" # Default is 0% + + _swap_total=5857276 + _swap_free=$(((100 - _swap_usage) * _swap_total / 100)) + + _mem_total=3940712 + _mem_free=225268 + _mem_buffers=146120 + _mem_cached=$((_mem_total * (100 - _mem_usage) / 100 - \ + _mem_free - _mem_buffers)) + + export FAKE_PROC_MEMINFO="\ +MemTotal: ${_mem_total} kB +MemFree: ${_mem_free} kB +Buffers: ${_mem_buffers} kB +Cached: ${_mem_cached} kB +SwapCached: 56016 kB +Active: 2422104 kB +Inactive: 1019928 kB +Active(anon): 1917580 kB +Inactive(anon): 523080 kB +Active(file): 504524 kB +Inactive(file): 496848 kB +Unevictable: 4844 kB +Mlocked: 4844 kB +SwapTotal: ${_swap_total} kB +SwapFree: ${_swap_free} kB +..." +} + +set_fs_usage() +{ + export FAKE_FS_USE="${1:-10}" # Default is 10% usage +} + +setup() +{ + setup_dbdir + + # Tests use default unless explicitly set + set_mem_usage + set_fs_usage +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/06.nfs.sh b/ctdb/tests/UNIT/eventscripts/scripts/06.nfs.sh new file mode 100644 index 0000000..5912a4b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/06.nfs.sh @@ -0,0 +1,4 @@ +setup() +{ + : +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/10.interface.sh b/ctdb/tests/UNIT/eventscripts/scripts/10.interface.sh new file mode 100644 index 0000000..579f3ee --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/10.interface.sh @@ -0,0 +1,72 @@ +setup() +{ + setup_public_addresses +} + +_tcp_connections() +{ + _count="$1" + _sip="$2" + _sport="$3" + _cip_base="$4" + _cport_base="$5" + + _cip_prefix="${_cip_base%.*}" + _cip_suffix="${_cip_base##*.}" + + for _i in $(seq 1 "$_count"); do + _cip_last=$((_cip_suffix + _i)) + _cip="${_cip_prefix}.${_cip_last}" + _cport=$((_cport_base + _i)) + echo "${_sip}:${_sport} ${_cip}:${_cport}" + done +} + +setup_tcp_connections() +{ + _t="${FAKE_NETWORK_STATE}/tcp-established" + export FAKE_NETSTAT_TCP_ESTABLISHED_FILE="$_t" + _tcp_connections "$@" >"$FAKE_NETSTAT_TCP_ESTABLISHED_FILE" +} + +setup_tcp_connections_unkillable() +{ + # These connections are listed by the "ss" stub but are not + # killed by the "ctdb killtcp" stub. So killing these + # connections will never succeed... and will look like a time + # out. + _t=$(_tcp_connections "$@" | sed -e 's/ /|/g') + export FAKE_NETSTAT_TCP_ESTABLISHED="$_t" +} + +# Setup some fake /proc/net/bonding files with just enough info for +# the eventscripts. + +# arg1 is interface name, arg2 is currently active slave (use "None" +# if none), arg3 is MII status ("up" or "down"). +setup_bond() +{ + _iface="$1" + _slave="${2:-${_iface}_sl_0}" + _mii_s="${3:-up}" + _mii_subs="${4:-${_mii_s:-up}}" + + cat <<EOF +Setting $_iface to be a bond with active slave $_slave and MII status $_mii_s +EOF + + _t="${FAKE_NETWORK_STATE}/proc-net-bonding" + export FAKE_PROC_NET_BONDING="$_t" + mkdir -p "$FAKE_PROC_NET_BONDING" + + cat >"${FAKE_PROC_NET_BONDING}/$_iface" <<EOF +Bonding Mode: IEEE 802.3ad Dynamic link aggregation +Currently Active Slave: $_slave +# Status of the bond +MII Status: $_mii_s +# Status of 1st pretend adapter +MII Status: $_mii_subs +# Status of 2nd pretend adapter +MII Status: $_mii_subs +EOF +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/11.natgw.sh b/ctdb/tests/UNIT/eventscripts/scripts/11.natgw.sh new file mode 100644 index 0000000..3b19895 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/11.natgw.sh @@ -0,0 +1,120 @@ +setup() +{ + debug "Setting up NAT gateway" + + natgw_nodes="${CTDB_BASE}/natgw_nodes" + + ctdb_set_pnn +} + +# A separate function for this makes sense because it can be done +# multiple times per test +setup_ctdb_natgw() +{ + # Read from stdin + while read -r _ip _opts; do + case "$_opts" in + leader) + export FAKE_CTDB_NATGW_LEADER="$_ip" + echo "$_ip" + ;; + follower-only) + printf "%s\tfollower-only\n" "$_ip" + ;; + *) + echo "$_ip" + ;; + esac + done >"$natgw_nodes" + + # Assume all of the nodes are on a /24 network and have IPv4 + # addresses: + read -r _ip <"$natgw_nodes" + + setup_script_options <<EOF +CTDB_NATGW_NODES="$natgw_nodes" +CTDB_NATGW_PRIVATE_NETWORK="${_ip%.*}.0/24" +# These are fixed. Probably don't use the same network for the +# private node IPs. To unset the default gateway just set it to +# "". :-) +CTDB_NATGW_PUBLIC_IP="10.1.1.121/24" +CTDB_NATGW_PUBLIC_IFACE="eth1" +CTDB_NATGW_DEFAULT_GATEWAY="10.1.1.254" +EOF +} + +ok_natgw_leader_ip_addr_show() +{ + _mac=$(echo "$CTDB_NATGW_PUBLIC_IFACE" | + cksum | + sed -r -e 's@(..)(..)(..).*@fe:fe:fe:\1:\2:\3@') + + # This is based on CTDB_NATGW_PUBLIC_IP + _brd="10.1.1.255" + + ok <<EOF +1: ${CTDB_NATGW_PUBLIC_IFACE}: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 + link/ether ${_mac} brd ff:ff:ff:ff:ff:ff + inet ${CTDB_NATGW_PUBLIC_IP} brd ${_brd} scope global ${CTDB_NATGW_PUBLIC_IFACE} + valid_lft forever preferred_lft forever +EOF +} + +ok_natgw_follower_ip_addr_show() +{ + _mac=$(echo "$CTDB_NATGW_PUBLIC_IFACE" | + cksum | + sed -r -e 's@(..)(..)(..).*@fe:fe:fe:\1:\2:\3@') + + ok <<EOF +1: ${CTDB_NATGW_PUBLIC_IFACE}: <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 +} + +ok_natgw_leader_static_routes() +{ + _nl=" +" + _t="" + for _i in $CTDB_NATGW_STATIC_ROUTES; do + # This is intentionally different to the code in 11.natgw ;-) + case "$_i" in + *@*) + _net=$(echo "$_i" | sed -e 's|@.*||') + _gw=$(echo "$_i" | sed -e 's|.*@||') + ;; + *) + _net="$_i" + _gw="$CTDB_NATGW_DEFAULT_GATEWAY" + ;; + esac + + [ -n "$_gw" ] || continue + _t="${_t}${_t:+${_nl}}" + _t="${_t}${_net} via ${_gw} dev ethXXX metric 10 " + done + _t=$(echo "$_t" | sort) + ok "$_t" +} + +ok_natgw_follower_static_routes() +{ + _nl=" +" + _t="" + for _i in $CTDB_NATGW_STATIC_ROUTES; do + # This is intentionally different to the code in 11.natgw ;-) + _net=$(echo "$_i" | sed -e 's|@.*||') + + # The interface for the private network isn't + # specified as part of the NATGW configuration and + # isn't part of the command to add the route. It is + # implicitly added by "ip route" but our stub doesn't + # do this and adds "ethXXX". + _t="${_t}${_t:+${_nl}}" + _t="${_t}${_net} via ${FAKE_CTDB_NATGW_LEADER} dev ethXXX metric 10 " + done + _t=$(echo "$_t" | sort) + ok "$_t" +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/13.per_ip_routing.sh b/ctdb/tests/UNIT/eventscripts/scripts/13.per_ip_routing.sh new file mode 100644 index 0000000..aac2c3d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/13.per_ip_routing.sh @@ -0,0 +1,47 @@ +setup() +{ + setup_public_addresses + + # shellcheck disable=SC2034 + # Used in expected output + service_name="per_ip_routing" + + setup_script_options <<EOF +CTDB_PER_IP_ROUTING_CONF="${CTDB_BASE}/policy_routing" +CTDB_PER_IP_ROUTING_RULE_PREF=100 +CTDB_PER_IP_ROUTING_TABLE_ID_LOW=1000 +CTDB_PER_IP_ROUTING_TABLE_ID_HIGH=2000 +EOF + + # Tests need to create and populate this file + rm -f "$CTDB_PER_IP_ROUTING_CONF" +} + +# Create policy routing configuration in $CTDB_PER_IP_ROUTING_CONF. +# $1 is the number of assigned IPs to use (<num>, all), defaulting to +# 1. If $2 is "default" then a default route is also added. +create_policy_routing_config() +{ + _num_ips="${1:-1}" + _should_add_default="$2" + + ctdb_get_my_public_addresses | + if [ "$_num_ips" = "all" ]; then + cat + else + { + head -n "$_num_ips" + cat >/dev/null + } + fi | + while read -r _dev _ip _bits; do + _net=$(ipv4_host_addr_to_net "$_ip" "$_bits") + _gw="${_net%.*}.254" # a dumb, calculated default + + echo "$_ip $_net" + + if [ "$_should_add_default" = "default" ]; then + echo "$_ip 0.0.0.0/0 $_gw" + fi + done >"$CTDB_PER_IP_ROUTING_CONF" +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/20.multipathd.sh b/ctdb/tests/UNIT/eventscripts/scripts/20.multipathd.sh new file mode 100644 index 0000000..9add0bc --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/20.multipathd.sh @@ -0,0 +1,25 @@ +setup() +{ + _failures="" + _devices="" + for i; do + case "$i" in + \!*) + _t="${i#!}" + echo "Marking ${_t} as having no active paths" + _failures="${_failures}${_failures:+ }${_t}" + ;; + *) + _t="$i" + ;; + esac + _devices="${_devices}${_devices:+ }${_t}" + done + + setup_script_options <<EOF +CTDB_MONITOR_MPDEVICES="$_devices" +EOF + + export FAKE_MULTIPATH_FAILURES="$_failures" + export FAKE_SLEEP_FORCE=0.1 +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/31.clamd.sh b/ctdb/tests/UNIT/eventscripts/scripts/31.clamd.sh new file mode 100644 index 0000000..27016cb --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/31.clamd.sh @@ -0,0 +1,8 @@ +setup() +{ + setup_script_options <<EOF +CTDB_CLAMD_SOCKET="/var/run/clamd.sock" +EOF + + setup_unix_listen +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/40.vsftpd.sh b/ctdb/tests/UNIT/eventscripts/scripts/40.vsftpd.sh new file mode 100644 index 0000000..236d130 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/40.vsftpd.sh @@ -0,0 +1,14 @@ +setup() +{ + debug "Setting up VSFTPD environment: service $1, not managed by CTDB" + + _service_name="vsftpd" + + if [ "$1" != "down" ]; then + service "$_service_name" start + setup_tcp_listen 21 + else + service "$_service_name" force-stopped + setup_tcp_listen "" + fi +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/41.httpd.sh b/ctdb/tests/UNIT/eventscripts/scripts/41.httpd.sh new file mode 100644 index 0000000..3fac4f0 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/41.httpd.sh @@ -0,0 +1,14 @@ +setup() +{ + debug "Setting up HTTPD environment: service $1, not managed by CTDB" + + if [ "$1" != "down" ]; then + for _service_name in "apache2" "httpd"; do + service "$_service_name" start + done + else + for _service_name in "apache2" "httpd"; do + service "$_service_name" force-stopped + done + fi +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/48.netbios.sh b/ctdb/tests/UNIT/eventscripts/scripts/48.netbios.sh new file mode 100644 index 0000000..6efcd8a --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/48.netbios.sh @@ -0,0 +1,23 @@ +setup() +{ + # shellcheck disable=SC2034 + # Used in expected output + service_name="netbios" + + if [ "$1" != "down" ]; then + + debug "Marking Netbios name services as up, listening and managed by CTDB" + + # All possible service names for all known distros. + for i in "nmb" "nmbd"; do + service "$i" force-started + done + else + debug "Marking Netbios name services as down, not listening and not managed by CTDB" + + # All possible service names for all known distros. + for i in "nmb" "nmbd"; do + service "$i" force-stopped + done + fi +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/49.winbind.sh b/ctdb/tests/UNIT/eventscripts/scripts/49.winbind.sh new file mode 100644 index 0000000..bbe1de2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/49.winbind.sh @@ -0,0 +1,28 @@ +setup() +{ + # shellcheck disable=SC2034 + # Used in expected output + service_name="winbind" + + if [ "$1" != "down" ]; then + + debug "Marking Winbind service as up and managed by CTDB" + + service "winbind" force-started + + export FAKE_WBINFO_FAIL="no" + + else + debug "Marking Winbind service as down and not managed by CTDB" + + service "winbind" force-stopped + + export FAKE_WBINFO_FAIL="yes" + fi +} + +wbinfo_down() +{ + debug "Making wbinfo commands fail" + FAKE_WBINFO_FAIL="yes" +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/50.samba.sh b/ctdb/tests/UNIT/eventscripts/scripts/50.samba.sh new file mode 100644 index 0000000..88af69b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/50.samba.sh @@ -0,0 +1,58 @@ +setup() +{ + # shellcheck disable=SC2034 + # Used in expected output + service_name="samba" + + if [ "$1" != "down" ]; then + + debug "Marking Samba services as up, listening and managed by CTDB" + + # All possible service names for all known distros. + for i in "smb" "samba" "smbd"; do + service "$i" force-started + done + + setup_tcp_listen 445 139 + + # Some things in 50.samba are backgrounded and waited + # for. If we don't sleep at all then timeouts can + # happen. This avoids that... :-) + export FAKE_SLEEP_FORCE=0.1 + else + debug "Marking Samba services as down, not listening and not managed by CTDB" + + # All possible service names for all known distros. + for i in "smb" "samba" "smbd"; do + service "$i" force-stopped + done + + setup_tcp_listen + fi + + setup_script_options <<EOF +CTDB_SAMBA_SKIP_SHARE_CHECK="no" +EOF + + setup_shares + +} + +samba_setup_fake_threads() +{ + export FAKE_SMBD_THREAD_PIDS="$*" + + _nl=" +" + _out="" + _count=0 + for _pid; do + [ "$_count" -lt 5 ] || break + _t=$(program_stack_trace "smbd" "$_pid") + _out="${_out:+${_out}${_nl}}${_t}" + _count=$((_count + 1)) + done + # shellcheck disable=SC2034 + # Used in expected output + SAMBA_STACK_TRACES="$_out" +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/60.nfs.sh b/ctdb/tests/UNIT/eventscripts/scripts/60.nfs.sh new file mode 100644 index 0000000..9c614c7 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/60.nfs.sh @@ -0,0 +1,435 @@ +setup() +{ + setup_public_addresses + setup_shares + + # shellcheck disable=SC2034 + # Used in expected output + service_name="nfs" + + if [ -z "$CTDB_NFS_DISTRO_STYLE" ]; then + # Currently supported: sysvinit-redhat, systemd-redhat + CTDB_NFS_DISTRO_STYLE="systemd-redhat" + fi + + export FAKE_RPCINFO_SERVICES="" + + setup_script_options <<EOF +CTDB_NFS_SKIP_SHARE_CHECK="no" +# This doesn't even need to exist +CTDB_NFS_EXPORTS_FILE="${CTDB_TEST_TMP_DIR}/etc-exports" +EOF + + export RPCNFSDCOUNT + + if [ "$1" != "down" ]; then + debug <<EOF +Setting up NFS environment: all RPC services up, NFS managed by CTDB +EOF + + case "$CTDB_NFS_DISTRO_STYLE" in + sysvinit-*) + service "nfs" force-started + service "nfslock" force-started + ;; + systemd-*) + service "nfs-service" force-started + service "nfs-mountd" force-started + service "rpc-rquotad" force-started + service "rpc-statd" force-started + ;; + esac + + rpc_services_up \ + "portmapper" "nfs" "mountd" "rquotad" \ + "nlockmgr" "status" + + nfs_setup_fake_threads "nfsd" + nfs_setup_fake_threads "rpc.foobar" # Set the variable to empty + else + debug <<EOF +Setting up NFS environment: all RPC services down, NFS not managed by CTDB +EOF + + case "$CTDB_NFS_DISTRO_STYLE" in + sysvinit-*) + service "nfs" force-stopped + service "nfslock" force-stopped + service "nfs-kernel-server" force-stopped + ;; + systemd-*) + service "nfs-server" force-stopped + service "nfs-mountd" force-stopped + service "rpc-quotad" force-stopped + service "rpc-statd" force-stopped + ;; + esac + fi + + # This is really nasty. However, when we test NFS we don't + # actually test statd-callout. If we leave it there then left + # over, backgrounded instances of statd-callout will do + # horrible things with the "ctdb ip" stub and cause the actual + # statd-callout tests that follow to fail. + rm "${CTDB_BASE}/statd-callout" +} + +rpc_services_down() +{ + _out="" + for _s in $FAKE_RPCINFO_SERVICES; do + for _i; do + if [ "$_i" = "${_s%%:*}" ]; then + debug "Marking RPC service \"${_i}\" as UNAVAILABLE" + continue 2 + fi + done + _out="${_out}${_out:+ }${_s}" + done + FAKE_RPCINFO_SERVICES="$_out" +} + +rpc_services_up() +{ + _out="$FAKE_RPCINFO_SERVICES" + for _i; do + debug "Marking RPC service \"${_i}\" as available" + case "$_i" in + portmapper) _t="2:4" ;; + nfs) _t="2:3" ;; + mountd) _t="1:3" ;; + rquotad) _t="1:2" ;; + nlockmgr) _t="3:4" ;; + status) _t="1:1" ;; + *) die "Internal error - unsupported RPC service \"${_i}\"" ;; + esac + + _out="${_out}${_out:+ }${_i}:${_t}" + done + export FAKE_RPCINFO_SERVICES="$_out" +} + +nfs_setup_fake_threads() +{ + _prog="$1" + shift + + case "$_prog" in + nfsd) + export PROCFS_PATH="${CTDB_TEST_TMP_DIR}/proc" + _threads="${PROCFS_PATH}/fs/nfsd/threads" + mkdir -p "$(dirname "$_threads")" + echo $# >"$_threads" + export FAKE_NFSD_THREAD_PIDS="$*" + ;; + *) + export FAKE_RPC_THREAD_PIDS="$*" + ;; + esac +} + +guess_output() +{ + case "$1" in + "${CTDB_NFS_CALLOUT} start nlockmgr") + case "$CTDB_NFS_DISTRO_STYLE" in + sysvinit-redhat) + echo "&Starting nfslock: OK" + ;; + sysvinit-debian) + cat <<EOF +&Starting nfs-kernel-server: OK +EOF + ;; + systemd-*) + echo "&Starting rpc-statd: OK" + ;; + esac + ;; + "${CTDB_NFS_CALLOUT} start nfs") + case "$CTDB_NFS_DISTRO_STYLE" in + sysvinit-redhat) + cat <<EOF +&Starting nfslock: OK +&Starting nfs: OK +EOF + ;; + sysvinit-debian) + cat <<EOF +&Starting nfs-kernel-server: OK +EOF + ;; + systemd-redhat) + cat <<EOF +&Starting rpc-statd: OK +&Starting nfs-server: OK +&Starting rpc-rquotad: OK +EOF + ;; + systemd-debian) + cat <<EOF +&Starting rpc-statd: OK +&Starting nfs-server: OK +&Starting quotarpc: OK +EOF + ;; + esac + ;; + "${CTDB_NFS_CALLOUT} stop mountd") + case "$CTDB_NFS_DISTRO_STYLE" in + systemd-*) + echo "Stopping nfs-mountd: OK" + ;; + esac + ;; + "${CTDB_NFS_CALLOUT} stop rquotad") + case "$CTDB_NFS_DISTRO_STYLE" in + systemd-redhat) + echo "Stopping rpc-rquotad: OK" + ;; + systemd-debian) + if service "quotarpc" status >/dev/null; then + echo "Stopping quotarpc: OK" + else + echo "service: can't stop quotarpc - not running" + fi + ;; + esac + ;; + "${CTDB_NFS_CALLOUT} stop status") + case "$CTDB_NFS_DISTRO_STYLE" in + systemd-*) + echo "Stopping rpc-statd: OK" + ;; + esac + ;; + "${CTDB_NFS_CALLOUT} start mountd") + case "$CTDB_NFS_DISTRO_STYLE" in + systemd-*) + echo "&Starting nfs-mountd: OK" + ;; + esac + ;; + "${CTDB_NFS_CALLOUT} start rquotad") + case "$CTDB_NFS_DISTRO_STYLE" in + systemd-redhat) + echo "&Starting rpc-rquotad: OK" + ;; + systemd-debian) + echo "&Starting quotarpc: OK" + ;; + esac + ;; + "${CTDB_NFS_CALLOUT} start status") + case "$CTDB_NFS_DISTRO_STYLE" in + systemd-*) + echo "&Starting rpc-statd: OK" + ;; + esac + ;; + *) + : # Nothing + ;; + esac +} + +# Set the required result for a particular RPC program having failed +# for a certain number of iterations. This is probably still a work +# in progress. Note that we could hook aggressively +# nfs_check_rpc_service() to try to implement this but we're better +# off testing nfs_check_rpc_service() using independent code... even +# if it is incomplete and hacky. So, if the 60.nfs eventscript +# changes and the tests start to fail then it may be due to this +# function being incomplete. +rpc_set_service_failure_response() +{ + _rpc_service="$1" + _numfails="${2:-1}" # default 1 + + # Default + ok_null + if [ "$_numfails" -eq 0 ]; then + return + fi + + nfs_load_config + + # A handy newline. :-) + _nl=" +" + + _dir="${CTDB_NFS_CHECKS_DIR:-${CTDB_BASE}/nfs-checks.d}" + + _file=$(ls "$_dir"/[0-9][0-9]."${_rpc_service}.check") + [ -r "$_file" ] || + die "RPC check file \"$_file\" does not exist or is not unique" + + _out="${CTDB_TEST_TMP_DIR}/rpc_failure_output" + : >"$_out" + _rc_file="${CTDB_TEST_TMP_DIR}/rpc_result" + + ( + # Subshell to restrict scope variables... + + # Defaults + # shellcheck disable=SC2034 + # Unused, but for completeness, possible future use + family="tcp" + version="" + unhealthy_after=1 + restart_every=0 + service_stop_cmd="" + service_start_cmd="" + # shellcheck disable=SC2034 + # Unused, but for completeness, possible future use + service_check_cmd="" + service_debug_cmd="" + + # Don't bother syntax checking, eventscript does that... + . "$_file" + + # Just use the first version, or use default. This is + # dumb but handles all the cases that we care about + # now... + if [ -n "$version" ]; then + _ver="${version%% *}" + else + case "$_rpc_service" in + portmapper) _ver="" ;; + *) _ver=1 ;; + esac + fi + _rpc_check_out="\ +$_rpc_service failed RPC check: +rpcinfo: RPC: Program not registered +program $_rpc_service${_ver:+ version }${_ver} is not available" + + if [ $unhealthy_after -gt 0 ] && + [ "$_numfails" -ge $unhealthy_after ]; then + _unhealthy=true + echo 1 >"$_rc_file" + echo "ERROR: ${_rpc_check_out}" >>"$_out" + else + _unhealthy=false + echo 0 >"$_rc_file" + fi + + if [ $restart_every -gt 0 ] && + [ $((_numfails % restart_every)) -eq 0 ]; then + if ! $_unhealthy; then + echo "WARNING: ${_rpc_check_out}" >>"$_out" + fi + + echo "Trying to restart service \"${_rpc_service}\"..." \ + >>"$_out" + + guess_output "$service_stop_cmd" >>"$_out" + + if [ -n "$service_debug_cmd" ]; then + $service_debug_cmd >>"$_out" 2>&1 + fi + + guess_output "$service_start_cmd" >>"$_out" + fi + ) + + read -r _rc <"$_rc_file" + required_result "$_rc" <"$_out" + + rm -f "$_out" "$_rc_file" +} + +program_stack_traces() +{ + _prog="$1" + _max="${2:-1}" + + _count=1 + if [ "$_prog" = "nfsd" ]; then + _pids="$FAKE_NFSD_THREAD_PIDS" + else + _pids="$FAKE_RPC_THREAD_PIDS" + fi + for _pid in $_pids; do + [ $_count -le "$_max" ] || break + + program_stack_trace "$_prog" "$_pid" + _count=$((_count + 1)) + done +} + +# Run an NFS eventscript iteratively. +# +# - 1st argument is the number of iterations. +# +# - 2nd argument is the NFS/RPC service being tested +# +# rpcinfo is used on each iteration to test the availability of the +# service +# +# If this is not set or null then no RPC service is checked and the +# required output is not reset on each iteration. This is useful in +# baseline tests to confirm that the eventscript and test +# infrastructure is working correctly. +# +# - Subsequent arguments come in pairs: an iteration number and +# something to eval before that iteration. Each time an iteration +# number is matched the associated argument is given to eval after +# the default setup is done. The iteration numbers need to be given +# in ascending order. +# +# These arguments can allow a service to be started or stopped +# before a particular iteration. +# +nfs_iterate_test() +{ + _repeats="$1" + _rpc_service="$2" + if [ -n "$2" ]; then + shift 2 + else + shift + fi + + # shellcheck disable=SC2154 + # Variables defined in define_test() + echo "Running $_repeats iterations of \"$script $event\" $args" + + _iterate_failcount=0 + for _iteration in $(seq 1 "$_repeats"); do + # This is not a numerical comparison because $1 will + # often not be set. + if [ "$_iteration" = "$1" ]; then + debug <<EOF +################################################## +EOF + eval "$2" + debug <<EOF +################################################## +EOF + shift 2 + fi + if [ -n "$_rpc_service" ]; then + if rpcinfo -T tcp localhost "$_rpc_service" \ + >/dev/null 2>&1 ; then + _iterate_failcount=0 + else + _iterate_failcount=$((_iterate_failcount + 1)) + fi + rpc_set_service_failure_response \ + "$_rpc_service" $_iterate_failcount + fi + _out=$(simple_test 2>&1) + _ret=$? + if "$CTDB_TEST_VERBOSE" || [ $_ret -ne 0 ]; then + cat <<EOF +################################################## +Iteration ${_iteration}: +$_out +EOF + fi + if [ $_ret -ne 0 ]; then + exit $_ret + fi + done +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/91.lvs.sh b/ctdb/tests/UNIT/eventscripts/scripts/91.lvs.sh new file mode 100644 index 0000000..1e307c5 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/91.lvs.sh @@ -0,0 +1,76 @@ +setup() +{ + _ip="$1" + _iface="$2" + + export FAKE_LVS_STATE_DIR="${FAKE_NETWORK_STATE}/lvs" + mkdir -p "$FAKE_LVS_STATE_DIR" + + lvs_header=$(ipvsadm -l -n) + + [ -n "$_ip" ] || return 0 + [ -n "$_iface" ] || return 0 + + setup_script_options <<EOF +CTDB_LVS_NODES="${CTDB_BASE}/lvs_nodes" +CTDB_LVS_PUBLIC_IP="$_ip" +CTDB_LVS_PUBLIC_IFACE="$_iface" +EOF + + export FAKE_CTDB_LVS_LEADER="" + + # Read from stdin + _pnn=0 + while read -r _ip _opts; do + case "$_opts" in + leader) + FAKE_CTDB_LVS_LEADER="$_pnn" + echo "$_ip" + ;; + follower-only) + printf "%s\tfollower-only\n" "$_ip" + ;; + *) + echo "$_ip" + ;; + esac + _pnn=$((_pnn + 1)) + done >"$CTDB_LVS_NODES" +} + +check_ipvsadm() +{ + if [ "$1" = "NULL" ]; then + required_result 0 <<EOF +$lvs_header +EOF + else + required_result 0 <<EOF +$lvs_header +$(cat) +EOF + fi + + simple_test_command ipvsadm -l -n +} + +check_lvs_ip() +{ + _scope="$1" + + if [ "$_scope" = "NULL" ]; then + required_result 0 <<EOF +1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 +EOF + else + required_result 0 <<EOF +1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet ${CTDB_LVS_PUBLIC_IP}/32 scope ${_scope} lo + valid_lft forever preferred_lft forever +EOF + fi + + simple_test_command ip addr show dev lo +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/debug_locks.sh b/ctdb/tests/UNIT/eventscripts/scripts/debug_locks.sh new file mode 100644 index 0000000..b0cd039 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/debug_locks.sh @@ -0,0 +1,272 @@ +setup() +{ + setup_dbdir +} + +result_filter() +{ + sed -e 's|\( of debug locks PID=\)[0-9]*|\1PID|' +} + +tdb_path() +{ + echo "${CTDB_DBDIR}/${1}.${FAKE_CTDB_PNN}" +} + +fake_file_id() +{ + _path="$1" + + echo "$FAKE_FILE_ID_MAP" | + awk -v path="$_path" '$1 == path { print $2 }' +} + +fake_stack_trace() +{ + _pid="$1" + _command="${2:-smbd}" + _state="$3" + + echo "----- Stack trace for PID=${_pid} -----" + + case "$_state" in + D*) + cat <<EOF +----- Process in D state, printing kernel stack only +[<ffffffff87654321>] fake_stack_trace_for_pid_${_pid}/stack+0x0/0xff +EOF + ;; + *) + cat <<EOF +Thread 1 (Thread 0x7f688fbfb180 (LWP ${_pid}) "${_command}"): +#0 0x00007f688ff7a076 in open (FAKE ARGS...) at FAKE PLACE +.... +#3 0x000055cd368ead72 in main (argc=<optimized out>, argv=<optimized out>) at ${_command}.c +EOF + ;; + esac +} + +do_test() +{ + _holder_scope="$1" + _holder_state="$2" + _helper_scope="$3" + _lock_type="${4:-FCNTL}" + + _lock_helper_pid="4132032" + + FAKE_PS_MAP=$( + cat <<EOF +1234567 ctdbd S +2345678 smbd S +4131931 smbd ${_holder_state} +${_lock_helper_pid} ctdb_lock_helpe S+ +EOF + ) + export FAKE_PS_MAP + + FAKE_FILE_ID_MAP="" + _tdbs="locking.tdb brlock.tdb test.tdb foo.tdb" + _n=1 + for _t in $_tdbs; do + _path=$(tdb_path "$_t") + _inode=$((19690818 + _n)) + FAKE_FILE_ID_MAP=$( + cat <<EOF +${FAKE_FILE_ID_MAP} +${_path} 103:04:${_inode} +EOF + ) + rm -f "$_path" + touch "$_path" + _n=$((_n + 1)) + done + export FAKE_FILE_ID_MAP + + _path=$(tdb_path "locking.tdb") + _locking_tdb_id=$(fake_file_id "$_path") + + _t=$( + cat <<EOF +POSIX ADVISORY WRITE 3769740 103:04:24380821 1073741826 1073742335 +FLOCK ADVISORY WRITE 3632524 103:02:1059266 0 EOF +FLOCK ADVISORY WRITE 4060231 00:17:17184 0 EOF +POSIX ADVISORY READ 1234567 ${_locking_tdb_id} 4 4 +POSIX ADVISORY WRITE 59178 103:04:24380821 1073741826 1073742335 +POSIX ADVISORY READ 4427 103:04:22152234 1073741826 1073742335 +POSIX ADVISORY WRITE 4427 103:04:22152494 0 EOF +POSIX ADVISORY READ 4427 103:04:22152702 1073741826 1073742335 +EOF + ) + + _holder_lock="" + if [ "$_holder_scope" = "DB" ]; then + if [ "$_lock_type" = "FCNTL" ]; then + _holder_lock=$( + cat <<EOF +POSIX ADVISORY WRITE 4131931 ${_locking_tdb_id} 168 EOF +EOF + ) + elif [ "$_lock_type" = "MUTEX" ]; then + _holder_lock=$( + cat <<EOF +POSIX ADVISORY WRITE 4131931 ${_locking_tdb_id} 400172 EOF +EOF + ) + fi + elif [ "$_holder_scope" = "RECORD" ] && + [ "$_lock_type" = "FCNTL" ]; then + _holder_lock=$( + cat <<EOF +POSIX ADVISORY WRITE 2345678 ${_locking_tdb_id} 112736 112736 +POSIX ADVISORY WRITE 4131931 ${_locking_tdb_id} 225472 225472 +EOF + ) + fi + + _t=$( + cat <<EOF +$_t +$_holder_lock +EOF + ) + + _helper_lock="" + if [ "$_helper_scope" = "DB" ] && + [ "$_lock_type" = "FCNTL" ]; then + _helper_lock=$( + cat <<EOF +-> POSIX ADVISORY WRITE ${_lock_helper_pid} ${_locking_tdb_id} 168 170 +EOF + ) + elif [ "$_helper_scope" = "RECORD" ] && + [ "$_lock_type" = "FCNTL" ]; then + _helper_lock=$( + cat <<EOF +-> POSIX ADVISORY WRITE ${_lock_helper_pid} ${_locking_tdb_id} 112736 112736 +EOF + ) + fi + _t=$( + cat <<EOF +$_t +$_helper_lock +EOF + ) + + if [ "$_holder_scope" = "DB" ]; then + _t=$( + cat <<EOF +$_t +POSIX ADVISORY READ 4131931 ${_locking_tdb_id} 4 4 +EOF + ) + elif [ "$_holder_scope" = "RECORD" ] && + [ "$_lock_type" = "FCNTL" ]; then + _t=$( + cat <<EOF +$_t +POSIX ADVISORY READ 2345678 ${_locking_tdb_id} 4 4 +POSIX ADVISORY READ 4131931 ${_locking_tdb_id} 4 4 +EOF + ) + fi + + _t=$( + cat <<EOF +$_t +POSIX ADVISORY READ 3769740 103:04:24390149 1073741826 1073742335 +POSIX ADVISORY WRITE 3769740 103:04:24380839 1073741826 1073742335 +FLOCK ADVISORY WRITE 3769302 103:02:1180313 0 EOF +FLOCK ADVISORY WRITE 3769302 103:02:1177487 0 EOF +FLOCK ADVISORY WRITE 3769302 103:02:1180308 0 EOF +OFDLCK ADVISORY READ -1 00:05:6 0 EOF +EOF + ) + + FAKE_PROC_LOCKS=$(echo "$_t" | awk '{ printf "%d: %s\n", NR, $0 }') + export FAKE_PROC_LOCKS + + _holder_mutex_lock="" + if [ "$_lock_type" = "MUTEX" ]; then + if [ "$_holder_scope" = "RECORD" ]; then + _holder_mutex_lock=$( + cat <<EOF +2345678 28142 +4131931 56284 +EOF + ) + fi + fi + + FAKE_TDB_MUTEX_CHECK="$_holder_mutex_lock" + export FAKE_TDB_MUTEX_CHECK + + _out='' + _nl=' +' + _db="locking.tdb.${FAKE_CTDB_PNN}" + + if [ -n "$_helper_lock" ]; then + read -r _ _ _ _ _pid _ _start _end <<EOF +$_helper_lock +EOF + _out="Waiter:${_nl}" + _out="${_out}${_pid} ctdb_lock_helpe ${_db} ${_start} ${_end}" + fi + + # fake lock info + _pids='' + _out="${_out:+${_out}${_nl}}Lock holders:" + if [ -n "$_holder_mutex_lock" ]; then + while read -r _pid _chain; do + _comm="smbd" + _out="${_out}${_nl}" + _out="${_out}${_pid} smbd ${_db} ${_chain}" + _pids="${_pids:+${_pids} }${_pid}" + done <<EOF +$_holder_mutex_lock +EOF + else + while read -r _ _ _ _pid _ _start _end; do + _comm="smbd" + _out="${_out}${_nl}" + _out="${_out}${_pid} smbd ${_db} ${_start} ${_end}" + _pids="${_pids:+${_pids} }${_pid}" + done <<EOF +$_holder_lock +EOF + fi + + # fake stack traces + for _pid in $_pids; do + _comm="smbd" + if [ "$_pid" = "4131931" ]; then + _state="$_holder_state" + else + _state="S" + fi + _out=$( + cat <<EOF +$_out +$(fake_stack_trace "$_pid" "$_comm" "$_state") +EOF + ) + done + + ok <<EOF +===== Start of debug locks PID=PID ===== +$_out +===== End of debug locks PID=PID ===== +EOF + + # shellcheck disable=SC2154 + # script_dir and script set in define_test() + script_test "${script_dir}/${script}" \ + "$_lock_helper_pid" \ + "$_helper_scope" \ + "$_path" \ + "$_lock_type" + +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/local.sh b/ctdb/tests/UNIT/eventscripts/scripts/local.sh new file mode 100644 index 0000000..3c28181 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/local.sh @@ -0,0 +1,568 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +# +# Augment PATH with relevant stubs/ directories. +# + +stubs_dir="${CTDB_TEST_SUITE_DIR}/stubs" +[ -d "${stubs_dir}" ] || die "Failed to locate stubs/ subdirectory" + +# Make the path absolute for tests that change directory +case "$stubs_dir" in +/*) : ;; +*) stubs_dir="${PWD}/${stubs_dir}" ;; +esac + +# Use stubs as helpers +export CTDB_HELPER_BINDIR="$stubs_dir" + +PATH="${stubs_dir}:${PATH}" + +export CTDB="ctdb" + +# Force this to be absolute - event scripts can change directory +CTDB_TEST_TMP_DIR=$(cd "$CTDB_TEST_TMP_DIR" && echo "$PWD") + +export CTDB_LOGGING="file:" + +if [ -d "${CTDB_TEST_SUITE_DIR}/etc" ]; then + cp -a "${CTDB_TEST_SUITE_DIR}/etc" "$CTDB_TEST_TMP_DIR" + export CTDB_SYS_ETCDIR="${CTDB_TEST_TMP_DIR}/etc" +else + die "Unable to setup \$CTDB_SYS_ETCDIR" +fi + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "etc-ctdb" \ + debug_locks.sh \ + functions \ + nfs-checks.d \ + nfs-linux-kernel-callout \ + statd-callout + +export FAKE_CTDB_STATE="${CTDB_TEST_TMP_DIR}/fake-ctdb" +mkdir -p "$FAKE_CTDB_STATE" + +export FAKE_NETWORK_STATE="${CTDB_TEST_TMP_DIR}/fake-network-state" +mkdir -p "$FAKE_NETWORK_STATE" + +###################################################################### + +if "$CTDB_TEST_VERBOSE"; then + debug() + { + if [ -n "$1" ]; then + echo "$@" >&2 + else + cat >&2 + fi + } +else + debug() + { + : + } +fi + +###################################################################### + +# General setup fakery + +# Default is to use script name with ".options" appended. With +# arguments, this can specify an alternate script name (and +# component). +setup_script_options() +{ + if [ $# -eq 2 ]; then + _script="$2" + elif [ $# -eq 0 ]; then + _script="" + else + die "usage: setup_script_options [ component script ]" + fi + + if [ -n "$_script" ]; then + _options="${CTDB_BASE}/events/legacy/${_script}.options" + else + _options="${script_dir}/${script%.script}.options" + fi + + cat >>"$_options" + + # Source the options so that tests can use the variables + . "$_options" +} + +setup_dbdir() +{ + export CTDB_DBDIR_BASE="${CTDB_TEST_TMP_DIR}/db" + CTDB_DBDIR="${CTDB_DBDIR_BASE}/volatile" + CTDB_DBDIR_PERSISTENT="${CTDB_DBDIR_BASE}/persistent" + CTDB_DBDIR_STATE="${CTDB_DBDIR_BASE}/state" + cat >>"${CTDB_BASE}/ctdb.conf" <<EOF +[database] + volatile database directory = ${CTDB_DBDIR} + persistent database directory = ${CTDB_DBDIR_PERSISTENT} + state database directory = ${CTDB_DBDIR_STATE} +EOF + mkdir -p "$CTDB_DBDIR" + mkdir -p "$CTDB_DBDIR_PERSISTENT" + mkdir -p "$CTDB_DBDIR_STATE" +} + +setup_date() +{ + export FAKE_DATE_OUTPUT="$1" +} + +setup_tcp_listen() +{ + export FAKE_TCP_LISTEN="$*" +} + +tcp_port_listening() +{ + for _i; do + FAKE_TCP_LISTEN="${FAKE_TCP_LISTEN} ${_i}" + done +} + +tcp_port_down() +{ + _port="$1" + debug "Marking TCP port \"${_port}\" as not listening" + + _t="" + for _i in $FAKE_TCP_LISTEN; do + if [ "$_i" = "$_port" ]; then + continue + fi + _t="${_t} ${_i}" + done + + FAKE_TCP_LISTEN="$_t" +} + +setup_unix_listen() +{ + export FAKE_NETSTAT_UNIX_LISTEN="$*" +} + +unix_socket_listening() +{ + _s="$1" + + FAKE_NETSTAT_UNIX_LISTEN="${FAKE_NETSTAT_UNIX_LISTEN} ${_s}" +} + +setup_shares() +{ + debug "Setting up shares (3 existing shares)" + # Create 3 fake shares/exports. + export FAKE_SHARES="" + for i in $(seq 1 3); do + _s="${CTDB_TEST_TMP_DIR}/shares/share${i}" + mkdir -p "$_s" + FAKE_SHARES="${FAKE_SHARES}${FAKE_SHARES:+ }${_s}" + done +} + +shares_missing() +{ + # Mark some shares as non-existent + _type="$1" + shift + + _out="" + _nl=" +" + + _n=1 + for _i in $FAKE_SHARES; do + for _j; do + if [ $_n -ne "$_j" ]; then + continue + fi + + debug "Mark share $_n as missing share \"$_i\"" + rmdir "$_i" + _t=$(printf "ERROR: %s directory \"%s\" not available" \ + "$_type" "${_i}") + _out="${_out}${_out:+${_nl}}${_t}" + done + _n=$((_n + 1)) + done + + echo "$_out" +} + +_ethtool_setup() +{ + FAKE_ETHTOOL_LINK_DOWN="${FAKE_NETWORK_STATE}/ethtool-link-down" + export FAKE_ETHTOOL_LINK_DOWN + mkdir -p "$FAKE_ETHTOOL_LINK_DOWN" +} + +ethtool_interfaces_down() +{ + _ethtool_setup + + for _i; do + echo "Marking interface $_i DOWN for ethtool" + touch "${FAKE_ETHTOOL_LINK_DOWN}/${_i}" + done +} + +ethtool_interfaces_up() +{ + _ethtool_setup + + for _i; do + echo "Marking interface $_i UP for ethtool" + rm -f "${FAKE_ETHTOOL_LINK_DOWN}/${_i}" + done +} + +dump_routes() +{ + echo "# ip rule show" + ip rule show + + ip rule show | + while read -r _p _ _i _ _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="." + # shellcheck disable=SC2086 + # Intentional word splitting + 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 + +# shellcheck disable=SC2120 +# Argument can be used in testcases +setup_numnodes() +{ + export FAKE_CTDB_NUMNODES="${1:-3}" + echo "Setting up CTDB with ${FAKE_CTDB_NUMNODES} fake nodes" +} + +# For now this creates the same public addresses each time. However, +# it could be made more flexible. +setup_public_addresses() +{ + _f="${CTDB_BASE}/public_addresses" + + echo "Setting up public addresses in ${_f}" + cat >"$_f" <<EOF +# This is a comment +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 +EOF + + # Needed for IP allocation + setup_numnodes +} + +# Need to cope with ctdb_get_pnn(). If a test changes PNN then it +# needs to be using a different state directory, otherwise the wrong +# PNN can already be cached in the state directory. +ctdb_set_pnn() +{ + export FAKE_CTDB_PNN="$1" + echo "Setting up PNN ${FAKE_CTDB_PNN}" + + CTDB_SCRIPT_VARDIR="${CTDB_TEST_TMP_DIR}/scripts/${FAKE_CTDB_PNN}" + export CTDB_SCRIPT_VARDIR + mkdir -p "$CTDB_SCRIPT_VARDIR" +} + +ctdb_get_interfaces() +{ + ctdb ifaces -X | awk -F'|' 'FNR > 1 {print $2}' | xargs +} + +ctdb_get_1_interface() +{ + _t=$(ctdb_get_interfaces) + echo "${_t%% *}" +} + +# 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 -X | { + read -r _ # skip header line + + while IFS="|" read -r _ _ip _ _iface _; do + [ -n "$_iface" ] || continue + while IFS="/$IFS" read -r _i _maskbits _; do + if [ "$_ip" = "$_i" ]; then + echo "$_iface $_ip $_maskbits" + break + fi + done <"${CTDB_BASE}/public_addresses" + done + } +} + +# Prints the 1st public address as: interface IP maskbits +# This is suitable for passing to takeip/releaseip +ctdb_get_1_public_address() +{ + ctdb_get_my_public_addresses | { + head -n 1 + cat >/dev/null + } +} + +# Check the routes against those that are expected. $1 is the number +# of assigned IPs to use (<num>, all), defaulting to 1. If $2 is +# "default" then expect default routes to have been added. +check_routes() +{ + _num_ips="${1:-1}" + _should_add_default="$2" + + _policy_rules="" + _policy_routes="" + + ctdb_get_my_public_addresses | + if [ "$_num_ips" = "all" ]; then + cat + else + { + head -n "$_num_ips" + cat >/dev/null + } + fi | { + while read -r _dev _ip _bits; do + _net=$(ipv4_host_addr_to_net "$_ip" "$_bits") + _gw="${_net%.*}.254" # a dumb, calculated default + + _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 " + + if [ "$_should_add_default" = "default" ]; then + _policy_routes="${_policy_routes} +default via $_gw dev $_dev " + fi + 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 + } || test_fail +} + +###################################################################### + +nfs_load_config() +{ + _etc="$CTDB_SYS_ETCDIR" # shortcut for readability + for _c in "$_etc/sysconfig/nfs" "$_etc/default/nfs" "$_etc/ctdb/sysconfig/nfs"; do + if [ -r "$_c" ]; then + . "$_c" + break + fi + done +} + +setup_nfs_callout() +{ + export CTDB_NFS_CALLOUT="${CTDB_HELPER_BINDIR}/nfs-fake-callout" + export NFS_FAKE_CALLOUT_MAGIC="$1" +} + +program_stack_trace() +{ + _prog="$1" + _pid="$2" + + cat <<EOF +Stack trace for ${_prog}[${_pid}]: +[<ffffffff87654321>] fake_stack_trace_for_pid_${_pid}/stack+0x0/0xff +EOF +} + +###################################################################### + +# Result and test functions + +############################################################ + +setup() +{ + die "setup() is not defined" +} + +# Set some globals and print the summary. +define_test() +{ + desc="$1" + + _f=$(basename "$0" ".sh") + + # Remaining format should be NN.script.event.NUM or + # NN.script.NUM or script.NUM: + _num="${_f##*.}" + _f="${_f%.*}" + + case "$_f" in + [0-9][0-9].*) + case "$_f" in + [0-9][0-9].*.*) + script="${_f%.*}.script" + event="${_f##*.}" + ;; + [0-9][0-9].*) + script="${_f}.script" + unset event + ;; + esac + # "Enable" the script + _subdir="events/legacy" + script_dir="${CTDB_BASE}/${_subdir}" + # Symlink target needs to be absolute + case "$CTDB_SCRIPTS_DATA_DIR" in + /*) _data_dir="${CTDB_SCRIPTS_DATA_DIR}/${_subdir}" ;; + *) _data_dir="${PWD}/${CTDB_SCRIPTS_DATA_DIR}/${_subdir}" ;; + esac + mkdir -p "$script_dir" + ln -s "${_data_dir}/${script}" "$script_dir" + ;; + *) + script="${_f%.*}" + script="$_f" + unset event + script_dir="${CTDB_BASE}" + ;; + esac + + _s="${script_dir}/${script}" + [ -r "$_s" ] || + die "Internal error - unable to find script \"${_s}\"" + + case "$script" in + *.script) script_short="${script%.script}" ;; + *.sh) script_short="${script%.sh}" ;; + *) script_short="$script" ;; + esac + + printf "%-17s %-10s %-4s - %s\n\n" \ + "$script_short" "$event" "$_num" "$desc" + + _f="${CTDB_TEST_SUITE_DIR}/scripts/${script_short}.sh" + if [ -r "$_f" ]; then + . "$_f" + fi + + ctdb_set_pnn 0 +} + +# Run an eventscript once. The test passes if the return code and +# output match those required. + +# Any args are passed to the eventscript. + +simple_test() +{ + [ -n "$event" ] || die 'simple_test: event not set' + + args="$*" + + # shellcheck disable=SC2317 + # used in unit_test(), etc. + test_header() + { + echo "Running script \"$script $event${args:+ }$args\"" + } + + # shellcheck disable=SC2317 + # used in unit_test(), etc. + extra_header() + { + cat <<EOF + +################################################## +CTDB_BASE="$CTDB_BASE" +CTDB_SYS_ETCDIR="$CTDB_SYS_ETCDIR" +ctdb client is "$(which ctdb)" +ip command is "$(which ip)" +EOF + } + + script_test "${script_dir}/${script}" "$event" "$@" + + reset_test_header + reset_extra_header +} + +simple_test_event() +{ + # If something has previously failed then don't continue. + : "${_passed:=true}" + $_passed || return 1 + + event="$1" + shift + echo "==================================================" + simple_test "$@" +} + +simple_test_command() +{ + unit_test_notrace "$@" +} diff --git a/ctdb/tests/UNIT/eventscripts/scripts/statd-callout.sh b/ctdb/tests/UNIT/eventscripts/scripts/statd-callout.sh new file mode 100644 index 0000000..e966cb4 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/scripts/statd-callout.sh @@ -0,0 +1,65 @@ +setup() +{ + ctdb_set_pnn + setup_public_addresses + setup_date "123456789" +} + +ctdb_catdb_format_pairs() +{ + _count=0 + + while read -r _k _v; do + _kn=$(printf '%s' "$_k" | wc -c) + _vn=$(printf '%s' "$_v" | wc -c) + cat <<EOF +key(${_kn}) = "${_k}" +dmaster: 0 +rsn: 1 +data(${_vn}) = "${_v}" + +EOF + _count=$((_count + 1)) + done + + echo "Dumped ${_count} records" +} + +check_ctdb_tdb_statd_state() +{ + ctdb_get_my_public_addresses | + while read -r _ _sip _; do + for _cip; do + cat <<EOF +statd-state@${_sip}@${_cip} $(date) +EOF + done + done | + ctdb_catdb_format_pairs | { + ok + simple_test_command ctdb catdb ctdb.tdb + } || exit $? +} + +check_statd_callout_smnotify() +{ + _state_even=$(( $(date '+%s') / 2 * 2)) + _state_odd=$((_state_even + 1)) + + nfs_load_config + + ctdb_get_my_public_addresses | + while read -r _ _sip _; do + for _cip; do + cat <<EOF +SM_NOTIFY: ${_sip} -> ${_cip}, MON_NAME=${_sip}, STATE=${_state_even} +SM_NOTIFY: ${_sip} -> ${_cip}, MON_NAME=${NFS_HOSTNAME}, STATE=${_state_even} +SM_NOTIFY: ${_sip} -> ${_cip}, MON_NAME=${_sip}, STATE=${_state_odd} +SM_NOTIFY: ${_sip} -> ${_cip}, MON_NAME=${NFS_HOSTNAME}, STATE=${_state_odd} +EOF + done + done | { + ok + simple_test_event "notify" + } || exit $? +} diff --git a/ctdb/tests/UNIT/eventscripts/statd-callout.001.sh b/ctdb/tests/UNIT/eventscripts/statd-callout.001.sh new file mode 100755 index 0000000..7293390 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/statd-callout.001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "single add-client" + +setup + +ok_null +simple_test_event "add-client" "192.168.123.45" +simple_test_event "update" + +check_ctdb_tdb_statd_state "192.168.123.45" diff --git a/ctdb/tests/UNIT/eventscripts/statd-callout.002.sh b/ctdb/tests/UNIT/eventscripts/statd-callout.002.sh new file mode 100755 index 0000000..ce9f139 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/statd-callout.002.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "2 x add-client, update" + +setup + +ok_null +simple_test_event "add-client" "192.168.123.45" +simple_test_event "add-client" "192.168.123.46" +simple_test_event "update" + +check_ctdb_tdb_statd_state "192.168.123.45" "192.168.123.46" diff --git a/ctdb/tests/UNIT/eventscripts/statd-callout.003.sh b/ctdb/tests/UNIT/eventscripts/statd-callout.003.sh new file mode 100755 index 0000000..25bec29 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/statd-callout.003.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "add-client, update, del-client, update" + +setup + +ok_null +simple_test_event "add-client" "192.168.123.45" +simple_test_event "update" + +simple_test_event "del-client" "192.168.123.45" +simple_test_event "update" + +check_ctdb_tdb_statd_state diff --git a/ctdb/tests/UNIT/eventscripts/statd-callout.004.sh b/ctdb/tests/UNIT/eventscripts/statd-callout.004.sh new file mode 100755 index 0000000..dc2156b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/statd-callout.004.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "single add-client, notify" + +setup + +ok_null +simple_test_event "add-client" "192.168.123.45" +simple_test_event "update" + +check_ctdb_tdb_statd_state "192.168.123.45" + +check_statd_callout_smnotify "192.168.123.45" + +check_ctdb_tdb_statd_state diff --git a/ctdb/tests/UNIT/eventscripts/statd-callout.005.sh b/ctdb/tests/UNIT/eventscripts/statd-callout.005.sh new file mode 100755 index 0000000..1f802a2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/statd-callout.005.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "2 x add-client to different nodes, notify on 1" + +setup + +ok_null +simple_test_event "add-client" "192.168.123.45" +simple_test_event "update" + +ctdb_set_pnn 1 + +ok_null +simple_test_event "add-client" "192.168.123.46" +simple_test_event "update" + +ctdb_set_pnn 0 + +check_statd_callout_smnotify "192.168.123.45" + +ctdb_set_pnn 1 + +check_ctdb_tdb_statd_state "192.168.123.46" diff --git a/ctdb/tests/UNIT/eventscripts/statd-callout.006.sh b/ctdb/tests/UNIT/eventscripts/statd-callout.006.sh new file mode 100755 index 0000000..8ecba5c --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/statd-callout.006.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "2 x add-client to different nodes, notify on both" + +setup + +ok_null +simple_test_event "add-client" "192.168.123.45" +simple_test_event "update" + +ctdb_set_pnn 1 + +ok_null +simple_test_event "add-client" "192.168.123.46" +simple_test_event "update" + +ctdb_set_pnn 0 + +check_statd_callout_smnotify "192.168.123.45" + +ctdb_set_pnn 1 + +check_statd_callout_smnotify "192.168.123.46" + +check_ctdb_tdb_statd_state diff --git a/ctdb/tests/UNIT/eventscripts/statd-callout.007.sh b/ctdb/tests/UNIT/eventscripts/statd-callout.007.sh new file mode 100755 index 0000000..4445fff --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/statd-callout.007.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "add-client, del-client, update" + +setup + +ok_null +simple_test_event "add-client" "192.168.123.45" +simple_test_event "del-client" "192.168.123.45" +simple_test_event "update" + +check_ctdb_tdb_statd_state diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ctdb b/ctdb/tests/UNIT/eventscripts/stubs/ctdb new file mode 100755 index 0000000..20135eb --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb @@ -0,0 +1,481 @@ +#!/bin/sh + +prog="ctdb" + +# Print a message and exit. +die() +{ + echo "$1" >&2 + exit "${2:-1}" +} + +not_implemented_exit_code=1 + +usage() +{ + cat >&2 <<EOF +Usage: $prog [-X] cmd + +A fake CTDB stub that prints items depending on the variables +FAKE_CTDB_PNN (default 0) depending on command-line options. +EOF + exit 1 +} + +not_implemented() +{ + echo "${prog}: command \"$1\" not implemented in stub" >&2 + exit $not_implemented_exit_code +} + +verbose=false +machine_readable=false +nodespec="" + +args="" + +# Options and command argument can appear in any order, so when +# getopts thinks it is done, process any non-option arguments and go +# around again. +while [ $# -gt 0 ]; do + while getopts "Xvhn:?" opt; do + case "$opt" in + X) machine_readable=true ;; + v) verbose=true ;; + n) nodespec="$OPTARG" ;; + \? | *) usage ;; + esac + done + shift $((OPTIND - 1)) + + # Anything left over must be a non-option arg + if [ $# -gt 0 ]; then + args="${args}${args:+ }${1}" + shift + fi +done + +[ -n "$args" ] || usage +# Want word splitting +# shellcheck disable=SC2086 +set -- $args + +setup_tickles() +{ + # Make sure tickles file exists. + tickles_file="${CTDB_TEST_TMP_DIR}/fake-ctdb/tickles" + mkdir -p "$(dirname "$tickles_file")" + touch "$tickles_file" +} + +ctdb_gettickles() +{ + _ip="$1" + _port="$2" + + setup_tickles + + echo "|source ip|port|destination ip|port|" + while read -r _src _dst; do + if [ -z "$_ip" ] || [ "$_ip" = "${_dst%:*}" ]; then + if [ -z "$_port" ] || [ "$_port" = "${_dst##*:}" ]; then + echo "|${_src%:*}|${_src##*:}|${_dst%:*}|${_dst##*:}|" + fi + fi + done <"$tickles_file" +} + +ctdb_addtickle() +{ + _src="$1" + _dst="$2" + + setup_tickles + + if [ -n "$_dst" ]; then + echo "${_src} ${_dst}" >>"$tickles_file" + else + cat >>"$tickles_file" + fi +} + +ctdb_deltickle() +{ + _src="$1" + _dst="$2" + + setup_tickles + + if [ -n "$_dst" ]; then + _t=$(grep -F -v "${_src} $${_dst}" "$tickles_file") + else + _t=$(cat "$tickles_file") + while read -r _src _dst; do + _t=$(echo "$_t" | grep -F -v "${_src} ${_dst}") + done + fi + echo "$_t" >"$tickles_file" +} + +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 + nodes=$(ctdb_pnn) + fi +} + +# For testing backward compatibility... +for i in $CTDB_NOT_IMPLEMENTED; do + if [ "$i" = "$1" ]; then + not_implemented "$i" + fi +done + +ctdb_pnn() +{ + # Defaults to 0 + echo "${FAKE_CTDB_PNN:-0}" +} + +###################################################################### + +FAKE_CTDB_NODE_STATE="$FAKE_CTDB_STATE/node-state" +FAKE_CTDB_NODES_DISABLED="$FAKE_CTDB_NODE_STATE/0x4" + +###################################################################### + +# NOTE: all nodes share public addresses file + +FAKE_CTDB_IP_LAYOUT="$FAKE_CTDB_STATE/ip-layout" + +ip_reallocate() +{ + touch "$FAKE_CTDB_IP_LAYOUT" + + # ShellCheck doesn't understand this flock pattern + # shellcheck disable=SC2094 + ( + flock 0 + + _pa="${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 + _tf="${_j%/*}" # dirname + _f="${_tf##*/}" # basename + _this=$((_this | _f)) + done + else + _this="0" + fi + _flags="${_flags}${_flags:+,}${_this}" + done + CTDB_TEST_LOGLEVEL=NOTICE \ + "ctdb_takeover_tests" \ + "ipalloc" "$_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 + + _mypnn=$(ctdb_pnn) + + if $machine_readable; then + if $verbose; then + echo "|Public IP|Node|ActiveInterface|AvailableInterfaces|ConfiguredInterfaces|" + else + echo "|Public IP|Node|" + fi + else + echo "Public IPs on node ${_mypnn}" + fi + + # Join public addresses file with $FAKE_CTDB_IP_LAYOUT, and + # process output line by line... + _pa="${CTDB_BASE}/public_addresses" + sed -e 's@/@ @' "$_pa" | sort | join - "$FAKE_CTDB_IP_LAYOUT" | + while read -r _ip _ _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 + if $machine_readable; then + echo "|${_ip}|${_pnn}|${_my_iface}|${_first_iface}|${_ifaces}|" + else + echo "${_ip} node[${_pnn}] active[${_my_iface}] available[${_first_iface}] configured[[${_ifaces}]" + fi + else + if $machine_readable; then + echo "|${_ip}|${_pnn}|" + else + echo "${_ip} ${_pnn}" + fi + fi + done +} + +ctdb_moveip() +{ + _ip="$1" + _target="$2" + + ip_reallocate # should be harmless and ensures we have good state + + # ShellCheck doesn't understand this flock pattern + # shellcheck disable=SC2094 + ( + flock 0 + + _t="${FAKE_CTDB_IP_LAYOUT}.new" + + while read -r _i _pnn; do + if [ "$_ip" = "$_i" ]; then + echo "$_i $_target" + else + echo "$_i $_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 +} + +###################################################################### + +ctdb_shutdown() +{ + echo "CTDB says BYE!" +} + +###################################################################### + +# This is only used by the NAT and LVS gateway code at the moment, so +# use a hack. Assume that $CTDB_NATGW_NODES or $CTDB_LVS_NODES +# contains all nodes in the cluster (which is what current tests +# assume). Use the PNN to find the address from this file. The NAT +# gateway code only used the address, so just mark the node healthy. +ctdb_nodestatus() +{ + echo '|Node|IP|Disconnected|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode|' + _line=$((FAKE_CTDB_PNN + 1)) + _ip=$(sed -e "${_line}p" "${CTDB_NATGW_NODES:-${CTDB_LVS_NODES}}") + echo "|${FAKE_CTDB_PNN}|${_ip}|0|0|0|0|0|0|0|Y|" +} + +###################################################################### + +_t_setup() +{ + _t_dir="${CTDB_TEST_TMP_DIR}/fake-ctdb/fake-tdb/$1" + mkdir -p "$_t_dir" +} + +_t_put() +{ + echo "$2" >"${_t_dir}/$1" +} + +_t_get() +{ + cat "${_t_dir}/$1" +} + +_t_del() +{ + rm -f "${_t_dir}/$1" +} + +ctdb_pstore() +{ + _t_setup "$1" + _t_put "$2" "$3" +} + +ctdb_pdelete() +{ + _t_setup "$1" + _t_del "$2" +} + +ctdb_pfetch() +{ + _t_setup "$1" + _t_get "$2" >"$3" 2>/dev/null +} + +ctdb_ptrans() +{ + _t_setup "$1" + + while IFS="" read -r _line; do + _k=$(echo "$_line" | sed -n -e 's@^"\([^"]*\)" "[^"]*"$@\1@p') + _v=$(echo "$_line" | sed -e 's@^"[^"]*" "\([^"]*\)"$@\1@') + [ -n "$_k" ] || die "ctdb ptrans: bad line \"${_line}\"" + if [ -n "$_v" ]; then + _t_put "$_k" "$_v" + else + _t_del "$_k" + fi + done +} + +ctdb_catdb() +{ + _t_setup "$1" + + # This will break on keys with spaces but we don't have any of + # those yet. + _count=0 + for _i in "${_t_dir}/"*; do + [ -r "$_i" ] || continue + _k="${_i##*/}" # basename + _v=$(_t_get "$_k") + _kn=$(printf '%s' "$_k" | wc -c) + _vn=$(printf '%s' "$_v" | wc -c) + cat <<EOF +key(${_kn}) = "${_k}" +dmaster: 0 +rsn: 1 +data(${_vn}) = "${_v}" + +EOF + _count=$((_count + 1)) + done + + echo "Dumped ${_count} records" +} + +###################################################################### + +FAKE_CTDB_IFACES_DOWN="${FAKE_CTDB_STATE}/ifaces-down" +rm -f "${FAKE_CTDB_IFACES_DOWN}"/* + +ctdb_ifaces() +{ + _f="${CTDB_BASE}/public_addresses" + + if [ ! -f "$_f" ]; then + die "Public addresses file \"${_f}\" not found" + fi + + # Assume -Y. + echo "|Name|LinkStatus|References|" + while read -r _ip _iface; do + case "$_ip" in + \#*) : ;; + *) + _status=1 + # For now assume _iface contains only 1. + if [ -f "{FAKE_CTDB_IFACES_DOWN}/${_iface}" ]; then + _status=0 + fi + # Nobody looks at references + echo "|${_iface}|${_status}|0|" + ;; + esac + done <"$_f" | + sort -u +} + +ctdb_setifacelink() +{ + _iface="$1" + _state="$2" + + mkdir -p "$FAKE_CTDB_IFACES_DOWN" + + # Existence of file means CTDB thinks interface is down. + _f="${FAKE_CTDB_IFACES_DOWN}/${_iface}" + + case "$_state" in + up) rm -f "$_f" ;; + down) touch "$_f" ;; + *) die "ctdb setifacelink: unsupported interface status ${_state}" ;; + esac +} + +###################################################################### + +ctdb_checktcpport() +{ + _port="$1" + + for _i in $FAKE_TCP_LISTEN; do + if [ "$_port" = "$_i" ]; then + exit 98 + fi + done + + exit 0 +} + +ctdb_gratarp() +{ + # Do nothing for now + : +} + +###################################################################### + +cmd="$1" +shift + +func="ctdb_${cmd}" + +# This could inadvertently run an external function instead of a local +# function. However, this can only happen if testing a script +# containing a new ctdb command that is not implemented, so this is +# unlikely to do harm. +if type "$func" >/dev/null 2>&1; then + "$func" "$@" +else + not_implemented "$cmd" +fi diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ctdb-config b/ctdb/tests/UNIT/eventscripts/stubs/ctdb-config new file mode 100755 index 0000000..818e3db --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb-config @@ -0,0 +1,2 @@ +#!/bin/sh +exec $VALGRIND "${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb-config" "$@" diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp new file mode 100755 index 0000000..2a4bac4 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp @@ -0,0 +1,10 @@ +#!/bin/sh + +# Only supports reading from stdin + +# shellcheck disable=SC2034 +iface="$1" # ignored + +while read -r src dst; do + sed -i -e "/^${dst} ${src}\$/d" "$FAKE_NETSTAT_TCP_ESTABLISHED_FILE" +done diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs new file mode 100755 index 0000000..31f56e8 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs @@ -0,0 +1,53 @@ +#!/bin/sh + +prog="ctdb_lvs" + +# Print a message and exit. +die() +{ + echo "$1" >&2 + exit "${2:-1}" +} + +not_implemented_exit_code=1 + +usage() +{ + cat >&2 <<EOF +Usage: $prog { leader | list } +EOF + exit 1 +} + +not_implemented() +{ + echo "${prog}: command \"$1\" not implemented in stub" >&2 + exit $not_implemented_exit_code +} + +ctdb_lvs_leader() +{ + if [ -n "$FAKE_CTDB_LVS_LEADER" ]; then + echo "$FAKE_CTDB_LVS_LEADER" + return 0 + else + return 255 + fi +} + +ctdb_lvs_list() +{ + _pnn=0 + while read -r _ip _; do + echo "${_pnn} ${_ip}" + _pnn=$((_pnn + 1)) + done <"$CTDB_LVS_NODES" +} + +###################################################################### + +case "$1" in +leader) ctdb_lvs_leader "$@" ;; +list) ctdb_lvs_list "$@" ;; +*) not_implemented "$1" ;; +esac diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ctdb_natgw b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_natgw new file mode 100755 index 0000000..22a2191 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/ctdb_natgw @@ -0,0 +1,34 @@ +#!/bin/sh + +prog="ctdb_natgw" + +not_implemented_exit_code=1 + +not_implemented() +{ + echo "${prog}: command \"$1\" not implemented in stub" >&2 + exit $not_implemented_exit_code +} + +ctdb_natgw_leader() +{ + [ -r "$CTDB_NATGW_NODES" ] || + die "error: missing CTDB_NATGW_NODES=${CTDB_NATGW_NODES}" + + # Determine the leader node + _leader="-1 0.0.0.0" + _pnn=0 + while read -r _ip; do + if [ "$FAKE_CTDB_NATGW_LEADER" = "$_ip" ]; then + _leader="${_pnn} ${_ip}" + break + fi + _pnn=$((_pnn + 1)) + done <"$CTDB_NATGW_NODES" + echo "$_leader" +} + +case "$1" in +leader) ctdb_natgw_leader "$@" ;; +*) not_implemented "$1" ;; +esac diff --git a/ctdb/tests/UNIT/eventscripts/stubs/date b/ctdb/tests/UNIT/eventscripts/stubs/date new file mode 100755 index 0000000..8319c9c --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/date @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ "$FAKE_DATE_OUTPUT" ]; then + echo "$FAKE_DATE_OUTPUT" +else + /bin/date "$@" +fi diff --git a/ctdb/tests/UNIT/eventscripts/stubs/df b/ctdb/tests/UNIT/eventscripts/stubs/df new file mode 100755 index 0000000..858f0ef --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/df @@ -0,0 +1,38 @@ +#!/bin/sh + +usage() +{ + echo "usage: df [-kP] [<mount-point>]" + exit 1 +} + +if [ "$1" = "-kP" ]; then + shift +fi + +case "$1" in +-*) usage ;; +esac + +fs="${1:-/}" + +# Anything starting with CTDB_DBDIR_BASE gets canonicalised to +# CTDB_DBDIR_BASE. This helps with the setting of defaults for the +# filesystem checks. +if [ "${fs#"${CTDB_DBDIR_BASE}"}" != "$fs" ]; then + fs="$CTDB_DBDIR_BASE" +fi + +# A default, for tests that don't initialise this... +if [ -z "$FAKE_FS_USE" ]; then + FAKE_FS_USE=10 +fi + +echo "Filesystem 1024-blocks Used Available Capacity Mounted on" + +blocks="1000000" +used=$((blocks * FAKE_FS_USE / 100)) +available=$((blocks - used)) + +printf "%-36s %10d %10d %10d %10d%% %s\n" \ + "/dev/sda1" "$blocks" "$used" "$available" "$FAKE_FS_USE" "$fs" diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ethtool b/ctdb/tests/UNIT/eventscripts/stubs/ethtool new file mode 100755 index 0000000..3d4b889 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/ethtool @@ -0,0 +1,12 @@ +#!/bin/sh + +link="yes" + +if [ -f "${FAKE_ETHTOOL_LINK_DOWN}/${1}" ]; then + link="no" +fi + +# Expect to add more fields later. +cat <<EOF + Link detected: ${link} +EOF diff --git a/ctdb/tests/UNIT/eventscripts/stubs/exportfs b/ctdb/tests/UNIT/eventscripts/stubs/exportfs new file mode 100755 index 0000000..e0970c5 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/exportfs @@ -0,0 +1,13 @@ +#!/bin/sh + +opts="10.0.0.0/16(rw,async,insecure,no_root_squash,no_subtree_check)" + +for i in $FAKE_SHARES; do + # Directories longer than 15 characters are printed on their own + # line. + if [ ${#i} -ge 15 ]; then + printf '%s\n\t\t%s\n' "$i" "$opts" + else + printf '%s\t%s\n' "$i" "$opts" + fi +done diff --git a/ctdb/tests/UNIT/eventscripts/stubs/gstack b/ctdb/tests/UNIT/eventscripts/stubs/gstack new file mode 100755 index 0000000..1dec235 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/gstack @@ -0,0 +1,19 @@ +#!/bin/sh + +pid="$1" + +if [ -n "$FAKE_PS_MAP" ]; then + command=$(echo "$FAKE_PS_MAP" | + awk -v pid="$pid" '$1 == pid { print $2 }') +fi + +if [ -z "$command" ]; then + command="smbd" +fi + +cat <<EOF +Thread 1 (Thread 0x7f688fbfb180 (LWP ${pid}) "${command}"): +#0 0x00007f688ff7a076 in open (FAKE ARGS...) at FAKE PLACE +.... +#3 0x000055cd368ead72 in main (argc=<optimized out>, argv=<optimized out>) at ${command}.c +EOF diff --git a/ctdb/tests/UNIT/eventscripts/stubs/id b/ctdb/tests/UNIT/eventscripts/stubs/id new file mode 100755 index 0000000..1ecd2f8 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/id @@ -0,0 +1,3 @@ +#!/bin/sh +# Make statd-callout happy +echo 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ip b/ctdb/tests/UNIT/eventscripts/stubs/ip new file mode 100755 index 0000000..090afae --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/ip @@ -0,0 +1,833 @@ +#!/bin/sh + +FAKE_IP_STATE="${FAKE_NETWORK_STATE}/ip-state" +mkdir -p "$FAKE_IP_STATE" + +promote_secondaries=true + +not_implemented() +{ + echo "ip stub command: \"$1\" not implemented" + exit 127 +} + +###################################################################### + +ip_link() +{ + case "$1" in + set) + shift + # iface="$1" + case "$2" in + up) ip_link_set_up "$1" ;; + down) ip_link_down_up "$1" ;; + *) not_implemented "\"$2\" in \"$orig_args\"" ;; + esac + ;; + show) + shift + ip_link_show "$@" + ;; + add*) + shift + ip_link_add "$@" + ;; + del*) + shift + ip_link_delete "$@" + ;; + *) not_implemented "$*" ;; + esac +} + +ip_link_add() +{ + _link="" + _name="" + _type="" + + while [ -n "$1" ]; do + case "$1" in + link) + _link="$2" + shift 2 + ;; + name) + _name="$2" + shift 2 + ;; + type) + if [ "$2" != "vlan" ]; then + not_implemented "link type $1" + fi + _type="$2" + shift 2 + ;; + id) shift 2 ;; + *) not_implemented "$1" ;; + esac + done + + case "$_type" in + vlan) + if [ -z "$_name" ] || [ -z "$_link" ]; then + not_implemented "ip link add with null name or link" + fi + + mkdir -p "${FAKE_IP_STATE}/interfaces-vlan" + echo "$_link" >"${FAKE_IP_STATE}/interfaces-vlan/${_name}" + ip_link_set_down "$_name" + ;; + esac +} + +ip_link_delete() +{ + mkdir -p "${FAKE_IP_STATE}/interfaces-deleted" + touch "${FAKE_IP_STATE}/interfaces-deleted/$1" + rm -f "${FAKE_IP_STATE}/interfaces-vlan/$1" +} + +ip_link_set_up() +{ + rm -f "${FAKE_IP_STATE}/interfaces-down/$1" + rm -f "${FAKE_IP_STATE}/interfaces-deleted/$1" +} + +ip_link_set_down() +{ + rm -f "${FAKE_IP_STATE}/interfaces-deleted/$1" + mkdir -p "${FAKE_IP_STATE}/interfaces-down" + touch "${FAKE_IP_STATE}/interfaces-down/$1" +} + +ip_link_show() +{ + dev="$1" + if [ "$dev" = "dev" ] && [ -n "$2" ]; then + dev="$2" + fi + + if [ -e "${FAKE_IP_STATE}/interfaces-deleted/$dev" ]; then + echo "Device \"${dev}\" does not exist." >&2 + exit 255 + fi + + if [ -r "${FAKE_IP_STATE}/interfaces-vlan/${dev}" ]; then + read -r _link <"${FAKE_IP_STATE}/interfaces-vlan/${dev}" + dev="${dev}@${_link}" + fi + + _state="UP" + _flags=",UP,LOWER_UP" + if [ -e "${FAKE_IP_STATE}/interfaces-down/$dev" ]; then + _state="DOWN" + _flags="" + fi + case "$dev" in + lo) + _mac="00:00:00:00:00:00" + _brd="00:00:00:00:00:00" + _type="loopback" + _state="UNKNOWN" + _status="<LOOPBACK${_flags}>" + _opts="mtu 65536 qdisc noqueue state ${_state}" + ;; + *) + _mac=$(echo "$dev" | cksum | sed -r -e 's@(..)(..)(..).*@fe:fe:fe:\1:\2:\3@') + _brd="ff:ff:ff:ff:ff:ff" + _type="ether" + _status="<BROADCAST,MULTICAST${_flags}>" + _opts="mtu 1500 qdisc pfifo_fast state ${_state} qlen 1000" + ;; + esac + + if $brief; then + printf '%-16s %-14s %-17s %s\n' \ + "$dev" "$_status" "$_mac" "$_status" + else + echo "${n:-42}: ${dev}: ${_status} ${_opts}" + echo " link/${_type} ${_mac} brd ${_brd}" + fi +} + +# This is incomplete because it doesn't actually look up table ids in +# /etc/iproute2/rt_tables. The rules/routes are actually associated +# with the name instead of the number. However, we include a variable +# to fake a bad table id. +[ -n "$IP_ROUTE_BAD_TABLE_ID" ] || IP_ROUTE_BAD_TABLE_ID=false + +ip_check_table() +{ + _cmd="$1" + + if [ "$_cmd" = "route" ] && [ -z "$_table" ]; then + _table="main" + fi + + [ -n "$_table" ] || not_implemented "ip rule/route without \"table\"" + + # Only allow tables names from 13.per_ip_routing and "main". This + # is a cheap way of avoiding implementing the default/local + # tables. + case "$_table" in + ctdb.* | main) + if $IP_ROUTE_BAD_TABLE_ID; then + # Ouch. Simulate inconsistent errors from ip. :-( + case "$_cmd" in + route) + echo "Error: argument \"${_table}\" is wrong: table id value is invalid" >&2 + + ;; + *) + echo "Error: argument \"${_table}\" is wrong: invalid table ID" >&2 + ;; + esac + exit 255 + fi + ;; + *) not_implemented "table=${_table} ${orig_args}" ;; + esac +} + +###################################################################### + +ip_addr() +{ + case "$1" in + show | list | "") + shift + ip_addr_show "$@" + ;; + add*) + shift + ip_addr_add "$@" + ;; + del*) + shift + ip_addr_del "$@" + ;; + *) not_implemented "\"$1\" in \"$orig_args\"" ;; + esac +} + +ip_addr_show() +{ + dev="" + primary=true + secondary=true + _to="" + + if $brief; then + not_implemented "ip -br addr show in \"$orig_args\"" + fi + + 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 + ;; + to) + _to="$2" + shift 2 + ;; + *) + # Assume an interface name + dev="$1" + shift 1 + ;; + esac + done + devices="$dev" + if [ -z "$devices" ]; then + # No device specified? Get all the primaries... + devices=$(find "${FAKE_IP_STATE}/addresses" -name "*-primary" | + sed -e 's@.*/@@' -e 's@-.*-primary$@@' | + sort -u) + fi + calc_brd() + { + case "${local#*/}" in + 24) brd="${local%.*}.255" ;; + 32) brd="" ;; + *) not_implemented "list ... fake bits other than 24/32: ${local#*/}" ;; + esac + } + show_iface() + { + ip_link_show "$dev" + + nets=$(find "${FAKE_IP_STATE}/addresses" -name "${dev}-*-primary" | + sed -e 's@.*/@@' -e "s@${dev}-\(.*\)-primary\$@\1@") + + for net in $nets; do + pf="${FAKE_IP_STATE}/addresses/${dev}-${net}-primary" + sf="${FAKE_IP_STATE}/addresses/${dev}-${net}-secondary" + if $primary && [ -r "$pf" ]; then + read -r local scope <"$pf" + if [ -z "$_to" ] || [ "${_to%/*}" = "${local%/*}" ]; then + calc_brd + echo " inet ${local} ${brd:+brd ${brd} }scope ${scope} ${dev}" + fi + fi + if $secondary && [ -r "$sf" ]; then + while read -r local scope; do + if [ -z "$_to" ] || [ "${_to%/*}" = "${local%/*}" ]; then + calc_brd + echo " inet ${local} ${brd:+brd }${brd} scope ${scope} secondary ${dev}" + fi + done <"$sf" + fi + if [ -z "$_to" ]; then + echo " valid_lft forever preferred_lft forever" + fi + done + } + n=1 + for dev in $devices; do + if [ -z "$_to" ] || + grep -F "${_to%/*}/" "${FAKE_IP_STATE}/addresses/${dev}-"* >/dev/null; then + show_iface + fi + n=$((n + 1)) + done +} + +# Copied from 13.per_ip_routing for now... so this is lazy testing :-( +ipv4_host_addr_to_net() +{ + _addr="$1" + + _host="${_addr%/*}" + _maskbits="${_addr#*/}" + + # Convert the host address to an unsigned long by splitting out + # the octets and doing the math. + _host_ul=0 + # Want word splitting here + # shellcheck disable=SC2086 + 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}" +} + +ip_addr_add() +{ + local="" + dev="" + brd="" + scope="global" + 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 + ;; + scope) + scope="$2" + shift 2 + ;; + *) + not_implemented "$@" + ;; + esac + done + if [ -z "$dev" ]; then + not_implemented "addr add (without dev)" + fi + mkdir -p "${FAKE_IP_STATE}/addresses" + net_str=$(ipv4_host_addr_to_net "$local") + net_str=$(echo "$net_str" | sed -e 's@/@_@') + pf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-primary" + sf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-secondary" + # We could lock here... but we should be the only ones playing + # around here with these stubs. + if [ ! -f "$pf" ]; then + echo "$local $scope" >"$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 $scope" >>"$sf" + fi +} + +ip_addr_del() +{ + 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" + net_str=$(ipv4_host_addr_to_net "$local") + net_str=$(echo "$net_str" | sed -e 's@/@_@') + pf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-primary" + sf="${FAKE_IP_STATE}/addresses/${dev}-${net_str}-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 + if $promote_secondaries && [ -s "$sf" ]; then + head -n 1 "$sf" >"$pf" + sed -i -e '1d' "$sf" + else + # Remove primaries AND SECONDARIES. + rm -f "$pf" "$sf" + fi + 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 "$1" in + show | list | "") + shift + ip_rule_show "$@" + ;; + add) + shift + ip_rule_add "$@" + ;; + del*) + shift + ip_rule_del "$@" + ;; + *) not_implemented "$1 in \"$orig_args\"" ;; + 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() +{ + if $brief; then + not_implemented "ip -br rule show in \"$orig_args\"" + fi + + 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 -r _pre _table _selectors; do + # Only print those in range + if [ "$_min" -le "$_pre" ] && + [ "$_pre" -le "$_max" ]; then + ip_rule_show_1 "$_pre" "$_table" "$_selectors" + fi + 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() +{ + _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 \"$orig_args\"" ;; + esac + done + + [ -n "$_pre" ] || not_implemented "ip rule without \"pref\"" + ip_check_table "rule" + # 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" + # ShellCheck doesn't understand this flock pattern + # shellcheck disable=SC2094 + ( + flock 0 + _tmp="${_f}.new" + : >"$_tmp" + _found=false + while read -r _p _t _s; do + if ! $_found && + [ "$_p" = "$_pre" ] && [ "$_t" = "$_table" ] && + [ "$_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 "$1" in + show | list) + shift + ip_route_show "$@" + ;; + flush) + shift + ip_route_flush "$@" + ;; + add) + shift + ip_route_add "$@" + ;; + del*) + shift + ip_route_del "$@" + ;; + *) not_implemented "$1 in \"ip route\"" ;; + esac +} + +ip_route_common() +{ + if [ "$1" = table ]; then + _table="$2" + shift 2 + fi + + ip_check_table "route" +} + +# 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 + sort "$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() +{ + _prefix="" + _dev="" + _gw="" + _table="" + _metric="" + + 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 + ;; + metric) + _metric="$2" + shift 2 + ;; + *) not_implemented "$1 in \"$orig_args\"" ;; + esac + done + + ip_check_table "route" + [ -n "$_prefix" ] || not_implemented "ip route without inet prefix in \"$orig_args\"" + # This can't be easily deduced, so print some garbage. + [ -n "$_dev" ] || _dev="ethXXX" + + # 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" + + # Check for duplicate + _prefix_regexp=$(echo "^${_prefix}" | sed -e 's@\.@\\.@g') + if [ -n "$_metric" ]; then + _prefix_regexp="${_prefix_regexp} .*metric ${_metric} " + fi + if grep -q "$_prefix_regexp" "$_f"; then + echo "RTNETLINK answers: File exists" >&2 + exit 1 + fi + + ( + flock 0 + + _out="${_prefix} " + [ -z "$_gw" ] || _out="${_out}via ${_gw} " + [ -z "$_dev" ] || _out="${_out}dev ${_dev} " + [ -n "$_gw" ] || _out="${_out} scope link " + [ -z "$_metric" ] || _out="${_out} metric ${_metric} " + echo "$_out" >>"$_f" + ) <"$_f" +} + +ip_route_del() +{ + _prefix="" + _dev="" + _gw="" + _table="" + _metric="" + + 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 + ;; + metric) + _metric="$2" + shift 2 + ;; + *) not_implemented "$1 in \"$orig_args\"" ;; + esac + done + + ip_check_table "route" + [ -n "$_prefix" ] || not_implemented "ip route without inet prefix in \"$orig_args\"" + # This can't be easily deduced, so print some garbage. + [ -n "$_dev" ] || _dev="ethXXX" + + # 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" + + # ShellCheck doesn't understand this flock pattern + # shellcheck disable=SC2094 + ( + flock 0 + + # Escape some dots + [ -z "$_gw" ] || _gw=$(echo "$_gw" | sed -e 's@\.@\\.@g') + _prefix=$(echo "$_prefix" | sed -e 's@\.@\\.@g' -e 's@/@\\/@') + + _re="^${_prefix}\>.*" + [ -z "$_gw" ] || _re="${_re}\<via ${_gw}\>.*" + [ -z "$_dev" ] || _re="${_re}\<dev ${_dev}\>.*" + [ -z "$_metric" ] || _re="${_re}.*\<metric ${_metric}\>.*" + sed -i -e "/${_re}/d" "$_f" + ) <"$_f" +} + +###################################################################### + +orig_args="$*" + +brief=false +case "$1" in +-br*) + brief=true + shift + ;; +esac + +case "$1" in +link) + shift + ip_link "$@" + ;; +addr*) + shift + ip_addr "$@" + ;; +rule) + shift + ip_rule "$@" + ;; +route) + shift + ip_route "$@" + ;; +*) not_implemented "$1" ;; +esac + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ip6tables b/ctdb/tests/UNIT/eventscripts/stubs/ip6tables new file mode 100755 index 0000000..2c65f7b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/ip6tables @@ -0,0 +1,5 @@ +#!/bin/sh + +# Always succeed. + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/iptables b/ctdb/tests/UNIT/eventscripts/stubs/iptables new file mode 100755 index 0000000..2c65f7b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/iptables @@ -0,0 +1,5 @@ +#!/bin/sh + +# Always succeed. + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ipvsadm b/ctdb/tests/UNIT/eventscripts/stubs/ipvsadm new file mode 100755 index 0000000..31bdf2c --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/ipvsadm @@ -0,0 +1,154 @@ +#!/bin/sh + +die() +{ + echo "$1" >&2 + exit "${2:-1}" +} + +[ -n "$FAKE_LVS_STATE_DIR" ] || die "FAKE_LVS_STATE_DIR not set" + +service_address="" +scheduling_method="wlc" +persistent_timeout="" +real_server="" +forwarding_method="Route" + +set_service_address() +{ + [ -z "$service_address" ] || + die "multiple 'service-address' options specified" 2 + case "$2" in + *:*) service_address="${1} ${2}" ;; + *) service_address="${1} ${2}:0" ;; + esac +} + +set_real_server() +{ + [ -z "$real_server" ] || + die "multiple 'real-server' options specified" 2 + case "$1" in + *\]:*) real_server="${1}" ;; + *\]) real_server="${1}:0" ;; + *:*) real_server="${1}" ;; + *) real_server="${1}:0" ;; + esac + + case "$real_server" in + 127.0.0.1:* | \[::1\]:*) forwarding_method="Local" ;; + esac +} + +case "$1" in +-A) + shift + while [ -n "$1" ]; do + case "$1" in + -t) + set_service_address "TCP" "$2" + shift 2 + ;; + -u) + set_service_address "UDP" "$2" + shift 2 + ;; + -s) + scheduling_method="$2" + shift 2 + ;; + -p) + persistent_timeout="persistent $2" + shift 2 + ;; + *) die "Unsupported -A option $1" ;; + esac + done + [ -n "$service_address" ] || + die "You need to supply the 'service-address' option for the 'add-service' command" 2 + d="${FAKE_LVS_STATE_DIR}/${service_address}" + mkdir "$d" 2>/dev/null || die "Service already exists" 255 + t="${scheduling_method}${persistent_timeout:+ }${persistent_timeout}" + echo "$t" >"${d}/.info" + ;; +-D) + shift + while [ -n "$1" ]; do + case "$1" in + -t) + set_service_address "TCP" "$2" + shift 2 + ;; + -u) + set_service_address "UDP" "$2" + shift 2 + ;; + *) die "Unsupported -D option $1" ;; + esac + done + [ -n "$service_address" ] || + die "You need to supply the 'service-address' option for the 'delete-service' command" 2 + d="${FAKE_LVS_STATE_DIR}/${service_address}" + rm -f "${d}/"* + rm -f "${d}/.info" + rmdir "$d" 2>/dev/null || die "No such service" 255 + ;; +-a) + shift + while [ -n "$1" ]; do + case "$1" in + -t) + set_service_address "TCP" "$2" + shift 2 + ;; + -u) + set_service_address "UDP" "$2" + shift 2 + ;; + -r) + set_real_server "$2" + shift 2 + ;; + -g) + forwarding_method="Route" + shift 1 + ;; + *) die "Unsupported -A option $1" ;; + esac + done + [ -n "$service_address" ] || + die "You need to supply the 'service-address' option for the 'delete-service' command" 2 + d="${FAKE_LVS_STATE_DIR}/${service_address}" + [ -d "$d" ] || die "Service not defined" 255 + [ -n "$real_server" ] || + die "You need to supply the 'real-server' option for the 'add-server' command" 2 + f="${d}/${real_server}" + echo "$forwarding_method" >"$f" + ;; +-l) + cat <<EOF +IP Virtual Server version 1.2.1 (size=4096) +Prot LocalAddress:Port Scheduler Flags + -> RemoteAddress:Port Forward Weight ActiveConn InActConn +EOF + cd "$FAKE_LVS_STATE_DIR" || exit 0 + ( + for d in *; do + [ -d "$d" ] || continue + printf '%s ' "$d" + cat "${d}/.info" + for f in "${d}/"*; do + [ -f "$f" ] || continue + read -r forwarding_method <"$f" + printf " -> %-28s %-7s %-6s %-10s %-10s\n" \ + "${f##*/}" "$forwarding_method" 1 0 0 + done + done + ) + ;; +*) + die "Unknown option $1" + ;; +esac + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/kill b/ctdb/tests/UNIT/eventscripts/stubs/kill new file mode 100755 index 0000000..b69e3e6 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/kill @@ -0,0 +1,7 @@ +#!/bin/sh + +# Always succeed. This means that kill -0 will always find a +# process and anything else will successfully kill. This should +# exercise a good avriety of code paths. + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/killall b/ctdb/tests/UNIT/eventscripts/stubs/killall new file mode 100755 index 0000000..1e182e1 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/killall @@ -0,0 +1,7 @@ +#!/bin/sh + +# Always succeed. This means that killall -0 will always find a +# process and anything else will successfully kill. This should +# exercise a good avriety of code paths. + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/multipath b/ctdb/tests/UNIT/eventscripts/stubs/multipath new file mode 100755 index 0000000..319b734 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/multipath @@ -0,0 +1,36 @@ +#!/bin/sh + +usage() +{ + die "usage: ${0} -ll device" +} + +[ "$1" = "-ll" ] || usage +shift +[ $# -eq 1 ] || usage + +device="$1" + +if [ -n "$FAKE_MULTIPATH_HANG" ]; then + FAKE_SLEEP_REALLY="yes" sleep 999 +fi + +path1_state="active" +path2_state="enabled" + +for i in $FAKE_MULTIPATH_FAILURES; do + if [ "$device" = "$i" ]; then + path1_state="inactive" + path2_state="inactive" + break + fi +done + +cat <<EOF +${device} (AUTO-01234567) dm-0 , +size=10G features='0' hwhandler='0' wp=rw +|-+- policy='round-robin 0' prio=1 status=${path1_state} +| \`- #:#:#:# vda 252:0 active ready running +\`-+- policy='round-robin 0' prio=1 status=${path2_state} + \`- #:#:#:# vdb 252:16 active ready running +EOF diff --git a/ctdb/tests/UNIT/eventscripts/stubs/net b/ctdb/tests/UNIT/eventscripts/stubs/net new file mode 100755 index 0000000..3f96413 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/net @@ -0,0 +1,5 @@ +#!/bin/sh + +# Always succeed for now... + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/nfs-fake-callout b/ctdb/tests/UNIT/eventscripts/stubs/nfs-fake-callout new file mode 100755 index 0000000..a4d43d0 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/nfs-fake-callout @@ -0,0 +1,15 @@ +#!/bin/sh + +case "$1" in +register) + echo "ALL" + exit + ;; +esac + +if [ "$NFS_FAKE_CALLOUT_MAGIC" = "$1" ]; then + echo "$1" + exit 1 +fi + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/nfsconf b/ctdb/tests/UNIT/eventscripts/stubs/nfsconf new file mode 100755 index 0000000..84dd9ea --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/nfsconf @@ -0,0 +1,5 @@ +#!/bin/sh + +# This always fails for now, since there are no tests that expect to +# use it. +exit 1 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/pidof b/ctdb/tests/UNIT/eventscripts/stubs/pidof new file mode 100755 index 0000000..6a25395 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/pidof @@ -0,0 +1,17 @@ +#!/bin/sh + +case "$1" in +nfsd) + echo "$FAKE_NFSD_THREAD_PIDS" + ;; +rpc.statd | rpc.rquotad | rpc.mountd) + echo "$FAKE_RPC_THREAD_PIDS" + ;; +smbd) + echo "$FAKE_SMBD_THREAD_PIDS" + ;; +*) + echo "pidof: \"$1\" not implemented" + exit 1 + ;; +esac diff --git a/ctdb/tests/UNIT/eventscripts/stubs/pkill b/ctdb/tests/UNIT/eventscripts/stubs/pkill new file mode 100755 index 0000000..b3f1de5 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/pkill @@ -0,0 +1,7 @@ +#!/bin/sh + +# Always succeed. This means that pkill -0 will always find a +# process and anything else will successfully kill. This should +# exercise a good avriety of code paths. + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ps b/ctdb/tests/UNIT/eventscripts/stubs/ps new file mode 100755 index 0000000..0d33203 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/ps @@ -0,0 +1,48 @@ +#!/bin/sh + +usage() +{ + echo "ps [ -p PID | -o FORMAT | aufxww ]" + exit 1 +} + +while getopts "o:p:h:?" opt; do + case "$opt" in + o) format="$OPTARG" ;; + p) pid="$OPTARG" ;; + \? | h) usage ;; + esac +done +shift $((OPTIND - 1)) + +if [ -n "$pid" ] && [ -n "$FAKE_PS_MAP" ]; then + # shellcheck disable=SC1001 + case "$format" in + comm\=) + echo "$FAKE_PS_MAP" | + awk -v pid="$pid" '$1 == pid { print $2 }' + ;; + state\=) + echo "$FAKE_PS_MAP" | + awk -v pid="$pid" '$1 == pid { print $3 }' + ;; + esac + + exit +fi + +if [ "$1" != "auxfww" ]; then + echo "option $1 not supported" + usage +fi + +cat <<EOF +USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +root 2 0.0 0.0 0 0 ? S Aug28 0:00 [kthreadd] +root 3 0.0 0.0 0 0 ? S Aug28 0:43 \_ [ksoftirqd/0] +... +root 1 0.0 0.0 2976 624 ? Ss Aug28 0:07 init [2] +root 495 0.0 0.0 3888 1640 ? Ss Aug28 0:00 udevd --daemon +... +[MORE FAKE ps OUTPUT] +EOF diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rm b/ctdb/tests/UNIT/eventscripts/stubs/rm new file mode 100755 index 0000000..6034d75 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/rm @@ -0,0 +1,6 @@ +#!/bin/sh +# Make statd-callout happy +case "$*" in +*/var/lib/nfs/statd/sm*) : ;; +*) exec /bin/rm "$@" ;; +esac diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rpc.lockd b/ctdb/tests/UNIT/eventscripts/stubs/rpc.lockd new file mode 100755 index 0000000..e71f6cd --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/rpc.lockd @@ -0,0 +1,6 @@ +#!/bin/sh + +# Restart always "works". However, the test infrastructure may +# continue to mark the service as down, so that's what matters. + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rpc.mountd b/ctdb/tests/UNIT/eventscripts/stubs/rpc.mountd new file mode 100755 index 0000000..e71f6cd --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/rpc.mountd @@ -0,0 +1,6 @@ +#!/bin/sh + +# Restart always "works". However, the test infrastructure may +# continue to mark the service as down, so that's what matters. + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rpc.rquotad b/ctdb/tests/UNIT/eventscripts/stubs/rpc.rquotad new file mode 100755 index 0000000..e71f6cd --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/rpc.rquotad @@ -0,0 +1,6 @@ +#!/bin/sh + +# Restart always "works". However, the test infrastructure may +# continue to mark the service as down, so that's what matters. + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rpc.statd b/ctdb/tests/UNIT/eventscripts/stubs/rpc.statd new file mode 100755 index 0000000..e71f6cd --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/rpc.statd @@ -0,0 +1,6 @@ +#!/bin/sh + +# Restart always "works". However, the test infrastructure may +# continue to mark the service as down, so that's what matters. + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/rpcinfo b/ctdb/tests/UNIT/eventscripts/stubs/rpcinfo new file mode 100755 index 0000000..8732751 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/rpcinfo @@ -0,0 +1,78 @@ +#!/bin/sh + +prog="rpcinfo" + +usage() +{ + cat >&2 <<EOF +Usage: $prog -T tcp host program [version] + +A fake rpcinfo stub that succeeds for items in FAKE_RPCINFO_SERVICES, +depending on command-line options. + +EOF + exit 1 +} + +parse_options() +{ + while getopts "T:h?" opt; do + case "$opt" in + T) netid="$OPTARG" ;; + \? | h) usage ;; + esac + done + shift $((OPTIND - 1)) + + [ "$netid" = "tcp" ] || usage + + host="$1" + shift + [ "$host" = "localhost" ] || [ "$host" = "127.0.0.1" ] || usage + + if [ $# -lt 1 ] || [ $# -gt 2 ]; then + usage + fi + + p="$1" + v="$2" +} + +parse_options "$@" + +for i in ${FAKE_RPCINFO_SERVICES}; do + # This is stupidly cumulative, but needs to happen after the + # initial split of the list above. + IFS="${IFS}:" + # Want glob expansion + # shellcheck disable=SC2086 + set -- $i + # $1 = program, $2 = low version, $3 = high version + + if [ "$1" = "$p" ]; then + if [ -n "$v" ]; then + if [ "$2" -le "$v" ] && [ "$v" -le "$3" ]; then + echo "program ${p} version ${v} ready and waiting" + exit 0 + else + echo "rpcinfo: RPC: Program/version mismatch; low version = ${2}, high version = ${3}" >&2 + echo "program ${p} version ${v} is not available" + exit 1 + fi + else + for j in $(seq "$2" "$3"); do + echo "program ${p} version ${j} ready and waiting" + done + exit 0 + fi + fi +done + +echo "rpcinfo: RPC: Program not registered" >&2 +if [ -n "$v" ]; then + echo "program ${p} version ${v} is not available" +else + echo "program ${p} is not available" +fi + +exit 1 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/service b/ctdb/tests/UNIT/eventscripts/stubs/service new file mode 100755 index 0000000..d706280 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/service @@ -0,0 +1,65 @@ +#!/bin/sh + +service_status_dir="${CTDB_TEST_TMP_DIR}/service_fake_status" +mkdir -p "$service_status_dir" + +service="$1" +flag="${service_status_dir}/${service}" + +start() +{ + if [ -f "$flag" ]; then + echo "service: can't start ${service} - already running" + exit 1 + else + touch "$flag" + echo "Starting ${service}: OK" + fi +} + +stop() +{ + if [ -f "$flag" ]; then + echo "Stopping ${service}: OK" + rm -f "$flag" + else + echo "service: can't stop ${service} - not running" + exit 1 + fi +} + +case "$2" in +start) + start + ;; +stop) + stop + ;; +restart | reload) + stop + start + ;; +status) + if [ -f "$flag" ]; then + echo "$service running" + exit 0 + else + echo "$service not running" + exit 3 + fi + ;; +force-started) + # For test setup... + touch "$flag" + ;; +force-stopped) + # For test setup... + rm -f "$flag" + ;; +*) + echo "service $service $2 not supported" + exit 1 + ;; +esac + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/sleep b/ctdb/tests/UNIT/eventscripts/stubs/sleep new file mode 100755 index 0000000..0d0e82b --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/sleep @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ "$FAKE_SLEEP_REALLY" = "yes" ]; then + /bin/sleep "$@" +elif [ -n "$FAKE_SLEEP_FORCE" ]; then + /bin/sleep "$FAKE_SLEEP_FORCE" +else + : +fi diff --git a/ctdb/tests/UNIT/eventscripts/stubs/smnotify b/ctdb/tests/UNIT/eventscripts/stubs/smnotify new file mode 100755 index 0000000..5606b3d --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/smnotify @@ -0,0 +1,65 @@ +#!/bin/sh + +usage() +{ + _prog="${0##*/}" # basename + cat <<EOF +Usage: ${_prog} --client=CLIENT --ip=IP --server=SERVER --stateval=STATEVAL +EOF + exit 1 +} + +cip="" +sip="" +mon_name="" +state="" + +while [ $# -gt 0 ]; do + case "$1" in + --client) + cip="$2" + shift 2 + ;; + --client=*) + cip="${1#*=}" + shift + ;; + --ip) + sip="$2" + shift 2 + ;; + --ip=*) + sip="${1#*=}" + shift + ;; + --server) + mon_name="$2" + shift 2 + ;; + --server=*) + mon_name="${1#*=}" + shift + ;; + --stateval) + state="$2" + shift 2 + ;; + --stateval=*) + state="${1#*=}" + shift + ;; + --) + shift + break + ;; + -*) usage ;; + *) break ;; + esac +done +[ $# -eq 0 ] || usage + +if [ -z "$cip" ] || [ -z "$sip" ] || [ -z "$mon_name" ] || [ -z "$state" ]; then + usage +fi + +echo "SM_NOTIFY: ${sip} -> ${cip}, MON_NAME=${mon_name}, STATE=${state}" diff --git a/ctdb/tests/UNIT/eventscripts/stubs/ss b/ctdb/tests/UNIT/eventscripts/stubs/ss new file mode 100755 index 0000000..c1199fe --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/ss @@ -0,0 +1,206 @@ +#!/bin/sh + +prog="ss" + +usage() +{ + cat >&2 <<EOF +Usage: $prog { -t|--tcp | -x|--unix } [options] [ FILTER ] + +A fake ss stub that prints items depending on the variables +FAKE_NETSTAT_TCP_ESTABLISHED, FAKE_TCP_LISTEN, +FAKE_NETSTAT_UNIX_LISTEN, depending on command-line options. + +Note that -n is ignored. + +EOF + exit 1 +} + +not_supported() +{ + echo "Options not supported in stub: $*" >&2 + usage +} + +############################################################ + +# +parse_filter() +{ + # Very limited implementation: + # We only expect to find || inside parentheses + # We don't expect to see && - it is implied by juxtaposition + # Operator for port comparison is ignored and assumed to be == + + # Build lists of source ports and source IP addresses where + # each entry is surrounded by '|' characters. These lists can + # be easily "searched" using the POSIX prefix and suffix + # removal operators. + in_parens=false + sports="|" + srcs="|" + + while [ -n "$1" ]; do + case "$1" in + \() + in_parens=true + shift + ;; + \)) + in_parens=false + shift + ;; + \|\|) + if ! $in_parens; then + not_supported "|| in parentheses" + fi + shift + ;; + sport) + p="${3#:}" + sports="${sports}${p}|" + shift 3 + ;; + src) + ip="${2#\[}" + ip="${ip%\]}" + srcs="${srcs}${ip}|" + shift 2 + ;; + *) + usage + ;; + esac + done +} + +# Check if socket has matches in both ok_ips and ok_ports +filter_socket() +{ + ok_ips="$1" + ok_ports="$2" + socket="$3" + + ip="${socket%:*}" + port="${socket##*:}" + + if [ "$ok_ports" != "|" ] && + [ "${ok_ports#*|"${port}"|}" = "$ok_ports" ]; then + return 1 + fi + if [ "$ok_ips" != "|" ] && [ "${ok_ips#*|"${ip}"|}" = "$ok_ips" ]; then + return 1 + fi + + return 0 +} + +ss_tcp_established() +{ + if $header; then + echo "Recv-Q Send-Q Local Address:Port Peer Address:Port" + fi + + # Yes, lose the quoting so we can do a hacky parsing job + # shellcheck disable=SC2048,SC2086 + parse_filter $* + + for i in $FAKE_NETSTAT_TCP_ESTABLISHED; do + src="${i%|*}" + dst="${i#*|}" + if filter_socket "$srcs" "$sports" "$src"; then + echo 0 0 "$src" "$dst" + fi + done + + if [ -z "$FAKE_NETSTAT_TCP_ESTABLISHED_FILE" ]; then + return + fi + while read -r src dst; do + if filter_socket "$srcs" "$sports" "$src"; then + echo 0 0 "$src" "$dst" + fi + done <"$FAKE_NETSTAT_TCP_ESTABLISHED_FILE" +} + +############################################################ + +unix_listen() +{ + if $header; then + cat <<EOF +Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port" +EOF + fi + + # Yes, lose the quoting so we can do a hacky parsing job + # shellcheck disable=SC2048,SC2086 + parse_filter $* + + _n=12345 + for _s in $FAKE_NETSTAT_UNIX_LISTEN; do + # ss matches Unix domain sockets as either src or + # sport. + if filter_socket "$srcs" "$sports" "${_s}:" || + filter_socket "$srcs" "$sports" ":${_s}"; then + printf "u_str LISTEN 0 128 %s %d * 0\n" "$_s" "$_n" + _n=$((_n + 1)) + fi + done +} + +############################################################ + +# Defaults. +tcp=false +unix=false +all=false +listen=false +header=true + +orig="$*" + +while getopts "txnalHh?" opt; do + case "$opt" in + t) tcp=true ;; + x) unix=true ;; + l) listen=true ;; + a) all=true ;; + H) header=false ;; + n) : ;; + \? | h) usage ;; + esac +done +shift $((OPTIND - 1)) + +$tcp || $unix || not_supported "$*" +if [ -z "$all" ]; then + nosupported "$*" +fi + +if $tcp; then + if [ "$1" != "state" ] || [ "$2" != "established" ] || $listen; then + usage + fi + + shift 2 + + # Yes, lose the quoting so we can do a hacky parsing job + # shellcheck disable=SC2048,SC2086 + ss_tcp_established $* + + exit +fi + +if $unix; then + if ! $listen; then + not_supported "$orig" + fi + + # Yes, lose the quoting so we can do a hacky parsing job + # shellcheck disable=SC2048,SC2086 + unix_listen $* + + exit +fi diff --git a/ctdb/tests/UNIT/eventscripts/stubs/stat b/ctdb/tests/UNIT/eventscripts/stubs/stat new file mode 100755 index 0000000..840265f --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/stat @@ -0,0 +1,71 @@ +#!/bin/sh + +usage() +{ + echo "stat -c FMT FILE ..." + exit 1 +} + +format="" + +while getopts "c:h:?" opt; do + case "$opt" in + c) format="$OPTARG" ;; + \? | h) usage ;; + esac +done +shift $((OPTIND - 1)) + +fake_device_id() +{ + _path="$1" + + _t=$(echo "$FAKE_FILE_ID_MAP" | + awk -v path="${_path}" '$1 == path { print $2 }') + _major_minor="${_t%:*}" + _major="0x${_major_minor%:*}" + _minor="0x${_major_minor#*:}" + _device_id=$((_major * 256 + _minor)) + echo "$_device_id" +} + +fake_inode() +{ + _path="$1" + + _t=$(echo "$FAKE_FILE_ID_MAP" | + awk -v path="${_path}" '$1 == path { print $2 }') + echo "${_t##*:}" +} + +if [ -n "$format" ]; then + for f; do + if [ ! -e "$f" ]; then + continue + fi + case "$f" in + /*) path="$f" ;; + *) path="${PWD}/${f}" ;; + esac + + case "$format" in + "s#[0-9a-f]*:[0-9a-f]*:%i #%n #") + inode=$(fake_inode "$path") + echo "s#[0-9a-f]*:[0-9a-f]*:${inode} #${f} #" + ;; + "%d:%i") + device_id=$(fake_device_id "$path") + inode=$(fake_inode "$path") + echo "${device_id}:${inode}" + ;; + *) + echo "Unsupported format \"${format}\"" + usage + ;; + esac + done + + exit +fi + +usage diff --git a/ctdb/tests/UNIT/eventscripts/stubs/tdb_mutex_check b/ctdb/tests/UNIT/eventscripts/stubs/tdb_mutex_check new file mode 100755 index 0000000..6cc7572 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/tdb_mutex_check @@ -0,0 +1,10 @@ +#!/bin/sh + +if [ -z "$FAKE_TDB_MUTEX_CHECK" ]; then + exit +fi + +echo "$FAKE_TDB_MUTEX_CHECK" | + while read -r pid chain; do + echo "[${chain}] pid=${pid}" + done diff --git a/ctdb/tests/UNIT/eventscripts/stubs/tdbdump b/ctdb/tests/UNIT/eventscripts/stubs/tdbdump new file mode 100755 index 0000000..92dcb8e --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/tdbdump @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ "$FAKE_TDB_IS_OK" = "yes" ]; then + echo "TDB good" + exit 0 +else + echo "TDB busted" + exit 1 +fi diff --git a/ctdb/tests/UNIT/eventscripts/stubs/tdbtool b/ctdb/tests/UNIT/eventscripts/stubs/tdbtool new file mode 100755 index 0000000..df83160 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/tdbtool @@ -0,0 +1,36 @@ +#!/bin/sh + +do_help() +{ + if [ "$FAKE_TDBTOOL_SUPPORTS_CHECK" = "yes" ]; then + echo "check" + fi + exit 0 +} + +do_check() +{ + if [ "$FAKE_TDB_IS_OK" = "yes" ]; then + echo "Database integrity is OK" + else + echo "Database is busted" + fi + exit 0 +} + +do_cmd() +{ + case "$*" in + *check) do_check ;; + help) do_help ;; + "") read -r tdb_cmd && [ -n "$tdb_cmd" ] && do_cmd "$tdb_cmd" ;; + *) + echo "$0: Not implemented: $*" + exit 1 + ;; + esac +} + +do_cmd "$@" + +exit 0 diff --git a/ctdb/tests/UNIT/eventscripts/stubs/testparm b/ctdb/tests/UNIT/eventscripts/stubs/testparm new file mode 100755 index 0000000..3a97e91 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/testparm @@ -0,0 +1,84 @@ +#!/bin/sh + +not_implemented() +{ + echo "testparm: option \"$1\" not implemented in stub" >&2 + exit 2 +} + +error() +{ + cat >&2 <<EOF +Load smb config files from ${CTDB_SYS_ETCDIR}/samba/smb.conf +rlimit_max: increasing rlimit_max (2048) to minimum Windows limit (16384) +EOF + + for i in $FAKE_SHARES; do + bi=$(basename "$i") + echo "Processing section \"[${bi}]\"" + done >&2 + + cat >&2 <<EOF +Loaded services file OK. +WARNING: 'workgroup' and 'netbios name' must differ. + +EOF + + exit 1 +} + +timeout() +{ + echo "$0: INTERNAL ERROR - timeout stub should avoid this" >&2 +} + +if [ -n "$FAKE_TESTPARM_FAIL" ]; then + error +fi + +if [ -n "$FAKE_TIMEOUT" ]; then + timeout +fi + +# Ensure that testparm always uses our canned configuration instead of +# the global one, unless some other file is specified. + +file="" +param="" +for i; do + case "$i" in + --parameter-name=*) param="${i#--parameter-name=}" ;; + -*) : ;; + *) file="$i" ;; + esac +done + +# Parse out parameter request +if [ -n "$param" ]; then + sed -n \ + -e "s|^[[:space:]]*${param}[[:space:]]*=[[:space:]]\(..*\)|\1|p" \ + "${file:-"${CTDB_SYS_ETCDIR}/samba/smb.conf"}" + exit 0 +fi + +if [ -n "$file" ]; then + # This should include the shares, since this is used when the + # samba eventscript caches the output. + cat "$file" +else + # We force our own smb.conf and add the shares. + cat "${CTDB_SYS_ETCDIR}/samba/smb.conf" + + for i in $FAKE_SHARES; do + bi=$(basename "$i") + cat <<EOF + +[${bi}] + path = $i + comment = fake share $bi + guest ok = no + read only = no + browsable = yes +EOF + done +fi diff --git a/ctdb/tests/UNIT/eventscripts/stubs/timeout b/ctdb/tests/UNIT/eventscripts/stubs/timeout new file mode 100755 index 0000000..26132ee --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/timeout @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ -n "$FAKE_TIMEOUT" ]; then + exit 124 +else + shift 1 + exec "$@" +fi diff --git a/ctdb/tests/UNIT/eventscripts/stubs/wbinfo b/ctdb/tests/UNIT/eventscripts/stubs/wbinfo new file mode 100755 index 0000000..b4bd9f2 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/stubs/wbinfo @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ "$FAKE_WBINFO_FAIL" = "yes" ]; then + exit 1 +fi + +exit 0 diff --git a/ctdb/tests/UNIT/onnode/0001.sh b/ctdb/tests/UNIT/onnode/0001.sh new file mode 100755 index 0000000..2853374 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0001.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE all hostname" + +define_test "$cmd" "all nodes OK" + +required_result <<EOF + +>> NODE: 192.168.1.101 << +-n 192.168.1.101 hostname + +>> NODE: 192.168.1.102 << +-n 192.168.1.102 hostname + +>> NODE: 192.168.1.103 << +-n 192.168.1.103 hostname + +>> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0002.sh b/ctdb/tests/UNIT/onnode/0002.sh new file mode 100755 index 0000000..c3c8c77 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0002.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE -q all hostname" + +define_test "$cmd" "all nodes OK" + +required_result <<EOF +-n 192.168.1.101 hostname +-n 192.168.1.102 hostname +-n 192.168.1.103 hostname +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0003.sh b/ctdb/tests/UNIT/onnode/0003.sh new file mode 100755 index 0000000..d79bca2 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0003.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE -p all hostname" + +define_test "$cmd" "all nodes OK" + +required_result <<EOF +[192.168.1.101] -n 192.168.1.101 hostname +[192.168.1.102] -n 192.168.1.102 hostname +[192.168.1.103] -n 192.168.1.103 hostname +[192.168.1.104] -n 192.168.1.104 hostname +EOF + +simple_test -s $cmd diff --git a/ctdb/tests/UNIT/onnode/0004.sh b/ctdb/tests/UNIT/onnode/0004.sh new file mode 100755 index 0000000..d0986b2 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0004.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE -pq all hostname" + +define_test "$cmd" "all nodes OK" + +required_result <<EOF +-n 192.168.1.101 hostname +-n 192.168.1.102 hostname +-n 192.168.1.103 hostname +-n 192.168.1.104 hostname +EOF + +simple_test -s $cmd diff --git a/ctdb/tests/UNIT/onnode/0005.sh b/ctdb/tests/UNIT/onnode/0005.sh new file mode 100755 index 0000000..0eccbb0 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0005.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE 3 hostname" + +define_test "$cmd" "all nodes OK" + +required_result <<EOF +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0006.sh b/ctdb/tests/UNIT/onnode/0006.sh new file mode 100755 index 0000000..b027850 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0006.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE -v 3 hostname" + +define_test "$cmd" "all nodes OK" + +required_result <<EOF + +>> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0010.sh b/ctdb/tests/UNIT/onnode/0010.sh new file mode 100755 index 0000000..241cf58 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0010.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE 4 hostname" + +define_test "$cmd" "invalid pnn 4" + +required_result 1 <<EOF +onnode: "node 4" does not exist +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0011.sh b/ctdb/tests/UNIT/onnode/0011.sh new file mode 100755 index 0000000..4604533 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0011.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE 99 hostname" + +define_test "$cmd" "invalid pnn 99" + +required_result 1 <<EOF +onnode: "node 99" does not exist +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0070.sh b/ctdb/tests/UNIT/onnode/0070.sh new file mode 100755 index 0000000..d649f82 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0070.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE ok hostname" + +define_test "$cmd" "all nodes OK" + +ctdb_set_output <<EOF +|Node|IP|Disconnected|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.1.101|0|0|0|0|0|0|0|Y| +|1|192.168.1.102|0|0|0|0|0|0|0|N| +|2|192.168.1.103|0|0|0|0|0|0|0|N| +|3|192.168.1.104|0|0|0|0|0|0|0|N| +EOF + +required_result <<EOF + +>> NODE: 192.168.1.101 << +-n 192.168.1.101 hostname + +>> NODE: 192.168.1.102 << +-n 192.168.1.102 hostname + +>> NODE: 192.168.1.103 << +-n 192.168.1.103 hostname + +>> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0071.sh b/ctdb/tests/UNIT/onnode/0071.sh new file mode 100755 index 0000000..4f945ac --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0071.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE ok hostname" + +define_test "$cmd" "2nd node disconnected" + +ctdb_set_output <<EOF +|Node|IP|Disconnected|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.1.101|0|0|0|0|0|0|0|Y| +|1|192.168.1.102|1|0|0|0|0|0|0|N| +|2|192.168.1.103|0|0|0|0|0|0|0|N| +|3|192.168.1.104|0|0|0|0|0|0|0|N| +EOF + +required_result <<EOF + +>> NODE: 192.168.1.101 << +-n 192.168.1.101 hostname + +>> NODE: 192.168.1.103 << +-n 192.168.1.103 hostname + +>> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0072.sh b/ctdb/tests/UNIT/onnode/0072.sh new file mode 100755 index 0000000..51a4c46 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0072.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE ok hostname" + +define_test "$cmd" "2nd node disconnected, extra status columns" + +ctdb_set_output <<EOF +|Node|IP|Disconnected|Banned|Disabled|Unhealthy|Stopped|Inactive|X1|X2|X3|X4| +|0|192.168.1.101|0|0|0|0|0|0|0|0|0|0| +|1|192.168.1.102|1|0|0|0|0|0|0|0|0|0| +|2|192.168.1.103|0|0|0|0|0|0|0|0|0|0| +|3|192.168.1.104|0|0|0|0|0|0|0|0|0|0| +EOF + +required_result <<EOF + +>> NODE: 192.168.1.101 << +-n 192.168.1.101 hostname + +>> NODE: 192.168.1.103 << +-n 192.168.1.103 hostname + +>> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0075.sh b/ctdb/tests/UNIT/onnode/0075.sh new file mode 100755 index 0000000..92fe220 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0075.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE con hostname" + +define_test "$cmd" "1st node disconnected" + +ctdb_set_output <<EOF +|Node|IP|Disconnected|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.1.101|1|0|0|0|0|0|0|N| +|1|192.168.1.102|0|0|0|0|0|0|0|Y| +|2|192.168.1.103|0|0|0|0|0|0|0|N| +|3|192.168.1.104|0|0|0|0|0|0|0|N| +EOF + +required_result <<EOF + +>> NODE: 192.168.1.102 << +-n 192.168.1.102 hostname + +>> NODE: 192.168.1.103 << +-n 192.168.1.103 hostname + +>> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/etc-ctdb/nodes b/ctdb/tests/UNIT/onnode/etc-ctdb/nodes new file mode 100644 index 0000000..e2fe268 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/etc-ctdb/nodes @@ -0,0 +1,4 @@ +192.168.1.101 +192.168.1.102 +192.168.1.103 +192.168.1.104 diff --git a/ctdb/tests/UNIT/onnode/scripts/local.sh b/ctdb/tests/UNIT/onnode/scripts/local.sh new file mode 100644 index 0000000..5b830c8 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/scripts/local.sh @@ -0,0 +1,64 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +# Default to just "onnode". +: ${ONNODE:=onnode} + +# Augment PATH with relevant stubs/ directory +stubs_dir="${CTDB_TEST_SUITE_DIR}/stubs" +[ -d "${stubs_dir}" ] || die "Failed to locate stubs/ subdirectory" +PATH="${stubs_dir}:${PATH}" + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "etc-ctdb" \ + functions + +define_test () +{ + _f=$(basename "$0") + + echo "$_f $1 - $2" +} + +# Set output for ctdb command. Option 1st argument is return code. +ctdb_set_output () +{ + _out="${CTDB_TEST_TMP_DIR}/ctdb.out" + cat >"$_out" + + _rc="${CTDB_TEST_TMP_DIR}/ctdb.rc" + echo "${1:-0}" >"$_rc" + + test_cleanup "rm -f $_out $_rc" +} + +extra_footer () +{ + cat <<EOF +-------------------------------------------------- +CTDB_BASE="$CTDB_BASE" +ctdb client is $(which ctdb) +-------------------------------------------------- +EOF +} + +simple_test () +{ + _sort="cat" + if [ "$1" = "-s" ] ; then + shift + _sort="sort" + fi + + if $CTDB_TEST_COMMAND_TRACE ; then + _onnode=$(which "$1") ; shift + _out=$(bash -x "$_onnode" "$@" 2>&1) + else + _out=$("$@" 2>&1) + fi + _rc=$? + _out=$(echo "$_out" | $_sort ) + + # Get the return code back into $? + (exit $_rc) + + result_check +} diff --git a/ctdb/tests/UNIT/onnode/stubs/ctdb b/ctdb/tests/UNIT/onnode/stubs/ctdb new file mode 100755 index 0000000..cca34c5 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/stubs/ctdb @@ -0,0 +1,19 @@ +#!/bin/sh + +# Fake ctdb client for onnode tests. + +out="${CTDB_TEST_TMP_DIR}/ctdb.out" +if [ -r "$out" ] ; then + cat "$out" + + rc="${CTDB_TEST_TMP_DIR}/ctdb.rc" + if [ -r "$rc" ] ; then + exit $(cat "$rc") + fi + + exit 0 +fi + +echo "fake ctdb: no implementation for \"$*\"" + +exit 1 diff --git a/ctdb/tests/UNIT/onnode/stubs/ssh b/ctdb/tests/UNIT/onnode/stubs/ssh new file mode 100755 index 0000000..7be778f --- /dev/null +++ b/ctdb/tests/UNIT/onnode/stubs/ssh @@ -0,0 +1,2 @@ +#!/bin/sh +echo "$*" diff --git a/ctdb/tests/UNIT/shellcheck/base_scripts.sh b/ctdb/tests/UNIT/shellcheck/base_scripts.sh new file mode 100755 index 0000000..cbb8502 --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/base_scripts.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "base scripts" + +shellcheck_test \ + "${CTDB_SCRIPTS_BASE}/ctdb-crash-cleanup.sh" \ + "${CTDB_SCRIPTS_BASE}/debug-hung-script.sh" \ + "${CTDB_SCRIPTS_BASE}/debug_locks.sh" \ + "${CTDB_SCRIPTS_BASE}/nfs-linux-kernel-callout" \ + "${CTDB_SCRIPTS_BASE}/statd-callout" diff --git a/ctdb/tests/UNIT/shellcheck/ctdb_helpers.sh b/ctdb/tests/UNIT/shellcheck/ctdb_helpers.sh new file mode 100755 index 0000000..f6c7e31 --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/ctdb_helpers.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "ctdb helpers" + +shellcheck_test \ + "${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/ctdb_lvs" \ + "${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/ctdb_natgw" diff --git a/ctdb/tests/UNIT/shellcheck/event_scripts.sh b/ctdb/tests/UNIT/shellcheck/event_scripts.sh new file mode 100755 index 0000000..dfb5ede --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/event_scripts.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "event scripts" + +shellcheck_test "${CTDB_SCRIPTS_DATA_DIR}/events/"*/[0-9][0-9].* diff --git a/ctdb/tests/UNIT/shellcheck/functions.sh b/ctdb/tests/UNIT/shellcheck/functions.sh new file mode 100755 index 0000000..7ce206d --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/functions.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "functions file" + +shellcheck_test -s sh "${CTDB_SCRIPTS_BASE}/functions" diff --git a/ctdb/tests/UNIT/shellcheck/init_script.sh b/ctdb/tests/UNIT/shellcheck/init_script.sh new file mode 100755 index 0000000..1e1d54c --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/init_script.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "init script" + +script="$CTDB_SCRIPTS_INIT_SCRIPT" + +if [ -z "$script" ] ; then + script="/etc/init.d/ctdb" + if [ ! -r "$script" ] ; then + script="/usr/local/etc/init.d/ctdb" + fi + if [ ! -r "$script" ] ; then + ctdb_test_skip "Unable to find ctdb init script" + fi +fi + +shellcheck_test "$script" diff --git a/ctdb/tests/UNIT/shellcheck/scripts/local.sh b/ctdb/tests/UNIT/shellcheck/scripts/local.sh new file mode 100644 index 0000000..07e72c3 --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/scripts/local.sh @@ -0,0 +1,33 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +. "${TEST_SCRIPTS_DIR}/script_install_paths.sh" + +define_test () +{ + _f=$(basename "$0" ".sh") + + printf "%-28s - %s\n" "$_f" "$1" +} +shellcheck_test () +{ + ok_null + if type shellcheck >/dev/null 2>&1 ; then + # Skip some recent checks: + # + # SC1090: Can't follow non-constant source. Use a + # directive to specify location. + # SC1091: Not following: FILE was not specified as + # input (see shellcheck -x). + # - Shellcheck doesn't handle our includes + # very well. Adding directives to handle + # include for both in-tree and installed + # cases just isn't going to be possible. + # SC2162: read without -r will mangle backslashes. + # - We never read things with backslashes, + # unnecessary churn. + _excludes="SC1090,SC1091,SC2162" + unit_test shellcheck --exclude="$_excludes" "$@" + else + ctdb_test_skip "shellcheck not installed" + fi +} diff --git a/ctdb/tests/UNIT/shellcheck/tests.sh b/ctdb/tests/UNIT/shellcheck/tests.sh new file mode 100755 index 0000000..fe55381 --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/tests.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "tests" + +if "$CTDB_TESTS_ARE_INSTALLED" ; then + run_tests="${CTDB_SCRIPTS_TESTS_BIN_DIR}/ctdb_run_tests" + local_daemons="${CTDB_SCRIPTS_TESTS_BIN_DIR}/ctdb_local_daemons" +else + run_tests="${CTDB_TEST_DIR}/run_tests.sh" + local_daemons="${CTDB_TEST_DIR}/local_daemons.sh" +fi + +# Scripts +shellcheck_test \ + "$run_tests" \ + "$local_daemons" \ + "${TEST_SCRIPTS_DIR}/test_wrap" + +# Includes +shellcheck_test -s sh \ + "${TEST_SCRIPTS_DIR}/common.sh" \ + "${TEST_SCRIPTS_DIR}/script_install_paths.sh" \ + "${TEST_SCRIPTS_DIR}/unit.sh" + +shellcheck_test -s bash \ + "${TEST_SCRIPTS_DIR}/cluster.bash" \ + "${TEST_SCRIPTS_DIR}/integration.bash" \ + "${TEST_SCRIPTS_DIR}/integration_local_daemons.bash" \ + "${TEST_SCRIPTS_DIR}/integration_real_cluster.bash" + +# Test scripts and stubs +shellcheck_test -s sh \ + "${CTDB_TEST_DIR}/UNIT/eventscripts/scripts/"* \ + "${CTDB_TEST_DIR}/UNIT/eventscripts/stubs/"* diff --git a/ctdb/tests/UNIT/shellcheck/tools.sh b/ctdb/tests/UNIT/shellcheck/tools.sh new file mode 100755 index 0000000..2cd322c --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/tools.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "tools" + +shellcheck_test \ + "${CTDB_SCRIPTS_TOOLS_BIN_DIR}/onnode" \ + "${CTDB_SCRIPTS_TOOLS_BIN_DIR}/ctdb_diagnostics" diff --git a/ctdb/tests/UNIT/takeover/README b/ctdb/tests/UNIT/takeover/README new file mode 100644 index 0000000..764f389 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/README @@ -0,0 +1,5 @@ +Unit tests for the CTDB IP allocation algorithm(s). + +Test case filenames look like <algorithm>.NNN.sh, where <algorithm> +indicates the IP allocation algorithm to use. These use the +ctdb_takeover_test test program. diff --git a/ctdb/tests/UNIT/takeover/det.001.sh b/ctdb/tests/UNIT/takeover/det.001.sh new file mode 100755 index 0000000..ad50287 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/det.001.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "ctdb-etc" + +define_test "3 nodes, 1 healthy" + +required_result <<EOF +${TEST_DATE_STAMP}Deterministic IPs enabled. Resetting all ip allocations +${TEST_DATE_STAMP}Unassign IP: 192.168.21.254 from 0 +${TEST_DATE_STAMP}Unassign IP: 192.168.21.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.254 from 0 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.251 from 0 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.250 from 1 +192.168.21.254 2 +192.168.21.253 2 +192.168.21.252 2 +192.168.20.254 2 +192.168.20.253 2 +192.168.20.252 2 +192.168.20.251 2 +192.168.20.250 2 +192.168.20.249 2 +EOF + +simple_test 2,2,0 <<EOF +192.168.20.249 0 +192.168.20.250 1 +192.168.20.251 2 +192.168.20.252 0 +192.168.20.253 1 +192.168.20.254 2 +192.168.21.252 0 +192.168.21.253 1 +192.168.21.254 2 +EOF diff --git a/ctdb/tests/UNIT/takeover/det.002.sh b/ctdb/tests/UNIT/takeover/det.002.sh new file mode 100755 index 0000000..b54edea --- /dev/null +++ b/ctdb/tests/UNIT/takeover/det.002.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "ctdb-etc" + +define_test "3 nodes, 2 healthy" + +required_result <<EOF +${TEST_DATE_STAMP}Deterministic IPs enabled. Resetting all ip allocations +${TEST_DATE_STAMP}Unassign IP: 192.168.21.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.250 from 1 +192.168.21.254 0 +192.168.21.253 0 +192.168.21.252 2 +192.168.20.254 0 +192.168.20.253 2 +192.168.20.252 2 +192.168.20.251 0 +192.168.20.250 0 +192.168.20.249 2 +EOF + +simple_test 0,2,0 <<EOF +192.168.20.249 0 +192.168.20.250 1 +192.168.20.251 2 +192.168.20.252 0 +192.168.20.253 1 +192.168.20.254 2 +192.168.21.252 0 +192.168.21.253 1 +192.168.21.254 2 +EOF diff --git a/ctdb/tests/UNIT/takeover/det.003.sh b/ctdb/tests/UNIT/takeover/det.003.sh new file mode 100755 index 0000000..931c498 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/det.003.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "ctdb-etc" + +define_test "3 nodes, 1 -> all healthy" + +required_result <<EOF +${TEST_DATE_STAMP}Deterministic IPs enabled. Resetting all ip allocations +192.168.21.254 0 +192.168.21.253 1 +192.168.21.252 2 +192.168.20.254 0 +192.168.20.253 1 +192.168.20.252 2 +192.168.20.251 0 +192.168.20.250 1 +192.168.20.249 2 +EOF + +simple_test 0,0,0 <<EOF +192.168.20.249 1 +192.168.20.250 1 +192.168.20.251 1 +192.168.20.252 1 +192.168.20.253 1 +192.168.20.254 1 +192.168.21.252 1 +192.168.21.253 1 +192.168.21.254 1 +EOF diff --git a/ctdb/tests/UNIT/takeover/det.004.sh b/ctdb/tests/UNIT/takeover/det.004.sh new file mode 100755 index 0000000..3673cc1 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/det.004.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "ctdb-etc" + +define_test "3 nodes, all healthy with home_nodes" + +home_nodes="$CTDB_BASE"/home_nodes + +cat > "$home_nodes" <<EOF +192.168.21.254 2 +192.168.20.251 1 +EOF + +required_result <<EOF +${TEST_DATE_STAMP}Deterministic IPs enabled. Resetting all ip allocations +192.168.21.254 2 +192.168.21.253 1 +192.168.21.252 2 +192.168.20.254 0 +192.168.20.253 1 +192.168.20.252 2 +192.168.20.251 1 +192.168.20.250 1 +192.168.20.249 2 +EOF + +simple_test 0,0,0 <<EOF +192.168.20.249 1 +192.168.20.250 1 +192.168.20.251 1 +192.168.20.252 1 +192.168.20.253 1 +192.168.20.254 1 +192.168.21.252 1 +192.168.21.253 1 +192.168.21.254 1 +EOF + +rm "$home_nodes" diff --git a/ctdb/tests/UNIT/takeover/det.005.sh b/ctdb/tests/UNIT/takeover/det.005.sh new file mode 100755 index 0000000..aaa5e0f --- /dev/null +++ b/ctdb/tests/UNIT/takeover/det.005.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "ctdb-etc" + +define_test "3 nodes, 2 healthy with home_nodes" + +home_nodes="$CTDB_BASE"/home_nodes + +cat > "$home_nodes" <<EOF +192.168.21.254 2 +192.168.20.251 1 +EOF + +required_result <<EOF +${TEST_DATE_STAMP}Deterministic IPs enabled. Resetting all ip allocations +${TEST_DATE_STAMP}Unassign IP: 192.168.21.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.251 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.250 from 1 +192.168.21.254 2 +192.168.21.253 0 +192.168.21.252 2 +192.168.20.254 0 +192.168.20.253 0 +192.168.20.252 2 +192.168.20.251 0 +192.168.20.250 0 +192.168.20.249 2 +EOF + +simple_test 0,2,0 <<EOF +192.168.20.249 0 +192.168.20.250 1 +192.168.20.251 2 +192.168.20.252 0 +192.168.20.253 1 +192.168.20.254 2 +192.168.21.252 0 +192.168.21.253 1 +192.168.21.254 2 +EOF + +rm "$home_nodes" diff --git a/ctdb/tests/UNIT/takeover/det.006.sh b/ctdb/tests/UNIT/takeover/det.006.sh new file mode 100755 index 0000000..504c430 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/det.006.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "ctdb-etc" + +define_test "3 nodes, 1 healthy with home_nodes" + +home_nodes="$CTDB_BASE"/home_nodes + +cat > "$home_nodes" <<EOF +192.168.21.254 2 +192.168.20.251 1 +EOF + +required_result <<EOF +${TEST_DATE_STAMP}Deterministic IPs enabled. Resetting all ip allocations +${TEST_DATE_STAMP}Unassign IP: 192.168.21.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.254 from 0 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.251 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.250 from 1 +192.168.21.254 2 +192.168.21.253 2 +192.168.21.252 2 +192.168.20.254 2 +192.168.20.253 2 +192.168.20.252 2 +192.168.20.251 2 +192.168.20.250 2 +192.168.20.249 2 +EOF + +simple_test 2,2,0 <<EOF +192.168.20.249 0 +192.168.20.250 1 +192.168.20.251 2 +192.168.20.252 0 +192.168.20.253 1 +192.168.20.254 2 +192.168.21.252 0 +192.168.21.253 1 +192.168.21.254 2 +EOF + +rm "$home_nodes" diff --git a/ctdb/tests/UNIT/takeover/lcp2.001.sh b/ctdb/tests/UNIT/takeover/lcp2.001.sh new file mode 100755 index 0000000..ee5b795 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.001.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 3 -> 1 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 2 +192.168.21.253 2 +192.168.21.252 2 +192.168.20.254 2 +192.168.20.253 2 +192.168.20.252 2 +192.168.20.251 2 +192.168.20.250 2 +192.168.20.249 2 +EOF + +simple_test 2,2,0 <<EOF +192.168.20.249 0 +192.168.20.250 1 +192.168.20.251 2 +192.168.20.252 0 +192.168.20.253 1 +192.168.20.254 2 +192.168.21.252 0 +192.168.21.253 1 +192.168.21.254 2 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.002.sh b/ctdb/tests/UNIT/takeover/lcp2.002.sh new file mode 100755 index 0000000..6489388 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.002.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 3 -> 2 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 2 +192.168.21.253 2 +192.168.21.252 0 +192.168.20.254 2 +192.168.20.253 2 +192.168.20.252 0 +192.168.20.251 2 +192.168.20.250 0 +192.168.20.249 0 +EOF + +simple_test 0,2,0 <<EOF +192.168.20.249 0 +192.168.20.250 1 +192.168.20.251 2 +192.168.20.252 0 +192.168.20.253 1 +192.168.20.254 2 +192.168.21.252 0 +192.168.21.253 1 +192.168.21.254 2 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.003.sh b/ctdb/tests/UNIT/takeover/lcp2.003.sh new file mode 100755 index 0000000..bdf2699 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.003.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 1 -> all healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 2 +192.168.21.253 0 +192.168.21.252 1 +192.168.20.254 2 +192.168.20.253 0 +192.168.20.252 1 +192.168.20.251 2 +192.168.20.250 0 +192.168.20.249 1 +EOF + +simple_test 0,0,0 <<EOF +192.168.20.249 1 +192.168.20.250 1 +192.168.20.251 1 +192.168.20.252 1 +192.168.20.253 1 +192.168.20.254 1 +192.168.21.252 1 +192.168.21.253 1 +192.168.21.254 1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.004.sh b/ctdb/tests/UNIT/takeover/lcp2.004.sh new file mode 100755 index 0000000..7ce97c3 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.004.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 1 -> all healthy, info logging" + +export CTDB_TEST_LOGLEVEL=INFO + +required_result <<EOF +${TEST_DATE_STAMP}1 [-121363] -> 192.168.20.253 -> 0 [+0] +${TEST_DATE_STAMP}1 [-105738] -> 192.168.20.251 -> 2 [+0] +${TEST_DATE_STAMP}1 [-88649] -> 192.168.21.253 -> 0 [+14161] +${TEST_DATE_STAMP}1 [-75448] -> 192.168.20.254 -> 2 [+15625] +${TEST_DATE_STAMP}1 [-59823] -> 192.168.20.250 -> 0 [+29786] +${TEST_DATE_STAMP}1 [-44198] -> 192.168.21.254 -> 2 [+28322] +192.168.21.254 2 +192.168.21.253 0 +192.168.21.252 1 +192.168.20.254 2 +192.168.20.253 0 +192.168.20.252 1 +192.168.20.251 2 +192.168.20.250 0 +192.168.20.249 1 +EOF + +simple_test 0,0,0 <<EOF +192.168.20.249 1 +192.168.20.250 1 +192.168.20.251 1 +192.168.20.252 1 +192.168.20.253 1 +192.168.20.254 1 +192.168.21.252 1 +192.168.21.253 1 +192.168.21.254 1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.005.sh b/ctdb/tests/UNIT/takeover/lcp2.005.sh new file mode 100755 index 0000000..f579a94 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.005.sh @@ -0,0 +1,198 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 1 -> all healthy, debug logging" + +export CTDB_TEST_LOGLEVEL=DEBUG + +required_result <<EOF +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES (UNASSIGNED) +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [0] +${TEST_DATE_STAMP} 1 [539166] +${TEST_DATE_STAMP} 2 [0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [539166] +${TEST_DATE_STAMP} 1 [-116718] -> 192.168.21.254 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-116718] -> 192.168.21.254 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-116971] -> 192.168.21.253 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-116971] -> 192.168.21.253 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-116971] -> 192.168.21.252 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-116971] -> 192.168.21.252 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121110] -> 192.168.20.254 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121110] -> 192.168.20.254 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.253 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.253 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.252 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.252 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.251 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.251 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.250 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.250 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121110] -> 192.168.20.249 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121110] -> 192.168.20.249 -> 2 [+0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-121363] -> 192.168.20.253 -> 0 [+0] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [0] +${TEST_DATE_STAMP} 1 [417803] +${TEST_DATE_STAMP} 2 [0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [417803] +${TEST_DATE_STAMP} 1 [-102557] -> 192.168.21.254 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-102557] -> 192.168.21.254 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-102810] -> 192.168.21.253 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-102810] -> 192.168.21.253 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-102810] -> 192.168.21.252 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-102810] -> 192.168.21.252 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-105234] -> 192.168.20.254 -> 0 [+15876] +${TEST_DATE_STAMP} 1 [-105234] -> 192.168.20.254 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-105234] -> 192.168.20.252 -> 0 [+16129] +${TEST_DATE_STAMP} 1 [-105234] -> 192.168.20.252 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-105738] -> 192.168.20.251 -> 0 [+15625] +${TEST_DATE_STAMP} 1 [-105738] -> 192.168.20.251 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-105738] -> 192.168.20.250 -> 0 [+15625] +${TEST_DATE_STAMP} 1 [-105738] -> 192.168.20.250 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-105485] -> 192.168.20.249 -> 0 [+15625] +${TEST_DATE_STAMP} 1 [-105485] -> 192.168.20.249 -> 2 [+0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-105738] -> 192.168.20.251 -> 2 [+0] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [0] +${TEST_DATE_STAMP} 1 [312065] +${TEST_DATE_STAMP} 2 [0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [312065] +${TEST_DATE_STAMP} 1 [-88396] -> 192.168.21.254 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-88396] -> 192.168.21.254 -> 2 [+14161] +${TEST_DATE_STAMP} 1 [-88649] -> 192.168.21.253 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-88649] -> 192.168.21.253 -> 2 [+14161] +${TEST_DATE_STAMP} 1 [-88649] -> 192.168.21.252 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-88649] -> 192.168.21.252 -> 2 [+14161] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.254 -> 0 [+15876] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.254 -> 2 [+15625] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.252 -> 0 [+16129] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.252 -> 2 [+15625] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.250 -> 0 [+15625] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.250 -> 2 [+16129] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.249 -> 0 [+15625] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.249 -> 2 [+15876] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-88649] -> 192.168.21.253 -> 0 [+14161] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [14161] +${TEST_DATE_STAMP} 1 [223416] +${TEST_DATE_STAMP} 2 [0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [223416] +${TEST_DATE_STAMP} 1 [-72520] -> 192.168.21.254 -> 0 [+30037] +${TEST_DATE_STAMP} 1 [-72520] -> 192.168.21.254 -> 2 [+14161] +${TEST_DATE_STAMP} 1 [-72520] -> 192.168.21.252 -> 0 [+30290] +${TEST_DATE_STAMP} 1 [-72520] -> 192.168.21.252 -> 2 [+14161] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.254 -> 0 [+30037] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.254 -> 2 [+15625] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.252 -> 0 [+30290] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.252 -> 2 [+15625] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.250 -> 0 [+29786] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.250 -> 2 [+16129] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.249 -> 0 [+29786] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.249 -> 2 [+15876] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-75448] -> 192.168.20.254 -> 2 [+15625] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [14161] +${TEST_DATE_STAMP} 1 [147968] +${TEST_DATE_STAMP} 2 [15625] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [147968] +${TEST_DATE_STAMP} 1 [-58359] -> 192.168.21.254 -> 0 [+30037] +${TEST_DATE_STAMP} 1 [-58359] -> 192.168.21.254 -> 2 [+28322] +${TEST_DATE_STAMP} 1 [-58359] -> 192.168.21.252 -> 0 [+30290] +${TEST_DATE_STAMP} 1 [-58359] -> 192.168.21.252 -> 2 [+28322] +${TEST_DATE_STAMP} 1 [-59572] -> 192.168.20.252 -> 0 [+30290] +${TEST_DATE_STAMP} 1 [-59572] -> 192.168.20.252 -> 2 [+31501] +${TEST_DATE_STAMP} 1 [-59823] -> 192.168.20.250 -> 0 [+29786] +${TEST_DATE_STAMP} 1 [-59823] -> 192.168.20.250 -> 2 [+31754] +${TEST_DATE_STAMP} 1 [-59823] -> 192.168.20.249 -> 0 [+29786] +${TEST_DATE_STAMP} 1 [-59823] -> 192.168.20.249 -> 2 [+31501] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-59823] -> 192.168.20.250 -> 0 [+29786] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [43947] +${TEST_DATE_STAMP} 1 [88145] +${TEST_DATE_STAMP} 2 [15625] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [88145] +${TEST_DATE_STAMP} 1 [-44198] -> 192.168.21.254 -> 0 [+44198] +${TEST_DATE_STAMP} 1 [-44198] -> 192.168.21.254 -> 2 [+28322] +${TEST_DATE_STAMP} 1 [-44198] -> 192.168.21.252 -> 0 [+44451] +${TEST_DATE_STAMP} 1 [-44198] -> 192.168.21.252 -> 2 [+28322] +${TEST_DATE_STAMP} 1 [-43947] -> 192.168.20.252 -> 0 [+45915] +${TEST_DATE_STAMP} 1 [-43947] -> 192.168.20.252 -> 2 [+31501] +${TEST_DATE_STAMP} 1 [-43947] -> 192.168.20.249 -> 0 [+45662] +${TEST_DATE_STAMP} 1 [-43947] -> 192.168.20.249 -> 2 [+31501] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-44198] -> 192.168.21.254 -> 2 [+28322] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [43947] +${TEST_DATE_STAMP} 1 [43947] +${TEST_DATE_STAMP} 2 [43947] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 0 [43947] +${TEST_DATE_STAMP} 0 [-28322] -> 192.168.21.253 -> 0 [+28322] +${TEST_DATE_STAMP} 0 [-28322] -> 192.168.21.253 -> 2 [+44198] +${TEST_DATE_STAMP} 0 [-29786] -> 192.168.20.253 -> 0 [+29786] +${TEST_DATE_STAMP} 0 [-29786] -> 192.168.20.253 -> 2 [+45662] +${TEST_DATE_STAMP} 0 [-29786] -> 192.168.20.250 -> 0 [+29786] +${TEST_DATE_STAMP} 0 [-29786] -> 192.168.20.250 -> 2 [+45915] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [43947] +${TEST_DATE_STAMP} 1 [-28322] -> 192.168.21.252 -> 0 [+44451] +${TEST_DATE_STAMP} 1 [-28322] -> 192.168.21.252 -> 2 [+44198] +${TEST_DATE_STAMP} 1 [-29786] -> 192.168.20.252 -> 0 [+45915] +${TEST_DATE_STAMP} 1 [-29786] -> 192.168.20.252 -> 2 [+45662] +${TEST_DATE_STAMP} 1 [-29786] -> 192.168.20.249 -> 0 [+45662] +${TEST_DATE_STAMP} 1 [-29786] -> 192.168.20.249 -> 2 [+45662] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 2 [43947] +${TEST_DATE_STAMP} 2 [-28322] -> 192.168.21.254 -> 0 [+44198] +${TEST_DATE_STAMP} 2 [-28322] -> 192.168.21.254 -> 2 [+28322] +${TEST_DATE_STAMP} 2 [-29786] -> 192.168.20.254 -> 0 [+45662] +${TEST_DATE_STAMP} 2 [-29786] -> 192.168.20.254 -> 2 [+29786] +${TEST_DATE_STAMP} 2 [-29786] -> 192.168.20.251 -> 0 [+45915] +${TEST_DATE_STAMP} 2 [-29786] -> 192.168.20.251 -> 2 [+29786] +${TEST_DATE_STAMP} ---------------------------------------- +192.168.21.254 2 +192.168.21.253 0 +192.168.21.252 1 +192.168.20.254 2 +192.168.20.253 0 +192.168.20.252 1 +192.168.20.251 2 +192.168.20.250 0 +192.168.20.249 1 +EOF + +simple_test 0,0,0 <<EOF +192.168.20.249 1 +192.168.20.250 1 +192.168.20.251 1 +192.168.20.252 1 +192.168.20.253 1 +192.168.20.254 1 +192.168.21.252 1 +192.168.21.253 1 +192.168.21.254 1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.006.sh b/ctdb/tests/UNIT/takeover/lcp2.006.sh new file mode 100755 index 0000000..c527992 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.006.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 0 -> 1 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 1 +192.168.21.253 1 +192.168.21.252 1 +192.168.20.254 1 +192.168.20.253 1 +192.168.20.252 1 +192.168.20.251 1 +192.168.20.250 1 +192.168.20.249 1 +EOF + +simple_test 2,0,2 <<EOF +192.168.20.249 -1 +192.168.20.250 -1 +192.168.20.251 -1 +192.168.20.252 -1 +192.168.20.253 -1 +192.168.20.254 -1 +192.168.21.252 -1 +192.168.21.253 -1 +192.168.21.254 -1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.007.sh b/ctdb/tests/UNIT/takeover/lcp2.007.sh new file mode 100755 index 0000000..a514025 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.007.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 0 -> 2 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 1 +192.168.21.253 2 +192.168.21.252 1 +192.168.20.254 1 +192.168.20.253 2 +192.168.20.252 1 +192.168.20.251 1 +192.168.20.250 2 +192.168.20.249 2 +EOF + +simple_test 2,0,0 <<EOF +192.168.20.249 -1 +192.168.20.250 -1 +192.168.20.251 -1 +192.168.20.252 -1 +192.168.20.253 -1 +192.168.20.254 -1 +192.168.21.252 -1 +192.168.21.253 -1 +192.168.21.254 -1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.008.sh b/ctdb/tests/UNIT/takeover/lcp2.008.sh new file mode 100755 index 0000000..6387223 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.008.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 0 -> all healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 0 +192.168.21.253 1 +192.168.21.252 2 +192.168.20.254 0 +192.168.20.253 1 +192.168.20.252 2 +192.168.20.251 0 +192.168.20.250 1 +192.168.20.249 2 +EOF + +simple_test 0,0,0 <<EOF +192.168.20.249 -1 +192.168.20.250 -1 +192.168.20.251 -1 +192.168.20.252 -1 +192.168.20.253 -1 +192.168.20.254 -1 +192.168.21.252 -1 +192.168.21.253 -1 +192.168.21.254 -1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.009.sh b/ctdb/tests/UNIT/takeover/lcp2.009.sh new file mode 100755 index 0000000..1b0c350 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.009.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 3 healthy -> all disconnected" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 -1 +192.168.21.253 -1 +192.168.21.252 -1 +192.168.20.254 -1 +192.168.20.253 -1 +192.168.20.252 -1 +192.168.20.251 -1 +192.168.20.250 -1 +192.168.20.249 -1 +EOF + +simple_test 1,1,1 <<EOF +192.168.20.249 0 +192.168.20.250 1 +192.168.20.251 2 +192.168.20.252 0 +192.168.20.253 1 +192.168.20.254 2 +192.168.21.252 0 +192.168.21.253 1 +192.168.21.254 2 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.010.sh b/ctdb/tests/UNIT/takeover/lcp2.010.sh new file mode 100755 index 0000000..f7dabdd --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.010.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "2 disjoint groups of nodes/addresses, a node becomes healthy" + +# This illustrates a bug in LCP2 when the the only candidate for a +# source node is chosen to be the "most imbalanced" node. This means +# that nodes in the smaller group aren't necessarily (depends on sort +# order and addresses used) considered as candidates. If the larger +# group has 6 addresses then the "necessarily" goes away and the +# smaller group won't be rebalanced. + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.209.102 3 +192.168.209.101 2 +192.168.140.4 1 +192.168.140.3 1 +192.168.140.2 0 +192.168.140.1 0 +EOF + +simple_test 0,0,0,0 <<EOF +192.168.140.1 0 0,1 +192.168.140.2 0 0,1 +192.168.140.3 1 0,1 +192.168.140.4 1 0,1 +192.168.209.101 2 2,3 +192.168.209.102 2 2,3 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.011.sh b/ctdb/tests/UNIT/takeover/lcp2.011.sh new file mode 100755 index 0000000..1f10bd1 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.011.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "2 disjoint groups of nodes/addresses, continue a stopped node" + +# Another LCP2 1.0 bug + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +10.11.19.46 3 +10.11.19.45 3 +10.11.19.44 1 +10.11.18.46 1 +10.11.18.45 3 +10.11.18.44 1 +10.11.17.46 3 +10.11.17.45 3 +10.11.17.44 1 +10.11.16.46 1 +10.11.16.45 3 +10.11.16.44 1 +9.11.136.46 2 +9.11.136.45 0 +9.11.136.44 2 +EOF + +simple_test 0,0,0,0 <<EOF +9.11.136.44 2 0,2 +9.11.136.45 2 0,2 +9.11.136.46 2 0,2 +10.11.16.44 1 1,3 +10.11.16.45 3 1,3 +10.11.16.46 1 1,3 +10.11.17.44 1 1,3 +10.11.17.45 3 1,3 +10.11.17.46 3 1,3 +10.11.18.44 1 1,3 +10.11.18.45 3 1,3 +10.11.18.46 1 1,3 +10.11.19.44 1 1,3 +10.11.19.45 3 1,3 +10.11.19.46 3 1,3 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.012.sh b/ctdb/tests/UNIT/takeover/lcp2.012.sh new file mode 100755 index 0000000..074cdcc --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.012.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "NoIPTakeover - nodes don't gain IPs" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 1 +192.168.21.253 1 +192.168.21.252 1 +192.168.20.254 1 +192.168.20.253 1 +192.168.20.252 1 +192.168.20.251 1 +192.168.20.250 1 +192.168.20.249 1 +EOF + +export CTDB_SET_NoIPTakeover=1 + +simple_test 0,0,0 <<EOF +192.168.20.249 1 +192.168.20.250 1 +192.168.20.251 1 +192.168.20.252 1 +192.168.20.253 1 +192.168.20.254 1 +192.168.21.252 1 +192.168.21.253 1 +192.168.21.254 1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.013.sh b/ctdb/tests/UNIT/takeover/lcp2.013.sh new file mode 100755 index 0000000..091a235 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.013.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "NoIPTakeover: nodes don't lose IPs" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 2 +192.168.21.253 1 +192.168.21.252 0 +192.168.20.254 2 +192.168.20.253 1 +192.168.20.252 0 +192.168.20.251 2 +192.168.20.250 1 +192.168.20.249 0 +EOF + +export CTDB_SET_NoIPTakeover=1 + +simple_test 0,0,0 <<EOF +192.168.20.249 0 +192.168.20.250 1 +192.168.20.251 2 +192.168.20.252 0 +192.168.20.253 1 +192.168.20.254 2 +192.168.21.252 0 +192.168.21.253 1 +192.168.21.254 2 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.014.sh b/ctdb/tests/UNIT/takeover/lcp2.014.sh new file mode 100755 index 0000000..25482c0 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.014.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, no IPs assigned, all unhealthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 -1 +192.168.21.253 -1 +192.168.21.252 -1 +192.168.20.254 -1 +192.168.20.253 -1 +192.168.20.252 -1 +192.168.20.251 -1 +192.168.20.250 -1 +192.168.20.249 -1 +EOF + +simple_test 2,2,2 <<EOF +192.168.21.254 -1 +192.168.21.253 -1 +192.168.21.252 -1 +192.168.20.254 -1 +192.168.20.253 -1 +192.168.20.252 -1 +192.168.20.251 -1 +192.168.20.250 -1 +192.168.20.249 -1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.015.sh b/ctdb/tests/UNIT/takeover/lcp2.015.sh new file mode 100755 index 0000000..63c87c6 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.015.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all IPs assigned, all unhealthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 -1 +192.168.21.253 -1 +192.168.21.252 -1 +192.168.20.254 -1 +192.168.20.253 -1 +192.168.20.252 -1 +192.168.20.251 -1 +192.168.20.250 -1 +192.168.20.249 -1 +EOF + +simple_test 2,2,2 <<EOF +192.168.21.254 2 +192.168.21.253 2 +192.168.21.252 2 +192.168.20.254 1 +192.168.20.253 1 +192.168.20.252 1 +192.168.20.251 0 +192.168.20.250 0 +192.168.20.249 0 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.016.sh b/ctdb/tests/UNIT/takeover/lcp2.016.sh new file mode 100755 index 0000000..da2f4b0 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.016.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all IPs assigned, 2->3 unhealthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.21.254 -1 +192.168.21.253 -1 +192.168.21.252 -1 +192.168.20.254 -1 +192.168.20.253 -1 +192.168.20.252 -1 +192.168.20.251 -1 +192.168.20.250 -1 +192.168.20.249 -1 +EOF + +simple_test 2,2,2 <<EOF +192.168.21.254 2 +192.168.21.253 2 +192.168.21.252 2 +192.168.20.254 2 +192.168.20.253 2 +192.168.20.252 2 +192.168.20.251 2 +192.168.20.250 2 +192.168.20.249 2 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.024.sh b/ctdb/tests/UNIT/takeover/lcp2.024.sh new file mode 100755 index 0000000..d297084 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.024.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, no IPs assigned, all healthy, all in STARTUP runstate" + +export CTDB_TEST_LOGLEVEL=NOTICE + +required_result <<EOF +${TEST_DATE_STAMP}Failed to find node to cover ip 192.168.21.254 +${TEST_DATE_STAMP}Failed to find node to cover ip 192.168.21.253 +${TEST_DATE_STAMP}Failed to find node to cover ip 192.168.21.252 +${TEST_DATE_STAMP}Failed to find node to cover ip 192.168.20.254 +${TEST_DATE_STAMP}Failed to find node to cover ip 192.168.20.253 +${TEST_DATE_STAMP}Failed to find node to cover ip 192.168.20.252 +${TEST_DATE_STAMP}Failed to find node to cover ip 192.168.20.251 +${TEST_DATE_STAMP}Failed to find node to cover ip 192.168.20.250 +${TEST_DATE_STAMP}Failed to find node to cover ip 192.168.20.249 +192.168.21.254 -1 +192.168.21.253 -1 +192.168.21.252 -1 +192.168.20.254 -1 +192.168.20.253 -1 +192.168.20.252 -1 +192.168.20.251 -1 +192.168.20.250 -1 +192.168.20.249 -1 +EOF + +export CTDB_TEST_RUNSTATE=4,4,4 + +simple_test 0,0,0 <<EOF +192.168.21.254 -1 +192.168.21.253 -1 +192.168.21.252 -1 +192.168.20.254 -1 +192.168.20.253 -1 +192.168.20.252 -1 +192.168.20.251 -1 +192.168.20.250 -1 +192.168.20.249 -1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.025.sh b/ctdb/tests/UNIT/takeover/lcp2.025.sh new file mode 100755 index 0000000..f52282e --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.025.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, no IPs assigned, all healthy, 1 in STARTUP runstate" + +export CTDB_TEST_LOGLEVEL=NOTICE + +required_result <<EOF +192.168.21.254 1 +192.168.21.253 2 +192.168.21.252 1 +192.168.20.254 1 +192.168.20.253 2 +192.168.20.252 1 +192.168.20.251 1 +192.168.20.250 2 +192.168.20.249 2 +EOF + +export CTDB_TEST_RUNSTATE=4,5,5 + +simple_test 0,0,0 <<EOF +192.168.21.254 -1 +192.168.21.253 -1 +192.168.21.252 -1 +192.168.20.254 -1 +192.168.20.253 -1 +192.168.20.252 -1 +192.168.20.251 -1 +192.168.20.250 -1 +192.168.20.249 -1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.027.sh b/ctdb/tests/UNIT/takeover/lcp2.027.sh new file mode 100755 index 0000000..f572b47 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.027.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "4 nodes, all IPs assigned, 3->4 unhealthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +130.216.30.181 0 +130.216.30.180 1 +130.216.30.179 3 +130.216.30.178 3 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 1 +130.216.30.173 0 +130.216.30.172 3 +130.216.30.171 1 +130.216.30.170 3 +10.19.99.253 0 +10.19.99.252 1 +10.19.99.251 0 +10.19.99.250 3 +EOF + +simple_test 0,0,2,0 <<EOF +130.216.30.170 3 +130.216.30.171 2 +130.216.30.172 3 +130.216.30.173 2 +130.216.30.174 1 +130.216.30.175 0 +130.216.30.176 1 +130.216.30.177 0 +130.216.30.178 3 +130.216.30.179 2 +130.216.30.180 1 +130.216.30.181 0 +10.19.99.250 3 +10.19.99.251 2 +10.19.99.252 1 +10.19.99.253 0 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.028.sh b/ctdb/tests/UNIT/takeover/lcp2.028.sh new file mode 100755 index 0000000..b0a8ef5 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.028.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "4 nodes, all healthy/assigned, stays unbalanced" + +export CTDB_TEST_LOGLEVEL=INFO + +required_result <<EOF +130.216.30.181 0 +130.216.30.180 1 +130.216.30.179 2 +130.216.30.178 3 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 1 +130.216.30.173 0 +130.216.30.172 3 +130.216.30.171 1 +130.216.30.170 3 +10.19.99.253 0 +10.19.99.252 1 +10.19.99.251 0 +10.19.99.250 3 +EOF + +simple_test 0,0,0,0 <<EOF +130.216.30.181 0 +130.216.30.180 1 +130.216.30.179 2 +130.216.30.178 3 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 1 +130.216.30.173 0 +130.216.30.172 3 +130.216.30.171 1 +130.216.30.170 3 +10.19.99.253 0 +10.19.99.252 1 +10.19.99.251 0 +10.19.99.250 3 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.029.sh b/ctdb/tests/UNIT/takeover/lcp2.029.sh new file mode 100755 index 0000000..5354963 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.029.sh @@ -0,0 +1,111 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "4 nodes, some IPs unassigned on target nodes" + +export CTDB_TEST_LOGLEVEL=INFO + +required_result <<EOF +${TEST_DATE_STAMP} 10.19.99.251 -> 2 [+9216] +${TEST_DATE_STAMP} 130.216.30.173 -> 2 [+24345] +${TEST_DATE_STAMP} 130.216.30.171 -> 2 [+39970] +130.216.30.181 0 +130.216.30.180 1 +130.216.30.179 2 +130.216.30.178 3 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 1 +130.216.30.173 2 +130.216.30.172 3 +130.216.30.171 2 +130.216.30.170 3 +10.19.99.253 0 +10.19.99.252 1 +10.19.99.251 2 +10.19.99.250 3 +EOF + +# In this example were 4 releases from node 2 in a previous iteration +# +# Release of IP 130.216.30.179/27 on interface ethX1 node:3 +# Release of IP 130.216.30.173/27 on interface ethX1 node:0 +# Release of IP 130.216.30.171/27 on interface ethX1 node:1 +# Release of IP 10.19.99.251/22 on interface ethX2 node:0 +# +# However, one release failed so no takeovers were done. This means +# that the target node for each IP still thinks that the IPs are held +# by node 2. The release of 130.216.30.179 was so late that node 2 +# still thought that it held that address. + +simple_test 0,0,0,0 multi <<EOF +130.216.30.181 0 +130.216.30.180 1 +130.216.30.179 3 +130.216.30.178 3 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 1 +130.216.30.173 2 +130.216.30.172 3 +130.216.30.171 1 +130.216.30.170 3 +10.19.99.253 0 +10.19.99.252 1 +10.19.99.251 2 +10.19.99.250 3 + +130.216.30.181 0 +130.216.30.180 1 +130.216.30.179 3 +130.216.30.178 3 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 1 +130.216.30.173 0 +130.216.30.172 3 +130.216.30.171 2 +130.216.30.170 3 +10.19.99.253 0 +10.19.99.252 1 +10.19.99.251 0 +10.19.99.250 3 + +130.216.30.181 0 +130.216.30.180 1 +130.216.30.179 2 +130.216.30.178 3 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 1 +130.216.30.173 0 +130.216.30.172 3 +130.216.30.171 1 +130.216.30.170 3 +10.19.99.253 0 +10.19.99.252 1 +10.19.99.251 0 +10.19.99.250 3 + +130.216.30.181 0 +130.216.30.180 1 +130.216.30.179 2 +130.216.30.178 3 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 1 +130.216.30.173 0 +130.216.30.172 3 +130.216.30.171 1 +130.216.30.170 3 +10.19.99.253 0 +10.19.99.252 1 +10.19.99.251 0 +10.19.99.250 3 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.030.sh b/ctdb/tests/UNIT/takeover/lcp2.030.sh new file mode 100755 index 0000000..87a7f58 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.030.sh @@ -0,0 +1,1813 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "900 IPs, 5 nodes, 0 -> 5 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.10.90 0 +192.168.10.89 1 +192.168.10.88 2 +192.168.10.87 3 +192.168.10.86 4 +192.168.10.85 0 +192.168.10.84 1 +192.168.10.83 2 +192.168.10.82 3 +192.168.10.81 4 +192.168.10.80 0 +192.168.10.79 0 +192.168.10.78 1 +192.168.10.77 2 +192.168.10.76 3 +192.168.10.75 4 +192.168.10.74 1 +192.168.10.73 2 +192.168.10.72 3 +192.168.10.71 3 +192.168.10.70 4 +192.168.10.69 0 +192.168.10.68 1 +192.168.10.67 2 +192.168.10.66 4 +192.168.10.65 0 +192.168.10.64 1 +192.168.10.63 0 +192.168.10.62 1 +192.168.10.61 2 +192.168.10.60 3 +192.168.10.59 4 +192.168.10.58 2 +192.168.10.57 3 +192.168.10.56 0 +192.168.10.55 0 +192.168.10.54 1 +192.168.10.53 2 +192.168.10.52 3 +192.168.10.51 4 +192.168.10.50 1 +192.168.10.49 4 +192.168.10.48 2 +192.168.10.47 0 +192.168.10.46 1 +192.168.10.45 2 +192.168.10.44 3 +192.168.10.43 4 +192.168.10.42 2 +192.168.10.41 3 +192.168.10.40 1 +192.168.10.39 3 +192.168.10.38 4 +192.168.10.37 0 +192.168.10.36 1 +192.168.10.35 2 +192.168.10.34 4 +192.168.10.33 0 +192.168.10.32 3 +192.168.10.31 0 +192.168.10.30 1 +192.168.10.29 2 +192.168.10.28 3 +192.168.10.27 4 +192.168.10.26 3 +192.168.10.25 2 +192.168.10.24 0 +192.168.10.23 3 +192.168.10.22 4 +192.168.10.21 0 +192.168.10.20 1 +192.168.10.19 2 +192.168.10.18 4 +192.168.10.17 1 +192.168.10.16 4 +192.168.10.15 0 +192.168.10.14 1 +192.168.10.13 2 +192.168.10.12 3 +192.168.10.11 4 +192.168.10.10 2 +192.168.10.9 3 +192.168.10.8 4 +192.168.10.7 0 +192.168.10.6 1 +192.168.10.5 2 +192.168.10.4 3 +192.168.10.3 4 +192.168.10.2 0 +192.168.10.1 1 +192.168.9.90 0 +192.168.9.89 1 +192.168.9.88 2 +192.168.9.87 3 +192.168.9.86 4 +192.168.9.85 0 +192.168.9.84 1 +192.168.9.83 2 +192.168.9.82 3 +192.168.9.81 4 +192.168.9.80 0 +192.168.9.79 0 +192.168.9.78 1 +192.168.9.77 2 +192.168.9.76 3 +192.168.9.75 4 +192.168.9.74 1 +192.168.9.73 2 +192.168.9.72 3 +192.168.9.71 3 +192.168.9.70 4 +192.168.9.69 0 +192.168.9.68 1 +192.168.9.67 2 +192.168.9.66 4 +192.168.9.65 0 +192.168.9.64 1 +192.168.9.63 0 +192.168.9.62 1 +192.168.9.61 2 +192.168.9.60 3 +192.168.9.59 4 +192.168.9.58 2 +192.168.9.57 3 +192.168.9.56 4 +192.168.9.55 0 +192.168.9.54 1 +192.168.9.53 2 +192.168.9.52 3 +192.168.9.51 4 +192.168.9.50 0 +192.168.9.49 1 +192.168.9.48 2 +192.168.9.47 0 +192.168.9.46 1 +192.168.9.45 2 +192.168.9.44 3 +192.168.9.43 4 +192.168.9.42 2 +192.168.9.41 4 +192.168.9.40 3 +192.168.9.39 0 +192.168.9.38 1 +192.168.9.37 2 +192.168.9.36 3 +192.168.9.35 4 +192.168.9.34 0 +192.168.9.33 1 +192.168.9.32 4 +192.168.9.31 0 +192.168.9.30 1 +192.168.9.29 2 +192.168.9.28 3 +192.168.9.27 4 +192.168.9.26 2 +192.168.9.25 3 +192.168.9.24 0 +192.168.9.23 3 +192.168.9.22 4 +192.168.9.21 0 +192.168.9.20 1 +192.168.9.19 2 +192.168.9.18 4 +192.168.9.17 1 +192.168.9.16 3 +192.168.9.15 0 +192.168.9.14 1 +192.168.9.13 2 +192.168.9.12 3 +192.168.9.11 4 +192.168.9.10 2 +192.168.9.9 4 +192.168.9.8 3 +192.168.9.7 0 +192.168.9.6 1 +192.168.9.5 2 +192.168.9.4 3 +192.168.9.3 4 +192.168.9.2 0 +192.168.9.1 1 +192.168.8.90 0 +192.168.8.89 1 +192.168.8.88 2 +192.168.8.87 3 +192.168.8.86 4 +192.168.8.85 0 +192.168.8.84 1 +192.168.8.83 2 +192.168.8.82 3 +192.168.8.81 4 +192.168.8.80 0 +192.168.8.79 0 +192.168.8.78 1 +192.168.8.77 2 +192.168.8.76 3 +192.168.8.75 4 +192.168.8.74 1 +192.168.8.73 2 +192.168.8.72 3 +192.168.8.71 3 +192.168.8.70 4 +192.168.8.69 0 +192.168.8.68 1 +192.168.8.67 2 +192.168.8.66 4 +192.168.8.65 3 +192.168.8.64 0 +192.168.8.63 0 +192.168.8.62 1 +192.168.8.61 2 +192.168.8.60 3 +192.168.8.59 4 +192.168.8.58 1 +192.168.8.57 2 +192.168.8.56 3 +192.168.8.55 0 +192.168.8.54 1 +192.168.8.53 2 +192.168.8.52 3 +192.168.8.51 4 +192.168.8.50 0 +192.168.8.49 4 +192.168.8.48 1 +192.168.8.47 0 +192.168.8.46 1 +192.168.8.45 2 +192.168.8.44 3 +192.168.8.43 4 +192.168.8.42 2 +192.168.8.41 1 +192.168.8.40 4 +192.168.8.39 0 +192.168.8.38 1 +192.168.8.37 2 +192.168.8.36 3 +192.168.8.35 4 +192.168.8.34 3 +192.168.8.33 0 +192.168.8.32 2 +192.168.8.31 0 +192.168.8.30 1 +192.168.8.29 2 +192.168.8.28 3 +192.168.8.27 4 +192.168.8.26 2 +192.168.8.25 1 +192.168.8.24 3 +192.168.8.23 3 +192.168.8.22 4 +192.168.8.21 0 +192.168.8.20 1 +192.168.8.19 2 +192.168.8.18 4 +192.168.8.17 0 +192.168.8.16 4 +192.168.8.15 0 +192.168.8.14 1 +192.168.8.13 2 +192.168.8.12 3 +192.168.8.11 4 +192.168.8.10 1 +192.168.8.9 2 +192.168.8.8 4 +192.168.8.7 0 +192.168.8.6 1 +192.168.8.5 2 +192.168.8.4 3 +192.168.8.3 4 +192.168.8.2 3 +192.168.8.1 0 +192.168.7.90 0 +192.168.7.89 1 +192.168.7.88 2 +192.168.7.87 3 +192.168.7.86 4 +192.168.7.85 0 +192.168.7.84 1 +192.168.7.83 2 +192.168.7.82 3 +192.168.7.81 4 +192.168.7.80 1 +192.168.7.79 0 +192.168.7.78 1 +192.168.7.77 2 +192.168.7.76 3 +192.168.7.75 4 +192.168.7.74 2 +192.168.7.73 3 +192.168.7.72 0 +192.168.7.71 3 +192.168.7.70 4 +192.168.7.69 0 +192.168.7.68 1 +192.168.7.67 2 +192.168.7.66 4 +192.168.7.65 1 +192.168.7.64 3 +192.168.7.63 0 +192.168.7.62 1 +192.168.7.61 2 +192.168.7.60 3 +192.168.7.59 4 +192.168.7.58 2 +192.168.7.57 0 +192.168.7.56 1 +192.168.7.55 0 +192.168.7.54 1 +192.168.7.53 2 +192.168.7.52 3 +192.168.7.51 4 +192.168.7.50 3 +192.168.7.49 4 +192.168.7.48 2 +192.168.7.47 0 +192.168.7.46 1 +192.168.7.45 2 +192.168.7.44 3 +192.168.7.43 4 +192.168.7.42 2 +192.168.7.41 0 +192.168.7.40 1 +192.168.7.39 4 +192.168.7.38 0 +192.168.7.37 1 +192.168.7.36 2 +192.168.7.35 3 +192.168.7.34 4 +192.168.7.33 3 +192.168.7.32 0 +192.168.7.31 0 +192.168.7.30 1 +192.168.7.29 2 +192.168.7.28 3 +192.168.7.27 4 +192.168.7.26 2 +192.168.7.25 0 +192.168.7.24 1 +192.168.7.23 3 +192.168.7.22 4 +192.168.7.21 0 +192.168.7.20 1 +192.168.7.19 2 +192.168.7.18 4 +192.168.7.17 3 +192.168.7.16 4 +192.168.7.15 0 +192.168.7.14 1 +192.168.7.13 2 +192.168.7.12 3 +192.168.7.11 4 +192.168.7.10 3 +192.168.7.9 2 +192.168.7.8 0 +192.168.7.7 2 +192.168.7.6 4 +192.168.7.5 0 +192.168.7.4 1 +192.168.7.3 3 +192.168.7.2 4 +192.168.7.1 1 +192.168.6.90 0 +192.168.6.89 1 +192.168.6.88 2 +192.168.6.87 3 +192.168.6.86 4 +192.168.6.85 0 +192.168.6.84 1 +192.168.6.83 2 +192.168.6.82 4 +192.168.6.81 3 +192.168.6.80 0 +192.168.6.79 0 +192.168.6.78 1 +192.168.6.77 2 +192.168.6.76 3 +192.168.6.75 4 +192.168.6.74 2 +192.168.6.73 3 +192.168.6.72 1 +192.168.6.71 3 +192.168.6.70 4 +192.168.6.69 0 +192.168.6.68 1 +192.168.6.67 2 +192.168.6.66 4 +192.168.6.65 0 +192.168.6.64 1 +192.168.6.63 0 +192.168.6.62 1 +192.168.6.61 2 +192.168.6.60 3 +192.168.6.59 4 +192.168.6.58 2 +192.168.6.57 3 +192.168.6.56 0 +192.168.6.55 3 +192.168.6.54 4 +192.168.6.53 1 +192.168.6.52 2 +192.168.6.51 0 +192.168.6.50 4 +192.168.6.49 1 +192.168.6.48 2 +192.168.6.47 0 +192.168.6.46 1 +192.168.6.45 2 +192.168.6.44 3 +192.168.6.43 4 +192.168.6.42 2 +192.168.6.41 4 +192.168.6.40 3 +192.168.6.39 0 +192.168.6.38 1 +192.168.6.37 2 +192.168.6.36 3 +192.168.6.35 4 +192.168.6.34 0 +192.168.6.33 1 +192.168.6.32 4 +192.168.6.31 0 +192.168.6.30 1 +192.168.6.29 2 +192.168.6.28 3 +192.168.6.27 4 +192.168.6.26 2 +192.168.6.25 3 +192.168.6.24 0 +192.168.6.23 3 +192.168.6.22 4 +192.168.6.21 0 +192.168.6.20 1 +192.168.6.19 2 +192.168.6.18 4 +192.168.6.17 1 +192.168.6.16 3 +192.168.6.15 0 +192.168.6.14 1 +192.168.6.13 2 +192.168.6.12 3 +192.168.6.11 4 +192.168.6.10 2 +192.168.6.9 3 +192.168.6.8 4 +192.168.6.7 0 +192.168.6.6 1 +192.168.6.5 2 +192.168.6.4 3 +192.168.6.3 4 +192.168.6.2 0 +192.168.6.1 1 +192.168.5.90 0 +192.168.5.89 1 +192.168.5.88 2 +192.168.5.87 3 +192.168.5.86 4 +192.168.5.85 0 +192.168.5.84 1 +192.168.5.83 2 +192.168.5.82 4 +192.168.5.81 3 +192.168.5.80 0 +192.168.5.79 0 +192.168.5.78 1 +192.168.5.77 2 +192.168.5.76 3 +192.168.5.75 4 +192.168.5.74 2 +192.168.5.73 3 +192.168.5.72 1 +192.168.5.71 3 +192.168.5.70 4 +192.168.5.69 2 +192.168.5.68 0 +192.168.5.67 1 +192.168.5.66 4 +192.168.5.65 2 +192.168.5.64 0 +192.168.5.63 0 +192.168.5.62 1 +192.168.5.61 2 +192.168.5.60 3 +192.168.5.59 4 +192.168.5.58 1 +192.168.5.57 3 +192.168.5.56 2 +192.168.5.55 0 +192.168.5.54 1 +192.168.5.53 2 +192.168.5.52 3 +192.168.5.51 4 +192.168.5.50 0 +192.168.5.49 4 +192.168.5.48 1 +192.168.5.47 0 +192.168.5.46 1 +192.168.5.45 2 +192.168.5.44 3 +192.168.5.43 4 +192.168.5.42 1 +192.168.5.41 3 +192.168.5.40 2 +192.168.5.39 2 +192.168.5.38 3 +192.168.5.37 4 +192.168.5.36 0 +192.168.5.35 1 +192.168.5.34 4 +192.168.5.33 0 +192.168.5.32 4 +192.168.5.31 0 +192.168.5.30 1 +192.168.5.29 2 +192.168.5.28 3 +192.168.5.27 4 +192.168.5.26 1 +192.168.5.25 3 +192.168.5.24 2 +192.168.5.23 3 +192.168.5.22 4 +192.168.5.21 2 +192.168.5.20 0 +192.168.5.19 1 +192.168.5.18 4 +192.168.5.17 0 +192.168.5.16 3 +192.168.5.15 0 +192.168.5.14 1 +192.168.5.13 2 +192.168.5.12 3 +192.168.5.11 4 +192.168.5.10 1 +192.168.5.9 4 +192.168.5.8 3 +192.168.5.7 0 +192.168.5.6 1 +192.168.5.5 2 +192.168.5.4 3 +192.168.5.3 4 +192.168.5.2 2 +192.168.5.1 0 +192.168.4.90 0 +192.168.4.89 1 +192.168.4.88 2 +192.168.4.87 3 +192.168.4.86 4 +192.168.4.85 0 +192.168.4.84 1 +192.168.4.83 2 +192.168.4.82 3 +192.168.4.81 4 +192.168.4.80 0 +192.168.4.79 0 +192.168.4.78 1 +192.168.4.77 2 +192.168.4.76 3 +192.168.4.75 4 +192.168.4.74 1 +192.168.4.73 2 +192.168.4.72 3 +192.168.4.71 3 +192.168.4.70 4 +192.168.4.69 0 +192.168.4.68 1 +192.168.4.67 2 +192.168.4.66 4 +192.168.4.65 1 +192.168.4.64 3 +192.168.4.63 0 +192.168.4.62 1 +192.168.4.61 2 +192.168.4.60 3 +192.168.4.59 4 +192.168.4.58 0 +192.168.4.57 2 +192.168.4.56 1 +192.168.4.55 0 +192.168.4.54 1 +192.168.4.53 2 +192.168.4.52 3 +192.168.4.51 4 +192.168.4.50 3 +192.168.4.49 4 +192.168.4.48 0 +192.168.4.47 0 +192.168.4.46 1 +192.168.4.45 2 +192.168.4.44 3 +192.168.4.43 4 +192.168.4.42 2 +192.168.4.41 0 +192.168.4.40 1 +192.168.4.39 4 +192.168.4.38 0 +192.168.4.37 1 +192.168.4.36 2 +192.168.4.35 3 +192.168.4.34 4 +192.168.4.33 3 +192.168.4.32 2 +192.168.4.31 0 +192.168.4.30 1 +192.168.4.29 2 +192.168.4.28 3 +192.168.4.27 4 +192.168.4.26 0 +192.168.4.25 2 +192.168.4.24 1 +192.168.4.23 3 +192.168.4.22 4 +192.168.4.21 0 +192.168.4.20 1 +192.168.4.19 2 +192.168.4.18 4 +192.168.4.17 3 +192.168.4.16 1 +192.168.4.15 0 +192.168.4.14 1 +192.168.4.13 2 +192.168.4.12 3 +192.168.4.11 4 +192.168.4.10 3 +192.168.4.9 0 +192.168.4.8 2 +192.168.4.7 2 +192.168.4.6 3 +192.168.4.5 4 +192.168.4.4 0 +192.168.4.3 1 +192.168.4.2 4 +192.168.4.1 4 +192.168.3.90 0 +192.168.3.89 1 +192.168.3.88 2 +192.168.3.87 3 +192.168.3.86 4 +192.168.3.85 0 +192.168.3.84 1 +192.168.3.83 2 +192.168.3.82 3 +192.168.3.81 4 +192.168.3.80 0 +192.168.3.79 0 +192.168.3.78 1 +192.168.3.77 2 +192.168.3.76 3 +192.168.3.75 4 +192.168.3.74 1 +192.168.3.73 2 +192.168.3.72 3 +192.168.3.71 3 +192.168.3.70 4 +192.168.3.69 0 +192.168.3.68 1 +192.168.3.67 2 +192.168.3.66 4 +192.168.3.65 0 +192.168.3.64 3 +192.168.3.63 0 +192.168.3.62 1 +192.168.3.61 2 +192.168.3.60 3 +192.168.3.59 4 +192.168.3.58 2 +192.168.3.57 1 +192.168.3.56 3 +192.168.3.55 0 +192.168.3.54 1 +192.168.3.53 2 +192.168.3.52 3 +192.168.3.51 4 +192.168.3.50 0 +192.168.3.49 4 +192.168.3.48 2 +192.168.3.47 0 +192.168.3.46 1 +192.168.3.45 2 +192.168.3.44 3 +192.168.3.43 4 +192.168.3.42 2 +192.168.3.41 1 +192.168.3.40 0 +192.168.3.39 1 +192.168.3.38 2 +192.168.3.37 3 +192.168.3.36 4 +192.168.3.35 0 +192.168.3.34 4 +192.168.3.33 3 +192.168.3.32 4 +192.168.3.31 0 +192.168.3.30 1 +192.168.3.29 2 +192.168.3.28 3 +192.168.3.27 4 +192.168.3.26 2 +192.168.3.25 1 +192.168.3.24 0 +192.168.3.23 3 +192.168.3.22 4 +192.168.3.21 0 +192.168.3.20 1 +192.168.3.19 2 +192.168.3.18 4 +192.168.3.17 3 +192.168.3.16 1 +192.168.3.15 0 +192.168.3.14 1 +192.168.3.13 2 +192.168.3.12 3 +192.168.3.11 4 +192.168.3.10 2 +192.168.3.9 1 +192.168.3.8 0 +192.168.3.7 4 +192.168.3.6 0 +192.168.3.5 1 +192.168.3.4 2 +192.168.3.3 3 +192.168.3.2 4 +192.168.3.1 3 +192.168.2.90 0 +192.168.2.89 1 +192.168.2.88 2 +192.168.2.87 3 +192.168.2.86 4 +192.168.2.85 0 +192.168.2.84 1 +192.168.2.83 2 +192.168.2.82 3 +192.168.2.81 4 +192.168.2.80 1 +192.168.2.79 0 +192.168.2.78 1 +192.168.2.77 2 +192.168.2.76 3 +192.168.2.75 4 +192.168.2.74 2 +192.168.2.73 3 +192.168.2.72 0 +192.168.2.71 3 +192.168.2.70 4 +192.168.2.69 0 +192.168.2.68 1 +192.168.2.67 2 +192.168.2.66 4 +192.168.2.65 1 +192.168.2.64 3 +192.168.2.63 0 +192.168.2.62 1 +192.168.2.61 2 +192.168.2.60 3 +192.168.2.59 4 +192.168.2.58 0 +192.168.2.57 2 +192.168.2.56 1 +192.168.2.55 0 +192.168.2.54 1 +192.168.2.53 2 +192.168.2.52 3 +192.168.2.51 4 +192.168.2.50 3 +192.168.2.49 4 +192.168.2.48 0 +192.168.2.47 0 +192.168.2.46 1 +192.168.2.45 2 +192.168.2.44 3 +192.168.2.43 4 +192.168.2.42 2 +192.168.2.41 0 +192.168.2.40 1 +192.168.2.39 0 +192.168.2.38 1 +192.168.2.37 2 +192.168.2.36 3 +192.168.2.35 4 +192.168.2.34 3 +192.168.2.33 4 +192.168.2.32 2 +192.168.2.31 0 +192.168.2.30 1 +192.168.2.29 2 +192.168.2.28 3 +192.168.2.27 4 +192.168.2.26 2 +192.168.2.25 0 +192.168.2.24 1 +192.168.2.23 3 +192.168.2.22 4 +192.168.2.21 0 +192.168.2.20 1 +192.168.2.19 2 +192.168.2.18 4 +192.168.2.17 3 +192.168.2.16 4 +192.168.2.15 0 +192.168.2.14 1 +192.168.2.13 2 +192.168.2.12 3 +192.168.2.11 4 +192.168.2.10 0 +192.168.2.9 2 +192.168.2.8 3 +192.168.2.7 2 +192.168.2.6 4 +192.168.2.5 0 +192.168.2.4 1 +192.168.2.3 3 +192.168.2.2 4 +192.168.2.1 1 +192.168.1.90 0 +192.168.1.89 1 +192.168.1.88 2 +192.168.1.87 3 +192.168.1.86 4 +192.168.1.85 0 +192.168.1.84 1 +192.168.1.83 2 +192.168.1.82 3 +192.168.1.81 4 +192.168.1.80 0 +192.168.1.79 0 +192.168.1.78 1 +192.168.1.77 2 +192.168.1.76 3 +192.168.1.75 4 +192.168.1.74 1 +192.168.1.73 2 +192.168.1.72 3 +192.168.1.71 3 +192.168.1.70 4 +192.168.1.69 0 +192.168.1.68 1 +192.168.1.67 2 +192.168.1.66 4 +192.168.1.65 0 +192.168.1.64 1 +192.168.1.63 0 +192.168.1.62 1 +192.168.1.61 2 +192.168.1.60 3 +192.168.1.59 4 +192.168.1.58 2 +192.168.1.57 3 +192.168.1.56 1 +192.168.1.55 0 +192.168.1.54 1 +192.168.1.53 2 +192.168.1.52 3 +192.168.1.51 4 +192.168.1.50 0 +192.168.1.49 4 +192.168.1.48 2 +192.168.1.47 0 +192.168.1.46 1 +192.168.1.45 2 +192.168.1.44 3 +192.168.1.43 4 +192.168.1.42 2 +192.168.1.41 3 +192.168.1.40 0 +192.168.1.39 3 +192.168.1.38 4 +192.168.1.37 0 +192.168.1.36 1 +192.168.1.35 2 +192.168.1.34 4 +192.168.1.33 1 +192.168.1.32 3 +192.168.1.31 0 +192.168.1.30 1 +192.168.1.29 2 +192.168.1.28 3 +192.168.1.27 4 +192.168.1.26 2 +192.168.1.25 3 +192.168.1.24 0 +192.168.1.23 3 +192.168.1.22 4 +192.168.1.21 0 +192.168.1.20 1 +192.168.1.19 2 +192.168.1.18 4 +192.168.1.17 1 +192.168.1.16 4 +192.168.1.15 0 +192.168.1.14 1 +192.168.1.13 2 +192.168.1.12 3 +192.168.1.11 4 +192.168.1.10 2 +192.168.1.9 3 +192.168.1.8 0 +192.168.1.7 3 +192.168.1.6 4 +192.168.1.5 0 +192.168.1.4 1 +192.168.1.3 2 +192.168.1.2 4 +192.168.1.1 1 +EOF + +simple_test 0,0,0,0,0 <<EOF +192.168.1.1 -1 +192.168.1.2 -1 +192.168.1.3 -1 +192.168.1.4 -1 +192.168.1.5 -1 +192.168.1.6 -1 +192.168.1.7 -1 +192.168.1.8 -1 +192.168.1.9 -1 +192.168.1.10 -1 +192.168.1.11 -1 +192.168.1.12 -1 +192.168.1.13 -1 +192.168.1.14 -1 +192.168.1.15 -1 +192.168.1.16 -1 +192.168.1.17 -1 +192.168.1.18 -1 +192.168.1.19 -1 +192.168.1.20 -1 +192.168.1.21 -1 +192.168.1.22 -1 +192.168.1.23 -1 +192.168.1.24 -1 +192.168.1.25 -1 +192.168.1.26 -1 +192.168.1.27 -1 +192.168.1.28 -1 +192.168.1.29 -1 +192.168.1.30 -1 +192.168.1.31 -1 +192.168.1.32 -1 +192.168.1.33 -1 +192.168.1.34 -1 +192.168.1.35 -1 +192.168.1.36 -1 +192.168.1.37 -1 +192.168.1.38 -1 +192.168.1.39 -1 +192.168.1.40 -1 +192.168.1.41 -1 +192.168.1.42 -1 +192.168.1.43 -1 +192.168.1.44 -1 +192.168.1.45 -1 +192.168.1.46 -1 +192.168.1.47 -1 +192.168.1.48 -1 +192.168.1.49 -1 +192.168.1.50 -1 +192.168.1.51 -1 +192.168.1.52 -1 +192.168.1.53 -1 +192.168.1.54 -1 +192.168.1.55 -1 +192.168.1.56 -1 +192.168.1.57 -1 +192.168.1.58 -1 +192.168.1.59 -1 +192.168.1.60 -1 +192.168.1.61 -1 +192.168.1.62 -1 +192.168.1.63 -1 +192.168.1.64 -1 +192.168.1.65 -1 +192.168.1.66 -1 +192.168.1.67 -1 +192.168.1.68 -1 +192.168.1.69 -1 +192.168.1.70 -1 +192.168.1.71 -1 +192.168.1.72 -1 +192.168.1.73 -1 +192.168.1.74 -1 +192.168.1.75 -1 +192.168.1.76 -1 +192.168.1.77 -1 +192.168.1.78 -1 +192.168.1.79 -1 +192.168.1.80 -1 +192.168.1.81 -1 +192.168.1.82 -1 +192.168.1.83 -1 +192.168.1.84 -1 +192.168.1.85 -1 +192.168.1.86 -1 +192.168.1.87 -1 +192.168.1.88 -1 +192.168.1.89 -1 +192.168.1.90 -1 +192.168.2.1 -1 +192.168.2.2 -1 +192.168.2.3 -1 +192.168.2.4 -1 +192.168.2.5 -1 +192.168.2.6 -1 +192.168.2.7 -1 +192.168.2.8 -1 +192.168.2.9 -1 +192.168.2.10 -1 +192.168.2.11 -1 +192.168.2.12 -1 +192.168.2.13 -1 +192.168.2.14 -1 +192.168.2.15 -1 +192.168.2.16 -1 +192.168.2.17 -1 +192.168.2.18 -1 +192.168.2.19 -1 +192.168.2.20 -1 +192.168.2.21 -1 +192.168.2.22 -1 +192.168.2.23 -1 +192.168.2.24 -1 +192.168.2.25 -1 +192.168.2.26 -1 +192.168.2.27 -1 +192.168.2.28 -1 +192.168.2.29 -1 +192.168.2.30 -1 +192.168.2.31 -1 +192.168.2.32 -1 +192.168.2.33 -1 +192.168.2.34 -1 +192.168.2.35 -1 +192.168.2.36 -1 +192.168.2.37 -1 +192.168.2.38 -1 +192.168.2.39 -1 +192.168.2.40 -1 +192.168.2.41 -1 +192.168.2.42 -1 +192.168.2.43 -1 +192.168.2.44 -1 +192.168.2.45 -1 +192.168.2.46 -1 +192.168.2.47 -1 +192.168.2.48 -1 +192.168.2.49 -1 +192.168.2.50 -1 +192.168.2.51 -1 +192.168.2.52 -1 +192.168.2.53 -1 +192.168.2.54 -1 +192.168.2.55 -1 +192.168.2.56 -1 +192.168.2.57 -1 +192.168.2.58 -1 +192.168.2.59 -1 +192.168.2.60 -1 +192.168.2.61 -1 +192.168.2.62 -1 +192.168.2.63 -1 +192.168.2.64 -1 +192.168.2.65 -1 +192.168.2.66 -1 +192.168.2.67 -1 +192.168.2.68 -1 +192.168.2.69 -1 +192.168.2.70 -1 +192.168.2.71 -1 +192.168.2.72 -1 +192.168.2.73 -1 +192.168.2.74 -1 +192.168.2.75 -1 +192.168.2.76 -1 +192.168.2.77 -1 +192.168.2.78 -1 +192.168.2.79 -1 +192.168.2.80 -1 +192.168.2.81 -1 +192.168.2.82 -1 +192.168.2.83 -1 +192.168.2.84 -1 +192.168.2.85 -1 +192.168.2.86 -1 +192.168.2.87 -1 +192.168.2.88 -1 +192.168.2.89 -1 +192.168.2.90 -1 +192.168.3.1 -1 +192.168.3.2 -1 +192.168.3.3 -1 +192.168.3.4 -1 +192.168.3.5 -1 +192.168.3.6 -1 +192.168.3.7 -1 +192.168.3.8 -1 +192.168.3.9 -1 +192.168.3.10 -1 +192.168.3.11 -1 +192.168.3.12 -1 +192.168.3.13 -1 +192.168.3.14 -1 +192.168.3.15 -1 +192.168.3.16 -1 +192.168.3.17 -1 +192.168.3.18 -1 +192.168.3.19 -1 +192.168.3.20 -1 +192.168.3.21 -1 +192.168.3.22 -1 +192.168.3.23 -1 +192.168.3.24 -1 +192.168.3.25 -1 +192.168.3.26 -1 +192.168.3.27 -1 +192.168.3.28 -1 +192.168.3.29 -1 +192.168.3.30 -1 +192.168.3.31 -1 +192.168.3.32 -1 +192.168.3.33 -1 +192.168.3.34 -1 +192.168.3.35 -1 +192.168.3.36 -1 +192.168.3.37 -1 +192.168.3.38 -1 +192.168.3.39 -1 +192.168.3.40 -1 +192.168.3.41 -1 +192.168.3.42 -1 +192.168.3.43 -1 +192.168.3.44 -1 +192.168.3.45 -1 +192.168.3.46 -1 +192.168.3.47 -1 +192.168.3.48 -1 +192.168.3.49 -1 +192.168.3.50 -1 +192.168.3.51 -1 +192.168.3.52 -1 +192.168.3.53 -1 +192.168.3.54 -1 +192.168.3.55 -1 +192.168.3.56 -1 +192.168.3.57 -1 +192.168.3.58 -1 +192.168.3.59 -1 +192.168.3.60 -1 +192.168.3.61 -1 +192.168.3.62 -1 +192.168.3.63 -1 +192.168.3.64 -1 +192.168.3.65 -1 +192.168.3.66 -1 +192.168.3.67 -1 +192.168.3.68 -1 +192.168.3.69 -1 +192.168.3.70 -1 +192.168.3.71 -1 +192.168.3.72 -1 +192.168.3.73 -1 +192.168.3.74 -1 +192.168.3.75 -1 +192.168.3.76 -1 +192.168.3.77 -1 +192.168.3.78 -1 +192.168.3.79 -1 +192.168.3.80 -1 +192.168.3.81 -1 +192.168.3.82 -1 +192.168.3.83 -1 +192.168.3.84 -1 +192.168.3.85 -1 +192.168.3.86 -1 +192.168.3.87 -1 +192.168.3.88 -1 +192.168.3.89 -1 +192.168.3.90 -1 +192.168.4.1 -1 +192.168.4.2 -1 +192.168.4.3 -1 +192.168.4.4 -1 +192.168.4.5 -1 +192.168.4.6 -1 +192.168.4.7 -1 +192.168.4.8 -1 +192.168.4.9 -1 +192.168.4.10 -1 +192.168.4.11 -1 +192.168.4.12 -1 +192.168.4.13 -1 +192.168.4.14 -1 +192.168.4.15 -1 +192.168.4.16 -1 +192.168.4.17 -1 +192.168.4.18 -1 +192.168.4.19 -1 +192.168.4.20 -1 +192.168.4.21 -1 +192.168.4.22 -1 +192.168.4.23 -1 +192.168.4.24 -1 +192.168.4.25 -1 +192.168.4.26 -1 +192.168.4.27 -1 +192.168.4.28 -1 +192.168.4.29 -1 +192.168.4.30 -1 +192.168.4.31 -1 +192.168.4.32 -1 +192.168.4.33 -1 +192.168.4.34 -1 +192.168.4.35 -1 +192.168.4.36 -1 +192.168.4.37 -1 +192.168.4.38 -1 +192.168.4.39 -1 +192.168.4.40 -1 +192.168.4.41 -1 +192.168.4.42 -1 +192.168.4.43 -1 +192.168.4.44 -1 +192.168.4.45 -1 +192.168.4.46 -1 +192.168.4.47 -1 +192.168.4.48 -1 +192.168.4.49 -1 +192.168.4.50 -1 +192.168.4.51 -1 +192.168.4.52 -1 +192.168.4.53 -1 +192.168.4.54 -1 +192.168.4.55 -1 +192.168.4.56 -1 +192.168.4.57 -1 +192.168.4.58 -1 +192.168.4.59 -1 +192.168.4.60 -1 +192.168.4.61 -1 +192.168.4.62 -1 +192.168.4.63 -1 +192.168.4.64 -1 +192.168.4.65 -1 +192.168.4.66 -1 +192.168.4.67 -1 +192.168.4.68 -1 +192.168.4.69 -1 +192.168.4.70 -1 +192.168.4.71 -1 +192.168.4.72 -1 +192.168.4.73 -1 +192.168.4.74 -1 +192.168.4.75 -1 +192.168.4.76 -1 +192.168.4.77 -1 +192.168.4.78 -1 +192.168.4.79 -1 +192.168.4.80 -1 +192.168.4.81 -1 +192.168.4.82 -1 +192.168.4.83 -1 +192.168.4.84 -1 +192.168.4.85 -1 +192.168.4.86 -1 +192.168.4.87 -1 +192.168.4.88 -1 +192.168.4.89 -1 +192.168.4.90 -1 +192.168.5.1 -1 +192.168.5.2 -1 +192.168.5.3 -1 +192.168.5.4 -1 +192.168.5.5 -1 +192.168.5.6 -1 +192.168.5.7 -1 +192.168.5.8 -1 +192.168.5.9 -1 +192.168.5.10 -1 +192.168.5.11 -1 +192.168.5.12 -1 +192.168.5.13 -1 +192.168.5.14 -1 +192.168.5.15 -1 +192.168.5.16 -1 +192.168.5.17 -1 +192.168.5.18 -1 +192.168.5.19 -1 +192.168.5.20 -1 +192.168.5.21 -1 +192.168.5.22 -1 +192.168.5.23 -1 +192.168.5.24 -1 +192.168.5.25 -1 +192.168.5.26 -1 +192.168.5.27 -1 +192.168.5.28 -1 +192.168.5.29 -1 +192.168.5.30 -1 +192.168.5.31 -1 +192.168.5.32 -1 +192.168.5.33 -1 +192.168.5.34 -1 +192.168.5.35 -1 +192.168.5.36 -1 +192.168.5.37 -1 +192.168.5.38 -1 +192.168.5.39 -1 +192.168.5.40 -1 +192.168.5.41 -1 +192.168.5.42 -1 +192.168.5.43 -1 +192.168.5.44 -1 +192.168.5.45 -1 +192.168.5.46 -1 +192.168.5.47 -1 +192.168.5.48 -1 +192.168.5.49 -1 +192.168.5.50 -1 +192.168.5.51 -1 +192.168.5.52 -1 +192.168.5.53 -1 +192.168.5.54 -1 +192.168.5.55 -1 +192.168.5.56 -1 +192.168.5.57 -1 +192.168.5.58 -1 +192.168.5.59 -1 +192.168.5.60 -1 +192.168.5.61 -1 +192.168.5.62 -1 +192.168.5.63 -1 +192.168.5.64 -1 +192.168.5.65 -1 +192.168.5.66 -1 +192.168.5.67 -1 +192.168.5.68 -1 +192.168.5.69 -1 +192.168.5.70 -1 +192.168.5.71 -1 +192.168.5.72 -1 +192.168.5.73 -1 +192.168.5.74 -1 +192.168.5.75 -1 +192.168.5.76 -1 +192.168.5.77 -1 +192.168.5.78 -1 +192.168.5.79 -1 +192.168.5.80 -1 +192.168.5.81 -1 +192.168.5.82 -1 +192.168.5.83 -1 +192.168.5.84 -1 +192.168.5.85 -1 +192.168.5.86 -1 +192.168.5.87 -1 +192.168.5.88 -1 +192.168.5.89 -1 +192.168.5.90 -1 +192.168.6.1 -1 +192.168.6.2 -1 +192.168.6.3 -1 +192.168.6.4 -1 +192.168.6.5 -1 +192.168.6.6 -1 +192.168.6.7 -1 +192.168.6.8 -1 +192.168.6.9 -1 +192.168.6.10 -1 +192.168.6.11 -1 +192.168.6.12 -1 +192.168.6.13 -1 +192.168.6.14 -1 +192.168.6.15 -1 +192.168.6.16 -1 +192.168.6.17 -1 +192.168.6.18 -1 +192.168.6.19 -1 +192.168.6.20 -1 +192.168.6.21 -1 +192.168.6.22 -1 +192.168.6.23 -1 +192.168.6.24 -1 +192.168.6.25 -1 +192.168.6.26 -1 +192.168.6.27 -1 +192.168.6.28 -1 +192.168.6.29 -1 +192.168.6.30 -1 +192.168.6.31 -1 +192.168.6.32 -1 +192.168.6.33 -1 +192.168.6.34 -1 +192.168.6.35 -1 +192.168.6.36 -1 +192.168.6.37 -1 +192.168.6.38 -1 +192.168.6.39 -1 +192.168.6.40 -1 +192.168.6.41 -1 +192.168.6.42 -1 +192.168.6.43 -1 +192.168.6.44 -1 +192.168.6.45 -1 +192.168.6.46 -1 +192.168.6.47 -1 +192.168.6.48 -1 +192.168.6.49 -1 +192.168.6.50 -1 +192.168.6.51 -1 +192.168.6.52 -1 +192.168.6.53 -1 +192.168.6.54 -1 +192.168.6.55 -1 +192.168.6.56 -1 +192.168.6.57 -1 +192.168.6.58 -1 +192.168.6.59 -1 +192.168.6.60 -1 +192.168.6.61 -1 +192.168.6.62 -1 +192.168.6.63 -1 +192.168.6.64 -1 +192.168.6.65 -1 +192.168.6.66 -1 +192.168.6.67 -1 +192.168.6.68 -1 +192.168.6.69 -1 +192.168.6.70 -1 +192.168.6.71 -1 +192.168.6.72 -1 +192.168.6.73 -1 +192.168.6.74 -1 +192.168.6.75 -1 +192.168.6.76 -1 +192.168.6.77 -1 +192.168.6.78 -1 +192.168.6.79 -1 +192.168.6.80 -1 +192.168.6.81 -1 +192.168.6.82 -1 +192.168.6.83 -1 +192.168.6.84 -1 +192.168.6.85 -1 +192.168.6.86 -1 +192.168.6.87 -1 +192.168.6.88 -1 +192.168.6.89 -1 +192.168.6.90 -1 +192.168.7.1 -1 +192.168.7.2 -1 +192.168.7.3 -1 +192.168.7.4 -1 +192.168.7.5 -1 +192.168.7.6 -1 +192.168.7.7 -1 +192.168.7.8 -1 +192.168.7.9 -1 +192.168.7.10 -1 +192.168.7.11 -1 +192.168.7.12 -1 +192.168.7.13 -1 +192.168.7.14 -1 +192.168.7.15 -1 +192.168.7.16 -1 +192.168.7.17 -1 +192.168.7.18 -1 +192.168.7.19 -1 +192.168.7.20 -1 +192.168.7.21 -1 +192.168.7.22 -1 +192.168.7.23 -1 +192.168.7.24 -1 +192.168.7.25 -1 +192.168.7.26 -1 +192.168.7.27 -1 +192.168.7.28 -1 +192.168.7.29 -1 +192.168.7.30 -1 +192.168.7.31 -1 +192.168.7.32 -1 +192.168.7.33 -1 +192.168.7.34 -1 +192.168.7.35 -1 +192.168.7.36 -1 +192.168.7.37 -1 +192.168.7.38 -1 +192.168.7.39 -1 +192.168.7.40 -1 +192.168.7.41 -1 +192.168.7.42 -1 +192.168.7.43 -1 +192.168.7.44 -1 +192.168.7.45 -1 +192.168.7.46 -1 +192.168.7.47 -1 +192.168.7.48 -1 +192.168.7.49 -1 +192.168.7.50 -1 +192.168.7.51 -1 +192.168.7.52 -1 +192.168.7.53 -1 +192.168.7.54 -1 +192.168.7.55 -1 +192.168.7.56 -1 +192.168.7.57 -1 +192.168.7.58 -1 +192.168.7.59 -1 +192.168.7.60 -1 +192.168.7.61 -1 +192.168.7.62 -1 +192.168.7.63 -1 +192.168.7.64 -1 +192.168.7.65 -1 +192.168.7.66 -1 +192.168.7.67 -1 +192.168.7.68 -1 +192.168.7.69 -1 +192.168.7.70 -1 +192.168.7.71 -1 +192.168.7.72 -1 +192.168.7.73 -1 +192.168.7.74 -1 +192.168.7.75 -1 +192.168.7.76 -1 +192.168.7.77 -1 +192.168.7.78 -1 +192.168.7.79 -1 +192.168.7.80 -1 +192.168.7.81 -1 +192.168.7.82 -1 +192.168.7.83 -1 +192.168.7.84 -1 +192.168.7.85 -1 +192.168.7.86 -1 +192.168.7.87 -1 +192.168.7.88 -1 +192.168.7.89 -1 +192.168.7.90 -1 +192.168.8.1 -1 +192.168.8.2 -1 +192.168.8.3 -1 +192.168.8.4 -1 +192.168.8.5 -1 +192.168.8.6 -1 +192.168.8.7 -1 +192.168.8.8 -1 +192.168.8.9 -1 +192.168.8.10 -1 +192.168.8.11 -1 +192.168.8.12 -1 +192.168.8.13 -1 +192.168.8.14 -1 +192.168.8.15 -1 +192.168.8.16 -1 +192.168.8.17 -1 +192.168.8.18 -1 +192.168.8.19 -1 +192.168.8.20 -1 +192.168.8.21 -1 +192.168.8.22 -1 +192.168.8.23 -1 +192.168.8.24 -1 +192.168.8.25 -1 +192.168.8.26 -1 +192.168.8.27 -1 +192.168.8.28 -1 +192.168.8.29 -1 +192.168.8.30 -1 +192.168.8.31 -1 +192.168.8.32 -1 +192.168.8.33 -1 +192.168.8.34 -1 +192.168.8.35 -1 +192.168.8.36 -1 +192.168.8.37 -1 +192.168.8.38 -1 +192.168.8.39 -1 +192.168.8.40 -1 +192.168.8.41 -1 +192.168.8.42 -1 +192.168.8.43 -1 +192.168.8.44 -1 +192.168.8.45 -1 +192.168.8.46 -1 +192.168.8.47 -1 +192.168.8.48 -1 +192.168.8.49 -1 +192.168.8.50 -1 +192.168.8.51 -1 +192.168.8.52 -1 +192.168.8.53 -1 +192.168.8.54 -1 +192.168.8.55 -1 +192.168.8.56 -1 +192.168.8.57 -1 +192.168.8.58 -1 +192.168.8.59 -1 +192.168.8.60 -1 +192.168.8.61 -1 +192.168.8.62 -1 +192.168.8.63 -1 +192.168.8.64 -1 +192.168.8.65 -1 +192.168.8.66 -1 +192.168.8.67 -1 +192.168.8.68 -1 +192.168.8.69 -1 +192.168.8.70 -1 +192.168.8.71 -1 +192.168.8.72 -1 +192.168.8.73 -1 +192.168.8.74 -1 +192.168.8.75 -1 +192.168.8.76 -1 +192.168.8.77 -1 +192.168.8.78 -1 +192.168.8.79 -1 +192.168.8.80 -1 +192.168.8.81 -1 +192.168.8.82 -1 +192.168.8.83 -1 +192.168.8.84 -1 +192.168.8.85 -1 +192.168.8.86 -1 +192.168.8.87 -1 +192.168.8.88 -1 +192.168.8.89 -1 +192.168.8.90 -1 +192.168.9.1 -1 +192.168.9.2 -1 +192.168.9.3 -1 +192.168.9.4 -1 +192.168.9.5 -1 +192.168.9.6 -1 +192.168.9.7 -1 +192.168.9.8 -1 +192.168.9.9 -1 +192.168.9.10 -1 +192.168.9.11 -1 +192.168.9.12 -1 +192.168.9.13 -1 +192.168.9.14 -1 +192.168.9.15 -1 +192.168.9.16 -1 +192.168.9.17 -1 +192.168.9.18 -1 +192.168.9.19 -1 +192.168.9.20 -1 +192.168.9.21 -1 +192.168.9.22 -1 +192.168.9.23 -1 +192.168.9.24 -1 +192.168.9.25 -1 +192.168.9.26 -1 +192.168.9.27 -1 +192.168.9.28 -1 +192.168.9.29 -1 +192.168.9.30 -1 +192.168.9.31 -1 +192.168.9.32 -1 +192.168.9.33 -1 +192.168.9.34 -1 +192.168.9.35 -1 +192.168.9.36 -1 +192.168.9.37 -1 +192.168.9.38 -1 +192.168.9.39 -1 +192.168.9.40 -1 +192.168.9.41 -1 +192.168.9.42 -1 +192.168.9.43 -1 +192.168.9.44 -1 +192.168.9.45 -1 +192.168.9.46 -1 +192.168.9.47 -1 +192.168.9.48 -1 +192.168.9.49 -1 +192.168.9.50 -1 +192.168.9.51 -1 +192.168.9.52 -1 +192.168.9.53 -1 +192.168.9.54 -1 +192.168.9.55 -1 +192.168.9.56 -1 +192.168.9.57 -1 +192.168.9.58 -1 +192.168.9.59 -1 +192.168.9.60 -1 +192.168.9.61 -1 +192.168.9.62 -1 +192.168.9.63 -1 +192.168.9.64 -1 +192.168.9.65 -1 +192.168.9.66 -1 +192.168.9.67 -1 +192.168.9.68 -1 +192.168.9.69 -1 +192.168.9.70 -1 +192.168.9.71 -1 +192.168.9.72 -1 +192.168.9.73 -1 +192.168.9.74 -1 +192.168.9.75 -1 +192.168.9.76 -1 +192.168.9.77 -1 +192.168.9.78 -1 +192.168.9.79 -1 +192.168.9.80 -1 +192.168.9.81 -1 +192.168.9.82 -1 +192.168.9.83 -1 +192.168.9.84 -1 +192.168.9.85 -1 +192.168.9.86 -1 +192.168.9.87 -1 +192.168.9.88 -1 +192.168.9.89 -1 +192.168.9.90 -1 +192.168.10.1 -1 +192.168.10.2 -1 +192.168.10.3 -1 +192.168.10.4 -1 +192.168.10.5 -1 +192.168.10.6 -1 +192.168.10.7 -1 +192.168.10.8 -1 +192.168.10.9 -1 +192.168.10.10 -1 +192.168.10.11 -1 +192.168.10.12 -1 +192.168.10.13 -1 +192.168.10.14 -1 +192.168.10.15 -1 +192.168.10.16 -1 +192.168.10.17 -1 +192.168.10.18 -1 +192.168.10.19 -1 +192.168.10.20 -1 +192.168.10.21 -1 +192.168.10.22 -1 +192.168.10.23 -1 +192.168.10.24 -1 +192.168.10.25 -1 +192.168.10.26 -1 +192.168.10.27 -1 +192.168.10.28 -1 +192.168.10.29 -1 +192.168.10.30 -1 +192.168.10.31 -1 +192.168.10.32 -1 +192.168.10.33 -1 +192.168.10.34 -1 +192.168.10.35 -1 +192.168.10.36 -1 +192.168.10.37 -1 +192.168.10.38 -1 +192.168.10.39 -1 +192.168.10.40 -1 +192.168.10.41 -1 +192.168.10.42 -1 +192.168.10.43 -1 +192.168.10.44 -1 +192.168.10.45 -1 +192.168.10.46 -1 +192.168.10.47 -1 +192.168.10.48 -1 +192.168.10.49 -1 +192.168.10.50 -1 +192.168.10.51 -1 +192.168.10.52 -1 +192.168.10.53 -1 +192.168.10.54 -1 +192.168.10.55 -1 +192.168.10.56 -1 +192.168.10.57 -1 +192.168.10.58 -1 +192.168.10.59 -1 +192.168.10.60 -1 +192.168.10.61 -1 +192.168.10.62 -1 +192.168.10.63 -1 +192.168.10.64 -1 +192.168.10.65 -1 +192.168.10.66 -1 +192.168.10.67 -1 +192.168.10.68 -1 +192.168.10.69 -1 +192.168.10.70 -1 +192.168.10.71 -1 +192.168.10.72 -1 +192.168.10.73 -1 +192.168.10.74 -1 +192.168.10.75 -1 +192.168.10.76 -1 +192.168.10.77 -1 +192.168.10.78 -1 +192.168.10.79 -1 +192.168.10.80 -1 +192.168.10.81 -1 +192.168.10.82 -1 +192.168.10.83 -1 +192.168.10.84 -1 +192.168.10.85 -1 +192.168.10.86 -1 +192.168.10.87 -1 +192.168.10.88 -1 +192.168.10.89 -1 +192.168.10.90 -1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.031.sh b/ctdb/tests/UNIT/takeover/lcp2.031.sh new file mode 100755 index 0000000..3a2cb79 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.031.sh @@ -0,0 +1,143 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "12+4 IPs, 4 nodes, 3 -> 4 healthy" + +export CTDB_TEST_LOGLEVEL=DEBUG + +required_result <<EOF +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES (UNASSIGNED) +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [0] +${TEST_DATE_STAMP} 1 [181370] +${TEST_DATE_STAMP} 2 [128630] +${TEST_DATE_STAMP} 3 [128881] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [181370] +${TEST_DATE_STAMP} 1 [-64566] -> 130.216.30.178 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-64566] -> 130.216.30.176 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-64315] -> 130.216.30.175 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-64315] -> 130.216.30.171 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-52489] -> 10.19.99.253 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-52489] -> 10.19.99.250 -> 0 [+0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-64566] -> 130.216.30.178 -> 0 [+0] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [0] +${TEST_DATE_STAMP} 1 [116804] +${TEST_DATE_STAMP} 2 [128630] +${TEST_DATE_STAMP} 3 [128881] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 3 [128881] +${TEST_DATE_STAMP} 3 [-55099] -> 130.216.30.180 -> 0 [+15625] +${TEST_DATE_STAMP} 3 [-55099] -> 130.216.30.177 -> 0 [+15876] +${TEST_DATE_STAMP} 3 [-55350] -> 130.216.30.174 -> 0 [+15129] +${TEST_DATE_STAMP} 3 [-55350] -> 130.216.30.173 -> 0 [+15129] +${TEST_DATE_STAMP} 3 [-36864] -> 10.19.99.252 -> 0 [+9216] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}3 [-55350] -> 130.216.30.174 -> 0 [+15129] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [15129] +${TEST_DATE_STAMP} 1 [116804] +${TEST_DATE_STAMP} 2 [128630] +${TEST_DATE_STAMP} 3 [73531] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 2 [128630] +${TEST_DATE_STAMP} 2 [-55099] -> 130.216.30.181 -> 0 [+30754] +${TEST_DATE_STAMP} 2 [-55099] -> 130.216.30.179 -> 0 [+31258] +${TEST_DATE_STAMP} 2 [-55099] -> 130.216.30.172 -> 0 [+31005] +${TEST_DATE_STAMP} 2 [-55099] -> 130.216.30.170 -> 0 [+30754] +${TEST_DATE_STAMP} 2 [-36864] -> 10.19.99.251 -> 0 [+18432] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}2 [-55099] -> 130.216.30.181 -> 0 [+30754] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [45883] +${TEST_DATE_STAMP} 1 [116804] +${TEST_DATE_STAMP} 2 [73531] +${TEST_DATE_STAMP} 3 [73531] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [116804] +${TEST_DATE_STAMP} 1 [-48690] -> 130.216.30.176 -> 0 [+46630] +${TEST_DATE_STAMP} 1 [-49186] -> 130.216.30.175 -> 0 [+46387] +${TEST_DATE_STAMP} 1 [-49186] -> 130.216.30.171 -> 0 [+45883] +${TEST_DATE_STAMP} 1 [-43273] -> 10.19.99.253 -> 0 [+27648] +${TEST_DATE_STAMP} 1 [-43273] -> 10.19.99.250 -> 0 [+27648] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-43273] -> 10.19.99.253 -> 0 [+27648] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [73531] +${TEST_DATE_STAMP} 1 [73531] +${TEST_DATE_STAMP} 2 [73531] +${TEST_DATE_STAMP} 3 [73531] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 0 [73531] +${TEST_DATE_STAMP} 0 [-39970] -> 130.216.30.181 -> 0 [+39970] +${TEST_DATE_STAMP} 0 [-39970] -> 130.216.30.178 -> 0 [+39970] +${TEST_DATE_STAMP} 0 [-39474] -> 130.216.30.174 -> 0 [+39474] +${TEST_DATE_STAMP} 0 [-27648] -> 10.19.99.253 -> 0 [+27648] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [73531] +${TEST_DATE_STAMP} 1 [-39474] -> 130.216.30.176 -> 0 [+55846] +${TEST_DATE_STAMP} 1 [-39970] -> 130.216.30.175 -> 0 [+55603] +${TEST_DATE_STAMP} 1 [-39970] -> 130.216.30.171 -> 0 [+55099] +${TEST_DATE_STAMP} 1 [-27648] -> 10.19.99.250 -> 0 [+43273] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 2 [73531] +${TEST_DATE_STAMP} 2 [-39474] -> 130.216.30.179 -> 0 [+56099] +${TEST_DATE_STAMP} 2 [-39970] -> 130.216.30.172 -> 0 [+55350] +${TEST_DATE_STAMP} 2 [-39970] -> 130.216.30.170 -> 0 [+55099] +${TEST_DATE_STAMP} 2 [-27648] -> 10.19.99.251 -> 0 [+43273] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 3 [73531] +${TEST_DATE_STAMP} 3 [-39970] -> 130.216.30.180 -> 0 [+56099] +${TEST_DATE_STAMP} 3 [-39970] -> 130.216.30.177 -> 0 [+55846] +${TEST_DATE_STAMP} 3 [-39474] -> 130.216.30.173 -> 0 [+55350] +${TEST_DATE_STAMP} 3 [-27648] -> 10.19.99.252 -> 0 [+43777] +${TEST_DATE_STAMP} ---------------------------------------- +130.216.30.181 0 +130.216.30.180 3 +130.216.30.179 2 +130.216.30.178 0 +130.216.30.177 3 +130.216.30.176 1 +130.216.30.175 1 +130.216.30.174 0 +130.216.30.173 3 +130.216.30.172 2 +130.216.30.171 1 +130.216.30.170 2 +10.19.99.253 0 +10.19.99.252 3 +10.19.99.251 2 +10.19.99.250 1 +EOF + +simple_test 0,0,0,0 <<EOF +10.19.99.250 1 +10.19.99.251 2 +10.19.99.252 3 +10.19.99.253 1 +130.216.30.170 2 +130.216.30.171 1 +130.216.30.172 2 +130.216.30.173 3 +130.216.30.174 3 +130.216.30.175 1 +130.216.30.176 1 +130.216.30.177 3 +130.216.30.178 1 +130.216.30.179 2 +130.216.30.180 3 +130.216.30.181 2 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.032.sh b/ctdb/tests/UNIT/takeover/lcp2.032.sh new file mode 100755 index 0000000..fa032f4 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.032.sh @@ -0,0 +1,450 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "12+4 IPs, 4 nodes, multiple transitions" + +export CTDB_TEST_LOGLEVEL=ERR + +set -e + +echo "Node 3 stopped -> continue node 3, all healthy" + +required_result <<EOF +130.216.30.181 2 +130.216.30.180 3 +130.216.30.179 2 +130.216.30.178 1 +130.216.30.177 3 +130.216.30.176 0 +130.216.30.175 1 +130.216.30.174 0 +130.216.30.173 3 +130.216.30.172 2 +130.216.30.171 1 +130.216.30.170 0 +10.19.99.253 1 +10.19.99.252 3 +10.19.99.251 2 +10.19.99.250 0 +EOF + +simple_test 0,0,0,0 <<EOF +10.19.99.250 0 +10.19.99.251 2 +10.19.99.252 0 +10.19.99.253 1 +130.216.30.170 0 +130.216.30.171 1 +130.216.30.172 2 +130.216.30.173 2 +130.216.30.174 0 +130.216.30.175 1 +130.216.30.176 0 +130.216.30.177 0 +130.216.30.178 1 +130.216.30.179 2 +130.216.30.180 1 +130.216.30.181 2 +EOF + +echo "All healthy -> stop node 0" + +required_result <<EOF +130.216.30.181 2 +130.216.30.180 3 +130.216.30.179 2 +130.216.30.178 1 +130.216.30.177 3 +130.216.30.176 1 +130.216.30.175 1 +130.216.30.174 3 +130.216.30.173 3 +130.216.30.172 2 +130.216.30.171 1 +130.216.30.170 2 +10.19.99.253 1 +10.19.99.252 3 +10.19.99.251 2 +10.19.99.250 1 +EOF + +simple_test 0x20,0,0,0 <<EOF +$_out +EOF + +echo "Continue node 0, all healthy" + +required_result <<EOF +130.216.30.181 0 +130.216.30.180 3 +130.216.30.179 2 +130.216.30.178 0 +130.216.30.177 3 +130.216.30.176 1 +130.216.30.175 1 +130.216.30.174 0 +130.216.30.173 3 +130.216.30.172 2 +130.216.30.171 1 +130.216.30.170 2 +10.19.99.253 0 +10.19.99.252 3 +10.19.99.251 2 +10.19.99.250 1 +EOF + +simple_test 0,0,0,0 <<EOF +$_out +EOF + +echo "All healthy -> stop node 1" + +required_result <<EOF +130.216.30.181 0 +130.216.30.180 3 +130.216.30.179 2 +130.216.30.178 0 +130.216.30.177 3 +130.216.30.176 2 +130.216.30.175 0 +130.216.30.174 0 +130.216.30.173 3 +130.216.30.172 2 +130.216.30.171 3 +130.216.30.170 2 +10.19.99.253 0 +10.19.99.252 3 +10.19.99.251 2 +10.19.99.250 0 +EOF + +simple_test 0,0x20,0,0 <<EOF +$_out +EOF + +echo "Continue node 1, all healthy" + +required_result <<EOF +130.216.30.181 0 +130.216.30.180 1 +130.216.30.179 1 +130.216.30.178 0 +130.216.30.177 3 +130.216.30.176 2 +130.216.30.175 1 +130.216.30.174 0 +130.216.30.173 3 +130.216.30.172 2 +130.216.30.171 3 +130.216.30.170 2 +10.19.99.253 1 +10.19.99.252 3 +10.19.99.251 2 +10.19.99.250 0 +EOF + +simple_test 0,0,0,0 <<EOF +$_out +EOF + +echo "All healthy -> Stop node 2" + +required_result <<EOF +130.216.30.181 0 +130.216.30.180 1 +130.216.30.179 1 +130.216.30.178 0 +130.216.30.177 3 +130.216.30.176 3 +130.216.30.175 1 +130.216.30.174 0 +130.216.30.173 3 +130.216.30.172 1 +130.216.30.171 3 +130.216.30.170 0 +10.19.99.253 1 +10.19.99.252 3 +10.19.99.251 1 +10.19.99.250 0 +EOF + +simple_test 0,0,0x20,0 <<EOF +$_out +EOF + +echo "Continue node 2, all healthy" + +required_result <<EOF +130.216.30.181 2 +130.216.30.180 1 +130.216.30.179 1 +130.216.30.178 0 +130.216.30.177 2 +130.216.30.176 3 +130.216.30.175 2 +130.216.30.174 0 +130.216.30.173 3 +130.216.30.172 1 +130.216.30.171 3 +130.216.30.170 0 +10.19.99.253 2 +10.19.99.252 3 +10.19.99.251 1 +10.19.99.250 0 +EOF + +simple_test 0,0,0,0 <<EOF +$_out +EOF + +echo "All healthy -> stop node 3" + +required_result <<EOF +130.216.30.181 2 +130.216.30.180 1 +130.216.30.179 1 +130.216.30.178 0 +130.216.30.177 2 +130.216.30.176 0 +130.216.30.175 2 +130.216.30.174 0 +130.216.30.173 2 +130.216.30.172 1 +130.216.30.171 1 +130.216.30.170 0 +10.19.99.253 2 +10.19.99.252 0 +10.19.99.251 1 +10.19.99.250 0 +EOF + +simple_test 0,0,0,0x20 <<EOF +$_out +EOF + +echo "Continue node 3, all healthy" + +required_result <<EOF +130.216.30.181 2 +130.216.30.180 3 +130.216.30.179 1 +130.216.30.178 3 +130.216.30.177 2 +130.216.30.176 0 +130.216.30.175 3 +130.216.30.174 0 +130.216.30.173 2 +130.216.30.172 1 +130.216.30.171 1 +130.216.30.170 0 +10.19.99.253 2 +10.19.99.252 3 +10.19.99.251 1 +10.19.99.250 0 +EOF + +simple_test 0,0,0,0 <<EOF +$_out +EOF + +echo "All healthy -> node 0 stopped" + +required_result <<EOF +130.216.30.181 2 +130.216.30.180 3 +130.216.30.179 1 +130.216.30.178 3 +130.216.30.177 2 +130.216.30.176 1 +130.216.30.175 3 +130.216.30.174 2 +130.216.30.173 2 +130.216.30.172 1 +130.216.30.171 1 +130.216.30.170 3 +10.19.99.253 2 +10.19.99.252 3 +10.19.99.251 1 +10.19.99.250 2 +EOF + +simple_test 0x20,0,0,0 <<EOF +$_out +EOF + +echo "Continue node 0, all healthy" + +required_result <<EOF +130.216.30.181 2 +130.216.30.180 0 +130.216.30.179 0 +130.216.30.178 3 +130.216.30.177 2 +130.216.30.176 1 +130.216.30.175 3 +130.216.30.174 0 +130.216.30.173 2 +130.216.30.172 1 +130.216.30.171 1 +130.216.30.170 3 +10.19.99.253 0 +10.19.99.252 3 +10.19.99.251 1 +10.19.99.250 2 +EOF + +simple_test 0,0,0,0 <<EOF +$_out +EOF + +echo "All healthy -> node 1 stopped" + +required_result <<EOF +130.216.30.181 2 +130.216.30.180 0 +130.216.30.179 0 +130.216.30.178 3 +130.216.30.177 2 +130.216.30.176 3 +130.216.30.175 3 +130.216.30.174 0 +130.216.30.173 2 +130.216.30.172 0 +130.216.30.171 2 +130.216.30.170 3 +10.19.99.253 0 +10.19.99.252 3 +10.19.99.251 0 +10.19.99.250 2 +EOF + +simple_test 0,0x20,0,0 <<EOF +$_out +EOF + +echo "Continue node 1, all healthy" + +required_result <<EOF +130.216.30.181 1 +130.216.30.180 0 +130.216.30.179 0 +130.216.30.178 1 +130.216.30.177 2 +130.216.30.176 3 +130.216.30.175 3 +130.216.30.174 1 +130.216.30.173 2 +130.216.30.172 0 +130.216.30.171 2 +130.216.30.170 3 +10.19.99.253 1 +10.19.99.252 3 +10.19.99.251 0 +10.19.99.250 2 +EOF + +simple_test 0,0,0,0 <<EOF +$_out +EOF + +echo "All healthy -> node 2 stopped" + +required_result <<EOF +130.216.30.181 1 +130.216.30.180 0 +130.216.30.179 0 +130.216.30.178 1 +130.216.30.177 3 +130.216.30.176 3 +130.216.30.175 3 +130.216.30.174 1 +130.216.30.173 1 +130.216.30.172 0 +130.216.30.171 0 +130.216.30.170 3 +10.19.99.253 1 +10.19.99.252 3 +10.19.99.251 0 +10.19.99.250 1 +EOF + +simple_test 0,0,0x20,0 <<EOF +$_out +EOF + +echo "Continue node 2, all healthy" + +required_result <<EOF +130.216.30.181 1 +130.216.30.180 2 +130.216.30.179 0 +130.216.30.178 1 +130.216.30.177 2 +130.216.30.176 3 +130.216.30.175 3 +130.216.30.174 2 +130.216.30.173 1 +130.216.30.172 0 +130.216.30.171 0 +130.216.30.170 3 +10.19.99.253 2 +10.19.99.252 3 +10.19.99.251 0 +10.19.99.250 1 +EOF + +simple_test 0,0,0,0 <<EOF +$_out +EOF + +echo "All healthy -> node 3 stopped" + +required_result <<EOF +130.216.30.181 1 +130.216.30.180 2 +130.216.30.179 0 +130.216.30.178 1 +130.216.30.177 2 +130.216.30.176 0 +130.216.30.175 2 +130.216.30.174 2 +130.216.30.173 1 +130.216.30.172 0 +130.216.30.171 0 +130.216.30.170 1 +10.19.99.253 2 +10.19.99.252 0 +10.19.99.251 0 +10.19.99.250 1 +EOF + +simple_test 0,0,0,0x20 <<EOF +$_out +EOF + +echo "Continue node 3, all healthy" + +required_result <<EOF +130.216.30.181 3 +130.216.30.180 2 +130.216.30.179 3 +130.216.30.178 1 +130.216.30.177 2 +130.216.30.176 0 +130.216.30.175 3 +130.216.30.174 2 +130.216.30.173 1 +130.216.30.172 0 +130.216.30.171 0 +130.216.30.170 1 +10.19.99.253 2 +10.19.99.252 3 +10.19.99.251 0 +10.19.99.250 1 +EOF + +simple_test 0,0,0,0 <<EOF +$_out +EOF + diff --git a/ctdb/tests/UNIT/takeover/lcp2.033.sh b/ctdb/tests/UNIT/takeover/lcp2.033.sh new file mode 100755 index 0000000..206699a --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.033.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "12+4 IPs, 4 nodes, 2 -> 3 -> 4 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +set -e + +echo "Nodes 2, 3 disconnected -> node 2 attaches" + +required_result <<EOF +130.216.30.181 2 +130.216.30.180 0 +130.216.30.179 2 +130.216.30.178 1 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 2 +130.216.30.173 1 +130.216.30.172 2 +130.216.30.171 1 +130.216.30.170 0 +10.19.99.253 2 +10.19.99.252 0 +10.19.99.251 1 +10.19.99.250 0 +EOF + +simple_test 0,0,0,1 <<EOF +10.19.99.253 1 +10.19.99.252 0 +10.19.99.251 1 +10.19.99.250 0 +130.216.30.181 1 +130.216.30.180 0 +130.216.30.179 0 +130.216.30.178 1 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 1 +130.216.30.173 1 +130.216.30.172 0 +130.216.30.171 1 +130.216.30.170 0 +EOF + +echo "Node 3 attaches" + +required_result <<EOF +130.216.30.181 2 +130.216.30.180 3 +130.216.30.179 3 +130.216.30.178 1 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 2 +130.216.30.173 3 +130.216.30.172 2 +130.216.30.171 1 +130.216.30.170 0 +10.19.99.253 2 +10.19.99.252 3 +10.19.99.251 1 +10.19.99.250 0 +EOF + +simple_test 0,0,0,0 <<EOF +$_out +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.034.sh b/ctdb/tests/UNIT/takeover/lcp2.034.sh new file mode 100755 index 0000000..6cea2d5 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.034.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 1 without IP addresses" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.140.4 0 +192.168.140.3 1 +192.168.140.2 0 +192.168.140.1 1 +EOF + +simple_test 0,0,0 <<EOF +192.168.140.1 -1 0,1 +192.168.140.2 -1 0,1 +192.168.140.3 -1 0,1 +192.168.140.4 -1 0,1 +EOF diff --git a/ctdb/tests/UNIT/takeover/lcp2.035.sh b/ctdb/tests/UNIT/takeover/lcp2.035.sh new file mode 100755 index 0000000..2bb58f5 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/lcp2.035.sh @@ -0,0 +1,1813 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "900 IPs, all 5 nodes healthy, all assigned, no-op" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <<EOF +192.168.10.90 0 +192.168.10.89 1 +192.168.10.88 2 +192.168.10.87 3 +192.168.10.86 4 +192.168.10.85 0 +192.168.10.84 1 +192.168.10.83 2 +192.168.10.82 3 +192.168.10.81 4 +192.168.10.80 0 +192.168.10.79 0 +192.168.10.78 1 +192.168.10.77 2 +192.168.10.76 3 +192.168.10.75 4 +192.168.10.74 1 +192.168.10.73 2 +192.168.10.72 3 +192.168.10.71 3 +192.168.10.70 4 +192.168.10.69 0 +192.168.10.68 1 +192.168.10.67 2 +192.168.10.66 4 +192.168.10.65 0 +192.168.10.64 1 +192.168.10.63 0 +192.168.10.62 1 +192.168.10.61 2 +192.168.10.60 3 +192.168.10.59 4 +192.168.10.58 2 +192.168.10.57 3 +192.168.10.56 0 +192.168.10.55 0 +192.168.10.54 1 +192.168.10.53 2 +192.168.10.52 3 +192.168.10.51 4 +192.168.10.50 1 +192.168.10.49 4 +192.168.10.48 2 +192.168.10.47 0 +192.168.10.46 1 +192.168.10.45 2 +192.168.10.44 3 +192.168.10.43 4 +192.168.10.42 2 +192.168.10.41 3 +192.168.10.40 1 +192.168.10.39 3 +192.168.10.38 4 +192.168.10.37 0 +192.168.10.36 1 +192.168.10.35 2 +192.168.10.34 4 +192.168.10.33 0 +192.168.10.32 3 +192.168.10.31 0 +192.168.10.30 1 +192.168.10.29 2 +192.168.10.28 3 +192.168.10.27 4 +192.168.10.26 3 +192.168.10.25 2 +192.168.10.24 0 +192.168.10.23 3 +192.168.10.22 4 +192.168.10.21 0 +192.168.10.20 1 +192.168.10.19 2 +192.168.10.18 4 +192.168.10.17 1 +192.168.10.16 4 +192.168.10.15 0 +192.168.10.14 1 +192.168.10.13 2 +192.168.10.12 3 +192.168.10.11 4 +192.168.10.10 2 +192.168.10.9 3 +192.168.10.8 4 +192.168.10.7 0 +192.168.10.6 1 +192.168.10.5 2 +192.168.10.4 3 +192.168.10.3 4 +192.168.10.2 0 +192.168.10.1 1 +192.168.9.90 0 +192.168.9.89 1 +192.168.9.88 2 +192.168.9.87 3 +192.168.9.86 4 +192.168.9.85 0 +192.168.9.84 1 +192.168.9.83 2 +192.168.9.82 3 +192.168.9.81 4 +192.168.9.80 0 +192.168.9.79 0 +192.168.9.78 1 +192.168.9.77 2 +192.168.9.76 3 +192.168.9.75 4 +192.168.9.74 1 +192.168.9.73 2 +192.168.9.72 3 +192.168.9.71 3 +192.168.9.70 4 +192.168.9.69 0 +192.168.9.68 1 +192.168.9.67 2 +192.168.9.66 4 +192.168.9.65 0 +192.168.9.64 1 +192.168.9.63 0 +192.168.9.62 1 +192.168.9.61 2 +192.168.9.60 3 +192.168.9.59 4 +192.168.9.58 2 +192.168.9.57 3 +192.168.9.56 4 +192.168.9.55 0 +192.168.9.54 1 +192.168.9.53 2 +192.168.9.52 3 +192.168.9.51 4 +192.168.9.50 0 +192.168.9.49 1 +192.168.9.48 2 +192.168.9.47 0 +192.168.9.46 1 +192.168.9.45 2 +192.168.9.44 3 +192.168.9.43 4 +192.168.9.42 2 +192.168.9.41 4 +192.168.9.40 3 +192.168.9.39 0 +192.168.9.38 1 +192.168.9.37 2 +192.168.9.36 3 +192.168.9.35 4 +192.168.9.34 0 +192.168.9.33 1 +192.168.9.32 4 +192.168.9.31 0 +192.168.9.30 1 +192.168.9.29 2 +192.168.9.28 3 +192.168.9.27 4 +192.168.9.26 2 +192.168.9.25 3 +192.168.9.24 0 +192.168.9.23 3 +192.168.9.22 4 +192.168.9.21 0 +192.168.9.20 1 +192.168.9.19 2 +192.168.9.18 4 +192.168.9.17 1 +192.168.9.16 3 +192.168.9.15 0 +192.168.9.14 1 +192.168.9.13 2 +192.168.9.12 3 +192.168.9.11 4 +192.168.9.10 2 +192.168.9.9 4 +192.168.9.8 3 +192.168.9.7 0 +192.168.9.6 1 +192.168.9.5 2 +192.168.9.4 3 +192.168.9.3 4 +192.168.9.2 0 +192.168.9.1 1 +192.168.8.90 0 +192.168.8.89 1 +192.168.8.88 2 +192.168.8.87 3 +192.168.8.86 4 +192.168.8.85 0 +192.168.8.84 1 +192.168.8.83 2 +192.168.8.82 3 +192.168.8.81 4 +192.168.8.80 0 +192.168.8.79 0 +192.168.8.78 1 +192.168.8.77 2 +192.168.8.76 3 +192.168.8.75 4 +192.168.8.74 1 +192.168.8.73 2 +192.168.8.72 3 +192.168.8.71 3 +192.168.8.70 4 +192.168.8.69 0 +192.168.8.68 1 +192.168.8.67 2 +192.168.8.66 4 +192.168.8.65 3 +192.168.8.64 0 +192.168.8.63 0 +192.168.8.62 1 +192.168.8.61 2 +192.168.8.60 3 +192.168.8.59 4 +192.168.8.58 1 +192.168.8.57 2 +192.168.8.56 3 +192.168.8.55 0 +192.168.8.54 1 +192.168.8.53 2 +192.168.8.52 3 +192.168.8.51 4 +192.168.8.50 0 +192.168.8.49 4 +192.168.8.48 1 +192.168.8.47 0 +192.168.8.46 1 +192.168.8.45 2 +192.168.8.44 3 +192.168.8.43 4 +192.168.8.42 2 +192.168.8.41 1 +192.168.8.40 4 +192.168.8.39 0 +192.168.8.38 1 +192.168.8.37 2 +192.168.8.36 3 +192.168.8.35 4 +192.168.8.34 3 +192.168.8.33 0 +192.168.8.32 2 +192.168.8.31 0 +192.168.8.30 1 +192.168.8.29 2 +192.168.8.28 3 +192.168.8.27 4 +192.168.8.26 2 +192.168.8.25 1 +192.168.8.24 3 +192.168.8.23 3 +192.168.8.22 4 +192.168.8.21 0 +192.168.8.20 1 +192.168.8.19 2 +192.168.8.18 4 +192.168.8.17 0 +192.168.8.16 4 +192.168.8.15 0 +192.168.8.14 1 +192.168.8.13 2 +192.168.8.12 3 +192.168.8.11 4 +192.168.8.10 1 +192.168.8.9 2 +192.168.8.8 4 +192.168.8.7 0 +192.168.8.6 1 +192.168.8.5 2 +192.168.8.4 3 +192.168.8.3 4 +192.168.8.2 3 +192.168.8.1 0 +192.168.7.90 0 +192.168.7.89 1 +192.168.7.88 2 +192.168.7.87 3 +192.168.7.86 4 +192.168.7.85 0 +192.168.7.84 1 +192.168.7.83 2 +192.168.7.82 3 +192.168.7.81 4 +192.168.7.80 1 +192.168.7.79 0 +192.168.7.78 1 +192.168.7.77 2 +192.168.7.76 3 +192.168.7.75 4 +192.168.7.74 2 +192.168.7.73 3 +192.168.7.72 0 +192.168.7.71 3 +192.168.7.70 4 +192.168.7.69 0 +192.168.7.68 1 +192.168.7.67 2 +192.168.7.66 4 +192.168.7.65 1 +192.168.7.64 3 +192.168.7.63 0 +192.168.7.62 1 +192.168.7.61 2 +192.168.7.60 3 +192.168.7.59 4 +192.168.7.58 2 +192.168.7.57 0 +192.168.7.56 1 +192.168.7.55 0 +192.168.7.54 1 +192.168.7.53 2 +192.168.7.52 3 +192.168.7.51 4 +192.168.7.50 3 +192.168.7.49 4 +192.168.7.48 2 +192.168.7.47 0 +192.168.7.46 1 +192.168.7.45 2 +192.168.7.44 3 +192.168.7.43 4 +192.168.7.42 2 +192.168.7.41 0 +192.168.7.40 1 +192.168.7.39 4 +192.168.7.38 0 +192.168.7.37 1 +192.168.7.36 2 +192.168.7.35 3 +192.168.7.34 4 +192.168.7.33 3 +192.168.7.32 0 +192.168.7.31 0 +192.168.7.30 1 +192.168.7.29 2 +192.168.7.28 3 +192.168.7.27 4 +192.168.7.26 2 +192.168.7.25 0 +192.168.7.24 1 +192.168.7.23 3 +192.168.7.22 4 +192.168.7.21 0 +192.168.7.20 1 +192.168.7.19 2 +192.168.7.18 4 +192.168.7.17 3 +192.168.7.16 4 +192.168.7.15 0 +192.168.7.14 1 +192.168.7.13 2 +192.168.7.12 3 +192.168.7.11 4 +192.168.7.10 3 +192.168.7.9 2 +192.168.7.8 0 +192.168.7.7 2 +192.168.7.6 4 +192.168.7.5 0 +192.168.7.4 1 +192.168.7.3 3 +192.168.7.2 4 +192.168.7.1 1 +192.168.6.90 0 +192.168.6.89 1 +192.168.6.88 2 +192.168.6.87 3 +192.168.6.86 4 +192.168.6.85 0 +192.168.6.84 1 +192.168.6.83 2 +192.168.6.82 4 +192.168.6.81 3 +192.168.6.80 0 +192.168.6.79 0 +192.168.6.78 1 +192.168.6.77 2 +192.168.6.76 3 +192.168.6.75 4 +192.168.6.74 2 +192.168.6.73 3 +192.168.6.72 1 +192.168.6.71 3 +192.168.6.70 4 +192.168.6.69 0 +192.168.6.68 1 +192.168.6.67 2 +192.168.6.66 4 +192.168.6.65 0 +192.168.6.64 1 +192.168.6.63 0 +192.168.6.62 1 +192.168.6.61 2 +192.168.6.60 3 +192.168.6.59 4 +192.168.6.58 2 +192.168.6.57 3 +192.168.6.56 0 +192.168.6.55 3 +192.168.6.54 4 +192.168.6.53 1 +192.168.6.52 2 +192.168.6.51 0 +192.168.6.50 4 +192.168.6.49 1 +192.168.6.48 2 +192.168.6.47 0 +192.168.6.46 1 +192.168.6.45 2 +192.168.6.44 3 +192.168.6.43 4 +192.168.6.42 2 +192.168.6.41 4 +192.168.6.40 3 +192.168.6.39 0 +192.168.6.38 1 +192.168.6.37 2 +192.168.6.36 3 +192.168.6.35 4 +192.168.6.34 0 +192.168.6.33 1 +192.168.6.32 4 +192.168.6.31 0 +192.168.6.30 1 +192.168.6.29 2 +192.168.6.28 3 +192.168.6.27 4 +192.168.6.26 2 +192.168.6.25 3 +192.168.6.24 0 +192.168.6.23 3 +192.168.6.22 4 +192.168.6.21 0 +192.168.6.20 1 +192.168.6.19 2 +192.168.6.18 4 +192.168.6.17 1 +192.168.6.16 3 +192.168.6.15 0 +192.168.6.14 1 +192.168.6.13 2 +192.168.6.12 3 +192.168.6.11 4 +192.168.6.10 2 +192.168.6.9 3 +192.168.6.8 4 +192.168.6.7 0 +192.168.6.6 1 +192.168.6.5 2 +192.168.6.4 3 +192.168.6.3 4 +192.168.6.2 0 +192.168.6.1 1 +192.168.5.90 0 +192.168.5.89 1 +192.168.5.88 2 +192.168.5.87 3 +192.168.5.86 4 +192.168.5.85 0 +192.168.5.84 1 +192.168.5.83 2 +192.168.5.82 4 +192.168.5.81 3 +192.168.5.80 0 +192.168.5.79 0 +192.168.5.78 1 +192.168.5.77 2 +192.168.5.76 3 +192.168.5.75 4 +192.168.5.74 2 +192.168.5.73 3 +192.168.5.72 1 +192.168.5.71 3 +192.168.5.70 4 +192.168.5.69 2 +192.168.5.68 0 +192.168.5.67 1 +192.168.5.66 4 +192.168.5.65 2 +192.168.5.64 0 +192.168.5.63 0 +192.168.5.62 1 +192.168.5.61 2 +192.168.5.60 3 +192.168.5.59 4 +192.168.5.58 1 +192.168.5.57 3 +192.168.5.56 2 +192.168.5.55 0 +192.168.5.54 1 +192.168.5.53 2 +192.168.5.52 3 +192.168.5.51 4 +192.168.5.50 0 +192.168.5.49 4 +192.168.5.48 1 +192.168.5.47 0 +192.168.5.46 1 +192.168.5.45 2 +192.168.5.44 3 +192.168.5.43 4 +192.168.5.42 1 +192.168.5.41 3 +192.168.5.40 2 +192.168.5.39 2 +192.168.5.38 3 +192.168.5.37 4 +192.168.5.36 0 +192.168.5.35 1 +192.168.5.34 4 +192.168.5.33 0 +192.168.5.32 4 +192.168.5.31 0 +192.168.5.30 1 +192.168.5.29 2 +192.168.5.28 3 +192.168.5.27 4 +192.168.5.26 1 +192.168.5.25 3 +192.168.5.24 2 +192.168.5.23 3 +192.168.5.22 4 +192.168.5.21 2 +192.168.5.20 0 +192.168.5.19 1 +192.168.5.18 4 +192.168.5.17 0 +192.168.5.16 3 +192.168.5.15 0 +192.168.5.14 1 +192.168.5.13 2 +192.168.5.12 3 +192.168.5.11 4 +192.168.5.10 1 +192.168.5.9 4 +192.168.5.8 3 +192.168.5.7 0 +192.168.5.6 1 +192.168.5.5 2 +192.168.5.4 3 +192.168.5.3 4 +192.168.5.2 2 +192.168.5.1 0 +192.168.4.90 0 +192.168.4.89 1 +192.168.4.88 2 +192.168.4.87 3 +192.168.4.86 4 +192.168.4.85 0 +192.168.4.84 1 +192.168.4.83 2 +192.168.4.82 3 +192.168.4.81 4 +192.168.4.80 0 +192.168.4.79 0 +192.168.4.78 1 +192.168.4.77 2 +192.168.4.76 3 +192.168.4.75 4 +192.168.4.74 1 +192.168.4.73 2 +192.168.4.72 3 +192.168.4.71 3 +192.168.4.70 4 +192.168.4.69 0 +192.168.4.68 1 +192.168.4.67 2 +192.168.4.66 4 +192.168.4.65 1 +192.168.4.64 3 +192.168.4.63 0 +192.168.4.62 1 +192.168.4.61 2 +192.168.4.60 3 +192.168.4.59 4 +192.168.4.58 0 +192.168.4.57 2 +192.168.4.56 1 +192.168.4.55 0 +192.168.4.54 1 +192.168.4.53 2 +192.168.4.52 3 +192.168.4.51 4 +192.168.4.50 3 +192.168.4.49 4 +192.168.4.48 0 +192.168.4.47 0 +192.168.4.46 1 +192.168.4.45 2 +192.168.4.44 3 +192.168.4.43 4 +192.168.4.42 2 +192.168.4.41 0 +192.168.4.40 1 +192.168.4.39 4 +192.168.4.38 0 +192.168.4.37 1 +192.168.4.36 2 +192.168.4.35 3 +192.168.4.34 4 +192.168.4.33 3 +192.168.4.32 2 +192.168.4.31 0 +192.168.4.30 1 +192.168.4.29 2 +192.168.4.28 3 +192.168.4.27 4 +192.168.4.26 0 +192.168.4.25 2 +192.168.4.24 1 +192.168.4.23 3 +192.168.4.22 4 +192.168.4.21 0 +192.168.4.20 1 +192.168.4.19 2 +192.168.4.18 4 +192.168.4.17 3 +192.168.4.16 1 +192.168.4.15 0 +192.168.4.14 1 +192.168.4.13 2 +192.168.4.12 3 +192.168.4.11 4 +192.168.4.10 3 +192.168.4.9 0 +192.168.4.8 2 +192.168.4.7 2 +192.168.4.6 3 +192.168.4.5 4 +192.168.4.4 0 +192.168.4.3 1 +192.168.4.2 4 +192.168.4.1 4 +192.168.3.90 0 +192.168.3.89 1 +192.168.3.88 2 +192.168.3.87 3 +192.168.3.86 4 +192.168.3.85 0 +192.168.3.84 1 +192.168.3.83 2 +192.168.3.82 3 +192.168.3.81 4 +192.168.3.80 0 +192.168.3.79 0 +192.168.3.78 1 +192.168.3.77 2 +192.168.3.76 3 +192.168.3.75 4 +192.168.3.74 1 +192.168.3.73 2 +192.168.3.72 3 +192.168.3.71 3 +192.168.3.70 4 +192.168.3.69 0 +192.168.3.68 1 +192.168.3.67 2 +192.168.3.66 4 +192.168.3.65 0 +192.168.3.64 3 +192.168.3.63 0 +192.168.3.62 1 +192.168.3.61 2 +192.168.3.60 3 +192.168.3.59 4 +192.168.3.58 2 +192.168.3.57 1 +192.168.3.56 3 +192.168.3.55 0 +192.168.3.54 1 +192.168.3.53 2 +192.168.3.52 3 +192.168.3.51 4 +192.168.3.50 0 +192.168.3.49 4 +192.168.3.48 2 +192.168.3.47 0 +192.168.3.46 1 +192.168.3.45 2 +192.168.3.44 3 +192.168.3.43 4 +192.168.3.42 2 +192.168.3.41 1 +192.168.3.40 0 +192.168.3.39 1 +192.168.3.38 2 +192.168.3.37 3 +192.168.3.36 4 +192.168.3.35 0 +192.168.3.34 4 +192.168.3.33 3 +192.168.3.32 4 +192.168.3.31 0 +192.168.3.30 1 +192.168.3.29 2 +192.168.3.28 3 +192.168.3.27 4 +192.168.3.26 2 +192.168.3.25 1 +192.168.3.24 0 +192.168.3.23 3 +192.168.3.22 4 +192.168.3.21 0 +192.168.3.20 1 +192.168.3.19 2 +192.168.3.18 4 +192.168.3.17 3 +192.168.3.16 1 +192.168.3.15 0 +192.168.3.14 1 +192.168.3.13 2 +192.168.3.12 3 +192.168.3.11 4 +192.168.3.10 2 +192.168.3.9 1 +192.168.3.8 0 +192.168.3.7 4 +192.168.3.6 0 +192.168.3.5 1 +192.168.3.4 2 +192.168.3.3 3 +192.168.3.2 4 +192.168.3.1 3 +192.168.2.90 0 +192.168.2.89 1 +192.168.2.88 2 +192.168.2.87 3 +192.168.2.86 4 +192.168.2.85 0 +192.168.2.84 1 +192.168.2.83 2 +192.168.2.82 3 +192.168.2.81 4 +192.168.2.80 1 +192.168.2.79 0 +192.168.2.78 1 +192.168.2.77 2 +192.168.2.76 3 +192.168.2.75 4 +192.168.2.74 2 +192.168.2.73 3 +192.168.2.72 0 +192.168.2.71 3 +192.168.2.70 4 +192.168.2.69 0 +192.168.2.68 1 +192.168.2.67 2 +192.168.2.66 4 +192.168.2.65 1 +192.168.2.64 3 +192.168.2.63 0 +192.168.2.62 1 +192.168.2.61 2 +192.168.2.60 3 +192.168.2.59 4 +192.168.2.58 0 +192.168.2.57 2 +192.168.2.56 1 +192.168.2.55 0 +192.168.2.54 1 +192.168.2.53 2 +192.168.2.52 3 +192.168.2.51 4 +192.168.2.50 3 +192.168.2.49 4 +192.168.2.48 0 +192.168.2.47 0 +192.168.2.46 1 +192.168.2.45 2 +192.168.2.44 3 +192.168.2.43 4 +192.168.2.42 2 +192.168.2.41 0 +192.168.2.40 1 +192.168.2.39 0 +192.168.2.38 1 +192.168.2.37 2 +192.168.2.36 3 +192.168.2.35 4 +192.168.2.34 3 +192.168.2.33 4 +192.168.2.32 2 +192.168.2.31 0 +192.168.2.30 1 +192.168.2.29 2 +192.168.2.28 3 +192.168.2.27 4 +192.168.2.26 2 +192.168.2.25 0 +192.168.2.24 1 +192.168.2.23 3 +192.168.2.22 4 +192.168.2.21 0 +192.168.2.20 1 +192.168.2.19 2 +192.168.2.18 4 +192.168.2.17 3 +192.168.2.16 4 +192.168.2.15 0 +192.168.2.14 1 +192.168.2.13 2 +192.168.2.12 3 +192.168.2.11 4 +192.168.2.10 0 +192.168.2.9 2 +192.168.2.8 3 +192.168.2.7 2 +192.168.2.6 4 +192.168.2.5 0 +192.168.2.4 1 +192.168.2.3 3 +192.168.2.2 4 +192.168.2.1 1 +192.168.1.90 0 +192.168.1.89 1 +192.168.1.88 2 +192.168.1.87 3 +192.168.1.86 4 +192.168.1.85 0 +192.168.1.84 1 +192.168.1.83 2 +192.168.1.82 3 +192.168.1.81 4 +192.168.1.80 0 +192.168.1.79 0 +192.168.1.78 1 +192.168.1.77 2 +192.168.1.76 3 +192.168.1.75 4 +192.168.1.74 1 +192.168.1.73 2 +192.168.1.72 3 +192.168.1.71 3 +192.168.1.70 4 +192.168.1.69 0 +192.168.1.68 1 +192.168.1.67 2 +192.168.1.66 4 +192.168.1.65 0 +192.168.1.64 1 +192.168.1.63 0 +192.168.1.62 1 +192.168.1.61 2 +192.168.1.60 3 +192.168.1.59 4 +192.168.1.58 2 +192.168.1.57 3 +192.168.1.56 1 +192.168.1.55 0 +192.168.1.54 1 +192.168.1.53 2 +192.168.1.52 3 +192.168.1.51 4 +192.168.1.50 0 +192.168.1.49 4 +192.168.1.48 2 +192.168.1.47 0 +192.168.1.46 1 +192.168.1.45 2 +192.168.1.44 3 +192.168.1.43 4 +192.168.1.42 2 +192.168.1.41 3 +192.168.1.40 0 +192.168.1.39 3 +192.168.1.38 4 +192.168.1.37 0 +192.168.1.36 1 +192.168.1.35 2 +192.168.1.34 4 +192.168.1.33 1 +192.168.1.32 3 +192.168.1.31 0 +192.168.1.30 1 +192.168.1.29 2 +192.168.1.28 3 +192.168.1.27 4 +192.168.1.26 2 +192.168.1.25 3 +192.168.1.24 0 +192.168.1.23 3 +192.168.1.22 4 +192.168.1.21 0 +192.168.1.20 1 +192.168.1.19 2 +192.168.1.18 4 +192.168.1.17 1 +192.168.1.16 4 +192.168.1.15 0 +192.168.1.14 1 +192.168.1.13 2 +192.168.1.12 3 +192.168.1.11 4 +192.168.1.10 2 +192.168.1.9 3 +192.168.1.8 0 +192.168.1.7 3 +192.168.1.6 4 +192.168.1.5 0 +192.168.1.4 1 +192.168.1.3 2 +192.168.1.2 4 +192.168.1.1 1 +EOF + +simple_test 0,0,0,0,0 <<EOF +192.168.10.90 0 +192.168.10.89 1 +192.168.10.88 2 +192.168.10.87 3 +192.168.10.86 4 +192.168.10.85 0 +192.168.10.84 1 +192.168.10.83 2 +192.168.10.82 3 +192.168.10.81 4 +192.168.10.80 0 +192.168.10.79 0 +192.168.10.78 1 +192.168.10.77 2 +192.168.10.76 3 +192.168.10.75 4 +192.168.10.74 1 +192.168.10.73 2 +192.168.10.72 3 +192.168.10.71 3 +192.168.10.70 4 +192.168.10.69 0 +192.168.10.68 1 +192.168.10.67 2 +192.168.10.66 4 +192.168.10.65 0 +192.168.10.64 1 +192.168.10.63 0 +192.168.10.62 1 +192.168.10.61 2 +192.168.10.60 3 +192.168.10.59 4 +192.168.10.58 2 +192.168.10.57 3 +192.168.10.56 0 +192.168.10.55 0 +192.168.10.54 1 +192.168.10.53 2 +192.168.10.52 3 +192.168.10.51 4 +192.168.10.50 1 +192.168.10.49 4 +192.168.10.48 2 +192.168.10.47 0 +192.168.10.46 1 +192.168.10.45 2 +192.168.10.44 3 +192.168.10.43 4 +192.168.10.42 2 +192.168.10.41 3 +192.168.10.40 1 +192.168.10.39 3 +192.168.10.38 4 +192.168.10.37 0 +192.168.10.36 1 +192.168.10.35 2 +192.168.10.34 4 +192.168.10.33 0 +192.168.10.32 3 +192.168.10.31 0 +192.168.10.30 1 +192.168.10.29 2 +192.168.10.28 3 +192.168.10.27 4 +192.168.10.26 3 +192.168.10.25 2 +192.168.10.24 0 +192.168.10.23 3 +192.168.10.22 4 +192.168.10.21 0 +192.168.10.20 1 +192.168.10.19 2 +192.168.10.18 4 +192.168.10.17 1 +192.168.10.16 4 +192.168.10.15 0 +192.168.10.14 1 +192.168.10.13 2 +192.168.10.12 3 +192.168.10.11 4 +192.168.10.10 2 +192.168.10.9 3 +192.168.10.8 4 +192.168.10.7 0 +192.168.10.6 1 +192.168.10.5 2 +192.168.10.4 3 +192.168.10.3 4 +192.168.10.2 0 +192.168.10.1 1 +192.168.9.90 0 +192.168.9.89 1 +192.168.9.88 2 +192.168.9.87 3 +192.168.9.86 4 +192.168.9.85 0 +192.168.9.84 1 +192.168.9.83 2 +192.168.9.82 3 +192.168.9.81 4 +192.168.9.80 0 +192.168.9.79 0 +192.168.9.78 1 +192.168.9.77 2 +192.168.9.76 3 +192.168.9.75 4 +192.168.9.74 1 +192.168.9.73 2 +192.168.9.72 3 +192.168.9.71 3 +192.168.9.70 4 +192.168.9.69 0 +192.168.9.68 1 +192.168.9.67 2 +192.168.9.66 4 +192.168.9.65 0 +192.168.9.64 1 +192.168.9.63 0 +192.168.9.62 1 +192.168.9.61 2 +192.168.9.60 3 +192.168.9.59 4 +192.168.9.58 2 +192.168.9.57 3 +192.168.9.56 4 +192.168.9.55 0 +192.168.9.54 1 +192.168.9.53 2 +192.168.9.52 3 +192.168.9.51 4 +192.168.9.50 0 +192.168.9.49 1 +192.168.9.48 2 +192.168.9.47 0 +192.168.9.46 1 +192.168.9.45 2 +192.168.9.44 3 +192.168.9.43 4 +192.168.9.42 2 +192.168.9.41 4 +192.168.9.40 3 +192.168.9.39 0 +192.168.9.38 1 +192.168.9.37 2 +192.168.9.36 3 +192.168.9.35 4 +192.168.9.34 0 +192.168.9.33 1 +192.168.9.32 4 +192.168.9.31 0 +192.168.9.30 1 +192.168.9.29 2 +192.168.9.28 3 +192.168.9.27 4 +192.168.9.26 2 +192.168.9.25 3 +192.168.9.24 0 +192.168.9.23 3 +192.168.9.22 4 +192.168.9.21 0 +192.168.9.20 1 +192.168.9.19 2 +192.168.9.18 4 +192.168.9.17 1 +192.168.9.16 3 +192.168.9.15 0 +192.168.9.14 1 +192.168.9.13 2 +192.168.9.12 3 +192.168.9.11 4 +192.168.9.10 2 +192.168.9.9 4 +192.168.9.8 3 +192.168.9.7 0 +192.168.9.6 1 +192.168.9.5 2 +192.168.9.4 3 +192.168.9.3 4 +192.168.9.2 0 +192.168.9.1 1 +192.168.8.90 0 +192.168.8.89 1 +192.168.8.88 2 +192.168.8.87 3 +192.168.8.86 4 +192.168.8.85 0 +192.168.8.84 1 +192.168.8.83 2 +192.168.8.82 3 +192.168.8.81 4 +192.168.8.80 0 +192.168.8.79 0 +192.168.8.78 1 +192.168.8.77 2 +192.168.8.76 3 +192.168.8.75 4 +192.168.8.74 1 +192.168.8.73 2 +192.168.8.72 3 +192.168.8.71 3 +192.168.8.70 4 +192.168.8.69 0 +192.168.8.68 1 +192.168.8.67 2 +192.168.8.66 4 +192.168.8.65 3 +192.168.8.64 0 +192.168.8.63 0 +192.168.8.62 1 +192.168.8.61 2 +192.168.8.60 3 +192.168.8.59 4 +192.168.8.58 1 +192.168.8.57 2 +192.168.8.56 3 +192.168.8.55 0 +192.168.8.54 1 +192.168.8.53 2 +192.168.8.52 3 +192.168.8.51 4 +192.168.8.50 0 +192.168.8.49 4 +192.168.8.48 1 +192.168.8.47 0 +192.168.8.46 1 +192.168.8.45 2 +192.168.8.44 3 +192.168.8.43 4 +192.168.8.42 2 +192.168.8.41 1 +192.168.8.40 4 +192.168.8.39 0 +192.168.8.38 1 +192.168.8.37 2 +192.168.8.36 3 +192.168.8.35 4 +192.168.8.34 3 +192.168.8.33 0 +192.168.8.32 2 +192.168.8.31 0 +192.168.8.30 1 +192.168.8.29 2 +192.168.8.28 3 +192.168.8.27 4 +192.168.8.26 2 +192.168.8.25 1 +192.168.8.24 3 +192.168.8.23 3 +192.168.8.22 4 +192.168.8.21 0 +192.168.8.20 1 +192.168.8.19 2 +192.168.8.18 4 +192.168.8.17 0 +192.168.8.16 4 +192.168.8.15 0 +192.168.8.14 1 +192.168.8.13 2 +192.168.8.12 3 +192.168.8.11 4 +192.168.8.10 1 +192.168.8.9 2 +192.168.8.8 4 +192.168.8.7 0 +192.168.8.6 1 +192.168.8.5 2 +192.168.8.4 3 +192.168.8.3 4 +192.168.8.2 3 +192.168.8.1 0 +192.168.7.90 0 +192.168.7.89 1 +192.168.7.88 2 +192.168.7.87 3 +192.168.7.86 4 +192.168.7.85 0 +192.168.7.84 1 +192.168.7.83 2 +192.168.7.82 3 +192.168.7.81 4 +192.168.7.80 1 +192.168.7.79 0 +192.168.7.78 1 +192.168.7.77 2 +192.168.7.76 3 +192.168.7.75 4 +192.168.7.74 2 +192.168.7.73 3 +192.168.7.72 0 +192.168.7.71 3 +192.168.7.70 4 +192.168.7.69 0 +192.168.7.68 1 +192.168.7.67 2 +192.168.7.66 4 +192.168.7.65 1 +192.168.7.64 3 +192.168.7.63 0 +192.168.7.62 1 +192.168.7.61 2 +192.168.7.60 3 +192.168.7.59 4 +192.168.7.58 2 +192.168.7.57 0 +192.168.7.56 1 +192.168.7.55 0 +192.168.7.54 1 +192.168.7.53 2 +192.168.7.52 3 +192.168.7.51 4 +192.168.7.50 3 +192.168.7.49 4 +192.168.7.48 2 +192.168.7.47 0 +192.168.7.46 1 +192.168.7.45 2 +192.168.7.44 3 +192.168.7.43 4 +192.168.7.42 2 +192.168.7.41 0 +192.168.7.40 1 +192.168.7.39 4 +192.168.7.38 0 +192.168.7.37 1 +192.168.7.36 2 +192.168.7.35 3 +192.168.7.34 4 +192.168.7.33 3 +192.168.7.32 0 +192.168.7.31 0 +192.168.7.30 1 +192.168.7.29 2 +192.168.7.28 3 +192.168.7.27 4 +192.168.7.26 2 +192.168.7.25 0 +192.168.7.24 1 +192.168.7.23 3 +192.168.7.22 4 +192.168.7.21 0 +192.168.7.20 1 +192.168.7.19 2 +192.168.7.18 4 +192.168.7.17 3 +192.168.7.16 4 +192.168.7.15 0 +192.168.7.14 1 +192.168.7.13 2 +192.168.7.12 3 +192.168.7.11 4 +192.168.7.10 3 +192.168.7.9 2 +192.168.7.8 0 +192.168.7.7 2 +192.168.7.6 4 +192.168.7.5 0 +192.168.7.4 1 +192.168.7.3 3 +192.168.7.2 4 +192.168.7.1 1 +192.168.6.90 0 +192.168.6.89 1 +192.168.6.88 2 +192.168.6.87 3 +192.168.6.86 4 +192.168.6.85 0 +192.168.6.84 1 +192.168.6.83 2 +192.168.6.82 4 +192.168.6.81 3 +192.168.6.80 0 +192.168.6.79 0 +192.168.6.78 1 +192.168.6.77 2 +192.168.6.76 3 +192.168.6.75 4 +192.168.6.74 2 +192.168.6.73 3 +192.168.6.72 1 +192.168.6.71 3 +192.168.6.70 4 +192.168.6.69 0 +192.168.6.68 1 +192.168.6.67 2 +192.168.6.66 4 +192.168.6.65 0 +192.168.6.64 1 +192.168.6.63 0 +192.168.6.62 1 +192.168.6.61 2 +192.168.6.60 3 +192.168.6.59 4 +192.168.6.58 2 +192.168.6.57 3 +192.168.6.56 0 +192.168.6.55 3 +192.168.6.54 4 +192.168.6.53 1 +192.168.6.52 2 +192.168.6.51 0 +192.168.6.50 4 +192.168.6.49 1 +192.168.6.48 2 +192.168.6.47 0 +192.168.6.46 1 +192.168.6.45 2 +192.168.6.44 3 +192.168.6.43 4 +192.168.6.42 2 +192.168.6.41 4 +192.168.6.40 3 +192.168.6.39 0 +192.168.6.38 1 +192.168.6.37 2 +192.168.6.36 3 +192.168.6.35 4 +192.168.6.34 0 +192.168.6.33 1 +192.168.6.32 4 +192.168.6.31 0 +192.168.6.30 1 +192.168.6.29 2 +192.168.6.28 3 +192.168.6.27 4 +192.168.6.26 2 +192.168.6.25 3 +192.168.6.24 0 +192.168.6.23 3 +192.168.6.22 4 +192.168.6.21 0 +192.168.6.20 1 +192.168.6.19 2 +192.168.6.18 4 +192.168.6.17 1 +192.168.6.16 3 +192.168.6.15 0 +192.168.6.14 1 +192.168.6.13 2 +192.168.6.12 3 +192.168.6.11 4 +192.168.6.10 2 +192.168.6.9 3 +192.168.6.8 4 +192.168.6.7 0 +192.168.6.6 1 +192.168.6.5 2 +192.168.6.4 3 +192.168.6.3 4 +192.168.6.2 0 +192.168.6.1 1 +192.168.5.90 0 +192.168.5.89 1 +192.168.5.88 2 +192.168.5.87 3 +192.168.5.86 4 +192.168.5.85 0 +192.168.5.84 1 +192.168.5.83 2 +192.168.5.82 4 +192.168.5.81 3 +192.168.5.80 0 +192.168.5.79 0 +192.168.5.78 1 +192.168.5.77 2 +192.168.5.76 3 +192.168.5.75 4 +192.168.5.74 2 +192.168.5.73 3 +192.168.5.72 1 +192.168.5.71 3 +192.168.5.70 4 +192.168.5.69 2 +192.168.5.68 0 +192.168.5.67 1 +192.168.5.66 4 +192.168.5.65 2 +192.168.5.64 0 +192.168.5.63 0 +192.168.5.62 1 +192.168.5.61 2 +192.168.5.60 3 +192.168.5.59 4 +192.168.5.58 1 +192.168.5.57 3 +192.168.5.56 2 +192.168.5.55 0 +192.168.5.54 1 +192.168.5.53 2 +192.168.5.52 3 +192.168.5.51 4 +192.168.5.50 0 +192.168.5.49 4 +192.168.5.48 1 +192.168.5.47 0 +192.168.5.46 1 +192.168.5.45 2 +192.168.5.44 3 +192.168.5.43 4 +192.168.5.42 1 +192.168.5.41 3 +192.168.5.40 2 +192.168.5.39 2 +192.168.5.38 3 +192.168.5.37 4 +192.168.5.36 0 +192.168.5.35 1 +192.168.5.34 4 +192.168.5.33 0 +192.168.5.32 4 +192.168.5.31 0 +192.168.5.30 1 +192.168.5.29 2 +192.168.5.28 3 +192.168.5.27 4 +192.168.5.26 1 +192.168.5.25 3 +192.168.5.24 2 +192.168.5.23 3 +192.168.5.22 4 +192.168.5.21 2 +192.168.5.20 0 +192.168.5.19 1 +192.168.5.18 4 +192.168.5.17 0 +192.168.5.16 3 +192.168.5.15 0 +192.168.5.14 1 +192.168.5.13 2 +192.168.5.12 3 +192.168.5.11 4 +192.168.5.10 1 +192.168.5.9 4 +192.168.5.8 3 +192.168.5.7 0 +192.168.5.6 1 +192.168.5.5 2 +192.168.5.4 3 +192.168.5.3 4 +192.168.5.2 2 +192.168.5.1 0 +192.168.4.90 0 +192.168.4.89 1 +192.168.4.88 2 +192.168.4.87 3 +192.168.4.86 4 +192.168.4.85 0 +192.168.4.84 1 +192.168.4.83 2 +192.168.4.82 3 +192.168.4.81 4 +192.168.4.80 0 +192.168.4.79 0 +192.168.4.78 1 +192.168.4.77 2 +192.168.4.76 3 +192.168.4.75 4 +192.168.4.74 1 +192.168.4.73 2 +192.168.4.72 3 +192.168.4.71 3 +192.168.4.70 4 +192.168.4.69 0 +192.168.4.68 1 +192.168.4.67 2 +192.168.4.66 4 +192.168.4.65 1 +192.168.4.64 3 +192.168.4.63 0 +192.168.4.62 1 +192.168.4.61 2 +192.168.4.60 3 +192.168.4.59 4 +192.168.4.58 0 +192.168.4.57 2 +192.168.4.56 1 +192.168.4.55 0 +192.168.4.54 1 +192.168.4.53 2 +192.168.4.52 3 +192.168.4.51 4 +192.168.4.50 3 +192.168.4.49 4 +192.168.4.48 0 +192.168.4.47 0 +192.168.4.46 1 +192.168.4.45 2 +192.168.4.44 3 +192.168.4.43 4 +192.168.4.42 2 +192.168.4.41 0 +192.168.4.40 1 +192.168.4.39 4 +192.168.4.38 0 +192.168.4.37 1 +192.168.4.36 2 +192.168.4.35 3 +192.168.4.34 4 +192.168.4.33 3 +192.168.4.32 2 +192.168.4.31 0 +192.168.4.30 1 +192.168.4.29 2 +192.168.4.28 3 +192.168.4.27 4 +192.168.4.26 0 +192.168.4.25 2 +192.168.4.24 1 +192.168.4.23 3 +192.168.4.22 4 +192.168.4.21 0 +192.168.4.20 1 +192.168.4.19 2 +192.168.4.18 4 +192.168.4.17 3 +192.168.4.16 1 +192.168.4.15 0 +192.168.4.14 1 +192.168.4.13 2 +192.168.4.12 3 +192.168.4.11 4 +192.168.4.10 3 +192.168.4.9 0 +192.168.4.8 2 +192.168.4.7 2 +192.168.4.6 3 +192.168.4.5 4 +192.168.4.4 0 +192.168.4.3 1 +192.168.4.2 4 +192.168.4.1 4 +192.168.3.90 0 +192.168.3.89 1 +192.168.3.88 2 +192.168.3.87 3 +192.168.3.86 4 +192.168.3.85 0 +192.168.3.84 1 +192.168.3.83 2 +192.168.3.82 3 +192.168.3.81 4 +192.168.3.80 0 +192.168.3.79 0 +192.168.3.78 1 +192.168.3.77 2 +192.168.3.76 3 +192.168.3.75 4 +192.168.3.74 1 +192.168.3.73 2 +192.168.3.72 3 +192.168.3.71 3 +192.168.3.70 4 +192.168.3.69 0 +192.168.3.68 1 +192.168.3.67 2 +192.168.3.66 4 +192.168.3.65 0 +192.168.3.64 3 +192.168.3.63 0 +192.168.3.62 1 +192.168.3.61 2 +192.168.3.60 3 +192.168.3.59 4 +192.168.3.58 2 +192.168.3.57 1 +192.168.3.56 3 +192.168.3.55 0 +192.168.3.54 1 +192.168.3.53 2 +192.168.3.52 3 +192.168.3.51 4 +192.168.3.50 0 +192.168.3.49 4 +192.168.3.48 2 +192.168.3.47 0 +192.168.3.46 1 +192.168.3.45 2 +192.168.3.44 3 +192.168.3.43 4 +192.168.3.42 2 +192.168.3.41 1 +192.168.3.40 0 +192.168.3.39 1 +192.168.3.38 2 +192.168.3.37 3 +192.168.3.36 4 +192.168.3.35 0 +192.168.3.34 4 +192.168.3.33 3 +192.168.3.32 4 +192.168.3.31 0 +192.168.3.30 1 +192.168.3.29 2 +192.168.3.28 3 +192.168.3.27 4 +192.168.3.26 2 +192.168.3.25 1 +192.168.3.24 0 +192.168.3.23 3 +192.168.3.22 4 +192.168.3.21 0 +192.168.3.20 1 +192.168.3.19 2 +192.168.3.18 4 +192.168.3.17 3 +192.168.3.16 1 +192.168.3.15 0 +192.168.3.14 1 +192.168.3.13 2 +192.168.3.12 3 +192.168.3.11 4 +192.168.3.10 2 +192.168.3.9 1 +192.168.3.8 0 +192.168.3.7 4 +192.168.3.6 0 +192.168.3.5 1 +192.168.3.4 2 +192.168.3.3 3 +192.168.3.2 4 +192.168.3.1 3 +192.168.2.90 0 +192.168.2.89 1 +192.168.2.88 2 +192.168.2.87 3 +192.168.2.86 4 +192.168.2.85 0 +192.168.2.84 1 +192.168.2.83 2 +192.168.2.82 3 +192.168.2.81 4 +192.168.2.80 1 +192.168.2.79 0 +192.168.2.78 1 +192.168.2.77 2 +192.168.2.76 3 +192.168.2.75 4 +192.168.2.74 2 +192.168.2.73 3 +192.168.2.72 0 +192.168.2.71 3 +192.168.2.70 4 +192.168.2.69 0 +192.168.2.68 1 +192.168.2.67 2 +192.168.2.66 4 +192.168.2.65 1 +192.168.2.64 3 +192.168.2.63 0 +192.168.2.62 1 +192.168.2.61 2 +192.168.2.60 3 +192.168.2.59 4 +192.168.2.58 0 +192.168.2.57 2 +192.168.2.56 1 +192.168.2.55 0 +192.168.2.54 1 +192.168.2.53 2 +192.168.2.52 3 +192.168.2.51 4 +192.168.2.50 3 +192.168.2.49 4 +192.168.2.48 0 +192.168.2.47 0 +192.168.2.46 1 +192.168.2.45 2 +192.168.2.44 3 +192.168.2.43 4 +192.168.2.42 2 +192.168.2.41 0 +192.168.2.40 1 +192.168.2.39 0 +192.168.2.38 1 +192.168.2.37 2 +192.168.2.36 3 +192.168.2.35 4 +192.168.2.34 3 +192.168.2.33 4 +192.168.2.32 2 +192.168.2.31 0 +192.168.2.30 1 +192.168.2.29 2 +192.168.2.28 3 +192.168.2.27 4 +192.168.2.26 2 +192.168.2.25 0 +192.168.2.24 1 +192.168.2.23 3 +192.168.2.22 4 +192.168.2.21 0 +192.168.2.20 1 +192.168.2.19 2 +192.168.2.18 4 +192.168.2.17 3 +192.168.2.16 4 +192.168.2.15 0 +192.168.2.14 1 +192.168.2.13 2 +192.168.2.12 3 +192.168.2.11 4 +192.168.2.10 0 +192.168.2.9 2 +192.168.2.8 3 +192.168.2.7 2 +192.168.2.6 4 +192.168.2.5 0 +192.168.2.4 1 +192.168.2.3 3 +192.168.2.2 4 +192.168.2.1 1 +192.168.1.90 0 +192.168.1.89 1 +192.168.1.88 2 +192.168.1.87 3 +192.168.1.86 4 +192.168.1.85 0 +192.168.1.84 1 +192.168.1.83 2 +192.168.1.82 3 +192.168.1.81 4 +192.168.1.80 0 +192.168.1.79 0 +192.168.1.78 1 +192.168.1.77 2 +192.168.1.76 3 +192.168.1.75 4 +192.168.1.74 1 +192.168.1.73 2 +192.168.1.72 3 +192.168.1.71 3 +192.168.1.70 4 +192.168.1.69 0 +192.168.1.68 1 +192.168.1.67 2 +192.168.1.66 4 +192.168.1.65 0 +192.168.1.64 1 +192.168.1.63 0 +192.168.1.62 1 +192.168.1.61 2 +192.168.1.60 3 +192.168.1.59 4 +192.168.1.58 2 +192.168.1.57 3 +192.168.1.56 1 +192.168.1.55 0 +192.168.1.54 1 +192.168.1.53 2 +192.168.1.52 3 +192.168.1.51 4 +192.168.1.50 0 +192.168.1.49 4 +192.168.1.48 2 +192.168.1.47 0 +192.168.1.46 1 +192.168.1.45 2 +192.168.1.44 3 +192.168.1.43 4 +192.168.1.42 2 +192.168.1.41 3 +192.168.1.40 0 +192.168.1.39 3 +192.168.1.38 4 +192.168.1.37 0 +192.168.1.36 1 +192.168.1.35 2 +192.168.1.34 4 +192.168.1.33 1 +192.168.1.32 3 +192.168.1.31 0 +192.168.1.30 1 +192.168.1.29 2 +192.168.1.28 3 +192.168.1.27 4 +192.168.1.26 2 +192.168.1.25 3 +192.168.1.24 0 +192.168.1.23 3 +192.168.1.22 4 +192.168.1.21 0 +192.168.1.20 1 +192.168.1.19 2 +192.168.1.18 4 +192.168.1.17 1 +192.168.1.16 4 +192.168.1.15 0 +192.168.1.14 1 +192.168.1.13 2 +192.168.1.12 3 +192.168.1.11 4 +192.168.1.10 2 +192.168.1.9 3 +192.168.1.8 0 +192.168.1.7 3 +192.168.1.6 4 +192.168.1.5 0 +192.168.1.4 1 +192.168.1.3 2 +192.168.1.2 4 +192.168.1.1 1 +EOF diff --git a/ctdb/tests/UNIT/takeover/nondet.001.sh b/ctdb/tests/UNIT/takeover/nondet.001.sh new file mode 100755 index 0000000..5f838ee --- /dev/null +++ b/ctdb/tests/UNIT/takeover/nondet.001.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 1 healthy" + +required_result <<EOF +${TEST_DATE_STAMP}Unassign IP: 192.168.21.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.21.252 from 0 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.252 from 0 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.250 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.249 from 0 +192.168.21.254 2 +192.168.21.253 2 +192.168.21.252 2 +192.168.20.254 2 +192.168.20.253 2 +192.168.20.252 2 +192.168.20.251 2 +192.168.20.250 2 +192.168.20.249 2 +EOF + +simple_test 2,2,0 <<EOF +192.168.20.249 0 +192.168.20.250 1 +192.168.20.251 2 +192.168.20.252 0 +192.168.20.253 1 +192.168.20.254 2 +192.168.21.252 0 +192.168.21.253 1 +192.168.21.254 2 +EOF diff --git a/ctdb/tests/UNIT/takeover/nondet.002.sh b/ctdb/tests/UNIT/takeover/nondet.002.sh new file mode 100755 index 0000000..bc80f5c --- /dev/null +++ b/ctdb/tests/UNIT/takeover/nondet.002.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 2 healthy" + +required_result <<EOF +${TEST_DATE_STAMP}Unassign IP: 192.168.21.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.253 from 1 +${TEST_DATE_STAMP}Unassign IP: 192.168.20.250 from 1 +192.168.21.254 2 +192.168.21.253 0 +192.168.21.252 0 +192.168.20.254 2 +192.168.20.253 2 +192.168.20.252 0 +192.168.20.251 2 +192.168.20.250 0 +192.168.20.249 0 +EOF + +simple_test 0,2,0 <<EOF +192.168.20.249 0 +192.168.20.250 1 +192.168.20.251 2 +192.168.20.252 0 +192.168.20.253 1 +192.168.20.254 2 +192.168.21.252 0 +192.168.21.253 1 +192.168.21.254 2 +EOF diff --git a/ctdb/tests/UNIT/takeover/nondet.003.sh b/ctdb/tests/UNIT/takeover/nondet.003.sh new file mode 100755 index 0000000..2a9dfb4 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/nondet.003.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 1 -> all healthy" + +required_result <<EOF +192.168.21.254 0 +192.168.21.253 2 +192.168.21.252 0 +192.168.20.254 2 +192.168.20.253 0 +192.168.20.252 2 +192.168.20.251 1 +192.168.20.250 1 +192.168.20.249 1 +EOF + +simple_test 0,0,0 <<EOF +192.168.20.249 1 +192.168.20.250 1 +192.168.20.251 1 +192.168.20.252 1 +192.168.20.253 1 +192.168.20.254 1 +192.168.21.252 1 +192.168.21.253 1 +192.168.21.254 1 +EOF diff --git a/ctdb/tests/UNIT/takeover/scripts/local.sh b/ctdb/tests/UNIT/takeover/scripts/local.sh new file mode 100644 index 0000000..0db3d90 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/scripts/local.sh @@ -0,0 +1,30 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +test_prog="ctdb_takeover_tests ipalloc" + +define_test () +{ + _f=$(basename "$0" ".sh") + + export CTDB_IP_ALGORITHM="${_f%%.*}" + case "$CTDB_IP_ALGORITHM" in + lcp2|nondet|det) : ;; + *) die "Unknown algorithm for testcase \"$_f\"" ;; + esac + + printf "%-12s - %s\n" "$_f" "$1" +} + +extra_footer () +{ + cat <<EOF +-------------------------------------------------- +Algorithm: $CTDB_IP_ALGORITHM +-------------------------------------------------- +EOF +} + +simple_test () +{ + unit_test $VALGRIND $test_prog "$@" +} diff --git a/ctdb/tests/UNIT/takeover_helper/000.sh b/ctdb/tests/UNIT/takeover_helper/000.sh new file mode 100755 index 0000000..3cb9635 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/000.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, no IPs" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 0 <<EOF +No nodes available to host public IPs yet +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/010.sh b/ctdb/tests/UNIT/takeover_helper/010.sh new file mode 100755 index 0000000..1275156 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/010.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IPs all unassigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 +EOF + +ok_null +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 2 +10.0.0.32 1 +10.0.0.33 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/011.sh b/ctdb/tests/UNIT/takeover_helper/011.sh new file mode 100755 index 0000000..12a2a1a --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/011.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 1 ok, IPs all unassigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 CURRENT RECMASTER +1 192.168.20.42 0x2 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 +EOF + +ok_null +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 2 +10.0.0.32 2 +10.0.0.33 2 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/012.sh b/ctdb/tests/UNIT/takeover_helper/012.sh new file mode 100755 index 0000000..04e4508 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/012.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, 1 IP unassigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 2 +10.0.0.33 1 +EOF + +ok_null +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 2 +10.0.0.33 1 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/013.sh b/ctdb/tests/UNIT/takeover_helper/013.sh new file mode 100755 index 0000000..ad55564 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/013.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 1 unhealthy, IPs all assigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x2 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 2 +10.0.0.33 1 +EOF + +ok_null +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 0 +10.0.0.33 1 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/014.sh b/ctdb/tests/UNIT/takeover_helper/014.sh new file mode 100755 index 0000000..e3d8515 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/014.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all unhealthy, all IPs assigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 CURRENT RECMASTER +1 192.168.20.42 0x2 +2 192.168.20.43 0x2 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 2 +10.0.0.33 1 +EOF + +ok <<EOF +Failed to find node to cover ip 10.0.0.33 +Failed to find node to cover ip 10.0.0.32 +Failed to find node to cover ip 10.0.0.31 +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/016.sh b/ctdb/tests/UNIT/takeover_helper/016.sh new file mode 100755 index 0000000..7fbed7e --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/016.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all healthy, IPs all unassigned, IP failover disabled" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 +EOF + +export CTDB_DISABLE_IP_FAILOVER=1 + +ok <<EOF +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/017.sh b/ctdb/tests/UNIT/takeover_helper/017.sh new file mode 100755 index 0000000..e5bcd20 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/017.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all healthy, IPs unbalanced, NoIPFailback" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 1 +EOF + +ctdb_cmd setvar NoIPFailback 1 + +ok <<EOF +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 1 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/018.sh b/ctdb/tests/UNIT/takeover_helper/018.sh new file mode 100755 index 0000000..61a26dd --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/018.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all healthy, IPs unbalanced" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 1 +EOF + +ok <<EOF +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/019.sh b/ctdb/tests/UNIT/takeover_helper/019.sh new file mode 100755 index 0000000..0802611 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/019.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 1 node unhealthy, IPs all assigned, NoIPTakeover" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x2 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +EOF + +ctdb_cmd setvar NoIPTakeover 1 + +ok <<EOF +Failed to find node to cover ip 10.0.0.32 +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 -1 +10.0.0.33 2 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/021.sh b/ctdb/tests/UNIT/takeover_helper/021.sh new file mode 100755 index 0000000..ad8e59f --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/021.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all healthy, IPs all assigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +10.0.0.34 -1 +EOF + +ctdb_cmd setvar NoIPTakeover 1 + +ok <<EOF +Failed to find node to cover ip 10.0.0.34 +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +10.0.0.34 -1 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/022.sh b/ctdb/tests/UNIT/takeover_helper/022.sh new file mode 100755 index 0000000..e8c5a96 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/022.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all healthy, IPs very unbalanced, no force rebalance" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +10.0.0.34 2 +10.0.0.35 2 +10.0.0.36 2 +EOF + +ok <<EOF +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +10.0.0.34 2 +10.0.0.35 2 +10.0.0.36 2 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/023.sh b/ctdb/tests/UNIT/takeover_helper/023.sh new file mode 100755 index 0000000..a76afef --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/023.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all healthy, IPs very unbalanced, force rebalance 1" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +10.0.0.34 2 +10.0.0.35 2 +10.0.0.36 2 +EOF + +ok <<EOF +Forcing rebalancing of IPs to node 1 +EOF +test_takeover_helper 1 + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +10.0.0.34 2 +10.0.0.35 1 +10.0.0.36 2 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/024.sh b/ctdb/tests/UNIT/takeover_helper/024.sh new file mode 100755 index 0000000..af7480c --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/024.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all healthy, IPs very unbalanced, force rebalance all" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +10.0.0.34 2 +10.0.0.35 2 +10.0.0.36 2 +EOF + +ok <<EOF +Forcing rebalancing of IPs to node 1 +Forcing rebalancing of IPs to node 0 +Forcing rebalancing of IPs to node 2 +EOF +test_takeover_helper 1,0,2 + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +10.0.0.34 2 +10.0.0.35 0 +10.0.0.36 1 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/025.sh b/ctdb/tests/UNIT/takeover_helper/025.sh new file mode 100755 index 0000000..28db486 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/025.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IPs all assigned randomly, deterministic IPs" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 1 +10.0.0.32 0 +10.0.0.33 2 +EOF + +ctdb_cmd setvar IPAllocAlgorithm 0 + +ok <<EOF +Deterministic IPs enabled. Resetting all ip allocations +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 2 +10.0.0.32 1 +10.0.0.33 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/026.sh b/ctdb/tests/UNIT/takeover_helper/026.sh new file mode 100755 index 0000000..08a7b6d --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/026.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IPs assigned, unbalanced, non-deterministic IPs" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +10.0.0.34 2 +10.0.0.35 2 +EOF + +ctdb_cmd setvar IPAllocAlgorithm 1 + +ok_null +test_takeover_helper + +# This is non-deterministic - LCP2 would not rebalance without +# force-rebalance-nodes +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +10.0.0.34 2 +10.0.0.35 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/027.sh b/ctdb/tests/UNIT/takeover_helper/027.sh new file mode 100755 index 0000000..1c36d87 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/027.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 2 banned, IPs all unassigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x8 +2 192.168.20.43 0x8 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 +EOF + +ok_null +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 0 +10.0.0.33 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/028.sh b/ctdb/tests/UNIT/takeover_helper/028.sh new file mode 100755 index 0000000..a69cd47 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/028.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 2 banned, IPs all unassigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x1 +2 192.168.20.43 0x1 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 +EOF + +ok_null +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 0 +10.0.0.33 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/030.sh b/ctdb/tests/UNIT/takeover_helper/030.sh new file mode 100755 index 0000000..e6411c5 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/030.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IPs defined on 2, IPs all unassigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 0,2 +10.0.0.32 -1 0,2 +10.0.0.33 -1 0,2 +10.0.0.34 -1 0,2 +EOF + +ok_null +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 2 +10.0.0.33 2 +10.0.0.34 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/031.sh b/ctdb/tests/UNIT/takeover_helper/031.sh new file mode 100755 index 0000000..13005ee --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/031.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IPs defined on 2, IPs all unassigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 0,2 +10.0.0.32 -1 0,2 +10.0.0.33 -1 0,2 +10.0.0.34 -1 0,2 +EOF + +HELPER_DEBUGLEVEL=INFO +ok <<EOF +Fetched public IPs from node 0 +Fetched public IPs from node 1 +Fetched public IPs from node 2 +Fetched public IPs from node 0 +Fetched public IPs from node 2 + 10.0.0.34 -> 0 [+0] + 10.0.0.33 -> 2 [+0] + 10.0.0.31 -> 0 [+14884] + 10.0.0.32 -> 2 [+16129] +RELEASE_IP 10.0.0.34 succeeded on 1 nodes +RELEASE_IP 10.0.0.33 succeeded on 1 nodes +RELEASE_IP 10.0.0.32 succeeded on 1 nodes +RELEASE_IP 10.0.0.31 succeeded on 1 nodes +TAKEOVER_IP 10.0.0.34 succeeded on node 0 +TAKEOVER_IP 10.0.0.33 succeeded on node 2 +TAKEOVER_IP 10.0.0.32 succeeded on node 2 +TAKEOVER_IP 10.0.0.31 succeeded on node 0 +IPREALLOCATED succeeded on 3 nodes +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 2 +10.0.0.33 2 +10.0.0.34 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/110.sh b/ctdb/tests/UNIT/takeover_helper/110.sh new file mode 100755 index 0000000..56dc16c --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/110.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, no IPs, IPREALLOCATED error" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +CONTROLFAILS +137 1 ERROR CTDB_CONTROL_IPREALLOCATED fake failure + +EOF + +required_result 255 <<EOF +No nodes available to host public IPs yet +IPREALLOCATED failed on node 1, ret=-1 +Assigning banning credits to node 1 +takeover run failed, ret=-1 +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/111.sh b/ctdb/tests/UNIT/takeover_helper/111.sh new file mode 100755 index 0000000..d14868b --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/111.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IPs all unassigned, IPREALLOCATED error" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 + +CONTROLFAILS +137 1 ERROR CTDB_CONTROL_IPREALLOCATED fake failure +EOF + +required_result 255 <<EOF +IPREALLOCATED failed on node 1, ret=-1 +Assigning banning credits to node 1 +takeover run failed, ret=-1 +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 2 +10.0.0.32 1 +10.0.0.33 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/120.sh b/ctdb/tests/UNIT/takeover_helper/120.sh new file mode 100755 index 0000000..af780d6 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/120.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IPs all unassigned, TAKEOVER_IP error" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 + +CONTROLFAILS +89 1 ERROR CTDB_CONTROL_TAKEOVER_IP fake failure +EOF + +required_result 255 <<EOF +TAKEOVER_IP 10.0.0.32 failed on node 1, ret=-1 +Assigning banning credits to node 1 +takeover run failed, ret=-1 +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 2 +10.0.0.32 -1 +10.0.0.33 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/121.sh b/ctdb/tests/UNIT/takeover_helper/121.sh new file mode 100755 index 0000000..cc113da --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/121.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, 2/3 IPs assigned, TAKEOVER_IP error (redundant)" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 2 +10.0.0.32 1 +10.0.0.33 -1 + +CONTROLFAILS +89 1 ERROR CTDB_CONTROL_TAKEOVER_IP fake failure +EOF + +required_result 255 <<EOF +TAKEOVER_IP 10.0.0.32 failed on node 1, ret=-1 +Assigning banning credits to node 1 +takeover run failed, ret=-1 +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 2 +10.0.0.32 1 +10.0.0.33 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/122.sh b/ctdb/tests/UNIT/takeover_helper/122.sh new file mode 100755 index 0000000..d823b09 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/122.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, 2/3 IPs assigned, TAKEOVER_IP error (target)" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 2 +10.0.0.32 1 +10.0.0.33 -1 + +CONTROLFAILS +89 0 ERROR CTDB_CONTROL_TAKEOVER_IP fake failure +EOF + +required_result 255 <<EOF +TAKEOVER_IP 10.0.0.33 failed on node 0, ret=-1 +Assigning banning credits to node 0 +takeover run failed, ret=-1 +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 2 +10.0.0.32 1 +10.0.0.33 -1 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/130.sh b/ctdb/tests/UNIT/takeover_helper/130.sh new file mode 100755 index 0000000..83735d4 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/130.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IPs all unassigned, RELEASE_IP error" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 + +CONTROLFAILS +88 2 ERROR CTDB_CONTROL_RELEASE_IP fake failure +EOF + +required_result 255 <<EOF +RELEASE_IP 10.0.0.33 failed on node 2, ret=-1 +RELEASE_IP 10.0.0.32 failed on node 2, ret=-1 +Assigning banning credits to node 2 +takeover run failed, ret=-1 +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/131.sh b/ctdb/tests/UNIT/takeover_helper/131.sh new file mode 100755 index 0000000..4e0cd46 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/131.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, all IPs assigned, RELEASE_IP error (redundant)" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x2 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 + +CONTROLFAILS +88 0 ERROR CTDB_CONTROL_RELEASE_IP fake failure +EOF + +required_result 255 <<EOF +RELEASE_IP 10.0.0.33 failed on node 0, ret=-1 +Assigning banning credits to node 0 +takeover run failed, ret=-1 +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 -1 +10.0.0.33 2 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/132.sh b/ctdb/tests/UNIT/takeover_helper/132.sh new file mode 100755 index 0000000..a1a4ce5 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/132.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, all IPs assigned, RELEASE_IP error (target)" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x2 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 + +CONTROLFAILS +88 1 ERROR CTDB_CONTROL_RELEASE_IP fake failure +EOF + +required_result 255 <<EOF +RELEASE_IP 10.0.0.33 failed on node 1, ret=-1 +RELEASE_IP 10.0.0.32 failed on node 1, ret=-1 +RELEASE_IP 10.0.0.31 failed on node 1, ret=-1 +Assigning banning credits to node 1 +takeover run failed, ret=-1 +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/140.sh b/ctdb/tests/UNIT/takeover_helper/140.sh new file mode 100755 index 0000000..844a35a --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/140.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, GET_PUBLIC_IPS error" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 1 +10.0.0.32 1 +10.0.0.33 1 + +CONTROLFAILS +90 2 ERROR CTDB_CONTROL_GET_PUBLIC_IPS fake failure +EOF + +required_result 255 <<EOF +control GET_PUBLIC_IPS failed on node 2, ret=-1 +Failed to fetch known public IPs +Assigning banning credits to node 2 +takeover run failed, ret=-1 +EOF +test_takeover_helper diff --git a/ctdb/tests/UNIT/takeover_helper/150.sh b/ctdb/tests/UNIT/takeover_helper/150.sh new file mode 100755 index 0000000..56042b4 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/150.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, GET_NODEMAP error" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 1 +10.0.0.32 1 +10.0.0.33 1 + +CONTROLFAILS +91 0 ERROR CTDB_CONTROL_GET_NODEMAP fake failure +EOF + +required_result 255 <<EOF +control GET_NODEMAP failed, ret=-1 +takeover run failed, ret=-1 +EOF +test_takeover_helper diff --git a/ctdb/tests/UNIT/takeover_helper/160.sh b/ctdb/tests/UNIT/takeover_helper/160.sh new file mode 100755 index 0000000..c09f649 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/160.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, GET_ALL_TUNABLES error" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 1 +10.0.0.32 1 +10.0.0.33 1 + +CONTROLFAILS +53 0 ERROR CTDB_CONTROL_GET_ALL_TUNABLES fake failure +EOF + +required_result 255 <<EOF +control GET_ALL_TUNABLES failed, ret=-1 +takeover run failed, ret=-1 +EOF +test_takeover_helper diff --git a/ctdb/tests/UNIT/takeover_helper/210.sh b/ctdb/tests/UNIT/takeover_helper/210.sh new file mode 100755 index 0000000..eacf024 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/210.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, no IPs, IPREALLOCATED timeout" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +CONTROLFAILS +137 1 TIMEOUT CTDB_CONTROL_IPREALLOCATED fake timeout + +EOF + +required_error ETIMEDOUT <<EOF +No nodes available to host public IPs yet +IPREALLOCATED failed on node 1, ret=$(errcode ETIMEDOUT) +Assigning banning credits to node 1 +takeover run failed, ret=$(errcode ETIMEDOUT) +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/211.sh b/ctdb/tests/UNIT/takeover_helper/211.sh new file mode 100755 index 0000000..27eebe3 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/211.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IPs all unassigned, IPREALLOCATED timeout" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 + +CONTROLFAILS +137 1 TIMEOUT CTDB_CONTROL_IPREALLOCATED fake timeout +EOF + +required_error ETIMEDOUT <<EOF +IPREALLOCATED failed on node 1, ret=$(errcode ETIMEDOUT) +Assigning banning credits to node 1 +takeover run failed, ret=$(errcode ETIMEDOUT) +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 2 +10.0.0.32 1 +10.0.0.33 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/220.sh b/ctdb/tests/UNIT/takeover_helper/220.sh new file mode 100755 index 0000000..84fc1d7 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/220.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IPs all unassigned, TAKEOVER_IP timeout" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 + +CONTROLFAILS +89 1 TIMEOUT CTDB_CONTROL_TAKEOVER_IP fake timeout +EOF + +required_error ETIMEDOUT <<EOF +TAKEOVER_IP 10.0.0.32 failed to node 1, ret=$(errcode ETIMEDOUT) +Assigning banning credits to node 1 +takeover run failed, ret=$(errcode ETIMEDOUT) +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 2 +10.0.0.32 -1 +10.0.0.33 0 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/230.sh b/ctdb/tests/UNIT/takeover_helper/230.sh new file mode 100755 index 0000000..13ed08b --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/230.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IPs all unassigned, RELEASE_IP timeout" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 + +CONTROLFAILS +88 2 TIMEOUT CTDB_CONTROL_RELEASE_IP fake timeout +EOF + +required_error ETIMEDOUT <<EOF +RELEASE_IP 10.0.0.33 failed on node 2, ret=$(errcode ETIMEDOUT) +RELEASE_IP 10.0.0.32 failed on node 2, ret=$(errcode ETIMEDOUT) +Assigning banning credits to node 2 +takeover run failed, ret=$(errcode ETIMEDOUT) +EOF +test_takeover_helper + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 -1 +10.0.0.32 -1 +10.0.0.33 -1 +EOF +test_ctdb_ip_all diff --git a/ctdb/tests/UNIT/takeover_helper/240.sh b/ctdb/tests/UNIT/takeover_helper/240.sh new file mode 100755 index 0000000..7afb2fc --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/240.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, GET_PUBLIC_IPS timeout" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 1 +10.0.0.32 1 +10.0.0.33 1 + +CONTROLFAILS +90 2 TIMEOUT CTDB_CONTROL_GET_PUBLIC_IPS fake timeout +EOF + +required_error ETIMEDOUT <<EOF +control GET_PUBLIC_IPS failed on node 2, ret=$(errcode ETIMEDOUT) +Failed to fetch known public IPs +Assigning banning credits to node 2 +takeover run failed, ret=$(errcode ETIMEDOUT) +EOF +test_takeover_helper diff --git a/ctdb/tests/UNIT/takeover_helper/250.sh b/ctdb/tests/UNIT/takeover_helper/250.sh new file mode 100755 index 0000000..91c6766 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/250.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, GET_NODEMAP timeout" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 1 +10.0.0.32 1 +10.0.0.33 1 + +CONTROLFAILS +91 0 TIMEOUT CTDB_CONTROL_GET_NODEMAP fake timeout +EOF + +required_error ETIMEDOUT <<EOF +control GET_NODEMAP failed to node 0, ret=$(errcode ETIMEDOUT) +takeover run failed, ret=$(errcode ETIMEDOUT) +EOF +test_takeover_helper diff --git a/ctdb/tests/UNIT/takeover_helper/260.sh b/ctdb/tests/UNIT/takeover_helper/260.sh new file mode 100755 index 0000000..7e24e32 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/260.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, GET_ALL_TUNABLES timeout" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 1 +10.0.0.32 1 +10.0.0.33 1 + +CONTROLFAILS +53 0 TIMEOUT CTDB_CONTROL_GET_ALL_TUNABLES fake timeout +EOF + +required_error ETIMEDOUT <<EOF +control GET_ALL_TUNABLES failed, ret=$(errcode ETIMEDOUT) +takeover run failed, ret=$(errcode ETIMEDOUT) +EOF +test_takeover_helper diff --git a/ctdb/tests/UNIT/takeover_helper/scripts/local.sh b/ctdb/tests/UNIT/takeover_helper/scripts/local.sh new file mode 100644 index 0000000..d36d4e4 --- /dev/null +++ b/ctdb/tests/UNIT/takeover_helper/scripts/local.sh @@ -0,0 +1,108 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +if "$CTDB_TEST_VERBOSE" ; then + debug () { echo "$@" ; } +else + debug () { : ; } +fi + +. "${TEST_SCRIPTS_DIR}/script_install_paths.sh" + +PATH="${PATH}:${CTDB_SCRIPTS_TOOLS_HELPER_DIR}" +PATH="${PATH}:${CTDB_SCRIPTS_HELPER_BINDIR}" + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "ctdb-etc" + +ctdbd_socket=$(ctdb-path socket "ctdbd") +ctdbd_pidfile=$(ctdb-path pidfile "ctdbd") +ctdbd_dbdir=$(ctdb-path vardir append "db") + +define_test () +{ + _f=$(basename "$0" ".sh") + + printf "%-28s - %s\n" "$_f" "$1" + + if [ -z "$FAKE_CTDBD_DEBUGLEVEL" ] ; then + FAKE_CTDBD_DEBUGLEVEL="ERR" + fi + if [ -z "$HELPER_DEBUGLEVEL" ] ; then + HELPER_DEBUGLEVEL="NOTICE" + fi + if [ -z "$CTDB_DEBUGLEVEL" ] ; then + CTDB_DEBUGLEVEL="ERR" + fi +} + +cleanup_ctdbd () +{ + debug "Cleaning up fake ctdbd" + + pid=$(cat "$ctdbd_pidfile" 2>/dev/null || echo) + if [ -n "$pid" ] ; then + kill $pid || true + rm -f "$ctdbd_pidfile" + fi + rm -f "$ctdbd_socket" + rm -rf "$ctdbd_dbdir" +} + +setup_ctdbd () +{ + debug "Setting up fake ctdbd" + + mkdir -p "$ctdbd_dbdir" + $VALGRIND fake_ctdbd -d "$FAKE_CTDBD_DEBUGLEVEL" \ + -s "$ctdbd_socket" -p "$ctdbd_pidfile" \ + -D "$ctdbd_dbdir" + # This current translates to a 6 second timeout for the + # important controls + ctdb setvar TakeoverTimeout 2 + test_cleanup cleanup_ctdbd +} + +# Render non-printable characters. The helper prints the status as +# binary, so render it for easy comparison. +result_filter () +{ + sed -e 's|ctdb-takeover\[[0-9]*\]: ||' +} + +ctdb_cmd () +{ + echo Running: ctdb -d "$CTDB_DEBUGLEVEL" "$@" + ctdb -d "$CTDB_DEBUGLEVEL" "$@" +} + +test_ctdb_ip_all () +{ + unit_test ctdb -d "$CTDB_DEBUGLEVEL" ip all || exit $? +} + +takeover_helper_out="${CTDB_TEST_TMP_DIR}/takover_helper.out" + +takeover_helper_format_outfd () +{ + od -A n -t d4 "$takeover_helper_out" | sed -e 's|[[:space:]]*||g' +} + +test_takeover_helper () +{ + ( + export CTDB_DEBUGLEVEL="$HELPER_DEBUGLEVEL" + export CTDB_LOGGING="file:" + unit_test ctdb_takeover_helper 3 "$ctdbd_socket" "$@" \ + 3>"$takeover_helper_out" + ) || exit $? + + case "$required_rc" in + 255) _t="-1" ;; + *) _t="$required_rc" ;; + esac + ok "$_t" + + unit_test_notrace takeover_helper_format_outfd + _ret=$? + rm "$takeover_helper_out" + [ $_ret -eq 0 ] || exit $_ret +} diff --git a/ctdb/tests/UNIT/tool/README b/ctdb/tests/UNIT/tool/README new file mode 100644 index 0000000..8160528 --- /dev/null +++ b/ctdb/tests/UNIT/tool/README @@ -0,0 +1,17 @@ +Unit tests for the ctdb tool (i.e. tools/ctdb). + +Test case filenames can take 2 forms: + +* func.<some_function>.NNN.sh + + Run <some_function> in the ctdb tool code using the + ctdb_tool_functest test program. This test program uses test stubs + for CTDB client functions. + +* stubby.<command>.NNN.sh + + Run the ctdb_tool_stubby test program with <command> as the 1st + argument - subsequent are passed to simple_test(). ctdb_tool_stubby + is linked against the test stubs for CTDB client functions. + +To add tests here you may need to add appropriate test stubs. diff --git a/ctdb/tests/UNIT/tool/ctdb.attach.001.sh b/ctdb/tests/UNIT/tool/ctdb.attach.001.sh new file mode 100755 index 0000000..82c3332 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.attach.001.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "attach volatile database" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test "volatile.tdb" + +ok <<EOF +Number of databases:1 +dbid:0x211bf47b name:volatile.tdb path:${ctdbd_dbdir}/volatile.tdb +EOF + +simple_test_other getdbmap + +ok <<EOF +dbid: 0x211bf47b +name: volatile.tdb +path: ${ctdbd_dbdir}/volatile.tdb +PERSISTENT: no +REPLICATED: no +STICKY: no +READONLY: no +HEALTH: OK +EOF + +simple_test_other getdbstatus "volatile.tdb" diff --git a/ctdb/tests/UNIT/tool/ctdb.attach.002.sh b/ctdb/tests/UNIT/tool/ctdb.attach.002.sh new file mode 100755 index 0000000..a4719bf --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.attach.002.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "attach persistent database" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test "persistent.tdb" persistent + +ok <<EOF +Number of databases:1 +dbid:0x54ef7d5e name:persistent.tdb path:${ctdbd_dbdir}/persistent.tdb PERSISTENT +EOF + +simple_test_other getdbmap + +ok <<EOF +dbid: 0x54ef7d5e +name: persistent.tdb +path: ${ctdbd_dbdir}/persistent.tdb +PERSISTENT: yes +REPLICATED: no +STICKY: no +READONLY: no +HEALTH: OK +EOF + +simple_test_other getdbstatus "persistent.tdb" diff --git a/ctdb/tests/UNIT/tool/ctdb.attach.003.sh b/ctdb/tests/UNIT/tool/ctdb.attach.003.sh new file mode 100755 index 0000000..1a4cdeb --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.attach.003.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "attach replicated database" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test "replicated.tdb" replicated + +ok <<EOF +Number of databases:1 +dbid:0x84241f7c name:replicated.tdb path:${ctdbd_dbdir}/replicated.tdb REPLICATED +EOF + +simple_test_other getdbmap + +ok <<EOF +dbid: 0x84241f7c +name: replicated.tdb +path: ${ctdbd_dbdir}/replicated.tdb +PERSISTENT: no +REPLICATED: yes +STICKY: no +READONLY: no +HEALTH: OK +EOF + +simple_test_other getdbstatus "replicated.tdb" diff --git a/ctdb/tests/UNIT/tool/ctdb.ban.001.sh b/ctdb/tests/UNIT/tool/ctdb.ban.001.sh new file mode 100755 index 0000000..3c17f75 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ban.001.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "ban default (0), wait for timeout" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test 4 + +required_result 8 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 BANNED|INACTIVE (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all + +echo +echo "Waiting 5 seconds for ban to expire..." +sleep 5 + +ok <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.ban.002.sh b/ctdb/tests/UNIT/tool/ctdb.ban.002.sh new file mode 100755 index 0000000..47a9995 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ban.002.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "ban node 1" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test 60 -n 1 + +required_result 8 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 BANNED|INACTIVE +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.ban.003.sh b/ctdb/tests/UNIT/tool/ctdb.ban.003.sh new file mode 100755 index 0000000..95acf50 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ban.003.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "already banned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x8 +2 192.168.20.43 0x0 +EOF + +ok "Node 1 is already banned" +simple_test 60 -n 1 + +required_result 8 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 BANNED|INACTIVE +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.catdb.001.sh b/ctdb/tests/UNIT/tool/ctdb.catdb.001.sh new file mode 100755 index 0000000..7fef1f1 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.catdb.001.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "volatile traverse" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test_other attach "volatile.tdb" + +for i in $(seq 1 9) ; do + ok_null + simple_test_other writekey "volatile.tdb" "key$i" "value$i" +done + +ok <<EOF +key(4) = "key2" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value2" + +key(4) = "key4" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value4" + +key(4) = "key9" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value9" + +key(4) = "key8" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value8" + +key(4) = "key6" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value6" + +key(4) = "key3" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value3" + +key(4) = "key7" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value7" + +key(4) = "key5" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value5" + +key(4) = "key1" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value1" + +Dumped 9 records +EOF + +simple_test "volatile.tdb" diff --git a/ctdb/tests/UNIT/tool/ctdb.catdb.002.sh b/ctdb/tests/UNIT/tool/ctdb.catdb.002.sh new file mode 100755 index 0000000..5258308 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.catdb.002.sh @@ -0,0 +1,86 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "persistent traverse" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test_other attach "persistent.tdb" persistent + +for i in $(seq 1 9) ; do + ok_null + simple_test_other pstore "persistent.tdb" "key$i" "value$i" +done + +ok <<EOF +key(23) = "__db_sequence_number__\00" +dmaster: 0 +rsn: 9 +flags: 0x00000000 +data(8) = "\09\00\00\00\00\00\00\00" + +key(4) = "key9" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value9" + +key(4) = "key8" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value8" + +key(4) = "key7" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value7" + +key(4) = "key6" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value6" + +key(4) = "key5" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value5" + +key(4) = "key4" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value4" + +key(4) = "key3" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value3" + +key(4) = "key2" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value2" + +key(4) = "key1" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value1" + +Dumped 10 records +EOF + +simple_test "persistent.tdb" diff --git a/ctdb/tests/UNIT/tool/ctdb.cattdb.001.sh b/ctdb/tests/UNIT/tool/ctdb.cattdb.001.sh new file mode 100755 index 0000000..be549e2 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.cattdb.001.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "volatile traverse" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test_other attach "volatile.tdb" + +for i in $(seq 1 9) ; do + ok_null + simple_test_other writekey "volatile.tdb" "key$i" "value$i" +done + +ok <<EOF +key(4) = "key2" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value2" + +key(4) = "key4" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value4" + +key(4) = "key9" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value9" + +key(4) = "key8" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value8" + +key(4) = "key6" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value6" + +key(4) = "key3" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value3" + +key(4) = "key7" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value7" + +key(4) = "key5" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value5" + +key(4) = "key1" +dmaster: 0 +rsn: 0 +flags: 0x00000000 +data(6) = "value1" + +Dumped 9 record(s) +EOF + +simple_test "volatile.tdb" diff --git a/ctdb/tests/UNIT/tool/ctdb.cattdb.002.sh b/ctdb/tests/UNIT/tool/ctdb.cattdb.002.sh new file mode 100755 index 0000000..03c5e7f --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.cattdb.002.sh @@ -0,0 +1,86 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "persistent traverse" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test_other attach "persistent.tdb" persistent + +for i in $(seq 1 9) ; do + ok_null + simple_test_other pstore "persistent.tdb" "key$i" "value$i" +done + +ok <<EOF +key(23) = "__db_sequence_number__\00" +dmaster: 0 +rsn: 9 +flags: 0x00000000 +data(8) = "\09\00\00\00\00\00\00\00" + +key(4) = "key9" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value9" + +key(4) = "key8" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value8" + +key(4) = "key7" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value7" + +key(4) = "key6" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value6" + +key(4) = "key5" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value5" + +key(4) = "key4" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value4" + +key(4) = "key3" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value3" + +key(4) = "key2" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value2" + +key(4) = "key1" +dmaster: 0 +rsn: 1 +flags: 0x00000000 +data(6) = "value1" + +Dumped 10 record(s) +EOF + +simple_test "persistent.tdb" diff --git a/ctdb/tests/UNIT/tool/ctdb.continue.001.sh b/ctdb/tests/UNIT/tool/ctdb.continue.001.sh new file mode 100755 index 0000000..fef1e00 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.continue.001.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "continue default (0)" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x20 CURRENT +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 RECMASTER +EOF + +ok_null +simple_test + +ok <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.continue.002.sh b/ctdb/tests/UNIT/tool/ctdb.continue.002.sh new file mode 100755 index 0000000..55ce7f5 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.continue.002.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "continue 1" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT +1 192.168.20.42 0x20 +2 192.168.20.43 0x0 RECMASTER +EOF + +ok_null +simple_test -n 1 + +ok <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.continue.003.sh b/ctdb/tests/UNIT/tool/ctdb.continue.003.sh new file mode 100755 index 0000000..7280125 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.continue.003.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "node is not stopped" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok "Node 2 is not stopped" +simple_test -n 2 + +ok <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.deletekey.001.sh b/ctdb/tests/UNIT/tool/ctdb.deletekey.001.sh new file mode 100755 index 0000000..f530801 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.deletekey.001.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "volatile delete" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test_other attach "volatile.tdb" + +ok_null +simple_test "volatile.tdb" "key1" + +ok_null +simple_test_other writekey "volatile.tdb" "key1" "value1" + +ok <<EOF +Data: size:6 ptr:[value1] +EOF +simple_test_other readkey "volatile.tdb" "key1" + +ok_null +simple_test "volatile.tdb" "key1" + +ok <<EOF +Data: size:0 ptr:[] +EOF +simple_test_other readkey "volatile.tdb" "key1" diff --git a/ctdb/tests/UNIT/tool/ctdb.disable.001.sh b/ctdb/tests/UNIT/tool/ctdb.disable.001.sh new file mode 100755 index 0000000..b2e419b --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.disable.001.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "disable default (0)" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test + +required_result 4 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 DISABLED (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.disable.002.sh b/ctdb/tests/UNIT/tool/ctdb.disable.002.sh new file mode 100755 index 0000000..ac90c75 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.disable.002.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "disable node 1" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test -n 1 + +required_result 4 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 DISABLED +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.disable.003.sh b/ctdb/tests/UNIT/tool/ctdb.disable.003.sh new file mode 100755 index 0000000..ef02ba0 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.disable.003.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "already disabled" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x4 +2 192.168.20.43 0x0 +EOF + +ok "Node 1 is already disabled" +simple_test -n 1 + +required_result 4 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 DISABLED +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.disable.004.sh b/ctdb/tests/UNIT/tool/ctdb.disable.004.sh new file mode 100755 index 0000000..da39d67 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.disable.004.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "invalid node" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 1 "Node 4 does not exist" +simple_test -n 4 diff --git a/ctdb/tests/UNIT/tool/ctdb.enable.001.sh b/ctdb/tests/UNIT/tool/ctdb.enable.001.sh new file mode 100755 index 0000000..9234f19 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.enable.001.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "enable default (0)" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x4 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test + +ok <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.enable.002.sh b/ctdb/tests/UNIT/tool/ctdb.enable.002.sh new file mode 100755 index 0000000..ee9b210 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.enable.002.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "enable node 1" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x4 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test -n 1 + +ok <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.enable.003.sh b/ctdb/tests/UNIT/tool/ctdb.enable.003.sh new file mode 100755 index 0000000..37656c2 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.enable.003.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "not disabled" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok "Node 1 is not disabled" +simple_test -n 1 + +ok <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.getcapabilities.001.sh b/ctdb/tests/UNIT/tool/ctdb.getcapabilities.001.sh new file mode 100755 index 0000000..da71f22 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getcapabilities.001.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 0 <<EOF +LEADER: YES +LMASTER: YES +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.getcapabilities.002.sh b/ctdb/tests/UNIT/tool/ctdb.getcapabilities.002.sh new file mode 100755 index 0000000..221ae81 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getcapabilities.002.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 1 disconnected" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x1 +2 192.168.20.43 0x0 +EOF + +required_result 0 <<EOF +LEADER: YES +LMASTER: YES +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.getcapabilities.003.sh b/ctdb/tests/UNIT/tool/ctdb.getcapabilities.003.sh new file mode 100755 index 0000000..74702d5 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getcapabilities.003.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, current disconnected" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +# Don't setup ctdbd - disconnected on current node +#setup_ctdbd <<EOF +#NODEMAP +#0 192.168.20.41 0x1 CURRENT RECMASTER +#1 192.168.20.42 0x0 +#2 192.168.20.43 0x0 +#EOF + +required_result 1 <<EOF +connect() failed, errno=2 +Failed to connect to CTDB daemon ($ctdbd_socket) +Failed to detect PNN of the current node. +Is this node part of CTDB cluster? +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.getcapabilities.004.sh b/ctdb/tests/UNIT/tool/ctdb.getcapabilities.004.sh new file mode 100755 index 0000000..8662ed3 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getcapabilities.004.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, non-default capabilities" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 -CTDB_CAP_LMASTER +2 192.168.20.43 0x0 -CTDB_CAP_RECMASTER +EOF + +# node 0 + +required_result 0 <<EOF +LEADER: YES +LMASTER: YES +EOF + +simple_test -n 0 + +# node 1 + +required_result 0 <<EOF +LEADER: YES +LMASTER: NO +EOF + +simple_test -n 1 + +# node 2 + +required_result 0 <<EOF +LEADER: NO +LMASTER: YES +EOF + +simple_test -n 2 diff --git a/ctdb/tests/UNIT/tool/ctdb.getdbmap.001.sh b/ctdb/tests/UNIT/tool/ctdb.getdbmap.001.sh new file mode 100755 index 0000000..f766e9c --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getdbmap.001.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "getdbmap from default (0)" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb READONLY +0x4e66c2b2 brlock.tdb STICKY +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb READONLY +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb STICKY +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.getdbseqnum.001.sh b/ctdb/tests/UNIT/tool/ctdb.getdbseqnum.001.sh new file mode 100755 index 0000000..95ef244 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getdbseqnum.001.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "by ID" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb +0x4e66c2b2 brlock.tdb +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 0x42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 0x23 +EOF + +# locking.tdb +ok "0x0" +simple_test 0x7a19d84d + +# secrets.tdb +ok "0x0" +simple_test 0x7132c184 + +# registry.tdb +ok "0x42" +simple_test 0x6cf2837d + +# ctdb-ip.tdb +ok "0x0" +simple_test 0xbc57b384 + +# ctdb-conn.tdb +ok "0x23" +simple_test 0xbec75f0b diff --git a/ctdb/tests/UNIT/tool/ctdb.getdbseqnum.002.sh b/ctdb/tests/UNIT/tool/ctdb.getdbseqnum.002.sh new file mode 100755 index 0000000..e0274f3 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getdbseqnum.002.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "by name" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb +0x4e66c2b2 brlock.tdb +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 0x42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 0x23 +EOF + +ok "0x0" +simple_test locking.tdb + +ok "0x0" +simple_test secrets.tdb + +ok "0x42" +simple_test registry.tdb + +ok "0x0" +simple_test ctdb-ip.tdb + +ok "0x23" +simple_test ctdb-conn.tdb diff --git a/ctdb/tests/UNIT/tool/ctdb.getdbstatus.001.sh b/ctdb/tests/UNIT/tool/ctdb.getdbstatus.001.sh new file mode 100755 index 0000000..5a2b79e --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getdbstatus.001.sh @@ -0,0 +1,108 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "by ID" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb READONLY +0x4e66c2b2 brlock.tdb STICKY +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +ok <<EOF +dbid: 0x7a19d84d +name: locking.tdb +path: ${ctdbd_dbdir}/locking.tdb +PERSISTENT: no +REPLICATED: no +STICKY: no +READONLY: yes +HEALTH: OK +EOF +simple_test 0x7a19d84d + +ok <<EOF +dbid: 0x4e66c2b2 +name: brlock.tdb +path: ${ctdbd_dbdir}/brlock.tdb +PERSISTENT: no +REPLICATED: no +STICKY: yes +READONLY: no +HEALTH: OK +EOF +simple_test 0x4e66c2b2 + +ok <<EOF +dbid: 0x4d2a432b +name: g_lock.tdb +path: ${ctdbd_dbdir}/g_lock.tdb +PERSISTENT: no +REPLICATED: no +STICKY: no +READONLY: no +HEALTH: OK +EOF +simple_test 0x4d2a432b + +ok <<EOF +dbid: 0x7132c184 +name: secrets.tdb +path: ${ctdbd_dbdir}/secrets.tdb +PERSISTENT: yes +REPLICATED: no +STICKY: no +READONLY: no +HEALTH: OK +EOF +simple_test 0x7132c184 + +ok <<EOF +dbid: 0x6cf2837d +name: registry.tdb +path: ${ctdbd_dbdir}/registry.tdb +PERSISTENT: yes +REPLICATED: no +STICKY: no +READONLY: no +HEALTH: OK +EOF +simple_test 0x6cf2837d + +ok <<EOF +dbid: 0xbc57b384 +name: ctdb-ip.tdb +path: ${ctdbd_dbdir}/ctdb-ip.tdb +PERSISTENT: no +REPLICATED: yes +STICKY: no +READONLY: no +HEALTH: OK +EOF +simple_test 0xbc57b384 + +ok <<EOF +dbid: 0xbec75f0b +name: ctdb-conn.tdb +path: ${ctdbd_dbdir}/ctdb-conn.tdb +PERSISTENT: no +REPLICATED: yes +STICKY: no +READONLY: no +HEALTH: OK +EOF +simple_test 0xbec75f0b + +required_result 1 "No database matching '0xdeadc0de' found" +simple_test 0xdeadc0de diff --git a/ctdb/tests/UNIT/tool/ctdb.getdbstatus.002.sh b/ctdb/tests/UNIT/tool/ctdb.getdbstatus.002.sh new file mode 100755 index 0000000..2ff6e7b --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getdbstatus.002.sh @@ -0,0 +1,108 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "by name, node 1" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb READONLY +0x4e66c2b2 brlock.tdb STICKY +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +ok <<EOF +dbid: 0x7a19d84d +name: locking.tdb +path: ${ctdbd_dbdir}/locking.tdb +PERSISTENT: no +REPLICATED: no +STICKY: no +READONLY: yes +HEALTH: OK +EOF +simple_test locking.tdb -n 1 + +ok <<EOF +dbid: 0x4e66c2b2 +name: brlock.tdb +path: ${ctdbd_dbdir}/brlock.tdb +PERSISTENT: no +REPLICATED: no +STICKY: yes +READONLY: no +HEALTH: OK +EOF +simple_test brlock.tdb -n 1 + +ok <<EOF +dbid: 0x4d2a432b +name: g_lock.tdb +path: ${ctdbd_dbdir}/g_lock.tdb +PERSISTENT: no +REPLICATED: no +STICKY: no +READONLY: no +HEALTH: OK +EOF +simple_test g_lock.tdb -n 1 + +ok <<EOF +dbid: 0x7132c184 +name: secrets.tdb +path: ${ctdbd_dbdir}/secrets.tdb +PERSISTENT: yes +REPLICATED: no +STICKY: no +READONLY: no +HEALTH: OK +EOF +simple_test secrets.tdb -n 1 + +ok <<EOF +dbid: 0x6cf2837d +name: registry.tdb +path: ${ctdbd_dbdir}/registry.tdb +PERSISTENT: yes +REPLICATED: no +STICKY: no +READONLY: no +HEALTH: OK +EOF +simple_test registry.tdb -n 1 + +ok <<EOF +dbid: 0xbc57b384 +name: ctdb-ip.tdb +path: ${ctdbd_dbdir}/ctdb-ip.tdb +PERSISTENT: no +REPLICATED: yes +STICKY: no +READONLY: no +HEALTH: OK +EOF +simple_test ctdb-ip.tdb -n 1 + +ok <<EOF +dbid: 0xbec75f0b +name: ctdb-conn.tdb +path: ${ctdbd_dbdir}/ctdb-conn.tdb +PERSISTENT: no +REPLICATED: yes +STICKY: no +READONLY: no +HEALTH: OK +EOF +simple_test ctdb-conn.tdb -n 1 + +required_result 1 "No database matching 'ctdb.tdb' found" +simple_test ctdb.tdb -n 1 diff --git a/ctdb/tests/UNIT/tool/ctdb.getpid.001.sh b/ctdb/tests/UNIT/tool/ctdb.getpid.001.sh new file mode 100755 index 0000000..5714102 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getpid.001.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "simple getpid" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +pid=$(ctdbd_getpid) +ok "$pid" + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.getpid.010.sh b/ctdb/tests/UNIT/tool/ctdb.getpid.010.sh new file mode 100755 index 0000000..6e220a2 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getpid.010.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, GET_PID control times out" + +setup_lvs <<EOF +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +CONTROLFAILS +30 0 TIMEOUT # Make "ctdb getpid" time out +EOF + +##### + +required_result 1 <<EOF +Maximum runtime exceeded - exiting +EOF +simple_test -T 3 diff --git a/ctdb/tests/UNIT/tool/ctdb.getreclock.001.sh b/ctdb/tests/UNIT/tool/ctdb.getreclock.001.sh new file mode 100755 index 0000000..bfa08d0 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getreclock.001.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "No reclock set" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.getreclock.002.sh b/ctdb/tests/UNIT/tool/ctdb.getreclock.002.sh new file mode 100755 index 0000000..6543f8f --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getreclock.002.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "No reclock set" + +reclock="/some/place/on/shared/storage" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +RECLOCK +${reclock} +EOF + +ok "$reclock" + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.getvar.001.sh b/ctdb/tests/UNIT/tool/ctdb.getvar.001.sh new file mode 100755 index 0000000..480788a --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getvar.001.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "confirm that getvar matches listvar" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +# Squash whitespace for predictable output +result_filter () +{ + sed -e 's|[[:space:]][[:space:]]*| |g' +} + +$CTDB -d $CTDB_DEBUGLEVEL listvars | + while read variable equals value ; do + # Variable, as per listvars + ok "${variable} = ${value}" + simple_test "$variable" + + # Uppercase variable + v_upper=$(echo "$variable" | tr "a-z" "A-Z") + ok "${v_upper} = ${value}" + simple_test "$v_upper" + + # Lowercase variable + v_lower=$(echo "$variable" | tr "A-Z" "a-z") + ok "${v_lower} = ${value}" + simple_test "$v_lower" + done diff --git a/ctdb/tests/UNIT/tool/ctdb.getvar.002.sh b/ctdb/tests/UNIT/tool/ctdb.getvar.002.sh new file mode 100755 index 0000000..c8aa302 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.getvar.002.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "invalid variable" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 1 <<EOF +No such tunable TheQuickBrownFoxJumpsOverTheLazyDog +EOF +simple_test "TheQuickBrownFoxJumpsOverTheLazyDog" diff --git a/ctdb/tests/UNIT/tool/ctdb.ifaces.001.sh b/ctdb/tests/UNIT/tool/ctdb.ifaces.001.sh new file mode 100755 index 0000000..5b92787 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ifaces.001.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "basic interface listing test" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: +EOF + +ok <<EOF +Interfaces on node 0 +name:eth2 link:up references:2 +name:eth1 link:up references:4 +EOF +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.ip.001.sh b/ctdb/tests/UNIT/tool/ctdb.ip.001.sh new file mode 100755 index 0000000..df0d141 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ip.001.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, no ips" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 0 <<EOF +Public IPs on node 0 +EOF +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.ip.002.sh b/ctdb/tests/UNIT/tool/ctdb.ip.002.sh new file mode 100755 index 0000000..98a821f --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ip.002.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, no ips" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 0 <<EOF +Public IPs on ALL nodes +EOF +simple_test all diff --git a/ctdb/tests/UNIT/tool/ctdb.ip.003.sh b/ctdb/tests/UNIT/tool/ctdb.ip.003.sh new file mode 100755 index 0000000..eec4634 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ip.003.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, same ips on all nodes" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +EOF + +required_result 0 <<EOF +Public IPs on node 0 +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +EOF +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.ip.004.sh b/ctdb/tests/UNIT/tool/ctdb.ip.004.sh new file mode 100755 index 0000000..53f090c --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ip.004.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IP missing on node 0" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 0,1,2 +10.0.0.32 1 0,1,2 +10.0.0.33 2 1,2 +EOF + +required_result 0 <<EOF +Public IPs on node 0 +10.0.0.31 0 +10.0.0.32 1 +EOF +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.ip.005.sh b/ctdb/tests/UNIT/tool/ctdb.ip.005.sh new file mode 100755 index 0000000..f84ac29 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ip.005.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, IP missing on node 0, get all" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 0,1,2 +10.0.0.32 1 0,1,2 +10.0.0.33 2 1,2 +EOF + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +EOF +simple_test all diff --git a/ctdb/tests/UNIT/tool/ctdb.ip.006.sh b/ctdb/tests/UNIT/tool/ctdb.ip.006.sh new file mode 100755 index 0000000..975a98c --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ip.006.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, same ips on all nodes, 1 unassigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 -1 +10.0.0.33 2 +EOF + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 node[0] active[eth2] available[eth2,eth1] configured[eth2,eth1] +10.0.0.32 node[-1] active[] available[] configured[] +10.0.0.33 node[2] active[eth2] available[eth2,eth1] configured[eth2,eth1] +EOF +simple_test -v all diff --git a/ctdb/tests/UNIT/tool/ctdb.ip.007.sh b/ctdb/tests/UNIT/tool/ctdb.ip.007.sh new file mode 100755 index 0000000..cb7939d --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ip.007.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, same ips on all nodes, IPv6, 1 unassigned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +fd00::5357:5f01 2 +10.0.0.32 -1 +fd00::5357:5f02 1 +10.0.0.33 2 +fd00::5357:5f03 0 +EOF + +required_result 0 <<EOF +Public IPs on ALL nodes +10.0.0.31 node[0] active[eth2] available[eth2,eth1] configured[eth2,eth1] +10.0.0.32 node[-1] active[] available[] configured[] +10.0.0.33 node[2] active[eth2] available[eth2,eth1] configured[eth2,eth1] +fd00::5357:5f01 node[2] active[eth2] available[eth2,eth1] configured[eth2,eth1] +fd00::5357:5f02 node[1] active[eth2] available[eth2,eth1] configured[eth2,eth1] +fd00::5357:5f03 node[0] active[eth2] available[eth2,eth1] configured[eth2,eth1] +EOF +simple_test -v all diff --git a/ctdb/tests/UNIT/tool/ctdb.ipinfo.001.sh b/ctdb/tests/UNIT/tool/ctdb.ipinfo.001.sh new file mode 100755 index 0000000..60f9462 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ipinfo.001.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, no ips" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 1 <<EOF +Control GET_PUBLIC_IP_INFO failed, ret=-1 +Node 0 does not know about IP 10.0.0.31 +EOF +simple_test 10.0.0.31 diff --git a/ctdb/tests/UNIT/tool/ctdb.ipinfo.002.sh b/ctdb/tests/UNIT/tool/ctdb.ipinfo.002.sh new file mode 100755 index 0000000..366cfd6 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ipinfo.002.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, same ips on all nodes" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +EOF + +required_result 0 <<EOF +Public IP[10.0.0.32] info on node 0 +IP:10.0.0.32 +CurrentNode:1 +NumInterfaces:2 +Interface[1]: Name:eth2 Link:up References:2 (active) +Interface[2]: Name:eth1 Link:up References:4 +EOF +simple_test 10.0.0.32 diff --git a/ctdb/tests/UNIT/tool/ctdb.ipinfo.003.sh b/ctdb/tests/UNIT/tool/ctdb.ipinfo.003.sh new file mode 100755 index 0000000..383f1c7 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ipinfo.003.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, same ips on all nodes, IPv6" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +PUBLICIPS +10.0.0.31 0 +10.0.0.32 1 +10.0.0.33 2 +fd00::5357:5f01 2 +fd00::5357:5f02 1 +fd00::5357:5f03 0 +EOF + +required_result 0 <<EOF +Public IP[fd00::5357:5f02] info on node 0 +IP:fd00::5357:5f02 +CurrentNode:1 +NumInterfaces:2 +Interface[1]: Name:eth2 Link:up References:2 (active) +Interface[2]: Name:eth1 Link:up References:4 +EOF +simple_test fd00::5357:5f02 diff --git a/ctdb/tests/UNIT/tool/ctdb.leader.001.sh b/ctdb/tests/UNIT/tool/ctdb.leader.001.sh new file mode 100755 index 0000000..2855304 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.leader.001.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "node 0" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok 0 + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.leader.002.sh b/ctdb/tests/UNIT/tool/ctdb.leader.002.sh new file mode 100755 index 0000000..93a9daf --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.leader.002.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "node 2" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 RECMASTER +EOF + +ok 2 + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.listnodes.001.sh b/ctdb/tests/UNIT/tool/ctdb.listnodes.001.sh new file mode 100755 index 0000000..5a494ee --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.listnodes.001.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "missing nodes file" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +f="${CTDB_BASE}/nodes" +rm -f "$f" + +required_result 1 <<EOF +${TEST_DATE_STAMP}Failed to read nodes file "${f}" +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.listnodes.002.sh b/ctdb/tests/UNIT/tool/ctdb.listnodes.002.sh new file mode 100755 index 0000000..95315d7 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.listnodes.002.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "missing nodes file" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +required_result 0 <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.listvars.001.sh b/ctdb/tests/UNIT/tool/ctdb.listvars.001.sh new file mode 100755 index 0000000..88f0fa4 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.listvars.001.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "exact check of output" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok << EOF +SeqnumInterval = 1000 +ControlTimeout = 60 +TraverseTimeout = 20 +KeepaliveInterval = 5 +KeepaliveLimit = 5 +RecoverTimeout = 30 +RecoverInterval = 1 +ElectionTimeout = 3 +TakeoverTimeout = 9 +MonitorInterval = 15 +TickleUpdateInterval = 20 +EventScriptTimeout = 30 +MonitorTimeoutCount = 20 +RecoveryGracePeriod = 120 +RecoveryBanPeriod = 300 +DatabaseHashSize = 100001 +DatabaseMaxDead = 5 +RerecoveryTimeout = 10 +EnableBans = 1 +NoIPFailback = 0 +VerboseMemoryNames = 0 +RecdPingTimeout = 60 +RecdFailCount = 10 +LogLatencyMs = 0 +RecLockLatencyMs = 1000 +RecoveryDropAllIPs = 120 +VacuumInterval = 10 +VacuumMaxRunTime = 120 +RepackLimit = 10000 +VacuumFastPathCount = 60 +MaxQueueDropMsg = 1000000 +AllowUnhealthyDBRead = 0 +StatHistoryInterval = 1 +DeferredAttachTO = 120 +AllowClientDBAttach = 1 +FetchCollapse = 1 +HopcountMakeSticky = 50 +StickyDuration = 600 +StickyPindown = 200 +NoIPTakeover = 0 +DBRecordCountWarn = 100000 +DBRecordSizeWarn = 10000000 +DBSizeWarn = 100000000 +PullDBPreallocation = 10485760 +LockProcessesPerDB = 200 +RecBufferSizeLimit = 1000000 +QueueBufferSize = 1024 +IPAllocAlgorithm = 2 +AllowMixedVersions = 0 +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.lvs.001.sh b/ctdb/tests/UNIT/tool/ctdb.lvs.001.sh new file mode 100755 index 0000000..70c726c --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.lvs.001.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, no LVS, all ok" + +setup_lvs <<EOF +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +##### + +required_result 255 <<EOF +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +EOF + +simple_test list + +##### + +required_result 0 <<EOF +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.lvs.002.sh b/ctdb/tests/UNIT/tool/ctdb.lvs.002.sh new file mode 100755 index 0000000..edde656 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.lvs.002.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all LVS, all ok" + +setup_lvs <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +##### + +required_result 0 <<EOF +0 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +0 192.168.20.41 +1 192.168.20.42 +2 192.168.20.43 +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.lvs.003.sh b/ctdb/tests/UNIT/tool/ctdb.lvs.003.sh new file mode 100755 index 0000000..0045ae4 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.lvs.003.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, some LVS, all ok" + +setup_lvs <<EOF +192.168.20.41 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +##### + +required_result 0 <<EOF +0 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +0 192.168.20.41 +2 192.168.20.43 +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:2 192.168.20.43 OK +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.lvs.004.sh b/ctdb/tests/UNIT/tool/ctdb.lvs.004.sh new file mode 100755 index 0000000..255966d --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.lvs.004.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all LVS, node 0 unhealthy" + +setup_lvs <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +##### + +required_result 0 <<EOF +1 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +1 192.168.20.42 +2 192.168.20.43 +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 UNHEALTHY (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.lvs.005.sh b/ctdb/tests/UNIT/tool/ctdb.lvs.005.sh new file mode 100755 index 0000000..73fcd80 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.lvs.005.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all LVS, all unhealthy" + +setup_lvs <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 CURRENT RECMASTER +1 192.168.20.42 0x2 +2 192.168.20.43 0x2 +EOF + +##### + +required_result 0 <<EOF +0 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +0 192.168.20.41 +1 192.168.20.42 +2 192.168.20.43 +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 UNHEALTHY (THIS NODE) +pnn:1 192.168.20.42 UNHEALTHY +pnn:2 192.168.20.43 UNHEALTHY +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.lvs.006.sh b/ctdb/tests/UNIT/tool/ctdb.lvs.006.sh new file mode 100755 index 0000000..55b4310 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.lvs.006.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all LVS, nodes 0,1 disabled, node 2 unhealthy" + +setup_lvs <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x4 CURRENT RECMASTER +1 192.168.20.42 0x4 +2 192.168.20.43 0x2 +EOF + +##### + +required_result 0 <<EOF +2 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +2 192.168.20.43 +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 DISABLED (THIS NODE) +pnn:1 192.168.20.42 DISABLED +pnn:2 192.168.20.43 UNHEALTHY +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.lvs.007.sh b/ctdb/tests/UNIT/tool/ctdb.lvs.007.sh new file mode 100755 index 0000000..3dd1104 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.lvs.007.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all LVS, all nodes disabled" + +setup_lvs <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x4 CURRENT RECMASTER +1 192.168.20.42 0x4 +2 192.168.20.43 0x4 +EOF + +##### + +required_result 255 <<EOF +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 DISABLED (THIS NODE) +pnn:1 192.168.20.42 DISABLED +pnn:2 192.168.20.43 DISABLED +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.lvs.008.sh b/ctdb/tests/UNIT/tool/ctdb.lvs.008.sh new file mode 100755 index 0000000..1997f4c --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.lvs.008.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, no LVS, current disconnected" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_lvs <<EOF +EOF + +# Don't setup ctdbd - disconnected on current node +#setup_ctdbd <<EOF +#NODEMAP +#0 192.168.20.41 0x1 CURRENT RECMASTER +#1 192.168.20.42 0x0 +#2 192.168.20.43 0x0 +#EOF + +##### + +required_result 1 <<EOF +connect() failed, errno=2 +Failed to connect to CTDB daemon ($ctdbd_socket) +Failed to detect PNN of the current node. +Is this node part of CTDB cluster? +EOF + +simple_test list + +##### + +required_result 1 <<EOF +connect() failed, errno=2 +Failed to connect to CTDB daemon ($ctdbd_socket) +Failed to detect PNN of the current node. +Is this node part of CTDB cluster? +EOF + +simple_test leader + +##### + +required_result 1 <<EOF +connect() failed, errno=2 +Failed to connect to CTDB daemon ($ctdbd_socket) +Failed to detect PNN of the current node. +Is this node part of CTDB cluster? +EOF + +simple_test list + +##### + +required_result 1 <<EOF +connect() failed, errno=2 +Failed to connect to CTDB daemon ($ctdbd_socket) +Failed to detect PNN of the current node. +Is this node part of CTDB cluster? +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.lvs.010.sh b/ctdb/tests/UNIT/tool/ctdb.lvs.010.sh new file mode 100755 index 0000000..d433939 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.lvs.010.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all ok, GET_NODEMAP control times out" + +setup_lvs <<EOF +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +CONTROLFAILS +91 0 TIMEOUT # Make "ctdb nodestatus" time out in ctdb_lvs helper +EOF + +##### + +required_result 1 <<EOF +Maximum runtime exceeded - exiting +EOF +simple_test status -T 3 diff --git a/ctdb/tests/UNIT/tool/ctdb.natgw.001.sh b/ctdb/tests/UNIT/tool/ctdb.natgw.001.sh new file mode 100755 index 0000000..ad18f9d --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.natgw.001.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all in natgw group, all ok" + +setup_natgw <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +##### + +required_result 0 <<EOF +0 192.168.20.41 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +192.168.20.41 LEADER +192.168.20.42 +192.168.20.43 +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.natgw.002.sh b/ctdb/tests/UNIT/tool/ctdb.natgw.002.sh new file mode 100755 index 0000000..424189f --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.natgw.002.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all in natgw group, 1 unhealthy" + +setup_natgw <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 +1 192.168.20.42 0x0 CURRENT RECMASTER +2 192.168.20.43 0x0 +EOF + +##### + +required_result 0 <<EOF +1 192.168.20.42 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +192.168.20.41 +192.168.20.42 LEADER +192.168.20.43 +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 UNHEALTHY +pnn:1 192.168.20.42 OK (THIS NODE) +pnn:2 192.168.20.43 OK +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.natgw.003.sh b/ctdb/tests/UNIT/tool/ctdb.natgw.003.sh new file mode 100755 index 0000000..93522d0 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.natgw.003.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 2 in natgw group, 1 unhealthy" + +setup_natgw <<EOF +192.168.20.41 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 +1 192.168.20.42 0x0 CURRENT RECMASTER +2 192.168.20.43 0x0 +EOF + +##### + +required_result 0 <<EOF +2 192.168.20.43 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +192.168.20.41 +192.168.20.43 LEADER +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 UNHEALTHY +pnn:2 192.168.20.43 OK +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.natgw.004.sh b/ctdb/tests/UNIT/tool/ctdb.natgw.004.sh new file mode 100755 index 0000000..af8ea22 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.natgw.004.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all unhealthy, all but 1 stopped" + +setup_natgw <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x22 +1 192.168.20.42 0x22 CURRENT RECMASTER +2 192.168.20.43 0x2 +EOF + +##### + +required_result 0 <<EOF +2 192.168.20.43 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 LEADER +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 UNHEALTHY|STOPPED|INACTIVE +pnn:1 192.168.20.42 UNHEALTHY|STOPPED|INACTIVE (THIS NODE) +pnn:2 192.168.20.43 UNHEALTHY +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.natgw.005.sh b/ctdb/tests/UNIT/tool/ctdb.natgw.005.sh new file mode 100755 index 0000000..6a6bbde --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.natgw.005.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all stopped" + +setup_natgw <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x20 +1 192.168.20.42 0x20 CURRENT RECMASTER +2 192.168.20.43 0x20 +EOF + +##### + +required_result 0 <<EOF +0 192.168.20.41 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +192.168.20.41 LEADER +192.168.20.42 +192.168.20.43 +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 STOPPED|INACTIVE +pnn:1 192.168.20.42 STOPPED|INACTIVE (THIS NODE) +pnn:2 192.168.20.43 STOPPED|INACTIVE +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.natgw.006.sh b/ctdb/tests/UNIT/tool/ctdb.natgw.006.sh new file mode 100755 index 0000000..8080f4e --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.natgw.006.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, node 0 is follower-only, all stopped" + +setup_natgw <<EOF +192.168.20.41 follower-only +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x20 +1 192.168.20.42 0x20 CURRENT RECMASTER +2 192.168.20.43 0x20 +EOF + +##### + +required_result 0 <<EOF +1 192.168.20.42 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +192.168.20.41 follower-only +192.168.20.42 LEADER +192.168.20.43 +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 STOPPED|INACTIVE +pnn:1 192.168.20.42 STOPPED|INACTIVE (THIS NODE) +pnn:2 192.168.20.43 STOPPED|INACTIVE +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.natgw.007.sh b/ctdb/tests/UNIT/tool/ctdb.natgw.007.sh new file mode 100755 index 0000000..ca8ea35 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.natgw.007.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all nodes are follower-only, all stopped" + +setup_natgw <<EOF +192.168.20.41 follower-only +192.168.20.42 follower-only +192.168.20.43 follower-only +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x20 +1 192.168.20.42 0x20 CURRENT RECMASTER +2 192.168.20.43 0x20 +EOF + +##### + +required_result 2 <<EOF +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +192.168.20.41 follower-only +192.168.20.42 follower-only +192.168.20.43 follower-only +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 STOPPED|INACTIVE +pnn:1 192.168.20.42 STOPPED|INACTIVE (THIS NODE) +pnn:2 192.168.20.43 STOPPED|INACTIVE +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.natgw.008.sh b/ctdb/tests/UNIT/tool/ctdb.natgw.008.sh new file mode 100755 index 0000000..3e485f8 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.natgw.008.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all in natgw group, 1 disconnected" + +setup_natgw <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x1 +1 192.168.20.42 0x0 CURRENT RECMASTER +2 192.168.20.43 0x0 +EOF + +##### + +required_result 0 <<EOF +1 192.168.20.42 +EOF + +simple_test leader + +##### + +required_result 0 <<EOF +192.168.20.41 +192.168.20.42 LEADER +192.168.20.43 +EOF + +simple_test list + +##### + +required_result 0 <<EOF +pnn:0 192.168.20.41 DISCONNECTED|INACTIVE +pnn:1 192.168.20.42 OK (THIS NODE) +pnn:2 192.168.20.43 OK +EOF + +simple_test status diff --git a/ctdb/tests/UNIT/tool/ctdb.natgw.010.sh b/ctdb/tests/UNIT/tool/ctdb.natgw.010.sh new file mode 100755 index 0000000..a3a0e9d --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.natgw.010.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, all OK, GET_NODEMAP control times out" + +setup_natgw <<EOF +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +CONTROLFAILS +91 0 TIMEOUT # Make "ctdb nodestatus" time out in ctdb_natgw helper +EOF + +##### + +required_result 1 <<EOF +Maximum runtime exceeded - exiting +EOF +simple_test status -T 3 diff --git a/ctdb/tests/UNIT/tool/ctdb.nodestatus.001.sh b/ctdb/tests/UNIT/tool/ctdb.nodestatus.001.sh new file mode 100755 index 0000000..3c754e2 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.nodestatus.001.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all, 3 nodes, all OK" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 CURRENT RECMASTER + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: +EOF + +required_result 0 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK (THIS NODE) +EOF +simple_test all + +required_result 0 <<EOF +|Node|IP|Disconnected|Unknown|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.20.41|0|0|0|0|0|0|0|0|N| +|1|192.168.20.42|0|0|0|0|0|0|0|0|N| +|2|192.168.20.43|0|0|0|0|0|0|0|0|Y| +EOF +simple_test -X all diff --git a/ctdb/tests/UNIT/tool/ctdb.nodestatus.002.sh b/ctdb/tests/UNIT/tool/ctdb.nodestatus.002.sh new file mode 100755 index 0000000..a5981df --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.nodestatus.002.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all, 3 nodes, 1 disconnected" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 +1 192.168.20.42 0x1 +2 192.168.20.43 0x0 CURRENT RECMASTER + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: +EOF + +required_result 1 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK +pnn:1 192.168.20.42 DISCONNECTED|INACTIVE +pnn:2 192.168.20.43 OK (THIS NODE) +EOF +simple_test all + +required_result 1 <<EOF +|Node|IP|Disconnected|Unknown|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.20.41|0|0|0|0|0|0|0|0|N| +|1|192.168.20.42|1|0|0|0|0|0|1|0|N| +|2|192.168.20.43|0|0|0|0|0|0|0|0|Y| +EOF +simple_test -X all diff --git a/ctdb/tests/UNIT/tool/ctdb.nodestatus.003.sh b/ctdb/tests/UNIT/tool/ctdb.nodestatus.003.sh new file mode 100755 index 0000000..52c2691 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.nodestatus.003.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all, 3 nodes, 1 unhealthy" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 CURRENT RECMASTER + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: +EOF + +required_result 2 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 UNHEALTHY +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK (THIS NODE) +EOF +simple_test all + +required_result 2 <<EOF +|Node|IP|Disconnected|Unknown|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.20.41|0|0|0|0|1|0|0|0|N| +|1|192.168.20.42|0|0|0|0|0|0|0|0|N| +|2|192.168.20.43|0|0|0|0|0|0|0|0|Y| +EOF +simple_test -X all diff --git a/ctdb/tests/UNIT/tool/ctdb.nodestatus.004.sh b/ctdb/tests/UNIT/tool/ctdb.nodestatus.004.sh new file mode 100755 index 0000000..c060fb9 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.nodestatus.004.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "current, 3 nodes, node 0 unhealthy" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 CURRENT RECMASTER + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: +EOF + +required_result 0 <<EOF +pnn:2 192.168.20.43 OK (THIS NODE) +EOF +simple_test + +required_result 0 <<EOF +|Node|IP|Disconnected|Unknown|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|2|192.168.20.43|0|0|0|0|0|0|0|0|Y| +EOF +simple_test -X diff --git a/ctdb/tests/UNIT/tool/ctdb.nodestatus.005.sh b/ctdb/tests/UNIT/tool/ctdb.nodestatus.005.sh new file mode 100755 index 0000000..59f6905 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.nodestatus.005.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "current, 3 nodes, node 0 unhealthy, query node 0" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 CURRENT RECMASTER + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: +EOF + +required_result 2 <<EOF +pnn:0 192.168.20.41 UNHEALTHY +EOF +simple_test 0 + +required_result 2 <<EOF +|Node|IP|Disconnected|Unknown|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.20.41|0|0|0|0|1|0|0|0|N| +EOF +simple_test -X 0 diff --git a/ctdb/tests/UNIT/tool/ctdb.nodestatus.006.sh b/ctdb/tests/UNIT/tool/ctdb.nodestatus.006.sh new file mode 100755 index 0000000..7d74451 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.nodestatus.006.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "current, 3 nodes, node 0 disabled+stopped, various queries" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x24 +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 CURRENT RECMASTER + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: +EOF + +required_result 36 <<EOF +pnn:0 192.168.20.41 DISABLED|STOPPED|INACTIVE +EOF +simple_test 0 + +required_result 36 <<EOF +|Node|IP|Disconnected|Unknown|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.20.41|0|0|0|1|0|1|1|0|N| +EOF +simple_test -X 0 + +required_result 36 <<EOF +pnn:0 192.168.20.41 DISABLED|STOPPED|INACTIVE +pnn:1 192.168.20.42 OK +EOF +simple_test 0,1 + +required_result 0 <<EOF +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK (THIS NODE) +EOF +simple_test 1,2 diff --git a/ctdb/tests/UNIT/tool/ctdb.nodestatus.007.sh b/ctdb/tests/UNIT/tool/ctdb.nodestatus.007.sh new file mode 100755 index 0000000..c96df4d --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.nodestatus.007.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all, 3 nodes, 1 unhealthy, runstate init" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 CURRENT RECMASTER + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +RUNSTATE +INIT +EOF + +required_result 64 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 UNKNOWN +pnn:1 192.168.20.42 UNKNOWN +pnn:2 192.168.20.43 OK (THIS NODE) +EOF +simple_test all + +required_result 64 <<EOF +|Node|IP|Disconnected|Unknown|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.20.41|0|1|0|0|0|0|0|0|N| +|1|192.168.20.42|0|1|0|0|0|0|0|0|N| +|2|192.168.20.43|0|0|0|0|0|0|0|0|Y| +EOF +simple_test -X all diff --git a/ctdb/tests/UNIT/tool/ctdb.pdelete.001.sh b/ctdb/tests/UNIT/tool/ctdb.pdelete.001.sh new file mode 100755 index 0000000..c0b7c17 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.pdelete.001.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "persistent delete" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test_other attach "persistent.tdb" persistent + +ok_null +simple_test_other pstore "persistent.tdb" "key1" "value1" + +ok_null +simple_test "persistent.tdb" "key1" + +ok_null +simple_test_other pfetch "persistent.tdb" "key1" + +ok "0x2" +simple_test_other getdbseqnum "persistent.tdb" diff --git a/ctdb/tests/UNIT/tool/ctdb.ping.001.sh b/ctdb/tests/UNIT/tool/ctdb.ping.001.sh new file mode 100755 index 0000000..1e6d7c1 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ping.001.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "simple ping" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +result_filter () +{ + sed -e "s@=[.0-9]* sec@=NUM sec@" +} + + +ok <<EOF +response from 0 time=NUM sec (1 clients) +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.pnn.001.sh b/ctdb/tests/UNIT/tool/ctdb.pnn.001.sh new file mode 100755 index 0000000..a492071 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.pnn.001.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "local and remote nodes" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok "0" +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.process-exists.001.sh b/ctdb/tests/UNIT/tool/ctdb.process-exists.001.sh new file mode 100755 index 0000000..d7dc3b2 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.process-exists.001.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "ctdbd process on node 0" + +ctdb_test_check_supported_OS "Linux" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +dummy_client -s $ctdbd_socket & +pid=$! + +wait_until 10 $CTDB process-exists "$pid" + +ok "PID $pid exists" +simple_test "$pid" + +kill -9 $pid + +pid=$(ctdbd_getpid) +required_result 1 "PID $pid does not exist" +simple_test "$pid" diff --git a/ctdb/tests/UNIT/tool/ctdb.process-exists.002.sh b/ctdb/tests/UNIT/tool/ctdb.process-exists.002.sh new file mode 100755 index 0000000..e432e21 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.process-exists.002.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "ctdbd process on node 0" + +ctdb_test_check_supported_OS "Linux" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +srvid="0xaebbccdd12345678" + +dummy_client -d INFO -s "$ctdbd_socket" -S "$srvid" & +pid=$! + +wait_until 10 $CTDB process-exists "$pid" + +srvid2="0x1234567812345678" +required_result 1 "PID $pid with SRVID $srvid2 does not exist" +simple_test "$pid" "$srvid2" + +ok "PID $pid with SRVID $srvid exists" +simple_test "$pid" "$srvid" + +kill -9 $pid diff --git a/ctdb/tests/UNIT/tool/ctdb.process-exists.003.sh b/ctdb/tests/UNIT/tool/ctdb.process-exists.003.sh new file mode 100755 index 0000000..6307026 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.process-exists.003.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "ctdbd process with multiple connections on node 0" + +ctdb_test_check_supported_OS "Linux" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +srvid="0xaebbccdd12345678" + +dummy_client -d INFO -s "$ctdbd_socket" -n 10 -S "$srvid" & +pid=$! + +wait_until 10 $CTDB process-exists "$pid" "$srvid" + +srvid2="0x1234567812345678" +required_result 1 "PID $pid with SRVID $srvid2 does not exist" +simple_test "$pid" "$srvid2" + +ok "PID $pid with SRVID $srvid exists" +simple_test "$pid" "$srvid" + +kill -9 $pid diff --git a/ctdb/tests/UNIT/tool/ctdb.pstore.001.sh b/ctdb/tests/UNIT/tool/ctdb.pstore.001.sh new file mode 100755 index 0000000..393b5a9 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.pstore.001.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "persistent store" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test_other attach "persistent.tdb" persistent + +ok_null +simple_test "persistent.tdb" "key1" "value1" + +ok "value1" +simple_test_other pfetch "persistent.tdb" "key1" + +ok "0x1" +simple_test_other getdbseqnum "persistent.tdb" diff --git a/ctdb/tests/UNIT/tool/ctdb.ptrans.001.sh b/ctdb/tests/UNIT/tool/ctdb.ptrans.001.sh new file mode 100755 index 0000000..40ef1a2 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.ptrans.001.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "persistent transactions" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test_other attach "persistent.tdb" persistent + +ok_null +simple_test_other pstore "persistent.tdb" "key0" "value0" + +ok_null +simple_test "persistent.tdb" <<EOF +"key1" "value1" +"key2" "value2" +"key1" "" +"key2" "value3" +EOF + +ok "value0" +simple_test_other pfetch "persistent.tdb" "key0" + +ok_null +simple_test_other pfetch "persistent.tdb" "key1" + +ok "value3" +simple_test_other pfetch "persistent.tdb" "key2" + +ok "0x2" +simple_test_other getdbseqnum "persistent.tdb" + +ok_null +simple_test "persistent.tdb" <<EOF +"key0" "value0" +EOF + +ok "value0" +simple_test_other pfetch "persistent.tdb" "key0" + +ok "0x2" +simple_test_other getdbseqnum "persistent.tdb" diff --git a/ctdb/tests/UNIT/tool/ctdb.readkey.001.sh b/ctdb/tests/UNIT/tool/ctdb.readkey.001.sh new file mode 100755 index 0000000..e2c58fd --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.readkey.001.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "volatile read" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test_other attach "volatile.tdb" + +ok <<EOF +Data: size:0 ptr:[] +EOF +simple_test "volatile.tdb" "key1" diff --git a/ctdb/tests/UNIT/tool/ctdb.recover.001.sh b/ctdb/tests/UNIT/tool/ctdb.recover.001.sh new file mode 100755 index 0000000..15e05ca --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.recover.001.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "Just a recovery" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT +1 192.168.20.42 0x0 RECMASTER +2 192.168.20.43 0x0 + +VNNMAP +654321 +0 +1 +2 +EOF + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.001.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.001.sh new file mode 100755 index 0000000..68d6cfb --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.001.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, no change" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok <<EOF +No change in nodes file, skipping unnecessary reload +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.002.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.002.sh new file mode 100755 index 0000000..570786d --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.002.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, no change, inconsistent file on 1" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_nodes 1 <<EOF +192.168.20.41 +#192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 1 <<EOF +ERROR: Nodes file on node 1 differs from current node (0) +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.003.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.003.sh new file mode 100755 index 0000000..99974d0 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.003.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, missing file on 1" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +# fake_ctdbd returns error for empty file +setup_nodes 1 <<EOF +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 1 <<EOF +Control GET_NODES_FILE failed, ret=-1 +ERROR: Failed to get nodes file from node 1 +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.011.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.011.sh new file mode 100755 index 0000000..261962e --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.011.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, add a node" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +192.168.20.44 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 0 <<EOF +Node 3 is NEW +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.012.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.012.sh new file mode 100755 index 0000000..c3ca0fe --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.012.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, delete last node" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +#192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x1 +EOF + +required_result 0 <<EOF +Node 2 is DELETED +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.013.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.013.sh new file mode 100755 index 0000000..1402b9d --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.013.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, delete connected last node" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +#192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 1 <<EOF +Node 2 is DELETED +ERROR: Node 2 is still connected +ERROR: Nodes will not be reloaded due to previous error +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.014.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.014.sh new file mode 100755 index 0000000..30e5148 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.014.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, delete first node" + +setup_nodes <<EOF +#192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x1 +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 CURRENT RECMASTER +EOF + +required_result 0 <<EOF +Node 0 is DELETED +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.015.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.015.sh new file mode 100755 index 0000000..5fad9de --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.015.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, delete connected first node" + +setup_nodes <<EOF +#192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 CURRENT RECMASTER +EOF + +required_result 1 <<EOF +Node 0 is DELETED +ERROR: Node 0 is still connected +ERROR: Nodes will not be reloaded due to previous error +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.016.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.016.sh new file mode 100755 index 0000000..d444a46 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.016.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, delete middle node" + +setup_nodes <<EOF +192.168.20.41 +#192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x1 +2 192.168.20.43 0x0 +EOF + +required_result 0 <<EOF +Node 1 is DELETED +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.017.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.017.sh new file mode 100755 index 0000000..b9a9694 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.017.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, delete connected middle node" + +setup_nodes <<EOF +192.168.20.41 +#192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 1 <<EOF +Node 1 is DELETED +ERROR: Node 1 is still connected +ERROR: Nodes will not be reloaded due to previous error +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.018.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.018.sh new file mode 100755 index 0000000..30be596 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.018.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, add a 3 nodes" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +192.168.20.44 +192.168.20.45 +192.168.20.46 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 0 <<EOF +Node 3 is NEW +Node 4 is NEW +Node 5 is NEW +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.019.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.019.sh new file mode 100755 index 0000000..5069485 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.019.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, delete middle, add 2 nodes" + +setup_nodes <<EOF +192.168.20.41 +#192.168.20.42 +192.168.20.43 +192.168.20.44 +192.168.20.45 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x1 +2 192.168.20.43 0x0 +EOF + +required_result 0 <<EOF +Node 1 is DELETED +Node 3 is NEW +Node 4 is NEW +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.020.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.020.sh new file mode 100755 index 0000000..66384c9 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.020.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, delete last, add 2 nodes" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +#192.168.20.43 +192.168.20.44 +192.168.20.45 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x1 +EOF + +required_result 0 <<EOF +Node 2 is DELETED +Node 3 is NEW +Node 4 is NEW +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.021.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.021.sh new file mode 100755 index 0000000..0f5f0d5 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.021.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, 1 disconnected, add a node" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +192.168.20.44 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x1 +2 192.168.20.43 0x0 +EOF + +required_result 0 <<EOF +WARNING: Node 1 is disconnected. You MUST fix this node manually! +Node 3 is NEW +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.023.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.023.sh new file mode 100755 index 0000000..b3823d3 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.023.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, undelete middle" + +setup_nodes <<EOF +192.168.20.41 +192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x11 +2 192.168.20.43 0x0 +EOF + +ok <<EOF +Node 1 is UNDELETED +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.reloadnodes.024.sh b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.024.sh new file mode 100755 index 0000000..9aa0d42 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.reloadnodes.024.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "3 nodes, middle node remains deleted" + +setup_nodes <<EOF +192.168.20.41 +#192.168.20.42 +192.168.20.43 +EOF + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x11 +2 192.168.20.43 0x0 +EOF + +ok <<EOF +No change in nodes file, skipping unnecessary reload +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.runstate.001.sh b/ctdb/tests/UNIT/tool/ctdb.runstate.001.sh new file mode 100755 index 0000000..d9559bd --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.runstate.001.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "get runstate, should be RUNNING" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok "RUNNING" +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.runstate.002.sh b/ctdb/tests/UNIT/tool/ctdb.runstate.002.sh new file mode 100755 index 0000000..b75b2ec --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.runstate.002.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "check if RUNNING" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok "RUNNING" +simple_test "RUNNING" diff --git a/ctdb/tests/UNIT/tool/ctdb.runstate.003.sh b/ctdb/tests/UNIT/tool/ctdb.runstate.003.sh new file mode 100755 index 0000000..eba41f8 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.runstate.003.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "check non-RUNNING states" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +for i in "INIT" "SETUP" "FIRST_RECOVERY" "STARTUP" "SHUTDOWN" ; do + required_result 1 "CTDB not in required run state (got RUNNING)" + simple_test "$i" +done diff --git a/ctdb/tests/UNIT/tool/ctdb.runstate.004.sh b/ctdb/tests/UNIT/tool/ctdb.runstate.004.sh new file mode 100755 index 0000000..666e84d --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.runstate.004.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "check invalid state" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 1 "Invalid run state (foobar)" +simple_test "foobar" diff --git a/ctdb/tests/UNIT/tool/ctdb.runstate.005.sh b/ctdb/tests/UNIT/tool/ctdb.runstate.005.sh new file mode 100755 index 0000000..972783c --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.runstate.005.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "check from multiple states" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok "RUNNING" +simple_test "STARTUP" "RUNNING" diff --git a/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.001.sh b/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.001.sh new file mode 100755 index 0000000..0a0cfe2 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.001.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set volatile non-read-only to read-only by ID" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb +0x4e66c2b2 brlock.tdb +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +ok_null +simple_test 0x7a19d84d + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb READONLY +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap + +ok_null +simple_test 0x7a19d84d + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb READONLY +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap diff --git a/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.002.sh b/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.002.sh new file mode 100755 index 0000000..246fb60 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.002.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set volatile non-read-only to read-only by name" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb +0x4e66c2b2 brlock.tdb +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +ok_null +simple_test locking.tdb + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb READONLY +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap diff --git a/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.003.sh b/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.003.sh new file mode 100755 index 0000000..3a11c79 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.003.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set persistent read-only by name" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb +0x4e66c2b2 brlock.tdb +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +required_result 1 <<EOF +READONLY can be set only on volatile DB +EOF +simple_test secrets.tdb + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap diff --git a/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.004.sh b/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.004.sh new file mode 100755 index 0000000..5d6561d --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.004.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set volatile sticky to sticky and read-only by name" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb STICKY +0x4e66c2b2 brlock.tdb +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +ok_null +simple_test locking.tdb + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb STICKY READONLY +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap diff --git a/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.005.sh b/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.005.sh new file mode 100755 index 0000000..ae336dd --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdbreadonly.005.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set replicated read-only by name" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb +0x4e66c2b2 brlock.tdb +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +required_result 1 <<EOF +READONLY can be set only on volatile DB +EOF +simple_test ctdb-ip.tdb + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap diff --git a/ctdb/tests/UNIT/tool/ctdb.setdbsticky.001.sh b/ctdb/tests/UNIT/tool/ctdb.setdbsticky.001.sh new file mode 100755 index 0000000..28cbfd7 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdbsticky.001.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set volatile non-sticky to sticky by ID" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb +0x4e66c2b2 brlock.tdb +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +ok_null +simple_test 0x4e66c2b2 + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb STICKY +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap + +ok_null +simple_test 0x4e66c2b2 + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb STICKY +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap diff --git a/ctdb/tests/UNIT/tool/ctdb.setdbsticky.002.sh b/ctdb/tests/UNIT/tool/ctdb.setdbsticky.002.sh new file mode 100755 index 0000000..1c39f54 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdbsticky.002.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set volatile non-sticky to sticky by name" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb +0x4e66c2b2 brlock.tdb +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +ok_null +simple_test brlock.tdb + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb STICKY +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap diff --git a/ctdb/tests/UNIT/tool/ctdb.setdbsticky.003.sh b/ctdb/tests/UNIT/tool/ctdb.setdbsticky.003.sh new file mode 100755 index 0000000..206fed9 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdbsticky.003.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set persistent sticky by name" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb +0x4e66c2b2 brlock.tdb +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +required_result 1 <<EOF +STICKY can be set only on volatile DB +EOF +simple_test secrets.tdb + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap diff --git a/ctdb/tests/UNIT/tool/ctdb.setdbsticky.004.sh b/ctdb/tests/UNIT/tool/ctdb.setdbsticky.004.sh new file mode 100755 index 0000000..a322a57 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdbsticky.004.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set volatile read-only to read-only and sticky by name" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb +0x4e66c2b2 brlock.tdb READONLY +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +ok_null +simple_test brlock.tdb + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb STICKY READONLY +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap diff --git a/ctdb/tests/UNIT/tool/ctdb.setdbsticky.005.sh b/ctdb/tests/UNIT/tool/ctdb.setdbsticky.005.sh new file mode 100755 index 0000000..9a9bec1 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdbsticky.005.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "set replicated sticky by name" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +DBMAP +0x7a19d84d locking.tdb +0x4e66c2b2 brlock.tdb +0x4d2a432b g_lock.tdb +0x7132c184 secrets.tdb PERSISTENT +0x6cf2837d registry.tdb PERSISTENT 42 +0xbc57b384 ctdb-ip.tdb REPLICATED +0xbec75f0b ctdb-conn.tdb REPLICATED 23 +EOF + +required_result 1 <<EOF +STICKY can be set only on volatile DB +EOF +simple_test ctdb-ip.tdb + +ok <<EOF +Number of databases:7 +dbid:0x7a19d84d name:locking.tdb path:${ctdbd_dbdir}/locking.tdb +dbid:0x4e66c2b2 name:brlock.tdb path:${ctdbd_dbdir}/brlock.tdb +dbid:0x4d2a432b name:g_lock.tdb path:${ctdbd_dbdir}/g_lock.tdb +dbid:0x7132c184 name:secrets.tdb path:${ctdbd_dbdir}/secrets.tdb PERSISTENT +dbid:0x6cf2837d name:registry.tdb path:${ctdbd_dbdir}/registry.tdb PERSISTENT +dbid:0xbc57b384 name:ctdb-ip.tdb path:${ctdbd_dbdir}/ctdb-ip.tdb REPLICATED +dbid:0xbec75f0b name:ctdb-conn.tdb path:${ctdbd_dbdir}/ctdb-conn.tdb REPLICATED +EOF + +simple_test_other getdbmap diff --git a/ctdb/tests/UNIT/tool/ctdb.setdebug.001.sh b/ctdb/tests/UNIT/tool/ctdb.setdebug.001.sh new file mode 100755 index 0000000..bec32a3 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdebug.001.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "bogus debug level string, ensure no change" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +orig=$($CTDB -d $CTDB_DEBUGLEVEL getdebug) + +required_result 1 <<EOF +Invalid debug level 'foobar'. Valid levels are: + ERROR | WARNING | NOTICE | INFO | DEBUG +EOF +simple_test foobar + +ok "$orig" +simple_test_other getdebug diff --git a/ctdb/tests/UNIT/tool/ctdb.setdebug.002.sh b/ctdb/tests/UNIT/tool/ctdb.setdebug.002.sh new file mode 100755 index 0000000..7819b0b --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdebug.002.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "bogus debug level integer, ensure no change" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +orig=$($CTDB -d $CTDB_DEBUGLEVEL getdebug) + +required_result 1 <<EOF +Invalid debug level '42'. Valid levels are: + ERROR | WARNING | NOTICE | INFO | DEBUG +EOF +simple_test 42 + +ok "$orig" +simple_test_other getdebug diff --git a/ctdb/tests/UNIT/tool/ctdb.setdebug.003.sh b/ctdb/tests/UNIT/tool/ctdb.setdebug.003.sh new file mode 100755 index 0000000..2a8be18 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setdebug.003.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all possible legal levels, including some abbreviations" + +debug_set_result () +{ + case "$1" in + 0|ERR*) ok "ERROR" ;; + 1|2|WARN*) ok "WARNING" ;; + 3|4|NOTICE) ok "NOTICE" ;; + 5|6|7|8|9|INFO) ok "INFO" ;; + 10|DEBUG) ok "DEBUG" ;; + *) required_result 42 "foo" ;; + esac +} + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +for i in "ERROR" "WARNING" "NOTICE" "INFO" "DEBUG" $(seq 0 10) "ERR" "WARN" ; do + ok_null + simple_test "$i" + + debug_set_result "$i" + simple_test_other getdebug +done diff --git a/ctdb/tests/UNIT/tool/ctdb.setifacelink.001.sh b/ctdb/tests/UNIT/tool/ctdb.setifacelink.001.sh new file mode 100755 index 0000000..53104cf --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setifacelink.001.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "toggle state of 2 interfaces" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:0:4: +EOF + +# eth1: down -> down + +ok_null +simple_test eth1 down + +ok <<EOF +Interfaces on node 0 +name:eth2 link:up references:2 +name:eth1 link:down references:4 +EOF +simple_test_other ifaces + +# eth1: down -> up + +ok_null +simple_test eth1 up + +ok <<EOF +Interfaces on node 0 +name:eth2 link:up references:2 +name:eth1 link:up references:4 +EOF +simple_test_other ifaces + +# eth1: up -> down +ok_null +simple_test eth1 down + +ok <<EOF +Interfaces on node 0 +name:eth2 link:up references:2 +name:eth1 link:down references:4 +EOF +simple_test_other ifaces + +# eth2: up -> down + +ok_null +simple_test eth2 down + +ok <<EOF +Interfaces on node 0 +name:eth2 link:down references:2 +name:eth1 link:down references:4 +EOF +simple_test_other ifaces + +# eth1: down -> up + +ok_null +simple_test eth1 up + +ok <<EOF +Interfaces on node 0 +name:eth2 link:down references:2 +name:eth1 link:up references:4 +EOF +simple_test_other ifaces diff --git a/ctdb/tests/UNIT/tool/ctdb.setifacelink.002.sh b/ctdb/tests/UNIT/tool/ctdb.setifacelink.002.sh new file mode 100755 index 0000000..a27062e --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setifacelink.002.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "invalid interface" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:0:4: +EOF + +required_result 1 <<EOF +Interface eth0 not configured on node 0 +EOF +simple_test eth0 down diff --git a/ctdb/tests/UNIT/tool/ctdb.setvar.001.sh b/ctdb/tests/UNIT/tool/ctdb.setvar.001.sh new file mode 100755 index 0000000..e11ff9c --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setvar.001.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "get a variable, change its value" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +# Squash whitespace for predictable output +result_filter () +{ + sed -e 's|[[:space:]][[:space:]]*| |g' +} + +$CTDB -d $CTDB_DEBUGLEVEL listvars | + tail -n 1 | + { + read variable equals value + + # Increment original variable + newvalue=$((value + 1)) + ok_null + simple_test "$variable" "$newvalue" + + ok "${variable} = ${newvalue}" + simple_test_other getvar "$variable" + + # Increment uppercase variable + v_upper=$(echo "$variable" | tr "a-z" "A-Z") + newvalue=$((newvalue + 1)) + ok_null + simple_test "$v_upper" "$newvalue" + + ok "${variable} = ${newvalue}" + simple_test_other getvar "$variable" + + # Put back original, lowercase + v_lower=$(echo "$variable" | tr "A-Z" "a-z") + ok_null + simple_test "$v_lower" "$value" + + ok "${variable} = ${value}" + simple_test_other getvar "$variable" + } diff --git a/ctdb/tests/UNIT/tool/ctdb.setvar.002.sh b/ctdb/tests/UNIT/tool/ctdb.setvar.002.sh new file mode 100755 index 0000000..bf788a2 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.setvar.002.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "invalid variable" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +required_result 1 <<EOF +No such tunable TheQuickBrownFoxJumpsOverTheLazyDog +EOF +simple_test "TheQuickBrownFoxJumpsOverTheLazyDog" 42 diff --git a/ctdb/tests/UNIT/tool/ctdb.status.001.sh b/ctdb/tests/UNIT/tool/ctdb.status.001.sh new file mode 100755 index 0000000..62c1dc7 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.status.001.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all, 3 nodes, all ok" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +VNNMAP +654321 +0 +1 +2 +EOF + +required_result 0 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +Generation:654321 +Size:3 +hash:0 lmaster:0 +hash:1 lmaster:1 +hash:2 lmaster:2 +Recovery mode:NORMAL (0) +Leader:0 +EOF +simple_test + +required_result 0 <<EOF +|Node|IP|Disconnected|Unknown|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.20.41|0|0|0|0|0|0|0|0|Y| +|1|192.168.20.42|0|0|0|0|0|0|0|0|N| +|2|192.168.20.43|0|0|0|0|0|0|0|0|N| +EOF +simple_test -X diff --git a/ctdb/tests/UNIT/tool/ctdb.status.002.sh b/ctdb/tests/UNIT/tool/ctdb.status.002.sh new file mode 100755 index 0000000..0cce443 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.status.002.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all, 3 nodes, 1 unhealthy" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 +1 192.168.20.42 0x0 CURRENT RECMASTER +2 192.168.20.43 0x0 + +VNNMAP +654321 +0 +1 +2 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: +EOF + +required_result 0 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 UNHEALTHY +pnn:1 192.168.20.42 OK (THIS NODE) +pnn:2 192.168.20.43 OK +Generation:654321 +Size:3 +hash:0 lmaster:0 +hash:1 lmaster:1 +hash:2 lmaster:2 +Recovery mode:NORMAL (0) +Leader:1 +EOF +simple_test + +required_result 0 <<EOF +|Node|IP|Disconnected|Unknown|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.20.41|0|0|0|0|1|0|0|0|N| +|1|192.168.20.42|0|0|0|0|0|0|0|0|Y| +|2|192.168.20.43|0|0|0|0|0|0|0|0|N| +EOF +simple_test -X diff --git a/ctdb/tests/UNIT/tool/ctdb.status.003.sh b/ctdb/tests/UNIT/tool/ctdb.status.003.sh new file mode 100755 index 0000000..67a2966 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.status.003.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "all, 3 nodes, 1 unhealthy" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x2 +1 192.168.20.42 0x0 CURRENT RECMASTER +2 192.168.20.43 0x0 + +VNNMAP +654321 +0 +1 +2 + +IFACES +:Name:LinkStatus:References: +:eth2:1:2: +:eth1:1:4: + +RUNSTATE +FIRST_RECOVERY +EOF + +required_result 0 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 UNKNOWN +pnn:1 192.168.20.42 OK (THIS NODE) +pnn:2 192.168.20.43 UNKNOWN +Generation:654321 +Size:3 +hash:0 lmaster:0 +hash:1 lmaster:1 +hash:2 lmaster:2 +Recovery mode:NORMAL (0) +Leader:1 +EOF +simple_test + +required_result 0 <<EOF +|Node|IP|Disconnected|Unknown|Banned|Disabled|Unhealthy|Stopped|Inactive|PartiallyOnline|ThisNode| +|0|192.168.20.41|0|1|0|0|0|0|0|0|N| +|1|192.168.20.42|0|0|0|0|0|0|0|0|Y| +|2|192.168.20.43|0|1|0|0|0|0|0|0|N| +EOF +simple_test -X diff --git a/ctdb/tests/UNIT/tool/ctdb.stop.001.sh b/ctdb/tests/UNIT/tool/ctdb.stop.001.sh new file mode 100755 index 0000000..d374ebf --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.stop.001.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "stop default (0)" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test + +required_result 32 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 STOPPED|INACTIVE (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.stop.002.sh b/ctdb/tests/UNIT/tool/ctdb.stop.002.sh new file mode 100755 index 0000000..f8cc792 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.stop.002.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "stop 1" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test -n 1 + +required_result 32 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 STOPPED|INACTIVE +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.stop.003.sh b/ctdb/tests/UNIT/tool/ctdb.stop.003.sh new file mode 100755 index 0000000..3e4981c --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.stop.003.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "node is already stopped" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x20 +EOF + +ok "Node 2 is already stopped" +simple_test -n 2 + +required_result 32 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 STOPPED|INACTIVE +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.unban.001.sh b/ctdb/tests/UNIT/tool/ctdb.unban.001.sh new file mode 100755 index 0000000..c771fb4 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.unban.001.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "unban default (0)" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x8 CURRENT +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 RECMASTER +EOF + +ok_null +simple_test + +ok <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.unban.002.sh b/ctdb/tests/UNIT/tool/ctdb.unban.002.sh new file mode 100755 index 0000000..b65143d --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.unban.002.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "ban, unban node 1" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test_other ban 60 -n 1 + +required_result 8 <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 BANNED|INACTIVE +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all + +ok_null +simple_test_other unban -n 1 + +ok <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.unban.003.sh b/ctdb/tests/UNIT/tool/ctdb.unban.003.sh new file mode 100755 index 0000000..8b94f30 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.unban.003.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "node not banned" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok "Node 0 is not banned" +simple_test + +ok <<EOF +Number of nodes:3 +pnn:0 192.168.20.41 OK (THIS NODE) +pnn:1 192.168.20.42 OK +pnn:2 192.168.20.43 OK +EOF +simple_test_other nodestatus all diff --git a/ctdb/tests/UNIT/tool/ctdb.uptime.001.sh b/ctdb/tests/UNIT/tool/ctdb.uptime.001.sh new file mode 100755 index 0000000..34fd1f4 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.uptime.001.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "simple ping" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +result_filter () +{ + _weekday="[A-Z][a-z][a-z]" + _month="[A-Z][a-z][a-z]" + _date="[0-9][0-9]*" + _time="[0-9][0-9]:[0-9][0-9]:[0-9][0-9]" + _year="[0-9][0-9]*" + _date_time="${_weekday} ${_month} *${_date} ${_time} ${_year}" + _duration="(000 00:00:[0-9][0-9])" + sed -e "s|${_date_time}\$|DATE/TIME|" \ + -e "s|[.0-9]* seconds|SEC seconds|" \ + -e "s|${_duration}|(DURATION)|" +} + + +ok <<EOF +Current time of node 0 : DATE/TIME +Ctdbd start time : (DURATION) DATE/TIME +Time of last recovery/failover: (DURATION) DATE/TIME +Duration of last recovery/failover: SEC seconds +EOF + +simple_test diff --git a/ctdb/tests/UNIT/tool/ctdb.writekey.001.sh b/ctdb/tests/UNIT/tool/ctdb.writekey.001.sh new file mode 100755 index 0000000..7adee9f --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.writekey.001.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "volatile write" + +setup_ctdbd <<EOF +NODEMAP +0 192.168.20.41 0x0 CURRENT RECMASTER +1 192.168.20.42 0x0 +2 192.168.20.43 0x0 +EOF + +ok_null +simple_test_other attach "volatile.tdb" + +ok_null +simple_test "volatile.tdb" "key1" "value1" + +ok <<EOF +Data: size:6 ptr:[value1] +EOF +simple_test_other readkey "volatile.tdb" "key1" + +ok_null +simple_test "volatile.tdb" "key1" "a new value" + +ok <<EOF +Data: size:11 ptr:[a new value] +EOF +simple_test_other readkey "volatile.tdb" "key1" diff --git a/ctdb/tests/UNIT/tool/scripts/local.sh b/ctdb/tests/UNIT/tool/scripts/local.sh new file mode 100644 index 0000000..618fa36 --- /dev/null +++ b/ctdb/tests/UNIT/tool/scripts/local.sh @@ -0,0 +1,112 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +PATH="${PATH}:${CTDB_SCRIPTS_TOOLS_HELPER_DIR}" +PATH="${PATH}:${CTDB_SCRIPTS_HELPER_BINDIR}" + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "ctdb-etc" \ + functions + +if "$CTDB_TEST_VERBOSE" ; then + debug () { echo "$@" ; } +else + debug () { : ; } +fi + +ctdbd_socket=$(ctdb-path socket "ctdbd") +ctdbd_pidfile=$(ctdb-path pidfile "ctdbd") +ctdbd_dbdir=$(ctdb-path vardir append "db") + +define_test () +{ + _f=$(basename "$0" ".sh") + + case "$_f" in + ctdb.*) + _cmd="${_f#ctdb.}" + _cmd="${_cmd%.*}" # Strip test number + export CTDB="ctdb" + export CTDB_DEBUGLEVEL=NOTICE + if [ -z "$FAKE_CTDBD_DEBUGLEVEL" ] ; then + FAKE_CTDBD_DEBUGLEVEL="ERR" + fi + export FAKE_CTDBD_DEBUGLEVEL + test_args="$_cmd" + ;; + *) + die "Unknown pattern for testcase \"$_f\"" + esac + + printf "%-28s - %s\n" "$_f" "$1" +} + +cleanup_ctdbd () +{ + debug "Cleaning up fake ctdbd" + + pid=$(cat "$ctdbd_pidfile" 2>/dev/null || echo) + if [ -n "$pid" ] ; then + kill $pid || true + rm -f "$ctdbd_pidfile" + fi + rm -f "$ctdbd_socket" + rm -rf "$ctdbd_dbdir" +} + +setup_ctdbd () +{ + echo "Setting up fake ctdbd" + + mkdir -p "$ctdbd_dbdir" + $VALGRIND fake_ctdbd -d "$FAKE_CTDBD_DEBUGLEVEL" \ + -s "$ctdbd_socket" -p "$ctdbd_pidfile" \ + -D "$ctdbd_dbdir" + # Wait till fake_ctdbd is running + wait_until 10 test -S "$ctdbd_socket" || \ + die "fake_ctdbd failed to start" + + test_cleanup cleanup_ctdbd +} + +ctdbd_getpid () +{ + cat "$ctdbd_pidfile" +} + +setup_natgw () +{ + debug "Setting up NAT gateway" + + export CTDB_NATGW_HELPER="${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/ctdb_natgw" + export CTDB_NATGW_NODES="${CTDB_BASE}/natgw_nodes" + + cat >"$CTDB_NATGW_NODES" +} + +setup_lvs () +{ + debug "Setting up LVS" + + export CTDB_LVS_HELPER="${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/ctdb_lvs" + export CTDB_LVS_NODES="${CTDB_BASE}/lvs_nodes" + + cat >"$CTDB_LVS_NODES" +} + +setup_nodes () +{ + _pnn="$1" + + _f="${CTDB_BASE}/nodes${_pnn:+.}${_pnn}" + + cat >"$_f" +} + +simple_test_other () +{ + unit_test $CTDB -d $CTDB_DEBUGLEVEL "$@" +} + +simple_test () +{ + simple_test_other $test_args "$@" +} diff --git a/ctdb/tests/etc-ctdb/events/legacy/00.test.script b/ctdb/tests/etc-ctdb/events/legacy/00.test.script new file mode 100755 index 0000000..c6797da --- /dev/null +++ b/ctdb/tests/etc-ctdb/events/legacy/00.test.script @@ -0,0 +1,30 @@ +#!/bin/sh +# event script for 'make test' + +. "${CTDB_BASE}/functions" + +load_script_options + +ctdb_check_args "$@" + +event="$1" +shift + +case "$event" in +monitor) + if [ "$CTDB_RUN_TIMEOUT_MONITOR" = "yes" ] ; then + timeout=9999 + echo "Sleeping for ${timeout} seconds..." + sleep $timeout + fi + ;; + +startup) + ifaces=$(ctdb ifaces -X | tail -n +2 | cut -d '|' -f2) + for i in $ifaces; do + ctdb setifacelink "$i" up + done + ;; +esac + +echo "${event} event${*:+ for }$*" diff --git a/ctdb/tests/local_daemons.sh b/ctdb/tests/local_daemons.sh new file mode 100755 index 0000000..b474668 --- /dev/null +++ b/ctdb/tests/local_daemons.sh @@ -0,0 +1,506 @@ +#!/bin/sh + +set -u + +export CTDB_TEST_MODE="yes" + +# Following 2 lines may be modified by installation script +CTDB_TESTS_ARE_INSTALLED=false +CTDB_TEST_DIR=$(dirname "$0") +export CTDB_TESTS_ARE_INSTALLED CTDB_TEST_DIR + +export TEST_SCRIPTS_DIR="${CTDB_TEST_DIR}/scripts" + +. "${TEST_SCRIPTS_DIR}/common.sh" + +if ! $CTDB_TESTS_ARE_INSTALLED ; then + hdir="$CTDB_SCRIPTS_HELPER_BINDIR" + export CTDB_EVENTD="${hdir}/ctdb-eventd" + export CTDB_EVENT_HELPER="${hdir}/ctdb-event" + export CTDB_LOCK_HELPER="${hdir}/ctdb_lock_helper" + export CTDB_RECOVERY_HELPER="${hdir}/ctdb_recovery_helper" + export CTDB_TAKEOVER_HELPER="${hdir}/ctdb_takeover_helper" + export CTDB_CLUSTER_MUTEX_HELPER="${hdir}/ctdb_mutex_fcntl_helper" +fi + +######################################## + +# If the given IP is hosted then print 2 items: maskbits and iface +have_ip () +{ + _addr="$1" + + case "$_addr" in + *:*) _bits=128 ;; + *) _bits=32 ;; + esac + + _t=$(ip addr show to "${_addr}/${_bits}") + [ -n "$_t" ] +} + +setup_nodes () +{ + _num_nodes="$1" + _use_ipv6="$2" + + _have_all_ips=true + for _i in $(seq 0 $((_num_nodes - 1)) ) ; do + if $_use_ipv6 ; then + _j=$(printf "%04x" $((0x5f00 + 1 + _i)) ) + _node_ip="fd00::5357:${_j}" + if have_ip "$_node_ip" ; then + echo "$_node_ip" + else + cat >&2 <<EOF +ERROR: ${_node_ip} not on an interface, please add it +EOF + _have_all_ips=false + fi + else + _c=$(( _i / 100 )) + _d=$(( 1 + (_i % 100) )) + echo "127.0.${_c}.${_d}" + fi + done + + # Fail if we don't have all of the IPv6 addresses assigned + $_have_all_ips +} + +setup_public_addresses () +{ + _num_nodes="$1" + _node_no_ips="$2" + _use_ipv6="$3" + + for _i in $(seq 0 $((_num_nodes - 1)) ) ; do + if [ "$_i" -eq "$_node_no_ips" ] ; then + continue + fi + + # 2 public addresses on most nodes, just to make + # things interesting + if $_use_ipv6 ; then + printf 'fc00:10::1:%x/64 lo\n' $((1 + _i)) + printf 'fc00:10::2:%x/64 lo\n' $((1 + _i)) + else + _c1=$(( 100 + (_i / 100) )) + _c2=$(( 200 + (_i / 100) )) + _d=$(( 1 + (_i % 100) )) + printf '192.168.%d.%d/24 lo\n' "$_c1" "$_d" + printf '192.168.%d.%d/24 lo\n' "$_c2" "$_d" + fi + done +} + +setup_socket_wrapper () +{ + _socket_wrapper_so="$1" + + _so="${directory}/libsocket-wrapper.so" + if [ ! -f "$_socket_wrapper_so" ] ; then + die "$0 setup: Unable to find ${_socket_wrapper_so}" + fi + + # Find absolute path if only relative is given + case "$_socket_wrapper_so" in + /*) : ;; + *) _socket_wrapper_so="${PWD}/${_socket_wrapper_so}" ;; + esac + + rm -f "$_so" + ln -s "$_socket_wrapper_so" "$_so" + + _d="${directory}/sw" + rm -rf "$_d" + mkdir -p "$_d" +} + +local_daemons_setup_usage () +{ + cat >&2 <<EOF +$0 <directory> setup [ <options>... ] + +Options: + -C Comment out given config item (default: item uncommented) + -F Disable failover (default: failover enabled) + -N <file> Nodes file (default: automatically generated) + -n <num> Number of nodes (default: 3) + -P <file> Public addresses file (default: automatically generated) + -R Use a command for the cluster lock (default: use a file) + -r <time> Like -R and set recheck interval to <time> (default: use a file) + -S <library> Socket wrapper shared library to preload (default: none) + -6 Generate IPv6 IPs for nodes, public addresses (default: IPv4) +EOF + + exit 1 +} + +local_daemons_setup () +{ + _commented_config="" + _disable_failover=false + _nodes_file="" + _num_nodes=3 + _public_addresses_file="" + _cluster_lock_use_command=false + _cluster_lock_recheck_interval="" + _socket_wrapper="" + _use_ipv6=false + + set -e + + while getopts "C:FN:n:P:Rr:S:6h?" _opt ; do + case "$_opt" in + C) _t="${_commented_config}${_commented_config:+|}" + _commented_config="${_t}${OPTARG}" + ;; + F) _disable_failover=true ;; + N) _nodes_file="$OPTARG" ;; + n) _num_nodes="$OPTARG" ;; + P) _public_addresses_file="$OPTARG" ;; + R) _cluster_lock_use_command=true ;; + r) _cluster_lock_use_command=true + _cluster_lock_recheck_interval="$OPTARG" + ;; + S) _socket_wrapper="$OPTARG" ;; + 6) _use_ipv6=true ;; + \?|h) local_daemons_setup_usage ;; + esac + done + shift $((OPTIND - 1)) + + mkdir -p "$directory" + + _nodes_all="${directory}/nodes" + if [ -n "$_nodes_file" ] ; then + cp "$_nodes_file" "$_nodes_all" + else + setup_nodes "$_num_nodes" $_use_ipv6 >"$_nodes_all" + fi + + # If there are (strictly) greater than 2 nodes then we'll + # "randomly" choose a node to have no public addresses + _node_no_ips=-1 + if [ "$_num_nodes" -gt 2 ] ; then + _node_no_ips=$(($$ % _num_nodes)) + fi + + _public_addresses_all="${directory}/public_addresses" + if [ -n "$_public_addresses_file" ] ; then + cp "$_public_addresses_file" "$_public_addresses_all" + else + setup_public_addresses "$_num_nodes" \ + $_node_no_ips \ + "$_use_ipv6" >"$_public_addresses_all" + fi + + _cluster_lock_dir="${directory}/shared/.ctdb" + mkdir -p "$_cluster_lock_dir" + _cluster_lock="${_cluster_lock_dir}/cluster.lock" + if $_cluster_lock_use_command ; then + _helper="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb_mutex_fcntl_helper" + _t="! ${_helper} ${_cluster_lock}" + if [ -n "$_cluster_lock_recheck_interval" ] ; then + _t="${_t} ${_cluster_lock_recheck_interval}" + fi + _cluster_lock="$_t" + fi + + if [ -n "$_socket_wrapper" ] ; then + setup_socket_wrapper "$_socket_wrapper" + fi + + for _n in $(seq 0 $((_num_nodes - 1))) ; do + # CTDB_TEST_SUITE_DIR needs to be correctly set so + # setup_ctdb_base() finds the etc-ctdb/ subdirectory + # and the test event script is correctly installed + # shellcheck disable=SC2034 + CTDB_TEST_SUITE_DIR="$CTDB_TEST_DIR" \ + setup_ctdb_base "$directory" "node.${_n}" \ + functions notify.sh debug-hung-script.sh + + cp "$_nodes_all" "${CTDB_BASE}/nodes" + + _public_addresses="${CTDB_BASE}/public_addresses" + + if [ -z "$_public_addresses_file" ] && \ + [ "$_node_no_ips" -eq "$_n" ] ; then + echo "Node ${_n} will have no public IPs." + : >"$_public_addresses" + else + cp "$_public_addresses_all" "$_public_addresses" + fi + + _node_ip=$(sed -n -e "$((_n + 1))p" "$_nodes_all") + + _db_dir="${CTDB_BASE}/db" + for _d in "volatile" "persistent" "state" ; do + mkdir -p "${_db_dir}/${_d}" + done + + cat >"${CTDB_BASE}/ctdb.conf" <<EOF +[logging] + location = file:${CTDB_BASE}/log.ctdb + log level = INFO + +[cluster] + cluster lock = ${_cluster_lock} + node address = ${_node_ip} + +[database] + volatile database directory = ${_db_dir}/volatile + persistent database directory = ${_db_dir}/persistent + state database directory = ${_db_dir}/state + +[failover] + disabled = ${_disable_failover} + +[event] + debug script = debug-hung-script.sh +EOF + + ( + IFS='|' + for _c in $_commented_config ; do + # Quote all backslashes due to double-quotes + sed -i -e "s|^\\t\\(${_c}\\) = |\\t# \\1 = |" \ + "${CTDB_BASE}/ctdb.conf" + done + ) + done +} + +local_daemons_ssh_usage () +{ + cat >&2 <<EOF +usage: $0 <directory> ssh [ -n ] <ip> <command> +EOF + + exit 1 +} + +local_daemons_ssh () +{ + if [ $# -lt 2 ] ; then + local_daemons_ssh_usage + fi + + # Only try to respect ssh -n option, others can't be used so discard them + _close_stdin=false + while getopts "nh?" _opt ; do + case "$_opt" in + n) _close_stdin=true ;; + \?|h) local_daemons_ssh_usage ;; + *) : ;; + esac + done + shift $((OPTIND - 1)) + + if [ $# -lt 2 ] ; then + local_daemons_ssh_usage + fi + + _nodes="${directory}/nodes" + + # IP address of node. onnode can pass hostnames but not in these tests + _ip="$1" ; shift + # "$*" is command + + + # Determine the correct CTDB base directory + _num=$(awk -v ip="$_ip" '$1 == ip { print NR }' "$_nodes") + _node=$((_num - 1)) + export CTDB_BASE="${directory}/node.${_node}" + + if [ ! -d "$CTDB_BASE" ] ; then + die "$0 ssh: Unable to find base for node ${_ip}" + fi + + if $_close_stdin ; then + exec sh -c "$*" </dev/null + else + exec sh -c "$*" + fi +} + +onnode_common () +{ + # onnode will execute this, which fakes ssh against local daemons + export ONNODE_SSH="${0} ${directory} ssh" + + # onnode just needs the nodes file, so use the common one + export CTDB_BASE="$directory" +} + +local_daemons_generic_usage () +{ + cat >&2 <<EOF +usage: $0 <directory> ${1} <nodes> + +<nodes> can be "all", a node number or any specification supported by onnode +EOF + + exit 1 +} + +local_daemons_start_socket_wrapper () +{ + _so="${directory}/libsocket-wrapper.so" + _d="${directory}/sw" + + if [ -d "$_d" ] && [ -f "$_so" ] ; then + export SOCKET_WRAPPER_DIR="$_d" + export LD_PRELOAD="$_so" + export SOCKET_WRAPPER_DIR_ALLOW_ORIG="1" + fi +} + +local_daemons_start () +{ + if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then + local_daemons_generic_usage "start" + fi + + local_daemons_start_socket_wrapper + + _nodes="$1" + + onnode_common + + onnode -i "$_nodes" "${VALGRIND:-} ctdbd" +} + +local_daemons_stop () +{ + if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then + local_daemons_generic_usage "stop" + fi + + _nodes="$1" + + onnode_common + + onnode -p "$_nodes" \ + "if [ -e \"\${CTDB_BASE}/run/ctdbd.pid\" ] ; then \ + ${CTDB:-${VALGRIND:-} ctdb} shutdown ; \ + fi" +} + +local_daemons_onnode_usage () +{ + cat >&2 <<EOF +usage: $0 <directory> onnode <nodes> <command>... + +<nodes> can be "all", a node number or any specification supported by onnode +EOF + + exit 1 +} + +local_daemons_onnode () +{ + if [ $# -lt 2 ] || [ "$1" = "-h" ] ; then + local_daemons_onnode_usage + fi + + _nodes="$1" + shift + + onnode_common + + onnode "$_nodes" "$@" +} + +local_daemons_print_socket () +{ + if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then + local_daemons_generic_usage "print-socket" + fi + + _nodes="$1" + shift + + onnode_common + + _path="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb-path" + onnode -q "$_nodes" "${VALGRIND:-} ${_path} socket ctdbd" +} + +local_daemons_print_log () +{ + if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then + local_daemons_generic_usage "print-log" + fi + + _nodes="$1" + shift + + onnode_common + + # shellcheck disable=SC2016 + # $CTDB_BASE must only be expanded under onnode, not in top-level shell + onnode -q "$_nodes" 'cat ${CTDB_BASE}/log.ctdb' | + sort + +} + +local_daemons_tail_log () +{ + if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then + local_daemons_generic_usage "tail-log" + fi + + _nodes="$1" + shift + + onnode_common + + # shellcheck disable=SC2016,SC2046 + # $CTDB_BASE must only be expanded under onnode, not in top-level shell + # Intentional word splitting to separate log filenames + tail -f $(onnode -q "$_nodes" 'echo ${CTDB_BASE}/log.ctdb') +} + +usage () +{ + cat <<EOF +usage: $0 <directory> <command> [ <options>... ] + +Commands: + setup Set up daemon configuration according to given options + start Start specified daemon(s) + stop Stop specified daemon(s) + onnode Run a command in the environment of specified daemon(s) + print-socket Print the Unix domain socket used by specified daemon(s) + print-log Print logs for specified daemon(s) to stdout + tail-log Follow logs for specified daemon(s) to stdout + +All commands use <directory> for daemon configuration + +Run command with -h option to see per-command usage +EOF + + exit 1 +} + +if [ $# -lt 2 ] ; then + usage +fi + +directory="$1" +command="$2" +shift 2 + +case "$command" in +setup) local_daemons_setup "$@" ;; +ssh) local_daemons_ssh "$@" ;; # Internal, not shown by usage() +start) local_daemons_start "$@" ;; +stop) local_daemons_stop "$@" ;; +onnode) local_daemons_onnode "$@" ;; +print-socket) local_daemons_print_socket "$@" ;; +print-log) local_daemons_print_log "$@" ;; +tail-log) local_daemons_tail_log "$@" ;; +*) usage ;; +esac diff --git a/ctdb/tests/run_cluster_tests.sh b/ctdb/tests/run_cluster_tests.sh new file mode 120000 index 0000000..5236e32 --- /dev/null +++ b/ctdb/tests/run_cluster_tests.sh @@ -0,0 +1 @@ +run_tests.sh
\ No newline at end of file diff --git a/ctdb/tests/run_tests.sh b/ctdb/tests/run_tests.sh new file mode 100755 index 0000000..dfe2a9a --- /dev/null +++ b/ctdb/tests/run_tests.sh @@ -0,0 +1,399 @@ +#!/usr/bin/env bash + +usage() { + cat <<EOF +Usage: $0 [OPTIONS] [TESTS] + +Options: + -A Use "cat -A" to print test output (only some tests) + -c Run integration tests on a cluster + -C Clean up when done by removing test state directory (see -V) + -D Show diff between failed/expected test output (some tests only) + -e Exit on the first test failure + -H No headers - for running single test with other wrapper + -I <count> Iterate tests <count> times, exiting on failure (implies -e, -N) + -l <count> Use <count> daemons for local daemon integration tests + -L Print daemon logs on test failure (only some tests) + -N Don't print summary of tests results after running all tests + -q Quiet - don't show tests being run (still displays summary) + -S <lib> Use socket wrapper library <lib> for local integration tests + -v Verbose - print test output for non-failures (only some tests) + -V <dir> Use <dir> as test state directory + -x Trace this script with the -x option + -X Trace certain scripts run by tests using -x (only some tests) +EOF + exit 1 +} + +# Print a message and exit. +die () +{ + echo "$1" >&2 ; exit "${2:-1}" +} + +###################################################################### + +with_summary=true +quiet=false +exit_on_fail=false +max_iterations=1 +no_header=false +test_state_dir="" +cleanup=false +test_time_limit=3600 + +export CTDB_TEST_VERBOSE=false +export CTDB_TEST_COMMAND_TRACE=false +export CTDB_TEST_CAT_RESULTS_OPTS="" +export CTDB_TEST_DIFF_RESULTS=false +export CTDB_TEST_PRINT_LOGS_ON_ERROR=false +export CTDB_TEST_LOCAL_DAEMONS=3 +export CTDB_TEST_SWRAP_SO_PATH="" + +while getopts "AcCDehHI:l:LNqS:T:vV:xX?" opt ; do + case "$opt" in + A) CTDB_TEST_CAT_RESULTS_OPTS="-A" ;; + c) CTDB_TEST_LOCAL_DAEMONS="" ;; + C) cleanup=true ;; + D) CTDB_TEST_DIFF_RESULTS=true ;; + e) exit_on_fail=true ;; + H) no_header=true ;; + I) max_iterations="$OPTARG" ; exit_on_fail=true ; with_summary=false ;; + l) CTDB_TEST_LOCAL_DAEMONS="$OPTARG" ;; + L) CTDB_TEST_PRINT_LOGS_ON_ERROR=true ;; + N) with_summary=false ;; + q) quiet=true ;; + S) CTDB_TEST_SWRAP_SO_PATH="$OPTARG" ;; + T) test_time_limit="$OPTARG" ;; + v) CTDB_TEST_VERBOSE=true ;; + V) test_state_dir="$OPTARG" ;; + x) set -x ;; + X) CTDB_TEST_COMMAND_TRACE=true ;; + \?|h) usage ;; + esac +done +shift $((OPTIND - 1)) + +case $(basename "$0") in + *run_cluster_tests*) + # Running on a cluster... same as -c + CTDB_TEST_LOCAL_DAEMONS="" + ;; +esac + +if $quiet ; then + show_progress() { cat >/dev/null ; } +else + show_progress() { cat ; } +fi + +###################################################################### + +test_header () +{ + local name="$1" + + echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--" + echo "Running test $name ($(date '+%T'))" + echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--" +} + +test_footer () +{ + local f="$1" + local status="$2" + local interp="$3" + local duration="$4" + + local statstr="" + if [ "$status" -eq 0 ] ; then + statstr="" + else + statstr=" (status $status)" + fi + + echo "==========================================================================" + echo "TEST ${interp}: ${f}${statstr} (duration: ${duration}s)" + echo "==========================================================================" +} + +ctdb_test_run () +{ + local f="$1" + + $no_header || test_header "$f" + + local status=0 + local start_time + + start_time=$(date '+%s') + + if [ -x "$f" ] ; then + timeout "$test_time_limit" "$f" </dev/null | show_progress + status=$? + else + echo "TEST IS NOT EXECUTABLE" + status=99 + fi + + local duration=$(($(date +%s) - start_time)) + + tests_total=$((tests_total + 1)) + + local interp + case "$status" in + 0) + interp="PASSED" + tests_passed=$((tests_passed + 1)) + ;; + 77) + interp="SKIPPED" + tests_skipped=$((tests_skipped + 1)) + ;; + 99) + interp="ERROR" + tests_failed=$((tests_failed + 1)) + ;; + 124) + interp="TIMEDOUT" + tests_failed=$((tests_failed + 1)) + ;; + *) + interp="FAILED" + tests_failed=$((tests_failed + 1)) + ;; + esac + + $no_header || test_footer "$f" "$status" "$interp" "$duration" + + if $with_summary ; then + local t + if [ $status -eq 0 ] ; then + t=" ${interp}" + else + t="*${interp}*" + fi + printf '%-10s %s\n' "$t" "$f" >>"$summary_file" + fi + + # Skipped tests should not cause failure + case "$status" in + 77) + status=0 + ;; + esac + + return $status +} + +###################################################################### + +tests_total=0 +tests_passed=0 +tests_skipped=0 +tests_failed=0 + +if ! type mktemp >/dev/null 2>&1 ; then + # Not perfect, but it will do... + mktemp () + { + local dir=false + if [ "$1" = "-d" ] ; then + dir=true + fi + local t="${TMPDIR:-/tmp}/tmp.$$.$RANDOM" + ( + umask 077 + if $dir ; then + mkdir "$t" + else + : >"$t" + fi + ) + echo "$t" + } +fi + +set -o pipefail + +run_one_test () +{ + local f="$1" + + CTDB_TEST_SUITE_DIR=$(dirname "$f") + export CTDB_TEST_SUITE_DIR + # This expands the most probable problem cases like "." and "..". + if [ "$(dirname "$CTDB_TEST_SUITE_DIR")" = "." ] ; then + CTDB_TEST_SUITE_DIR=$(cd "$CTDB_TEST_SUITE_DIR" && pwd) + fi + + # Set CTDB_TEST_TMP_DIR + # + # Determine the relative test suite subdirectory. The top-level + # test directory needs to be a prefix of the test suite directory, + # so make absolute versions of both. + local test_dir test_suite_dir reldir + test_dir=$(cd "$CTDB_TEST_DIR" && pwd) + test_suite_dir=$(cd "$CTDB_TEST_SUITE_DIR" && pwd) + reldir="${test_suite_dir#"${test_dir}"/}" + + export CTDB_TEST_TMP_DIR="${test_state_dir}/${reldir}" + rm -rf "$CTDB_TEST_TMP_DIR" + mkdir -p "$CTDB_TEST_TMP_DIR" + + ctdb_test_run "$f" + status=$? +} + +run_tests () +{ + local f + + for f ; do + case "$f" in + */README|*/README.md) + continue + ;; + esac + + if [ ! -e "$f" ] ; then + # Can't find it? Check relative to CTDB_TEST_DIR. + # Strip off current directory from beginning, + # if there, just to make paths more friendly. + f="${CTDB_TEST_DIR#"${PWD}"/}/${f}" + fi + + if [ -d "$f" ] ; then + local test_dir dir reldir subtests + + test_dir=$(cd "$CTDB_TEST_DIR" && pwd) + dir=$(cd "$f" && pwd) + reldir="${dir#"${test_dir}"/}" + + case "$reldir" in + */*/*) + die "test \"$f\" is not recognised" + ;; + */*) + # This is a test suite + subtests=$(echo "${f%/}/"*".sh") + if [ "$subtests" = "${f%/}/*.sh" ] ; then + # Probably empty directory + die "test \"$f\" is not recognised" + fi + ;; + CLUSTER|INTEGRATION|UNIT) + # A collection of test suites + subtests=$(echo "${f%/}/"*) + ;; + *) + die "test \"$f\" is not recognised" + esac + + # Recurse - word-splitting wanted + # shellcheck disable=SC2086 + run_tests $subtests + elif [ -f "$f" ] ; then + run_one_test "$f" + else + # Time to give up + die "test \"$f\" is not recognised" + fi + + if $exit_on_fail && [ "$status" -ne 0 ] ; then + return "$status" + fi + done +} + +export CTDB_TEST_MODE="yes" + +# Following 2 lines may be modified by installation script +CTDB_TESTS_ARE_INSTALLED=false +CTDB_TEST_DIR=$(dirname "$0") +export CTDB_TESTS_ARE_INSTALLED CTDB_TEST_DIR + +if [ -z "$test_state_dir" ] ; then + if $CTDB_TESTS_ARE_INSTALLED ; then + test_state_dir=$(mktemp -d) + else + test_state_dir="${CTDB_TEST_DIR}/var" + fi +fi +mkdir -p "$test_state_dir" + +summary_file="${test_state_dir}/.summary" +: >"$summary_file" + +export TEST_SCRIPTS_DIR="${CTDB_TEST_DIR}/scripts" + +# If no tests specified then run some defaults +if [ -z "$1" ] ; then + if [ -n "$CTDB_TEST_LOCAL_DAEMONS" ] ; then + set -- UNIT INTEGRATION + else + set -- INTEGRATION CLUSTER + fi +fi + +do_cleanup () +{ + if $cleanup ; then + echo "Removing test state directory: ${test_state_dir}" + rm -rf "$test_state_dir" + else + echo "Not cleaning up test state directory: ${test_state_dir}" + fi +} + +trap "do_cleanup ; exit 130" SIGINT +trap "do_cleanup ; exit 143" SIGTERM + +iterations=0 +# Special case: -I 0 means iterate forever (until failure) +while [ "$max_iterations" -eq 0 ] || [ $iterations -lt "$max_iterations" ] ; do + iterations=$((iterations + 1)) + + if [ "$max_iterations" -ne 1 ] ; then + echo + echo "##################################################" + echo "ITERATION ${iterations}" + echo "##################################################" + echo + fi + + run_tests "$@" + status=$? + + if [ $status -ne 0 ] ; then + break + fi +done + +if $with_summary ; then + if [ "$status" -eq 0 ] || ! $exit_on_fail ; then + echo + cat "$summary_file" + + echo + tests_run=$((tests_total - tests_skipped)) + printf '%d/%d tests passed' $tests_passed $tests_run + if [ $tests_skipped -gt 0 ] ; then + printf ' (%d skipped)' $tests_skipped + fi + printf '\n' + fi +fi +rm -f "$summary_file" + +echo + +do_cleanup + +if $no_header || $exit_on_fail ; then + exit "$status" +elif [ $tests_failed -gt 0 ] ; then + exit 1 +else + exit 0 +fi diff --git a/ctdb/tests/scripts/cluster.bash b/ctdb/tests/scripts/cluster.bash new file mode 100755 index 0000000..916fc84 --- /dev/null +++ b/ctdb/tests/scripts/cluster.bash @@ -0,0 +1,18 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +if ! ctdb_test_on_cluster ; then + # Do not run on local daemons + ctdb_test_error \ + "ERROR: This test must be run against a real/virtual cluster" +fi + +h=$(hostname) + +for i in $(onnode -q all hostname) ; do + if [ "$h" = "$i" ] ; then + ctdb_test_error \ + "ERROR: This test must not be run from a cluster node" + fi +done diff --git a/ctdb/tests/scripts/common.sh b/ctdb/tests/scripts/common.sh new file mode 100644 index 0000000..5bc5869 --- /dev/null +++ b/ctdb/tests/scripts/common.sh @@ -0,0 +1,146 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +# Common variables and functions for all CTDB tests. + + +# Commands on different platforms may quote or sort things differently +# without this +export LANG=C + +# Print a message and exit. +die () +{ + echo "$1" >&2 ; exit "${2:-1}" +} + +. "${TEST_SCRIPTS_DIR}/script_install_paths.sh" + +if [ -d "$CTDB_SCRIPTS_TOOLS_BIN_DIR" ] ; then + PATH="${CTDB_SCRIPTS_TOOLS_BIN_DIR}:${PATH}" +fi + +if [ -d "$CTDB_SCRIPTS_TESTS_LIBEXEC_DIR" ] ; then + PATH="${CTDB_SCRIPTS_TESTS_LIBEXEC_DIR}:${PATH}" +fi + +ctdb_test_error () +{ + if [ $# -gt 0 ] ; then + echo "$*" + fi + exit 99 +} + +ctdb_test_fail () +{ + if [ $# -gt 0 ] ; then + echo "$*" + fi + exit 1 +} + +ctdb_test_skip () +{ + if [ $# -gt 0 ] ; then + echo "$*" + fi + exit 77 +} + +# "$@" is supported OSes +ctdb_test_check_supported_OS () +{ + _os=$(uname -s) + for _i ; do + if [ "$_os" = "$_i" ] ; then + return + fi + done + + ctdb_test_skip "This test is not supported on ${_os}" +} + +# Wait until either timeout expires or command succeeds. The command +# will be tried once per second, unless timeout has format T/I, where +# I is the recheck interval. +wait_until () +{ + _timeout="$1" ; shift # "$@" is the command... + + _interval=1 + case "$_timeout" in + */*) + _interval="${_timeout#*/}" + _timeout="${_timeout%/*}" + esac + + _negate=false + if [ "$1" = "!" ] ; then + _negate=true + shift + fi + + printf '<%d|' "$_timeout" + _t="$_timeout" + while [ "$_t" -gt 0 ] ; do + _rc=0 + "$@" || _rc=$? + if { ! $_negate && [ $_rc -eq 0 ] ; } || \ + { $_negate && [ $_rc -ne 0 ] ; } ; then + echo "|$((_timeout - _t))|" + echo "OK" + return 0 + fi + for _i in $(seq 1 "$_interval") ; do + printf '.' + done + _t=$((_t - _interval)) + sleep "$_interval" + done + + echo "*TIMEOUT*" + + return 1 +} + +# setup_ctdb_base <parent> <subdir> [item-to-copy]... +setup_ctdb_base () +{ + [ $# -ge 2 ] || die "usage: setup_ctdb_base <parent> <subdir> [item]..." + # If empty arguments are passed then we attempt to remove / + # (i.e. the root directory) below + if [ -z "$1" ] || [ -z "$2" ] ; then + die "usage: setup_ctdb_base <parent> <subdir> [item]..." + fi + + _parent="$1" + _subdir="$2" + + # Other arguments are files/directories to copy + shift 2 + + export CTDB_BASE="${_parent}/${_subdir}" + if [ -d "$CTDB_BASE" ] ; then + rm -r "$CTDB_BASE" + fi + mkdir -p "$CTDB_BASE" || die "Failed to create CTDB_BASE=$CTDB_BASE" + mkdir -p "${CTDB_BASE}/run" || die "Failed to create ${CTDB_BASE}/run" + mkdir -p "${CTDB_BASE}/var" || die "Failed to create ${CTDB_BASE}/var" + + for _i ; do + cp -pr "${CTDB_SCRIPTS_BASE}/${_i}" "${CTDB_BASE}/" + done + + mkdir -p "${CTDB_BASE}/events/legacy" + + if [ -z "$CTDB_TEST_SUITE_DIR" ] ; then + return + fi + + for _i in "${CTDB_TEST_SUITE_DIR}/etc-ctdb/"* ; do + # No/empty etc-ctdb directory + [ -e "$_i" ] || break + + cp -pr "$_i" "${CTDB_BASE}/" + done +} diff --git a/ctdb/tests/scripts/integration.bash b/ctdb/tests/scripts/integration.bash new file mode 100644 index 0000000..65e974e --- /dev/null +++ b/ctdb/tests/scripts/integration.bash @@ -0,0 +1,864 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +. "${TEST_SCRIPTS_DIR}/common.sh" + +###################################################################### + +export CTDB_TIMEOUT=60 + +if [ -n "$CTDB_TEST_REMOTE_DIR" ] ; then + CTDB_TEST_WRAPPER="${CTDB_TEST_REMOTE_DIR}/test_wrap" +else + _d=$(cd "$TEST_SCRIPTS_DIR" && echo "$PWD") + CTDB_TEST_WRAPPER="$_d/test_wrap" +fi +export CTDB_TEST_WRAPPER + +# If $VALGRIND is set then use it whenever ctdb is called, but only if +# $CTDB is not already set. +[ -n "$CTDB" ] || export CTDB="${VALGRIND}${VALGRIND:+ }ctdb" + +# why??? +PATH="${TEST_SCRIPTS_DIR}:${PATH}" + +###################################################################### + +ctdb_test_on_cluster () +{ + [ -z "$CTDB_TEST_LOCAL_DAEMONS" ] +} + +ctdb_test_exit () +{ + local status=$? + + trap - 0 + + # run_tests.sh pipes stdout into tee. If the tee process is + # killed then any attempt to write to stdout (e.g. echo) will + # result in SIGPIPE, terminating the caller. Ignore SIGPIPE to + # ensure that all clean-up is run. + trap '' PIPE + + # Avoid making a test fail from this point onwards. The test is + # now complete. + set +e + + echo "*** TEST COMPLETED (RC=$status) AT $(date '+%F %T'), CLEANING UP..." + + eval "$ctdb_test_exit_hook" || true + unset ctdb_test_exit_hook + + echo "Stopping cluster..." + ctdb_nodes_stop || ctdb_test_error "Cluster shutdown failed" + + exit $status +} + +ctdb_test_exit_hook_add () +{ + ctdb_test_exit_hook="${ctdb_test_exit_hook}${ctdb_test_exit_hook:+ ; }$*" +} + +# Setting cleanup_pid to <pid>@<node> will cause <pid> to be killed on +# <node> when the test completes. To cancel, just unset cleanup_pid. +ctdb_test_cleanup_pid="" +ctdb_test_cleanup_pid_exit_hook () +{ + if [ -n "$ctdb_test_cleanup_pid" ] ; then + local pid="${ctdb_test_cleanup_pid%@*}" + local node="${ctdb_test_cleanup_pid#*@}" + + try_command_on_node "$node" "kill ${pid}" + fi +} + +ctdb_test_exit_hook_add ctdb_test_cleanup_pid_exit_hook + +ctdb_test_cleanup_pid_set () +{ + local node="$1" + local pid="$2" + + ctdb_test_cleanup_pid="${pid}@${node}" +} + +ctdb_test_cleanup_pid_clear () +{ + ctdb_test_cleanup_pid="" +} + +# -n option means do not configure/start cluster +ctdb_test_init () +{ + trap "ctdb_test_exit" 0 + + ctdb_nodes_stop >/dev/null 2>&1 || true + + if [ "$1" != "-n" ] ; then + echo "Configuring cluster..." + setup_ctdb || ctdb_test_error "Cluster configuration failed" + + echo "Starting cluster..." + ctdb_init || ctdb_test_error "Cluster startup failed" + fi + + echo "*** SETUP COMPLETE AT $(date '+%F %T'), RUNNING TEST..." +} + +ctdb_nodes_start_custom () +{ + if ctdb_test_on_cluster ; then + ctdb_test_error "ctdb_nodes_start_custom() on real cluster" + fi + + ctdb_nodes_stop >/dev/null 2>&1 || true + + echo "Configuring cluster..." + setup_ctdb "$@" || ctdb_test_error "Cluster configuration failed" + + echo "Starting cluster..." + ctdb_init || ctdb_test_fail "Cluster startup failed" +} + +ctdb_test_skip_on_cluster () +{ + if ctdb_test_on_cluster ; then + ctdb_test_skip \ + "SKIPPING this test - only runs against local daemons" + fi +} + + +ctdb_nodes_restart () +{ + ctdb_nodes_stop "$@" + ctdb_nodes_start "$@" +} + +######################################## + +# Sets: $out, $outfile +# * The first 1KB of output is put into $out +# * Tests should use $outfile for handling large output +# * $outfile is removed after each test +out="" +outfile="${CTDB_TEST_TMP_DIR}/try_command_on_node.out" + +outfile_cleanup () +{ + rm -f "$outfile" +} + +ctdb_test_exit_hook_add outfile_cleanup + +try_command_on_node () +{ + local nodespec="$1" ; shift + + local verbose=false + local onnode_opts="" + + while [ "${nodespec#-}" != "$nodespec" ] ; do + if [ "$nodespec" = "-v" ] ; then + verbose=true + else + onnode_opts="${onnode_opts}${onnode_opts:+ }${nodespec}" + fi + nodespec="$1" ; shift + done + + local cmd="$*" + + local status=0 + # Intentionally unquoted - might be empty + # shellcheck disable=SC2086 + onnode -q $onnode_opts "$nodespec" "$cmd" >"$outfile" 2>&1 || status=$? + out=$(dd if="$outfile" bs=1k count=1 2>/dev/null) + + if [ $status -ne 0 ] ; then + echo "Failed to execute \"$cmd\" on node(s) \"$nodespec\"" + cat "$outfile" + return $status + fi + + if $verbose ; then + echo "Output of \"$cmd\":" + cat "$outfile" || true + fi +} + +_run_onnode () +{ + local thing="$1" + shift + + local options nodespec + + while : ; do + case "$1" in + -*) + options="${options}${options:+ }${1}" + shift + ;; + *) + nodespec="$1" + shift + break + esac + done + + # shellcheck disable=SC2086 + # $options can be multi-word + try_command_on_node $options "$nodespec" "${thing} $*" +} + +ctdb_onnode () +{ + _run_onnode "$CTDB" "$@" +} + +testprog_onnode () +{ + _run_onnode "${CTDB_TEST_WRAPPER} ${VALGRIND}" "$@" +} + +function_onnode () +{ + _run_onnode "${CTDB_TEST_WRAPPER}" "$@" +} + +sanity_check_output () +{ + local min_lines="$1" + local regexp="$2" # Should be anchored as necessary. + + local ret=0 + + local num_lines + num_lines=$(wc -l <"$outfile" | tr -d '[:space:]') + echo "There are $num_lines lines of output" + if [ "$num_lines" -lt "$min_lines" ] ; then + ctdb_test_fail "BAD: that's less than the required number (${min_lines})" + fi + + local status=0 + local unexpected # local doesn't pass through status of command on RHS. + unexpected=$(grep -Ev "$regexp" "$outfile") || status=$? + + # Note that this is reversed. + if [ $status -eq 0 ] ; then + echo "BAD: unexpected lines in output:" + echo "$unexpected" | cat -A + ret=1 + else + echo "Output lines look OK" + fi + + return $ret +} + +select_test_node () +{ + try_command_on_node any ctdb pnn || return 1 + + test_node="$out" + echo "Selected node ${test_node}" +} + +# This returns a list of "ip node" lines in $outfile +all_ips_on_node() +{ + local node="$1" + try_command_on_node "$node" \ + "$CTDB ip -X | awk -F'|' 'NR > 1 { print \$2, \$3 }'" +} + +_select_test_node_and_ips () +{ + try_command_on_node any \ + "$CTDB ip -X all | awk -F'|' 'NR > 1 { print \$2, \$3 }'" + + test_node="" # this matches no PNN + test_node_ips="" + local ip pnn + while read -r ip pnn ; do + if [ -z "$test_node" ] && [ "$pnn" != "-1" ] ; then + test_node="$pnn" + fi + if [ "$pnn" = "$test_node" ] ; then + test_node_ips="${test_node_ips}${test_node_ips:+ }${ip}" + fi + done <"$outfile" + + echo "Selected node ${test_node} with IPs: ${test_node_ips}." + test_ip="${test_node_ips%% *}" + + # test_prefix used by caller + # shellcheck disable=SC2034 + case "$test_ip" in + *:*) test_prefix="${test_ip}/128" ;; + *) test_prefix="${test_ip}/32" ;; + esac + + [ -n "$test_node" ] || return 1 +} + +select_test_node_and_ips () +{ + local timeout=10 + while ! _select_test_node_and_ips ; do + echo "Unable to find a test node with IPs assigned" + if [ $timeout -le 0 ] ; then + ctdb_test_error "BAD: Too many attempts" + return 1 + fi + sleep_for 1 + timeout=$((timeout - 1)) + done + + return 0 +} + +# Sets: mask, iface +get_test_ip_mask_and_iface () +{ + # Find the interface + ctdb_onnode "$test_node" "ip -v -X" + iface=$(awk -F'|' -v ip="$test_ip" '$2 == ip { print $4 }' "$outfile") + + if ctdb_test_on_cluster ; then + # Find the netmask + try_command_on_node "$test_node" ip addr show to "$test_ip" + mask="${out##*/}" + mask="${mask%% *}" + else + mask="24" + fi + + echo "$test_ip/$mask is on $iface" +} + +ctdb_get_all_pnns () +{ + try_command_on_node -q all "$CTDB pnn" + all_pnns="$out" +} + +# The subtlety is that "ctdb delip" will fail if the IP address isn't +# configured on a node... +delete_ip_from_all_nodes () +{ + _ip="$1" + + ctdb_get_all_pnns + + _nodes="" + + for _pnn in $all_pnns ; do + all_ips_on_node "$_pnn" + while read -r _i _ ; do + if [ "$_ip" = "$_i" ] ; then + _nodes="${_nodes}${_nodes:+,}${_pnn}" + fi + done <"$outfile" + done + + try_command_on_node -pq "$_nodes" "$CTDB delip $_ip" +} + +####################################### + +sleep_for () +{ + echo -n "=${1}|" + for i in $(seq 1 "$1") ; do + echo -n '.' + sleep 1 + done + echo '|' +} + +_cluster_is_healthy () +{ + $CTDB nodestatus all >/dev/null +} + +_cluster_is_recovered () +{ + node_has_status 0 recovered +} + +_cluster_is_ready () +{ + _cluster_is_healthy && _cluster_is_recovered +} + +cluster_is_healthy () +{ + if onnode 0 "$CTDB_TEST_WRAPPER" _cluster_is_healthy ; then + echo "Cluster is HEALTHY" + if ! onnode 0 "$CTDB_TEST_WRAPPER" _cluster_is_recovered ; then + echo "WARNING: cluster in recovery mode!" + fi + return 0 + fi + + echo "Cluster is UNHEALTHY" + + echo "DEBUG AT $(date '+%F %T'):" + local i + for i in "onnode -q 0 $CTDB status" \ + "onnode -q 0 onnode all $CTDB scriptstatus" ; do + echo "$i" + $i || true + done + + return 1 +} + +wait_until_ready () +{ + local timeout="${1:-120}" + + echo "Waiting for cluster to become ready..." + + wait_until "$timeout" onnode -q any "$CTDB_TEST_WRAPPER" _cluster_is_ready +} + +# This function is becoming nicely overloaded. Soon it will collapse! :-) +node_has_status () +{ + local pnn="$1" + local status="$2" + + case "$status" in + recovered) + ! $CTDB status -n "$pnn" | \ + grep -Eq '^Recovery mode:RECOVERY \(1\)$' + return + ;; + notlmaster) + ! $CTDB status | grep -Eq "^hash:.* lmaster:${pnn}\$" + return + ;; + esac + + local bits + case "$status" in + unhealthy) bits="?|?|?|?|1|*" ;; + healthy) bits="?|?|?|?|0|*" ;; + disconnected) bits="1|*" ;; + connected) bits="0|*" ;; + banned) bits="?|?|1|*" ;; + unbanned) bits="?|?|0|*" ;; + disabled) bits="?|?|?|1|*" ;; + enabled) bits="?|?|?|0|*" ;; + stopped) bits="?|?|?|?|?|1|*" ;; + notstopped) bits="?|?|?|?|?|0|*" ;; + *) + echo "node_has_status: unknown status \"$status\"" + return 1 + esac + local out _ line + + out=$($CTDB -X status 2>&1) || return 1 + + { + read -r _ + while read -r line ; do + # This needs to be done in 2 steps to + # avoid false matches. + local line_bits="${line#|"${pnn}"|*|}" + [ "$line_bits" = "$line" ] && continue + # shellcheck disable=SC2295 + # This depends on $bits being a pattern + [ "${line_bits#${bits}}" != "$line_bits" ] && \ + return 0 + done + return 1 + } <<<"$out" # Yay bash! +} + +wait_until_node_has_status () +{ + local pnn="$1" + local status="$2" + local timeout="${3:-30}" + local proxy_pnn="${4:-any}" + + echo "Waiting until node $pnn has status \"$status\"..." + + if ! wait_until "$timeout" onnode "$proxy_pnn" \ + "$CTDB_TEST_WRAPPER" node_has_status "$pnn" "$status" ; then + + for i in "onnode -q any $CTDB status" "onnode -q any onnode all $CTDB scriptstatus" ; do + echo "$i" + $i || true + done + + return 1 + fi + +} + +# Useful for superficially testing IP failover. +# IPs must be on the given node. +# If the first argument is '!' then the IPs must not be on the given node. +ips_are_on_node () +{ + local negating=false + if [ "$1" = "!" ] ; then + negating=true ; shift + fi + local node="$1" ; shift + local ips="$*" + + local out + + all_ips_on_node "$node" + + local check + for check in $ips ; do + local ip pnn + while read -r ip pnn ; do + if [ "$check" = "$ip" ] ; then + if [ "$pnn" = "$node" ] ; then + if $negating ; then return 1 ; fi + else + if ! $negating ; then return 1 ; fi + fi + ips="${ips/${ip}}" # Remove from list + break + fi + # If we're negating and we didn't see the address then it + # isn't hosted by anyone! + if $negating ; then + ips="${ips/${check}}" + fi + done <"$outfile" + done + + ips="${ips// }" # Remove any spaces. + [ -z "$ips" ] +} + +wait_until_ips_are_on_node () +{ + # Go to some trouble to print a use description of what is happening + local not="" + if [ "$1" == "!" ] ; then + not="no longer " + fi + local node="" + local ips="" + local i + for i ; do + [ "$i" != "!" ] || continue + if [ -z "$node" ] ; then + node="$i" + continue + fi + ips="${ips}${ips:+, }${i}" + done + echo "Waiting for ${ips} to ${not}be assigned to node ${node}" + + wait_until 60 ips_are_on_node "$@" +} + +node_has_some_ips () +{ + local node="$1" + + local out + + all_ips_on_node "$node" + + while read -r ip pnn ; do + if [ "$node" = "$pnn" ] ; then + return 0 + fi + done <"$outfile" + + return 1 +} + +wait_until_node_has_some_ips () +{ + echo "Waiting for some IPs to be assigned to node ${test_node}" + + wait_until 60 node_has_some_ips "$@" +} + +wait_until_node_has_no_ips () +{ + echo "Waiting until no IPs are assigned to node ${test_node}" + + wait_until 60 ! node_has_some_ips "$@" +} + +####################################### + +ctdb_init () +{ + if ! ctdb_nodes_start ; then + echo "Cluster start failed" + return 1 + fi + + if ! wait_until_ready 120 ; then + echo "Cluster didn't become ready" + return 1 + fi + + echo "Setting RerecoveryTimeout to 1" + onnode -pq all "$CTDB setvar RerecoveryTimeout 1" + + echo "Forcing a recovery..." + onnode -q 0 "$CTDB recover" + sleep_for 2 + + if ! onnode -q all "$CTDB_TEST_WRAPPER _cluster_is_recovered" ; then + echo "Cluster has gone into recovery again, waiting..." + if ! wait_until 30/2 onnode -q all \ + "$CTDB_TEST_WRAPPER _cluster_is_recovered" ; then + echo "Cluster did not come out of recovery" + return 1 + fi + fi + + if ! onnode 0 "$CTDB_TEST_WRAPPER _cluster_is_healthy" ; then + echo "Cluster became UNHEALTHY again [$(date)]" + return 1 + fi + + echo "Doing a sync..." + onnode -q 0 "$CTDB sync" + + echo "ctdb is ready" + return 0 +} + +ctdb_base_show () +{ + echo "${CTDB_BASE:-${CTDB_SCRIPTS_BASE}}" +} + +####################################### + +# sets: leader +_leader_get () +{ + local node="$1" + + ctdb_onnode "$node" leader + # shellcheck disable=SC2154 + # $out set by ctdb_onnode() above + leader="$out" +} + +leader_get () +{ + local node="$1" + + echo "Get leader" + _leader_get "$node" + echo "Leader is ${leader}" + echo +} + +_leader_has_changed () +{ + local node="$1" + local leader_old="$2" + + _leader_get "$node" + + [ "$leader" != "$leader_old" ] +} + +# uses: leader +wait_until_leader_has_changed () +{ + local node="$1" + + echo + echo "Wait until leader changes..." + wait_until 30 _leader_has_changed "$node" "$leader" + echo "Leader changed to ${leader}" +} + +####################################### + +# sets: generation +_generation_get () +{ + local node="$1" + + ctdb_onnode "$node" status + # shellcheck disable=SC2154 + # $outfile set by ctdb_onnode() above + generation=$(sed -n -e 's/^Generation:\([0-9]*\)/\1/p' "$outfile") +} + +generation_get () +{ + local node="$1" + + echo "Get generation" + _generation_get "$node" + echo "Generation is ${generation}" + echo +} + +_generation_has_changed () +{ + local node="$1" + local generation_old="$2" + + _generation_get "$node" + + [ "$generation" != "$generation_old" ] +} + +# uses: generation +wait_until_generation_has_changed () +{ + local node="$1" + + echo "Wait until generation changes..." + wait_until 30 _generation_has_changed "$node" "$generation" + echo "Generation changed to ${generation}" + echo +} + +####################################### + +wait_for_monitor_event () +{ + local pnn="$1" + local timeout=120 + + echo "Waiting for a monitor event on node ${pnn}..." + + ctdb_onnode "$pnn" scriptstatus || { + echo "Unable to get scriptstatus from node $pnn" + return 1 + } + + mv "$outfile" "${outfile}.orig" + + wait_until 120 _ctdb_scriptstatus_changed +} + +_ctdb_scriptstatus_changed () +{ + ctdb_onnode "$pnn" scriptstatus || { + echo "Unable to get scriptstatus from node $pnn" + return 1 + } + + ! diff "$outfile" "${outfile}.orig" >/dev/null +} + +####################################### + +# If the given IP is hosted then print 2 items: maskbits and iface +ip_maskbits_iface () +{ + _addr="$1" + + case "$_addr" in + *:*) _family="inet6" ; _bits=128 ;; + *) _family="inet" ; _bits=32 ;; + esac + + # Literal backslashes in awk script + # shellcheck disable=SC1004 + ip addr show to "${_addr}/${_bits}" 2>/dev/null | \ + awk -v family="${_family}" \ + 'NR == 1 { iface = $2; sub(":$", "", iface) } \ + $1 ~ /inet/ { mask = $2; sub(".*/", "", mask); \ + print mask, iface, family }' +} + +drop_ip () +{ + _addr="${1%/*}" # Remove optional maskbits + + # Intentional word splitting + # shellcheck disable=SC2046,SC2086 + set -- $(ip_maskbits_iface $_addr) + if [ -n "$1" ] ; then + _maskbits="$1" + _iface="$2" + echo "Removing public address $_addr/$_maskbits from device $_iface" + ip addr del "$_ip/$_maskbits" dev "$_iface" >/dev/null 2>&1 || true + fi +} + +drop_ips () +{ + for _ip ; do + drop_ip "$_ip" + done +} + +####################################### + +# $1: pnn, $2: DB name +db_get_path () +{ + ctdb_onnode -v "$1" "getdbstatus $2" | sed -n -e "s@^path: @@p" +} + +# $1: pnn, $2: DB name +db_ctdb_cattdb_count_records () +{ + # Count the number of keys, excluding any that begin with '_'. + # This excludes at least the sequence number record in + # persistent/replicated databases. The trailing "|| :" forces + # the command to succeed when no records are matched. + ctdb_onnode "$1" "cattdb $2 | grep -c '^key([0-9][0-9]*) = \"[^_]' || :" + echo "$out" +} + +# $1: pnn, $2: DB name, $3: key string, $4: value string, $5: RSN (default 7) +db_ctdb_tstore () +{ + _tdb=$(db_get_path "$1" "$2") + _rsn="${5:-7}" + ctdb_onnode "$1" tstore "$_tdb" "$3" "$4" "$_rsn" +} + +# $1: pnn, $2: DB name, $3: dbseqnum (must be < 255!!!!!) +db_ctdb_tstore_dbseqnum () +{ + # "__db_sequence_number__" + trailing 0x00 + _key='0x5f5f64625f73657175656e63655f6e756d6265725f5f00' + + # Construct 8 byte (unit64_t) database sequence number. This + # probably breaks if $3 > 255 + _value=$(printf "0x%02x%014x" "$3" 0) + + db_ctdb_tstore "$1" "$2" "$_key" "$_value" +} + +######################################## + +# Make sure that $CTDB is set. +if [ -z "$CTDB" ] ; then + CTDB="ctdb" +fi + +if ctdb_test_on_cluster ; then + . "${TEST_SCRIPTS_DIR}/integration_real_cluster.bash" +else + . "${TEST_SCRIPTS_DIR}/integration_local_daemons.bash" +fi + + +local="${CTDB_TEST_SUITE_DIR}/scripts/local.bash" +if [ -r "$local" ] ; then + . "$local" +fi diff --git a/ctdb/tests/scripts/integration_local_daemons.bash b/ctdb/tests/scripts/integration_local_daemons.bash new file mode 100644 index 0000000..643fc5e --- /dev/null +++ b/ctdb/tests/scripts/integration_local_daemons.bash @@ -0,0 +1,95 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +hdir="$CTDB_SCRIPTS_HELPER_BINDIR" +export CTDB_EVENT_HELPER="${hdir}/ctdb-event" + +if $CTDB_TESTS_ARE_INSTALLED ; then + # Find it in $PATH + helper="ctdb_local_daemons" +else + helper="${CTDB_TEST_DIR}/local_daemons.sh" +fi + +ctdb_local_daemons="${helper} ${CTDB_TEST_TMP_DIR}" + +####################################### + +setup_ctdb () +{ + local no_event_scripts=false + + # All other options are passed through to local_daemons.sh setup + case "$1" in + --no-event-scripts) no_event_scripts=true ; shift ;; + esac + + $ctdb_local_daemons setup "$@" \ + -n "$CTDB_TEST_LOCAL_DAEMONS" \ + ${CTDB_USE_IPV6:+-6} \ + ${CTDB_TEST_SWRAP_SO_PATH:+-S ${CTDB_TEST_SWRAP_SO_PATH}} + # Burying the above in an if-statement condition reduces readability. + # shellcheck disable=SC2181 + if [ $? -ne 0 ] ; then + exit 1 + fi + + if $no_event_scripts ; then + # Want CTDB_BASE expanded when executed under onnode + # shellcheck disable=SC2016 + $ctdb_local_daemons onnode -q all \ + 'rm "${CTDB_BASE}/events/legacy/"*' + fi + + if $CTDB_TEST_PRINT_LOGS_ON_ERROR ; then + ctdb_test_exit_hook_add _print_logs_on_test_failure + fi +} + +ctdb_nodes_start () +{ + local nodespec="${1:-all}" + + $ctdb_local_daemons start "$nodespec" +} + +ctdb_nodes_stop () +{ + local nodespec="${1:-all}" + + if $ctdb_local_daemons stop "$nodespec" ; then + return 0 + fi + + # Failed, dump logs? + if $CTDB_TEST_PRINT_LOGS_ON_ERROR ; then + _print_logs + fi + + # Next level up can log the error... + return 1 +} + +onnode () +{ + $ctdb_local_daemons onnode "$@" +} + + + +_print_logs () +{ + echo "*** LOG START --------------------" + $ctdb_local_daemons print-log all | tail -n 500 + echo "*** LOG END --------------------" +} + +_print_logs_on_test_failure () +{ + # This is called from ctdb_test_exit() where $status is available + # shellcheck disable=SC2154 + if [ "$status" -eq 0 ] ; then + return + fi + + _print_logs +} diff --git a/ctdb/tests/scripts/integration_real_cluster.bash b/ctdb/tests/scripts/integration_real_cluster.bash new file mode 100644 index 0000000..8d3f68a --- /dev/null +++ b/ctdb/tests/scripts/integration_real_cluster.bash @@ -0,0 +1,53 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +####################################### + +# Enables all of the event scripts used in cluster tests, except for +# the mandatory scripts +_ctdb_enable_cluster_test_event_scripts () +{ + local scripts=" + 06.nfs + 10.interface + 49.winbind + 50.samba + 60.nfs + " + + local s + for s in $scripts ; do + try_command_on_node all ctdb event script enable legacy "$s" + done +} + +setup_ctdb () +{ + _ctdb_enable_cluster_test_event_scripts +} + +####################################### + +_service_ctdb () +{ + cmd="$1" + + if [ -e /etc/redhat-release ] ; then + service ctdb "$cmd" + else + /etc/init.d/ctdb "$cmd" + fi +} + +# Stop/start CTDB on all nodes. Override for local daemons. +ctdb_nodes_stop () +{ + local nodespec="${1:-all}" + + onnode -p "$nodespec" "$CTDB_TEST_WRAPPER" _service_ctdb stop +} +ctdb_nodes_start () +{ + local nodespec="${1:-all}" + + onnode -p "$nodespec" "$CTDB_TEST_WRAPPER" _service_ctdb start +} diff --git a/ctdb/tests/scripts/script_install_paths.sh b/ctdb/tests/scripts/script_install_paths.sh new file mode 100644 index 0000000..6890cf8 --- /dev/null +++ b/ctdb/tests/scripts/script_install_paths.sh @@ -0,0 +1,67 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +# Sets $bin_dir +find_bin_dir () +{ + _ctdb_dir="$1" + + bin_dir="$(pwd -P)/bin" + if [ -d "$(pwd -P)/bin" ] ; then + return + fi + + bin_dir="${_ctdb_dir}/bin" + if [ -d "$bin_dir" ] ; then + return + fi + + bin_dir="$(dirname "${_ctdb_dir}")/bin" + if [ -d "$bin_dir" ] ; then + return + fi + + die "Unable to locate bin/ subdirectory" +} + + +if ! $CTDB_TESTS_ARE_INSTALLED ; then + if [ ! -f "${CTDB_TEST_DIR}/run_tests.sh" ] ; then + die "Tests not installed but can't find run_tests.sh" + fi + + ctdb_dir=$(cd -P "$(dirname "$CTDB_TEST_DIR")" && pwd) # real path + + find_bin_dir "$ctdb_dir" + + CTDB_SCRIPTS_BASE="${ctdb_dir}/config" + CTDB_SCRIPTS_INIT_SCRIPT="${ctdb_dir}/config/ctdb.init" + CTDB_SCRIPTS_SBIN_DIR="${ctdb_dir}/config" + CTDB_SCRIPTS_TOOLS_BIN_DIR="${ctdb_dir}/tools" + CTDB_SCRIPTS_TOOLS_HELPER_DIR="${ctdb_dir}/tools" + CTDB_SCRIPTS_HELPER_BINDIR="$bin_dir" + CTDB_SCRIPTS_DATA_DIR="${ctdb_dir}/config" + CTDB_SCRIPTS_TESTS_LIBEXEC_DIR="$bin_dir" + CTDB_SCRIPTS_TESTS_BIN_DIR="$CTDB_TEST_DIR" +else + # Installed + CTDB_SCRIPTS_BASE="/usr/local/etc/ctdb" + CTDB_SCRIPTS_INIT_SCRIPT="" # No ideas here... this is a packaging choice + CTDB_SCRIPTS_SBIN_DIR="/usr/local/sbin" + CTDB_SCRIPTS_TOOLS_BIN_DIR="/usr/local/bin" + CTDB_SCRIPTS_TOOLS_HELPER_DIR="/usr/local/libexec/ctdb" + CTDB_SCRIPTS_HELPER_BINDIR="/usr/local/libexec/ctdb" + CTDB_SCRIPTS_DATA_DIR="/usr/local/share/ctdb" + CTDB_SCRIPTS_TESTS_LIBEXEC_DIR="/usr/local/libexec/ctdb/tests" + CTDB_SCRIPTS_TESTS_BIN_DIR="/usr/local/bin" +fi + +export CTDB_SCRIPTS_BASE \ + CTDB_SCRIPTS_BIN_DIR \ + CTDB_SCRIPTS_INIT_SCRIPT \ + CTDB_SCRIPTS_SBIN_DIR \ + CTDB_SCRIPTS_TOOLS_BIN_DIR \ + CTDB_SCRIPTS_TOOLS_HELPER_DIR \ + CTDB_SCRIPTS_HELPER_BINDIR \ + CTDB_SCRIPTS_DATA_DIR \ + CTDB_SCRIPTS_TESTS_LIBEXEC_DIR \ + CTDB_SCRIPTS_TESTS_BIN_DIR diff --git a/ctdb/tests/scripts/test_wrap b/ctdb/tests/scripts/test_wrap new file mode 100755 index 0000000..619ac7c --- /dev/null +++ b/ctdb/tests/scripts/test_wrap @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# Execute the given command. The intention is that it is either +# * a function from "${TEST_SCRIPTS_DIR}/integration.bash"; or +# * a test helper binary + +TEST_SCRIPTS_DIR=$(dirname "$0") + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +"$@" diff --git a/ctdb/tests/scripts/unit.sh b/ctdb/tests/scripts/unit.sh new file mode 100644 index 0000000..403ee07 --- /dev/null +++ b/ctdb/tests/scripts/unit.sh @@ -0,0 +1,267 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +. "${TEST_SCRIPTS_DIR}/common.sh" + +# Common variables and functions for CTDB unit tests. + +trap -- '' PIPE + +# Set the required result for a test. +# - Argument 1 is exit code. +# - Argument 2, if present is the required test output but "--" +# indicates empty output. +# If argument 2 is not present or null then read required test output +# from stdin. +required_result() +{ + required_rc="${1:-0}" + if [ -n "$2" ]; then + if [ "$2" = "--" ]; then + required_output="" + else + # Use a sub-shell to strip trailing newlines. + # They can't be matched anyway because the + # test is run in a sub-shell, which strips + # trailing newlines. + # shellcheck disable=SC2116 + required_output=$(echo "$2") + fi + else + if ! tty -s; then + required_output=$(cat) + else + required_output="" + fi + fi +} + +required_error() +{ + rc=$(errcode "$1") + shift + required_result "$rc" "$@" +} + +ok() +{ + required_result 0 "$@" +} + +ok_null() +{ + ok -- +} + +reset_extra_header() +{ + # Re-define this function to output extra header information + extra_header() + { + : + } +} + +reset_extra_footer() +{ + # Re-define this function to output extra footer information + extra_footer() + { + : + } +} + +reset_extra_header +reset_extra_footer + +result_print() +{ + _passed="$1" + _out="$2" + _rc="$3" + + if "$CTDB_TEST_VERBOSE" || ! $_passed; then + extra_header + + cat <<EOF +-------------------------------------------------- +Output (Exit status: ${_rc}): +-------------------------------------------------- +EOF + # Avoid echo, which might expand unintentional escapes + printf '%s\n' "$_out" | + result_filter | + cat "${CTDB_TEST_CAT_RESULTS_OPTS:--}" + fi + + if ! $_passed; then + cat <<EOF +-------------------------------------------------- +Required output (Exit status: ${required_rc}): +-------------------------------------------------- +EOF + # Avoid echo, which might expand unintentional escapes + printf '%s\n' "$required_output" | + cat "${CTDB_TEST_CAT_RESULTS_OPTS:--}" + + if $CTDB_TEST_DIFF_RESULTS; then + _outr=$(mktemp) + # Avoid echo, which might expand unintentional escapes + printf '%s\n' "$required_output" >"$_outr" + + _outf=$(mktemp) + # Avoid echo, which might expand unintentional escapes + printf '%s\n' "$_fout" >"$_outf" + + cat <<EOF +-------------------------------------------------- +Diff: +-------------------------------------------------- +EOF + diff -u "$_outr" "$_outf" | cat -A + rm "$_outr" "$_outf" + fi + fi +} + +result_footer() +{ + _passed="$1" + + if "$CTDB_TEST_VERBOSE" || ! $_passed; then + extra_footer + fi + + if $_passed; then + echo "PASSED" + return 0 + else + echo + echo "FAILED" + return 1 + fi +} + +# Result filtering is (usually) used to replace the date/time/PID +# prefix on some CTDB tool/client log messages with the literal string +# "DATE TIME [PID]". This allows tests to loosely match this output, +# since it can't otherwise be matched. +result_filter_default() +{ + _date_time_pid='[0-9/][0-9/]*\ [0-9:\.][0-9:\.]*\ \[[\ 0-9][\ 0-9]*\]' + sed -e "s@^${_date_time_pid}:@DATE\ TIME\ \[PID\]:@" +} +# Used in testcases +# shellcheck disable=SC2034 +TEST_DATE_STAMP="" + +# Override this function to customise output filtering. +result_filter() +{ + result_filter_default +} + +result_check() +{ + _rc=$? + + # Avoid echo, which might expand unintentional escapes + _fout=$(printf '%s\n' "$_out" | result_filter) + + if [ "$_fout" = "$required_output" ] && + [ "$_rc" = "$required_rc" ]; then + _passed=true + else + _passed=false + fi + + result_print "$_passed" "$_out" "$_rc" + result_footer "$_passed" +} + +test_fail() +{ + _passed=false + return 1 +} + +test_case_string="" +test_case() +{ + test_case_string="$*" +} + +test_header_default() +{ + echo "==================================================" + if [ -n "$test_case_string" ]; then + echo "Summary: ${test_case_string}" + test_case_string="" + fi + echo "Running: $*" +} + +reset_test_header() +{ + # Re-define this function to get different header + test_header() + { + test_header_default "$@" + } +} + +reset_test_header + +# Simple test harness for running binary unit tests +unit_test() +{ + test_header "$@" + + _wrapper="$VALGRIND" + if $CTDB_TEST_COMMAND_TRACE; then + _wrapper="strace" + fi + _out=$($_wrapper "$@" 2>&1) + + result_check || exit $? +} + +# Simple test harness for running shell script unit tests +script_test() +{ + test_header "$@" + + _shell="" + if ${CTDB_TEST_COMMAND_TRACE}; then + _shell="sh -x" + else + _shell="sh" + fi + + _out=$($_shell "$@" 2>&1) + + result_check || exit $? +} + +# Simple test harness for running tests without tracing +unit_test_notrace() +{ + test_header "$@" + + _out=$("$@" 2>&1) + + result_check || exit $? +} + +test_cleanup_hooks="" + +test_cleanup() +{ + test_cleanup_hooks="${test_cleanup_hooks}${test_cleanup_hooks:+ ; }$*" +} + +trap 'eval $test_cleanup_hooks' 0 + +local="${CTDB_TEST_SUITE_DIR}/scripts/local.sh" +if [ -r "$local" ]; then + . "$local" +fi diff --git a/ctdb/tests/src/cluster_mutex_test.c b/ctdb/tests/src/cluster_mutex_test.c new file mode 100644 index 0000000..2576163 --- /dev/null +++ b/ctdb/tests/src/cluster_mutex_test.c @@ -0,0 +1,844 @@ +/* + CTDB cluster mutex test + + Copyright (C) Martin Schwenke 2019 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" +#include "system/wait.h" + +#include <assert.h> + +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/util.h" +#include "lib/util/smb_strtox.h" + +#include "tests/src/test_backtrace.h" + +/* + * ctdb_cluster_mutex.c is included below. This requires a few hacks... + */ + +/* Avoid inclusion of ctdb_private.h */ +#define _CTDB_PRIVATE_H + +/* Fake ctdb_context */ +struct ctdb_context { + struct tevent_context *ev; +}; + +/* + * ctdb_fork() and ctdb_kill() are used in ctdb_cluster_mutex.c for + * safer tracking of PIDs. Fake them here to avoid dragging in the + * world. + */ + +static pid_t ctdb_fork(struct ctdb_context *ctdb) +{ + return fork(); +} + +static int ctdb_kill(struct ctdb_context *ctdb, pid_t pid, int signum) +{ + /* + * Tests need to wait for the child to exit to ensure that the + * lock really has been released. The PID is only accessible + * in ctdb_cluster_mutex.c, so make a best attempt to ensure + * that the child process is waited for after it is killed. + * Avoid waiting if the process is already gone. + */ + int ret; + + if (signum == 0) { + return kill(pid, signum); + } + + ret = kill(pid, signum); + waitpid(pid, NULL, 0); + + return ret; +} + +#include "server/ctdb_cluster_mutex.c" + +/* + * Mutex testing support + */ + +struct mutex_handle { + bool done; + bool locked; + struct ctdb_cluster_mutex_handle *h; +}; + +struct do_lock_context { + struct mutex_handle *mh; + struct ctdb_context *ctdb; +}; + +static void do_lock_handler(char status, double latency, void *private_data) +{ + struct do_lock_context *dl = talloc_get_type_abort( + private_data, struct do_lock_context); + struct mutex_handle *mh; + + assert(dl->mh != NULL); + mh = dl->mh; + + mh->locked = (status == '0') ; + + /* + * If unsuccessful then ensure the process has exited and that + * the file descriptor event handler has been cancelled + */ + if (! mh->locked) { + TALLOC_FREE(mh->h); + } + + switch (status) { + case '0': + printf("LOCK\n"); + break; + + case '1': + printf("CONTENTION\n"); + break; + + case '2': + printf("TIMEOUT\n"); + break; + + default: + printf("ERROR\n"); + } + + fflush(stdout); + mh->done = true; +} + +static void do_lock_lost_handler(void *private_data) +{ + struct do_lock_context *dl = talloc_get_type_abort( + private_data, struct do_lock_context); + + printf("LOST\n"); + fflush(stdout); + TALLOC_FREE(dl->mh); +} + +static void do_lock_take(struct do_lock_context *dl, + const char *mutex_string) +{ + struct ctdb_cluster_mutex_handle *h; + + dl->mh = talloc_zero(dl, struct mutex_handle); + assert(dl->mh != NULL); + + h = ctdb_cluster_mutex(dl->mh, + dl->ctdb, + mutex_string, + 120, + do_lock_handler, + dl, + do_lock_lost_handler, + dl); + assert(h != NULL); + + dl->mh->h = h; +} + +static void do_lock_wait_done(struct do_lock_context *dl) +{ + assert(dl->mh != NULL); + + while (! dl->mh->done) { + tevent_loop_once(dl->ctdb->ev); + } +} + +static void do_lock_check(struct do_lock_context *dl) +{ + assert(dl->mh != NULL); + + if (! dl->mh->locked) { + printf("NOLOCK\n"); + fflush(stdout); + TALLOC_FREE(dl->mh); + } +} + +static void do_lock(struct do_lock_context *dl, + const char *mutex_string) +{ + do_lock_take(dl, mutex_string); + + do_lock_wait_done(dl); + + do_lock_check(dl); +} + +static void do_unlock(struct do_lock_context *dl) +{ + if (dl->mh == NULL) { + return; + } + + if (! dl->mh->done) { + /* + * Taking of lock still in progress. Free the cluster + * mutex handle to release it but leave the lock + * handle in place to allow taking of the lock to + * fail. + */ + printf("CANCEL\n"); + fflush(stdout); + TALLOC_FREE(dl->mh->h); + dl->mh->done = true; + dl->mh->locked = false; + return; + } + + printf("UNLOCK\n"); + fflush(stdout); + TALLOC_FREE(dl->mh); +} + +static void wait_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, + void *private_data) +{ + bool *done = (bool *)private_data; + + *done = true; +} + +static void do_lock_wait_time(struct do_lock_context *dl, + unsigned long wait_time) +{ + struct tevent_timer *tt; + bool done = false; + + tt = tevent_add_timer(dl->ctdb->ev, + dl, + tevent_timeval_current_ofs(wait_time, 0), + wait_handler, + &done); + assert(tt != NULL); + + while (!done && dl->mh != NULL) { + tevent_loop_once(dl->ctdb->ev); + } +} + +/* + * Testcases + */ + +static void test_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl1; + struct do_lock_context *dl2; + + dl1 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl1 != NULL); + dl1->ctdb = ctdb; + + dl2 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl2 != NULL); + dl2->ctdb = ctdb; + + /* LOCK */ + do_lock(dl1, mutex_string); + assert(dl1->mh != NULL); + + /* CONTENTION */ + do_lock(dl2, mutex_string); + assert(dl2->mh == NULL); + + /* UNLOCK */ + do_unlock(dl1); + assert(dl1->mh == NULL); +} + +static void test_lock_unlock_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl1; + struct do_lock_context *dl2; + + dl1 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl1 != NULL); + dl1->ctdb = ctdb; + + dl2 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl2 != NULL); + dl2->ctdb = ctdb; + + /* LOCK */ + do_lock(dl1, mutex_string); + assert(dl1->mh != NULL); + + /* UNLOCK */ + do_unlock(dl1); + assert(dl1->mh == NULL); + + /* LOCK */ + do_lock(dl2, mutex_string); + assert(dl2->mh != NULL); + + /* UNLOCK */ + do_unlock(dl2); + assert(dl2->mh == NULL); +} + +static void test_lock_cancel_check(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + do_lock_take(dl, mutex_string); + assert(dl->mh != NULL); + + /* CANCEL */ + do_unlock(dl); + assert(dl->mh != NULL); + + do_lock_wait_done(dl); + + /* NOLOCK */ + do_lock_check(dl); + assert(dl->mh == NULL); +} + +static void test_lock_cancel_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + do_lock_take(dl, mutex_string); + assert(dl->mh != NULL); + + /* CANCEL */ + do_unlock(dl); + assert(dl->mh != NULL); + + do_lock_wait_done(dl); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_wait_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* Wait for twice as long as the PPID timeout */ + do_lock_wait_time(dl, 2 * 5); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void fd_done_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + bool *done = (bool *)private_data; + + *done = true; +} + +static void test_lock_ppid_gone_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + struct tevent_fd *fde; + int pipefd[2]; + int ret; + pid_t pid, pid2; + ssize_t nread; + bool done; + + /* + * Do this in the parent - debugging aborts of the child is + * trickier + */ + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(pipefd[0]); + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* + * Note that we never see corresponding LOST. That + * would come from this process, but it is killed + * below. + */ + + nwritten = write(pipefd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + sleep(999); + exit(1); + } + + close(pipefd[1]); + + nread = read(pipefd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + /* + * pipefd[1] is leaked into the helper, so there will be an + * event generated when the helper exits + */ + done = false; + fde = tevent_add_fd(ctdb->ev, + ctdb, + pipefd[0], + TEVENT_FD_READ, + fd_done_handler, + &done); + assert(fde != NULL); + + ret = kill(pid, SIGKILL); + assert(ret == 0); + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + + while (! done) { + tevent_loop_once(ctdb->ev); + } + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_file_removed_no_recheck(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file) +{ + struct do_lock_context *dl1; + struct do_lock_context *dl2; + int ret; + + dl1 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl1 != NULL); + dl1->ctdb = ctdb; + + dl2 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl2 != NULL); + dl2->ctdb = ctdb; + + /* LOCK */ + do_lock(dl1, mutex_string); + assert(dl1->mh != NULL); + + ret = unlink(lock_file); + assert(ret == 0); + + /* LOCK */ + do_lock(dl2, mutex_string); + assert(dl2->mh != NULL); + + /* UNLOCK */ + do_unlock(dl2); + assert(dl2->mh == NULL); + + /* UNLOCK */ + do_unlock(dl1); + assert(dl1->mh == NULL); +} + +static void test_lock_file_wait_recheck_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + unsigned long wait_time) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + do_lock_wait_time(dl, wait_time); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_file_removed(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file) +{ + struct do_lock_context *dl; + int ret; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + ret = unlink(lock_file); + assert(ret == 0); + + while (dl->mh != NULL) { + /* LOST */ + tevent_loop_once(ctdb->ev); + } +} + +static void test_lock_file_changed(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file) +{ + struct do_lock_context *dl; + char *t; + int fd; + int ret; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + t = talloc_asprintf(ctdb, "%s.new", lock_file); + assert(t != NULL); + + fd = open(t, O_RDWR|O_CREAT, 0600); + assert(fd != -1); + close(fd); + + ret = rename(t, lock_file); + assert(ret == 0); + + while (dl->mh != NULL) { + /* LOST */ + tevent_loop_once(ctdb->ev); + } +} + +static void test_lock_io_timeout(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file, + unsigned long block_wait, + unsigned long block_time) +{ + struct do_lock_context *dl; + int pipefd[2]; + int ret; + pid_t pid, pid2; + ssize_t nwritten; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + static struct flock lock = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + .l_start = 1, + .l_len = 1, + .l_pid = 0, + }; + ssize_t nread; + int fd; + + close(pipefd[1]); + + /* Only continue when the parent is ready */ + nread = read(pipefd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + sleep(block_wait); + + fd = open(lock_file, O_RDWR, 0600); + assert(fd != -1); + + ret = fcntl(fd, F_SETLKW, &lock); + assert(ret == 0); + + sleep(block_time); + + close(fd); + + sleep(999); + + _exit(0); + } + + close(pipefd[0]); + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + nwritten = write(pipefd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + do_lock_wait_time(dl, block_wait + block_time * 2); + if (dl->mh != NULL) { + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); + } + + ret = kill(pid, SIGKILL); + assert(ret == 0); + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); +} + +/* + * Main + */ + +static const char *prog; + +static void usage(void) +{ + fprintf(stderr, "usage: %s <test> <mutex-string> [<arg>...]\n", prog); + exit(1); +} + +static void alarm_handler(int sig) +{ + abort(); +} + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct ctdb_context *ctdb; + const char *mutex_string; + const char *test; + struct sigaction sa = { .sa_handler = NULL, }; + int ret; + const char *lock_file; + unsigned int wait_time; + + prog = argv[0]; + + if (argc < 3) { + usage(); + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ctdb = talloc_zero(mem_ctx, struct ctdb_context); + assert(ctdb != NULL); + + ctdb->ev = tevent_context_init(ctdb); + assert(ctdb->ev != NULL); + + /* Add a 60s timeout for the whole test */ + sa.sa_handler = alarm_handler; + sigemptyset(&sa.sa_mask); + ret = sigaction(SIGALRM, &sa, NULL); + assert(ret == 0); + alarm(60); + + test_backtrace_setup(); + + test = argv[1]; + mutex_string = argv[2]; + + if (strcmp(test, "lock-unlock") == 0) { + test_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-lock-unlock") == 0) { + test_lock_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-unlock-lock-unlock") == 0) { + test_lock_unlock_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-cancel-check") == 0) { + test_lock_cancel_check(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-cancel-unlock") == 0) { + test_lock_cancel_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-wait-unlock") == 0) { + test_lock_wait_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-ppid-gone-lock-unlock") == 0) { + test_lock_ppid_gone_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-file-removed-no-recheck") == 0) { + if (argc != 4) { + usage(); + } + + lock_file = argv[3]; + + test_lock_file_removed_no_recheck(mem_ctx, + ctdb, + mutex_string, + lock_file); + } else if (strcmp(test, "lock-file-wait-recheck-unlock") == 0) { + if (argc != 4) { + usage(); + } + + wait_time = smb_strtoul(argv[3], + NULL, + 10, + &ret, + SMB_STR_STANDARD); + if (ret != 0) { + usage(); + } + + test_lock_file_wait_recheck_unlock(mem_ctx, + ctdb, + mutex_string, + wait_time); + } else if (strcmp(test, "lock-file-removed") == 0) { + if (argc != 4) { + usage(); + } + + lock_file = argv[3]; + + test_lock_file_removed(mem_ctx, + ctdb, + mutex_string, + lock_file); + } else if (strcmp(test, "lock-file-changed") == 0) { + if (argc != 4) { + usage(); + } + + lock_file = argv[3]; + + test_lock_file_changed(mem_ctx, + ctdb, + mutex_string, + lock_file); + } else if (strcmp(test, "lock-io-timeout") == 0) { + unsigned long block_wait; + unsigned long block_time; + + if (argc != 6) { + usage(); + } + + lock_file = argv[3]; + block_wait = (unsigned long)atol(argv[4]); + block_time = (unsigned long)atol(argv[5]); + + test_lock_io_timeout(mem_ctx, + ctdb, + mutex_string, + lock_file, + block_wait, + block_time); + } else { + fprintf(stderr, "Unknown test\n"); + exit(1); + } + + talloc_free(mem_ctx); + + return 0; +} diff --git a/ctdb/tests/src/cluster_wait.c b/ctdb/tests/src/cluster_wait.c new file mode 100644 index 0000000..d411591 --- /dev/null +++ b/ctdb/tests/src/cluster_wait.c @@ -0,0 +1,346 @@ +/* + Cluster wide synchronization + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/tevent_unix.h" + +#include "client/client.h" + +#include "tests/src/cluster_wait.h" + +#define MSG_ID_JOIN (CTDB_SRVID_TEST_RANGE | 0x1) +#define MSG_ID_SYNC (CTDB_SRVID_TEST_RANGE | 0x2) + +/* Wait for all the clients to initialize */ + +struct cluster_wait_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + uint32_t num_nodes; + bool *ready; + bool join_done; +}; + +static void cluster_wait_join_registered(struct tevent_req *subreq); +static void cluster_wait_sync_registered(struct tevent_req *subreq); +static void cluster_wait_join(struct tevent_req *subreq); +static void cluster_wait_join_sent(struct tevent_req *subreq); +static void cluster_wait_join_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void cluster_wait_join_unregistered(struct tevent_req *subreq); +static void cluster_wait_sync_sent(struct tevent_req *subreq); +static void cluster_wait_sync_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void cluster_wait_sync_unregistered(struct tevent_req *subreq); + +struct tevent_req *cluster_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + uint32_t num_nodes) +{ + struct tevent_req *req, *subreq; + struct cluster_wait_state *state; + bool ok; + + req = tevent_req_create(mem_ctx, &state, struct cluster_wait_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->num_nodes = num_nodes; + + state->join_done = false; + + if (ctdb_client_pnn(client) == 0) { + state->ready = talloc_zero_array(state, bool, num_nodes); + if (tevent_req_nomem(state->ready, req)) { + return tevent_req_post(req, ev); + } + + subreq = ctdb_client_set_message_handler_send( + state, ev, client, MSG_ID_JOIN, + cluster_wait_join_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, cluster_wait_join_registered, + req); + } + + subreq = ctdb_client_set_message_handler_send( + state, ev, client, MSG_ID_SYNC, + cluster_wait_sync_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, cluster_wait_sync_registered, req); + + /* If cluster is not synchronized within 30 seconds, time out */ + ok = tevent_req_set_endtime( + req, + ev, + tevent_timeval_current_ofs(30, 0)); + if (!ok) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void cluster_wait_join_registered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + printf("Waiting for cluster\n"); + fflush(stdout); +} + +static void cluster_wait_sync_registered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join, req); +} + +static void cluster_wait_join(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct ctdb_req_message msg; + uint32_t pnn; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + pnn = ctdb_client_pnn(state->client); + + msg.srvid = MSG_ID_JOIN; + msg.data.data.dsize = sizeof(pnn); + msg.data.data.dptr = (uint8_t *)&pnn; + + subreq = ctdb_client_message_send(state, state->ev, state->client, + 0, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join_sent, req); +} + +static void cluster_wait_join_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join, req); +} + +static void cluster_wait_join_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct tevent_req *subreq; + uint32_t pnn; + uint32_t i; + + if (srvid != MSG_ID_JOIN) { + return; + } + + if (data.dsize != sizeof(uint32_t)) { + return; + } + + pnn = *(uint32_t *)data.dptr; + + if (pnn > state->num_nodes) { + return; + } + + state->ready[pnn] = true; + + for (i=0; i<state->num_nodes; i++) { + if (! state->ready[i]) { + return; + } + } + + if (state->join_done) { + return; + } + + state->join_done = true; + subreq = ctdb_client_remove_message_handler_send( + state, state->ev, state->client, + MSG_ID_JOIN, req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join_unregistered, req); +} + +static void cluster_wait_join_unregistered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct ctdb_req_message msg; + bool status; + int ret; + + status = ctdb_client_remove_message_handler_recv(subreq, &ret); + if (! status) { + tevent_req_error(req, ret); + return; + } + + msg.srvid = MSG_ID_SYNC; + msg.data.data = tdb_null; + + subreq = ctdb_client_message_send(state, state->ev, state->client, + CTDB_BROADCAST_CONNECTED, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_sync_sent, req); +} + +static void cluster_wait_sync_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } +} + +static void cluster_wait_sync_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct tevent_req *subreq; + + if (srvid != MSG_ID_SYNC) { + return; + } + + subreq = ctdb_client_remove_message_handler_send( + state, state->ev, state->client, + MSG_ID_SYNC, req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_sync_unregistered, req); +} + +static void cluster_wait_sync_unregistered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_remove_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +bool cluster_wait_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} diff --git a/ctdb/tests/src/cluster_wait.h b/ctdb/tests/src/cluster_wait.h new file mode 100644 index 0000000..e0c64cc --- /dev/null +++ b/ctdb/tests/src/cluster_wait.h @@ -0,0 +1,30 @@ +/* + Cluster wide synchronization + + Copyright (C) Amitay Isaacs 2015 + + 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/>. +*/ + +#ifndef __CLUSTER_WAIT_H__ +#define __CLUSTER_WAIT_H__ + +struct tevent_req *cluster_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + uint32_t num_nodes); + +bool cluster_wait_recv(struct tevent_req *req, int *perr); + +#endif /* __CLUSTER_WAIT_H__ */ diff --git a/ctdb/tests/src/cmdline_test.c b/ctdb/tests/src/cmdline_test.c new file mode 100644 index 0000000..916d820 --- /dev/null +++ b/ctdb/tests/src/cmdline_test.c @@ -0,0 +1,480 @@ +/* + Command line processing tests + + Copyright (C) Amitay Isaacs 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <popt.h> +#include <talloc.h> + +#include <assert.h> + +#include "common/cmdline.c" + +static int dummy_func(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + return 0; +} + +static struct poptOption dummy_options[] = { + POPT_TABLEEND +}; + +static struct cmdline_command dummy_commands[] = { + CMDLINE_TABLEEND +}; + +static void test1(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, NULL, NULL, NULL, NULL, &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, "test1", NULL, NULL, NULL, &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test1", + dummy_options, + NULL, + NULL, + &cmdline); + assert(ret == EINVAL); + + talloc_free(mem_ctx); +} + +static struct cmdline_command test2_nofunc[] = { + { "nofunc", NULL, NULL, NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_nohelp[] = { + { "nohelp", dummy_func, NULL, NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_long[] = { + { "really really long command with lots of words", + dummy_func, "long command help", + "<and lots of really long long arguments>" }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_longhelp[] = { + { "longhelp", dummy_func, + "this is a really really really long help message" \ + "with lots of words and lots of description", + NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_twowords[] = { + { "multiple words", dummy_func, "multiple words help", NULL }, + CMDLINE_TABLEEND +}; + +static void test2(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_nofunc, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_nohelp, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_long, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_longhelp, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_twowords, + &cmdline); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +struct { + const char *str; +} test3_data; + +static struct poptOption test3_noname[] = { + { NULL, 'o', POPT_ARG_STRING, &test3_data.str, 0, + "Noname option", NULL }, + POPT_TABLEEND +}; + +static struct poptOption test3_notype[] = { + { "debug", 'd', POPT_ARG_NONE, NULL, 0, + "No argument option", NULL }, + POPT_TABLEEND +}; + +static struct poptOption test3_noarg[] = { + { "debug", 'd', POPT_ARG_STRING, NULL, 0, + "No argument option", NULL }, + POPT_TABLEEND +}; + +static void test3(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test3", + test3_noname, + NULL, + dummy_commands, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test3", + test3_notype, + NULL, + dummy_commands, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test3", + test3_noarg, + NULL, + dummy_commands, + &cmdline); + assert(ret == EINVAL); + + talloc_free(mem_ctx); +} + +static int test4_count; +static int test4_value; + +static struct poptOption test4_options[] = { + { "count", 'c', POPT_ARG_INT, &test4_count, 0, + "Option help of length thirty.", NULL }, + { "value", 'v', POPT_ARG_INT, &test4_value, 0, + "Short description", "Value help of length 23" }, + POPT_TABLEEND +}; + +static struct cmdline_command test4_commands[] = { + { "A really really long command", dummy_func, + "This is a really long help message", + "<a long arguments message>" }, + { "short command", dummy_func, + "short msg for short command", "<short arg msg>" }, + CMDLINE_TABLEEND +}; + +static void test4(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test4", + test4_options, + NULL, + test4_commands, + &cmdline); + assert(ret == 0); + + cmdline_usage(cmdline, NULL); + cmdline_usage(cmdline, "short command"); + + talloc_free(mem_ctx); +} + +static int action_func(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + if (argc != 1) { + return 100; + } + + printf("%s\n", argv[0]); + return 200; +} + +static struct cmdline_command action_commands[] = { + { "action one", dummy_func, "action one help", NULL }, + { "action two", action_func, "action two help", NULL }, + CMDLINE_TABLEEND +}; + +static void test5(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + const char *argv1[] = { "test5", "--help" }; + const char *argv2[] = { "test5", "action" }; + const char *argv3[] = { "test5", "action", "--help" }; + const char *argv4[] = { "test5", "action", "one" }; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test5", + NULL, + "Action", + action_commands, + &cmdline); + assert(ret == 0); + + ret = cmdline_parse(cmdline, 2, argv1, true); + assert(ret == EAGAIN); + + ret = cmdline_parse(cmdline, 2, argv2, true); + assert(ret == ENOENT); + + ret = cmdline_parse(cmdline, 3, argv3, true); + assert(ret == EAGAIN); + + ret = cmdline_parse(cmdline, 3, argv4, true); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +static void test6(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + const char *argv1[] = { "action", "two" }; + const char *argv2[] = { "action", "two", "arg1" }; + const char *argv3[] = { "action", "two", "arg1", "arg2" }; + int ret, result; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test6", + NULL, + NULL, + action_commands, + &cmdline); + assert(ret == 0); + + ret = cmdline_parse(cmdline, 2, argv1, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 100); + + ret = cmdline_parse(cmdline, 3, argv2, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 200); + + ret = cmdline_parse(cmdline, 4, argv3, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 100); + + talloc_free(mem_ctx); +} + +static int test7_func(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + assert(argc == 1); + + printf("%s\n", argv[0]); + + return 0; +} + +static struct cmdline_command test7_basic_commands[] = { + { "cmd1", test7_func, "command one help", NULL }, + { "cmd2", test7_func, "command two help", NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test7_advanced_commands[] = { + { "cmd3", test7_func, "command three help", NULL }, + { "cmd4", test7_func, "command four help", NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test7_ultimate_commands[] = { + { "cmd5", test7_func, "command five help", NULL }, + { "cmd6", test7_func, "command six help", NULL }, + CMDLINE_TABLEEND +}; + +static void test7(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + const char *argv1[] = { "cmd1", "one" }; + const char *argv2[] = { "cmd3", "three" }; + const char *argv3[] = { "cmd6", "six" }; + int ret, result; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test7", + NULL, + "Basic", + test7_basic_commands, + &cmdline); + assert(ret == 0); + + ret = cmdline_add(cmdline, "Advanced", test7_advanced_commands); + assert(ret == 0); + + ret = cmdline_add(cmdline, "Ultimate", test7_ultimate_commands); + assert(ret == 0); + + cmdline_usage(cmdline, NULL); + + printf("\n"); + + ret = cmdline_parse(cmdline, 2, argv1, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 0); + + ret = cmdline_parse(cmdline, 2, argv2, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 0); + + ret = cmdline_parse(cmdline, 2, argv3, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 0); + + talloc_free(mem_ctx); +} + + +int main(int argc, const char **argv) +{ + int num; + + if (argc < 2) { + fprintf(stderr, "Usage %s <testnum>\n", argv[0]); + exit(1); + } + + num = atoi(argv[1]); + + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + case 4: + test4(); + break; + + case 5: + test5(); + break; + + case 6: + test6(); + break; + + case 7: + test7(); + break; + } + + return 0; +} diff --git a/ctdb/tests/src/comm_client_test.c b/ctdb/tests/src/comm_client_test.c new file mode 100644 index 0000000..41ed5f7 --- /dev/null +++ b/ctdb/tests/src/comm_client_test.c @@ -0,0 +1,217 @@ +/* + comm tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" + + +struct writer_state { + struct tevent_context *ev; + struct comm_context *comm; + uint8_t *buf; + size_t *pkt_size; + size_t count, id; +}; + +static void writer_done(struct tevent_req *subreq); +static void read_handler(uint8_t *buf, size_t buflen, void *private_data); +static void dead_handler(void *private_data); + +static struct tevent_req *writer_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, size_t *pkt_size, + size_t count) +{ + struct tevent_req *req, *subreq; + struct writer_state *state; + size_t max_size = 0, buflen; + size_t i; + int ret; + + for (i=0; i<count; i++) { + if (pkt_size[i] > max_size) { + max_size = pkt_size[i]; + } + } + + req = tevent_req_create(mem_ctx, &state, struct writer_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->pkt_size = pkt_size; + state->count = count; + state->id = 0; + + ret = comm_setup(state, ev, fd, read_handler, req, + dead_handler, req, &state->comm); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + state->buf = talloc_array(state, uint8_t, max_size); + if (state->buf == NULL) { + talloc_free(req); + return NULL; + } + for (i=0; i<max_size; i++) { + state->buf[i] = i%256; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, writer_done, req); + + return req; +} + +static void writer_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool ret; + int err; + + ret = comm_write_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, err); + return; + } +} + +static void read_handler(uint8_t *buf, size_t buflen, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct writer_state *state = tevent_req_data( + req, struct writer_state); + struct tevent_req *subreq; + + if (buflen != state->pkt_size[state->id]) { + tevent_req_error(req, EIO); + return; + } + + state->id++; + if (state->id >= state->count) { + tevent_req_done(req); + return; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, writer_done, req); +} + +static void dead_handler(void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + + tevent_req_error(req, EPIPE); +} + +static void writer_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return; + } + *perr = 0; +} + +static int socket_init(char *sockpath) +{ + struct sockaddr_un addr; + int fd, ret, i; + size_t len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + assert(len < sizeof(addr.sun_path)); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(fd != -1); + + for (i=0; i<5; i++) { + ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret == 0) { + break; + } + sleep(1); + } + assert(ret != -1); + + return fd; +} + +int main(int argc, char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + int fd; + size_t pkt_size[13] = { 100, 2048, 500, 4096, 1024, 8192, + 200, 16384, 300, 32768, 400, 65536, + 1024*1024 }; + int err; + + if (argc != 2) { + printf("Usage: %s <sockpath>\n", argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + fd = socket_init(argv[1]); + + req = writer_send(mem_ctx, ev, fd, pkt_size, 13); + assert(req != NULL); + + tevent_req_poll(req, ev); + + writer_recv(req, &err); + assert(err == 0); + + exit(0); +} diff --git a/ctdb/tests/src/comm_server_test.c b/ctdb/tests/src/comm_server_test.c new file mode 100644 index 0000000..86b5658 --- /dev/null +++ b/ctdb/tests/src/comm_server_test.c @@ -0,0 +1,292 @@ +/* + comm tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "lib/async_req/async_sock.h" + +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" + +struct echo_state { + struct tevent_context *ev; + int fd; + struct comm_context *comm; + uint8_t *data; +}; + +static void read_handler(uint8_t *buf, size_t buflen, void *private_data); +static void read_failed(void *private_data); +static void write_done(struct tevent_req *subreq); + +static struct tevent_req *echo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, int fd) +{ + struct tevent_req *req; + struct echo_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct echo_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + + ret = comm_setup(state, ev, fd, read_handler, req, + read_failed, req, &state->comm); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + return req; +} + +static void read_handler(uint8_t *buf, size_t buflen, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct echo_state *state = tevent_req_data( + req, struct echo_state); + struct tevent_req *subreq; + + state->data = talloc_memdup(state, buf, buflen); + if (tevent_req_nomem(state->data, req)) { + return; + } + + subreq = comm_write_send(state, state->ev, state->comm, + state->data, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, write_done, req); +} + +static void read_failed(void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + + tevent_req_done(req); +} + +static void write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct echo_state *state = tevent_req_data( + req, struct echo_state); + bool ret; + int err; + + TALLOC_FREE(state->data); + + ret = comm_write_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, err); + return; + } +} + +static bool echo_recv(struct tevent_req *req, int *perr) +{ + struct echo_state *state = tevent_req_data( + req, struct echo_state); + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + + close(state->fd); + return true; +} + + +struct socket_process_state { + struct tevent_context *ev; + int fd; + int max_clients; + int num_clients; +}; + +static void socket_process_client(struct tevent_req *subreq); +static void socket_process_client_done(struct tevent_req *subreq); + +static struct tevent_req *socket_process_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, int max_clients) +{ + struct tevent_req *req, *subreq; + struct socket_process_state *state; + + req = tevent_req_create(mem_ctx, &state, struct socket_process_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->max_clients = max_clients; + state->num_clients = 0; + + subreq = accept_send(state, ev, fd); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, socket_process_client, req); + + return req; +} + +static void socket_process_client(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct socket_process_state *state = tevent_req_data( + req, struct socket_process_state); + int client_fd; + int err = 0; + + client_fd = accept_recv(subreq, NULL, NULL, &err); + TALLOC_FREE(subreq); + + state->num_clients++; + + if (client_fd == -1) { + tevent_req_error(req, err); + return; + } + + subreq = echo_send(state, state->ev, client_fd); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, socket_process_client_done, req); + + if (state->num_clients == state->max_clients) { + /* Stop accepting any more clients */ + return; + } + + subreq = accept_send(state, state->ev, state->fd); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, socket_process_client, req); +} + +static void socket_process_client_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct socket_process_state *state = tevent_req_data( + req, struct socket_process_state); + bool ret; + int err = 0; + + ret = echo_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, EIO); + return; + } + + if (state->num_clients == state->max_clients) { + tevent_req_done(req); + } +} + +static void socket_process_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + } +} + +static int socket_init(char *sockpath) +{ + struct sockaddr_un addr; + int fd, ret; + size_t len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + assert(len < sizeof(addr.sun_path)); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(fd != -1); + + ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret != -1); + + ret = listen(fd, 10); + assert(ret != -1); + + return fd; +} + +int main(int argc, char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + int fd, err = 0; + int num_clients; + + if (argc != 3) { + printf("Usage: %s <sockpath> <num_clients>\n", argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + fd = socket_init(argv[1]); + num_clients = atoi(argv[2]); + assert(num_clients > 0); + + req = socket_process_send(mem_ctx, ev, fd, num_clients); + assert(req != NULL); + + tevent_req_poll(req, ev); + + socket_process_recv(req, &err); + return err; +} diff --git a/ctdb/tests/src/comm_test.c b/ctdb/tests/src/comm_test.c new file mode 100644 index 0000000..4595928 --- /dev/null +++ b/ctdb/tests/src/comm_test.c @@ -0,0 +1,501 @@ +/* + comm tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" + +/* + * Test read_handler and dead_handler + */ + +static void test1_read_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + int *result = (int *)private_data; + + *result = -1; +} + +static void test1_dead_handler(void *private_data) +{ + int *result = (int *)private_data; + + *result = 1; +} + +static void test1(void) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm; + int fd[2]; + int result = 0; + uint32_t data[2]; + int ret; + ssize_t n; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = pipe(fd); + assert(ret == 0); + + ret = comm_setup(ev, ev, fd[0], test1_read_handler, &result, + test1_dead_handler, &result, &comm); + assert(ret == 0); + + data[0] = 2 * sizeof(uint32_t); + data[1] = 0; + + n = write(fd[1], (void *)&data, data[0]); + assert(n == data[0]); + + while (result == 0) { + tevent_loop_once(ev); + } + + assert(result == -1); + + result = 0; + close(fd[1]); + + while (result == 0) { + tevent_loop_once(ev); + } + + assert(result == 1); + + talloc_free(mem_ctx); +} + +/* + * Test that the tevent_req returned by comm_write_send() can be free'd. + */ + +struct test2_state { + TALLOC_CTX *mem_ctx; + bool done; +}; + +static void test2_read_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test2_state *state = (struct test2_state *)private_data; + + TALLOC_FREE(state->mem_ctx); +} + +static void test2_dead_handler(void *private_data) +{ + abort(); +} + +struct test2_write_state { + int count; +}; + +static void test2_write_done(struct tevent_req *subreq); + +static struct tevent_req *test2_write_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct comm_context *comm, + uint8_t *buf, size_t buflen) +{ + struct tevent_req *req, *subreq; + struct test2_write_state *state; + int i; + + req = tevent_req_create(mem_ctx, &state, struct test2_write_state); + if (req == NULL) { + return NULL; + } + + state->count = 0; + + for (i=0; i<10; i++) { + subreq = comm_write_send(state, ev, comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test2_write_done, req); + } + + return req; +} + +static void test2_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test2_write_state *state = tevent_req_data( + req, struct test2_write_state); + bool status; + int ret; + + status = comm_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->count += 1; + + if (state->count == 10) { + tevent_req_done(req); + } +} + +static void test2_timer_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval cur_time, + void *private_data) +{ + struct test2_state *state = (struct test2_state *)private_data; + + state->done = true; +} + +static void test2(void) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm_reader, *comm_writer; + struct test2_state test2_state; + struct tevent_req *req; + struct tevent_timer *te; + int fd[2]; + uint32_t data[2]; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + test2_state.mem_ctx = talloc_new(mem_ctx); + assert(test2_state.mem_ctx != NULL); + + test2_state.done = false; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = pipe(fd); + assert(ret == 0); + + ret = comm_setup(ev, ev, fd[0], test2_read_handler, &test2_state, + test2_dead_handler, NULL, &comm_reader); + assert(ret == 0); + + ret = comm_setup(ev, ev, fd[1], NULL, NULL, test2_dead_handler, NULL, + &comm_writer); + assert(ret == 0); + + data[0] = 2 * sizeof(uint32_t); + data[1] = 0; + + req = test2_write_send(test2_state.mem_ctx, ev, comm_writer, + (uint8_t *)data, data[0]); + assert(req != NULL); + + te = tevent_add_timer(ev, ev, tevent_timeval_current_ofs(5,0), + test2_timer_handler, &test2_state); + assert(te != NULL); + + while (! test2_state.done) { + tevent_loop_once(ev); + } + + talloc_free(mem_ctx); +} + +/* + * Test that data is written and read correctly. + */ + +static void test3_dead_handler(void *private_data) +{ + int dead_data = *(int *)private_data; + + assert(dead_data == 1 || dead_data == 2); + + if (dead_data == 1) { + /* reader */ + fprintf(stderr, "writer closed pipe\n"); + } else { + /* writer */ + fprintf(stderr, "reader closed pipe\n"); + } +} + +struct test3_writer_state { + struct tevent_context *ev; + struct comm_context *comm; + uint8_t *buf; + size_t *pkt_size; + int count, id; +}; + +static void test3_writer_next(struct tevent_req *subreq); + +static struct tevent_req *test3_writer_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct comm_context *comm, + size_t *pkt_size, size_t count) +{ + struct tevent_req *req, *subreq; + struct test3_writer_state *state; + size_t max_size = 0, buflen; + size_t i; + + for (i=0; i<count; i++) { + if (pkt_size[i] > max_size) { + max_size = pkt_size[i]; + } + } + + req = tevent_req_create(mem_ctx, &state, struct test3_writer_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->comm = comm; + state->pkt_size = pkt_size; + state->count = count; + state->id = 0; + + state->buf = talloc_array(state, uint8_t, max_size); + if (state->buf == NULL) { + talloc_free(req); + return NULL; + } + for (i=0; i<max_size; i++) { + state->buf[i] = i%256; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, test3_writer_next, req); + return req; +} + +static void test3_writer_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test3_writer_state *state = tevent_req_data( + req, struct test3_writer_state); + bool ret; + int err; + size_t buflen; + + ret = comm_write_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, err); + return; + } + + state->id++; + if (state->id >= state->count) { + tevent_req_done(req); + return; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + + tevent_req_set_callback(subreq, test3_writer_next, req); +} + +static void test3_writer_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return; + } + *perr = 0; +} + +static void test3_writer(int fd, size_t *pkt_size, size_t count) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm; + struct tevent_req *req; + int dead_data = 2; + int err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + err = comm_setup(mem_ctx, ev, fd, NULL, NULL, + test3_dead_handler, &dead_data, &comm); + assert(err == 0); + assert(comm != NULL); + + req = test3_writer_send(mem_ctx, ev, comm, pkt_size, count); + assert(req != NULL); + + tevent_req_poll(req, ev); + + test3_writer_recv(req, &err); + assert(err == 0); + + talloc_free(mem_ctx); +} + +struct test3_reader_state { + size_t *pkt_size; + int count, received; + bool done; +}; + +static void test3_reader_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test3_reader_state *state = talloc_get_type_abort( + private_data, struct test3_reader_state); + + assert(buflen == state->pkt_size[state->received]); + printf("%zi ", buflen); + state->received++; + + if (state->received == state->count) { + printf("\n"); + state->done = true; + } +} + +static void test3_reader(int fd, size_t *pkt_size, int count) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm; + struct test3_reader_state *state; + int dead_data = 1; + int err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + state = talloc_zero(mem_ctx, struct test3_reader_state); + assert(state != NULL); + + state->pkt_size = pkt_size; + state->count = count; + state->received = 0; + state->done = false; + + err = comm_setup(mem_ctx, ev, fd, test3_reader_handler, state, + test3_dead_handler, &dead_data, &comm); + assert(err == 0); + assert(comm != NULL); + + while (!state->done) { + tevent_loop_once(ev); + } + + talloc_free(mem_ctx); +} + +static void test3(void) +{ + int fd[2]; + int ret; + pid_t pid; + size_t pkt_size[13] = { 100, 2048, 500, 4096, 1024, 8192, + 200, 16384, 300, 32768, 400, 65536, + 1024*1024 }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Child process */ + close(fd[0]); + test3_writer(fd[1], pkt_size, 13); + close(fd[1]); + exit(0); + } + + close(fd[1]); + test3_reader(fd[0], pkt_size, 13); + close(fd[0]); +} + + +int main(int argc, const char **argv) +{ + int num; + + if (argc != 2) { + fprintf(stderr, "%s <testnum>\n", argv[0]); + exit(1); + } + + num = atoi(argv[1]); + + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + default: + fprintf(stderr, "Unknown test number %s\n", argv[1]); + } + + return 0; +} diff --git a/ctdb/tests/src/conf_test.c b/ctdb/tests/src/conf_test.c new file mode 100644 index 0000000..9b3bd8f --- /dev/null +++ b/ctdb/tests/src/conf_test.c @@ -0,0 +1,513 @@ +/* + Configuration file handling on top of tini + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> + +#include "common/conf.c" + +static void test1(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_section(conf, NULL, NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test2(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_string(conf, "section1", "key1", "default", NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test3(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", NULL, NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test4(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", NULL, NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_integer(conf, "section1", "key1", 10, NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test5(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + enum conf_type type; + int ret; + bool status; + const char *s_val; + int i_val; + bool b_val; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + conf_assign_string_pointer(conf, "section1", "key1", &s_val); + conf_assign_integer_pointer(conf, "section1", "key2", &i_val); + conf_assign_boolean_pointer(conf, "section1", "key3", &b_val); + + status = conf_valid(conf); + assert(status == true); + + status = conf_query(conf, "section1", "key1", &type); + assert(status == true); + assert(type == CONF_STRING); + + status = conf_query(conf, "section1", "key2", &type); + assert(status == true); + assert(type == CONF_INTEGER); + + status = conf_query(conf, "section1", "key3", &type); + assert(status == true); + assert(type == CONF_BOOLEAN); + + assert(strcmp(s_val, "value1") == 0); + assert(i_val == 10); + assert(b_val == true); + + conf_set_defaults(conf); + + assert(strcmp(s_val, "value1") == 0); + assert(i_val == 10); + assert(b_val == true); + + talloc_free(mem_ctx); +} + +static void test6(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + const char *s_val, *s2_val; + int i_val, i2_val; + bool b_val, b2_val, is_default; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "default", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + conf_assign_string_pointer(conf, "section1", "key1", &s_val); + conf_assign_integer_pointer(conf, "section1", "key2", &i_val); + conf_assign_boolean_pointer(conf, "section1", "key3", &b_val); + + status = conf_valid(conf); + assert(status == true); + + is_default = false; + ret = conf_get_string(conf, "section1", "key1", &s2_val, &is_default); + assert(ret == 0); + assert(strcmp(s2_val, "default") == 0); + assert(is_default == true); + + is_default = false; + ret = conf_get_integer(conf, "section1", "key2", &i2_val, &is_default); + assert(ret == 0); + assert(i2_val == 10); + assert(is_default == true); + + is_default = false; + ret = conf_get_boolean(conf, "section1", "key3", &b2_val, &is_default); + assert(ret == 0); + assert(b2_val == true); + assert(is_default == true); + + ret = conf_set_string(conf, "section1", "key1", "foobar"); + assert(ret == 0); + + ret = conf_set_integer(conf, "section1", "key2", 20); + assert(ret == 0); + + ret = conf_set_boolean(conf, "section1", "key3", false); + assert(ret == 0); + + assert(strcmp(s_val, "foobar") == 0); + assert(i_val == 20); + assert(b_val == false); + + is_default = true; + ret = conf_get_string(conf, "section1", "key1", &s2_val, &is_default); + assert(ret == 0); + assert(strcmp(s2_val, "foobar") == 0); + assert(is_default == false); + + is_default = true; + ret = conf_get_integer(conf, "section1", "key2", &i2_val, &is_default); + assert(ret == 0); + assert(i2_val == 20); + assert(is_default == false); + + is_default = true; + ret = conf_get_boolean(conf, "section1", "key3", &b2_val, &is_default); + assert(ret == 0); + assert(b2_val == false); + assert(is_default == false); + + conf_dump(conf, stdout); + + conf_set_defaults(conf); + + assert(strcmp(s_val, "default") == 0); + assert(i_val == 10); + assert(b_val == true); + + talloc_free(mem_ctx); +} + +static bool test7_validate_string(const char *key, + const char *old_value, const char *new_value, + enum conf_update_mode mode) +{ + return false; +} + +static bool test7_validate_integer(const char *key, + int old_value, int new_value, + enum conf_update_mode mode) +{ + return false; +} + +static bool test7_validate_boolean(const char *key, + bool old_value, bool new_value, + enum conf_update_mode mode) +{ + return false; +} + +static void test7(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + const char *s_val, *s2_val; + int i_val, i2_val; + bool b_val, b2_val; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "default", + test7_validate_string); + conf_define_integer(conf, "section1", "key2", 10, + test7_validate_integer); + conf_define_boolean(conf, "section1", "key3", true, + test7_validate_boolean); + + conf_assign_string_pointer(conf, "section1", "key1", &s_val); + conf_assign_integer_pointer(conf, "section1", "key2", &i_val); + conf_assign_boolean_pointer(conf, "section1", "key3", &b_val); + + status = conf_valid(conf); + assert(status == true); + + ret = conf_set_string(conf, "section1", "key1", "default"); + assert(ret == 0); + + ret = conf_set_string(conf, "section1", "key1", "foobar"); + assert(ret == EINVAL); + + ret = conf_set_integer(conf, "section1", "key2", 10); + assert(ret == 0); + + ret = conf_set_integer(conf, "section1", "key2", 20); + assert(ret == EINVAL); + + ret = conf_set_boolean(conf, "section1", "key3", true); + assert(ret == 0); + + ret = conf_set_boolean(conf, "section1", "key3", false); + assert(ret == EINVAL); + + assert(strcmp(s_val, "default") == 0); + assert(i_val == 10); + assert(b_val == true); + + ret = conf_get_string(conf, "section1", "key2", &s2_val, NULL); + assert(ret == EINVAL); + + ret = conf_get_integer(conf, "section1", "key3", &i2_val, NULL); + assert(ret == EINVAL); + + ret = conf_get_boolean(conf, "section1", "key1", &b2_val, NULL); + assert(ret == EINVAL); + + talloc_free(mem_ctx); +} + +static bool test8_validate(struct conf_context *conf, + const char *section, + enum conf_update_mode mode) +{ + return false; +} + +static void test8(const char *filename) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", test8_validate); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "default", NULL); + + status = conf_valid(conf); + assert(status == true); + + ret = conf_load(conf, filename, true); + conf_dump(conf, stdout); + + talloc_free(mem_ctx); + exit(ret); +} + +static void test9(const char *filename, bool ignore_unknown) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + status = conf_valid(conf); + assert(status == true); + + conf_set_boolean(conf, "section1", "key3", false); + + ret = conf_load(conf, filename, ignore_unknown); + conf_dump(conf, stdout); + + talloc_free(mem_ctx); + exit(ret); +} + +static void test11(const char *filename) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + char reload[PATH_MAX]; + struct conf_context *conf; + int ret; + bool status; + + ret = snprintf(reload, sizeof(reload), "%s.reload", filename); + assert((size_t)ret < sizeof(reload)); + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + status = conf_valid(conf); + assert(status == true); + + ret = conf_load(conf, filename, false); + assert(ret == 0); + + ret = rename(reload, filename); + assert(ret == 0); + + ret = conf_reload(conf); + assert(ret == 0); + + conf_dump(conf, stdout); + + talloc_free(mem_ctx); + exit(ret); +} + +int main(int argc, const char **argv) +{ + int num; + + if (argc < 2) { + fprintf(stderr, "Usage: %s <testnum> [<config>]\n", argv[0]); + exit(1); + } + + num = atoi(argv[1]); + if (num > 7 && argc != 3) { + fprintf(stderr, "Usage: %s <testnum> [<config>]\n", argv[0]); + exit(1); + } + + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + case 4: + test4(); + break; + + case 5: + test5(); + break; + + case 6: + test6(); + break; + + case 7: + test7(); + break; + + case 8: + test8(argv[2]); + break; + + case 9: + test9(argv[2], true); + break; + + case 10: + test9(argv[2], false); + break; + + case 11: + test11(argv[2]); + break; + } + + return 0; +} diff --git a/ctdb/tests/src/ctdb_io_test.c b/ctdb/tests/src/ctdb_io_test.c new file mode 100644 index 0000000..b035342 --- /dev/null +++ b/ctdb/tests/src/ctdb_io_test.c @@ -0,0 +1,356 @@ +/* + ctdb_io tests + + Copyright (C) Christof Schmitt 2019 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "common/ctdb_io.c" + +void ctdb_set_error(struct ctdb_context *ctdb, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + assert(false); +} + +static void test_setup(ctdb_queue_cb_fn_t cb, + int *pfd, + struct ctdb_context **pctdb, + struct ctdb_queue **pqueue) +{ + int pipefd[2], ret; + struct ctdb_context *ctdb; + struct ctdb_queue *queue; + + ret = pipe(pipefd); + assert(ret == 0); + + ctdb = talloc_zero(NULL, struct ctdb_context); + assert(ctdb != NULL); + + ctdb->ev = tevent_context_init(NULL); + + queue = ctdb_queue_setup(ctdb, ctdb, pipefd[0], 0, cb, + NULL, "test queue"); + assert(queue != NULL); + + *pctdb = ctdb; + *pfd = pipefd[1]; + if (pqueue != NULL) { + *pqueue = queue; + } +} + +static const size_t test1_req_len = 8; +static const char *test1_req = "abcdefgh"; + +static void test1_callback(uint8_t *data, size_t length, void *private_data) +{ + uint32_t len; + + len = *(uint32_t *)data; + assert(len == sizeof(uint32_t) + test1_req_len); + + assert(length == sizeof(uint32_t) + test1_req_len); + assert(memcmp(data + sizeof(len), test1_req, test1_req_len) == 0); +} + +static void test1(void) +{ + struct ctdb_context *ctdb; + int fd; + ssize_t ret; + uint32_t pkt_size; + + test_setup(test1_callback, &fd, &ctdb, NULL); + + pkt_size = sizeof(uint32_t) + test1_req_len; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, test1_req, test1_req_len); + assert(ret != -1 && (size_t)ret == test1_req_len); + + tevent_loop_once(ctdb->ev); + + TALLOC_FREE(ctdb); +} + +static const size_t test2_req_len[] = { 900, 24, 600 }; + +static int test2_cb_num = 0; + +static void test2_callback(uint8_t *data, size_t length, void *private_data) +{ + uint32_t len; + + len = *(uint32_t *)data; + assert(len == sizeof(uint32_t) + test2_req_len[test2_cb_num]); + assert(length == sizeof(uint32_t) + test2_req_len[test2_cb_num]); + + test2_cb_num++; +} + +static void test2(void) +{ + struct ctdb_context *ctdb; + int fd; + ssize_t ret; + size_t i; + uint32_t pkt_size; + char req[1024] = { 0 }; + + for (i = 0; i < sizeof(req); i++) { + req[i] = i % CHAR_MAX; + } + + test_setup(test2_callback, &fd, &ctdb, NULL); + + /* + * request 0 + */ + + pkt_size = sizeof(uint32_t) + test2_req_len[0]; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, req, test2_req_len[0]); + assert(ret != -1 && (size_t)ret == test2_req_len[0]); + + /* + * request 1 + */ + pkt_size = sizeof(uint32_t) + test2_req_len[1]; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + /* + * Omit the last byte to avoid buffer processing. + */ + ret = write(fd, req, test2_req_len[1] - 1); + assert(ret != -1 && (size_t)ret == test2_req_len[1] - 1); + + tevent_loop_once(ctdb->ev); + + /* + * Write the missing byte now. + */ + ret = write(fd, &req[test2_req_len[1] - 1], 1); + assert(ret != -1 && (size_t)ret == 1); + + /* + * request 2 + */ + pkt_size = sizeof(uint32_t) + test2_req_len[2]; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, req, test2_req_len[2]); + assert(ret != -1 && (size_t)ret == test2_req_len[2]); + + tevent_loop_once(ctdb->ev); + tevent_loop_once(ctdb->ev); + + assert(test2_cb_num == 2); + + TALLOC_FREE(ctdb); +} + +static void test_cb(uint8_t *data, size_t length, void *private_data) +{ + /* dummy handler, not verifying anything */ + TALLOC_FREE(data); +} + +static void test3(void) +{ + struct ctdb_context *ctdb; + struct ctdb_queue *queue; + uint32_t pkt_size; + char *request; + size_t req_len; + int fd; + ssize_t ret; + + test_setup(test_cb, &fd, &ctdb, &queue); + request = talloc_zero_size(queue, queue->buffer_size); + + /* + * calculate a request length which will fit into the buffer + * but not twice. Because we need to write the size integer + * as well (4-bytes) we're guaranteed that no 2 packets will fit. + */ + req_len = queue->buffer_size >> 1; + + /* writing first packet */ + pkt_size = sizeof(uint32_t) + req_len; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, request, req_len); + assert(ret != -1 && (size_t)ret == req_len); + + /* writing second, incomplete packet */ + pkt_size = sizeof(uint32_t) + req_len; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, request, req_len >> 1); + assert(ret != -1 && (size_t)ret == req_len >> 1); + + /* process...only 1st packet can be processed */ + tevent_loop_once(ctdb->ev); + + /* we should see a progressed offset of req_len + sizeof(pkt_size) */ + assert(queue->buffer.offset == req_len + sizeof(pkt_size)); + + /* writing another few bytes of the still incomplete packet */ + ret = write(fd, request, (req_len >> 1) - 1); + assert(ret != -1 && (size_t)ret == (req_len >> 1) - 1); + + /* + * the packet is still incomplete and cannot be processed + * but the packet data had to be moved in the buffer in order + * to fetch the new 199 bytes -> offset must be 0 now. + */ + tevent_loop_once(ctdb->ev); + /* + * needs to be called twice as an incomplete packet + * does not trigger a schedule_immediate + */ + tevent_loop_once(ctdb->ev); + + assert(queue->buffer.offset == 0); + + TALLOC_FREE(ctdb); +} + +static void test4(void) +{ + struct ctdb_context *ctdb; + struct ctdb_queue *queue; + uint32_t pkt_size; + char *request; + size_t req_len, half_buf_size; + int fd; + ssize_t ret; + + test_setup(test_cb, &fd, &ctdb, &queue); + + req_len = queue->buffer_size << 1; /* double the buffer size */ + request = talloc_zero_size(queue, req_len); + + /* writing first part of packet exceeding standard buffer size */ + pkt_size = sizeof(uint32_t) + req_len; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + half_buf_size = queue->buffer_size >> 1; + + ret = write(fd, request, req_len - half_buf_size); + assert(ret != -1 && (size_t)ret == req_len - half_buf_size); + + /* + * process... + * this needs to be done to have things changed + */ + tevent_loop_once(ctdb->ev); + /* + * needs to be called twice as an initial incomplete packet + * does not trigger a schedule_immediate + */ + tevent_loop_once(ctdb->ev); + + /* the buffer should be resized to packet size now */ + assert(queue->buffer.size == pkt_size); + + /* writing remaining data */ + ret = write(fd, request, half_buf_size); + assert(ret != -1 && (size_t)ret == half_buf_size); + + /* process... */ + tevent_loop_once(ctdb->ev); + + /* + * the buffer was increased beyond its standard size. + * once packet got processed, the buffer has to be free'd + * and will be re-allocated with standard size on new request arrival. + */ + + assert(queue->buffer.size == 0); + + /* writing new packet to verify standard buffer size */ + pkt_size = sizeof(uint32_t) + half_buf_size; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, request, half_buf_size); + assert(ret != -1 && (size_t)ret == half_buf_size); + + /* process... */ + tevent_loop_once(ctdb->ev); + + /* back to standard buffer size */ + assert(queue->buffer.size == queue->buffer_size); + + TALLOC_FREE(ctdb); +} + +int main(int argc, const char **argv) +{ + int num; + + if (argc != 2) { + fprintf(stderr, "%s <testnum>\n", argv[0]); + exit(1); + } + + + num = atoi(argv[1]); + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + case 4: + test4(); + break; + + default: + fprintf(stderr, "Unknown test number %s\n", argv[1]); + } + + return 0; +} diff --git a/ctdb/tests/src/ctdb_packet_parse.c b/ctdb/tests/src/ctdb_packet_parse.c new file mode 100644 index 0000000..0b99b34 --- /dev/null +++ b/ctdb/tests/src/ctdb_packet_parse.c @@ -0,0 +1,136 @@ +/* + CTDB protocol parser + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" +#include "system/locale.h" + +#include <talloc.h> +#include <tdb.h> + +#include "protocol/protocol.h" +#include "protocol/protocol_api.h" + +static TDB_DATA strace_parser(char *buf, TALLOC_CTX *mem_ctx) +{ + TDB_DATA data; + size_t i = 0, j = 0; + + data.dptr = talloc_size(mem_ctx, strlen(buf)); + if (data.dptr == NULL) { + return tdb_null; + } + + while (i < strlen(buf)) { + if (buf[i] == '\\') { + /* first char after '\' is a digit or other escape */ + if (isdigit(buf[i+1])) { + char tmp[4] = { '\0', '\0', '\0', '\0' }; + + tmp[0] = buf[i+1]; + if (isdigit(buf[i+2])) { + tmp[1] = buf[i+2]; + if (isdigit(buf[i+3])) { + tmp[2] = buf[i+3]; + i += 4; + } else { + i += 3; + } + } else { + i += 2; + } + data.dptr[j] = strtol(tmp, NULL, 8); + } else if (buf[i+1] == 'a') { + data.dptr[j] = 7; + i += 2; + } else if (buf[i+1] == 'b') { + data.dptr[j] = 8; + i += 2; + } else if (buf[i+1] == 't') { + data.dptr[j] = 9; + i += 2; + } else if (buf[i+1] == 'n') { + data.dptr[j] = 10; + i += 2; + } else if (buf[i+1] == 'v') { + data.dptr[j] = 11; + i += 2; + } else if (buf[i+1] == 'f') { + data.dptr[j] = 12; + i += 2; + } else if (buf[i+1] == 'r') { + data.dptr[j] = 13; + i += 2; + } else { + fprintf(stderr, + "Unknown escape \\%c\n", + buf[i+1]); + data.dptr[j] = 0; + } + + j += 1; + } else if (buf[i] == '\n') { + i += 1; + } else if (buf[i] == '\0') { + break; + } else { + data.dptr[j] = buf[i]; + i += 1; + j += 1; + } + } + + data.dsize = j; + + return data; +} + +int main(int argc, char *argv[]) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + char line[1024]; + char *ptr; + TDB_DATA (*parser)(char *, TALLOC_CTX *); + + if (argc != 2) { + fprintf(stderr, "Usage: %s strace\n", argv[0]); + exit(1); + } + + if (strcmp(argv[1], "strace") == 0) { + parser = strace_parser; + } else { + fprintf(stderr, "Unknown input format - %s\n", argv[1]); + exit(1); + } + + while ((ptr = fgets(line, sizeof(line), stdin)) != NULL) { + TDB_DATA data; + + data = parser(ptr, mem_ctx); + if (data.dptr == NULL) { + continue; + } + + ctdb_packet_print(data.dptr, data.dsize, stdout); + TALLOC_FREE(data.dptr); + } + + return 0; +} diff --git a/ctdb/tests/src/ctdb_takeover_tests.c b/ctdb/tests/src/ctdb_takeover_tests.c new file mode 100644 index 0000000..ad7d7ee --- /dev/null +++ b/ctdb/tests/src/ctdb_takeover_tests.c @@ -0,0 +1,281 @@ +/* + Tests for ctdb_takeover.c + + Copyright (C) Martin Schwenke 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <assert.h> +#include <talloc.h> + +#include "lib/util/debug.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_util.h" +#include "common/logging.h" +#include "common/system.h" + +#include "server/ipalloc.h" + +#include "ipalloc_read_known_ips.h" + +static void print_ctdb_public_ip_list(TALLOC_CTX *mem_ctx, + struct public_ip_list * ips) +{ + while (ips) { + printf("%s %d\n", + ctdb_sock_addr_to_string(mem_ctx, &(ips->addr), false), + ips->pnn); + ips = ips->next; + } +} + +static uint32_t *get_tunable_values(TALLOC_CTX *tmp_ctx, + int numnodes, + const char *tunable); +static enum ctdb_runstate *get_runstate(TALLOC_CTX *tmp_ctx, + int numnodes); + +static void read_ctdb_public_ip_info(TALLOC_CTX *ctx, + int numnodes, + bool multi, + struct ctdb_public_ip_list ** known, + struct ctdb_public_ip_list ** avail) +{ + int n; + enum ctdb_runstate *runstate; + + *known = ipalloc_read_known_ips(ctx, numnodes, multi); + assert(*known != NULL); + + *avail = talloc_zero_array(ctx, struct ctdb_public_ip_list, + numnodes); + assert(*avail != NULL); + + runstate = get_runstate(ctx, numnodes); + for (n = 0; n < numnodes; n++) { + if (runstate[n] == CTDB_RUNSTATE_RUNNING) { + (*avail)[n] = (*known)[n]; + } + } +} + +static uint32_t *get_tunable_values(TALLOC_CTX *tmp_ctx, + int numnodes, + const char *tunable) +{ + int i; + char *tok; + uint32_t *tvals = talloc_zero_array(tmp_ctx, uint32_t, numnodes); + char *t = getenv(tunable); + + if (t == NULL) { + return tvals; + } + + if (strcmp(t, "1") == 0) { + for (i = 0; i < numnodes; i++) { + tvals[i] = 1; + } + } else { + tok = strtok(t, ","); + i = 0; + while (tok != NULL) { + tvals[i] = (uint32_t)strtol(tok, NULL, 0); + i++; + tok = strtok(NULL, ","); + } + if (i != numnodes) { + fprintf(stderr, + "ERROR: Wrong number of values in %s\n", + tunable); + exit(1); + } + } + + return tvals; +} + +static enum ctdb_runstate *get_runstate(TALLOC_CTX *tmp_ctx, + int numnodes) +{ + int i; + uint32_t *tvals; + enum ctdb_runstate *runstate = + talloc_zero_array(tmp_ctx, enum ctdb_runstate, numnodes); + char *t = getenv("CTDB_TEST_RUNSTATE"); + + if (t == NULL) { + for (i=0; i<numnodes; i++) { + runstate[i] = CTDB_RUNSTATE_RUNNING; + } + } else { + tvals = get_tunable_values(tmp_ctx, numnodes, "CTDB_TEST_RUNSTATE"); + for (i=0; i<numnodes; i++) { + runstate[i] = (enum ctdb_runstate) tvals[i]; + } + talloc_free(tvals); + } + + return runstate; +} + +/* Fake up enough CTDB state to be able to run the IP allocation + * algorithm. Usually this sets up some standard state, sets the node + * states from the command-line and reads the current IP layout from + * stdin. + * + * However, if read_ips_for_multiple_nodes is true then each node's + * idea of the IP layout is read separately from stdin. In this mode + * is doesn't make much sense to use read_ctdb_public_ip_info's + * optional ALLOWED_PNN,... list in the input, since each node is + * being handled separately anyway. IPs for each node are separated + * by a blank line. This mode is for testing weird behaviours where + * the IP layouts differs across nodes and we want to improve + * create_merged_ip_list(), so should only be used in tests of + * ipalloc(). Yes, it is a hack... :-) + */ +static void ctdb_test_init(TALLOC_CTX *mem_ctx, + const char nodestates[], + struct ipalloc_state **ipalloc_state, + bool read_ips_for_multiple_nodes) +{ + struct ctdb_public_ip_list *known; + struct ctdb_public_ip_list *avail; + char *tok, *ns; + const char *t; + struct ctdb_node_map *nodemap; + uint32_t noiptakeover; + ctdb_sock_addr sa_zero = { .ip = { 0 } }; + enum ipalloc_algorithm algorithm; + uint32_t n; + + /* Avoid that const */ + ns = talloc_strdup(mem_ctx, nodestates); + + nodemap = talloc_zero(mem_ctx, struct ctdb_node_map); + assert(nodemap != NULL); + nodemap->num = 0; + tok = strtok(ns, ","); + while (tok != NULL) { + n = nodemap->num; + nodemap->node = talloc_realloc(nodemap, nodemap->node, + struct ctdb_node_and_flags, n+1); + nodemap->node[n].pnn = n; + nodemap->node[n].flags = (uint32_t) strtol(tok, NULL, 0); + nodemap->node[n].addr = sa_zero; + nodemap->num++; + tok = strtok(NULL, ","); + } + + algorithm = IPALLOC_LCP2; + if ((t = getenv("CTDB_IP_ALGORITHM"))) { + if (strcmp(t, "lcp2") == 0) { + algorithm = IPALLOC_LCP2; + } else if (strcmp(t, "nondet") == 0) { + algorithm = IPALLOC_NONDETERMINISTIC; + } else if (strcmp(t, "det") == 0) { + algorithm = IPALLOC_DETERMINISTIC; + } else { + DEBUG(DEBUG_ERR, + ("ERROR: unknown IP algorithm %s\n", t)); + exit(1); + } + } + + t = getenv("CTDB_SET_NoIPTakeover"); + if (t != NULL) { + noiptakeover = (uint32_t) strtol(t, NULL, 0); + } else { + noiptakeover = 0; + } + + *ipalloc_state = ipalloc_state_init(mem_ctx, nodemap->num, + algorithm, + (noiptakeover != 0), + false, + NULL); + assert(*ipalloc_state != NULL); + + read_ctdb_public_ip_info(mem_ctx, nodemap->num, + read_ips_for_multiple_nodes, + &known, &avail); + + /* Drop available IPs for INACTIVE/DISABLED nodes */ + for (n = 0; n < nodemap->num; n++) { + uint32_t flags = nodemap->node[n].flags; + if ((flags & (NODE_FLAGS_INACTIVE|NODE_FLAGS_DISABLED)) != 0) { + avail[n].num = 0; + } + } + + ipalloc_set_public_ips(*ipalloc_state, known, avail); +} + +/* IP layout is read from stdin. See comment for ctdb_test_init() for + * explanation of read_ips_for_multiple_nodes. + */ +static void ctdb_test_ipalloc(const char nodestates[], + bool read_ips_for_multiple_nodes) +{ + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + struct ipalloc_state *ipalloc_state; + + ctdb_test_init(tmp_ctx, nodestates, &ipalloc_state, + read_ips_for_multiple_nodes); + + print_ctdb_public_ip_list(tmp_ctx, ipalloc(ipalloc_state)); + + talloc_free(tmp_ctx); +} + +static void usage(void) +{ + fprintf(stderr, "usage: ctdb_takeover_tests <op>\n"); + exit(1); +} + +int main(int argc, const char *argv[]) +{ + int loglevel; + const char *debuglevelstr = getenv("CTDB_TEST_LOGLEVEL"); + + setup_logging("ctdb_takeover_tests", DEBUG_STDERR); + + if (! debug_level_parse(debuglevelstr, &loglevel)) { + loglevel = DEBUG_DEBUG; + } + debuglevel_set(loglevel); + + if (argc < 2) { + usage(); + } + + if (argc == 3 && + strcmp(argv[1], "ipalloc") == 0) { + ctdb_test_ipalloc(argv[2], false); + } else if (argc == 4 && + strcmp(argv[1], "ipalloc") == 0 && + strcmp(argv[3], "multi") == 0) { + ctdb_test_ipalloc(argv[2], true); + } else { + usage(); + } + + return 0; +} diff --git a/ctdb/tests/src/db_hash_test.c b/ctdb/tests/src/db_hash_test.c new file mode 100644 index 0000000..31aa501 --- /dev/null +++ b/ctdb/tests/src/db_hash_test.c @@ -0,0 +1,138 @@ +/* + db_hash tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> + +#include "common/db_hash.c" + +static int record_parser(uint8_t *keybuf, size_t keylen, + uint8_t *databuf, size_t datalen, + void *private_data) +{ + int *count = (int *)private_data; + + (*count) += 1; + return 0; +} + +static void do_test(enum db_hash_type type) +{ + struct db_hash_context *dh = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + uint8_t key[] = "This is a long key"; + uint8_t value[] = "This is a long value"; + int ret; + int count = 0; + + ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == EINVAL); + + ret = db_hash_add(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == EINVAL); + + ret = db_hash_exists(dh, key, sizeof(key)); + assert(ret == EINVAL); + + ret = db_hash_delete(dh, key, sizeof(key)); + assert(ret == EINVAL); + + ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh); + assert(ret == 0); + + ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == 0); + + ret = db_hash_exists(dh, key, sizeof(key)); + assert(ret == 0); + + ret = db_hash_fetch(dh, key, sizeof(key), NULL, NULL); + assert(ret == EINVAL); + + ret = db_hash_fetch(dh, key, sizeof(key), record_parser, &count); + assert(ret == 0); + assert(count == 1); + + ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == EEXIST); + + ret = db_hash_delete(dh, key, sizeof(key)); + assert(ret == 0); + + ret = db_hash_exists(dh, key, sizeof(key)); + assert(ret == ENOENT); + + ret = db_hash_delete(dh, key, sizeof(key)); + assert(ret == ENOENT); + + ret = db_hash_add(dh, key, sizeof(key), key, sizeof(key)); + assert(ret == 0); + + ret = db_hash_add(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == 0); + + talloc_free(dh); + ret = talloc_get_size(mem_ctx); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +static void do_traverse_test(enum db_hash_type type) +{ + struct db_hash_context *dh = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + char key[16] = "keyXXXX"; + char value[] = "This is some test value"; + int count, ret, i; + + ret = db_hash_traverse(dh, NULL, NULL, &count); + assert(ret == EINVAL); + + ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh); + assert(ret == 0); + + for (i=0; i<2000; i++) { + sprintf(key, "key%04d", i); + ret = db_hash_insert(dh, (uint8_t *)key, sizeof(key), + (uint8_t *)value, sizeof(value)); + assert(ret == 0); + } + + ret = db_hash_traverse(dh, NULL, NULL, &count); + assert(ret == 0); + assert(count == 2000); + + ret = db_hash_traverse(dh, record_parser, &count, NULL); + assert(ret == 0); + assert(count == 4000); + + talloc_free(dh); + talloc_free(mem_ctx); +} + +int main(void) +{ + do_test(DB_HASH_SIMPLE); + do_test(DB_HASH_COMPLEX); + do_traverse_test(DB_HASH_SIMPLE); + do_traverse_test(DB_HASH_COMPLEX); + return 0; +} diff --git a/ctdb/tests/src/db_test_tool.c b/ctdb/tests/src/db_test_tool.c new file mode 100644 index 0000000..e99da3c --- /dev/null +++ b/ctdb/tests/src/db_test_tool.c @@ -0,0 +1,792 @@ +/* + CTDB DB test tool + + Copyright (C) Martin Schwenke 2019 + + Parts based on ctdb.c, event_tool.c: + + Copyright (C) Amitay Isaacs 2015, 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/time.h" + +#include <ctype.h> +#include <popt.h> +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/debug.h" +#include "lib/util/sys_rw.h" +#include "lib/util/util.h" +#include "lib/util/smb_strtox.h" +#include "lib/tdb_wrap/tdb_wrap.h" + +#include "common/cmdline.h" +#include "common/logging.h" +#include "common/path.h" +#include "common/event_script.h" +#include "common/system_socket.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_api.h" +#include "protocol/protocol_util.h" + +#include "client/client.h" +#include "client/client_sync.h" + +struct tdb_context *client_db_tdb(struct ctdb_db_context *db); + +#define TIMEOUT() tevent_timeval_current_ofs(ctx->timelimit, 0) + +struct db_test_tool_context { + struct cmdline_context *cmdline; + struct tevent_context *ev; + struct ctdb_client_context *client; + uint32_t destnode; + uint32_t timelimit; +}; + +/* + * If this is ever consolidated into a larger test tool then these + * forward declarations can be moved to an include file + */ +int db_test_tool_init(TALLOC_CTX *mem_ctx, + const char *prog, + struct poptOption *options, + int argc, + const char **argv, + bool parse_options, + struct db_test_tool_context **result); +int db_test_tool_run(struct db_test_tool_context *ctx, int *result); + +static int db_test_get_lmaster(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_vnn_map *vnnmap; + TDB_DATA key; + uint32_t idx, lmaster; + unsigned int hash; + int ret = 0; + + if (argc != 1) { + cmdline_usage(ctx->cmdline, "get-lmaster"); + return 1; + } + + ret = ctdb_ctrl_getvnnmap(mem_ctx, + ctx->ev, + ctx->client, + CTDB_CURRENT_NODE, + TIMEOUT(), + &vnnmap); + if (ret != 0) { + D_ERR("Control GETVNN_MAP failed, ret=%d\n", ret); + return ret; + } + + key.dsize = strlen(argv[0]); + key.dptr = (uint8_t *)discard_const(argv[0]); + + hash = tdb_jenkins_hash(&key); + idx = hash % vnnmap->size; + lmaster = vnnmap->map[idx]; + + printf("%"PRId32"\n", lmaster); + + return 0; +} + +static struct ctdb_dbid *db_find(TALLOC_CTX *mem_ctx, + struct db_test_tool_context *ctx, + struct ctdb_dbid_map *dbmap, + const char *db_name) +{ + struct ctdb_dbid *db = NULL; + const char *name; + unsigned int i; + int ret; + + for (i=0; i<dbmap->num; i++) { + ret = ctdb_ctrl_get_dbname(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + dbmap->dbs[i].db_id, + &name); + if (ret != 0) { + return NULL; + } + + if (strcmp(db_name, name) == 0) { + talloc_free(discard_const(name)); + db = &dbmap->dbs[i]; + break; + } + } + + return db; +} + +static bool db_exists(TALLOC_CTX *mem_ctx, + struct db_test_tool_context *ctx, + const char *db_arg, + uint32_t *db_id, + const char **db_name, + uint8_t *db_flags) +{ + struct ctdb_dbid_map *dbmap; + struct ctdb_dbid *db = NULL; + uint32_t id = 0; + const char *name = NULL; + unsigned int i; + int ret = 0; + + ret = ctdb_ctrl_get_dbmap(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + &dbmap); + if (ret != 0) { + return false; + } + + if (strncmp(db_arg, "0x", 2) == 0) { + id = smb_strtoul(db_arg, NULL, 0, &ret, SMB_STR_STANDARD); + if (ret != 0) { + return false; + } + for (i=0; i<dbmap->num; i++) { + if (id == dbmap->dbs[i].db_id) { + db = &dbmap->dbs[i]; + break; + } + } + } else { + name = db_arg; + db = db_find(mem_ctx, ctx, dbmap, name); + } + + if (db == NULL) { + fprintf(stderr, "No database matching '%s' found\n", db_arg); + return false; + } + + if (name == NULL) { + ret = ctdb_ctrl_get_dbname(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + id, + &name); + if (ret != 0) { + return false; + } + } + + if (db_id != NULL) { + *db_id = db->db_id; + } + if (db_name != NULL) { + *db_name = talloc_strdup(mem_ctx, name); + } + if (db_flags != NULL) { + *db_flags = db->flags; + } + return true; +} + +static int db_test_fetch_local_delete(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_context *db = NULL; + struct ctdb_record_handle *h = NULL; + struct tdb_context *tdb; + struct ctdb_ltdb_header header; + const char *db_name; + TDB_DATA key, data; + uint32_t db_id; + uint8_t db_flags; + size_t len; + uint8_t *buf; + size_t np; + int ret; + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "fetch-local-delete"); + return 1; + } + + if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) { + return ENOENT; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return EINVAL; + } + + ret = ctdb_attach(ctx->ev, + ctx->client, + TIMEOUT(), + db_name, + db_flags, + &db); + if (ret != 0) { + D_ERR("Failed to attach to DB %s\n", db_name); + return ret; + } + + key.dsize = strlen(argv[1]); + key.dptr = (uint8_t *)discard_const(argv[1]); + + ret = ctdb_fetch_lock(mem_ctx, + ctx->ev, + ctx->client, + db, + key, + false, + &h, + &header, + NULL); + if (ret != 0) { + D_ERR("Failed to fetch record for key %s\n", argv[1]); + goto done; + } + + len = ctdb_ltdb_header_len(&header); + buf = talloc_size(mem_ctx, len); + if (buf == NULL) { + D_ERR("Memory allocation error\n"); + ret = ENOMEM; + goto done; + } + + ctdb_ltdb_header_push(&header, buf, &np); + + data.dsize = np; + data.dptr = buf; + + tdb = client_db_tdb(db); + + ret = tdb_store(tdb, key, data, TDB_REPLACE); + TALLOC_FREE(buf); + if (ret != 0) { + D_ERR("fetch_lock delete: %s tdb_store failed, %s\n", + db_name, + tdb_errorstr(tdb)); + } + +done: + TALLOC_FREE(h); + + return ret; +} + +#define ISASCII(x) (isprint(x) && ! strchr("\"\\", (x))) + +static void dump(const char *name, uint8_t *dptr, size_t dsize) +{ + size_t i; + + fprintf(stdout, "%s(%zu) = \"", name, dsize); + for (i = 0; i < dsize; i++) { + if (ISASCII(dptr[i])) { + fprintf(stdout, "%c", dptr[i]); + } else { + fprintf(stdout, "\\%02X", dptr[i]); + } + } + fprintf(stdout, "\"\n"); +} + +static void dump_ltdb_header(struct ctdb_ltdb_header *header) +{ + fprintf(stdout, "dmaster: %u\n", header->dmaster); + fprintf(stdout, "rsn: %" PRIu64 "\n", header->rsn); + fprintf(stdout, "flags: 0x%08x", header->flags); + if (header->flags & CTDB_REC_FLAG_MIGRATED_WITH_DATA) { + fprintf(stdout, " MIGRATED_WITH_DATA"); + } + if (header->flags & CTDB_REC_FLAG_VACUUM_MIGRATED) { + fprintf(stdout, " VACUUM_MIGRATED"); + } + if (header->flags & CTDB_REC_FLAG_AUTOMATIC) { + fprintf(stdout, " AUTOMATIC"); + } + if (header->flags & CTDB_REC_RO_HAVE_DELEGATIONS) { + fprintf(stdout, " RO_HAVE_DELEGATIONS"); + } + if (header->flags & CTDB_REC_RO_HAVE_READONLY) { + fprintf(stdout, " RO_HAVE_READONLY"); + } + if (header->flags & CTDB_REC_RO_REVOKING_READONLY) { + fprintf(stdout, " RO_REVOKING_READONLY"); + } + if (header->flags & CTDB_REC_RO_REVOKE_COMPLETE) { + fprintf(stdout, " RO_REVOKE_COMPLETE"); + } + fprintf(stdout, "\n"); + +} + +static int db_test_local_lock(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_context *db; + const char *db_name; + int pipefd[2]; + TDB_DATA key; + uint32_t db_id; + uint8_t db_flags; + pid_t pid; + int ret; + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "local-lock"); + return 1; + } + + + if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) { + D_ERR("DB %s not attached\n", db_name); + return 1; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return 1; + } + + ret = ctdb_attach(ctx->ev, + ctx->client, + TIMEOUT(), + db_name, + db_flags, + &db); + if (ret != 0) { + D_ERR("Failed to attach to DB %s\n", db_name); + return 1; + } + + ret = pipe(pipefd); + if (ret != 0) { + DBG_ERR("Failed to create pipe\n"); + return 1; + } + + pid = fork(); + if (pid < 0) { + DBG_ERR("Failed to fork()\n"); + return 1; + } + + if (pid != 0) { + ssize_t nread; + int status; + + close(pipefd[1]); + + nread = sys_read(pipefd[0], &status, sizeof(status)); + if (nread < 0 || (size_t)nread != sizeof(status)) { + status = EINVAL; + } + + if (status == 0) { + printf("OK %d\n", pid); + } else { + printf("FAIL %d\n", status); + } + fflush(stdout); + + return status; + } + + close(pipefd[0]); + + key.dsize = strlen(argv[1]); + key.dptr = (uint8_t *)discard_const(argv[1]); + + ret = tdb_chainlock(client_db_tdb(db), key); + if (ret != 0) { + D_ERR("Failed to lock chain for key %s\n", argv[1]); + goto fail; + } + + sys_write(pipefd[1], &ret, sizeof(ret)); + + fclose(stdin); + fclose(stdout); + fclose(stderr); + + /* Hold the lock- the caller should SIGTERM to release the lock */ + sleep(120); + exit(1); + +fail: + sys_write(pipefd[1], &ret, sizeof(ret)); + return ret; +} + +static int db_test_local_read(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_context *db; + struct ctdb_ltdb_header header; + const char *db_name; + TDB_DATA key, data; + uint32_t db_id; + uint8_t db_flags; + size_t np; + int ret; + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "local-read"); + return 1; + } + + if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) { + return ENOENT; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return EINVAL; + } + + ret = ctdb_attach(ctx->ev, + ctx->client, + TIMEOUT(), + db_name, + db_flags, + &db); + if (ret != 0) { + D_ERR("Failed to attach to DB %s\n", db_name); + return ret; + } + + key.dsize = strlen(argv[1]); + key.dptr = (uint8_t *)discard_const(argv[1]); + + data = tdb_fetch(client_db_tdb(db), key); + + if (data.dptr == NULL) { + D_ERR("No record for key %s\n", argv[1]); + return 1; + } + + if (data.dsize < sizeof(struct ctdb_ltdb_header)) { + D_ERR("Invalid record for key %s\n", argv[1]); + free(data.dptr); + return 1; + } + + ret = ctdb_ltdb_header_pull(data.dptr, data.dsize, &header, &np); + if (ret != 0) { + D_ERR("Failed to parse header from data\n"); + free(data.dptr); + return 1; + } + + dump_ltdb_header(&header); + dump("data", data.dptr + np, data.dsize - np); + + free(data.dptr); + + return 0; +} + +static int db_test_vacuum(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_vacuum db_vacuum; + struct ctdb_req_control request; + struct ctdb_reply_control *reply; + const char *db_arg; + uint32_t db_id; + const char *db_name; + uint8_t db_flags; + int ret = 0; + + if (argc != 1 && argc != 2) { + cmdline_usage(ctx->cmdline, "vacuum"); + return 1; + } + + db_arg = argv[0]; + + db_vacuum.full_vacuum_run = false; + if (argc == 2) { + if (strcmp(argv[1], "full") == 0) { + db_vacuum.full_vacuum_run = true; + } else { + cmdline_usage(ctx->cmdline, "vacuum"); + return 1; + } + } + + if (! db_exists(mem_ctx, ctx, db_arg, &db_id, &db_name, &db_flags)) { + return ENOENT; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return EINVAL; + } + + db_vacuum.db_id = db_id; + + ctdb_req_control_db_vacuum(&request, &db_vacuum); + + ret = ctdb_client_control(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + &request, + &reply); + if (ret != 0) { + D_ERR("Control DB_VACUUM failed to node %u, ret=%d\n", + ctx->destnode, + ret); + return ret; + } + + + ret = ctdb_reply_control_db_vacuum(reply); + if (ret != 0) { + D_ERR("Control DB_VACUUM failed, ret=%d\n", ret); + return ret; + } + + return 0; +} + +struct cmdline_command db_test_commands[] = { + { + .name = "get-lmaster", + .fn = db_test_get_lmaster, + .msg_help = "Print lmaster for key", + .msg_args = "<key>" + }, + { + .name = "fetch-local-delete", + .fn = db_test_fetch_local_delete, + .msg_help = "Fetch record and delete from local database", + .msg_args = "<dbname|dbid> <key>" + }, + { + .name = "local-lock", + .fn = db_test_local_lock, + .msg_help = "Lock a record in a local database", + .msg_args = "<dbname|dbid> <key>" + }, + { + .name = "local-read", + .fn = db_test_local_read, + .msg_help = "Read a record from local database", + .msg_args = "<dbname|dbid> <key>" + }, + { + .name = "vacuum", + .fn = db_test_vacuum, + .msg_help = "Vacuum a database", + .msg_args = "<dbname|dbid> [full]" + }, + CMDLINE_TABLEEND +}; + +int db_test_tool_init(TALLOC_CTX *mem_ctx, + const char *prog, + struct poptOption *options, + int argc, + const char **argv, + bool parse_options, + struct db_test_tool_context **result) +{ + struct db_test_tool_context *ctx; + int ret; + + ctx = talloc_zero(mem_ctx, struct db_test_tool_context); + if (ctx == NULL) { + D_ERR("Memory allocation error\n"); + return ENOMEM; + } + + ret = cmdline_init(mem_ctx, + prog, + options, + NULL, + db_test_commands, + &ctx->cmdline); + if (ret != 0) { + D_ERR("Failed to initialize cmdline, ret=%d\n", ret); + talloc_free(ctx); + return ret; + } + + ret = cmdline_parse(ctx->cmdline, argc, argv, parse_options); + if (ret != 0) { + cmdline_usage(ctx->cmdline, NULL); + talloc_free(ctx); + return ret; + } + + *result = ctx; + return 0; +} + +int db_test_tool_run(struct db_test_tool_context *ctx, int *result) +{ + char *ctdb_socket; + int ret; + + ctx->ev = tevent_context_init(ctx); + if (ctx->ev == NULL) { + D_ERR("Failed to initialize tevent\n"); + return ENOMEM; + } + + ctdb_socket = path_socket(ctx, "ctdbd"); + if (ctdb_socket == NULL) { + fprintf(stderr, "Memory allocation error\n"); + return ENOMEM; + } + + ret = ctdb_client_init(ctx, ctx->ev, ctdb_socket, &ctx->client); + if (ret != 0) { + D_ERR("Failed to connect to CTDB daemon (%s)\n", ctdb_socket); + return ret; + } + + ret = cmdline_run(ctx->cmdline, ctx, result); + return ret; +} + +#ifdef CTDB_DB_TEST_TOOL + +static struct { + const char *debug; + int destnode; + int timelimit; +} db_test_data = { + .debug = "ERROR", + .destnode = CTDB_CURRENT_NODE, + .timelimit = 60, +}; + +struct poptOption db_test_options[] = { + { + .longName = "debug", + .shortName = 'd', + .argInfo = POPT_ARG_STRING, + .arg = &db_test_data.debug, + .val = 0, + .descrip = "debug level", + .argDescrip = "ERROR|WARNING|NOTICE|INFO|DEBUG" + }, + { + .longName = "node", + .shortName = 'n', + .argInfo = POPT_ARG_INT, + .arg = &db_test_data.destnode, + .val = 0, + .descrip = "node number", + .argDescrip = "NUM" + }, + { + .longName = "timelimit", + .shortName = 't', + .argInfo = POPT_ARG_INT, + .arg = &db_test_data.timelimit, + .val = 0, + .descrip = "control time limit", + .argDescrip = "SECONDS" + }, + POPT_TABLEEND +}; + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct db_test_tool_context *ctx; + int ret, result = 0; + int level; + bool ok; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = db_test_tool_init(mem_ctx, + "ctdb-db-test", + db_test_options, + argc, + argv, + true, + &ctx); + if (ret != 0) { + talloc_free(mem_ctx); + exit(1); + } + + setup_logging("ctdb-db-test", DEBUG_STDERR); + ok = debug_level_parse(db_test_data.debug, &level); + if (!ok) { + level = DEBUG_ERR; + } + debuglevel_set(level); + + ctx->destnode = db_test_data.destnode; + ctx->timelimit = db_test_data.timelimit; + + ret = db_test_tool_run(ctx, &result); + if (ret != 0) { + result = ret; + } + + talloc_free(mem_ctx); + exit(result); +} + +#endif /* CTDB_DB_TEST_TOOL */ diff --git a/ctdb/tests/src/dummy_client.c b/ctdb/tests/src/dummy_client.c new file mode 100644 index 0000000..13e0691 --- /dev/null +++ b/ctdb/tests/src/dummy_client.c @@ -0,0 +1,163 @@ +/* + Dummy CTDB client for testing + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <popt.h> +#include <talloc.h> +#include <tevent.h> + +#include "common/logging.h" +#include "common/path.h" + +#include "client/client.h" + +static struct { + const char *sockpath; + const char *debuglevel; + int num_connections; + int timelimit; + const char *srvidstr; +} options; + +static struct poptOption cmdline_options[] = { + POPT_AUTOHELP + { "socket", 's', POPT_ARG_STRING, &options.sockpath, 0, + "Unix domain socket path", "filename" }, + { "debug", 'd', POPT_ARG_STRING, &options.debuglevel, 0, + "debug level", "ERR|WARNING|NOTICE|INFO|DEBUG" } , + { "nconn", 'n', POPT_ARG_INT, &options.num_connections, 0, + "number of connections", "" }, + { "timelimit", 't', POPT_ARG_INT, &options.timelimit, 0, + "time limit", "seconds" }, + { "srvid", 'S', POPT_ARG_STRING, &options.srvidstr, 0, + "srvid to register", "srvid" }, + POPT_TABLEEND +}; + +static void dummy_handler(uint64_t srvid, TDB_DATA data, void *private_data) +{ + bool *done = (bool *)private_data; + + *done = true; +} + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context **client; + struct ctdb_client_context *last_client; + poptContext pc; + int opt, ret, i; + int log_level; + bool status, done; + + /* Set default options */ + options.sockpath = NULL; + options.debuglevel = "ERR"; + options.num_connections = 1; + options.timelimit = 60; + options.srvidstr = NULL; + + pc = poptGetContext(argv[0], argc, argv, cmdline_options, + POPT_CONTEXT_KEEP_FIRST); + while ((opt = poptGetNextOpt(pc)) != -1) { + fprintf(stderr, "Invalid option %s\n", poptBadOption(pc, 0)); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + status = debug_level_parse(options.debuglevel, &log_level); + if (! status) { + fprintf(stderr, "Invalid debug level\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + setup_logging("dummy_client", DEBUG_STDERR); + debuglevel_set(log_level); + + if (options.sockpath == NULL) { + options.sockpath = path_socket(mem_ctx, "ctdbd"); + if (options.sockpath == NULL) { + D_ERR("Memory allocation error\n"); + exit(1); + } + } + + client = talloc_array(mem_ctx, struct ctdb_client_context *, + options.num_connections); + if (client == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + for (i=0; i<options.num_connections; i++) { + ret = ctdb_client_init(client, ev, options.sockpath, + &client[i]); + if (ret != 0) { + D_ERR("Failed to initialize client %d, ret=%d\n", + i, ret); + exit(1); + } + } + + last_client = client[options.num_connections-1]; + + done = false; + if (options.srvidstr != NULL) { + uint64_t srvid; + + srvid = strtoull(options.srvidstr, NULL, 0); + + ret = ctdb_client_set_message_handler(ev, last_client, srvid, + dummy_handler, &done); + if (ret != 0) { + D_ERR("Failed to register srvid, ret=%d\n", ret); + talloc_free(client); + exit(1); + } + + D_INFO("Registered SRVID 0x%"PRIx64"\n", srvid); + } + + ret = ctdb_client_wait_timeout(ev, &done, + tevent_timeval_current_ofs(options.timelimit, 0)); + if (ret != 0 && ret == ETIMEDOUT) { + D_ERR("client_wait_timeout() failed, ret=%d\n", ret); + talloc_free(client); + exit(1); + } + + talloc_free(mem_ctx); + exit(0); +} diff --git a/ctdb/tests/src/errcode.c b/ctdb/tests/src/errcode.c new file mode 100644 index 0000000..7343e81 --- /dev/null +++ b/ctdb/tests/src/errcode.c @@ -0,0 +1,189 @@ +/* + Portability layer for error codes + + Copyright (C) Amitay Isaacs 2018 + + 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/>. +*/ + +/* + * These errors are as listed in POSIX standard + * IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008) + * + * Error codes marked obsolete are removed (ENODATA, ENOSR, ENOSTR, ETIME) + */ + +#include "replace.h" + +struct { + const char *label; + int code; +} err_codes[] = { + { "E2BIG", E2BIG }, + + { "EACCES", EACCES }, + { "EADDRINUSE", EADDRINUSE }, + { "EADDRNOTAVAIL", EADDRNOTAVAIL }, + { "EAFNOSUPPORT", EAFNOSUPPORT }, + { "EAGAIN", EAGAIN }, + { "EALREADY", EALREADY }, + + { "EBADF", EBADF }, + { "EBADMSG", EBADMSG }, + { "EBUSY", EBUSY }, + + { "ECANCELED", ECANCELED }, + { "ECHILD", ECHILD }, + { "ECONNABORTED", ECONNABORTED }, + { "ECONNREFUSED", ECONNREFUSED }, + { "ECONNRESET", ECONNRESET }, + + { "EDEADLK", EDEADLK }, + { "EDESTADDRREQ", EDESTADDRREQ }, + { "EDOM", EDOM }, + { "EDQUOT", EDQUOT }, + + { "EEXIST", EEXIST }, + + { "EFAULT", EFAULT }, + { "EFBIG", EFBIG }, + + { "EHOSTUNREACH", EHOSTUNREACH }, + + { "EIDRM", EIDRM }, + { "EILSEQ", EILSEQ }, + { "EINPROGRESS", EINPROGRESS }, + { "EINTR", EINTR }, + { "EINVAL", EINVAL }, + { "EIO", EIO }, + { "EISCONN", EISCONN }, + { "EISDIR", EISDIR }, + + { "ELOOP", ELOOP }, + + { "EMFILE", EMFILE }, + { "EMLINK", EMLINK }, + { "EMSGSIZE", EMSGSIZE }, + { "EMULTIHOP", EMULTIHOP }, + + { "ENAMETOOLONG", ENAMETOOLONG }, + { "ENETDOWN", ENETDOWN }, + { "ENETRESET", ENETRESET }, + { "ENETUNREACH", ENETUNREACH }, + { "ENFILE", ENFILE }, + { "ENOBUFS", ENOBUFS }, + { "ENODEV", ENODEV }, + { "ENOENT", ENOENT }, + { "ENOEXEC", ENOEXEC }, + { "ENOLCK", ENOLCK }, + { "ENOLINK", ENOLINK }, + { "ENOMEM", ENOMEM }, + { "ENOMSG", ENOMSG }, + { "ENOPROTOOPT", ENOPROTOOPT }, + { "ENOSPC", ENOSPC }, + { "ENOSYS", ENOSYS }, + { "ENOTCONN", ENOTCONN }, + { "ENOTDIR", ENOTDIR }, + { "ENOTEMPTY", ENOTEMPTY }, + { "ENOTSOCK", ENOTSOCK }, + { "ENOTSUP", ENOTSUP }, + { "ENOTTY", ENOTTY }, + { "ENXIO", ENXIO }, + + { "EOPNOTSUPP", EOPNOTSUPP }, + { "EOVERFLOW", EOVERFLOW }, + + { "EPERM", EPERM }, + { "EPIPE", EPIPE }, + { "EPROTO", EPROTO }, + { "EPROTONOSUPPORT", EPROTONOSUPPORT }, + { "EPROTOTYPE", EPROTOTYPE }, + + { "ERANGE", ERANGE }, + { "EROFS", EROFS }, + + { "ESPIPE", ESPIPE }, + { "ESRCH", ESRCH }, + { "ESTALE", ESTALE }, + + { "ETIMEDOUT", ETIMEDOUT }, + { "ETXTBSY", ETXTBSY }, + + { "EWOULDBLOCK", EWOULDBLOCK }, + + { "EXDEV", EXDEV }, +}; + +static void dump(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(err_codes); i++) { + printf("%s %d\n", err_codes[i].label, err_codes[i].code); + } +} + +static void match_label(const char *str) +{ + int code = -1; + size_t i; + + for (i=0; i<ARRAY_SIZE(err_codes); i++) { + if (strcasecmp(err_codes[i].label, str) == 0) { + code = err_codes[i].code; + break; + } + } + + printf("%d\n", code); +} + +static void match_code(int code) +{ + const char *label = "UNKNOWN"; + size_t i; + + for (i=0; i<ARRAY_SIZE(err_codes); i++) { + if (err_codes[i].code == code) { + label = err_codes[i].label; + break; + } + } + + printf("%s\n", label); +} + +int main(int argc, const char **argv) +{ + long int code; + char *endptr; + + if (argc != 2) { + fprintf(stderr, "Usage: %s dump|<errcode>\n", argv[0]); + exit(1); + } + + if (strcmp(argv[1], "dump") == 0) { + dump(); + } else { + code = strtol(argv[1], &endptr, 0); + if (*endptr == '\0') { + match_code(code); + } else { + match_label(argv[1]); + } + } + + exit(0); +} diff --git a/ctdb/tests/src/event_script_test.c b/ctdb/tests/src/event_script_test.c new file mode 100644 index 0000000..f06725a --- /dev/null +++ b/ctdb/tests/src/event_script_test.c @@ -0,0 +1,120 @@ +/* + Low level event script handling tests + + Copyright (C) Martin Schwenke 2018 + + Based on run_event_test.c: + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <popt.h> +#include <talloc.h> + +#include <assert.h> + +#include "common/event_script.c" + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s list <scriptdir>\n", + prog); + fprintf(stderr, + " %s chmod enable <scriptdir> <scriptname>\n", + prog); + fprintf(stderr, + " %s chmod disable <scriptdir> <scriptname>\n", + prog); +} + +static void do_list(TALLOC_CTX *mem_ctx, int argc, const char **argv) +{ + struct event_script_list *script_list = NULL; + unsigned int i; + int ret; + + if (argc != 3) { + usage(argv[0]); + exit(1); + } + + ret = event_script_get_list(mem_ctx, argv[2], &script_list); + if (ret != 0) { + printf("Script list %s failed with result=%d\n", argv[2], ret); + return; + } + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No scripts found\n"); + return; + } + + for (i=0; i < script_list->num_scripts; i++) { + struct event_script *s = script_list->script[i]; + printf("%s\n", s->name); + } +} + +static void do_chmod(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + bool enable) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = event_script_chmod(argv[2], argv[3], enable); + + printf("Script %s %s %s completed with result=%d\n", + argv[1], argv[2], argv[3], ret); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + + if (argc < 3) { + usage(argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "talloc_new() failed\n"); + exit(1); + } + + if (strcmp(argv[1], "list") == 0) { + do_list(mem_ctx, argc, argv); + } else if (strcmp(argv[1], "enable") == 0) { + do_chmod(mem_ctx, argc, argv, true); + } else if (strcmp(argv[1], "disable") == 0) { + do_chmod(mem_ctx, argc, argv, false); + } else { + fprintf(stderr, "Invalid command %s\n", argv[2]); + usage(argv[0]); + } + + talloc_free(mem_ctx); + exit(0); +} diff --git a/ctdb/tests/src/fake_ctdbd.c b/ctdb/tests/src/fake_ctdbd.c new file mode 100644 index 0000000..0d430a3 --- /dev/null +++ b/ctdb/tests/src/fake_ctdbd.c @@ -0,0 +1,4781 @@ +/* + Fake CTDB server for testing + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" +#include "system/time.h" +#include "system/filesys.h" + +#include <popt.h> +#include <talloc.h> +#include <tevent.h> +#include <tdb.h> + +#include "lib/util/dlinklist.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/debug.h" +#include "lib/util/samba_util.h" +#include "lib/async_req/async_sock.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_api.h" +#include "protocol/protocol_util.h" +#include "protocol/protocol_private.h" + +#include "common/comm.h" +#include "common/logging.h" +#include "common/tunable.h" +#include "common/srvid.h" +#include "common/system.h" + +#include "ipalloc_read_known_ips.h" + + +#define CTDB_PORT 4379 + +/* A fake flag that is only supported by some functions */ +#define NODE_FLAGS_FAKE_TIMEOUT 0x80000000 + +struct node { + ctdb_sock_addr addr; + uint32_t pnn; + uint32_t flags; + uint32_t capabilities; + bool recovery_disabled; + void *recovery_substate; +}; + +struct node_map { + uint32_t num_nodes; + struct node *node; + uint32_t pnn; + uint32_t recmaster; +}; + +struct interface { + const char *name; + bool link_up; + uint32_t references; +}; + +struct interface_map { + int num; + struct interface *iface; +}; + +struct vnn_map { + uint32_t recmode; + uint32_t generation; + uint32_t size; + uint32_t *map; +}; + +struct database { + struct database *prev, *next; + const char *name; + const char *path; + struct tdb_context *tdb; + uint32_t id; + uint8_t flags; + uint64_t seq_num; +}; + +struct database_map { + struct database *db; + const char *dbdir; +}; + +struct fake_control_failure { + struct fake_control_failure *prev, *next; + enum ctdb_controls opcode; + uint32_t pnn; + const char *error; + const char *comment; +}; + +struct ctdb_client { + struct ctdb_client *prev, *next; + struct ctdbd_context *ctdb; + pid_t pid; + void *state; +}; + +struct ctdbd_context { + struct node_map *node_map; + struct interface_map *iface_map; + struct vnn_map *vnn_map; + struct database_map *db_map; + struct srvid_context *srv; + int num_clients; + struct timeval start_time; + struct timeval recovery_start_time; + struct timeval recovery_end_time; + bool takeover_disabled; + int log_level; + enum ctdb_runstate runstate; + struct ctdb_tunable_list tun_list; + char *reclock; + struct ctdb_public_ip_list *known_ips; + struct fake_control_failure *control_failures; + struct ctdb_client *client_list; +}; + +/* + * Parse routines + */ + +static struct node_map *nodemap_init(TALLOC_CTX *mem_ctx) +{ + struct node_map *node_map; + + node_map = talloc_zero(mem_ctx, struct node_map); + if (node_map == NULL) { + return NULL; + } + + node_map->pnn = CTDB_UNKNOWN_PNN; + node_map->recmaster = CTDB_UNKNOWN_PNN; + + return node_map; +} + +/* Read a nodemap from stdin. Each line looks like: + * <PNN> <FLAGS> [RECMASTER] [CURRENT] [CAPABILITIES] + * EOF or a blank line terminates input. + * + * By default, capabilities for each node are + * CTDB_CAP_RECMASTER|CTDB_CAP_LMASTER. These 2 + * capabilities can be faked off by adding, for example, + * -CTDB_CAP_RECMASTER. + */ + +static bool nodemap_parse(struct node_map *node_map) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + uint32_t pnn, flags, capabilities; + char *tok, *t; + char *ip; + ctdb_sock_addr saddr; + struct node *node; + int ret; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Get PNN */ + tok = strtok(line, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing PNN\n", line); + continue; + } + pnn = (uint32_t)strtoul(tok, NULL, 0); + + /* Get IP */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing IP\n", line); + continue; + } + ret = ctdb_sock_addr_from_string(tok, &saddr, false); + if (ret != 0) { + fprintf(stderr, "bad line (%s) - invalid IP\n", line); + continue; + } + ctdb_sock_addr_set_port(&saddr, CTDB_PORT); + ip = talloc_strdup(node_map, tok); + if (ip == NULL) { + goto fail; + } + + /* Get flags */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing flags\n", + line); + continue; + } + flags = (uint32_t)strtoul(tok, NULL, 0); + /* Handle deleted nodes */ + if (flags & NODE_FLAGS_DELETED) { + talloc_free(ip); + ip = talloc_strdup(node_map, "0.0.0.0"); + if (ip == NULL) { + goto fail; + } + } + capabilities = CTDB_CAP_RECMASTER|CTDB_CAP_LMASTER; + + tok = strtok(NULL, " \t"); + while (tok != NULL) { + if (strcmp(tok, "CURRENT") == 0) { + node_map->pnn = pnn; + } else if (strcmp(tok, "RECMASTER") == 0) { + node_map->recmaster = pnn; + } else if (strcmp(tok, "-CTDB_CAP_RECMASTER") == 0) { + capabilities &= ~CTDB_CAP_RECMASTER; + } else if (strcmp(tok, "-CTDB_CAP_LMASTER") == 0) { + capabilities &= ~CTDB_CAP_LMASTER; + } else if (strcmp(tok, "TIMEOUT") == 0) { + /* This can be done with just a flag + * value but it is probably clearer + * and less error-prone to fake this + * with an explicit token */ + flags |= NODE_FLAGS_FAKE_TIMEOUT; + } + tok = strtok(NULL, " \t"); + } + + node_map->node = talloc_realloc(node_map, node_map->node, + struct node, + node_map->num_nodes + 1); + if (node_map->node == NULL) { + goto fail; + } + node = &node_map->node[node_map->num_nodes]; + + ret = ctdb_sock_addr_from_string(ip, &node->addr, false); + if (ret != 0) { + fprintf(stderr, "bad line (%s) - invalid IP\n", line); + continue; + } + ctdb_sock_addr_set_port(&node->addr, CTDB_PORT); + node->pnn = pnn; + node->flags = flags; + node->capabilities = capabilities; + node->recovery_disabled = false; + node->recovery_substate = NULL; + + node_map->num_nodes += 1; + } + + if (node_map->num_nodes == 0) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing nodemap done\n")); + return true; + +fail: + DEBUG(DEBUG_INFO, ("Parsing nodemap failed\n")); + return false; + +} + +/* Append a node to a node map with given address and flags */ +static bool node_map_add(struct ctdb_node_map *nodemap, + const char *nstr, uint32_t flags) +{ + ctdb_sock_addr addr; + uint32_t num; + struct ctdb_node_and_flags *n; + int ret; + + ret = ctdb_sock_addr_from_string(nstr, &addr, false); + if (ret != 0) { + fprintf(stderr, "Invalid IP address %s\n", nstr); + return false; + } + ctdb_sock_addr_set_port(&addr, CTDB_PORT); + + num = nodemap->num; + nodemap->node = talloc_realloc(nodemap, nodemap->node, + struct ctdb_node_and_flags, num+1); + if (nodemap->node == NULL) { + return false; + } + + n = &nodemap->node[num]; + n->addr = addr; + n->pnn = num; + n->flags = flags; + + nodemap->num = num+1; + return true; +} + +/* Read a nodes file into a node map */ +static struct ctdb_node_map *ctdb_read_nodes_file(TALLOC_CTX *mem_ctx, + const char *nlist) +{ + char **lines; + int nlines; + int i; + struct ctdb_node_map *nodemap; + + nodemap = talloc_zero(mem_ctx, struct ctdb_node_map); + if (nodemap == NULL) { + return NULL; + } + + lines = file_lines_load(nlist, &nlines, 0, mem_ctx); + if (lines == NULL) { + return NULL; + } + + while (nlines > 0 && strcmp(lines[nlines-1], "") == 0) { + nlines--; + } + + for (i=0; i<nlines; i++) { + char *node; + uint32_t flags; + size_t len; + + node = lines[i]; + /* strip leading spaces */ + while((*node == ' ') || (*node == '\t')) { + node++; + } + + len = strlen(node); + + /* strip trailing spaces */ + while ((len > 1) && + ((node[len-1] == ' ') || (node[len-1] == '\t'))) + { + node[len-1] = '\0'; + len--; + } + + if (len == 0) { + continue; + } + if (*node == '#') { + /* A "deleted" node is a node that is + commented out in the nodes file. This is + used instead of removing a line, which + would cause subsequent nodes to change + their PNN. */ + flags = NODE_FLAGS_DELETED; + node = discard_const("0.0.0.0"); + } else { + flags = 0; + } + if (! node_map_add(nodemap, node, flags)) { + talloc_free(lines); + TALLOC_FREE(nodemap); + return NULL; + } + } + + talloc_free(lines); + return nodemap; +} + +static struct ctdb_node_map *read_nodes_file(TALLOC_CTX *mem_ctx, + uint32_t pnn) +{ + struct ctdb_node_map *nodemap; + char nodes_list[PATH_MAX]; + const char *ctdb_base; + int num; + + ctdb_base = getenv("CTDB_BASE"); + if (ctdb_base == NULL) { + D_ERR("CTDB_BASE is not set\n"); + return NULL; + } + + /* read optional node-specific nodes file */ + num = snprintf(nodes_list, sizeof(nodes_list), + "%s/nodes.%d", ctdb_base, pnn); + if (num == sizeof(nodes_list)) { + D_ERR("nodes file path too long\n"); + return NULL; + } + nodemap = ctdb_read_nodes_file(mem_ctx, nodes_list); + if (nodemap != NULL) { + /* Fake a load failure for an empty nodemap */ + if (nodemap->num == 0) { + talloc_free(nodemap); + + D_ERR("Failed to read nodes file \"%s\"\n", nodes_list); + return NULL; + } + + return nodemap; + } + + /* read normal nodes file */ + num = snprintf(nodes_list, sizeof(nodes_list), "%s/nodes", ctdb_base); + if (num == sizeof(nodes_list)) { + D_ERR("nodes file path too long\n"); + return NULL; + } + nodemap = ctdb_read_nodes_file(mem_ctx, nodes_list); + if (nodemap != NULL) { + return nodemap; + } + + DBG_ERR("Failed to read nodes file \"%s\"\n", nodes_list); + return NULL; +} + +static struct interface_map *interfaces_init(TALLOC_CTX *mem_ctx) +{ + struct interface_map *iface_map; + + iface_map = talloc_zero(mem_ctx, struct interface_map); + if (iface_map == NULL) { + return NULL; + } + + return iface_map; +} + +/* Read interfaces information. Same format as "ctdb ifaces -Y" + * output: + * :Name:LinkStatus:References: + * :eth2:1:4294967294 + * :eth1:1:4294967292 + */ + +static bool interfaces_parse(struct interface_map *iface_map) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + uint16_t link_state; + uint32_t references; + char *tok, *t, *name; + struct interface *iface; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + if (strcmp(line, ":Name:LinkStatus:References:") == 0) { + continue; + } + + /* Leading colon... */ + // tok = strtok(line, ":"); + + /* name */ + tok = strtok(line, ":"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing name\n", line); + continue; + } + name = tok; + + /* link_state */ + tok = strtok(NULL, ":"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing link state\n", + line); + continue; + } + link_state = (uint16_t)strtoul(tok, NULL, 0); + + /* references... */ + tok = strtok(NULL, ":"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing references\n", + line); + continue; + } + references = (uint32_t)strtoul(tok, NULL, 0); + + iface_map->iface = talloc_realloc(iface_map, iface_map->iface, + struct interface, + iface_map->num + 1); + if (iface_map->iface == NULL) { + goto fail; + } + + iface = &iface_map->iface[iface_map->num]; + + iface->name = talloc_strdup(iface_map, name); + if (iface->name == NULL) { + goto fail; + } + iface->link_up = link_state; + iface->references = references; + + iface_map->num += 1; + } + + if (iface_map->num == 0) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing interfaces done\n")); + return true; + +fail: + fprintf(stderr, "Parsing interfaces failed\n"); + return false; +} + +static struct vnn_map *vnnmap_init(TALLOC_CTX *mem_ctx) +{ + struct vnn_map *vnn_map; + + vnn_map = talloc_zero(mem_ctx, struct vnn_map); + if (vnn_map == NULL) { + fprintf(stderr, "Memory error\n"); + return NULL; + } + vnn_map->recmode = CTDB_RECOVERY_ACTIVE; + vnn_map->generation = INVALID_GENERATION; + + return vnn_map; +} + +/* Read vnn map. + * output: + * <GENERATION> + * <LMASTER0> + * <LMASTER1> + * ... + */ + +static bool vnnmap_parse(struct vnn_map *vnn_map) +{ + char line[1024]; + + while (fgets(line, sizeof(line), stdin) != NULL) { + uint32_t n; + char *t; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + n = (uint32_t) strtol(line, NULL, 0); + + /* generation */ + if (vnn_map->generation == INVALID_GENERATION) { + vnn_map->generation = n; + continue; + } + + vnn_map->map = talloc_realloc(vnn_map, vnn_map->map, uint32_t, + vnn_map->size + 1); + if (vnn_map->map == NULL) { + fprintf(stderr, "Memory error\n"); + goto fail; + } + + vnn_map->map[vnn_map->size] = n; + vnn_map->size += 1; + } + + if (vnn_map->size == 0) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing vnnmap done\n")); + return true; + +fail: + fprintf(stderr, "Parsing vnnmap failed\n"); + return false; +} + +static bool reclock_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + char *t; + + if (fgets(line, sizeof(line), stdin) == NULL) { + goto fail; + } + + if (line[0] == '\n') { + goto fail; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + ctdb->reclock = talloc_strdup(ctdb, line); + if (ctdb->reclock == NULL) { + goto fail; + } + + /* Swallow possible blank line following section. Picky + * compiler settings don't allow the return value to be + * ignored, so make the compiler happy. + */ + if (fgets(line, sizeof(line), stdin) == NULL) { + ; + } + DEBUG(DEBUG_INFO, ("Parsing reclock done\n")); + return true; + +fail: + fprintf(stderr, "Parsing reclock failed\n"); + return false; +} + +static struct database_map *dbmap_init(TALLOC_CTX *mem_ctx, + const char *dbdir) +{ + struct database_map *db_map; + + db_map = talloc_zero(mem_ctx, struct database_map); + if (db_map == NULL) { + return NULL; + } + + db_map->dbdir = talloc_strdup(db_map, dbdir); + if (db_map->dbdir == NULL) { + talloc_free(db_map); + return NULL; + } + + return db_map; +} + +/* Read a database map from stdin. Each line looks like: + * <ID> <NAME> [FLAGS] [SEQ_NUM] + * EOF or a blank line terminates input. + * + * By default, flags and seq_num are 0 + */ + +static bool dbmap_parse(struct database_map *db_map) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + uint32_t id; + uint8_t flags = 0; + uint32_t seq_num = 0; + char *tok, *t; + char *name; + struct database *db; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Get ID */ + tok = strtok(line, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing ID\n", line); + continue; + } + id = (uint32_t)strtoul(tok, NULL, 0); + + /* Get NAME */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing NAME\n", line); + continue; + } + name = talloc_strdup(db_map, tok); + if (name == NULL) { + goto fail; + } + + /* Get flags */ + tok = strtok(NULL, " \t"); + while (tok != NULL) { + if (strcmp(tok, "PERSISTENT") == 0) { + flags |= CTDB_DB_FLAGS_PERSISTENT; + } else if (strcmp(tok, "STICKY") == 0) { + flags |= CTDB_DB_FLAGS_STICKY; + } else if (strcmp(tok, "READONLY") == 0) { + flags |= CTDB_DB_FLAGS_READONLY; + } else if (strcmp(tok, "REPLICATED") == 0) { + flags |= CTDB_DB_FLAGS_REPLICATED; + } else if (tok[0] >= '0'&& tok[0] <= '9') { + uint8_t nv = CTDB_DB_FLAGS_PERSISTENT | + CTDB_DB_FLAGS_REPLICATED; + + if ((flags & nv) == 0) { + fprintf(stderr, + "seq_num for volatile db\n"); + goto fail; + } + seq_num = (uint64_t)strtoull(tok, NULL, 0); + } + + tok = strtok(NULL, " \t"); + } + + db = talloc_zero(db_map, struct database); + if (db == NULL) { + goto fail; + } + + db->id = id; + db->name = talloc_steal(db, name); + db->path = talloc_asprintf(db, "%s/%s", db_map->dbdir, name); + if (db->path == NULL) { + talloc_free(db); + goto fail; + } + db->flags = flags; + db->seq_num = seq_num; + + DLIST_ADD_END(db_map->db, db); + } + + if (db_map->db == NULL) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing dbmap done\n")); + return true; + +fail: + DEBUG(DEBUG_INFO, ("Parsing dbmap failed\n")); + return false; + +} + +static struct database *database_find(struct database_map *db_map, + uint32_t db_id) +{ + struct database *db; + + for (db = db_map->db; db != NULL; db = db->next) { + if (db->id == db_id) { + return db; + } + } + + return NULL; +} + +static int database_count(struct database_map *db_map) +{ + struct database *db; + int count = 0; + + for (db = db_map->db; db != NULL; db = db->next) { + count += 1; + } + + return count; +} + +static int database_flags(uint8_t db_flags) +{ + int tdb_flags = 0; + + if (db_flags & CTDB_DB_FLAGS_PERSISTENT) { + tdb_flags = TDB_DEFAULT; + } else { + /* volatile and replicated use the same flags */ + tdb_flags = TDB_NOSYNC | + TDB_CLEAR_IF_FIRST | + TDB_INCOMPATIBLE_HASH; + } + + tdb_flags |= TDB_DISALLOW_NESTING; + + return tdb_flags; +} + +static struct database *database_new(struct database_map *db_map, + const char *name, uint8_t flags) +{ + struct database *db; + TDB_DATA key; + int tdb_flags; + + db = talloc_zero(db_map, struct database); + if (db == NULL) { + return NULL; + } + + db->name = talloc_strdup(db, name); + if (db->name == NULL) { + goto fail; + } + + db->path = talloc_asprintf(db, "%s/%s", db_map->dbdir, name); + if (db->path == NULL) { + goto fail; + } + + key.dsize = strlen(db->name) + 1; + key.dptr = discard_const(db->name); + + db->id = tdb_jenkins_hash(&key); + db->flags = flags; + + tdb_flags = database_flags(flags); + + db->tdb = tdb_open(db->path, 8192, tdb_flags, O_CREAT|O_RDWR, 0644); + if (db->tdb == NULL) { + DBG_ERR("tdb_open\n"); + goto fail; + } + + DLIST_ADD_END(db_map->db, db); + return db; + +fail: + DBG_ERR("Memory error\n"); + talloc_free(db); + return NULL; + +} + +static int ltdb_store(struct database *db, TDB_DATA key, + struct ctdb_ltdb_header *header, TDB_DATA data) +{ + int ret; + bool db_volatile = true; + bool keep = false; + + if (db->tdb == NULL) { + return EINVAL; + } + + if ((db->flags & CTDB_DB_FLAGS_PERSISTENT) || + (db->flags & CTDB_DB_FLAGS_REPLICATED)) { + db_volatile = false; + } + + if (data.dsize > 0) { + keep = true; + } else { + if (db_volatile && header->rsn == 0) { + keep = true; + } + } + + if (keep) { + TDB_DATA rec[2]; + + rec[0].dsize = ctdb_ltdb_header_len(header); + rec[0].dptr = (uint8_t *)header; + + rec[1].dsize = data.dsize; + rec[1].dptr = data.dptr; + + ret = tdb_storev(db->tdb, key, rec, 2, TDB_REPLACE); + } else { + if (header->rsn > 0) { + ret = tdb_delete(db->tdb, key); + } else { + ret = 0; + } + } + + return ret; +} + +static int ltdb_fetch(struct database *db, TDB_DATA key, + struct ctdb_ltdb_header *header, + TALLOC_CTX *mem_ctx, TDB_DATA *data) +{ + TDB_DATA rec; + size_t np; + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + rec = tdb_fetch(db->tdb, key); + ret = ctdb_ltdb_header_pull(rec.dptr, rec.dsize, header, &np); + if (ret != 0) { + if (rec.dptr != NULL) { + free(rec.dptr); + } + + *header = (struct ctdb_ltdb_header) { + .rsn = 0, + .dmaster = 0, + .flags = 0, + }; + + ret = ltdb_store(db, key, header, tdb_null); + if (ret != 0) { + return ret; + } + + *data = tdb_null; + return 0; + } + + data->dsize = rec.dsize - ctdb_ltdb_header_len(header); + data->dptr = talloc_memdup(mem_ctx, + rec.dptr + ctdb_ltdb_header_len(header), + data->dsize); + + free(rec.dptr); + + if (data->dptr == NULL) { + return ENOMEM; + } + + return 0; +} + +static int database_seqnum(struct database *db, uint64_t *seqnum) +{ + const char *keyname = CTDB_DB_SEQNUM_KEY; + TDB_DATA key, data; + struct ctdb_ltdb_header header; + size_t np; + int ret; + + if (db->tdb == NULL) { + *seqnum = db->seq_num; + return 0; + } + + key.dptr = discard_const(keyname); + key.dsize = strlen(keyname) + 1; + + ret = ltdb_fetch(db, key, &header, db, &data); + if (ret != 0) { + return ret; + } + + if (data.dsize == 0) { + *seqnum = 0; + return 0; + } + + ret = ctdb_uint64_pull(data.dptr, data.dsize, seqnum, &np); + talloc_free(data.dptr); + if (ret != 0) { + *seqnum = 0; + } + + return ret; +} + +static int ltdb_transaction_update(uint32_t reqid, + struct ctdb_ltdb_header *no_header, + TDB_DATA key, TDB_DATA data, + void *private_data) +{ + struct database *db = (struct database *)private_data; + TALLOC_CTX *tmp_ctx = talloc_new(db); + struct ctdb_ltdb_header header = { 0 }, oldheader; + TDB_DATA olddata; + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + ret = ctdb_ltdb_header_extract(&data, &header); + if (ret != 0) { + return ret; + } + + ret = ltdb_fetch(db, key, &oldheader, tmp_ctx, &olddata); + if (ret != 0) { + return ret; + } + + if (olddata.dsize > 0) { + if (oldheader.rsn > header.rsn || + (oldheader.rsn == header.rsn && + olddata.dsize != data.dsize)) { + return -1; + } + } + + talloc_free(tmp_ctx); + + ret = ltdb_store(db, key, &header, data); + return ret; +} + +static int ltdb_transaction(struct database *db, + struct ctdb_rec_buffer *recbuf) +{ + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + ret = tdb_transaction_start(db->tdb); + if (ret == -1) { + return ret; + } + + ret = ctdb_rec_buffer_traverse(recbuf, ltdb_transaction_update, db); + if (ret != 0) { + tdb_transaction_cancel(db->tdb); + } + + ret = tdb_transaction_commit(db->tdb); + return ret; +} + +static bool public_ips_parse(struct ctdbd_context *ctdb, + uint32_t numnodes) +{ + bool status; + + if (numnodes == 0) { + D_ERR("Must initialise nodemap before public IPs\n"); + return false; + } + + ctdb->known_ips = ipalloc_read_known_ips(ctdb, numnodes, false); + + status = (ctdb->known_ips != NULL && ctdb->known_ips->num != 0); + + if (status) { + D_INFO("Parsing public IPs done\n"); + } else { + D_INFO("Parsing public IPs failed\n"); + } + + return status; +} + +/* Read information about controls to fail. Format is: + * <opcode> <pnn> {ERROR|TIMEOUT} <comment> + */ +static bool control_failures_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + char *tok, *t; + enum ctdb_controls opcode; + uint32_t pnn; + const char *error; + const char *comment; + struct fake_control_failure *failure = NULL; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Get opcode */ + tok = strtok(line, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing opcode\n", line); + continue; + } + opcode = (enum ctdb_controls)strtoul(tok, NULL, 0); + + /* Get PNN */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing PNN\n", line); + continue; + } + pnn = (uint32_t)strtoul(tok, NULL, 0); + + /* Get error */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing errno\n", line); + continue; + } + error = talloc_strdup(ctdb, tok); + if (error == NULL) { + goto fail; + } + if (strcmp(error, "ERROR") != 0 && + strcmp(error, "TIMEOUT") != 0) { + D_ERR("bad line (%s) " + "- error must be \"ERROR\" or \"TIMEOUT\"\n", + line); + goto fail; + } + + /* Get comment */ + tok = strtok(NULL, "\n"); /* rest of line */ + if (tok == NULL) { + D_ERR("bad line (%s) - missing comment\n", line); + continue; + } + comment = talloc_strdup(ctdb, tok); + if (comment == NULL) { + goto fail; + } + + failure = talloc_zero(ctdb, struct fake_control_failure); + if (failure == NULL) { + goto fail; + } + + failure->opcode = opcode; + failure->pnn = pnn; + failure->error = error; + failure->comment = comment; + + DLIST_ADD(ctdb->control_failures, failure); + } + + if (ctdb->control_failures == NULL) { + goto fail; + } + + D_INFO("Parsing fake control failures done\n"); + return true; + +fail: + D_INFO("Parsing fake control failures failed\n"); + return false; +} + +static bool runstate_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + char *t; + + if (fgets(line, sizeof(line), stdin) == NULL) { + goto fail; + } + + if (line[0] == '\n') { + goto fail; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + ctdb->runstate = ctdb_runstate_from_string(line); + if (ctdb->runstate == CTDB_RUNSTATE_UNKNOWN) { + goto fail; + } + + /* Swallow possible blank line following section. Picky + * compiler settings don't allow the return value to be + * ignored, so make the compiler happy. + */ + if (fgets(line, sizeof(line), stdin) == NULL) { + ; + } + D_INFO("Parsing runstate done\n"); + return true; + +fail: + D_ERR("Parsing runstate failed\n"); + return false; +} + +/* + * Manage clients + */ + +static int ctdb_client_destructor(struct ctdb_client *client) +{ + DLIST_REMOVE(client->ctdb->client_list, client); + return 0; +} + +static int client_add(struct ctdbd_context *ctdb, pid_t client_pid, + void *client_state) +{ + struct ctdb_client *client; + + client = talloc_zero(client_state, struct ctdb_client); + if (client == NULL) { + return ENOMEM; + } + + client->ctdb = ctdb; + client->pid = client_pid; + client->state = client_state; + + DLIST_ADD(ctdb->client_list, client); + talloc_set_destructor(client, ctdb_client_destructor); + return 0; +} + +static void *client_find(struct ctdbd_context *ctdb, pid_t client_pid) +{ + struct ctdb_client *client; + + for (client=ctdb->client_list; client != NULL; client=client->next) { + if (client->pid == client_pid) { + return client->state; + } + } + + return NULL; +} + +/* + * CTDB context setup + */ + +static uint32_t new_generation(uint32_t old_generation) +{ + uint32_t generation; + + while (1) { + generation = random(); + if (generation != INVALID_GENERATION && + generation != old_generation) { + break; + } + } + + return generation; +} + +static struct ctdbd_context *ctdbd_setup(TALLOC_CTX *mem_ctx, + const char *dbdir) +{ + struct ctdbd_context *ctdb; + char line[1024]; + bool status; + int ret; + + ctdb = talloc_zero(mem_ctx, struct ctdbd_context); + if (ctdb == NULL) { + return NULL; + } + + ctdb->node_map = nodemap_init(ctdb); + if (ctdb->node_map == NULL) { + goto fail; + } + + ctdb->iface_map = interfaces_init(ctdb); + if (ctdb->iface_map == NULL) { + goto fail; + } + + ctdb->vnn_map = vnnmap_init(ctdb); + if (ctdb->vnn_map == NULL) { + goto fail; + } + + ctdb->db_map = dbmap_init(ctdb, dbdir); + if (ctdb->db_map == NULL) { + goto fail; + } + + ret = srvid_init(ctdb, &ctdb->srv); + if (ret != 0) { + goto fail; + } + + ctdb->runstate = CTDB_RUNSTATE_RUNNING; + + while (fgets(line, sizeof(line), stdin) != NULL) { + char *t; + + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + if (strcmp(line, "NODEMAP") == 0) { + status = nodemap_parse(ctdb->node_map); + } else if (strcmp(line, "IFACES") == 0) { + status = interfaces_parse(ctdb->iface_map); + } else if (strcmp(line, "VNNMAP") == 0) { + status = vnnmap_parse(ctdb->vnn_map); + } else if (strcmp(line, "DBMAP") == 0) { + status = dbmap_parse(ctdb->db_map); + } else if (strcmp(line, "PUBLICIPS") == 0) { + status = public_ips_parse(ctdb, + ctdb->node_map->num_nodes); + } else if (strcmp(line, "RECLOCK") == 0) { + status = reclock_parse(ctdb); + } else if (strcmp(line, "CONTROLFAILS") == 0) { + status = control_failures_parse(ctdb); + } else if (strcmp(line, "RUNSTATE") == 0) { + status = runstate_parse(ctdb); + } else { + fprintf(stderr, "Unknown line %s\n", line); + status = false; + } + + if (! status) { + goto fail; + } + } + + ctdb->start_time = tevent_timeval_current(); + ctdb->recovery_start_time = tevent_timeval_current(); + ctdb->vnn_map->recmode = CTDB_RECOVERY_NORMAL; + if (ctdb->vnn_map->generation == INVALID_GENERATION) { + ctdb->vnn_map->generation = + new_generation(ctdb->vnn_map->generation); + } + ctdb->recovery_end_time = tevent_timeval_current(); + + ctdb->log_level = DEBUG_ERR; + + ctdb_tunable_set_defaults(&ctdb->tun_list); + + return ctdb; + +fail: + TALLOC_FREE(ctdb); + return NULL; +} + +static bool ctdbd_verify(struct ctdbd_context *ctdb) +{ + struct node *node; + unsigned int i; + + if (ctdb->node_map->num_nodes == 0) { + return true; + } + + /* Make sure all the nodes are in order */ + for (i=0; i<ctdb->node_map->num_nodes; i++) { + node = &ctdb->node_map->node[i]; + if (node->pnn != i) { + fprintf(stderr, "Expected node %u, found %u\n", + i, node->pnn); + return false; + } + } + + node = &ctdb->node_map->node[ctdb->node_map->pnn]; + if (node->flags & NODE_FLAGS_DISCONNECTED) { + DEBUG(DEBUG_INFO, ("Node disconnected, exiting\n")); + exit(0); + } + + return true; +} + +/* + * Doing a recovery + */ + +struct recover_state { + struct tevent_context *ev; + struct ctdbd_context *ctdb; +}; + +static int recover_check(struct tevent_req *req); +static void recover_wait_done(struct tevent_req *subreq); +static void recover_done(struct tevent_req *subreq); + +static struct tevent_req *recover_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdbd_context *ctdb) +{ + struct tevent_req *req; + struct recover_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct recover_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ctdb = ctdb; + + ret = recover_check(req); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + return req; +} + +static int recover_check(struct tevent_req *req) +{ + struct recover_state *state = tevent_req_data( + req, struct recover_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + bool recovery_disabled; + unsigned int i; + + recovery_disabled = false; + for (i=0; i<ctdb->node_map->num_nodes; i++) { + if (ctdb->node_map->node[i].recovery_disabled) { + recovery_disabled = true; + break; + } + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (subreq == NULL) { + return ENOMEM; + } + + if (recovery_disabled) { + tevent_req_set_callback(subreq, recover_wait_done, req); + } else { + ctdb->recovery_start_time = tevent_timeval_current(); + tevent_req_set_callback(subreq, recover_done, req); + } + + return 0; +} + +static void recover_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + ret = recover_check(req); + if (ret != 0) { + tevent_req_error(req, ret); + } +} + +static void recover_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct recover_state *state = tevent_req_data( + req, struct recover_state); + struct ctdbd_context *ctdb = state->ctdb; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + ctdb->vnn_map->recmode = CTDB_RECOVERY_NORMAL; + ctdb->recovery_end_time = tevent_timeval_current(); + ctdb->vnn_map->generation = new_generation(ctdb->vnn_map->generation); + + tevent_req_done(req); +} + +static bool recover_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + + return true; +} + +/* + * Routines for ctdb_req_header + */ + +static void header_fix_pnn(struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + if (header->srcnode == CTDB_CURRENT_NODE) { + header->srcnode = ctdb->node_map->pnn; + } + + if (header->destnode == CTDB_CURRENT_NODE) { + header->destnode = ctdb->node_map->pnn; + } +} + +static struct ctdb_req_header header_reply_call( + struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + struct ctdb_req_header reply_header; + + reply_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REPLY_CALL, + .destnode = header->srcnode, + .srcnode = header->destnode, + .reqid = header->reqid, + }; + + return reply_header; +} + +static struct ctdb_req_header header_reply_control( + struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + struct ctdb_req_header reply_header; + + reply_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REPLY_CONTROL, + .destnode = header->srcnode, + .srcnode = header->destnode, + .reqid = header->reqid, + }; + + return reply_header; +} + +static struct ctdb_req_header header_reply_message( + struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + struct ctdb_req_header reply_header; + + reply_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REQ_MESSAGE, + .destnode = header->srcnode, + .srcnode = header->destnode, + .reqid = 0, + }; + + return reply_header; +} + +/* + * Client state + */ + +struct client_state { + struct tevent_context *ev; + int fd; + struct ctdbd_context *ctdb; + int pnn; + pid_t pid; + struct comm_context *comm; + struct srvid_register_state *rstate; + int status; +}; + +/* + * Send replies to call, controls and messages + */ + +static void client_reply_done(struct tevent_req *subreq); + +static void client_send_call(struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_reply_call *reply) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header reply_header; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + reply_header = header_reply_call(header, ctdb); + + datalen = ctdb_reply_call_len(&reply_header, reply); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_call_push(&reply_header, reply, buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, client_reply_done, req); + + talloc_steal(subreq, buf); +} + +static void client_send_message(struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_message_data *message) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header reply_header; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + reply_header = header_reply_message(header, ctdb); + + datalen = ctdb_req_message_data_len(&reply_header, message); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_req_message_data_push(&reply_header, message, + buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + DEBUG(DEBUG_INFO, ("message srvid = 0x%"PRIx64"\n", message->srvid)); + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, client_reply_done, req); + + talloc_steal(subreq, buf); +} + +static void client_send_control(struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_reply_control *reply) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header reply_header; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + reply_header = header_reply_control(header, ctdb); + + datalen = ctdb_reply_control_len(&reply_header, reply); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_control_push(&reply_header, reply, buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + DEBUG(DEBUG_INFO, ("reply opcode = %u\n", reply->rdata.opcode)); + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, client_reply_done, req); + + talloc_steal(subreq, buf); +} + +static void client_reply_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool status; + + status = comm_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + } +} + +/* + * Handling protocol - controls + */ + +static void control_process_exists(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct client_state *cstate; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + cstate = client_find(ctdb, request->rdata.data.pid); + if (cstate == NULL) { + reply.status = -1; + reply.errmsg = "No client for PID"; + } else { + reply.status = kill(request->rdata.data.pid, 0); + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static void control_ping(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = ctdb->num_clients; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_getdbpath(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.db_path = + talloc_strdup(mem_ctx, db->path); + if (reply.rdata.data.db_path == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + } else { + reply.status = 0; + reply.errmsg = NULL; + } + } + + client_send_control(req, header, &reply); +} + +static void control_getvnnmap(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_vnn_map *vnnmap; + + reply.rdata.opcode = request->opcode; + + vnnmap = talloc_zero(mem_ctx, struct ctdb_vnn_map); + if (vnnmap == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + } else { + vnnmap->generation = ctdb->vnn_map->generation; + vnnmap->size = ctdb->vnn_map->size; + vnnmap->map = ctdb->vnn_map->map; + + reply.rdata.data.vnnmap = vnnmap; + reply.status = 0; + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static void control_get_debug(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.rdata.data.loglevel = (uint32_t)ctdb->log_level; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_set_debug(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + ctdb->log_level = (int)request->rdata.data.loglevel; + + reply.rdata.opcode = request->opcode; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_dbmap(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_dbid_map *dbmap; + struct database *db; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + dbmap = talloc_zero(mem_ctx, struct ctdb_dbid_map); + if (dbmap == NULL) { + goto fail; + } + + dbmap->num = database_count(ctdb->db_map); + dbmap->dbs = talloc_zero_array(dbmap, struct ctdb_dbid, dbmap->num); + if (dbmap->dbs == NULL) { + goto fail; + } + + db = ctdb->db_map->db; + for (i = 0; i < dbmap->num; i++) { + dbmap->dbs[i] = (struct ctdb_dbid) { + .db_id = db->id, + .flags = db->flags, + }; + + db = db->next; + } + + reply.rdata.data.dbmap = dbmap; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_get_recmode(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = ctdb->vnn_map->recmode; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +struct set_recmode_state { + struct tevent_req *req; + struct ctdbd_context *ctdb; + struct ctdb_req_header header; + struct ctdb_reply_control reply; +}; + +static void set_recmode_callback(struct tevent_req *subreq) +{ + struct set_recmode_state *substate = tevent_req_callback_data( + subreq, struct set_recmode_state); + bool status; + int ret; + + status = recover_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + substate->reply.status = ret; + substate->reply.errmsg = "recovery failed"; + } else { + substate->reply.status = 0; + substate->reply.errmsg = NULL; + } + + client_send_control(substate->req, &substate->header, &substate->reply); + talloc_free(substate); +} + +static void control_set_recmode(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct tevent_req *subreq; + struct ctdbd_context *ctdb = state->ctdb; + struct set_recmode_state *substate; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + if (request->rdata.data.recmode == CTDB_RECOVERY_NORMAL) { + reply.status = -1; + reply.errmsg = "Client cannot set recmode to NORMAL"; + goto fail; + } + + substate = talloc_zero(ctdb, struct set_recmode_state); + if (substate == NULL) { + reply.status = -1; + reply.errmsg = "Memory error"; + goto fail; + } + + substate->req = req; + substate->ctdb = ctdb; + substate->header = *header; + substate->reply.rdata.opcode = request->opcode; + + subreq = recover_send(substate, state->ev, state->ctdb); + if (subreq == NULL) { + talloc_free(substate); + goto fail; + } + tevent_req_set_callback(subreq, set_recmode_callback, substate); + + ctdb->vnn_map->recmode = CTDB_RECOVERY_ACTIVE; + return; + +fail: + client_send_control(req, header, &reply); + +} + +static void control_db_attach(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, 0); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void srvid_handler_done(struct tevent_req *subreq); + +static void srvid_handler(uint64_t srvid, TDB_DATA data, void *private_data) +{ + struct client_state *state = talloc_get_type_abort( + private_data, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header request_header; + struct ctdb_req_message_data message; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + request_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REQ_MESSAGE, + .destnode = state->pnn, + .srcnode = ctdb->node_map->recmaster, + .reqid = 0, + }; + + message = (struct ctdb_req_message_data) { + .srvid = srvid, + .data = data, + }; + + datalen = ctdb_req_message_data_len(&request_header, &message); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + return; + } + + ret = ctdb_req_message_data_push(&request_header, + &message, + buf, + &buflen); + if (ret != 0) { + talloc_free(buf); + return; + } + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (subreq == NULL) { + talloc_free(buf); + return; + } + tevent_req_set_callback(subreq, srvid_handler_done, state); + + talloc_steal(subreq, buf); +} + +static void srvid_handler_done(struct tevent_req *subreq) +{ + struct client_state *state = tevent_req_callback_data( + subreq, struct client_state); + int ret; + bool ok; + + ok = comm_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (!ok) { + DEBUG(DEBUG_ERR, + ("Failed to dispatch message to client pid=%u, ret=%d\n", + state->pid, + ret)); + } +} + +static void control_register_srvid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + int ret; + + reply.rdata.opcode = request->opcode; + + ret = srvid_register(ctdb->srv, state, request->srvid, + srvid_handler, state); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "Memory error"; + goto fail; + } + + DEBUG(DEBUG_INFO, ("Register srvid 0x%"PRIx64"\n", request->srvid)); + + reply.status = 0; + reply.errmsg = NULL; + +fail: + client_send_control(req, header, &reply); +} + +static void control_deregister_srvid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + int ret; + + reply.rdata.opcode = request->opcode; + + ret = srvid_deregister(ctdb->srv, request->srvid, state); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "srvid not registered"; + goto fail; + } + + DEBUG(DEBUG_INFO, ("Deregister srvid 0x%"PRIx64"\n", request->srvid)); + + reply.status = 0; + reply.errmsg = NULL; + +fail: + client_send_control(req, header, &reply); +} + +static void control_get_dbname(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.db_name = talloc_strdup(mem_ctx, db->name); + if (reply.rdata.data.db_name == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + } else { + reply.status = 0; + reply.errmsg = NULL; + } + } + + client_send_control(req, header, &reply); +} + +static void control_get_pid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = getpid(); + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_pnn(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = header->destnode; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_shutdown(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *hdr, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + + state->status = 99; +} + +static void control_set_tunable(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + bool ret, obsolete; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + ret = ctdb_tunable_set_value(&ctdb->tun_list, + request->rdata.data.tunable->name, + request->rdata.data.tunable->value, + &obsolete); + if (! ret) { + reply.status = -1; + } else if (obsolete) { + reply.status = 1; + } else { + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_get_tunable(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + uint32_t value; + bool ret; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + ret = ctdb_tunable_get_value(&ctdb->tun_list, + request->rdata.data.tun_var, &value); + if (! ret) { + reply.status = -1; + } else { + reply.rdata.data.tun_value = value; + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_list_tunables(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + struct ctdb_var_list *var_list; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + var_list = ctdb_tunable_names(mem_ctx); + if (var_list == NULL) { + reply.status = -1; + } else { + reply.rdata.data.tun_var_list = var_list; + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_modify_flags(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_node_flag_change *change = request->rdata.data.flag_change; + struct ctdb_reply_control reply; + struct node *node; + + reply.rdata.opcode = request->opcode; + + if ((change->old_flags & ~NODE_FLAGS_PERMANENTLY_DISABLED) || + (change->new_flags & ~NODE_FLAGS_PERMANENTLY_DISABLED) != 0) { + DEBUG(DEBUG_INFO, + ("MODIFY_FLAGS control not for PERMANENTLY_DISABLED\n")); + reply.status = EINVAL; + reply.errmsg = "Failed to MODIFY_FLAGS"; + client_send_control(req, header, &reply); + return; + } + + /* There's all sorts of broadcast weirdness here. Only change + * the specified node, not the destination node of the + * control. */ + node = &ctdb->node_map->node[change->pnn]; + + if ((node->flags & + change->old_flags & NODE_FLAGS_PERMANENTLY_DISABLED) == 0 && + (change->new_flags & NODE_FLAGS_PERMANENTLY_DISABLED) != 0) { + DEBUG(DEBUG_INFO,("Disabling node %d\n", header->destnode)); + node->flags |= NODE_FLAGS_PERMANENTLY_DISABLED; + goto done; + } + + if ((node->flags & + change->old_flags & NODE_FLAGS_PERMANENTLY_DISABLED) != 0 && + (change->new_flags & NODE_FLAGS_PERMANENTLY_DISABLED) == 0) { + DEBUG(DEBUG_INFO,("Enabling node %d\n", header->destnode)); + node->flags &= ~NODE_FLAGS_PERMANENTLY_DISABLED; + goto done; + } + + DEBUG(DEBUG_INFO, ("Flags unchanged for node %d\n", header->destnode)); + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_all_tunables(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.rdata.data.tun_list = &ctdb->tun_list; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_db_attach_persistent(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, + CTDB_DB_FLAGS_PERSISTENT); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_uptime(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_uptime *uptime;; + + reply.rdata.opcode = request->opcode; + + uptime = talloc_zero(mem_ctx, struct ctdb_uptime); + if (uptime == NULL) { + goto fail; + } + + uptime->current_time = tevent_timeval_current(); + uptime->ctdbd_start_time = ctdb->start_time; + uptime->last_recovery_started = ctdb->recovery_start_time; + uptime->last_recovery_finished = ctdb->recovery_end_time; + + reply.rdata.data.uptime = uptime; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_reload_nodes_file(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_node_map *nodemap; + struct node_map *node_map = ctdb->node_map; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + nodemap = read_nodes_file(mem_ctx, header->destnode); + if (nodemap == NULL) { + goto fail; + } + + for (i=0; i<nodemap->num; i++) { + struct node *node; + + if (i < node_map->num_nodes && + ctdb_sock_addr_same(&nodemap->node[i].addr, + &node_map->node[i].addr)) { + continue; + } + + if (nodemap->node[i].flags & NODE_FLAGS_DELETED) { + int ret; + + node = &node_map->node[i]; + + node->flags |= NODE_FLAGS_DELETED; + ret = ctdb_sock_addr_from_string("0.0.0.0", &node->addr, + false); + if (ret != 0) { + /* Can't happen, but Coverity... */ + goto fail; + } + + continue; + } + + if (i < node_map->num_nodes && + node_map->node[i].flags & NODE_FLAGS_DELETED) { + node = &node_map->node[i]; + + node->flags &= ~NODE_FLAGS_DELETED; + node->addr = nodemap->node[i].addr; + + continue; + } + + node_map->node = talloc_realloc(node_map, node_map->node, + struct node, + node_map->num_nodes+1); + if (node_map->node == NULL) { + goto fail; + } + node = &node_map->node[node_map->num_nodes]; + + node->addr = nodemap->node[i].addr; + node->pnn = nodemap->node[i].pnn; + node->flags = 0; + node->capabilities = CTDB_CAP_DEFAULT; + node->recovery_disabled = false; + node->recovery_substate = NULL; + + node_map->num_nodes += 1; + } + + talloc_free(nodemap); + + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_get_capabilities(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct node *node; + uint32_t caps = 0; + + reply.rdata.opcode = request->opcode; + + node = &ctdb->node_map->node[header->destnode]; + caps = node->capabilities; + + if (node->flags & NODE_FLAGS_FAKE_TIMEOUT) { + /* Don't send reply */ + return; + } + + reply.rdata.data.caps = caps; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_release_ip(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_public_ip *ip = request->rdata.data.pubip; + struct ctdb_reply_control reply; + struct ctdb_public_ip_list *ips = NULL; + struct ctdb_public_ip *t = NULL; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + D_INFO("RELEASE_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + ips = &ctdb->known_ips[header->destnode]; + + t = NULL; + for (i = 0; i < ips->num; i++) { + if (ctdb_sock_addr_same_ip(&ips->ip[i].addr, &ip->addr)) { + t = &ips->ip[i]; + break; + } + } + if (t == NULL) { + D_INFO("RELEASE_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + if (t->pnn != header->destnode) { + if (header->destnode == ip->pnn) { + D_ERR("error: RELEASE_IP %s - to TAKE_IP node %d\n", + ctdb_sock_addr_to_string(mem_ctx, + &ip->addr, false), + ip->pnn); + reply.status = -1; + reply.errmsg = "RELEASE_IP to TAKE_IP node"; + client_send_control(req, header, &reply); + return; + } + + D_INFO("RELEASE_IP %s - to node %d - redundant\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false), + ip->pnn); + t->pnn = ip->pnn; + } else { + D_NOTICE("RELEASE_IP %s - to node %d\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false), + ip->pnn); + t->pnn = ip->pnn; + } + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_takeover_ip(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_public_ip *ip = request->rdata.data.pubip; + struct ctdb_reply_control reply; + struct ctdb_public_ip_list *ips = NULL; + struct ctdb_public_ip *t = NULL; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + D_INFO("TAKEOVER_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + ips = &ctdb->known_ips[header->destnode]; + + t = NULL; + for (i = 0; i < ips->num; i++) { + if (ctdb_sock_addr_same_ip(&ips->ip[i].addr, &ip->addr)) { + t = &ips->ip[i]; + break; + } + } + if (t == NULL) { + D_INFO("TAKEOVER_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + if (t->pnn == header->destnode) { + D_INFO("TAKEOVER_IP %s - redundant\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + } else { + D_NOTICE("TAKEOVER_IP %s\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + t->pnn = ip->pnn; + } + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_public_ips(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_public_ip_list *ips = NULL; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + /* No IPs defined so create a dummy empty struct and ship it */ + ips = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (ips == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + goto ok; + } + + ips = &ctdb->known_ips[header->destnode]; + + if (request->flags & CTDB_PUBLIC_IP_FLAGS_ONLY_AVAILABLE) { + /* If runstate is not RUNNING or a node is then return + * no available IPs. Don't worry about interface + * states here - we're not faking down to that level. + */ + uint32_t flags = ctdb->node_map->node[header->destnode].flags; + if (ctdb->runstate != CTDB_RUNSTATE_RUNNING || + ((flags & (NODE_FLAGS_INACTIVE|NODE_FLAGS_DISABLED)) != 0)) { + /* No available IPs: return dummy empty struct */ + ips = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (ips == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } + } + +ok: + reply.rdata.data.pubip_list = ips; + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_get_nodemap(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_node_map *nodemap; + struct node *node; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + nodemap = talloc_zero(mem_ctx, struct ctdb_node_map); + if (nodemap == NULL) { + goto fail; + } + + nodemap->num = ctdb->node_map->num_nodes; + nodemap->node = talloc_array(nodemap, struct ctdb_node_and_flags, + nodemap->num); + if (nodemap->node == NULL) { + goto fail; + } + + for (i=0; i<nodemap->num; i++) { + node = &ctdb->node_map->node[i]; + nodemap->node[i] = (struct ctdb_node_and_flags) { + .pnn = node->pnn, + .flags = node->flags, + .addr = node->addr, + }; + } + + reply.rdata.data.nodemap = nodemap; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_get_reclock_file(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + if (ctdb->reclock != NULL) { + reply.rdata.data.reclock_file = + talloc_strdup(mem_ctx, ctdb->reclock); + if (reply.rdata.data.reclock_file == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } else { + reply.rdata.data.reclock_file = NULL; + } + + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_stop_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Stopping node\n")); + ctdb->node_map->node[header->destnode].flags |= NODE_FLAGS_STOPPED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void control_continue_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Continue node\n")); + ctdb->node_map->node[header->destnode].flags &= ~NODE_FLAGS_STOPPED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void set_ban_state_callback(struct tevent_req *subreq) +{ + struct node *node = tevent_req_callback_data( + subreq, struct node); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + DEBUG(DEBUG_INFO, ("tevent_wakeup_recv failed\n")); + } + + node->flags &= ~NODE_FLAGS_BANNED; +} + +static void control_set_ban_state(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct tevent_req *subreq; + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_ban_state *ban = request->rdata.data.ban_state; + struct ctdb_reply_control reply; + struct node *node; + + reply.rdata.opcode = request->opcode; + + if (ban->pnn != header->destnode) { + DEBUG(DEBUG_INFO, + ("SET_BAN_STATE control for PNN %d rejected\n", + ban->pnn)); + reply.status = EINVAL; + goto fail; + } + + node = &ctdb->node_map->node[header->destnode]; + + if (ban->time == 0) { + DEBUG(DEBUG_INFO,("Unbanning this node\n")); + node->flags &= ~NODE_FLAGS_BANNED; + goto done; + } + + subreq = tevent_wakeup_send(ctdb->node_map, state->ev, + tevent_timeval_current_ofs( + ban->time, 0)); + if (subreq == NULL) { + reply.status = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, set_ban_state_callback, node); + + DEBUG(DEBUG_INFO, ("Banning this node for %d seconds\n", ban->time)); + node->flags |= NODE_FLAGS_BANNED; + ctdb->vnn_map->generation = INVALID_GENERATION; + +done: + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; + +fail: + reply.errmsg = "Failed to ban node"; +} + +static void control_trans3_commit(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + int ret; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.recbuf->db_id); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Unknown database"; + client_send_control(req, header, &reply); + return; + } + + if (! (db->flags & + (CTDB_DB_FLAGS_PERSISTENT|CTDB_DB_FLAGS_REPLICATED))) { + reply.status = -1; + reply.errmsg = "Transactions on volatile database"; + client_send_control(req, header, &reply); + return; + } + + ret = ltdb_transaction(db, request->rdata.data.recbuf); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "Transaction failed"; + client_send_control(req, header, &reply); + return; + } + + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_db_seqnum(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + int ret; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + uint64_t seqnum; + + ret = database_seqnum(db, &seqnum); + if (ret == 0) { + reply.rdata.data.seqnum = seqnum; + reply.status = 0; + reply.errmsg = NULL; + } else { + reply.status = ret; + reply.errmsg = "Failed to get seqnum"; + } + } + + client_send_control(req, header, &reply); +} + +static void control_db_get_health(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.reason = NULL; + reply.status = 0; + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static struct ctdb_iface_list *get_ctdb_iface_list(TALLOC_CTX *mem_ctx, + struct ctdbd_context *ctdb) +{ + struct ctdb_iface_list *iface_list; + struct interface *iface; + unsigned int i; + + iface_list = talloc_zero(mem_ctx, struct ctdb_iface_list); + if (iface_list == NULL) { + goto done; + } + + iface_list->num = ctdb->iface_map->num; + iface_list->iface = talloc_array(iface_list, struct ctdb_iface, + iface_list->num); + if (iface_list->iface == NULL) { + TALLOC_FREE(iface_list); + goto done; + } + + for (i=0; i<iface_list->num; i++) { + iface = &ctdb->iface_map->iface[i]; + iface_list->iface[i] = (struct ctdb_iface) { + .link_state = iface->link_up, + .references = iface->references, + }; + strlcpy(iface_list->iface[i].name, iface->name, + sizeof(iface_list->iface[i].name)); + } + +done: + return iface_list; +} + +static void control_get_public_ip_info(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + ctdb_sock_addr *addr = request->rdata.data.addr; + struct ctdb_public_ip_list *known = NULL; + struct ctdb_public_ip_info *info = NULL; + unsigned i; + + reply.rdata.opcode = request->opcode; + + info = talloc_zero(mem_ctx, struct ctdb_public_ip_info); + if (info == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + + reply.rdata.data.ipinfo = info; + + if (ctdb->known_ips != NULL) { + known = &ctdb->known_ips[header->destnode]; + } else { + /* No IPs defined so create a dummy empty struct and + * fall through. The given IP won't be matched + * below... + */ + known = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (known == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } + + for (i = 0; i < known->num; i++) { + if (ctdb_sock_addr_same_ip(&known->ip[i].addr, + addr)) { + break; + } + } + + if (i == known->num) { + D_ERR("GET_PUBLIC_IP_INFO: not known public IP %s\n", + ctdb_sock_addr_to_string(mem_ctx, addr, false)); + reply.status = -1; + reply.errmsg = "Unknown address"; + goto done; + } + + info->ip = known->ip[i]; + + /* The fake PUBLICIPS stanza and resulting known_ips data + * don't know anything about interfaces, so completely fake + * this. + */ + info->active_idx = 0; + + info->ifaces = get_ctdb_iface_list(mem_ctx, ctdb); + if (info->ifaces == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_get_ifaces(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_iface_list *iface_list; + + reply.rdata.opcode = request->opcode; + + iface_list = get_ctdb_iface_list(mem_ctx, ctdb); + if (iface_list == NULL) { + goto fail; + } + + reply.rdata.data.iface_list = iface_list; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_set_iface_link_state(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_iface *in_iface; + struct interface *iface = NULL; + bool link_up = false; + int i; + + reply.rdata.opcode = request->opcode; + + in_iface = request->rdata.data.iface; + + if (in_iface->name[CTDB_IFACE_SIZE] != '\0') { + reply.errmsg = "interface name not terminated"; + goto fail; + } + + switch (in_iface->link_state) { + case 0: + link_up = false; + break; + + case 1: + link_up = true; + break; + + default: + reply.errmsg = "invalid link state"; + goto fail; + } + + if (in_iface->references != 0) { + reply.errmsg = "references should be 0"; + goto fail; + } + + for (i=0; i<ctdb->iface_map->num; i++) { + if (strcmp(ctdb->iface_map->iface[i].name, + in_iface->name) == 0) { + iface = &ctdb->iface_map->iface[i]; + break; + } + } + + if (iface == NULL) { + reply.errmsg = "interface not found"; + goto fail; + } + + iface->link_up = link_up; + + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + client_send_control(req, header, &reply); +} + +static void control_set_db_readonly(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + goto done; + } + + if (db->flags & CTDB_DB_FLAGS_PERSISTENT) { + reply.status = EINVAL; + reply.errmsg = "Can not set READONLY on persistent db"; + goto done; + } + + db->flags |= CTDB_DB_FLAGS_READONLY; + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +struct traverse_start_ext_state { + struct tevent_req *req; + struct ctdb_req_header *header; + uint32_t reqid; + uint64_t srvid; + bool withemptyrecords; + int status; +}; + +static int traverse_start_ext_handler(struct tdb_context *tdb, + TDB_DATA key, TDB_DATA data, + void *private_data) +{ + struct traverse_start_ext_state *state = + (struct traverse_start_ext_state *)private_data; + struct ctdb_rec_data rec; + struct ctdb_req_message_data message; + size_t np; + + if (data.dsize < sizeof(struct ctdb_ltdb_header)) { + return 0; + } + + if ((data.dsize == sizeof(struct ctdb_ltdb_header)) && + (!state->withemptyrecords)) { + return 0; + } + + rec = (struct ctdb_rec_data) { + .reqid = state->reqid, + .header = NULL, + .key = key, + .data = data, + }; + + message.srvid = state->srvid; + message.data.dsize = ctdb_rec_data_len(&rec); + message.data.dptr = talloc_size(state->req, message.data.dsize); + if (message.data.dptr == NULL) { + state->status = ENOMEM; + return 1; + } + + ctdb_rec_data_push(&rec, message.data.dptr, &np); + client_send_message(state->req, state->header, &message); + + talloc_free(message.data.dptr); + + return 0; +} + +static void control_traverse_start_ext(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + struct ctdb_traverse_start_ext *ext; + struct traverse_start_ext_state t_state; + struct ctdb_rec_data rec; + struct ctdb_req_message_data message; + uint8_t buffer[32]; + size_t np; + int ret; + + reply.rdata.opcode = request->opcode; + + ext = request->rdata.data.traverse_start_ext; + + db = database_find(ctdb->db_map, ext->db_id); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Unknown database"; + client_send_control(req, header, &reply); + return; + } + + t_state = (struct traverse_start_ext_state) { + .req = req, + .header = header, + .reqid = ext->reqid, + .srvid = ext->srvid, + .withemptyrecords = ext->withemptyrecords, + }; + + ret = tdb_traverse_read(db->tdb, traverse_start_ext_handler, &t_state); + DEBUG(DEBUG_INFO, ("traversed %d records\n", ret)); + if (t_state.status != 0) { + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); + } + + reply.status = 0; + client_send_control(req, header, &reply); + + rec = (struct ctdb_rec_data) { + .reqid = ext->reqid, + .header = NULL, + .key = tdb_null, + .data = tdb_null, + }; + + message.srvid = ext->srvid; + message.data.dsize = ctdb_rec_data_len(&rec); + ctdb_rec_data_push(&rec, buffer, &np); + message.data.dptr = buffer; + client_send_message(req, header, &message); +} + +static void control_set_db_sticky(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + goto done; + } + + if (db->flags & CTDB_DB_FLAGS_PERSISTENT) { + reply.status = EINVAL; + reply.errmsg = "Can not set STICKY on persistent db"; + goto done; + } + + db->flags |= CTDB_DB_FLAGS_STICKY; + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_ipreallocated(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + /* Always succeed */ + reply.rdata.opcode = request->opcode; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_runstate(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.rdata.data.runstate = ctdb->runstate; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_nodes_file(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + struct ctdb_node_map *nodemap; + + reply.rdata.opcode = request->opcode; + + nodemap = read_nodes_file(mem_ctx, header->destnode); + if (nodemap == NULL) { + goto fail; + } + + reply.rdata.data.nodemap = nodemap; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Failed to read nodes file"; + client_send_control(req, header, &reply); +} + +static void control_db_open_flags(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.tdb_flags = database_flags(db->flags); + reply.status = 0; + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static void control_db_attach_replicated(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, + CTDB_DB_FLAGS_REPLICATED); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_check_pid_srvid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_client *client; + struct client_state *cstate; + struct ctdb_reply_control reply; + bool pid_found, srvid_found; + int ret; + + reply.rdata.opcode = request->opcode; + + pid_found = false; + srvid_found = false; + + for (client=ctdb->client_list; client != NULL; client=client->next) { + if (client->pid == request->rdata.data.pid_srvid->pid) { + pid_found = true; + cstate = (struct client_state *)client->state; + ret = srvid_exists(ctdb->srv, + request->rdata.data.pid_srvid->srvid, + cstate); + if (ret == 0) { + srvid_found = true; + ret = kill(cstate->pid, 0); + if (ret != 0) { + reply.status = ret; + reply.errmsg = strerror(errno); + } else { + reply.status = 0; + reply.errmsg = NULL; + } + } + } + } + + if (! pid_found) { + reply.status = -1; + reply.errmsg = "No client for PID"; + } else if (! srvid_found) { + reply.status = -1; + reply.errmsg = "No client for PID and SRVID"; + } + + client_send_control(req, header, &reply); +} + +static void control_disable_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Disabling node\n")); + ctdb->node_map->node[header->destnode].flags |= + NODE_FLAGS_PERMANENTLY_DISABLED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void control_enable_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Enable node\n")); + ctdb->node_map->node[header->destnode].flags &= + ~NODE_FLAGS_PERMANENTLY_DISABLED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static bool fake_control_failure(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct fake_control_failure *f = NULL; + + D_DEBUG("Checking fake control failure for control %u on node %u\n", + request->opcode, header->destnode); + for (f = ctdb->control_failures; f != NULL; f = f->next) { + if (f->opcode == request->opcode && + (f->pnn == header->destnode || + f->pnn == CTDB_UNKNOWN_PNN)) { + + reply.rdata.opcode = request->opcode; + if (strcmp(f->error, "TIMEOUT") == 0) { + /* Causes no reply */ + D_ERR("Control %u fake timeout on node %u\n", + request->opcode, header->destnode); + return true; + } else if (strcmp(f->error, "ERROR") == 0) { + D_ERR("Control %u fake error on node %u\n", + request->opcode, header->destnode); + reply.status = -1; + reply.errmsg = f->comment; + client_send_control(req, header, &reply); + return true; + } + } + } + + return false; +} + +static void control_error(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + D_DEBUG("Control %u not implemented\n", request->opcode); + + reply.rdata.opcode = request->opcode; + reply.status = -1; + reply.errmsg = "Not implemented"; + + client_send_control(req, header, &reply); +} + +/* + * Handling protocol - messages + */ + +struct disable_recoveries_state { + struct node *node; +}; + +static void disable_recoveries_callback(struct tevent_req *subreq) +{ + struct disable_recoveries_state *substate = tevent_req_callback_data( + subreq, struct disable_recoveries_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + DEBUG(DEBUG_INFO, ("tevent_wakeup_recv failed\n")); + } + + substate->node->recovery_disabled = false; + TALLOC_FREE(substate->node->recovery_substate); +} + +static void message_disable_recoveries(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_message *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct tevent_req *subreq; + struct ctdbd_context *ctdb = state->ctdb; + struct disable_recoveries_state *substate; + struct ctdb_disable_message *disable = request->data.disable; + struct ctdb_req_message_data reply; + struct node *node; + int ret = -1; + TDB_DATA data; + + node = &ctdb->node_map->node[header->destnode]; + + if (disable->timeout == 0) { + TALLOC_FREE(node->recovery_substate); + node->recovery_disabled = false; + DEBUG(DEBUG_INFO, ("Enabled recoveries on node %u\n", + header->destnode)); + goto done; + } + + substate = talloc_zero(ctdb->node_map, + struct disable_recoveries_state); + if (substate == NULL) { + goto fail; + } + + substate->node = node; + + subreq = tevent_wakeup_send(substate, state->ev, + tevent_timeval_current_ofs( + disable->timeout, 0)); + if (subreq == NULL) { + talloc_free(substate); + goto fail; + } + tevent_req_set_callback(subreq, disable_recoveries_callback, substate); + + DEBUG(DEBUG_INFO, ("Disabled recoveries for %d seconds on node %u\n", + disable->timeout, header->destnode)); + node->recovery_substate = substate; + node->recovery_disabled = true; + +done: + ret = header->destnode; + +fail: + reply.srvid = disable->srvid; + data.dptr = (uint8_t *)&ret; + data.dsize = sizeof(int); + reply.data = data; + + client_send_message(req, header, &reply); +} + +static void message_takeover_run(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_message *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_srvid_message *srvid = request->data.msg; + struct ctdb_req_message_data reply; + int ret = -1; + TDB_DATA data; + + if (header->destnode != ctdb->node_map->recmaster) { + /* No reply! Only recmaster replies... */ + return; + } + + DEBUG(DEBUG_INFO, ("IP takover run on node %u\n", + header->destnode)); + ret = header->destnode; + + reply.srvid = srvid->srvid; + data.dptr = (uint8_t *)&ret; + data.dsize = sizeof(int); + reply.data = data; + + client_send_message(req, header, &reply); +} + +/* + * Handle a single client + */ + +static void client_read_handler(uint8_t *buf, size_t buflen, + void *private_data); +static void client_dead_handler(void *private_data); +static void client_process_packet(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_process_call(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_process_message(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_process_control(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_reply_done(struct tevent_req *subreq); + +static struct tevent_req *client_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, struct ctdbd_context *ctdb, + int pnn) +{ + struct tevent_req *req; + struct client_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct client_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->ctdb = ctdb; + state->pnn = pnn; + + (void) ctdb_get_peer_pid(fd, &state->pid); + + ret = comm_setup(state, ev, fd, client_read_handler, req, + client_dead_handler, req, &state->comm); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + ret = client_add(ctdb, state->pid, state); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + DEBUG(DEBUG_INFO, ("New client fd=%d\n", fd)); + + return req; +} + +static void client_read_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_req_header header; + size_t np; + unsigned int i; + int ret; + + ret = ctdb_req_header_pull(buf, buflen, &header, &np); + if (ret != 0) { + return; + } + + if (buflen != header.length) { + return; + } + + ret = ctdb_req_header_verify(&header, 0); + if (ret != 0) { + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode == CTDB_BROADCAST_ALL) { + for (i=0; i<ctdb->node_map->num_nodes; i++) { + header.destnode = i; + + ctdb_req_header_push(&header, buf, &np); + client_process_packet(req, buf, buflen); + } + return; + } + + if (header.destnode == CTDB_BROADCAST_CONNECTED) { + for (i=0; i<ctdb->node_map->num_nodes; i++) { + if (ctdb->node_map->node[i].flags & + NODE_FLAGS_DISCONNECTED) { + continue; + } + + header.destnode = i; + + ctdb_req_header_push(&header, buf, &np); + client_process_packet(req, buf, buflen); + } + return; + } + + if (header.destnode > ctdb->node_map->num_nodes) { + fprintf(stderr, "Invalid destination pnn 0x%x\n", + header.destnode); + return; + } + + + if (ctdb->node_map->node[header.destnode].flags & NODE_FLAGS_DISCONNECTED) { + fprintf(stderr, "Packet for disconnected node pnn %u\n", + header.destnode); + return; + } + + ctdb_req_header_push(&header, buf, &np); + client_process_packet(req, buf, buflen); +} + +static void client_dead_handler(void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + + tevent_req_done(req); +} + +static void client_process_packet(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct ctdb_req_header header; + size_t np; + int ret; + + ret = ctdb_req_header_pull(buf, buflen, &header, &np); + if (ret != 0) { + return; + } + + switch (header.operation) { + case CTDB_REQ_CALL: + client_process_call(req, buf, buflen); + break; + + case CTDB_REQ_MESSAGE: + client_process_message(req, buf, buflen); + break; + + case CTDB_REQ_CONTROL: + client_process_control(req, buf, buflen); + break; + + default: + break; + } +} + +static void client_process_call(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + TALLOC_CTX *mem_ctx; + struct ctdb_req_header header; + struct ctdb_req_call request; + struct ctdb_reply_call reply; + struct database *db; + struct ctdb_ltdb_header hdr; + TDB_DATA data; + int ret; + + mem_ctx = talloc_new(state); + if (tevent_req_nomem(mem_ctx, req)) { + return; + } + + ret = ctdb_req_call_pull(buf, buflen, &header, mem_ctx, &request); + if (ret != 0) { + talloc_free(mem_ctx); + tevent_req_error(req, ret); + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode >= ctdb->node_map->num_nodes) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("call db_id = %u\n", request.db_id)); + + db = database_find(ctdb->db_map, request.db_id); + if (db == NULL) { + goto fail; + } + + ret = ltdb_fetch(db, request.key, &hdr, mem_ctx, &data); + if (ret != 0) { + goto fail; + } + + /* Fake migration */ + if (hdr.dmaster != ctdb->node_map->pnn) { + hdr.dmaster = ctdb->node_map->pnn; + + ret = ltdb_store(db, request.key, &hdr, data); + if (ret != 0) { + goto fail; + } + } + + talloc_free(mem_ctx); + + reply.status = 0; + reply.data = tdb_null; + + client_send_call(req, &header, &reply); + return; + +fail: + talloc_free(mem_ctx); + reply.status = -1; + reply.data = tdb_null; + + client_send_call(req, &header, &reply); +} + +static void client_process_message(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + TALLOC_CTX *mem_ctx; + struct ctdb_req_header header; + struct ctdb_req_message request; + uint64_t srvid; + int ret; + + mem_ctx = talloc_new(state); + if (tevent_req_nomem(mem_ctx, req)) { + return; + } + + ret = ctdb_req_message_pull(buf, buflen, &header, mem_ctx, &request); + if (ret != 0) { + talloc_free(mem_ctx); + tevent_req_error(req, ret); + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode >= ctdb->node_map->num_nodes) { + /* Many messages are not replied to, so just behave as + * though this message was not received */ + fprintf(stderr, "Invalid node %d\n", header.destnode); + talloc_free(mem_ctx); + return; + } + + srvid = request.srvid; + DEBUG(DEBUG_INFO, ("request srvid = 0x%"PRIx64"\n", srvid)); + + if (srvid == CTDB_SRVID_DISABLE_RECOVERIES) { + message_disable_recoveries(mem_ctx, req, &header, &request); + } else if (srvid == CTDB_SRVID_TAKEOVER_RUN) { + message_takeover_run(mem_ctx, req, &header, &request); + } else { + D_DEBUG("Message id 0x%"PRIx64" not implemented\n", srvid); + } + + /* check srvid */ + talloc_free(mem_ctx); +} + +static void client_process_control(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + TALLOC_CTX *mem_ctx; + struct ctdb_req_header header; + struct ctdb_req_control request; + int ret; + + mem_ctx = talloc_new(state); + if (tevent_req_nomem(mem_ctx, req)) { + return; + } + + ret = ctdb_req_control_pull(buf, buflen, &header, mem_ctx, &request); + if (ret != 0) { + talloc_free(mem_ctx); + tevent_req_error(req, ret); + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode >= ctdb->node_map->num_nodes) { + struct ctdb_reply_control reply; + + reply.rdata.opcode = request.opcode; + reply.errmsg = "Invalid node"; + reply.status = -1; + client_send_control(req, &header, &reply); + return; + } + + DEBUG(DEBUG_INFO, ("request opcode = %u, reqid = %u\n", + request.opcode, header.reqid)); + + if (fake_control_failure(mem_ctx, req, &header, &request)) { + goto done; + } + + switch (request.opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + control_process_exists(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_PING: + control_ping(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GETDBPATH: + control_getdbpath(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GETVNNMAP: + control_getvnnmap(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DEBUG: + control_get_debug(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DEBUG: + control_set_debug(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DBMAP: + control_get_dbmap(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_RECMODE: + control_get_recmode(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_RECMODE: + control_set_recmode(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_ATTACH: + control_db_attach(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_REGISTER_SRVID: + control_register_srvid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + control_deregister_srvid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DBNAME: + control_get_dbname(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PID: + control_get_pid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PNN: + control_get_pnn(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SHUTDOWN: + control_shutdown(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_TUNABLE: + control_set_tunable(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_TUNABLE: + control_get_tunable(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + control_list_tunables(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + control_modify_flags(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + control_get_all_tunables(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + control_db_attach_persistent(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_UPTIME: + control_uptime(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + control_reload_nodes_file(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + control_get_capabilities(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_RELEASE_IP: + control_release_ip(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TAKEOVER_IP: + control_takeover_ip(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + control_get_public_ips(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_NODEMAP: + control_get_nodemap(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + control_get_reclock_file(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_STOP_NODE: + control_stop_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_CONTINUE_NODE: + control_continue_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_BAN_STATE: + control_set_ban_state(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + control_trans3_commit(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + control_get_db_seqnum(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + control_db_get_health(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + control_get_public_ip_info(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_IFACES: + control_get_ifaces(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + control_set_iface_link_state(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DB_READONLY: + control_set_db_readonly(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + control_traverse_start_ext(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + control_set_db_sticky(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_IPREALLOCATED: + control_ipreallocated(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_RUNSTATE: + control_get_runstate(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_NODES_FILE: + control_get_nodes_file(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + control_db_open_flags(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + control_db_attach_replicated(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + control_check_pid_srvid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DISABLE_NODE: + control_disable_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_ENABLE_NODE: + control_enable_node(mem_ctx, req, &header, &request); + break; + + default: + if (! (request.flags & CTDB_CTRL_FLAG_NOREPLY)) { + control_error(mem_ctx, req, &header, &request); + } + break; + } + +done: + talloc_free(mem_ctx); +} + +static int client_recv(struct tevent_req *req, int *perr) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + int err; + + DEBUG(DEBUG_INFO, ("Client done fd=%d\n", state->fd)); + close(state->fd); + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return -1; + } + + return state->status; +} + +/* + * Fake CTDB server + */ + +struct server_state { + struct tevent_context *ev; + struct ctdbd_context *ctdb; + struct tevent_timer *leader_broadcast_te; + int fd; +}; + +static void server_leader_broadcast(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data); +static void server_new_client(struct tevent_req *subreq); +static void server_client_done(struct tevent_req *subreq); + +static struct tevent_req *server_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdbd_context *ctdb, + int fd) +{ + struct tevent_req *req, *subreq; + struct server_state *state; + + req = tevent_req_create(mem_ctx, &state, struct server_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ctdb = ctdb; + state->fd = fd; + + state->leader_broadcast_te = tevent_add_timer(state->ev, + state, + timeval_current_ofs(0, 0), + server_leader_broadcast, + state); + if (state->leader_broadcast_te == NULL) { + DBG_WARNING("Failed to set up leader broadcast\n"); + } + + subreq = accept_send(state, ev, fd); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, server_new_client, req); + + return req; +} + +static void server_leader_broadcast(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct server_state *state = talloc_get_type_abort( + private_data, struct server_state); + struct ctdbd_context *ctdb = state->ctdb; + uint32_t leader = ctdb->node_map->recmaster; + TDB_DATA data; + int ret; + + if (leader == CTDB_UNKNOWN_PNN) { + goto done; + } + + data.dptr = (uint8_t *)&leader; + data.dsize = sizeof(leader); + + ret = srvid_dispatch(ctdb->srv, CTDB_SRVID_LEADER, 0, data); + if (ret != 0) { + DBG_WARNING("Failed to send leader broadcast, ret=%d\n", ret); + } + +done: + state->leader_broadcast_te = tevent_add_timer(state->ev, + state, + timeval_current_ofs(1, 0), + server_leader_broadcast, + state); + if (state->leader_broadcast_te == NULL) { + DBG_WARNING("Failed to set up leader broadcast\n"); + } +} + +static void server_new_client(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct server_state *state = tevent_req_data( + req, struct server_state); + struct ctdbd_context *ctdb = state->ctdb; + int client_fd; + int ret = 0; + + client_fd = accept_recv(subreq, NULL, NULL, &ret); + TALLOC_FREE(subreq); + if (client_fd == -1) { + tevent_req_error(req, ret); + return; + } + + subreq = client_send(state, state->ev, client_fd, + ctdb, ctdb->node_map->pnn); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, server_client_done, req); + + ctdb->num_clients += 1; + + subreq = accept_send(state, state->ev, state->fd); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, server_new_client, req); +} + +static void server_client_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct server_state *state = tevent_req_data( + req, struct server_state); + struct ctdbd_context *ctdb = state->ctdb; + int ret = 0; + int status; + + status = client_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (status < 0) { + tevent_req_error(req, ret); + return; + } + + ctdb->num_clients -= 1; + + if (status == 99) { + /* Special status, to shutdown server */ + DEBUG(DEBUG_INFO, ("Shutting down server\n")); + tevent_req_done(req); + } +} + +static bool server_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +/* + * Main functions + */ + +static int socket_init(const char *sockpath) +{ + struct sockaddr_un addr; + size_t len; + int ret, fd; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + if (len >= sizeof(addr.sun_path)) { + fprintf(stderr, "path too long: %s\n", sockpath); + return -1; + } + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + fprintf(stderr, "socket failed - %s\n", sockpath); + return -1; + } + + ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret != 0) { + fprintf(stderr, "bind failed - %s\n", sockpath); + goto fail; + } + + ret = listen(fd, 10); + if (ret != 0) { + fprintf(stderr, "listen failed\n"); + goto fail; + } + + DEBUG(DEBUG_INFO, ("Socket init done\n")); + + return fd; + +fail: + if (fd != -1) { + close(fd); + } + return -1; +} + +static struct options { + const char *dbdir; + const char *sockpath; + const char *pidfile; + const char *debuglevel; +} options; + +static struct poptOption cmdline_options[] = { + POPT_AUTOHELP + { "dbdir", 'D', POPT_ARG_STRING, &options.dbdir, 0, + "Database directory", "directory" }, + { "socket", 's', POPT_ARG_STRING, &options.sockpath, 0, + "Unix domain socket path", "filename" }, + { "pidfile", 'p', POPT_ARG_STRING, &options.pidfile, 0, + "pid file", "filename" } , + { "debug", 'd', POPT_ARG_STRING, &options.debuglevel, 0, + "debug level", "ERR|WARNING|NOTICE|INFO|DEBUG" } , + POPT_TABLEEND +}; + +static void cleanup(void) +{ + unlink(options.sockpath); + unlink(options.pidfile); +} + +static void signal_handler(int sig) +{ + cleanup(); + exit(0); +} + +static void start_server(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct ctdbd_context *ctdb, int fd, int pfd) +{ + struct tevent_req *req; + int ret = 0; + ssize_t len; + + atexit(cleanup); + signal(SIGTERM, signal_handler); + + req = server_send(mem_ctx, ev, ctdb, fd); + if (req == NULL) { + fprintf(stderr, "Memory error\n"); + exit(1); + } + + len = write(pfd, &ret, sizeof(ret)); + if (len != sizeof(ret)) { + fprintf(stderr, "Failed to send message to parent\n"); + exit(1); + } + close(pfd); + + tevent_req_poll(req, ev); + + server_recv(req, &ret); + if (ret != 0) { + exit(1); + } +} + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct ctdbd_context *ctdb; + struct tevent_context *ev; + poptContext pc; + int opt, fd, ret, pfd[2]; + ssize_t len; + pid_t pid; + FILE *fp; + + pc = poptGetContext(argv[0], argc, argv, cmdline_options, + POPT_CONTEXT_KEEP_FIRST); + while ((opt = poptGetNextOpt(pc)) != -1) { + fprintf(stderr, "Invalid option %s\n", poptBadOption(pc, 0)); + exit(1); + } + + if (options.dbdir == NULL) { + fprintf(stderr, "Please specify database directory\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + if (options.sockpath == NULL) { + fprintf(stderr, "Please specify socket path\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + if (options.pidfile == NULL) { + fprintf(stderr, "Please specify pid file\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory error\n"); + exit(1); + } + + ret = logging_init(mem_ctx, "file:", options.debuglevel, "fake-ctdbd"); + if (ret != 0) { + fprintf(stderr, "Invalid debug level\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + ctdb = ctdbd_setup(mem_ctx, options.dbdir); + if (ctdb == NULL) { + exit(1); + } + + if (! ctdbd_verify(ctdb)) { + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory error\n"); + exit(1); + } + + fd = socket_init(options.sockpath); + if (fd == -1) { + exit(1); + } + + ret = pipe(pfd); + if (ret != 0) { + fprintf(stderr, "Failed to create pipe\n"); + cleanup(); + exit(1); + } + + pid = fork(); + if (pid == -1) { + fprintf(stderr, "Failed to fork\n"); + cleanup(); + exit(1); + } + + if (pid == 0) { + /* Child */ + close(pfd[0]); + start_server(mem_ctx, ev, ctdb, fd, pfd[1]); + exit(1); + } + + /* Parent */ + close(pfd[1]); + + len = read(pfd[0], &ret, sizeof(ret)); + close(pfd[0]); + if (len != sizeof(ret)) { + fprintf(stderr, "len = %zi\n", len); + fprintf(stderr, "Failed to get message from child\n"); + kill(pid, SIGTERM); + exit(1); + } + + fp = fopen(options.pidfile, "w"); + if (fp == NULL) { + fprintf(stderr, "Failed to open pid file %s\n", + options.pidfile); + kill(pid, SIGTERM); + exit(1); + } + fprintf(fp, "%d\n", pid); + fclose(fp); + + return 0; +} diff --git a/ctdb/tests/src/fetch_loop.c b/ctdb/tests/src/fetch_loop.c new file mode 100644 index 0000000..0e1d9da --- /dev/null +++ b/ctdb/tests/src/fetch_loop.c @@ -0,0 +1,288 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define TESTDB "fetch_loop.tdb" +#define TESTKEY "testkey" + +struct fetch_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int num_nodes; + int timelimit; + TDB_DATA key; + int locks_count; +}; + +static void fetch_loop_start(struct tevent_req *subreq); +static void fetch_loop_next(struct tevent_req *subreq); +static void fetch_loop_each_second(struct tevent_req *subreq); +static void fetch_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->key.dptr = discard_const(TESTKEY); + state->key.dsize = strlen(TESTKEY); + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_loop_start, req); + + return req; +} + +static void fetch_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_finish, req); +} + +static void fetch_loop_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + struct ctdb_record_handle *h; + TDB_DATA data; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + if (data.dsize == sizeof(uint32_t)) { + state->locks_count = *(uint32_t *)data.dptr; + } + TALLOC_FREE(data.dptr); + + state->locks_count += 1; + data.dsize = sizeof(uint32_t); + data.dptr = (uint8_t *)&state->locks_count; + + ret = ctdb_store_record(h, data); + if (ret != 0) { + talloc_free(h); + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); +} + +static void fetch_loop_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\r", state->locks_count); + fflush(stdout); + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); +} + +static void fetch_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\n", state->locks_count); + + tevent_req_done(req); +} + +static bool fetch_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_loop", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), TESTDB, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", TESTDB); + exit(1); + } + + req = fetch_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "fetch loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_loop_key.c b/ctdb/tests/src/fetch_loop_key.c new file mode 100644 index 0000000..3f41ca7 --- /dev/null +++ b/ctdb/tests/src/fetch_loop_key.c @@ -0,0 +1,217 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct fetch_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int timelimit; + TDB_DATA key; + int locks_count; +}; + +static void fetch_loop_next(struct tevent_req *subreq); + +static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + const char *keystr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->timelimit = timelimit; + state->key.dptr = discard_const(keystr); + state->key.dsize = strlen(keystr); + + subreq = ctdb_fetch_lock_send(state, ev, client, ctdb_db, + state->key, false); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_loop_next, req); + + return req; +} + +static void fetch_loop_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + struct ctdb_record_handle *h; + TDB_DATA data; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + if (data.dsize == sizeof(uint32_t)) { + state->locks_count = *(uint32_t *)data.dptr; + } + TALLOC_FREE(data.dptr); + + state->locks_count += 1; + data.dsize = sizeof(uint32_t); + data.dptr = (uint8_t *)&state->locks_count; + + ret = ctdb_store_record(h, data); + if (ret != 0) { + talloc_free(h); + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); +} + +static bool fetch_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +static struct tevent_req *global_req; + +static void alarm_handler(int sig) +{ + struct fetch_loop_state *state = tevent_req_data( + global_req, struct fetch_loop_state); + static int time_passed = 0; + + time_passed += 1; + + printf("Locks:%d\n", state->locks_count); + fflush(stdout); + + if (time_passed >= state->timelimit) { + tevent_req_done(global_req); + } + + alarm(1); +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int ret; + bool status; + + setup_logging("fetch_loop_key", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, %s\n", + strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname); + exit(1); + } + + global_req = fetch_loop_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->timelimit); + if (global_req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + signal(SIGALRM, alarm_handler); + alarm(1); + + tevent_req_poll(global_req, ev); + + status = fetch_loop_recv(global_req, &ret); + if (! status) { + fprintf(stderr, "fetch loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_readonly.c b/ctdb/tests/src/fetch_readonly.c new file mode 100644 index 0000000..ff126bd --- /dev/null +++ b/ctdb/tests/src/fetch_readonly.c @@ -0,0 +1,166 @@ +/* + Fetch a single record using readonly + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + + +struct fetch_readonly_state { + struct tevent_context *ev; +}; + +static void fetch_readonly_done(struct tevent_req *subreq); + +static struct tevent_req *fetch_readonly_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + const char *keystr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_readonly_state *state; + TDB_DATA key; + + req = tevent_req_create(mem_ctx, &state, struct fetch_readonly_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + + key.dptr = (uint8_t *)discard_const(keystr); + key.dsize = strlen(keystr); + + subreq = ctdb_fetch_lock_send(state, ev, client, db, key, true); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_readonly_done, req); + + return req; +} + +static void fetch_readonly_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_readonly_state *state = tevent_req_data( + req, struct fetch_readonly_state); + struct ctdb_record_handle *h; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, NULL, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + tevent_req_done(req); +} + +static bool fetch_readonly_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_readonly", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, %s\n", + strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname); + exit(1); + } + + req = fetch_readonly_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_readonly_recv(req, &ret); + if (! status) { + fprintf(stderr, "fetch readonly loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_readonly_loop.c b/ctdb/tests/src/fetch_readonly_loop.c new file mode 100644 index 0000000..08cf476 --- /dev/null +++ b/ctdb/tests/src/fetch_readonly_loop.c @@ -0,0 +1,272 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define TESTDB "fetch_readonly_loop.tdb" +#define TESTKEY "testkey" + +struct fetch_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int num_nodes; + int timelimit; + TDB_DATA key; + int locks_count; +}; + +static void fetch_loop_start(struct tevent_req *subreq); +static void fetch_loop_next(struct tevent_req *subreq); +static void fetch_loop_each_second(struct tevent_req *subreq); +static void fetch_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->key.dptr = discard_const(TESTKEY); + state->key.dsize = strlen(TESTKEY); + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_loop_start, req); + + return req; +} + +static void fetch_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, true); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_finish, req); +} + +static void fetch_loop_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + struct ctdb_record_handle *h; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, NULL, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + state->locks_count += 1; + talloc_free(h); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, true); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); +} + +static void fetch_loop_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\r", state->locks_count); + fflush(stdout); + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); +} + +static void fetch_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\n", state->locks_count); + + tevent_req_done(req); +} + +static bool fetch_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_readonly_loop", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), TESTDB, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", TESTDB); + exit(1); + } + + req = fetch_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "fetch readonly loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_ring.c b/ctdb/tests/src/fetch_ring.c new file mode 100644 index 0000000..f1786ef --- /dev/null +++ b/ctdb/tests/src/fetch_ring.c @@ -0,0 +1,398 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/time.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define MSG_ID_FETCH 0 + +static uint32_t next_node(struct ctdb_client_context *client, uint32_t num_nodes) +{ + return (ctdb_client_pnn(client) + 1) % num_nodes; +} + +struct fetch_ring_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + uint32_t num_nodes; + int timelimit; + int interactive; + TDB_DATA key; + int msg_count; + struct timeval start_time; +}; + +static void fetch_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void fetch_ring_wait(struct tevent_req *subreq); +static void fetch_ring_start(struct tevent_req *subreq); +static void fetch_ring_update(struct tevent_req *subreq); +static void fetch_ring_msg_sent(struct tevent_req *subreq); +static void fetch_ring_finish(struct tevent_req *subreq); +static void fetch_ring_final_read(struct tevent_req *subreq); + +static struct tevent_req *fetch_ring_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + const char *keystr, + uint32_t num_nodes, + int timelimit, + int interactive) +{ + struct tevent_req *req, *subreq; + struct fetch_ring_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_ring_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->interactive = interactive; + state->key.dptr = discard_const(keystr); + state->key.dsize = strlen(keystr); + + subreq = ctdb_client_set_message_handler_send( + state, ev, client, MSG_ID_FETCH, + fetch_ring_msg_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_ring_wait, req); + + return req; +} + +static void fetch_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + struct tevent_req *subreq; + + state->msg_count += 1; + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_update, req); +} + +static void fetch_ring_wait(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_start, req); +} + +static void fetch_ring_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->start_time = tevent_timeval_current(); + + if (ctdb_client_pnn(state->client) == state->num_nodes-1) { + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, + false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_update, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_finish, req); + +} + +static void fetch_ring_update(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + struct ctdb_record_handle *h; + struct ctdb_req_message msg; + TDB_DATA data; + uint32_t pnn; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + if (data.dsize > 1000) { + TALLOC_FREE(data.dptr); + data.dsize = 0; + } + + if (data.dsize == 0) { + data.dptr = (uint8_t *)talloc_asprintf(state, "Test data\n"); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + } + + data.dptr = (uint8_t *)talloc_asprintf_append( + (char *)data.dptr, + "msg_count=%d on node %d\n", + state->msg_count, + ctdb_client_pnn(state->client)); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + + data.dsize = strlen((const char *)data.dptr) + 1; + + ret = ctdb_store_record(h, data); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + talloc_free(data.dptr); + talloc_free(h); + + msg.srvid = MSG_ID_FETCH; + msg.data.data = tdb_null; + + pnn = next_node(state->client, state->num_nodes); + + subreq = ctdb_client_message_send(state, state->ev, state->client, + pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_msg_sent, req); +} + +static void fetch_ring_msg_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + } +} + +static void fetch_ring_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + bool status; + double t; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + t = timeval_elapsed(&state->start_time); + + printf("Fetch[%u]: %.2f msgs/sec\n", ctdb_client_pnn(state->client), + state->msg_count / t); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_final_read, req); +} + +static void fetch_ring_final_read(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + struct ctdb_record_handle *h; + TDB_DATA data; + int err; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &err); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, err); + return; + } + + if (state->interactive == 1) { + printf("DATA:\n%s\n", (char *)data.dptr); + } + talloc_free(data.dptr); + talloc_free(h); + + tevent_req_done(req); +} + +static bool fetch_ring_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_ring", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, + client, + tevent_timeval_zero(), + opts->dbname, + 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname); + exit(1); + } + + req = fetch_ring_send(mem_ctx, + ev, + client, + ctdb_db, + opts->keystr, + opts->num_nodes, + opts->timelimit, + opts->interactive); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_ring_recv(req, NULL); + if (! status) { + fprintf(stderr, "fetch ring test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/g_lock_loop.c b/ctdb/tests/src/g_lock_loop.c new file mode 100644 index 0000000..3b84241 --- /dev/null +++ b/ctdb/tests/src/g_lock_loop.c @@ -0,0 +1,270 @@ +/* + simple ctdb benchmark for g_lock operations + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/tevent_unix.h" +#include "lib/util/debug.h" + +#include "protocol/protocol_api.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define TESTKEY "testkey" + +struct glock_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *db; + int num_nodes; + int timelimit; + uint32_t pnn; + uint32_t counter; + struct ctdb_server_id sid; + const char *key; +}; + +static void glock_loop_start(struct tevent_req *subreq); +static void glock_loop_locked(struct tevent_req *subreq); +static void glock_loop_unlocked(struct tevent_req *subreq); +static void glock_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *glock_loop_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct glock_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct glock_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->db = db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->pnn = ctdb_client_pnn(client); + state->counter = 0; + state->sid = ctdb_client_get_server_id(client, 1); + state->key = TESTKEY; + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, glock_loop_start, req); + + return req; +} + +static void glock_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_g_lock_lock_send(state, state->ev, state->client, + state->db, state->key, &state->sid, + false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_locked, req); + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_finish, req); +} + +static void glock_loop_locked(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + int ret; + bool status; + + status = ctdb_g_lock_lock_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + fprintf(stderr, "g_lock_lock failed\n"); + tevent_req_error(req, ret); + return; + } + + state->counter += 1; + + subreq = ctdb_g_lock_unlock_send(state, state->ev, state->client, + state->db, state->key, state->sid); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_unlocked, req); +} + +static void glock_loop_unlocked(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + int ret; + bool status; + + status = ctdb_g_lock_unlock_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + fprintf(stderr, "g_lock_unlock failed\n"); + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_g_lock_lock_send(state, state->ev, state->client, + state->db, state->key, &state->sid, + false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_locked, req); +} + +static void glock_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("PNN:%u counter:%u\n", state->pnn, state->counter); + + tevent_req_done(req); +} + +static bool glock_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("glock_loop", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), "g_lock.tdb", + 0, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to g_lock.tdb\n"); + exit(1); + } + + req = glock_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = glock_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "g_lock loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/hash_count_test.c b/ctdb/tests/src/hash_count_test.c new file mode 100644 index 0000000..6ddde08 --- /dev/null +++ b/ctdb/tests/src/hash_count_test.c @@ -0,0 +1,132 @@ +/* + hash_count tests + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> + +#include "common/db_hash.c" +#include "common/hash_count.c" + +#define KEY "this_is_a_test_key" + +static void test1_handler(TDB_DATA key, uint64_t counter, void *private_data) +{ + int *count = (int *)private_data; + + assert(key.dsize == strlen(KEY)); + assert(strcmp((char *)key.dptr, KEY) == 0); + assert(counter > 0); + + (*count) += 1; +} + +static void do_test1(void) +{ + struct hash_count_context *hc = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct timeval interval = {1, 0}; + TDB_DATA key; + int count = 0; + int ret, i; + + key.dptr = (uint8_t *)discard_const(KEY); + key.dsize = strlen(KEY); + + ret = hash_count_increment(hc, key); + assert(ret == EINVAL); + + ret = hash_count_init(mem_ctx, interval, NULL, NULL, &hc); + assert(ret == EINVAL); + + ret = hash_count_init(mem_ctx, interval, test1_handler, &count, &hc); + assert(ret == 0); + assert(hc != NULL); + + for (i=0; i<10; i++) { + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == i+1); + } + + talloc_free(hc); + ret = talloc_get_size(mem_ctx); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +static void test2_handler(TDB_DATA key, uint64_t counter, void *private_data) +{ + uint64_t *count = (uint64_t *)private_data; + + *count = counter; +} + +static void do_test2(void) +{ + struct hash_count_context *hc; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct timeval interval = {1, 0}; + TDB_DATA key; + uint64_t count = 0; + int ret; + + key.dptr = (uint8_t *)discard_const(KEY); + key.dsize = strlen(KEY); + + ret = hash_count_init(mem_ctx, interval, test2_handler, &count, &hc); + assert(ret == 0); + + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == 1); + + hash_count_expire(hc, &ret); + assert(ret == 0); + + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == 2); + + sleep(2); + + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == 1); + + sleep(2); + + hash_count_expire(hc, &ret); + assert(ret == 1); + + talloc_free(hc); + ret = talloc_get_size(mem_ctx); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +int main(void) +{ + do_test1(); + do_test2(); + + return 0; +} diff --git a/ctdb/tests/src/ipalloc_read_known_ips.c b/ctdb/tests/src/ipalloc_read_known_ips.c new file mode 100644 index 0000000..33d0f94 --- /dev/null +++ b/ctdb/tests/src/ipalloc_read_known_ips.c @@ -0,0 +1,179 @@ +/* + Tests support for CTDB IP allocation + + Copyright (C) Martin Schwenke 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <talloc.h> + +#include "lib/util/debug.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_util.h" +#include "common/logging.h" + +#include "ipalloc_read_known_ips.h" + +static bool add_ip(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list *l, + ctdb_sock_addr *addr, + uint32_t pnn) +{ + + l->ip = talloc_realloc(mem_ctx, l->ip, + struct ctdb_public_ip, l->num + 1); + if (l->ip == NULL) { + D_ERR(__location__ " out of memory\n"); + return false; + } + + l->ip[l->num].addr = *addr; + l->ip[l->num].pnn = pnn; + l->num++; + + return true; +} + +/* Format of each line is "IP CURRENT_PNN [ALLOWED_PNN,...]". + * If multi is true then ALLOWED_PNNs are not allowed. */ +static bool read_ctdb_public_ip_info_node(bool multi, + int numnodes, + struct ctdb_public_ip_list **k, + struct ctdb_public_ip_list *known) +{ + char line[1024]; + ctdb_sock_addr addr; + char *t, *tok; + int pnn, n; + + /* Known public IPs */ + *k = talloc_zero(known, struct ctdb_public_ip_list); + if (*k == NULL) { + goto fail; + } + + while (fgets(line, sizeof(line), stdin) != NULL) { + int ret; + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Exit on an empty line */ + if (line[0] == '\0') { + break; + } + + /* Get the IP address */ + tok = strtok(line, " \t"); + if (tok == NULL) { + D_WARNING("WARNING, bad line ignored :%s\n", line); + continue; + } + + ret = ctdb_sock_addr_from_string(tok, &addr, false); + if (ret != 0) { + D_ERR("ERROR, bad address :%s\n", tok); + continue; + } + + /* Get the PNN */ + pnn = -1; + tok = strtok(NULL, " \t"); + if (tok != NULL) { + pnn = (int) strtol(tok, (char **) NULL, 10); + } + + if (! add_ip(*k, *k, &addr, pnn)) { + goto fail; + } + + tok = strtok(NULL, " \t#"); + if (tok == NULL) { + if (! multi) { + for (n = 0; n < numnodes; n++) { + if (! add_ip(known, &known[n], + &addr, pnn)) { + goto fail; + } + } + } + continue; + } + + /* Handle allowed nodes for addr */ + if (multi) { + D_ERR("ERROR, bad token\n"); + goto fail; + } + t = strtok(tok, ","); + while (t != NULL) { + n = (int) strtol(t, (char **) NULL, 10); + if (! add_ip(known, &known[n], &addr, pnn)) { + goto fail; + } + t = strtok(NULL, ","); + } + } + + return true; + +fail: + TALLOC_FREE(*k); + return false; +} + +struct ctdb_public_ip_list * ipalloc_read_known_ips(TALLOC_CTX *ctx, + int numnodes, + bool multi) +{ + int n; + struct ctdb_public_ip_list *k; + struct ctdb_public_ip_list *known; + + known = talloc_zero_array(ctx, struct ctdb_public_ip_list, + numnodes); + if (known == NULL) { + D_ERR(__location__ " out of memory\n"); + goto fail; + } + + if (multi) { + for (n = 0; n < numnodes; n++) { + if (! read_ctdb_public_ip_info_node(multi, numnodes, + &k, known)) { + goto fail; + } + + known[n] = *k; + } + } else { + if (! read_ctdb_public_ip_info_node(multi, numnodes, + &k, known)) { + goto fail; + } + } + + return known; + +fail: + talloc_free(known); + return NULL; +} diff --git a/ctdb/tests/src/ipalloc_read_known_ips.h b/ctdb/tests/src/ipalloc_read_known_ips.h new file mode 100644 index 0000000..aa6d154 --- /dev/null +++ b/ctdb/tests/src/ipalloc_read_known_ips.h @@ -0,0 +1,32 @@ +/* + Tests support for CTDB IP allocation + + Copyright (C) Martin Schwenke 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __IPALLOC_READ_KNOWN_IPS_H__ +#define __IPALLOC_READ_KNOWN_IPS_H__ + +#include <stdbool.h> +#include <talloc.h> + +#include "protocol/protocol.h" + +struct ctdb_public_ip_list * ipalloc_read_known_ips(TALLOC_CTX *ctx, + int numnodes, + bool multi); + +#endif /* __IPALLOC_READ_KNOWN_IPS_H__ */ diff --git a/ctdb/tests/src/line_test.c b/ctdb/tests/src/line_test.c new file mode 100644 index 0000000..806d883 --- /dev/null +++ b/ctdb/tests/src/line_test.c @@ -0,0 +1,102 @@ +/* + Test code for line based I/O over fds + + Copyright (C) Amitay Isaacs 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <talloc.h> +#include <assert.h> + +#include "common/line.c" + +static int line_print(char *line, void *private_data) +{ + printf("%s\n", line); + fflush(stdout); + + return 0; +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + size_t hint = 32; + pid_t pid; + int ret, lines = 0; + int pipefd[2]; + + if (argc < 2 || argc > 3) { + fprintf(stderr, "Usage: %s <filename> [<hint>]\n", argv[0]); + exit(1); + } + + if (argc == 3) { + long value; + + value = atol(argv[2]); + assert(value > 0); + hint = value; + } + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + char buffer[16]; + ssize_t n, n2; + int fd; + + close(pipefd[0]); + + fd = open(argv[1], O_RDONLY); + assert(fd != -1); + + while (1) { + n = read(fd, buffer, sizeof(buffer)); + assert(n >= 0 && (size_t)n <= sizeof(buffer)); + + if (n == 0) { + break; + } + + n2 = write(pipefd[1], buffer, n); + assert(n2 == n); + } + + close(pipefd[1]); + close(fd); + + exit(0); + } + + close(pipefd[1]); + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = line_read(pipefd[0], hint, NULL, line_print, NULL, &lines); + assert(ret == 0); + + talloc_free(mem_ctx); + + return lines; +} diff --git a/ctdb/tests/src/lock_tdb.c b/ctdb/tests/src/lock_tdb.c new file mode 100644 index 0000000..c37f846 --- /dev/null +++ b/ctdb/tests/src/lock_tdb.c @@ -0,0 +1,60 @@ +/* + Lock a tdb and sleep + + Copyright (C) Amitay Isaacs 2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <tdb.h> + +const char *tdb_file; +TDB_CONTEXT *tdb; + +static void signal_handler(int signum) +{ + tdb_close(tdb); +} + +int +main(int argc, char *argv[]) +{ + if (argc != 2) { + printf("Usage: %s <tdb file>\n", argv[0]); + exit(1); + } + + tdb_file = argv[1]; + + tdb = tdb_open(tdb_file, 0, 0, O_RDWR, 0); + if (tdb == NULL) { + fprintf(stderr, "Failed to open TDB file %s\n", tdb_file); + exit(1); + } + + signal(SIGINT, signal_handler); + + if (tdb_lockall(tdb) != 0) { + fprintf(stderr, "Failed to lock database %s\n", tdb_file); + tdb_close(tdb); + exit(1); + } + + sleep(999999); + + return 0; +} diff --git a/ctdb/tests/src/message_ring.c b/ctdb/tests/src/message_ring.c new file mode 100644 index 0000000..d1fcee4 --- /dev/null +++ b/ctdb/tests/src/message_ring.c @@ -0,0 +1,369 @@ +/* + simple ctdb benchmark - send messages in a ring around cluster + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/time.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define MSG_ID_BENCH 0 + +struct message_ring_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + int num_nodes; + int timelimit; + int interactive; + int msg_count; + int msg_plus, msg_minus; + struct timeval start_time; +}; + +static void message_ring_wait(struct tevent_req *subreq); +static void message_ring_start(struct tevent_req *subreq); +static void message_ring_each_second(struct tevent_req *subreq); +static void message_ring_msg_sent(struct tevent_req *subreq); +static void message_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void message_ring_finish(struct tevent_req *subreq); + +static struct tevent_req *message_ring_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + int num_nodes, int timelimit, + int interactive) +{ + struct tevent_req *req, *subreq; + struct message_ring_state *state; + + req = tevent_req_create(mem_ctx, &state, struct message_ring_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->interactive = interactive; + + subreq = ctdb_client_set_message_handler_send( + state, state->ev, state->client, + MSG_ID_BENCH, + message_ring_msg_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, message_ring_wait, req); + + return req; +} + +static void message_ring_wait(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_start, req); +} + +static void message_ring_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->start_time = tevent_timeval_current(); + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_each_second, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_finish, req); +} + +static uint32_t next_node(struct ctdb_client_context *client, + int num_nodes, int incr) +{ + return (ctdb_client_pnn(client) + num_nodes + incr) % num_nodes; +} + +static void message_ring_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + struct ctdb_req_message msg; + uint32_t pnn; + int incr; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + pnn = ctdb_client_pnn(state->client); + if (pnn == 0 && state->interactive == 1) { + double t; + + t = timeval_elapsed(&state->start_time); + printf("Ring[%u]: %.2f msgs/sec (+ve=%d -ve=%d)\n", + pnn, state->msg_count / t, + state->msg_plus, state->msg_minus); + fflush(stdout); + } + + if (state->msg_plus == 0) { + incr = 1; + + msg.srvid = 0; + msg.data.data.dptr = (uint8_t *)&incr; + msg.data.data.dsize = sizeof(incr); + + pnn = next_node(state->client, state->num_nodes, incr); + + subreq = ctdb_client_message_send(state, state->ev, + state->client, pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_msg_sent, req); + } + + if (state->msg_minus == 0) { + incr = -1; + + msg.srvid = 0; + msg.data.data.dptr = (uint8_t *)&incr; + msg.data.data.dsize = sizeof(incr); + + pnn = next_node(state->client, state->num_nodes, incr); + + subreq = ctdb_client_message_send(state, state->ev, + state->client, pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_msg_sent, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_each_second, req); +} + +static void message_ring_msg_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + } +} + +static void message_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + struct ctdb_req_message msg; + struct tevent_req *subreq; + int incr; + uint32_t pnn; + + if (srvid != MSG_ID_BENCH) { + return; + } + + if (data.dsize != sizeof(int)) { + return; + } + incr = *(int *)data.dptr; + + state->msg_count += 1; + if (incr == 1) { + state->msg_plus += 1; + } else { + state->msg_minus += 1; + } + + pnn = next_node(state->client, state->num_nodes, incr); + + msg.srvid = srvid; + msg.data.data = data; + + subreq = ctdb_client_message_send(state, state->ev, state->client, + pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_msg_sent, req); +} + +static void message_ring_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + bool status; + double t; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + t = timeval_elapsed(&state->start_time); + + printf("Ring[%u]: %.2f msgs/sec (+ve=%d -ve=%d)\n", + ctdb_client_pnn(state->client), state->msg_count / t, + state->msg_plus, state->msg_minus); + + tevent_req_done(req); +} + +static bool message_ring_recv(struct tevent_req *req) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("message_ring", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Failed to wait for recovery\n"); + exit(1); + } + + req = message_ring_send(mem_ctx, ev, client, + opts->num_nodes, opts->timelimit, + opts->interactive); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = message_ring_recv(req); + if (! status) { + fprintf(stderr, "message ring test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/pidfile_test.c b/ctdb/tests/src/pidfile_test.c new file mode 100644 index 0000000..592fc2b --- /dev/null +++ b/ctdb/tests/src/pidfile_test.c @@ -0,0 +1,242 @@ +/* + pidfile tests + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/wait.h" + +#include <assert.h> + +#include "common/pidfile.c" + + +/* create pid file, check pid file exists, check pid and remove pid file */ +static void test1(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + int ret; + struct stat st; + FILE *fp; + pid_t pid; + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + ret = stat(pidfile, &st); + assert(ret == 0); + assert(S_ISREG(st.st_mode)); + + fp = fopen(pidfile, "r"); + assert(fp != NULL); + ret = fscanf(fp, "%d", &pid); + assert(ret == 1); + assert(pid == getpid()); + fclose(fp); + + TALLOC_FREE(pid_ctx); + + ret = stat(pidfile, &st); + assert(ret == -1); +} + +/* create pid file in two processes */ +static void test2(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + pid_t pid, pid2; + int fd[2]; + int ret; + size_t nread; + FILE *fp; + struct stat st; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(fd[0]); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + sleep(10); + + TALLOC_FREE(pid_ctx); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + exit(1); + } + + close(fd[1]); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + fp = fopen(pidfile, "r"); + assert(fp != NULL); + ret = fscanf(fp, "%d", &pid2); + assert(ret == 1); + assert(pid == pid2); + fclose(fp); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret != 0); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + TALLOC_FREE(pid_ctx); + + ret = stat(pidfile, &st); + assert(ret == -1); +} + +/* create pid file, fork, try to remove pid file in separate process */ +static void test3(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + pid_t pid; + int fd[2]; + int ret; + size_t nread; + struct stat st; + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(fd[0]); + + TALLOC_FREE(pid_ctx); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + exit(1); + } + + close(fd[1]); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + + ret = stat(pidfile, &st); + assert(ret == 0); + + TALLOC_FREE(pid_ctx); + + ret = stat(pidfile, &st); + assert(ret == -1); +} + +/* create pid file, kill process, overwrite pid file in different process */ +static void test4(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + pid_t pid, pid2; + int fd[2]; + int ret; + size_t nread; + struct stat st; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(fd[0]); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + sleep(99); + exit(1); + } + + close(fd[1]); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + ret = stat(pidfile, &st); + assert(ret == 0); + + ret = kill(pid, SIGKILL); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + ret = stat(pidfile, &st); + assert(ret == 0); + + TALLOC_FREE(pid_ctx); +} + +int main(int argc, const char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s <pidfile>\n", argv[0]); + exit(1); + } + + test1(argv[1]); + test2(argv[1]); + test3(argv[1]); + test4(argv[1]); + + return 0; +} diff --git a/ctdb/tests/src/pkt_read_test.c b/ctdb/tests/src/pkt_read_test.c new file mode 100644 index 0000000..a3ebe0a --- /dev/null +++ b/ctdb/tests/src/pkt_read_test.c @@ -0,0 +1,249 @@ +/* + packet read tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "lib/util/blocking.h" + +#include "common/pkt_read.c" + +static void writer(int fd) +{ + uint8_t buf[1024*1024]; + size_t buflen; + size_t pkt_size[4] = { 100, 500, 1024, 1024*1024 }; + int i, j; + int ret; + + for (i=0; i<1024*1024; i++) { + buf[i] = i%256; + } + + for (i=0; i<1000; i++) { + for (j=0; j<4; j++) { + buflen = pkt_size[j]; + memcpy(buf, &buflen, sizeof(buflen)); + + ret = write(fd, buf, buflen); + if (ret < 0) { + printf("write error: %s\n", strerror(errno)); + assert(ret > 0); + } + } + } + + close(fd); +} + +struct reader_state { + struct tevent_context *ev; + int fd; + uint8_t *buf; + size_t buflen; + struct tevent_req *subreq; +}; + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data); +static void reader_done(struct tevent_req *subreq); + +static struct tevent_req *reader_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, uint8_t *buf, + size_t buflen) +{ + struct tevent_req *req, *subreq; + struct reader_state *state; + + req = tevent_req_create(mem_ctx, &state, struct reader_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->buf = buf; + state->buflen = buflen; + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, state->buflen, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + tevent_req_post(req, ev); + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); + return req; +} + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data) +{ + uint32_t pkt_len; + + if (buflen < sizeof(pkt_len)) { + return sizeof(pkt_len) - buflen; + } + + pkt_len = *(uint32_t *)buf; + return pkt_len - buflen; +} + +static void reader_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + ssize_t nread; + uint8_t *buf; + bool free_buf; + int err; + + nread = pkt_read_recv(subreq, state, &buf, &free_buf, &err); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (nread == -1) { + if (err == EPIPE) { + tevent_req_done(req); + } else { + tevent_req_error(req, err); + } + return; + } + + if (free_buf) { + talloc_free(buf); + } + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, state->buflen, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + return; + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); +} + +static void reader_recv(struct tevent_req *req, int *perr) +{ + struct reader_state *state = tevent_req_data( + req, struct reader_state); + int err = 0; + + if (state->subreq != NULL) { + *perr = -1; + } + + if (tevent_req_is_unix_error(req, &err)) { + *perr = err; + return; + } + + *perr = 0; +} + +static void reader_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + + assert(state->subreq != NULL); + pkt_read_handler(ev, fde, flags, state->subreq); +} + +static void reader(int fd, bool fixed) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_fd *fde; + struct tevent_req *req; + int err; + uint8_t *buf = NULL; + size_t buflen = 0; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + if (fixed) { + buflen = 1024; + buf = talloc_size(mem_ctx, buflen); + assert(buf != NULL); + } + + req = reader_send(mem_ctx, ev, fd, buf, buflen); + assert(req != NULL); + + fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_READ, + reader_handler, req); + assert(fde != NULL); + + tevent_req_poll(req, ev); + + reader_recv(req, &err); + assert(err == 0); + + close(fd); + + talloc_free(mem_ctx); +} + +static void reader_test(bool fixed) +{ + int fd[2]; + int ret; + pid_t pid; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Child process */ + close(fd[0]); + writer(fd[1]); + exit(0); + } + + close(fd[1]); + ret = set_blocking(fd[0], false); + if (ret == -1) { + exit(1); + } + + reader(fd[0], fixed); +} + +int main(void) +{ + reader_test(true); + reader_test(false); + + return 0; +} diff --git a/ctdb/tests/src/pkt_write_test.c b/ctdb/tests/src/pkt_write_test.c new file mode 100644 index 0000000..dae92a5 --- /dev/null +++ b/ctdb/tests/src/pkt_write_test.c @@ -0,0 +1,359 @@ +/* + packet write tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "lib/util/blocking.h" + +#include "common/pkt_read.c" +#include "common/pkt_write.c" + +struct writer_state { + struct tevent_context *ev; + int fd; + uint8_t *buf; + size_t buflen; + int count; + struct tevent_req *subreq; +}; + +static void writer_next(struct tevent_req *subreq); + +static struct tevent_req *writer_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, uint8_t *buf, size_t buflen) +{ + struct tevent_req *req, *subreq; + struct writer_state *state; + + req = tevent_req_create(mem_ctx, &state, struct writer_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->buf = buf; + state->buflen = buflen; + state->count = 0; + + subreq = pkt_write_send(state, state->ev, state->fd, + state->buf, state->buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, writer_next, req); + return req; +} + +static void writer_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct writer_state *state = tevent_req_data( + req, struct writer_state); + ssize_t nwritten; + int err = 0; + + nwritten = pkt_write_recv(subreq, &err); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (nwritten == -1) { + tevent_req_error(req, err); + return; + } + + if ((size_t)nwritten != state->buflen) { + tevent_req_error(req, EIO); + return; + } + + state->count++; + if (state->count >= 1000) { + tevent_req_done(req); + return; + } + + subreq = pkt_write_send(state, state->ev, state->fd, + state->buf, state->buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, writer_next, req); +} + +static void writer_recv(struct tevent_req *req, int *perr) +{ + struct writer_state *state = tevent_req_data( + req, struct writer_state); + int err = 0; + + if (state->subreq != NULL) { + *perr = -1; + return; + } + + if (tevent_req_is_unix_error(req, &err)) { + *perr = err; + return; + } + + *perr = 0; +} + +static void writer_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct writer_state *state = tevent_req_data( + req, struct writer_state); + + assert(state->subreq != NULL); + pkt_write_handler(ev, fde, flags, state->subreq); +} + +static void writer(int fd) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_fd *fde; + struct tevent_req *req; + uint8_t buf[1024*1024]; + size_t buflen; + size_t pkt_size[4] = { 100, 500, 1024, 1024*1024 }; + int i, err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + for (i=0; i<1024*1024; i++) { + buf[i] = i%256; + } + + for (i=0; i<4; i++) { + buflen = pkt_size[i]; + memcpy(buf, &buflen, sizeof(buflen)); + + req = writer_send(mem_ctx, ev, fd, buf, buflen); + assert(req != NULL); + + fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_WRITE, + writer_handler, req); + assert(fde != NULL); + + tevent_req_poll(req, ev); + + writer_recv(req, &err); + assert(err == 0); + + talloc_free(fde); + talloc_free(req); + } + + close(fd); + + talloc_free(mem_ctx); +} + +struct reader_state { + struct tevent_context *ev; + int fd; + uint8_t buf[1024]; + struct tevent_req *subreq; +}; + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data); +static void reader_done(struct tevent_req *subreq); + +static struct tevent_req *reader_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd) +{ + struct tevent_req *req, *subreq; + struct reader_state *state; + + req = tevent_req_create(mem_ctx, &state, struct reader_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, 1024, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + tevent_req_post(req, ev); + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); + return req; +} + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data) +{ + uint32_t pkt_len; + + if (buflen < sizeof(pkt_len)) { + return sizeof(pkt_len) - buflen; + } + + pkt_len = *(uint32_t *)buf; + return pkt_len - buflen; +} + +static void reader_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + ssize_t nread; + uint8_t *buf; + bool free_buf; + int err = 0; + + nread = pkt_read_recv(subreq, state, &buf, &free_buf, &err); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (nread == -1) { + if (err == EPIPE) { + tevent_req_done(req); + } else { + tevent_req_error(req, err); + } + return; + } + + if (free_buf) { + talloc_free(buf); + } + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, 1024, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + return; + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); +} + +static void reader_recv(struct tevent_req *req, int *perr) +{ + struct reader_state *state = tevent_req_data( + req, struct reader_state); + int err = 0; + + if (state->subreq != NULL) { + *perr = -1; + } + + if (tevent_req_is_unix_error(req, &err)) { + *perr = err; + return; + } + + *perr = 0; +} + +static void reader_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + + assert(state->subreq != NULL); + pkt_read_handler(ev, fde, flags, state->subreq); +} + +static void reader(int fd) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_fd *fde; + struct tevent_req *req; + int err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + req = reader_send(mem_ctx, ev, fd); + assert(req != NULL); + + fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_READ, + reader_handler, req); + assert(fde != NULL); + + tevent_req_poll(req, ev); + + reader_recv(req, &err); + assert(err == 0); + + close(fd); + + talloc_free(mem_ctx); +} + +int main(void) +{ + int fd[2]; + int ret; + pid_t pid; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Child process */ + close(fd[0]); + writer(fd[1]); + exit(0); + } + + close(fd[1]); + ret = set_blocking(fd[0], false); + if (ret == -1) { + exit(1); + } + + reader(fd[0]); + + return 0; +} diff --git a/ctdb/tests/src/porting_tests.c b/ctdb/tests/src/porting_tests.c new file mode 100644 index 0000000..00618d2 --- /dev/null +++ b/ctdb/tests/src/porting_tests.c @@ -0,0 +1,262 @@ +/* + Test porting lib (common/system_*.c) + + Copyright (C) Mathieu Parent 2013 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" + +#include <popt.h> +#include <talloc.h> +#include <tevent.h> +#include <tdb.h> +#include <assert.h> + +#include "lib/util/debug.h" +#include "lib/util/blocking.h" + +#include "protocol/protocol.h" +#include "common/system.h" +#include "common/logging.h" + + +static struct { + const char *socketname; + const char *debuglevel; + pid_t helper_pid; + int socket; +} globals = { + .socketname = "/tmp/test.sock" +}; + + + +/* + Socket functions +*/ +/* + create a unix domain socket and bind it + return a file descriptor open on the socket +*/ +static int socket_server_create(void) +{ + struct sockaddr_un addr; + int ret; + + globals.socket = socket(AF_UNIX, SOCK_STREAM, 0); + assert(globals.socket != -1); + + set_close_on_exec(globals.socket); + //set_blocking(globals.socket, false); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, globals.socketname, sizeof(addr.sun_path)-1); + + ret = bind(globals.socket, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret == 0); + + ret = chown(globals.socketname, geteuid(), getegid()); + assert(ret == 0); + + ret = chmod(globals.socketname, 0700); + assert(ret == 0); + + ret = listen(globals.socket, 100); + assert(ret == 0); + + return 0; +} + +static int socket_server_wait_peer(void) +{ + struct sockaddr_un addr; + socklen_t len; + int fd; + + memset(&addr, 0, sizeof(addr)); + len = sizeof(addr); + fd = accept(globals.socket, (struct sockaddr *)&addr, &len); + assert(fd != -1); + + //set_blocking(fd, false); + set_close_on_exec(fd); + return fd; +} + +static int socket_server_close(void) +{ + int ret; + + ret = close(globals.socket); + assert(ret == 0); + + ret = unlink(globals.socketname); + assert(ret == 0); + + return 0; +} + +static int socket_client_connect(void) +{ + struct sockaddr_un addr; + int client = 0; + int ret; + + client = socket(AF_UNIX, SOCK_STREAM, 0); + assert(client != -1); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, globals.socketname, sizeof(addr.sun_path)-1); + + ret = connect(client, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret == 0); + + return client; +} + +static int socket_client_close(int client) +{ + int ret; + + ret = close(client); + assert(ret == 0); + + return 0; +} + +/* + forked program +*/ +static int fork_helper(void) +{ + pid_t pid; + int client; + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { // Child + pid = getppid(); + client = socket_client_connect(); + while (kill(pid, 0) == 0) { + sleep(1); + } + socket_client_close(client); + exit(0); + } else { + globals.helper_pid = pid; + } + return 0; +} + +/* + tests +*/ +static int test_ctdb_sys_check_iface_exists(void) +{ + bool test1, test2; + + test1 = ctdb_sys_check_iface_exists("unlikely123xyz"); + assert(!test1); + + /* Linux and others */ + test1 = ctdb_sys_check_iface_exists("lo"); + /* FreeBSD */ + test2 = ctdb_sys_check_iface_exists("lo0"); + assert(test1 || test2); + + return 0; +} + +static int test_ctdb_get_peer_pid(void) +{ + int ret; + int fd; + pid_t peer_pid = 0; + + fd = socket_server_wait_peer(); + + ret = ctdb_get_peer_pid(fd, &peer_pid); + assert(ret == 0 || ret == ENOSYS); + + if (ret == 0) { + assert(peer_pid == globals.helper_pid); + + kill(peer_pid, SIGTERM); + } else { + kill(globals.helper_pid, SIGTERM); + } + + close(fd); + return 0; +} + +/* + main program +*/ +int main(int argc, const char *argv[]) +{ + struct poptOption popt_options[] = { + POPT_AUTOHELP + { "socket", 0, POPT_ARG_STRING, &globals.socketname, 0, "local socket name", "filename" }, + POPT_TABLEEND + }; + int opt, ret; + const char **extra_argv; + int extra_argc = 0; + poptContext pc; + + pc = poptGetContext(argv[0], argc, argv, popt_options, POPT_CONTEXT_KEEP_FIRST); + + while ((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + default: + fprintf(stderr, "Invalid option %s: %s\n", + poptBadOption(pc, 0), poptStrerror(opt)); + exit(1); + } + } + + /* setup the remaining options for the main program to use */ + extra_argv = poptGetArgs(pc); + if (extra_argv) { + extra_argv++; + while (extra_argv[extra_argc]) extra_argc++; + } + + assert(globals.socketname != NULL); + + ret = socket_server_create(); + assert(ret == 0); + + /* FIXME: Test tcp_checksum6, tcp_checksum */ + /* FIXME: Test ctdb_sys_send_arp, ctdb_sys_send_tcp */ + /* FIXME: Test ctdb_sys_{open,close}_capture_socket, ctdb_sys_read_tcp_packet */ + test_ctdb_sys_check_iface_exists(); + + ret = fork_helper(); + assert(ret == 0); + test_ctdb_get_peer_pid(); + + ret = socket_server_close(); + assert(ret == 0); + + return 0; +} diff --git a/ctdb/tests/src/protocol_basic_test.c b/ctdb/tests/src/protocol_basic_test.c new file mode 100644 index 0000000..7046718 --- /dev/null +++ b/ctdb/tests/src/protocol_basic_test.c @@ -0,0 +1,106 @@ +/* + protocol types tests + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> + +#include "protocol/protocol_basic.c" + +#include "tests/src/protocol_common_basic.h" + +PROTOCOL_TYPE1_TEST(uint8_t, ctdb_uint8); +PROTOCOL_TYPE1_TEST(uint16_t, ctdb_uint16); +PROTOCOL_TYPE1_TEST(int32_t, ctdb_int32); +PROTOCOL_TYPE1_TEST(uint32_t, ctdb_uint32); +PROTOCOL_TYPE1_TEST(uint64_t, ctdb_uint64); +PROTOCOL_TYPE1_TEST(double, ctdb_double); +PROTOCOL_TYPE1_TEST(bool, ctdb_bool); + +static void test_ctdb_chararray(void) +{ + size_t len = rand_int(1000) + 1; + char p1[len], p2[len]; + size_t buflen, np = 0; + size_t i; + int ret; + + for (i=0; i<len-1; i++) { + p1[i] = 'A' + rand_int(26); + } + p1[len-1] = '\0'; + buflen = ctdb_chararray_len(p1, len); + assert(buflen < sizeof(BUFFER)); + ctdb_chararray_push(p1, len, BUFFER, &np); + assert(np == buflen); + np = 0; + ret = ctdb_chararray_pull(BUFFER, buflen, p2, len, &np); + assert(ret == 0); + assert(np == buflen); + assert(strncmp(p1, p2, len) == 0); +} + +PROTOCOL_TYPE2_TEST(const char *, ctdb_string); +PROTOCOL_TYPE2_TEST(const char *, ctdb_stringn); + +PROTOCOL_TYPE1_TEST(pid_t, ctdb_pid); +PROTOCOL_TYPE1_TEST(struct timeval, ctdb_timeval); + +static void test_ctdb_padding(void) +{ + int padding; + size_t buflen, np = 0; + int ret; + + padding = rand_int(8); + + buflen = ctdb_padding_len(padding); + assert(buflen < sizeof(BUFFER)); + ctdb_padding_push(padding, BUFFER, &np); + assert(np == buflen); + np = 0; + ret = ctdb_padding_pull(BUFFER, buflen, padding, &np); + assert(ret == 0); + assert(np == buflen); +} + +static void protocol_basic_test(void) +{ + TEST_FUNC(ctdb_uint8)(); + TEST_FUNC(ctdb_uint16)(); + TEST_FUNC(ctdb_int32)(); + TEST_FUNC(ctdb_uint32)(); + TEST_FUNC(ctdb_uint64)(); + TEST_FUNC(ctdb_double)(); + TEST_FUNC(ctdb_bool)(); + + test_ctdb_chararray(); + + TEST_FUNC(ctdb_string)(); + TEST_FUNC(ctdb_stringn)(); + + TEST_FUNC(ctdb_pid)(); + TEST_FUNC(ctdb_timeval)(); + + test_ctdb_padding(); +} + +int main(int argc, const char *argv[]) +{ + protocol_test_iterate(argc, argv, protocol_basic_test); + return 0; +} diff --git a/ctdb/tests/src/protocol_common.c b/ctdb/tests/src/protocol_common.c new file mode 100644 index 0000000..212c23c --- /dev/null +++ b/ctdb/tests/src/protocol_common.c @@ -0,0 +1,1260 @@ +/* + protocol tests - common functions + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <assert.h> + +#include "protocol/protocol_api.h" + +#include "tests/src/protocol_common_basic.h" +#include "tests/src/protocol_common.h" + +void fill_tdb_data_nonnull(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + p->dsize = rand_int(1024) + 1; + p->dptr = talloc_array(mem_ctx, uint8_t, p->dsize); + assert(p->dptr != NULL); + fill_buffer(p->dptr, p->dsize); +} + +void fill_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + if (rand_int(5) == 0) { + p->dsize = 0; + p->dptr = NULL; + } else { + fill_tdb_data_nonnull(mem_ctx, p); + } +} + +void verify_tdb_data(TDB_DATA *p1, TDB_DATA *p2) +{ + assert(p1->dsize == p2->dsize); + verify_buffer(p1->dptr, p2->dptr, p1->dsize); +} + +void fill_ctdb_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + fill_tdb_data(mem_ctx, p); +} + +void verify_ctdb_tdb_data(TDB_DATA *p1, TDB_DATA *p2) +{ + verify_tdb_data(p1, p2); +} + +void fill_ctdb_tdb_datan(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + fill_tdb_data(mem_ctx, p); +} + +void verify_ctdb_tdb_datan(TDB_DATA *p1, TDB_DATA *p2) +{ + verify_tdb_data(p1, p2); +} + +void fill_ctdb_latency_counter(struct ctdb_latency_counter *p) +{ + p->num = rand32i(); + p->min = rand_double(); + p->max = rand_double(); + p->total = rand_double(); +} + +void verify_ctdb_latency_counter(struct ctdb_latency_counter *p1, + struct ctdb_latency_counter *p2) +{ + assert(p1->num == p2->num); + assert(p1->min == p2->min); + assert(p1->max == p2->max); + assert(p1->total == p2->total); +} + +void fill_ctdb_statistics(TALLOC_CTX *mem_ctx, struct ctdb_statistics *p) +{ + int i; + + p->num_clients = rand32(); + p->frozen = rand32(); + p->recovering = rand32(); + p->client_packets_sent = rand32(); + p->client_packets_recv = rand32(); + p->node_packets_sent = rand32(); + p->node_packets_recv = rand32(); + p->keepalive_packets_sent = rand32(); + p->keepalive_packets_recv = rand32(); + + p->node.req_call = rand32(); + p->node.reply_call = rand32(); + p->node.req_dmaster = rand32(); + p->node.reply_dmaster = rand32(); + p->node.reply_error = rand32(); + p->node.req_message = rand32(); + p->node.req_control = rand32(); + p->node.reply_control = rand32(); + + p->client.req_call = rand32(); + p->client.req_message = rand32(); + p->client.req_control = rand32(); + + p->timeouts.call = rand32(); + p->timeouts.control = rand32(); + p->timeouts.traverse = rand32(); + + fill_ctdb_latency_counter(&p->reclock.ctdbd); + fill_ctdb_latency_counter(&p->reclock.recd); + + p->locks.num_calls = rand32(); + p->locks.num_current = rand32(); + p->locks.num_pending = rand32(); + p->locks.num_failed = rand32(); + fill_ctdb_latency_counter(&p->locks.latency); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + p->locks.buckets[i] = rand32(); + } + + p->total_calls = rand32(); + p->pending_calls = rand32(); + p->childwrite_calls = rand32(); + p->pending_childwrite_calls = rand32(); + p->memory_used = rand32(); + p->__last_counter = rand32(); + p->max_hop_count = rand32(); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + p->hop_count_bucket[i] = rand32(); + } + fill_ctdb_latency_counter(&p->call_latency); + fill_ctdb_latency_counter(&p->childwrite_latency); + p->num_recoveries = rand32(); + fill_ctdb_timeval(&p->statistics_start_time); + fill_ctdb_timeval(&p->statistics_current_time); + p->total_ro_delegations = rand32(); + p->total_ro_revokes = rand32(); +} + +void verify_ctdb_statistics(struct ctdb_statistics *p1, + struct ctdb_statistics *p2) +{ + int i; + + assert(p1->num_clients == p2->num_clients); + assert(p1->frozen == p2->frozen); + assert(p1->recovering == p2->recovering); + assert(p1->client_packets_sent == p2->client_packets_sent); + assert(p1->client_packets_recv == p2->client_packets_recv); + assert(p1->node_packets_sent == p2->node_packets_sent); + assert(p1->node_packets_recv == p2->node_packets_recv); + assert(p1->keepalive_packets_sent == p2->keepalive_packets_sent); + assert(p1->keepalive_packets_recv == p2->keepalive_packets_recv); + + assert(p1->node.req_call == p2->node.req_call); + assert(p1->node.reply_call == p2->node.reply_call); + assert(p1->node.req_dmaster == p2->node.req_dmaster); + assert(p1->node.reply_dmaster == p2->node.reply_dmaster); + assert(p1->node.reply_error == p2->node.reply_error); + assert(p1->node.req_message == p2->node.req_message); + assert(p1->node.req_control == p2->node.req_control); + assert(p1->node.reply_control == p2->node.reply_control); + + assert(p1->client.req_call == p2->client.req_call); + assert(p1->client.req_message == p2->client.req_message); + assert(p1->client.req_control == p2->client.req_control); + + assert(p1->timeouts.call == p2->timeouts.call); + assert(p1->timeouts.control == p2->timeouts.control); + assert(p1->timeouts.traverse == p2->timeouts.traverse); + + verify_ctdb_latency_counter(&p1->reclock.ctdbd, &p2->reclock.ctdbd); + verify_ctdb_latency_counter(&p1->reclock.recd, &p2->reclock.recd); + + assert(p1->locks.num_calls == p2->locks.num_calls); + assert(p1->locks.num_current == p2->locks.num_current); + assert(p1->locks.num_pending == p2->locks.num_pending); + assert(p1->locks.num_failed == p2->locks.num_failed); + verify_ctdb_latency_counter(&p1->locks.latency, &p2->locks.latency); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + assert(p1->locks.buckets[i] == p2->locks.buckets[i]); + } + + assert(p1->total_calls == p2->total_calls); + assert(p1->pending_calls == p2->pending_calls); + assert(p1->childwrite_calls == p2->childwrite_calls); + assert(p1->pending_childwrite_calls == p2->pending_childwrite_calls); + assert(p1->memory_used == p2->memory_used); + assert(p1->__last_counter == p2->__last_counter); + assert(p1->max_hop_count == p2->max_hop_count); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + assert(p1->hop_count_bucket[i] == p2->hop_count_bucket[i]); + } + verify_ctdb_latency_counter(&p1->call_latency, &p2->call_latency); + verify_ctdb_latency_counter(&p1->childwrite_latency, + &p2->childwrite_latency); + assert(p1->num_recoveries == p2->num_recoveries); + verify_ctdb_timeval(&p1->statistics_start_time, + &p2->statistics_start_time); + verify_ctdb_timeval(&p1->statistics_current_time, + &p2->statistics_current_time); + assert(p1->total_ro_delegations == p2->total_ro_delegations); + assert(p1->total_ro_revokes == p2->total_ro_revokes); +} + +void fill_ctdb_vnn_map(TALLOC_CTX *mem_ctx, struct ctdb_vnn_map *p) +{ + unsigned int i; + + p->generation = rand32(); + p->size = rand_int(20); + if (p->size > 0) { + p->map = talloc_array(mem_ctx, uint32_t, p->size); + assert(p->map != NULL); + + for (i=0; i<p->size; i++) { + p->map[i] = rand32(); + } + } else { + p->map = NULL; + } +} + +void verify_ctdb_vnn_map(struct ctdb_vnn_map *p1, struct ctdb_vnn_map *p2) +{ + unsigned int i; + + assert(p1->generation == p2->generation); + assert(p1->size == p2->size); + for (i=0; i<p1->size; i++) { + assert(p1->map[i] == p2->map[i]); + } +} + +void fill_ctdb_dbid(TALLOC_CTX *mem_ctx, struct ctdb_dbid *p) +{ + p->db_id = rand32(); + p->flags = rand8(); +} + +void verify_ctdb_dbid(struct ctdb_dbid *p1, struct ctdb_dbid *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->flags == p2->flags); +} + +void fill_ctdb_dbid_map(TALLOC_CTX *mem_ctx, struct ctdb_dbid_map *p) +{ + unsigned int i; + + p->num = rand_int(40); + if (p->num > 0) { + p->dbs = talloc_zero_array(mem_ctx, struct ctdb_dbid, p->num); + assert(p->dbs != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_dbid(mem_ctx, &p->dbs[i]); + } + } else { + p->dbs = NULL; + } +} + +void verify_ctdb_dbid_map(struct ctdb_dbid_map *p1, struct ctdb_dbid_map *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_dbid(&p1->dbs[i], &p2->dbs[i]); + } +} + +void fill_ctdb_pulldb(TALLOC_CTX *mem_ctx, struct ctdb_pulldb *p) +{ + p->db_id = rand32(); + p->lmaster = rand32(); +} + +void verify_ctdb_pulldb(struct ctdb_pulldb *p1, struct ctdb_pulldb *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->lmaster == p2->lmaster); +} + +void fill_ctdb_pulldb_ext(TALLOC_CTX *mem_ctx, struct ctdb_pulldb_ext *p) +{ + p->db_id = rand32(); + p->lmaster = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_pulldb_ext(struct ctdb_pulldb_ext *p1, + struct ctdb_pulldb_ext *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->lmaster == p2->lmaster); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_db_vacuum(TALLOC_CTX *mem_ctx, struct ctdb_db_vacuum *p) +{ + fill_ctdb_uint32(&p->db_id); + fill_ctdb_bool(&p->full_vacuum_run); +} + +void verify_ctdb_db_vacuum(struct ctdb_db_vacuum *p1, + struct ctdb_db_vacuum *p2) +{ + verify_ctdb_uint32(&p1->db_id, &p2->db_id); + verify_ctdb_bool(&p1->full_vacuum_run, &p2->full_vacuum_run); +} + +void fill_ctdb_echo_data(TALLOC_CTX *mem_ctx, struct ctdb_echo_data *p) +{ + fill_ctdb_uint32(&p->timeout); + fill_tdb_data(mem_ctx, &p->buf); +} + +void verify_ctdb_echo_data(struct ctdb_echo_data *p1, + struct ctdb_echo_data *p2) +{ + verify_ctdb_uint32(&p1->timeout, &p2->timeout); + verify_tdb_data(&p1->buf, &p2->buf); +} + +void fill_ctdb_ltdb_header(struct ctdb_ltdb_header *p) +{ + p->rsn = rand64(); + p->dmaster = rand32(); + p->reserved1 = rand32(); + p->flags = rand32(); +} + +void verify_ctdb_ltdb_header(struct ctdb_ltdb_header *p1, + struct ctdb_ltdb_header *p2) +{ + assert(p1->rsn == p2->rsn); + assert(p1->dmaster == p2->dmaster); + assert(p1->reserved1 == p2->reserved1); + assert(p1->flags == p2->flags); +} + +void fill_ctdb_rec_data(TALLOC_CTX *mem_ctx, struct ctdb_rec_data *p) +{ + p->reqid = rand32(); + if (p->reqid % 5 == 0) { + p->header = talloc(mem_ctx, struct ctdb_ltdb_header); + assert(p->header != NULL); + fill_ctdb_ltdb_header(p->header); + } else { + p->header = NULL; + } + fill_tdb_data_nonnull(mem_ctx, &p->key); + fill_tdb_data(mem_ctx, &p->data); +} + +void verify_ctdb_rec_data(struct ctdb_rec_data *p1, struct ctdb_rec_data *p2) +{ + struct ctdb_ltdb_header header; + + assert(p1->reqid == p2->reqid); + if (p1->header != NULL) { + assert(ctdb_ltdb_header_extract(&p2->data, &header) == 0); + verify_ctdb_ltdb_header(p1->header, &header); + } + verify_tdb_data(&p1->key, &p2->key); + verify_tdb_data(&p1->data, &p2->data); +} + +void fill_ctdb_rec_buffer(TALLOC_CTX *mem_ctx, struct ctdb_rec_buffer *p) +{ + struct ctdb_rec_data rec; + int ret, i; + int count; + + p->db_id = rand32(); + p->count = 0; + p->buf = NULL; + p->buflen = 0; + + count = rand_int(100); + if (count > 0) { + for (i=0; i<count; i++) { + fill_ctdb_rec_data(mem_ctx, &rec); + ret = ctdb_rec_buffer_add(mem_ctx, p, rec.reqid, + rec.header, + rec.key, rec.data); + assert(ret == 0); + } + } +} + +void verify_ctdb_rec_buffer(struct ctdb_rec_buffer *p1, + struct ctdb_rec_buffer *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->count == p2->count); + assert(p1->buflen == p2->buflen); + verify_buffer(p1->buf, p2->buf, p1->buflen); +} + +void fill_ctdb_traverse_start(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_traverse_start(struct ctdb_traverse_start *p1, + struct ctdb_traverse_start *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_traverse_all(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->pnn = rand32(); + p->client_reqid = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_traverse_all(struct ctdb_traverse_all *p1, + struct ctdb_traverse_all *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->pnn == p2->pnn); + assert(p1->client_reqid == p2->client_reqid); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_traverse_start_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start_ext *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->srvid = rand64(); + p->withemptyrecords = rand_int(2); +} + +void verify_ctdb_traverse_start_ext(struct ctdb_traverse_start_ext *p1, + struct ctdb_traverse_start_ext *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->srvid == p2->srvid); + assert(p1->withemptyrecords == p2->withemptyrecords); +} + +void fill_ctdb_traverse_all_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all_ext *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->pnn = rand32(); + p->client_reqid = rand32(); + p->srvid = rand64(); + p->withemptyrecords = rand_int(2); +} + +void verify_ctdb_traverse_all_ext(struct ctdb_traverse_all_ext *p1, + struct ctdb_traverse_all_ext *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->pnn == p2->pnn); + assert(p1->client_reqid == p2->client_reqid); + assert(p1->srvid == p2->srvid); + assert(p1->withemptyrecords == p2->withemptyrecords); +} + +void fill_ctdb_sock_addr(TALLOC_CTX *mem_ctx, ctdb_sock_addr *p) +{ + if (rand_int(2) == 0) { + p->ip.sin_family = AF_INET; + p->ip.sin_port = rand_int(65535); + fill_buffer(&p->ip.sin_addr, sizeof(struct in_addr)); + } else { + p->ip6.sin6_family = AF_INET6; + p->ip6.sin6_port = rand_int(65535); + fill_buffer(&p->ip6.sin6_addr, sizeof(struct in6_addr)); + } +} + +void verify_ctdb_sock_addr(ctdb_sock_addr *p1, ctdb_sock_addr *p2) +{ + assert(p1->sa.sa_family == p2->sa.sa_family); + if (p1->sa.sa_family == AF_INET) { + assert(p1->ip.sin_port == p2->ip.sin_port); + verify_buffer(&p1->ip.sin_addr, &p2->ip.sin_addr, + sizeof(struct in_addr)); + } else { + assert(p1->ip6.sin6_port == p2->ip6.sin6_port); + verify_buffer(&p1->ip6.sin6_addr, &p2->ip6.sin6_addr, + sizeof(struct in6_addr)); + } +} + +void fill_ctdb_connection(TALLOC_CTX *mem_ctx, struct ctdb_connection *p) +{ + fill_ctdb_sock_addr(mem_ctx, &p->src); + fill_ctdb_sock_addr(mem_ctx, &p->dst); +} + +void verify_ctdb_connection(struct ctdb_connection *p1, + struct ctdb_connection *p2) +{ + verify_ctdb_sock_addr(&p1->src, &p2->src); + verify_ctdb_sock_addr(&p1->dst, &p2->dst); +} + +void fill_ctdb_connection_list(TALLOC_CTX *mem_ctx, + struct ctdb_connection_list *p) +{ + uint32_t i; + + p->num = rand_int(1000); + if (p->num > 0) { + p->conn = talloc_array(mem_ctx, struct ctdb_connection, p->num); + assert(p->conn != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_connection(mem_ctx, &p->conn[i]); + } + } else { + p->conn = NULL; + } +} + +void verify_ctdb_connection_list(struct ctdb_connection_list *p1, + struct ctdb_connection_list *p2) +{ + uint32_t i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_connection(&p1->conn[i], &p2->conn[i]); + } +} + +void fill_ctdb_tunable(TALLOC_CTX *mem_ctx, struct ctdb_tunable *p) +{ + fill_ctdb_string(mem_ctx, &p->name); + p->value = rand32(); +} + +void verify_ctdb_tunable(struct ctdb_tunable *p1, struct ctdb_tunable *p2) +{ + verify_ctdb_string(&p1->name, &p2->name); + assert(p1->value == p2->value); +} + +void fill_ctdb_node_flag_change(TALLOC_CTX *mem_ctx, + struct ctdb_node_flag_change *p) +{ + p->pnn = rand32(); + p->new_flags = rand32(); + p->old_flags = rand32(); +} + +void verify_ctdb_node_flag_change(struct ctdb_node_flag_change *p1, + struct ctdb_node_flag_change *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->new_flags == p2->new_flags); + assert(p1->old_flags == p2->old_flags); +} + +void fill_ctdb_var_list(TALLOC_CTX *mem_ctx, struct ctdb_var_list *p) +{ + int i; + + p->count = rand_int(100) + 1; + p->var = talloc_array(mem_ctx, const char *, p->count); + for (i=0; i<p->count; i++) { + fill_ctdb_string(p->var, &p->var[i]); + } +} + +void verify_ctdb_var_list(struct ctdb_var_list *p1, struct ctdb_var_list *p2) +{ + int i; + + assert(p1->count == p2->count); + for (i=0; i<p1->count; i++) { + verify_ctdb_string(&p1->var[i], &p2->var[i]); + } +} + +void fill_ctdb_tunable_list(TALLOC_CTX *mem_ctx, struct ctdb_tunable_list *p) +{ + p->max_redirect_count = rand32(); + p->seqnum_interval = rand32(); + p->control_timeout = rand32(); + p->traverse_timeout = rand32(); + p->keepalive_interval = rand32(); + p->keepalive_limit = rand32(); + p->recover_timeout = rand32(); + p->recover_interval = rand32(); + p->election_timeout = rand32(); + p->takeover_timeout = rand32(); + p->monitor_interval = rand32(); + p->tickle_update_interval = rand32(); + p->script_timeout = rand32(); + p->monitor_timeout_count = rand32(); + p->script_unhealthy_on_timeout = rand32(); + p->recovery_grace_period = rand32(); + p->recovery_ban_period = rand32(); + p->database_hash_size = rand32(); + p->database_max_dead = rand32(); + p->rerecovery_timeout = rand32(); + p->enable_bans = rand32(); + p->deterministic_public_ips = rand32(); + p->reclock_ping_period = rand32(); + p->no_ip_failback = rand32(); + p->disable_ip_failover = rand32(); + p->verbose_memory_names = rand32(); + p->recd_ping_timeout = rand32(); + p->recd_ping_failcount = rand32(); + p->log_latency_ms = rand32(); + p->reclock_latency_ms = rand32(); + p->recovery_drop_all_ips = rand32(); + p->verify_recovery_lock = rand32(); + p->vacuum_interval = rand32(); + p->vacuum_max_run_time = rand32(); + p->repack_limit = rand32(); + p->vacuum_limit = rand32(); + p->max_queue_depth_drop_msg = rand32(); + p->allow_unhealthy_db_read = rand32(); + p->stat_history_interval = rand32(); + p->deferred_attach_timeout = rand32(); + p->vacuum_fast_path_count = rand32(); + p->lcp2_public_ip_assignment = rand32(); + p->allow_client_db_attach = rand32(); + p->recover_pdb_by_seqnum = rand32(); + p->deferred_rebalance_on_node_add = rand32(); + p->fetch_collapse = rand32(); + p->hopcount_make_sticky = rand32(); + p->sticky_duration = rand32(); + p->sticky_pindown = rand32(); + p->no_ip_takeover = rand32(); + p->db_record_count_warn = rand32(); + p->db_record_size_warn = rand32(); + p->db_size_warn = rand32(); + p->pulldb_preallocation_size = rand32(); + p->no_ip_host_on_all_disabled = rand32(); + p->samba3_hack = rand32(); + p->mutex_enabled = rand32(); + p->lock_processes_per_db = rand32(); + p->rec_buffer_size_limit = rand32(); + p->queue_buffer_size = rand32(); + p->ip_alloc_algorithm = rand32(); + p->allow_mixed_versions = rand32(); +} + +void verify_ctdb_tunable_list(struct ctdb_tunable_list *p1, + struct ctdb_tunable_list *p2) +{ + assert(p1->max_redirect_count == p2->max_redirect_count); + assert(p1->seqnum_interval == p2->seqnum_interval); + assert(p1->control_timeout == p2->control_timeout); + assert(p1->traverse_timeout == p2->traverse_timeout); + assert(p1->keepalive_interval == p2->keepalive_interval); + assert(p1->keepalive_limit == p2->keepalive_limit); + assert(p1->recover_timeout == p2->recover_timeout); + assert(p1->recover_interval == p2->recover_interval); + assert(p1->election_timeout == p2->election_timeout); + assert(p1->takeover_timeout == p2->takeover_timeout); + assert(p1->monitor_interval == p2->monitor_interval); + assert(p1->tickle_update_interval == p2->tickle_update_interval); + assert(p1->script_timeout == p2->script_timeout); + assert(p1->monitor_timeout_count == p2->monitor_timeout_count); + assert(p1->script_unhealthy_on_timeout == p2->script_unhealthy_on_timeout); + assert(p1->recovery_grace_period == p2->recovery_grace_period); + assert(p1->recovery_ban_period == p2->recovery_ban_period); + assert(p1->database_hash_size == p2->database_hash_size); + assert(p1->database_max_dead == p2->database_max_dead); + assert(p1->rerecovery_timeout == p2->rerecovery_timeout); + assert(p1->enable_bans == p2->enable_bans); + assert(p1->deterministic_public_ips == p2->deterministic_public_ips); + assert(p1->reclock_ping_period == p2->reclock_ping_period); + assert(p1->no_ip_failback == p2->no_ip_failback); + assert(p1->disable_ip_failover == p2->disable_ip_failover); + assert(p1->verbose_memory_names == p2->verbose_memory_names); + assert(p1->recd_ping_timeout == p2->recd_ping_timeout); + assert(p1->recd_ping_failcount == p2->recd_ping_failcount); + assert(p1->log_latency_ms == p2->log_latency_ms); + assert(p1->reclock_latency_ms == p2->reclock_latency_ms); + assert(p1->recovery_drop_all_ips == p2->recovery_drop_all_ips); + assert(p1->verify_recovery_lock == p2->verify_recovery_lock); + assert(p1->vacuum_interval == p2->vacuum_interval); + assert(p1->vacuum_max_run_time == p2->vacuum_max_run_time); + assert(p1->repack_limit == p2->repack_limit); + assert(p1->vacuum_limit == p2->vacuum_limit); + assert(p1->max_queue_depth_drop_msg == p2->max_queue_depth_drop_msg); + assert(p1->allow_unhealthy_db_read == p2->allow_unhealthy_db_read); + assert(p1->stat_history_interval == p2->stat_history_interval); + assert(p1->deferred_attach_timeout == p2->deferred_attach_timeout); + assert(p1->vacuum_fast_path_count == p2->vacuum_fast_path_count); + assert(p1->lcp2_public_ip_assignment == p2->lcp2_public_ip_assignment); + assert(p1->allow_client_db_attach == p2->allow_client_db_attach); + assert(p1->recover_pdb_by_seqnum == p2->recover_pdb_by_seqnum); + assert(p1->deferred_rebalance_on_node_add == p2->deferred_rebalance_on_node_add); + assert(p1->fetch_collapse == p2->fetch_collapse); + assert(p1->hopcount_make_sticky == p2->hopcount_make_sticky); + assert(p1->sticky_duration == p2->sticky_duration); + assert(p1->sticky_pindown == p2->sticky_pindown); + assert(p1->no_ip_takeover == p2->no_ip_takeover); + assert(p1->db_record_count_warn == p2->db_record_count_warn); + assert(p1->db_record_size_warn == p2->db_record_size_warn); + assert(p1->db_size_warn == p2->db_size_warn); + assert(p1->pulldb_preallocation_size == p2->pulldb_preallocation_size); + assert(p1->no_ip_host_on_all_disabled == p2->no_ip_host_on_all_disabled); + assert(p1->samba3_hack == p2->samba3_hack); + assert(p1->mutex_enabled == p2->mutex_enabled); + assert(p1->lock_processes_per_db == p2->lock_processes_per_db); + assert(p1->rec_buffer_size_limit == p2->rec_buffer_size_limit); + assert(p1->queue_buffer_size == p2->queue_buffer_size); + assert(p1->ip_alloc_algorithm == p2->ip_alloc_algorithm); + assert(p1->allow_mixed_versions == p2->allow_mixed_versions); +} + +void fill_ctdb_tickle_list(TALLOC_CTX *mem_ctx, struct ctdb_tickle_list *p) +{ + unsigned int i; + + fill_ctdb_sock_addr(mem_ctx, &p->addr); + p->num = rand_int(1000); + if (p->num > 0) { + p->conn = talloc_array(mem_ctx, struct ctdb_connection, p->num); + assert(p->conn != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_connection(mem_ctx, &p->conn[i]); + } + } else { + p->conn = NULL; + } +} + +void verify_ctdb_tickle_list(struct ctdb_tickle_list *p1, + struct ctdb_tickle_list *p2) +{ + unsigned int i; + + verify_ctdb_sock_addr(&p1->addr, &p2->addr); + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_connection(&p1->conn[i], &p2->conn[i]); + } +} + +void fill_ctdb_addr_info(TALLOC_CTX *mem_ctx, struct ctdb_addr_info *p) +{ + fill_ctdb_sock_addr(mem_ctx, &p->addr); + p->mask = rand_int(33); + if (rand_int(2) == 0) { + p->iface = NULL; + } else { + fill_ctdb_string(mem_ctx, &p->iface); + } +} + +void verify_ctdb_addr_info(struct ctdb_addr_info *p1, + struct ctdb_addr_info *p2) +{ + verify_ctdb_sock_addr(&p1->addr, &p2->addr); + assert(p1->mask == p2->mask); + verify_ctdb_string(&p1->iface, &p2->iface); +} + +void fill_ctdb_transdb(TALLOC_CTX *mem_ctx, struct ctdb_transdb *p) +{ + p->db_id = rand32(); + p->tid = rand32(); +} + +void verify_ctdb_transdb(struct ctdb_transdb *p1, struct ctdb_transdb *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->tid == p2->tid); +} + +void fill_ctdb_uptime(TALLOC_CTX *mem_ctx, struct ctdb_uptime *p) +{ + fill_ctdb_timeval(&p->current_time); + fill_ctdb_timeval(&p->ctdbd_start_time); + fill_ctdb_timeval(&p->last_recovery_started); + fill_ctdb_timeval(&p->last_recovery_finished); +} + +void verify_ctdb_uptime(struct ctdb_uptime *p1, struct ctdb_uptime *p2) +{ + verify_ctdb_timeval(&p1->current_time, &p2->current_time); + verify_ctdb_timeval(&p1->ctdbd_start_time, &p2->ctdbd_start_time); + verify_ctdb_timeval(&p1->last_recovery_started, + &p2->last_recovery_started); + verify_ctdb_timeval(&p1->last_recovery_finished, + &p2->last_recovery_finished); +} + +void fill_ctdb_public_ip(TALLOC_CTX *mem_ctx, struct ctdb_public_ip *p) +{ + p->pnn = rand32(); + fill_ctdb_sock_addr(mem_ctx, &p->addr); +} + +void verify_ctdb_public_ip(struct ctdb_public_ip *p1, + struct ctdb_public_ip *p2) +{ + assert(p1->pnn == p2->pnn); + verify_ctdb_sock_addr(&p1->addr, &p2->addr); +} + +void fill_ctdb_public_ip_list(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list *p) +{ + unsigned int i; + + p->num = rand_int(32); + if (p->num > 0) { + p->ip = talloc_array(mem_ctx, struct ctdb_public_ip, p->num); + assert(p->ip != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_public_ip(mem_ctx, &p->ip[i]); + } + } else { + p->ip = NULL; + } +} + +void verify_ctdb_public_ip_list(struct ctdb_public_ip_list *p1, + struct ctdb_public_ip_list *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_public_ip(&p1->ip[i], &p2->ip[i]); + } +} + +void fill_ctdb_node_and_flags(TALLOC_CTX *mem_ctx, + struct ctdb_node_and_flags *p) +{ + p->pnn = rand32(); + p->flags = rand32(); + fill_ctdb_sock_addr(mem_ctx, &p->addr); +} + +void verify_ctdb_node_and_flags(struct ctdb_node_and_flags *p1, + struct ctdb_node_and_flags *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->flags == p2->flags); + verify_ctdb_sock_addr(&p1->addr, &p2->addr); +} + +void fill_ctdb_node_map(TALLOC_CTX *mem_ctx, struct ctdb_node_map *p) +{ + unsigned int i; + + p->num = rand_int(32); + if (p->num > 0) { + p->node = talloc_array(mem_ctx, struct ctdb_node_and_flags, + p->num); + assert(p->node != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_node_and_flags(mem_ctx, &p->node[i]); + } + } else { + p->node = NULL; + } +} + +void verify_ctdb_node_map(struct ctdb_node_map *p1, struct ctdb_node_map *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_node_and_flags(&p1->node[i], &p2->node[i]); + } +} + +void fill_ctdb_script(TALLOC_CTX *mem_ctx, struct ctdb_script *p) +{ + fill_string(p->name, MAX_SCRIPT_NAME+1); + fill_ctdb_timeval(&p->start); + fill_ctdb_timeval(&p->finished); + p->status = rand32i(); + fill_string(p->output, MAX_SCRIPT_OUTPUT+1); +} + +void verify_ctdb_script(struct ctdb_script *p1, struct ctdb_script *p2) +{ + verify_string(p1->name, p2->name); + verify_ctdb_timeval(&p1->start, &p2->start); + verify_ctdb_timeval(&p1->finished, &p2->finished); + assert(p1->status == p2->status); + verify_string(p1->output, p2->output); +} + +void fill_ctdb_script_list(TALLOC_CTX *mem_ctx, struct ctdb_script_list *p) +{ + unsigned int i; + + p->num_scripts = rand_int(32); + if (p->num_scripts > 0) { + p->script = talloc_zero_array(mem_ctx, struct ctdb_script, + p->num_scripts); + assert(p->script != NULL); + for (i=0; i<p->num_scripts; i++) { + fill_ctdb_script(mem_ctx, &p->script[i]); + } + } else { + p->script = NULL; + } +} + +void verify_ctdb_script_list(struct ctdb_script_list *p1, + struct ctdb_script_list *p2) +{ + unsigned int i; + + assert(p1->num_scripts == p2->num_scripts); + for (i=0; i<p1->num_scripts; i++) { + verify_ctdb_script(&p1->script[i], &p2->script[i]); + } +} + +void fill_ctdb_ban_state(TALLOC_CTX *mem_ctx, struct ctdb_ban_state *p) +{ + p->pnn = rand32(); + p->time = rand32(); +} + +void verify_ctdb_ban_state(struct ctdb_ban_state *p1, + struct ctdb_ban_state *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->time == p2->time); +} + +void fill_ctdb_notify_data(TALLOC_CTX *mem_ctx, struct ctdb_notify_data *p) +{ + p->srvid = rand64(); + fill_tdb_data(mem_ctx, &p->data); +} + +void verify_ctdb_notify_data(struct ctdb_notify_data *p1, + struct ctdb_notify_data *p2) +{ + assert(p1->srvid == p2->srvid); + verify_tdb_data(&p1->data, &p2->data); +} + +void fill_ctdb_iface(TALLOC_CTX *mem_ctx, struct ctdb_iface *p) +{ + fill_string(p->name, CTDB_IFACE_SIZE+2); + p->link_state = rand16(); + p->references = rand32(); +} + +void verify_ctdb_iface(struct ctdb_iface *p1, struct ctdb_iface *p2) +{ + verify_string(p1->name, p2->name); + assert(p1->link_state == p2->link_state); + assert(p1->references == p2->references); +} + +void fill_ctdb_iface_list(TALLOC_CTX *mem_ctx, struct ctdb_iface_list *p) +{ + unsigned int i; + + p->num = rand_int(32); + if (p->num > 0) { + p->iface = talloc_array(mem_ctx, struct ctdb_iface, p->num); + assert(p->iface != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_iface(mem_ctx, &p->iface[i]); + } + } else { + p->iface = NULL; + } +} + +void verify_ctdb_iface_list(struct ctdb_iface_list *p1, + struct ctdb_iface_list *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_iface(&p1->iface[i], &p2->iface[i]); + } +} + +void fill_ctdb_public_ip_info(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_info *p) +{ + fill_ctdb_public_ip(mem_ctx, &p->ip); + p->active_idx = rand_int(32) + 1; + p->ifaces = talloc(mem_ctx, struct ctdb_iface_list); + assert(p->ifaces != NULL); + fill_ctdb_iface_list(mem_ctx, p->ifaces); +} + +void verify_ctdb_public_ip_info(struct ctdb_public_ip_info *p1, + struct ctdb_public_ip_info *p2) +{ + verify_ctdb_public_ip(&p1->ip, &p2->ip); + assert(p1->active_idx == p2->active_idx); + verify_ctdb_iface_list(p1->ifaces, p2->ifaces); +} + +void fill_ctdb_statistics_list(TALLOC_CTX *mem_ctx, + struct ctdb_statistics_list *p) +{ + int i; + + p->num = rand_int(10); + if (p->num > 0) { + p->stats = talloc_zero_array(mem_ctx, struct ctdb_statistics, + p->num); + assert(p->stats != NULL); + + for (i=0; i<p->num; i++) { + fill_ctdb_statistics(mem_ctx, &p->stats[i]); + } + } else { + p->stats = NULL; + } +} + +void verify_ctdb_statistics_list(struct ctdb_statistics_list *p1, + struct ctdb_statistics_list *p2) +{ + int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_statistics(&p1->stats[i], &p2->stats[i]); + } +} + +void fill_ctdb_key_data(TALLOC_CTX *mem_ctx, struct ctdb_key_data *p) +{ + p->db_id = rand32(); + fill_ctdb_ltdb_header(&p->header); + fill_tdb_data_nonnull(mem_ctx, &p->key); +} + +void verify_ctdb_key_data(struct ctdb_key_data *p1, struct ctdb_key_data *p2) +{ + assert(p1->db_id == p2->db_id); + verify_ctdb_ltdb_header(&p1->header, &p2->header); + verify_tdb_data(&p1->key, &p2->key); +} + +void fill_ctdb_db_statistics(TALLOC_CTX *mem_ctx, + struct ctdb_db_statistics *p) +{ + unsigned int i; + + p->locks.num_calls = rand32(); + p->locks.num_current = rand32(); + p->locks.num_pending = rand32(); + p->locks.num_failed = rand32(); + fill_ctdb_latency_counter(&p->locks.latency); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + p->locks.buckets[i] = rand32(); + } + + fill_ctdb_latency_counter(&p->vacuum.latency); + + p->db_ro_delegations = rand32(); + p->db_ro_revokes = rand32(); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + p->hop_count_bucket[i] = rand32(); + } + + p->num_hot_keys = MAX_HOT_KEYS; + for (i=0; i<p->num_hot_keys; i++) { + p->hot_keys[i].count = rand32(); + fill_tdb_data(mem_ctx, &p->hot_keys[i].key); + } +} + +void verify_ctdb_db_statistics(struct ctdb_db_statistics *p1, + struct ctdb_db_statistics *p2) +{ + unsigned int i; + + assert(p1->locks.num_calls == p2->locks.num_calls); + assert(p1->locks.num_current == p2->locks.num_current); + assert(p1->locks.num_pending == p2->locks.num_pending); + assert(p1->locks.num_failed == p2->locks.num_failed); + verify_ctdb_latency_counter(&p1->locks.latency, &p2->locks.latency); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + assert(p1->locks.buckets[i] == p2->locks.buckets[i]); + } + + verify_ctdb_latency_counter(&p1->vacuum.latency, &p2->vacuum.latency); + + assert(p1->db_ro_delegations == p2->db_ro_delegations); + assert(p1->db_ro_revokes == p2->db_ro_revokes); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + assert(p1->hop_count_bucket[i] == p2->hop_count_bucket[i]); + } + + assert(p1->num_hot_keys == p2->num_hot_keys); + for (i=0; i<p1->num_hot_keys; i++) { + assert(p1->hot_keys[i].count == p2->hot_keys[i].count); + verify_tdb_data(&p1->hot_keys[i].key, &p2->hot_keys[i].key); + } +} + +void fill_ctdb_pid_srvid(TALLOC_CTX *mem_ctx, struct ctdb_pid_srvid *p) +{ + p->pid = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_pid_srvid(struct ctdb_pid_srvid *p1, + struct ctdb_pid_srvid *p2) +{ + assert(p1->pid == p2->pid); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_election_message(TALLOC_CTX *mem_ctx, + struct ctdb_election_message *p) +{ + p->num_connected = rand_int(32); + fill_ctdb_timeval(&p->priority_time); + p->pnn = rand_int(32); + p->node_flags = rand32(); +} + +void verify_ctdb_election_message(struct ctdb_election_message *p1, + struct ctdb_election_message *p2) +{ + assert(p1->num_connected == p2->num_connected); + verify_ctdb_timeval(&p1->priority_time, &p2->priority_time); + assert(p1->pnn == p2->pnn); + assert(p1->node_flags == p2->node_flags); +} + +void fill_ctdb_srvid_message(TALLOC_CTX *mem_ctx, + struct ctdb_srvid_message *p) +{ + p->pnn = rand_int(32); + p->srvid = rand64(); +} + +void verify_ctdb_srvid_message(struct ctdb_srvid_message *p1, + struct ctdb_srvid_message *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_disable_message(TALLOC_CTX *mem_ctx, + struct ctdb_disable_message *p) +{ + p->pnn = rand_int(32); + p->srvid = rand64(); + p->timeout = rand32(); +} + +void verify_ctdb_disable_message(struct ctdb_disable_message *p1, + struct ctdb_disable_message *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->srvid == p2->srvid); + assert(p1->timeout == p2->timeout); +} + +void fill_ctdb_server_id(struct ctdb_server_id *p) +{ + p->pid = rand64(); + p->task_id = rand32(); + p->vnn = rand_int(32); + p->unique_id = rand64(); +} + +void verify_ctdb_server_id(struct ctdb_server_id *p1, + struct ctdb_server_id *p2) +{ + assert(p1->pid == p2->pid); + assert(p1->task_id == p2->task_id); + assert(p1->vnn == p2->vnn); + assert(p1->unique_id == p2->unique_id); +} + +void fill_ctdb_g_lock(struct ctdb_g_lock *p) +{ + p->type = rand_int(2); + fill_ctdb_server_id(&p->sid); +} + +void verify_ctdb_g_lock(struct ctdb_g_lock *p1, struct ctdb_g_lock *p2) +{ + assert(p1->type == p2->type); + verify_ctdb_server_id(&p1->sid, &p2->sid); +} + +void fill_ctdb_g_lock_list(TALLOC_CTX *mem_ctx, struct ctdb_g_lock_list *p) +{ + unsigned int i; + + p->num = rand_int(20) + 1; + p->lock = talloc_zero_array(mem_ctx, struct ctdb_g_lock, p->num); + assert(p->lock != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_g_lock(&p->lock[i]); + } +} + +void verify_ctdb_g_lock_list(struct ctdb_g_lock_list *p1, + struct ctdb_g_lock_list *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_g_lock(&p1->lock[i], &p2->lock[i]); + } +} + +void fill_sock_packet_header(struct sock_packet_header *p) +{ + p->length = rand32(); + p->reqid = rand32(); +} + +void verify_sock_packet_header(struct sock_packet_header *p1, + struct sock_packet_header *p2) +{ + assert(p1->length == p2->length); + assert(p1->reqid == p2->reqid); +} diff --git a/ctdb/tests/src/protocol_common.h b/ctdb/tests/src/protocol_common.h new file mode 100644 index 0000000..171b19b --- /dev/null +++ b/ctdb/tests/src/protocol_common.h @@ -0,0 +1,238 @@ +/* + protocol tests - common functions + + Copyright (C) Amitay Isaacs 2015-2017 + + 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/>. +*/ + +#ifndef __CTDB_PROTOCOL_COMMON_H__ +#define __CTDB_PROTOCOL_COMMON_H__ + +#include "replace.h" +#include "system/network.h" + +#include <talloc.h> +#include <tdb.h> + +#include "protocol/protocol.h" + +#include "tests/src/protocol_common_basic.h" + +void fill_tdb_data_nonnull(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void fill_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void verify_tdb_data(TDB_DATA *p1, TDB_DATA *p2); + +void fill_ctdb_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void verify_ctdb_tdb_data(TDB_DATA *p1, TDB_DATA *p2); + +void fill_ctdb_tdb_datan(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void verify_ctdb_tdb_datan(TDB_DATA *p1, TDB_DATA *p2); + +void fill_ctdb_latency_counter(struct ctdb_latency_counter *p); +void verify_ctdb_latency_counter(struct ctdb_latency_counter *p1, + struct ctdb_latency_counter *p2); + +void fill_ctdb_statistics(TALLOC_CTX *mem_ctx, struct ctdb_statistics *p); +void verify_ctdb_statistics(struct ctdb_statistics *p1, + struct ctdb_statistics *p2); + +void fill_ctdb_vnn_map(TALLOC_CTX *mem_ctx, struct ctdb_vnn_map *p); +void verify_ctdb_vnn_map(struct ctdb_vnn_map *p1, struct ctdb_vnn_map *p2); + +void fill_ctdb_dbid(TALLOC_CTX *mem_ctx, struct ctdb_dbid *p); +void verify_ctdb_dbid(struct ctdb_dbid *p1, struct ctdb_dbid *p2); + +void fill_ctdb_dbid_map(TALLOC_CTX *mem_ctx, struct ctdb_dbid_map *p); +void verify_ctdb_dbid_map(struct ctdb_dbid_map *p1, struct ctdb_dbid_map *p2); + +void fill_ctdb_pulldb(TALLOC_CTX *mem_ctx, struct ctdb_pulldb *p); +void verify_ctdb_pulldb(struct ctdb_pulldb *p1, struct ctdb_pulldb *p2); + +void fill_ctdb_pulldb_ext(TALLOC_CTX *mem_ctx, struct ctdb_pulldb_ext *p); +void verify_ctdb_pulldb_ext(struct ctdb_pulldb_ext *p1, + struct ctdb_pulldb_ext *p2); + +void fill_ctdb_db_vacuum(TALLOC_CTX *mem_ctx, struct ctdb_db_vacuum *p); +void verify_ctdb_db_vacuum(struct ctdb_db_vacuum *p1, + struct ctdb_db_vacuum *p2); + +void fill_ctdb_echo_data(TALLOC_CTX *mem_ctx, struct ctdb_echo_data *p); +void verify_ctdb_echo_data(struct ctdb_echo_data *p1, + struct ctdb_echo_data *p2); + +void fill_ctdb_ltdb_header(struct ctdb_ltdb_header *p); +void verify_ctdb_ltdb_header(struct ctdb_ltdb_header *p1, + struct ctdb_ltdb_header *p2); + +void fill_ctdb_rec_data(TALLOC_CTX *mem_ctx, struct ctdb_rec_data *p); +void verify_ctdb_rec_data(struct ctdb_rec_data *p1, struct ctdb_rec_data *p2); + +void fill_ctdb_rec_buffer(TALLOC_CTX *mem_ctx, struct ctdb_rec_buffer *p); +void verify_ctdb_rec_buffer(struct ctdb_rec_buffer *p1, + struct ctdb_rec_buffer *p2); + +void fill_ctdb_traverse_start(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start *p); +void verify_ctdb_traverse_start(struct ctdb_traverse_start *p1, + struct ctdb_traverse_start *p2); + +void fill_ctdb_traverse_all(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all *p); +void verify_ctdb_traverse_all(struct ctdb_traverse_all *p1, + struct ctdb_traverse_all *p2); + +void fill_ctdb_traverse_start_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start_ext *p); +void verify_ctdb_traverse_start_ext(struct ctdb_traverse_start_ext *p1, + struct ctdb_traverse_start_ext *p2); + +void fill_ctdb_traverse_all_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all_ext *p); +void verify_ctdb_traverse_all_ext(struct ctdb_traverse_all_ext *p1, + struct ctdb_traverse_all_ext *p2); + +void fill_ctdb_sock_addr(TALLOC_CTX *mem_ctx, ctdb_sock_addr *p); +void verify_ctdb_sock_addr(ctdb_sock_addr *p1, ctdb_sock_addr *p2); + +void fill_ctdb_connection(TALLOC_CTX *mem_ctx, struct ctdb_connection *p); +void verify_ctdb_connection(struct ctdb_connection *p1, + struct ctdb_connection *p2); + +void fill_ctdb_connection_list(TALLOC_CTX *mem_ctx, + struct ctdb_connection_list *p); +void verify_ctdb_connection_list(struct ctdb_connection_list *p1, + struct ctdb_connection_list *p2); + +void fill_ctdb_tunable(TALLOC_CTX *mem_ctx, struct ctdb_tunable *p); +void verify_ctdb_tunable(struct ctdb_tunable *p1, struct ctdb_tunable *p2); + +void fill_ctdb_node_flag_change(TALLOC_CTX *mem_ctx, + struct ctdb_node_flag_change *p); +void verify_ctdb_node_flag_change(struct ctdb_node_flag_change *p1, + struct ctdb_node_flag_change *p2); + +void fill_ctdb_var_list(TALLOC_CTX *mem_ctx, struct ctdb_var_list *p); +void verify_ctdb_var_list(struct ctdb_var_list *p1, struct ctdb_var_list *p2); + +void fill_ctdb_tunable_list(TALLOC_CTX *mem_ctx, struct ctdb_tunable_list *p); +void verify_ctdb_tunable_list(struct ctdb_tunable_list *p1, + struct ctdb_tunable_list *p2); + +void fill_ctdb_tickle_list(TALLOC_CTX *mem_ctx, struct ctdb_tickle_list *p); +void verify_ctdb_tickle_list(struct ctdb_tickle_list *p1, + struct ctdb_tickle_list *p2); + +void fill_ctdb_addr_info(TALLOC_CTX *mem_ctx, struct ctdb_addr_info *p); +void verify_ctdb_addr_info(struct ctdb_addr_info *p1, + struct ctdb_addr_info *p2); + +void fill_ctdb_transdb(TALLOC_CTX *mem_ctx, struct ctdb_transdb *p); +void verify_ctdb_transdb(struct ctdb_transdb *p1, struct ctdb_transdb *p2); + +void fill_ctdb_uptime(TALLOC_CTX *mem_ctx, struct ctdb_uptime *p); +void verify_ctdb_uptime(struct ctdb_uptime *p1, struct ctdb_uptime *p2); + +void fill_ctdb_public_ip(TALLOC_CTX *mem_ctx, struct ctdb_public_ip *p); +void verify_ctdb_public_ip(struct ctdb_public_ip *p1, + struct ctdb_public_ip *p2); + +void fill_ctdb_public_ip_list(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list *p); +void verify_ctdb_public_ip_list(struct ctdb_public_ip_list *p1, + struct ctdb_public_ip_list *p2); + +void fill_ctdb_node_and_flags(TALLOC_CTX *mem_ctx, + struct ctdb_node_and_flags *p); +void verify_ctdb_node_and_flags(struct ctdb_node_and_flags *p1, + struct ctdb_node_and_flags *p2); + +void fill_ctdb_node_map(TALLOC_CTX *mem_ctx, struct ctdb_node_map *p); +void verify_ctdb_node_map(struct ctdb_node_map *p1, struct ctdb_node_map *p2); + +void fill_ctdb_script(TALLOC_CTX *mem_ctx, struct ctdb_script *p); +void verify_ctdb_script(struct ctdb_script *p1, struct ctdb_script *p2); + +void fill_ctdb_script_list(TALLOC_CTX *mem_ctx, struct ctdb_script_list *p); +void verify_ctdb_script_list(struct ctdb_script_list *p1, + struct ctdb_script_list *p2); + +void fill_ctdb_ban_state(TALLOC_CTX *mem_ctx, struct ctdb_ban_state *p); +void verify_ctdb_ban_state(struct ctdb_ban_state *p1, + struct ctdb_ban_state *p2); + +void fill_ctdb_notify_data(TALLOC_CTX *mem_ctx, struct ctdb_notify_data *p); +void verify_ctdb_notify_data(struct ctdb_notify_data *p1, + struct ctdb_notify_data *p2); + +void fill_ctdb_iface(TALLOC_CTX *mem_ctx, struct ctdb_iface *p); +void verify_ctdb_iface(struct ctdb_iface *p1, struct ctdb_iface *p2); + +void fill_ctdb_iface_list(TALLOC_CTX *mem_ctx, struct ctdb_iface_list *p); +void verify_ctdb_iface_list(struct ctdb_iface_list *p1, + struct ctdb_iface_list *p2); + +void fill_ctdb_public_ip_info(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_info *p); +void verify_ctdb_public_ip_info(struct ctdb_public_ip_info *p1, + struct ctdb_public_ip_info *p2); + +void fill_ctdb_statistics_list(TALLOC_CTX *mem_ctx, + struct ctdb_statistics_list *p); +void verify_ctdb_statistics_list(struct ctdb_statistics_list *p1, + struct ctdb_statistics_list *p2); + +void fill_ctdb_key_data(TALLOC_CTX *mem_ctx, struct ctdb_key_data *p); +void verify_ctdb_key_data(struct ctdb_key_data *p1, struct ctdb_key_data *p2); + +void fill_ctdb_db_statistics(TALLOC_CTX *mem_ctx, + struct ctdb_db_statistics *p); +void verify_ctdb_db_statistics(struct ctdb_db_statistics *p1, + struct ctdb_db_statistics *p2); + +void fill_ctdb_pid_srvid(TALLOC_CTX *mem_ctx, struct ctdb_pid_srvid *p); +void verify_ctdb_pid_srvid(struct ctdb_pid_srvid *p1, + struct ctdb_pid_srvid *p2); + +void fill_ctdb_election_message(TALLOC_CTX *mem_ctx, + struct ctdb_election_message *p); +void verify_ctdb_election_message(struct ctdb_election_message *p1, + struct ctdb_election_message *p2); + +void fill_ctdb_srvid_message(TALLOC_CTX *mem_ctx, + struct ctdb_srvid_message *p); +void verify_ctdb_srvid_message(struct ctdb_srvid_message *p1, + struct ctdb_srvid_message *p2); + +void fill_ctdb_disable_message(TALLOC_CTX *mem_ctx, + struct ctdb_disable_message *p); +void verify_ctdb_disable_message(struct ctdb_disable_message *p1, + struct ctdb_disable_message *p2); + +void fill_ctdb_server_id(struct ctdb_server_id *p); +void verify_ctdb_server_id(struct ctdb_server_id *p1, + struct ctdb_server_id *p2); + +void fill_ctdb_g_lock(struct ctdb_g_lock *p); +void verify_ctdb_g_lock(struct ctdb_g_lock *p1, struct ctdb_g_lock *p2); + +void fill_ctdb_g_lock_list(TALLOC_CTX *mem_ctx, struct ctdb_g_lock_list *p); +void verify_ctdb_g_lock_list(struct ctdb_g_lock_list *p1, + struct ctdb_g_lock_list *p2); + +void fill_sock_packet_header(struct sock_packet_header *p); +void verify_sock_packet_header(struct sock_packet_header *p1, + struct sock_packet_header *p2); + +#endif /* __CTDB_PROTOCOL_COMMON_H__ */ diff --git a/ctdb/tests/src/protocol_common_basic.c b/ctdb/tests/src/protocol_common_basic.c new file mode 100644 index 0000000..7567f7b --- /dev/null +++ b/ctdb/tests/src/protocol_common_basic.c @@ -0,0 +1,305 @@ +/* + protocol tests - common functions - basic types + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/wait.h" + +#include <assert.h> + +#include "lib/util/fault.h" + +#include "tests/src/protocol_common_basic.h" + +uint8_t BUFFER[1024*1024]; + +/* + * Functions to generation random data + */ + +int rand_int(int max) +{ + return random() % max; +} + +uint8_t rand8(void) +{ + uint8_t val = rand_int(256) & 0xff; + return val; +} + +uint16_t rand16(void) +{ + uint16_t val = rand_int(0xffff) & 0xffff; + return val; +} + +int32_t rand32i(void) +{ + return INT_MIN + random(); +} + +uint32_t rand32(void) +{ + return random(); +} + +uint64_t rand64(void) +{ + uint64_t t = random(); + t = (t << 32) | random(); + return t; +} + +double rand_double(void) +{ + return 1.0 / rand64(); +} + +void fill_buffer(void *p, size_t len) +{ + size_t i; + uint8_t *ptr = p; + + for (i=0; i<len; i++) { + ptr[i] = rand8(); + } +} + +void verify_buffer(void *p1, void *p2, size_t len) +{ + if (len > 0) { + assert(memcmp(p1, p2, len) == 0); + } +} + +void fill_string(char *p, size_t len) +{ + size_t i; + + for (i=0; i<len-1; i++) { + p[i] = 'A' + rand_int(26); + } + p[len-1] = '\0'; +} + +void verify_string(const char *p1, const char *p2) +{ + assert(strlen(p1) == strlen(p2)); + assert(strcmp(p1, p2) == 0); +} + +void fill_ctdb_uint8(uint8_t *p) +{ + *p = rand8(); +} + +void verify_ctdb_uint8(uint8_t *p1, uint8_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_uint16(uint16_t *p) +{ + *p = rand16(); +} + +void verify_ctdb_uint16(uint16_t *p1, uint16_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_int32(int32_t *p) +{ + *p = rand32i(); +} + +void verify_ctdb_int32(int32_t *p1, int32_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_uint32(uint32_t *p) +{ + *p = rand32(); +} + +void verify_ctdb_uint32(uint32_t *p1, uint32_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_uint64(uint64_t *p) +{ + *p = rand64(); +} + +void verify_ctdb_uint64(uint64_t *p1, uint64_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_double(double *p) +{ + *p = rand_double(); +} + +void verify_ctdb_double(double *p1, double *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_bool(bool *p) +{ + if (rand_int(2) == 0) { + *p = true; + } else { + *p = false; + } +} + +void verify_ctdb_bool(bool *p1, bool *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_string(TALLOC_CTX *mem_ctx, const char **p) +{ + char *str; + int len; + + len = rand_int(1024) + 2; + str = talloc_size(mem_ctx, len+1); + assert(str != NULL); + + fill_string(str, len); + *p = str; +} + +void verify_ctdb_string(const char **p1, const char **p2) +{ + if (*p1 == NULL || *p2 == NULL) { + assert(*p1 == *p2); + } else { + verify_string(*p1, *p2); + } +} + +void fill_ctdb_stringn(TALLOC_CTX *mem_ctx, const char **p) +{ + fill_ctdb_string(mem_ctx, p); +} + +void verify_ctdb_stringn(const char **p1, const char **p2) +{ + verify_ctdb_string(p1, p2); +} + +void fill_ctdb_pid(pid_t *p) +{ + *p = rand32(); +} + +void verify_ctdb_pid(pid_t *p1, pid_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_timeval(struct timeval *p) +{ + p->tv_sec = rand32(); + p->tv_usec = rand_int(1000000); +} + +void verify_ctdb_timeval(struct timeval *p1, struct timeval *p2) +{ + assert(p1->tv_sec == p2->tv_sec); + assert(p1->tv_usec == p2->tv_usec); +} + +static unsigned int seed; +static char protocol_test_iterate_buf[1024]; + +static void protocol_test_iterate_abort_handler(int sig) +{ + struct sigaction act = { + .sa_handler = SIG_DFL, + }; + + fprintf(stderr, "Failed with seed: %d\n", seed); + if (protocol_test_iterate_buf[0] != '\0') { + fprintf(stderr, " tag: %s\n", protocol_test_iterate_buf); + } + log_stack_trace(); + sigaction(SIGABRT, &act, NULL); + abort(); +} + +void protocol_test_iterate_tag(const char *fmt, ...) +{ + va_list ap; + int count; + + va_start(ap,fmt); + count = vsnprintf(protocol_test_iterate_buf, + sizeof(protocol_test_iterate_buf), + fmt, + ap); + va_end(ap); + + assert(count >= 0); + protocol_test_iterate_buf[sizeof(protocol_test_iterate_buf) - 1] = '\0'; +} + +void protocol_test_iterate(int argc, + const char *argv[], + void (*test_func)(void)) +{ + struct sigaction act = { + .sa_handler = protocol_test_iterate_abort_handler, + }; + unsigned int min, max; + + if (argc == 2 || argc == 3) { + min = atoi(argv[1]); + + if (argc == 3) { + max = atoi(argv[2]); + if (min >= max) { + fprintf(stderr, + "%s: min must be less than max\n", + argv[0]); + exit(1); + } + + } else { + max = min; + } + } else { + fprintf(stderr, "usage: %s min [max]\n", argv[0]); + exit(1); + } + + sigaction(SIGABRT, &act, NULL); + + for (seed = min; seed <= max ; seed++) { + srandom(seed); + + test_func(); + } +} diff --git a/ctdb/tests/src/protocol_common_basic.h b/ctdb/tests/src/protocol_common_basic.h new file mode 100644 index 0000000..22a11b3 --- /dev/null +++ b/ctdb/tests/src/protocol_common_basic.h @@ -0,0 +1,175 @@ +/* + protocol tests - common functions - basic types + + Copyright (C) Amitay Isaacs 2015-2017 + + 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/>. +*/ + +#ifndef __CTDB_PROTOCOL_COMMON_BASIC_H__ +#define __CTDB_PROTOCOL_COMMON_BASIC_H__ + +#include "replace.h" + +#include <talloc.h> + +/* + * Generate test routines + */ + +#define TEST_FUNC(NAME) test_ ##NAME +#define FILL_FUNC(NAME) fill_ ##NAME +#define VERIFY_FUNC(NAME) verify_ ##NAME +#define LEN_FUNC(NAME) NAME## _len +#define PUSH_FUNC(NAME) NAME## _push +#define PULL_FUNC(NAME) NAME## _pull + +/* + * Test for basic data types that do not need memory allocation + * For example - int32_t, uint32_t, uint64_t + */ +#define PROTOCOL_TYPE1_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TYPE p1; \ + TYPE p2; \ + size_t buflen, np = 0; \ + int ret; \ +\ + FILL_FUNC(NAME)(&p1); \ + buflen = LEN_FUNC(NAME)(&p1); \ + assert(buflen < sizeof(BUFFER)); \ + PUSH_FUNC(NAME)(&p1, BUFFER, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(BUFFER, buflen, &p2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ +} + +/* + * Test for container data types that need memory allocation for sub-elements + * For example - TDB_DATA + */ +#define PROTOCOL_TYPE2_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE p1; \ + TYPE p2; \ + size_t buflen, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(mem_ctx, &p1); \ + buflen = LEN_FUNC(NAME)(&p1); \ + assert(buflen < sizeof(BUFFER)); \ + PUSH_FUNC(NAME)(&p1, BUFFER, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(BUFFER, buflen, mem_ctx, &p2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +/* + * Test for derived data types that need memory allocation + * For example - most ctdb structures + */ +#define PROTOCOL_TYPE3_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE *p1, *p2; \ + size_t buflen, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + p1 = talloc_zero(mem_ctx, TYPE); \ + assert(p1 != NULL); \ + FILL_FUNC(NAME)(p1, p1); \ + buflen = LEN_FUNC(NAME)(p1); \ + assert(buflen < sizeof(BUFFER)); \ + PUSH_FUNC(NAME)(p1, BUFFER, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(BUFFER, buflen, mem_ctx, &p2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(p1, p2); \ + talloc_free(mem_ctx); \ +} + +extern uint8_t BUFFER[1024*1024]; + +int rand_int(int max); +uint8_t rand8(void); +uint16_t rand16(void); +int32_t rand32i(void); +uint32_t rand32(void); +uint64_t rand64(void); +double rand_double(void); + +void fill_buffer(void *p, size_t len); +void verify_buffer(void *p1, void *p2, size_t len); + +void fill_string(char *p, size_t len); +void verify_string(const char *p1, const char *p2); + +void fill_ctdb_uint8(uint8_t *p); +void verify_ctdb_uint8(uint8_t *p1, uint8_t *p2); + +void fill_ctdb_uint16(uint16_t *p); +void verify_ctdb_uint16(uint16_t *p1, uint16_t *p2); + +void fill_ctdb_int32(int32_t *p); +void verify_ctdb_int32(int32_t *p1, int32_t *p2); + +void fill_ctdb_uint32(uint32_t *p); +void verify_ctdb_uint32(uint32_t *p1, uint32_t *p2); + +void fill_ctdb_uint64(uint64_t *p); +void verify_ctdb_uint64(uint64_t *p1, uint64_t *p2); + +void fill_ctdb_double(double *p); +void verify_ctdb_double(double *p1, double *p2); + +void fill_ctdb_bool(bool *p); +void verify_ctdb_bool(bool *p1, bool *p2); + +void fill_ctdb_string(TALLOC_CTX *mem_ctx, const char **p); +void verify_ctdb_string(const char **p1, const char **p2); + +void fill_ctdb_stringn(TALLOC_CTX *mem_ctx, const char **p); +void verify_ctdb_stringn(const char **p1, const char **p2); + +void fill_ctdb_pid(pid_t *p); +void verify_ctdb_pid(pid_t *p1, pid_t *p2); + +void fill_ctdb_timeval(struct timeval *p); +void verify_ctdb_timeval(struct timeval *p1, struct timeval *p2); + +void protocol_test_iterate_tag(const char *fmt, ...) PRINTF_ATTRIBUTE(1,0); +void protocol_test_iterate(int argc, + const char *argv[], + void (*test_func)(void)); + +#endif /* __CTDB_PROTOCOL_COMMON_BASIC_H__ */ + + diff --git a/ctdb/tests/src/protocol_common_ctdb.c b/ctdb/tests/src/protocol_common_ctdb.c new file mode 100644 index 0000000..8a8e114 --- /dev/null +++ b/ctdb/tests/src/protocol_common_ctdb.c @@ -0,0 +1,1967 @@ +/* + protocol tests - ctdb protocol + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <assert.h> + +#include "tests/src/protocol_common.h" +#include "tests/src/protocol_common_ctdb.h" + +/* + * Functions to fill and verify protocol structures + */ + +void fill_ctdb_req_header(struct ctdb_req_header *h) +{ + h->length = rand32(); + h->ctdb_magic = rand32(); + h->ctdb_version = rand32(); + h->generation = rand32(); + h->operation = rand32(); + h->destnode = rand32(); + h->srcnode = rand32(); + h->reqid = rand32(); +} + +void verify_ctdb_req_header(struct ctdb_req_header *h, + struct ctdb_req_header *h2) +{ + assert(h->length == h2->length); + assert(h->ctdb_magic == h2->ctdb_magic); + assert(h->ctdb_version == h2->ctdb_version); + assert(h->generation == h2->generation); + assert(h->operation == h2->operation); + assert(h->destnode == h2->destnode); + assert(h->srcnode == h2->srcnode); + assert(h->reqid == h2->reqid); +} + +void fill_ctdb_req_call(TALLOC_CTX *mem_ctx, struct ctdb_req_call *c) +{ + c->flags = rand32(); + c->db_id = rand32(); + c->callid = rand32(); + c->hopcount = rand32(); + fill_tdb_data_nonnull(mem_ctx, &c->key); + fill_tdb_data(mem_ctx, &c->calldata); +} + +void verify_ctdb_req_call(struct ctdb_req_call *c, struct ctdb_req_call *c2) +{ + assert(c->flags == c2->flags); + assert(c->db_id == c2->db_id); + assert(c->callid == c2->callid); + assert(c->hopcount == c2->hopcount); + verify_tdb_data(&c->key, &c2->key); + verify_tdb_data(&c->calldata, &c2->calldata); +} + +void fill_ctdb_reply_call(TALLOC_CTX *mem_ctx, struct ctdb_reply_call *c) +{ + c->status = rand32(); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_reply_call(struct ctdb_reply_call *c, + struct ctdb_reply_call *c2) +{ + assert(c->status == c2->status); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_reply_error(TALLOC_CTX *mem_ctx, struct ctdb_reply_error *c) +{ + c->status = rand32(); + fill_tdb_data(mem_ctx, &c->msg); +} + +void verify_ctdb_reply_error(struct ctdb_reply_error *c, + struct ctdb_reply_error *c2) +{ + assert(c->status == c2->status); + verify_tdb_data(&c->msg, &c2->msg); +} + +void fill_ctdb_req_dmaster(TALLOC_CTX *mem_ctx, struct ctdb_req_dmaster *c) +{ + c->db_id = rand32(); + c->rsn = rand64(); + c->dmaster = rand32(); + fill_tdb_data_nonnull(mem_ctx, &c->key); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_req_dmaster(struct ctdb_req_dmaster *c, + struct ctdb_req_dmaster *c2) +{ + assert(c->db_id == c2->db_id); + assert(c->rsn == c2->rsn); + assert(c->dmaster == c2->dmaster); + verify_tdb_data(&c->key, &c2->key); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_reply_dmaster(TALLOC_CTX *mem_ctx, + struct ctdb_reply_dmaster *c) +{ + c->db_id = rand32(); + c->rsn = rand64(); + fill_tdb_data_nonnull(mem_ctx, &c->key); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_reply_dmaster(struct ctdb_reply_dmaster *c, + struct ctdb_reply_dmaster *c2) +{ + assert(c->db_id == c2->db_id); + assert(c->rsn == c2->rsn); + verify_tdb_data(&c->key, &c2->key); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_req_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_control_data *cd, + uint32_t opcode) +{ + cd->opcode = opcode; + switch (opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + cd->data.pid = rand32(); + break; + + case CTDB_CONTROL_STATISTICS: + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_GETVNNMAP: + break; + + case CTDB_CONTROL_SETVNNMAP: + cd->data.vnnmap = talloc(mem_ctx, struct ctdb_vnn_map); + assert(cd->data.vnnmap != NULL); + fill_ctdb_vnn_map(mem_ctx, cd->data.vnnmap); + break; + + case CTDB_CONTROL_GET_DEBUG: + break; + + case CTDB_CONTROL_SET_DEBUG: + cd->data.loglevel = rand_int(5); + break; + + case CTDB_CONTROL_GET_DBMAP: + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + cd->data.recmode = rand_int(2); + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name != NULL); + break; + + case CTDB_CONTROL_TRAVERSE_START: + cd->data.traverse_start = talloc(mem_ctx, struct ctdb_traverse_start); + assert(cd->data.traverse_start != NULL); + fill_ctdb_traverse_start(mem_ctx, cd->data.traverse_start); + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + cd->data.traverse_all = talloc(mem_ctx, struct ctdb_traverse_all); + assert(cd->data.traverse_all != NULL); + fill_ctdb_traverse_all(mem_ctx, cd->data.traverse_all); + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + cd->data.rec_data = talloc(mem_ctx, struct ctdb_rec_data); + assert(cd->data.rec_data != NULL); + fill_ctdb_rec_data(mem_ctx, cd->data.rec_data); + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DUMP_MEMORY: + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_TCP_ADD: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_TCP_REMOVE: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + cd->data.tunable = talloc(mem_ctx, struct ctdb_tunable); + assert(cd->data.tunable != NULL); + fill_ctdb_tunable(mem_ctx, cd->data.tunable); + break; + + case CTDB_CONTROL_GET_TUNABLE: + fill_ctdb_string(mem_ctx, &cd->data.tun_var); + assert(cd->data.tun_var != NULL); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + cd->data.flag_change = talloc(mem_ctx, struct ctdb_node_flag_change); + assert(cd->data.flag_change != NULL); + fill_ctdb_node_flag_change(mem_ctx, cd->data.flag_change); + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + cd->data.addr = talloc(mem_ctx, ctdb_sock_addr); + assert(cd->data.addr != NULL); + fill_ctdb_sock_addr(mem_ctx, cd->data.addr); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + cd->data.tickles = talloc(mem_ctx, struct ctdb_tickle_list); + assert(cd->data.tickles != NULL); + fill_ctdb_tickle_list(mem_ctx, cd->data.tickles); + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name != NULL); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info); + assert(cd->data.addr_info != NULL); + fill_ctdb_addr_info(mem_ctx, cd->data.addr_info); + break; + + case CTDB_CONTROL_WIPE_DATABASE: + cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb); + assert(cd->data.transdb != NULL); + fill_ctdb_transdb(mem_ctx, cd->data.transdb); + break; + + case CTDB_CONTROL_UPTIME: + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info); + assert(cd->data.addr_info != NULL); + fill_ctdb_addr_info(mem_ctx, cd->data.addr_info); + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info); + assert(cd->data.addr_info != NULL); + fill_ctdb_addr_info(mem_ctx, cd->data.addr_info); + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + cd->data.pubip = talloc(mem_ctx, struct ctdb_public_ip); + assert(cd->data.pubip != NULL); + fill_ctdb_public_ip(mem_ctx, cd->data.pubip); + break; + + case CTDB_CONTROL_TAKEOVER_IP: + cd->data.pubip = talloc(mem_ctx, struct ctdb_public_ip); + assert(cd->data.pubip != NULL); + fill_ctdb_public_ip(mem_ctx, cd->data.pubip); + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + break; + + case CTDB_CONTROL_GET_NODEMAP: + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + cd->data.traverse_start = talloc(mem_ctx, struct ctdb_traverse_start); + assert(cd->data.traverse_start != NULL); + fill_ctdb_traverse_start(mem_ctx, cd->data.traverse_start); + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + cd->data.reclock_latency = rand_double(); + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + cd->data.role = rand_int(2); + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + cd->data.role = rand_int(2); + break; + + case CTDB_CONTROL_SET_BAN_STATE: + cd->data.ban_state = talloc(mem_ctx, struct ctdb_ban_state); + assert(cd->data.ban_state != NULL); + fill_ctdb_ban_state(mem_ctx, cd->data.ban_state); + break; + + case CTDB_CONTROL_GET_BAN_STATE: + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + cd->data.notify = talloc(mem_ctx, struct ctdb_notify_data); + assert(cd->data.notify != NULL); + fill_ctdb_notify_data(mem_ctx, cd->data.notify); + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + cd->data.srvid = rand64(); + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + cd->data.addr = talloc(mem_ctx, ctdb_sock_addr); + assert(cd->data.addr != NULL); + fill_ctdb_sock_addr(mem_ctx, cd->data.addr); + break; + + case CTDB_CONTROL_GET_IFACES: + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + cd->data.iface = talloc(mem_ctx, struct ctdb_iface); + assert(cd->data.iface != NULL); + fill_ctdb_iface(mem_ctx, cd->data.iface); + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + cd->data.key = talloc(mem_ctx, struct ctdb_key_data); + assert(cd->data.key != NULL); + fill_ctdb_key_data(mem_ctx, cd->data.key); + break; + + case CTDB_CONTROL_SET_DB_READONLY: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + cd->data.traverse_start_ext = talloc(mem_ctx, struct ctdb_traverse_start_ext); + assert(cd->data.traverse_start_ext != NULL); + fill_ctdb_traverse_start_ext(mem_ctx, cd->data.traverse_start_ext); + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + cd->data.traverse_all_ext = talloc(mem_ctx, struct ctdb_traverse_all_ext); + assert(cd->data.traverse_all_ext != NULL); + fill_ctdb_traverse_all_ext(mem_ctx, cd->data.traverse_all_ext); + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + break; + + case CTDB_CONTROL_DB_DETACH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_GET_NODES_FILE: + break; + + case CTDB_CONTROL_DB_FREEZE: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_THAW: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_TRANSACTION_START: + cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb); + assert(cd->data.transdb != NULL); + fill_ctdb_transdb(mem_ctx, cd->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_COMMIT: + cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb); + assert(cd->data.transdb != NULL); + fill_ctdb_transdb(mem_ctx, cd->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_CANCEL: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_PULL: + cd->data.pulldb_ext = talloc(mem_ctx, struct ctdb_pulldb_ext); + assert(cd->data.pulldb_ext != NULL); + fill_ctdb_pulldb_ext(mem_ctx, cd->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_START: + cd->data.pulldb_ext = talloc(mem_ctx, struct ctdb_pulldb_ext); + assert(cd->data.pulldb_ext != NULL); + fill_ctdb_pulldb_ext(mem_ctx, cd->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name != NULL); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + cd->data.pid_srvid = talloc(mem_ctx, struct ctdb_pid_srvid); + assert(cd->data.pid_srvid != NULL); + fill_ctdb_pid_srvid(mem_ctx, cd->data.pid_srvid); + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_DB_VACUUM: + cd->data.db_vacuum = talloc(mem_ctx, struct ctdb_db_vacuum); + assert(cd->data.db_vacuum != NULL); + fill_ctdb_db_vacuum(mem_ctx, cd->data.db_vacuum); + break; + + case CTDB_CONTROL_ECHO_DATA: + cd->data.echo_data = talloc(mem_ctx, struct ctdb_echo_data); + assert(cd->data.echo_data != NULL); + fill_ctdb_echo_data(mem_ctx, cd->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + + case CTDB_CONTROL_TCP_CLIENT_DISCONNECTED: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_TCP_CLIENT_PASSED: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + } +} + +void verify_ctdb_req_control_data(struct ctdb_req_control_data *cd, + struct ctdb_req_control_data *cd2) +{ + assert(cd->opcode == cd2->opcode); + + switch (cd->opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + assert(cd->data.pid == cd2->data.pid); + break; + + case CTDB_CONTROL_STATISTICS: + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_GETVNNMAP: + break; + + case CTDB_CONTROL_SETVNNMAP: + verify_ctdb_vnn_map(cd->data.vnnmap, cd2->data.vnnmap); + break; + + case CTDB_CONTROL_GET_DEBUG: + break; + + case CTDB_CONTROL_SET_DEBUG: + assert(cd->data.loglevel == cd2->data.loglevel); + break; + + case CTDB_CONTROL_GET_DBMAP: + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + assert(cd->data.recmode == cd2->data.recmode); + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_TRAVERSE_START: + verify_ctdb_traverse_start(cd->data.traverse_start, + cd2->data.traverse_start); + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + verify_ctdb_traverse_all(cd->data.traverse_all, + cd2->data.traverse_all); + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + verify_ctdb_rec_data(cd->data.rec_data, cd2->data.rec_data); + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DUMP_MEMORY: + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_TCP_ADD: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_TCP_REMOVE: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + verify_ctdb_tunable(cd->data.tunable, cd2->data.tunable); + break; + + case CTDB_CONTROL_GET_TUNABLE: + verify_ctdb_string(&cd->data.tun_var, &cd2->data.tun_var); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + verify_ctdb_node_flag_change(cd->data.flag_change, + cd2->data.flag_change); + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + verify_ctdb_sock_addr(cd->data.addr, cd2->data.addr); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + verify_ctdb_tickle_list(cd->data.tickles, cd2->data.tickles); + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info); + break; + + case CTDB_CONTROL_WIPE_DATABASE: + verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb); + break; + + case CTDB_CONTROL_UPTIME: + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info); + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info); + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + verify_ctdb_public_ip(cd->data.pubip, cd2->data.pubip); + break; + + case CTDB_CONTROL_TAKEOVER_IP: + verify_ctdb_public_ip(cd->data.pubip, cd2->data.pubip); + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + break; + + case CTDB_CONTROL_GET_NODEMAP: + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + verify_ctdb_traverse_start(cd->data.traverse_start, + cd2->data.traverse_start); + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + assert(cd->data.reclock_latency == cd2->data.reclock_latency); + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + assert(cd->data.role == cd2->data.role); + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + assert(cd->data.role == cd2->data.role); + break; + + case CTDB_CONTROL_SET_BAN_STATE: + verify_ctdb_ban_state(cd->data.ban_state, cd2->data.ban_state); + break; + + case CTDB_CONTROL_GET_BAN_STATE: + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + verify_ctdb_notify_data(cd->data.notify, cd2->data.notify); + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + assert(cd->data.srvid == cd2->data.srvid); + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + verify_ctdb_sock_addr(cd->data.addr, cd2->data.addr); + break; + + case CTDB_CONTROL_GET_IFACES: + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + verify_ctdb_iface(cd->data.iface, cd2->data.iface); + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + verify_ctdb_key_data(cd->data.key, cd2->data.key); + break; + + case CTDB_CONTROL_SET_DB_READONLY: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + verify_ctdb_traverse_start_ext(cd->data.traverse_start_ext, + cd2->data.traverse_start_ext); + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + verify_ctdb_traverse_all_ext(cd->data.traverse_all_ext, + cd2->data.traverse_all_ext); + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + break; + + case CTDB_CONTROL_DB_DETACH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_GET_NODES_FILE: + break; + + case CTDB_CONTROL_DB_FREEZE: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_THAW: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_TRANSACTION_START: + verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_COMMIT: + verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_CANCEL: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_PULL: + verify_ctdb_pulldb_ext(cd->data.pulldb_ext, + cd2->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_START: + verify_ctdb_pulldb_ext(cd->data.pulldb_ext, + cd2->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + verify_ctdb_pid_srvid(cd->data.pid_srvid, cd2->data.pid_srvid); + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_DB_VACUUM: + verify_ctdb_db_vacuum(cd->data.db_vacuum, cd2->data.db_vacuum); + break; + + case CTDB_CONTROL_ECHO_DATA: + verify_ctdb_echo_data(cd->data.echo_data, cd2->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + + case CTDB_CONTROL_TCP_CLIENT_DISCONNECTED: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_TCP_CLIENT_PASSED: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + } +} + +void fill_ctdb_req_control(TALLOC_CTX *mem_ctx, struct ctdb_req_control *c, + uint32_t opcode) +{ + c->opcode = opcode; + c->pad = rand32(); + c->srvid = rand64(); + c->client_id = rand32(); + c->flags = rand32(); + + fill_ctdb_req_control_data(mem_ctx, &c->rdata, opcode); +} + +void verify_ctdb_req_control(struct ctdb_req_control *c, + struct ctdb_req_control *c2) +{ + assert(c->opcode == c2->opcode); + assert(c->pad == c2->pad); + assert(c->srvid == c2->srvid); + assert(c->client_id == c2->client_id); + assert(c->flags == c2->flags); + + verify_ctdb_req_control_data(&c->rdata, &c2->rdata); +} + +void fill_ctdb_reply_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control_data *cd, + uint32_t opcode) +{ + cd->opcode = opcode; + + switch (opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + break; + + case CTDB_CONTROL_STATISTICS: + cd->data.stats = talloc(mem_ctx, struct ctdb_statistics); + assert(cd->data.stats != NULL); + fill_ctdb_statistics(mem_ctx, cd->data.stats); + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + fill_ctdb_string(mem_ctx, &cd->data.db_path); + assert(cd->data.db_path != NULL); + break; + + case CTDB_CONTROL_GETVNNMAP: + cd->data.vnnmap = talloc(mem_ctx, struct ctdb_vnn_map); + assert(cd->data.vnnmap != NULL); + fill_ctdb_vnn_map(mem_ctx, cd->data.vnnmap); + break; + + case CTDB_CONTROL_SETVNNMAP: + break; + + case CTDB_CONTROL_GET_DEBUG: + cd->data.loglevel = rand_int(5); + break; + + case CTDB_CONTROL_SET_DEBUG: + break; + + case CTDB_CONTROL_GET_DBMAP: + cd->data.dbmap = talloc(mem_ctx, struct ctdb_dbid_map); + assert(cd->data.dbmap != NULL); + fill_ctdb_dbid_map(mem_ctx, cd->data.dbmap); + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_TRAVERSE_START: + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + break; + + case CTDB_CONTROL_DUMP_MEMORY: + fill_ctdb_string(mem_ctx, &cd->data.mem_str); + assert(cd->data.mem_str); + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + break; + + case CTDB_CONTROL_TCP_ADD: + break; + + case CTDB_CONTROL_TCP_REMOVE: + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + break; + + case CTDB_CONTROL_GET_TUNABLE: + cd->data.tun_value = rand32(); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + cd->data.tun_var_list = talloc(mem_ctx, struct ctdb_var_list); + assert(cd->data.tun_var_list != NULL); + fill_ctdb_var_list(mem_ctx, cd->data.tun_var_list); + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + cd->data.tun_list = talloc(mem_ctx, struct ctdb_tunable_list); + assert(cd->data.tun_list != NULL); + fill_ctdb_tunable_list(mem_ctx, cd->data.tun_list); + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + cd->data.tickles = talloc(mem_ctx, struct ctdb_tickle_list); + assert(cd->data.tickles != NULL); + fill_ctdb_tickle_list(mem_ctx, cd->data.tickles); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + break; + + case CTDB_CONTROL_WIPE_DATABASE: + break; + + case CTDB_CONTROL_UPTIME: + cd->data.uptime = talloc(mem_ctx, struct ctdb_uptime); + assert(cd->data.uptime != NULL); + fill_ctdb_uptime(mem_ctx, cd->data.uptime); + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + cd->data.caps = rand32(); + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + break; + + case CTDB_CONTROL_TAKEOVER_IP: + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + cd->data.pubip_list = talloc(mem_ctx, struct ctdb_public_ip_list); + assert(cd->data.pubip_list != NULL); + fill_ctdb_public_ip_list(mem_ctx, cd->data.pubip_list); + break; + + case CTDB_CONTROL_GET_NODEMAP: + cd->data.nodemap = talloc(mem_ctx, struct ctdb_node_map); + assert(cd->data.nodemap != NULL); + fill_ctdb_node_map(mem_ctx, cd->data.nodemap); + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + fill_ctdb_string(mem_ctx, &cd->data.reclock_file); + assert(cd->data.reclock_file != NULL); + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + break; + + case CTDB_CONTROL_SET_BAN_STATE: + break; + + case CTDB_CONTROL_GET_BAN_STATE: + cd->data.ban_state = talloc(mem_ctx, struct ctdb_ban_state); + assert(cd->data.ban_state != NULL); + fill_ctdb_ban_state(mem_ctx, cd->data.ban_state); + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + cd->data.seqnum = rand64(); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + fill_ctdb_string(mem_ctx, &cd->data.reason); + assert(cd->data.reason != NULL); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + cd->data.ipinfo = talloc(mem_ctx, struct ctdb_public_ip_info); + assert(cd->data.ipinfo != NULL); + fill_ctdb_public_ip_info(mem_ctx, cd->data.ipinfo); + break; + + case CTDB_CONTROL_GET_IFACES: + cd->data.iface_list = talloc(mem_ctx, struct ctdb_iface_list); + assert(cd->data.iface_list != NULL); + fill_ctdb_iface_list(mem_ctx, cd->data.iface_list); + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + cd->data.stats_list = talloc(mem_ctx, struct ctdb_statistics_list); + assert(cd->data.stats_list != NULL); + fill_ctdb_statistics_list(mem_ctx, cd->data.stats_list); + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + break; + + case CTDB_CONTROL_SET_DB_READONLY: + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + cd->data.dbstats = talloc(mem_ctx, struct ctdb_db_statistics); + assert(cd->data.dbstats != NULL); + fill_ctdb_db_statistics(mem_ctx, cd->data.dbstats); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + cd->data.runstate = rand32(); + break; + + case CTDB_CONTROL_DB_DETACH: + break; + + case CTDB_CONTROL_GET_NODES_FILE: + cd->data.nodemap = talloc(mem_ctx, struct ctdb_node_map); + assert(cd->data.nodemap != NULL); + fill_ctdb_node_map(mem_ctx, cd->data.nodemap); + break; + + case CTDB_CONTROL_DB_PULL: + cd->data.num_records = rand32(); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + cd->data.num_records = rand32(); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + cd->data.tdb_flags = rand32(); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + break; + + case CTDB_CONTROL_DB_VACUUM: + break; + + case CTDB_CONTROL_ECHO_DATA: + cd->data.echo_data = talloc(mem_ctx, struct ctdb_echo_data); + assert(cd->data.echo_data != NULL); + fill_ctdb_echo_data(mem_ctx, cd->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + + case CTDB_CONTROL_TCP_CLIENT_DISCONNECTED: + break; + + case CTDB_CONTROL_TCP_CLIENT_PASSED: + break; + } +} + +void verify_ctdb_reply_control_data(struct ctdb_reply_control_data *cd, + struct ctdb_reply_control_data *cd2) +{ + assert(cd->opcode == cd2->opcode); + + switch (cd->opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + break; + + case CTDB_CONTROL_STATISTICS: + verify_ctdb_statistics(cd->data.stats, cd2->data.stats); + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + verify_ctdb_string(&cd->data.db_path, &cd2->data.db_path); + break; + + case CTDB_CONTROL_GETVNNMAP: + verify_ctdb_vnn_map(cd->data.vnnmap, cd2->data.vnnmap); + break; + + case CTDB_CONTROL_SETVNNMAP: + break; + + case CTDB_CONTROL_GET_DEBUG: + assert(cd->data.loglevel == cd2->data.loglevel); + break; + + case CTDB_CONTROL_SET_DEBUG: + break; + + case CTDB_CONTROL_GET_DBMAP: + verify_ctdb_dbid_map(cd->data.dbmap, cd2->data.dbmap); + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_TRAVERSE_START: + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + break; + + case CTDB_CONTROL_DUMP_MEMORY: + verify_ctdb_string(&cd->data.mem_str, &cd2->data.mem_str); + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + break; + + case CTDB_CONTROL_TCP_ADD: + break; + + case CTDB_CONTROL_TCP_REMOVE: + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + break; + + case CTDB_CONTROL_GET_TUNABLE: + assert(cd->data.tun_value == cd2->data.tun_value); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + verify_ctdb_var_list(cd->data.tun_var_list, + cd2->data.tun_var_list); + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + verify_ctdb_tunable_list(cd->data.tun_list, cd2->data.tun_list); + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + verify_ctdb_tickle_list(cd->data.tickles, cd2->data.tickles); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + break; + + case CTDB_CONTROL_WIPE_DATABASE: + break; + + case CTDB_CONTROL_UPTIME: + verify_ctdb_uptime(cd->data.uptime, cd2->data.uptime); + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + assert(cd->data.caps == cd2->data.caps); + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + break; + + case CTDB_CONTROL_TAKEOVER_IP: + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + verify_ctdb_public_ip_list(cd->data.pubip_list, + cd2->data.pubip_list); + break; + + case CTDB_CONTROL_GET_NODEMAP: + verify_ctdb_node_map(cd->data.nodemap, cd2->data.nodemap); + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + verify_ctdb_string(&cd->data.reclock_file, + &cd2->data.reclock_file); + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + break; + + case CTDB_CONTROL_SET_BAN_STATE: + break; + + case CTDB_CONTROL_GET_BAN_STATE: + verify_ctdb_ban_state(cd->data.ban_state, cd2->data.ban_state); + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + assert(cd->data.seqnum == cd2->data.seqnum); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + verify_ctdb_string(&cd->data.reason, &cd2->data.reason); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + verify_ctdb_public_ip_info(cd->data.ipinfo, cd2->data.ipinfo); + break; + + case CTDB_CONTROL_GET_IFACES: + verify_ctdb_iface_list(cd->data.iface_list, + cd2->data.iface_list); + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + verify_ctdb_statistics_list(cd->data.stats_list, + cd2->data.stats_list); + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + break; + + case CTDB_CONTROL_SET_DB_READONLY: + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + verify_ctdb_db_statistics(cd->data.dbstats, cd2->data.dbstats); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + assert(cd->data.runstate == cd2->data.runstate); + break; + + case CTDB_CONTROL_DB_DETACH: + break; + + case CTDB_CONTROL_GET_NODES_FILE: + verify_ctdb_node_map(cd->data.nodemap, cd2->data.nodemap); + break; + + case CTDB_CONTROL_DB_PULL: + assert(cd->data.num_records == cd2->data.num_records); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + assert(cd->data.num_records == cd2->data.num_records); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + assert(cd->data.tdb_flags == cd2->data.tdb_flags); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + break; + + case CTDB_CONTROL_DB_VACUUM: + break; + + case CTDB_CONTROL_ECHO_DATA: + verify_ctdb_echo_data(cd->data.echo_data, cd2->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + + case CTDB_CONTROL_TCP_CLIENT_DISCONNECTED: + break; + + case CTDB_CONTROL_TCP_CLIENT_PASSED: + break; + } +} + +void fill_ctdb_reply_control(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control *c, uint32_t opcode) +{ + c->status = -rand_int(2); + if (c->status == 0) { + c->errmsg = NULL; + fill_ctdb_reply_control_data(mem_ctx, &c->rdata, opcode); + } else { + fill_ctdb_string(mem_ctx, &c->errmsg); + } +} + +void verify_ctdb_reply_control(struct ctdb_reply_control *c, + struct ctdb_reply_control *c2) +{ + assert(c->status == c2->status); + verify_ctdb_string(&c->errmsg, &c2->errmsg); + if (c->status == 0) { + verify_ctdb_reply_control_data(&c->rdata, &c2->rdata); + } +} + +void fill_ctdb_message_data(TALLOC_CTX *mem_ctx, union ctdb_message_data *md, + uint64_t srvid) +{ + switch (srvid) { + case CTDB_SRVID_RECONFIGURE: + case CTDB_SRVID_GETLOG: + case CTDB_SRVID_CLEARLOG: + case CTDB_SRVID_RELOAD_NODES: + break; + + case CTDB_SRVID_ELECTION: + md->election = talloc(mem_ctx, struct ctdb_election_message); + assert(md->election != NULL); + fill_ctdb_election_message(md->election, md->election); + break; + + case CTDB_SRVID_RELEASE_IP: + case CTDB_SRVID_TAKE_IP: + fill_ctdb_string(mem_ctx, &md->ipaddr); + break; + + case CTDB_SRVID_SET_NODE_FLAGS: + case CTDB_SRVID_PUSH_NODE_FLAGS: + md->flag_change = talloc(mem_ctx, + struct ctdb_node_flag_change); + assert(md->flag_change != NULL); + fill_ctdb_node_flag_change(md->flag_change, md->flag_change); + break; + + case CTDB_SRVID_RECD_UPDATE_IP: + md->pubip = talloc(mem_ctx, struct ctdb_public_ip); + assert(md->pubip != NULL); + fill_ctdb_public_ip(md->pubip, md->pubip); + break; + + case CTDB_SRVID_VACUUM_FETCH: + md->recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(md->recbuf != NULL); + fill_ctdb_rec_buffer(md->recbuf, md->recbuf); + break; + + case CTDB_SRVID_DETACH_DATABASE: + md->db_id = rand32(); + break; + + case CTDB_SRVID_MEM_DUMP: + case CTDB_SRVID_TAKEOVER_RUN: + md->msg = talloc(mem_ctx, struct ctdb_srvid_message); + assert(md->msg != NULL); + fill_ctdb_srvid_message(md->msg, md->msg); + break; + + case CTDB_SRVID_LEADER: + case CTDB_SRVID_BANNING: + case CTDB_SRVID_REBALANCE_NODE: + md->pnn = rand32(); + break; + + case CTDB_SRVID_DISABLE_TAKEOVER_RUNS: + case CTDB_SRVID_DISABLE_RECOVERIES: + md->disable = talloc(mem_ctx, struct ctdb_disable_message); + assert(md->disable != NULL); + fill_ctdb_disable_message(md->disable, md->disable); + break; + + case CTDB_SRVID_DISABLE_IP_CHECK: + md->timeout = rand32(); + break; + + default: + abort(); + } +} + +void verify_ctdb_message_data(union ctdb_message_data *md, + union ctdb_message_data *md2, uint64_t srvid) +{ + switch (srvid) { + case CTDB_SRVID_RECONFIGURE: + case CTDB_SRVID_GETLOG: + case CTDB_SRVID_CLEARLOG: + case CTDB_SRVID_RELOAD_NODES: + break; + + case CTDB_SRVID_ELECTION: + verify_ctdb_election_message(md->election, md2->election); + break; + + case CTDB_SRVID_RELEASE_IP: + case CTDB_SRVID_TAKE_IP: + verify_ctdb_string(&md->ipaddr, &md2->ipaddr); + break; + + case CTDB_SRVID_SET_NODE_FLAGS: + case CTDB_SRVID_PUSH_NODE_FLAGS: + verify_ctdb_node_flag_change(md->flag_change, + md2->flag_change); + break; + + case CTDB_SRVID_RECD_UPDATE_IP: + verify_ctdb_public_ip(md->pubip, md2->pubip); + break; + + case CTDB_SRVID_VACUUM_FETCH: + verify_ctdb_rec_buffer(md->recbuf, md2->recbuf); + break; + + case CTDB_SRVID_DETACH_DATABASE: + assert(md->db_id == md2->db_id); + break; + + case CTDB_SRVID_MEM_DUMP: + case CTDB_SRVID_TAKEOVER_RUN: + verify_ctdb_srvid_message(md->msg, md2->msg); + break; + + case CTDB_SRVID_LEADER: + case CTDB_SRVID_BANNING: + case CTDB_SRVID_REBALANCE_NODE: + assert(md->pnn == md2->pnn); + break; + + case CTDB_SRVID_DISABLE_TAKEOVER_RUNS: + case CTDB_SRVID_DISABLE_RECOVERIES: + verify_ctdb_disable_message(md->disable, md2->disable); + break; + + case CTDB_SRVID_DISABLE_IP_CHECK: + assert(md->timeout == md2->timeout); + break; + + default: + abort(); + } +} + +void fill_ctdb_req_message(TALLOC_CTX *mem_ctx, struct ctdb_req_message *c, + uint64_t srvid) +{ + c->srvid = srvid; + fill_ctdb_message_data(mem_ctx, &c->data, srvid); +} + +void verify_ctdb_req_message(struct ctdb_req_message *c, + struct ctdb_req_message *c2) +{ + assert(c->srvid == c2->srvid); + verify_ctdb_message_data(&c->data, &c2->data, c->srvid); +} + +void fill_ctdb_req_message_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_message_data *c) +{ + c->srvid = rand64(); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_req_message_data(struct ctdb_req_message_data *c, + struct ctdb_req_message_data *c2) +{ + assert(c->srvid == c2->srvid); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_req_keepalive(TALLOC_CTX *mem_ctx, + struct ctdb_req_keepalive *c) +{ + c->version = rand32(); + c->uptime = rand32(); +} + +void verify_ctdb_req_keepalive(struct ctdb_req_keepalive *c, + struct ctdb_req_keepalive *c2) +{ + assert(c->version == c2->version); + assert(c->uptime == c2->uptime); +} + +void fill_ctdb_req_tunnel(TALLOC_CTX *mem_ctx, struct ctdb_req_tunnel *c) +{ + c->tunnel_id = rand64(); + c->flags = rand32(); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_req_tunnel(struct ctdb_req_tunnel *c, + struct ctdb_req_tunnel *c2) +{ + assert(c->tunnel_id == c2->tunnel_id); + assert(c->flags == c2->flags); + verify_tdb_data(&c->data, &c2->data); +} diff --git a/ctdb/tests/src/protocol_common_ctdb.h b/ctdb/tests/src/protocol_common_ctdb.h new file mode 100644 index 0000000..0681089 --- /dev/null +++ b/ctdb/tests/src/protocol_common_ctdb.h @@ -0,0 +1,101 @@ +/* + protocol tests - ctdb protocol + + Copyright (C) Amitay Isaacs 2017 + + 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/>. +*/ + +#ifndef __CTDB_PROTOCOL_COMMON_CTDB_H__ +#define __CTDB_PROTOCOL_COMMON_CTDB_H__ + +#include "replace.h" +#include "system/network.h" + +#include <talloc.h> +#include <tdb.h> + +#include "protocol/protocol.h" + +void fill_ctdb_req_header(struct ctdb_req_header *h); +void verify_ctdb_req_header(struct ctdb_req_header *h, + struct ctdb_req_header *h2); + +void fill_ctdb_req_call(TALLOC_CTX *mem_ctx, struct ctdb_req_call *c); +void verify_ctdb_req_call(struct ctdb_req_call *c, struct ctdb_req_call *c2); + +void fill_ctdb_reply_call(TALLOC_CTX *mem_ctx, struct ctdb_reply_call *c); +void verify_ctdb_reply_call(struct ctdb_reply_call *c, + struct ctdb_reply_call *c2); + +void fill_ctdb_reply_error(TALLOC_CTX *mem_ctx, struct ctdb_reply_error *c); +void verify_ctdb_reply_error(struct ctdb_reply_error *c, + struct ctdb_reply_error *c2); + +void fill_ctdb_req_dmaster(TALLOC_CTX *mem_ctx, struct ctdb_req_dmaster *c); +void verify_ctdb_req_dmaster(struct ctdb_req_dmaster *c, + struct ctdb_req_dmaster *c2); + +void fill_ctdb_reply_dmaster(TALLOC_CTX *mem_ctx, + struct ctdb_reply_dmaster *c); +void verify_ctdb_reply_dmaster(struct ctdb_reply_dmaster *c, + struct ctdb_reply_dmaster *c2); + +void fill_ctdb_req_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_control_data *cd, + uint32_t opcode); +void verify_ctdb_req_control_data(struct ctdb_req_control_data *cd, + struct ctdb_req_control_data *cd2); + +void fill_ctdb_req_control(TALLOC_CTX *mem_ctx, struct ctdb_req_control *c, + uint32_t opcode); +void verify_ctdb_req_control(struct ctdb_req_control *c, + struct ctdb_req_control *c2); + +void fill_ctdb_reply_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control_data *cd, + uint32_t opcode); +void verify_ctdb_reply_control_data(struct ctdb_reply_control_data *cd, + struct ctdb_reply_control_data *cd2); + +void fill_ctdb_reply_control(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control *c, uint32_t opcode); +void verify_ctdb_reply_control(struct ctdb_reply_control *c, + struct ctdb_reply_control *c2); + +void fill_ctdb_message_data(TALLOC_CTX *mem_ctx, union ctdb_message_data *md, + uint64_t srvid); +void verify_ctdb_message_data(union ctdb_message_data *md, + union ctdb_message_data *md2, uint64_t srvid); + +void fill_ctdb_req_message(TALLOC_CTX *mem_ctx, struct ctdb_req_message *c, + uint64_t srvid); +void verify_ctdb_req_message(struct ctdb_req_message *c, + struct ctdb_req_message *c2); + +void fill_ctdb_req_message_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_message_data *c); +void verify_ctdb_req_message_data(struct ctdb_req_message_data *c, + struct ctdb_req_message_data *c2); + +void fill_ctdb_req_keepalive(TALLOC_CTX *mem_ctx, + struct ctdb_req_keepalive *c); +void verify_ctdb_req_keepalive(struct ctdb_req_keepalive *c, + struct ctdb_req_keepalive *c2); + +void fill_ctdb_req_tunnel(TALLOC_CTX *mem_ctx, struct ctdb_req_tunnel *c); +void verify_ctdb_req_tunnel(struct ctdb_req_tunnel *c, + struct ctdb_req_tunnel *c2); + +#endif /* __CTDB_PROTOCOL_COMMON_CTDB_H__ */ diff --git a/ctdb/tests/src/protocol_ctdb_compat_test.c b/ctdb/tests/src/protocol_ctdb_compat_test.c new file mode 100644 index 0000000..fc9f82e --- /dev/null +++ b/ctdb/tests/src/protocol_ctdb_compat_test.c @@ -0,0 +1,1270 @@ +/* + ctdb protocol backward compatibility test + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_header.c" +#include "protocol/protocol_call.c" +#include "protocol/protocol_control.c" +#include "protocol/protocol_message.c" +#include "protocol/protocol_keepalive.c" +#include "protocol/protocol_tunnel.c" + +#include "tests/src/protocol_common.h" +#include "tests/src/protocol_common_ctdb.h" + +#define COMPAT_TEST_FUNC(NAME) test_ ##NAME## _compat +#define OLD_LEN_FUNC(NAME) NAME## _len_old +#define OLD_PUSH_FUNC(NAME) NAME## _push_old +#define OLD_PULL_FUNC(NAME) NAME## _pull_old + +#define COMPAT_CTDB1_TEST(TYPE, NAME) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(&p); \ + buflen1 = LEN_FUNC(NAME)(&p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + PUSH_FUNC(NAME)(&p, buf1, &np); \ + OLD_PUSH_FUNC(NAME)(&p, buf2); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &p1, &np); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &p2); \ + assert(ret == 0); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB4_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB5_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p, opcode); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB6_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p, opcode); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, opcode, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, opcode, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB7_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(uint64_t srvid) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p, srvid); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + + +static size_t ctdb_req_header_len_old(struct ctdb_req_header *in) +{ + return sizeof(struct ctdb_req_header); +} + +static void ctdb_req_header_push_old(struct ctdb_req_header *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_req_header)); +} + +static int ctdb_req_header_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *out) +{ + if (buflen < sizeof(struct ctdb_req_header)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_req_header)); + return 0; +} + +struct ctdb_req_call_wire { + struct ctdb_req_header hdr; + uint32_t flags; + uint32_t db_id; + uint32_t callid; + uint32_t hopcount; + uint32_t keylen; + uint32_t calldatalen; + uint8_t data[1]; /* key[] followed by calldata[] */ +}; + +static size_t ctdb_req_call_len_old(struct ctdb_req_header *h, + struct ctdb_req_call *c) +{ + return offsetof(struct ctdb_req_call_wire, data) + + ctdb_tdb_data_len(&c->key) + + ctdb_tdb_data_len(&c->calldata); +} + +static int ctdb_req_call_push_old(struct ctdb_req_header *h, + struct ctdb_req_call *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_call_wire *wire = + (struct ctdb_req_call_wire *)buf; + size_t length, np; + + if (c->key.dsize == 0) { + return EINVAL; + } + + length = ctdb_req_call_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->flags = c->flags; + wire->db_id = c->db_id; + wire->callid = c->callid; + wire->hopcount = c->hopcount; + wire->keylen = ctdb_tdb_data_len(&c->key); + wire->calldatalen = ctdb_tdb_data_len(&c->calldata); + ctdb_tdb_data_push(&c->key, wire->data, &np); + ctdb_tdb_data_push(&c->calldata, wire->data + wire->keylen, &np); + + return 0; +} + +static int ctdb_req_call_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_call *c) +{ + struct ctdb_req_call_wire *wire = + (struct ctdb_req_call_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_call_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->calldatalen > buflen) { + return EMSGSIZE; + } + if (length + wire->keylen < length) { + return EMSGSIZE; + } + if (length + wire->keylen + wire->calldatalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->keylen + wire->calldatalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->flags = wire->flags; + c->db_id = wire->db_id; + c->callid = wire->callid; + c->hopcount = wire->hopcount; + + ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key, + &np); + if (ret != 0) { + return ret; + } + + ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->calldatalen, + mem_ctx, &c->calldata, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_call_wire { + struct ctdb_req_header hdr; + uint32_t status; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_reply_call_len_old(struct ctdb_req_header *h, + struct ctdb_reply_call *c) +{ + return offsetof(struct ctdb_reply_call_wire, data) + + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_reply_call_push_old(struct ctdb_req_header *h, + struct ctdb_reply_call *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_call_wire *wire = + (struct ctdb_reply_call_wire *)buf; + size_t length, np; + + length = ctdb_reply_call_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->status = c->status; + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->data, wire->data, &np); + + return 0; +} + +static int ctdb_reply_call_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_call *c) +{ + struct ctdb_reply_call_wire *wire = + (struct ctdb_reply_call_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_call_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->status = wire->status; + + ret = ctdb_tdb_data_pull(wire->data, wire->datalen, mem_ctx, &c->data, + &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_error_wire { + struct ctdb_req_header hdr; + uint32_t status; + uint32_t msglen; + uint8_t msg[1]; +}; + +static size_t ctdb_reply_error_len_old(struct ctdb_req_header *h, + struct ctdb_reply_error *c) +{ + return offsetof(struct ctdb_reply_error_wire, msg) + + ctdb_tdb_data_len(&c->msg); +} + +static int ctdb_reply_error_push_old(struct ctdb_req_header *h, + struct ctdb_reply_error *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_error_wire *wire = + (struct ctdb_reply_error_wire *)buf; + size_t length, np; + + length = ctdb_reply_error_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->status = c->status; + wire->msglen = ctdb_tdb_data_len(&c->msg); + ctdb_tdb_data_push(&c->msg, wire->msg, &np); + + return 0; +} + +static int ctdb_reply_error_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_error *c) +{ + struct ctdb_reply_error_wire *wire = + (struct ctdb_reply_error_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_error_wire, msg); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->msglen > buflen) { + return EMSGSIZE; + } + if (length + wire->msglen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->msglen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->status = wire->status; + + ret = ctdb_tdb_data_pull(wire->msg, wire->msglen, mem_ctx, &c->msg, + &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_dmaster_wire { + struct ctdb_req_header hdr; + uint32_t db_id; + uint64_t rsn; + uint32_t dmaster; + uint32_t keylen; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_dmaster_len_old(struct ctdb_req_header *h, + struct ctdb_req_dmaster *c) +{ + return offsetof(struct ctdb_req_dmaster_wire, data) + + ctdb_tdb_data_len(&c->key) + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_req_dmaster_push_old(struct ctdb_req_header *h, + struct ctdb_req_dmaster *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_dmaster_wire *wire = + (struct ctdb_req_dmaster_wire *)buf; + size_t length, np; + + length = ctdb_req_dmaster_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->db_id = c->db_id; + wire->rsn = c->rsn; + wire->dmaster = c->dmaster; + wire->keylen = ctdb_tdb_data_len(&c->key); + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->key, wire->data, &np); + ctdb_tdb_data_push(&c->data, wire->data + wire->keylen, &np); + + return 0; +} + +static int ctdb_req_dmaster_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_dmaster *c) +{ + struct ctdb_req_dmaster_wire *wire = + (struct ctdb_req_dmaster_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_dmaster_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->keylen < length) { + return EMSGSIZE; + } + if (length + wire->keylen + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->keylen + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->db_id = wire->db_id; + c->rsn = wire->rsn; + c->dmaster = wire->dmaster; + + ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key, + &np); + if (ret != 0) { + return ret; + } + + ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->datalen, + mem_ctx, &c->data, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_dmaster_wire { + struct ctdb_req_header hdr; + uint32_t db_id; + uint64_t rsn; + uint32_t keylen; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_reply_dmaster_len_old(struct ctdb_req_header *h, + struct ctdb_reply_dmaster *c) +{ + return offsetof(struct ctdb_reply_dmaster_wire, data) + + ctdb_tdb_data_len(&c->key) + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_reply_dmaster_push_old(struct ctdb_req_header *h, + struct ctdb_reply_dmaster *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_dmaster_wire *wire = + (struct ctdb_reply_dmaster_wire *)buf; + size_t length, np; + + length = ctdb_reply_dmaster_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->db_id = c->db_id; + wire->rsn = c->rsn; + wire->keylen = ctdb_tdb_data_len(&c->key); + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->key, wire->data, &np); + ctdb_tdb_data_push(&c->data, wire->data + wire->keylen, &np); + + return 0; +} + +static int ctdb_reply_dmaster_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_dmaster *c) +{ + struct ctdb_reply_dmaster_wire *wire = + (struct ctdb_reply_dmaster_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_dmaster_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->keylen < length) { + return EMSGSIZE; + } + if (length + wire->keylen + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->keylen + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->db_id = wire->db_id; + c->rsn = wire->rsn; + + ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key, + &np); + if (ret != 0) { + return ret; + } + + ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->datalen, + mem_ctx, &c->data, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_control_wire { + struct ctdb_req_header hdr; + uint32_t opcode; + uint32_t pad; + uint64_t srvid; + uint32_t client_id; + uint32_t flags; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_control_len_old(struct ctdb_req_header *h, + struct ctdb_req_control *c) +{ + return offsetof(struct ctdb_req_control_wire, data) + + ctdb_req_control_data_len(&c->rdata); +} + +static int ctdb_req_control_push_old(struct ctdb_req_header *h, + struct ctdb_req_control *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_control_wire *wire = + (struct ctdb_req_control_wire *)buf; + size_t length, np; + + length = ctdb_req_control_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->opcode = c->opcode; + wire->pad = c->pad; + wire->srvid = c->srvid; + wire->client_id = c->client_id; + wire->flags = c->flags; + + wire->datalen = ctdb_req_control_data_len(&c->rdata); + ctdb_req_control_data_push(&c->rdata, wire->data, &np); + + return 0; +} + +static int ctdb_req_control_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_control *c) +{ + struct ctdb_req_control_wire *wire = + (struct ctdb_req_control_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_control_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->opcode = wire->opcode; + c->pad = wire->pad; + c->srvid = wire->srvid; + c->client_id = wire->client_id; + c->flags = wire->flags; + + ret = ctdb_req_control_data_pull(wire->data, wire->datalen, + c->opcode, mem_ctx, &c->rdata, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_control_wire { + struct ctdb_req_header hdr; + int32_t status; + uint32_t datalen; + uint32_t errorlen; + uint8_t data[1]; +}; + +static size_t ctdb_reply_control_len_old(struct ctdb_req_header *h, + struct ctdb_reply_control *c) +{ + return offsetof(struct ctdb_reply_control_wire, data) + + (c->status == 0 ? + ctdb_reply_control_data_len(&c->rdata) : + ctdb_string_len(&c->errmsg)); +} + +static int ctdb_reply_control_push_old(struct ctdb_req_header *h, + struct ctdb_reply_control *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_control_wire *wire = + (struct ctdb_reply_control_wire *)buf; + size_t length, np; + + length = ctdb_reply_control_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->status = c->status; + + if (c->status == 0) { + wire->datalen = ctdb_reply_control_data_len(&c->rdata); + wire->errorlen = 0; + ctdb_reply_control_data_push(&c->rdata, wire->data, &np); + } else { + wire->datalen = 0; + wire->errorlen = ctdb_string_len(&c->errmsg); + ctdb_string_push(&c->errmsg, wire->data + wire->datalen, &np); + } + + return 0; +} + +static int ctdb_reply_control_pull_old(uint8_t *buf, size_t buflen, + uint32_t opcode, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_control *c) +{ + struct ctdb_reply_control_wire *wire = + (struct ctdb_reply_control_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_control_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen || wire->errorlen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (length + wire->datalen + wire->errorlen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen + wire->errorlen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->status = wire->status; + + if (c->status != -1) { + ret = ctdb_reply_control_data_pull(wire->data, wire->datalen, + opcode, mem_ctx, + &c->rdata, &np); + if (ret != 0) { + return ret; + } + } + + ret = ctdb_string_pull(wire->data + wire->datalen, wire->errorlen, + mem_ctx, &c->errmsg, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_message_wire { + struct ctdb_req_header hdr; + uint64_t srvid; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_message_len_old(struct ctdb_req_header *h, + struct ctdb_req_message *c) +{ + return offsetof(struct ctdb_req_message_wire, data) + + ctdb_message_data_len(&c->data, c->srvid); +} + +static int ctdb_req_message_push_old(struct ctdb_req_header *h, + struct ctdb_req_message *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + + length = ctdb_req_message_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->srvid = c->srvid; + wire->datalen = ctdb_message_data_len(&c->data, c->srvid); + ctdb_message_data_push(&c->data, c->srvid, wire->data, &np); + + return 0; +} + +static int ctdb_req_message_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_message *c) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_message_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->srvid = wire->srvid; + ret = ctdb_message_data_pull(wire->data, wire->datalen, wire->srvid, + mem_ctx, &c->data, &np); + return ret; +} + +static size_t ctdb_req_message_data_len_old(struct ctdb_req_header *h, + struct ctdb_req_message_data *c) +{ + return offsetof(struct ctdb_req_message_wire, data) + + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_req_message_data_push_old(struct ctdb_req_header *h, + struct ctdb_req_message_data *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + + length = ctdb_req_message_data_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push(h, (uint8_t *)&wire->hdr, &np); + + wire->srvid = c->srvid; + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->data, wire->data, &np); + + return 0; +} + +static int ctdb_req_message_data_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_message_data *c) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_message_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->srvid = wire->srvid; + + ret = ctdb_tdb_data_pull(wire->data, wire->datalen, + mem_ctx, &c->data, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_keepalive_wire { + struct ctdb_req_header hdr; + uint32_t version; + uint32_t uptime; +}; + +static size_t ctdb_req_keepalive_len_old(struct ctdb_req_header *h, + struct ctdb_req_keepalive *c) +{ + return sizeof(struct ctdb_req_keepalive_wire); +} + +static int ctdb_req_keepalive_push_old(struct ctdb_req_header *h, + struct ctdb_req_keepalive *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_keepalive_wire *wire = + (struct ctdb_req_keepalive_wire *)buf; + size_t length; + + length = ctdb_req_keepalive_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->version = c->version; + wire->uptime = c->uptime; + + return 0; +} + +static int ctdb_req_keepalive_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_keepalive *c) +{ + struct ctdb_req_keepalive_wire *wire = + (struct ctdb_req_keepalive_wire *)buf; + size_t length; + int ret; + + length = sizeof(struct ctdb_req_keepalive_wire); + if (buflen < length) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->version = wire->version; + c->uptime = wire->uptime; + + return 0; +} + +struct ctdb_req_tunnel_wire { + struct ctdb_req_header hdr; + uint64_t tunnel_id; + uint32_t flags; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_tunnel_len_old(struct ctdb_req_header *h, + struct ctdb_req_tunnel *c) +{ + return offsetof(struct ctdb_req_tunnel_wire, data) + + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_req_tunnel_push_old(struct ctdb_req_header *h, + struct ctdb_req_tunnel *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_tunnel_wire *wire = + (struct ctdb_req_tunnel_wire *)buf; + size_t length, np; + + length = ctdb_req_tunnel_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->tunnel_id = c->tunnel_id; + wire->flags = c->flags; + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->data, wire->data, &np); + + return 0; +} + +static int ctdb_req_tunnel_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_tunnel *c) +{ + struct ctdb_req_tunnel_wire *wire = + (struct ctdb_req_tunnel_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_tunnel_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->tunnel_id = wire->tunnel_id; + c->flags = wire->flags; + + ret = ctdb_tdb_data_pull(wire->data, wire->datalen, mem_ctx, &c->data, + &np); + if (ret != 0) { + return ret; + } + + return 0; +} + + +COMPAT_CTDB1_TEST(struct ctdb_req_header, ctdb_req_header); + +COMPAT_CTDB4_TEST(struct ctdb_req_call, ctdb_req_call, CTDB_REQ_CALL); +COMPAT_CTDB4_TEST(struct ctdb_reply_call, ctdb_reply_call, CTDB_REPLY_CALL); +COMPAT_CTDB4_TEST(struct ctdb_reply_error, ctdb_reply_error, CTDB_REPLY_ERROR); +COMPAT_CTDB4_TEST(struct ctdb_req_dmaster, ctdb_req_dmaster, CTDB_REQ_DMASTER); +COMPAT_CTDB4_TEST(struct ctdb_reply_dmaster, ctdb_reply_dmaster, CTDB_REPLY_DMASTER); + +COMPAT_CTDB5_TEST(struct ctdb_req_control, ctdb_req_control, CTDB_REQ_CONTROL); +COMPAT_CTDB6_TEST(struct ctdb_reply_control, ctdb_reply_control, CTDB_REPLY_CONTROL); + +COMPAT_CTDB7_TEST(struct ctdb_req_message, ctdb_req_message, CTDB_REQ_MESSAGE); +COMPAT_CTDB4_TEST(struct ctdb_req_message_data, ctdb_req_message_data, CTDB_REQ_MESSAGE); + +COMPAT_CTDB4_TEST(struct ctdb_req_keepalive, ctdb_req_keepalive, CTDB_REQ_KEEPALIVE); +COMPAT_CTDB4_TEST(struct ctdb_req_tunnel, ctdb_req_tunnel, CTDB_REQ_TUNNEL); + +#define NUM_CONTROLS 151 + +static void protocol_ctdb_compat_test(void) +{ + uint32_t opcode; + uint64_t test_srvid[] = { + CTDB_SRVID_BANNING, + CTDB_SRVID_ELECTION, + CTDB_SRVID_LEADER, + CTDB_SRVID_RECONFIGURE, + CTDB_SRVID_RELEASE_IP, + CTDB_SRVID_TAKE_IP, + CTDB_SRVID_SET_NODE_FLAGS, + CTDB_SRVID_RECD_UPDATE_IP, + CTDB_SRVID_VACUUM_FETCH, + CTDB_SRVID_DETACH_DATABASE, + CTDB_SRVID_MEM_DUMP, + CTDB_SRVID_GETLOG, + CTDB_SRVID_CLEARLOG, + CTDB_SRVID_PUSH_NODE_FLAGS, + CTDB_SRVID_RELOAD_NODES, + CTDB_SRVID_TAKEOVER_RUN, + CTDB_SRVID_REBALANCE_NODE, + CTDB_SRVID_DISABLE_TAKEOVER_RUNS, + CTDB_SRVID_DISABLE_RECOVERIES, + CTDB_SRVID_DISABLE_IP_CHECK, + }; + unsigned int i; + + COMPAT_TEST_FUNC(ctdb_req_header)(); + + COMPAT_TEST_FUNC(ctdb_req_call)(); + COMPAT_TEST_FUNC(ctdb_reply_call)(); + COMPAT_TEST_FUNC(ctdb_reply_error)(); + COMPAT_TEST_FUNC(ctdb_req_dmaster)(); + COMPAT_TEST_FUNC(ctdb_reply_dmaster)(); + + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + COMPAT_TEST_FUNC(ctdb_req_control)(opcode); + } + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + COMPAT_TEST_FUNC(ctdb_reply_control)(opcode); + } + + for (i=0; i<ARRAY_SIZE(test_srvid); i++) { + COMPAT_TEST_FUNC(ctdb_req_message)(test_srvid[i]); + } + COMPAT_TEST_FUNC(ctdb_req_message_data)(); + + COMPAT_TEST_FUNC(ctdb_req_keepalive)(); + COMPAT_TEST_FUNC(ctdb_req_tunnel)(); +} + +int main(int argc, const char *argv[]) +{ + protocol_test_iterate(argc, argv, protocol_ctdb_compat_test); + return 0; +} diff --git a/ctdb/tests/src/protocol_ctdb_test.c b/ctdb/tests/src/protocol_ctdb_test.c new file mode 100644 index 0000000..840d465 --- /dev/null +++ b/ctdb/tests/src/protocol_ctdb_test.c @@ -0,0 +1,365 @@ +/* + protocol tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_header.c" +#include "protocol/protocol_call.c" +#include "protocol/protocol_control.c" +#include "protocol/protocol_message.c" +#include "protocol/protocol_keepalive.c" +#include "protocol/protocol_tunnel.c" +#include "protocol/protocol_packet.c" + +#include "tests/src/protocol_common.h" +#include "tests/src/protocol_common_ctdb.h" + +/* + * Functions to test marshalling + */ + +/* for ctdb_req_header */ +#define PROTOCOL_CTDB1_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, np; \ + int ret; \ +\ + protocol_test_iterate_tag("%s\n", #NAME); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(&c1); \ + buflen = LEN_FUNC(NAME)(&c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + np = 0; \ + PUSH_FUNC(NAME)(&c1, pkt, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &c2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_control_data, ctdb_reply_control_data */ +#define PROTOCOL_CTDB2_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, np; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \ + buflen = LEN_FUNC(NAME)(&c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + np = 0; \ + PUSH_FUNC(NAME)(&c1, pkt, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, opcode, mem_ctx, &c2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_message_data */ +#define PROTOCOL_CTDB3_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(uint64_t srvid) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, np; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %"PRIx64"\n", #NAME, srvid); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(mem_ctx, &c1, srvid); \ + buflen = LEN_FUNC(NAME)(&c1, srvid); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + np = 0; \ + PUSH_FUNC(NAME)(&c1, srvid, pkt, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, srvid, mem_ctx, &c2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&c1, &c2, srvid); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_call, ctdb_reply_call, etc. */ +#define PROTOCOL_CTDB4_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s\n", #NAME); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_control */ +#define PROTOCOL_CTDB5_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_reply_control */ +#define PROTOCOL_CTDB6_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, opcode, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_message */ +#define PROTOCOL_CTDB7_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(uint64_t srvid) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %"PRIx64"\n", #NAME, srvid); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1, srvid); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +PROTOCOL_CTDB1_TEST(struct ctdb_req_header, ctdb_req_header); + +PROTOCOL_CTDB4_TEST(struct ctdb_req_call, ctdb_req_call, CTDB_REQ_CALL); +PROTOCOL_CTDB4_TEST(struct ctdb_reply_call, ctdb_reply_call, CTDB_REPLY_CALL); +PROTOCOL_CTDB4_TEST(struct ctdb_reply_error, ctdb_reply_error, + CTDB_REPLY_ERROR); +PROTOCOL_CTDB4_TEST(struct ctdb_req_dmaster, ctdb_req_dmaster, + CTDB_REQ_DMASTER); +PROTOCOL_CTDB4_TEST(struct ctdb_reply_dmaster, ctdb_reply_dmaster, + CTDB_REPLY_DMASTER); + +#define NUM_CONTROLS 161 + +PROTOCOL_CTDB2_TEST(struct ctdb_req_control_data, ctdb_req_control_data); +PROTOCOL_CTDB2_TEST(struct ctdb_reply_control_data, ctdb_reply_control_data); + +PROTOCOL_CTDB5_TEST(struct ctdb_req_control, ctdb_req_control, + CTDB_REQ_CONTROL); +PROTOCOL_CTDB6_TEST(struct ctdb_reply_control, ctdb_reply_control, + CTDB_REPLY_CONTROL); + +PROTOCOL_CTDB3_TEST(union ctdb_message_data, ctdb_message_data); +PROTOCOL_CTDB7_TEST(struct ctdb_req_message, ctdb_req_message, + CTDB_REQ_MESSAGE); +PROTOCOL_CTDB4_TEST(struct ctdb_req_message_data, ctdb_req_message_data, + CTDB_REQ_MESSAGE); + +PROTOCOL_CTDB4_TEST(struct ctdb_req_keepalive, ctdb_req_keepalive, + CTDB_REQ_KEEPALIVE); +PROTOCOL_CTDB4_TEST(struct ctdb_req_tunnel, ctdb_req_tunnel, CTDB_REQ_TUNNEL); + +static void protocol_ctdb_test(void) +{ + uint32_t opcode; + uint64_t test_srvid[] = { + CTDB_SRVID_BANNING, + CTDB_SRVID_ELECTION, + CTDB_SRVID_LEADER, + CTDB_SRVID_RECONFIGURE, + CTDB_SRVID_RELEASE_IP, + CTDB_SRVID_TAKE_IP, + CTDB_SRVID_SET_NODE_FLAGS, + CTDB_SRVID_RECD_UPDATE_IP, + CTDB_SRVID_VACUUM_FETCH, + CTDB_SRVID_DETACH_DATABASE, + CTDB_SRVID_MEM_DUMP, + CTDB_SRVID_GETLOG, + CTDB_SRVID_CLEARLOG, + CTDB_SRVID_PUSH_NODE_FLAGS, + CTDB_SRVID_RELOAD_NODES, + CTDB_SRVID_TAKEOVER_RUN, + CTDB_SRVID_REBALANCE_NODE, + CTDB_SRVID_DISABLE_TAKEOVER_RUNS, + CTDB_SRVID_DISABLE_RECOVERIES, + CTDB_SRVID_DISABLE_IP_CHECK, + }; + size_t i; + + TEST_FUNC(ctdb_req_header)(); + + TEST_FUNC(ctdb_req_call)(); + TEST_FUNC(ctdb_reply_call)(); + TEST_FUNC(ctdb_reply_error)(); + TEST_FUNC(ctdb_req_dmaster)(); + TEST_FUNC(ctdb_reply_dmaster)(); + + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + TEST_FUNC(ctdb_req_control_data)(opcode); + } + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + TEST_FUNC(ctdb_reply_control_data)(opcode); + } + + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + TEST_FUNC(ctdb_req_control)(opcode); + } + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + TEST_FUNC(ctdb_reply_control)(opcode); + } + + for (i=0; i<ARRAY_SIZE(test_srvid); i++) { + TEST_FUNC(ctdb_message_data)(test_srvid[i]); + } + for (i=0; i<ARRAY_SIZE(test_srvid); i++) { + TEST_FUNC(ctdb_req_message)(test_srvid[i]); + } + TEST_FUNC(ctdb_req_message_data)(); + + TEST_FUNC(ctdb_req_keepalive)(); + TEST_FUNC(ctdb_req_tunnel)(); +} + +int main(int argc, const char *argv[]) +{ + protocol_test_iterate(argc, argv, protocol_ctdb_test); + return 0; +} diff --git a/ctdb/tests/src/protocol_types_compat_test.c b/ctdb/tests/src/protocol_types_compat_test.c new file mode 100644 index 0000000..140ea86 --- /dev/null +++ b/ctdb/tests/src/protocol_types_compat_test.c @@ -0,0 +1,2371 @@ +/* + protocol types backward compatibility test + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" + +#include "tests/src/protocol_common.h" + +#define COMPAT_TEST_FUNC(NAME) test_ ##NAME## _compat +#define OLD_LEN_FUNC(NAME) NAME## _len_old +#define OLD_PUSH_FUNC(NAME) NAME## _push_old +#define OLD_PULL_FUNC(NAME) NAME## _pull_old + +#define COMPAT_TYPE1_TEST(TYPE, NAME) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(&p); \ + buflen1 = LEN_FUNC(NAME)(&p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + PUSH_FUNC(NAME)(&p, buf1, &np); \ + OLD_PUSH_FUNC(NAME)(&p, buf2); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &p1, &np); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &p2); \ + assert(ret == 0); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_TYPE3_TEST(TYPE, NAME) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + TYPE *p, *p1, *p2; \ + size_t buflen1, buflen2, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + p = talloc_zero(mem_ctx, TYPE); \ + assert(p != NULL); \ + FILL_FUNC(NAME)(p, p); \ + buflen1 = LEN_FUNC(NAME)(p); \ + buflen2 = OLD_LEN_FUNC(NAME)(p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + PUSH_FUNC(NAME)(p, buf1, &np); \ + OLD_PUSH_FUNC(NAME)(p, buf2); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, mem_ctx, &p1, &np); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, mem_ctx, &p2); \ + assert(ret == 0); \ + VERIFY_FUNC(NAME)(p1, p2); \ + talloc_free(mem_ctx); \ +} + + +static size_t ctdb_statistics_len_old(struct ctdb_statistics *in) +{ + return sizeof(struct ctdb_statistics); +} + +static void ctdb_statistics_push_old(struct ctdb_statistics *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_statistics)); +} + +static int ctdb_statistics_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_statistics **out) +{ + struct ctdb_statistics *val; + + if (buflen < sizeof(struct ctdb_statistics)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_statistics); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, buf, sizeof(struct ctdb_statistics)); + + *out = val; + return 0; +} + +struct ctdb_vnn_map_wire { + uint32_t generation; + uint32_t size; + uint32_t map[1]; +}; + +static size_t ctdb_vnn_map_len_old(struct ctdb_vnn_map *in) +{ + return offsetof(struct ctdb_vnn_map, map) + + in->size * sizeof(uint32_t); +} + +static void ctdb_vnn_map_push_old(struct ctdb_vnn_map *in, uint8_t *buf) +{ + struct ctdb_vnn_map_wire *wire = (struct ctdb_vnn_map_wire *)buf; + + memcpy(wire, in, offsetof(struct ctdb_vnn_map, map)); + memcpy(wire->map, in->map, in->size * sizeof(uint32_t)); +} + +static int ctdb_vnn_map_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_vnn_map **out) +{ + struct ctdb_vnn_map *val; + struct ctdb_vnn_map_wire *wire = (struct ctdb_vnn_map_wire *)buf; + + if (buflen < offsetof(struct ctdb_vnn_map_wire, map)) { + return EMSGSIZE; + } + if (wire->size > buflen / sizeof(uint32_t)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_vnn_map_wire, map) + + wire->size * sizeof(uint32_t) < + offsetof(struct ctdb_vnn_map_wire, map)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_vnn_map_wire, map) + + wire->size * sizeof(uint32_t)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_vnn_map); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, offsetof(struct ctdb_vnn_map, map)); + + val->map = talloc_memdup(val, wire->map, + wire->size * sizeof(uint32_t)); + if (val->map == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_dbid_map_wire { + uint32_t num; + struct ctdb_dbid dbs[1]; +}; + +static size_t ctdb_dbid_map_len_old(struct ctdb_dbid_map *in) +{ + return sizeof(uint32_t) + in->num * sizeof(struct ctdb_dbid); +} + +static void ctdb_dbid_map_push_old(struct ctdb_dbid_map *in, uint8_t *buf) +{ + struct ctdb_dbid_map_wire *wire = (struct ctdb_dbid_map_wire *)buf; + + wire->num = in->num; + memcpy(wire->dbs, in->dbs, in->num * sizeof(struct ctdb_dbid)); +} + +static int ctdb_dbid_map_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_dbid_map **out) +{ + struct ctdb_dbid_map *val; + struct ctdb_dbid_map_wire *wire = (struct ctdb_dbid_map_wire *)buf; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_dbid)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_dbid) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + wire->num * sizeof(struct ctdb_dbid)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_dbid_map); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + + val->dbs = talloc_memdup(val, wire->dbs, + wire->num * sizeof(struct ctdb_dbid)); + if (val->dbs == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_pulldb_len_old(struct ctdb_pulldb *in) +{ + return sizeof(struct ctdb_pulldb); +} + +static void ctdb_pulldb_push_old(struct ctdb_pulldb *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_pulldb)); +} + +static int ctdb_pulldb_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_pulldb **out) +{ + struct ctdb_pulldb *val; + + if (buflen < sizeof(struct ctdb_pulldb)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_pulldb)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_pulldb_ext_len_old(struct ctdb_pulldb_ext *in) +{ + return sizeof(struct ctdb_pulldb_ext); +} + +static void ctdb_pulldb_ext_push_old(struct ctdb_pulldb_ext *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_pulldb_ext)); +} + +static int ctdb_pulldb_ext_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_pulldb_ext **out) +{ + struct ctdb_pulldb_ext *val; + + if (buflen < sizeof(struct ctdb_pulldb_ext)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_pulldb_ext)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_ltdb_header_len_old(struct ctdb_ltdb_header *in) +{ + return sizeof(struct ctdb_ltdb_header); +} + +static void ctdb_ltdb_header_push_old(struct ctdb_ltdb_header *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_ltdb_header)); +} + +static int ctdb_ltdb_header_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_ltdb_header *out) +{ + if (buflen < sizeof(struct ctdb_ltdb_header)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_ltdb_header)); + return 0; +} + +struct ctdb_rec_data_wire { + uint32_t length; + uint32_t reqid; + uint32_t keylen; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_rec_data_len_old(struct ctdb_rec_data *in) +{ + return offsetof(struct ctdb_rec_data_wire, data) + + in->key.dsize + in->data.dsize + + (in->header == NULL ? 0 : sizeof(struct ctdb_ltdb_header)); +} + +static void ctdb_rec_data_push_old(struct ctdb_rec_data *in, uint8_t *buf) +{ + struct ctdb_rec_data_wire *wire = (struct ctdb_rec_data_wire *)buf; + size_t offset; + + wire->length = ctdb_rec_data_len(in); + wire->reqid = in->reqid; + wire->keylen = in->key.dsize; + wire->datalen = in->data.dsize; + if (in->header != NULL) { + wire->datalen += sizeof(struct ctdb_ltdb_header); + } + + memcpy(wire->data, in->key.dptr, in->key.dsize); + offset = in->key.dsize; + if (in->header != NULL) { + memcpy(&wire->data[offset], in->header, + sizeof(struct ctdb_ltdb_header)); + offset += sizeof(struct ctdb_ltdb_header); + } + if (in->data.dsize > 0) { + memcpy(&wire->data[offset], in->data.dptr, in->data.dsize); + } +} + +static int ctdb_rec_data_pull_data_old(uint8_t *buf, size_t buflen, + uint32_t *reqid, + struct ctdb_ltdb_header **header, + TDB_DATA *key, TDB_DATA *data, + size_t *reclen) +{ + struct ctdb_rec_data_wire *wire = (struct ctdb_rec_data_wire *)buf; + size_t offset; + + if (buflen < offsetof(struct ctdb_rec_data_wire, data)) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->datalen > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_rec_data_wire, data) + wire->keylen < + offsetof(struct ctdb_rec_data_wire, data)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_rec_data_wire, data) + + wire->keylen + wire->datalen < + offsetof(struct ctdb_rec_data_wire, data)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_rec_data_wire, data) + + wire->keylen + wire->datalen) { + return EMSGSIZE; + } + + *reqid = wire->reqid; + + key->dsize = wire->keylen; + key->dptr = wire->data; + offset = wire->keylen; + + /* Always set header to NULL. If it is required, exact it using + * ctdb_rec_data_extract_header() + */ + *header = NULL; + + data->dsize = wire->datalen; + data->dptr = &wire->data[offset]; + + *reclen = offsetof(struct ctdb_rec_data_wire, data) + + wire->keylen + wire->datalen; + + return 0; +} + +static int ctdb_rec_data_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_rec_data *out) +{ + uint32_t reqid; + struct ctdb_ltdb_header *header; + TDB_DATA key, data; + size_t reclen; + int ret; + + ret = ctdb_rec_data_pull_data_old(buf, buflen, &reqid, &header, + &key, &data, &reclen); + if (ret != 0) { + return ret; + } + + out->reqid = reqid; + out->header = NULL; + + out->key.dsize = key.dsize; + if (key.dsize > 0) { + out->key.dptr = talloc_memdup(mem_ctx, key.dptr, key.dsize); + if (out->key.dptr == NULL) { + return ENOMEM; + } + } + + out->data.dsize = data.dsize; + if (data.dsize > 0) { + out->data.dptr = talloc_memdup(mem_ctx, data.dptr, data.dsize); + if (out->data.dptr == NULL) { + return ENOMEM; + } + } + + return 0; +} + +static int ctdb_rec_data_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_rec_data **out) +{ + struct ctdb_rec_data *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_rec_data); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_rec_data_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_rec_buffer_wire { + uint32_t db_id; + uint32_t count; + uint8_t data[1]; +}; + +static size_t ctdb_rec_buffer_len_old(struct ctdb_rec_buffer *in) +{ + return offsetof(struct ctdb_rec_buffer_wire, data) + in->buflen; +} + +static void ctdb_rec_buffer_push_old(struct ctdb_rec_buffer *in, uint8_t *buf) +{ + struct ctdb_rec_buffer_wire *wire = (struct ctdb_rec_buffer_wire *)buf; + + wire->db_id = in->db_id; + wire->count = in->count; + if (in->buflen > 0) { + memcpy(wire->data, in->buf, in->buflen); + } +} + +static int ctdb_rec_buffer_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_rec_buffer **out) +{ + struct ctdb_rec_buffer *val; + struct ctdb_rec_buffer_wire *wire = (struct ctdb_rec_buffer_wire *)buf; + size_t offset; + + if (buflen < offsetof(struct ctdb_rec_buffer_wire, data)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_rec_buffer); + if (val == NULL) { + return ENOMEM; + } + + val->db_id = wire->db_id; + val->count = wire->count; + + offset = offsetof(struct ctdb_rec_buffer_wire, data); + val->buflen = buflen - offset; + val->buf = talloc_memdup(val, wire->data, val->buflen); + if (val->buf == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_start_len_old(struct ctdb_traverse_start *in) +{ + return sizeof(struct ctdb_traverse_start); +} + +static void ctdb_traverse_start_push_old(struct ctdb_traverse_start *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_start)); +} + +static int ctdb_traverse_start_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start **out) +{ + struct ctdb_traverse_start *val; + + if (buflen < sizeof(struct ctdb_traverse_start)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_traverse_start)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_all_len_old(struct ctdb_traverse_all *in) +{ + return sizeof(struct ctdb_traverse_all); +} + +static void ctdb_traverse_all_push_old(struct ctdb_traverse_all *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_all)); +} + +static int ctdb_traverse_all_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all **out) +{ + struct ctdb_traverse_all *val; + + if (buflen < sizeof(struct ctdb_traverse_all)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_traverse_all)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_start_ext_len_old( + struct ctdb_traverse_start_ext *in) +{ + return sizeof(struct ctdb_traverse_start_ext); +} + +static void ctdb_traverse_start_ext_push_old( + struct ctdb_traverse_start_ext *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_start_ext)); +} + +static int ctdb_traverse_start_ext_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start_ext **out) +{ + struct ctdb_traverse_start_ext *val; + + if (buflen < sizeof(struct ctdb_traverse_start_ext)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_traverse_start_ext)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_all_ext_len_old(struct ctdb_traverse_all_ext *in) +{ + return sizeof(struct ctdb_traverse_all_ext); +} + +static void ctdb_traverse_all_ext_push_old(struct ctdb_traverse_all_ext *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_all_ext)); +} + +static int ctdb_traverse_all_ext_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all_ext **out) +{ + struct ctdb_traverse_all_ext *val; + + if (buflen < sizeof(struct ctdb_traverse_all_ext)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_traverse_all_ext)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_sock_addr_len_old(ctdb_sock_addr *in) +{ + return sizeof(ctdb_sock_addr); +} + +static void ctdb_sock_addr_push_old(ctdb_sock_addr *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(ctdb_sock_addr)); +} + +static int ctdb_sock_addr_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + ctdb_sock_addr *out) +{ + if (buflen < sizeof(ctdb_sock_addr)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(ctdb_sock_addr)); + + return 0; +} + +static int ctdb_sock_addr_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, ctdb_sock_addr **out) +{ + ctdb_sock_addr *val; + int ret; + + val = talloc(mem_ctx, ctdb_sock_addr); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_sock_addr_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +static size_t ctdb_connection_len_old(struct ctdb_connection *in) +{ + return sizeof(struct ctdb_connection); +} + +static void ctdb_connection_push_old(struct ctdb_connection *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_connection)); +} + +static int ctdb_connection_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_connection *out) +{ + if (buflen < sizeof(struct ctdb_connection)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_connection)); + + return 0; +} + +static int ctdb_connection_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_connection **out) +{ + struct ctdb_connection *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_connection); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_connection_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_tunable_wire { + uint32_t value; + uint32_t length; + uint8_t name[1]; +}; + +static size_t ctdb_tunable_len_old(struct ctdb_tunable *in) +{ + return offsetof(struct ctdb_tunable_wire, name) + + strlen(in->name) + 1; +} + +static void ctdb_tunable_push_old(struct ctdb_tunable *in, uint8_t *buf) +{ + struct ctdb_tunable_wire *wire = (struct ctdb_tunable_wire *)buf; + + wire->value = in->value; + wire->length = strlen(in->name) + 1; + memcpy(wire->name, in->name, wire->length); +} + +static int ctdb_tunable_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_tunable **out) +{ + struct ctdb_tunable *val; + struct ctdb_tunable_wire *wire = (struct ctdb_tunable_wire *)buf; + + if (buflen < offsetof(struct ctdb_tunable_wire, name)) { + return EMSGSIZE; + } + if (wire->length > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_tunable_wire, name) + wire->length < + offsetof(struct ctdb_tunable_wire, name)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_tunable_wire, name) + wire->length) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_tunable); + if (val == NULL) { + return ENOMEM; + } + + val->value = wire->value; + val->name = talloc_memdup(val, wire->name, wire->length); + if (val->name == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_node_flag_change_len_old(struct ctdb_node_flag_change *in) +{ + return sizeof(struct ctdb_node_flag_change); +} + +static void ctdb_node_flag_change_push_old(struct ctdb_node_flag_change *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_node_flag_change)); +} + +static int ctdb_node_flag_change_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_node_flag_change **out) +{ + struct ctdb_node_flag_change *val; + + if (buflen < sizeof(struct ctdb_node_flag_change)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_node_flag_change)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_var_list_wire { + uint32_t length; + char list_str[1]; +}; + +static size_t ctdb_var_list_len_old(struct ctdb_var_list *in) +{ + int i; + size_t len = sizeof(uint32_t); + + for (i=0; i<in->count; i++) { + assert(in->var[i] != NULL); + len += strlen(in->var[i]) + 1; + } + return len; +} + +static void ctdb_var_list_push_old(struct ctdb_var_list *in, uint8_t *buf) +{ + struct ctdb_var_list_wire *wire = (struct ctdb_var_list_wire *)buf; + int i, n; + size_t offset = 0; + + if (in->count > 0) { + n = sprintf(wire->list_str, "%s", in->var[0]); + offset += n; + } + for (i=1; i<in->count; i++) { + n = sprintf(&wire->list_str[offset], ":%s", in->var[i]); + offset += n; + } + wire->length = offset + 1; +} + +static int ctdb_var_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_var_list **out) +{ + struct ctdb_var_list *val = NULL; + struct ctdb_var_list_wire *wire = (struct ctdb_var_list_wire *)buf; + char *str, *s, *tok, *ptr; + const char **list; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->length > buflen) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->length < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + wire->length) { + return EMSGSIZE; + } + + str = talloc_strndup(mem_ctx, (char *)wire->list_str, wire->length); + if (str == NULL) { + return ENOMEM; + } + + val = talloc_zero(mem_ctx, struct ctdb_var_list); + if (val == NULL) { + goto fail; + } + + s = str; + while ((tok = strtok_r(s, ":", &ptr)) != NULL) { + s = NULL; + list = talloc_realloc(val, val->var, const char *, + val->count+1); + if (list == NULL) { + goto fail; + } + + val->var = list; + val->var[val->count] = talloc_strdup(val, tok); + if (val->var[val->count] == NULL) { + goto fail; + } + val->count++; + } + + talloc_free(str); + *out = val; + return 0; + +fail: + talloc_free(str); + talloc_free(val); + return ENOMEM; +} + +static size_t ctdb_tunable_list_len_old(struct ctdb_tunable_list *in) +{ + return sizeof(struct ctdb_tunable_list); +} + +static void ctdb_tunable_list_push_old(struct ctdb_tunable_list *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_tunable_list)); +} + +static int ctdb_tunable_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_tunable_list **out) +{ + struct ctdb_tunable_list *val; + + if (buflen < sizeof(struct ctdb_tunable_list)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_tunable_list)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_tickle_list_wire { + ctdb_sock_addr addr; + uint32_t num; + struct ctdb_connection conn[1]; +}; + +static size_t ctdb_tickle_list_len_old(struct ctdb_tickle_list *in) +{ + return offsetof(struct ctdb_tickle_list, conn) + + in->num * sizeof(struct ctdb_connection); +} + +static void ctdb_tickle_list_push_old(struct ctdb_tickle_list *in, + uint8_t *buf) +{ + struct ctdb_tickle_list_wire *wire = + (struct ctdb_tickle_list_wire *)buf; + size_t offset; + unsigned int i; + + memcpy(&wire->addr, &in->addr, sizeof(ctdb_sock_addr)); + wire->num = in->num; + + offset = offsetof(struct ctdb_tickle_list_wire, conn); + for (i=0; i<in->num; i++) { + ctdb_connection_push_old(&in->conn[i], &buf[offset]); + offset += ctdb_connection_len_old(&in->conn[i]); + } +} + +static int ctdb_tickle_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_tickle_list **out) +{ + struct ctdb_tickle_list *val; + struct ctdb_tickle_list_wire *wire = + (struct ctdb_tickle_list_wire *)buf; + size_t offset; + unsigned int i; + int ret; + + if (buflen < offsetof(struct ctdb_tickle_list_wire, conn)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_connection)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_tickle_list_wire, conn) + + wire->num * sizeof(struct ctdb_connection) < + offsetof(struct ctdb_tickle_list_wire, conn)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_tickle_list_wire, conn) + + wire->num * sizeof(struct ctdb_connection)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_tickle_list); + if (val == NULL) { + return ENOMEM; + } + + offset = offsetof(struct ctdb_tickle_list, conn); + memcpy(val, wire, offset); + + val->conn = talloc_array(val, struct ctdb_connection, wire->num); + if (val->conn == NULL) { + talloc_free(val); + return ENOMEM; + } + + for (i=0; i<wire->num; i++) { + ret = ctdb_connection_pull_elems_old(&buf[offset], + buflen-offset, + val->conn, + &val->conn[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_connection_len_old(&val->conn[i]); + } + + *out = val; + return 0; +} + +struct ctdb_addr_info_wire { + ctdb_sock_addr addr; + uint32_t mask; + uint32_t len; + char iface[1]; +}; + +static size_t ctdb_addr_info_len_old(struct ctdb_addr_info *in) +{ + uint32_t len; + + len = offsetof(struct ctdb_addr_info_wire, iface); + if (in->iface != NULL) { + len += strlen(in->iface)+1; + } + + return len; +} + +static void ctdb_addr_info_push_old(struct ctdb_addr_info *in, uint8_t *buf) +{ + struct ctdb_addr_info_wire *wire = (struct ctdb_addr_info_wire *)buf; + + wire->addr = in->addr; + wire->mask = in->mask; + if (in->iface == NULL) { + wire->len = 0; + } else { + wire->len = strlen(in->iface)+1; + memcpy(wire->iface, in->iface, wire->len); + } +} + +static int ctdb_addr_info_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_addr_info **out) +{ + struct ctdb_addr_info *val; + struct ctdb_addr_info_wire *wire = (struct ctdb_addr_info_wire *)buf; + + if (buflen < offsetof(struct ctdb_addr_info_wire, iface)) { + return EMSGSIZE; + } + if (wire->len > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_addr_info_wire, iface) + wire->len < + offsetof(struct ctdb_addr_info_wire, iface)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_addr_info_wire, iface) + wire->len) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_addr_info); + if (val == NULL) { + return ENOMEM; + } + + val->addr = wire->addr; + val->mask = wire->mask; + + if (wire->len == 0) { + val->iface = NULL; + } else { + val->iface = talloc_strndup(val, wire->iface, wire->len); + if (val->iface == NULL) { + talloc_free(val); + return ENOMEM; + } + } + + *out = val; + return 0; +} + +static size_t ctdb_transdb_len_old(struct ctdb_transdb *in) +{ + return sizeof(struct ctdb_transdb); +} + +static void ctdb_transdb_push_old(struct ctdb_transdb *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_transdb)); +} + +static int ctdb_transdb_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_transdb **out) +{ + struct ctdb_transdb *val; + + if (buflen < sizeof(struct ctdb_transdb)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_transdb)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_uptime_len_old(struct ctdb_uptime *in) +{ + return sizeof(struct ctdb_uptime); +} + +static void ctdb_uptime_push_old(struct ctdb_uptime *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_uptime)); +} + +static int ctdb_uptime_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_uptime **out) +{ + struct ctdb_uptime *val; + + if (buflen < sizeof(struct ctdb_uptime)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_uptime)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_public_ip_len_old(struct ctdb_public_ip *in) +{ + return sizeof(struct ctdb_public_ip); +} + +static void ctdb_public_ip_push_old(struct ctdb_public_ip *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_public_ip)); +} + +static int ctdb_public_ip_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip *out) +{ + if (buflen < sizeof(struct ctdb_public_ip)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_public_ip)); + + return 0; +} + +static int ctdb_public_ip_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip **out) +{ + struct ctdb_public_ip *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_public_ip); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_public_ip_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_public_ip_list_wire { + uint32_t num; + struct ctdb_public_ip ip[1]; +}; + +static size_t ctdb_public_ip_list_len_old(struct ctdb_public_ip_list *in) +{ + unsigned int i; + size_t len; + + len = sizeof(uint32_t); + for (i=0; i<in->num; i++) { + len += ctdb_public_ip_len_old(&in->ip[i]); + } + return len; +} + +static void ctdb_public_ip_list_push_old(struct ctdb_public_ip_list *in, + uint8_t *buf) +{ + struct ctdb_public_ip_list_wire *wire = + (struct ctdb_public_ip_list_wire *)buf; + size_t offset; + unsigned int i; + + wire->num = in->num; + + offset = offsetof(struct ctdb_public_ip_list_wire, ip); + for (i=0; i<in->num; i++) { + ctdb_public_ip_push_old(&in->ip[i], &buf[offset]); + offset += ctdb_public_ip_len_old(&in->ip[i]); + } +} + +static int ctdb_public_ip_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list **out) +{ + struct ctdb_public_ip_list *val; + struct ctdb_public_ip_list_wire *wire = + (struct ctdb_public_ip_list_wire *)buf; + size_t offset; + unsigned int i; + bool ret; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_public_ip)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_public_ip) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + + wire->num * sizeof(struct ctdb_public_ip)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_public_ip_list); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + if (wire->num == 0) { + val->ip = NULL; + *out = val; + return 0; + } + val->ip = talloc_array(val, struct ctdb_public_ip, wire->num); + if (val->ip == NULL) { + talloc_free(val); + return ENOMEM; + } + + offset = offsetof(struct ctdb_public_ip_list_wire, ip); + for (i=0; i<wire->num; i++) { + ret = ctdb_public_ip_pull_elems_old(&buf[offset], + buflen-offset, + val->ip, + &val->ip[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_public_ip_len_old(&val->ip[i]); + } + + *out = val; + return 0; +} + +static size_t ctdb_node_and_flags_len_old(struct ctdb_node_and_flags *in) +{ + return sizeof(struct ctdb_node_and_flags); +} + +static void ctdb_node_and_flags_push_old(struct ctdb_node_and_flags *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_node_and_flags)); +} + +static int ctdb_node_and_flags_pull_elems_old(TALLOC_CTX *mem_ctx, + uint8_t *buf, size_t buflen, + struct ctdb_node_and_flags *out) +{ + if (buflen < sizeof(struct ctdb_node_and_flags)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_node_and_flags)); + + return 0; +} + +static int ctdb_node_and_flags_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_node_and_flags **out) +{ + struct ctdb_node_and_flags *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_node_and_flags); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_node_and_flags_pull_elems_old(val, buf, buflen, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_node_map_wire { + uint32_t num; + struct ctdb_node_and_flags node[1]; +}; + +static size_t ctdb_node_map_len_old(struct ctdb_node_map *in) +{ + return sizeof(uint32_t) + + in->num * sizeof(struct ctdb_node_and_flags); +} + +static void ctdb_node_map_push_old(struct ctdb_node_map *in, uint8_t *buf) +{ + struct ctdb_node_map_wire *wire = (struct ctdb_node_map_wire *)buf; + size_t offset; + unsigned int i; + + wire->num = in->num; + + offset = offsetof(struct ctdb_node_map_wire, node); + for (i=0; i<in->num; i++) { + ctdb_node_and_flags_push_old(&in->node[i], &buf[offset]); + offset += ctdb_node_and_flags_len_old(&in->node[i]); + } +} + +static int ctdb_node_map_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_node_map **out) +{ + struct ctdb_node_map *val; + struct ctdb_node_map_wire *wire = (struct ctdb_node_map_wire *)buf; + size_t offset; + unsigned int i; + bool ret; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_node_and_flags)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_node_and_flags) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + + wire->num * sizeof(struct ctdb_node_and_flags)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_node_map); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + val->node = talloc_array(val, struct ctdb_node_and_flags, wire->num); + if (val->node == NULL) { + talloc_free(val); + return ENOMEM; + } + + offset = offsetof(struct ctdb_node_map_wire, node); + for (i=0; i<wire->num; i++) { + ret = ctdb_node_and_flags_pull_elems_old(val->node, + &buf[offset], + buflen-offset, + &val->node[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_node_and_flags_len_old(&val->node[i]); + } + + *out = val; + return 0; +} + +static size_t ctdb_script_len_old(struct ctdb_script *in) +{ + return sizeof(struct ctdb_script); +} + +static void ctdb_script_push_old(struct ctdb_script *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_script)); +} + +static int ctdb_script_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_script *out) +{ + if (buflen < sizeof(struct ctdb_script)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_script)); + + return 0; +} + +static int ctdb_script_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_script **out) +{ + struct ctdb_script *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_script); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_script_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_script_list_wire { + uint32_t num_scripts; + struct ctdb_script script[1]; +}; + +static size_t ctdb_script_list_len_old(struct ctdb_script_list *in) +{ + unsigned int i; + size_t len; + + if (in == NULL) { + return 0; + } + + len = offsetof(struct ctdb_script_list_wire, script); + for (i=0; i<in->num_scripts; i++) { + len += ctdb_script_len_old(&in->script[i]); + } + return len; +} + +static void ctdb_script_list_push_old(struct ctdb_script_list *in, + uint8_t *buf) +{ + struct ctdb_script_list_wire *wire = + (struct ctdb_script_list_wire *)buf; + size_t offset; + unsigned int i; + + if (in == NULL) { + return; + } + + wire->num_scripts = in->num_scripts; + + offset = offsetof(struct ctdb_script_list_wire, script); + for (i=0; i<in->num_scripts; i++) { + ctdb_script_push_old(&in->script[i], &buf[offset]); + offset += ctdb_script_len_old(&in->script[i]); + } +} + +static int ctdb_script_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_script_list **out) +{ + struct ctdb_script_list *val; + struct ctdb_script_list_wire *wire = + (struct ctdb_script_list_wire *)buf; + size_t offset; + unsigned int i; + bool ret; + + /* If event scripts have never been run, the result will be NULL */ + if (buflen == 0) { + *out = NULL; + return 0; + } + + offset = offsetof(struct ctdb_script_list_wire, script); + + if (buflen < offset) { + return EMSGSIZE; + } + if (wire->num_scripts > buflen / sizeof(struct ctdb_script)) { + return EMSGSIZE; + } + if (offset + wire->num_scripts * sizeof(struct ctdb_script) < offset) { + return EMSGSIZE; + } + if (buflen < offset + wire->num_scripts * sizeof(struct ctdb_script)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_script_list); + if (val == NULL) { + return ENOMEM; + + } + + val->num_scripts = wire->num_scripts; + val->script = talloc_array(val, struct ctdb_script, wire->num_scripts); + if (val->script == NULL) { + talloc_free(val); + return ENOMEM; + } + + for (i=0; i<wire->num_scripts; i++) { + ret = ctdb_script_pull_elems_old(&buf[offset], buflen-offset, + val->script, + &val->script[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_script_len_old(&val->script[i]); + } + + *out = val; + return 0; +} + +static size_t ctdb_ban_state_len_old(struct ctdb_ban_state *in) +{ + return sizeof(struct ctdb_ban_state); +} + +static void ctdb_ban_state_push_old(struct ctdb_ban_state *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_ban_state)); +} + +static int ctdb_ban_state_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_ban_state **out) +{ + struct ctdb_ban_state *val; + + if (buflen < sizeof(struct ctdb_ban_state)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_ban_state)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_notify_data_wire { + uint64_t srvid; + uint32_t len; + uint8_t data[1]; +}; + +static size_t ctdb_notify_data_len_old(struct ctdb_notify_data *in) +{ + return offsetof(struct ctdb_notify_data_wire, data) + + in->data.dsize; +} + +static void ctdb_notify_data_push_old(struct ctdb_notify_data *in, + uint8_t *buf) +{ + struct ctdb_notify_data_wire *wire = + (struct ctdb_notify_data_wire *)buf; + + wire->srvid = in->srvid; + wire->len = in->data.dsize; + memcpy(wire->data, in->data.dptr, in->data.dsize); +} + +static int ctdb_notify_data_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_notify_data **out) +{ + struct ctdb_notify_data *val; + struct ctdb_notify_data_wire *wire = + (struct ctdb_notify_data_wire *)buf; + + if (buflen < offsetof(struct ctdb_notify_data_wire, data)) { + return EMSGSIZE; + } + if (wire->len > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_notify_data_wire, data) + wire->len < + offsetof(struct ctdb_notify_data_wire, data)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_notify_data_wire, data) + wire->len) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_notify_data); + if (val == NULL) { + return ENOMEM; + } + + val->srvid = wire->srvid; + val->data.dsize = wire->len; + val->data.dptr = talloc_memdup(val, wire->data, wire->len); + if (val->data.dptr == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_iface_len_old(struct ctdb_iface *in) +{ + return sizeof(struct ctdb_iface); +} + +static void ctdb_iface_push_old(struct ctdb_iface *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_iface)); +} + +static int ctdb_iface_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_iface *out) +{ + if (buflen < sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_iface)); + + return 0; +} + +static int ctdb_iface_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_iface **out) +{ + struct ctdb_iface *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_iface); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_iface_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_iface_list_wire { + uint32_t num; + struct ctdb_iface iface[1]; +}; + +static size_t ctdb_iface_list_len_old(struct ctdb_iface_list *in) +{ + return sizeof(uint32_t) + + in->num * sizeof(struct ctdb_iface); +} + +static void ctdb_iface_list_push_old(struct ctdb_iface_list *in, uint8_t *buf) +{ + struct ctdb_iface_list_wire *wire = + (struct ctdb_iface_list_wire *)buf; + + wire->num = in->num; + memcpy(wire->iface, in->iface, in->num * sizeof(struct ctdb_iface)); +} + +static int ctdb_iface_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_iface_list **out) +{ + struct ctdb_iface_list *val; + struct ctdb_iface_list_wire *wire = + (struct ctdb_iface_list_wire *)buf; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_iface) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + wire->num * sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_iface_list); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + val->iface = talloc_array(val, struct ctdb_iface, wire->num); + if (val->iface == NULL) { + talloc_free(val); + return ENOMEM; + } + + memcpy(val->iface, wire->iface, wire->num * sizeof(struct ctdb_iface)); + + *out = val; + return 0; +} + +struct ctdb_public_ip_info_wire { + struct ctdb_public_ip ip; + uint32_t active_idx; + uint32_t num; + struct ctdb_iface ifaces[1]; +}; + +static size_t ctdb_public_ip_info_len_old(struct ctdb_public_ip_info *in) +{ + return offsetof(struct ctdb_public_ip_info_wire, num) + + ctdb_iface_list_len_old(in->ifaces); +} + +static void ctdb_public_ip_info_push_old(struct ctdb_public_ip_info *in, + uint8_t *buf) +{ + struct ctdb_public_ip_info_wire *wire = + (struct ctdb_public_ip_info_wire *)buf; + size_t offset; + + offset = offsetof(struct ctdb_public_ip_info_wire, num); + memcpy(wire, in, offset); + wire->num = in->ifaces->num; + memcpy(wire->ifaces, in->ifaces->iface, + in->ifaces->num * sizeof(struct ctdb_iface)); +} + +static int ctdb_public_ip_info_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_info **out) +{ + struct ctdb_public_ip_info *val; + struct ctdb_public_ip_info_wire *wire = + (struct ctdb_public_ip_info_wire *)buf; + + if (buflen < offsetof(struct ctdb_public_ip_info_wire, ifaces)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_public_ip_info_wire, ifaces) + + wire->num * sizeof(struct ctdb_iface) < + offsetof(struct ctdb_public_ip_info_wire, ifaces)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_public_ip_info_wire, ifaces) + + wire->num * sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_public_ip_info); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, offsetof(struct ctdb_public_ip_info_wire, num)); + + val->ifaces = talloc(val, struct ctdb_iface_list); + if (val->ifaces == NULL) { + talloc_free(val); + return ENOMEM; + } + + val->ifaces->num = wire->num; + val->ifaces->iface = talloc_array(val->ifaces, struct ctdb_iface, + wire->num); + if (val->ifaces->iface == NULL) { + talloc_free(val); + return ENOMEM; + } + + memcpy(val->ifaces->iface, wire->ifaces, + wire->num * sizeof(struct ctdb_iface)); + + *out = val; + return 0; +} + +struct ctdb_statistics_list_wire { + uint32_t num; + struct ctdb_statistics stats[1]; +}; + +static size_t ctdb_statistics_list_len_old(struct ctdb_statistics_list *in) +{ + return offsetof(struct ctdb_statistics_list_wire, stats) + + in->num * sizeof(struct ctdb_statistics); +} + +static void ctdb_statistics_list_push_old(struct ctdb_statistics_list *in, + uint8_t *buf) +{ + struct ctdb_statistics_list_wire *wire = + (struct ctdb_statistics_list_wire *)buf; + + wire->num = in->num; + memcpy(wire->stats, in->stats, + in->num * sizeof(struct ctdb_statistics)); +} + +static int ctdb_statistics_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_statistics_list **out) +{ + struct ctdb_statistics_list *val; + struct ctdb_statistics_list_wire *wire = + (struct ctdb_statistics_list_wire *)buf; + + if (buflen < offsetof(struct ctdb_statistics_list_wire, stats)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_statistics)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_statistics_list_wire, stats) + + wire->num * sizeof(struct ctdb_statistics) < + offsetof(struct ctdb_statistics_list_wire, stats)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_statistics_list_wire, stats) + + wire->num * sizeof(struct ctdb_statistics)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_statistics_list); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + + val->stats = talloc_array(val, struct ctdb_statistics, wire->num); + if (val->stats == NULL) { + talloc_free(val); + return ENOMEM; + } + + memcpy(val->stats, wire->stats, + wire->num * sizeof(struct ctdb_statistics)); + + *out = val; + return 0; +} + +struct ctdb_key_data_wire { + uint32_t db_id; + struct ctdb_ltdb_header header; + uint32_t keylen; + uint8_t key[1]; +}; + +static size_t ctdb_key_data_len_old(struct ctdb_key_data *in) +{ + return offsetof(struct ctdb_key_data_wire, key) + in->key.dsize; +} + +static void ctdb_key_data_push_old(struct ctdb_key_data *in, uint8_t *buf) +{ + struct ctdb_key_data_wire *wire = (struct ctdb_key_data_wire *)buf; + + memcpy(wire, in, offsetof(struct ctdb_key_data, key)); + wire->keylen = in->key.dsize; + memcpy(wire->key, in->key.dptr, in->key.dsize); +} + +static int ctdb_key_data_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_key_data **out) +{ + struct ctdb_key_data *val; + struct ctdb_key_data_wire *wire = (struct ctdb_key_data_wire *)buf; + + if (buflen < offsetof(struct ctdb_key_data_wire, key)) { + return EMSGSIZE; + } + if (wire->keylen > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_key_data_wire, key) + wire->keylen < + offsetof(struct ctdb_key_data_wire, key)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_key_data_wire, key) + wire->keylen) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_key_data); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, offsetof(struct ctdb_key_data, key)); + + val->key.dsize = wire->keylen; + val->key.dptr = talloc_memdup(val, wire->key, wire->keylen); + if (val->key.dptr == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_db_statistics_wire { + struct ctdb_db_statistics dbstats; + char hot_keys_wire[1]; +}; + +static size_t ctdb_db_statistics_len_old(struct ctdb_db_statistics *in) +{ + size_t len; + int i; + + len = sizeof(struct ctdb_db_statistics); + for (i=0; i<MAX_HOT_KEYS; i++) { + len += in->hot_keys[i].key.dsize; + } + return len; +} + +static void ctdb_db_statistics_push_old(struct ctdb_db_statistics *in, + void *buf) +{ + struct ctdb_db_statistics_wire *wire = + (struct ctdb_db_statistics_wire *)buf; + size_t offset; + int i; + + in->num_hot_keys = MAX_HOT_KEYS; + memcpy(wire, in, sizeof(struct ctdb_db_statistics)); + + offset = 0; + for (i=0; i<MAX_HOT_KEYS; i++) { + memcpy(&wire->hot_keys_wire[offset], + in->hot_keys[i].key.dptr, + in->hot_keys[i].key.dsize); + offset += in->hot_keys[i].key.dsize; + } +} + +static int ctdb_db_statistics_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_db_statistics **out) +{ + struct ctdb_db_statistics *val; + struct ctdb_db_statistics_wire *wire = + (struct ctdb_db_statistics_wire *)buf; + size_t offset; + unsigned int i; + + if (buflen < sizeof(struct ctdb_db_statistics)) { + return EMSGSIZE; + } + + offset = 0; + for (i=0; i<wire->dbstats.num_hot_keys; i++) { + if (wire->dbstats.hot_keys[i].key.dsize > buflen) { + return EMSGSIZE; + } + if (offset + wire->dbstats.hot_keys[i].key.dsize < offset) { + return EMSGSIZE; + } + offset += wire->dbstats.hot_keys[i].key.dsize; + if (offset > buflen) { + return EMSGSIZE; + } + } + if (sizeof(struct ctdb_db_statistics) + offset < + sizeof(struct ctdb_db_statistics)) { + return EMSGSIZE; + } + if (buflen < sizeof(struct ctdb_db_statistics) + offset) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_db_statistics); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, sizeof(struct ctdb_db_statistics)); + + offset = 0; + for (i=0; i<wire->dbstats.num_hot_keys; i++) { + uint8_t *ptr; + size_t key_size; + + key_size = val->hot_keys[i].key.dsize; + ptr = talloc_memdup(mem_ctx, &wire->hot_keys_wire[offset], + key_size); + if (ptr == NULL) { + talloc_free(val); + return ENOMEM; + } + val->hot_keys[i].key.dptr = ptr; + offset += key_size; + } + + *out = val; + return 0; +} + +static size_t ctdb_election_message_len_old(struct ctdb_election_message *in) +{ + return sizeof(struct ctdb_election_message); +} + +static void ctdb_election_message_push_old(struct ctdb_election_message *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_election_message)); +} + +static int ctdb_election_message_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_election_message **out) +{ + struct ctdb_election_message *val; + + if (buflen < sizeof(struct ctdb_election_message)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_election_message)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_srvid_message_len_old(struct ctdb_srvid_message *in) +{ + return sizeof(struct ctdb_srvid_message); +} + +static void ctdb_srvid_message_push_old(struct ctdb_srvid_message *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_srvid_message)); +} + +static int ctdb_srvid_message_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_srvid_message **out) +{ + struct ctdb_srvid_message *val; + + if (buflen < sizeof(struct ctdb_srvid_message)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_srvid_message)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_disable_message_len_old(struct ctdb_disable_message *in) +{ + return sizeof(struct ctdb_disable_message); +} + +static void ctdb_disable_message_push_old(struct ctdb_disable_message *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_disable_message)); +} + +static int ctdb_disable_message_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_disable_message **out) +{ + struct ctdb_disable_message *val; + + if (buflen < sizeof(struct ctdb_disable_message)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_disable_message)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_server_id_len_old(struct ctdb_server_id *in) +{ + return sizeof(struct ctdb_server_id); +} + +static void ctdb_server_id_push_old(struct ctdb_server_id *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_server_id)); +} + +static int ctdb_server_id_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_server_id *out) +{ + if (buflen < sizeof(struct ctdb_server_id)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_server_id)); + return 0; +} + +static size_t ctdb_g_lock_len_old(struct ctdb_g_lock *in) +{ + return sizeof(struct ctdb_g_lock); +} + +static void ctdb_g_lock_push_old(struct ctdb_g_lock *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_g_lock)); +} + +static int ctdb_g_lock_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_g_lock *out) +{ + if (buflen < sizeof(struct ctdb_g_lock)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_g_lock)); + return 0; +} + +static size_t ctdb_g_lock_list_len_old(struct ctdb_g_lock_list *in) +{ + return in->num * sizeof(struct ctdb_g_lock); +} + +static void ctdb_g_lock_list_push_old(struct ctdb_g_lock_list *in, + uint8_t *buf) +{ + size_t offset = 0; + unsigned int i; + + for (i=0; i<in->num; i++) { + ctdb_g_lock_push_old(&in->lock[i], &buf[offset]); + offset += sizeof(struct ctdb_g_lock); + } +} + +static int ctdb_g_lock_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_g_lock_list **out) +{ + struct ctdb_g_lock_list *val; + unsigned count; + size_t offset; + unsigned int i; + int ret; + + val = talloc_zero(mem_ctx, struct ctdb_g_lock_list); + if (val == NULL) { + return ENOMEM; + } + + count = buflen / sizeof(struct ctdb_g_lock); + val->lock = talloc_array(val, struct ctdb_g_lock, count); + if (val->lock == NULL) { + talloc_free(val); + return ENOMEM; + } + + offset = 0; + for (i=0; i<count; i++) { + ret = ctdb_g_lock_pull_old(&buf[offset], buflen-offset, + &val->lock[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += sizeof(struct ctdb_g_lock); + } + + val->num = count; + + *out = val; + return 0; +} + +COMPAT_TYPE3_TEST(struct ctdb_statistics, ctdb_statistics); +COMPAT_TYPE3_TEST(struct ctdb_vnn_map, ctdb_vnn_map); +COMPAT_TYPE3_TEST(struct ctdb_dbid_map, ctdb_dbid_map); +COMPAT_TYPE3_TEST(struct ctdb_pulldb, ctdb_pulldb); +COMPAT_TYPE3_TEST(struct ctdb_pulldb_ext, ctdb_pulldb_ext); + +COMPAT_TYPE1_TEST(struct ctdb_ltdb_header, ctdb_ltdb_header); + +COMPAT_TYPE3_TEST(struct ctdb_rec_data, ctdb_rec_data); +COMPAT_TYPE3_TEST(struct ctdb_rec_buffer, ctdb_rec_buffer); +COMPAT_TYPE3_TEST(struct ctdb_traverse_start, ctdb_traverse_start); +COMPAT_TYPE3_TEST(struct ctdb_traverse_all, ctdb_traverse_all); +COMPAT_TYPE3_TEST(struct ctdb_traverse_start_ext, ctdb_traverse_start_ext); +COMPAT_TYPE3_TEST(struct ctdb_traverse_all_ext, ctdb_traverse_all_ext); +COMPAT_TYPE3_TEST(ctdb_sock_addr, ctdb_sock_addr); +COMPAT_TYPE3_TEST(struct ctdb_connection, ctdb_connection); +COMPAT_TYPE3_TEST(struct ctdb_tunable, ctdb_tunable); +COMPAT_TYPE3_TEST(struct ctdb_node_flag_change, ctdb_node_flag_change); +COMPAT_TYPE3_TEST(struct ctdb_var_list, ctdb_var_list); +COMPAT_TYPE3_TEST(struct ctdb_tunable_list, ctdb_tunable_list); +COMPAT_TYPE3_TEST(struct ctdb_tickle_list, ctdb_tickle_list); +COMPAT_TYPE3_TEST(struct ctdb_addr_info, ctdb_addr_info); +COMPAT_TYPE3_TEST(struct ctdb_transdb, ctdb_transdb); +COMPAT_TYPE3_TEST(struct ctdb_uptime, ctdb_uptime); +COMPAT_TYPE3_TEST(struct ctdb_public_ip, ctdb_public_ip); +COMPAT_TYPE3_TEST(struct ctdb_public_ip_list, ctdb_public_ip_list); +COMPAT_TYPE3_TEST(struct ctdb_node_and_flags, ctdb_node_and_flags); +COMPAT_TYPE3_TEST(struct ctdb_node_map, ctdb_node_map); +COMPAT_TYPE3_TEST(struct ctdb_script, ctdb_script); +COMPAT_TYPE3_TEST(struct ctdb_script_list, ctdb_script_list); +COMPAT_TYPE3_TEST(struct ctdb_ban_state, ctdb_ban_state); +COMPAT_TYPE3_TEST(struct ctdb_notify_data, ctdb_notify_data); +COMPAT_TYPE3_TEST(struct ctdb_iface, ctdb_iface); +COMPAT_TYPE3_TEST(struct ctdb_iface_list, ctdb_iface_list); +COMPAT_TYPE3_TEST(struct ctdb_public_ip_info, ctdb_public_ip_info); +COMPAT_TYPE3_TEST(struct ctdb_statistics_list, ctdb_statistics_list); +COMPAT_TYPE3_TEST(struct ctdb_key_data, ctdb_key_data); +COMPAT_TYPE3_TEST(struct ctdb_db_statistics, ctdb_db_statistics); + +COMPAT_TYPE3_TEST(struct ctdb_election_message, ctdb_election_message); +COMPAT_TYPE3_TEST(struct ctdb_srvid_message, ctdb_srvid_message); +COMPAT_TYPE3_TEST(struct ctdb_disable_message, ctdb_disable_message); + +COMPAT_TYPE1_TEST(struct ctdb_server_id, ctdb_server_id); +COMPAT_TYPE1_TEST(struct ctdb_g_lock, ctdb_g_lock); + +COMPAT_TYPE3_TEST(struct ctdb_g_lock_list, ctdb_g_lock_list); + +static void protocol_types_compat_test(void) +{ + COMPAT_TEST_FUNC(ctdb_statistics)(); + COMPAT_TEST_FUNC(ctdb_vnn_map)(); + COMPAT_TEST_FUNC(ctdb_dbid_map)(); + COMPAT_TEST_FUNC(ctdb_pulldb)(); + COMPAT_TEST_FUNC(ctdb_pulldb_ext)(); + COMPAT_TEST_FUNC(ctdb_ltdb_header)(); + COMPAT_TEST_FUNC(ctdb_rec_data)(); + COMPAT_TEST_FUNC(ctdb_rec_buffer)(); + COMPAT_TEST_FUNC(ctdb_traverse_start)(); + COMPAT_TEST_FUNC(ctdb_traverse_all)(); + COMPAT_TEST_FUNC(ctdb_traverse_start_ext)(); + COMPAT_TEST_FUNC(ctdb_traverse_all_ext)(); + COMPAT_TEST_FUNC(ctdb_sock_addr)(); + COMPAT_TEST_FUNC(ctdb_connection)(); + COMPAT_TEST_FUNC(ctdb_tunable)(); + COMPAT_TEST_FUNC(ctdb_node_flag_change)(); + COMPAT_TEST_FUNC(ctdb_var_list)(); + COMPAT_TEST_FUNC(ctdb_tunable_list)(); + COMPAT_TEST_FUNC(ctdb_tickle_list)(); + COMPAT_TEST_FUNC(ctdb_addr_info)(); + COMPAT_TEST_FUNC(ctdb_transdb)(); + COMPAT_TEST_FUNC(ctdb_uptime)(); + COMPAT_TEST_FUNC(ctdb_public_ip)(); + COMPAT_TEST_FUNC(ctdb_public_ip_list)(); + COMPAT_TEST_FUNC(ctdb_node_and_flags)(); + COMPAT_TEST_FUNC(ctdb_node_map)(); + COMPAT_TEST_FUNC(ctdb_script)(); + COMPAT_TEST_FUNC(ctdb_script_list)(); + COMPAT_TEST_FUNC(ctdb_ban_state)(); + COMPAT_TEST_FUNC(ctdb_notify_data)(); + COMPAT_TEST_FUNC(ctdb_iface)(); + COMPAT_TEST_FUNC(ctdb_iface_list)(); + COMPAT_TEST_FUNC(ctdb_public_ip_info)(); + COMPAT_TEST_FUNC(ctdb_statistics_list)(); + COMPAT_TEST_FUNC(ctdb_key_data)(); + COMPAT_TEST_FUNC(ctdb_db_statistics)(); + + COMPAT_TEST_FUNC(ctdb_election_message)(); + COMPAT_TEST_FUNC(ctdb_srvid_message)(); + COMPAT_TEST_FUNC(ctdb_disable_message)(); + COMPAT_TEST_FUNC(ctdb_server_id)(); + COMPAT_TEST_FUNC(ctdb_g_lock)(); + COMPAT_TEST_FUNC(ctdb_g_lock_list)(); +} + +int main(int argc, const char *argv[]) +{ + protocol_test_iterate(argc, argv, protocol_types_compat_test); + return 0; +} diff --git a/ctdb/tests/src/protocol_types_test.c b/ctdb/tests/src/protocol_types_test.c new file mode 100644 index 0000000..f4a3048 --- /dev/null +++ b/ctdb/tests/src/protocol_types_test.c @@ -0,0 +1,194 @@ +/* + protocol types tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_sock.c" + +#include "tests/src/protocol_common.h" + +PROTOCOL_TYPE2_TEST(TDB_DATA, ctdb_tdb_data); +PROTOCOL_TYPE2_TEST(TDB_DATA, ctdb_tdb_datan); +PROTOCOL_TYPE1_TEST(struct ctdb_latency_counter, ctdb_latency_counter); + +PROTOCOL_TYPE3_TEST(struct ctdb_statistics, ctdb_statistics); +PROTOCOL_TYPE3_TEST(struct ctdb_vnn_map, ctdb_vnn_map); +PROTOCOL_TYPE3_TEST(struct ctdb_dbid, ctdb_dbid); +PROTOCOL_TYPE3_TEST(struct ctdb_dbid_map, ctdb_dbid_map); +PROTOCOL_TYPE3_TEST(struct ctdb_pulldb, ctdb_pulldb); +PROTOCOL_TYPE3_TEST(struct ctdb_pulldb_ext, ctdb_pulldb_ext); +PROTOCOL_TYPE3_TEST(struct ctdb_db_vacuum, ctdb_db_vacuum); +PROTOCOL_TYPE3_TEST(struct ctdb_echo_data, ctdb_echo_data); +PROTOCOL_TYPE1_TEST(struct ctdb_ltdb_header, ctdb_ltdb_header); +PROTOCOL_TYPE3_TEST(struct ctdb_rec_data, ctdb_rec_data); +PROTOCOL_TYPE3_TEST(struct ctdb_rec_buffer, ctdb_rec_buffer); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_start, ctdb_traverse_start); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_all, ctdb_traverse_all); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_start_ext, ctdb_traverse_start_ext); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_all_ext, ctdb_traverse_all_ext); +PROTOCOL_TYPE3_TEST(ctdb_sock_addr, ctdb_sock_addr); +PROTOCOL_TYPE3_TEST(struct ctdb_connection, ctdb_connection); +PROTOCOL_TYPE3_TEST(struct ctdb_connection_list, ctdb_connection_list); +PROTOCOL_TYPE3_TEST(struct ctdb_tunable, ctdb_tunable); +PROTOCOL_TYPE3_TEST(struct ctdb_node_flag_change, ctdb_node_flag_change); +PROTOCOL_TYPE3_TEST(struct ctdb_var_list, ctdb_var_list); +PROTOCOL_TYPE3_TEST(struct ctdb_tunable_list, ctdb_tunable_list); +PROTOCOL_TYPE3_TEST(struct ctdb_tickle_list, ctdb_tickle_list); +PROTOCOL_TYPE3_TEST(struct ctdb_addr_info, ctdb_addr_info); +PROTOCOL_TYPE3_TEST(struct ctdb_transdb, ctdb_transdb); +PROTOCOL_TYPE3_TEST(struct ctdb_uptime, ctdb_uptime); +PROTOCOL_TYPE3_TEST(struct ctdb_public_ip, ctdb_public_ip); +PROTOCOL_TYPE3_TEST(struct ctdb_public_ip_list, ctdb_public_ip_list); +PROTOCOL_TYPE3_TEST(struct ctdb_node_and_flags, ctdb_node_and_flags); +PROTOCOL_TYPE3_TEST(struct ctdb_node_map, ctdb_node_map); +PROTOCOL_TYPE3_TEST(struct ctdb_script, ctdb_script); +PROTOCOL_TYPE3_TEST(struct ctdb_script_list, ctdb_script_list); +PROTOCOL_TYPE3_TEST(struct ctdb_ban_state, ctdb_ban_state); +PROTOCOL_TYPE3_TEST(struct ctdb_notify_data, ctdb_notify_data); +PROTOCOL_TYPE3_TEST(struct ctdb_iface, ctdb_iface); +PROTOCOL_TYPE3_TEST(struct ctdb_iface_list, ctdb_iface_list); +PROTOCOL_TYPE3_TEST(struct ctdb_public_ip_info, ctdb_public_ip_info); +PROTOCOL_TYPE3_TEST(struct ctdb_statistics_list, ctdb_statistics_list); +PROTOCOL_TYPE3_TEST(struct ctdb_key_data, ctdb_key_data); +PROTOCOL_TYPE3_TEST(struct ctdb_db_statistics, ctdb_db_statistics); +PROTOCOL_TYPE3_TEST(struct ctdb_pid_srvid, ctdb_pid_srvid); +PROTOCOL_TYPE3_TEST(struct ctdb_election_message, ctdb_election_message); +PROTOCOL_TYPE3_TEST(struct ctdb_srvid_message, ctdb_srvid_message); +PROTOCOL_TYPE3_TEST(struct ctdb_disable_message, ctdb_disable_message); +PROTOCOL_TYPE1_TEST(struct ctdb_server_id, ctdb_server_id); +PROTOCOL_TYPE1_TEST(struct ctdb_g_lock, ctdb_g_lock); +PROTOCOL_TYPE3_TEST(struct ctdb_g_lock_list, ctdb_g_lock_list); + +PROTOCOL_TYPE1_TEST(struct sock_packet_header, sock_packet_header); + +static void test_ctdb_rec_buffer_read_write(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct ctdb_rec_buffer *p1, **p2; + const char *filename = "ctdb_rec_buffer_test.dat"; + int count = 100; + int fd, i, ret; + off_t offset; + + p1 = talloc_array(mem_ctx, struct ctdb_rec_buffer, count); + assert(p1 != NULL); + for (i=0; i<count; i++) { + fill_ctdb_rec_buffer(mem_ctx, &p1[i]); + } + + fd = open(filename, O_RDWR|O_CREAT, 0600); + assert(fd != -1); + unlink(filename); + + for (i=0; i<count; i++) { + ret = ctdb_rec_buffer_write(&p1[i], fd); + assert(ret == 0); + } + + offset = lseek(fd, 0, SEEK_CUR); + assert(offset != -1); + offset = lseek(fd, -offset, SEEK_CUR); + assert(offset == 0); + + p2 = talloc_array(mem_ctx, struct ctdb_rec_buffer *, count); + assert(p2 != NULL); + + for (i=0; i<count; i++) { + ret = ctdb_rec_buffer_read(fd, mem_ctx, &p2[i]); + assert(ret == 0); + } + + close(fd); + + for (i=0; i<count; i++) { + verify_ctdb_rec_buffer(&p1[i], p2[i]); + } + + talloc_free(mem_ctx); +} + +static void protocol_types_test(void) +{ + TEST_FUNC(ctdb_tdb_data)(); + TEST_FUNC(ctdb_tdb_datan)(); + TEST_FUNC(ctdb_latency_counter)(); + + TEST_FUNC(ctdb_statistics)(); + TEST_FUNC(ctdb_vnn_map)(); + TEST_FUNC(ctdb_dbid)(); + TEST_FUNC(ctdb_dbid_map)(); + TEST_FUNC(ctdb_pulldb)(); + TEST_FUNC(ctdb_pulldb_ext)(); + TEST_FUNC(ctdb_db_vacuum)(); + TEST_FUNC(ctdb_echo_data)(); + TEST_FUNC(ctdb_ltdb_header)(); + TEST_FUNC(ctdb_rec_data)(); + TEST_FUNC(ctdb_rec_buffer)(); + TEST_FUNC(ctdb_traverse_start)(); + TEST_FUNC(ctdb_traverse_all)(); + TEST_FUNC(ctdb_traverse_start_ext)(); + TEST_FUNC(ctdb_traverse_all_ext)(); + TEST_FUNC(ctdb_sock_addr)(); + TEST_FUNC(ctdb_connection)(); + TEST_FUNC(ctdb_connection_list)(); + TEST_FUNC(ctdb_tunable)(); + TEST_FUNC(ctdb_node_flag_change)(); + TEST_FUNC(ctdb_var_list)(); + TEST_FUNC(ctdb_tunable_list)(); + TEST_FUNC(ctdb_tickle_list)(); + TEST_FUNC(ctdb_addr_info)(); + TEST_FUNC(ctdb_transdb)(); + TEST_FUNC(ctdb_uptime)(); + TEST_FUNC(ctdb_public_ip)(); + TEST_FUNC(ctdb_public_ip_list)(); + TEST_FUNC(ctdb_node_and_flags)(); + TEST_FUNC(ctdb_node_map)(); + TEST_FUNC(ctdb_script)(); + TEST_FUNC(ctdb_script_list)(); + TEST_FUNC(ctdb_ban_state)(); + TEST_FUNC(ctdb_notify_data)(); + TEST_FUNC(ctdb_iface)(); + TEST_FUNC(ctdb_iface_list)(); + TEST_FUNC(ctdb_public_ip_info)(); + TEST_FUNC(ctdb_statistics_list)(); + TEST_FUNC(ctdb_key_data)(); + TEST_FUNC(ctdb_db_statistics)(); + TEST_FUNC(ctdb_pid_srvid)(); + TEST_FUNC(ctdb_election_message)(); + TEST_FUNC(ctdb_srvid_message)(); + TEST_FUNC(ctdb_disable_message)(); + TEST_FUNC(ctdb_server_id)(); + TEST_FUNC(ctdb_g_lock)(); + TEST_FUNC(ctdb_g_lock_list)(); + + TEST_FUNC(sock_packet_header)(); + + test_ctdb_rec_buffer_read_write(); +} + +int main(int argc, const char *argv[]) +{ + protocol_test_iterate(argc, argv, protocol_types_test); + return 0; +} diff --git a/ctdb/tests/src/protocol_util_test.c b/ctdb/tests/src/protocol_util_test.c new file mode 100644 index 0000000..4ffe58c --- /dev/null +++ b/ctdb/tests/src/protocol_util_test.c @@ -0,0 +1,417 @@ +/* + protocol utilities tests + + Copyright (C) Martin Schwenke 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <assert.h> + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_util.c" + +/* + * Test parsing of IPs, conversion to string + */ + +static void test_sock_addr_to_string(const char *ip, bool with_port) +{ + ctdb_sock_addr sa; + const char *s; + int ret; + + ret = ctdb_sock_addr_from_string(ip, &sa, with_port); + assert(ret == 0); + s = ctdb_sock_addr_to_string(NULL, &sa, with_port); + assert(strcmp(ip, s) == 0); + talloc_free(discard_const(s)); +} + +static void test_sock_addr_from_string_bad(const char *ip, bool with_port) +{ + ctdb_sock_addr sa; + int ret; + + ret = ctdb_sock_addr_from_string(ip, &sa, with_port); + assert(ret == EINVAL); +} + +static void test_sock_addr_from_string_memcmp(const char *ip1, + const char* ip2) +{ + ctdb_sock_addr sa1, sa2; + int ret; + + ret = ctdb_sock_addr_from_string(ip1, &sa1, false); + assert(ret == 0); + ret = ctdb_sock_addr_from_string(ip2, &sa2, false); + assert(ret == 0); + ret = memcmp(&sa1, &sa2, sizeof(ctdb_sock_addr)); + assert(ret == 0); +} + +static void test_sock_addr_cmp(const char *ip1, const char *ip2, + bool with_port, int res) +{ + ctdb_sock_addr sa1, sa2; + int ret; + + ret = ctdb_sock_addr_from_string(ip1, &sa1, with_port); + assert(ret == 0); + ret = ctdb_sock_addr_from_string(ip2, &sa2, with_port); + assert(ret == 0); + ret = ctdb_sock_addr_cmp(&sa1, &sa2); + if (ret < 0) { + ret = -1; + } else if (ret > 0) { + ret = 1; + } + + assert(ret == res); +} + +/* + * Test parsing of IP/mask, conversion to string + */ + +static void test_sock_addr_mask_from_string(const char *ip_mask) +{ + ctdb_sock_addr sa; + unsigned mask; + const char *s, *t; + int ret; + + ret = ctdb_sock_addr_mask_from_string(ip_mask, &sa, &mask); + assert(ret == 0); + s = ctdb_sock_addr_to_string(NULL, &sa, false); + assert(s != NULL); + t = talloc_asprintf(s, "%s/%u", s, mask); + assert(strcmp(ip_mask, t) == 0); + talloc_free(discard_const(s)); +} + +static void test_sock_addr_mask_from_string_bad(const char *ip_mask) +{ + ctdb_sock_addr sa; + unsigned mask; + int ret; + + ret = ctdb_sock_addr_mask_from_string(ip_mask, &sa, &mask); + assert(ret == EINVAL); +} + +/* + * Test parsing of connection, conversion to string + */ + +static void test_connection_to_string(const char *conn_str) +{ + TALLOC_CTX *tmp_ctx; + struct ctdb_connection conn; + const char *s, *r; + int ret; + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + /* + * Test non-reversed parse and render + */ + + ret = ctdb_connection_from_string(conn_str, false, &conn); + assert(ret == 0); + + s = ctdb_connection_to_string(tmp_ctx, &conn, false); + assert(s != NULL); + ret = strcmp(conn_str, s); + assert(ret == 0); + + talloc_free(discard_const(s)); + + /* + * Reversed render + */ + r = ctdb_connection_to_string(tmp_ctx, &conn, true); + assert(r != NULL); + ret = strcmp(conn_str, r); + assert(ret != 0); + + /* + * Reversed parse with forward render + */ + ret = ctdb_connection_from_string(conn_str, true, &conn); + assert(ret == 0); + + s = ctdb_connection_to_string(tmp_ctx, &conn, false); + assert(s != NULL); + ret = strcmp(r, s); + assert(ret == 0); + + talloc_free(discard_const(s)); + + /* + * Reversed parse and render + */ + ret = ctdb_connection_from_string(conn_str, true, &conn); + assert(ret == 0); + + s = ctdb_connection_to_string(tmp_ctx, &conn, true); + assert(s != NULL); + ret = strcmp(conn_str, s); + assert(ret == 0); + + talloc_free(tmp_ctx); +} + +static void test_connection_from_string_bad(const char *conn_str) +{ + struct ctdb_connection conn; + int ret; + + ret = ctdb_connection_from_string(conn_str, false, &conn); + assert(ret == EINVAL); +} + +/* + * Test connection list utilities + */ + +static void test_connection_list_read(const char *s1, const char *s2) +{ + TALLOC_CTX *tmp_ctx; + int pipefd[2]; + pid_t pid; + struct ctdb_connection_list *conn_list = NULL; + const char *t; + int ret; + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + close(pipefd[0]); + + ret = dup2(pipefd[1], STDOUT_FILENO); + assert(ret != -1); + + close(pipefd[1]); + + printf("%s", s1); + fflush(stdout); + + exit(0); + } + + close(pipefd[1]); + + ret = ctdb_connection_list_read(tmp_ctx, pipefd[0], false, &conn_list); + assert(ret == 0); + + close(pipefd[0]); + + ret = ctdb_connection_list_sort(conn_list); + assert(ret == 0); + + t = ctdb_connection_list_to_string(tmp_ctx, conn_list, false); + assert(t != NULL); + ret = strcmp(t, s2); + assert(ret == 0); + + talloc_free(tmp_ctx); +} + +static void test_connection_list_read_bad(const char *s1) +{ + TALLOC_CTX *tmp_ctx; + int pipefd[2]; + pid_t pid; + struct ctdb_connection_list *conn_list = NULL; + int ret; + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + close(pipefd[0]); + + ret = dup2(pipefd[1], STDOUT_FILENO); + assert(ret != -1); + + close(pipefd[1]); + + printf("%s", s1); + fflush(stdout); + + exit(0); + } + + close(pipefd[1]); + + ret = ctdb_connection_list_read(tmp_ctx, pipefd[0], false, &conn_list); + assert(ret == EINVAL); + + close(pipefd[0]); + + talloc_free(tmp_ctx); +} + +/* + * Use macros for these to make them easy to concatenate + */ + +#define CONN4 \ +"\ +127.0.0.1:12345 127.0.0.2:54321\n\ +127.0.0.2:12345 127.0.0.1:54322\n\ +127.0.0.1:12346 127.0.0.2:54323\n\ +127.0.0.2:12345 127.0.0.1:54324\n\ +127.0.0.1:12345 127.0.0.2:54325\n\ +" + +#define CONN4_SORT \ +"\ +127.0.0.1:12345 127.0.0.2:54321\n\ +127.0.0.1:12345 127.0.0.2:54325\n\ +127.0.0.1:12346 127.0.0.2:54323\n\ +127.0.0.2:12345 127.0.0.1:54322\n\ +127.0.0.2:12345 127.0.0.1:54324\n\ +" + +#define CONN6 \ +"\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54321\n\ +[fe80::6af7:28ff:fefa:d138]:12345 [fe80::6af7:28ff:fefa:d137]:54322\n\ +[fe80::6af7:28ff:fefa:d136]:12346 [fe80::6af7:28ff:fefa:d137]:54323\n\ +[fe80::6af7:28ff:fefa:d132]:12345 [fe80::6af7:28ff:fefa:d137]:54324\n\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54325\n\ +" + +#define CONN6_SORT \ +"\ +[fe80::6af7:28ff:fefa:d132]:12345 [fe80::6af7:28ff:fefa:d137]:54324\n\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54321\n\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54325\n\ +[fe80::6af7:28ff:fefa:d136]:12346 [fe80::6af7:28ff:fefa:d137]:54323\n\ +[fe80::6af7:28ff:fefa:d138]:12345 [fe80::6af7:28ff:fefa:d137]:54322\n\ +" + +int main(int argc, char *argv[]) +{ + test_sock_addr_to_string("0.0.0.0", false); + test_sock_addr_to_string("127.0.0.1", false); + test_sock_addr_to_string("::1", false); + test_sock_addr_to_string("192.168.2.1", false); + test_sock_addr_to_string("fe80::6af7:28ff:fefa:d136", false); + + test_sock_addr_to_string("0.0.0.0:0", true); + test_sock_addr_to_string("127.0.0.1:123", true); + test_sock_addr_to_string("[::1]:234", true); + test_sock_addr_to_string("192.168.2.1:123", true); + test_sock_addr_to_string("[fe80::6af7:28ff:fefa:d136]:234", true); + + test_sock_addr_from_string_bad("0.0.0", false); + test_sock_addr_from_string_bad("0.0.0:0", true); + test_sock_addr_from_string_bad("fe80::6af7:28ff:fefa:d136", true); + test_sock_addr_from_string_bad("junk", false); + test_sock_addr_from_string_bad("0.0.0.0:0 trailing junk", true); + + test_sock_addr_from_string_memcmp("127.0.0.1", "127.0.0.1"); + test_sock_addr_from_string_memcmp("fe80::6af7:28ff:fefa:d136", + "fe80::6af7:28ff:fefa:d136"); + test_sock_addr_from_string_memcmp("::ffff:192.0.2.128", "192.0.2.128"); + + test_sock_addr_cmp("127.0.0.1", "127.0.0.1" , false, 0); + test_sock_addr_cmp("127.0.0.1", "127.0.0.2" , false, -1); + test_sock_addr_cmp("127.0.0.2", "127.0.0.1" , false, 1); + test_sock_addr_cmp("127.0.1.2", "127.0.2.1" , false, -1); + test_sock_addr_cmp("127.0.2.1", "127.0.1.2" , false, 1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", "127.0.1.2" , false, 1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", + "fe80::6af7:28ff:fefa:d136" , false, 0); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", + "fe80::6af7:28ff:fefa:d137" , false, -1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", + "fe80:0000:0000:0000:6af7:28ff:fefa:d136" , + false, 0); + test_sock_addr_cmp("::ffff:192.0.2.128", "192.0.2.128", false, 0); + + test_sock_addr_cmp("127.0.0.1:123", "127.0.0.1:124" , true, -1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136:123", + "fe80::6af7:28ff:fefa:d136:122" , true, 1); + + /* + * Confirm equivalence of IPv6 sockets with and without + * square-brackets + */ + test_sock_addr_cmp("[::1]:234", "::1:234", true, 0); + test_sock_addr_cmp("[fe80::6af7:28ff:fefa:d136]:234", + "fe80::6af7:28ff:fefa:d136:234", + true, + 0); + /* Check IPv4-mapped IPv6 addresses */ + test_sock_addr_cmp("::ffff:172.16.0.27:977", + "172.16.0.27:977", + true, + 0); + test_sock_addr_cmp("[::ffff:172.16.0.27]:977", + "172.16.0.27:977", + true, + 0); + + test_sock_addr_mask_from_string("127.0.0.1/8"); + test_sock_addr_mask_from_string("::1/128"); + test_sock_addr_mask_from_string("fe80::6af7:28ff:fefa:d136/64"); + test_sock_addr_mask_from_string_bad("127.0.0.1"); + + test_connection_to_string("127.0.0.1:12345 127.0.0.2:54321"); + test_connection_to_string("[fe80::6af7:28ff:fefa:d137]:12345 " + "[fe80::6af7:28ff:fefa:d138]:54321"); + + test_connection_from_string_bad("127.0.0.1:12345 127.0.0.2:"); + test_connection_from_string_bad("127.0.0.1:12345"); + test_connection_from_string_bad("127.0.0.1:12345 " + "[fe80::6af7:28ff:fefa:d136]:122"); + test_connection_from_string_bad("Junk!"); + test_connection_from_string_bad("More junk"); + + test_connection_list_read(CONN4, CONN4_SORT); + test_connection_list_read(CONN6, CONN6_SORT); + test_connection_list_read(CONN4 CONN6, CONN4_SORT CONN6_SORT); + test_connection_list_read(CONN4 "# Comment\n\n# Comment\n" CONN6, + CONN4_SORT CONN6_SORT); + + test_connection_list_read_bad(CONN4 "# Comment\n\nJunk!!!\n" CONN6); + test_connection_list_read_bad(CONN4 + "# Comment\n\n127.0.0.1: 127.0.0.1:124\n" + CONN6); + + return 0; +} diff --git a/ctdb/tests/src/rb_test.c b/ctdb/tests/src/rb_test.c new file mode 100644 index 0000000..d712c9a --- /dev/null +++ b/ctdb/tests/src/rb_test.c @@ -0,0 +1,336 @@ +/* + simple rb test tool + + Copyright (C) Ronnie Sahlberg 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/>. +*/ + +#include "replace.h" +#include "system/time.h" + +#include <talloc.h> +#include <assert.h> + +#include "lib/util/dlinklist.h" +#include "lib/util/debug.h" + +#include "common/rb_tree.c" + +static struct timeval tp1,tp2; + +static void start_timer(void) +{ + gettimeofday(&tp1,NULL); +} + +static double end_timer(void) +{ + gettimeofday(&tp2,NULL); + return (tp2.tv_sec + (tp2.tv_usec*1.0e-6)) - + (tp1.tv_sec + (tp1.tv_usec*1.0e-6)); +} + +int num_records=5; + +static void *callback(void *p, void *d) +{ + uint32_t *data = (uint32_t *)d; + + if (d==NULL) { + data = (uint32_t *)p; + } + + (*data)++; + + return data; +} + +static void *random_add(void *p, void *d) +{ + return p; +} + +static int traverse(void *p, void *d) +{ + uint32_t *data = (uint32_t *)d; + + printf("traverse data:%d\n",*data); + return 0; +} + +static int random_traverse(void *p, void *d) +{ + printf("%s ",(char *)d); + return 0; +} + +static uint32_t calc_checksum = 0; +static int traverse_checksum(void *p, void *d) +{ + int i,j,k; + + sscanf(d, "%d.%d.%d", &i, &j, &k); + calc_checksum += i*100+j*10+k; + return 0; +} + +static int count_traverse(void *p, void *d) +{ + int *count = p; + (*count)++; + return 0; +} + +static int count_traverse_abort(void *p, void *d) +{ + int *count = p; + (*count)++; + return -1; +} + +/* + main program +*/ +int main(int argc, const char *argv[]) +{ + int traverse_count; + int i,j,k; + trbt_tree_t *tree; + uint32_t *data; + uint32_t key[3]; + uint32_t key1[3] = {0,10,20}; + uint32_t key2[3] = {0,10,21}; + uint32_t key3[3] = {0,11,20}; + uint32_t key4[3] = {2,10,20}; + TALLOC_CTX *memctx; + uint32_t **u32array; + uint32_t checksum; + + /* testing trbt_insert32_callback for num_records */ + memctx = talloc_new(NULL); + assert(memctx != NULL); + + u32array = talloc_array(memctx, uint32_t *, num_records); + assert(u32array != NULL); + + tree = trbt_create(memctx, 0); + assert(tree != NULL); + + for (i=0; i<num_records; i++) { + u32array[i] = talloc(u32array, uint32_t); + assert(u32array[i] != NULL); + *u32array[i] = 0; + trbt_insert32_callback(tree, i, callback, u32array[i]); + } + for (i=3; i<num_records; i++) { + trbt_insert32_callback(tree, i, callback, NULL); + } + + /* first 3 keys should have data == 1 + * the rest of the keys should have data == 2 + */ + for (i=0; i<num_records; i++) { + data = trbt_lookup32(tree, i); + assert(data != NULL); + if (i < 3) { + assert(*data == 1); + } else { + assert(*data == 2); + } + } + + /* deleting key 2 */ + talloc_free(u32array[2]); + + /* deleting key 1 */ + talloc_free(u32array[1]); + + assert(talloc_total_size(memctx) == 212); + + /* freeing tree */ + talloc_free(memctx); + + + printf("testing trbt_insertarray32_callback\n"); + memctx = talloc_new(NULL); + assert(memctx != NULL); + + tree = trbt_create(memctx, 0); + assert(tree != NULL); + + u32array = talloc_array(memctx, uint32_t *, 4); + assert(u32array != NULL); + + for (i=0;i<4;i++) { + u32array[i] = talloc(u32array, uint32_t); + assert(u32array[i] != NULL); + *u32array[i] = 0; + } + + trbt_insertarray32_callback(tree, 3, key1, callback, u32array[0]); + trbt_insertarray32_callback(tree, 3, key1, callback, u32array[0]); + trbt_insertarray32_callback(tree, 3, key2, callback, u32array[1]); + trbt_insertarray32_callback(tree, 3, key3, callback, u32array[2]); + trbt_insertarray32_callback(tree, 3, key2, callback, u32array[1]); + trbt_insertarray32_callback(tree, 3, key1, callback, u32array[0]); + + data = trbt_lookuparray32(tree, 3, key1); + assert(data != NULL && *data == 3); + data = trbt_lookuparray32(tree, 3, key2); + assert(data != NULL && *data == 2); + data = trbt_lookuparray32(tree, 3, key3); + assert(data != NULL && *data == 1); + data = trbt_lookuparray32(tree, 3, key4); + assert(data == NULL); + trbt_traversearray32(tree, 3, traverse, NULL); + + printf("\ndeleting key4\n"); + talloc_free(trbt_lookuparray32(tree, 3, key4)); + + data = trbt_lookuparray32(tree, 3, key1); + assert(data != NULL && *data == 3); + data = trbt_lookuparray32(tree, 3, key2); + assert(data != NULL && *data == 2); + data = trbt_lookuparray32(tree, 3, key3); + assert(data != NULL && *data == 1); + data = trbt_lookuparray32(tree, 3, key4); + assert(data == NULL); + trbt_traversearray32(tree, 3, traverse, NULL); + + printf("\ndeleting key2\n"); + talloc_free(trbt_lookuparray32(tree, 3, key2)); + + data = trbt_lookuparray32(tree, 3, key1); + assert(data != NULL && *data == 3); + data = trbt_lookuparray32(tree, 3, key2); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key3); + assert(data != NULL && *data == 1); + data = trbt_lookuparray32(tree, 3, key4); + assert(data == NULL); + trbt_traversearray32(tree, 3, traverse, NULL); + + printf("\ndeleting key3\n"); + talloc_free(trbt_lookuparray32(tree, 3, key3)); + + data = trbt_lookuparray32(tree, 3, key1); + assert(data != NULL && *data == 3); + data = trbt_lookuparray32(tree, 3, key2); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key3); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key4); + assert(data == NULL); + trbt_traversearray32(tree, 3, traverse, NULL); + + printf("\ndeleting key1\n"); + talloc_free(trbt_lookuparray32(tree, 3, key1)); + + data = trbt_lookuparray32(tree, 3, key1); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key2); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key3); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key4); + assert(data == NULL); + trbt_traversearray32(tree, 3, traverse, NULL); + + talloc_free(tree); + talloc_free(memctx); + + + printf("\nrun random insert and delete for 60 seconds\n"); + memctx = talloc_new(NULL); + assert(memctx != NULL); + + tree = trbt_create(memctx, 0); + assert(tree != NULL); + + i=0; + start_timer(); + checksum = 0; + /* Add and delete nodes from a 3 level tree for 60 seconds. + Each time a node is added or deleted, traverse the tree and + compute a checksum over the data stored in the tree and compare this + with a checksum we keep which contains what the checksum should be + */ + while(end_timer() < 60.0){ + char *str; + + i++; + key[0]=random()%10; + key[1]=random()%10; + key[2]=random()%10; + + if (random()%2) { + if (trbt_lookuparray32(tree, 3, key) == NULL) { + /* this node does not yet exist, add it to the + tree and update the checksum + */ + str=talloc_asprintf(memctx, "%d.%d.%d", key[0],key[1],key[2]); + trbt_insertarray32_callback(tree, 3, key, random_add, str); + checksum += key[0]*100+key[1]*10+key[2]; + } + } else { + if ((str=trbt_lookuparray32(tree, 3, key)) != NULL) { + /* this node does exist in the tree, delete + it and update the checksum accordingly + */ + talloc_free(str); + checksum -= key[0]*100+key[1]*10+key[2]; + } + } + /* traverse all nodes in the tree and calculate the checksum + it better match the one we keep track of in + 'checksum' + */ + calc_checksum = 0; + trbt_traversearray32(tree, 3, traverse_checksum, NULL); + assert(checksum == calc_checksum); + } + + /* + printf("\niterations passed:%d\n", i); + trbt_traversearray32(tree, 3, random_traverse, NULL); + printf("\n"); + printf("first node: %s\n", (char *)trbt_findfirstarray32(tree, 3)); + */ + + traverse_count = 0; + trbt_traversearray32(tree, 3, count_traverse, &traverse_count); + assert(traverse_count > 0); + + traverse_count = 0; + trbt_traversearray32(tree, 3, count_traverse_abort, &traverse_count); + assert(traverse_count == 1); + + printf("\ndeleting all entries\n"); + for(i=0;i<10;i++){ + for(j=0;j<10;j++){ + for(k=0;k<10;k++){ + key[0]=i; + key[1]=j; + key[2]=k; + talloc_free(trbt_lookuparray32(tree, 3, key)); + } + } + } + trbt_traversearray32(tree, 3, random_traverse, NULL); + + assert(talloc_total_size(memctx) == 16); + + return 0; +} diff --git a/ctdb/tests/src/reqid_test.c b/ctdb/tests/src/reqid_test.c new file mode 100644 index 0000000..2a0828c --- /dev/null +++ b/ctdb/tests/src/reqid_test.c @@ -0,0 +1,89 @@ +/* + reqid tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> +#include <assert.h> + +#include "common/reqid.c" + + +int main(void) +{ + struct reqid_context *reqid_ctx; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + int i, ret; + uint32_t reqid; + int *data, *tmp; + + ret = reqid_init(mem_ctx, INT_MAX-200, &reqid_ctx); + assert(ret == 0); + + data = talloc_zero(mem_ctx, int); + assert(data != 0); + + for (i=0; i<1024*1024; i++) { + reqid = reqid_new(reqid_ctx, data); + assert(reqid != REQID_INVALID); + } + + for (i=0; i<1024; i++) { + tmp = reqid_find(reqid_ctx, i, int); + assert(tmp == data); + } + + for (i=0; i<1024; i++) { + ret = reqid_remove(reqid_ctx, i); + assert(ret == 0); + } + + for (i=0; i<1024; i++) { + tmp = reqid_find(reqid_ctx, i, int); + assert(tmp == NULL); + } + + for (i=0; i<1024; i++) { + ret = reqid_remove(reqid_ctx, i); + assert(ret == ENOENT); + } + + talloc_free(reqid_ctx); + assert(talloc_get_size(mem_ctx) == 0); + + ret = reqid_init(mem_ctx, INT_MAX-1, &reqid_ctx); + assert(ret == 0); + + reqid = reqid_new(reqid_ctx, data); + assert(reqid == INT_MAX); + + reqid = reqid_new(reqid_ctx, data); + assert(reqid == 0); + + reqid_remove(reqid_ctx, 0); + + reqid = reqid_new(reqid_ctx, data); + assert(reqid == 1); + + talloc_free(reqid_ctx); + + talloc_free(mem_ctx); + + return 0; +} diff --git a/ctdb/tests/src/run_event_test.c b/ctdb/tests/src/run_event_test.c new file mode 100644 index 0000000..9454864 --- /dev/null +++ b/ctdb/tests/src/run_event_test.c @@ -0,0 +1,251 @@ +/* + run_event test wrapper + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> +#include <tevent.h> + +#include "common/db_hash.c" +#include "common/run_proc.c" +#include "common/event_script.c" +#include "common/run_event.c" + +static void usage(const char *prog) +{ + fprintf(stderr, "Usage: %s <scriptdir> run|list|enable|disable <options>\n", prog); + fprintf(stderr, " %s <scriptdir> run <timeout> <event> [<args>]\n", prog); + fprintf(stderr, " %s <scriptdir> list\n", prog); + fprintf(stderr, " %s <scriptdir> enable <scriptname>\n", prog); + fprintf(stderr, " %s <scriptdir> disable <scriptname>\n", prog); +} + +static char *compact_args(const char **argv, int argc, int from) +{ + char *arg_str = NULL; + int i; + + for (i = from; i < argc; i++) { + arg_str = talloc_asprintf_append(arg_str, "%s ", argv[i]); + if (arg_str == NULL) { + fprintf(stderr, "talloc_asprintf_append() failed\n"); + exit(1); + } + } + + return arg_str; +} + +static void run_done(struct tevent_req *req) +{ + struct run_event_script_list **script_list = + tevent_req_callback_data_void(req); + bool status; + int ret; + + status = run_event_recv(req, &ret, NULL, script_list); + if (!status) { + fprintf(stderr, "run_event_recv() failed, ret=%d\n", ret); + } +} + +static void do_run(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + struct tevent_req *req; + struct timeval timeout; + struct run_event_script_list *script_list = NULL; + char *arg_str; + unsigned int i; + int t; + bool wait_for_signal = false; + + if (argc < 5) { + usage(argv[0]); + exit(1); + } + + t = atoi(argv[3]); + if (t > 0) { + timeout = tevent_timeval_current_ofs(t, 0); + } else { + timeout = tevent_timeval_zero(); + } + + arg_str = compact_args(argv, argc, 5); + + req = run_event_send(mem_ctx, + ev, + run_ctx, + argv[4], + arg_str, + timeout, + false); + if (req == NULL) { + fprintf(stderr, "run_event_send() failed\n"); + return; + } + + tevent_req_set_callback(req, run_done, &script_list); + + tevent_req_poll(req, ev); + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No event scripts found\n"); + return; + } + + printf("Event %s completed with result=%d\n", + argv[4], script_list->summary); + for (i=0; i<script_list->num_scripts; i++) { + struct run_event_script *s = &script_list->script[i]; + printf("%s result=%d\n", s->name, s->summary); + + if (s->summary == -ETIMEDOUT) { + wait_for_signal = true; + } + } + + TALLOC_FREE(script_list); + TALLOC_FREE(req); + + if (!wait_for_signal) { + return; + } + + req = tevent_wakeup_send( + ev, ev, tevent_timeval_current_ofs(10, 0)); + if (req == NULL) { + fprintf(stderr, "Could not wait for signal\n"); + return; + } + + tevent_req_poll(req, ev); + TALLOC_FREE(req); +} + +static void do_list(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + struct run_event_script_list *script_list = NULL; + unsigned int i; + int ret; + + ret = run_event_list(run_ctx, mem_ctx, &script_list); + if (ret != 0) { + printf("Script list failed with result=%d\n", ret); + return; + } + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No event scripts found\n"); + return; + } + + for (i=0; i<script_list->num_scripts; i++) { + printf("%s\n", script_list->script[i].name); + } +} + +static void do_enable(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = run_event_script_enable(run_ctx, argv[3]); + printf("Script enable %s completed with result=%d\n", argv[3], ret); +} + +static void do_disable(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = run_event_script_disable(run_ctx, argv[3]); + printf("Script disable %s completed with result=%d\n", argv[3], ret); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct run_proc_context *run_proc_ctx = NULL; + struct run_event_context *run_ctx = NULL; + int ret; + + if (argc < 3) { + usage(argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "talloc_new() failed\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "tevent_context_init() failed\n"); + exit(1); + } + + ret = run_proc_init(mem_ctx, ev, &run_proc_ctx); + if (ret != 0) { + fprintf(stderr, "run_proc_init() failed, ret=%d\n", ret); + exit(1); + } + + ret = run_event_init(mem_ctx, run_proc_ctx, argv[1], NULL, &run_ctx); + if (ret != 0) { + fprintf(stderr, "run_event_init() failed, ret=%d\n", ret); + exit(1); + } + + if (strcmp(argv[2], "run") == 0) { + do_run(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "list") == 0) { + do_list(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "enable") == 0) { + do_enable(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "disable") == 0) { + do_disable(mem_ctx, ev, run_ctx, argc, argv); + } else { + fprintf(stderr, "Invalid command %s\n", argv[2]); + usage(argv[0]); + } + + talloc_free(mem_ctx); + exit(0); +} + diff --git a/ctdb/tests/src/run_proc_test.c b/ctdb/tests/src/run_proc_test.c new file mode 100644 index 0000000..7cfb870 --- /dev/null +++ b/ctdb/tests/src/run_proc_test.c @@ -0,0 +1,111 @@ +/* + run_proc test wrapper + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> +#include <tevent.h> + +#include "common/db_hash.c" +#include "common/run_proc.c" + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + struct run_proc_context *run_ctx; + struct timeval tv; + char *output; + struct run_proc_result result; + pid_t pid; + int timeout, ret, fd; + bool status; + + if (argc < 4) { + fprintf(stderr, + "Usage: %s <timeout> <stdin-fd> <program> <args>\n", + argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "talloc_new() failed\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "tevent_context_init() failed\n"); + exit(1); + } + + timeout = atoi(argv[1]); + if (timeout <= 0) { + tv = tevent_timeval_zero(); + } else { + tv = tevent_timeval_current_ofs(timeout, 0); + } + + fd = atoi(argv[2]); + if (fd < 0) { + fd = -1; + } + + ret = run_proc_init(mem_ctx, ev, &run_ctx); + if (ret != 0) { + fprintf(stderr, "run_proc_init() failed, ret=%d\n", ret); + exit(1); + } + + req = run_proc_send(mem_ctx, ev, run_ctx, argv[3], &argv[3], fd, tv); + if (req == NULL) { + fprintf(stderr, "run_proc_send() failed\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = run_proc_recv(req, &ret, &result, &pid, mem_ctx, &output); + if (! status) { + fprintf(stderr, "run_proc_recv() failed, ret=%d\n", ret); + exit(1); + } + + if (result.sig > 0) { + printf("Process exited with signal %d\n", result.sig); + } else if (result.err > 0) { + printf("Process exited with error %d\n", result.err); + } else { + printf("Process exited with status %d\n", result.status); + } + + if (pid != -1) { + printf("Child = %d\n", pid); + } + + if (output != NULL) { + printf("Output = (%s)\n", output); + } + + talloc_free(mem_ctx); + + exit(0); +} diff --git a/ctdb/tests/src/sigcode.c b/ctdb/tests/src/sigcode.c new file mode 100644 index 0000000..9e5ed81 --- /dev/null +++ b/ctdb/tests/src/sigcode.c @@ -0,0 +1,120 @@ +/* + Portability layer for signal codes + + Copyright (C) Amitay Isaacs 2018 + + 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/>. +*/ + +/* + * These signals are as listed in POSIX standard + * IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008) + */ + +#include "replace.h" +#include "system/wait.h" + +struct { + const char *label; + int code; +} sig_codes[] = { + { "SIGABRT", SIGABRT }, + { "SIGALRM", SIGALRM }, + { "SIBGUS", SIGBUS }, + { "SIGCHLD", SIGCHLD }, + { "SIGCONT", SIGCONT }, + { "SIGFPE", SIGFPE }, + { "SIGHUP", SIGHUP }, + { "SIGILL", SIGILL }, + { "SIGINT", SIGINT }, + { "SIGKILL", SIGKILL }, + { "SIGPIPE", SIGPIPE }, + { "SIGQUIT", SIGQUIT }, + { "SIGSEGV", SIGSEGV }, + { "SIGSTOP", SIGSTOP }, + { "SIGTERM", SIGTERM }, + { "SIGTSTP", SIGTSTP }, + { "SIGTTIN", SIGTTIN }, + { "SIGTTOU", SIGTTOU }, + { "SIGUSR1", SIGUSR1 }, + { "SIGUSR2", SIGUSR2 }, + { "SIGTRAP", SIGTRAP }, + { "SIGURG", SIGURG }, + { "SIGXCPU", SIGXCPU }, + { "SIGXFSZ", SIGXFSZ }, + +}; + +static void dump(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(sig_codes); i++) { + printf("%s %d\n", sig_codes[i].label, sig_codes[i].code); + } +} + +static void match_label(const char *str) +{ + int code = -1; + size_t i; + + for (i=0; i<ARRAY_SIZE(sig_codes); i++) { + if (strcasecmp(sig_codes[i].label, str) == 0) { + code = sig_codes[i].code; + break; + } + } + + printf("%d\n", code); +} + +static void match_code(int code) +{ + const char *label = "UNKNOWN"; + size_t i; + + for (i=0; i<ARRAY_SIZE(sig_codes); i++) { + if (sig_codes[i].code == code) { + label = sig_codes[i].label; + break; + } + } + + printf("%s\n", label); +} + +int main(int argc, const char **argv) +{ + long int code; + char *endptr; + + if (argc != 2) { + fprintf(stderr, "Usage: %s dump|<sigcode>\n", argv[0]); + exit(1); + } + + if (strcmp(argv[1], "dump") == 0) { + dump(); + } else { + code = strtol(argv[1], &endptr, 0); + if (*endptr == '\0') { + match_code(code); + } else { + match_label(argv[1]); + } + } + + exit(0); +} diff --git a/ctdb/tests/src/sock_daemon_test.c b/ctdb/tests/src/sock_daemon_test.c new file mode 100644 index 0000000..acafc9f --- /dev/null +++ b/ctdb/tests/src/sock_daemon_test.c @@ -0,0 +1,1980 @@ +/* + sock daemon tests + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/wait.h" + +#include <assert.h> + +#include "common/logging.c" +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" +#include "common/pidfile.c" +#include "common/sock_daemon.c" +#include "common/sock_io.c" + +struct dummy_wait_state { +}; + +static struct tevent_req *dummy_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct dummy_wait_state *state; + const char *sockpath = (const char *)private_data; + struct stat st; + int ret; + + ret = stat(sockpath, &st); + assert(ret == 0); + assert(S_ISSOCK(st.st_mode)); + + req = tevent_req_create(mem_ctx, &state, struct dummy_wait_state); + if (req == NULL) { + return NULL; + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static bool dummy_wait_recv(struct tevent_req *req, int *perr) +{ + return true; +} + +static int test1_startup_fail(void *private_data) +{ + return 1; +} + +static int test1_startup(void *private_data) +{ + const char *sockpath = (const char *)private_data; + struct stat st; + int ret; + + ret = stat(sockpath, &st); + assert(ret == -1); + + return 0; +} + +struct test1_startup_state { +}; + +static struct tevent_req *test1_startup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test1_startup_state *state; + + req = tevent_req_create(mem_ctx, &state, struct test1_startup_state); + if (req == NULL) { + return NULL; + } + + tevent_req_error(req, 2); + return tevent_req_post(req, ev); +} + +static bool test1_startup_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static struct tevent_req *dummy_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *client, + uint8_t *buf, size_t buflen, + void *private_data) +{ + return NULL; +} + +static bool dummy_read_recv(struct tevent_req *req, int *perr) +{ + if (perr != NULL) { + *perr = EINVAL; + } + return false; +} + +static struct sock_socket_funcs dummy_socket_funcs = { + .read_send = dummy_read_send, + .read_recv = dummy_read_recv, +}; + +/* + * test1 + * + * Check setup without actually running daemon + */ + +static void test1(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct sock_daemon_funcs test1_funcs; + struct stat st; + int ret; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + test1_funcs = (struct sock_daemon_funcs){ + .startup = test1_startup_fail, + }; + + ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE", + &test1_funcs, NULL, &sockd); + assert(ret == 0); + assert(sockd != NULL); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, -1); + assert(ret == EIO); + talloc_free(sockd); + + test1_funcs = (struct sock_daemon_funcs){ + .startup_send = test1_startup_send, + .startup_recv = test1_startup_recv, + }; + + ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE", + &test1_funcs, NULL, &sockd); + assert(ret == 0); + assert(sockd != NULL); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, -1); + assert(ret == EIO); + talloc_free(sockd); + + test1_funcs = (struct sock_daemon_funcs){ + .startup = test1_startup, + .wait_send = dummy_wait_send, + .wait_recv = dummy_wait_recv, + }; + + ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE", + &test1_funcs, discard_const(sockpath), &sockd); + assert(ret == 0); + assert(sockd != NULL); + + ret = sock_daemon_add_unix(sockd, sockpath, &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = stat(sockpath, &st); + assert(ret == -1); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, -1); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +/* + * test2 + * + * Start daemon, check PID file, sock daemon functions, termination, + * exit code + */ + +static int test2_startup(void *private_data) +{ + int fd = *(int *)private_data; + int ret = 1; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + return 0; +} + +static int test2_reconfigure(void *private_data) +{ + static bool first_time = true; + int fd = *(int *)private_data; + int ret = 2; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (first_time) { + first_time = false; + return 1; + } + + return 0; +} + +struct test2_reconfigure_state { + int fd; +}; + +static struct tevent_req *test2_reconfigure_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test2_reconfigure_state *state; + static bool first_time = true; + + req = tevent_req_create(mem_ctx, &state, + struct test2_reconfigure_state); + if (req == NULL) { + return NULL; + } + + state->fd = *(int *)private_data; + + if (first_time) { + first_time = false; + tevent_req_error(req, 2); + } else { + tevent_req_done(req); + } + + return tevent_req_post(req, ev); +} + +static bool test2_reconfigure_recv(struct tevent_req *req, int *perr) +{ + struct test2_reconfigure_state *state = tevent_req_data( + req, struct test2_reconfigure_state); + int ret = 2; + ssize_t nwritten; + + nwritten = write(state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static int test2_reopen_logs(void *private_data) +{ + static bool first_time = true; + int fd = *(int *)private_data; + int ret = 4; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (first_time) { + first_time = false; + return 1; + } + + return 0; +} + +struct test2_reopen_logs_state { + int fd; +}; + +static struct tevent_req *test2_reopen_logs_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test2_reopen_logs_state *state; + static bool first_time = true; + + req = tevent_req_create(mem_ctx, &state, + struct test2_reopen_logs_state); + if (req == NULL) { + return NULL; + } + + state->fd = *(int *)private_data; + + if (first_time) { + first_time = false; + tevent_req_error(req, 2); + } else { + tevent_req_done(req); + } + + return tevent_req_post(req, ev); +} + +static bool test2_reopen_logs_recv(struct tevent_req *req, int *perr) +{ + struct test2_reopen_logs_state *state = tevent_req_data( + req, struct test2_reopen_logs_state); + int ret = 4; + ssize_t nwritten; + + nwritten = write(state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static void test2_shutdown(void *private_data) +{ + int fd = *(int *)private_data; + int ret = 3; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); +} + +struct test2_shutdown_state { + int fd; +}; + +static struct tevent_req *test2_shutdown_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test2_shutdown_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct test2_shutdown_state); + if (req == NULL) { + return NULL; + } + + state->fd = *(int *)private_data; + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static void test2_shutdown_recv(struct tevent_req *req) +{ + struct test2_shutdown_state *state = tevent_req_data( + req, struct test2_shutdown_state); + int ret = 3; + ssize_t nwritten; + + nwritten = write(state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); +} + +static void test2(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + int fd[2]; + pid_t pid, pid2; + int ret; + ssize_t n; + int pidfile_fd; + char pidstr[20] = { 0 }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct sock_daemon_funcs test2_funcs = { + .startup = test2_startup, + .reconfigure = test2_reconfigure, + .reopen_logs = test2_reopen_logs, + .shutdown = test2_shutdown, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test2", "file:", "NOTICE", + &test2_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + pidfile_fd = open(pidfile, O_RDONLY, 0644); + assert(pidfile_fd != -1); + ret = fstat(pidfile_fd, &st); + assert(ret == 0); + assert(S_ISREG(st.st_mode)); + n = read(pidfile_fd, pidstr, sizeof(pidstr)-1); + assert(n != -1); + pid2 = (pid_t)atoi(pidstr); + assert(pid == pid2); + close(pidfile_fd); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct sock_daemon_funcs test2_funcs = { + .startup = test2_startup, + .reconfigure_send = test2_reconfigure_send, + .reconfigure_recv = test2_reconfigure_recv, + .reopen_logs_send = test2_reopen_logs_send, + .reopen_logs_recv = test2_reopen_logs_recv, + .shutdown_send = test2_shutdown_send, + .shutdown_recv = test2_shutdown_recv, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test2", "file:", "NOTICE", + &test2_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); +} + +/* + * test3 + * + * Start daemon, test watching of (parent) PID + */ + +static void test3(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + pid_t pid_watch, pid, pid2; + int ret; + + pid_watch = fork(); + assert(pid_watch != -1); + + if (pid_watch == 0) { + sleep(10); + exit(0); + } + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test3", "file:", "NOTICE", + NULL, NULL, &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, pid_watch); + assert(ret == ESRCH); + + exit(0); + } + + pid2 = waitpid(pid_watch, &ret, 0); + assert(pid2 == pid_watch); + assert(WEXITSTATUS(ret) == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); +} + +/* + * test4 + * + * Start daemon, test termination via wait_send function + */ + +struct test4_wait_state { +}; + +static void test4_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test4_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req, *subreq; + struct test4_wait_state *state; + + req = tevent_req_create(mem_ctx, &state, struct test4_wait_state); + if (req == NULL) { + return NULL; + } + + subreq = tevent_wakeup_send(state, ev, + tevent_timeval_current_ofs(10,0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test4_wait_done, req); + + return req; +} + +static void test4_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + + if (! status) { + tevent_req_error(req, EIO); + } else { + tevent_req_done(req); + } +} + +static bool test4_wait_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_daemon_funcs test4_funcs = { + .wait_send = test4_wait_send, + .wait_recv = test4_wait_recv, +}; + +static void test4(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + pid_t pid, pid2; + int ret; + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test4", "file:", "NOTICE", + &test4_funcs, NULL, &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == 0); + + exit(0); + } + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); +} + +/* + * test5 + * + * Start daemon, multiple client connects, requests, disconnects + */ + +#define TEST5_VALID_CLIENTS 10 +#define TEST5_MAX_CLIENTS 100 + +struct test5_pkt { + uint32_t len; + int data; +}; + +struct test5_client_state { + int id; + int fd; + bool done; +}; + +static void test5_client_callback(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test5_client_state *state = + (struct test5_client_state *)private_data; + struct test5_pkt *pkt; + ssize_t n; + int ret; + + if (buf == NULL) { + assert(buflen == 0); + + ret = 0; + } else { + assert(buflen == sizeof(struct test5_pkt)); + pkt = (struct test5_pkt *)buf; + assert(pkt->len == sizeof(struct test5_pkt)); + + ret = pkt->data; + } + + assert(state->fd != -1); + + n = write(state->fd, (void *)&ret, sizeof(int)); + assert(n == sizeof(int)); + + state->done = true; +} + +static int test5_client(const char *sockpath, int id, pid_t pid_server, + pid_t *client_pid) +{ + pid_t pid; + int fd[2]; + int ret; + ssize_t n; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct test5_client_state state; + struct sock_queue *queue; + struct test5_pkt pkt; + int conn; + + close(fd[0]); + + ev = tevent_context_init(NULL); + assert(ev != NULL); + + conn = sock_connect(sockpath); + assert(conn != -1); + + state.id = id; + state.fd = fd[1]; + state.done = false; + + queue = sock_queue_setup(ev, ev, conn, + test5_client_callback, &state); + assert(queue != NULL); + + pkt.len = 8; + pkt.data = 0xbaba; + + ret = sock_queue_write(queue, (uint8_t *)&pkt, + sizeof(struct test5_pkt)); + assert(ret == 0); + + while (! state.done) { + tevent_loop_once(ev); + } + + close(fd[1]); + state.fd = -1; + + while (kill(pid_server, 0) == 0 || errno != ESRCH) { + sleep(1); + } + exit(0); + } + + close(fd[1]); + + ret = 0; + n = read(fd[0], &ret, sizeof(ret)); + if (n == 0) { + fprintf(stderr, "client id %d read 0 bytes\n", id); + } + assert(n == 0 || n == sizeof(ret)); + + close(fd[0]); + + *client_pid = pid; + return ret; +} + +struct test5_server_state { + int num_clients; +}; + +static bool test5_connect(struct sock_client_context *client, + pid_t pid, + void *private_data) +{ + struct test5_server_state *state = + (struct test5_server_state *)private_data; + + if (state->num_clients == TEST5_VALID_CLIENTS) { + return false; + } + + state->num_clients += 1; + assert(state->num_clients <= TEST5_VALID_CLIENTS); + return true; +} + +static void test5_disconnect(struct sock_client_context *client, + void *private_data) +{ + struct test5_server_state *state = + (struct test5_server_state *)private_data; + + state->num_clients -= 1; + assert(state->num_clients >= 0); +} + +struct test5_read_state { + struct test5_pkt reply; +}; + +static void test5_read_done(struct tevent_req *subreq); + +static struct tevent_req *test5_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *client, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test5_server_state *server_state = + (struct test5_server_state *)private_data; + struct tevent_req *req, *subreq; + struct test5_read_state *state; + struct test5_pkt *pkt; + + req = tevent_req_create(mem_ctx, &state, struct test5_read_state); + assert(req != NULL); + + assert(buflen == sizeof(struct test5_pkt)); + + pkt = (struct test5_pkt *)buf; + assert(pkt->data == 0xbaba); + + state->reply.len = sizeof(struct test5_pkt); + state->reply.data = server_state->num_clients; + + subreq = sock_socket_write_send(state, ev, client, + (uint8_t *)&state->reply, + state->reply.len); + assert(subreq != NULL); + + tevent_req_set_callback(subreq, test5_read_done, req); + + return req; +} + +static void test5_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool status; + + status = sock_socket_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static bool test5_read_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_socket_funcs test5_client_funcs = { + .connect = test5_connect, + .disconnect = test5_disconnect, + .read_send = test5_read_send, + .read_recv = test5_read_recv, +}; + +struct test5_wait_state { +}; + +static struct tevent_req *test5_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test5_wait_state *state; + int fd = *(int *)private_data; + int ret = 1; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + close(fd); + + req = tevent_req_create(mem_ctx, &state, struct test5_wait_state); + if (req == NULL) { + return NULL; + } + + return req; +} + +static bool test5_wait_recv(struct tevent_req *req, int *perr) +{ + return true; +} + +static struct sock_daemon_funcs test5_funcs = { + .wait_send = test5_wait_send, + .wait_recv = test5_wait_recv, +}; + +static void test5(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + pid_t pid_server, pid; + int fd[2], ret, i; + ssize_t n; + pid_t client_pid[TEST5_MAX_CLIENTS]; + + pid = getpid(); + + ret = pipe(fd); + assert(ret == 0); + + pid_server = fork(); + assert(pid_server != -1); + + if (pid_server == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct test5_server_state state; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test5", "file:", "NOTICE", + &test5_funcs, &fd[1], &sockd); + assert(ret == 0); + + state.num_clients = 0; + + ret = sock_daemon_add_unix(sockd, sockpath, + &test5_client_funcs, &state); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, pid); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + close(fd[0]); + + for (i=0; i<TEST5_MAX_CLIENTS; i++) { + ret = test5_client(sockpath, i, pid_server, &client_pid[i]); + if (i < TEST5_VALID_CLIENTS) { + assert(ret == i+1); + } else { + assert(ret == 0); + } + } + + for (i=TEST5_MAX_CLIENTS-1; i>=0; i--) { + kill(client_pid[i], SIGKILL); + + pid = wait(&ret); + assert(pid != -1); + } + + ret = kill(pid_server, SIGTERM); + assert(ret == 0); + + pid = waitpid(pid_server, &ret, 0); + assert(pid == pid_server); + assert(WEXITSTATUS(ret) == 0); +} + +/* + * test6 + * + * Start daemon, test client connects, requests, replies, disconnects + */ + +struct test6_pkt { + uint32_t len; + uint32_t data; +}; + +struct test6_client_state { + bool done; +}; + +static void test6_client_callback(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test6_client_state *state = + (struct test6_client_state *)private_data; + struct test6_pkt *pkt; + + assert(buflen == sizeof(struct test6_pkt)); + pkt = (struct test6_pkt *)buf; + assert(pkt->len == sizeof(struct test6_pkt)); + assert(pkt->data == 0xffeeddcc); + + state->done = true; +} + +static void test6_client(const char *sockpath) +{ + struct tevent_context *ev; + struct test6_client_state state; + struct sock_queue *queue; + struct test6_pkt pkt; + int conn, ret; + + ev = tevent_context_init(NULL); + assert(ev != NULL); + + conn = sock_connect(sockpath); + assert(conn != -1); + + state.done = false; + + queue = sock_queue_setup(ev, ev, conn, + test6_client_callback, &state); + assert(queue != NULL); + + pkt.len = 8; + pkt.data = 0xaabbccdd; + + ret = sock_queue_write(queue, (uint8_t *)&pkt, + sizeof(struct test6_pkt)); + assert(ret == 0); + + while (! state.done) { + tevent_loop_once(ev); + } + + talloc_free(ev); +} + +struct test6_server_state { + struct sock_daemon_context *sockd; + int fd, done; +}; + +struct test6_read_state { + struct test6_server_state *server_state; + struct test6_pkt reply; +}; + +static void test6_read_done(struct tevent_req *subreq); + +static struct tevent_req *test6_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *client, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test6_server_state *server_state = + (struct test6_server_state *)private_data; + struct tevent_req *req, *subreq; + struct test6_read_state *state; + struct test6_pkt *pkt; + + req = tevent_req_create(mem_ctx, &state, struct test6_read_state); + assert(req != NULL); + + state->server_state = server_state; + + assert(buflen == sizeof(struct test6_pkt)); + + pkt = (struct test6_pkt *)buf; + assert(pkt->data == 0xaabbccdd); + + state->reply.len = sizeof(struct test6_pkt); + state->reply.data = 0xffeeddcc; + + subreq = sock_socket_write_send(state, ev, client, + (uint8_t *)&state->reply, + state->reply.len); + assert(subreq != NULL); + + tevent_req_set_callback(subreq, test6_read_done, req); + + return req; +} + +static void test6_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test6_read_state *state = tevent_req_data( + req, struct test6_read_state); + int ret; + bool status; + + status = sock_socket_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->server_state->done = 1; + tevent_req_done(req); +} + +static bool test6_read_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_socket_funcs test6_client_funcs = { + .read_send = test6_read_send, + .read_recv = test6_read_recv, +}; + +struct test6_wait_state { + struct test6_server_state *server_state; +}; + +static void test6_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test6_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct test6_server_state *server_state = + (struct test6_server_state *)private_data; + struct tevent_req *req, *subreq; + struct test6_wait_state *state; + ssize_t nwritten; + int ret = 1; + + nwritten = write(server_state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + close(server_state->fd); + server_state->fd = -1; + + req = tevent_req_create(mem_ctx, &state, struct test6_wait_state); + if (req == NULL) { + return NULL; + } + + state->server_state = (struct test6_server_state *)private_data; + + subreq = tevent_wakeup_send(state, ev, + tevent_timeval_current_ofs(10,0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test6_wait_done, req); + + return req; +} + +static void test6_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test6_wait_state *state = tevent_req_data( + req, struct test6_wait_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + if (state->server_state->done == 0) { + tevent_req_error(req, EIO); + return; + } + + tevent_req_done(req); +} + +static bool test6_wait_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_daemon_funcs test6_funcs = { + .wait_send = test6_wait_send, + .wait_recv = test6_wait_recv, +}; + +static void test6(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + pid_t pid_server, pid; + int fd[2], ret; + ssize_t n; + + pid = getpid(); + + ret = pipe(fd); + assert(ret == 0); + + pid_server = fork(); + assert(pid_server != -1); + + if (pid_server == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct test6_server_state server_state = { 0 }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + server_state.fd = fd[1]; + + ret = sock_daemon_setup(mem_ctx, "test6", "file:", "NOTICE", + &test6_funcs, &server_state, + &sockd); + assert(ret == 0); + + server_state.sockd = sockd; + server_state.done = 0; + + ret = sock_daemon_add_unix(sockd, sockpath, + &test6_client_funcs, &server_state); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, pid); + assert(ret == 0); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + close(fd[0]); + + test6_client(sockpath); + + pid = waitpid(pid_server, &ret, 0); + assert(pid == pid_server); + assert(WEXITSTATUS(ret) == 0); +} + +/* + * test7 + * + * Start daemon twice, confirm PID file contention + */ + +static void test7(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct sock_daemon_funcs test7_funcs; + struct stat st; + int fd[2]; + pid_t pid, pid2; + int ret; + struct tevent_context *ev; + struct sock_daemon_context *sockd; + ssize_t n; + + /* Reuse test2 funcs for the startup synchronisation */ + test7_funcs = (struct sock_daemon_funcs) { + .startup = test2_startup, + .reconfigure = test2_reconfigure, + .shutdown = test2_shutdown, + }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test7", "file:", "NOTICE", + &test7_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + ret = stat(pidfile, &st); + assert(ret == 0); + assert(S_ISREG(st.st_mode)); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test7-parent", "file:", "NOTICE", + &test7_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EEXIST); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); +} + +/* + * test8 + * + * Start daemon, confirm that create_session argument works as expected + */ + +static void test8(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + int fd[2]; + pid_t pid, pid2, sid; + int ret; + struct tevent_context *ev; + struct sock_daemon_context *sockd; + ssize_t n; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test8_funcs = { + .startup = test2_startup, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test8", "file:", "NOTICE", + &test8_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* create_session false above, so pid != sid */ + sid = getsid(pid); + assert(pid != sid); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test8_funcs = { + .startup = test2_startup, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test8", "file:", "NOTICE", + &test8_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, true, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* create_session true above, so pid == sid */ + sid = getsid(pid); + assert(pid == sid); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); +} + +/* + * test9 + * + * Confirm that do_fork causes the daemon to be forked as a separate child + */ + +static void test9(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + int fd[2]; + pid_t pid, pid2; + int ret; + struct tevent_context *ev; + struct sock_daemon_context *sockd; + ssize_t n; + int pidfile_fd; + char pidstr[20] = { 0 }; + struct stat st; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test9_funcs = { + .startup = test2_startup, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test9", "file:", "NOTICE", + &test9_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* do_fork false above, so pid should be active */ + ret = kill(pid, 0); + assert(ret == 0); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test9_funcs = { + .startup = test2_startup, + .shutdown = test2_shutdown, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test9", "file:", "NOTICE", + &test9_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, true, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* do_fork true above, so pid should have exited */ + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + pidfile_fd = open(pidfile, O_RDONLY, 0644); + assert(pidfile_fd != -1); + n = read(pidfile_fd, pidstr, sizeof(pidstr)-1); + assert(n != -1); + pid2 = (pid_t)atoi(pidstr); + assert(pid != pid2); + close(pidfile_fd); + + ret = kill(pid2, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + /* + * pid2 isn't our child, so can't call waitpid(). kill(pid2, 0) + * is unreliable - pid2 may have been recycled. Above indicates + * that the shutdown function was called, so just do 1 final + * check to see if pidfile has been removed. + */ + ret = stat(sockpath, &st); + assert(ret == -1); + + close(fd[0]); +} + +static void test10_shutdown(void *private_data) +{ + int fd = *(int *)private_data; + int ret = 3; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); +} + +struct test10_wait_state { +}; + +static void test10_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test10_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + int fd = *(int *)private_data; + struct tevent_req *req, *subreq; + struct test10_wait_state *state; + size_t nwritten; + int ret = 1; + + req = tevent_req_create(mem_ctx, &state, struct test10_wait_state); + if (req == NULL) { + return NULL; + } + + subreq = tevent_wakeup_send(state, ev, + tevent_timeval_current_ofs(10, 0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test10_wait_done, req); + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + return req; +} + +static void test10_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + + status = tevent_wakeup_recv(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + tevent_req_done(req); +} + +static bool test10_wait_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_daemon_funcs test10_funcs = { + .shutdown = test10_shutdown, + .wait_send = test10_wait_send, + .wait_recv = test10_wait_recv, +}; + +/* + * test10 + * + * Confirm that the daemon starts successfully if there is a stale socket + */ + +static void test10(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + int fd[2]; + pid_t pid, pid2; + int ret; + ssize_t n; + int pidfile_fd; + char pidstr[20] = { 0 }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test10", "file:", "NOTICE", + &test10_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* KILL will leave PID file and socket behind */ + ret = kill (pid, SIGKILL); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + ret = stat(sockpath, &st); + assert(ret == 0); + + close(fd[0]); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test10", "file:", "NOTICE", + &test10_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + pidfile_fd = open(pidfile, O_RDONLY, 0644); + assert(pidfile_fd != -1); + n = read(pidfile_fd, pidstr, sizeof(pidstr)-1); + assert(n != -1); + pid2 = (pid_t)atoi(pidstr); + assert(pid == pid2); + close(pidfile_fd); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + const char *pidfile, *sockpath; + int num; + + if (argc != 4) { + fprintf(stderr, "%s <pidfile> <sockpath> <testnum>\n", argv[0]); + exit(1); + } + + pidfile = argv[1]; + sockpath = argv[2]; + num = atoi(argv[3]); + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + switch (num) { + case 1: + test1(mem_ctx, pidfile, sockpath); + break; + + case 2: + test2(mem_ctx, pidfile, sockpath); + break; + + case 3: + test3(mem_ctx, pidfile, sockpath); + break; + + case 4: + test4(mem_ctx, pidfile, sockpath); + break; + + case 5: + test5(mem_ctx, pidfile, sockpath); + break; + + case 6: + test6(mem_ctx, pidfile, sockpath); + break; + + case 7: + test7(mem_ctx, pidfile, sockpath); + break; + + case 8: + test8(mem_ctx, pidfile, sockpath); + break; + + case 9: + test9(mem_ctx, pidfile, sockpath); + break; + + case 10: + test10(mem_ctx, pidfile, sockpath); + break; + + default: + fprintf(stderr, "Unknown test number %d\n", num); + } + + return 0; +} diff --git a/ctdb/tests/src/sock_io_test.c b/ctdb/tests/src/sock_io_test.c new file mode 100644 index 0000000..ba4b637 --- /dev/null +++ b/ctdb/tests/src/sock_io_test.c @@ -0,0 +1,283 @@ +/* + sock I/O tests + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/wait.h" + +#include <assert.h> + +#include "common/sock_io.c" + +static int socket_init(const char *sockpath) +{ + struct sockaddr_un addr; + int fd, ret; + size_t len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + assert(len < sizeof(addr.sun_path)); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(fd != -1); + + ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret != -1); + + ret = listen(fd, 10); + assert(ret != -1); + + return fd; +} + +static void test1_writer(int fd) +{ + uint8_t buf[1024]; + ssize_t nwritten; + uint32_t len; + + for (len = 10; len < 1000; len += 10) { + int value = len / 10; + uint32_t buflen = len + sizeof(uint32_t); + + memset(buf, value, buflen); + memcpy(buf, &buflen, sizeof(uint32_t)); + + nwritten = sys_write(fd, buf, buflen); + assert(nwritten == buflen); + } +} + +struct test1_reader_state { + size_t pkt_len; + bool done; +}; + +static void test1_reader(uint8_t *buf, size_t buflen, void *private_data) +{ + struct test1_reader_state *state = + (struct test1_reader_state *)private_data; + + if (buflen == 0) { + state->done = true; + return; + } + + assert(buflen == state->pkt_len); + + state->pkt_len += 10; +} + +static void test1(TALLOC_CTX *mem_ctx, const char *sockpath) +{ + struct test1_reader_state state; + struct tevent_context *ev; + struct sock_queue *queue; + pid_t pid; + int pfd[2], fd, ret; + ssize_t n; + + ret = pipe(pfd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + int newfd; + + close(pfd[0]); + + fd = socket_init(sockpath); + assert(fd != -1); + + ret = 1; + n = sys_write(pfd[1], &ret, sizeof(int)); + assert(n == sizeof(int)); + + newfd = accept(fd, NULL, NULL); + assert(newfd != -1); + + test1_writer(newfd); + close(newfd); + unlink(sockpath); + + exit(0); + } + + close(pfd[1]); + + n = sys_read(pfd[0], &ret, sizeof(int)); + assert(n == sizeof(int)); + assert(ret == 1); + + close(pfd[0]); + + fd = sock_connect(sockpath); + assert(fd != -1); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + state.pkt_len = 10 + sizeof(uint32_t); + state.done = false; + + queue = sock_queue_setup(mem_ctx, ev, fd, test1_reader, &state); + assert(queue != NULL); + + while (! state.done) { + tevent_loop_once(ev); + } + + talloc_free(queue); + talloc_free(ev); + + pid = wait(&ret); + assert(pid != -1); +} + +static void test2_reader(int fd) +{ + uint8_t buf[1024]; + size_t pkt_len = 10 + sizeof(uint32_t); + ssize_t n; + + while (1) { + n = sys_read(fd, buf, 1024); + assert(n != -1); + + if (n == 0) { + return; + } + + assert((size_t)n == pkt_len); + pkt_len += 10; + } +} + +static void test2_dummy_reader(uint8_t *buf, size_t buflen, + void *private_data) +{ + abort(); +} + +static void test2_writer(struct sock_queue *queue) +{ + uint8_t buf[1024]; + uint32_t len; + int ret; + + for (len = 10; len < 1000; len += 10) { + int value = len / 10; + uint32_t buflen = len + sizeof(uint32_t); + + memset(buf, value, buflen); + memcpy(buf, &buflen, sizeof(uint32_t)); + + ret = sock_queue_write(queue, buf, buflen); + assert(ret == 0); + } +} + +static void test2(TALLOC_CTX *mem_ctx, const char *sockpath) +{ + struct tevent_context *ev; + struct sock_queue *queue; + pid_t pid; + int pfd[2], fd, ret; + ssize_t n; + + ret = pipe(pfd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + int newfd; + + close(pfd[0]); + + fd = socket_init(sockpath); + assert(fd != -1); + + ret = 1; + n = sys_write(pfd[1], &ret, sizeof(int)); + assert(n == sizeof(int)); + + newfd = accept(fd, NULL, NULL); + assert(newfd != -1); + + test2_reader(newfd); + close(newfd); + unlink(sockpath); + + exit(0); + } + + close(pfd[1]); + + n = sys_read(pfd[0], &ret, sizeof(int)); + assert(n == sizeof(int)); + assert(ret == 1); + + close(pfd[0]); + + fd = sock_connect(sockpath); + assert(fd != -1); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + queue = sock_queue_setup(mem_ctx, ev, fd, test2_dummy_reader, NULL); + assert(queue != NULL); + + test2_writer(queue); + + talloc_free(queue); + talloc_free(ev); + + pid = wait(&ret); + assert(pid != -1); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + const char *sockpath; + + if (argc != 2) { + fprintf(stderr, "%s <sockpath>\n", argv[0]); + exit(1); + } + + sockpath = argv[1]; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + test1(mem_ctx, sockpath); + test2(mem_ctx, sockpath); + + return 0; +} diff --git a/ctdb/tests/src/srvid_test.c b/ctdb/tests/src/srvid_test.c new file mode 100644 index 0000000..2367c6c --- /dev/null +++ b/ctdb/tests/src/srvid_test.c @@ -0,0 +1,105 @@ +/* + srvid tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> + +#include "common/db_hash.c" +#include "common/srvid.c" + +#define TEST_SRVID 0xBE11223344556677 + +static void test_handler(uint64_t srvid, TDB_DATA data, void *private_data) +{ + int *count = (int *)private_data; + (*count)++; +} + +int main(void) +{ + struct srvid_context *srv = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + int ret; + int count = 0; + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == EINVAL); + + ret = srvid_init(mem_ctx, &srv); + assert(ret == 0); + + ret = srvid_deregister(srv, TEST_SRVID, &count); + assert(ret == ENOENT); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == 0); + + ret = srvid_exists(srv, TEST_SRVID, NULL); + assert(ret == 0); + + ret = srvid_exists(srv, TEST_SRVID, &count); + assert(ret == 0); + + ret = srvid_dispatch(srv, TEST_SRVID, 0, tdb_null); + assert(ret == 0); + assert(count == 1); + + ret = srvid_dispatch(srv, 0, TEST_SRVID, tdb_null); + assert(ret == 0); + assert(count == 2); + + ret = srvid_deregister(srv, TEST_SRVID, NULL); + assert(ret == ENOENT); + + ret = srvid_deregister(srv, TEST_SRVID, &count); + assert(ret == 0); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == 0); + + talloc_free(tmp_ctx); + ret = srvid_exists(srv, TEST_SRVID, NULL); + assert(ret == ENOENT); + + ret = srvid_dispatch(srv, TEST_SRVID, 0, tdb_null); + assert(ret == ENOENT); + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, NULL); + assert(ret == 0); + ret = srvid_exists(srv, TEST_SRVID, &count); + assert(ret == ENOENT); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == 0); + ret = srvid_exists(srv, TEST_SRVID, &count); + assert(ret == 0); + + talloc_free(srv); + assert(talloc_get_size(mem_ctx) == 0); + assert(talloc_get_size(tmp_ctx) == 0); + + talloc_free(mem_ctx); + + return 0; +} diff --git a/ctdb/tests/src/system_socket_test.c b/ctdb/tests/src/system_socket_test.c new file mode 100644 index 0000000..436f52a --- /dev/null +++ b/ctdb/tests/src/system_socket_test.c @@ -0,0 +1,266 @@ +/* + Raw socket (un) marshalling tests + + Copyright (C) Martin Schwenke 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> + +/* For ether_aton() */ +#ifdef _AIX +#include <arpa/inet.h> +#endif +#ifdef __FreeBSD__ +#include <net/ethernet.h> +#endif +#ifdef linux +#include <netinet/ether.h> +#endif + +#include "common/system_socket.c" + +#include "protocol/protocol_util.h" + +#include "tests/src/test_backtrace.h" + +static void hexdump(uint8_t *buf, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (i % 16 == 0) { + if (i != 0) { + printf("\n"); + } + printf("%06zx", i); + } + printf(" %02x", buf[i]); + } + + printf("\n%06zx\n", i); +} + +static void test_types(void) +{ + /* + * We use this struct in the code but don't pack it due to + * portability concerns. It should have no padding. + */ + struct { + struct ip ip; + struct tcphdr tcp; + } ip4pkt; + + assert(sizeof(ip4pkt) == sizeof(struct ip) + sizeof(struct tcphdr)); +} + +#ifdef HAVE_PACKETSOCKET + +static void test_arp(const char *addr_str, const char *hwaddr_str, bool reply) +{ + ctdb_sock_addr addr; + struct ether_addr *hw, *dhw; + uint8_t buf[512]; + size_t buflen = sizeof(buf); + size_t len; + int ret; + + ret = ctdb_sock_addr_from_string(addr_str, &addr, false); + assert(ret == 0); + + hw = ether_aton(hwaddr_str); + assert(hw != NULL); + + switch (addr.ip.sin_family) { + case AF_INET: + ret = arp_build(buf, buflen, &addr.ip, hw, reply, &dhw, &len); + break; + case AF_INET6: + ret = ip6_na_build(buf, buflen, &addr.ip6, hw, &dhw, &len); + break; + default: + abort(); + } + + assert(ret == 0); + + hexdump(buf, len); +} + +#else /* HAVE_PACKETSOCKET */ + +static void test_arp(const char *addr_str, const char *hwaddr_str, bool reply) +{ + fprintf(stderr, "PACKETSOCKET not supported\n"); +} + +#endif /* HAVE_PACKETSOCKET */ + +static void test_tcp(const char *src_str, + const char *dst_str, + const char *seq_str, + const char *ack_str, + const char *rst_str) +{ + ctdb_sock_addr src, dst; + uint32_t seq, ack; + int rst; + uint8_t buf[512]; + struct ether_header *eth; + size_t expected_len, len; + char src_str_out[64], dst_str_out[64]; + uint32_t seq_out, ack_out; + int rst_out = 0; + uint16_t window; + int ret; + + ret = ctdb_sock_addr_from_string(src_str, &src, true); + assert(ret == 0); + + ret = ctdb_sock_addr_from_string(dst_str, &dst, true); + assert(ret == 0); + + seq = atoi(seq_str); + ack = atoi(ack_str); + rst = atoi(rst_str); + + /* Need to fake this up */ + eth = (struct ether_header *) buf; + memset(eth, 0, sizeof(*eth)); + + switch (src.ip.sin_family) { + case AF_INET: + eth->ether_type = htons(ETHERTYPE_IP); + expected_len = 40; + ret = tcp4_build(buf + sizeof(struct ether_header), + sizeof(buf) - sizeof(struct ether_header), + &src.ip, + &dst.ip, + seq, + ack, + rst, + &len); + break; + case AF_INET6: + eth->ether_type = htons(ETHERTYPE_IP6); + expected_len = 60; + ret = tcp6_build(buf + sizeof(struct ether_header), + sizeof(buf) - sizeof(struct ether_header), + &src.ip6, + &dst.ip6, + seq, + ack, + rst, + &len); + break; + default: + abort(); + } + + assert(ret == 0); + assert(len == expected_len); + + hexdump(buf + sizeof(struct ether_header), len); + + switch (ntohs(eth->ether_type)) { + case ETHERTYPE_IP: + ret = tcp4_extract(buf + sizeof(struct ether_header), + len, + &src.ip, + &dst.ip, + &ack_out, + &seq_out, + &rst_out, + &window); + break; + case ETHERTYPE_IP6: + ret = tcp6_extract(buf + sizeof(struct ether_header), + len, + &src.ip6, + &dst.ip6, + &ack_out, + &seq_out, + &rst_out, + &window); + break; + default: + abort(); + } + + assert(ret == 0); + + assert(seq == seq_out); + assert(ack == ack_out); + assert((rst != 0) == (rst_out != 0)); + assert(window == htons(1234)); + + ret = ctdb_sock_addr_to_buf(src_str_out, sizeof(src_str_out), + &src, true); + assert(ret == 0); + ret = strcmp(src_str, src_str_out); + assert(ret == 0); + + ret = ctdb_sock_addr_to_buf(dst_str_out, sizeof(dst_str_out), + &dst, true); + assert(ret == 0); + ret = strcmp(dst_str, dst_str_out); + assert(ret == 0); +} + +static void usage(const char *prog) +{ + fprintf(stderr, "usage: %s <cmd> [<arg> ...]\n", prog); + fprintf(stderr, " commands:\n"); + fprintf(stderr, " types\n"); + fprintf(stderr, " arp <ipaddr> <hwaddr> [reply]\n"); + fprintf(stderr, " tcp <src> <dst> <seq> <ack> <rst>\n"); + + exit(1); +} + +int main(int argc, char **argv) +{ + + if (argc < 2) { + usage(argv[0]); + } + + test_backtrace_setup(); + + if (strcmp(argv[1], "types") == 0) { + test_types(); + } else if (strcmp(argv[1], "arp") == 0) { + /* + * Extra arg indicates that a reply should be + * constructed for IPv4 - value is ignored + */ + if (argc != 4 && argc != 5) { + usage(argv[0]); + } + test_arp(argv[2], argv[3], (argc == 5)); + } else if (strcmp(argv[1], "tcp") == 0) { + if (argc != 7) { + usage(argv[0]); + } + test_tcp(argv[2], argv[3], argv[4], argv[5], argv[6]); + } else { + usage(argv[0]); + } + + return 0; +} diff --git a/ctdb/tests/src/test_backtrace.c b/ctdb/tests/src/test_backtrace.c new file mode 100644 index 0000000..aa3fc0c --- /dev/null +++ b/ctdb/tests/src/test_backtrace.c @@ -0,0 +1,37 @@ +/* + Print a backtrace when a test aborts + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include "lib/util/fault.h" +#include "lib/util/signal.h" + +#include "tests/src/test_backtrace.h" + +static void test_abort_backtrace_handler(int sig) +{ + log_stack_trace(); + CatchSignal(SIGABRT, SIG_DFL); + abort(); +} + +void test_backtrace_setup(void) +{ + CatchSignal(SIGABRT, test_abort_backtrace_handler); +} diff --git a/ctdb/tests/src/test_backtrace.h b/ctdb/tests/src/test_backtrace.h new file mode 100644 index 0000000..a6089c9 --- /dev/null +++ b/ctdb/tests/src/test_backtrace.h @@ -0,0 +1,25 @@ +/* + Print a backtrace when a test aborts + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + 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/>. +*/ + +#ifndef __CTDB_TEST_BACKTRACE_H__ +#define __CTDB_TEST_BACKTRACE_H__ + +void test_backtrace_setup(void); + +#endif /* __CTDB_TEST_BACKTRACE_H__ */ diff --git a/ctdb/tests/src/test_mutex_raw.c b/ctdb/tests/src/test_mutex_raw.c new file mode 100644 index 0000000..8ebf77e --- /dev/null +++ b/ctdb/tests/src/test_mutex_raw.c @@ -0,0 +1,434 @@ +/* + * Test the system robust mutex implementation + * + * Copyright (C) 2016 Amitay Isaacs + * Copyright (C) 2018 Red Hat Inc. + * + * 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 <https://www.gnu.org/licenses/>. + */ + +/* + * To run the test do the following: + * + * (a) Compile the test. + * + * gcc -O2 -g3 -o test-robust-mutex test-robust-mutex.c -lpthread + * + * (b) Start the "init" process. + * + * ./test-robust-mutex /tmp/shared-mutex init + * + * (c) Start any number of "worker" instances. + * + * ./test-robust-mutex <Shared memory file> worker <#> <Priority> + * + * <Shared memory file> e.g. /tmp/shared-mutex. + * + * <#> : Number of children processes. + * + * <Priority> : 0 - Normal, 1 - Realtime, 2 - Nice 20. + * + * For example: + * + * As non-root: + * + * $ while true ; do ./test-robust-mutex /tmp/foo worker 10 0 ; done; + * + * As root: + * + * while true ; do ./test-robust-mutex /tmp/foo worker 10 1 ; done; + * + * This creates 20 processes, 10 at normal priority and 10 at realtime + * priority, all taking the lock, being killed and recovering the lock. + * + * If while running (c) the processes block, it might mean that a futex wakeup + * was lost, or that the handoff of EOWNERDEAD did not happen correctly. In + * either case you can debug the resulting mutex like this: + * + * $ ./test-robust-mutex /tmp/shared-mutex debug + * + * This prints the PID of the process holding the mutex or nothing if + * the value was cleared by the kernel and now no process holds the mutex. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <pthread.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <sys/wait.h> + +/* Define DEBUG to 1 to enable verbose debugging. */ +#define DEBUG 0 + +/* Implement the worker. The worker has to do the following things: + + * Succeed at locking the mutex, including possible recovery. + * Kill itself. + + Other workers are attempting exactly the same thing in order to + test the loss and recovery of the robust mutex. */ +static void worker (const char *filename) +{ + pthread_mutex_t *mutex; + void *addr; + int ret, fd; + + /* Open the file and map the shared robust mutex. */ + fd = open(filename, O_RDWR, 0600); + if (fd == -1) { + perror ("FAIL: open"); + exit(EXIT_FAILURE); + } + + addr = mmap(NULL, + sizeof(pthread_mutex_t), + PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_FILE, + fd, + 0); + if (addr == NULL) { + perror ("FAIL: mmap"); + exit(EXIT_FAILURE); + } + + mutex = (pthread_mutex_t *)addr; + + /* Every process will lock once, and die once. */ + printf("INFO: pid %u locking\n", getpid()); + do { + ret = pthread_mutex_lock(mutex); + +#if DEBUG + fprintf(stderr, + "DEBUG: pid %u lock attempt, ret=%d\n", + getpid(), + ret); +#endif + + if (ret == EOWNERDEAD) { + int rc; + + rc = pthread_mutex_consistent(mutex); + if (rc == 0) { + pthread_mutex_unlock(mutex); + } else { + fprintf(stderr, + "FAIL: pthread_mutex_consistent " + "failed\n"); + exit(EXIT_FAILURE); + } +#if DEBUG + fprintf(stderr, + "DEBUG: pid %u recovery lock attempt, ret=%d\n", + getpid(), + ret); +#endif + /* Will loop and try to lock again. */ + } + + } while (ret != 0); + + printf ("INFO: pid %u locked, now killing\n", getpid()); + kill(getpid(), SIGKILL); +} + +/* One of three priority modes. */ +#define PRIO_NORMAL 0 +#define PRIO_REALTIME 1 +#define PRIO_NICE_20 2 + +/* One of three operation modes. */ +#define MODE_INIT 0 +#define MODE_WORKER 1 +#define MODE_DEBUG 2 + +/* Print usage information and exit. */ +static void usage (const char *name) +{ + fprintf(stderr, + "Usage: %s <file> [init|worker|debug] [#] [0|1|2]\n", + name); + exit(EXIT_FAILURE); +} + +/* Set the process priority. */ +static void set_priority (int priority) +{ + struct sched_param p; + int ret; + + switch (priority) { + case PRIO_REALTIME: + p.sched_priority = 1; + ret = sched_setscheduler(0, SCHED_FIFO, &p); + if (ret == -1) + perror("FAIL: sched_setscheduler"); + break; + + case PRIO_NICE_20: + ret = nice(-20); + if (ret == -1) + perror("FAIL: nice"); + break; + + case PRIO_NORMAL: + default: + /* Normal priority is the default. */ + break; + } +} + +int main(int argc, const char **argv) +{ + int i, fd, ret, num_children, mode = -1, priority = PRIO_NORMAL; + const char *mode_str; + const char *file; + char *addr; + pthread_mutex_t *mutex; + pthread_mutexattr_t mattr; + pid_t pid; + + /* One of three modes, init, worker, or debug. */ + if (argc < 3 || argc > 5) + usage (argv[0]); + + /* + * The shared memory file. Care should be taken here because if glibc + * is upgraded between runs the internals of the robust mutex could + * change. See this blog post about the dangers: + * https://developers.redhat.com/blog/2017/03/13/cc-library-upgrades-and-opaque-data-types-in-process-shared-memory/ + * and how to avoid problems inherent in this. + */ + file = argv[1]; + + /* Set the mode. */ + mode_str = argv[2]; + if (strcmp ("init", mode_str) == 0) { + mode = MODE_INIT; + } else if (strcmp ("worker", mode_str) == 0) { + mode = MODE_WORKER; + } else if (strcmp ("debug", mode_str) == 0) { + mode = MODE_DEBUG; + } else { + usage (argv[0]); + } + + /* This is "worker" mode, so set the priority. */ + if (mode == MODE_WORKER) { + priority = atoi(argv[4]); + set_priority(priority); + } + + /* All modes open the file. */ + fd = open(argv[1], O_CREAT|O_RDWR, 0600); + if (fd == -1) { + perror("FAIL: open"); + exit(EXIT_FAILURE); + } + + ret = lseek(fd, 0, SEEK_SET); + if (ret != 0) { + perror("FAIL: lseek"); + exit(EXIT_FAILURE); + } + + /* Truncate the file backing the mutex only in the init phase. */ + if (mode == MODE_INIT) { + ret = ftruncate(fd, sizeof(pthread_mutex_t)); + if (ret != 0) { + perror("FAIL: ftruncate"); + exit(EXIT_FAILURE); + } + } + + /* Map the robust mutex. */ + addr = mmap(NULL, + sizeof(pthread_mutex_t), + PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_FILE, + fd, + 0); + if (addr == NULL) { + perror("FAIL: mmap"); + exit(EXIT_FAILURE); + } + + mutex = (pthread_mutex_t *)(void *)addr; + + /* + * In the debug mode we try to recover the mutex and print it. + * WARNING: All other processes should be stuck, otherwise they may + * change the value of the lock between trylock and the printing after + * EBUSY. + */ + if (mode == MODE_DEBUG) { + ret = pthread_mutex_trylock(mutex); + if (ret == EOWNERDEAD) { + ret = pthread_mutex_consistent(mutex); + if (ret == 0) { + pthread_mutex_unlock(mutex); + } else { + fprintf(stderr, + "FAIL: pthread_mutex_consistent " + "failed\n"); + exit (EXIT_FAILURE); + } + } else if (ret == EBUSY) { + printf("INFO: pid=%u\n", mutex->__data.__owner); + } else if (ret == 0) { + pthread_mutex_unlock(mutex); + } + exit(EXIT_SUCCESS); + } + + /* + * Only the initializing process does initialization because it is + * undefined behaviour to re-initialize an already initialized mutex + * that was not destroyed. + */ + if (mode == MODE_INIT) { + + ret = pthread_mutexattr_init(&mattr); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_init failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutexattr_settype(&mattr, + PTHREAD_MUTEX_ERRORCHECK); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_settype failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutexattr_setpshared(&mattr, + PTHREAD_PROCESS_SHARED); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_setpshared failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_setrobust failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutex_init(mutex, &mattr); + if (ret != 0) { + fprintf(stderr, "FAIL: pthread_mutex_init failed\n"); + exit(EXIT_FAILURE); + } + + printf ("INFO: init: Mutex initialization complete.\n"); + /* Never exit. */ + for (;;) + sleep (1); + } + + /* Acquire the mutext for the first time. Might be dead. + Might also be concurrent with the high-priority threads. */ + fprintf(stderr, + "INFO: parent: Acquiring mutex (pid = %d).\n", + getpid()); + do { + ret = pthread_mutex_lock(mutex); + + /* Not consistent? Try to make it so. */ + if (ret == EOWNERDEAD) { + int rc; + + rc = pthread_mutex_consistent(mutex); + if (rc == 0) { + pthread_mutex_unlock (mutex); + } else { + fprintf(stderr, + "FAIL: pthread_mutex_consistent " + "failed\n"); + exit (EXIT_FAILURE); + } + + /* Will loop and try to lock again. */ + fprintf(stderr, + "INFO: parent: Unlock recovery ret = %d\n", + ret); + } + + } while (ret != 0); + + /* + * Set the parent process into it's own process group (hides the + * children). + */ + setpgid(0, 0); + + /* Create # of children. */ + fprintf(stderr, "INFO: parent: Creating children\n"); + num_children = atoi(argv[3]); + + for (i = 0; i < num_children; i++) { + pid = fork(); + if (pid < 0) { + fprintf(stderr, "FAIL: fork() failed\n"); + exit(EXIT_FAILURE); + } + if (pid == 0) { + close(fd); + worker(file); + exit(EXIT_FAILURE); + } + } + + fprintf(stderr, "INFO: parent: Waiting for children\n"); + + /* Unlock the recently acquired mutex or the old lost mutex. */ + ret = pthread_mutex_unlock(mutex); + if (ret != 0) { + fprintf(stderr, "FAIL: pthread_mutex_unlock failed\n"); + exit(EXIT_FAILURE); + } + + /* + * All threads are running now, and each will take the lock and + * die in turn. When they are all dead we will exit and be started + * again by the caller. + */ + for (i = 0; i < num_children; i++) { + int status; + pid = waitpid(-1, &status, 0); + if (pid <= 0) { + fprintf(stderr, "FAIL: waitpid() failed\n"); + exit(EXIT_FAILURE); + } + fprintf(stderr, + "INFO: parent: Reaped %u\n", + (unsigned int) pid); + } + + /* We never unlink fd. The file must be cleaned up by the caller. */ + close(fd); + + exit(EXIT_SUCCESS); +} diff --git a/ctdb/tests/src/test_options.c b/ctdb/tests/src/test_options.c new file mode 100644 index 0000000..2c64404 --- /dev/null +++ b/ctdb/tests/src/test_options.c @@ -0,0 +1,245 @@ +/* + CTDB tests commandline options + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> +#include <popt.h> +#include <talloc.h> + +#include "lib/util/debug.h" + +#include "common/logging.h" +#include "common/path.h" + +#include "tests/src/test_options.h" + +static struct test_options _values; + +static struct poptOption options_basic[] = { + { + .longName = "socket", + .shortName = 's', + .argInfo = POPT_ARG_STRING, + .arg = &_values.socket, + .descrip = "CTDB socket path", + .argDescrip = "filename", + }, + { + .longName = "timelimit", + .shortName = 't', + .argInfo = POPT_ARG_INT, + .arg = &_values.timelimit, + .descrip = "Time limit (in seconds)", + }, + { + .longName = "num-nodes", + .shortName = 'n', + .argInfo = POPT_ARG_INT, + .arg = &_values.num_nodes, + .descrip = "Number of cluster nodes", + }, + { + .longName = "debug", + .shortName = 'd', + .argInfo = POPT_ARG_STRING, + .arg = &_values.debugstr, + .descrip = "Debug level", + }, + { + .longName = "interactive", + .shortName = 'i', + .argInfo = POPT_ARG_NONE, + .arg = &_values.interactive, + .val = 0, + .descrip = "Interactive output", + }, + POPT_TABLEEND +}; + +#define TEST_OPTIONS_BASIC \ + { \ + .argInfo = POPT_ARG_INCLUDE_TABLE, \ + .arg = options_basic, \ + .descrip = "General options:", \ + }, + +static struct poptOption options_database[] = { + { + .longName = "database", + .shortName = 'D', + .argInfo = POPT_ARG_STRING, + .arg = &_values.dbname, + .descrip = "CTDB database name", + }, + { + .longName = "key", + .shortName = 'k', + .argInfo = POPT_ARG_STRING, + .arg = &_values.keystr, + .descrip = "Name of database key", + }, + { + .longName = "value", + .shortName = 'v', + .argInfo = POPT_ARG_STRING, + .arg = &_values.valuestr, + .descrip = "Value of database key", + }, + { + .longName = "dbtype", + .shortName = 'T', + .argInfo = POPT_ARG_STRING, + .arg = &_values.dbtype, + .descrip = "CTDB database type", + }, + POPT_TABLEEND +}; + +#define TEST_OPTIONS_DATABASE \ + { \ + .argInfo = POPT_ARG_INCLUDE_TABLE, \ + .arg = options_database, \ + .descrip = "Database options:", \ + }, + +static void set_defaults_basic(struct test_options *opts) +{ + /* Set default options */ + opts->socket = path_socket(NULL, "ctdbd"); /* leaked */ + assert(opts->socket != NULL); + + opts->timelimit = 10; + opts->num_nodes = 1; + opts->debugstr = "ERR"; + opts->interactive = 0; +} + +static void set_defaults_database(struct test_options *opts) +{ + opts->dbname = NULL; + opts->keystr = NULL; + opts->valuestr = NULL; + opts->dbtype = "volatile"; +} + +static bool verify_options_basic(struct test_options *opts) +{ + int log_level; + bool status; + + status = debug_level_parse(opts->debugstr, &log_level); + if (! status) { + fprintf(stderr, "Error: Invalid debug string '%s'\n", + opts->debugstr); + return false; + } + + debuglevel_set(log_level); + + return true; +} + +static bool verify_options_database(struct test_options *opts) +{ + if (opts->dbname == NULL) { + fprintf(stderr, "Error: Please specify database\n"); + return false; + } + if (opts->keystr == NULL) { + fprintf(stderr, "Error: Please specify key name\n"); + return false; + } + + if ((strcmp(opts->dbtype, "volatile") != 0) && + (strcmp(opts->dbtype, "persistent") != 0) && + (strcmp(opts->dbtype, "replicated") != 0)) { + return false; + } + + return true; +} + +static bool process_options_common(int argc, const char **argv, + struct poptOption *options) +{ + poptContext pc; + int opt; + + pc = poptGetContext(argv[0], argc, argv, options, + POPT_CONTEXT_KEEP_FIRST); + while ((opt = poptGetNextOpt(pc)) != -1) { + fprintf(stderr, "Invalid option %s: %s\n", + poptBadOption(pc, 0), poptStrerror(opt)); + return false; + } + + return true; +} + +bool process_options_basic(int argc, const char **argv, + const struct test_options **opts) +{ + struct poptOption options[] = { + POPT_AUTOHELP + TEST_OPTIONS_BASIC + POPT_TABLEEND + }; + + set_defaults_basic(&_values); + + if (! process_options_common(argc, argv, options)) { + return false; + } + + if (! verify_options_basic(&_values)) { + return false; + } + + *opts = &_values; + return true; +} + +bool process_options_database(int argc, const char **argv, + const struct test_options **opts) +{ + struct poptOption options[] = { + POPT_AUTOHELP + TEST_OPTIONS_BASIC + TEST_OPTIONS_DATABASE + POPT_TABLEEND + }; + + set_defaults_basic(&_values); + set_defaults_database(&_values); + + if (! process_options_common(argc, argv, options)) { + return false; + } + + if (! verify_options_basic(&_values)) { + return false; + } + if (! verify_options_database(&_values)) { + return false; + } + + *opts = &_values; + return true; +} diff --git a/ctdb/tests/src/test_options.h b/ctdb/tests/src/test_options.h new file mode 100644 index 0000000..1e194c9 --- /dev/null +++ b/ctdb/tests/src/test_options.h @@ -0,0 +1,44 @@ +/* + CTDB tests commandline options + + Copyright (C) Amitay Isaacs 2015 + + 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/>. +*/ + +#ifndef __TEST_OPTIONS_H__ +#define __TEST_OPTIONS_H__ + +struct test_options { + /* Basic options */ + const char *socket; + int timelimit; + int num_nodes; + const char *debugstr; + int interactive; + + /* Database options */ + const char *dbname; + const char *keystr; + const char *valuestr; + const char *dbtype; +}; + +bool process_options_basic(int argc, const char **argv, + const struct test_options **opts); + +bool process_options_database(int argc, const char **argv, + const struct test_options **opts); + +#endif /* __TEST_OPTIONS_H__ */ diff --git a/ctdb/tests/src/tmon_ping_test.c b/ctdb/tests/src/tmon_ping_test.c new file mode 100644 index 0000000..c0c0aae --- /dev/null +++ b/ctdb/tests/src/tmon_ping_test.c @@ -0,0 +1,381 @@ +/* + Test trivial FD monitoring + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/wait.h" + +#include <talloc.h> +#include <tevent.h> +#include <assert.h> + +#include "lib/util/tevent_unix.h" + +#include "common/tmon.h" + +#include "tests/src/test_backtrace.h" + +struct test_state { + const char *label; + unsigned long async_wait_time; + unsigned long blocking_sleep_time; +}; + +static void test_tmon_ping_done(struct tevent_req *subreq); +static void test_async_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *label, + int fd, + int direction, + unsigned long timeout, + unsigned long interval, + unsigned long async_wait_time, + unsigned long blocking_sleep_time) +{ + struct tevent_req *req, *subreq; + struct test_state *state; + + req = tevent_req_create(mem_ctx, &state, struct test_state); + if (req == NULL) { + return NULL; + } + + state->label = label; + state->async_wait_time = async_wait_time; + state->blocking_sleep_time = blocking_sleep_time; + + subreq = tmon_ping_send(state, + ev, + fd, + direction, + timeout, + interval); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_tmon_ping_done, req); + + if (state->async_wait_time != 0) { + fprintf(stderr, + "%s: async wait start %lu\n", + state->label, + state->async_wait_time); + } + subreq = tevent_wakeup_send(state, + ev, + tevent_timeval_current_ofs( + (uint32_t)async_wait_time, 0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_async_wait_done, req); + + return req; +} + +static void test_tmon_ping_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test_state *state = tevent_req_data(req, struct test_state); + bool status; + int err; + + status = tmon_ping_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!status) { + switch(err) { + case EPIPE: + fprintf(stderr, "%s: pipe closed\n", state->label); + break; + case ETIMEDOUT: + fprintf(stderr, "%s: ping timeout\n", state->label); + break; + default: + fprintf(stderr, "%s: error (%d)\n", state->label, err); + } + tevent_req_error(req, err); + return; + } + + fprintf(stderr, "%s: done\n", state->label); + tevent_req_done(req); +} + +static void test_async_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test_state *state = tevent_req_data(req, struct test_state); + unsigned int left; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (!status) { + fprintf(stderr, + "%s: tevent_wakeup_recv() failed\n", + state->label); + /* Ignore error */ + } + if (state->async_wait_time != 0) { + fprintf(stderr, "%s: async wait end\n", state->label); + } + + if (state->blocking_sleep_time == 0) { + goto done; + } + + fprintf(stderr, + "%s: blocking sleep start %lu\n", + state->label, + state->blocking_sleep_time); + left = sleep((unsigned int)state->blocking_sleep_time); + fprintf(stderr, + "%s: blocking sleep end\n", + state->label); + if (left != 0) { + tevent_req_error(req, EINTR); + return; + } + +done: + tevent_req_done(req); +} + +static bool test_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static int test_one(bool is_parent, + int sync_fd, + int fd, + int direction, + unsigned long timeout, + unsigned long interval, + unsigned long async_wait_time, + unsigned long blocking_sleep_time) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + bool status; + char buf[1] = ""; + ssize_t count; + int err; + int ret; + + if (!is_parent) { + count = read(sync_fd, buf, sizeof(buf)); + assert(count == 1); + assert(buf[0] == '\0'); + close(sync_fd); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + ret = ENOMEM; + goto done; + } + + req = test_send(mem_ctx, + ev, + is_parent ? "parent" : "child", + fd, + direction, + timeout, + interval, + async_wait_time, + blocking_sleep_time); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + if (is_parent) { + count = write(sync_fd, buf, sizeof(buf)); + assert(count == 1); + } + + status = tevent_req_poll(req, ev); + if (!status) { + ret = EIO; + goto done; + } + + status = test_recv(req, &err); + ret = status ? 0 : err; + +done: + return ret; +} + +static void test(unsigned long parent_timeout, + unsigned long parent_interval, + unsigned long parent_async_wait_time, + unsigned long parent_blocking_sleep_time, + int parent_result, + unsigned long child_timeout, + unsigned long child_interval, + unsigned long child_async_wait_time, + unsigned long child_blocking_sleep_time, + int child_result) +{ + int sync[2]; + int fd[2]; + pid_t pid; + int wstatus; + int ret; + + /* Pipe for synchronisation */ + ret = pipe(sync); + assert(ret == 0); + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* child */ + close(sync[1]); + close(fd[0]); + + ret = test_one(false, + sync[0], + fd[1], + TMON_FD_BOTH, + child_timeout, + child_interval, + child_async_wait_time, + child_blocking_sleep_time); + _exit(ret); + } + + /* Parent */ + close(sync[0]); + close(fd[1]); + + ret = test_one(true, + sync[1], + fd[0], + TMON_FD_BOTH, + parent_timeout, + parent_interval, + parent_async_wait_time, + parent_blocking_sleep_time); + assert(ret == parent_result); + + /* Close to mimic exit, so child status can be checked below */ + close(fd[0]); + + /* Abort if child failed */ + waitpid(pid, &wstatus, 0); + if (WIFEXITED(wstatus)) { + assert(WEXITSTATUS(wstatus) == child_result); + } +} + +struct test_inputs { + unsigned int timeout; + unsigned int interval; + unsigned int async_wait_time; + unsigned int blocking_sleep_time; + int expected_result; +}; + +static void get_test_inputs(const char **args, struct test_inputs *inputs) +{ + if (strcmp(args[0], "false") == 0) { + inputs->interval = 0; + } else if (strcmp(args[0], "true") == 0) { + inputs->interval = 1; + } else { + inputs->interval = strtoul(args[0], NULL, 0); + } + + inputs->timeout = strtoul(args[1], NULL, 0); + inputs->async_wait_time = (unsigned int)strtoul(args[2], NULL, 0); + inputs->blocking_sleep_time = (unsigned int)strtoul(args[3], NULL, 0); + inputs->expected_result = (int)strtoul(args[4], NULL, 0); +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "usage: %s " + "\\\n\t" + "<parent_send_pings> " + "<parent_ping_timeout> " + "<parent_async_wait_time> " + "<parent_blocking_sleep_time> " + "<parent_expected_result> " + "\\\n\t" + "<child_send_pings> " + "<child_ping_timeout> " + "<child_async_wait_time> " + "<child_blocking_sleep_time> " + "<child_expected_result> " + "\n", + prog); + exit(1); +} + +int main(int argc, const char **argv) +{ + struct test_inputs parent; + struct test_inputs child; + + if (argc != 11) { + usage(argv[0]); + } + + test_backtrace_setup(); + + get_test_inputs(&argv[1], &parent); + get_test_inputs(&argv[6], &child); + + test(parent.timeout, + parent.interval, + parent.async_wait_time, + parent.blocking_sleep_time, + parent.expected_result, + child.timeout, + child.interval, + child.async_wait_time, + child.blocking_sleep_time, + child.expected_result); + + return 0; +} diff --git a/ctdb/tests/src/tmon_test.c b/ctdb/tests/src/tmon_test.c new file mode 100644 index 0000000..10eaa72 --- /dev/null +++ b/ctdb/tests/src/tmon_test.c @@ -0,0 +1,406 @@ +/* + Test trivial FD monitoring + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/wait.h" + +#include <talloc.h> +#include <tevent.h> +#include <assert.h> +#include <ctype.h> + +#include "lib/util/tevent_unix.h" + +#include "common/tmon.h" + +#include "tests/src/test_backtrace.h" + +struct test_write_state { + const char *write_data; + size_t write_data_len; + unsigned int offset; + struct tevent_req *req; +}; + +static int test_write_callback(void *private_data, struct tmon_pkt *pkt) +{ + struct test_write_state *state = talloc_get_type_abort( + private_data, struct test_write_state); + bool status; + size_t len; + char *end; + int err; + char c; + const char *t; + + assert(state->write_data != NULL); + + len = strlen(state->write_data); + if (state->offset >= len) { + return TMON_STATUS_EXIT; + } + + c = state->write_data[state->offset]; + state->offset++; + + if (isdigit(c)) { + err = c - '0'; + + if (err == 0) { + status = tmon_set_exit(pkt); + } else { + status = tmon_set_errno(pkt, err); + } + } else if (ispunct(c)) { + switch (c) { + case '.': + return TMON_STATUS_SKIP; + break; + case '!': + status = tmon_set_ping(pkt); + break; + case '#': + /* Additional errno syntax: #nnn[;] */ + t = &state->write_data[state->offset]; + err = (int)strtol(t, &end, 10); + state->offset += (end - t); + if (state->write_data[state->offset] == ';') { + state->offset++; + } + status = tmon_set_errno(pkt, err); + break; + default: + status = false; + } + } else if (isascii(c) && !isspace(c)) { + status = tmon_set_ascii(pkt, c); + } else { + status = tmon_set_custom(pkt, (uint16_t)c); + } + + if (!status) { + return EDOM; + } + + t = getenv("CTDB_TEST_TMON_WRITE_SKIP_MODE"); + if (t == NULL) { + return 0; + } + + /* + * This is write-skip mode: tmon_write() is called directly + * here in the callback and TMON_WRITE_SKIP is returned. This + * allows tmon_write() to be exercised by reusing test cases + * rather than writing extra test code and test cases. + */ + + status = tmon_write(state->req, pkt); + if (!status) { + return EIO; + } + + return TMON_STATUS_SKIP; +} + +static void test_tmon_done(struct tevent_req *subreq); + +static struct tevent_req *test_write_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, + const char *write_data) +{ + struct tevent_req *req, *subreq; + struct test_write_state *state; + struct tmon_actions actions = { + .write_callback = test_write_callback, + }; + + req = tevent_req_create(mem_ctx, &state, struct test_write_state); + if (req == NULL) { + return NULL; + } + + state->write_data = write_data; + state->offset = 0; + + subreq = tmon_send(state, + ev, + fd, + TMON_FD_WRITE, + 0, + 1, + &actions, + state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_tmon_done, req); + + /* Nasty hack, but OK to cheapen testing - see test_write_callback() */ + state->req = subreq; + + return req; +} + +static void test_tmon_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int err; + + status = tmon_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!status) { + tevent_req_error(req, err); + return; + } + + tevent_req_done(req); +} + +static bool test_write_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static int test_timeout_ok_callback(void *private_data) +{ + return 0; +} + +static int test_read_callback(void *private_data, struct tmon_pkt *pkt) +{ + bool status; + char c; + uint16_t val; + + status = tmon_parse_ping(pkt); + if (status) { + printf("PING\n"); + fflush(stdout); + return 0; + } + + status = tmon_parse_ascii(pkt, &c); + if (status) { + printf("ASCII %c\n", c); + fflush(stdout); + return 0; + } + + status = tmon_parse_custom(pkt, &val); + if (status) { + printf("CUSTOM 0x%"PRIx16"\n", val); + fflush(stdout); + return 0; + } + + return 0; +} + +static int test_close_ok_callback(void *private_data) +{ + return 0; +} + +struct test_read_state { +}; + +static struct tevent_req *test_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, + bool close_ok, + unsigned long timeout, + bool timeout_ok) +{ + struct tevent_req *req, *subreq; + struct test_read_state *state; + struct tmon_actions actions = { + .read_callback = test_read_callback, + }; + + req = tevent_req_create(mem_ctx, &state, struct test_read_state); + if (req == NULL) { + return NULL; + } + + if (timeout_ok) { + actions.timeout_callback = test_timeout_ok_callback; + } + if (close_ok) { + actions.close_callback = test_close_ok_callback; + } + + subreq = tmon_send(state, + ev, + fd, + TMON_FD_READ, + timeout, + 0, + &actions, + state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_tmon_done, req); + + return req; +} + +static bool test_read_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static void test(const char *write_data, + bool close_ok, + unsigned long timeout, + bool timeout_ok) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + int fd[2]; + pid_t pid; + int wstatus; + bool status; + int err; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* child */ + close(fd[1]); + + req = test_read_send(mem_ctx, + ev, + fd[0], + close_ok, + timeout, + timeout_ok); + assert(req != NULL); + + status = tevent_req_poll(req, ev); + assert(status); + + status = test_read_recv(req, &err); + if (status) { + err = 0; + printf("READER OK\n"); + } else { + printf("READER ERR=%d\n", err); + } + fflush(stdout); + + _exit(ret); + } + + /* Parent */ + close(fd[0]); + + req = test_write_send(mem_ctx, + ev, + fd[1], + write_data); + assert(req != NULL); + + status = tevent_req_poll(req, ev); + assert(status); + + status = test_write_recv(req, &err); + if (status) { + err = 0; + printf("WRITER OK\n"); + } else { + printf("WRITER ERR=%d\n", err); + } + fflush(stdout); + + /* Close to mimic exit, so child status can be checked below */ + close(fd[1]); + + waitpid(pid, &wstatus, 0); +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "usage: %s <write_data> <close_ok> <timeout> <timeout_ok>\n\n" + " <write_data> is processed by test_write_callback(), " + "1 character per second:\n" + " 0: write EXIT\n" + " 1-9: write ERRNO 1-9\n" + " .: skip write\n" + " <space>: write CUSTOM containing <space>\n" + " other <ascii>: write ASCII containing <ascii>\n" + " other: write CUSTOM\n" + " See test_write_callback() for more details\n" + , + prog); + exit(1); +} + +int main(int argc, const char **argv) +{ + bool close_ok, timeout_ok; + unsigned long timeout; + + if (argc != 5) { + usage(argv[0]); + } + + test_backtrace_setup(); + + close_ok = (strcmp(argv[2], "true") == 0); + timeout = strtoul(argv[3], NULL, 0); + if (timeout == 0) { + /* + * Default timeout that should not come into play but + * will cause tests to fail after a reasonable amount + * of time, if something unexpected happens. + */ + timeout = 20; + } + timeout_ok = (strcmp(argv[4], "true") == 0); + + test(argv[1], close_ok, timeout, timeout_ok); + + return 0; +} diff --git a/ctdb/tests/src/transaction_loop.c b/ctdb/tests/src/transaction_loop.c new file mode 100644 index 0000000..c6bf35d --- /dev/null +++ b/ctdb/tests/src/transaction_loop.c @@ -0,0 +1,419 @@ +/* + simple ctdb benchmark for persistent databases + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct transaction_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int num_nodes; + int timelimit; + int interactive; + TDB_DATA key; + uint32_t pnn; + struct ctdb_transaction_handle *h; + uint32_t *old_counter, *counter; + struct tevent_req *subreq; + bool done; +}; + +static void transaction_loop_start(struct tevent_req *subreq); +static void transaction_loop_started(struct tevent_req *subreq); +static void transaction_loop_committed(struct tevent_req *subreq); +static void transaction_loop_each_second(struct tevent_req *subreq); +static bool transaction_loop_check_counters(struct tevent_req *req); +static void transaction_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *transaction_loop_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + int num_nodes, int timelimit, int interactive, + const char *keystr) +{ + struct tevent_req *req, *subreq; + struct transaction_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct transaction_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->interactive = interactive; + state->key.dptr = discard_const(keystr); + state->key.dsize = strlen(keystr); + state->pnn = ctdb_client_pnn(client); + state->old_counter = talloc_zero_array(state, uint32_t, num_nodes); + if (tevent_req_nomem(state->old_counter, req)) { + return tevent_req_post(req, ev); + } + state->counter = talloc_zero_array(state, uint32_t, num_nodes); + if (tevent_req_nomem(state->counter, req)) { + return tevent_req_post(req, ev); + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, transaction_loop_start, req); + + return req; +} + +static void transaction_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_transaction_start_send(state, state->ev, state->client, + tevent_timeval_current_ofs( + state->timelimit, 0), + state->ctdb_db, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_started, req); + state->subreq = subreq; + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_each_second, + req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_finish, req); +} + +static void transaction_loop_started(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + TDB_DATA data; + int ret; + uint32_t *counter; + + state->h = ctdb_transaction_start_recv(subreq, &ret); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (state->h == NULL) { + fprintf(stderr, "transaction start failed\n"); + tevent_req_error(req, ret); + return; + } + + ret = ctdb_transaction_fetch_record(state->h, state->key, + state, &data); + if (ret != 0) { + fprintf(stderr, "transaction fetch record failed\n"); + tevent_req_error(req, ret); + return; + } + + if (data.dsize < state->num_nodes * sizeof(uint32_t)) { + TALLOC_FREE(data.dptr); + + data.dsize = state->num_nodes * sizeof(uint32_t); + data.dptr = (uint8_t *)talloc_zero_array(state, uint32_t, + state->num_nodes); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + } + + counter = (uint32_t *)data.dptr; + counter[state->pnn] += 1; + memcpy(state->counter, counter, state->num_nodes * sizeof(uint32_t)); + + ret = ctdb_transaction_store_record(state->h, state->key, data); + if (ret != 0) { + fprintf(stderr, "transaction store failed\n"); + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_transaction_commit_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0), + state->h); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_committed, req); + state->subreq = subreq; +} + +static void transaction_loop_committed(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + int ret; + bool status; + + status = ctdb_transaction_commit_recv(subreq, &ret); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (! status) { + fprintf(stderr, "transaction commit failed - %s\n", + strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->pnn == 0) { + if (! transaction_loop_check_counters(req)) { + return; + } + } + + if (state->done) { + int i; + + printf("Transaction[%u]: ", ctdb_client_pnn(state->client)); + for (i=0; i<state->num_nodes; i++) { + printf("%6u ", state->counter[i]); + } + printf("\n"); + + tevent_req_done(req); + + return; + } + + subreq = ctdb_transaction_start_send(state, state->ev, state->client, + tevent_timeval_current_ofs( + state->timelimit, 0), + state->ctdb_db, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_started, req); +} + +static void transaction_loop_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + bool status; + int i; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + fprintf(stderr, "tevent wakeup failed\n"); + tevent_req_error(req, EIO); + return; + } + + if (state->interactive == 1) { + printf("Transaction[%u]: ", ctdb_client_pnn(state->client)); + for (i=0; i<state->num_nodes; i++) { + printf("%6u ", state->counter[i]); + } + printf("\n"); + fflush(stdout); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_each_second, req); +} + +static bool transaction_loop_check_counters(struct tevent_req *req) +{ + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + int i; + bool monotonous = true; + + for (i=0; i<state->num_nodes; i++) { + if (state->counter[i] < state->old_counter[i]) { + fprintf(stderr, + "Counter reduced for node %d: %u -> %u\n", + i, state->old_counter[i], state->counter[i]); + monotonous = false; + break; + } + } + + if (monotonous) { + memcpy(state->old_counter, state->counter, + state->num_nodes * sizeof(uint32_t)); + } + + return monotonous; +} + +static void transaction_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + + state->done = true; + + if (! status) { + tevent_req_error(req, EIO); + return; + } +} + +static bool transaction_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + uint8_t db_flags; + int ret; + bool status; + + setup_logging("transaction_loop", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + if (strcmp(opts->dbtype, "persistent") == 0) { + db_flags = CTDB_DB_FLAGS_PERSISTENT; + } else if (strcmp(opts->dbtype, "replicated") == 0) { + db_flags = CTDB_DB_FLAGS_REPLICATED; + } else { + fprintf(stderr, "Database must be persistent or replicated\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, + db_flags, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to persistent DB %s\n", + opts->dbname); + exit(1); + } + + req = transaction_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit, + opts->interactive, opts->keystr); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = transaction_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "transaction loop test failed, ret=%d\n", ret); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/tunable_test.c b/ctdb/tests/src/tunable_test.c new file mode 100644 index 0000000..ea94aec --- /dev/null +++ b/ctdb/tests/src/tunable_test.c @@ -0,0 +1,71 @@ +/* + Test tunable handling + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <talloc.h> +#include <assert.h> + +#include "common/tunable.c" + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct ctdb_tunable_list tun_list; + struct ctdb_var_list *list; + bool status; + int ret = 0; + int i; + + if (argc != 2) { + fprintf(stderr, "Usage: %s <filename>\n", argv[0]); + return 1; + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + status = ctdb_tunable_load_file(mem_ctx, &tun_list, argv[1]); + if (!status) { + ret = EINVAL; + goto done; + } + + list = ctdb_tunable_names(mem_ctx); + assert(list != NULL); + + for (i = 0; i < list->count; i++) { + const char *var = list->var[i]; + uint32_t val; + + status = ctdb_tunable_get_value(&tun_list, var, &val); + if (!status) { + ret = EIO; + goto done; + } + + printf("%s=%"PRIu32"\n", var, val); + fflush(stdout); + } + +done: + talloc_free(mem_ctx); + return ret; +} diff --git a/ctdb/tests/src/tunnel_cmd.c b/ctdb/tests/src/tunnel_cmd.c new file mode 100644 index 0000000..73a2297 --- /dev/null +++ b/ctdb/tests/src/tunnel_cmd.c @@ -0,0 +1,199 @@ +/* + CTDB tunnel test + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_private.h" +#include "client/client.h" + +#define TUNNEL_ID (CTDB_TUNNEL_TEST | 0xf0f0f0f0) + +struct listen_state { + TALLOC_CTX *mem_ctx; + bool done; +}; + +static void listen_callback(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct listen_state *state = (struct listen_state *)private_data; + const char *msg; + size_t np; + int ret; + + ret = ctdb_stringn_pull(buf, buflen, state->mem_ctx, &msg, &np); + if (ret != 0) { + fprintf(stderr, "Invalid tunnel message, ret=%d\n", ret); + return; + } + + fprintf(stderr, "%u: %s\n", srcnode, msg); + + if (strcmp(msg, "quit") == 0) { + state->done = true; + } + + talloc_free(discard_const(msg)); +} + +static int cmd_listen(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct ctdb_client_context *client) +{ + struct ctdb_tunnel_context *tunnel; + struct listen_state state; + int ret; + + state.mem_ctx = mem_ctx; + state.done = false; + + ret = ctdb_tunnel_setup(mem_ctx, ev, client, TUNNEL_ID, + listen_callback, &state, &tunnel); + if (ret != 0) { + return ret; + } + + ctdb_client_wait(ev, &state.done); + + ret = ctdb_tunnel_destroy(ev, tunnel); + if (ret != 0) { + return ret; + } + + return 0; +} + +static void send_callback(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, void *private_data) +{ + fprintf(stderr, "send received a message - %u: %zu\n", srcnode, buflen); +} + +static int cmd_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct ctdb_client_context *client, + uint32_t destnode, const char *msg) +{ + struct ctdb_tunnel_context *tunnel; + uint8_t *buf; + size_t buflen, np; + int ret; + + ret = ctdb_tunnel_setup(mem_ctx, ev, client, TUNNEL_ID, + send_callback, NULL, &tunnel); + if (ret != 0) { + return ret; + } + + buflen = ctdb_stringn_len(&msg); + buf = talloc_size(mem_ctx, buflen); + if (buf == NULL) { + return ENOMEM; + } + ctdb_stringn_push(&msg, buf, &np); + + ret = ctdb_tunnel_request(mem_ctx, ev, tunnel, destnode, + tevent_timeval_zero(), buf, buflen, false); + if (ret != 0) { + return ret; + } + + ret = ctdb_tunnel_destroy(ev, tunnel); + if (ret != 0) { + return ret; + } + + return 0; +} + +static void usage(const char *cmd) +{ + fprintf(stderr, "usage: %s <ctdb-socket> listen\n", cmd); + fprintf(stderr, "usage: %s <ctdb-socket> send <pnn> <msg>\n", cmd); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + const char *socket = NULL, *msg = NULL; + uint32_t pnn = CTDB_UNKNOWN_PNN; + int ret; + bool do_listen = false; + bool do_send = false; + + if (argc != 3 && argc != 5) { + usage(argv[0]); + exit(1); + } + + socket = argv[1]; + + if (strcmp(argv[2], "listen") == 0) { + do_listen = true; + } else if (strcmp(argv[2], "send") == 0) { + if (argc != 5) { + usage(argv[0]); + exit(1); + } + + pnn = atol(argv[3]); + msg = argv[4]; + do_send = true; + } else { + usage(argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + talloc_free(mem_ctx); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, socket, &client); + if (ret != 0) { + talloc_free(mem_ctx); + exit(1); + } + + if (do_listen) { + ret = cmd_listen(mem_ctx, ev, client); + } + if (do_send) { + ret = cmd_send(mem_ctx, ev, client, pnn, msg); + } + + talloc_free(mem_ctx); + + return ret; +} diff --git a/ctdb/tests/src/tunnel_test.c b/ctdb/tests/src/tunnel_test.c new file mode 100644 index 0000000..a6d44ba --- /dev/null +++ b/ctdb/tests/src/tunnel_test.c @@ -0,0 +1,480 @@ +/* + CTDB tunnel test + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_private.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct test_data { + uint32_t pnn; + uint32_t count; +}; + +static size_t test_data_len(struct test_data *in) +{ + return ctdb_uint32_len(&in->pnn) + ctdb_uint32_len(&in->count); +} + +static void test_data_push(struct test_data *in, uint8_t *buf, size_t *npush) +{ + size_t offset = 0, np; + + ctdb_uint32_push(&in->pnn, buf+offset, &np); + offset += np; + + ctdb_uint32_push(&in->count, buf+offset, &np); + offset += np; + + *npush = offset; +} + +static int test_data_pull(uint8_t *buf, size_t buflen, struct test_data *out, + size_t *npull) +{ + size_t offset = 0, np; + int ret; + + ret = ctdb_uint32_pull(buf+offset, buflen-offset, &out->pnn, &np); + if (ret != 0) { + return ret; + } + offset += np; + + ret = ctdb_uint32_pull(buf+offset, buflen-offset, &out->count, &np); + if (ret != 0) { + return ret; + } + offset += np; + + *npull = offset; + return 0; +} + +/* + * Set up 2 tunnels from each node - one to the next node and one to the + * previous node. The tunnel to the next node is used for sending data and + * tunnel to the previous node is used for receiving data. + */ + +struct tunnel_test_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + int num_nodes; + int timelimit; + + uint32_t pnn; + uint32_t next_node; + uint32_t prev_node; + bool done; + struct ctdb_tunnel_context *send_tunnel; + struct ctdb_tunnel_context *recv_tunnel; + uint32_t count; + uint8_t *buf; +}; + +static void tunnel_test_send_tunnel_done(struct tevent_req *subreq); +static void tunnel_test_recv_tunnel_done(struct tevent_req *subreq); +static void tunnel_test_start(struct tevent_req *subreq); +static void tunnel_test_msg_send(struct tevent_req *req, + struct test_data *tdata); +static void tunnel_test_msg_send_done(struct tevent_req *subreq); +static void tunnel_test_handler(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, + void *private_data); +static void tunnel_test_done(struct tevent_req *subreq); +static void tunnel_test_finish(struct tevent_req *subreq); +static void tunnel_test_send_tunnel_closed(struct tevent_req *subreq); +static void tunnel_test_recv_tunnel_closed(struct tevent_req *subreq); + +static struct tevent_req *tunnel_test_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct tunnel_test_state *state; + + req = tevent_req_create(mem_ctx, &state, struct tunnel_test_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->pnn = ctdb_client_pnn(client); + state->prev_node = (state->pnn + num_nodes - 1) % num_nodes; + state->next_node = (state->pnn + 1) % num_nodes; + state->done = false; + + subreq = ctdb_tunnel_setup_send(state, state->ev, state->client, + CTDB_TUNNEL_TEST | state->pnn, + tunnel_test_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, tunnel_test_send_tunnel_done, req); + + return req; +} + +static void tunnel_test_send_tunnel_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_setup_recv(subreq, &ret, &state->send_tunnel); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_tunnel_setup_send(state, state->ev, state->client, + CTDB_TUNNEL_TEST | state->prev_node, + tunnel_test_handler, req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_recv_tunnel_done, req); +} + +static void tunnel_test_recv_tunnel_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_setup_recv(subreq, &ret, &state->recv_tunnel); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_start, req); +} + +static void tunnel_test_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + struct test_data tdata; + int ret; + bool status; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_done, req); + + tdata.pnn = state->pnn; + tdata.count = state->count; + tunnel_test_msg_send(req, &tdata); +} + +static void tunnel_test_msg_send(struct tevent_req *req, + struct test_data *tdata) +{ + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + struct tevent_req *subreq; + size_t buflen, np; + + buflen = test_data_len(tdata); + state->buf = talloc_size(state, buflen); + if (tevent_req_nomem(state->buf, req)) { + return; + } + test_data_push(tdata, state->buf, &np); + + subreq = ctdb_tunnel_request_send(state, state->ev, + state->send_tunnel, + state->next_node, + tevent_timeval_zero(), + state->buf, buflen, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_msg_send_done, req); +} + +static void tunnel_test_msg_send_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_request_recv(subreq, &ret, NULL, NULL, NULL); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + TALLOC_FREE(state->buf); +} + +static void tunnel_test_handler(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + struct test_data tdata; + size_t np; + int ret; + + if (state->done) { + return; + } + + if (tctx == state->send_tunnel) { + fprintf(stderr, "pnn:%u Received data on send tunnel\n", + state->pnn); + tevent_req_error(req, EPROTO); + return; + } + + ret = test_data_pull(buf, buflen, &tdata, &np); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + if (tdata.pnn == state->pnn) { + if (tdata.count != state->count) { + tevent_req_error(req, EPROTO); + return; + } + + state->count = tdata.count + 1; + tdata.count = state->count; + } + + tunnel_test_msg_send(req, &tdata); +} + +static void tunnel_test_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("pnn[%u] %.1lf msgs/sec\n", + state->pnn, (double)state->count / state->timelimit); + + state->done = true; + + /* wait few more seconds */ + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(3, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_finish, req); +} + +static void tunnel_test_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + subreq = ctdb_tunnel_destroy_send(state, state->ev, + state->send_tunnel); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_send_tunnel_closed, req); +} + +static void tunnel_test_send_tunnel_closed(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_destroy_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + state->send_tunnel = NULL; + + subreq = ctdb_tunnel_destroy_send(state, state->ev, + state->recv_tunnel); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_recv_tunnel_closed, req); +} + +static void tunnel_test_recv_tunnel_closed(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_destroy_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + state->recv_tunnel = NULL; + + tevent_req_done(req); +} + +static bool tunnel_test_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("tunnel_test", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + req = tunnel_test_send(mem_ctx, ev, client, opts->num_nodes, + opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = tunnel_test_recv(req, &ret); + if (! status) { + fprintf(stderr, "tunnel test failed, ret=%d\n", ret); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/update_record.c b/ctdb/tests/src/update_record.c new file mode 100644 index 0000000..11b6050 --- /dev/null +++ b/ctdb/tests/src/update_record.c @@ -0,0 +1,236 @@ +/* + Update a record and increase it's RSN + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_api.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct update_record_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *db; + int timelimit; + TDB_DATA key; +}; + +static void update_record_fetch_done(struct tevent_req *subreq); +static void update_record_update_done(struct tevent_req *subreq); + +static struct tevent_req *update_record_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + const char *keystr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct update_record_state *state; + + req = tevent_req_create(mem_ctx, &state, struct update_record_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->db = db; + state->timelimit = timelimit; + state->key.dptr = (uint8_t *)discard_const(keystr); + state->key.dsize = strlen(keystr); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, update_record_fetch_done, req); + + return req; +} + +static void update_record_fetch_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct update_record_state *state = tevent_req_data( + req, struct update_record_state); + struct ctdb_record_handle *h; + struct ctdb_ltdb_header header; + struct ctdb_rec_buffer *recbuf; + struct ctdb_req_control request; + TDB_DATA data; + int ret; + + h = ctdb_fetch_lock_recv(subreq, &header, NULL, NULL, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + + header.rsn += 10; + + recbuf = ctdb_rec_buffer_init(state, ctdb_db_id(state->db)); + if (tevent_req_nomem(recbuf, req)) { + return; + } + + data.dptr = (uint8_t *)talloc_asprintf(recbuf, "%"PRIu64, header.rsn); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + data.dsize = strlen((char *)data.dptr); + + ret = ctdb_rec_buffer_add(state, recbuf, 0, &header, state->key, data); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ctdb_req_control_update_record(&request, recbuf); + subreq = ctdb_client_control_send(state, state->ev, state->client, + CTDB_CURRENT_NODE, + tevent_timeval_current_ofs( + state->timelimit, 0), + &request); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, update_record_update_done, req); + + talloc_free(recbuf); +} + +static void update_record_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct update_record_state *state = tevent_req_data( + req, struct update_record_state); + struct ctdb_reply_control *reply; + int ret; + bool status; + + status = ctdb_client_control_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_control_update_record(reply); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + talloc_free(reply); + + tevent_req_done(req); +} + +static bool update_record_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("update_record", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client (%s), %s\n", + opts->socket, strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, + 0, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach DB %s\n", opts->dbname); + exit(1); + } + + req = update_record_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = update_record_recv(req, &ret); + if (! status) { + fprintf(stderr, "update record failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/update_record_persistent.c b/ctdb/tests/src/update_record_persistent.c new file mode 100644 index 0000000..2d6d21e --- /dev/null +++ b/ctdb/tests/src/update_record_persistent.c @@ -0,0 +1,218 @@ +/* + Update a record in persistent database + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_api.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct update_record_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *db; + int timelimit; + TDB_DATA key, data; +}; + +static void update_record_update_done(struct tevent_req *subreq); + +static struct tevent_req *update_record_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + const char *keystr, + const char *valuestr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct update_record_state *state; + struct ctdb_ltdb_header header; + struct ctdb_rec_buffer *recbuf; + struct ctdb_req_control request; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct update_record_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->db = db; + state->timelimit = timelimit; + state->key.dptr = (uint8_t *)discard_const(keystr); + state->key.dsize = strlen(keystr); + state->data.dptr = (uint8_t *)discard_const(valuestr); + state->data.dsize = strlen(valuestr); + + ret = ctdb_ltdb_fetch(state->db, state->key, &header, NULL, NULL); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + header.rsn += 1; + + recbuf = ctdb_rec_buffer_init(state, ctdb_db_id(state->db)); + if (tevent_req_nomem(recbuf, req)) { + return tevent_req_post(req, ev); + } + + ret = ctdb_rec_buffer_add(state, recbuf, 0, &header, + state->key, state->data); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + ctdb_req_control_update_record(&request, recbuf); + subreq = ctdb_client_control_send(state, state->ev, state->client, + CTDB_CURRENT_NODE, + tevent_timeval_current_ofs( + state->timelimit, 0), + &request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, update_record_update_done, req); + + talloc_free(recbuf); + return req; +} + +static void update_record_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct update_record_state *state = tevent_req_data( + req, struct update_record_state); + struct ctdb_reply_control *reply; + int ret; + bool status; + + status = ctdb_client_control_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_control_update_record(reply); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + talloc_free(reply); + + tevent_req_done(req); +} + +static bool update_record_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("update_record_persistene", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + if (opts->valuestr == NULL) { + fprintf(stderr, "Error: please specify key value (-v)\n"); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client (%s), %s\n", + opts->socket, strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, + CTDB_DB_FLAGS_PERSISTENT, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach DB %s\n", opts->dbname); + exit(1); + } + + req = update_record_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->valuestr, + opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = update_record_recv(req, &ret); + if (! status) { + fprintf(stderr, "update record failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/test_check_tcp_ports.sh b/ctdb/tests/test_check_tcp_ports.sh new file mode 100755 index 0000000..1272d88 --- /dev/null +++ b/ctdb/tests/test_check_tcp_ports.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +DIRNAME=$(dirname $0) + +CTDB_BASE="${DIRNAME}/../config" +. "${CTDB_BASE}/functions" + +SERVICE="test-service" + +PORTS="$@" + +if [ "x${PORTS}" = "x" ] ; then + PORTS=139 +fi + +ctdb_check_tcp_ports ${SERVICE} ${PORTS} + +echo "Test for service '${SERVICE}' on tcp ports ${PORTS} succeeded!" |