summaryrefslogtreecommitdiffstats
path: root/ctdb/tests
diff options
context:
space:
mode:
Diffstat (limited to 'ctdb/tests')
-rwxr-xr-xctdb/tests/CLUSTER/complex/11_ctdb_delip_removes_ip.sh36
-rwxr-xr-xctdb/tests/CLUSTER/complex/18_ctdb_reloadips.sh257
-rwxr-xr-xctdb/tests/CLUSTER/complex/30_nfs_tickle_killtcp.sh57
-rwxr-xr-xctdb/tests/CLUSTER/complex/31_nfs_tickle.sh77
-rwxr-xr-xctdb/tests/CLUSTER/complex/32_cifs_tickle.sh69
-rwxr-xr-xctdb/tests/CLUSTER/complex/33_gratuitous_arp.sh74
-rwxr-xr-xctdb/tests/CLUSTER/complex/34_nfs_tickle_restart.sh81
-rwxr-xr-xctdb/tests/CLUSTER/complex/36_smb_reset_server.sh78
-rwxr-xr-xctdb/tests/CLUSTER/complex/37_nfs_reset_server.sh78
-rwxr-xr-xctdb/tests/CLUSTER/complex/41_failover_ping_discrete.sh56
-rwxr-xr-xctdb/tests/CLUSTER/complex/42_failover_ssh_hostname.sh70
-rwxr-xr-xctdb/tests/CLUSTER/complex/43_failover_nfs_basic.sh62
-rwxr-xr-xctdb/tests/CLUSTER/complex/44_failover_nfs_oneway.sh82
-rwxr-xr-xctdb/tests/CLUSTER/complex/45_failover_nfs_kill.sh69
-rwxr-xr-xctdb/tests/CLUSTER/complex/60_rogueip_releaseip.sh56
-rwxr-xr-xctdb/tests/CLUSTER/complex/61_rogueip_takeip.sh46
-rw-r--r--ctdb/tests/CLUSTER/complex/README2
-rw-r--r--ctdb/tests/CLUSTER/complex/scripts/local.bash289
-rwxr-xr-xctdb/tests/INTEGRATION/database/basics.001.attach.sh48
-rwxr-xr-xctdb/tests/INTEGRATION/database/basics.002.attach.sh116
-rwxr-xr-xctdb/tests/INTEGRATION/database/basics.003.detach.sh166
-rwxr-xr-xctdb/tests/INTEGRATION/database/basics.004.wipe.sh56
-rwxr-xr-xctdb/tests/INTEGRATION/database/basics.010.backup_restore.sh97
-rwxr-xr-xctdb/tests/INTEGRATION/database/fetch.001.ring.sh34
-rwxr-xr-xctdb/tests/INTEGRATION/database/fetch.002.ring-hotkeys.sh161
-rwxr-xr-xctdb/tests/INTEGRATION/database/readonly.001.basic.sh178
-rwxr-xr-xctdb/tests/INTEGRATION/database/recovery.001.volatile.sh118
-rwxr-xr-xctdb/tests/INTEGRATION/database/recovery.002.large.sh106
-rwxr-xr-xctdb/tests/INTEGRATION/database/recovery.003.no_resurrect.sh63
-rwxr-xr-xctdb/tests/INTEGRATION/database/recovery.010.persistent.sh103
-rwxr-xr-xctdb/tests/INTEGRATION/database/recovery.011.continue.sh73
-rw-r--r--ctdb/tests/INTEGRATION/database/scripts/local.bash116
-rwxr-xr-xctdb/tests/INTEGRATION/database/transaction.001.ptrans.sh110
-rwxr-xr-xctdb/tests/INTEGRATION/database/transaction.002.loop.sh28
-rwxr-xr-xctdb/tests/INTEGRATION/database/transaction.003.loop_recovery.sh50
-rwxr-xr-xctdb/tests/INTEGRATION/database/transaction.004.update_record.sh80
-rwxr-xr-xctdb/tests/INTEGRATION/database/transaction.010.loop_recovery.sh51
-rwxr-xr-xctdb/tests/INTEGRATION/database/traverse.001.one.sh116
-rwxr-xr-xctdb/tests/INTEGRATION/database/traverse.002.many.sh52
-rwxr-xr-xctdb/tests/INTEGRATION/database/vacuum.001.fast.sh159
-rwxr-xr-xctdb/tests/INTEGRATION/database/vacuum.002.full.sh96
-rwxr-xr-xctdb/tests/INTEGRATION/database/vacuum.003.recreate.sh139
-rwxr-xr-xctdb/tests/INTEGRATION/database/vacuum.030.locked.sh102
-rwxr-xr-xctdb/tests/INTEGRATION/database/vacuum.031.locked.sh114
-rwxr-xr-xctdb/tests/INTEGRATION/database/vacuum.032.locked.sh102
-rwxr-xr-xctdb/tests/INTEGRATION/database/vacuum.033.locked.sh117
-rwxr-xr-xctdb/tests/INTEGRATION/database/vacuum.034.locked.sh129
-rwxr-xr-xctdb/tests/INTEGRATION/failover/pubips.001.list.sh48
-rwxr-xr-xctdb/tests/INTEGRATION/failover/pubips.010.addip.sh25
-rwxr-xr-xctdb/tests/INTEGRATION/failover/pubips.011.delip.sh16
-rwxr-xr-xctdb/tests/INTEGRATION/failover/pubips.012.reloadips.sh117
-rwxr-xr-xctdb/tests/INTEGRATION/failover/pubips.013.failover_noop.sh44
-rwxr-xr-xctdb/tests/INTEGRATION/failover/pubips.014.iface_gc.sh51
-rwxr-xr-xctdb/tests/INTEGRATION/failover/pubips.020.moveip.sh76
-rwxr-xr-xctdb/tests/INTEGRATION/failover/pubips.030.disable_enable.sh23
-rwxr-xr-xctdb/tests/INTEGRATION/failover/pubips.032.stop_continue.sh21
-rwxr-xr-xctdb/tests/INTEGRATION/failover/pubips.040.NoIPTakeover.sh71
-rwxr-xr-xctdb/tests/INTEGRATION/failover/pubips.050.missing_ip.sh71
-rw-r--r--ctdb/tests/INTEGRATION/simple/README2
-rwxr-xr-xctdb/tests/INTEGRATION/simple/basics.000.onnode.sh12
-rwxr-xr-xctdb/tests/INTEGRATION/simple/basics.001.listnodes.sh38
-rwxr-xr-xctdb/tests/INTEGRATION/simple/basics.002.tunables.sh67
-rwxr-xr-xctdb/tests/INTEGRATION/simple/basics.003.ping.sh34
-rwxr-xr-xctdb/tests/INTEGRATION/simple/basics.004.getpid.sh55
-rwxr-xr-xctdb/tests/INTEGRATION/simple/basics.005.process_exists.sh66
-rwxr-xr-xctdb/tests/INTEGRATION/simple/basics.010.statistics.sh17
-rwxr-xr-xctdb/tests/INTEGRATION/simple/basics.011.statistics_reset.sh62
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.001.stop_leader_yield.sh26
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.002.ban_leader_yield.sh26
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.003.capability_leader_yield.sh24
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.006.stop_leader_yield_no_lock.sh30
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.007.ban_leader_yield_no_lock.sh30
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.008.capability_leader_yield_no_lock.sh29
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.010.getrelock.sh24
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.012.reclock_command.sh20
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.015.reclock_remove_lock.sh80
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.016.reclock_move_lock_dir.sh92
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.020.message_ring.sh53
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.021.tunnel_ring.sh34
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.030.node_stall_leader_timeout.sh48
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.090.unreachable.sh39
-rwxr-xr-xctdb/tests/INTEGRATION/simple/cluster.091.version_check.sh55
-rwxr-xr-xctdb/tests/INTEGRATION/simple/debug.001.getdebug.sh42
-rwxr-xr-xctdb/tests/INTEGRATION/simple/debug.002.setdebug.sh74
-rwxr-xr-xctdb/tests/INTEGRATION/simple/debug.003.dumpmemory.sh18
-rwxr-xr-xctdb/tests/INTEGRATION/simple/eventscripts.001.zero_scripts.sh16
-rwxr-xr-xctdb/tests/INTEGRATION/simple/eventscripts.090.debug_hung.sh76
-rw-r--r--ctdb/tests/README145
-rw-r--r--ctdb/tests/TODO4
-rwxr-xr-xctdb/tests/UNIT/cunit/cluster_mutex_001.sh66
-rwxr-xr-xctdb/tests/UNIT/cunit/cluster_mutex_002.sh132
-rwxr-xr-xctdb/tests/UNIT/cunit/cluster_mutex_003.sh75
-rwxr-xr-xctdb/tests/UNIT/cunit/cmdline_test_001.sh98
-rwxr-xr-xctdb/tests/UNIT/cunit/comm_test_001.sh13
-rwxr-xr-xctdb/tests/UNIT/cunit/comm_test_002.sh24
-rwxr-xr-xctdb/tests/UNIT/cunit/conf_test_001.sh196
-rwxr-xr-xctdb/tests/UNIT/cunit/config_test_001.sh115
-rwxr-xr-xctdb/tests/UNIT/cunit/config_test_002.sh65
-rwxr-xr-xctdb/tests/UNIT/cunit/config_test_003.sh52
-rwxr-xr-xctdb/tests/UNIT/cunit/config_test_004.sh144
-rwxr-xr-xctdb/tests/UNIT/cunit/config_test_005.sh97
-rwxr-xr-xctdb/tests/UNIT/cunit/config_test_006.sh56
-rwxr-xr-xctdb/tests/UNIT/cunit/config_test_007.sh24
-rwxr-xr-xctdb/tests/UNIT/cunit/ctdb_io_test_001.sh10
-rwxr-xr-xctdb/tests/UNIT/cunit/db_hash_test_001.sh7
-rwxr-xr-xctdb/tests/UNIT/cunit/event_protocol_test_001.sh7
-rwxr-xr-xctdb/tests/UNIT/cunit/event_script_test_001.sh127
-rwxr-xr-xctdb/tests/UNIT/cunit/hash_count_test_001.sh7
-rwxr-xr-xctdb/tests/UNIT/cunit/line_test_001.sh90
-rwxr-xr-xctdb/tests/UNIT/cunit/path_tests_001.sh62
-rwxr-xr-xctdb/tests/UNIT/cunit/pidfile_test_001.sh8
-rwxr-xr-xctdb/tests/UNIT/cunit/pkt_read_001.sh7
-rwxr-xr-xctdb/tests/UNIT/cunit/pkt_write_001.sh7
-rwxr-xr-xctdb/tests/UNIT/cunit/porting_tests_001.sh15
-rwxr-xr-xctdb/tests/UNIT/cunit/protocol_test_001.sh7
-rwxr-xr-xctdb/tests/UNIT/cunit/protocol_test_002.sh7
-rwxr-xr-xctdb/tests/UNIT/cunit/protocol_test_012.sh7
-rwxr-xr-xctdb/tests/UNIT/cunit/protocol_test_101.sh7
-rwxr-xr-xctdb/tests/UNIT/cunit/protocol_test_111.sh7
-rwxr-xr-xctdb/tests/UNIT/cunit/protocol_test_201.sh6
-rwxr-xr-xctdb/tests/UNIT/cunit/rb_test_001.sh31
-rwxr-xr-xctdb/tests/UNIT/cunit/reqid_test_001.sh13
-rwxr-xr-xctdb/tests/UNIT/cunit/run_event_001.sh137
-rwxr-xr-xctdb/tests/UNIT/cunit/run_proc_001.sh159
-rwxr-xr-xctdb/tests/UNIT/cunit/sock_daemon_test_001.sh135
-rwxr-xr-xctdb/tests/UNIT/cunit/sock_io_test_001.sh9
-rwxr-xr-xctdb/tests/UNIT/cunit/srvid_test_001.sh7
-rwxr-xr-xctdb/tests/UNIT/cunit/system_socket_test_001.sh6
-rwxr-xr-xctdb/tests/UNIT/cunit/system_socket_test_002.sh68
-rwxr-xr-xctdb/tests/UNIT/cunit/system_socket_test_003.sh42
-rwxr-xr-xctdb/tests/UNIT/cunit/tmon_test_001.sh195
-rwxr-xr-xctdb/tests/UNIT/cunit/tmon_test_002.sh142
-rwxr-xr-xctdb/tests/UNIT/cunit/tunable_test_001.sh312
-rw-r--r--ctdb/tests/UNIT/eventd/README1
-rw-r--r--ctdb/tests/UNIT/eventd/etc-ctdb/ctdb.conf6
-rwxr-xr-xctdb/tests/UNIT/eventd/etc-ctdb/debug-script.sh22
-rw-r--r--ctdb/tests/UNIT/eventd/etc-ctdb/events/data/03.notalink.script2
-rw-r--r--ctdb/tests/UNIT/eventd/etc-ctdb/events/data/README1
-rw-r--r--ctdb/tests/UNIT/eventd/etc-ctdb/events/empty/README1
-rwxr-xr-xctdb/tests/UNIT/eventd/etc-ctdb/events/multi/01.test.script11
-rwxr-xr-xctdb/tests/UNIT/eventd/etc-ctdb/events/multi/02.test.script9
-rwxr-xr-xctdb/tests/UNIT/eventd/etc-ctdb/events/multi/03.test.script9
-rw-r--r--ctdb/tests/UNIT/eventd/etc-ctdb/events/random/01.disabled.script3
-rwxr-xr-xctdb/tests/UNIT/eventd/etc-ctdb/events/random/02.enabled.script49
-rw-r--r--ctdb/tests/UNIT/eventd/etc-ctdb/events/random/README.script1
-rwxr-xr-xctdb/tests/UNIT/eventd/etc-ctdb/events/random/a.script3
-rwxr-xr-xctdb/tests/UNIT/eventd/etc-ctdb/share/events/data/01.dummy.script6
-rwxr-xr-xctdb/tests/UNIT/eventd/etc-ctdb/share/events/data/02.disabled.script6
-rw-r--r--ctdb/tests/UNIT/eventd/etc-ctdb/share/events/empty/README1
-rw-r--r--ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/01.disabled.script3
-rwxr-xr-xctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/02.enabled.script12
-rw-r--r--ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/README.script1
-rwxr-xr-xctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/a.script3
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_001.sh27
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_002.sh21
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_003.sh45
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_004.sh33
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_005.sh34
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_006.sh19
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_007.sh19
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_008.sh83
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_009.sh155
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_011.sh40
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_012.sh27
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_013.sh27
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_014.sh27
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_021.sh26
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_022.sh22
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_023.sh22
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_024.sh31
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_031.sh17
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_032.sh43
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_033.sh43
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_041.sh26
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_042.sh29
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_043.sh29
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_044.sh37
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_051.sh15
-rwxr-xr-xctdb/tests/UNIT/eventd/eventd_052.sh35
-rw-r--r--ctdb/tests/UNIT/eventd/scripts/local.sh122
-rwxr-xr-xctdb/tests/UNIT/eventscripts/00.ctdb.init.001.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/00.ctdb.init.002.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/00.ctdb.init.003.sh16
-rwxr-xr-xctdb/tests/UNIT/eventscripts/00.ctdb.init.004.sh18
-rwxr-xr-xctdb/tests/UNIT/eventscripts/00.ctdb.init.005.sh20
-rwxr-xr-xctdb/tests/UNIT/eventscripts/00.ctdb.init.006.sh23
-rwxr-xr-xctdb/tests/UNIT/eventscripts/00.ctdb.init.007.sh16
-rwxr-xr-xctdb/tests/UNIT/eventscripts/00.ctdb.init.008.sh19
-rwxr-xr-xctdb/tests/UNIT/eventscripts/00.ctdb.init.009.sh51
-rwxr-xr-xctdb/tests/UNIT/eventscripts/01.reclock.init.001.sh10
-rwxr-xr-xctdb/tests/UNIT/eventscripts/01.reclock.init.002.sh10
-rwxr-xr-xctdb/tests/UNIT/eventscripts/01.reclock.init.003.sh20
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.001.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.002.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.003.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.004.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.005.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.006.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.007.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.011.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.012.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.014.sh18
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.015.sh20
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.017.sh20
-rwxr-xr-xctdb/tests/UNIT/eventscripts/05.system.monitor.018.sh82
-rwxr-xr-xctdb/tests/UNIT/eventscripts/06.nfs.releaseip.001.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/06.nfs.releaseip.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/06.nfs.takeip.001.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/06.nfs.takeip.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.010.sh23
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.011.sh28
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.012.sh31
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.013.sh36
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.init.001.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.init.002.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.init.021.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.init.022.sh18
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.init.023.sh23
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.001.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.002.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.003.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.004.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.005.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.006.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.009.sh19
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.010.sh25
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.011.sh21
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.012.sh29
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.013.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.014.sh16
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.015.sh16
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.016.sh20
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.017.sh20
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.monitor.018.sh20
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.multi.001.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.releaseip.001.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.releaseip.002.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.startup.001.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.startup.002.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.takeip.001.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.takeip.002.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/10.interface.takeip.003.sh22
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.001.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.002.sh24
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.003.sh24
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.004.sh24
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.011.sh23
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.012.sh23
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.013.sh27
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.014.sh27
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.015.sh61
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.021.sh27
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.022.sh27
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.023.sh27
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.024.sh27
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.025.sh65
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.031.sh62
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.041.sh24
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.042.sh25
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.051.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.052.sh21
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.053.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/11.natgw.054.sh21
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.001.sh19
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.002.sh18
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.003.sh16
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.004.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.005.sh20
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.006.sh24
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.007.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.008.sh23
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.009.sh20
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.010.sh19
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.011.sh21
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.012.sh29
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.013.sh23
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.014.sh29
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.015.sh29
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.016.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.017.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.018.sh21
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.019.sh23
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.021.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.022.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.023.sh25
-rwxr-xr-xctdb/tests/UNIT/eventscripts/13.per_ip_routing.024.sh30
-rwxr-xr-xctdb/tests/UNIT/eventscripts/20.multipathd.monitor.001.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/20.multipathd.monitor.002.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/20.multipathd.monitor.003.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/20.multipathd.monitor.004.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/31.clamd.monitor.002.sh16
-rwxr-xr-xctdb/tests/UNIT/eventscripts/31.clamd.monitor.003.sh16
-rwxr-xr-xctdb/tests/UNIT/eventscripts/40.vsftpd.monitor.002.sh52
-rwxr-xr-xctdb/tests/UNIT/eventscripts/40.vsftpd.shutdown.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/40.vsftpd.startup.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/41.httpd.monitor.002.sh30
-rwxr-xr-xctdb/tests/UNIT/eventscripts/41.httpd.shutdown.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/41.httpd.startup.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/48.netbios.shutdown.011.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/48.netbios.startup.011.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/49.winbind.monitor.101.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/49.winbind.monitor.102.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/49.winbind.shutdown.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/49.winbind.startup.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.monitor.101.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.monitor.103.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.monitor.104.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.monitor.105.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.monitor.106.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.monitor.110.sh20
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.monitor.111.sh23
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.monitor.112.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.monitor.113.sh16
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.shutdown.001.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.shutdown.002.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.shutdown.011.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/50.samba.startup.011.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.101.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.102.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.103.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.104.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.105.sh10
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.106.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.107.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.108.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.109.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.111.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.112.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.113.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.114.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.121.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.122.sh18
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.131.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.132.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.141.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.142.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.143.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.144.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.151.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.152.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.153.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.161.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.monitor.162.sh15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.multi.001.sh19
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.multi.002.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.releaseip.001.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.releaseip.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.shutdown.001.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.shutdown.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.startup.001.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.startup.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.takeip.001.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/60.nfs.takeip.002.sh12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.001.sh54
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.011.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.012.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.013.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.014.sh27
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.monitor.001.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.monitor.002.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.monitor.003.sh19
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.shutdown.001.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.shutdown.002.sh18
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.startup.001.sh11
-rwxr-xr-xctdb/tests/UNIT/eventscripts/91.lvs.startup.002.sh14
-rw-r--r--ctdb/tests/UNIT/eventscripts/README46
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.001.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.002.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.003.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.004.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.005.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.006.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.007.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.008.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.021.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.022.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.023.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.024.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.025.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.026.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.027.sh9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/debug_locks.sh.028.sh9
-rw-r--r--ctdb/tests/UNIT/eventscripts/etc-ctdb/public_addresses9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/etc-ctdb/rc.local56
-rwxr-xr-xctdb/tests/UNIT/eventscripts/etc/init.d/nfs7
-rwxr-xr-xctdb/tests/UNIT/eventscripts/etc/init.d/nfslock7
-rw-r--r--ctdb/tests/UNIT/eventscripts/etc/os-release2
-rw-r--r--ctdb/tests/UNIT/eventscripts/etc/samba/smb.conf43
-rw-r--r--ctdb/tests/UNIT/eventscripts/etc/sysconfig/nfs2
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/00.ctdb.sh24
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/01.reclock.sh16
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/05.system.sh48
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/06.nfs.sh4
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/10.interface.sh72
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/11.natgw.sh120
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/13.per_ip_routing.sh47
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/20.multipathd.sh25
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/31.clamd.sh8
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/40.vsftpd.sh14
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/41.httpd.sh14
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/48.netbios.sh23
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/49.winbind.sh28
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/50.samba.sh58
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/60.nfs.sh435
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/91.lvs.sh76
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/debug_locks.sh272
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/local.sh568
-rw-r--r--ctdb/tests/UNIT/eventscripts/scripts/statd-callout.sh65
-rwxr-xr-xctdb/tests/UNIT/eventscripts/statd-callout.001.sh13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/statd-callout.002.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/statd-callout.003.sh16
-rwxr-xr-xctdb/tests/UNIT/eventscripts/statd-callout.004.sh17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/statd-callout.005.sh25
-rwxr-xr-xctdb/tests/UNIT/eventscripts/statd-callout.006.sh27
-rwxr-xr-xctdb/tests/UNIT/eventscripts/statd-callout.007.sh14
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb481
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb-config2
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp10
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs53
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ctdb_natgw34
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/date7
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/df38
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ethtool12
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/exportfs13
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/gstack19
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/id3
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ip833
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ip6tables5
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/iptables5
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ipvsadm154
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/kill7
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/killall7
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/multipath36
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/net5
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/nfs-fake-callout15
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/nfsconf5
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/pidof17
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/pkill7
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ps48
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rm6
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rpc.lockd6
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rpc.mountd6
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rpc.rquotad6
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rpc.statd6
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/rpcinfo78
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/service65
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/sleep9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/smnotify65
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/ss206
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/stat71
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/tdb_mutex_check10
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/tdbdump9
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/tdbtool36
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/testparm84
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/timeout8
-rwxr-xr-xctdb/tests/UNIT/eventscripts/stubs/wbinfo7
-rwxr-xr-xctdb/tests/UNIT/onnode/0001.sh24
-rwxr-xr-xctdb/tests/UNIT/onnode/0002.sh16
-rwxr-xr-xctdb/tests/UNIT/onnode/0003.sh16
-rwxr-xr-xctdb/tests/UNIT/onnode/0004.sh16
-rwxr-xr-xctdb/tests/UNIT/onnode/0005.sh13
-rwxr-xr-xctdb/tests/UNIT/onnode/0006.sh15
-rwxr-xr-xctdb/tests/UNIT/onnode/0010.sh13
-rwxr-xr-xctdb/tests/UNIT/onnode/0011.sh13
-rwxr-xr-xctdb/tests/UNIT/onnode/0070.sh32
-rwxr-xr-xctdb/tests/UNIT/onnode/0071.sh29
-rwxr-xr-xctdb/tests/UNIT/onnode/0072.sh29
-rwxr-xr-xctdb/tests/UNIT/onnode/0075.sh29
-rw-r--r--ctdb/tests/UNIT/onnode/etc-ctdb/nodes4
-rw-r--r--ctdb/tests/UNIT/onnode/scripts/local.sh64
-rwxr-xr-xctdb/tests/UNIT/onnode/stubs/ctdb19
-rwxr-xr-xctdb/tests/UNIT/onnode/stubs/ssh2
-rwxr-xr-xctdb/tests/UNIT/shellcheck/base_scripts.sh12
-rwxr-xr-xctdb/tests/UNIT/shellcheck/ctdb_helpers.sh9
-rwxr-xr-xctdb/tests/UNIT/shellcheck/event_scripts.sh7
-rwxr-xr-xctdb/tests/UNIT/shellcheck/functions.sh7
-rwxr-xr-xctdb/tests/UNIT/shellcheck/init_script.sh19
-rw-r--r--ctdb/tests/UNIT/shellcheck/scripts/local.sh33
-rwxr-xr-xctdb/tests/UNIT/shellcheck/tests.sh36
-rwxr-xr-xctdb/tests/UNIT/shellcheck/tools.sh9
-rw-r--r--ctdb/tests/UNIT/takeover/README5
-rwxr-xr-xctdb/tests/UNIT/takeover/det.001.sh38
-rwxr-xr-xctdb/tests/UNIT/takeover/det.002.sh35
-rwxr-xr-xctdb/tests/UNIT/takeover/det.003.sh32
-rwxr-xr-xctdb/tests/UNIT/takeover/det.004.sh41
-rwxr-xr-xctdb/tests/UNIT/takeover/det.005.sh45
-rwxr-xr-xctdb/tests/UNIT/takeover/det.006.sh46
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.001.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.002.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.003.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.004.sh37
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.005.sh198
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.006.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.007.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.008.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.009.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.010.sh32
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.011.sh45
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.012.sh33
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.013.sh33
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.014.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.015.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.016.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.024.sh42
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.025.sh33
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.027.sh45
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.028.sh45
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.029.sh111
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.030.sh1813
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.031.sh143
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.032.sh450
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.033.sh74
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.034.sh21
-rwxr-xr-xctdb/tests/UNIT/takeover/lcp2.035.sh1813
-rwxr-xr-xctdb/tests/UNIT/takeover/nondet.001.sh35
-rwxr-xr-xctdb/tests/UNIT/takeover/nondet.002.sh32
-rwxr-xr-xctdb/tests/UNIT/takeover/nondet.003.sh29
-rw-r--r--ctdb/tests/UNIT/takeover/scripts/local.sh30
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/000.sh22
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/010.sh33
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/011.sh33
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/012.sh33
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/013.sh33
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/014.sh37
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/016.sh36
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/017.sh36
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/018.sh34
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/019.sh37
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/021.sh39
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/022.sh40
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/023.sh41
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/024.sh43
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/025.sh37
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/026.sh41
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/027.sh33
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/028.sh33
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/030.sh35
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/031.sh55
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/110.sh29
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/111.sh40
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/120.sh40
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/121.sh40
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/122.sh40
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/130.sh41
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/131.sh40
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/132.sh42
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/140.sh33
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/150.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/160.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/210.sh29
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/211.sh40
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/220.sh40
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/230.sh41
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/240.sh33
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/250.sh31
-rwxr-xr-xctdb/tests/UNIT/takeover_helper/260.sh31
-rw-r--r--ctdb/tests/UNIT/takeover_helper/scripts/local.sh108
-rw-r--r--ctdb/tests/UNIT/tool/README17
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.attach.001.sh35
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.attach.002.sh35
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.attach.003.sh35
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ban.001.sh35
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ban.002.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ban.003.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.catdb.001.sh80
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.catdb.002.sh86
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.cattdb.001.sh80
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.cattdb.002.sh86
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.continue.001.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.continue.002.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.continue.003.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.deletekey.001.sh34
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.disable.001.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.disable.002.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.disable.003.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.disable.004.sh15
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.enable.001.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.enable.002.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.enable.003.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getcapabilities.001.sh19
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getcapabilities.002.sh19
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getcapabilities.003.sh28
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getcapabilities.004.sh39
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getdbmap.001.sh34
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getdbseqnum.001.sh41
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getdbseqnum.002.sh36
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getdbstatus.001.sh108
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getdbstatus.002.sh108
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getpid.001.sh17
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getpid.010.sh25
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getreclock.001.sh16
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getreclock.002.sh21
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getvar.001.sh35
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.getvar.002.sh17
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ifaces.001.sh24
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ip.001.sh17
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ip.002.sh17
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ip.003.sh30
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ip.004.sh29
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ip.005.sh30
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ip.006.sh30
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ip.007.sh36
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ipinfo.001.sh18
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ipinfo.002.sh32
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ipinfo.003.sh35
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.leader.001.sh16
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.leader.002.sh16
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.listnodes.001.sh20
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.listnodes.002.sh19
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.listvars.001.sh66
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.lvs.001.sh36
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.lvs.002.sh46
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.lvs.003.sh43
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.lvs.004.sh45
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.lvs.005.sh46
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.lvs.006.sh44
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.lvs.007.sh42
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.lvs.008.sh66
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.lvs.010.sh25
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.natgw.001.sh46
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.natgw.002.sh46
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.natgw.003.sh43
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.natgw.004.sh46
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.natgw.005.sh46
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.natgw.006.sh46
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.natgw.007.sh45
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.natgw.008.sh46
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.natgw.010.sh25
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.nodestatus.001.sh33
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.nodestatus.002.sh33
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.nodestatus.003.sh33
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.nodestatus.004.sh28
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.nodestatus.005.sh28
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.nodestatus.006.sh40
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.nodestatus.007.sh36
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.pdelete.001.sh27
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ping.001.sh24
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.pnn.001.sh15
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.process-exists.001.sh28
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.process-exists.002.sh30
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.process-exists.003.sh30
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.pstore.001.sh24
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.ptrans.001.sh49
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.readkey.001.sh20
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.recover.001.sh22
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.001.sh24
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.002.sh30
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.003.sh29
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.011.sh25
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.012.sh24
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.013.sh26
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.014.sh24
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.015.sh26
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.016.sh24
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.017.sh26
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.018.sh29
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.019.sh28
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.020.sh28
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.021.sh26
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.023.sh24
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.reloadnodes.024.sh24
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.runstate.001.sh15
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.runstate.002.sh15
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.runstate.003.sh17
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.runstate.004.sh15
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.runstate.005.sh15
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdbreadonly.001.sh53
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdbreadonly.002.sh37
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdbreadonly.003.sh39
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdbreadonly.004.sh37
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdbreadonly.005.sh39
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdbsticky.001.sh53
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdbsticky.002.sh37
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdbsticky.003.sh39
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdbsticky.004.sh37
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdbsticky.005.sh39
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdebug.001.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdebug.002.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setdebug.003.sh32
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setifacelink.001.sh76
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setifacelink.002.sh22
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setvar.001.sh49
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.setvar.002.sh17
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.status.001.sh46
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.status.002.sh46
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.status.003.sh49
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.stop.001.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.stop.002.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.stop.003.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.unban.001.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.unban.002.sh34
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.unban.003.sh23
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.uptime.001.sh36
-rwxr-xr-xctdb/tests/UNIT/tool/ctdb.writekey.001.sh31
-rw-r--r--ctdb/tests/UNIT/tool/scripts/local.sh112
-rwxr-xr-xctdb/tests/etc-ctdb/events/legacy/00.test.script30
-rwxr-xr-xctdb/tests/local_daemons.sh506
l---------ctdb/tests/run_cluster_tests.sh1
-rwxr-xr-xctdb/tests/run_tests.sh399
-rwxr-xr-xctdb/tests/scripts/cluster.bash18
-rw-r--r--ctdb/tests/scripts/common.sh146
-rw-r--r--ctdb/tests/scripts/integration.bash864
-rw-r--r--ctdb/tests/scripts/integration_local_daemons.bash95
-rw-r--r--ctdb/tests/scripts/integration_real_cluster.bash53
-rw-r--r--ctdb/tests/scripts/script_install_paths.sh67
-rwxr-xr-xctdb/tests/scripts/test_wrap11
-rw-r--r--ctdb/tests/scripts/unit.sh267
-rw-r--r--ctdb/tests/src/cluster_mutex_test.c844
-rw-r--r--ctdb/tests/src/cluster_wait.c346
-rw-r--r--ctdb/tests/src/cluster_wait.h30
-rw-r--r--ctdb/tests/src/cmdline_test.c480
-rw-r--r--ctdb/tests/src/comm_client_test.c217
-rw-r--r--ctdb/tests/src/comm_server_test.c292
-rw-r--r--ctdb/tests/src/comm_test.c501
-rw-r--r--ctdb/tests/src/conf_test.c513
-rw-r--r--ctdb/tests/src/ctdb_io_test.c356
-rw-r--r--ctdb/tests/src/ctdb_packet_parse.c136
-rw-r--r--ctdb/tests/src/ctdb_takeover_tests.c281
-rw-r--r--ctdb/tests/src/db_hash_test.c138
-rw-r--r--ctdb/tests/src/db_test_tool.c792
-rw-r--r--ctdb/tests/src/dummy_client.c163
-rw-r--r--ctdb/tests/src/errcode.c189
-rw-r--r--ctdb/tests/src/event_script_test.c120
-rw-r--r--ctdb/tests/src/fake_ctdbd.c4781
-rw-r--r--ctdb/tests/src/fetch_loop.c288
-rw-r--r--ctdb/tests/src/fetch_loop_key.c217
-rw-r--r--ctdb/tests/src/fetch_readonly.c166
-rw-r--r--ctdb/tests/src/fetch_readonly_loop.c272
-rw-r--r--ctdb/tests/src/fetch_ring.c398
-rw-r--r--ctdb/tests/src/g_lock_loop.c270
-rw-r--r--ctdb/tests/src/hash_count_test.c132
-rw-r--r--ctdb/tests/src/ipalloc_read_known_ips.c179
-rw-r--r--ctdb/tests/src/ipalloc_read_known_ips.h32
-rw-r--r--ctdb/tests/src/line_test.c102
-rw-r--r--ctdb/tests/src/lock_tdb.c60
-rw-r--r--ctdb/tests/src/message_ring.c369
-rw-r--r--ctdb/tests/src/pidfile_test.c242
-rw-r--r--ctdb/tests/src/pkt_read_test.c249
-rw-r--r--ctdb/tests/src/pkt_write_test.c359
-rw-r--r--ctdb/tests/src/porting_tests.c262
-rw-r--r--ctdb/tests/src/protocol_basic_test.c106
-rw-r--r--ctdb/tests/src/protocol_common.c1260
-rw-r--r--ctdb/tests/src/protocol_common.h238
-rw-r--r--ctdb/tests/src/protocol_common_basic.c305
-rw-r--r--ctdb/tests/src/protocol_common_basic.h175
-rw-r--r--ctdb/tests/src/protocol_common_ctdb.c1967
-rw-r--r--ctdb/tests/src/protocol_common_ctdb.h101
-rw-r--r--ctdb/tests/src/protocol_ctdb_compat_test.c1270
-rw-r--r--ctdb/tests/src/protocol_ctdb_test.c365
-rw-r--r--ctdb/tests/src/protocol_types_compat_test.c2371
-rw-r--r--ctdb/tests/src/protocol_types_test.c194
-rw-r--r--ctdb/tests/src/protocol_util_test.c417
-rw-r--r--ctdb/tests/src/rb_test.c336
-rw-r--r--ctdb/tests/src/reqid_test.c89
-rw-r--r--ctdb/tests/src/run_event_test.c251
-rw-r--r--ctdb/tests/src/run_proc_test.c111
-rw-r--r--ctdb/tests/src/sigcode.c120
-rw-r--r--ctdb/tests/src/sock_daemon_test.c1980
-rw-r--r--ctdb/tests/src/sock_io_test.c283
-rw-r--r--ctdb/tests/src/srvid_test.c105
-rw-r--r--ctdb/tests/src/system_socket_test.c266
-rw-r--r--ctdb/tests/src/test_backtrace.c37
-rw-r--r--ctdb/tests/src/test_backtrace.h25
-rw-r--r--ctdb/tests/src/test_mutex_raw.c434
-rw-r--r--ctdb/tests/src/test_options.c245
-rw-r--r--ctdb/tests/src/test_options.h44
-rw-r--r--ctdb/tests/src/tmon_ping_test.c381
-rw-r--r--ctdb/tests/src/tmon_test.c406
-rw-r--r--ctdb/tests/src/transaction_loop.c419
-rw-r--r--ctdb/tests/src/tunable_test.c71
-rw-r--r--ctdb/tests/src/tunnel_cmd.c199
-rw-r--r--ctdb/tests/src/tunnel_test.c480
-rw-r--r--ctdb/tests/src/update_record.c236
-rw-r--r--ctdb/tests/src/update_record_persistent.c218
-rwxr-xr-xctdb/tests/test_check_tcp_ports.sh18
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!"