summaryrefslogtreecommitdiffstats
path: root/ctdb/tests/UNIT
diff options
context:
space:
mode:
Diffstat (limited to 'ctdb/tests/UNIT')
-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
606 files changed, 24580 insertions, 0 deletions
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 "$@"
+}