summaryrefslogtreecommitdiffstats
path: root/ctdb/tests/UNIT/eventscripts
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /ctdb/tests/UNIT/eventscripts
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ctdb/tests/UNIT/eventscripts')
-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
276 files changed, 8286 insertions, 0 deletions
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