From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- .../CLUSTER/complex/11_ctdb_delip_removes_ip.sh | 36 + ctdb/tests/CLUSTER/complex/18_ctdb_reloadips.sh | 257 ++ .../tests/CLUSTER/complex/30_nfs_tickle_killtcp.sh | 57 + ctdb/tests/CLUSTER/complex/31_nfs_tickle.sh | 77 + ctdb/tests/CLUSTER/complex/32_cifs_tickle.sh | 69 + ctdb/tests/CLUSTER/complex/33_gratuitous_arp.sh | 74 + .../tests/CLUSTER/complex/34_nfs_tickle_restart.sh | 81 + ctdb/tests/CLUSTER/complex/36_smb_reset_server.sh | 78 + ctdb/tests/CLUSTER/complex/37_nfs_reset_server.sh | 78 + .../CLUSTER/complex/41_failover_ping_discrete.sh | 56 + .../CLUSTER/complex/42_failover_ssh_hostname.sh | 70 + .../tests/CLUSTER/complex/43_failover_nfs_basic.sh | 62 + .../CLUSTER/complex/44_failover_nfs_oneway.sh | 82 + ctdb/tests/CLUSTER/complex/45_failover_nfs_kill.sh | 69 + ctdb/tests/CLUSTER/complex/60_rogueip_releaseip.sh | 56 + ctdb/tests/CLUSTER/complex/61_rogueip_takeip.sh | 46 + ctdb/tests/CLUSTER/complex/README | 2 + ctdb/tests/CLUSTER/complex/scripts/local.bash | 289 ++ .../INTEGRATION/database/basics.001.attach.sh | 48 + .../INTEGRATION/database/basics.002.attach.sh | 116 + .../INTEGRATION/database/basics.003.detach.sh | 166 + ctdb/tests/INTEGRATION/database/basics.004.wipe.sh | 56 + .../database/basics.010.backup_restore.sh | 97 + ctdb/tests/INTEGRATION/database/fetch.001.ring.sh | 34 + .../INTEGRATION/database/fetch.002.ring-hotkeys.sh | 161 + .../INTEGRATION/database/readonly.001.basic.sh | 178 + .../INTEGRATION/database/recovery.001.volatile.sh | 118 + .../INTEGRATION/database/recovery.002.large.sh | 106 + .../database/recovery.003.no_resurrect.sh | 63 + .../database/recovery.010.persistent.sh | 103 + .../INTEGRATION/database/recovery.011.continue.sh | 73 + ctdb/tests/INTEGRATION/database/scripts/local.bash | 116 + .../INTEGRATION/database/transaction.001.ptrans.sh | 110 + .../INTEGRATION/database/transaction.002.loop.sh | 28 + .../database/transaction.003.loop_recovery.sh | 50 + .../database/transaction.004.update_record.sh | 80 + .../database/transaction.010.loop_recovery.sh | 51 + .../tests/INTEGRATION/database/traverse.001.one.sh | 116 + .../INTEGRATION/database/traverse.002.many.sh | 52 + ctdb/tests/INTEGRATION/database/vacuum.001.fast.sh | 159 + ctdb/tests/INTEGRATION/database/vacuum.002.full.sh | 96 + .../INTEGRATION/database/vacuum.003.recreate.sh | 139 + .../INTEGRATION/database/vacuum.030.locked.sh | 102 + .../INTEGRATION/database/vacuum.031.locked.sh | 114 + .../INTEGRATION/database/vacuum.032.locked.sh | 102 + .../INTEGRATION/database/vacuum.033.locked.sh | 117 + .../INTEGRATION/database/vacuum.034.locked.sh | 129 + ctdb/tests/INTEGRATION/failover/pubips.001.list.sh | 48 + .../tests/INTEGRATION/failover/pubips.010.addip.sh | 25 + .../tests/INTEGRATION/failover/pubips.011.delip.sh | 16 + .../INTEGRATION/failover/pubips.012.reloadips.sh | 117 + .../failover/pubips.013.failover_noop.sh | 44 + .../INTEGRATION/failover/pubips.014.iface_gc.sh | 51 + .../INTEGRATION/failover/pubips.020.moveip.sh | 76 + .../failover/pubips.030.disable_enable.sh | 23 + .../failover/pubips.032.stop_continue.sh | 21 + .../failover/pubips.040.NoIPTakeover.sh | 71 + .../INTEGRATION/failover/pubips.050.missing_ip.sh | 71 + ctdb/tests/INTEGRATION/simple/README | 2 + ctdb/tests/INTEGRATION/simple/basics.000.onnode.sh | 12 + .../INTEGRATION/simple/basics.001.listnodes.sh | 38 + .../INTEGRATION/simple/basics.002.tunables.sh | 67 + ctdb/tests/INTEGRATION/simple/basics.003.ping.sh | 34 + ctdb/tests/INTEGRATION/simple/basics.004.getpid.sh | 55 + .../simple/basics.005.process_exists.sh | 66 + .../INTEGRATION/simple/basics.010.statistics.sh | 17 + .../simple/basics.011.statistics_reset.sh | 62 + .../simple/cluster.001.stop_leader_yield.sh | 26 + .../simple/cluster.002.ban_leader_yield.sh | 26 + .../simple/cluster.003.capability_leader_yield.sh | 24 + .../cluster.006.stop_leader_yield_no_lock.sh | 30 + .../simple/cluster.007.ban_leader_yield_no_lock.sh | 30 + .../cluster.008.capability_leader_yield_no_lock.sh | 29 + .../INTEGRATION/simple/cluster.010.getrelock.sh | 24 + .../simple/cluster.012.reclock_command.sh | 20 + .../simple/cluster.015.reclock_remove_lock.sh | 80 + .../simple/cluster.016.reclock_move_lock_dir.sh | 92 + .../INTEGRATION/simple/cluster.020.message_ring.sh | 53 + .../INTEGRATION/simple/cluster.021.tunnel_ring.sh | 34 + .../cluster.030.node_stall_leader_timeout.sh | 48 + .../INTEGRATION/simple/cluster.090.unreachable.sh | 39 + .../simple/cluster.091.version_check.sh | 55 + .../tests/INTEGRATION/simple/debug.001.getdebug.sh | 42 + .../tests/INTEGRATION/simple/debug.002.setdebug.sh | 74 + .../INTEGRATION/simple/debug.003.dumpmemory.sh | 18 + .../simple/eventscripts.001.zero_scripts.sh | 16 + .../simple/eventscripts.090.debug_hung.sh | 76 + ctdb/tests/README | 145 + ctdb/tests/TODO | 4 + ctdb/tests/UNIT/cunit/cluster_mutex_001.sh | 66 + ctdb/tests/UNIT/cunit/cluster_mutex_002.sh | 132 + ctdb/tests/UNIT/cunit/cluster_mutex_003.sh | 75 + ctdb/tests/UNIT/cunit/cmdline_test_001.sh | 98 + ctdb/tests/UNIT/cunit/comm_test_001.sh | 13 + ctdb/tests/UNIT/cunit/comm_test_002.sh | 24 + ctdb/tests/UNIT/cunit/conf_test_001.sh | 196 + ctdb/tests/UNIT/cunit/config_test_001.sh | 115 + ctdb/tests/UNIT/cunit/config_test_002.sh | 65 + ctdb/tests/UNIT/cunit/config_test_003.sh | 52 + ctdb/tests/UNIT/cunit/config_test_004.sh | 144 + ctdb/tests/UNIT/cunit/config_test_005.sh | 97 + ctdb/tests/UNIT/cunit/config_test_006.sh | 56 + ctdb/tests/UNIT/cunit/config_test_007.sh | 24 + ctdb/tests/UNIT/cunit/ctdb_io_test_001.sh | 10 + ctdb/tests/UNIT/cunit/db_hash_test_001.sh | 7 + ctdb/tests/UNIT/cunit/event_protocol_test_001.sh | 7 + ctdb/tests/UNIT/cunit/event_script_test_001.sh | 127 + ctdb/tests/UNIT/cunit/hash_count_test_001.sh | 7 + ctdb/tests/UNIT/cunit/line_test_001.sh | 90 + ctdb/tests/UNIT/cunit/path_tests_001.sh | 62 + ctdb/tests/UNIT/cunit/pidfile_test_001.sh | 8 + ctdb/tests/UNIT/cunit/pkt_read_001.sh | 7 + ctdb/tests/UNIT/cunit/pkt_write_001.sh | 7 + ctdb/tests/UNIT/cunit/porting_tests_001.sh | 15 + ctdb/tests/UNIT/cunit/protocol_test_001.sh | 7 + ctdb/tests/UNIT/cunit/protocol_test_002.sh | 7 + ctdb/tests/UNIT/cunit/protocol_test_012.sh | 7 + ctdb/tests/UNIT/cunit/protocol_test_101.sh | 7 + ctdb/tests/UNIT/cunit/protocol_test_111.sh | 7 + ctdb/tests/UNIT/cunit/protocol_test_201.sh | 6 + ctdb/tests/UNIT/cunit/rb_test_001.sh | 31 + ctdb/tests/UNIT/cunit/reqid_test_001.sh | 13 + ctdb/tests/UNIT/cunit/run_event_001.sh | 137 + ctdb/tests/UNIT/cunit/run_proc_001.sh | 159 + ctdb/tests/UNIT/cunit/sock_daemon_test_001.sh | 135 + ctdb/tests/UNIT/cunit/sock_io_test_001.sh | 9 + ctdb/tests/UNIT/cunit/srvid_test_001.sh | 7 + ctdb/tests/UNIT/cunit/system_socket_test_001.sh | 6 + ctdb/tests/UNIT/cunit/system_socket_test_002.sh | 68 + ctdb/tests/UNIT/cunit/system_socket_test_003.sh | 42 + ctdb/tests/UNIT/cunit/tmon_test_001.sh | 195 + ctdb/tests/UNIT/cunit/tmon_test_002.sh | 142 + ctdb/tests/UNIT/cunit/tunable_test_001.sh | 312 ++ ctdb/tests/UNIT/eventd/README | 1 + ctdb/tests/UNIT/eventd/etc-ctdb/ctdb.conf | 6 + ctdb/tests/UNIT/eventd/etc-ctdb/debug-script.sh | 22 + .../eventd/etc-ctdb/events/data/03.notalink.script | 2 + ctdb/tests/UNIT/eventd/etc-ctdb/events/data/README | 1 + .../tests/UNIT/eventd/etc-ctdb/events/empty/README | 1 + .../eventd/etc-ctdb/events/multi/01.test.script | 11 + .../eventd/etc-ctdb/events/multi/02.test.script | 9 + .../eventd/etc-ctdb/events/multi/03.test.script | 9 + .../etc-ctdb/events/random/01.disabled.script | 3 + .../etc-ctdb/events/random/02.enabled.script | 49 + .../eventd/etc-ctdb/events/random/README.script | 1 + .../UNIT/eventd/etc-ctdb/events/random/a.script | 3 + .../etc-ctdb/share/events/data/01.dummy.script | 6 + .../etc-ctdb/share/events/data/02.disabled.script | 6 + .../UNIT/eventd/etc-ctdb/share/events/empty/README | 1 + .../share/events/random/01.disabled.script | 3 + .../etc-ctdb/share/events/random/02.enabled.script | 12 + .../etc-ctdb/share/events/random/README.script | 1 + .../eventd/etc-ctdb/share/events/random/a.script | 3 + ctdb/tests/UNIT/eventd/eventd_001.sh | 27 + ctdb/tests/UNIT/eventd/eventd_002.sh | 21 + ctdb/tests/UNIT/eventd/eventd_003.sh | 45 + ctdb/tests/UNIT/eventd/eventd_004.sh | 33 + ctdb/tests/UNIT/eventd/eventd_005.sh | 34 + ctdb/tests/UNIT/eventd/eventd_006.sh | 19 + ctdb/tests/UNIT/eventd/eventd_007.sh | 19 + ctdb/tests/UNIT/eventd/eventd_008.sh | 83 + ctdb/tests/UNIT/eventd/eventd_009.sh | 155 + ctdb/tests/UNIT/eventd/eventd_011.sh | 40 + ctdb/tests/UNIT/eventd/eventd_012.sh | 27 + ctdb/tests/UNIT/eventd/eventd_013.sh | 27 + ctdb/tests/UNIT/eventd/eventd_014.sh | 27 + ctdb/tests/UNIT/eventd/eventd_021.sh | 26 + ctdb/tests/UNIT/eventd/eventd_022.sh | 22 + ctdb/tests/UNIT/eventd/eventd_023.sh | 22 + ctdb/tests/UNIT/eventd/eventd_024.sh | 31 + ctdb/tests/UNIT/eventd/eventd_031.sh | 17 + ctdb/tests/UNIT/eventd/eventd_032.sh | 43 + ctdb/tests/UNIT/eventd/eventd_033.sh | 43 + ctdb/tests/UNIT/eventd/eventd_041.sh | 26 + ctdb/tests/UNIT/eventd/eventd_042.sh | 29 + ctdb/tests/UNIT/eventd/eventd_043.sh | 29 + ctdb/tests/UNIT/eventd/eventd_044.sh | 37 + ctdb/tests/UNIT/eventd/eventd_051.sh | 15 + ctdb/tests/UNIT/eventd/eventd_052.sh | 35 + ctdb/tests/UNIT/eventd/scripts/local.sh | 122 + ctdb/tests/UNIT/eventscripts/00.ctdb.init.001.sh | 13 + ctdb/tests/UNIT/eventscripts/00.ctdb.init.002.sh | 17 + ctdb/tests/UNIT/eventscripts/00.ctdb.init.003.sh | 16 + ctdb/tests/UNIT/eventscripts/00.ctdb.init.004.sh | 18 + ctdb/tests/UNIT/eventscripts/00.ctdb.init.005.sh | 20 + ctdb/tests/UNIT/eventscripts/00.ctdb.init.006.sh | 23 + ctdb/tests/UNIT/eventscripts/00.ctdb.init.007.sh | 16 + ctdb/tests/UNIT/eventscripts/00.ctdb.init.008.sh | 19 + ctdb/tests/UNIT/eventscripts/00.ctdb.init.009.sh | 51 + .../tests/UNIT/eventscripts/01.reclock.init.001.sh | 10 + .../tests/UNIT/eventscripts/01.reclock.init.002.sh | 10 + .../tests/UNIT/eventscripts/01.reclock.init.003.sh | 20 + .../UNIT/eventscripts/05.system.monitor.001.sh | 13 + .../UNIT/eventscripts/05.system.monitor.002.sh | 14 + .../UNIT/eventscripts/05.system.monitor.003.sh | 17 + .../UNIT/eventscripts/05.system.monitor.004.sh | 15 + .../UNIT/eventscripts/05.system.monitor.005.sh | 17 + .../UNIT/eventscripts/05.system.monitor.006.sh | 17 + .../UNIT/eventscripts/05.system.monitor.007.sh | 14 + .../UNIT/eventscripts/05.system.monitor.011.sh | 13 + .../UNIT/eventscripts/05.system.monitor.012.sh | 14 + .../UNIT/eventscripts/05.system.monitor.014.sh | 18 + .../UNIT/eventscripts/05.system.monitor.015.sh | 20 + .../UNIT/eventscripts/05.system.monitor.017.sh | 20 + .../UNIT/eventscripts/05.system.monitor.018.sh | 82 + .../UNIT/eventscripts/06.nfs.releaseip.001.sh | 12 + .../UNIT/eventscripts/06.nfs.releaseip.002.sh | 12 + ctdb/tests/UNIT/eventscripts/06.nfs.takeip.001.sh | 12 + ctdb/tests/UNIT/eventscripts/06.nfs.takeip.002.sh | 12 + ctdb/tests/UNIT/eventscripts/10.interface.010.sh | 23 + ctdb/tests/UNIT/eventscripts/10.interface.011.sh | 28 + ctdb/tests/UNIT/eventscripts/10.interface.012.sh | 31 + ctdb/tests/UNIT/eventscripts/10.interface.013.sh | 36 + .../UNIT/eventscripts/10.interface.init.001.sh | 13 + .../UNIT/eventscripts/10.interface.init.002.sh | 11 + .../UNIT/eventscripts/10.interface.init.021.sh | 11 + .../UNIT/eventscripts/10.interface.init.022.sh | 18 + .../UNIT/eventscripts/10.interface.init.023.sh | 23 + .../UNIT/eventscripts/10.interface.monitor.001.sh | 13 + .../UNIT/eventscripts/10.interface.monitor.002.sh | 11 + .../UNIT/eventscripts/10.interface.monitor.003.sh | 15 + .../UNIT/eventscripts/10.interface.monitor.004.sh | 15 + .../UNIT/eventscripts/10.interface.monitor.005.sh | 15 + .../UNIT/eventscripts/10.interface.monitor.006.sh | 15 + .../UNIT/eventscripts/10.interface.monitor.009.sh | 19 + .../UNIT/eventscripts/10.interface.monitor.010.sh | 25 + .../UNIT/eventscripts/10.interface.monitor.011.sh | 21 + .../UNIT/eventscripts/10.interface.monitor.012.sh | 29 + .../UNIT/eventscripts/10.interface.monitor.013.sh | 15 + .../UNIT/eventscripts/10.interface.monitor.014.sh | 16 + .../UNIT/eventscripts/10.interface.monitor.015.sh | 16 + .../UNIT/eventscripts/10.interface.monitor.016.sh | 20 + .../UNIT/eventscripts/10.interface.monitor.017.sh | 20 + .../UNIT/eventscripts/10.interface.monitor.018.sh | 20 + .../UNIT/eventscripts/10.interface.multi.001.sh | 14 + .../eventscripts/10.interface.releaseip.001.sh | 13 + .../eventscripts/10.interface.releaseip.002.sh | 14 + .../UNIT/eventscripts/10.interface.startup.001.sh | 13 + .../UNIT/eventscripts/10.interface.startup.002.sh | 11 + .../UNIT/eventscripts/10.interface.takeip.001.sh | 13 + .../UNIT/eventscripts/10.interface.takeip.002.sh | 13 + .../UNIT/eventscripts/10.interface.takeip.003.sh | 22 + ctdb/tests/UNIT/eventscripts/11.natgw.001.sh | 12 + ctdb/tests/UNIT/eventscripts/11.natgw.002.sh | 24 + ctdb/tests/UNIT/eventscripts/11.natgw.003.sh | 24 + ctdb/tests/UNIT/eventscripts/11.natgw.004.sh | 24 + ctdb/tests/UNIT/eventscripts/11.natgw.011.sh | 23 + ctdb/tests/UNIT/eventscripts/11.natgw.012.sh | 23 + ctdb/tests/UNIT/eventscripts/11.natgw.013.sh | 27 + ctdb/tests/UNIT/eventscripts/11.natgw.014.sh | 27 + ctdb/tests/UNIT/eventscripts/11.natgw.015.sh | 61 + ctdb/tests/UNIT/eventscripts/11.natgw.021.sh | 27 + ctdb/tests/UNIT/eventscripts/11.natgw.022.sh | 27 + ctdb/tests/UNIT/eventscripts/11.natgw.023.sh | 27 + ctdb/tests/UNIT/eventscripts/11.natgw.024.sh | 27 + ctdb/tests/UNIT/eventscripts/11.natgw.025.sh | 65 + ctdb/tests/UNIT/eventscripts/11.natgw.031.sh | 62 + ctdb/tests/UNIT/eventscripts/11.natgw.041.sh | 24 + ctdb/tests/UNIT/eventscripts/11.natgw.042.sh | 25 + ctdb/tests/UNIT/eventscripts/11.natgw.051.sh | 17 + ctdb/tests/UNIT/eventscripts/11.natgw.052.sh | 21 + ctdb/tests/UNIT/eventscripts/11.natgw.053.sh | 17 + ctdb/tests/UNIT/eventscripts/11.natgw.054.sh | 21 + .../UNIT/eventscripts/13.per_ip_routing.001.sh | 19 + .../UNIT/eventscripts/13.per_ip_routing.002.sh | 18 + .../UNIT/eventscripts/13.per_ip_routing.003.sh | 16 + .../UNIT/eventscripts/13.per_ip_routing.004.sh | 17 + .../UNIT/eventscripts/13.per_ip_routing.005.sh | 20 + .../UNIT/eventscripts/13.per_ip_routing.006.sh | 24 + .../UNIT/eventscripts/13.per_ip_routing.007.sh | 17 + .../UNIT/eventscripts/13.per_ip_routing.008.sh | 23 + .../UNIT/eventscripts/13.per_ip_routing.009.sh | 20 + .../UNIT/eventscripts/13.per_ip_routing.010.sh | 19 + .../UNIT/eventscripts/13.per_ip_routing.011.sh | 21 + .../UNIT/eventscripts/13.per_ip_routing.012.sh | 29 + .../UNIT/eventscripts/13.per_ip_routing.013.sh | 23 + .../UNIT/eventscripts/13.per_ip_routing.014.sh | 29 + .../UNIT/eventscripts/13.per_ip_routing.015.sh | 29 + .../UNIT/eventscripts/13.per_ip_routing.016.sh | 14 + .../UNIT/eventscripts/13.per_ip_routing.017.sh | 15 + .../UNIT/eventscripts/13.per_ip_routing.018.sh | 21 + .../UNIT/eventscripts/13.per_ip_routing.019.sh | 23 + .../UNIT/eventscripts/13.per_ip_routing.021.sh | 15 + .../UNIT/eventscripts/13.per_ip_routing.022.sh | 15 + .../UNIT/eventscripts/13.per_ip_routing.023.sh | 25 + .../UNIT/eventscripts/13.per_ip_routing.024.sh | 30 + .../UNIT/eventscripts/20.multipathd.monitor.001.sh | 11 + .../UNIT/eventscripts/20.multipathd.monitor.002.sh | 11 + .../UNIT/eventscripts/20.multipathd.monitor.003.sh | 14 + .../UNIT/eventscripts/20.multipathd.monitor.004.sh | 15 + .../UNIT/eventscripts/31.clamd.monitor.002.sh | 16 + .../UNIT/eventscripts/31.clamd.monitor.003.sh | 16 + .../UNIT/eventscripts/40.vsftpd.monitor.002.sh | 52 + .../UNIT/eventscripts/40.vsftpd.shutdown.002.sh | 12 + .../UNIT/eventscripts/40.vsftpd.startup.002.sh | 12 + .../UNIT/eventscripts/41.httpd.monitor.002.sh | 30 + .../UNIT/eventscripts/41.httpd.shutdown.002.sh | 12 + .../UNIT/eventscripts/41.httpd.startup.002.sh | 12 + .../UNIT/eventscripts/48.netbios.shutdown.011.sh | 14 + .../UNIT/eventscripts/48.netbios.startup.011.sh | 14 + .../UNIT/eventscripts/49.winbind.monitor.101.sh | 11 + .../UNIT/eventscripts/49.winbind.monitor.102.sh | 13 + .../UNIT/eventscripts/49.winbind.shutdown.002.sh | 12 + .../UNIT/eventscripts/49.winbind.startup.002.sh | 12 + .../UNIT/eventscripts/50.samba.monitor.101.sh | 11 + .../UNIT/eventscripts/50.samba.monitor.103.sh | 13 + .../UNIT/eventscripts/50.samba.monitor.104.sh | 13 + .../UNIT/eventscripts/50.samba.monitor.105.sh | 12 + .../UNIT/eventscripts/50.samba.monitor.106.sh | 15 + .../UNIT/eventscripts/50.samba.monitor.110.sh | 20 + .../UNIT/eventscripts/50.samba.monitor.111.sh | 23 + .../UNIT/eventscripts/50.samba.monitor.112.sh | 13 + .../UNIT/eventscripts/50.samba.monitor.113.sh | 16 + .../UNIT/eventscripts/50.samba.shutdown.001.sh | 12 + .../UNIT/eventscripts/50.samba.shutdown.002.sh | 15 + .../UNIT/eventscripts/50.samba.shutdown.011.sh | 14 + .../UNIT/eventscripts/50.samba.startup.011.sh | 14 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.101.sh | 11 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.102.sh | 14 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.103.sh | 14 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.104.sh | 17 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.105.sh | 10 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.106.sh | 11 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.107.sh | 14 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.108.sh | 12 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.109.sh | 12 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.111.sh | 13 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.112.sh | 13 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.113.sh | 14 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.114.sh | 14 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.121.sh | 15 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.122.sh | 18 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.131.sh | 11 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.132.sh | 15 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.141.sh | 13 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.142.sh | 14 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.143.sh | 14 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.144.sh | 13 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.151.sh | 13 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.152.sh | 15 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.153.sh | 15 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.161.sh | 12 + ctdb/tests/UNIT/eventscripts/60.nfs.monitor.162.sh | 15 + ctdb/tests/UNIT/eventscripts/60.nfs.multi.001.sh | 19 + ctdb/tests/UNIT/eventscripts/60.nfs.multi.002.sh | 17 + .../UNIT/eventscripts/60.nfs.releaseip.001.sh | 14 + .../UNIT/eventscripts/60.nfs.releaseip.002.sh | 12 + .../tests/UNIT/eventscripts/60.nfs.shutdown.001.sh | 14 + .../tests/UNIT/eventscripts/60.nfs.shutdown.002.sh | 12 + ctdb/tests/UNIT/eventscripts/60.nfs.startup.001.sh | 14 + ctdb/tests/UNIT/eventscripts/60.nfs.startup.002.sh | 12 + ctdb/tests/UNIT/eventscripts/60.nfs.takeip.001.sh | 14 + ctdb/tests/UNIT/eventscripts/60.nfs.takeip.002.sh | 12 + ctdb/tests/UNIT/eventscripts/91.lvs.001.sh | 54 + .../UNIT/eventscripts/91.lvs.ipreallocated.011.sh | 14 + .../UNIT/eventscripts/91.lvs.ipreallocated.012.sh | 17 + .../UNIT/eventscripts/91.lvs.ipreallocated.013.sh | 17 + .../UNIT/eventscripts/91.lvs.ipreallocated.014.sh | 27 + ctdb/tests/UNIT/eventscripts/91.lvs.monitor.001.sh | 11 + ctdb/tests/UNIT/eventscripts/91.lvs.monitor.002.sh | 14 + ctdb/tests/UNIT/eventscripts/91.lvs.monitor.003.sh | 19 + .../tests/UNIT/eventscripts/91.lvs.shutdown.001.sh | 11 + .../tests/UNIT/eventscripts/91.lvs.shutdown.002.sh | 18 + ctdb/tests/UNIT/eventscripts/91.lvs.startup.001.sh | 11 + ctdb/tests/UNIT/eventscripts/91.lvs.startup.002.sh | 14 + ctdb/tests/UNIT/eventscripts/README | 46 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.001.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.002.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.003.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.004.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.005.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.006.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.007.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.008.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.021.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.022.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.023.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.024.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.025.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.026.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.027.sh | 9 + ctdb/tests/UNIT/eventscripts/debug_locks.sh.028.sh | 9 + .../UNIT/eventscripts/etc-ctdb/public_addresses | 9 + ctdb/tests/UNIT/eventscripts/etc-ctdb/rc.local | 56 + ctdb/tests/UNIT/eventscripts/etc/init.d/nfs | 7 + ctdb/tests/UNIT/eventscripts/etc/init.d/nfslock | 7 + ctdb/tests/UNIT/eventscripts/etc/os-release | 2 + ctdb/tests/UNIT/eventscripts/etc/samba/smb.conf | 43 + ctdb/tests/UNIT/eventscripts/etc/sysconfig/nfs | 2 + ctdb/tests/UNIT/eventscripts/scripts/00.ctdb.sh | 24 + ctdb/tests/UNIT/eventscripts/scripts/01.reclock.sh | 16 + ctdb/tests/UNIT/eventscripts/scripts/05.system.sh | 48 + ctdb/tests/UNIT/eventscripts/scripts/06.nfs.sh | 4 + .../UNIT/eventscripts/scripts/10.interface.sh | 72 + ctdb/tests/UNIT/eventscripts/scripts/11.natgw.sh | 120 + .../UNIT/eventscripts/scripts/13.per_ip_routing.sh | 47 + .../UNIT/eventscripts/scripts/20.multipathd.sh | 25 + ctdb/tests/UNIT/eventscripts/scripts/31.clamd.sh | 8 + ctdb/tests/UNIT/eventscripts/scripts/40.vsftpd.sh | 14 + ctdb/tests/UNIT/eventscripts/scripts/41.httpd.sh | 14 + ctdb/tests/UNIT/eventscripts/scripts/48.netbios.sh | 23 + ctdb/tests/UNIT/eventscripts/scripts/49.winbind.sh | 28 + ctdb/tests/UNIT/eventscripts/scripts/50.samba.sh | 58 + ctdb/tests/UNIT/eventscripts/scripts/60.nfs.sh | 435 ++ ctdb/tests/UNIT/eventscripts/scripts/91.lvs.sh | 76 + .../tests/UNIT/eventscripts/scripts/debug_locks.sh | 272 ++ ctdb/tests/UNIT/eventscripts/scripts/local.sh | 568 +++ .../UNIT/eventscripts/scripts/statd-callout.sh | 65 + ctdb/tests/UNIT/eventscripts/statd-callout.001.sh | 13 + ctdb/tests/UNIT/eventscripts/statd-callout.002.sh | 14 + ctdb/tests/UNIT/eventscripts/statd-callout.003.sh | 16 + ctdb/tests/UNIT/eventscripts/statd-callout.004.sh | 17 + ctdb/tests/UNIT/eventscripts/statd-callout.005.sh | 25 + ctdb/tests/UNIT/eventscripts/statd-callout.006.sh | 27 + ctdb/tests/UNIT/eventscripts/statd-callout.007.sh | 14 + ctdb/tests/UNIT/eventscripts/stubs/ctdb | 481 ++ ctdb/tests/UNIT/eventscripts/stubs/ctdb-config | 2 + ctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp | 10 + ctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs | 53 + ctdb/tests/UNIT/eventscripts/stubs/ctdb_natgw | 34 + ctdb/tests/UNIT/eventscripts/stubs/date | 7 + ctdb/tests/UNIT/eventscripts/stubs/df | 38 + ctdb/tests/UNIT/eventscripts/stubs/ethtool | 12 + ctdb/tests/UNIT/eventscripts/stubs/exportfs | 13 + ctdb/tests/UNIT/eventscripts/stubs/gstack | 19 + ctdb/tests/UNIT/eventscripts/stubs/id | 3 + ctdb/tests/UNIT/eventscripts/stubs/ip | 833 ++++ ctdb/tests/UNIT/eventscripts/stubs/ip6tables | 5 + ctdb/tests/UNIT/eventscripts/stubs/iptables | 5 + ctdb/tests/UNIT/eventscripts/stubs/ipvsadm | 154 + ctdb/tests/UNIT/eventscripts/stubs/kill | 7 + ctdb/tests/UNIT/eventscripts/stubs/killall | 7 + ctdb/tests/UNIT/eventscripts/stubs/multipath | 36 + ctdb/tests/UNIT/eventscripts/stubs/net | 5 + .../tests/UNIT/eventscripts/stubs/nfs-fake-callout | 15 + ctdb/tests/UNIT/eventscripts/stubs/nfsconf | 5 + ctdb/tests/UNIT/eventscripts/stubs/pidof | 17 + ctdb/tests/UNIT/eventscripts/stubs/pkill | 7 + ctdb/tests/UNIT/eventscripts/stubs/ps | 48 + ctdb/tests/UNIT/eventscripts/stubs/rm | 6 + ctdb/tests/UNIT/eventscripts/stubs/rpc.lockd | 6 + ctdb/tests/UNIT/eventscripts/stubs/rpc.mountd | 6 + ctdb/tests/UNIT/eventscripts/stubs/rpc.rquotad | 6 + ctdb/tests/UNIT/eventscripts/stubs/rpc.statd | 6 + ctdb/tests/UNIT/eventscripts/stubs/rpcinfo | 78 + ctdb/tests/UNIT/eventscripts/stubs/service | 65 + ctdb/tests/UNIT/eventscripts/stubs/sleep | 9 + ctdb/tests/UNIT/eventscripts/stubs/smnotify | 65 + ctdb/tests/UNIT/eventscripts/stubs/ss | 206 + ctdb/tests/UNIT/eventscripts/stubs/stat | 71 + ctdb/tests/UNIT/eventscripts/stubs/tdb_mutex_check | 10 + ctdb/tests/UNIT/eventscripts/stubs/tdbdump | 9 + ctdb/tests/UNIT/eventscripts/stubs/tdbtool | 36 + ctdb/tests/UNIT/eventscripts/stubs/testparm | 84 + ctdb/tests/UNIT/eventscripts/stubs/timeout | 8 + ctdb/tests/UNIT/eventscripts/stubs/wbinfo | 7 + ctdb/tests/UNIT/onnode/0001.sh | 24 + ctdb/tests/UNIT/onnode/0002.sh | 16 + ctdb/tests/UNIT/onnode/0003.sh | 16 + ctdb/tests/UNIT/onnode/0004.sh | 16 + ctdb/tests/UNIT/onnode/0005.sh | 13 + ctdb/tests/UNIT/onnode/0006.sh | 15 + ctdb/tests/UNIT/onnode/0010.sh | 13 + ctdb/tests/UNIT/onnode/0011.sh | 13 + ctdb/tests/UNIT/onnode/0070.sh | 32 + ctdb/tests/UNIT/onnode/0071.sh | 29 + ctdb/tests/UNIT/onnode/0072.sh | 29 + ctdb/tests/UNIT/onnode/0075.sh | 29 + ctdb/tests/UNIT/onnode/etc-ctdb/nodes | 4 + ctdb/tests/UNIT/onnode/scripts/local.sh | 64 + ctdb/tests/UNIT/onnode/stubs/ctdb | 19 + ctdb/tests/UNIT/onnode/stubs/ssh | 2 + ctdb/tests/UNIT/shellcheck/base_scripts.sh | 12 + ctdb/tests/UNIT/shellcheck/ctdb_helpers.sh | 9 + ctdb/tests/UNIT/shellcheck/event_scripts.sh | 7 + ctdb/tests/UNIT/shellcheck/functions.sh | 7 + ctdb/tests/UNIT/shellcheck/init_script.sh | 19 + ctdb/tests/UNIT/shellcheck/scripts/local.sh | 33 + ctdb/tests/UNIT/shellcheck/tests.sh | 36 + ctdb/tests/UNIT/shellcheck/tools.sh | 9 + ctdb/tests/UNIT/takeover/README | 5 + ctdb/tests/UNIT/takeover/det.001.sh | 38 + ctdb/tests/UNIT/takeover/det.002.sh | 35 + ctdb/tests/UNIT/takeover/det.003.sh | 32 + ctdb/tests/UNIT/takeover/det.004.sh | 41 + ctdb/tests/UNIT/takeover/det.005.sh | 45 + ctdb/tests/UNIT/takeover/det.006.sh | 46 + ctdb/tests/UNIT/takeover/lcp2.001.sh | 31 + ctdb/tests/UNIT/takeover/lcp2.002.sh | 31 + ctdb/tests/UNIT/takeover/lcp2.003.sh | 31 + ctdb/tests/UNIT/takeover/lcp2.004.sh | 37 + ctdb/tests/UNIT/takeover/lcp2.005.sh | 198 + ctdb/tests/UNIT/takeover/lcp2.006.sh | 31 + ctdb/tests/UNIT/takeover/lcp2.007.sh | 31 + ctdb/tests/UNIT/takeover/lcp2.008.sh | 31 + ctdb/tests/UNIT/takeover/lcp2.009.sh | 31 + ctdb/tests/UNIT/takeover/lcp2.010.sh | 32 + ctdb/tests/UNIT/takeover/lcp2.011.sh | 45 + ctdb/tests/UNIT/takeover/lcp2.012.sh | 33 + ctdb/tests/UNIT/takeover/lcp2.013.sh | 33 + ctdb/tests/UNIT/takeover/lcp2.014.sh | 31 + ctdb/tests/UNIT/takeover/lcp2.015.sh | 31 + ctdb/tests/UNIT/takeover/lcp2.016.sh | 31 + ctdb/tests/UNIT/takeover/lcp2.024.sh | 42 + ctdb/tests/UNIT/takeover/lcp2.025.sh | 33 + ctdb/tests/UNIT/takeover/lcp2.027.sh | 45 + ctdb/tests/UNIT/takeover/lcp2.028.sh | 45 + ctdb/tests/UNIT/takeover/lcp2.029.sh | 111 + ctdb/tests/UNIT/takeover/lcp2.030.sh | 1813 ++++++++ ctdb/tests/UNIT/takeover/lcp2.031.sh | 143 + ctdb/tests/UNIT/takeover/lcp2.032.sh | 450 ++ ctdb/tests/UNIT/takeover/lcp2.033.sh | 74 + ctdb/tests/UNIT/takeover/lcp2.034.sh | 21 + ctdb/tests/UNIT/takeover/lcp2.035.sh | 1813 ++++++++ ctdb/tests/UNIT/takeover/nondet.001.sh | 35 + ctdb/tests/UNIT/takeover/nondet.002.sh | 32 + ctdb/tests/UNIT/takeover/nondet.003.sh | 29 + ctdb/tests/UNIT/takeover/scripts/local.sh | 30 + ctdb/tests/UNIT/takeover_helper/000.sh | 22 + ctdb/tests/UNIT/takeover_helper/010.sh | 33 + ctdb/tests/UNIT/takeover_helper/011.sh | 33 + ctdb/tests/UNIT/takeover_helper/012.sh | 33 + ctdb/tests/UNIT/takeover_helper/013.sh | 33 + ctdb/tests/UNIT/takeover_helper/014.sh | 37 + ctdb/tests/UNIT/takeover_helper/016.sh | 36 + ctdb/tests/UNIT/takeover_helper/017.sh | 36 + ctdb/tests/UNIT/takeover_helper/018.sh | 34 + ctdb/tests/UNIT/takeover_helper/019.sh | 37 + ctdb/tests/UNIT/takeover_helper/021.sh | 39 + ctdb/tests/UNIT/takeover_helper/022.sh | 40 + ctdb/tests/UNIT/takeover_helper/023.sh | 41 + ctdb/tests/UNIT/takeover_helper/024.sh | 43 + ctdb/tests/UNIT/takeover_helper/025.sh | 37 + ctdb/tests/UNIT/takeover_helper/026.sh | 41 + ctdb/tests/UNIT/takeover_helper/027.sh | 33 + ctdb/tests/UNIT/takeover_helper/028.sh | 33 + ctdb/tests/UNIT/takeover_helper/030.sh | 35 + ctdb/tests/UNIT/takeover_helper/031.sh | 55 + ctdb/tests/UNIT/takeover_helper/110.sh | 29 + ctdb/tests/UNIT/takeover_helper/111.sh | 40 + ctdb/tests/UNIT/takeover_helper/120.sh | 40 + ctdb/tests/UNIT/takeover_helper/121.sh | 40 + ctdb/tests/UNIT/takeover_helper/122.sh | 40 + ctdb/tests/UNIT/takeover_helper/130.sh | 41 + ctdb/tests/UNIT/takeover_helper/131.sh | 40 + ctdb/tests/UNIT/takeover_helper/132.sh | 42 + ctdb/tests/UNIT/takeover_helper/140.sh | 33 + ctdb/tests/UNIT/takeover_helper/150.sh | 31 + ctdb/tests/UNIT/takeover_helper/160.sh | 31 + ctdb/tests/UNIT/takeover_helper/210.sh | 29 + ctdb/tests/UNIT/takeover_helper/211.sh | 40 + ctdb/tests/UNIT/takeover_helper/220.sh | 40 + ctdb/tests/UNIT/takeover_helper/230.sh | 41 + ctdb/tests/UNIT/takeover_helper/240.sh | 33 + ctdb/tests/UNIT/takeover_helper/250.sh | 31 + ctdb/tests/UNIT/takeover_helper/260.sh | 31 + ctdb/tests/UNIT/takeover_helper/scripts/local.sh | 108 + ctdb/tests/UNIT/tool/README | 17 + ctdb/tests/UNIT/tool/ctdb.attach.001.sh | 35 + ctdb/tests/UNIT/tool/ctdb.attach.002.sh | 35 + ctdb/tests/UNIT/tool/ctdb.attach.003.sh | 35 + ctdb/tests/UNIT/tool/ctdb.ban.001.sh | 35 + ctdb/tests/UNIT/tool/ctdb.ban.002.sh | 23 + ctdb/tests/UNIT/tool/ctdb.ban.003.sh | 23 + ctdb/tests/UNIT/tool/ctdb.catdb.001.sh | 80 + ctdb/tests/UNIT/tool/ctdb.catdb.002.sh | 86 + ctdb/tests/UNIT/tool/ctdb.cattdb.001.sh | 80 + ctdb/tests/UNIT/tool/ctdb.cattdb.002.sh | 86 + ctdb/tests/UNIT/tool/ctdb.continue.001.sh | 23 + ctdb/tests/UNIT/tool/ctdb.continue.002.sh | 23 + ctdb/tests/UNIT/tool/ctdb.continue.003.sh | 23 + ctdb/tests/UNIT/tool/ctdb.deletekey.001.sh | 34 + ctdb/tests/UNIT/tool/ctdb.disable.001.sh | 23 + ctdb/tests/UNIT/tool/ctdb.disable.002.sh | 23 + ctdb/tests/UNIT/tool/ctdb.disable.003.sh | 23 + ctdb/tests/UNIT/tool/ctdb.disable.004.sh | 15 + ctdb/tests/UNIT/tool/ctdb.enable.001.sh | 23 + ctdb/tests/UNIT/tool/ctdb.enable.002.sh | 23 + ctdb/tests/UNIT/tool/ctdb.enable.003.sh | 23 + ctdb/tests/UNIT/tool/ctdb.getcapabilities.001.sh | 19 + ctdb/tests/UNIT/tool/ctdb.getcapabilities.002.sh | 19 + ctdb/tests/UNIT/tool/ctdb.getcapabilities.003.sh | 28 + ctdb/tests/UNIT/tool/ctdb.getcapabilities.004.sh | 39 + ctdb/tests/UNIT/tool/ctdb.getdbmap.001.sh | 34 + ctdb/tests/UNIT/tool/ctdb.getdbseqnum.001.sh | 41 + ctdb/tests/UNIT/tool/ctdb.getdbseqnum.002.sh | 36 + ctdb/tests/UNIT/tool/ctdb.getdbstatus.001.sh | 108 + ctdb/tests/UNIT/tool/ctdb.getdbstatus.002.sh | 108 + ctdb/tests/UNIT/tool/ctdb.getpid.001.sh | 17 + ctdb/tests/UNIT/tool/ctdb.getpid.010.sh | 25 + ctdb/tests/UNIT/tool/ctdb.getreclock.001.sh | 16 + ctdb/tests/UNIT/tool/ctdb.getreclock.002.sh | 21 + ctdb/tests/UNIT/tool/ctdb.getvar.001.sh | 35 + ctdb/tests/UNIT/tool/ctdb.getvar.002.sh | 17 + ctdb/tests/UNIT/tool/ctdb.ifaces.001.sh | 24 + ctdb/tests/UNIT/tool/ctdb.ip.001.sh | 17 + ctdb/tests/UNIT/tool/ctdb.ip.002.sh | 17 + ctdb/tests/UNIT/tool/ctdb.ip.003.sh | 30 + ctdb/tests/UNIT/tool/ctdb.ip.004.sh | 29 + ctdb/tests/UNIT/tool/ctdb.ip.005.sh | 30 + ctdb/tests/UNIT/tool/ctdb.ip.006.sh | 30 + ctdb/tests/UNIT/tool/ctdb.ip.007.sh | 36 + ctdb/tests/UNIT/tool/ctdb.ipinfo.001.sh | 18 + ctdb/tests/UNIT/tool/ctdb.ipinfo.002.sh | 32 + ctdb/tests/UNIT/tool/ctdb.ipinfo.003.sh | 35 + ctdb/tests/UNIT/tool/ctdb.leader.001.sh | 16 + ctdb/tests/UNIT/tool/ctdb.leader.002.sh | 16 + ctdb/tests/UNIT/tool/ctdb.listnodes.001.sh | 20 + ctdb/tests/UNIT/tool/ctdb.listnodes.002.sh | 19 + ctdb/tests/UNIT/tool/ctdb.listvars.001.sh | 66 + ctdb/tests/UNIT/tool/ctdb.lvs.001.sh | 36 + ctdb/tests/UNIT/tool/ctdb.lvs.002.sh | 46 + ctdb/tests/UNIT/tool/ctdb.lvs.003.sh | 43 + ctdb/tests/UNIT/tool/ctdb.lvs.004.sh | 45 + ctdb/tests/UNIT/tool/ctdb.lvs.005.sh | 46 + ctdb/tests/UNIT/tool/ctdb.lvs.006.sh | 44 + ctdb/tests/UNIT/tool/ctdb.lvs.007.sh | 42 + ctdb/tests/UNIT/tool/ctdb.lvs.008.sh | 66 + ctdb/tests/UNIT/tool/ctdb.lvs.010.sh | 25 + ctdb/tests/UNIT/tool/ctdb.natgw.001.sh | 46 + ctdb/tests/UNIT/tool/ctdb.natgw.002.sh | 46 + ctdb/tests/UNIT/tool/ctdb.natgw.003.sh | 43 + ctdb/tests/UNIT/tool/ctdb.natgw.004.sh | 46 + ctdb/tests/UNIT/tool/ctdb.natgw.005.sh | 46 + ctdb/tests/UNIT/tool/ctdb.natgw.006.sh | 46 + ctdb/tests/UNIT/tool/ctdb.natgw.007.sh | 45 + ctdb/tests/UNIT/tool/ctdb.natgw.008.sh | 46 + ctdb/tests/UNIT/tool/ctdb.natgw.010.sh | 25 + ctdb/tests/UNIT/tool/ctdb.nodestatus.001.sh | 33 + ctdb/tests/UNIT/tool/ctdb.nodestatus.002.sh | 33 + ctdb/tests/UNIT/tool/ctdb.nodestatus.003.sh | 33 + ctdb/tests/UNIT/tool/ctdb.nodestatus.004.sh | 28 + ctdb/tests/UNIT/tool/ctdb.nodestatus.005.sh | 28 + ctdb/tests/UNIT/tool/ctdb.nodestatus.006.sh | 40 + ctdb/tests/UNIT/tool/ctdb.nodestatus.007.sh | 36 + ctdb/tests/UNIT/tool/ctdb.pdelete.001.sh | 27 + ctdb/tests/UNIT/tool/ctdb.ping.001.sh | 24 + ctdb/tests/UNIT/tool/ctdb.pnn.001.sh | 15 + ctdb/tests/UNIT/tool/ctdb.process-exists.001.sh | 28 + ctdb/tests/UNIT/tool/ctdb.process-exists.002.sh | 30 + ctdb/tests/UNIT/tool/ctdb.process-exists.003.sh | 30 + ctdb/tests/UNIT/tool/ctdb.pstore.001.sh | 24 + ctdb/tests/UNIT/tool/ctdb.ptrans.001.sh | 49 + ctdb/tests/UNIT/tool/ctdb.readkey.001.sh | 20 + ctdb/tests/UNIT/tool/ctdb.recover.001.sh | 22 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.001.sh | 24 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.002.sh | 30 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.003.sh | 29 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.011.sh | 25 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.012.sh | 24 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.013.sh | 26 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.014.sh | 24 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.015.sh | 26 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.016.sh | 24 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.017.sh | 26 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.018.sh | 29 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.019.sh | 28 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.020.sh | 28 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.021.sh | 26 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.023.sh | 24 + ctdb/tests/UNIT/tool/ctdb.reloadnodes.024.sh | 24 + ctdb/tests/UNIT/tool/ctdb.runstate.001.sh | 15 + ctdb/tests/UNIT/tool/ctdb.runstate.002.sh | 15 + ctdb/tests/UNIT/tool/ctdb.runstate.003.sh | 17 + ctdb/tests/UNIT/tool/ctdb.runstate.004.sh | 15 + ctdb/tests/UNIT/tool/ctdb.runstate.005.sh | 15 + ctdb/tests/UNIT/tool/ctdb.setdbreadonly.001.sh | 53 + ctdb/tests/UNIT/tool/ctdb.setdbreadonly.002.sh | 37 + ctdb/tests/UNIT/tool/ctdb.setdbreadonly.003.sh | 39 + ctdb/tests/UNIT/tool/ctdb.setdbreadonly.004.sh | 37 + ctdb/tests/UNIT/tool/ctdb.setdbreadonly.005.sh | 39 + ctdb/tests/UNIT/tool/ctdb.setdbsticky.001.sh | 53 + ctdb/tests/UNIT/tool/ctdb.setdbsticky.002.sh | 37 + ctdb/tests/UNIT/tool/ctdb.setdbsticky.003.sh | 39 + ctdb/tests/UNIT/tool/ctdb.setdbsticky.004.sh | 37 + ctdb/tests/UNIT/tool/ctdb.setdbsticky.005.sh | 39 + ctdb/tests/UNIT/tool/ctdb.setdebug.001.sh | 23 + ctdb/tests/UNIT/tool/ctdb.setdebug.002.sh | 23 + ctdb/tests/UNIT/tool/ctdb.setdebug.003.sh | 32 + ctdb/tests/UNIT/tool/ctdb.setifacelink.001.sh | 76 + ctdb/tests/UNIT/tool/ctdb.setifacelink.002.sh | 22 + ctdb/tests/UNIT/tool/ctdb.setvar.001.sh | 49 + ctdb/tests/UNIT/tool/ctdb.setvar.002.sh | 17 + ctdb/tests/UNIT/tool/ctdb.status.001.sh | 46 + ctdb/tests/UNIT/tool/ctdb.status.002.sh | 46 + ctdb/tests/UNIT/tool/ctdb.status.003.sh | 49 + ctdb/tests/UNIT/tool/ctdb.stop.001.sh | 23 + ctdb/tests/UNIT/tool/ctdb.stop.002.sh | 23 + ctdb/tests/UNIT/tool/ctdb.stop.003.sh | 23 + ctdb/tests/UNIT/tool/ctdb.unban.001.sh | 23 + ctdb/tests/UNIT/tool/ctdb.unban.002.sh | 34 + ctdb/tests/UNIT/tool/ctdb.unban.003.sh | 23 + ctdb/tests/UNIT/tool/ctdb.uptime.001.sh | 36 + ctdb/tests/UNIT/tool/ctdb.writekey.001.sh | 31 + ctdb/tests/UNIT/tool/scripts/local.sh | 112 + ctdb/tests/etc-ctdb/events/legacy/00.test.script | 30 + ctdb/tests/local_daemons.sh | 506 +++ ctdb/tests/run_cluster_tests.sh | 1 + ctdb/tests/run_tests.sh | 399 ++ ctdb/tests/scripts/cluster.bash | 18 + ctdb/tests/scripts/common.sh | 146 + ctdb/tests/scripts/integration.bash | 864 ++++ ctdb/tests/scripts/integration_local_daemons.bash | 95 + ctdb/tests/scripts/integration_real_cluster.bash | 53 + ctdb/tests/scripts/script_install_paths.sh | 67 + ctdb/tests/scripts/test_wrap | 11 + ctdb/tests/scripts/unit.sh | 267 ++ ctdb/tests/src/cluster_mutex_test.c | 844 ++++ ctdb/tests/src/cluster_wait.c | 346 ++ ctdb/tests/src/cluster_wait.h | 30 + ctdb/tests/src/cmdline_test.c | 480 ++ ctdb/tests/src/comm_client_test.c | 217 + ctdb/tests/src/comm_server_test.c | 292 ++ ctdb/tests/src/comm_test.c | 501 ++ ctdb/tests/src/conf_test.c | 513 +++ ctdb/tests/src/ctdb_io_test.c | 356 ++ ctdb/tests/src/ctdb_packet_parse.c | 136 + ctdb/tests/src/ctdb_takeover_tests.c | 281 ++ ctdb/tests/src/db_hash_test.c | 138 + ctdb/tests/src/db_test_tool.c | 792 ++++ ctdb/tests/src/dummy_client.c | 163 + ctdb/tests/src/errcode.c | 189 + ctdb/tests/src/event_script_test.c | 120 + ctdb/tests/src/fake_ctdbd.c | 4781 ++++++++++++++++++++ ctdb/tests/src/fetch_loop.c | 288 ++ ctdb/tests/src/fetch_loop_key.c | 217 + ctdb/tests/src/fetch_readonly.c | 166 + ctdb/tests/src/fetch_readonly_loop.c | 272 ++ ctdb/tests/src/fetch_ring.c | 398 ++ ctdb/tests/src/g_lock_loop.c | 270 ++ ctdb/tests/src/hash_count_test.c | 132 + ctdb/tests/src/ipalloc_read_known_ips.c | 179 + ctdb/tests/src/ipalloc_read_known_ips.h | 32 + ctdb/tests/src/line_test.c | 102 + ctdb/tests/src/lock_tdb.c | 60 + ctdb/tests/src/message_ring.c | 369 ++ ctdb/tests/src/pidfile_test.c | 242 + ctdb/tests/src/pkt_read_test.c | 249 + ctdb/tests/src/pkt_write_test.c | 359 ++ ctdb/tests/src/porting_tests.c | 262 ++ ctdb/tests/src/protocol_basic_test.c | 106 + ctdb/tests/src/protocol_common.c | 1260 ++++++ ctdb/tests/src/protocol_common.h | 238 + ctdb/tests/src/protocol_common_basic.c | 305 ++ ctdb/tests/src/protocol_common_basic.h | 175 + ctdb/tests/src/protocol_common_ctdb.c | 1967 ++++++++ ctdb/tests/src/protocol_common_ctdb.h | 101 + ctdb/tests/src/protocol_ctdb_compat_test.c | 1270 ++++++ ctdb/tests/src/protocol_ctdb_test.c | 365 ++ ctdb/tests/src/protocol_types_compat_test.c | 2371 ++++++++++ ctdb/tests/src/protocol_types_test.c | 194 + ctdb/tests/src/protocol_util_test.c | 417 ++ ctdb/tests/src/rb_test.c | 336 ++ ctdb/tests/src/reqid_test.c | 89 + ctdb/tests/src/run_event_test.c | 251 + ctdb/tests/src/run_proc_test.c | 111 + ctdb/tests/src/sigcode.c | 120 + ctdb/tests/src/sock_daemon_test.c | 1980 ++++++++ ctdb/tests/src/sock_io_test.c | 283 ++ ctdb/tests/src/srvid_test.c | 105 + ctdb/tests/src/system_socket_test.c | 266 ++ ctdb/tests/src/test_backtrace.c | 37 + ctdb/tests/src/test_backtrace.h | 25 + ctdb/tests/src/test_mutex_raw.c | 434 ++ ctdb/tests/src/test_options.c | 245 + ctdb/tests/src/test_options.h | 44 + ctdb/tests/src/tmon_ping_test.c | 381 ++ ctdb/tests/src/tmon_test.c | 406 ++ ctdb/tests/src/transaction_loop.c | 419 ++ ctdb/tests/src/tunable_test.c | 71 + ctdb/tests/src/tunnel_cmd.c | 199 + ctdb/tests/src/tunnel_test.c | 480 ++ ctdb/tests/src/update_record.c | 236 + ctdb/tests/src/update_record_persistent.c | 218 + ctdb/tests/test_check_tcp_ports.sh | 18 + 775 files changed, 62656 insertions(+) create mode 100755 ctdb/tests/CLUSTER/complex/11_ctdb_delip_removes_ip.sh create mode 100755 ctdb/tests/CLUSTER/complex/18_ctdb_reloadips.sh create mode 100755 ctdb/tests/CLUSTER/complex/30_nfs_tickle_killtcp.sh create mode 100755 ctdb/tests/CLUSTER/complex/31_nfs_tickle.sh create mode 100755 ctdb/tests/CLUSTER/complex/32_cifs_tickle.sh create mode 100755 ctdb/tests/CLUSTER/complex/33_gratuitous_arp.sh create mode 100755 ctdb/tests/CLUSTER/complex/34_nfs_tickle_restart.sh create mode 100755 ctdb/tests/CLUSTER/complex/36_smb_reset_server.sh create mode 100755 ctdb/tests/CLUSTER/complex/37_nfs_reset_server.sh create mode 100755 ctdb/tests/CLUSTER/complex/41_failover_ping_discrete.sh create mode 100755 ctdb/tests/CLUSTER/complex/42_failover_ssh_hostname.sh create mode 100755 ctdb/tests/CLUSTER/complex/43_failover_nfs_basic.sh create mode 100755 ctdb/tests/CLUSTER/complex/44_failover_nfs_oneway.sh create mode 100755 ctdb/tests/CLUSTER/complex/45_failover_nfs_kill.sh create mode 100755 ctdb/tests/CLUSTER/complex/60_rogueip_releaseip.sh create mode 100755 ctdb/tests/CLUSTER/complex/61_rogueip_takeip.sh create mode 100644 ctdb/tests/CLUSTER/complex/README create mode 100644 ctdb/tests/CLUSTER/complex/scripts/local.bash create mode 100755 ctdb/tests/INTEGRATION/database/basics.001.attach.sh create mode 100755 ctdb/tests/INTEGRATION/database/basics.002.attach.sh create mode 100755 ctdb/tests/INTEGRATION/database/basics.003.detach.sh create mode 100755 ctdb/tests/INTEGRATION/database/basics.004.wipe.sh create mode 100755 ctdb/tests/INTEGRATION/database/basics.010.backup_restore.sh create mode 100755 ctdb/tests/INTEGRATION/database/fetch.001.ring.sh create mode 100755 ctdb/tests/INTEGRATION/database/fetch.002.ring-hotkeys.sh create mode 100755 ctdb/tests/INTEGRATION/database/readonly.001.basic.sh create mode 100755 ctdb/tests/INTEGRATION/database/recovery.001.volatile.sh create mode 100755 ctdb/tests/INTEGRATION/database/recovery.002.large.sh create mode 100755 ctdb/tests/INTEGRATION/database/recovery.003.no_resurrect.sh create mode 100755 ctdb/tests/INTEGRATION/database/recovery.010.persistent.sh create mode 100755 ctdb/tests/INTEGRATION/database/recovery.011.continue.sh create mode 100644 ctdb/tests/INTEGRATION/database/scripts/local.bash create mode 100755 ctdb/tests/INTEGRATION/database/transaction.001.ptrans.sh create mode 100755 ctdb/tests/INTEGRATION/database/transaction.002.loop.sh create mode 100755 ctdb/tests/INTEGRATION/database/transaction.003.loop_recovery.sh create mode 100755 ctdb/tests/INTEGRATION/database/transaction.004.update_record.sh create mode 100755 ctdb/tests/INTEGRATION/database/transaction.010.loop_recovery.sh create mode 100755 ctdb/tests/INTEGRATION/database/traverse.001.one.sh create mode 100755 ctdb/tests/INTEGRATION/database/traverse.002.many.sh create mode 100755 ctdb/tests/INTEGRATION/database/vacuum.001.fast.sh create mode 100755 ctdb/tests/INTEGRATION/database/vacuum.002.full.sh create mode 100755 ctdb/tests/INTEGRATION/database/vacuum.003.recreate.sh create mode 100755 ctdb/tests/INTEGRATION/database/vacuum.030.locked.sh create mode 100755 ctdb/tests/INTEGRATION/database/vacuum.031.locked.sh create mode 100755 ctdb/tests/INTEGRATION/database/vacuum.032.locked.sh create mode 100755 ctdb/tests/INTEGRATION/database/vacuum.033.locked.sh create mode 100755 ctdb/tests/INTEGRATION/database/vacuum.034.locked.sh create mode 100755 ctdb/tests/INTEGRATION/failover/pubips.001.list.sh create mode 100755 ctdb/tests/INTEGRATION/failover/pubips.010.addip.sh create mode 100755 ctdb/tests/INTEGRATION/failover/pubips.011.delip.sh create mode 100755 ctdb/tests/INTEGRATION/failover/pubips.012.reloadips.sh create mode 100755 ctdb/tests/INTEGRATION/failover/pubips.013.failover_noop.sh create mode 100755 ctdb/tests/INTEGRATION/failover/pubips.014.iface_gc.sh create mode 100755 ctdb/tests/INTEGRATION/failover/pubips.020.moveip.sh create mode 100755 ctdb/tests/INTEGRATION/failover/pubips.030.disable_enable.sh create mode 100755 ctdb/tests/INTEGRATION/failover/pubips.032.stop_continue.sh create mode 100755 ctdb/tests/INTEGRATION/failover/pubips.040.NoIPTakeover.sh create mode 100755 ctdb/tests/INTEGRATION/failover/pubips.050.missing_ip.sh create mode 100644 ctdb/tests/INTEGRATION/simple/README create mode 100755 ctdb/tests/INTEGRATION/simple/basics.000.onnode.sh create mode 100755 ctdb/tests/INTEGRATION/simple/basics.001.listnodes.sh create mode 100755 ctdb/tests/INTEGRATION/simple/basics.002.tunables.sh create mode 100755 ctdb/tests/INTEGRATION/simple/basics.003.ping.sh create mode 100755 ctdb/tests/INTEGRATION/simple/basics.004.getpid.sh create mode 100755 ctdb/tests/INTEGRATION/simple/basics.005.process_exists.sh create mode 100755 ctdb/tests/INTEGRATION/simple/basics.010.statistics.sh create mode 100755 ctdb/tests/INTEGRATION/simple/basics.011.statistics_reset.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.001.stop_leader_yield.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.002.ban_leader_yield.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.003.capability_leader_yield.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.006.stop_leader_yield_no_lock.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.007.ban_leader_yield_no_lock.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.008.capability_leader_yield_no_lock.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.010.getrelock.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.012.reclock_command.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.015.reclock_remove_lock.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.016.reclock_move_lock_dir.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.020.message_ring.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.021.tunnel_ring.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.030.node_stall_leader_timeout.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.090.unreachable.sh create mode 100755 ctdb/tests/INTEGRATION/simple/cluster.091.version_check.sh create mode 100755 ctdb/tests/INTEGRATION/simple/debug.001.getdebug.sh create mode 100755 ctdb/tests/INTEGRATION/simple/debug.002.setdebug.sh create mode 100755 ctdb/tests/INTEGRATION/simple/debug.003.dumpmemory.sh create mode 100755 ctdb/tests/INTEGRATION/simple/eventscripts.001.zero_scripts.sh create mode 100755 ctdb/tests/INTEGRATION/simple/eventscripts.090.debug_hung.sh create mode 100644 ctdb/tests/README create mode 100644 ctdb/tests/TODO create mode 100755 ctdb/tests/UNIT/cunit/cluster_mutex_001.sh create mode 100755 ctdb/tests/UNIT/cunit/cluster_mutex_002.sh create mode 100755 ctdb/tests/UNIT/cunit/cluster_mutex_003.sh create mode 100755 ctdb/tests/UNIT/cunit/cmdline_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/comm_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/comm_test_002.sh create mode 100755 ctdb/tests/UNIT/cunit/conf_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/config_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/config_test_002.sh create mode 100755 ctdb/tests/UNIT/cunit/config_test_003.sh create mode 100755 ctdb/tests/UNIT/cunit/config_test_004.sh create mode 100755 ctdb/tests/UNIT/cunit/config_test_005.sh create mode 100755 ctdb/tests/UNIT/cunit/config_test_006.sh create mode 100755 ctdb/tests/UNIT/cunit/config_test_007.sh create mode 100755 ctdb/tests/UNIT/cunit/ctdb_io_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/db_hash_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/event_protocol_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/event_script_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/hash_count_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/line_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/path_tests_001.sh create mode 100755 ctdb/tests/UNIT/cunit/pidfile_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/pkt_read_001.sh create mode 100755 ctdb/tests/UNIT/cunit/pkt_write_001.sh create mode 100755 ctdb/tests/UNIT/cunit/porting_tests_001.sh create mode 100755 ctdb/tests/UNIT/cunit/protocol_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/protocol_test_002.sh create mode 100755 ctdb/tests/UNIT/cunit/protocol_test_012.sh create mode 100755 ctdb/tests/UNIT/cunit/protocol_test_101.sh create mode 100755 ctdb/tests/UNIT/cunit/protocol_test_111.sh create mode 100755 ctdb/tests/UNIT/cunit/protocol_test_201.sh create mode 100755 ctdb/tests/UNIT/cunit/rb_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/reqid_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/run_event_001.sh create mode 100755 ctdb/tests/UNIT/cunit/run_proc_001.sh create mode 100755 ctdb/tests/UNIT/cunit/sock_daemon_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/sock_io_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/srvid_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/system_socket_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/system_socket_test_002.sh create mode 100755 ctdb/tests/UNIT/cunit/system_socket_test_003.sh create mode 100755 ctdb/tests/UNIT/cunit/tmon_test_001.sh create mode 100755 ctdb/tests/UNIT/cunit/tmon_test_002.sh create mode 100755 ctdb/tests/UNIT/cunit/tunable_test_001.sh create mode 100644 ctdb/tests/UNIT/eventd/README create mode 100644 ctdb/tests/UNIT/eventd/etc-ctdb/ctdb.conf create mode 100755 ctdb/tests/UNIT/eventd/etc-ctdb/debug-script.sh create mode 100644 ctdb/tests/UNIT/eventd/etc-ctdb/events/data/03.notalink.script create mode 100644 ctdb/tests/UNIT/eventd/etc-ctdb/events/data/README create mode 100644 ctdb/tests/UNIT/eventd/etc-ctdb/events/empty/README create mode 100755 ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/01.test.script create mode 100755 ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/02.test.script create mode 100755 ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/03.test.script create mode 100644 ctdb/tests/UNIT/eventd/etc-ctdb/events/random/01.disabled.script create mode 100755 ctdb/tests/UNIT/eventd/etc-ctdb/events/random/02.enabled.script create mode 100644 ctdb/tests/UNIT/eventd/etc-ctdb/events/random/README.script create mode 100755 ctdb/tests/UNIT/eventd/etc-ctdb/events/random/a.script create mode 100755 ctdb/tests/UNIT/eventd/etc-ctdb/share/events/data/01.dummy.script create mode 100755 ctdb/tests/UNIT/eventd/etc-ctdb/share/events/data/02.disabled.script create mode 100644 ctdb/tests/UNIT/eventd/etc-ctdb/share/events/empty/README create mode 100644 ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/01.disabled.script create mode 100755 ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/02.enabled.script create mode 100644 ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/README.script create mode 100755 ctdb/tests/UNIT/eventd/etc-ctdb/share/events/random/a.script create mode 100755 ctdb/tests/UNIT/eventd/eventd_001.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_002.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_003.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_004.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_005.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_006.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_007.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_008.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_009.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_011.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_012.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_013.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_014.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_021.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_022.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_023.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_024.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_031.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_032.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_033.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_041.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_042.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_043.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_044.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_051.sh create mode 100755 ctdb/tests/UNIT/eventd/eventd_052.sh create mode 100644 ctdb/tests/UNIT/eventd/scripts/local.sh create mode 100755 ctdb/tests/UNIT/eventscripts/00.ctdb.init.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/00.ctdb.init.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/00.ctdb.init.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/00.ctdb.init.004.sh create mode 100755 ctdb/tests/UNIT/eventscripts/00.ctdb.init.005.sh create mode 100755 ctdb/tests/UNIT/eventscripts/00.ctdb.init.006.sh create mode 100755 ctdb/tests/UNIT/eventscripts/00.ctdb.init.007.sh create mode 100755 ctdb/tests/UNIT/eventscripts/00.ctdb.init.008.sh create mode 100755 ctdb/tests/UNIT/eventscripts/00.ctdb.init.009.sh create mode 100755 ctdb/tests/UNIT/eventscripts/01.reclock.init.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/01.reclock.init.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/01.reclock.init.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.004.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.005.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.006.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.007.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.011.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.012.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.014.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.015.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.017.sh create mode 100755 ctdb/tests/UNIT/eventscripts/05.system.monitor.018.sh create mode 100755 ctdb/tests/UNIT/eventscripts/06.nfs.releaseip.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/06.nfs.releaseip.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/06.nfs.takeip.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/06.nfs.takeip.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.010.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.011.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.012.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.013.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.init.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.init.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.init.021.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.init.022.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.init.023.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.004.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.005.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.006.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.009.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.010.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.011.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.012.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.013.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.014.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.015.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.016.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.017.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.monitor.018.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.multi.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.releaseip.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.releaseip.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.startup.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.startup.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.takeip.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.takeip.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/10.interface.takeip.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.004.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.011.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.012.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.013.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.014.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.015.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.021.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.022.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.023.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.024.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.025.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.031.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.041.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.042.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.051.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.052.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.053.sh create mode 100755 ctdb/tests/UNIT/eventscripts/11.natgw.054.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.004.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.005.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.006.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.007.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.008.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.009.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.010.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.011.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.012.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.013.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.014.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.015.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.016.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.017.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.018.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.019.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.021.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.022.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.023.sh create mode 100755 ctdb/tests/UNIT/eventscripts/13.per_ip_routing.024.sh create mode 100755 ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/20.multipathd.monitor.004.sh create mode 100755 ctdb/tests/UNIT/eventscripts/31.clamd.monitor.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/31.clamd.monitor.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/40.vsftpd.monitor.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/40.vsftpd.shutdown.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/40.vsftpd.startup.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/41.httpd.monitor.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/41.httpd.shutdown.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/41.httpd.startup.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/48.netbios.shutdown.011.sh create mode 100755 ctdb/tests/UNIT/eventscripts/48.netbios.startup.011.sh create mode 100755 ctdb/tests/UNIT/eventscripts/49.winbind.monitor.101.sh create mode 100755 ctdb/tests/UNIT/eventscripts/49.winbind.monitor.102.sh create mode 100755 ctdb/tests/UNIT/eventscripts/49.winbind.shutdown.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/49.winbind.startup.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.monitor.101.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.monitor.103.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.monitor.104.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.monitor.105.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.monitor.106.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.monitor.110.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.monitor.111.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.monitor.112.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.monitor.113.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.shutdown.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.shutdown.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.shutdown.011.sh create mode 100755 ctdb/tests/UNIT/eventscripts/50.samba.startup.011.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.101.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.102.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.103.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.104.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.105.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.106.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.107.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.108.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.109.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.111.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.112.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.113.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.114.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.121.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.122.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.131.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.132.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.141.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.142.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.143.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.144.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.151.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.152.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.153.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.161.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.monitor.162.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.multi.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.multi.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.releaseip.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.releaseip.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.shutdown.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.shutdown.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.startup.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.startup.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.takeip.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/60.nfs.takeip.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.011.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.012.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.013.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.ipreallocated.014.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.monitor.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.monitor.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.monitor.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.shutdown.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.shutdown.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.startup.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/91.lvs.startup.002.sh create mode 100644 ctdb/tests/UNIT/eventscripts/README create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.004.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.005.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.006.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.007.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.008.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.021.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.022.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.023.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.024.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.025.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.026.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.027.sh create mode 100755 ctdb/tests/UNIT/eventscripts/debug_locks.sh.028.sh create mode 100644 ctdb/tests/UNIT/eventscripts/etc-ctdb/public_addresses create mode 100755 ctdb/tests/UNIT/eventscripts/etc-ctdb/rc.local create mode 100755 ctdb/tests/UNIT/eventscripts/etc/init.d/nfs create mode 100755 ctdb/tests/UNIT/eventscripts/etc/init.d/nfslock create mode 100644 ctdb/tests/UNIT/eventscripts/etc/os-release create mode 100644 ctdb/tests/UNIT/eventscripts/etc/samba/smb.conf create mode 100644 ctdb/tests/UNIT/eventscripts/etc/sysconfig/nfs create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/00.ctdb.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/01.reclock.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/05.system.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/06.nfs.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/10.interface.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/11.natgw.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/13.per_ip_routing.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/20.multipathd.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/31.clamd.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/40.vsftpd.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/41.httpd.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/48.netbios.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/49.winbind.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/50.samba.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/60.nfs.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/91.lvs.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/debug_locks.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/local.sh create mode 100644 ctdb/tests/UNIT/eventscripts/scripts/statd-callout.sh create mode 100755 ctdb/tests/UNIT/eventscripts/statd-callout.001.sh create mode 100755 ctdb/tests/UNIT/eventscripts/statd-callout.002.sh create mode 100755 ctdb/tests/UNIT/eventscripts/statd-callout.003.sh create mode 100755 ctdb/tests/UNIT/eventscripts/statd-callout.004.sh create mode 100755 ctdb/tests/UNIT/eventscripts/statd-callout.005.sh create mode 100755 ctdb/tests/UNIT/eventscripts/statd-callout.006.sh create mode 100755 ctdb/tests/UNIT/eventscripts/statd-callout.007.sh create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/ctdb create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/ctdb-config create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/ctdb_killtcp create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/ctdb_lvs create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/ctdb_natgw create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/date create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/df create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/ethtool create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/exportfs create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/gstack create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/id create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/ip create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/ip6tables create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/iptables create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/ipvsadm create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/kill create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/killall create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/multipath create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/net create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/nfs-fake-callout create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/nfsconf create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/pidof create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/pkill create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/ps create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/rm create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/rpc.lockd create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/rpc.mountd create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/rpc.rquotad create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/rpc.statd create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/rpcinfo create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/service create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/sleep create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/smnotify create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/ss create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/stat create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/tdb_mutex_check create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/tdbdump create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/tdbtool create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/testparm create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/timeout create mode 100755 ctdb/tests/UNIT/eventscripts/stubs/wbinfo create mode 100755 ctdb/tests/UNIT/onnode/0001.sh create mode 100755 ctdb/tests/UNIT/onnode/0002.sh create mode 100755 ctdb/tests/UNIT/onnode/0003.sh create mode 100755 ctdb/tests/UNIT/onnode/0004.sh create mode 100755 ctdb/tests/UNIT/onnode/0005.sh create mode 100755 ctdb/tests/UNIT/onnode/0006.sh create mode 100755 ctdb/tests/UNIT/onnode/0010.sh create mode 100755 ctdb/tests/UNIT/onnode/0011.sh create mode 100755 ctdb/tests/UNIT/onnode/0070.sh create mode 100755 ctdb/tests/UNIT/onnode/0071.sh create mode 100755 ctdb/tests/UNIT/onnode/0072.sh create mode 100755 ctdb/tests/UNIT/onnode/0075.sh create mode 100644 ctdb/tests/UNIT/onnode/etc-ctdb/nodes create mode 100644 ctdb/tests/UNIT/onnode/scripts/local.sh create mode 100755 ctdb/tests/UNIT/onnode/stubs/ctdb create mode 100755 ctdb/tests/UNIT/onnode/stubs/ssh create mode 100755 ctdb/tests/UNIT/shellcheck/base_scripts.sh create mode 100755 ctdb/tests/UNIT/shellcheck/ctdb_helpers.sh create mode 100755 ctdb/tests/UNIT/shellcheck/event_scripts.sh create mode 100755 ctdb/tests/UNIT/shellcheck/functions.sh create mode 100755 ctdb/tests/UNIT/shellcheck/init_script.sh create mode 100644 ctdb/tests/UNIT/shellcheck/scripts/local.sh create mode 100755 ctdb/tests/UNIT/shellcheck/tests.sh create mode 100755 ctdb/tests/UNIT/shellcheck/tools.sh create mode 100644 ctdb/tests/UNIT/takeover/README create mode 100755 ctdb/tests/UNIT/takeover/det.001.sh create mode 100755 ctdb/tests/UNIT/takeover/det.002.sh create mode 100755 ctdb/tests/UNIT/takeover/det.003.sh create mode 100755 ctdb/tests/UNIT/takeover/det.004.sh create mode 100755 ctdb/tests/UNIT/takeover/det.005.sh create mode 100755 ctdb/tests/UNIT/takeover/det.006.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.001.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.002.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.003.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.004.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.005.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.006.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.007.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.008.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.009.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.010.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.011.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.012.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.013.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.014.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.015.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.016.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.024.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.025.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.027.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.028.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.029.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.030.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.031.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.032.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.033.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.034.sh create mode 100755 ctdb/tests/UNIT/takeover/lcp2.035.sh create mode 100755 ctdb/tests/UNIT/takeover/nondet.001.sh create mode 100755 ctdb/tests/UNIT/takeover/nondet.002.sh create mode 100755 ctdb/tests/UNIT/takeover/nondet.003.sh create mode 100644 ctdb/tests/UNIT/takeover/scripts/local.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/000.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/010.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/011.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/012.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/013.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/014.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/016.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/017.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/018.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/019.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/021.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/022.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/023.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/024.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/025.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/026.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/027.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/028.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/030.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/031.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/110.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/111.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/120.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/121.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/122.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/130.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/131.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/132.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/140.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/150.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/160.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/210.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/211.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/220.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/230.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/240.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/250.sh create mode 100755 ctdb/tests/UNIT/takeover_helper/260.sh create mode 100644 ctdb/tests/UNIT/takeover_helper/scripts/local.sh create mode 100644 ctdb/tests/UNIT/tool/README create mode 100755 ctdb/tests/UNIT/tool/ctdb.attach.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.attach.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.attach.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ban.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ban.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ban.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.catdb.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.catdb.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.cattdb.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.cattdb.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.continue.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.continue.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.continue.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.deletekey.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.disable.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.disable.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.disable.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.disable.004.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.enable.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.enable.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.enable.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getcapabilities.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getcapabilities.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getcapabilities.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getcapabilities.004.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getdbmap.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getdbseqnum.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getdbseqnum.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getdbstatus.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getdbstatus.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getpid.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getpid.010.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getreclock.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getreclock.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getvar.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.getvar.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ifaces.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ip.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ip.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ip.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ip.004.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ip.005.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ip.006.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ip.007.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ipinfo.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ipinfo.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ipinfo.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.leader.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.leader.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.listnodes.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.listnodes.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.listvars.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.lvs.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.lvs.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.lvs.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.lvs.004.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.lvs.005.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.lvs.006.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.lvs.007.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.lvs.008.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.lvs.010.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.natgw.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.natgw.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.natgw.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.natgw.004.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.natgw.005.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.natgw.006.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.natgw.007.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.natgw.008.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.natgw.010.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.nodestatus.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.nodestatus.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.nodestatus.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.nodestatus.004.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.nodestatus.005.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.nodestatus.006.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.nodestatus.007.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.pdelete.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ping.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.pnn.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.process-exists.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.process-exists.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.process-exists.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.pstore.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.ptrans.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.readkey.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.recover.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.011.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.012.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.013.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.014.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.015.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.016.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.017.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.018.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.019.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.020.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.021.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.023.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.reloadnodes.024.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.runstate.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.runstate.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.runstate.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.runstate.004.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.runstate.005.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdbreadonly.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdbreadonly.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdbreadonly.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdbreadonly.004.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdbreadonly.005.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdbsticky.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdbsticky.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdbsticky.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdbsticky.004.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdbsticky.005.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdebug.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdebug.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setdebug.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setifacelink.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setifacelink.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setvar.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.setvar.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.status.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.status.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.status.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.stop.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.stop.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.stop.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.unban.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.unban.002.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.unban.003.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.uptime.001.sh create mode 100755 ctdb/tests/UNIT/tool/ctdb.writekey.001.sh create mode 100644 ctdb/tests/UNIT/tool/scripts/local.sh create mode 100755 ctdb/tests/etc-ctdb/events/legacy/00.test.script create mode 100755 ctdb/tests/local_daemons.sh create mode 120000 ctdb/tests/run_cluster_tests.sh create mode 100755 ctdb/tests/run_tests.sh create mode 100755 ctdb/tests/scripts/cluster.bash create mode 100644 ctdb/tests/scripts/common.sh create mode 100644 ctdb/tests/scripts/integration.bash create mode 100644 ctdb/tests/scripts/integration_local_daemons.bash create mode 100644 ctdb/tests/scripts/integration_real_cluster.bash create mode 100644 ctdb/tests/scripts/script_install_paths.sh create mode 100755 ctdb/tests/scripts/test_wrap create mode 100644 ctdb/tests/scripts/unit.sh create mode 100644 ctdb/tests/src/cluster_mutex_test.c create mode 100644 ctdb/tests/src/cluster_wait.c create mode 100644 ctdb/tests/src/cluster_wait.h create mode 100644 ctdb/tests/src/cmdline_test.c create mode 100644 ctdb/tests/src/comm_client_test.c create mode 100644 ctdb/tests/src/comm_server_test.c create mode 100644 ctdb/tests/src/comm_test.c create mode 100644 ctdb/tests/src/conf_test.c create mode 100644 ctdb/tests/src/ctdb_io_test.c create mode 100644 ctdb/tests/src/ctdb_packet_parse.c create mode 100644 ctdb/tests/src/ctdb_takeover_tests.c create mode 100644 ctdb/tests/src/db_hash_test.c create mode 100644 ctdb/tests/src/db_test_tool.c create mode 100644 ctdb/tests/src/dummy_client.c create mode 100644 ctdb/tests/src/errcode.c create mode 100644 ctdb/tests/src/event_script_test.c create mode 100644 ctdb/tests/src/fake_ctdbd.c create mode 100644 ctdb/tests/src/fetch_loop.c create mode 100644 ctdb/tests/src/fetch_loop_key.c create mode 100644 ctdb/tests/src/fetch_readonly.c create mode 100644 ctdb/tests/src/fetch_readonly_loop.c create mode 100644 ctdb/tests/src/fetch_ring.c create mode 100644 ctdb/tests/src/g_lock_loop.c create mode 100644 ctdb/tests/src/hash_count_test.c create mode 100644 ctdb/tests/src/ipalloc_read_known_ips.c create mode 100644 ctdb/tests/src/ipalloc_read_known_ips.h create mode 100644 ctdb/tests/src/line_test.c create mode 100644 ctdb/tests/src/lock_tdb.c create mode 100644 ctdb/tests/src/message_ring.c create mode 100644 ctdb/tests/src/pidfile_test.c create mode 100644 ctdb/tests/src/pkt_read_test.c create mode 100644 ctdb/tests/src/pkt_write_test.c create mode 100644 ctdb/tests/src/porting_tests.c create mode 100644 ctdb/tests/src/protocol_basic_test.c create mode 100644 ctdb/tests/src/protocol_common.c create mode 100644 ctdb/tests/src/protocol_common.h create mode 100644 ctdb/tests/src/protocol_common_basic.c create mode 100644 ctdb/tests/src/protocol_common_basic.h create mode 100644 ctdb/tests/src/protocol_common_ctdb.c create mode 100644 ctdb/tests/src/protocol_common_ctdb.h create mode 100644 ctdb/tests/src/protocol_ctdb_compat_test.c create mode 100644 ctdb/tests/src/protocol_ctdb_test.c create mode 100644 ctdb/tests/src/protocol_types_compat_test.c create mode 100644 ctdb/tests/src/protocol_types_test.c create mode 100644 ctdb/tests/src/protocol_util_test.c create mode 100644 ctdb/tests/src/rb_test.c create mode 100644 ctdb/tests/src/reqid_test.c create mode 100644 ctdb/tests/src/run_event_test.c create mode 100644 ctdb/tests/src/run_proc_test.c create mode 100644 ctdb/tests/src/sigcode.c create mode 100644 ctdb/tests/src/sock_daemon_test.c create mode 100644 ctdb/tests/src/sock_io_test.c create mode 100644 ctdb/tests/src/srvid_test.c create mode 100644 ctdb/tests/src/system_socket_test.c create mode 100644 ctdb/tests/src/test_backtrace.c create mode 100644 ctdb/tests/src/test_backtrace.h create mode 100644 ctdb/tests/src/test_mutex_raw.c create mode 100644 ctdb/tests/src/test_options.c create mode 100644 ctdb/tests/src/test_options.h create mode 100644 ctdb/tests/src/tmon_ping_test.c create mode 100644 ctdb/tests/src/tmon_test.c create mode 100644 ctdb/tests/src/transaction_loop.c create mode 100644 ctdb/tests/src/tunable_test.c create mode 100644 ctdb/tests/src/tunnel_cmd.c create mode 100644 ctdb/tests/src/tunnel_test.c create mode 100644 ctdb/tests/src/update_record.c create mode 100644 ctdb/tests/src/update_record_persistent.c create mode 100755 ctdb/tests/test_check_tcp_ports.sh (limited to 'ctdb/tests') diff --git a/ctdb/tests/CLUSTER/complex/11_ctdb_delip_removes_ip.sh b/ctdb/tests/CLUSTER/complex/11_ctdb_delip_removes_ip.sh new file mode 100755 index 0000000..072780a --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/11_ctdb_delip_removes_ip.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Verify that a node's public IP address can be deleted using 'ctdb deleteip'. + +# This is an extended version of simple/17_ctdb_config_delete_ip.sh + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +test_node_has_test_ip() +{ + # $test_node and $test_ip set by select_test_node_and_ips() + # shellcheck disable=SC2154 + try_command_on_node "$test_node" "ip addr show to ${test_ip}" + [ -n "$out" ] +} + +ctdb_test_init + +select_test_node_and_ips + +# $test_node and $test_ip set by select_test_node_and_ips() +# shellcheck disable=SC2154 +echo "Checking that node ${test_node} hosts ${test_ip}..." +test_node_has_test_ip + +echo "Attempting to remove ${test_ip} from node ${test_node}." +ctdb_onnode "$test_node" "delip ${test_ip}" +ctdb_onnode "$test_node" "ipreallocate" +wait_until_ips_are_on_node '!' "$test_node" "$test_ip" + +echo "Waiting for ${test_ip} to disappear from node ${test_node}..." +wait_until 60/5 '!' test_node_has_test_ip + +echo "GOOD: IP was successfully removed!" diff --git a/ctdb/tests/CLUSTER/complex/18_ctdb_reloadips.sh b/ctdb/tests/CLUSTER/complex/18_ctdb_reloadips.sh new file mode 100755 index 0000000..150aeea --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/18_ctdb_reloadips.sh @@ -0,0 +1,257 @@ +#!/bin/bash + +# Verify that adding/deleting IPs using 'ctdb reloadips' works + +# Checks that when IPs are added to and deleted from a single node then +# those IPs are actually assigned and unassigned from the specified +# interface. + +# Prerequisites: + +# * An active CTDB cluster with public IP addresses configured + +# Expected results: + +# * When IPs are added to a single node then they are assigned to an +# interface. + +# * When IPs are deleted from a single node then they disappear from an +# interface. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +#################### + +# Search for an unused 10.B.1.0/24 network on which to add public IP +# addresses. + +# The initial search is for a 10.B.0.0/16 network since some +# configurations may use a whole class B for the private network. +# Check that there are no public IP addresses (as reported by "ctdb ip +# all") or other IP addresses (as reported by "ip addr show") with +# the provided prefix. Note that this is an IPv4-specific test. + +echo "Getting public IP information from CTDB..." +ctdb_onnode "$test_node" "ip -X -v all" +ctdb_ip_info=$(awk -F'|' 'NR > 1 { print $2, $3, $5 }' "$outfile") + +echo "Getting IP information from interfaces..." +try_command_on_node all "ip addr show" +ip_addr_info=$(awk '$1 == "inet" { ip = $2; sub(/\/.*/, "", ip); print ip }' \ + "$outfile") + +prefix="" +for b in $(seq 0 255) ; do + prefix="10.${b}" + + # Does the prefix match any IP address returned by "ip addr info"? + while read ip ; do + if [ "${ip#${prefix}.}" != "$ip" ] ; then + prefix="" + continue 2 + fi + done <<<"$ip_addr_info" + + # Does the prefix match any public IP address "ctdb ip all"? + while read ip pnn iface ; do + if [ "${ip#${prefix}.}" != "$ip" ] ; then + prefix="" + continue 2 + fi + done <<<"$ctdb_ip_info" + + # Got through the IPs without matching prefix - done! + break +done + +[ -n "$prefix" ] || die "Unable to find a usable IP address prefix" + +# We really want a class C: 10.B.1.0/24 +prefix="${prefix}.1" + +#################### + +iface=$(echo "$ctdb_ip_info" | awk -v pnn=$test_node '$2 == pnn { print $3 ; exit }') + +#################### + +# This needs to be set only on the recmaster. All nodes should do the trick. +new_takeover_timeout=90 +echo "Setting TakeoverTimeout=${new_takeover_timeout} to avoid potential bans" +try_command_on_node all "$CTDB setvar TakeoverTimeout ${new_takeover_timeout}" + +#################### + +try_command_on_node $test_node $CTDB_TEST_WRAPPER ctdb_base_show +addresses="${out}/public_addresses" +echo "Public addresses file on node $test_node is \"$addresses\"" +backup="${addresses}.$$" + +backup_public_addresses () +{ + try_command_on_node $test_node "cp -a $addresses $backup" +} + +restore_public_addresses () +{ + try_command_on_node $test_node "mv $backup $addresses >/dev/null 2>&1 || true" +} +ctdb_test_exit_hook_add restore_public_addresses + +# Now create that backup +backup_public_addresses + +#################### + +add_ips_to_original_config () +{ + local test_node="$1" + local addresses="$2" + local iface="$3" + local prefix="$4" + local first="$5" + local last="$6" + + echo "Adding new public IPs to original config on node ${test_node}..." + echo "IPs will be ${prefix}.${first}/24..${prefix}.${last}/24" + + # Implement this by completely rebuilding the public_addresses + # file. This is easier than deleting entries on a remote node. + restore_public_addresses + backup_public_addresses + + # Note that tee is a safe way of creating a file on a remote node. + # This avoids potential fragility with quoting or redirection. + for i in $(seq $first $last) ; do + echo "${prefix}.${i}/24 ${iface}" + done | + try_command_on_node -i $test_node "tee -a $addresses" +} + +check_ips () +{ + local test_node="$1" + local iface="$2" + local prefix="$3" + local first="$4" + local last="$5" + + # If just 0 specified then this is an empty range + local public_ips_file=$(mktemp) + if [ "$first" = 0 -a -z "$last" ] ; then + echo "Checking that there are no IPs in ${prefix}.0/24" + else + local prefix_regexp="inet *${prefix//./\.}" + + echo "Checking IPs in range ${prefix}.${first}/24..${prefix}.${last}/24" + + local i + for i in $(seq $first $last) ; do + echo "${prefix}.${i}" + done | sort >"$public_ips_file" + fi + + try_command_on_node $test_node "ip addr show dev ${iface}" + local ip_addrs_file=$(mktemp) + cat "$outfile" | \ + sed -n -e "s@.*inet * \(${prefix//./\.}\.[0-9]*\)/.*@\1@p" | \ + sort >"$ip_addrs_file" + + local diffs=$(diff "$public_ips_file" "$ip_addrs_file") || true + rm -f "$ip_addrs_file" "$public_ips_file" + + if [ -z "$diffs" ] ; then + echo "GOOD: IP addresses are as expected" + else + echo "BAD: IP addresses are incorrect:" + echo "$diffs" + exit 1 + fi +} + +# ctdb reloadips will fail if it can't disable takover runs. The most +# likely reason for this is that there is already a takeover run in +# progress. We can't predict when this will happen, so retry if this +# occurs. +do_ctdb_reloadips () +{ + local retry_max=10 + local retry_count=0 + while : ; do + if try_command_on_node "$test_node" "$CTDB reloadips" ; then + return 0 + fi + + if [ "$out" != "Failed to disable takeover runs" ] ; then + return 1 + fi + + if [ $retry_count -ge $retry_max ] ; then + return 1 + fi + + retry_count=$((retry_count + 1)) + echo "Retrying..." + sleep_for 1 + done +} + +#################### + +new_ip_max=100 + +#################### + +add_ips_to_original_config \ + $test_node "$addresses" "$iface" "$prefix" 1 $new_ip_max + +do_ctdb_reloadips + +check_ips $test_node "$iface" "$prefix" 1 $new_ip_max + +ctdb_onnode "$test_node" sync + +#################### + +# This should be the primary. Ensure that no other IPs are lost +echo "Using 'ctdb reloadips' to remove the 1st address just added..." + +add_ips_to_original_config \ + $test_node "$addresses" "$iface" "$prefix" 2 $new_ip_max + +do_ctdb_reloadips + +check_ips $test_node "$iface" "$prefix" 2 $new_ip_max + +ctdb_onnode "$test_node" sync + +#################### + +# Get rid of about 1/2 the IPs +start=$(($new_ip_max / 2 + 1)) +echo "Updating to include only about 1/2 of the new IPs..." + +add_ips_to_original_config \ + $test_node "$addresses" "$iface" "$prefix" $start $new_ip_max + +do_ctdb_reloadips + +check_ips $test_node "$iface" "$prefix" $start $new_ip_max + +ctdb_onnode "$test_node" sync + +#################### + +# Delete the rest +echo "Restoring original IP configuration..." +restore_public_addresses + +do_ctdb_reloadips + +check_ips $test_node "$iface" "$prefix" 0 diff --git a/ctdb/tests/CLUSTER/complex/30_nfs_tickle_killtcp.sh b/ctdb/tests/CLUSTER/complex/30_nfs_tickle_killtcp.sh new file mode 100755 index 0000000..4d8f617 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/30_nfs_tickle_killtcp.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Verify that NFS connections are monitored and that NFS tickles are sent. + +# Create a connection to the NFS server on a node. Then disable the +# relevant NFS server node and ensure that it sends an appropriate reset +# packet. The packet must come from the releasing node. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# * Cluster nodes must be listening on the NFS TCP port (2049). + +# Expected results: + +# * CTDB on the releasing node should correctly send a reset packet when +# the node is disabled. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +test_port=2049 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with netcat..." + +sleep 30 | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +echo "Getting MAC address associated with ${test_ip}..." +releasing_mac=$(ip neigh show $test_prefix | awk '$4 == "lladdr" {print $5}') +[ -n "$releasing_mac" ] || die "Couldn't get MAC address for ${test_prefix}" +echo "MAC address is: ${releasing_mac}" + +tcptickle_sniff_start $src_socket "${test_ip}:${test_port}" + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +# Only look for a reset from the releasing node +tcptickle_sniff_wait_show "$releasing_mac" diff --git a/ctdb/tests/CLUSTER/complex/31_nfs_tickle.sh b/ctdb/tests/CLUSTER/complex/31_nfs_tickle.sh new file mode 100755 index 0000000..e3f1540 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/31_nfs_tickle.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# Verify that NFS connections are monitored and that NFS tickles are sent. + +# We create a connection to the NFS server on a node and confirm that +# this connection is registered in the nfs-tickles/ subdirectory in +# shared storage. Then kill ctdbd on the relevant NFS server node and +# ensure that the takeover node sends an appropriate reset packet. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# * Cluster nodes must be listening on the NFS TCP port (2049). + +# Expected results: + +# * CTDB should correctly record the socket and on failover the takeover +# node should send a reset packet. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips +try_command_on_node $test_node "$CTDB listnodes | wc -l" +numnodes="$out" + +# We need this for later, so we know how long to run nc for. +ctdb_onnode "$test_node" "getvar MonitorInterval" +monitor_interval="${out#*= }" + +test_port=2049 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with netcat..." + +sleep $((monitor_interval * 4)) | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +wait_for_monitor_event $test_node + +echo "Wait until NFS connection is tracked by CTDB on test node ..." +wait_until 10 check_tickles $test_node $test_ip $test_port $src_socket + +echo "Getting TicklesUpdateInterval..." +try_command_on_node $test_node $CTDB getvar TickleUpdateInterval +update_interval="$out" + +echo "Wait until NFS connection is tracked by CTDB on all nodes..." +wait_until $(($update_interval * 2)) \ + check_tickles_all $numnodes $test_ip $test_port $src_socket + +tcptickle_sniff_start $src_socket "${test_ip}:${test_port}" + +# We need to be nasty to make that the node being failed out doesn't +# get a chance to send any tickles and confuse our sniff. IPs also +# need to be dropped because we're simulating a dead node rather than +# a CTDB failure. To properly handle a CTDB failure we would need a +# watchdog to drop the IPs when CTDB disappears. +echo "Killing ctdbd on ${test_node}..." +try_command_on_node -v $test_node "killall -9 ctdbd ; $CTDB_TEST_WRAPPER drop_ips ${test_node_ips}" + +wait_until_node_has_status $test_node disconnected + +tcptickle_sniff_wait_show diff --git a/ctdb/tests/CLUSTER/complex/32_cifs_tickle.sh b/ctdb/tests/CLUSTER/complex/32_cifs_tickle.sh new file mode 100755 index 0000000..78b8948 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/32_cifs_tickle.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Verify that CIFS connections are monitored and that CIFS tickles are sent. + +# We create a connection to the CIFS server on a node and confirm that +# this connection is registered by CTDB. Then disable the relevant CIFS +# server node and ensure that the takeover node sends an appropriate +# reset packet. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# * Clustered Samba must be listening on TCP port 445. + +# Expected results: + +# * CTDB should correctly record the connection and the takeover node +# should send a reset packet. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +# We need this for later, so we know how long to sleep. +try_command_on_node 0 $CTDB getvar MonitorInterval +monitor_interval="${out#*= }" +#echo "Monitor interval on node $test_node is $monitor_interval seconds." + +select_test_node_and_ips + +test_port=445 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with netcat..." + +sleep $((monitor_interval * 4)) | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +# This should happen as soon as connection is up... but unless we wait +# we sometimes beat the registration. +echo "Checking if CIFS connection is tracked by CTDB on test node..." +wait_until 10 check_tickles $test_node $test_ip $test_port $src_socket + +# This is almost immediate. However, it is sent between nodes +# asynchronously, so it is worth checking... +echo "Wait until CIFS connection is tracked by CTDB on all nodes..." +try_command_on_node $test_node "$CTDB listnodes | wc -l" +numnodes="$out" +wait_until 5 \ + check_tickles_all $numnodes $test_ip $test_port $src_socket +tcptickle_sniff_start $src_socket "${test_ip}:${test_port}" + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +tcptickle_sniff_wait_show diff --git a/ctdb/tests/CLUSTER/complex/33_gratuitous_arp.sh b/ctdb/tests/CLUSTER/complex/33_gratuitous_arp.sh new file mode 100755 index 0000000..7a0944f --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/33_gratuitous_arp.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# Verify that a gratuitous ARP is sent when a node is failed out. + +# We ping a public IP and lookup the MAC address in the ARP table. We +# then disable the node and check the ARP table again - the MAC address +# should have changed. This test does NOT test connectivity after the +# failover. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. Remove any entries for the chosen address from the ARP table. +# 4. Send a single ping request packet to the selected public address. +# 5. Determine the MAC address corresponding to the public address by +# checking the ARP table. +# 6. Disable the selected node. +# 7. Check the ARP table and check the MAC associated with the public +# address. + +# Expected results: + +# * When a node is disabled the MAC address associated with public +# addresses on that node should change. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Removing ${test_ip} from the local ARP table..." +ip neigh flush "$test_prefix" >/dev/null 2>&1 || true + +echo "Pinging ${test_ip}..." +ping_wrapper -q -n -c 1 $test_ip + +echo "Getting MAC address associated with ${test_ip}..." +original_mac=$(ip neigh show $test_prefix | awk '$4 == "lladdr" {print $5}') +[ -n "$original_mac" ] || die "Couldn't get MAC address for ${test_prefix}" + +echo "MAC address is: ${original_mac}" + +gratarp_sniff_start + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +gratarp_sniff_wait_show + +echo "Getting MAC address associated with ${test_ip} again..." +new_mac=$(ip neigh show $test_prefix | awk '$4 == "lladdr" {print $5}') +[ -n "$new_mac" ] || die "Couldn't get MAC address for ${test_prefix}" + +echo "MAC address is: ${new_mac}" + +if [ "$original_mac" != "$new_mac" ] ; then + echo "GOOD: MAC address changed" +else + die "BAD: MAC address did not change" +fi diff --git a/ctdb/tests/CLUSTER/complex/34_nfs_tickle_restart.sh b/ctdb/tests/CLUSTER/complex/34_nfs_tickle_restart.sh new file mode 100755 index 0000000..b81510d --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/34_nfs_tickle_restart.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# Verify that a newly started CTDB node gets updated tickle details + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Cluster nodes must be listening on the NFS TCP port (2049). + +# Steps: + +# As with 31_nfs_tickle.sh but restart a node after the tickle is +# registered. + +# Expected results: + +# * CTDB should correctly communicated tickles to new CTDB instances as +# they join the cluster. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips +try_command_on_node $test_node "$CTDB listnodes -X" +listnodes_output="$out" +numnodes=$(wc -l <<<"$listnodes_output") + +test_port=2049 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with netcat..." + +sleep 600 | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +wait_for_monitor_event $test_node + +echo "Wait until NFS connection is tracked by CTDB on test node ..." +wait_until 10 check_tickles $test_node $test_ip $test_port $src_socket + +echo "Select a node to restart ctdbd" +rn=$(awk -F'|' -v test_node=$test_node \ + '$2 != test_node { print $2 ; exit }' <<<"$listnodes_output") + +echo "Restarting CTDB on node ${rn}" +ctdb_nodes_restart "$rn" + +# In some theoretical world this is racy. In practice, the node will +# take quite a while to become healthy, so this will beat any +# assignment of IPs to the node. +echo "Setting NoIPTakeover on node ${rn}" +try_command_on_node $rn $CTDB setvar NoIPTakeover 1 + +wait_until_ready + +echo "Getting TickleUpdateInterval..." +try_command_on_node $test_node $CTDB getvar TickleUpdateInterval +update_interval="$out" + +echo "Wait until NFS connection is tracked by CTDB on all nodes..." +if ! wait_until $(($update_interval * 2)) \ + check_tickles_all $numnodes $test_ip $test_port $src_socket ; then + echo "BAD: connection not tracked on all nodes:" + echo "$out" + exit 1 +fi + +# We could go on to test whether the tickle ACK gets sent. However, +# this is tested in previous tests and the use of NoIPTakeover +# complicates things on a 2 node cluster. diff --git a/ctdb/tests/CLUSTER/complex/36_smb_reset_server.sh b/ctdb/tests/CLUSTER/complex/36_smb_reset_server.sh new file mode 100755 index 0000000..d0f3d08 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/36_smb_reset_server.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Verify that the server end of an SMB connection is correctly reset + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# * Clustered Samba must be listening on TCP port 445. + +# Expected results: + +# * CTDB should correctly record the connection and the releasing node +# should reset the server end of the connection. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +# We need this for later, so we know how long to sleep. +try_command_on_node 0 $CTDB getvar MonitorInterval +monitor_interval="${out#*= }" + +select_test_node_and_ips + +test_port=445 + +echo "Set NoIPTakeover=1 on all nodes" +try_command_on_node all $CTDB setvar NoIPTakeover 1 + +echo "Give the recovery daemon some time to reload tunables" +sleep_for 5 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with nc..." + +sleep $((monitor_interval * 4)) | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +# This should happen as soon as connection is up... but unless we wait +# we sometimes beat the registration. +echo "Waiting until SMB connection is tracked by CTDB on test node..." +wait_until 10 check_tickles $test_node $test_ip $test_port $src_socket + +# It would be nice if ss consistently used local/peer instead of src/dst +ss_filter="src ${test_ip}:${test_port} dst ${src_socket}" + +try_command_on_node $test_node \ + "ss -tn state established '${ss_filter}' | tail -n +2" +if [ -z "$out" ] ; then + echo "BAD: ss did not list the socket" + exit 1 +fi +echo "GOOD: ss lists the socket:" +cat "$outfile" + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +try_command_on_node $test_node \ + "ss -tn state established '${ss_filter}' | tail -n +2" +if [ -n "$out" ] ; then + echo "BAD: ss listed the socket after failover" + exit 1 +fi +echo "GOOD: ss no longer lists the socket" diff --git a/ctdb/tests/CLUSTER/complex/37_nfs_reset_server.sh b/ctdb/tests/CLUSTER/complex/37_nfs_reset_server.sh new file mode 100755 index 0000000..3e249f9 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/37_nfs_reset_server.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Verify that the server end of an NFS connection is correctly reset + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# * Cluster nodes must be listening on the NFS TCP port (2049). + +# Expected results: + +# * CTDB should correctly record the connection and the releasing node +# should reset the server end of the connection. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +# We need this for later, so we know how long to sleep. +try_command_on_node 0 $CTDB getvar MonitorInterval +monitor_interval="${out#*= }" + +select_test_node_and_ips + +test_port=2049 + +echo "Set NoIPTakeover=1 on all nodes" +try_command_on_node all $CTDB setvar NoIPTakeover 1 + +echo "Give the recovery daemon some time to reload tunables" +sleep_for 5 + +echo "Connecting to node ${test_node} on IP ${test_ip}:${test_port} with nc..." + +sleep $((monitor_interval * 4)) | nc $test_ip $test_port & +nc_pid=$! +ctdb_test_exit_hook_add "kill $nc_pid >/dev/null 2>&1" + +wait_until_get_src_socket "tcp" "${test_ip}:${test_port}" $nc_pid "nc" +src_socket="$out" +echo "Source socket is $src_socket" + +echo "Wait until NFS connection is tracked by CTDB on test node ..." +wait_until $((monitor_interval * 2)) \ + check_tickles $test_node $test_ip $test_port $src_socket +cat "$outfile" + +# It would be nice if ss consistently used local/peer instead of src/dst +ss_filter="src ${test_ip}:${test_port} dst ${src_socket}" + +try_command_on_node $test_node \ + "ss -tn state established '${ss_filter}' | tail -n +2" +if [ -z "$out" ] ; then + echo "BAD: ss did not list the socket" + exit 1 +fi +echo "GOOD: ss lists the socket:" +cat "$outfile" + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +try_command_on_node $test_node \ + "ss -tn state established '${ss_filter}' | tail -n +2" +if [ -n "$out" ] ; then + echo "BAD: ss listed the socket after failover" + exit 1 +fi +echo "GOOD: ss no longer lists the socket" diff --git a/ctdb/tests/CLUSTER/complex/41_failover_ping_discrete.sh b/ctdb/tests/CLUSTER/complex/41_failover_ping_discrete.sh new file mode 100755 index 0000000..539d25e --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/41_failover_ping_discrete.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Verify that it is possible to ping a public address after disabling a node. + +# We ping a public IP, disable the node hosting it and then ping the +# public IP again. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. Send a single ping request packet to the selected public address. +# 4. Disable the selected node. +# 5. Send another single ping request packet to the selected public address. + +# Expected results: + +# * When a node is disabled the public address fails over and the +# address is still pingable. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Removing ${test_ip} from the local neighbor table..." +ip neigh flush "$test_prefix" >/dev/null 2>&1 || true + +echo "Pinging ${test_ip}..." +ping_wrapper -q -n -c 1 $test_ip + +gratarp_sniff_start + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +gratarp_sniff_wait_show + +echo "Removing ${test_ip} from the local neighbor table again..." +ip neigh flush "$test_prefix" >/dev/null 2>&1 || true + +echo "Pinging ${test_ip} again..." +ping_wrapper -q -n -c 1 $test_ip diff --git a/ctdb/tests/CLUSTER/complex/42_failover_ssh_hostname.sh b/ctdb/tests/CLUSTER/complex/42_failover_ssh_hostname.sh new file mode 100755 index 0000000..233819b --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/42_failover_ssh_hostname.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# Verify that it is possible to SSH to a public address after disabling a node. + +# We SSH to a public IP and check the hostname, disable the node hosting +# it and then SSH again to confirm that the hostname has changed. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. SSH to the selected public address and run hostname. +# 4. Disable the selected node. +# 5. SSH to the selected public address again and run hostname. + +# Expected results: + +# * When a node is disabled the public address fails over and it is +# still possible to SSH to the node. The hostname should change. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Removing ${test_ip} from the local neighbor table..." +ip neigh flush "$test_prefix" >/dev/null 2>&1 || true + +echo "SSHing to ${test_ip} and running hostname..." +if ! original_hostname=$(ssh -o "StrictHostKeyChecking no" $test_ip hostname) ; then + die "Failed to get original hostname via SSH..." +fi + +echo "Hostname is: ${original_hostname}" + +gratarp_sniff_start + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +gratarp_sniff_wait_show + +echo "SSHing to ${test_ip} and running hostname (again)..." +if ! new_hostname=$(ssh -o "StrictHostKeyChecking no" $test_ip hostname) ; then + echo "Failed to get new hostname via SSH..." + echo "DEBUG:" + ip neigh show + exit 1 +fi + +echo "Hostname is: ${new_hostname}" + +if [ "$original_hostname" != "$new_hostname" ] ; then + echo "GOOD: hostname changed" +else + die "BAD: hostname did not change" +fi diff --git a/ctdb/tests/CLUSTER/complex/43_failover_nfs_basic.sh b/ctdb/tests/CLUSTER/complex/43_failover_nfs_basic.sh new file mode 100755 index 0000000..ac2cafd --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/43_failover_nfs_basic.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# Verify that a mounted NFS share is still operational after failover. + +# We mount an NFS share from a node, write a file via NFS and then +# confirm that we can correctly read the file after a failover. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. Select the 1st NFS share exported on the node. +# 4. Mount the selected NFS share. +# 5. Create a file in the NFS mount and calculate its checksum. +# 6. Disable the selected node. +# 7. Read the file and calculate its checksum. +# 8. Compare the checksums. + +# Expected results: + +# * When a node is disabled the public address fails over and it is +# possible to correctly read a file over NFS. The checksums should be +# the same before and after. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +nfs_test_setup + +echo "Create file containing random data..." +dd if=/dev/urandom of=$nfs_local_file bs=1k count=1 +original_sum=$(sum $nfs_local_file) +[ $? -eq 0 ] + +gratarp_sniff_start + +echo "Disabling node $test_node" +try_command_on_node 0 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +gratarp_sniff_wait_show + +new_sum=$(sum $nfs_local_file) +[ $? -eq 0 ] + +if [ "$original_md5" = "$new_md5" ] ; then + echo "GOOD: file contents unchanged after failover" +else + die "BAD: file contents are different after failover" +fi diff --git a/ctdb/tests/CLUSTER/complex/44_failover_nfs_oneway.sh b/ctdb/tests/CLUSTER/complex/44_failover_nfs_oneway.sh new file mode 100755 index 0000000..5c8324c --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/44_failover_nfs_oneway.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# Verify that a file created on a node is readable via NFS after a failover. + +# We write a file into an exported directory on a node, mount the NFS +# share from a node, verify that we can read the file via NFS and that +# we can still read it after a failover. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. Select the 1st NFS share exported on the node. +# 4. Write a file into exported directory on the node and calculate its +# checksum. +# 5. Mount the selected NFS share. +# 6. Read the file via the NFS mount and calculate its checksum. +# 7. Compare checksums. +# 8. Disable the selected node. +# 9. Read the file via NFS and calculate its checksum. +# 10. Compare the checksums. + +# Expected results: + +# * Checksums for the file on all 3 occasions should be the same. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +nfs_test_setup + +echo "Create file containing random data..." +local_f=$(mktemp) +ctdb_test_exit_hook_add rm -f "$local_f" +dd if=/dev/urandom of=$local_f bs=1k count=1 +local_sum=$(sum $local_f) + +scp -p "$local_f" "[${test_ip}]:${nfs_remote_file}" +try_command_on_node $test_node "chmod 644 $nfs_remote_file" + +nfs_sum=$(sum $nfs_local_file) + +if [ "$local_sum" = "$nfs_sum" ] ; then + echo "GOOD: file contents read correctly via NFS" +else + echo "BAD: file contents are different over NFS" + echo " original file: $local_sum" + echo " NFS file: $nfs_sum" + exit 1 +fi + +gratarp_sniff_start + +echo "Disabling node $test_node" +try_command_on_node 0 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled + +gratarp_sniff_wait_show + +new_sum=$(sum $nfs_local_file) +[ $? -eq 0 ] + +if [ "$nfs_sum" = "$new_sum" ] ; then + echo "GOOD: file contents unchanged after failover" +else + echo "BAD: file contents are different after failover" + echo " original file: $nfs_sum" + echo " NFS file: $new_sum" + exit 1 +fi diff --git a/ctdb/tests/CLUSTER/complex/45_failover_nfs_kill.sh b/ctdb/tests/CLUSTER/complex/45_failover_nfs_kill.sh new file mode 100755 index 0000000..2d15748 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/45_failover_nfs_kill.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Verify that a mounted NFS share is still operational after failover. + +# We mount an NFS share from a node, write a file via NFS and then +# confirm that we can correctly read the file after a failover. + +# Prerequisites: + +# * An active CTDB cluster with at least 2 nodes with public addresses. + +# * Test must be run on a real or virtual cluster rather than against +# local daemons. + +# * Test must not be run from a cluster node. + +# Steps: + +# 1. Verify that the cluster is healthy. +# 2. Select a public address and its corresponding node. +# 3. Select the 1st NFS share exported on the node. +# 4. Mount the selected NFS share. +# 5. Create a file in the NFS mount and calculate its checksum. +# 6. Kill CTDB on the selected node. +# 7. Read the file and calculate its checksum. +# 8. Compare the checksums. + +# Expected results: + +# * When a node is disabled the public address fails over and it is +# possible to correctly read a file over NFS. The checksums should be +# the same before and after. + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +nfs_test_setup + +echo "Create file containing random data..." +dd if=/dev/urandom of=$nfs_local_file bs=1k count=1 +original_sum=$(sum $nfs_local_file) +[ $? -eq 0 ] + +gratarp_sniff_start + +echo "Killing node $test_node" +try_command_on_node $test_node $CTDB getpid +pid=${out#*:} +# We need to be nasty to make that the node being failed out doesn't +# get a chance to send any tickles or doing anything else clever. IPs +# also need to be dropped because we're simulating a dead node rather +# than a CTDB failure. To properly handle a CTDB failure we would +# need a watchdog to drop the IPs when CTDB disappears. +try_command_on_node -v $test_node "kill -9 $pid ; $CTDB_TEST_WRAPPER drop_ips ${test_node_ips}" +wait_until_node_has_status $test_node disconnected + +gratarp_sniff_wait_show + +new_sum=$(sum $nfs_local_file) +[ $? -eq 0 ] + +if [ "$original_md5" = "$new_md5" ] ; then + echo "GOOD: file contents unchanged after failover" +else + die "BAD: file contents are different after failover" +fi diff --git a/ctdb/tests/CLUSTER/complex/60_rogueip_releaseip.sh b/ctdb/tests/CLUSTER/complex/60_rogueip_releaseip.sh new file mode 100755 index 0000000..efa9ef2 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/60_rogueip_releaseip.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Verify that the recovery daemon correctly handles a rogue IP + +# It should be released... + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Using $test_ip, which is onnode $test_node" + +# This test depends on being able to assign a duplicate address on a +# 2nd node. However, IPv6 guards against this and causes the test to +# fail. +case "$test_ip" in +*:*) ctdb_test_skip "This test is not supported for IPv6 addresses" ;; +esac + +get_test_ip_mask_and_iface + +echo "Finding another node that knows about $test_ip" +ctdb_get_all_pnns +other_node="" +for i in $all_pnns ; do + if [ "$i" = "$test_node" ] ; then + continue + fi + try_command_on_node $i "$CTDB ip" + n=$(awk -v ip="$test_ip" '$1 == ip { print }' "$outfile") + if [ -n "$n" ] ; then + other_node="$i" + break + fi +done +if [ -z "$other_node" ] ; then + die "Unable to find another node that knows about $test_ip" +fi + +echo "Adding $test_ip on node $other_node" +try_command_on_node $other_node "ip addr add ${test_ip}/${mask} dev ${iface}" + +rogue_ip_is_gone () +{ + local pnn="$1" + local test_ip="$2" + try_command_on_node $pnn $CTDB_TEST_WRAPPER ip_maskbits_iface $test_ip + [ -z "$out" ] +} + +echo "Waiting until rogue IP is no longer assigned..." +wait_until 30 rogue_ip_is_gone $other_node $test_ip diff --git a/ctdb/tests/CLUSTER/complex/61_rogueip_takeip.sh b/ctdb/tests/CLUSTER/complex/61_rogueip_takeip.sh new file mode 100755 index 0000000..5ee4e54 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/61_rogueip_takeip.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Verify that TAKE_IP will work for an IP that is already on an interface + +# This is a variation of simple/60_recoverd_missing_ip.sh + +. "${TEST_SCRIPTS_DIR}/cluster.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Running test against node $test_node and IP $test_ip" + +# This test puts an address on an interface and then needs to quickly +# configure that address and cause an IP takeover. However, an IPv6 +# address will be tentative for a while so "quickly" is not possible". +# When ctdb_control_takeover_ip() calls ctdb_sys_have_ip() it will +# decide that the address is not present. It then attempts a takeip, +# which can fail if the address is suddenly present because it is no +# longer tentative. +case "$test_ip" in +*:*) ctdb_test_skip "This test is not supported for IPv6 addresses" ;; +esac + +get_test_ip_mask_and_iface + +echo "Deleting IP $test_ip from all nodes" +delete_ip_from_all_nodes $test_ip +try_command_on_node -v $test_node $CTDB ipreallocate +wait_until_ips_are_on_node ! $test_node $test_ip + +try_command_on_node -v all $CTDB ip + +# The window here needs to small, to try to avoid the address being +# released. The test will still pass either way but if the first IP +# takeover run does a release then this doesn't test the code path we +# expect it to... +echo "Adding IP $test_ip to $iface and CTDB on node $test_node" +ip_cmd="ip addr add $test_ip/$mask dev $iface" +ctdb_cmd="$CTDB addip $test_ip/$mask $iface && $CTDB ipreallocate" +try_command_on_node $test_node "$ip_cmd && $ctdb_cmd" + +wait_until_ips_are_on_node $test_node $test_ip diff --git a/ctdb/tests/CLUSTER/complex/README b/ctdb/tests/CLUSTER/complex/README new file mode 100644 index 0000000..72de396 --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/README @@ -0,0 +1,2 @@ +Complex integration tests. These need a real or virtual cluster. +That is, they can not be run against local daemons. diff --git a/ctdb/tests/CLUSTER/complex/scripts/local.bash b/ctdb/tests/CLUSTER/complex/scripts/local.bash new file mode 100644 index 0000000..0ef5c0a --- /dev/null +++ b/ctdb/tests/CLUSTER/complex/scripts/local.bash @@ -0,0 +1,289 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +# Thanks/blame to Stephen Rothwell for suggesting that this can be +# done in the shell. ;-) +ipv6_to_hex () +{ + local addr="$1" + + # Replace "::" by something special. + local foo="${addr/::/:@:}" + + # Join the groups of digits together, 0-padding each group of + # digits out to 4 digits, and count the number of (non-@) groups + local out="" + local count=0 + local i + for i in $(IFS=":" ; echo $foo ) ; do + if [ "$i" = "@" ] ; then + out="${out}@" + else + out="${out}$(printf '%04x' 0x${i})" + count=$(($count + 4)) + fi + done + + # Replace '@' with correct number of zeroes + local zeroes=$(printf "%0$((32 - $count))x" 0) + echo "${out/@/${zeroes}}" +} + +####################################### + +get_src_socket () +{ + local proto="$1" + local dst_socket="$2" + local pid="$3" + local prog="$4" + + local pat="^${proto}6?[[:space:]]+[[:digit:]]+[[:space:]]+[[:digit:]]+[[:space:]]+[^[:space:]]+[[:space:]]+${dst_socket//./\\.}[[:space:]]+ESTABLISHED[[:space:]]+${pid}/${prog}[[:space:]]*\$" + out=$(netstat -tanp | + grep -E "$pat" | + awk '{ print $4 }') + + [ -n "$out" ] +} + +wait_until_get_src_socket () +{ + local proto="$1" + local dst_socket="$2" + local pid="$3" + local prog="$4" + + echo "Waiting for ${prog} to establish connection to ${dst_socket}..." + + wait_until 5 get_src_socket "$@" +} + +####################################### + +check_tickles () +{ + local node="$1" + local test_ip="$2" + local test_port="$3" + local src_socket="$4" + try_command_on_node $node ctdb gettickles $test_ip $test_port + # SRC: 10.0.2.45:49091 DST: 10.0.2.143:445 + grep -Fq "SRC: ${src_socket} " "$outfile" +} + +check_tickles_all () +{ + local numnodes="$1" + local test_ip="$2" + local test_port="$3" + local src_socket="$4" + + try_command_on_node all ctdb gettickles $test_ip $test_port + # SRC: 10.0.2.45:49091 DST: 10.0.2.143:445 + local count=$(grep -Fc "SRC: ${src_socket} " "$outfile" || true) + [ $count -eq $numnodes ] +} + + + +####################################### + +# filename will be in $tcpdump_filename, pid in $tcpdump_pid +tcpdump_start () +{ + tcpdump_filter="$1" # global + + echo "Running tcpdump..." + tcpdump_filename=$(mktemp) + ctdb_test_exit_hook_add "rm -f $tcpdump_filename" + + # The only way of being sure that tcpdump is listening is to send + # some packets that it will see. So we use dummy pings - the -U + # option to tcpdump ensures that packets are flushed to the file + # as they are captured. + local dummy_addr="127.3.2.1" + local dummy="icmp and dst host ${dummy_addr} and icmp[icmptype] == icmp-echo" + tcpdump -n -p -s 0 -e -U -w $tcpdump_filename -i any "($tcpdump_filter) or ($dummy)" & + ctdb_test_exit_hook_add "kill $! >/dev/null 2>&1" + + echo "Waiting for tcpdump output file to be ready..." + ping -q "$dummy_addr" >/dev/null 2>&1 & + ctdb_test_exit_hook_add "kill $! >/dev/null 2>&1" + + tcpdump_listen_for_dummy () + { + tcpdump -n -r $tcpdump_filename -c 1 "$dummy" >/dev/null 2>&1 + } + + wait_until 10 tcpdump_listen_for_dummy +} + +# By default, wait for 1 matching packet. +tcpdump_wait () +{ + local count="${1:-1}" + local filter="${2:-${tcpdump_filter}}" + + tcpdump_check () + { + # It would be much nicer to add "ether src + # $releasing_mac" to the filter. However, tcpdump + # does not allow MAC filtering unless an ethernet + # interface is specified with -i. It doesn't work + # with "-i any" and it doesn't work when reading from + # a file. :-( + local found + if [ -n "$releasing_mac" ] ; then + found=$(tcpdump -n -e -r "$tcpdump_filename" \ + "$filter" 2>/dev/null | + grep -c "In ${releasing_mac}") + else + found=$(tcpdump -n -e -r "$tcpdump_filename" \ + "$filter" 2>/dev/null | + wc -l) + fi + + [ $found -ge $count ] + } + + echo "Waiting for tcpdump to capture some packets..." + if ! wait_until 30 tcpdump_check ; then + echo "DEBUG AT $(date '+%F %T'):" + local i + for i in "onnode -q 0 $CTDB status" \ + "netstat -tanp" \ + "tcpdump -n -e -r $tcpdump_filename" ; do + echo "$i" + $i || true + done + return 1 + fi +} + +tcpdump_show () +{ + local filter="${1:-${tcpdump_filter}}" + + tcpdump -n -e -vv -XX -r $tcpdump_filename "$filter" 2>/dev/null +} + +tcp4tickle_sniff_start () +{ + local src="$1" + local dst="$2" + + local in="src host ${dst%:*} and tcp src port ${dst##*:} and dst host ${src%:*} and tcp dst port ${src##*:}" + local out="src host ${src%:*} and tcp src port ${src##*:} and dst host ${dst%:*} and tcp dst port ${dst##*:}" + local tickle_ack="${in} and (tcp[tcpflags] & tcp-ack != 0) and (tcp[14:2] == 1234)" # win == 1234 + local ack_ack="${out} and (tcp[tcpflags] & tcp-ack != 0)" + tcptickle_reset="${in} and tcp[tcpflags] & tcp-rst != 0" + local filter="(${tickle_ack}) or (${ack_ack}) or (${tcptickle_reset})" + + tcpdump_start "$filter" +} + +# tcp[] does not work for IPv6 (in some versions of tcpdump) +tcp6tickle_sniff_start () +{ + local src="$1" + local dst="$2" + + local in="src host ${dst%:*} and tcp src port ${dst##*:} and dst host ${src%:*} and tcp dst port ${src##*:}" + local out="src host ${src%:*} and tcp src port ${src##*:} and dst host ${dst%:*} and tcp dst port ${dst##*:}" + local tickle_ack="${in} and (ip6[53] & tcp-ack != 0) and (ip6[54:2] == 1234)" # win == 1234 + local ack_ack="${out} and (ip6[53] & tcp-ack != 0)" + tcptickle_reset="${in} and ip6[53] & tcp-rst != 0" + local filter="(${tickle_ack}) or (${ack_ack}) or (${tcptickle_reset})" + + tcpdump_start "$filter" +} + +tcptickle_sniff_start () +{ + local src="$1" + local dst="$2" + + case "${dst%:*}" in + *:*) tcp6tickle_sniff_start "$src" "$dst" ;; + *) tcp4tickle_sniff_start "$src" "$dst" ;; + esac +} + +tcptickle_sniff_wait_show () +{ + local releasing_mac="$1" # optional, used by tcpdump_wait() + + tcpdump_wait 1 "$tcptickle_reset" + + echo "GOOD: here are some TCP tickle packets:" + tcpdump_show +} + +gratarp4_sniff_start () +{ + tcpdump_start "arp host ${test_ip}" +} + +gratarp6_sniff_start () +{ + local neighbor_advertisement="icmp6 and ip6[40] == 136" + local hex=$(ipv6_to_hex "$test_ip") + local match_target="ip6[48:4] == 0x${hex:0:8} and ip6[52:4] == 0x${hex:8:8} and ip6[56:4] == 0x${hex:16:8} and ip6[60:4] == 0x${hex:24:8}" + + tcpdump_start "${neighbor_advertisement} and ${match_target}" +} + +gratarp_sniff_start () +{ + case "$test_ip" in + *:*) gratarp6_sniff_start ;; + *) gratarp4_sniff_start ;; + esac +} + +gratarp_sniff_wait_show () +{ + tcpdump_wait 2 + + echo "GOOD: this should be the some gratuitous ARPs:" + tcpdump_show +} + +ping_wrapper () +{ + case "$*" in + *:*) ping6 "$@" ;; + *) ping "$@" ;; + esac +} + +####################################### + +nfs_test_setup () +{ + select_test_node_and_ips + + nfs_first_export=$(showmount -e $test_ip | sed -n -e '2s/ .*//p') + + echo "Creating test subdirectory..." + try_command_on_node $test_node "TMPDIR=$nfs_first_export mktemp -d" + nfs_test_dir="$out" + try_command_on_node $test_node "chmod 777 $nfs_test_dir" + + nfs_mnt_d=$(mktemp -d) + nfs_local_file="${nfs_mnt_d}/${nfs_test_dir##*/}/TEST_FILE" + nfs_remote_file="${nfs_test_dir}/TEST_FILE" + + ctdb_test_exit_hook_add nfs_test_cleanup + + echo "Mounting ${test_ip}:${nfs_first_export} on ${nfs_mnt_d} ..." + mount -o timeo=1,hard,intr,vers=3 \ + "[${test_ip}]:${nfs_first_export}" ${nfs_mnt_d} +} + +nfs_test_cleanup () +{ + rm -f "$nfs_local_file" + umount -f "$nfs_mnt_d" + rmdir "$nfs_mnt_d" + onnode -q $test_node rmdir "$nfs_test_dir" +} diff --git a/ctdb/tests/INTEGRATION/database/basics.001.attach.sh b/ctdb/tests/INTEGRATION/database/basics.001.attach.sh new file mode 100755 index 0000000..1fbffc5 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/basics.001.attach.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb getdbmap' operates as expected + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node + +# test_node set by select_test_node() above +# shellcheck disable=SC2154 +ctdb_onnode -v "$test_node" getdbmap + +dbid='dbid:0x[[:xdigit:]]+' +name='name:[^[:space:]]+' +path='path:[^[:space:]]+' +opts='( (PERSISTENT|STICKY|READONLY|REPLICATED|UNHEALTHY))*' +line="${dbid} ${name} ${path}${opts}" +dbmap_pattern="^(Number of databases:[[:digit:]]+|${line})\$" + +# outfile set by ctdb_onnode() above +# shellcheck disable=SC2154 +num_db_init=$(sed -n -e '1s/.*://p' "$outfile") + +sanity_check_output $(($num_db_init + 1)) "$dbmap_pattern" + +for i in $(seq 1 5) ; do + f="attach_test_${i}.tdb" + echo "Creating test database: $f" + ctdb_onnode "$test_node" "attach ${f}" + + ctdb_onnode "$test_node" getdbmap + sanity_check_output $((num_db_init + 1)) "$dbmap_pattern" + num=$(sed -n -e '1s/^.*://p' "$outfile") + if [ "$num" = $((num_db_init + i)) ] ; then + echo "OK: correct number of additional databases" + else + ctdb_test_fail "BAD: no additional database" + fi + if awk '{print $2}' "$outfile" | grep -Fqx "name:$f" ; then + echo "OK: getdbmap knows about \"$f\"" + else + ctdb_test_fail "BAD: getdbmap does not know about \"$f\"" + fi +done diff --git a/ctdb/tests/INTEGRATION/database/basics.002.attach.sh b/ctdb/tests/INTEGRATION/database/basics.002.attach.sh new file mode 100755 index 0000000..6a5c812 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/basics.002.attach.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +# Verify that databases are attached a node joins the cluster: +# 1. Shut down CTDB on one node +# 2. Attach test databases +# 3. Check that databases are attached on all up nodes +# 4. Start CTDB on the node where it is shut down +# 5. Verify that the test databases are attached on this node +# 6. Restart one of the nodes +# 7. Verify that the test databases are attached on this node + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +###################################################################### + +try_command_on_node 0 "$CTDB listnodes -X | wc -l" +numnodes="$out" +lastnode=$(( numnodes - 1 )) + +###################################################################### + +# Confirm that the database is attached with appropriate flags +check_db_once () +{ + local pnn="$1" + local db="$2" + + try_command_on_node "$pnn" $CTDB getdbmap + if grep -qF "name:${db}" "$outfile" >/dev/null ; then + return 0 + else + return 1 + fi +} + +check_db () +{ + local pnn="$1" + local db="$2" + local flag="$3" + + local flags + + echo "Waiting until database ${db} is attached on node ${pnn}" + wait_until 10 check_db_once "$pnn" "$db" + + flags=$(awk -v db="$db" '$2 == "name:" db {print $4}' "$outfile") + if [ "$flags" = "$flag" ]; then + echo "GOOD: db ${db} attached on node ${pnn} with flag $flag" + else + echo "BAD: db ${db} attached on node ${pnn} with wrong flag" + cat "$outfile" + exit 1 + fi +} + +###################################################################### + +testdb1="test_volatile.tdb" +testdb2="test_persistent.tdb" +testdb3="test_replicated.tdb" + +test_node="0" + +echo "Shutting down node $test_node" +ctdb_nodes_stop "$test_node" +sleep 1 +wait_until_node_has_status 1 recovered +try_command_on_node -v 1 $CTDB status + +echo "Create test databases" +try_command_on_node 1 $CTDB attach "$testdb1" +try_command_on_node 1 $CTDB attach "$testdb2" persistent +try_command_on_node 1 $CTDB attach "$testdb3" replicated + +echo +echo "Checking if database is attached with correct flags" +for node in $(seq 0 $lastnode) ; do + if [ $node -ne $test_node ] ; then + check_db $node $testdb1 "" + check_db $node $testdb2 PERSISTENT + check_db $node $testdb3 REPLICATED + fi +done + +###################################################################### + +echo +echo "Start node $test_node" +ctdb_nodes_start "$test_node" +sleep 1 +wait_until_ready + +echo +echo "Checking if database is attached with correct flags" +check_db $test_node $testdb1 "" +check_db $test_node $testdb2 PERSISTENT +check_db $test_node $testdb3 REPLICATED + +###################################################################### + +echo +echo "Restarting node $test_node" +ctdb_nodes_restart "$test_node" +sleep 1 +wait_until_ready + +echo +echo "Checking if database is attached with correct flags" +check_db $test_node $testdb1 "" +check_db $test_node $testdb2 PERSISTENT +check_db $test_node $testdb3 REPLICATED diff --git a/ctdb/tests/INTEGRATION/database/basics.003.detach.sh b/ctdb/tests/INTEGRATION/database/basics.003.detach.sh new file mode 100755 index 0000000..cb44955 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/basics.003.detach.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb detach' works as expected: +# 1. Attach test databases +# 2. Detach test databases +# 3. Confirm test databases are not attached + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +###################################################################### + +try_command_on_node 0 "$CTDB listnodes -X | wc -l" +numnodes="$out" + +###################################################################### + +# Confirm that the database is attached +check_db_once () +{ + local db="$1" + + local num_db + + try_command_on_node all "$CTDB getdbmap" + num_db=$(grep -cF "name:${db}" "$outfile") || true + if [ "$num_db" -eq "$numnodes" ]; then + return 0 + else + return 1 + fi +} + +check_db () +{ + local db="$1" + + echo "Waiting until database ${db} is attached on all nodes" + wait_until 10 check_db_once "$db" +} + +# Confirm that no nodes have databases attached +check_no_db_once () +{ + local db="$1" + + local num_db + + try_command_on_node all "$CTDB getdbmap" + num_db=$(grep -cF "name:${db}" "$outfile") || true + if [ "$num_db" -eq 0 ]; then + return 0 + else + return 1 + fi +} + +check_no_db () +{ + local db="$1" + + echo "Waiting until database ${db} is detached on all nodes" + wait_until 10 check_no_db_once "$db" +} + +###################################################################### + +testdb1="detach_test1.tdb" +testdb2="detach_test2.tdb" +testdb3="detach_test3.tdb" +testdb4="detach_test4.tdb" + +echo "Create test databases" +for db in "$testdb1" "$testdb2" "$testdb3" "$testdb4" ; do + echo " $db" + try_command_on_node 0 $CTDB attach "$db" +done + +for db in "$testdb1" "$testdb2" "$testdb3" "$testdb4" ; do + check_db "$db" +done + +###################################################################### + +echo +echo "Ensuring AllowClientDBAttach=1 on all nodes" +try_command_on_node all $CTDB setvar AllowClientDBAttach 1 + +echo "Check failure detaching single test database $testdb1" +try_command_on_node 1 "! $CTDB detach $testdb1" +check_db "$testdb1" + +echo +echo "Setting AllowClientDBAttach=0 on node 0" +try_command_on_node 0 $CTDB setvar AllowClientDBAttach 0 + +echo "Check failure detaching single test database $testdb1" +try_command_on_node 1 "! $CTDB detach $testdb1" +check_db "$testdb1" + +echo +echo "Setting AllowClientDBAttach=0 on all nodes" +try_command_on_node all $CTDB setvar AllowClientDBAttach 0 + +echo "Check detaching single test database $testdb1" +try_command_on_node 1 "$CTDB detach $testdb1" +check_no_db "$testdb1" + +###################################################################### + +echo +echo "Detach multiple test databases" +echo " $testdb2, $testdb3, $testdb4" +try_command_on_node 0 $CTDB detach $testdb2 $testdb3 $testdb4 + +for db in "$testdb2" "$testdb3" "$testdb4" ; do + check_no_db "$db" +done + +###################################################################### + +echo +echo "Attach a single test database" +try_command_on_node all $CTDB setvar AllowClientDBAttach 1 +try_command_on_node 0 $CTDB attach $testdb1 +check_db "$testdb1" + +echo +echo "Write a key to database" +try_command_on_node 0 $CTDB writekey $testdb1 foo bar +try_command_on_node 0 $CTDB catdb $testdb1 +num_keys=$(sed -n -e 's/Dumped \([0-9]*\) records/\1/p' "$outfile") || true +if [ -n "$num_keys" -a $num_keys -eq 1 ]; then + echo "GOOD: Key added to database" +else + echo "BAD: Key did not get added to database" + cat "$outfile" + exit 1 +fi + +echo +echo "Detach test database" +try_command_on_node all $CTDB setvar AllowClientDBAttach 0 +try_command_on_node 0 $CTDB detach $testdb1 +check_no_db "$testdb1" + +echo +echo "Re-attach test database" +try_command_on_node all $CTDB setvar AllowClientDBAttach 1 +try_command_on_node 0 $CTDB attach $testdb1 +check_db "$testdb1" + +echo +echo "Check if the database is empty" +try_command_on_node 0 $CTDB catdb $testdb1 +num_keys=$(sed -n -e 's/Dumped \([0-9]*\) records/\1/p' "$outfile") || true +if [ -n "$num_keys" -a $num_keys -eq 0 ]; then + echo "GOOD: Database $testdb1 is empty" +else + echo "BAD: Database $testdb1 is not empty" + cat "$outfile" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/basics.004.wipe.sh b/ctdb/tests/INTEGRATION/database/basics.004.wipe.sh new file mode 100755 index 0000000..115d64c --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/basics.004.wipe.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb wipedb' can clear a persistent database: +# 1. Verify that the status on all of the ctdb nodes is 'OK'. +# 2. Create a persistent test database +# 3. Add some records to node 0 and node 1 +# 4. Run wipedb on node 0 +# 5. verify the database is empty on both node 0 and 1 + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +# 2. +test_db="persistent_test.tdb" +echo "Create persistent test database \"$test_db\"" +try_command_on_node 0 $CTDB attach "$test_db" persistent + +# 3. +# add one record to node 0 key==ABC data==ABC +echo "Store key(ABC) data(ABC) on node 0" +db_ctdb_tstore 0 "$test_db" "ABC" "ABC" + +# add one record to node 1 key==DEF data==DEF +echo "Store key(DEF) data(DEF) on node 1" +db_ctdb_tstore 1 "$test_db" "DEF" "DEF" + +# 4. +echo "Wipe database" +try_command_on_node 0 $CTDB wipedb "$test_db" + +# check that the database is wiped +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "0" ] ; then + echo "OK: Database was wiped" +else + echo "BAD: We did not end up with an empty database" + exit 1 +fi + +echo "Force a recovery" +try_command_on_node 0 $CTDB recover + +# check that the database is wiped +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "0" ] ; then + echo "OK: Database was wiped" +else + echo "BAD: We did not end up with an empty database" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/basics.010.backup_restore.sh b/ctdb/tests/INTEGRATION/database/basics.010.backup_restore.sh new file mode 100755 index 0000000..8c469d4 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/basics.010.backup_restore.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash + +# Confirm that 'ctdb restoredb' works correctly: +# 1. Create a persistent test database +# 2. Add some records to test database +# 3. Backup database +# 4. Wipe database and verify the database is empty on all nodes +# 5. Restore database and make sure all the records are restored +# 6. Make sure no recovery has been triggered + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 $CTDB status +generation=$(sed -n -e 's/^Generation:\([0-9]*\)/\1/p' "$outfile") + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +# 2. +test_db="restoredb_test.tdb" +test_dump=$(mktemp) +echo $test_dump +echo "Create persistent test database \"$test_db\"" +try_command_on_node 0 $CTDB attach "$test_db" persistent +try_command_on_node 0 $CTDB wipedb "$test_db" + +# 3. +# add 10,000 records to database +echo "Adding 10000 records to database" +( +for i in $(seq 1 10000) ; do + echo "\"key$i\" \"value$i\"" +done +) | try_command_on_node -i 0 $CTDB ptrans "$test_db" + +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "10000" ] ; then + echo "OK: Records added" +else + echo "BAD: We did not end up with 10000 records" + echo "num records = $num_records" + exit 1 +fi + +ctdb_test_exit_hook_add "rm -f $test_dump" + +# 4. +echo "Backup database" +try_command_on_node 0 $CTDB backupdb "$test_db" "$test_dump" + +# 5. +echo "Wipe database" +try_command_on_node 0 $CTDB wipedb "$test_db" + +# check that the database is restored +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "0" ] ; then + echo "OK: Database was wiped" +else + echo "BAD: We did not end up with an empty database" + echo "num records = $num_records" + exit 1 +fi + +# 6. +echo "Restore database" +try_command_on_node 0 $CTDB restoredb "$test_dump" "$test_db" + +# check that the database is restored +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "10000" ] ; then + echo "OK: Database was restored" +else + echo "BAD: We did not end up with 10000 records" + echo "num records = $num_records" + exit 1 +fi + +# 7. +wait_until_ready + +try_command_on_node 0 $CTDB status +new_generation=$(sed -n -e 's/^Generation:\([0-9]*\)/\1/p' "$outfile") + +echo "Old generation = $generation" +echo "New generation = $new_generation" + +if [ "$generation" = "$new_generation" ]; then + echo "OK: Database recovery not triggered." +else + echo "BAD: Database recovery triggered." + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/fetch.001.ring.sh b/ctdb/tests/INTEGRATION/database/fetch.001.ring.sh new file mode 100755 index 0000000..4d7d392 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/fetch.001.ring.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Run the fetch_ring test and sanity check the output + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +echo "Running fetch_ring on all $num_nodes nodes." +testprog_onnode -v -p all \ + fetch_ring -n "$num_nodes" -D "fetch_ring.tdb" -k "testkey" + +pat='^(Waiting for cluster|Fetch\[[[:digit:]]+\]: [[:digit:]]+(\.[[:digit:]]+)? msgs/sec)$' +sanity_check_output 1 "$pat" + +# Get the last line of output. +last=$(tail -n 1 "$outfile") + +# $last should look like this: +# Fetch[1]: 10670.93 msgs/sec +stuff="${last##*Fetch\[*\]: }" +mps="${stuff% msgs/sec*}" + +if [ ${mps%.*} -ge 10 ] ; then + echo "OK: $mps msgs/sec >= 10 msgs/sec" +else + echo "BAD: $mps msgs/sec < 10 msgs/sec" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/fetch.002.ring-hotkeys.sh b/ctdb/tests/INTEGRATION/database/fetch.002.ring-hotkeys.sh new file mode 100755 index 0000000..6d44253 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/fetch.002.ring-hotkeys.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash + +# Run the fetch_ring test, sanity check the output and check hot keys +# statistics + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +testdb="fetch_ring.tdb" + +ctdb_get_all_pnns +# $all_pnns is set above +# shellcheck disable=SC2154 +num_nodes=$(echo "$all_pnns" | wc -w | tr -d '[:space:]') +first=$(echo "$all_pnns" | sed -n -e '1p') + +get_key () +{ + _n="$1" + + echo "testkey${_n}" +} + +run_fetch_ring () +{ + _timelimit="$1" + _key_num="$2" + + _key=$(get_key "$_key_num") + _base_cmd="fetch_ring -n ${num_nodes} -D ${testdb}" + _cmd="${_base_cmd} -t ${_timelimit} -k ${_key}" + echo "Running \"${_cmd}\" on all $num_nodes nodes." + testprog_onnode -v -p all "$_cmd" + + _pat='^(Waiting for cluster|Fetch\[[[:digit:]]+\]: [[:digit:]]+(\.[[:digit:]]+)? msgs/sec)$' + sanity_check_output 1 "$_pat" + + # Get the last line of output. + # $outfile is set above by testprog_onnode() + # shellcheck disable=SC2154 + _last=$(tail -n 1 "$outfile") + + # $last should look like this: + # Fetch[1]: 10670.93 msgs/sec + _stuff="${_last##*Fetch\[*\]: }" + _mps="${_stuff% msgs/sec*}" + + if [ "${_mps%.*}" -ge 10 ] ; then + echo "OK: ${_mps} msgs/sec >= 10 msgs/sec" + else + ctdb_test_fail "BAD: ${_mps} msgs/sec < 10 msgs/sec" + fi +} + +check_hot_keys () +{ + _pnn="$1" + _first_key="$2" + _num_keys="$3" + + echo + echo "Checking hot keys on node ${_pnn}" + + ctdb_onnode "$_pnn" dbstatistics "$testdb" + + # Get hot keys with a non-empty key + _hotkeys=$(grep -Ex '[[:space:]]+Count:[[:digit:]]+ Key:[[:xdigit:]]+' \ + "$outfile") || true + + # Check that there are the right number of non-empty slots + if [ -z "$_hotkeys" ] ; then + _num=0 + else + _num=$(echo "$_hotkeys" | wc -l | tr -d '[:space:]') + fi + _msg="hot key slots in use = ${_num}" + if [ "$_num_keys" -ne "$_num" ] ; then + echo + cat "$outfile" + ctdb_test_fail "BAD: ${_msg} (expected ${_num_keys})" + fi + echo "GOOD: ${_msg}" + + # No hot keys? Done... + if [ "$_num" = 0 ] ; then + return + fi + + # Check that hot key counts are correctly sorted + # + # Try to be as POSIX as possible + # shellcheck disable=SC2001 + _counts=$(echo "$_hotkeys" | \ + sed -e 's|.*Count:\([[:digit:]][[:digit:]]*\).*|\1|') + _counts_sorted=$(echo "$_counts" | sort -n) + if [ "$_counts" != "$_counts_sorted" ] ; then + echo + cat "$outfile" + ctdb_test_fail "BAD: hot keys not sorted" + fi + echo "GOOD: hot key counts are correctly sorted" + + # Check that all keys are considered hot + for _j in $(seq "$_first_key" $((_first_key + _num_keys - 1))) ; do + _key=$(get_key "$_j") + _key_hex=$(printf '%s' "$_key" | \ + od -A n -t x1 | \ + tr -d '[:space:]') + if ! echo "$_hotkeys" | grep -q "Key:${_key_hex}\$" ; then + echo + cat "$outfile" + ctdb_test_fail "BAD: key \"${_key}\" is not a hot key" + fi + done + echo "GOOD: all keys are listed as hot keys" +} + +# Run fetch_ring for each of 10 keys. After each run confirm that all +# keys used so far are considered hot keys (and do other hot key +# sanity checks) on all nodes. +for i in $(seq 1 10) ; do + run_fetch_ring 5 "$i" + + for pnn in $all_pnns ; do + check_hot_keys "$pnn" 1 "$i" + done + + echo +done + +echo +echo "Resetting statistics on node ${first}" +ctdb_onnode "$first" statisticsreset + +# Ensure that only node $first has had statistics reset +for pnn in $all_pnns ; do + if [ "$pnn" = "$first" ] ; then + check_hot_keys "$pnn" 1 0 + else + check_hot_keys "$pnn" 1 10 + fi +done + +echo + +# Run fetch_ring for each of 3 new keys. After each run confirm that +# the new keys used so far are considered hot keys (and do other hot +# key sanity checks) on node $first. +# +# Note that nothing can be said about hot keys on other nodes, since +# they may be an arbitrary blend of old and new keys. +for i in $(seq 1 3) ; do + run_fetch_ring 5 $((100 + i)) + + check_hot_keys 0 101 "$i" + + echo +done diff --git a/ctdb/tests/INTEGRATION/database/readonly.001.basic.sh b/ctdb/tests/INTEGRATION/database/readonly.001.basic.sh new file mode 100755 index 0000000..aeb9740 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/readonly.001.basic.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash + +# Test support for read-only records + +# Read-only records can be activated at runtime using a ctdb command. +# If read-only records are not activated, then any attempt to fetch a +# read-only copy should be automatically upgraded to a read-write +# fetch_locked(). + +# If read-only delegations are present, then any attempt to acquire a +# read-write fetch_lock will trigger revocation of all delegations +# before the fetch_locked(). + +# 1. Create a test database and some records +# 2. Try to fetch read-only records, this should not result in any delegations +# 3. Activate read-only support +# 4. Try to fetch read-only records, this should result in delegations +# 5. Do a fetchlock and the delegations should be revoked +# 6. Try to fetch read-only records, this should result in delegations + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +###################################################################### + +# Confirm that no nodes have databases with read-only delegations +check_no_readonly () +{ + try_command_on_node all $CTDB cattdb $testdb + local ro_flags="RO_HAVE_READONLY|RO_HAVE_DELEGATIONS" + local numreadonly=$(grep -c -E "$ro_flags" "$outfile") || true + if [ $numreadonly -eq 0 ] ; then + echo "GOOD: no read-only delegations" + else + echo "BAD: there are read-only delegations" + cat "$outfile" + exit 1 + fi +} + +# Check that the test record has the correct read-only flags on the +# given nodes. The first node is the dmaster, which should know there +# are delegations but should not be flagged as having a read-only +# copy. Subsequent nodes should have a read-only copy but not know +# about any (other) delegations. +check_readonly () +{ + local dmaster="$1" ; shift + local others="$*" + + local count + + try_command_on_node $dmaster $CTDB cattdb $testdb + count=$(grep -c -E "RO_HAVE_DELEGATIONS" "$outfile") || true + if [ $count -eq 1 ] ; then + echo "GOOD: dmaster ${dmaster} has read-only delegations" + else + echo "BAD: dmaster ${dmaster} has no read-only delegations" + cat "$outfile" + exit 1 + fi + count=$(grep -c -E "RO_HAVE_READONLY" "$outfile") || true + if [ $count -ne 0 ] ; then + echo "BAD: dmaster ${dmaster} has a read-only copy" + cat "$outfile" + exit 1 + fi + + local o + for o in $others ; do + try_command_on_node $o $CTDB cattdb $testdb + count=$(grep -c -E "RO_HAVE_READONLY" "$outfile") || true + if [ $count -eq 1 ] ; then + echo "GOOD: node ${o} has a read-only copy" + else + echo "BAD: node ${o} has no read-only copy" + cat "$outfile" + exit 1 + fi + count=$(grep -c -E "RO_HAVE_DELEGATIONS" "$outfile") || true + if [ $count -ne 0 ] ; then + echo "BAD: other node ${o} has read-only delegations" + cat "$outfile" + exit 1 + fi + done +} + +###################################################################### + +echo "Get list of nodes..." +ctdb_onnode 0 "-X listnodes" +all_nodes=$(awk -F'|' '{print $2}' "$outfile") + +###################################################################### + +testdb="test.tdb" +echo "Create test database \"${testdb}\"" +try_command_on_node 0 $CTDB attach $testdb + +echo "Create some records..." +try_command_on_node all $CTDB_TEST_WRAPPER $VALGRIND update_record \ + -D ${testdb} -k testkey + +###################################################################### + +echo "Try some readonly fetches, these should all be upgraded to full fetchlocks..." +try_command_on_node all $CTDB_TEST_WRAPPER $VALGRIND fetch_readonly \ + -D ${testdb} -k testkey + +check_no_readonly + +###################################################################### + +echo "Activate read-only record support for \"$testdb\"..." +try_command_on_node all $CTDB setdbreadonly $testdb + +# Database should be tagged as READONLY +try_command_on_node 0 $CTDB getdbmap +db_details=$(awk -v db="$testdb" '$2 == foo="name:" db { print }' "$outfile") +if grep -q "READONLY" <<<"$db_details" ; then + echo "GOOD: read-only record support is enabled" +else + echo "BAD: could not activate read-only support" + echo "$db_details" + exit 1 +fi + +###################################################################### + +echo "Create 1 read-only delegation ..." +# dmaster=1 +try_command_on_node 1 $CTDB_TEST_WRAPPER $VALGRIND update_record \ + -D ${testdb} -k testkey + +# Fetch read-only to node 0 +try_command_on_node 0 $CTDB_TEST_WRAPPER $VALGRIND fetch_readonly \ + -D ${testdb} -k testkey + +check_readonly 1 0 + +###################################################################### + +echo "Verify that a fetchlock revokes read-only delegations..." +# Node 1 becomes dmaster +try_command_on_node 1 $CTDB_TEST_WRAPPER $VALGRIND update_record \ + -D ${testdb} -k testkey + +check_no_readonly + +###################################################################### + +echo "Create more read-only delegations..." +dmaster=1 +try_command_on_node $dmaster $CTDB_TEST_WRAPPER $VALGRIND update_record \ + -D ${testdb} -k testkey + +others="" +for n in $all_nodes ; do + if [ "$n" != "$dmaster" ] ; then + # Fetch read-only copy to this node + try_command_on_node $n $CTDB_TEST_WRAPPER $VALGRIND fetch_readonly \ + -D ${testdb} -k testkey + others="${others} ${n}" + fi +done + +check_readonly $dmaster $others + +###################################################################### + +echo "Verify that a recovery will revoke the delegations..." +try_command_on_node 0 $CTDB recover + +check_no_readonly diff --git a/ctdb/tests/INTEGRATION/database/recovery.001.volatile.sh b/ctdb/tests/INTEGRATION/database/recovery.001.volatile.sh new file mode 100755 index 0000000..d7aaa3b --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/recovery.001.volatile.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash + +# Test that recovery correctly handles RSNs + +# Recovery can under certain circumstances lead to old record copies +# resurrecting: Recovery selects the newest record copy purely by RSN. At +# the end of the recovery, the leader is the dmaster for all +# records in all (non-persistent) databases. And the other nodes locally +# hold the complete copy of the databases. The bug is that the recovery +# process does not increment the RSN on the leader at the end of +# the recovery. Now clients acting directly on the leader will +# directly change a record's content on the leader without migration +# and hence without RSN bump. So a subsequent recovery can not tell that +# the leader's copy is newer than the copies on the other nodes, since +# their RSN is the same. Hence, if the leader is not node 0 (or more +# precisely not the active node with the lowest node number), the recovery +# will choose copies from nodes with lower number and stick to these. + +# 1. Create a test database +# 2. Add a record with value value1 on leader +# 3. Force a recovery +# 4. Update the record with value value2 on leader +# 5. Force a recovery +# 6. Confirm that the value is value2 + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# +# Main test +# +TESTDB="rec_test.tdb" + +status=0 + +# Make sure node 0 is not the leader +echo "find out which node is leader" +ctdb_onnode 0 leader +leader="$out" +if [ "$leader" = "0" ]; then + echo "node 0 is leader, disable leader role on node 0" + # + # Note: + # It should be sufficient to run "ctdb setleaderrole off" + # on node 0 and wait for election and recovery to finish. + # But there were problems related to this in this automatic + # test, so for now use "ctdb stop" and "ctdb continue". + # + echo "stop node 0" + try_command_on_node 0 $CTDB stop + wait_until_node_has_status 0 stopped + echo "continue node 0" + try_command_on_node 0 $CTDB continue + wait_until_node_has_status 0 notstopped + + ctdb_onnode 0 leader + leader="$out" + if [ "$leader" = "0" ]; then + echo "failed to move leader to different node" + exit 1 + fi +fi + +echo "Leader:${leader}" + +# Create a temporary non-persistent database to test with +echo "create test database $TESTDB" +ctdb_onnode "$leader" attach "$TESTDB" + +# Wipe Test database +echo "wipe test database" +ctdb_onnode "$leader" wipedb "$TESTDB" + +# Add a record key=test1 data=value1 +echo "store key(test1) data(value1)" +ctdb_onnode "$leader" writekey "$TESTDB" test1 value1 + +# Fetch a record key=test1 +echo "read key(test1)" +ctdb_onnode "$leader" readkey "$TESTDB" test1 +cat "$outfile" + +# Do a recovery +echo "force recovery" +ctdb_onnode "$leader" recover + +wait_until_node_has_status "$leader" recovered + +# Add a record key=test1 data=value2 +echo "store key(test1) data(value2)" +ctdb_onnode "$leader" writekey "$TESTDB" test1 value2 + +# Fetch a record key=test1 +echo "read key(test1)" +ctdb_onnode "$leader" readkey "$TESTDB" test1 +cat "$outfile" + +# Do a recovery +echo "force recovery" +ctdb_onnode "$leader" recover + +wait_until_node_has_status "$leader" recovered + +# Verify record key=test1 +echo "read key(test1)" +ctdb_onnode "$leader" readkey "$TESTDB" test1 +cat "$outfile" +if [ "$out" = "Data: size:6 ptr:[value2]" ]; then + echo "GOOD: Recovery did not corrupt database" +else + echo "BAD: Recovery corrupted database" + status=1 +fi + +exit $status diff --git a/ctdb/tests/INTEGRATION/database/recovery.002.large.sh b/ctdb/tests/INTEGRATION/database/recovery.002.large.sh new file mode 100755 index 0000000..4736071 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/recovery.002.large.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +# Test recovery of large volatile and persistent databases + +# Recovery now uses DB_PULL and DB_PUSH_START/DB_PUSH_CONFIRM +# controls. This sends the records in batches of ~RecBufferSizeLimit +# in size at a time. Test that large databases are re-assembled +# correctly. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# +# Main test +# +TEST1DB="large_persistent_db.tdb" +TEST2DB="large_volatile_db.tdb" +RECDATA=$(onnode 0 mktemp) + +# Create a persistent database to test +echo "create persistent test database $TEST1DB" +try_command_on_node 0 $CTDB attach $TEST1DB persistent + +# Wipe Test database +echo "wipe test database $TEST1DB" +try_command_on_node 0 $CTDB wipedb $TEST1DB + +# Create dummy record data +echo "creating dummy record data" +onnode 0 dd if=/dev/urandom of=$RECDATA bs=10K count=1 + +# Add 345 records +echo "Adding 345 records" +for i in $(seq 1 345) ; do + try_command_on_node 0 $CTDB pstore $TEST1DB record$i $RECDATA || exit 1 +done + +num_records=$(db_ctdb_cattdb_count_records 0 $TEST1DB) +if [ $num_records = "345" ] ; then + echo "OK: records added correctly" +else + echo "BAD: persistent database has $num_records of 345 records" + try_command_on_node -v 0 "$CTDB cattdb $TEST1DB | tail -n 1" + exit 1 +fi + +# Create a volatile database to test +echo "create volatile test database $TEST2DB" +try_command_on_node 0 $CTDB attach $TEST2DB + +# Wipe Test database +echo "wipe test database $TEST2DB" +try_command_on_node 0 $CTDB wipedb $TEST2DB + +# Create dummy record data +v1="1234567890" +v2="$v1$v1$v1$v1$v1$v1$v1$v1$v1$v1" +v3="$v2$v2$v2$v2$v2$v2$v2$v2$v2$v2" + +# Add 1234 records +echo "Adding 1234 records" +for i in $(seq 1 1234) ; do + try_command_on_node 0 $CTDB writekey $TEST2DB record$i $v3 || exit 1 +done + +num_records=$(db_ctdb_cattdb_count_records 0 $TEST2DB) +if [ $num_records = "1234" ] ; then + echo "OK: records added correctly" +else + echo "BAD: volatile database has $num_records of 1234 records" + try_command_on_node -v 0 "$CTDB cattdb $TEST2DB | tail -n 1" + exit 1 +fi + +echo +leader_get 0 +# Set RecBufferSizeLimit to 10000 +ctdb_onnode "$leader" setvar RecBufferSizeLimit 10000 + +# Do a recovery +echo "force recovery" +try_command_on_node 0 $CTDB recover + +wait_until_node_has_status 0 recovered 30 + +# check that there are correct number of records +num_records=$(db_ctdb_cattdb_count_records 0 $TEST1DB) +if [ $num_records = "345" ] ; then + echo "OK: persistent database recovered correctly" +else + echo "BAD: persistent database has $num_records of 345 records" + try_command_on_node -v 0 "$CTDB cattdb $TEST1DB | tail -n 1" + exit 1 +fi + +num_records=$(db_ctdb_cattdb_count_records 0 $TEST2DB) +if [ $num_records = "1234" ] ; then + echo "OK: volatile database recovered correctly" +else + echo "BAD: volatile database has $num_records of 1234 records" + try_command_on_node -v 0 "$CTDB cattdb $TEST2DB | tail -n 1" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/recovery.003.no_resurrect.sh b/ctdb/tests/INTEGRATION/database/recovery.003.no_resurrect.sh new file mode 100755 index 0000000..b314d4d --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/recovery.003.no_resurrect.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +# Ensure recovery doesn't resurrect deleted records from recently +# inactive nodes + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +testdb="rec_test.tdb" + +echo "Getting list of nodes..." +ctdb_get_all_pnns + +first=$(echo "$all_pnns" | sed -n -e '1p') +second=$(echo "$all_pnns" | sed -n -e '2p') +notfirst=$(echo "$all_pnns" | tail -n +2) + +echo "Create/wipe test database ${testdb}" +try_command_on_node $first $CTDB attach "$testdb" +try_command_on_node $first $CTDB wipedb "$testdb" + +echo "store key(test1) data(value1)" +try_command_on_node $first $CTDB writekey "$testdb" test1 value1 + +echo "Migrate key(test1) to all nodes" +try_command_on_node all $CTDB readkey "$testdb" test1 + +echo "Stop node ${first}" +try_command_on_node $first $CTDB stop +wait_until_node_has_status $first stopped + +echo "Delete key(test1)" +try_command_on_node $second $CTDB deletekey "$testdb" test1 + +database_has_zero_records () +{ + # shellcheck disable=SC2086 + # $notfirst can be multi-word + check_cattdb_num_records "$testdb" 0 "$notfirst" +} + +echo "Trigger a recovery" +try_command_on_node "$second" $CTDB recover + +echo "Checking that database has 0 records" +database_has_zero_records + +echo "Continue node ${first}" +try_command_on_node $first $CTDB continue +wait_until_node_has_status $first notstopped + +echo "Get database contents" +try_command_on_node -v $first $CTDB catdb "$testdb" + +if grep -q '^key(' "$outfile" ; then + echo "BAD: Deleted record has been resurrected" + exit 1 +fi + +echo "GOOD: Deleted record is still gone" diff --git a/ctdb/tests/INTEGRATION/database/recovery.010.persistent.sh b/ctdb/tests/INTEGRATION/database/recovery.010.persistent.sh new file mode 100755 index 0000000..d13a9a5 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/recovery.010.persistent.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash + +# Ensure that persistent databases are correctly recovered by database +# sequence number +# +# 1. Create and wipe a persistent test database +# 2. Directly add a single record to the database on each node +# 3. Trigger a recover +# 4. Ensure that the database contains only a single record +# +# Repeat but with sequence numbers set by hand on each node + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +add_record_per_node () +{ + _i=0 + while [ $_i -lt $num_nodes ] ; do + _k="KEY${_i}" + _d="DATA${_i}" + echo "Store key(${_k}) data(${_d}) on node ${_i}" + db_ctdb_tstore $_i "$test_db" "$_k" "$_d" + _i=$(($_i + 1)) + done +} + +test_db="persistent_test.tdb" +echo "Create persistent test database \"$test_db\"" +try_command_on_node 0 $CTDB attach "$test_db" persistent + +# 3, +# If no __db_sequence_number__ recover whole database +# + +echo +echo "Test that no __db_sequence_number__ does not blend the database during recovery" + +# wipe database +echo "Wipe the test database" +try_command_on_node 0 $CTDB wipedb "$test_db" + +add_record_per_node + +# force a recovery +echo force a recovery +try_command_on_node 0 $CTDB recover + +# Check that we now have 1 record on node 0 +num_records=$(db_ctdb_cattdb_count_records 0 "$test_db") +if [ $num_records = "1" ] ; then + echo "OK: databases were not blended" +else + echo "BAD: we did not end up with the expected single record after the recovery" + exit 1 +fi + + +# 4, +# If __db_sequence_number__ recover whole database +# + +echo +echo test that __db_sequence_number__ does not blend the database during recovery + +# wipe database +echo wipe the test database +try_command_on_node 0 $CTDB wipedb persistent_test.tdb + +add_record_per_node + +echo "Add __db_sequence_number__==5 record to all nodes" +pnn=0 +while [ $pnn -lt $num_nodes ] ; do + db_ctdb_tstore_dbseqnum $pnn "$test_db" 5 + pnn=$(($pnn + 1)) +done + +echo "Set __db_sequence_number__ to 7 on node 0" +db_ctdb_tstore_dbseqnum 0 "$test_db" 7 + +echo "Set __db_sequence_number__ to 8 on node 1" +db_ctdb_tstore_dbseqnum 1 "$test_db" 8 + + +# force a recovery +echo force a recovery +try_command_on_node 0 $CTDB recover + +# check that we now have both records on node 0 +num_records=$(db_ctdb_cattdb_count_records 0 "$test_db") +if [ $num_records = "1" ] ; then + echo "OK: databases were not blended" +else + echo "BAD: we did not end up with the expected single record after the recovery" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/database/recovery.011.continue.sh b/ctdb/tests/INTEGRATION/database/recovery.011.continue.sh new file mode 100755 index 0000000..995b282 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/recovery.011.continue.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +# Confirm that the deleted records are not resurrected after recovery +# +# 1. Create a persistent database +# 2. Add a record and update it few times. +# 3. Delete the record +# 4. Use "ctdb stop" to stop one of the nodes +# 5. Add a record with same key. +# 6. Continue on the stopped node +# 7. Confirm that the record still exists + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +do_test() +{ +# Wipe Test database +echo "wipe test database" +try_command_on_node 0 $CTDB wipedb $TESTDB + +# Add a record key=test1 data=value1 +# and update values +for value in value1 value2 value3 value4 value5 ; do + echo "store key(test1) data($value)" + echo "\"test1\" \"$value\"" | try_command_on_node -i 0 $CTDB ptrans "$TESTDB" +done + +# Delete record +echo "delete key(test1)" +try_command_on_node 0 $CTDB pdelete $TESTDB test1 + +# Stop a node +echo "stop node 1" +try_command_on_node 1 $CTDB stop + +wait_until_node_has_status 1 stopped + +# Add a record key=test1 data=value2 +echo "store key(test1) data(newvalue1)" +echo '"test1" "newvalue1"' | try_command_on_node -i 0 $CTDB ptrans "$TESTDB" + +# Continue node +echo "continue node 1" +try_command_on_node 1 $CTDB continue + +wait_until_node_has_status 1 notstopped + +} + +# +# Main test +# +TESTDB="persistent_test.tdb" + +status=0 + +# Create a temporary persistent database to test with +echo "create persistent test database $TESTDB" +try_command_on_node 0 $CTDB attach $TESTDB persistent + +do_test +if try_command_on_node 0 $CTDB pfetch $TESTDB test1 ; then + echo "GOOD: Record was not deleted (recovery by sequence number worked)" +else + echo "BAD: Record was deleted" + status=1 +fi + +exit $status diff --git a/ctdb/tests/INTEGRATION/database/scripts/local.bash b/ctdb/tests/INTEGRATION/database/scripts/local.bash new file mode 100644 index 0000000..ae2e0d5 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/scripts/local.bash @@ -0,0 +1,116 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +check_cattdb_num_records () +{ + local db="$1" + local num="$2" + local nodes="$3" + + # $nodes has embedded newlines - put list on 1 line for printing + local t + t=$(echo "$nodes" | xargs) + echo "Confirm that ${db} has ${num} record(s) on node(s): ${t}" + + local ret=0 + local node + for node in $nodes ; do + local num_found + + num_found=$(db_ctdb_cattdb_count_records "$node" "$db") + if [ "$num_found" = "$num" ] ; then + continue + fi + + printf 'BAD: %s on node %d has %d record(s), expected %d\n' \ + "$db" "$node" "$num_found" "$num" + ctdb_onnode -v "$node" "cattdb $db" + ret=1 + done + + return $ret +} + +_key_dmaster_check () +{ + local node="$1" + local db="$2" + local key="$3" + local dmaster="${4:-${node}}" + + testprog_onnode "$node" "ctdb-db-test local-read ${db} ${key}" + + # shellcheck disable=SC2154 + # $outfile is set above by try_command_on_node() + grep -Fqx "dmaster: ${dmaster}" "$outfile" +} + +_key_dmaster_fail () +{ + local dmaster="$1" + + echo "BAD: node ${dmaster} is not dmaster" + # shellcheck disable=SC2154 + # $outfile is set by the caller via _key_dmaster_check() + cat "$outfile" + ctdb_test_fail +} + +vacuum_test_key_dmaster () +{ + local node="$1" + local db="$2" + local key="$3" + local dmaster="${4:-${node}}" + + if ! _key_dmaster_check "$node" "$db" "$key" "$dmaster" ; then + _key_dmaster_fail "$dmaster" + fi +} + +vacuum_test_wait_key_dmaster () +{ + local node="$1" + local db="$2" + local key="$3" + local dmaster="${4:-${node}}" + + if ! wait_until 30 \ + _key_dmaster_check "$node" "$db" "$key" "$dmaster" ; then + _key_dmaster_fail "$dmaster" + fi +} + +vacuum_confirm_key_empty_dmaster () +{ + local node="$1" + local db="$2" + local key="$3" + local dmaster="${4:-${node}}" + + echo "Confirm record key=\"${key}\" is empty and dmaster=${dmaster}" + + vacuum_test_key_dmaster "$node" "$db" "$key" "$dmaster" + + if ! grep -Fqx 'data(0) = ""' "$outfile" ; then + echo "BAD: record not empty" + cat "$outfile" + ctdb_test_fail + fi +} + +db_confirm_key_has_value () +{ + local node="$1" + local db="$2" + local key="$3" + local val="$4" + + local out + + ctdb_onnode "$node" "readkey ${db} ${key}" + outv=$(echo "$out" | sed -n 's|^Data: size:.* ptr:\[\(.*\)\]$|\1|p') + if [ "$val" != "$outv" ] ; then + ctdb_test_fail \ + "BAD: value for \"${key}\"=\"${outv}\" (not \"${val}\")" + fi +} diff --git a/ctdb/tests/INTEGRATION/database/transaction.001.ptrans.sh b/ctdb/tests/INTEGRATION/database/transaction.001.ptrans.sh new file mode 100755 index 0000000..556e523 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/transaction.001.ptrans.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash + +# Verify that the 'ctdb ptrans' works as expected +# +# Pipe some operation to ctdb ptrans and validate the TDB contents +# with ctdb catdb + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +TESTDB="ptrans_test.tdb" + +# Create a temporary persistent database to test with +echo "create persistent test database $TESTDB" +try_command_on_node 0 $CTDB attach $TESTDB persistent + +# Wipe Test database +echo "wipe test database" +try_command_on_node 0 $CTDB wipedb $TESTDB + +########## + +echo "Adding 3 records" + +items=' +"key1" "value1" +"key2" "value1" +"key3" "value1"' + +echo "$items" | try_command_on_node -i 0 $CTDB ptrans "$TESTDB" + +try_command_on_node 0 $CTDB catdb "$TESTDB" + +n=$(grep -c '^key.*= "key.*"' "$outfile" || true) + +if [ $n -ne 3 ] ; then + echo "BAD: expected 3 keys in..." + cat "$outfile" + exit 1 +else + echo "GOOD: 3 records were inserted" +fi + +########## + +echo "Deleting 1 record, updating 1, adding 1 new record, 1 bogus input line" + +items=' +"key1" "" +"key2" "value2" +"key3" +"key4" "value1"' + +echo "$items" | try_command_on_node -i 0 $CTDB ptrans "$TESTDB" + +try_command_on_node 0 $CTDB catdb "$TESTDB" + +n=$(grep -c '^key.*= "key.*"' "$outfile" || true) + +if [ $n -ne 3 ] ; then + echo "BAD: expected 3 keys in..." + cat "$outfile" + exit 1 +else + echo "GOOD: 3 records found" +fi + +########## + +echo "Verifying records" + +while read key value ; do + try_command_on_node 0 $CTDB pfetch "$TESTDB" "$key" + if [ "$value" != "$out" ] ; then + echo "BAD: for key \"$key\" expected \"$value\" but got \"$out\"" + exit 1 + else + echo "GOOD: for key \"$key\" got \"$out\"" + fi +done </dev/null & + RECLOOP_PID=$! + ctdb_test_exit_hook_add "kill $RECLOOP_PID >/dev/null 2>&1" +} + +TESTDB="persistent_trans.tdb" + +try_command_on_node 0 "$CTDB attach $TESTDB persistent" +try_command_on_node 0 "$CTDB wipedb $TESTDB" + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +if [ -z "$CTDB_TEST_TIMELIMIT" ] ; then + CTDB_TEST_TIMELIMIT=30 +fi + +t="$CTDB_TEST_WRAPPER $VALGRIND transaction_loop \ + -n ${num_nodes} -t ${CTDB_TEST_TIMELIMIT} \ + -D ${TESTDB} -T persistent -k testkey" + +echo "Starting recovery loop" +recovery_loop_start + +echo "Running transaction_loop on all $num_nodes nodes." +try_command_on_node -v -p all "$t" diff --git a/ctdb/tests/INTEGRATION/database/transaction.004.update_record.sh b/ctdb/tests/INTEGRATION/database/transaction.004.update_record.sh new file mode 100755 index 0000000..528303a --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/transaction.004.update_record.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# Verify that "ctdb update_record_persistent" creates new records and +# updates existing records in a persistent database +# +# 1. Create and wipe a persistent test database +# 2. Do a recovery +# 3. Confirm that the database is empty +# 4. Create a new record using "ctdb update_record_persistent" +# 5. Confirm the record exists in the database using "ctdb cattdb" +# 6. Update the record's value using "ctdb update_record_persistent" +# 7. Confirm that the original value no longer exists using "ctdb cattdb" + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +test_db="persistent_test.tdb" + +# create a temporary persistent database to test with +echo "Create persistent test database \"$test_db\"" +try_command_on_node 0 $CTDB attach "$test_db" persistent + + +# 3. +echo "Wipe the persistent test database" +try_command_on_node 0 $CTDB wipedb "$test_db" +echo "Force a recovery" +try_command_on_node 0 $CTDB recover + +# check that the database is wiped +num_records=$(db_ctdb_cattdb_count_records 1 "$test_db") +if [ $num_records = "0" ] ; then + echo "OK: database was wiped" +else + echo "BAD: we did not end up with an empty database" + exit 1 +fi + +# 4. +echo "Create a new record in the persistent database using UPDATE_RECORD" +try_command_on_node 0 $CTDB_TEST_WRAPPER $VALGRIND update_record_persistent \ + -D "$test_db" -k "Update_Record_Persistent" -v "FirstValue" + +try_command_on_node 0 "$CTDB cattdb "$test_db" | grep 'FirstValue' | wc -l" +if [ "$out" = 1 ] ; then + echo "GOOD: we did not find the record after the create/update" +else + echo "BAD: we did find the record after the create/update" + exit 1 +fi + +# 5. +echo Modify an existing record in the persistent database using UPDATE_RECORD +try_command_on_node 0 $CTDB_TEST_WRAPPER $VALGRIND update_record_persistent \ + -D "$test_db" -k "Update_Record_Persistent" -v "SecondValue" + +try_command_on_node 0 "$CTDB cattdb "$test_db" | grep 'FirstValue' | wc -l" +if [ "$out" = 0 ] ; then + echo "GOOD: did not find old record after the modify/update" +else + echo "BAD: we still found the old record after the modify/update" + exit 1 +fi + +try_command_on_node 0 "$CTDB cattdb "$test_db" | grep 'SecondValue' | wc -l" +if [ "$out" = 1 ] ; then + echo "GOOD: found the record after the modify/update" +else + echo "BAD: could not find the record after the modify/update" + exit 1 +fi + +echo "Wipe the persistent test databases and clean up" +try_command_on_node 0 $CTDB wipedb "$test_db" diff --git a/ctdb/tests/INTEGRATION/database/transaction.010.loop_recovery.sh b/ctdb/tests/INTEGRATION/database/transaction.010.loop_recovery.sh new file mode 100755 index 0000000..9de6c34 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/transaction.010.loop_recovery.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +# Verify that the transaction_loop test succeeds with recoveries for +# replicated databases + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +recovery_loop() +{ + local COUNT=1 + + while true ; do + echo Recovery $COUNT + try_command_on_node 0 $CTDB recover + sleep 2 + COUNT=$((COUNT + 1)) + done +} + +recovery_loop_start() +{ + recovery_loop >/dev/null & + RECLOOP_PID=$! + ctdb_test_exit_hook_add "kill $RECLOOP_PID >/dev/null 2>&1" +} + +TESTDB="replicated_trans.tdb" + +try_command_on_node 0 "$CTDB attach $TESTDB replicated" +try_command_on_node 0 "$CTDB wipedb $TESTDB" + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +if [ -z "$CTDB_TEST_TIMELIMIT" ] ; then + CTDB_TEST_TIMELIMIT=30 +fi + +t="$CTDB_TEST_WRAPPER $VALGRIND transaction_loop \ + -n ${num_nodes} -t ${CTDB_TEST_TIMELIMIT} \ + -D ${TESTDB} -T replicated -k testkey" + +echo "Starting recovery loop" +recovery_loop_start + +echo "Running transaction_loop on all $num_nodes nodes." +try_command_on_node -v -p all "$t" diff --git a/ctdb/tests/INTEGRATION/database/traverse.001.one.sh b/ctdb/tests/INTEGRATION/database/traverse.001.one.sh new file mode 100755 index 0000000..1b3b7c2 --- /dev/null +++ b/ctdb/tests/INTEGRATION/database/traverse.001.one.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +# Confirm that traverses of volatile databases work as expected + +# This is a very simple example. It writes a single record, updates it +# on another node and then confirms that the correct value is found when +# traversing. It then repeats this after removing the LMASTER role from +# the node where the value is updated. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# +# Main test +# +TESTDB="traverse_db.tdb" + +echo "create volatile test database $TESTDB" +try_command_on_node 0 $CTDB attach "$TESTDB" + +echo "wipe test database $TESTDB" +try_command_on_node 0 $CTDB wipedb "$TESTDB" + +echo "write foo=bar0 on node 0" +try_command_on_node 0 $CTDB writekey "$TESTDB" "foo" "bar0" + +echo "write foo=bar1 on node 1" +try_command_on_node 1 $CTDB writekey "$TESTDB" "foo" "bar1" + +echo + +check_db_num_records () +{ + local node="$1" + local db="$2" + local n="$3" + + echo "Checking on node ${node} to ensure ${db} has ${n} records..." + try_command_on_node "$node" "${CTDB} catdb ${db}" + + num=$(sed -n -e 's|^Dumped \(.*\) records$|\1|p' "$outfile") + if [ "$num" = "$n" ] ; then + echo "OK: Number of records=${num}" + echo + else + echo "BAD: There were ${num} (!= ${n}) records" + cat "$outfile" + exit 1 + fi +} + +check_db_num_records 0 "$TESTDB" 1 +check_db_num_records 1 "$TESTDB" 1 + +cat </dev/null 2>&1 || true" +} +ctdb_test_exit_hook_add restore_public_addresses + +# ctdb reloadips will fail if it can't disable takover runs. The most +# likely reason for this is that there is already a takeover run in +# progress. We can't predict when this will happen, so retry if this +# occurs. +do_ctdb_reloadips () +{ + local retry_max=10 + local retry_count=0 + while : ; do + if ctdb_onnode "$test_node" "reloadips all" ; then + return 0 + fi + + if [ "$out" != "Failed to disable takeover runs" ] ; then + return 1 + fi + + if [ $retry_count -ge $retry_max ] ; then + return 1 + fi + + retry_count=$((retry_count + 1)) + echo "Retrying..." + sleep_for 1 + done +} + + +echo "Removing IP $test_ip from node $test_node" + +try_command_on_node $test_node "mv $addresses $backup && grep -v '^${test_ip}/' $backup >$addresses" + +do_ctdb_reloadips + +try_command_on_node $test_node $CTDB ip + +if grep "^${test_ip} " <<<"$out" ; then + cat < 1 { print $2 }' "$outfile") +echo "Node ${test_node} has interfaces: ${ifaces}" + +# Delete all IPs on each interface... deleting IPs from one interface +# can cause other interfaces to disappear, so we need to be careful... +for i in $ifaces ; do + try_command_on_node $test_node "$CTDB ifaces -X" + info=$(awk -F'|' -v iface="$i" '$2 == iface { print $0 }' "$outfile") + + if [ -z "$info" ] ; then + echo "Interface ${i} missing... assuming already deleted!" + continue + fi + + echo "Deleting IPs on interface ${i}, with this information:" + echo " $info" + + try_command_on_node $test_node "$CTDB ip -v -X | tail -n +2" + awk -F'|' -v i="$i" \ + '$6 == i { print $2 }' "$outfile" | + while read ip ; do + echo " $ip" + try_command_on_node $test_node "$CTDB delip $ip" + done + try_command_on_node $test_node "$CTDB ipreallocate" + + try_command_on_node $test_node "$CTDB ifaces -X" + info=$(awk -F'|' -v iface="$i" '$2 == iface { print $0 }' "$outfile") + + if [ -z "$info" ] ; then + echo "GOOD: Interface ${i} has been garbage collected" + else + echo "BAD: Interface ${i} still exists" + echo "$out" + exit 1 + fi +done diff --git a/ctdb/tests/INTEGRATION/failover/pubips.020.moveip.sh b/ctdb/tests/INTEGRATION/failover/pubips.020.moveip.sh new file mode 100755 index 0000000..8daf3f5 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.020.moveip.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb moveip' allows movement of public IPs between nodes + +# This test does not do any network level checks to make sure IP +# addresses are actually on interfaces. It just consults "ctdb ip". + +# To work, this test ensures that IPAllocAlgorithm is not set to 0 +# (Deterministic IPs) and sets NoIPFailback. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +sanity_check_ips () +{ + echo "Sanity checking IPs..." + + local x ipp prev + prev="" + while read x ipp ; do + [ "$ipp" = "-1" ] && break + if [ -n "$prev" -a "$ipp" != "$prev" ] ; then + echo "OK" + return 0 + fi + prev="$ipp" + done <"$outfile" + + echo "BAD: a node was -1 or IPs are only assigned to one node:" + cat "$outfile" + echo "Are you running an old version of CTDB?" + return 1 +} + +sanity_check_ips + +# Find a target node - it must be willing to host $test_ip + +# $test_node set above by select_test_node_and_ips() +# shellcheck disable=SC2154 +try_command_on_node "$test_node" "$CTDB listnodes | wc -l" +num_nodes="$out" +to_node="" +for i in $(seq 0 $(($num_nodes - 1)) ) ; do + [ $i -ne $test_node ] || continue + all_ips_on_node $i + while read ip x ; do + if [ "$ip" = "$test_ip" ] ; then + to_node="$i" + break 2 + fi + done <"$outfile" +done + +if [ -z "$to_node" ] ; then + echo "Unable to find target node" + exit 1 +fi + +echo "Target node is ${to_node}" + +echo "Setting IPAllocAlgorithm=2 to avoid Deterministic IPs..." +try_command_on_node -q all $CTDB setvar IPAllocAlgorithm 2 + +echo "Turning on NoIPFailback..." +try_command_on_node -q all $CTDB setvar NoIPFailback 1 + +echo "Attempting to move ${test_ip} from node ${test_node} to node ${to_node}" +try_command_on_node $test_node $CTDB moveip $test_ip $to_node +wait_until_ips_are_on_node '!' $test_node $test_ip +wait_until_ips_are_on_node $to_node $test_ip diff --git a/ctdb/tests/INTEGRATION/failover/pubips.030.disable_enable.sh b/ctdb/tests/INTEGRATION/failover/pubips.030.disable_enable.sh new file mode 100755 index 0000000..3f40097 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.030.disable_enable.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Verify the operation of "ctdb disable" and "ctdb enable" + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +######################################## + +select_test_node_and_ips + +echo "Disabling node $test_node" +try_command_on_node 1 $CTDB disable -n $test_node +wait_until_node_has_status $test_node disabled 30 all +wait_until_node_has_no_ips "$test_node" + +echo "Re-enabling node $test_node" +try_command_on_node 1 $CTDB enable -n $test_node +wait_until_node_has_status $test_node enabled 30 all +wait_until_node_has_some_ips "$test_node" diff --git a/ctdb/tests/INTEGRATION/failover/pubips.032.stop_continue.sh b/ctdb/tests/INTEGRATION/failover/pubips.032.stop_continue.sh new file mode 100755 index 0000000..f5936b0 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.032.stop_continue.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# Verify the operation of "ctdb stop" and "ctdb continue" + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Stopping node ${test_node}..." +try_command_on_node 1 $CTDB stop -n $test_node +wait_until_node_has_status $test_node stopped +wait_until_node_has_no_ips "$test_node" + +echo "Continuing node $test_node" +try_command_on_node 1 $CTDB continue -n $test_node +wait_until_node_has_status $test_node notstopped +wait_until_node_has_some_ips "$test_node" diff --git a/ctdb/tests/INTEGRATION/failover/pubips.040.NoIPTakeover.sh b/ctdb/tests/INTEGRATION/failover/pubips.040.NoIPTakeover.sh new file mode 100755 index 0000000..e99a265 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.040.NoIPTakeover.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb setvar NoIPTakeover 1' stops IP addresses being taken over + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +ctdb_get_all_pnns +# out is set above +# shellcheck disable=SC2154 +num_nodes=$(echo "$out" | wc -l | tr -d '[:space:]') +echo "There are $num_nodes nodes..." + +if [ "$num_nodes" -lt 2 ] ; then + echo "Less than 2 nodes!" + exit 1 +fi + +select_test_node_and_ips + + +# sets: num +count_ips_on_node () +{ + local node="$1" + + ctdb_onnode "$node" ip + # outfile is set by ctdb_onnode() above + # shellcheck disable=SC2154,SC2126 + # * || true is needed to avoid command failure when there are no matches + # * Using "wc -l | tr -d '[:space:]'" is our standard + # pattern... and "grep -c" requires handling of special case + # for no match + num=$(grep -v 'Public' "$outfile" | \ + grep " ${node}\$" | \ + wc -l | \ + tr -d '[:space:]') + echo "Number of addresses on node ${node}: ${num}" +} + + +# test_node is set by select_test_node_and_ips() above +# shellcheck disable=SC2154 +count_ips_on_node "$test_node" + +echo "Turning on NoIPTakeover on all nodes" +ctdb_onnode all "setvar NoIPTakeover 1" +ctdb_onnode "$test_node" ipreallocate + +echo "Disable node ${test_node}" +ctdb_onnode "$test_node" disable + +count_ips_on_node "$test_node" +if [ "$num" != "0" ] ; then + test_fail "BAD: node 1 still hosts IP addresses" +fi + + +echo "Enable node 1 again" +ctdb_onnode "$test_node" enable + +count_ips_on_node "$test_node" +if [ "$num" != "0" ] ; then + test_fail "BAD: node 1 took over IP addresses" +fi + + +echo "OK: IP addresses were not taken over" diff --git a/ctdb/tests/INTEGRATION/failover/pubips.050.missing_ip.sh b/ctdb/tests/INTEGRATION/failover/pubips.050.missing_ip.sh new file mode 100755 index 0000000..543f9a9 --- /dev/null +++ b/ctdb/tests/INTEGRATION/failover/pubips.050.missing_ip.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +# Verify that the recovery daemon handles unhosted IPs properly + +# This test does not do any network level checks to make sure the IP +# address is actually on an interface. It just consults "ctdb ip". + +# This is a variation of the "addip" test. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node_and_ips + +echo "Running test against node $test_node and IP $test_ip" + +get_test_ip_mask_and_iface + +echo "Deleting IP $test_ip from all nodes" +delete_ip_from_all_nodes $test_ip +try_command_on_node -v $test_node $CTDB ipreallocate +wait_until_ips_are_on_node ! $test_node $test_ip + +try_command_on_node -v all $CTDB ip + +my_exit_hook () +{ + if ctdb_test_on_cluster ; then + onnode -q all $CTDB event script enable legacy "10.interface" + fi +} + +ctdb_test_exit_hook_add my_exit_hook + +# This forces us to wait until the ipreallocated associated with the +# delips is complete. +try_command_on_node $test_node $CTDB sync + +# Wait for a monitor event. Then the next steps are unlikely to occur +# in the middle of a monitor event and will have the expected effect. +wait_for_monitor_event $test_node + +if ctdb_test_on_cluster ; then + # Stop monitor events from bringing up the link status of an interface + try_command_on_node $test_node $CTDB event script disable legacy 10.interface +fi + +echo "Marking interface $iface down on node $test_node" +try_command_on_node $test_node $CTDB setifacelink $iface down + +echo "Adding IP $test_ip to node $test_node" +try_command_on_node $test_node $CTDB addip $test_ip/$mask $iface +try_command_on_node $test_node $CTDB ipreallocate + +echo "Wait long enough for IP verification to have taken place" +sleep_for 15 + +echo "Ensuring that IP ${test_ip} is not hosted on node ${test_node} when interface is down" +if ips_are_on_node '!' $test_node $test_ip; then + echo "GOOD: the IP has not been hosted while the interface is down" +else + echo "BAD: the IP is hosted but the interface is down" + exit 1 +fi + +echo "Marking interface $iface up on node $test_node" +try_command_on_node $test_node $CTDB setifacelink $iface up +wait_until_ips_are_on_node $test_node $test_ip diff --git a/ctdb/tests/INTEGRATION/simple/README b/ctdb/tests/INTEGRATION/simple/README new file mode 100644 index 0000000..3ac738d --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/README @@ -0,0 +1,2 @@ +Simple integration tests. These can be run against a pool of CTDB +daemons running on the local machine - aka "local daemons". diff --git a/ctdb/tests/INTEGRATION/simple/basics.000.onnode.sh b/ctdb/tests/INTEGRATION/simple/basics.000.onnode.sh new file mode 100755 index 0000000..4ca6e46 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.000.onnode.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# Use 'onnode' to confirm connectivity between all cluster nodes + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +echo "Checking connectivity between nodes..." +onnode all onnode -p all hostname diff --git a/ctdb/tests/INTEGRATION/simple/basics.001.listnodes.sh b/ctdb/tests/INTEGRATION/simple/basics.001.listnodes.sh new file mode 100755 index 0000000..aafe27e --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.001.listnodes.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb listnodes' shows the list of nodes + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node -v 0 "$CTDB listnodes" + +num_nodes=$(wc -l <"$outfile") + +# Each line should look like an IP address. +ipv4_pat='[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+' +ipv6_pat='[[:xdigit:]]+:[[:xdigit:]:]+[[:xdigit:]]+' +sanity_check_output \ + 2 \ + "^${ipv4_pat}|${ipv6_pat}\$" + +out_0="$out" + +echo "Checking other nodes..." + +n=1 +while [ $n -lt $num_nodes ] ; do + echo -n "Node ${n}: " + try_command_on_node $n "$CTDB listnodes" + if [ "$out_0" = "$out" ] ; then + echo "OK" + else + echo "DIFFERs from node 0:" + echo "$out" + exit 1 + fi + n=$(($n + 1)) +done diff --git a/ctdb/tests/INTEGRATION/simple/basics.002.tunables.sh b/ctdb/tests/INTEGRATION/simple/basics.002.tunables.sh new file mode 100755 index 0000000..6f362c6 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.002.tunables.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +# Verify the operation of "ctdb listvars", "ctdb getvar", "ctdb setvar" + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node -v 0 "$CTDB listvars" + +sanity_check_output \ + 5 \ + '^[[:alpha:]][[:alnum:]]+[[:space:]]*=[[:space:]]*[[:digit:]]+$' + +echo "Verifying all variable values using \"ctdb getvar\"..." + +while read var x val ; do + try_command_on_node 0 "$CTDB getvar $var" + + val2="${out#*= }" + + if [ "$val" != "$val2" ] ; then + echo "MISMATCH on $var: $val != $val2" + exit 1 + fi +done <"$outfile" + +echo "GOOD: all tunables match" + +var="RecoverTimeout" + +try_command_on_node -v 0 $CTDB getvar $var + +val="${out#*= }" + +echo "Going to try incrementing it..." + +incr=$(($val + 1)) + +try_command_on_node 0 $CTDB setvar $var $incr + +echo "That seemed to work, let's check the value..." + +try_command_on_node -v 0 $CTDB getvar $var + +newval="${out#*= }" + +if [ "$incr" != "$newval" ] ; then + echo "Nope, that didn't work..." + exit 1 +fi + +echo "Look's good! Now verifying with \"ctdb listvars\"" +try_command_on_node -v 0 "$CTDB listvars | grep '^$var'" + +check="${out#*= }" + +if [ "$incr" != "$check" ] ; then + echo "Nope, that didn't work..." + exit 1 +fi + +echo "Look's good! Putting the old value back..." +cmd="$CTDB setvar $var $val" +try_command_on_node 0 $cmd diff --git a/ctdb/tests/INTEGRATION/simple/basics.003.ping.sh b/ctdb/tests/INTEGRATION/simple/basics.003.ping.sh new file mode 100755 index 0000000..8071762 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.003.ping.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Verify the operation of the 'ctdb ping' command +# +# 1. Run the 'ctdb ping' command on one of the nodes and verify that it +# shows valid and expected output. +# 2. Shutdown one of the cluster nodes, using the 'ctdb shutdown' +# command. +# 3. Run the 'ctdb ping -n ' command from another node to this +# node. +# 4. Verify that the command is not successful since th ctdb daemon is +# not running on the node. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node -v 0 "$CTDB ping -n 1" + +sanity_check_output \ + 1 \ + '^response from 1 time=-?[.0-9]+ sec[[:space:]]+\([[:digit:]]+ clients\)$' + +ctdb_onnode -v 1 "shutdown" + +wait_until_node_has_status 1 disconnected 30 0 + +try_command_on_node -v 0 "! $CTDB ping -n 1" + +sanity_check_output \ + 1 \ + "(: ctdb_control error: ('ctdb_control to disconnected node'|'node is disconnected')|Unable to get ping response from node 1|Node 1 is DISCONNECTED|ctdb_control for getpnn failed|: Can not access node. Node is not operational\.|Node 1 has status DISCONNECTED\|UNHEALTHY\|INACTIVE$)" diff --git a/ctdb/tests/INTEGRATION/simple/basics.004.getpid.sh b/ctdb/tests/INTEGRATION/simple/basics.004.getpid.sh new file mode 100755 index 0000000..27025df --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.004.getpid.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb getpid' works as expected + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" +echo "There are $num_nodes nodes..." + +# Call getpid a few different ways and make sure the answer is always the same. + +try_command_on_node -v 0 "onnode -q all $CTDB getpid" +pids_onnode="$out" + +cmd="" +n=0 +while [ $n -lt $num_nodes ] ; do + cmd="${cmd}${cmd:+; }$CTDB getpid -n $n" + n=$(($n + 1)) +done +try_command_on_node -v 0 "( $cmd )" +pids_getpid_n="$out" + +if [ "$pids_onnode" = "$pids_getpid_n" ] ; then + echo "They're the same... cool!" +else + die "Error: they differ." +fi + +echo "Checking each PID for validity" + +n=0 +while [ $n -lt $num_nodes ] ; do + read pid + try_command_on_node $n "ls -l /proc/${pid}/exe | sed -e 's@.*/@@'" + echo -n "Node ${n}, PID ${pid} looks to be running \"$out\" - " + case "$out" in + ctdbd) : ;; + memcheck*) + if [ -z "$VALGRIND" ] ; then + die "BAD" + fi + ;; + *) die "BAD" + esac + + echo "GOOD!" + + n=$(($n + 1)) +done <<<"$pids_onnode" diff --git a/ctdb/tests/INTEGRATION/simple/basics.005.process_exists.sh b/ctdb/tests/INTEGRATION/simple/basics.005.process_exists.sh new file mode 100755 index 0000000..c6212fd --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.005.process_exists.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb process-exists' shows correct information + +# The implementation is creative about how it gets PIDs for existing and +# non-existing processes. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +test_node=1 +srvid=0xAE00000012345678 + +# Execute a ctdb client on $test_node that will last for 60 seconds. +# It should still be there when we check. +try_command_on_node -v $test_node \ + "$CTDB_TEST_WRAPPER exec dummy_client -n 10 -S ${srvid} >/dev/null 2>&1 & echo \$!" +client_pid="$out" + +cleanup () +{ + if [ -n "$client_pid" ] ; then + onnode $test_node kill -9 "$client_pid" + fi +} + +ctdb_test_exit_hook_add cleanup + +echo "Waiting until PID $client_pid is registered on node $test_node" +status=0 +wait_until 30 try_command_on_node $test_node \ + "$CTDB process-exists ${client_pid}" || status=$? +echo "$out" + +if [ $status -eq 0 ] ; then + echo "OK" +else + die "BAD" +fi + +echo "Checking for PID $client_pid with SRVID $srvid on node $test_node" +status=0 +try_command_on_node $test_node \ + "$CTDB process-exists ${client_pid} ${srvid}" || status=$? +echo "$out" + +if [ $status -eq 0 ] ; then + echo "OK" +else + die "BAD" +fi + +echo "Checking for PID $client_pid with SRVID $client_pid on node $test_node" +try_command_on_node -v $test_node \ + "! $CTDB process-exists ${client_pid} ${client_pid}" + +# Now just echo the PID of the ctdb daemon on test node. +# This is not a ctdb client and process-exists should return error. +try_command_on_node $test_node "ctdb getpid" +pid="$out" + +echo "Checking for PID $pid on node $test_node" +try_command_on_node -v $test_node "! $CTDB process-exists ${pid}" diff --git a/ctdb/tests/INTEGRATION/simple/basics.010.statistics.sh b/ctdb/tests/INTEGRATION/simple/basics.010.statistics.sh new file mode 100755 index 0000000..d97e035 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.010.statistics.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb statistics' works as expected + +# This is pretty superficial and could do more validation. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +pattern='^(CTDB version 1|Current time of statistics[[:space:]]*:.*|Statistics collected since[[:space:]]*:.*|Gathered statistics for [[:digit:]]+ nodes|[[:space:]]+[[:alpha:]_]+[[:space:]]+[[:digit:]]+|[[:space:]]+(node|client|timeouts|locks)|[[:space:]]+([[:alpha:]_]+_latency|max_reclock_[[:alpha:]]+)[[:space:]]+[[:digit:]-]+\.[[:digit:]]+[[:space:]]sec|[[:space:]]*(locks_latency|reclock_ctdbd|reclock_recd|call_latency|lockwait_latency|childwrite_latency)[[:space:]]+MIN/AVG/MAX[[:space:]]+[-.[:digit:]]+/[-.[:digit:]]+/[-.[:digit:]]+ sec out of [[:digit:]]+|[[:space:]]+(hop_count_buckets|lock_buckets):[[:space:][:digit:]]+)$' + +try_command_on_node -v 1 "$CTDB statistics" + +sanity_check_output 40 "$pattern" diff --git a/ctdb/tests/INTEGRATION/simple/basics.011.statistics_reset.sh b/ctdb/tests/INTEGRATION/simple/basics.011.statistics_reset.sh new file mode 100755 index 0000000..51f34d9 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/basics.011.statistics_reset.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb statisticsreset' works as expected + +# This is pretty superficial. It just checks that a few particular +# items reduce. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +get_stat () +{ + local label="$1" + + cat "$outfile" | + sed -rn -e "s@^[[:space:]]+${label}[[:space:]]+([[:digit:]])@\1@p" | + head -1 +} + +check_reduced () +{ + local label="$1" + local before="$2" + local after="$3" + + if [ $after -lt $before ] ; then + echo "GOOD: ${label} reduced from ${before} to ${after}" + else + die "BAD: ${label} did not reduce from ${before} to ${after}" + fi +} + +n=0 +while [ $n -lt $num_nodes ] ; do + echo "Getting initial statistics for node ${n}..." + + try_command_on_node -v $n $CTDB statistics + + before_req_control=$(get_stat "req_control") + before_reply_control=$(get_stat "reply_control") + before_node_packets_recv=$(get_stat "node_packets_recv") + + try_command_on_node $n $CTDB statisticsreset + + try_command_on_node -v $n $CTDB statistics + + after_req_control=$(get_stat "req_control") + after_reply_control=$(get_stat "reply_control") + after_node_packets_recv=$(get_stat "node_packets_recv") + + check_reduced "req_control" "$before_req_control" "$after_req_control" + check_reduced "reply_control" "$before_reply_control" "$after_reply_control" + check_reduced "node_packets_recv" "$before_node_packets_recv" "$after_node_packets_recv" + + n=$(($n + 1)) +done diff --git a/ctdb/tests/INTEGRATION/simple/cluster.001.stop_leader_yield.sh b/ctdb/tests/INTEGRATION/simple/cluster.001.stop_leader_yield.sh new file mode 100755 index 0000000..180b4ae --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.001.stop_leader_yield.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb stop' causes a node to yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Stopping leader ${leader}..." +ctdb_onnode "$test_node" stop -n "$leader" + +wait_until_node_has_status "$leader" stopped + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.002.ban_leader_yield.sh b/ctdb/tests/INTEGRATION/simple/cluster.002.ban_leader_yield.sh new file mode 100755 index 0000000..234869c --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.002.ban_leader_yield.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb ban' causes a node to yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Banning leader ${leader}..." +ctdb_onnode "$test_node" ban 300 -n "$leader" + +wait_until_node_has_status "$leader" banned + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.003.capability_leader_yield.sh b/ctdb/tests/INTEGRATION/simple/cluster.003.capability_leader_yield.sh new file mode 100755 index 0000000..94bcf27 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.003.capability_leader_yield.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb ban' causes a node to yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Removing leader capability from leader ${leader}..." +ctdb_onnode "$test_node" setleaderrole off -n "$leader" + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.006.stop_leader_yield_no_lock.sh b/ctdb/tests/INTEGRATION/simple/cluster.006.stop_leader_yield_no_lock.sh new file mode 100755 index 0000000..95f522d --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.006.stop_leader_yield_no_lock.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb stop' causes a node to yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +ctdb_nodes_start_custom -C "cluster lock" + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Stopping leader ${leader}..." +ctdb_onnode "$test_node" stop -n "$leader" + +wait_until_node_has_status "$leader" stopped + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.007.ban_leader_yield_no_lock.sh b/ctdb/tests/INTEGRATION/simple/cluster.007.ban_leader_yield_no_lock.sh new file mode 100755 index 0000000..0ef4e2b --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.007.ban_leader_yield_no_lock.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb ban' causes a node to yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +ctdb_nodes_start_custom -C "cluster lock" + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Banning leader ${leader}..." +ctdb_onnode "$test_node" ban 300 -n "$leader" + +wait_until_node_has_status "$leader" banned + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.008.capability_leader_yield_no_lock.sh b/ctdb/tests/INTEGRATION/simple/cluster.008.capability_leader_yield_no_lock.sh new file mode 100755 index 0000000..4489bc5 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.008.capability_leader_yield_no_lock.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# Verify that removing the the leader capability causes a node to +# yield the leader role + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +ctdb_nodes_start_custom -C "cluster lock" + +# This is the node used to execute commands +select_test_node +echo + +# test_node set by select_test_node() +# shellcheck disable=SC2154 +leader_get "$test_node" + +# leader set by leader_get() +# shellcheck disable=SC2154 +echo "Removing leader capability from leader ${leader}..." +ctdb_onnode "$test_node" setleaderrole off -n "$leader" + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.010.getrelock.sh b/ctdb/tests/INTEGRATION/simple/cluster.010.getrelock.sh new file mode 100755 index 0000000..3a76654 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.010.getrelock.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Verify that "ctdb getreclock" gets the recovery lock correctly + +# Make sure the recovery lock is consistent across all nodes. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +echo "Check that recovery lock is set the same on all nodes..." +ctdb_onnode all getreclock + +# outfile is set above by ctdb_onnode +# shellcheck disable=SC2154 +n=$(sort -u "$outfile" | wc -l | tr -d '[:space:]') + +case "$n" in +0) echo "GOOD: Recovery lock is unset on all nodes" ;; +1) echo "GOOD: All nodes have the same recovery lock setting" ;; +*) ctdb_test_fail "BAD: Recovery lock setting differs across nodes" ;; +esac diff --git a/ctdb/tests/INTEGRATION/simple/cluster.012.reclock_command.sh b/ctdb/tests/INTEGRATION/simple/cluster.012.reclock_command.sh new file mode 100755 index 0000000..d043c7e --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.012.reclock_command.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# Check that CTDB operates correctly if the recovery lock is configured +# as a command. + +# This test works only with local daemons. On a real cluster it has +# no way of updating configuration. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +echo "Starting CTDB with recovery lock command configured..." +ctdb_nodes_start_custom -R + +echo "Good, that seems to work!" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.015.reclock_remove_lock.sh b/ctdb/tests/INTEGRATION/simple/cluster.015.reclock_remove_lock.sh new file mode 100755 index 0000000..2bb058c --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.015.reclock_remove_lock.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# Verify that the cluster recovers if the recovery lock is removed. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +echo "Starting CTDB with cluster lock recheck interval set to 5s..." +ctdb_nodes_start_custom -r 5 + +generation_has_changed () +{ + local node="$1" + local generation_init="$2" + + # Leak this so it can be printed by test + generation_new="" + + ctdb_onnode "$node" status + # shellcheck disable=SC2154 + # $outfile set by ctdb_onnode() above + generation_new=$(sed -n -e 's/^Generation:\([0-9]*\)/\1/p' "$outfile") + + [ "$generation_new" != "$generation_init" ] +} + +select_test_node + +echo "Get recovery lock setting" +# shellcheck disable=SC2154 +# $test_node set by select_test_node() above +ctdb_onnode "$test_node" getreclock +# shellcheck disable=SC2154 +# $out set by ctdb_onnode() above +reclock_setting="$out" + +if [ -z "$reclock_setting" ] ; then + ctdb_test_skip "Recovery lock is not set" +fi + +t="${reclock_setting% 5}" +reclock="${t##* }" + +if [ ! -f "$reclock" ] ; then + ctdb_test_error "Recovery lock file \"${reclock}\" is missing" +fi + +echo "Recovery lock setting is \"${reclock_setting}\"" +echo "Recovery lock file is \"${reclock}\"" +echo + +leader_get "$test_node" + +generation_get + +echo "Remove recovery lock" +rm "$reclock" +echo + +# This will mean an election has taken place and a recovery has occurred +wait_until_generation_has_changed "$test_node" + +# shellcheck disable=SC2154 +# $leader set by leader_get() above +leader_old="$leader" + +leader_get "$test_node" + +if [ "$leader" != "$leader_old" ] ; then + echo "OK: Leader has changed to node ${leader_new}" +fi +echo "GOOD: Leader is still node ${leader}" +echo + +cluster_is_healthy diff --git a/ctdb/tests/INTEGRATION/simple/cluster.016.reclock_move_lock_dir.sh b/ctdb/tests/INTEGRATION/simple/cluster.016.reclock_move_lock_dir.sh new file mode 100755 index 0000000..147547d --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.016.reclock_move_lock_dir.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +# Verify that if the directory containing the cluster lock is moved +# then the current cluster leader no longer claims to be leader, and +# no other node claims to be leader. Confirm that if the directory is +# moved back then a node will become leader. + +# This simulates the cluster filesystem containing the cluster lock +# being unmounted and remounted. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init -n + +echo "Starting CTDB with cluster lock recheck interval set to 5s..." +ctdb_nodes_start_custom -r 5 + +select_test_node + +echo "Get cluster lock setting" +# shellcheck disable=SC2154 +# $test_node set by select_test_node() above +ctdb_onnode "$test_node" getreclock +# shellcheck disable=SC2154 +# $out set by ctdb_onnode() above +reclock_setting="$out" + +if [ -z "$reclock_setting" ] ; then + ctdb_test_skip "Cluster lock is not set" +fi + +t="${reclock_setting% 5}" +reclock="${t##* }" + +if [ ! -f "$reclock" ] ; then + ctdb_test_error "Cluster lock file \"${reclock}\" is missing" +fi + +echo "Cluster lock setting is \"${reclock_setting}\"" +echo "Cluster lock file is \"${reclock}\"" +echo + +leader_get "$test_node" + +dir=$(dirname "$reclock") + +echo "Rename cluster lock directory" +mv "$dir" "${dir}.$$" + +wait_until_leader_has_changed "$test_node" +echo + +# shellcheck disable=SC2154 +# $leader set by leader_get() & wait_until_leader_has_changed(), above +if [ "$leader" != "UNKNOWN" ]; then + test_fail "BAD: leader is ${leader}" +fi + +echo "OK: leader is UNKNOWN" +echo + +echo 'Get "leader timeout":' +conf_tool="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb-config" +# shellcheck disable=SC2154 +# $test_node set by select_test_node() above +try_command_on_node "$test_node" "${conf_tool} get cluster 'leader timeout'" +# shellcheck disable=SC2154 +# $out set by ctdb_onnode() above +leader_timeout="$out" +echo "Leader timeout is ${leader_timeout}s" +echo + +sleep_time=$((2 * leader_timeout)) +echo "Waiting for ${sleep_time}s to confirm leader stays UNKNOWN" +sleep_for $sleep_time + +leader_get "$test_node" +if [ "$leader" = "UNKNOWN" ]; then + echo "OK: leader is UNKNOWN" + echo +else + test_fail "BAD: leader is ${leader}" +fi + +echo "Restore cluster lock directory" +mv "${dir}.$$" "$dir" + +wait_until_leader_has_changed "$test_node" diff --git a/ctdb/tests/INTEGRATION/simple/cluster.020.message_ring.sh b/ctdb/tests/INTEGRATION/simple/cluster.020.message_ring.sh new file mode 100755 index 0000000..b841f5b --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.020.message_ring.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# Run the message_ring test and sanity check the output + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +echo "Running message_ring on all $num_nodes nodes." +try_command_on_node -v -p all $CTDB_TEST_WRAPPER $VALGRIND message_ring -n $num_nodes + +# Get the last line of output. +last=$(tail -n 1 "$outfile") + +pat='^(Waiting for cluster|Ring\[[[:digit:]]+\]: [[:digit:]]+(\.[[:digit:]]+)? msgs/sec \(\+ve=[[:digit:]]+ -ve=[[:digit:]]+\))$' +sanity_check_output 1 "$pat" + +# $last should look like this: +# Ring[1]: 10670.93 msgs/sec (+ve=53391 -ve=53373) +stuff="${last##Ring\[*\]: }" +mps="${stuff% msgs/sec*}" + +if [ ${mps%.*} -ge 10 ] ; then + echo "OK: $mps msgs/sec >= 10 msgs/sec" +else + echo "BAD: $mps msgs/sec < 10 msgs/sec" + exit 1 +fi + +stuff="${stuff#*msgs/sec (+ve=}" +positive="${stuff%% *}" + +if [ $positive -ge 10 ] ; then + echo "OK: +ive ($positive) >= 10" +else + echo "BAD: +ive ($positive) < 10" + exit 1 +fi + +stuff="${stuff#*-ve=}" +negative="${stuff%)}" + +if [ $negative -ge 10 ] ; then + echo "OK: -ive ($negative) >= 10" +else + echo "BAD: -ive ($negative) < 10" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/simple/cluster.021.tunnel_ring.sh b/ctdb/tests/INTEGRATION/simple/cluster.021.tunnel_ring.sh new file mode 100755 index 0000000..f86d080 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.021.tunnel_ring.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# Run tunnel_test and sanity check the output + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +echo "Running tunnel_test on all $num_nodes nodes." +try_command_on_node -v -p all $CTDB_TEST_WRAPPER $VALGRIND \ + tunnel_test -t 30 -n $num_nodes + +# Get the last line of output. +last=$(tail -n 1 "$outfile") + +pat='^(Waiting for cluster|pnn\[[[:digit:]]+\] [[:digit:]]+(\.[[:digit:]]+)? msgs/sec)$' +sanity_check_output 1 "$pat" + +# $last should look like this: +# pnn[2] count=85400 +stuff="${last##pnn\[*\] }" +mps="${stuff% msgs/sec}" + +if [ ${mps%.*} -ge 10 ] ; then + echo "OK: $mps msgs/sec >= 10 msgs/sec" +else + echo "BAD: $mps msgs/sec < 10 msgs/sec" + exit 1 +fi diff --git a/ctdb/tests/INTEGRATION/simple/cluster.030.node_stall_leader_timeout.sh b/ctdb/tests/INTEGRATION/simple/cluster.030.node_stall_leader_timeout.sh new file mode 100755 index 0000000..7bca58c --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.030.node_stall_leader_timeout.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# Verify that nothing bad occurs if a node stalls and the leader +# broadcast timeout triggers + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node +echo + +echo 'Get "leader timeout":' +conf_tool="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb-config" +# shellcheck disable=SC2154 +# $test_node set by select_test_node() above +try_command_on_node "$test_node" "${conf_tool} get cluster 'leader timeout'" +# shellcheck disable=SC2154 +# $out set by ctdb_onnode() above +leader_timeout="$out" +echo "Leader timeout is ${leader_timeout} seconds" +echo + +# Assume leader timeout is reasonable and doesn't cause node to be +# disconnected +stall_time=$((leader_timeout * 2)) + +generation_get "$test_node" + +echo "Get ctdbd PID on node ${test_node}..." +ctdb_onnode -v "$test_node" "getpid" +ctdbd_pid="$out" +echo + +echo "Sending SIGSTOP to ctdbd on ${test_node}" +try_command_on_node "$test_node" "kill -STOP ${ctdbd_pid}" + +sleep_for "$stall_time" + +echo "Sending SIGCONT to ctdbd on ${test_node}" +try_command_on_node "$test_node" "kill -CONT ${ctdbd_pid}" +echo + +wait_until_generation_has_changed "$test_node" + +cluster_is_healthy diff --git a/ctdb/tests/INTEGRATION/simple/cluster.090.unreachable.sh b/ctdb/tests/INTEGRATION/simple/cluster.090.unreachable.sh new file mode 100755 index 0000000..1410a12 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.090.unreachable.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# Verify an error occurs if a ctdb command is run against a node +# without a ctdbd + +# That is, check that an error message is printed if an attempt is made +# to execute a ctdb command against a node that is not running ctdbd. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +test_node=1 + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" +echo "There are $num_nodes nodes." + +echo "Shutting down node ${test_node}..." +try_command_on_node $test_node $CTDB shutdown + +wait_until_node_has_status $test_node disconnected 30 0 + +wait_until_node_has_status 0 recovered 30 0 + +pat="ctdb_control error: 'ctdb_control to disconnected node'|ctdb_control error: 'node is disconnected'|Node $test_node is DISCONNECTED|Node $test_node has status DISCONNECTED\|UNHEALTHY\|INACTIVE" + +for i in ip disable enable "ban 0" unban listvars ; do + try_command_on_node -v 0 ! $CTDB $i -n $test_node + + if grep -Eq "$pat" "$outfile" ; then + echo "OK: \"ctdb ${i}\" fails with expected \"disconnected node\" message" + else + echo "BAD: \"ctdb ${i}\" does not fail with expected \"disconnected node\" message" + exit 1 + fi +done diff --git a/ctdb/tests/INTEGRATION/simple/cluster.091.version_check.sh b/ctdb/tests/INTEGRATION/simple/cluster.091.version_check.sh new file mode 100755 index 0000000..be71750 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/cluster.091.version_check.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Check that the CTDB version consistency checking operates correctly + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_skip_on_cluster + +ctdb_test_init + +select_test_node + +try_command_on_node -v "$test_node" ctdb version +version="$out" + +major="${version%%.*}" +rest="${version#*.}" +minor="${rest%%.*}" + +echo "Node ${test_node} has version ${major}.${minor}" + +# Unchanged version - this should work +export CTDB_TEST_SAMBA_VERSION=$(( (major << 16) | minor )) +printf '\nRestarting node %d with CTDB_TEST_SAMBA_VERSION=0x%08x\n' \ + "$test_node" \ + "$CTDB_TEST_SAMBA_VERSION" +ctdb_nodes_restart "$test_node" +wait_until_ready +echo "GOOD: ctdbd restarted successfully on node ${test_node}" + +d="$CTDB_SCRIPTS_HELPER_BINDIR" +try_command_on_node "$test_node" "${d}/ctdb-path" "pidfile" "ctdbd" +pidfile="$out" + +# Changed major version - this should fail +export CTDB_TEST_SAMBA_VERSION=$(( ((major + 1) << 16) | minor )) +printf '\nRestarting node %d with CTDB_TEST_SAMBA_VERSION=0x%08x\n' \ + "$test_node" \ + "$CTDB_TEST_SAMBA_VERSION" +ctdb_nodes_restart "$test_node" +echo "Will use PID file ${pidfile} to check for ctdbd exit" +wait_until 30 ! test -f "$pidfile" +echo "GOOD: ctdbd exited early on node ${test_node}" + +# Changed minor version - this should fail +export CTDB_TEST_SAMBA_VERSION=$(( (major << 16) | (minor + 1) )) +printf '\nRestarting node %d with CTDB_TEST_SAMBA_VERSION=0x%08x\n' \ + "$test_node" \ + "$CTDB_TEST_SAMBA_VERSION" +ctdb_nodes_start "$test_node" +echo "Will use PID file ${pidfile} to check for ctdbd exit" +wait_until 30 ! test -f "$pidfile" +echo "GOOD: ctdbd exited early on node ${test_node}" diff --git a/ctdb/tests/INTEGRATION/simple/debug.001.getdebug.sh b/ctdb/tests/INTEGRATION/simple/debug.001.getdebug.sh new file mode 100755 index 0000000..2220a20 --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/debug.001.getdebug.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb getdebug' works as expected + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +try_command_on_node 0 "$CTDB listnodes | wc -l" +num_nodes="$out" + +try_command_on_node -v 1 "onnode -q all $CTDB getdebug" +getdebug_onnode="$out" + +sanity_check_output \ + $num_nodes \ + '^(ERROR|WARNING|NOTICE|INFO|DEBUG)$' + +cmd="" +n=0 +while [ $n -lt $num_nodes ] ; do + cmd="${cmd}${cmd:+; }$CTDB getdebug -n $n" + n=$(($n + 1)) +done +try_command_on_node -v 1 "$cmd" +getdebug_n="$out" + +if [ "$getdebug_onnode" = "$getdebug_n" ] ; then + echo "They're the same... cool!" +else + die "Error: they differ." +fi + +seps="" +nl=" +" +while read line ; do + t=$(echo "$line" | sed -r -e 's@Node [[:digit:]]+ is at debug level ([[:alpha:]]+) \((-?[[:digit:]]+)\)$@\|\1\|\2|@') + seps="${seps}${seps:+${nl}}|Name|Level|${nl}${t}" +done <<<"$getdebug_onnode" diff --git a/ctdb/tests/INTEGRATION/simple/debug.002.setdebug.sh b/ctdb/tests/INTEGRATION/simple/debug.002.setdebug.sh new file mode 100755 index 0000000..dd5949e --- /dev/null +++ b/ctdb/tests/INTEGRATION/simple/debug.002.setdebug.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# Verify that 'ctdb setdebug' works as expected. + +# This is a little superficial. It checks that CTDB thinks the debug +# level has been changed but doesn't actually check that logging occurs +# at the new level. + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +set -e + +ctdb_test_init + +select_test_node + +get_debug () +{ + # Sets: check_debug + local node="$1" + + local out + + try_command_on_node -v $node "$CTDB getdebug" + check_debug="$out" +} + +set_and_check_debug () +{ + local node="$1" + local level="$2" + local levelstr="${3:-$level}" + + echo "Setting debug level on node ${node} to ${level}." + try_command_on_node $node "$CTDB setdebug ${level}" + + local check_debug + get_debug $node + + if [ "$levelstr" != "$check_debug" ] ; then + die "BAD: Debug level \"$levelstr\" != \"$check_debug\"." + fi +} + +get_debug $test_node +initial_debug="$check_debug" + +levels="ERROR WARNING NOTICE INFO DEBUG" + +for new_debug in $levels ; do + [ "$initial_debug" != "$new_debug" ] || continue + + echo + set_and_check_debug $test_node "$new_debug" +done + +while read new_debug i ; do + [ "$initial_debug" != "$i" ] || continue + + echo + set_and_check_debug $test_node "$i" "$new_debug" +done </dev/null 2>&1" ; then + stackpat=' +---- Stack trace of interesting process [0-9]*\\[sleep\\] ---- +[<[0-9a-f]*>] .*sleep+.* +' +else + stackpat='' +fi + +while IFS="" read pattern ; do + [ -n "$pattern" ] || continue + if grep -q -- "^${pattern}\$" "$debug_output" ; then + printf 'GOOD: output contains "%s"\n' "$pattern" + else + printf 'BAD: output does not contain "%s"\n' "$pattern" + exit 1 + fi +done <] [] + +Help Options: + -h, --help Show this help message + +Options: + -c, --count=INT Option help of length thirty. + -v, --value=Value help of length 23 Short description + +Commands: + A really really long command This is a really long help message + short command short msg for short command +Usage: test4 [-h] [-h|--help] [-c|--count=INT] + [-v|--value=Value help of length 23] [] + + short command short msg for short command +EOF +unit_test cmdline_test 4 + +ok <] [] + +Help Options: + -h, --help Show this help message + +Action Commands: + action one action one help + action two action two help +Usage: test5 [] [] + +Help Options: + -h, --help Show this help message + +Action Commands: + action one action one help + action two action two help +Usage: test5 [] [] + +Help Options: + -h, --help Show this help message + +Action Commands: + action one action one help + action two action two help +EOF +unit_test cmdline_test 5 + +ok <] [] + +Help Options: + -h, --help Show this help message + +Basic Commands: + cmd1 command one help + cmd2 command two help + +Advanced Commands: + cmd3 command three help + cmd4 command four help + +Ultimate Commands: + cmd5 command five help + cmd6 command six help + +one +three +six +EOF +unit_test cmdline_test 7 diff --git a/ctdb/tests/UNIT/cunit/comm_test_001.sh b/ctdb/tests/UNIT/cunit/comm_test_001.sh new file mode 100755 index 0000000..ac09f5c --- /dev/null +++ b/ctdb/tests/UNIT/cunit/comm_test_001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + + +ok_null +unit_test comm_test 1 + +ok_null +unit_test comm_test 2 + +ok "100 2048 500 4096 1024 8192 200 16384 300 32768 400 65536 1048576 " +unit_test comm_test 3 diff --git a/ctdb/tests/UNIT/cunit/comm_test_002.sh b/ctdb/tests/UNIT/cunit/comm_test_002.sh new file mode 100755 index 0000000..a2fbf51 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/comm_test_002.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +socket="${CTDB_TEST_TMP_DIR}/test_sock.$$" +num_clients=10 + +remove_socket () +{ + rm -f "$socket" +} + +test_cleanup remove_socket + +ok_null + +unit_test comm_server_test "$socket" $num_clients & +pid=$! + +for i in $(seq 1 $num_clients) ; do + unit_test comm_client_test "$socket" +done + +wait $pid diff --git a/ctdb/tests/UNIT/cunit/conf_test_001.sh b/ctdb/tests/UNIT/cunit/conf_test_001.sh new file mode 100755 index 0000000..188964e --- /dev/null +++ b/ctdb/tests/UNIT/cunit/conf_test_001.sh @@ -0,0 +1,196 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +conffile="${CTDB_TEST_TMP_DIR}/config.$$" + +remove_files () +{ + rm -f "$conffile" +} + +test_cleanup remove_files + +ok_null +unit_test conf_test 1 + +ok < "$conffile" < "$conffile" < "$conffile" < "key1" +[section1] + # key1 = value1 + # key2 = 10 + key3 = false # temporary +EOF +unit_test conf_test 9 "$conffile" + +cat > "$conffile" < "key3" +[section1] + # key1 = value1 + # key2 = 10 + key3 = false # temporary +EOF +unit_test conf_test 9 "$conffile" + +cat > "$conffile" < "$conffile" < "$conffile" < "$conffile" < "foo" +[section1] + # key1 = value1 + # key2 = 10 + key3 = false # temporary +EOF +unit_test conf_test 10 "$conffile" + +cat > "$conffile" < "$conffile" < "${conffile}.reload" < "$conffile" < "$conffile" < "$conffile" < "key" not defined +EOF +unit_test ctdb-config get section key + +# Confirm that an unknown key doesn't stop the rest of the file from +# loading +cat > "$conffile" < "unknown key" +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +ok < "$conffile" < "$conffile" < "$conffile" < "$conffile" < "$conffile" < "$conffile" < "$conffile" < "$conffile" < transport = invalid +conf: validation for option "transport" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" < "$conffile" < "$conffile" < node address = 10.1.2.3:123 +conf: validation for option "node address" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" < "$conffile" < recovery lock is deprecated +EOF +unit_test ctdb-config -d WARNING validate + +cat > "$conffile" < "$conffile" < leader timeout = 0 +conf: validation for option "leader timeout" failed +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" < "leader timeout" = "-5" +Failed to load config file $conffile +EOF +unit_test ctdb-config validate + +cat > "$conffile" < "$conffile" < "$conffile" < "$conffile" < "$conffile" < script log level = INVALID +conf: validation for option "script log level" failed +Failed to load config file ${conffile} +EOF +unit_test ctdb-config validate diff --git a/ctdb/tests/UNIT/cunit/config_test_007.sh b/ctdb/tests/UNIT/cunit/config_test_007.sh new file mode 100755 index 0000000..8804448 --- /dev/null +++ b/ctdb/tests/UNIT/cunit/config_test_007.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +PATH="$PATH:$CTDB_SCRIPTS_HELPER_BINDIR" + +setup_ctdb_base "${CTDB_TEST_TMP_DIR}" "ctdb-etc" + +conffile="${CTDB_BASE}/ctdb.conf" + +remove_files () +{ + rm -f "$conffile" +} + +test_cleanup remove_files + +cat > "$conffile" < "$tfile" + +ok_null +unit_test line_test "$tfile" + +printf "\0" > "$tfile" + +required_result 1 < "$tfile" + +ok_null +unit_test line_test "$tfile" + +cat < "$tfile" +hello +world +EOF + +required_result 2 << EOF +hello +world +EOF +unit_test line_test "$tfile" + +required_result 2 << EOF +hello +world +EOF +unit_test line_test "$tfile" + +cat < "$tfile" +This is a really long long line full of random words and hopefully it will be read properly by the line test program and identified as a single line +EOF + +required_result 1 < "$tfile" +line number one +line number two +line number one +line number two +line number one +EOF + +required_result 5 < "$tfile" +this is line number one +this is line number two +this is line number three +this is line number four +this is line number five +EOF + +required_result 5 < "$scriptdir/prog" < "$scriptdir/11.foo.script" < "$scriptdir/22.bar.script" < "$scriptdir/22.bar.script" < "$prog" < "$prog" < "$prog" <"$output" 2>&1 +echo hello +EOF + +ok < "$prog" < "$prog" < "$prog" < "$prog" < "$pidfile" +sleep 10 +EOF + +ok < "$prog" < "$output" <"$tfile" +chmod a-r "$tfile" +uid=$(id -u) +# root can read unreadable files +if [ "$uid" = 0 ]; then + ok_tunable_defaults +else + required_error EINVAL <"$tfile" +required_error EINVAL <"$tfile" +required_error EINVAL <"$tfile" +required_error EINVAL <"$tfile" +required_error EINVAL <"$tfile" +required_error EINVAL <"$tfile" +required_error EINVAL <"$tfile" +required_error EINVAL <"$tfile" <"$tfile" <"$tfile" +ok_tunable_defaults +unit_test tunable_test "$tfile" + +test_case "OK, comments and blanks only" +cat >"$tfile" <"$tfile" <"$tfile" <"$tfile" <"$tfile" <"$tfile" <"$tfile" <"$tfile" < "$log" + ;; + +"verbosetimeout") + (ctdb-event status random $2) > "$log" + ;; + +"verbosetimeout2") + exec > "$log" 2>&1 + ctdb-event status random $2 + ;; + +*) + ;; + +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/03.notalink.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/03.notalink.script new file mode 100644 index 0000000..039e4d0 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/03.notalink.script @@ -0,0 +1,2 @@ +#!/bin/sh +exit 0 diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/README b/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/README new file mode 100644 index 0000000..f38a189 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/data/README @@ -0,0 +1 @@ +initially empty event scripts directory diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/empty/README b/ctdb/tests/UNIT/eventd/etc-ctdb/events/empty/README new file mode 100644 index 0000000..a5614a9 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/empty/README @@ -0,0 +1 @@ +empty event scripts directory diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/01.test.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/01.test.script new file mode 100755 index 0000000..d16f0de --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/01.test.script @@ -0,0 +1,11 @@ +#!/bin/sh + +case "$1" in +"startup") sleep 5; exit 0 ;; +"monitor") sleep 5; exit 0 ;; +"event1") sleep 1; exit 0 ;; +"event2") sleep 1; exit 0 ;; +"event3") exit 3 ;; +"timeout1") sleep 99 ;; +*) exit 0 ;; +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/02.test.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/02.test.script new file mode 100755 index 0000000..5c841aa --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/02.test.script @@ -0,0 +1,9 @@ +#!/bin/sh + +case "$1" in +"monitor") sleep 1; exit 0 ;; +"event1") exit 1 ;; +"event2") sleep 1; exit 0 ;; +"timeout2") sleep 99 ;; +*) exit 0 ;; +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/03.test.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/03.test.script new file mode 100755 index 0000000..b48b68c --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/multi/03.test.script @@ -0,0 +1,9 @@ +#!/bin/sh + +case "$1" in +"monitor") sleep 1; exit 0 ;; +"event1") sleep 1; exit 0 ;; +"event2") exit 2 ;; +"timeout3") sleep 99 ;; +*) exit 0 ;; +esac diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/01.disabled.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/01.disabled.script new file mode 100644 index 0000000..c52d3c2 --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/01.disabled.script @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 0 diff --git a/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/02.enabled.script b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/02.enabled.script new file mode 100755 index 0000000..ace80fd --- /dev/null +++ b/ctdb/tests/UNIT/eventd/etc-ctdb/events/random/02.enabled.script @@ -0,0 +1,49 @@ +#!/bin/sh + +case "$1" in +"monitor") exit 0 ;; +"failure") exit 1 ;; +"timeout") sleep 99 ;; +"verbose") echo "Running event $1" ; exit 0 ;; +"verbosemultiline") + cat </dev/null || echo) + if [ -n "$pid" ] ; then + kill $pid || true + fi +} + +setup_eventd () +{ + echo "Setting up eventd" + + $VALGRIND ctdb-eventd 2>&1 | tee "$eventd_logfile" & + # Wait till eventd is running + wait_until 10 test -S "$eventd_socket" || \ + die "ctdb_eventd failed to start" + + test_cleanup cleanup_eventd +} + +simple_test_background () +{ + background_log="${CTDB_BASE}/background.log" + background_status="${CTDB_BASE}/background.status" + background_running=1 + + ( + (unit_test ctdb-event "$@") > "$background_log" 2>&1 + echo $? > "$background_status" + ) & + background_pid=$! +} + +background_wait () +{ + [ -n "$background_running" ] || return + + count=0 + while [ ! -s "$background_status" -a $count -lt 30 ] ; do + count=$(( $count + 1 )) + sleep 1 + done + + if [ ! -s "$background_status" ] ; then + kill -9 "$background_pid" + echo TIMEOUT > "$background_status" + fi +} + +background_output () +{ + [ -n "$background_running" ] || return + + bg_status=$(cat "$background_status") + rm -f "$background_status" + echo "--- Background ---" + if [ "$bg_status" = "TIMEOUT" ] ; then + echo "Background process did not complete" + bg_status=1 + else + cat "$background_log" + rm -f "$background_log" + fi + echo "--- Background ---" + unset background_running + [ $bg_status -eq 0 ] || exit $bg_status +} + +simple_test () +{ + (unit_test ctdb-event "$@") + status=$? + + background_wait + background_output + + [ $status -eq 0 ] || exit $status +} + +result_filter () +{ + _duration="\<[0-9][0-9]*\.[0-9][0-9][0-9]\>" + _day="[FMSTW][aehoru][deintu]" + _month="[ADFJMNOS][aceopu][bcglnprtvy]" + _date="[ 0-9][0-9]" + _time="[0-9][0-9]:[0-9][0-9]:[0-9][0-9]" + _year="[0-9][0-9][0-9][0-9]" + _datetime="${_day} ${_month} ${_date} ${_time} ${_year}" + _pid="[0-9][0-9]*" + sed -e "s#${_duration}#DURATION#" \ + -e "s#${_datetime}#DATETIME#" \ + -e "s#,${_pid}#,PID#" +} diff --git a/ctdb/tests/UNIT/eventscripts/00.ctdb.init.001.sh b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.001.sh new file mode 100755 index 0000000..807f3ef --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.001.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "TDB check, tdbtool supports check" + +setup + +FAKE_TDBTOOL_SUPPORTS_CHECK="yes" + +ok_null + +simple_test diff --git a/ctdb/tests/UNIT/eventscripts/00.ctdb.init.002.sh b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.002.sh new file mode 100755 index 0000000..7ff5385 --- /dev/null +++ b/ctdb/tests/UNIT/eventscripts/00.ctdb.init.002.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "TDB check, tdbtool does no support check" + +setup + +FAKE_TDBTOOL_SUPPORTS_CHECK="no" + +ok <= 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 <= 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 <= 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 <= 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 <= 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 <= 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 <= 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 <= 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 <= 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 <= 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 <= threshold 70%" +simple_test + +# Back up above critical threshold... unhealthy +set_mem_usage 81 81 +required_result 1 <= 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 <= threshold 1 +EOF +simple_test + +setup "up" + +ok <= threshold 1 +EOF +simple_test + +required_result 1 <= threshold 2 +EOF +simple_test + +required_result 1 <= threshold 2 +EOF +simple_test + +setup "up" + +ok < reconfigure" + +setup + +public_address=$(ctdb_get_1_public_address) + +ok_null + +simple_test_event "takeip" $public_address + +ok < 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 < 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 < 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 < 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" < 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 <..NNN.sh + +The test helper functions will run with specified +options. If using the simple_test() helper function then the 1st + 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 "[] 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 >"${CTDB_BASE}/ctdb.conf" <"$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 <"${FAKE_PROC_NET_BONDING}/$_iface" <"$natgw_nodes" + + # Assume all of the nodes are on a /24 network and have IPv4 + # addresses: + read -r _ip <"$natgw_nodes" + + setup_script_options < 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 < 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 <, 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 <"$_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 </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 </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 <"$CTDB_LVS_NODES" +} + +check_ipvsadm() +{ + if [ "$1" = "NULL" ]; then + required_result 0 < 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 < 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 <] fake_stack_trace_for_pid_${_pid}/stack+0x0/0xff +EOF + ;; + *) + cat <, argv=) 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 < POSIX ADVISORY WRITE ${_lock_helper_pid} ${_locking_tdb_id} 168 170 +EOF + ) + elif [ "$_helper_scope" = "RECORD" ] && + [ "$_lock_type" = "FCNTL" ]; then + _helper_lock=$( + cat < POSIX ADVISORY WRITE ${_lock_helper_pid} ${_locking_tdb_id} 112736 112736 +EOF + ) + fi + _t=$( + cat <&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" <> 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" < 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 (, 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 <] 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 < ${_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 <&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 </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 <&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] []" + 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 <, argv=) 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="" + _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="" + _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}\.*" + [ -z "$_dev" ] || _re="${_re}\.*" + [ -z "$_metric" ] || _re="${_re}.*\.*" + 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 < 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 <&2 <&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 < ${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 <&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 <&2 + exit 2 +} + +error() +{ + cat >&2 <&2 + + cat >&2 <&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 <> NODE: 192.168.1.101 << +-n 192.168.1.101 hostname + +>> NODE: 192.168.1.102 << +-n 192.168.1.102 hostname + +>> NODE: 192.168.1.103 << +-n 192.168.1.103 hostname + +>> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0002.sh b/ctdb/tests/UNIT/onnode/0002.sh new file mode 100755 index 0000000..c3c8c77 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0002.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE -q all hostname" + +define_test "$cmd" "all nodes OK" + +required_result <> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0010.sh b/ctdb/tests/UNIT/onnode/0010.sh new file mode 100755 index 0000000..241cf58 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0010.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE 4 hostname" + +define_test "$cmd" "invalid pnn 4" + +required_result 1 <> NODE: 192.168.1.101 << +-n 192.168.1.101 hostname + +>> NODE: 192.168.1.102 << +-n 192.168.1.102 hostname + +>> NODE: 192.168.1.103 << +-n 192.168.1.103 hostname + +>> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0071.sh b/ctdb/tests/UNIT/onnode/0071.sh new file mode 100755 index 0000000..4f945ac --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0071.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE ok hostname" + +define_test "$cmd" "2nd node disconnected" + +ctdb_set_output <> NODE: 192.168.1.101 << +-n 192.168.1.101 hostname + +>> NODE: 192.168.1.103 << +-n 192.168.1.103 hostname + +>> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0072.sh b/ctdb/tests/UNIT/onnode/0072.sh new file mode 100755 index 0000000..51a4c46 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0072.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE ok hostname" + +define_test "$cmd" "2nd node disconnected, extra status columns" + +ctdb_set_output <> NODE: 192.168.1.101 << +-n 192.168.1.101 hostname + +>> NODE: 192.168.1.103 << +-n 192.168.1.103 hostname + +>> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/0075.sh b/ctdb/tests/UNIT/onnode/0075.sh new file mode 100755 index 0000000..92fe220 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/0075.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +cmd="$ONNODE con hostname" + +define_test "$cmd" "1st node disconnected" + +ctdb_set_output <> NODE: 192.168.1.102 << +-n 192.168.1.102 hostname + +>> NODE: 192.168.1.103 << +-n 192.168.1.103 hostname + +>> NODE: 192.168.1.104 << +-n 192.168.1.104 hostname +EOF + +simple_test $cmd diff --git a/ctdb/tests/UNIT/onnode/etc-ctdb/nodes b/ctdb/tests/UNIT/onnode/etc-ctdb/nodes new file mode 100644 index 0000000..e2fe268 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/etc-ctdb/nodes @@ -0,0 +1,4 @@ +192.168.1.101 +192.168.1.102 +192.168.1.103 +192.168.1.104 diff --git a/ctdb/tests/UNIT/onnode/scripts/local.sh b/ctdb/tests/UNIT/onnode/scripts/local.sh new file mode 100644 index 0000000..5b830c8 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/scripts/local.sh @@ -0,0 +1,64 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +# Default to just "onnode". +: ${ONNODE:=onnode} + +# Augment PATH with relevant stubs/ directory +stubs_dir="${CTDB_TEST_SUITE_DIR}/stubs" +[ -d "${stubs_dir}" ] || die "Failed to locate stubs/ subdirectory" +PATH="${stubs_dir}:${PATH}" + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "etc-ctdb" \ + functions + +define_test () +{ + _f=$(basename "$0") + + echo "$_f $1 - $2" +} + +# Set output for ctdb command. Option 1st argument is return code. +ctdb_set_output () +{ + _out="${CTDB_TEST_TMP_DIR}/ctdb.out" + cat >"$_out" + + _rc="${CTDB_TEST_TMP_DIR}/ctdb.rc" + echo "${1:-0}" >"$_rc" + + test_cleanup "rm -f $_out $_rc" +} + +extra_footer () +{ + cat <&1) + else + _out=$("$@" 2>&1) + fi + _rc=$? + _out=$(echo "$_out" | $_sort ) + + # Get the return code back into $? + (exit $_rc) + + result_check +} diff --git a/ctdb/tests/UNIT/onnode/stubs/ctdb b/ctdb/tests/UNIT/onnode/stubs/ctdb new file mode 100755 index 0000000..cca34c5 --- /dev/null +++ b/ctdb/tests/UNIT/onnode/stubs/ctdb @@ -0,0 +1,19 @@ +#!/bin/sh + +# Fake ctdb client for onnode tests. + +out="${CTDB_TEST_TMP_DIR}/ctdb.out" +if [ -r "$out" ] ; then + cat "$out" + + rc="${CTDB_TEST_TMP_DIR}/ctdb.rc" + if [ -r "$rc" ] ; then + exit $(cat "$rc") + fi + + exit 0 +fi + +echo "fake ctdb: no implementation for \"$*\"" + +exit 1 diff --git a/ctdb/tests/UNIT/onnode/stubs/ssh b/ctdb/tests/UNIT/onnode/stubs/ssh new file mode 100755 index 0000000..7be778f --- /dev/null +++ b/ctdb/tests/UNIT/onnode/stubs/ssh @@ -0,0 +1,2 @@ +#!/bin/sh +echo "$*" diff --git a/ctdb/tests/UNIT/shellcheck/base_scripts.sh b/ctdb/tests/UNIT/shellcheck/base_scripts.sh new file mode 100755 index 0000000..cbb8502 --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/base_scripts.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "base scripts" + +shellcheck_test \ + "${CTDB_SCRIPTS_BASE}/ctdb-crash-cleanup.sh" \ + "${CTDB_SCRIPTS_BASE}/debug-hung-script.sh" \ + "${CTDB_SCRIPTS_BASE}/debug_locks.sh" \ + "${CTDB_SCRIPTS_BASE}/nfs-linux-kernel-callout" \ + "${CTDB_SCRIPTS_BASE}/statd-callout" diff --git a/ctdb/tests/UNIT/shellcheck/ctdb_helpers.sh b/ctdb/tests/UNIT/shellcheck/ctdb_helpers.sh new file mode 100755 index 0000000..f6c7e31 --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/ctdb_helpers.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "ctdb helpers" + +shellcheck_test \ + "${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/ctdb_lvs" \ + "${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/ctdb_natgw" diff --git a/ctdb/tests/UNIT/shellcheck/event_scripts.sh b/ctdb/tests/UNIT/shellcheck/event_scripts.sh new file mode 100755 index 0000000..dfb5ede --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/event_scripts.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "event scripts" + +shellcheck_test "${CTDB_SCRIPTS_DATA_DIR}/events/"*/[0-9][0-9].* diff --git a/ctdb/tests/UNIT/shellcheck/functions.sh b/ctdb/tests/UNIT/shellcheck/functions.sh new file mode 100755 index 0000000..7ce206d --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/functions.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "functions file" + +shellcheck_test -s sh "${CTDB_SCRIPTS_BASE}/functions" diff --git a/ctdb/tests/UNIT/shellcheck/init_script.sh b/ctdb/tests/UNIT/shellcheck/init_script.sh new file mode 100755 index 0000000..1e1d54c --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/init_script.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "init script" + +script="$CTDB_SCRIPTS_INIT_SCRIPT" + +if [ -z "$script" ] ; then + script="/etc/init.d/ctdb" + if [ ! -r "$script" ] ; then + script="/usr/local/etc/init.d/ctdb" + fi + if [ ! -r "$script" ] ; then + ctdb_test_skip "Unable to find ctdb init script" + fi +fi + +shellcheck_test "$script" diff --git a/ctdb/tests/UNIT/shellcheck/scripts/local.sh b/ctdb/tests/UNIT/shellcheck/scripts/local.sh new file mode 100644 index 0000000..07e72c3 --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/scripts/local.sh @@ -0,0 +1,33 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +. "${TEST_SCRIPTS_DIR}/script_install_paths.sh" + +define_test () +{ + _f=$(basename "$0" ".sh") + + printf "%-28s - %s\n" "$_f" "$1" +} +shellcheck_test () +{ + ok_null + if type shellcheck >/dev/null 2>&1 ; then + # Skip some recent checks: + # + # SC1090: Can't follow non-constant source. Use a + # directive to specify location. + # SC1091: Not following: FILE was not specified as + # input (see shellcheck -x). + # - Shellcheck doesn't handle our includes + # very well. Adding directives to handle + # include for both in-tree and installed + # cases just isn't going to be possible. + # SC2162: read without -r will mangle backslashes. + # - We never read things with backslashes, + # unnecessary churn. + _excludes="SC1090,SC1091,SC2162" + unit_test shellcheck --exclude="$_excludes" "$@" + else + ctdb_test_skip "shellcheck not installed" + fi +} diff --git a/ctdb/tests/UNIT/shellcheck/tests.sh b/ctdb/tests/UNIT/shellcheck/tests.sh new file mode 100755 index 0000000..fe55381 --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/tests.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "tests" + +if "$CTDB_TESTS_ARE_INSTALLED" ; then + run_tests="${CTDB_SCRIPTS_TESTS_BIN_DIR}/ctdb_run_tests" + local_daemons="${CTDB_SCRIPTS_TESTS_BIN_DIR}/ctdb_local_daemons" +else + run_tests="${CTDB_TEST_DIR}/run_tests.sh" + local_daemons="${CTDB_TEST_DIR}/local_daemons.sh" +fi + +# Scripts +shellcheck_test \ + "$run_tests" \ + "$local_daemons" \ + "${TEST_SCRIPTS_DIR}/test_wrap" + +# Includes +shellcheck_test -s sh \ + "${TEST_SCRIPTS_DIR}/common.sh" \ + "${TEST_SCRIPTS_DIR}/script_install_paths.sh" \ + "${TEST_SCRIPTS_DIR}/unit.sh" + +shellcheck_test -s bash \ + "${TEST_SCRIPTS_DIR}/cluster.bash" \ + "${TEST_SCRIPTS_DIR}/integration.bash" \ + "${TEST_SCRIPTS_DIR}/integration_local_daemons.bash" \ + "${TEST_SCRIPTS_DIR}/integration_real_cluster.bash" + +# Test scripts and stubs +shellcheck_test -s sh \ + "${CTDB_TEST_DIR}/UNIT/eventscripts/scripts/"* \ + "${CTDB_TEST_DIR}/UNIT/eventscripts/stubs/"* diff --git a/ctdb/tests/UNIT/shellcheck/tools.sh b/ctdb/tests/UNIT/shellcheck/tools.sh new file mode 100755 index 0000000..2cd322c --- /dev/null +++ b/ctdb/tests/UNIT/shellcheck/tools.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "tools" + +shellcheck_test \ + "${CTDB_SCRIPTS_TOOLS_BIN_DIR}/onnode" \ + "${CTDB_SCRIPTS_TOOLS_BIN_DIR}/ctdb_diagnostics" diff --git a/ctdb/tests/UNIT/takeover/README b/ctdb/tests/UNIT/takeover/README new file mode 100644 index 0000000..764f389 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/README @@ -0,0 +1,5 @@ +Unit tests for the CTDB IP allocation algorithm(s). + +Test case filenames look like .NNN.sh, where +indicates the IP allocation algorithm to use. These use the +ctdb_takeover_test test program. diff --git a/ctdb/tests/UNIT/takeover/det.001.sh b/ctdb/tests/UNIT/takeover/det.001.sh new file mode 100755 index 0000000..ad50287 --- /dev/null +++ b/ctdb/tests/UNIT/takeover/det.001.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +setup_ctdb_base "$CTDB_TEST_TMP_DIR" "ctdb-etc" + +define_test "3 nodes, 1 healthy" + +required_result < all healthy" + +required_result < "$home_nodes" < "$home_nodes" < "$home_nodes" < 1 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result < 2 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result < all healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result < all healthy, info logging" + +export CTDB_TEST_LOGLEVEL=INFO + +required_result < 192.168.20.253 -> 0 [+0] +${TEST_DATE_STAMP}1 [-105738] -> 192.168.20.251 -> 2 [+0] +${TEST_DATE_STAMP}1 [-88649] -> 192.168.21.253 -> 0 [+14161] +${TEST_DATE_STAMP}1 [-75448] -> 192.168.20.254 -> 2 [+15625] +${TEST_DATE_STAMP}1 [-59823] -> 192.168.20.250 -> 0 [+29786] +${TEST_DATE_STAMP}1 [-44198] -> 192.168.21.254 -> 2 [+28322] +192.168.21.254 2 +192.168.21.253 0 +192.168.21.252 1 +192.168.20.254 2 +192.168.20.253 0 +192.168.20.252 1 +192.168.20.251 2 +192.168.20.250 0 +192.168.20.249 1 +EOF + +simple_test 0,0,0 < all healthy, debug logging" + +export CTDB_TEST_LOGLEVEL=DEBUG + +required_result < 192.168.21.254 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-116718] -> 192.168.21.254 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-116971] -> 192.168.21.253 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-116971] -> 192.168.21.253 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-116971] -> 192.168.21.252 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-116971] -> 192.168.21.252 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121110] -> 192.168.20.254 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121110] -> 192.168.20.254 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.253 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.253 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.252 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.252 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.251 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.251 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.250 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121363] -> 192.168.20.250 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-121110] -> 192.168.20.249 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-121110] -> 192.168.20.249 -> 2 [+0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-121363] -> 192.168.20.253 -> 0 [+0] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [0] +${TEST_DATE_STAMP} 1 [417803] +${TEST_DATE_STAMP} 2 [0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [417803] +${TEST_DATE_STAMP} 1 [-102557] -> 192.168.21.254 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-102557] -> 192.168.21.254 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-102810] -> 192.168.21.253 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-102810] -> 192.168.21.253 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-102810] -> 192.168.21.252 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-102810] -> 192.168.21.252 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-105234] -> 192.168.20.254 -> 0 [+15876] +${TEST_DATE_STAMP} 1 [-105234] -> 192.168.20.254 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-105234] -> 192.168.20.252 -> 0 [+16129] +${TEST_DATE_STAMP} 1 [-105234] -> 192.168.20.252 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-105738] -> 192.168.20.251 -> 0 [+15625] +${TEST_DATE_STAMP} 1 [-105738] -> 192.168.20.251 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-105738] -> 192.168.20.250 -> 0 [+15625] +${TEST_DATE_STAMP} 1 [-105738] -> 192.168.20.250 -> 2 [+0] +${TEST_DATE_STAMP} 1 [-105485] -> 192.168.20.249 -> 0 [+15625] +${TEST_DATE_STAMP} 1 [-105485] -> 192.168.20.249 -> 2 [+0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-105738] -> 192.168.20.251 -> 2 [+0] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [0] +${TEST_DATE_STAMP} 1 [312065] +${TEST_DATE_STAMP} 2 [0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [312065] +${TEST_DATE_STAMP} 1 [-88396] -> 192.168.21.254 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-88396] -> 192.168.21.254 -> 2 [+14161] +${TEST_DATE_STAMP} 1 [-88649] -> 192.168.21.253 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-88649] -> 192.168.21.253 -> 2 [+14161] +${TEST_DATE_STAMP} 1 [-88649] -> 192.168.21.252 -> 0 [+14161] +${TEST_DATE_STAMP} 1 [-88649] -> 192.168.21.252 -> 2 [+14161] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.254 -> 0 [+15876] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.254 -> 2 [+15625] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.252 -> 0 [+16129] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.252 -> 2 [+15625] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.250 -> 0 [+15625] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.250 -> 2 [+16129] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.249 -> 0 [+15625] +${TEST_DATE_STAMP} 1 [-89609] -> 192.168.20.249 -> 2 [+15876] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-88649] -> 192.168.21.253 -> 0 [+14161] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [14161] +${TEST_DATE_STAMP} 1 [223416] +${TEST_DATE_STAMP} 2 [0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [223416] +${TEST_DATE_STAMP} 1 [-72520] -> 192.168.21.254 -> 0 [+30037] +${TEST_DATE_STAMP} 1 [-72520] -> 192.168.21.254 -> 2 [+14161] +${TEST_DATE_STAMP} 1 [-72520] -> 192.168.21.252 -> 0 [+30290] +${TEST_DATE_STAMP} 1 [-72520] -> 192.168.21.252 -> 2 [+14161] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.254 -> 0 [+30037] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.254 -> 2 [+15625] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.252 -> 0 [+30290] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.252 -> 2 [+15625] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.250 -> 0 [+29786] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.250 -> 2 [+16129] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.249 -> 0 [+29786] +${TEST_DATE_STAMP} 1 [-75448] -> 192.168.20.249 -> 2 [+15876] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-75448] -> 192.168.20.254 -> 2 [+15625] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [14161] +${TEST_DATE_STAMP} 1 [147968] +${TEST_DATE_STAMP} 2 [15625] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [147968] +${TEST_DATE_STAMP} 1 [-58359] -> 192.168.21.254 -> 0 [+30037] +${TEST_DATE_STAMP} 1 [-58359] -> 192.168.21.254 -> 2 [+28322] +${TEST_DATE_STAMP} 1 [-58359] -> 192.168.21.252 -> 0 [+30290] +${TEST_DATE_STAMP} 1 [-58359] -> 192.168.21.252 -> 2 [+28322] +${TEST_DATE_STAMP} 1 [-59572] -> 192.168.20.252 -> 0 [+30290] +${TEST_DATE_STAMP} 1 [-59572] -> 192.168.20.252 -> 2 [+31501] +${TEST_DATE_STAMP} 1 [-59823] -> 192.168.20.250 -> 0 [+29786] +${TEST_DATE_STAMP} 1 [-59823] -> 192.168.20.250 -> 2 [+31754] +${TEST_DATE_STAMP} 1 [-59823] -> 192.168.20.249 -> 0 [+29786] +${TEST_DATE_STAMP} 1 [-59823] -> 192.168.20.249 -> 2 [+31501] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-59823] -> 192.168.20.250 -> 0 [+29786] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [43947] +${TEST_DATE_STAMP} 1 [88145] +${TEST_DATE_STAMP} 2 [15625] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [88145] +${TEST_DATE_STAMP} 1 [-44198] -> 192.168.21.254 -> 0 [+44198] +${TEST_DATE_STAMP} 1 [-44198] -> 192.168.21.254 -> 2 [+28322] +${TEST_DATE_STAMP} 1 [-44198] -> 192.168.21.252 -> 0 [+44451] +${TEST_DATE_STAMP} 1 [-44198] -> 192.168.21.252 -> 2 [+28322] +${TEST_DATE_STAMP} 1 [-43947] -> 192.168.20.252 -> 0 [+45915] +${TEST_DATE_STAMP} 1 [-43947] -> 192.168.20.252 -> 2 [+31501] +${TEST_DATE_STAMP} 1 [-43947] -> 192.168.20.249 -> 0 [+45662] +${TEST_DATE_STAMP} 1 [-43947] -> 192.168.20.249 -> 2 [+31501] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-44198] -> 192.168.21.254 -> 2 [+28322] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [43947] +${TEST_DATE_STAMP} 1 [43947] +${TEST_DATE_STAMP} 2 [43947] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 0 [43947] +${TEST_DATE_STAMP} 0 [-28322] -> 192.168.21.253 -> 0 [+28322] +${TEST_DATE_STAMP} 0 [-28322] -> 192.168.21.253 -> 2 [+44198] +${TEST_DATE_STAMP} 0 [-29786] -> 192.168.20.253 -> 0 [+29786] +${TEST_DATE_STAMP} 0 [-29786] -> 192.168.20.253 -> 2 [+45662] +${TEST_DATE_STAMP} 0 [-29786] -> 192.168.20.250 -> 0 [+29786] +${TEST_DATE_STAMP} 0 [-29786] -> 192.168.20.250 -> 2 [+45915] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [43947] +${TEST_DATE_STAMP} 1 [-28322] -> 192.168.21.252 -> 0 [+44451] +${TEST_DATE_STAMP} 1 [-28322] -> 192.168.21.252 -> 2 [+44198] +${TEST_DATE_STAMP} 1 [-29786] -> 192.168.20.252 -> 0 [+45915] +${TEST_DATE_STAMP} 1 [-29786] -> 192.168.20.252 -> 2 [+45662] +${TEST_DATE_STAMP} 1 [-29786] -> 192.168.20.249 -> 0 [+45662] +${TEST_DATE_STAMP} 1 [-29786] -> 192.168.20.249 -> 2 [+45662] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 2 [43947] +${TEST_DATE_STAMP} 2 [-28322] -> 192.168.21.254 -> 0 [+44198] +${TEST_DATE_STAMP} 2 [-28322] -> 192.168.21.254 -> 2 [+28322] +${TEST_DATE_STAMP} 2 [-29786] -> 192.168.20.254 -> 0 [+45662] +${TEST_DATE_STAMP} 2 [-29786] -> 192.168.20.254 -> 2 [+29786] +${TEST_DATE_STAMP} 2 [-29786] -> 192.168.20.251 -> 0 [+45915] +${TEST_DATE_STAMP} 2 [-29786] -> 192.168.20.251 -> 2 [+29786] +${TEST_DATE_STAMP} ---------------------------------------- +192.168.21.254 2 +192.168.21.253 0 +192.168.21.252 1 +192.168.20.254 2 +192.168.20.253 0 +192.168.20.252 1 +192.168.20.251 2 +192.168.20.250 0 +192.168.20.249 1 +EOF + +simple_test 0,0,0 < 1 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result < 2 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result < all healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result < all disconnected" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <3 unhealthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result <4 unhealthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result < 2 [+9216] +${TEST_DATE_STAMP} 130.216.30.173 -> 2 [+24345] +${TEST_DATE_STAMP} 130.216.30.171 -> 2 [+39970] +130.216.30.181 0 +130.216.30.180 1 +130.216.30.179 2 +130.216.30.178 3 +130.216.30.177 0 +130.216.30.176 1 +130.216.30.175 0 +130.216.30.174 1 +130.216.30.173 2 +130.216.30.172 3 +130.216.30.171 2 +130.216.30.170 3 +10.19.99.253 0 +10.19.99.252 1 +10.19.99.251 2 +10.19.99.250 3 +EOF + +# In this example were 4 releases from node 2 in a previous iteration +# +# Release of IP 130.216.30.179/27 on interface ethX1 node:3 +# Release of IP 130.216.30.173/27 on interface ethX1 node:0 +# Release of IP 130.216.30.171/27 on interface ethX1 node:1 +# Release of IP 10.19.99.251/22 on interface ethX2 node:0 +# +# However, one release failed so no takeovers were done. This means +# that the target node for each IP still thinks that the IPs are held +# by node 2. The release of 130.216.30.179 was so late that node 2 +# still thought that it held that address. + +simple_test 0,0,0,0 multi < 5 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +required_result < 4 healthy" + +export CTDB_TEST_LOGLEVEL=DEBUG + +required_result < 130.216.30.178 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-64566] -> 130.216.30.176 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-64315] -> 130.216.30.175 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-64315] -> 130.216.30.171 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-52489] -> 10.19.99.253 -> 0 [+0] +${TEST_DATE_STAMP} 1 [-52489] -> 10.19.99.250 -> 0 [+0] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-64566] -> 130.216.30.178 -> 0 [+0] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [0] +${TEST_DATE_STAMP} 1 [116804] +${TEST_DATE_STAMP} 2 [128630] +${TEST_DATE_STAMP} 3 [128881] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 3 [128881] +${TEST_DATE_STAMP} 3 [-55099] -> 130.216.30.180 -> 0 [+15625] +${TEST_DATE_STAMP} 3 [-55099] -> 130.216.30.177 -> 0 [+15876] +${TEST_DATE_STAMP} 3 [-55350] -> 130.216.30.174 -> 0 [+15129] +${TEST_DATE_STAMP} 3 [-55350] -> 130.216.30.173 -> 0 [+15129] +${TEST_DATE_STAMP} 3 [-36864] -> 10.19.99.252 -> 0 [+9216] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}3 [-55350] -> 130.216.30.174 -> 0 [+15129] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [15129] +${TEST_DATE_STAMP} 1 [116804] +${TEST_DATE_STAMP} 2 [128630] +${TEST_DATE_STAMP} 3 [73531] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 2 [128630] +${TEST_DATE_STAMP} 2 [-55099] -> 130.216.30.181 -> 0 [+30754] +${TEST_DATE_STAMP} 2 [-55099] -> 130.216.30.179 -> 0 [+31258] +${TEST_DATE_STAMP} 2 [-55099] -> 130.216.30.172 -> 0 [+31005] +${TEST_DATE_STAMP} 2 [-55099] -> 130.216.30.170 -> 0 [+30754] +${TEST_DATE_STAMP} 2 [-36864] -> 10.19.99.251 -> 0 [+18432] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}2 [-55099] -> 130.216.30.181 -> 0 [+30754] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [45883] +${TEST_DATE_STAMP} 1 [116804] +${TEST_DATE_STAMP} 2 [73531] +${TEST_DATE_STAMP} 3 [73531] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [116804] +${TEST_DATE_STAMP} 1 [-48690] -> 130.216.30.176 -> 0 [+46630] +${TEST_DATE_STAMP} 1 [-49186] -> 130.216.30.175 -> 0 [+46387] +${TEST_DATE_STAMP} 1 [-49186] -> 130.216.30.171 -> 0 [+45883] +${TEST_DATE_STAMP} 1 [-43273] -> 10.19.99.253 -> 0 [+27648] +${TEST_DATE_STAMP} 1 [-43273] -> 10.19.99.250 -> 0 [+27648] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP}1 [-43273] -> 10.19.99.253 -> 0 [+27648] +${TEST_DATE_STAMP}+++++++++++++++++++++++++++++++++++++++++ +${TEST_DATE_STAMP}Selecting most imbalanced node from: +${TEST_DATE_STAMP} 0 [73531] +${TEST_DATE_STAMP} 1 [73531] +${TEST_DATE_STAMP} 2 [73531] +${TEST_DATE_STAMP} 3 [73531] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 0 [73531] +${TEST_DATE_STAMP} 0 [-39970] -> 130.216.30.181 -> 0 [+39970] +${TEST_DATE_STAMP} 0 [-39970] -> 130.216.30.178 -> 0 [+39970] +${TEST_DATE_STAMP} 0 [-39474] -> 130.216.30.174 -> 0 [+39474] +${TEST_DATE_STAMP} 0 [-27648] -> 10.19.99.253 -> 0 [+27648] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 1 [73531] +${TEST_DATE_STAMP} 1 [-39474] -> 130.216.30.176 -> 0 [+55846] +${TEST_DATE_STAMP} 1 [-39970] -> 130.216.30.175 -> 0 [+55603] +${TEST_DATE_STAMP} 1 [-39970] -> 130.216.30.171 -> 0 [+55099] +${TEST_DATE_STAMP} 1 [-27648] -> 10.19.99.250 -> 0 [+43273] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 2 [73531] +${TEST_DATE_STAMP} 2 [-39474] -> 130.216.30.179 -> 0 [+56099] +${TEST_DATE_STAMP} 2 [-39970] -> 130.216.30.172 -> 0 [+55350] +${TEST_DATE_STAMP} 2 [-39970] -> 130.216.30.170 -> 0 [+55099] +${TEST_DATE_STAMP} 2 [-27648] -> 10.19.99.251 -> 0 [+43273] +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} ---------------------------------------- +${TEST_DATE_STAMP} CONSIDERING MOVES FROM 3 [73531] +${TEST_DATE_STAMP} 3 [-39970] -> 130.216.30.180 -> 0 [+56099] +${TEST_DATE_STAMP} 3 [-39970] -> 130.216.30.177 -> 0 [+55846] +${TEST_DATE_STAMP} 3 [-39474] -> 130.216.30.173 -> 0 [+55350] +${TEST_DATE_STAMP} 3 [-27648] -> 10.19.99.252 -> 0 [+43777] +${TEST_DATE_STAMP} ---------------------------------------- +130.216.30.181 0 +130.216.30.180 3 +130.216.30.179 2 +130.216.30.178 0 +130.216.30.177 3 +130.216.30.176 1 +130.216.30.175 1 +130.216.30.174 0 +130.216.30.173 3 +130.216.30.172 2 +130.216.30.171 1 +130.216.30.170 2 +10.19.99.253 0 +10.19.99.252 3 +10.19.99.251 2 +10.19.99.250 1 +EOF + +simple_test 0,0,0,0 < continue node 3, all healthy" + +required_result < stop node 0" + +required_result < stop node 1" + +required_result < Stop node 2" + +required_result < stop node 3" + +required_result < node 0 stopped" + +required_result < node 1 stopped" + +required_result < node 2 stopped" + +required_result < node 3 stopped" + +required_result < 3 -> 4 healthy" + +export CTDB_TEST_LOGLEVEL=ERR + +set -e + +echo "Nodes 2, 3 disconnected -> node 2 attaches" + +required_result < all healthy" + +required_result < 0 [+0] + 10.0.0.33 -> 2 [+0] + 10.0.0.31 -> 0 [+14884] + 10.0.0.32 -> 2 [+16129] +RELEASE_IP 10.0.0.34 succeeded on 1 nodes +RELEASE_IP 10.0.0.33 succeeded on 1 nodes +RELEASE_IP 10.0.0.32 succeeded on 1 nodes +RELEASE_IP 10.0.0.31 succeeded on 1 nodes +TAKEOVER_IP 10.0.0.34 succeeded on node 0 +TAKEOVER_IP 10.0.0.33 succeeded on node 2 +TAKEOVER_IP 10.0.0.32 succeeded on node 2 +TAKEOVER_IP 10.0.0.31 succeeded on node 0 +IPREALLOCATED succeeded on 3 nodes +EOF +test_takeover_helper + +required_result 0 </dev/null || echo) + if [ -n "$pid" ] ; then + kill $pid || true + rm -f "$ctdbd_pidfile" + fi + rm -f "$ctdbd_socket" + rm -rf "$ctdbd_dbdir" +} + +setup_ctdbd () +{ + debug "Setting up fake ctdbd" + + mkdir -p "$ctdbd_dbdir" + $VALGRIND fake_ctdbd -d "$FAKE_CTDBD_DEBUGLEVEL" \ + -s "$ctdbd_socket" -p "$ctdbd_pidfile" \ + -D "$ctdbd_dbdir" + # This current translates to a 6 second timeout for the + # important controls + ctdb setvar TakeoverTimeout 2 + test_cleanup cleanup_ctdbd +} + +# Render non-printable characters. The helper prints the status as +# binary, so render it for easy comparison. +result_filter () +{ + sed -e 's|ctdb-takeover\[[0-9]*\]: ||' +} + +ctdb_cmd () +{ + echo Running: ctdb -d "$CTDB_DEBUGLEVEL" "$@" + ctdb -d "$CTDB_DEBUGLEVEL" "$@" +} + +test_ctdb_ip_all () +{ + unit_test ctdb -d "$CTDB_DEBUGLEVEL" ip all || exit $? +} + +takeover_helper_out="${CTDB_TEST_TMP_DIR}/takover_helper.out" + +takeover_helper_format_outfd () +{ + od -A n -t d4 "$takeover_helper_out" | sed -e 's|[[:space:]]*||g' +} + +test_takeover_helper () +{ + ( + export CTDB_DEBUGLEVEL="$HELPER_DEBUGLEVEL" + export CTDB_LOGGING="file:" + unit_test ctdb_takeover_helper 3 "$ctdbd_socket" "$@" \ + 3>"$takeover_helper_out" + ) || exit $? + + case "$required_rc" in + 255) _t="-1" ;; + *) _t="$required_rc" ;; + esac + ok "$_t" + + unit_test_notrace takeover_helper_format_outfd + _ret=$? + rm "$takeover_helper_out" + [ $_ret -eq 0 ] || exit $_ret +} diff --git a/ctdb/tests/UNIT/tool/README b/ctdb/tests/UNIT/tool/README new file mode 100644 index 0000000..8160528 --- /dev/null +++ b/ctdb/tests/UNIT/tool/README @@ -0,0 +1,17 @@ +Unit tests for the ctdb tool (i.e. tools/ctdb). + +Test case filenames can take 2 forms: + +* func..NNN.sh + + Run in the ctdb tool code using the + ctdb_tool_functest test program. This test program uses test stubs + for CTDB client functions. + +* stubby..NNN.sh + + Run the ctdb_tool_stubby test program with as the 1st + argument - subsequent are passed to simple_test(). ctdb_tool_stubby + is linked against the test stubs for CTDB client functions. + +To add tests here you may need to add appropriate test stubs. diff --git a/ctdb/tests/UNIT/tool/ctdb.attach.001.sh b/ctdb/tests/UNIT/tool/ctdb.attach.001.sh new file mode 100755 index 0000000..82c3332 --- /dev/null +++ b/ctdb/tests/UNIT/tool/ctdb.attach.001.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +define_test "attach volatile database" + +setup_ctdbd < down + +ok_null +simple_test eth1 down + +ok < up + +ok_null +simple_test eth1 up + +ok < down +ok_null +simple_test eth1 down + +ok < down + +ok_null +simple_test eth2 down + +ok < up + +ok_null +simple_test eth1 up + +ok </dev/null || echo) + if [ -n "$pid" ] ; then + kill $pid || true + rm -f "$ctdbd_pidfile" + fi + rm -f "$ctdbd_socket" + rm -rf "$ctdbd_dbdir" +} + +setup_ctdbd () +{ + echo "Setting up fake ctdbd" + + mkdir -p "$ctdbd_dbdir" + $VALGRIND fake_ctdbd -d "$FAKE_CTDBD_DEBUGLEVEL" \ + -s "$ctdbd_socket" -p "$ctdbd_pidfile" \ + -D "$ctdbd_dbdir" + # Wait till fake_ctdbd is running + wait_until 10 test -S "$ctdbd_socket" || \ + die "fake_ctdbd failed to start" + + test_cleanup cleanup_ctdbd +} + +ctdbd_getpid () +{ + cat "$ctdbd_pidfile" +} + +setup_natgw () +{ + debug "Setting up NAT gateway" + + export CTDB_NATGW_HELPER="${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/ctdb_natgw" + export CTDB_NATGW_NODES="${CTDB_BASE}/natgw_nodes" + + cat >"$CTDB_NATGW_NODES" +} + +setup_lvs () +{ + debug "Setting up LVS" + + export CTDB_LVS_HELPER="${CTDB_SCRIPTS_TOOLS_HELPER_DIR}/ctdb_lvs" + export CTDB_LVS_NODES="${CTDB_BASE}/lvs_nodes" + + cat >"$CTDB_LVS_NODES" +} + +setup_nodes () +{ + _pnn="$1" + + _f="${CTDB_BASE}/nodes${_pnn:+.}${_pnn}" + + cat >"$_f" +} + +simple_test_other () +{ + unit_test $CTDB -d $CTDB_DEBUGLEVEL "$@" +} + +simple_test () +{ + simple_test_other $test_args "$@" +} diff --git a/ctdb/tests/etc-ctdb/events/legacy/00.test.script b/ctdb/tests/etc-ctdb/events/legacy/00.test.script new file mode 100755 index 0000000..c6797da --- /dev/null +++ b/ctdb/tests/etc-ctdb/events/legacy/00.test.script @@ -0,0 +1,30 @@ +#!/bin/sh +# event script for 'make test' + +. "${CTDB_BASE}/functions" + +load_script_options + +ctdb_check_args "$@" + +event="$1" +shift + +case "$event" in +monitor) + if [ "$CTDB_RUN_TIMEOUT_MONITOR" = "yes" ] ; then + timeout=9999 + echo "Sleeping for ${timeout} seconds..." + sleep $timeout + fi + ;; + +startup) + ifaces=$(ctdb ifaces -X | tail -n +2 | cut -d '|' -f2) + for i in $ifaces; do + ctdb setifacelink "$i" up + done + ;; +esac + +echo "${event} event${*:+ for }$*" diff --git a/ctdb/tests/local_daemons.sh b/ctdb/tests/local_daemons.sh new file mode 100755 index 0000000..b474668 --- /dev/null +++ b/ctdb/tests/local_daemons.sh @@ -0,0 +1,506 @@ +#!/bin/sh + +set -u + +export CTDB_TEST_MODE="yes" + +# Following 2 lines may be modified by installation script +CTDB_TESTS_ARE_INSTALLED=false +CTDB_TEST_DIR=$(dirname "$0") +export CTDB_TESTS_ARE_INSTALLED CTDB_TEST_DIR + +export TEST_SCRIPTS_DIR="${CTDB_TEST_DIR}/scripts" + +. "${TEST_SCRIPTS_DIR}/common.sh" + +if ! $CTDB_TESTS_ARE_INSTALLED ; then + hdir="$CTDB_SCRIPTS_HELPER_BINDIR" + export CTDB_EVENTD="${hdir}/ctdb-eventd" + export CTDB_EVENT_HELPER="${hdir}/ctdb-event" + export CTDB_LOCK_HELPER="${hdir}/ctdb_lock_helper" + export CTDB_RECOVERY_HELPER="${hdir}/ctdb_recovery_helper" + export CTDB_TAKEOVER_HELPER="${hdir}/ctdb_takeover_helper" + export CTDB_CLUSTER_MUTEX_HELPER="${hdir}/ctdb_mutex_fcntl_helper" +fi + +######################################## + +# If the given IP is hosted then print 2 items: maskbits and iface +have_ip () +{ + _addr="$1" + + case "$_addr" in + *:*) _bits=128 ;; + *) _bits=32 ;; + esac + + _t=$(ip addr show to "${_addr}/${_bits}") + [ -n "$_t" ] +} + +setup_nodes () +{ + _num_nodes="$1" + _use_ipv6="$2" + + _have_all_ips=true + for _i in $(seq 0 $((_num_nodes - 1)) ) ; do + if $_use_ipv6 ; then + _j=$(printf "%04x" $((0x5f00 + 1 + _i)) ) + _node_ip="fd00::5357:${_j}" + if have_ip "$_node_ip" ; then + echo "$_node_ip" + else + cat >&2 <&2 < setup [ ... ] + +Options: + -C Comment out given config item (default: item uncommented) + -F Disable failover (default: failover enabled) + -N Nodes file (default: automatically generated) + -n Number of nodes (default: 3) + -P Public addresses file (default: automatically generated) + -R Use a command for the cluster lock (default: use a file) + -r &2 < ${1} + + can be "all", a node number or any specification supported by onnode +EOF + + exit 1 +} + +local_daemons_start_socket_wrapper () +{ + _so="${directory}/libsocket-wrapper.so" + _d="${directory}/sw" + + if [ -d "$_d" ] && [ -f "$_so" ] ; then + export SOCKET_WRAPPER_DIR="$_d" + export LD_PRELOAD="$_so" + export SOCKET_WRAPPER_DIR_ALLOW_ORIG="1" + fi +} + +local_daemons_start () +{ + if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then + local_daemons_generic_usage "start" + fi + + local_daemons_start_socket_wrapper + + _nodes="$1" + + onnode_common + + onnode -i "$_nodes" "${VALGRIND:-} ctdbd" +} + +local_daemons_stop () +{ + if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then + local_daemons_generic_usage "stop" + fi + + _nodes="$1" + + onnode_common + + onnode -p "$_nodes" \ + "if [ -e \"\${CTDB_BASE}/run/ctdbd.pid\" ] ; then \ + ${CTDB:-${VALGRIND:-} ctdb} shutdown ; \ + fi" +} + +local_daemons_onnode_usage () +{ + cat >&2 < onnode ... + + can be "all", a node number or any specification supported by onnode +EOF + + exit 1 +} + +local_daemons_onnode () +{ + if [ $# -lt 2 ] || [ "$1" = "-h" ] ; then + local_daemons_onnode_usage + fi + + _nodes="$1" + shift + + onnode_common + + onnode "$_nodes" "$@" +} + +local_daemons_print_socket () +{ + if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then + local_daemons_generic_usage "print-socket" + fi + + _nodes="$1" + shift + + onnode_common + + _path="${CTDB_SCRIPTS_HELPER_BINDIR}/ctdb-path" + onnode -q "$_nodes" "${VALGRIND:-} ${_path} socket ctdbd" +} + +local_daemons_print_log () +{ + if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then + local_daemons_generic_usage "print-log" + fi + + _nodes="$1" + shift + + onnode_common + + # shellcheck disable=SC2016 + # $CTDB_BASE must only be expanded under onnode, not in top-level shell + onnode -q "$_nodes" 'cat ${CTDB_BASE}/log.ctdb' | + sort + +} + +local_daemons_tail_log () +{ + if [ $# -ne 1 ] || [ "$1" = "-h" ] ; then + local_daemons_generic_usage "tail-log" + fi + + _nodes="$1" + shift + + onnode_common + + # shellcheck disable=SC2016,SC2046 + # $CTDB_BASE must only be expanded under onnode, not in top-level shell + # Intentional word splitting to separate log filenames + tail -f $(onnode -q "$_nodes" 'echo ${CTDB_BASE}/log.ctdb') +} + +usage () +{ + cat < [ ... ] + +Commands: + setup Set up daemon configuration according to given options + start Start specified daemon(s) + stop Stop specified daemon(s) + onnode Run a command in the environment of specified daemon(s) + print-socket Print the Unix domain socket used by specified daemon(s) + print-log Print logs for specified daemon(s) to stdout + tail-log Follow logs for specified daemon(s) to stdout + +All commands use for daemon configuration + +Run command with -h option to see per-command usage +EOF + + exit 1 +} + +if [ $# -lt 2 ] ; then + usage +fi + +directory="$1" +command="$2" +shift 2 + +case "$command" in +setup) local_daemons_setup "$@" ;; +ssh) local_daemons_ssh "$@" ;; # Internal, not shown by usage() +start) local_daemons_start "$@" ;; +stop) local_daemons_stop "$@" ;; +onnode) local_daemons_onnode "$@" ;; +print-socket) local_daemons_print_socket "$@" ;; +print-log) local_daemons_print_log "$@" ;; +tail-log) local_daemons_tail_log "$@" ;; +*) usage ;; +esac diff --git a/ctdb/tests/run_cluster_tests.sh b/ctdb/tests/run_cluster_tests.sh new file mode 120000 index 0000000..5236e32 --- /dev/null +++ b/ctdb/tests/run_cluster_tests.sh @@ -0,0 +1 @@ +run_tests.sh \ No newline at end of file diff --git a/ctdb/tests/run_tests.sh b/ctdb/tests/run_tests.sh new file mode 100755 index 0000000..dfe2a9a --- /dev/null +++ b/ctdb/tests/run_tests.sh @@ -0,0 +1,399 @@ +#!/usr/bin/env bash + +usage() { + cat < Iterate tests times, exiting on failure (implies -e, -N) + -l Use daemons for local daemon integration tests + -L Print daemon logs on test failure (only some tests) + -N Don't print summary of tests results after running all tests + -q Quiet - don't show tests being run (still displays summary) + -S Use socket wrapper library for local integration tests + -v Verbose - print test output for non-failures (only some tests) + -V Use as test state directory + -x Trace this script with the -x option + -X Trace certain scripts run by tests using -x (only some tests) +EOF + exit 1 +} + +# Print a message and exit. +die () +{ + echo "$1" >&2 ; exit "${2:-1}" +} + +###################################################################### + +with_summary=true +quiet=false +exit_on_fail=false +max_iterations=1 +no_header=false +test_state_dir="" +cleanup=false +test_time_limit=3600 + +export CTDB_TEST_VERBOSE=false +export CTDB_TEST_COMMAND_TRACE=false +export CTDB_TEST_CAT_RESULTS_OPTS="" +export CTDB_TEST_DIFF_RESULTS=false +export CTDB_TEST_PRINT_LOGS_ON_ERROR=false +export CTDB_TEST_LOCAL_DAEMONS=3 +export CTDB_TEST_SWRAP_SO_PATH="" + +while getopts "AcCDehHI:l:LNqS:T:vV:xX?" opt ; do + case "$opt" in + A) CTDB_TEST_CAT_RESULTS_OPTS="-A" ;; + c) CTDB_TEST_LOCAL_DAEMONS="" ;; + C) cleanup=true ;; + D) CTDB_TEST_DIFF_RESULTS=true ;; + e) exit_on_fail=true ;; + H) no_header=true ;; + I) max_iterations="$OPTARG" ; exit_on_fail=true ; with_summary=false ;; + l) CTDB_TEST_LOCAL_DAEMONS="$OPTARG" ;; + L) CTDB_TEST_PRINT_LOGS_ON_ERROR=true ;; + N) with_summary=false ;; + q) quiet=true ;; + S) CTDB_TEST_SWRAP_SO_PATH="$OPTARG" ;; + T) test_time_limit="$OPTARG" ;; + v) CTDB_TEST_VERBOSE=true ;; + V) test_state_dir="$OPTARG" ;; + x) set -x ;; + X) CTDB_TEST_COMMAND_TRACE=true ;; + \?|h) usage ;; + esac +done +shift $((OPTIND - 1)) + +case $(basename "$0") in + *run_cluster_tests*) + # Running on a cluster... same as -c + CTDB_TEST_LOCAL_DAEMONS="" + ;; +esac + +if $quiet ; then + show_progress() { cat >/dev/null ; } +else + show_progress() { cat ; } +fi + +###################################################################### + +test_header () +{ + local name="$1" + + echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--" + echo "Running test $name ($(date '+%T'))" + echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--" +} + +test_footer () +{ + local f="$1" + local status="$2" + local interp="$3" + local duration="$4" + + local statstr="" + if [ "$status" -eq 0 ] ; then + statstr="" + else + statstr=" (status $status)" + fi + + echo "==========================================================================" + echo "TEST ${interp}: ${f}${statstr} (duration: ${duration}s)" + echo "==========================================================================" +} + +ctdb_test_run () +{ + local f="$1" + + $no_header || test_header "$f" + + local status=0 + local start_time + + start_time=$(date '+%s') + + if [ -x "$f" ] ; then + timeout "$test_time_limit" "$f" >"$summary_file" + fi + + # Skipped tests should not cause failure + case "$status" in + 77) + status=0 + ;; + esac + + return $status +} + +###################################################################### + +tests_total=0 +tests_passed=0 +tests_skipped=0 +tests_failed=0 + +if ! type mktemp >/dev/null 2>&1 ; then + # Not perfect, but it will do... + mktemp () + { + local dir=false + if [ "$1" = "-d" ] ; then + dir=true + fi + local t="${TMPDIR:-/tmp}/tmp.$$.$RANDOM" + ( + umask 077 + if $dir ; then + mkdir "$t" + else + : >"$t" + fi + ) + echo "$t" + } +fi + +set -o pipefail + +run_one_test () +{ + local f="$1" + + CTDB_TEST_SUITE_DIR=$(dirname "$f") + export CTDB_TEST_SUITE_DIR + # This expands the most probable problem cases like "." and "..". + if [ "$(dirname "$CTDB_TEST_SUITE_DIR")" = "." ] ; then + CTDB_TEST_SUITE_DIR=$(cd "$CTDB_TEST_SUITE_DIR" && pwd) + fi + + # Set CTDB_TEST_TMP_DIR + # + # Determine the relative test suite subdirectory. The top-level + # test directory needs to be a prefix of the test suite directory, + # so make absolute versions of both. + local test_dir test_suite_dir reldir + test_dir=$(cd "$CTDB_TEST_DIR" && pwd) + test_suite_dir=$(cd "$CTDB_TEST_SUITE_DIR" && pwd) + reldir="${test_suite_dir#"${test_dir}"/}" + + export CTDB_TEST_TMP_DIR="${test_state_dir}/${reldir}" + rm -rf "$CTDB_TEST_TMP_DIR" + mkdir -p "$CTDB_TEST_TMP_DIR" + + ctdb_test_run "$f" + status=$? +} + +run_tests () +{ + local f + + for f ; do + case "$f" in + */README|*/README.md) + continue + ;; + esac + + if [ ! -e "$f" ] ; then + # Can't find it? Check relative to CTDB_TEST_DIR. + # Strip off current directory from beginning, + # if there, just to make paths more friendly. + f="${CTDB_TEST_DIR#"${PWD}"/}/${f}" + fi + + if [ -d "$f" ] ; then + local test_dir dir reldir subtests + + test_dir=$(cd "$CTDB_TEST_DIR" && pwd) + dir=$(cd "$f" && pwd) + reldir="${dir#"${test_dir}"/}" + + case "$reldir" in + */*/*) + die "test \"$f\" is not recognised" + ;; + */*) + # This is a test suite + subtests=$(echo "${f%/}/"*".sh") + if [ "$subtests" = "${f%/}/*.sh" ] ; then + # Probably empty directory + die "test \"$f\" is not recognised" + fi + ;; + CLUSTER|INTEGRATION|UNIT) + # A collection of test suites + subtests=$(echo "${f%/}/"*) + ;; + *) + die "test \"$f\" is not recognised" + esac + + # Recurse - word-splitting wanted + # shellcheck disable=SC2086 + run_tests $subtests + elif [ -f "$f" ] ; then + run_one_test "$f" + else + # Time to give up + die "test \"$f\" is not recognised" + fi + + if $exit_on_fail && [ "$status" -ne 0 ] ; then + return "$status" + fi + done +} + +export CTDB_TEST_MODE="yes" + +# Following 2 lines may be modified by installation script +CTDB_TESTS_ARE_INSTALLED=false +CTDB_TEST_DIR=$(dirname "$0") +export CTDB_TESTS_ARE_INSTALLED CTDB_TEST_DIR + +if [ -z "$test_state_dir" ] ; then + if $CTDB_TESTS_ARE_INSTALLED ; then + test_state_dir=$(mktemp -d) + else + test_state_dir="${CTDB_TEST_DIR}/var" + fi +fi +mkdir -p "$test_state_dir" + +summary_file="${test_state_dir}/.summary" +: >"$summary_file" + +export TEST_SCRIPTS_DIR="${CTDB_TEST_DIR}/scripts" + +# If no tests specified then run some defaults +if [ -z "$1" ] ; then + if [ -n "$CTDB_TEST_LOCAL_DAEMONS" ] ; then + set -- UNIT INTEGRATION + else + set -- INTEGRATION CLUSTER + fi +fi + +do_cleanup () +{ + if $cleanup ; then + echo "Removing test state directory: ${test_state_dir}" + rm -rf "$test_state_dir" + else + echo "Not cleaning up test state directory: ${test_state_dir}" + fi +} + +trap "do_cleanup ; exit 130" SIGINT +trap "do_cleanup ; exit 143" SIGTERM + +iterations=0 +# Special case: -I 0 means iterate forever (until failure) +while [ "$max_iterations" -eq 0 ] || [ $iterations -lt "$max_iterations" ] ; do + iterations=$((iterations + 1)) + + if [ "$max_iterations" -ne 1 ] ; then + echo + echo "##################################################" + echo "ITERATION ${iterations}" + echo "##################################################" + echo + fi + + run_tests "$@" + status=$? + + if [ $status -ne 0 ] ; then + break + fi +done + +if $with_summary ; then + if [ "$status" -eq 0 ] || ! $exit_on_fail ; then + echo + cat "$summary_file" + + echo + tests_run=$((tests_total - tests_skipped)) + printf '%d/%d tests passed' $tests_passed $tests_run + if [ $tests_skipped -gt 0 ] ; then + printf ' (%d skipped)' $tests_skipped + fi + printf '\n' + fi +fi +rm -f "$summary_file" + +echo + +do_cleanup + +if $no_header || $exit_on_fail ; then + exit "$status" +elif [ $tests_failed -gt 0 ] ; then + exit 1 +else + exit 0 +fi diff --git a/ctdb/tests/scripts/cluster.bash b/ctdb/tests/scripts/cluster.bash new file mode 100755 index 0000000..916fc84 --- /dev/null +++ b/ctdb/tests/scripts/cluster.bash @@ -0,0 +1,18 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +if ! ctdb_test_on_cluster ; then + # Do not run on local daemons + ctdb_test_error \ + "ERROR: This test must be run against a real/virtual cluster" +fi + +h=$(hostname) + +for i in $(onnode -q all hostname) ; do + if [ "$h" = "$i" ] ; then + ctdb_test_error \ + "ERROR: This test must not be run from a cluster node" + fi +done diff --git a/ctdb/tests/scripts/common.sh b/ctdb/tests/scripts/common.sh new file mode 100644 index 0000000..5bc5869 --- /dev/null +++ b/ctdb/tests/scripts/common.sh @@ -0,0 +1,146 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +# Common variables and functions for all CTDB tests. + + +# Commands on different platforms may quote or sort things differently +# without this +export LANG=C + +# Print a message and exit. +die () +{ + echo "$1" >&2 ; exit "${2:-1}" +} + +. "${TEST_SCRIPTS_DIR}/script_install_paths.sh" + +if [ -d "$CTDB_SCRIPTS_TOOLS_BIN_DIR" ] ; then + PATH="${CTDB_SCRIPTS_TOOLS_BIN_DIR}:${PATH}" +fi + +if [ -d "$CTDB_SCRIPTS_TESTS_LIBEXEC_DIR" ] ; then + PATH="${CTDB_SCRIPTS_TESTS_LIBEXEC_DIR}:${PATH}" +fi + +ctdb_test_error () +{ + if [ $# -gt 0 ] ; then + echo "$*" + fi + exit 99 +} + +ctdb_test_fail () +{ + if [ $# -gt 0 ] ; then + echo "$*" + fi + exit 1 +} + +ctdb_test_skip () +{ + if [ $# -gt 0 ] ; then + echo "$*" + fi + exit 77 +} + +# "$@" is supported OSes +ctdb_test_check_supported_OS () +{ + _os=$(uname -s) + for _i ; do + if [ "$_os" = "$_i" ] ; then + return + fi + done + + ctdb_test_skip "This test is not supported on ${_os}" +} + +# Wait until either timeout expires or command succeeds. The command +# will be tried once per second, unless timeout has format T/I, where +# I is the recheck interval. +wait_until () +{ + _timeout="$1" ; shift # "$@" is the command... + + _interval=1 + case "$_timeout" in + */*) + _interval="${_timeout#*/}" + _timeout="${_timeout%/*}" + esac + + _negate=false + if [ "$1" = "!" ] ; then + _negate=true + shift + fi + + printf '<%d|' "$_timeout" + _t="$_timeout" + while [ "$_t" -gt 0 ] ; do + _rc=0 + "$@" || _rc=$? + if { ! $_negate && [ $_rc -eq 0 ] ; } || \ + { $_negate && [ $_rc -ne 0 ] ; } ; then + echo "|$((_timeout - _t))|" + echo "OK" + return 0 + fi + for _i in $(seq 1 "$_interval") ; do + printf '.' + done + _t=$((_t - _interval)) + sleep "$_interval" + done + + echo "*TIMEOUT*" + + return 1 +} + +# setup_ctdb_base [item-to-copy]... +setup_ctdb_base () +{ + [ $# -ge 2 ] || die "usage: setup_ctdb_base [item]..." + # If empty arguments are passed then we attempt to remove / + # (i.e. the root directory) below + if [ -z "$1" ] || [ -z "$2" ] ; then + die "usage: setup_ctdb_base [item]..." + fi + + _parent="$1" + _subdir="$2" + + # Other arguments are files/directories to copy + shift 2 + + export CTDB_BASE="${_parent}/${_subdir}" + if [ -d "$CTDB_BASE" ] ; then + rm -r "$CTDB_BASE" + fi + mkdir -p "$CTDB_BASE" || die "Failed to create CTDB_BASE=$CTDB_BASE" + mkdir -p "${CTDB_BASE}/run" || die "Failed to create ${CTDB_BASE}/run" + mkdir -p "${CTDB_BASE}/var" || die "Failed to create ${CTDB_BASE}/var" + + for _i ; do + cp -pr "${CTDB_SCRIPTS_BASE}/${_i}" "${CTDB_BASE}/" + done + + mkdir -p "${CTDB_BASE}/events/legacy" + + if [ -z "$CTDB_TEST_SUITE_DIR" ] ; then + return + fi + + for _i in "${CTDB_TEST_SUITE_DIR}/etc-ctdb/"* ; do + # No/empty etc-ctdb directory + [ -e "$_i" ] || break + + cp -pr "$_i" "${CTDB_BASE}/" + done +} diff --git a/ctdb/tests/scripts/integration.bash b/ctdb/tests/scripts/integration.bash new file mode 100644 index 0000000..65e974e --- /dev/null +++ b/ctdb/tests/scripts/integration.bash @@ -0,0 +1,864 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +. "${TEST_SCRIPTS_DIR}/common.sh" + +###################################################################### + +export CTDB_TIMEOUT=60 + +if [ -n "$CTDB_TEST_REMOTE_DIR" ] ; then + CTDB_TEST_WRAPPER="${CTDB_TEST_REMOTE_DIR}/test_wrap" +else + _d=$(cd "$TEST_SCRIPTS_DIR" && echo "$PWD") + CTDB_TEST_WRAPPER="$_d/test_wrap" +fi +export CTDB_TEST_WRAPPER + +# If $VALGRIND is set then use it whenever ctdb is called, but only if +# $CTDB is not already set. +[ -n "$CTDB" ] || export CTDB="${VALGRIND}${VALGRIND:+ }ctdb" + +# why??? +PATH="${TEST_SCRIPTS_DIR}:${PATH}" + +###################################################################### + +ctdb_test_on_cluster () +{ + [ -z "$CTDB_TEST_LOCAL_DAEMONS" ] +} + +ctdb_test_exit () +{ + local status=$? + + trap - 0 + + # run_tests.sh pipes stdout into tee. If the tee process is + # killed then any attempt to write to stdout (e.g. echo) will + # result in SIGPIPE, terminating the caller. Ignore SIGPIPE to + # ensure that all clean-up is run. + trap '' PIPE + + # Avoid making a test fail from this point onwards. The test is + # now complete. + set +e + + echo "*** TEST COMPLETED (RC=$status) AT $(date '+%F %T'), CLEANING UP..." + + eval "$ctdb_test_exit_hook" || true + unset ctdb_test_exit_hook + + echo "Stopping cluster..." + ctdb_nodes_stop || ctdb_test_error "Cluster shutdown failed" + + exit $status +} + +ctdb_test_exit_hook_add () +{ + ctdb_test_exit_hook="${ctdb_test_exit_hook}${ctdb_test_exit_hook:+ ; }$*" +} + +# Setting cleanup_pid to @ will cause to be killed on +# when the test completes. To cancel, just unset cleanup_pid. +ctdb_test_cleanup_pid="" +ctdb_test_cleanup_pid_exit_hook () +{ + if [ -n "$ctdb_test_cleanup_pid" ] ; then + local pid="${ctdb_test_cleanup_pid%@*}" + local node="${ctdb_test_cleanup_pid#*@}" + + try_command_on_node "$node" "kill ${pid}" + fi +} + +ctdb_test_exit_hook_add ctdb_test_cleanup_pid_exit_hook + +ctdb_test_cleanup_pid_set () +{ + local node="$1" + local pid="$2" + + ctdb_test_cleanup_pid="${pid}@${node}" +} + +ctdb_test_cleanup_pid_clear () +{ + ctdb_test_cleanup_pid="" +} + +# -n option means do not configure/start cluster +ctdb_test_init () +{ + trap "ctdb_test_exit" 0 + + ctdb_nodes_stop >/dev/null 2>&1 || true + + if [ "$1" != "-n" ] ; then + echo "Configuring cluster..." + setup_ctdb || ctdb_test_error "Cluster configuration failed" + + echo "Starting cluster..." + ctdb_init || ctdb_test_error "Cluster startup failed" + fi + + echo "*** SETUP COMPLETE AT $(date '+%F %T'), RUNNING TEST..." +} + +ctdb_nodes_start_custom () +{ + if ctdb_test_on_cluster ; then + ctdb_test_error "ctdb_nodes_start_custom() on real cluster" + fi + + ctdb_nodes_stop >/dev/null 2>&1 || true + + echo "Configuring cluster..." + setup_ctdb "$@" || ctdb_test_error "Cluster configuration failed" + + echo "Starting cluster..." + ctdb_init || ctdb_test_fail "Cluster startup failed" +} + +ctdb_test_skip_on_cluster () +{ + if ctdb_test_on_cluster ; then + ctdb_test_skip \ + "SKIPPING this test - only runs against local daemons" + fi +} + + +ctdb_nodes_restart () +{ + ctdb_nodes_stop "$@" + ctdb_nodes_start "$@" +} + +######################################## + +# Sets: $out, $outfile +# * The first 1KB of output is put into $out +# * Tests should use $outfile for handling large output +# * $outfile is removed after each test +out="" +outfile="${CTDB_TEST_TMP_DIR}/try_command_on_node.out" + +outfile_cleanup () +{ + rm -f "$outfile" +} + +ctdb_test_exit_hook_add outfile_cleanup + +try_command_on_node () +{ + local nodespec="$1" ; shift + + local verbose=false + local onnode_opts="" + + while [ "${nodespec#-}" != "$nodespec" ] ; do + if [ "$nodespec" = "-v" ] ; then + verbose=true + else + onnode_opts="${onnode_opts}${onnode_opts:+ }${nodespec}" + fi + nodespec="$1" ; shift + done + + local cmd="$*" + + local status=0 + # Intentionally unquoted - might be empty + # shellcheck disable=SC2086 + onnode -q $onnode_opts "$nodespec" "$cmd" >"$outfile" 2>&1 || status=$? + out=$(dd if="$outfile" bs=1k count=1 2>/dev/null) + + if [ $status -ne 0 ] ; then + echo "Failed to execute \"$cmd\" on node(s) \"$nodespec\"" + cat "$outfile" + return $status + fi + + if $verbose ; then + echo "Output of \"$cmd\":" + cat "$outfile" || true + fi +} + +_run_onnode () +{ + local thing="$1" + shift + + local options nodespec + + while : ; do + case "$1" in + -*) + options="${options}${options:+ }${1}" + shift + ;; + *) + nodespec="$1" + shift + break + esac + done + + # shellcheck disable=SC2086 + # $options can be multi-word + try_command_on_node $options "$nodespec" "${thing} $*" +} + +ctdb_onnode () +{ + _run_onnode "$CTDB" "$@" +} + +testprog_onnode () +{ + _run_onnode "${CTDB_TEST_WRAPPER} ${VALGRIND}" "$@" +} + +function_onnode () +{ + _run_onnode "${CTDB_TEST_WRAPPER}" "$@" +} + +sanity_check_output () +{ + local min_lines="$1" + local regexp="$2" # Should be anchored as necessary. + + local ret=0 + + local num_lines + num_lines=$(wc -l <"$outfile" | tr -d '[:space:]') + echo "There are $num_lines lines of output" + if [ "$num_lines" -lt "$min_lines" ] ; then + ctdb_test_fail "BAD: that's less than the required number (${min_lines})" + fi + + local status=0 + local unexpected # local doesn't pass through status of command on RHS. + unexpected=$(grep -Ev "$regexp" "$outfile") || status=$? + + # Note that this is reversed. + if [ $status -eq 0 ] ; then + echo "BAD: unexpected lines in output:" + echo "$unexpected" | cat -A + ret=1 + else + echo "Output lines look OK" + fi + + return $ret +} + +select_test_node () +{ + try_command_on_node any ctdb pnn || return 1 + + test_node="$out" + echo "Selected node ${test_node}" +} + +# This returns a list of "ip node" lines in $outfile +all_ips_on_node() +{ + local node="$1" + try_command_on_node "$node" \ + "$CTDB ip -X | awk -F'|' 'NR > 1 { print \$2, \$3 }'" +} + +_select_test_node_and_ips () +{ + try_command_on_node any \ + "$CTDB ip -X all | awk -F'|' 'NR > 1 { print \$2, \$3 }'" + + test_node="" # this matches no PNN + test_node_ips="" + local ip pnn + while read -r ip pnn ; do + if [ -z "$test_node" ] && [ "$pnn" != "-1" ] ; then + test_node="$pnn" + fi + if [ "$pnn" = "$test_node" ] ; then + test_node_ips="${test_node_ips}${test_node_ips:+ }${ip}" + fi + done <"$outfile" + + echo "Selected node ${test_node} with IPs: ${test_node_ips}." + test_ip="${test_node_ips%% *}" + + # test_prefix used by caller + # shellcheck disable=SC2034 + case "$test_ip" in + *:*) test_prefix="${test_ip}/128" ;; + *) test_prefix="${test_ip}/32" ;; + esac + + [ -n "$test_node" ] || return 1 +} + +select_test_node_and_ips () +{ + local timeout=10 + while ! _select_test_node_and_ips ; do + echo "Unable to find a test node with IPs assigned" + if [ $timeout -le 0 ] ; then + ctdb_test_error "BAD: Too many attempts" + return 1 + fi + sleep_for 1 + timeout=$((timeout - 1)) + done + + return 0 +} + +# Sets: mask, iface +get_test_ip_mask_and_iface () +{ + # Find the interface + ctdb_onnode "$test_node" "ip -v -X" + iface=$(awk -F'|' -v ip="$test_ip" '$2 == ip { print $4 }' "$outfile") + + if ctdb_test_on_cluster ; then + # Find the netmask + try_command_on_node "$test_node" ip addr show to "$test_ip" + mask="${out##*/}" + mask="${mask%% *}" + else + mask="24" + fi + + echo "$test_ip/$mask is on $iface" +} + +ctdb_get_all_pnns () +{ + try_command_on_node -q all "$CTDB pnn" + all_pnns="$out" +} + +# The subtlety is that "ctdb delip" will fail if the IP address isn't +# configured on a node... +delete_ip_from_all_nodes () +{ + _ip="$1" + + ctdb_get_all_pnns + + _nodes="" + + for _pnn in $all_pnns ; do + all_ips_on_node "$_pnn" + while read -r _i _ ; do + if [ "$_ip" = "$_i" ] ; then + _nodes="${_nodes}${_nodes:+,}${_pnn}" + fi + done <"$outfile" + done + + try_command_on_node -pq "$_nodes" "$CTDB delip $_ip" +} + +####################################### + +sleep_for () +{ + echo -n "=${1}|" + for i in $(seq 1 "$1") ; do + echo -n '.' + sleep 1 + done + echo '|' +} + +_cluster_is_healthy () +{ + $CTDB nodestatus all >/dev/null +} + +_cluster_is_recovered () +{ + node_has_status 0 recovered +} + +_cluster_is_ready () +{ + _cluster_is_healthy && _cluster_is_recovered +} + +cluster_is_healthy () +{ + if onnode 0 "$CTDB_TEST_WRAPPER" _cluster_is_healthy ; then + echo "Cluster is HEALTHY" + if ! onnode 0 "$CTDB_TEST_WRAPPER" _cluster_is_recovered ; then + echo "WARNING: cluster in recovery mode!" + fi + return 0 + fi + + echo "Cluster is UNHEALTHY" + + echo "DEBUG AT $(date '+%F %T'):" + local i + for i in "onnode -q 0 $CTDB status" \ + "onnode -q 0 onnode all $CTDB scriptstatus" ; do + echo "$i" + $i || true + done + + return 1 +} + +wait_until_ready () +{ + local timeout="${1:-120}" + + echo "Waiting for cluster to become ready..." + + wait_until "$timeout" onnode -q any "$CTDB_TEST_WRAPPER" _cluster_is_ready +} + +# This function is becoming nicely overloaded. Soon it will collapse! :-) +node_has_status () +{ + local pnn="$1" + local status="$2" + + case "$status" in + recovered) + ! $CTDB status -n "$pnn" | \ + grep -Eq '^Recovery mode:RECOVERY \(1\)$' + return + ;; + notlmaster) + ! $CTDB status | grep -Eq "^hash:.* lmaster:${pnn}\$" + return + ;; + esac + + local bits + case "$status" in + unhealthy) bits="?|?|?|?|1|*" ;; + healthy) bits="?|?|?|?|0|*" ;; + disconnected) bits="1|*" ;; + connected) bits="0|*" ;; + banned) bits="?|?|1|*" ;; + unbanned) bits="?|?|0|*" ;; + disabled) bits="?|?|?|1|*" ;; + enabled) bits="?|?|?|0|*" ;; + stopped) bits="?|?|?|?|?|1|*" ;; + notstopped) bits="?|?|?|?|?|0|*" ;; + *) + echo "node_has_status: unknown status \"$status\"" + return 1 + esac + local out _ line + + out=$($CTDB -X status 2>&1) || return 1 + + { + read -r _ + while read -r line ; do + # This needs to be done in 2 steps to + # avoid false matches. + local line_bits="${line#|"${pnn}"|*|}" + [ "$line_bits" = "$line" ] && continue + # shellcheck disable=SC2295 + # This depends on $bits being a pattern + [ "${line_bits#${bits}}" != "$line_bits" ] && \ + return 0 + done + return 1 + } <<<"$out" # Yay bash! +} + +wait_until_node_has_status () +{ + local pnn="$1" + local status="$2" + local timeout="${3:-30}" + local proxy_pnn="${4:-any}" + + echo "Waiting until node $pnn has status \"$status\"..." + + if ! wait_until "$timeout" onnode "$proxy_pnn" \ + "$CTDB_TEST_WRAPPER" node_has_status "$pnn" "$status" ; then + + for i in "onnode -q any $CTDB status" "onnode -q any onnode all $CTDB scriptstatus" ; do + echo "$i" + $i || true + done + + return 1 + fi + +} + +# Useful for superficially testing IP failover. +# IPs must be on the given node. +# If the first argument is '!' then the IPs must not be on the given node. +ips_are_on_node () +{ + local negating=false + if [ "$1" = "!" ] ; then + negating=true ; shift + fi + local node="$1" ; shift + local ips="$*" + + local out + + all_ips_on_node "$node" + + local check + for check in $ips ; do + local ip pnn + while read -r ip pnn ; do + if [ "$check" = "$ip" ] ; then + if [ "$pnn" = "$node" ] ; then + if $negating ; then return 1 ; fi + else + if ! $negating ; then return 1 ; fi + fi + ips="${ips/${ip}}" # Remove from list + break + fi + # If we're negating and we didn't see the address then it + # isn't hosted by anyone! + if $negating ; then + ips="${ips/${check}}" + fi + done <"$outfile" + done + + ips="${ips// }" # Remove any spaces. + [ -z "$ips" ] +} + +wait_until_ips_are_on_node () +{ + # Go to some trouble to print a use description of what is happening + local not="" + if [ "$1" == "!" ] ; then + not="no longer " + fi + local node="" + local ips="" + local i + for i ; do + [ "$i" != "!" ] || continue + if [ -z "$node" ] ; then + node="$i" + continue + fi + ips="${ips}${ips:+, }${i}" + done + echo "Waiting for ${ips} to ${not}be assigned to node ${node}" + + wait_until 60 ips_are_on_node "$@" +} + +node_has_some_ips () +{ + local node="$1" + + local out + + all_ips_on_node "$node" + + while read -r ip pnn ; do + if [ "$node" = "$pnn" ] ; then + return 0 + fi + done <"$outfile" + + return 1 +} + +wait_until_node_has_some_ips () +{ + echo "Waiting for some IPs to be assigned to node ${test_node}" + + wait_until 60 node_has_some_ips "$@" +} + +wait_until_node_has_no_ips () +{ + echo "Waiting until no IPs are assigned to node ${test_node}" + + wait_until 60 ! node_has_some_ips "$@" +} + +####################################### + +ctdb_init () +{ + if ! ctdb_nodes_start ; then + echo "Cluster start failed" + return 1 + fi + + if ! wait_until_ready 120 ; then + echo "Cluster didn't become ready" + return 1 + fi + + echo "Setting RerecoveryTimeout to 1" + onnode -pq all "$CTDB setvar RerecoveryTimeout 1" + + echo "Forcing a recovery..." + onnode -q 0 "$CTDB recover" + sleep_for 2 + + if ! onnode -q all "$CTDB_TEST_WRAPPER _cluster_is_recovered" ; then + echo "Cluster has gone into recovery again, waiting..." + if ! wait_until 30/2 onnode -q all \ + "$CTDB_TEST_WRAPPER _cluster_is_recovered" ; then + echo "Cluster did not come out of recovery" + return 1 + fi + fi + + if ! onnode 0 "$CTDB_TEST_WRAPPER _cluster_is_healthy" ; then + echo "Cluster became UNHEALTHY again [$(date)]" + return 1 + fi + + echo "Doing a sync..." + onnode -q 0 "$CTDB sync" + + echo "ctdb is ready" + return 0 +} + +ctdb_base_show () +{ + echo "${CTDB_BASE:-${CTDB_SCRIPTS_BASE}}" +} + +####################################### + +# sets: leader +_leader_get () +{ + local node="$1" + + ctdb_onnode "$node" leader + # shellcheck disable=SC2154 + # $out set by ctdb_onnode() above + leader="$out" +} + +leader_get () +{ + local node="$1" + + echo "Get leader" + _leader_get "$node" + echo "Leader is ${leader}" + echo +} + +_leader_has_changed () +{ + local node="$1" + local leader_old="$2" + + _leader_get "$node" + + [ "$leader" != "$leader_old" ] +} + +# uses: leader +wait_until_leader_has_changed () +{ + local node="$1" + + echo + echo "Wait until leader changes..." + wait_until 30 _leader_has_changed "$node" "$leader" + echo "Leader changed to ${leader}" +} + +####################################### + +# sets: generation +_generation_get () +{ + local node="$1" + + ctdb_onnode "$node" status + # shellcheck disable=SC2154 + # $outfile set by ctdb_onnode() above + generation=$(sed -n -e 's/^Generation:\([0-9]*\)/\1/p' "$outfile") +} + +generation_get () +{ + local node="$1" + + echo "Get generation" + _generation_get "$node" + echo "Generation is ${generation}" + echo +} + +_generation_has_changed () +{ + local node="$1" + local generation_old="$2" + + _generation_get "$node" + + [ "$generation" != "$generation_old" ] +} + +# uses: generation +wait_until_generation_has_changed () +{ + local node="$1" + + echo "Wait until generation changes..." + wait_until 30 _generation_has_changed "$node" "$generation" + echo "Generation changed to ${generation}" + echo +} + +####################################### + +wait_for_monitor_event () +{ + local pnn="$1" + local timeout=120 + + echo "Waiting for a monitor event on node ${pnn}..." + + ctdb_onnode "$pnn" scriptstatus || { + echo "Unable to get scriptstatus from node $pnn" + return 1 + } + + mv "$outfile" "${outfile}.orig" + + wait_until 120 _ctdb_scriptstatus_changed +} + +_ctdb_scriptstatus_changed () +{ + ctdb_onnode "$pnn" scriptstatus || { + echo "Unable to get scriptstatus from node $pnn" + return 1 + } + + ! diff "$outfile" "${outfile}.orig" >/dev/null +} + +####################################### + +# If the given IP is hosted then print 2 items: maskbits and iface +ip_maskbits_iface () +{ + _addr="$1" + + case "$_addr" in + *:*) _family="inet6" ; _bits=128 ;; + *) _family="inet" ; _bits=32 ;; + esac + + # Literal backslashes in awk script + # shellcheck disable=SC1004 + ip addr show to "${_addr}/${_bits}" 2>/dev/null | \ + awk -v family="${_family}" \ + 'NR == 1 { iface = $2; sub(":$", "", iface) } \ + $1 ~ /inet/ { mask = $2; sub(".*/", "", mask); \ + print mask, iface, family }' +} + +drop_ip () +{ + _addr="${1%/*}" # Remove optional maskbits + + # Intentional word splitting + # shellcheck disable=SC2046,SC2086 + set -- $(ip_maskbits_iface $_addr) + if [ -n "$1" ] ; then + _maskbits="$1" + _iface="$2" + echo "Removing public address $_addr/$_maskbits from device $_iface" + ip addr del "$_ip/$_maskbits" dev "$_iface" >/dev/null 2>&1 || true + fi +} + +drop_ips () +{ + for _ip ; do + drop_ip "$_ip" + done +} + +####################################### + +# $1: pnn, $2: DB name +db_get_path () +{ + ctdb_onnode -v "$1" "getdbstatus $2" | sed -n -e "s@^path: @@p" +} + +# $1: pnn, $2: DB name +db_ctdb_cattdb_count_records () +{ + # Count the number of keys, excluding any that begin with '_'. + # This excludes at least the sequence number record in + # persistent/replicated databases. The trailing "|| :" forces + # the command to succeed when no records are matched. + ctdb_onnode "$1" "cattdb $2 | grep -c '^key([0-9][0-9]*) = \"[^_]' || :" + echo "$out" +} + +# $1: pnn, $2: DB name, $3: key string, $4: value string, $5: RSN (default 7) +db_ctdb_tstore () +{ + _tdb=$(db_get_path "$1" "$2") + _rsn="${5:-7}" + ctdb_onnode "$1" tstore "$_tdb" "$3" "$4" "$_rsn" +} + +# $1: pnn, $2: DB name, $3: dbseqnum (must be < 255!!!!!) +db_ctdb_tstore_dbseqnum () +{ + # "__db_sequence_number__" + trailing 0x00 + _key='0x5f5f64625f73657175656e63655f6e756d6265725f5f00' + + # Construct 8 byte (unit64_t) database sequence number. This + # probably breaks if $3 > 255 + _value=$(printf "0x%02x%014x" "$3" 0) + + db_ctdb_tstore "$1" "$2" "$_key" "$_value" +} + +######################################## + +# Make sure that $CTDB is set. +if [ -z "$CTDB" ] ; then + CTDB="ctdb" +fi + +if ctdb_test_on_cluster ; then + . "${TEST_SCRIPTS_DIR}/integration_real_cluster.bash" +else + . "${TEST_SCRIPTS_DIR}/integration_local_daemons.bash" +fi + + +local="${CTDB_TEST_SUITE_DIR}/scripts/local.bash" +if [ -r "$local" ] ; then + . "$local" +fi diff --git a/ctdb/tests/scripts/integration_local_daemons.bash b/ctdb/tests/scripts/integration_local_daemons.bash new file mode 100644 index 0000000..643fc5e --- /dev/null +++ b/ctdb/tests/scripts/integration_local_daemons.bash @@ -0,0 +1,95 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +hdir="$CTDB_SCRIPTS_HELPER_BINDIR" +export CTDB_EVENT_HELPER="${hdir}/ctdb-event" + +if $CTDB_TESTS_ARE_INSTALLED ; then + # Find it in $PATH + helper="ctdb_local_daemons" +else + helper="${CTDB_TEST_DIR}/local_daemons.sh" +fi + +ctdb_local_daemons="${helper} ${CTDB_TEST_TMP_DIR}" + +####################################### + +setup_ctdb () +{ + local no_event_scripts=false + + # All other options are passed through to local_daemons.sh setup + case "$1" in + --no-event-scripts) no_event_scripts=true ; shift ;; + esac + + $ctdb_local_daemons setup "$@" \ + -n "$CTDB_TEST_LOCAL_DAEMONS" \ + ${CTDB_USE_IPV6:+-6} \ + ${CTDB_TEST_SWRAP_SO_PATH:+-S ${CTDB_TEST_SWRAP_SO_PATH}} + # Burying the above in an if-statement condition reduces readability. + # shellcheck disable=SC2181 + if [ $? -ne 0 ] ; then + exit 1 + fi + + if $no_event_scripts ; then + # Want CTDB_BASE expanded when executed under onnode + # shellcheck disable=SC2016 + $ctdb_local_daemons onnode -q all \ + 'rm "${CTDB_BASE}/events/legacy/"*' + fi + + if $CTDB_TEST_PRINT_LOGS_ON_ERROR ; then + ctdb_test_exit_hook_add _print_logs_on_test_failure + fi +} + +ctdb_nodes_start () +{ + local nodespec="${1:-all}" + + $ctdb_local_daemons start "$nodespec" +} + +ctdb_nodes_stop () +{ + local nodespec="${1:-all}" + + if $ctdb_local_daemons stop "$nodespec" ; then + return 0 + fi + + # Failed, dump logs? + if $CTDB_TEST_PRINT_LOGS_ON_ERROR ; then + _print_logs + fi + + # Next level up can log the error... + return 1 +} + +onnode () +{ + $ctdb_local_daemons onnode "$@" +} + + + +_print_logs () +{ + echo "*** LOG START --------------------" + $ctdb_local_daemons print-log all | tail -n 500 + echo "*** LOG END --------------------" +} + +_print_logs_on_test_failure () +{ + # This is called from ctdb_test_exit() where $status is available + # shellcheck disable=SC2154 + if [ "$status" -eq 0 ] ; then + return + fi + + _print_logs +} diff --git a/ctdb/tests/scripts/integration_real_cluster.bash b/ctdb/tests/scripts/integration_real_cluster.bash new file mode 100644 index 0000000..8d3f68a --- /dev/null +++ b/ctdb/tests/scripts/integration_real_cluster.bash @@ -0,0 +1,53 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +####################################### + +# Enables all of the event scripts used in cluster tests, except for +# the mandatory scripts +_ctdb_enable_cluster_test_event_scripts () +{ + local scripts=" + 06.nfs + 10.interface + 49.winbind + 50.samba + 60.nfs + " + + local s + for s in $scripts ; do + try_command_on_node all ctdb event script enable legacy "$s" + done +} + +setup_ctdb () +{ + _ctdb_enable_cluster_test_event_scripts +} + +####################################### + +_service_ctdb () +{ + cmd="$1" + + if [ -e /etc/redhat-release ] ; then + service ctdb "$cmd" + else + /etc/init.d/ctdb "$cmd" + fi +} + +# Stop/start CTDB on all nodes. Override for local daemons. +ctdb_nodes_stop () +{ + local nodespec="${1:-all}" + + onnode -p "$nodespec" "$CTDB_TEST_WRAPPER" _service_ctdb stop +} +ctdb_nodes_start () +{ + local nodespec="${1:-all}" + + onnode -p "$nodespec" "$CTDB_TEST_WRAPPER" _service_ctdb start +} diff --git a/ctdb/tests/scripts/script_install_paths.sh b/ctdb/tests/scripts/script_install_paths.sh new file mode 100644 index 0000000..6890cf8 --- /dev/null +++ b/ctdb/tests/scripts/script_install_paths.sh @@ -0,0 +1,67 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +# Sets $bin_dir +find_bin_dir () +{ + _ctdb_dir="$1" + + bin_dir="$(pwd -P)/bin" + if [ -d "$(pwd -P)/bin" ] ; then + return + fi + + bin_dir="${_ctdb_dir}/bin" + if [ -d "$bin_dir" ] ; then + return + fi + + bin_dir="$(dirname "${_ctdb_dir}")/bin" + if [ -d "$bin_dir" ] ; then + return + fi + + die "Unable to locate bin/ subdirectory" +} + + +if ! $CTDB_TESTS_ARE_INSTALLED ; then + if [ ! -f "${CTDB_TEST_DIR}/run_tests.sh" ] ; then + die "Tests not installed but can't find run_tests.sh" + fi + + ctdb_dir=$(cd -P "$(dirname "$CTDB_TEST_DIR")" && pwd) # real path + + find_bin_dir "$ctdb_dir" + + CTDB_SCRIPTS_BASE="${ctdb_dir}/config" + CTDB_SCRIPTS_INIT_SCRIPT="${ctdb_dir}/config/ctdb.init" + CTDB_SCRIPTS_SBIN_DIR="${ctdb_dir}/config" + CTDB_SCRIPTS_TOOLS_BIN_DIR="${ctdb_dir}/tools" + CTDB_SCRIPTS_TOOLS_HELPER_DIR="${ctdb_dir}/tools" + CTDB_SCRIPTS_HELPER_BINDIR="$bin_dir" + CTDB_SCRIPTS_DATA_DIR="${ctdb_dir}/config" + CTDB_SCRIPTS_TESTS_LIBEXEC_DIR="$bin_dir" + CTDB_SCRIPTS_TESTS_BIN_DIR="$CTDB_TEST_DIR" +else + # Installed + CTDB_SCRIPTS_BASE="/usr/local/etc/ctdb" + CTDB_SCRIPTS_INIT_SCRIPT="" # No ideas here... this is a packaging choice + CTDB_SCRIPTS_SBIN_DIR="/usr/local/sbin" + CTDB_SCRIPTS_TOOLS_BIN_DIR="/usr/local/bin" + CTDB_SCRIPTS_TOOLS_HELPER_DIR="/usr/local/libexec/ctdb" + CTDB_SCRIPTS_HELPER_BINDIR="/usr/local/libexec/ctdb" + CTDB_SCRIPTS_DATA_DIR="/usr/local/share/ctdb" + CTDB_SCRIPTS_TESTS_LIBEXEC_DIR="/usr/local/libexec/ctdb/tests" + CTDB_SCRIPTS_TESTS_BIN_DIR="/usr/local/bin" +fi + +export CTDB_SCRIPTS_BASE \ + CTDB_SCRIPTS_BIN_DIR \ + CTDB_SCRIPTS_INIT_SCRIPT \ + CTDB_SCRIPTS_SBIN_DIR \ + CTDB_SCRIPTS_TOOLS_BIN_DIR \ + CTDB_SCRIPTS_TOOLS_HELPER_DIR \ + CTDB_SCRIPTS_HELPER_BINDIR \ + CTDB_SCRIPTS_DATA_DIR \ + CTDB_SCRIPTS_TESTS_LIBEXEC_DIR \ + CTDB_SCRIPTS_TESTS_BIN_DIR diff --git a/ctdb/tests/scripts/test_wrap b/ctdb/tests/scripts/test_wrap new file mode 100755 index 0000000..619ac7c --- /dev/null +++ b/ctdb/tests/scripts/test_wrap @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# Execute the given command. The intention is that it is either +# * a function from "${TEST_SCRIPTS_DIR}/integration.bash"; or +# * a test helper binary + +TEST_SCRIPTS_DIR=$(dirname "$0") + +. "${TEST_SCRIPTS_DIR}/integration.bash" + +"$@" diff --git a/ctdb/tests/scripts/unit.sh b/ctdb/tests/scripts/unit.sh new file mode 100644 index 0000000..403ee07 --- /dev/null +++ b/ctdb/tests/scripts/unit.sh @@ -0,0 +1,267 @@ +# Hey Emacs, this is a -*- shell-script -*- !!! :-) + +. "${TEST_SCRIPTS_DIR}/common.sh" + +# Common variables and functions for CTDB unit tests. + +trap -- '' PIPE + +# Set the required result for a test. +# - Argument 1 is exit code. +# - Argument 2, if present is the required test output but "--" +# indicates empty output. +# If argument 2 is not present or null then read required test output +# from stdin. +required_result() +{ + required_rc="${1:-0}" + if [ -n "$2" ]; then + if [ "$2" = "--" ]; then + required_output="" + else + # Use a sub-shell to strip trailing newlines. + # They can't be matched anyway because the + # test is run in a sub-shell, which strips + # trailing newlines. + # shellcheck disable=SC2116 + required_output=$(echo "$2") + fi + else + if ! tty -s; then + required_output=$(cat) + else + required_output="" + fi + fi +} + +required_error() +{ + rc=$(errcode "$1") + shift + required_result "$rc" "$@" +} + +ok() +{ + required_result 0 "$@" +} + +ok_null() +{ + ok -- +} + +reset_extra_header() +{ + # Re-define this function to output extra header information + extra_header() + { + : + } +} + +reset_extra_footer() +{ + # Re-define this function to output extra footer information + extra_footer() + { + : + } +} + +reset_extra_header +reset_extra_footer + +result_print() +{ + _passed="$1" + _out="$2" + _rc="$3" + + if "$CTDB_TEST_VERBOSE" || ! $_passed; then + extra_header + + cat <"$_outr" + + _outf=$(mktemp) + # Avoid echo, which might expand unintentional escapes + printf '%s\n' "$_fout" >"$_outf" + + cat <&1) + + result_check || exit $? +} + +# Simple test harness for running shell script unit tests +script_test() +{ + test_header "$@" + + _shell="" + if ${CTDB_TEST_COMMAND_TRACE}; then + _shell="sh -x" + else + _shell="sh" + fi + + _out=$($_shell "$@" 2>&1) + + result_check || exit $? +} + +# Simple test harness for running tests without tracing +unit_test_notrace() +{ + test_header "$@" + + _out=$("$@" 2>&1) + + result_check || exit $? +} + +test_cleanup_hooks="" + +test_cleanup() +{ + test_cleanup_hooks="${test_cleanup_hooks}${test_cleanup_hooks:+ ; }$*" +} + +trap 'eval $test_cleanup_hooks' 0 + +local="${CTDB_TEST_SUITE_DIR}/scripts/local.sh" +if [ -r "$local" ]; then + . "$local" +fi diff --git a/ctdb/tests/src/cluster_mutex_test.c b/ctdb/tests/src/cluster_mutex_test.c new file mode 100644 index 0000000..2576163 --- /dev/null +++ b/ctdb/tests/src/cluster_mutex_test.c @@ -0,0 +1,844 @@ +/* + CTDB cluster mutex test + + Copyright (C) Martin Schwenke 2019 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" +#include "system/wait.h" + +#include + +#include +#include + +#include "lib/util/util.h" +#include "lib/util/smb_strtox.h" + +#include "tests/src/test_backtrace.h" + +/* + * ctdb_cluster_mutex.c is included below. This requires a few hacks... + */ + +/* Avoid inclusion of ctdb_private.h */ +#define _CTDB_PRIVATE_H + +/* Fake ctdb_context */ +struct ctdb_context { + struct tevent_context *ev; +}; + +/* + * ctdb_fork() and ctdb_kill() are used in ctdb_cluster_mutex.c for + * safer tracking of PIDs. Fake them here to avoid dragging in the + * world. + */ + +static pid_t ctdb_fork(struct ctdb_context *ctdb) +{ + return fork(); +} + +static int ctdb_kill(struct ctdb_context *ctdb, pid_t pid, int signum) +{ + /* + * Tests need to wait for the child to exit to ensure that the + * lock really has been released. The PID is only accessible + * in ctdb_cluster_mutex.c, so make a best attempt to ensure + * that the child process is waited for after it is killed. + * Avoid waiting if the process is already gone. + */ + int ret; + + if (signum == 0) { + return kill(pid, signum); + } + + ret = kill(pid, signum); + waitpid(pid, NULL, 0); + + return ret; +} + +#include "server/ctdb_cluster_mutex.c" + +/* + * Mutex testing support + */ + +struct mutex_handle { + bool done; + bool locked; + struct ctdb_cluster_mutex_handle *h; +}; + +struct do_lock_context { + struct mutex_handle *mh; + struct ctdb_context *ctdb; +}; + +static void do_lock_handler(char status, double latency, void *private_data) +{ + struct do_lock_context *dl = talloc_get_type_abort( + private_data, struct do_lock_context); + struct mutex_handle *mh; + + assert(dl->mh != NULL); + mh = dl->mh; + + mh->locked = (status == '0') ; + + /* + * If unsuccessful then ensure the process has exited and that + * the file descriptor event handler has been cancelled + */ + if (! mh->locked) { + TALLOC_FREE(mh->h); + } + + switch (status) { + case '0': + printf("LOCK\n"); + break; + + case '1': + printf("CONTENTION\n"); + break; + + case '2': + printf("TIMEOUT\n"); + break; + + default: + printf("ERROR\n"); + } + + fflush(stdout); + mh->done = true; +} + +static void do_lock_lost_handler(void *private_data) +{ + struct do_lock_context *dl = talloc_get_type_abort( + private_data, struct do_lock_context); + + printf("LOST\n"); + fflush(stdout); + TALLOC_FREE(dl->mh); +} + +static void do_lock_take(struct do_lock_context *dl, + const char *mutex_string) +{ + struct ctdb_cluster_mutex_handle *h; + + dl->mh = talloc_zero(dl, struct mutex_handle); + assert(dl->mh != NULL); + + h = ctdb_cluster_mutex(dl->mh, + dl->ctdb, + mutex_string, + 120, + do_lock_handler, + dl, + do_lock_lost_handler, + dl); + assert(h != NULL); + + dl->mh->h = h; +} + +static void do_lock_wait_done(struct do_lock_context *dl) +{ + assert(dl->mh != NULL); + + while (! dl->mh->done) { + tevent_loop_once(dl->ctdb->ev); + } +} + +static void do_lock_check(struct do_lock_context *dl) +{ + assert(dl->mh != NULL); + + if (! dl->mh->locked) { + printf("NOLOCK\n"); + fflush(stdout); + TALLOC_FREE(dl->mh); + } +} + +static void do_lock(struct do_lock_context *dl, + const char *mutex_string) +{ + do_lock_take(dl, mutex_string); + + do_lock_wait_done(dl); + + do_lock_check(dl); +} + +static void do_unlock(struct do_lock_context *dl) +{ + if (dl->mh == NULL) { + return; + } + + if (! dl->mh->done) { + /* + * Taking of lock still in progress. Free the cluster + * mutex handle to release it but leave the lock + * handle in place to allow taking of the lock to + * fail. + */ + printf("CANCEL\n"); + fflush(stdout); + TALLOC_FREE(dl->mh->h); + dl->mh->done = true; + dl->mh->locked = false; + return; + } + + printf("UNLOCK\n"); + fflush(stdout); + TALLOC_FREE(dl->mh); +} + +static void wait_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, + void *private_data) +{ + bool *done = (bool *)private_data; + + *done = true; +} + +static void do_lock_wait_time(struct do_lock_context *dl, + unsigned long wait_time) +{ + struct tevent_timer *tt; + bool done = false; + + tt = tevent_add_timer(dl->ctdb->ev, + dl, + tevent_timeval_current_ofs(wait_time, 0), + wait_handler, + &done); + assert(tt != NULL); + + while (!done && dl->mh != NULL) { + tevent_loop_once(dl->ctdb->ev); + } +} + +/* + * Testcases + */ + +static void test_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl1; + struct do_lock_context *dl2; + + dl1 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl1 != NULL); + dl1->ctdb = ctdb; + + dl2 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl2 != NULL); + dl2->ctdb = ctdb; + + /* LOCK */ + do_lock(dl1, mutex_string); + assert(dl1->mh != NULL); + + /* CONTENTION */ + do_lock(dl2, mutex_string); + assert(dl2->mh == NULL); + + /* UNLOCK */ + do_unlock(dl1); + assert(dl1->mh == NULL); +} + +static void test_lock_unlock_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl1; + struct do_lock_context *dl2; + + dl1 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl1 != NULL); + dl1->ctdb = ctdb; + + dl2 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl2 != NULL); + dl2->ctdb = ctdb; + + /* LOCK */ + do_lock(dl1, mutex_string); + assert(dl1->mh != NULL); + + /* UNLOCK */ + do_unlock(dl1); + assert(dl1->mh == NULL); + + /* LOCK */ + do_lock(dl2, mutex_string); + assert(dl2->mh != NULL); + + /* UNLOCK */ + do_unlock(dl2); + assert(dl2->mh == NULL); +} + +static void test_lock_cancel_check(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + do_lock_take(dl, mutex_string); + assert(dl->mh != NULL); + + /* CANCEL */ + do_unlock(dl); + assert(dl->mh != NULL); + + do_lock_wait_done(dl); + + /* NOLOCK */ + do_lock_check(dl); + assert(dl->mh == NULL); +} + +static void test_lock_cancel_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + do_lock_take(dl, mutex_string); + assert(dl->mh != NULL); + + /* CANCEL */ + do_unlock(dl); + assert(dl->mh != NULL); + + do_lock_wait_done(dl); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_wait_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* Wait for twice as long as the PPID timeout */ + do_lock_wait_time(dl, 2 * 5); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void fd_done_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + bool *done = (bool *)private_data; + + *done = true; +} + +static void test_lock_ppid_gone_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + struct tevent_fd *fde; + int pipefd[2]; + int ret; + pid_t pid, pid2; + ssize_t nread; + bool done; + + /* + * Do this in the parent - debugging aborts of the child is + * trickier + */ + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(pipefd[0]); + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* + * Note that we never see corresponding LOST. That + * would come from this process, but it is killed + * below. + */ + + nwritten = write(pipefd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + sleep(999); + exit(1); + } + + close(pipefd[1]); + + nread = read(pipefd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + /* + * pipefd[1] is leaked into the helper, so there will be an + * event generated when the helper exits + */ + done = false; + fde = tevent_add_fd(ctdb->ev, + ctdb, + pipefd[0], + TEVENT_FD_READ, + fd_done_handler, + &done); + assert(fde != NULL); + + ret = kill(pid, SIGKILL); + assert(ret == 0); + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + + while (! done) { + tevent_loop_once(ctdb->ev); + } + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_file_removed_no_recheck(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file) +{ + struct do_lock_context *dl1; + struct do_lock_context *dl2; + int ret; + + dl1 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl1 != NULL); + dl1->ctdb = ctdb; + + dl2 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl2 != NULL); + dl2->ctdb = ctdb; + + /* LOCK */ + do_lock(dl1, mutex_string); + assert(dl1->mh != NULL); + + ret = unlink(lock_file); + assert(ret == 0); + + /* LOCK */ + do_lock(dl2, mutex_string); + assert(dl2->mh != NULL); + + /* UNLOCK */ + do_unlock(dl2); + assert(dl2->mh == NULL); + + /* UNLOCK */ + do_unlock(dl1); + assert(dl1->mh == NULL); +} + +static void test_lock_file_wait_recheck_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + unsigned long wait_time) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + do_lock_wait_time(dl, wait_time); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_file_removed(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file) +{ + struct do_lock_context *dl; + int ret; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + ret = unlink(lock_file); + assert(ret == 0); + + while (dl->mh != NULL) { + /* LOST */ + tevent_loop_once(ctdb->ev); + } +} + +static void test_lock_file_changed(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file) +{ + struct do_lock_context *dl; + char *t; + int fd; + int ret; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + t = talloc_asprintf(ctdb, "%s.new", lock_file); + assert(t != NULL); + + fd = open(t, O_RDWR|O_CREAT, 0600); + assert(fd != -1); + close(fd); + + ret = rename(t, lock_file); + assert(ret == 0); + + while (dl->mh != NULL) { + /* LOST */ + tevent_loop_once(ctdb->ev); + } +} + +static void test_lock_io_timeout(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file, + unsigned long block_wait, + unsigned long block_time) +{ + struct do_lock_context *dl; + int pipefd[2]; + int ret; + pid_t pid, pid2; + ssize_t nwritten; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + static struct flock lock = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + .l_start = 1, + .l_len = 1, + .l_pid = 0, + }; + ssize_t nread; + int fd; + + close(pipefd[1]); + + /* Only continue when the parent is ready */ + nread = read(pipefd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + sleep(block_wait); + + fd = open(lock_file, O_RDWR, 0600); + assert(fd != -1); + + ret = fcntl(fd, F_SETLKW, &lock); + assert(ret == 0); + + sleep(block_time); + + close(fd); + + sleep(999); + + _exit(0); + } + + close(pipefd[0]); + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + nwritten = write(pipefd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + do_lock_wait_time(dl, block_wait + block_time * 2); + if (dl->mh != NULL) { + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); + } + + ret = kill(pid, SIGKILL); + assert(ret == 0); + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); +} + +/* + * Main + */ + +static const char *prog; + +static void usage(void) +{ + fprintf(stderr, "usage: %s [...]\n", prog); + exit(1); +} + +static void alarm_handler(int sig) +{ + abort(); +} + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct ctdb_context *ctdb; + const char *mutex_string; + const char *test; + struct sigaction sa = { .sa_handler = NULL, }; + int ret; + const char *lock_file; + unsigned int wait_time; + + prog = argv[0]; + + if (argc < 3) { + usage(); + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ctdb = talloc_zero(mem_ctx, struct ctdb_context); + assert(ctdb != NULL); + + ctdb->ev = tevent_context_init(ctdb); + assert(ctdb->ev != NULL); + + /* Add a 60s timeout for the whole test */ + sa.sa_handler = alarm_handler; + sigemptyset(&sa.sa_mask); + ret = sigaction(SIGALRM, &sa, NULL); + assert(ret == 0); + alarm(60); + + test_backtrace_setup(); + + test = argv[1]; + mutex_string = argv[2]; + + if (strcmp(test, "lock-unlock") == 0) { + test_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-lock-unlock") == 0) { + test_lock_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-unlock-lock-unlock") == 0) { + test_lock_unlock_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-cancel-check") == 0) { + test_lock_cancel_check(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-cancel-unlock") == 0) { + test_lock_cancel_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-wait-unlock") == 0) { + test_lock_wait_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-ppid-gone-lock-unlock") == 0) { + test_lock_ppid_gone_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-file-removed-no-recheck") == 0) { + if (argc != 4) { + usage(); + } + + lock_file = argv[3]; + + test_lock_file_removed_no_recheck(mem_ctx, + ctdb, + mutex_string, + lock_file); + } else if (strcmp(test, "lock-file-wait-recheck-unlock") == 0) { + if (argc != 4) { + usage(); + } + + wait_time = smb_strtoul(argv[3], + NULL, + 10, + &ret, + SMB_STR_STANDARD); + if (ret != 0) { + usage(); + } + + test_lock_file_wait_recheck_unlock(mem_ctx, + ctdb, + mutex_string, + wait_time); + } else if (strcmp(test, "lock-file-removed") == 0) { + if (argc != 4) { + usage(); + } + + lock_file = argv[3]; + + test_lock_file_removed(mem_ctx, + ctdb, + mutex_string, + lock_file); + } else if (strcmp(test, "lock-file-changed") == 0) { + if (argc != 4) { + usage(); + } + + lock_file = argv[3]; + + test_lock_file_changed(mem_ctx, + ctdb, + mutex_string, + lock_file); + } else if (strcmp(test, "lock-io-timeout") == 0) { + unsigned long block_wait; + unsigned long block_time; + + if (argc != 6) { + usage(); + } + + lock_file = argv[3]; + block_wait = (unsigned long)atol(argv[4]); + block_time = (unsigned long)atol(argv[5]); + + test_lock_io_timeout(mem_ctx, + ctdb, + mutex_string, + lock_file, + block_wait, + block_time); + } else { + fprintf(stderr, "Unknown test\n"); + exit(1); + } + + talloc_free(mem_ctx); + + return 0; +} diff --git a/ctdb/tests/src/cluster_wait.c b/ctdb/tests/src/cluster_wait.c new file mode 100644 index 0000000..d411591 --- /dev/null +++ b/ctdb/tests/src/cluster_wait.c @@ -0,0 +1,346 @@ +/* + Cluster wide synchronization + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/tevent_unix.h" + +#include "client/client.h" + +#include "tests/src/cluster_wait.h" + +#define MSG_ID_JOIN (CTDB_SRVID_TEST_RANGE | 0x1) +#define MSG_ID_SYNC (CTDB_SRVID_TEST_RANGE | 0x2) + +/* Wait for all the clients to initialize */ + +struct cluster_wait_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + uint32_t num_nodes; + bool *ready; + bool join_done; +}; + +static void cluster_wait_join_registered(struct tevent_req *subreq); +static void cluster_wait_sync_registered(struct tevent_req *subreq); +static void cluster_wait_join(struct tevent_req *subreq); +static void cluster_wait_join_sent(struct tevent_req *subreq); +static void cluster_wait_join_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void cluster_wait_join_unregistered(struct tevent_req *subreq); +static void cluster_wait_sync_sent(struct tevent_req *subreq); +static void cluster_wait_sync_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void cluster_wait_sync_unregistered(struct tevent_req *subreq); + +struct tevent_req *cluster_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + uint32_t num_nodes) +{ + struct tevent_req *req, *subreq; + struct cluster_wait_state *state; + bool ok; + + req = tevent_req_create(mem_ctx, &state, struct cluster_wait_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->num_nodes = num_nodes; + + state->join_done = false; + + if (ctdb_client_pnn(client) == 0) { + state->ready = talloc_zero_array(state, bool, num_nodes); + if (tevent_req_nomem(state->ready, req)) { + return tevent_req_post(req, ev); + } + + subreq = ctdb_client_set_message_handler_send( + state, ev, client, MSG_ID_JOIN, + cluster_wait_join_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, cluster_wait_join_registered, + req); + } + + subreq = ctdb_client_set_message_handler_send( + state, ev, client, MSG_ID_SYNC, + cluster_wait_sync_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, cluster_wait_sync_registered, req); + + /* If cluster is not synchronized within 30 seconds, time out */ + ok = tevent_req_set_endtime( + req, + ev, + tevent_timeval_current_ofs(30, 0)); + if (!ok) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void cluster_wait_join_registered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + printf("Waiting for cluster\n"); + fflush(stdout); +} + +static void cluster_wait_sync_registered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join, req); +} + +static void cluster_wait_join(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct ctdb_req_message msg; + uint32_t pnn; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + pnn = ctdb_client_pnn(state->client); + + msg.srvid = MSG_ID_JOIN; + msg.data.data.dsize = sizeof(pnn); + msg.data.data.dptr = (uint8_t *)&pnn; + + subreq = ctdb_client_message_send(state, state->ev, state->client, + 0, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join_sent, req); +} + +static void cluster_wait_join_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join, req); +} + +static void cluster_wait_join_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct tevent_req *subreq; + uint32_t pnn; + uint32_t i; + + if (srvid != MSG_ID_JOIN) { + return; + } + + if (data.dsize != sizeof(uint32_t)) { + return; + } + + pnn = *(uint32_t *)data.dptr; + + if (pnn > state->num_nodes) { + return; + } + + state->ready[pnn] = true; + + for (i=0; inum_nodes; i++) { + if (! state->ready[i]) { + return; + } + } + + if (state->join_done) { + return; + } + + state->join_done = true; + subreq = ctdb_client_remove_message_handler_send( + state, state->ev, state->client, + MSG_ID_JOIN, req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join_unregistered, req); +} + +static void cluster_wait_join_unregistered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct ctdb_req_message msg; + bool status; + int ret; + + status = ctdb_client_remove_message_handler_recv(subreq, &ret); + if (! status) { + tevent_req_error(req, ret); + return; + } + + msg.srvid = MSG_ID_SYNC; + msg.data.data = tdb_null; + + subreq = ctdb_client_message_send(state, state->ev, state->client, + CTDB_BROADCAST_CONNECTED, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_sync_sent, req); +} + +static void cluster_wait_sync_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } +} + +static void cluster_wait_sync_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct tevent_req *subreq; + + if (srvid != MSG_ID_SYNC) { + return; + } + + subreq = ctdb_client_remove_message_handler_send( + state, state->ev, state->client, + MSG_ID_SYNC, req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_sync_unregistered, req); +} + +static void cluster_wait_sync_unregistered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_remove_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +bool cluster_wait_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} diff --git a/ctdb/tests/src/cluster_wait.h b/ctdb/tests/src/cluster_wait.h new file mode 100644 index 0000000..e0c64cc --- /dev/null +++ b/ctdb/tests/src/cluster_wait.h @@ -0,0 +1,30 @@ +/* + Cluster wide synchronization + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#ifndef __CLUSTER_WAIT_H__ +#define __CLUSTER_WAIT_H__ + +struct tevent_req *cluster_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + uint32_t num_nodes); + +bool cluster_wait_recv(struct tevent_req *req, int *perr); + +#endif /* __CLUSTER_WAIT_H__ */ diff --git a/ctdb/tests/src/cmdline_test.c b/ctdb/tests/src/cmdline_test.c new file mode 100644 index 0000000..916d820 --- /dev/null +++ b/ctdb/tests/src/cmdline_test.c @@ -0,0 +1,480 @@ +/* + Command line processing tests + + Copyright (C) Amitay Isaacs 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include +#include + +#include + +#include "common/cmdline.c" + +static int dummy_func(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + return 0; +} + +static struct poptOption dummy_options[] = { + POPT_TABLEEND +}; + +static struct cmdline_command dummy_commands[] = { + CMDLINE_TABLEEND +}; + +static void test1(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, NULL, NULL, NULL, NULL, &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, "test1", NULL, NULL, NULL, &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test1", + dummy_options, + NULL, + NULL, + &cmdline); + assert(ret == EINVAL); + + talloc_free(mem_ctx); +} + +static struct cmdline_command test2_nofunc[] = { + { "nofunc", NULL, NULL, NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_nohelp[] = { + { "nohelp", dummy_func, NULL, NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_long[] = { + { "really really long command with lots of words", + dummy_func, "long command help", + "" }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_longhelp[] = { + { "longhelp", dummy_func, + "this is a really really really long help message" \ + "with lots of words and lots of description", + NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_twowords[] = { + { "multiple words", dummy_func, "multiple words help", NULL }, + CMDLINE_TABLEEND +}; + +static void test2(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_nofunc, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_nohelp, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_long, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_longhelp, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_twowords, + &cmdline); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +struct { + const char *str; +} test3_data; + +static struct poptOption test3_noname[] = { + { NULL, 'o', POPT_ARG_STRING, &test3_data.str, 0, + "Noname option", NULL }, + POPT_TABLEEND +}; + +static struct poptOption test3_notype[] = { + { "debug", 'd', POPT_ARG_NONE, NULL, 0, + "No argument option", NULL }, + POPT_TABLEEND +}; + +static struct poptOption test3_noarg[] = { + { "debug", 'd', POPT_ARG_STRING, NULL, 0, + "No argument option", NULL }, + POPT_TABLEEND +}; + +static void test3(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test3", + test3_noname, + NULL, + dummy_commands, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test3", + test3_notype, + NULL, + dummy_commands, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test3", + test3_noarg, + NULL, + dummy_commands, + &cmdline); + assert(ret == EINVAL); + + talloc_free(mem_ctx); +} + +static int test4_count; +static int test4_value; + +static struct poptOption test4_options[] = { + { "count", 'c', POPT_ARG_INT, &test4_count, 0, + "Option help of length thirty.", NULL }, + { "value", 'v', POPT_ARG_INT, &test4_value, 0, + "Short description", "Value help of length 23" }, + POPT_TABLEEND +}; + +static struct cmdline_command test4_commands[] = { + { "A really really long command", dummy_func, + "This is a really long help message", + "" }, + { "short command", dummy_func, + "short msg for short command", "" }, + CMDLINE_TABLEEND +}; + +static void test4(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test4", + test4_options, + NULL, + test4_commands, + &cmdline); + assert(ret == 0); + + cmdline_usage(cmdline, NULL); + cmdline_usage(cmdline, "short command"); + + talloc_free(mem_ctx); +} + +static int action_func(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + if (argc != 1) { + return 100; + } + + printf("%s\n", argv[0]); + return 200; +} + +static struct cmdline_command action_commands[] = { + { "action one", dummy_func, "action one help", NULL }, + { "action two", action_func, "action two help", NULL }, + CMDLINE_TABLEEND +}; + +static void test5(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + const char *argv1[] = { "test5", "--help" }; + const char *argv2[] = { "test5", "action" }; + const char *argv3[] = { "test5", "action", "--help" }; + const char *argv4[] = { "test5", "action", "one" }; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test5", + NULL, + "Action", + action_commands, + &cmdline); + assert(ret == 0); + + ret = cmdline_parse(cmdline, 2, argv1, true); + assert(ret == EAGAIN); + + ret = cmdline_parse(cmdline, 2, argv2, true); + assert(ret == ENOENT); + + ret = cmdline_parse(cmdline, 3, argv3, true); + assert(ret == EAGAIN); + + ret = cmdline_parse(cmdline, 3, argv4, true); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +static void test6(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + const char *argv1[] = { "action", "two" }; + const char *argv2[] = { "action", "two", "arg1" }; + const char *argv3[] = { "action", "two", "arg1", "arg2" }; + int ret, result; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test6", + NULL, + NULL, + action_commands, + &cmdline); + assert(ret == 0); + + ret = cmdline_parse(cmdline, 2, argv1, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 100); + + ret = cmdline_parse(cmdline, 3, argv2, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 200); + + ret = cmdline_parse(cmdline, 4, argv3, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 100); + + talloc_free(mem_ctx); +} + +static int test7_func(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + assert(argc == 1); + + printf("%s\n", argv[0]); + + return 0; +} + +static struct cmdline_command test7_basic_commands[] = { + { "cmd1", test7_func, "command one help", NULL }, + { "cmd2", test7_func, "command two help", NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test7_advanced_commands[] = { + { "cmd3", test7_func, "command three help", NULL }, + { "cmd4", test7_func, "command four help", NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test7_ultimate_commands[] = { + { "cmd5", test7_func, "command five help", NULL }, + { "cmd6", test7_func, "command six help", NULL }, + CMDLINE_TABLEEND +}; + +static void test7(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + const char *argv1[] = { "cmd1", "one" }; + const char *argv2[] = { "cmd3", "three" }; + const char *argv3[] = { "cmd6", "six" }; + int ret, result; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test7", + NULL, + "Basic", + test7_basic_commands, + &cmdline); + assert(ret == 0); + + ret = cmdline_add(cmdline, "Advanced", test7_advanced_commands); + assert(ret == 0); + + ret = cmdline_add(cmdline, "Ultimate", test7_ultimate_commands); + assert(ret == 0); + + cmdline_usage(cmdline, NULL); + + printf("\n"); + + ret = cmdline_parse(cmdline, 2, argv1, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 0); + + ret = cmdline_parse(cmdline, 2, argv2, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 0); + + ret = cmdline_parse(cmdline, 2, argv3, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 0); + + talloc_free(mem_ctx); +} + + +int main(int argc, const char **argv) +{ + int num; + + if (argc < 2) { + fprintf(stderr, "Usage %s \n", argv[0]); + exit(1); + } + + num = atoi(argv[1]); + + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + case 4: + test4(); + break; + + case 5: + test5(); + break; + + case 6: + test6(); + break; + + case 7: + test7(); + break; + } + + return 0; +} diff --git a/ctdb/tests/src/comm_client_test.c b/ctdb/tests/src/comm_client_test.c new file mode 100644 index 0000000..41ed5f7 --- /dev/null +++ b/ctdb/tests/src/comm_client_test.c @@ -0,0 +1,217 @@ +/* + comm tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include + +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" + + +struct writer_state { + struct tevent_context *ev; + struct comm_context *comm; + uint8_t *buf; + size_t *pkt_size; + size_t count, id; +}; + +static void writer_done(struct tevent_req *subreq); +static void read_handler(uint8_t *buf, size_t buflen, void *private_data); +static void dead_handler(void *private_data); + +static struct tevent_req *writer_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, size_t *pkt_size, + size_t count) +{ + struct tevent_req *req, *subreq; + struct writer_state *state; + size_t max_size = 0, buflen; + size_t i; + int ret; + + for (i=0; i max_size) { + max_size = pkt_size[i]; + } + } + + req = tevent_req_create(mem_ctx, &state, struct writer_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->pkt_size = pkt_size; + state->count = count; + state->id = 0; + + ret = comm_setup(state, ev, fd, read_handler, req, + dead_handler, req, &state->comm); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + state->buf = talloc_array(state, uint8_t, max_size); + if (state->buf == NULL) { + talloc_free(req); + return NULL; + } + for (i=0; ibuf[i] = i%256; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, writer_done, req); + + return req; +} + +static void writer_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool ret; + int err; + + ret = comm_write_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, err); + return; + } +} + +static void read_handler(uint8_t *buf, size_t buflen, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct writer_state *state = tevent_req_data( + req, struct writer_state); + struct tevent_req *subreq; + + if (buflen != state->pkt_size[state->id]) { + tevent_req_error(req, EIO); + return; + } + + state->id++; + if (state->id >= state->count) { + tevent_req_done(req); + return; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, writer_done, req); +} + +static void dead_handler(void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + + tevent_req_error(req, EPIPE); +} + +static void writer_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return; + } + *perr = 0; +} + +static int socket_init(char *sockpath) +{ + struct sockaddr_un addr; + int fd, ret, i; + size_t len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + assert(len < sizeof(addr.sun_path)); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(fd != -1); + + for (i=0; i<5; i++) { + ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret == 0) { + break; + } + sleep(1); + } + assert(ret != -1); + + return fd; +} + +int main(int argc, char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + int fd; + size_t pkt_size[13] = { 100, 2048, 500, 4096, 1024, 8192, + 200, 16384, 300, 32768, 400, 65536, + 1024*1024 }; + int err; + + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + fd = socket_init(argv[1]); + + req = writer_send(mem_ctx, ev, fd, pkt_size, 13); + assert(req != NULL); + + tevent_req_poll(req, ev); + + writer_recv(req, &err); + assert(err == 0); + + exit(0); +} diff --git a/ctdb/tests/src/comm_server_test.c b/ctdb/tests/src/comm_server_test.c new file mode 100644 index 0000000..86b5658 --- /dev/null +++ b/ctdb/tests/src/comm_server_test.c @@ -0,0 +1,292 @@ +/* + comm tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include + +#include "lib/async_req/async_sock.h" + +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" + +struct echo_state { + struct tevent_context *ev; + int fd; + struct comm_context *comm; + uint8_t *data; +}; + +static void read_handler(uint8_t *buf, size_t buflen, void *private_data); +static void read_failed(void *private_data); +static void write_done(struct tevent_req *subreq); + +static struct tevent_req *echo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, int fd) +{ + struct tevent_req *req; + struct echo_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct echo_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + + ret = comm_setup(state, ev, fd, read_handler, req, + read_failed, req, &state->comm); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + return req; +} + +static void read_handler(uint8_t *buf, size_t buflen, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct echo_state *state = tevent_req_data( + req, struct echo_state); + struct tevent_req *subreq; + + state->data = talloc_memdup(state, buf, buflen); + if (tevent_req_nomem(state->data, req)) { + return; + } + + subreq = comm_write_send(state, state->ev, state->comm, + state->data, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, write_done, req); +} + +static void read_failed(void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + + tevent_req_done(req); +} + +static void write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct echo_state *state = tevent_req_data( + req, struct echo_state); + bool ret; + int err; + + TALLOC_FREE(state->data); + + ret = comm_write_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, err); + return; + } +} + +static bool echo_recv(struct tevent_req *req, int *perr) +{ + struct echo_state *state = tevent_req_data( + req, struct echo_state); + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + + close(state->fd); + return true; +} + + +struct socket_process_state { + struct tevent_context *ev; + int fd; + int max_clients; + int num_clients; +}; + +static void socket_process_client(struct tevent_req *subreq); +static void socket_process_client_done(struct tevent_req *subreq); + +static struct tevent_req *socket_process_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, int max_clients) +{ + struct tevent_req *req, *subreq; + struct socket_process_state *state; + + req = tevent_req_create(mem_ctx, &state, struct socket_process_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->max_clients = max_clients; + state->num_clients = 0; + + subreq = accept_send(state, ev, fd); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, socket_process_client, req); + + return req; +} + +static void socket_process_client(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct socket_process_state *state = tevent_req_data( + req, struct socket_process_state); + int client_fd; + int err = 0; + + client_fd = accept_recv(subreq, NULL, NULL, &err); + TALLOC_FREE(subreq); + + state->num_clients++; + + if (client_fd == -1) { + tevent_req_error(req, err); + return; + } + + subreq = echo_send(state, state->ev, client_fd); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, socket_process_client_done, req); + + if (state->num_clients == state->max_clients) { + /* Stop accepting any more clients */ + return; + } + + subreq = accept_send(state, state->ev, state->fd); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, socket_process_client, req); +} + +static void socket_process_client_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct socket_process_state *state = tevent_req_data( + req, struct socket_process_state); + bool ret; + int err = 0; + + ret = echo_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, EIO); + return; + } + + if (state->num_clients == state->max_clients) { + tevent_req_done(req); + } +} + +static void socket_process_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + } +} + +static int socket_init(char *sockpath) +{ + struct sockaddr_un addr; + int fd, ret; + size_t len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + assert(len < sizeof(addr.sun_path)); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(fd != -1); + + ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret != -1); + + ret = listen(fd, 10); + assert(ret != -1); + + return fd; +} + +int main(int argc, char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + int fd, err = 0; + int num_clients; + + if (argc != 3) { + printf("Usage: %s \n", argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + fd = socket_init(argv[1]); + num_clients = atoi(argv[2]); + assert(num_clients > 0); + + req = socket_process_send(mem_ctx, ev, fd, num_clients); + assert(req != NULL); + + tevent_req_poll(req, ev); + + socket_process_recv(req, &err); + return err; +} diff --git a/ctdb/tests/src/comm_test.c b/ctdb/tests/src/comm_test.c new file mode 100644 index 0000000..4595928 --- /dev/null +++ b/ctdb/tests/src/comm_test.c @@ -0,0 +1,501 @@ +/* + comm tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include + +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" + +/* + * Test read_handler and dead_handler + */ + +static void test1_read_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + int *result = (int *)private_data; + + *result = -1; +} + +static void test1_dead_handler(void *private_data) +{ + int *result = (int *)private_data; + + *result = 1; +} + +static void test1(void) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm; + int fd[2]; + int result = 0; + uint32_t data[2]; + int ret; + ssize_t n; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = pipe(fd); + assert(ret == 0); + + ret = comm_setup(ev, ev, fd[0], test1_read_handler, &result, + test1_dead_handler, &result, &comm); + assert(ret == 0); + + data[0] = 2 * sizeof(uint32_t); + data[1] = 0; + + n = write(fd[1], (void *)&data, data[0]); + assert(n == data[0]); + + while (result == 0) { + tevent_loop_once(ev); + } + + assert(result == -1); + + result = 0; + close(fd[1]); + + while (result == 0) { + tevent_loop_once(ev); + } + + assert(result == 1); + + talloc_free(mem_ctx); +} + +/* + * Test that the tevent_req returned by comm_write_send() can be free'd. + */ + +struct test2_state { + TALLOC_CTX *mem_ctx; + bool done; +}; + +static void test2_read_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test2_state *state = (struct test2_state *)private_data; + + TALLOC_FREE(state->mem_ctx); +} + +static void test2_dead_handler(void *private_data) +{ + abort(); +} + +struct test2_write_state { + int count; +}; + +static void test2_write_done(struct tevent_req *subreq); + +static struct tevent_req *test2_write_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct comm_context *comm, + uint8_t *buf, size_t buflen) +{ + struct tevent_req *req, *subreq; + struct test2_write_state *state; + int i; + + req = tevent_req_create(mem_ctx, &state, struct test2_write_state); + if (req == NULL) { + return NULL; + } + + state->count = 0; + + for (i=0; i<10; i++) { + subreq = comm_write_send(state, ev, comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test2_write_done, req); + } + + return req; +} + +static void test2_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test2_write_state *state = tevent_req_data( + req, struct test2_write_state); + bool status; + int ret; + + status = comm_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->count += 1; + + if (state->count == 10) { + tevent_req_done(req); + } +} + +static void test2_timer_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval cur_time, + void *private_data) +{ + struct test2_state *state = (struct test2_state *)private_data; + + state->done = true; +} + +static void test2(void) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm_reader, *comm_writer; + struct test2_state test2_state; + struct tevent_req *req; + struct tevent_timer *te; + int fd[2]; + uint32_t data[2]; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + test2_state.mem_ctx = talloc_new(mem_ctx); + assert(test2_state.mem_ctx != NULL); + + test2_state.done = false; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = pipe(fd); + assert(ret == 0); + + ret = comm_setup(ev, ev, fd[0], test2_read_handler, &test2_state, + test2_dead_handler, NULL, &comm_reader); + assert(ret == 0); + + ret = comm_setup(ev, ev, fd[1], NULL, NULL, test2_dead_handler, NULL, + &comm_writer); + assert(ret == 0); + + data[0] = 2 * sizeof(uint32_t); + data[1] = 0; + + req = test2_write_send(test2_state.mem_ctx, ev, comm_writer, + (uint8_t *)data, data[0]); + assert(req != NULL); + + te = tevent_add_timer(ev, ev, tevent_timeval_current_ofs(5,0), + test2_timer_handler, &test2_state); + assert(te != NULL); + + while (! test2_state.done) { + tevent_loop_once(ev); + } + + talloc_free(mem_ctx); +} + +/* + * Test that data is written and read correctly. + */ + +static void test3_dead_handler(void *private_data) +{ + int dead_data = *(int *)private_data; + + assert(dead_data == 1 || dead_data == 2); + + if (dead_data == 1) { + /* reader */ + fprintf(stderr, "writer closed pipe\n"); + } else { + /* writer */ + fprintf(stderr, "reader closed pipe\n"); + } +} + +struct test3_writer_state { + struct tevent_context *ev; + struct comm_context *comm; + uint8_t *buf; + size_t *pkt_size; + int count, id; +}; + +static void test3_writer_next(struct tevent_req *subreq); + +static struct tevent_req *test3_writer_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct comm_context *comm, + size_t *pkt_size, size_t count) +{ + struct tevent_req *req, *subreq; + struct test3_writer_state *state; + size_t max_size = 0, buflen; + size_t i; + + for (i=0; i max_size) { + max_size = pkt_size[i]; + } + } + + req = tevent_req_create(mem_ctx, &state, struct test3_writer_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->comm = comm; + state->pkt_size = pkt_size; + state->count = count; + state->id = 0; + + state->buf = talloc_array(state, uint8_t, max_size); + if (state->buf == NULL) { + talloc_free(req); + return NULL; + } + for (i=0; ibuf[i] = i%256; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, test3_writer_next, req); + return req; +} + +static void test3_writer_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test3_writer_state *state = tevent_req_data( + req, struct test3_writer_state); + bool ret; + int err; + size_t buflen; + + ret = comm_write_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, err); + return; + } + + state->id++; + if (state->id >= state->count) { + tevent_req_done(req); + return; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + + tevent_req_set_callback(subreq, test3_writer_next, req); +} + +static void test3_writer_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return; + } + *perr = 0; +} + +static void test3_writer(int fd, size_t *pkt_size, size_t count) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm; + struct tevent_req *req; + int dead_data = 2; + int err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + err = comm_setup(mem_ctx, ev, fd, NULL, NULL, + test3_dead_handler, &dead_data, &comm); + assert(err == 0); + assert(comm != NULL); + + req = test3_writer_send(mem_ctx, ev, comm, pkt_size, count); + assert(req != NULL); + + tevent_req_poll(req, ev); + + test3_writer_recv(req, &err); + assert(err == 0); + + talloc_free(mem_ctx); +} + +struct test3_reader_state { + size_t *pkt_size; + int count, received; + bool done; +}; + +static void test3_reader_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test3_reader_state *state = talloc_get_type_abort( + private_data, struct test3_reader_state); + + assert(buflen == state->pkt_size[state->received]); + printf("%zi ", buflen); + state->received++; + + if (state->received == state->count) { + printf("\n"); + state->done = true; + } +} + +static void test3_reader(int fd, size_t *pkt_size, int count) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm; + struct test3_reader_state *state; + int dead_data = 1; + int err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + state = talloc_zero(mem_ctx, struct test3_reader_state); + assert(state != NULL); + + state->pkt_size = pkt_size; + state->count = count; + state->received = 0; + state->done = false; + + err = comm_setup(mem_ctx, ev, fd, test3_reader_handler, state, + test3_dead_handler, &dead_data, &comm); + assert(err == 0); + assert(comm != NULL); + + while (!state->done) { + tevent_loop_once(ev); + } + + talloc_free(mem_ctx); +} + +static void test3(void) +{ + int fd[2]; + int ret; + pid_t pid; + size_t pkt_size[13] = { 100, 2048, 500, 4096, 1024, 8192, + 200, 16384, 300, 32768, 400, 65536, + 1024*1024 }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Child process */ + close(fd[0]); + test3_writer(fd[1], pkt_size, 13); + close(fd[1]); + exit(0); + } + + close(fd[1]); + test3_reader(fd[0], pkt_size, 13); + close(fd[0]); +} + + +int main(int argc, const char **argv) +{ + int num; + + if (argc != 2) { + fprintf(stderr, "%s \n", argv[0]); + exit(1); + } + + num = atoi(argv[1]); + + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + default: + fprintf(stderr, "Unknown test number %s\n", argv[1]); + } + + return 0; +} diff --git a/ctdb/tests/src/conf_test.c b/ctdb/tests/src/conf_test.c new file mode 100644 index 0000000..9b3bd8f --- /dev/null +++ b/ctdb/tests/src/conf_test.c @@ -0,0 +1,513 @@ +/* + Configuration file handling on top of tini + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include + +#include "common/conf.c" + +static void test1(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_section(conf, NULL, NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test2(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_string(conf, "section1", "key1", "default", NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test3(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", NULL, NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test4(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", NULL, NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_integer(conf, "section1", "key1", 10, NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test5(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + enum conf_type type; + int ret; + bool status; + const char *s_val; + int i_val; + bool b_val; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + conf_assign_string_pointer(conf, "section1", "key1", &s_val); + conf_assign_integer_pointer(conf, "section1", "key2", &i_val); + conf_assign_boolean_pointer(conf, "section1", "key3", &b_val); + + status = conf_valid(conf); + assert(status == true); + + status = conf_query(conf, "section1", "key1", &type); + assert(status == true); + assert(type == CONF_STRING); + + status = conf_query(conf, "section1", "key2", &type); + assert(status == true); + assert(type == CONF_INTEGER); + + status = conf_query(conf, "section1", "key3", &type); + assert(status == true); + assert(type == CONF_BOOLEAN); + + assert(strcmp(s_val, "value1") == 0); + assert(i_val == 10); + assert(b_val == true); + + conf_set_defaults(conf); + + assert(strcmp(s_val, "value1") == 0); + assert(i_val == 10); + assert(b_val == true); + + talloc_free(mem_ctx); +} + +static void test6(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + const char *s_val, *s2_val; + int i_val, i2_val; + bool b_val, b2_val, is_default; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "default", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + conf_assign_string_pointer(conf, "section1", "key1", &s_val); + conf_assign_integer_pointer(conf, "section1", "key2", &i_val); + conf_assign_boolean_pointer(conf, "section1", "key3", &b_val); + + status = conf_valid(conf); + assert(status == true); + + is_default = false; + ret = conf_get_string(conf, "section1", "key1", &s2_val, &is_default); + assert(ret == 0); + assert(strcmp(s2_val, "default") == 0); + assert(is_default == true); + + is_default = false; + ret = conf_get_integer(conf, "section1", "key2", &i2_val, &is_default); + assert(ret == 0); + assert(i2_val == 10); + assert(is_default == true); + + is_default = false; + ret = conf_get_boolean(conf, "section1", "key3", &b2_val, &is_default); + assert(ret == 0); + assert(b2_val == true); + assert(is_default == true); + + ret = conf_set_string(conf, "section1", "key1", "foobar"); + assert(ret == 0); + + ret = conf_set_integer(conf, "section1", "key2", 20); + assert(ret == 0); + + ret = conf_set_boolean(conf, "section1", "key3", false); + assert(ret == 0); + + assert(strcmp(s_val, "foobar") == 0); + assert(i_val == 20); + assert(b_val == false); + + is_default = true; + ret = conf_get_string(conf, "section1", "key1", &s2_val, &is_default); + assert(ret == 0); + assert(strcmp(s2_val, "foobar") == 0); + assert(is_default == false); + + is_default = true; + ret = conf_get_integer(conf, "section1", "key2", &i2_val, &is_default); + assert(ret == 0); + assert(i2_val == 20); + assert(is_default == false); + + is_default = true; + ret = conf_get_boolean(conf, "section1", "key3", &b2_val, &is_default); + assert(ret == 0); + assert(b2_val == false); + assert(is_default == false); + + conf_dump(conf, stdout); + + conf_set_defaults(conf); + + assert(strcmp(s_val, "default") == 0); + assert(i_val == 10); + assert(b_val == true); + + talloc_free(mem_ctx); +} + +static bool test7_validate_string(const char *key, + const char *old_value, const char *new_value, + enum conf_update_mode mode) +{ + return false; +} + +static bool test7_validate_integer(const char *key, + int old_value, int new_value, + enum conf_update_mode mode) +{ + return false; +} + +static bool test7_validate_boolean(const char *key, + bool old_value, bool new_value, + enum conf_update_mode mode) +{ + return false; +} + +static void test7(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + const char *s_val, *s2_val; + int i_val, i2_val; + bool b_val, b2_val; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "default", + test7_validate_string); + conf_define_integer(conf, "section1", "key2", 10, + test7_validate_integer); + conf_define_boolean(conf, "section1", "key3", true, + test7_validate_boolean); + + conf_assign_string_pointer(conf, "section1", "key1", &s_val); + conf_assign_integer_pointer(conf, "section1", "key2", &i_val); + conf_assign_boolean_pointer(conf, "section1", "key3", &b_val); + + status = conf_valid(conf); + assert(status == true); + + ret = conf_set_string(conf, "section1", "key1", "default"); + assert(ret == 0); + + ret = conf_set_string(conf, "section1", "key1", "foobar"); + assert(ret == EINVAL); + + ret = conf_set_integer(conf, "section1", "key2", 10); + assert(ret == 0); + + ret = conf_set_integer(conf, "section1", "key2", 20); + assert(ret == EINVAL); + + ret = conf_set_boolean(conf, "section1", "key3", true); + assert(ret == 0); + + ret = conf_set_boolean(conf, "section1", "key3", false); + assert(ret == EINVAL); + + assert(strcmp(s_val, "default") == 0); + assert(i_val == 10); + assert(b_val == true); + + ret = conf_get_string(conf, "section1", "key2", &s2_val, NULL); + assert(ret == EINVAL); + + ret = conf_get_integer(conf, "section1", "key3", &i2_val, NULL); + assert(ret == EINVAL); + + ret = conf_get_boolean(conf, "section1", "key1", &b2_val, NULL); + assert(ret == EINVAL); + + talloc_free(mem_ctx); +} + +static bool test8_validate(struct conf_context *conf, + const char *section, + enum conf_update_mode mode) +{ + return false; +} + +static void test8(const char *filename) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", test8_validate); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "default", NULL); + + status = conf_valid(conf); + assert(status == true); + + ret = conf_load(conf, filename, true); + conf_dump(conf, stdout); + + talloc_free(mem_ctx); + exit(ret); +} + +static void test9(const char *filename, bool ignore_unknown) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + status = conf_valid(conf); + assert(status == true); + + conf_set_boolean(conf, "section1", "key3", false); + + ret = conf_load(conf, filename, ignore_unknown); + conf_dump(conf, stdout); + + talloc_free(mem_ctx); + exit(ret); +} + +static void test11(const char *filename) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + char reload[PATH_MAX]; + struct conf_context *conf; + int ret; + bool status; + + ret = snprintf(reload, sizeof(reload), "%s.reload", filename); + assert((size_t)ret < sizeof(reload)); + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + status = conf_valid(conf); + assert(status == true); + + ret = conf_load(conf, filename, false); + assert(ret == 0); + + ret = rename(reload, filename); + assert(ret == 0); + + ret = conf_reload(conf); + assert(ret == 0); + + conf_dump(conf, stdout); + + talloc_free(mem_ctx); + exit(ret); +} + +int main(int argc, const char **argv) +{ + int num; + + if (argc < 2) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + exit(1); + } + + num = atoi(argv[1]); + if (num > 7 && argc != 3) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + exit(1); + } + + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + case 4: + test4(); + break; + + case 5: + test5(); + break; + + case 6: + test6(); + break; + + case 7: + test7(); + break; + + case 8: + test8(argv[2]); + break; + + case 9: + test9(argv[2], true); + break; + + case 10: + test9(argv[2], false); + break; + + case 11: + test11(argv[2]); + break; + } + + return 0; +} diff --git a/ctdb/tests/src/ctdb_io_test.c b/ctdb/tests/src/ctdb_io_test.c new file mode 100644 index 0000000..b035342 --- /dev/null +++ b/ctdb/tests/src/ctdb_io_test.c @@ -0,0 +1,356 @@ +/* + ctdb_io tests + + Copyright (C) Christof Schmitt 2019 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include + +#include "common/ctdb_io.c" + +void ctdb_set_error(struct ctdb_context *ctdb, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + assert(false); +} + +static void test_setup(ctdb_queue_cb_fn_t cb, + int *pfd, + struct ctdb_context **pctdb, + struct ctdb_queue **pqueue) +{ + int pipefd[2], ret; + struct ctdb_context *ctdb; + struct ctdb_queue *queue; + + ret = pipe(pipefd); + assert(ret == 0); + + ctdb = talloc_zero(NULL, struct ctdb_context); + assert(ctdb != NULL); + + ctdb->ev = tevent_context_init(NULL); + + queue = ctdb_queue_setup(ctdb, ctdb, pipefd[0], 0, cb, + NULL, "test queue"); + assert(queue != NULL); + + *pctdb = ctdb; + *pfd = pipefd[1]; + if (pqueue != NULL) { + *pqueue = queue; + } +} + +static const size_t test1_req_len = 8; +static const char *test1_req = "abcdefgh"; + +static void test1_callback(uint8_t *data, size_t length, void *private_data) +{ + uint32_t len; + + len = *(uint32_t *)data; + assert(len == sizeof(uint32_t) + test1_req_len); + + assert(length == sizeof(uint32_t) + test1_req_len); + assert(memcmp(data + sizeof(len), test1_req, test1_req_len) == 0); +} + +static void test1(void) +{ + struct ctdb_context *ctdb; + int fd; + ssize_t ret; + uint32_t pkt_size; + + test_setup(test1_callback, &fd, &ctdb, NULL); + + pkt_size = sizeof(uint32_t) + test1_req_len; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, test1_req, test1_req_len); + assert(ret != -1 && (size_t)ret == test1_req_len); + + tevent_loop_once(ctdb->ev); + + TALLOC_FREE(ctdb); +} + +static const size_t test2_req_len[] = { 900, 24, 600 }; + +static int test2_cb_num = 0; + +static void test2_callback(uint8_t *data, size_t length, void *private_data) +{ + uint32_t len; + + len = *(uint32_t *)data; + assert(len == sizeof(uint32_t) + test2_req_len[test2_cb_num]); + assert(length == sizeof(uint32_t) + test2_req_len[test2_cb_num]); + + test2_cb_num++; +} + +static void test2(void) +{ + struct ctdb_context *ctdb; + int fd; + ssize_t ret; + size_t i; + uint32_t pkt_size; + char req[1024] = { 0 }; + + for (i = 0; i < sizeof(req); i++) { + req[i] = i % CHAR_MAX; + } + + test_setup(test2_callback, &fd, &ctdb, NULL); + + /* + * request 0 + */ + + pkt_size = sizeof(uint32_t) + test2_req_len[0]; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, req, test2_req_len[0]); + assert(ret != -1 && (size_t)ret == test2_req_len[0]); + + /* + * request 1 + */ + pkt_size = sizeof(uint32_t) + test2_req_len[1]; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + /* + * Omit the last byte to avoid buffer processing. + */ + ret = write(fd, req, test2_req_len[1] - 1); + assert(ret != -1 && (size_t)ret == test2_req_len[1] - 1); + + tevent_loop_once(ctdb->ev); + + /* + * Write the missing byte now. + */ + ret = write(fd, &req[test2_req_len[1] - 1], 1); + assert(ret != -1 && (size_t)ret == 1); + + /* + * request 2 + */ + pkt_size = sizeof(uint32_t) + test2_req_len[2]; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, req, test2_req_len[2]); + assert(ret != -1 && (size_t)ret == test2_req_len[2]); + + tevent_loop_once(ctdb->ev); + tevent_loop_once(ctdb->ev); + + assert(test2_cb_num == 2); + + TALLOC_FREE(ctdb); +} + +static void test_cb(uint8_t *data, size_t length, void *private_data) +{ + /* dummy handler, not verifying anything */ + TALLOC_FREE(data); +} + +static void test3(void) +{ + struct ctdb_context *ctdb; + struct ctdb_queue *queue; + uint32_t pkt_size; + char *request; + size_t req_len; + int fd; + ssize_t ret; + + test_setup(test_cb, &fd, &ctdb, &queue); + request = talloc_zero_size(queue, queue->buffer_size); + + /* + * calculate a request length which will fit into the buffer + * but not twice. Because we need to write the size integer + * as well (4-bytes) we're guaranteed that no 2 packets will fit. + */ + req_len = queue->buffer_size >> 1; + + /* writing first packet */ + pkt_size = sizeof(uint32_t) + req_len; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, request, req_len); + assert(ret != -1 && (size_t)ret == req_len); + + /* writing second, incomplete packet */ + pkt_size = sizeof(uint32_t) + req_len; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, request, req_len >> 1); + assert(ret != -1 && (size_t)ret == req_len >> 1); + + /* process...only 1st packet can be processed */ + tevent_loop_once(ctdb->ev); + + /* we should see a progressed offset of req_len + sizeof(pkt_size) */ + assert(queue->buffer.offset == req_len + sizeof(pkt_size)); + + /* writing another few bytes of the still incomplete packet */ + ret = write(fd, request, (req_len >> 1) - 1); + assert(ret != -1 && (size_t)ret == (req_len >> 1) - 1); + + /* + * the packet is still incomplete and cannot be processed + * but the packet data had to be moved in the buffer in order + * to fetch the new 199 bytes -> offset must be 0 now. + */ + tevent_loop_once(ctdb->ev); + /* + * needs to be called twice as an incomplete packet + * does not trigger a schedule_immediate + */ + tevent_loop_once(ctdb->ev); + + assert(queue->buffer.offset == 0); + + TALLOC_FREE(ctdb); +} + +static void test4(void) +{ + struct ctdb_context *ctdb; + struct ctdb_queue *queue; + uint32_t pkt_size; + char *request; + size_t req_len, half_buf_size; + int fd; + ssize_t ret; + + test_setup(test_cb, &fd, &ctdb, &queue); + + req_len = queue->buffer_size << 1; /* double the buffer size */ + request = talloc_zero_size(queue, req_len); + + /* writing first part of packet exceeding standard buffer size */ + pkt_size = sizeof(uint32_t) + req_len; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + half_buf_size = queue->buffer_size >> 1; + + ret = write(fd, request, req_len - half_buf_size); + assert(ret != -1 && (size_t)ret == req_len - half_buf_size); + + /* + * process... + * this needs to be done to have things changed + */ + tevent_loop_once(ctdb->ev); + /* + * needs to be called twice as an initial incomplete packet + * does not trigger a schedule_immediate + */ + tevent_loop_once(ctdb->ev); + + /* the buffer should be resized to packet size now */ + assert(queue->buffer.size == pkt_size); + + /* writing remaining data */ + ret = write(fd, request, half_buf_size); + assert(ret != -1 && (size_t)ret == half_buf_size); + + /* process... */ + tevent_loop_once(ctdb->ev); + + /* + * the buffer was increased beyond its standard size. + * once packet got processed, the buffer has to be free'd + * and will be re-allocated with standard size on new request arrival. + */ + + assert(queue->buffer.size == 0); + + /* writing new packet to verify standard buffer size */ + pkt_size = sizeof(uint32_t) + half_buf_size; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, request, half_buf_size); + assert(ret != -1 && (size_t)ret == half_buf_size); + + /* process... */ + tevent_loop_once(ctdb->ev); + + /* back to standard buffer size */ + assert(queue->buffer.size == queue->buffer_size); + + TALLOC_FREE(ctdb); +} + +int main(int argc, const char **argv) +{ + int num; + + if (argc != 2) { + fprintf(stderr, "%s \n", argv[0]); + exit(1); + } + + + num = atoi(argv[1]); + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + case 4: + test4(); + break; + + default: + fprintf(stderr, "Unknown test number %s\n", argv[1]); + } + + return 0; +} diff --git a/ctdb/tests/src/ctdb_packet_parse.c b/ctdb/tests/src/ctdb_packet_parse.c new file mode 100644 index 0000000..0b99b34 --- /dev/null +++ b/ctdb/tests/src/ctdb_packet_parse.c @@ -0,0 +1,136 @@ +/* + CTDB protocol parser + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" +#include "system/locale.h" + +#include +#include + +#include "protocol/protocol.h" +#include "protocol/protocol_api.h" + +static TDB_DATA strace_parser(char *buf, TALLOC_CTX *mem_ctx) +{ + TDB_DATA data; + size_t i = 0, j = 0; + + data.dptr = talloc_size(mem_ctx, strlen(buf)); + if (data.dptr == NULL) { + return tdb_null; + } + + while (i < strlen(buf)) { + if (buf[i] == '\\') { + /* first char after '\' is a digit or other escape */ + if (isdigit(buf[i+1])) { + char tmp[4] = { '\0', '\0', '\0', '\0' }; + + tmp[0] = buf[i+1]; + if (isdigit(buf[i+2])) { + tmp[1] = buf[i+2]; + if (isdigit(buf[i+3])) { + tmp[2] = buf[i+3]; + i += 4; + } else { + i += 3; + } + } else { + i += 2; + } + data.dptr[j] = strtol(tmp, NULL, 8); + } else if (buf[i+1] == 'a') { + data.dptr[j] = 7; + i += 2; + } else if (buf[i+1] == 'b') { + data.dptr[j] = 8; + i += 2; + } else if (buf[i+1] == 't') { + data.dptr[j] = 9; + i += 2; + } else if (buf[i+1] == 'n') { + data.dptr[j] = 10; + i += 2; + } else if (buf[i+1] == 'v') { + data.dptr[j] = 11; + i += 2; + } else if (buf[i+1] == 'f') { + data.dptr[j] = 12; + i += 2; + } else if (buf[i+1] == 'r') { + data.dptr[j] = 13; + i += 2; + } else { + fprintf(stderr, + "Unknown escape \\%c\n", + buf[i+1]); + data.dptr[j] = 0; + } + + j += 1; + } else if (buf[i] == '\n') { + i += 1; + } else if (buf[i] == '\0') { + break; + } else { + data.dptr[j] = buf[i]; + i += 1; + j += 1; + } + } + + data.dsize = j; + + return data; +} + +int main(int argc, char *argv[]) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + char line[1024]; + char *ptr; + TDB_DATA (*parser)(char *, TALLOC_CTX *); + + if (argc != 2) { + fprintf(stderr, "Usage: %s strace\n", argv[0]); + exit(1); + } + + if (strcmp(argv[1], "strace") == 0) { + parser = strace_parser; + } else { + fprintf(stderr, "Unknown input format - %s\n", argv[1]); + exit(1); + } + + while ((ptr = fgets(line, sizeof(line), stdin)) != NULL) { + TDB_DATA data; + + data = parser(ptr, mem_ctx); + if (data.dptr == NULL) { + continue; + } + + ctdb_packet_print(data.dptr, data.dsize, stdout); + TALLOC_FREE(data.dptr); + } + + return 0; +} diff --git a/ctdb/tests/src/ctdb_takeover_tests.c b/ctdb/tests/src/ctdb_takeover_tests.c new file mode 100644 index 0000000..ad7d7ee --- /dev/null +++ b/ctdb/tests/src/ctdb_takeover_tests.c @@ -0,0 +1,281 @@ +/* + Tests for ctdb_takeover.c + + Copyright (C) Martin Schwenke 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include +#include + +#include "lib/util/debug.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_util.h" +#include "common/logging.h" +#include "common/system.h" + +#include "server/ipalloc.h" + +#include "ipalloc_read_known_ips.h" + +static void print_ctdb_public_ip_list(TALLOC_CTX *mem_ctx, + struct public_ip_list * ips) +{ + while (ips) { + printf("%s %d\n", + ctdb_sock_addr_to_string(mem_ctx, &(ips->addr), false), + ips->pnn); + ips = ips->next; + } +} + +static uint32_t *get_tunable_values(TALLOC_CTX *tmp_ctx, + int numnodes, + const char *tunable); +static enum ctdb_runstate *get_runstate(TALLOC_CTX *tmp_ctx, + int numnodes); + +static void read_ctdb_public_ip_info(TALLOC_CTX *ctx, + int numnodes, + bool multi, + struct ctdb_public_ip_list ** known, + struct ctdb_public_ip_list ** avail) +{ + int n; + enum ctdb_runstate *runstate; + + *known = ipalloc_read_known_ips(ctx, numnodes, multi); + assert(*known != NULL); + + *avail = talloc_zero_array(ctx, struct ctdb_public_ip_list, + numnodes); + assert(*avail != NULL); + + runstate = get_runstate(ctx, numnodes); + for (n = 0; n < numnodes; n++) { + if (runstate[n] == CTDB_RUNSTATE_RUNNING) { + (*avail)[n] = (*known)[n]; + } + } +} + +static uint32_t *get_tunable_values(TALLOC_CTX *tmp_ctx, + int numnodes, + const char *tunable) +{ + int i; + char *tok; + uint32_t *tvals = talloc_zero_array(tmp_ctx, uint32_t, numnodes); + char *t = getenv(tunable); + + if (t == NULL) { + return tvals; + } + + if (strcmp(t, "1") == 0) { + for (i = 0; i < numnodes; i++) { + tvals[i] = 1; + } + } else { + tok = strtok(t, ","); + i = 0; + while (tok != NULL) { + tvals[i] = (uint32_t)strtol(tok, NULL, 0); + i++; + tok = strtok(NULL, ","); + } + if (i != numnodes) { + fprintf(stderr, + "ERROR: Wrong number of values in %s\n", + tunable); + exit(1); + } + } + + return tvals; +} + +static enum ctdb_runstate *get_runstate(TALLOC_CTX *tmp_ctx, + int numnodes) +{ + int i; + uint32_t *tvals; + enum ctdb_runstate *runstate = + talloc_zero_array(tmp_ctx, enum ctdb_runstate, numnodes); + char *t = getenv("CTDB_TEST_RUNSTATE"); + + if (t == NULL) { + for (i=0; inum = 0; + tok = strtok(ns, ","); + while (tok != NULL) { + n = nodemap->num; + nodemap->node = talloc_realloc(nodemap, nodemap->node, + struct ctdb_node_and_flags, n+1); + nodemap->node[n].pnn = n; + nodemap->node[n].flags = (uint32_t) strtol(tok, NULL, 0); + nodemap->node[n].addr = sa_zero; + nodemap->num++; + tok = strtok(NULL, ","); + } + + algorithm = IPALLOC_LCP2; + if ((t = getenv("CTDB_IP_ALGORITHM"))) { + if (strcmp(t, "lcp2") == 0) { + algorithm = IPALLOC_LCP2; + } else if (strcmp(t, "nondet") == 0) { + algorithm = IPALLOC_NONDETERMINISTIC; + } else if (strcmp(t, "det") == 0) { + algorithm = IPALLOC_DETERMINISTIC; + } else { + DEBUG(DEBUG_ERR, + ("ERROR: unknown IP algorithm %s\n", t)); + exit(1); + } + } + + t = getenv("CTDB_SET_NoIPTakeover"); + if (t != NULL) { + noiptakeover = (uint32_t) strtol(t, NULL, 0); + } else { + noiptakeover = 0; + } + + *ipalloc_state = ipalloc_state_init(mem_ctx, nodemap->num, + algorithm, + (noiptakeover != 0), + false, + NULL); + assert(*ipalloc_state != NULL); + + read_ctdb_public_ip_info(mem_ctx, nodemap->num, + read_ips_for_multiple_nodes, + &known, &avail); + + /* Drop available IPs for INACTIVE/DISABLED nodes */ + for (n = 0; n < nodemap->num; n++) { + uint32_t flags = nodemap->node[n].flags; + if ((flags & (NODE_FLAGS_INACTIVE|NODE_FLAGS_DISABLED)) != 0) { + avail[n].num = 0; + } + } + + ipalloc_set_public_ips(*ipalloc_state, known, avail); +} + +/* IP layout is read from stdin. See comment for ctdb_test_init() for + * explanation of read_ips_for_multiple_nodes. + */ +static void ctdb_test_ipalloc(const char nodestates[], + bool read_ips_for_multiple_nodes) +{ + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + struct ipalloc_state *ipalloc_state; + + ctdb_test_init(tmp_ctx, nodestates, &ipalloc_state, + read_ips_for_multiple_nodes); + + print_ctdb_public_ip_list(tmp_ctx, ipalloc(ipalloc_state)); + + talloc_free(tmp_ctx); +} + +static void usage(void) +{ + fprintf(stderr, "usage: ctdb_takeover_tests \n"); + exit(1); +} + +int main(int argc, const char *argv[]) +{ + int loglevel; + const char *debuglevelstr = getenv("CTDB_TEST_LOGLEVEL"); + + setup_logging("ctdb_takeover_tests", DEBUG_STDERR); + + if (! debug_level_parse(debuglevelstr, &loglevel)) { + loglevel = DEBUG_DEBUG; + } + debuglevel_set(loglevel); + + if (argc < 2) { + usage(); + } + + if (argc == 3 && + strcmp(argv[1], "ipalloc") == 0) { + ctdb_test_ipalloc(argv[2], false); + } else if (argc == 4 && + strcmp(argv[1], "ipalloc") == 0 && + strcmp(argv[3], "multi") == 0) { + ctdb_test_ipalloc(argv[2], true); + } else { + usage(); + } + + return 0; +} diff --git a/ctdb/tests/src/db_hash_test.c b/ctdb/tests/src/db_hash_test.c new file mode 100644 index 0000000..31aa501 --- /dev/null +++ b/ctdb/tests/src/db_hash_test.c @@ -0,0 +1,138 @@ +/* + db_hash tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include + +#include "common/db_hash.c" + +static int record_parser(uint8_t *keybuf, size_t keylen, + uint8_t *databuf, size_t datalen, + void *private_data) +{ + int *count = (int *)private_data; + + (*count) += 1; + return 0; +} + +static void do_test(enum db_hash_type type) +{ + struct db_hash_context *dh = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + uint8_t key[] = "This is a long key"; + uint8_t value[] = "This is a long value"; + int ret; + int count = 0; + + ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == EINVAL); + + ret = db_hash_add(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == EINVAL); + + ret = db_hash_exists(dh, key, sizeof(key)); + assert(ret == EINVAL); + + ret = db_hash_delete(dh, key, sizeof(key)); + assert(ret == EINVAL); + + ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh); + assert(ret == 0); + + ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == 0); + + ret = db_hash_exists(dh, key, sizeof(key)); + assert(ret == 0); + + ret = db_hash_fetch(dh, key, sizeof(key), NULL, NULL); + assert(ret == EINVAL); + + ret = db_hash_fetch(dh, key, sizeof(key), record_parser, &count); + assert(ret == 0); + assert(count == 1); + + ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == EEXIST); + + ret = db_hash_delete(dh, key, sizeof(key)); + assert(ret == 0); + + ret = db_hash_exists(dh, key, sizeof(key)); + assert(ret == ENOENT); + + ret = db_hash_delete(dh, key, sizeof(key)); + assert(ret == ENOENT); + + ret = db_hash_add(dh, key, sizeof(key), key, sizeof(key)); + assert(ret == 0); + + ret = db_hash_add(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == 0); + + talloc_free(dh); + ret = talloc_get_size(mem_ctx); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +static void do_traverse_test(enum db_hash_type type) +{ + struct db_hash_context *dh = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + char key[16] = "keyXXXX"; + char value[] = "This is some test value"; + int count, ret, i; + + ret = db_hash_traverse(dh, NULL, NULL, &count); + assert(ret == EINVAL); + + ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh); + assert(ret == 0); + + for (i=0; i<2000; i++) { + sprintf(key, "key%04d", i); + ret = db_hash_insert(dh, (uint8_t *)key, sizeof(key), + (uint8_t *)value, sizeof(value)); + assert(ret == 0); + } + + ret = db_hash_traverse(dh, NULL, NULL, &count); + assert(ret == 0); + assert(count == 2000); + + ret = db_hash_traverse(dh, record_parser, &count, NULL); + assert(ret == 0); + assert(count == 4000); + + talloc_free(dh); + talloc_free(mem_ctx); +} + +int main(void) +{ + do_test(DB_HASH_SIMPLE); + do_test(DB_HASH_COMPLEX); + do_traverse_test(DB_HASH_SIMPLE); + do_traverse_test(DB_HASH_COMPLEX); + return 0; +} diff --git a/ctdb/tests/src/db_test_tool.c b/ctdb/tests/src/db_test_tool.c new file mode 100644 index 0000000..e99da3c --- /dev/null +++ b/ctdb/tests/src/db_test_tool.c @@ -0,0 +1,792 @@ +/* + CTDB DB test tool + + Copyright (C) Martin Schwenke 2019 + + Parts based on ctdb.c, event_tool.c: + + Copyright (C) Amitay Isaacs 2015, 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/time.h" + +#include +#include +#include +#include + +#include "lib/util/debug.h" +#include "lib/util/sys_rw.h" +#include "lib/util/util.h" +#include "lib/util/smb_strtox.h" +#include "lib/tdb_wrap/tdb_wrap.h" + +#include "common/cmdline.h" +#include "common/logging.h" +#include "common/path.h" +#include "common/event_script.h" +#include "common/system_socket.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_api.h" +#include "protocol/protocol_util.h" + +#include "client/client.h" +#include "client/client_sync.h" + +struct tdb_context *client_db_tdb(struct ctdb_db_context *db); + +#define TIMEOUT() tevent_timeval_current_ofs(ctx->timelimit, 0) + +struct db_test_tool_context { + struct cmdline_context *cmdline; + struct tevent_context *ev; + struct ctdb_client_context *client; + uint32_t destnode; + uint32_t timelimit; +}; + +/* + * If this is ever consolidated into a larger test tool then these + * forward declarations can be moved to an include file + */ +int db_test_tool_init(TALLOC_CTX *mem_ctx, + const char *prog, + struct poptOption *options, + int argc, + const char **argv, + bool parse_options, + struct db_test_tool_context **result); +int db_test_tool_run(struct db_test_tool_context *ctx, int *result); + +static int db_test_get_lmaster(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_vnn_map *vnnmap; + TDB_DATA key; + uint32_t idx, lmaster; + unsigned int hash; + int ret = 0; + + if (argc != 1) { + cmdline_usage(ctx->cmdline, "get-lmaster"); + return 1; + } + + ret = ctdb_ctrl_getvnnmap(mem_ctx, + ctx->ev, + ctx->client, + CTDB_CURRENT_NODE, + TIMEOUT(), + &vnnmap); + if (ret != 0) { + D_ERR("Control GETVNN_MAP failed, ret=%d\n", ret); + return ret; + } + + key.dsize = strlen(argv[0]); + key.dptr = (uint8_t *)discard_const(argv[0]); + + hash = tdb_jenkins_hash(&key); + idx = hash % vnnmap->size; + lmaster = vnnmap->map[idx]; + + printf("%"PRId32"\n", lmaster); + + return 0; +} + +static struct ctdb_dbid *db_find(TALLOC_CTX *mem_ctx, + struct db_test_tool_context *ctx, + struct ctdb_dbid_map *dbmap, + const char *db_name) +{ + struct ctdb_dbid *db = NULL; + const char *name; + unsigned int i; + int ret; + + for (i=0; inum; i++) { + ret = ctdb_ctrl_get_dbname(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + dbmap->dbs[i].db_id, + &name); + if (ret != 0) { + return NULL; + } + + if (strcmp(db_name, name) == 0) { + talloc_free(discard_const(name)); + db = &dbmap->dbs[i]; + break; + } + } + + return db; +} + +static bool db_exists(TALLOC_CTX *mem_ctx, + struct db_test_tool_context *ctx, + const char *db_arg, + uint32_t *db_id, + const char **db_name, + uint8_t *db_flags) +{ + struct ctdb_dbid_map *dbmap; + struct ctdb_dbid *db = NULL; + uint32_t id = 0; + const char *name = NULL; + unsigned int i; + int ret = 0; + + ret = ctdb_ctrl_get_dbmap(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + &dbmap); + if (ret != 0) { + return false; + } + + if (strncmp(db_arg, "0x", 2) == 0) { + id = smb_strtoul(db_arg, NULL, 0, &ret, SMB_STR_STANDARD); + if (ret != 0) { + return false; + } + for (i=0; inum; i++) { + if (id == dbmap->dbs[i].db_id) { + db = &dbmap->dbs[i]; + break; + } + } + } else { + name = db_arg; + db = db_find(mem_ctx, ctx, dbmap, name); + } + + if (db == NULL) { + fprintf(stderr, "No database matching '%s' found\n", db_arg); + return false; + } + + if (name == NULL) { + ret = ctdb_ctrl_get_dbname(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + id, + &name); + if (ret != 0) { + return false; + } + } + + if (db_id != NULL) { + *db_id = db->db_id; + } + if (db_name != NULL) { + *db_name = talloc_strdup(mem_ctx, name); + } + if (db_flags != NULL) { + *db_flags = db->flags; + } + return true; +} + +static int db_test_fetch_local_delete(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_context *db = NULL; + struct ctdb_record_handle *h = NULL; + struct tdb_context *tdb; + struct ctdb_ltdb_header header; + const char *db_name; + TDB_DATA key, data; + uint32_t db_id; + uint8_t db_flags; + size_t len; + uint8_t *buf; + size_t np; + int ret; + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "fetch-local-delete"); + return 1; + } + + if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) { + return ENOENT; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return EINVAL; + } + + ret = ctdb_attach(ctx->ev, + ctx->client, + TIMEOUT(), + db_name, + db_flags, + &db); + if (ret != 0) { + D_ERR("Failed to attach to DB %s\n", db_name); + return ret; + } + + key.dsize = strlen(argv[1]); + key.dptr = (uint8_t *)discard_const(argv[1]); + + ret = ctdb_fetch_lock(mem_ctx, + ctx->ev, + ctx->client, + db, + key, + false, + &h, + &header, + NULL); + if (ret != 0) { + D_ERR("Failed to fetch record for key %s\n", argv[1]); + goto done; + } + + len = ctdb_ltdb_header_len(&header); + buf = talloc_size(mem_ctx, len); + if (buf == NULL) { + D_ERR("Memory allocation error\n"); + ret = ENOMEM; + goto done; + } + + ctdb_ltdb_header_push(&header, buf, &np); + + data.dsize = np; + data.dptr = buf; + + tdb = client_db_tdb(db); + + ret = tdb_store(tdb, key, data, TDB_REPLACE); + TALLOC_FREE(buf); + if (ret != 0) { + D_ERR("fetch_lock delete: %s tdb_store failed, %s\n", + db_name, + tdb_errorstr(tdb)); + } + +done: + TALLOC_FREE(h); + + return ret; +} + +#define ISASCII(x) (isprint(x) && ! strchr("\"\\", (x))) + +static void dump(const char *name, uint8_t *dptr, size_t dsize) +{ + size_t i; + + fprintf(stdout, "%s(%zu) = \"", name, dsize); + for (i = 0; i < dsize; i++) { + if (ISASCII(dptr[i])) { + fprintf(stdout, "%c", dptr[i]); + } else { + fprintf(stdout, "\\%02X", dptr[i]); + } + } + fprintf(stdout, "\"\n"); +} + +static void dump_ltdb_header(struct ctdb_ltdb_header *header) +{ + fprintf(stdout, "dmaster: %u\n", header->dmaster); + fprintf(stdout, "rsn: %" PRIu64 "\n", header->rsn); + fprintf(stdout, "flags: 0x%08x", header->flags); + if (header->flags & CTDB_REC_FLAG_MIGRATED_WITH_DATA) { + fprintf(stdout, " MIGRATED_WITH_DATA"); + } + if (header->flags & CTDB_REC_FLAG_VACUUM_MIGRATED) { + fprintf(stdout, " VACUUM_MIGRATED"); + } + if (header->flags & CTDB_REC_FLAG_AUTOMATIC) { + fprintf(stdout, " AUTOMATIC"); + } + if (header->flags & CTDB_REC_RO_HAVE_DELEGATIONS) { + fprintf(stdout, " RO_HAVE_DELEGATIONS"); + } + if (header->flags & CTDB_REC_RO_HAVE_READONLY) { + fprintf(stdout, " RO_HAVE_READONLY"); + } + if (header->flags & CTDB_REC_RO_REVOKING_READONLY) { + fprintf(stdout, " RO_REVOKING_READONLY"); + } + if (header->flags & CTDB_REC_RO_REVOKE_COMPLETE) { + fprintf(stdout, " RO_REVOKE_COMPLETE"); + } + fprintf(stdout, "\n"); + +} + +static int db_test_local_lock(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_context *db; + const char *db_name; + int pipefd[2]; + TDB_DATA key; + uint32_t db_id; + uint8_t db_flags; + pid_t pid; + int ret; + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "local-lock"); + return 1; + } + + + if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) { + D_ERR("DB %s not attached\n", db_name); + return 1; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return 1; + } + + ret = ctdb_attach(ctx->ev, + ctx->client, + TIMEOUT(), + db_name, + db_flags, + &db); + if (ret != 0) { + D_ERR("Failed to attach to DB %s\n", db_name); + return 1; + } + + ret = pipe(pipefd); + if (ret != 0) { + DBG_ERR("Failed to create pipe\n"); + return 1; + } + + pid = fork(); + if (pid < 0) { + DBG_ERR("Failed to fork()\n"); + return 1; + } + + if (pid != 0) { + ssize_t nread; + int status; + + close(pipefd[1]); + + nread = sys_read(pipefd[0], &status, sizeof(status)); + if (nread < 0 || (size_t)nread != sizeof(status)) { + status = EINVAL; + } + + if (status == 0) { + printf("OK %d\n", pid); + } else { + printf("FAIL %d\n", status); + } + fflush(stdout); + + return status; + } + + close(pipefd[0]); + + key.dsize = strlen(argv[1]); + key.dptr = (uint8_t *)discard_const(argv[1]); + + ret = tdb_chainlock(client_db_tdb(db), key); + if (ret != 0) { + D_ERR("Failed to lock chain for key %s\n", argv[1]); + goto fail; + } + + sys_write(pipefd[1], &ret, sizeof(ret)); + + fclose(stdin); + fclose(stdout); + fclose(stderr); + + /* Hold the lock- the caller should SIGTERM to release the lock */ + sleep(120); + exit(1); + +fail: + sys_write(pipefd[1], &ret, sizeof(ret)); + return ret; +} + +static int db_test_local_read(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_context *db; + struct ctdb_ltdb_header header; + const char *db_name; + TDB_DATA key, data; + uint32_t db_id; + uint8_t db_flags; + size_t np; + int ret; + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "local-read"); + return 1; + } + + if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) { + return ENOENT; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return EINVAL; + } + + ret = ctdb_attach(ctx->ev, + ctx->client, + TIMEOUT(), + db_name, + db_flags, + &db); + if (ret != 0) { + D_ERR("Failed to attach to DB %s\n", db_name); + return ret; + } + + key.dsize = strlen(argv[1]); + key.dptr = (uint8_t *)discard_const(argv[1]); + + data = tdb_fetch(client_db_tdb(db), key); + + if (data.dptr == NULL) { + D_ERR("No record for key %s\n", argv[1]); + return 1; + } + + if (data.dsize < sizeof(struct ctdb_ltdb_header)) { + D_ERR("Invalid record for key %s\n", argv[1]); + free(data.dptr); + return 1; + } + + ret = ctdb_ltdb_header_pull(data.dptr, data.dsize, &header, &np); + if (ret != 0) { + D_ERR("Failed to parse header from data\n"); + free(data.dptr); + return 1; + } + + dump_ltdb_header(&header); + dump("data", data.dptr + np, data.dsize - np); + + free(data.dptr); + + return 0; +} + +static int db_test_vacuum(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_vacuum db_vacuum; + struct ctdb_req_control request; + struct ctdb_reply_control *reply; + const char *db_arg; + uint32_t db_id; + const char *db_name; + uint8_t db_flags; + int ret = 0; + + if (argc != 1 && argc != 2) { + cmdline_usage(ctx->cmdline, "vacuum"); + return 1; + } + + db_arg = argv[0]; + + db_vacuum.full_vacuum_run = false; + if (argc == 2) { + if (strcmp(argv[1], "full") == 0) { + db_vacuum.full_vacuum_run = true; + } else { + cmdline_usage(ctx->cmdline, "vacuum"); + return 1; + } + } + + if (! db_exists(mem_ctx, ctx, db_arg, &db_id, &db_name, &db_flags)) { + return ENOENT; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return EINVAL; + } + + db_vacuum.db_id = db_id; + + ctdb_req_control_db_vacuum(&request, &db_vacuum); + + ret = ctdb_client_control(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + &request, + &reply); + if (ret != 0) { + D_ERR("Control DB_VACUUM failed to node %u, ret=%d\n", + ctx->destnode, + ret); + return ret; + } + + + ret = ctdb_reply_control_db_vacuum(reply); + if (ret != 0) { + D_ERR("Control DB_VACUUM failed, ret=%d\n", ret); + return ret; + } + + return 0; +} + +struct cmdline_command db_test_commands[] = { + { + .name = "get-lmaster", + .fn = db_test_get_lmaster, + .msg_help = "Print lmaster for key", + .msg_args = "" + }, + { + .name = "fetch-local-delete", + .fn = db_test_fetch_local_delete, + .msg_help = "Fetch record and delete from local database", + .msg_args = " " + }, + { + .name = "local-lock", + .fn = db_test_local_lock, + .msg_help = "Lock a record in a local database", + .msg_args = " " + }, + { + .name = "local-read", + .fn = db_test_local_read, + .msg_help = "Read a record from local database", + .msg_args = " " + }, + { + .name = "vacuum", + .fn = db_test_vacuum, + .msg_help = "Vacuum a database", + .msg_args = " [full]" + }, + CMDLINE_TABLEEND +}; + +int db_test_tool_init(TALLOC_CTX *mem_ctx, + const char *prog, + struct poptOption *options, + int argc, + const char **argv, + bool parse_options, + struct db_test_tool_context **result) +{ + struct db_test_tool_context *ctx; + int ret; + + ctx = talloc_zero(mem_ctx, struct db_test_tool_context); + if (ctx == NULL) { + D_ERR("Memory allocation error\n"); + return ENOMEM; + } + + ret = cmdline_init(mem_ctx, + prog, + options, + NULL, + db_test_commands, + &ctx->cmdline); + if (ret != 0) { + D_ERR("Failed to initialize cmdline, ret=%d\n", ret); + talloc_free(ctx); + return ret; + } + + ret = cmdline_parse(ctx->cmdline, argc, argv, parse_options); + if (ret != 0) { + cmdline_usage(ctx->cmdline, NULL); + talloc_free(ctx); + return ret; + } + + *result = ctx; + return 0; +} + +int db_test_tool_run(struct db_test_tool_context *ctx, int *result) +{ + char *ctdb_socket; + int ret; + + ctx->ev = tevent_context_init(ctx); + if (ctx->ev == NULL) { + D_ERR("Failed to initialize tevent\n"); + return ENOMEM; + } + + ctdb_socket = path_socket(ctx, "ctdbd"); + if (ctdb_socket == NULL) { + fprintf(stderr, "Memory allocation error\n"); + return ENOMEM; + } + + ret = ctdb_client_init(ctx, ctx->ev, ctdb_socket, &ctx->client); + if (ret != 0) { + D_ERR("Failed to connect to CTDB daemon (%s)\n", ctdb_socket); + return ret; + } + + ret = cmdline_run(ctx->cmdline, ctx, result); + return ret; +} + +#ifdef CTDB_DB_TEST_TOOL + +static struct { + const char *debug; + int destnode; + int timelimit; +} db_test_data = { + .debug = "ERROR", + .destnode = CTDB_CURRENT_NODE, + .timelimit = 60, +}; + +struct poptOption db_test_options[] = { + { + .longName = "debug", + .shortName = 'd', + .argInfo = POPT_ARG_STRING, + .arg = &db_test_data.debug, + .val = 0, + .descrip = "debug level", + .argDescrip = "ERROR|WARNING|NOTICE|INFO|DEBUG" + }, + { + .longName = "node", + .shortName = 'n', + .argInfo = POPT_ARG_INT, + .arg = &db_test_data.destnode, + .val = 0, + .descrip = "node number", + .argDescrip = "NUM" + }, + { + .longName = "timelimit", + .shortName = 't', + .argInfo = POPT_ARG_INT, + .arg = &db_test_data.timelimit, + .val = 0, + .descrip = "control time limit", + .argDescrip = "SECONDS" + }, + POPT_TABLEEND +}; + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct db_test_tool_context *ctx; + int ret, result = 0; + int level; + bool ok; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = db_test_tool_init(mem_ctx, + "ctdb-db-test", + db_test_options, + argc, + argv, + true, + &ctx); + if (ret != 0) { + talloc_free(mem_ctx); + exit(1); + } + + setup_logging("ctdb-db-test", DEBUG_STDERR); + ok = debug_level_parse(db_test_data.debug, &level); + if (!ok) { + level = DEBUG_ERR; + } + debuglevel_set(level); + + ctx->destnode = db_test_data.destnode; + ctx->timelimit = db_test_data.timelimit; + + ret = db_test_tool_run(ctx, &result); + if (ret != 0) { + result = ret; + } + + talloc_free(mem_ctx); + exit(result); +} + +#endif /* CTDB_DB_TEST_TOOL */ diff --git a/ctdb/tests/src/dummy_client.c b/ctdb/tests/src/dummy_client.c new file mode 100644 index 0000000..13e0691 --- /dev/null +++ b/ctdb/tests/src/dummy_client.c @@ -0,0 +1,163 @@ +/* + Dummy CTDB client for testing + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include +#include +#include + +#include "common/logging.h" +#include "common/path.h" + +#include "client/client.h" + +static struct { + const char *sockpath; + const char *debuglevel; + int num_connections; + int timelimit; + const char *srvidstr; +} options; + +static struct poptOption cmdline_options[] = { + POPT_AUTOHELP + { "socket", 's', POPT_ARG_STRING, &options.sockpath, 0, + "Unix domain socket path", "filename" }, + { "debug", 'd', POPT_ARG_STRING, &options.debuglevel, 0, + "debug level", "ERR|WARNING|NOTICE|INFO|DEBUG" } , + { "nconn", 'n', POPT_ARG_INT, &options.num_connections, 0, + "number of connections", "" }, + { "timelimit", 't', POPT_ARG_INT, &options.timelimit, 0, + "time limit", "seconds" }, + { "srvid", 'S', POPT_ARG_STRING, &options.srvidstr, 0, + "srvid to register", "srvid" }, + POPT_TABLEEND +}; + +static void dummy_handler(uint64_t srvid, TDB_DATA data, void *private_data) +{ + bool *done = (bool *)private_data; + + *done = true; +} + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context **client; + struct ctdb_client_context *last_client; + poptContext pc; + int opt, ret, i; + int log_level; + bool status, done; + + /* Set default options */ + options.sockpath = NULL; + options.debuglevel = "ERR"; + options.num_connections = 1; + options.timelimit = 60; + options.srvidstr = NULL; + + pc = poptGetContext(argv[0], argc, argv, cmdline_options, + POPT_CONTEXT_KEEP_FIRST); + while ((opt = poptGetNextOpt(pc)) != -1) { + fprintf(stderr, "Invalid option %s\n", poptBadOption(pc, 0)); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + status = debug_level_parse(options.debuglevel, &log_level); + if (! status) { + fprintf(stderr, "Invalid debug level\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + setup_logging("dummy_client", DEBUG_STDERR); + debuglevel_set(log_level); + + if (options.sockpath == NULL) { + options.sockpath = path_socket(mem_ctx, "ctdbd"); + if (options.sockpath == NULL) { + D_ERR("Memory allocation error\n"); + exit(1); + } + } + + client = talloc_array(mem_ctx, struct ctdb_client_context *, + options.num_connections); + if (client == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + for (i=0; i. +*/ + +/* + * These errors are as listed in POSIX standard + * IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008) + * + * Error codes marked obsolete are removed (ENODATA, ENOSR, ENOSTR, ETIME) + */ + +#include "replace.h" + +struct { + const char *label; + int code; +} err_codes[] = { + { "E2BIG", E2BIG }, + + { "EACCES", EACCES }, + { "EADDRINUSE", EADDRINUSE }, + { "EADDRNOTAVAIL", EADDRNOTAVAIL }, + { "EAFNOSUPPORT", EAFNOSUPPORT }, + { "EAGAIN", EAGAIN }, + { "EALREADY", EALREADY }, + + { "EBADF", EBADF }, + { "EBADMSG", EBADMSG }, + { "EBUSY", EBUSY }, + + { "ECANCELED", ECANCELED }, + { "ECHILD", ECHILD }, + { "ECONNABORTED", ECONNABORTED }, + { "ECONNREFUSED", ECONNREFUSED }, + { "ECONNRESET", ECONNRESET }, + + { "EDEADLK", EDEADLK }, + { "EDESTADDRREQ", EDESTADDRREQ }, + { "EDOM", EDOM }, + { "EDQUOT", EDQUOT }, + + { "EEXIST", EEXIST }, + + { "EFAULT", EFAULT }, + { "EFBIG", EFBIG }, + + { "EHOSTUNREACH", EHOSTUNREACH }, + + { "EIDRM", EIDRM }, + { "EILSEQ", EILSEQ }, + { "EINPROGRESS", EINPROGRESS }, + { "EINTR", EINTR }, + { "EINVAL", EINVAL }, + { "EIO", EIO }, + { "EISCONN", EISCONN }, + { "EISDIR", EISDIR }, + + { "ELOOP", ELOOP }, + + { "EMFILE", EMFILE }, + { "EMLINK", EMLINK }, + { "EMSGSIZE", EMSGSIZE }, + { "EMULTIHOP", EMULTIHOP }, + + { "ENAMETOOLONG", ENAMETOOLONG }, + { "ENETDOWN", ENETDOWN }, + { "ENETRESET", ENETRESET }, + { "ENETUNREACH", ENETUNREACH }, + { "ENFILE", ENFILE }, + { "ENOBUFS", ENOBUFS }, + { "ENODEV", ENODEV }, + { "ENOENT", ENOENT }, + { "ENOEXEC", ENOEXEC }, + { "ENOLCK", ENOLCK }, + { "ENOLINK", ENOLINK }, + { "ENOMEM", ENOMEM }, + { "ENOMSG", ENOMSG }, + { "ENOPROTOOPT", ENOPROTOOPT }, + { "ENOSPC", ENOSPC }, + { "ENOSYS", ENOSYS }, + { "ENOTCONN", ENOTCONN }, + { "ENOTDIR", ENOTDIR }, + { "ENOTEMPTY", ENOTEMPTY }, + { "ENOTSOCK", ENOTSOCK }, + { "ENOTSUP", ENOTSUP }, + { "ENOTTY", ENOTTY }, + { "ENXIO", ENXIO }, + + { "EOPNOTSUPP", EOPNOTSUPP }, + { "EOVERFLOW", EOVERFLOW }, + + { "EPERM", EPERM }, + { "EPIPE", EPIPE }, + { "EPROTO", EPROTO }, + { "EPROTONOSUPPORT", EPROTONOSUPPORT }, + { "EPROTOTYPE", EPROTOTYPE }, + + { "ERANGE", ERANGE }, + { "EROFS", EROFS }, + + { "ESPIPE", ESPIPE }, + { "ESRCH", ESRCH }, + { "ESTALE", ESTALE }, + + { "ETIMEDOUT", ETIMEDOUT }, + { "ETXTBSY", ETXTBSY }, + + { "EWOULDBLOCK", EWOULDBLOCK }, + + { "EXDEV", EXDEV }, +}; + +static void dump(void) +{ + size_t i; + + for (i=0; i\n", argv[0]); + exit(1); + } + + if (strcmp(argv[1], "dump") == 0) { + dump(); + } else { + code = strtol(argv[1], &endptr, 0); + if (*endptr == '\0') { + match_code(code); + } else { + match_label(argv[1]); + } + } + + exit(0); +} diff --git a/ctdb/tests/src/event_script_test.c b/ctdb/tests/src/event_script_test.c new file mode 100644 index 0000000..f06725a --- /dev/null +++ b/ctdb/tests/src/event_script_test.c @@ -0,0 +1,120 @@ +/* + Low level event script handling tests + + Copyright (C) Martin Schwenke 2018 + + Based on run_event_test.c: + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include +#include + +#include + +#include "common/event_script.c" + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s list \n", + prog); + fprintf(stderr, + " %s chmod enable \n", + prog); + fprintf(stderr, + " %s chmod disable \n", + prog); +} + +static void do_list(TALLOC_CTX *mem_ctx, int argc, const char **argv) +{ + struct event_script_list *script_list = NULL; + unsigned int i; + int ret; + + if (argc != 3) { + usage(argv[0]); + exit(1); + } + + ret = event_script_get_list(mem_ctx, argv[2], &script_list); + if (ret != 0) { + printf("Script list %s failed with result=%d\n", argv[2], ret); + return; + } + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No scripts found\n"); + return; + } + + for (i=0; i < script_list->num_scripts; i++) { + struct event_script *s = script_list->script[i]; + printf("%s\n", s->name); + } +} + +static void do_chmod(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + bool enable) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = event_script_chmod(argv[2], argv[3], enable); + + printf("Script %s %s %s completed with result=%d\n", + argv[1], argv[2], argv[3], ret); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + + if (argc < 3) { + usage(argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "talloc_new() failed\n"); + exit(1); + } + + if (strcmp(argv[1], "list") == 0) { + do_list(mem_ctx, argc, argv); + } else if (strcmp(argv[1], "enable") == 0) { + do_chmod(mem_ctx, argc, argv, true); + } else if (strcmp(argv[1], "disable") == 0) { + do_chmod(mem_ctx, argc, argv, false); + } else { + fprintf(stderr, "Invalid command %s\n", argv[2]); + usage(argv[0]); + } + + talloc_free(mem_ctx); + exit(0); +} diff --git a/ctdb/tests/src/fake_ctdbd.c b/ctdb/tests/src/fake_ctdbd.c new file mode 100644 index 0000000..0d430a3 --- /dev/null +++ b/ctdb/tests/src/fake_ctdbd.c @@ -0,0 +1,4781 @@ +/* + Fake CTDB server for testing + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" +#include "system/time.h" +#include "system/filesys.h" + +#include +#include +#include +#include + +#include "lib/util/dlinklist.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/debug.h" +#include "lib/util/samba_util.h" +#include "lib/async_req/async_sock.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_api.h" +#include "protocol/protocol_util.h" +#include "protocol/protocol_private.h" + +#include "common/comm.h" +#include "common/logging.h" +#include "common/tunable.h" +#include "common/srvid.h" +#include "common/system.h" + +#include "ipalloc_read_known_ips.h" + + +#define CTDB_PORT 4379 + +/* A fake flag that is only supported by some functions */ +#define NODE_FLAGS_FAKE_TIMEOUT 0x80000000 + +struct node { + ctdb_sock_addr addr; + uint32_t pnn; + uint32_t flags; + uint32_t capabilities; + bool recovery_disabled; + void *recovery_substate; +}; + +struct node_map { + uint32_t num_nodes; + struct node *node; + uint32_t pnn; + uint32_t recmaster; +}; + +struct interface { + const char *name; + bool link_up; + uint32_t references; +}; + +struct interface_map { + int num; + struct interface *iface; +}; + +struct vnn_map { + uint32_t recmode; + uint32_t generation; + uint32_t size; + uint32_t *map; +}; + +struct database { + struct database *prev, *next; + const char *name; + const char *path; + struct tdb_context *tdb; + uint32_t id; + uint8_t flags; + uint64_t seq_num; +}; + +struct database_map { + struct database *db; + const char *dbdir; +}; + +struct fake_control_failure { + struct fake_control_failure *prev, *next; + enum ctdb_controls opcode; + uint32_t pnn; + const char *error; + const char *comment; +}; + +struct ctdb_client { + struct ctdb_client *prev, *next; + struct ctdbd_context *ctdb; + pid_t pid; + void *state; +}; + +struct ctdbd_context { + struct node_map *node_map; + struct interface_map *iface_map; + struct vnn_map *vnn_map; + struct database_map *db_map; + struct srvid_context *srv; + int num_clients; + struct timeval start_time; + struct timeval recovery_start_time; + struct timeval recovery_end_time; + bool takeover_disabled; + int log_level; + enum ctdb_runstate runstate; + struct ctdb_tunable_list tun_list; + char *reclock; + struct ctdb_public_ip_list *known_ips; + struct fake_control_failure *control_failures; + struct ctdb_client *client_list; +}; + +/* + * Parse routines + */ + +static struct node_map *nodemap_init(TALLOC_CTX *mem_ctx) +{ + struct node_map *node_map; + + node_map = talloc_zero(mem_ctx, struct node_map); + if (node_map == NULL) { + return NULL; + } + + node_map->pnn = CTDB_UNKNOWN_PNN; + node_map->recmaster = CTDB_UNKNOWN_PNN; + + return node_map; +} + +/* Read a nodemap from stdin. Each line looks like: + * [RECMASTER] [CURRENT] [CAPABILITIES] + * EOF or a blank line terminates input. + * + * By default, capabilities for each node are + * CTDB_CAP_RECMASTER|CTDB_CAP_LMASTER. These 2 + * capabilities can be faked off by adding, for example, + * -CTDB_CAP_RECMASTER. + */ + +static bool nodemap_parse(struct node_map *node_map) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + uint32_t pnn, flags, capabilities; + char *tok, *t; + char *ip; + ctdb_sock_addr saddr; + struct node *node; + int ret; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Get PNN */ + tok = strtok(line, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing PNN\n", line); + continue; + } + pnn = (uint32_t)strtoul(tok, NULL, 0); + + /* Get IP */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing IP\n", line); + continue; + } + ret = ctdb_sock_addr_from_string(tok, &saddr, false); + if (ret != 0) { + fprintf(stderr, "bad line (%s) - invalid IP\n", line); + continue; + } + ctdb_sock_addr_set_port(&saddr, CTDB_PORT); + ip = talloc_strdup(node_map, tok); + if (ip == NULL) { + goto fail; + } + + /* Get flags */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing flags\n", + line); + continue; + } + flags = (uint32_t)strtoul(tok, NULL, 0); + /* Handle deleted nodes */ + if (flags & NODE_FLAGS_DELETED) { + talloc_free(ip); + ip = talloc_strdup(node_map, "0.0.0.0"); + if (ip == NULL) { + goto fail; + } + } + capabilities = CTDB_CAP_RECMASTER|CTDB_CAP_LMASTER; + + tok = strtok(NULL, " \t"); + while (tok != NULL) { + if (strcmp(tok, "CURRENT") == 0) { + node_map->pnn = pnn; + } else if (strcmp(tok, "RECMASTER") == 0) { + node_map->recmaster = pnn; + } else if (strcmp(tok, "-CTDB_CAP_RECMASTER") == 0) { + capabilities &= ~CTDB_CAP_RECMASTER; + } else if (strcmp(tok, "-CTDB_CAP_LMASTER") == 0) { + capabilities &= ~CTDB_CAP_LMASTER; + } else if (strcmp(tok, "TIMEOUT") == 0) { + /* This can be done with just a flag + * value but it is probably clearer + * and less error-prone to fake this + * with an explicit token */ + flags |= NODE_FLAGS_FAKE_TIMEOUT; + } + tok = strtok(NULL, " \t"); + } + + node_map->node = talloc_realloc(node_map, node_map->node, + struct node, + node_map->num_nodes + 1); + if (node_map->node == NULL) { + goto fail; + } + node = &node_map->node[node_map->num_nodes]; + + ret = ctdb_sock_addr_from_string(ip, &node->addr, false); + if (ret != 0) { + fprintf(stderr, "bad line (%s) - invalid IP\n", line); + continue; + } + ctdb_sock_addr_set_port(&node->addr, CTDB_PORT); + node->pnn = pnn; + node->flags = flags; + node->capabilities = capabilities; + node->recovery_disabled = false; + node->recovery_substate = NULL; + + node_map->num_nodes += 1; + } + + if (node_map->num_nodes == 0) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing nodemap done\n")); + return true; + +fail: + DEBUG(DEBUG_INFO, ("Parsing nodemap failed\n")); + return false; + +} + +/* Append a node to a node map with given address and flags */ +static bool node_map_add(struct ctdb_node_map *nodemap, + const char *nstr, uint32_t flags) +{ + ctdb_sock_addr addr; + uint32_t num; + struct ctdb_node_and_flags *n; + int ret; + + ret = ctdb_sock_addr_from_string(nstr, &addr, false); + if (ret != 0) { + fprintf(stderr, "Invalid IP address %s\n", nstr); + return false; + } + ctdb_sock_addr_set_port(&addr, CTDB_PORT); + + num = nodemap->num; + nodemap->node = talloc_realloc(nodemap, nodemap->node, + struct ctdb_node_and_flags, num+1); + if (nodemap->node == NULL) { + return false; + } + + n = &nodemap->node[num]; + n->addr = addr; + n->pnn = num; + n->flags = flags; + + nodemap->num = num+1; + return true; +} + +/* Read a nodes file into a node map */ +static struct ctdb_node_map *ctdb_read_nodes_file(TALLOC_CTX *mem_ctx, + const char *nlist) +{ + char **lines; + int nlines; + int i; + struct ctdb_node_map *nodemap; + + nodemap = talloc_zero(mem_ctx, struct ctdb_node_map); + if (nodemap == NULL) { + return NULL; + } + + lines = file_lines_load(nlist, &nlines, 0, mem_ctx); + if (lines == NULL) { + return NULL; + } + + while (nlines > 0 && strcmp(lines[nlines-1], "") == 0) { + nlines--; + } + + for (i=0; i 1) && + ((node[len-1] == ' ') || (node[len-1] == '\t'))) + { + node[len-1] = '\0'; + len--; + } + + if (len == 0) { + continue; + } + if (*node == '#') { + /* A "deleted" node is a node that is + commented out in the nodes file. This is + used instead of removing a line, which + would cause subsequent nodes to change + their PNN. */ + flags = NODE_FLAGS_DELETED; + node = discard_const("0.0.0.0"); + } else { + flags = 0; + } + if (! node_map_add(nodemap, node, flags)) { + talloc_free(lines); + TALLOC_FREE(nodemap); + return NULL; + } + } + + talloc_free(lines); + return nodemap; +} + +static struct ctdb_node_map *read_nodes_file(TALLOC_CTX *mem_ctx, + uint32_t pnn) +{ + struct ctdb_node_map *nodemap; + char nodes_list[PATH_MAX]; + const char *ctdb_base; + int num; + + ctdb_base = getenv("CTDB_BASE"); + if (ctdb_base == NULL) { + D_ERR("CTDB_BASE is not set\n"); + return NULL; + } + + /* read optional node-specific nodes file */ + num = snprintf(nodes_list, sizeof(nodes_list), + "%s/nodes.%d", ctdb_base, pnn); + if (num == sizeof(nodes_list)) { + D_ERR("nodes file path too long\n"); + return NULL; + } + nodemap = ctdb_read_nodes_file(mem_ctx, nodes_list); + if (nodemap != NULL) { + /* Fake a load failure for an empty nodemap */ + if (nodemap->num == 0) { + talloc_free(nodemap); + + D_ERR("Failed to read nodes file \"%s\"\n", nodes_list); + return NULL; + } + + return nodemap; + } + + /* read normal nodes file */ + num = snprintf(nodes_list, sizeof(nodes_list), "%s/nodes", ctdb_base); + if (num == sizeof(nodes_list)) { + D_ERR("nodes file path too long\n"); + return NULL; + } + nodemap = ctdb_read_nodes_file(mem_ctx, nodes_list); + if (nodemap != NULL) { + return nodemap; + } + + DBG_ERR("Failed to read nodes file \"%s\"\n", nodes_list); + return NULL; +} + +static struct interface_map *interfaces_init(TALLOC_CTX *mem_ctx) +{ + struct interface_map *iface_map; + + iface_map = talloc_zero(mem_ctx, struct interface_map); + if (iface_map == NULL) { + return NULL; + } + + return iface_map; +} + +/* Read interfaces information. Same format as "ctdb ifaces -Y" + * output: + * :Name:LinkStatus:References: + * :eth2:1:4294967294 + * :eth1:1:4294967292 + */ + +static bool interfaces_parse(struct interface_map *iface_map) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + uint16_t link_state; + uint32_t references; + char *tok, *t, *name; + struct interface *iface; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + if (strcmp(line, ":Name:LinkStatus:References:") == 0) { + continue; + } + + /* Leading colon... */ + // tok = strtok(line, ":"); + + /* name */ + tok = strtok(line, ":"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing name\n", line); + continue; + } + name = tok; + + /* link_state */ + tok = strtok(NULL, ":"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing link state\n", + line); + continue; + } + link_state = (uint16_t)strtoul(tok, NULL, 0); + + /* references... */ + tok = strtok(NULL, ":"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing references\n", + line); + continue; + } + references = (uint32_t)strtoul(tok, NULL, 0); + + iface_map->iface = talloc_realloc(iface_map, iface_map->iface, + struct interface, + iface_map->num + 1); + if (iface_map->iface == NULL) { + goto fail; + } + + iface = &iface_map->iface[iface_map->num]; + + iface->name = talloc_strdup(iface_map, name); + if (iface->name == NULL) { + goto fail; + } + iface->link_up = link_state; + iface->references = references; + + iface_map->num += 1; + } + + if (iface_map->num == 0) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing interfaces done\n")); + return true; + +fail: + fprintf(stderr, "Parsing interfaces failed\n"); + return false; +} + +static struct vnn_map *vnnmap_init(TALLOC_CTX *mem_ctx) +{ + struct vnn_map *vnn_map; + + vnn_map = talloc_zero(mem_ctx, struct vnn_map); + if (vnn_map == NULL) { + fprintf(stderr, "Memory error\n"); + return NULL; + } + vnn_map->recmode = CTDB_RECOVERY_ACTIVE; + vnn_map->generation = INVALID_GENERATION; + + return vnn_map; +} + +/* Read vnn map. + * output: + * + * + * + * ... + */ + +static bool vnnmap_parse(struct vnn_map *vnn_map) +{ + char line[1024]; + + while (fgets(line, sizeof(line), stdin) != NULL) { + uint32_t n; + char *t; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + n = (uint32_t) strtol(line, NULL, 0); + + /* generation */ + if (vnn_map->generation == INVALID_GENERATION) { + vnn_map->generation = n; + continue; + } + + vnn_map->map = talloc_realloc(vnn_map, vnn_map->map, uint32_t, + vnn_map->size + 1); + if (vnn_map->map == NULL) { + fprintf(stderr, "Memory error\n"); + goto fail; + } + + vnn_map->map[vnn_map->size] = n; + vnn_map->size += 1; + } + + if (vnn_map->size == 0) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing vnnmap done\n")); + return true; + +fail: + fprintf(stderr, "Parsing vnnmap failed\n"); + return false; +} + +static bool reclock_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + char *t; + + if (fgets(line, sizeof(line), stdin) == NULL) { + goto fail; + } + + if (line[0] == '\n') { + goto fail; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + ctdb->reclock = talloc_strdup(ctdb, line); + if (ctdb->reclock == NULL) { + goto fail; + } + + /* Swallow possible blank line following section. Picky + * compiler settings don't allow the return value to be + * ignored, so make the compiler happy. + */ + if (fgets(line, sizeof(line), stdin) == NULL) { + ; + } + DEBUG(DEBUG_INFO, ("Parsing reclock done\n")); + return true; + +fail: + fprintf(stderr, "Parsing reclock failed\n"); + return false; +} + +static struct database_map *dbmap_init(TALLOC_CTX *mem_ctx, + const char *dbdir) +{ + struct database_map *db_map; + + db_map = talloc_zero(mem_ctx, struct database_map); + if (db_map == NULL) { + return NULL; + } + + db_map->dbdir = talloc_strdup(db_map, dbdir); + if (db_map->dbdir == NULL) { + talloc_free(db_map); + return NULL; + } + + return db_map; +} + +/* Read a database map from stdin. Each line looks like: + * [FLAGS] [SEQ_NUM] + * EOF or a blank line terminates input. + * + * By default, flags and seq_num are 0 + */ + +static bool dbmap_parse(struct database_map *db_map) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + uint32_t id; + uint8_t flags = 0; + uint32_t seq_num = 0; + char *tok, *t; + char *name; + struct database *db; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Get ID */ + tok = strtok(line, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing ID\n", line); + continue; + } + id = (uint32_t)strtoul(tok, NULL, 0); + + /* Get NAME */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing NAME\n", line); + continue; + } + name = talloc_strdup(db_map, tok); + if (name == NULL) { + goto fail; + } + + /* Get flags */ + tok = strtok(NULL, " \t"); + while (tok != NULL) { + if (strcmp(tok, "PERSISTENT") == 0) { + flags |= CTDB_DB_FLAGS_PERSISTENT; + } else if (strcmp(tok, "STICKY") == 0) { + flags |= CTDB_DB_FLAGS_STICKY; + } else if (strcmp(tok, "READONLY") == 0) { + flags |= CTDB_DB_FLAGS_READONLY; + } else if (strcmp(tok, "REPLICATED") == 0) { + flags |= CTDB_DB_FLAGS_REPLICATED; + } else if (tok[0] >= '0'&& tok[0] <= '9') { + uint8_t nv = CTDB_DB_FLAGS_PERSISTENT | + CTDB_DB_FLAGS_REPLICATED; + + if ((flags & nv) == 0) { + fprintf(stderr, + "seq_num for volatile db\n"); + goto fail; + } + seq_num = (uint64_t)strtoull(tok, NULL, 0); + } + + tok = strtok(NULL, " \t"); + } + + db = talloc_zero(db_map, struct database); + if (db == NULL) { + goto fail; + } + + db->id = id; + db->name = talloc_steal(db, name); + db->path = talloc_asprintf(db, "%s/%s", db_map->dbdir, name); + if (db->path == NULL) { + talloc_free(db); + goto fail; + } + db->flags = flags; + db->seq_num = seq_num; + + DLIST_ADD_END(db_map->db, db); + } + + if (db_map->db == NULL) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing dbmap done\n")); + return true; + +fail: + DEBUG(DEBUG_INFO, ("Parsing dbmap failed\n")); + return false; + +} + +static struct database *database_find(struct database_map *db_map, + uint32_t db_id) +{ + struct database *db; + + for (db = db_map->db; db != NULL; db = db->next) { + if (db->id == db_id) { + return db; + } + } + + return NULL; +} + +static int database_count(struct database_map *db_map) +{ + struct database *db; + int count = 0; + + for (db = db_map->db; db != NULL; db = db->next) { + count += 1; + } + + return count; +} + +static int database_flags(uint8_t db_flags) +{ + int tdb_flags = 0; + + if (db_flags & CTDB_DB_FLAGS_PERSISTENT) { + tdb_flags = TDB_DEFAULT; + } else { + /* volatile and replicated use the same flags */ + tdb_flags = TDB_NOSYNC | + TDB_CLEAR_IF_FIRST | + TDB_INCOMPATIBLE_HASH; + } + + tdb_flags |= TDB_DISALLOW_NESTING; + + return tdb_flags; +} + +static struct database *database_new(struct database_map *db_map, + const char *name, uint8_t flags) +{ + struct database *db; + TDB_DATA key; + int tdb_flags; + + db = talloc_zero(db_map, struct database); + if (db == NULL) { + return NULL; + } + + db->name = talloc_strdup(db, name); + if (db->name == NULL) { + goto fail; + } + + db->path = talloc_asprintf(db, "%s/%s", db_map->dbdir, name); + if (db->path == NULL) { + goto fail; + } + + key.dsize = strlen(db->name) + 1; + key.dptr = discard_const(db->name); + + db->id = tdb_jenkins_hash(&key); + db->flags = flags; + + tdb_flags = database_flags(flags); + + db->tdb = tdb_open(db->path, 8192, tdb_flags, O_CREAT|O_RDWR, 0644); + if (db->tdb == NULL) { + DBG_ERR("tdb_open\n"); + goto fail; + } + + DLIST_ADD_END(db_map->db, db); + return db; + +fail: + DBG_ERR("Memory error\n"); + talloc_free(db); + return NULL; + +} + +static int ltdb_store(struct database *db, TDB_DATA key, + struct ctdb_ltdb_header *header, TDB_DATA data) +{ + int ret; + bool db_volatile = true; + bool keep = false; + + if (db->tdb == NULL) { + return EINVAL; + } + + if ((db->flags & CTDB_DB_FLAGS_PERSISTENT) || + (db->flags & CTDB_DB_FLAGS_REPLICATED)) { + db_volatile = false; + } + + if (data.dsize > 0) { + keep = true; + } else { + if (db_volatile && header->rsn == 0) { + keep = true; + } + } + + if (keep) { + TDB_DATA rec[2]; + + rec[0].dsize = ctdb_ltdb_header_len(header); + rec[0].dptr = (uint8_t *)header; + + rec[1].dsize = data.dsize; + rec[1].dptr = data.dptr; + + ret = tdb_storev(db->tdb, key, rec, 2, TDB_REPLACE); + } else { + if (header->rsn > 0) { + ret = tdb_delete(db->tdb, key); + } else { + ret = 0; + } + } + + return ret; +} + +static int ltdb_fetch(struct database *db, TDB_DATA key, + struct ctdb_ltdb_header *header, + TALLOC_CTX *mem_ctx, TDB_DATA *data) +{ + TDB_DATA rec; + size_t np; + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + rec = tdb_fetch(db->tdb, key); + ret = ctdb_ltdb_header_pull(rec.dptr, rec.dsize, header, &np); + if (ret != 0) { + if (rec.dptr != NULL) { + free(rec.dptr); + } + + *header = (struct ctdb_ltdb_header) { + .rsn = 0, + .dmaster = 0, + .flags = 0, + }; + + ret = ltdb_store(db, key, header, tdb_null); + if (ret != 0) { + return ret; + } + + *data = tdb_null; + return 0; + } + + data->dsize = rec.dsize - ctdb_ltdb_header_len(header); + data->dptr = talloc_memdup(mem_ctx, + rec.dptr + ctdb_ltdb_header_len(header), + data->dsize); + + free(rec.dptr); + + if (data->dptr == NULL) { + return ENOMEM; + } + + return 0; +} + +static int database_seqnum(struct database *db, uint64_t *seqnum) +{ + const char *keyname = CTDB_DB_SEQNUM_KEY; + TDB_DATA key, data; + struct ctdb_ltdb_header header; + size_t np; + int ret; + + if (db->tdb == NULL) { + *seqnum = db->seq_num; + return 0; + } + + key.dptr = discard_const(keyname); + key.dsize = strlen(keyname) + 1; + + ret = ltdb_fetch(db, key, &header, db, &data); + if (ret != 0) { + return ret; + } + + if (data.dsize == 0) { + *seqnum = 0; + return 0; + } + + ret = ctdb_uint64_pull(data.dptr, data.dsize, seqnum, &np); + talloc_free(data.dptr); + if (ret != 0) { + *seqnum = 0; + } + + return ret; +} + +static int ltdb_transaction_update(uint32_t reqid, + struct ctdb_ltdb_header *no_header, + TDB_DATA key, TDB_DATA data, + void *private_data) +{ + struct database *db = (struct database *)private_data; + TALLOC_CTX *tmp_ctx = talloc_new(db); + struct ctdb_ltdb_header header = { 0 }, oldheader; + TDB_DATA olddata; + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + ret = ctdb_ltdb_header_extract(&data, &header); + if (ret != 0) { + return ret; + } + + ret = ltdb_fetch(db, key, &oldheader, tmp_ctx, &olddata); + if (ret != 0) { + return ret; + } + + if (olddata.dsize > 0) { + if (oldheader.rsn > header.rsn || + (oldheader.rsn == header.rsn && + olddata.dsize != data.dsize)) { + return -1; + } + } + + talloc_free(tmp_ctx); + + ret = ltdb_store(db, key, &header, data); + return ret; +} + +static int ltdb_transaction(struct database *db, + struct ctdb_rec_buffer *recbuf) +{ + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + ret = tdb_transaction_start(db->tdb); + if (ret == -1) { + return ret; + } + + ret = ctdb_rec_buffer_traverse(recbuf, ltdb_transaction_update, db); + if (ret != 0) { + tdb_transaction_cancel(db->tdb); + } + + ret = tdb_transaction_commit(db->tdb); + return ret; +} + +static bool public_ips_parse(struct ctdbd_context *ctdb, + uint32_t numnodes) +{ + bool status; + + if (numnodes == 0) { + D_ERR("Must initialise nodemap before public IPs\n"); + return false; + } + + ctdb->known_ips = ipalloc_read_known_ips(ctdb, numnodes, false); + + status = (ctdb->known_ips != NULL && ctdb->known_ips->num != 0); + + if (status) { + D_INFO("Parsing public IPs done\n"); + } else { + D_INFO("Parsing public IPs failed\n"); + } + + return status; +} + +/* Read information about controls to fail. Format is: + * {ERROR|TIMEOUT} + */ +static bool control_failures_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + char *tok, *t; + enum ctdb_controls opcode; + uint32_t pnn; + const char *error; + const char *comment; + struct fake_control_failure *failure = NULL; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Get opcode */ + tok = strtok(line, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing opcode\n", line); + continue; + } + opcode = (enum ctdb_controls)strtoul(tok, NULL, 0); + + /* Get PNN */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing PNN\n", line); + continue; + } + pnn = (uint32_t)strtoul(tok, NULL, 0); + + /* Get error */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing errno\n", line); + continue; + } + error = talloc_strdup(ctdb, tok); + if (error == NULL) { + goto fail; + } + if (strcmp(error, "ERROR") != 0 && + strcmp(error, "TIMEOUT") != 0) { + D_ERR("bad line (%s) " + "- error must be \"ERROR\" or \"TIMEOUT\"\n", + line); + goto fail; + } + + /* Get comment */ + tok = strtok(NULL, "\n"); /* rest of line */ + if (tok == NULL) { + D_ERR("bad line (%s) - missing comment\n", line); + continue; + } + comment = talloc_strdup(ctdb, tok); + if (comment == NULL) { + goto fail; + } + + failure = talloc_zero(ctdb, struct fake_control_failure); + if (failure == NULL) { + goto fail; + } + + failure->opcode = opcode; + failure->pnn = pnn; + failure->error = error; + failure->comment = comment; + + DLIST_ADD(ctdb->control_failures, failure); + } + + if (ctdb->control_failures == NULL) { + goto fail; + } + + D_INFO("Parsing fake control failures done\n"); + return true; + +fail: + D_INFO("Parsing fake control failures failed\n"); + return false; +} + +static bool runstate_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + char *t; + + if (fgets(line, sizeof(line), stdin) == NULL) { + goto fail; + } + + if (line[0] == '\n') { + goto fail; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + ctdb->runstate = ctdb_runstate_from_string(line); + if (ctdb->runstate == CTDB_RUNSTATE_UNKNOWN) { + goto fail; + } + + /* Swallow possible blank line following section. Picky + * compiler settings don't allow the return value to be + * ignored, so make the compiler happy. + */ + if (fgets(line, sizeof(line), stdin) == NULL) { + ; + } + D_INFO("Parsing runstate done\n"); + return true; + +fail: + D_ERR("Parsing runstate failed\n"); + return false; +} + +/* + * Manage clients + */ + +static int ctdb_client_destructor(struct ctdb_client *client) +{ + DLIST_REMOVE(client->ctdb->client_list, client); + return 0; +} + +static int client_add(struct ctdbd_context *ctdb, pid_t client_pid, + void *client_state) +{ + struct ctdb_client *client; + + client = talloc_zero(client_state, struct ctdb_client); + if (client == NULL) { + return ENOMEM; + } + + client->ctdb = ctdb; + client->pid = client_pid; + client->state = client_state; + + DLIST_ADD(ctdb->client_list, client); + talloc_set_destructor(client, ctdb_client_destructor); + return 0; +} + +static void *client_find(struct ctdbd_context *ctdb, pid_t client_pid) +{ + struct ctdb_client *client; + + for (client=ctdb->client_list; client != NULL; client=client->next) { + if (client->pid == client_pid) { + return client->state; + } + } + + return NULL; +} + +/* + * CTDB context setup + */ + +static uint32_t new_generation(uint32_t old_generation) +{ + uint32_t generation; + + while (1) { + generation = random(); + if (generation != INVALID_GENERATION && + generation != old_generation) { + break; + } + } + + return generation; +} + +static struct ctdbd_context *ctdbd_setup(TALLOC_CTX *mem_ctx, + const char *dbdir) +{ + struct ctdbd_context *ctdb; + char line[1024]; + bool status; + int ret; + + ctdb = talloc_zero(mem_ctx, struct ctdbd_context); + if (ctdb == NULL) { + return NULL; + } + + ctdb->node_map = nodemap_init(ctdb); + if (ctdb->node_map == NULL) { + goto fail; + } + + ctdb->iface_map = interfaces_init(ctdb); + if (ctdb->iface_map == NULL) { + goto fail; + } + + ctdb->vnn_map = vnnmap_init(ctdb); + if (ctdb->vnn_map == NULL) { + goto fail; + } + + ctdb->db_map = dbmap_init(ctdb, dbdir); + if (ctdb->db_map == NULL) { + goto fail; + } + + ret = srvid_init(ctdb, &ctdb->srv); + if (ret != 0) { + goto fail; + } + + ctdb->runstate = CTDB_RUNSTATE_RUNNING; + + while (fgets(line, sizeof(line), stdin) != NULL) { + char *t; + + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + if (strcmp(line, "NODEMAP") == 0) { + status = nodemap_parse(ctdb->node_map); + } else if (strcmp(line, "IFACES") == 0) { + status = interfaces_parse(ctdb->iface_map); + } else if (strcmp(line, "VNNMAP") == 0) { + status = vnnmap_parse(ctdb->vnn_map); + } else if (strcmp(line, "DBMAP") == 0) { + status = dbmap_parse(ctdb->db_map); + } else if (strcmp(line, "PUBLICIPS") == 0) { + status = public_ips_parse(ctdb, + ctdb->node_map->num_nodes); + } else if (strcmp(line, "RECLOCK") == 0) { + status = reclock_parse(ctdb); + } else if (strcmp(line, "CONTROLFAILS") == 0) { + status = control_failures_parse(ctdb); + } else if (strcmp(line, "RUNSTATE") == 0) { + status = runstate_parse(ctdb); + } else { + fprintf(stderr, "Unknown line %s\n", line); + status = false; + } + + if (! status) { + goto fail; + } + } + + ctdb->start_time = tevent_timeval_current(); + ctdb->recovery_start_time = tevent_timeval_current(); + ctdb->vnn_map->recmode = CTDB_RECOVERY_NORMAL; + if (ctdb->vnn_map->generation == INVALID_GENERATION) { + ctdb->vnn_map->generation = + new_generation(ctdb->vnn_map->generation); + } + ctdb->recovery_end_time = tevent_timeval_current(); + + ctdb->log_level = DEBUG_ERR; + + ctdb_tunable_set_defaults(&ctdb->tun_list); + + return ctdb; + +fail: + TALLOC_FREE(ctdb); + return NULL; +} + +static bool ctdbd_verify(struct ctdbd_context *ctdb) +{ + struct node *node; + unsigned int i; + + if (ctdb->node_map->num_nodes == 0) { + return true; + } + + /* Make sure all the nodes are in order */ + for (i=0; inode_map->num_nodes; i++) { + node = &ctdb->node_map->node[i]; + if (node->pnn != i) { + fprintf(stderr, "Expected node %u, found %u\n", + i, node->pnn); + return false; + } + } + + node = &ctdb->node_map->node[ctdb->node_map->pnn]; + if (node->flags & NODE_FLAGS_DISCONNECTED) { + DEBUG(DEBUG_INFO, ("Node disconnected, exiting\n")); + exit(0); + } + + return true; +} + +/* + * Doing a recovery + */ + +struct recover_state { + struct tevent_context *ev; + struct ctdbd_context *ctdb; +}; + +static int recover_check(struct tevent_req *req); +static void recover_wait_done(struct tevent_req *subreq); +static void recover_done(struct tevent_req *subreq); + +static struct tevent_req *recover_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdbd_context *ctdb) +{ + struct tevent_req *req; + struct recover_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct recover_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ctdb = ctdb; + + ret = recover_check(req); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + return req; +} + +static int recover_check(struct tevent_req *req) +{ + struct recover_state *state = tevent_req_data( + req, struct recover_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + bool recovery_disabled; + unsigned int i; + + recovery_disabled = false; + for (i=0; inode_map->num_nodes; i++) { + if (ctdb->node_map->node[i].recovery_disabled) { + recovery_disabled = true; + break; + } + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (subreq == NULL) { + return ENOMEM; + } + + if (recovery_disabled) { + tevent_req_set_callback(subreq, recover_wait_done, req); + } else { + ctdb->recovery_start_time = tevent_timeval_current(); + tevent_req_set_callback(subreq, recover_done, req); + } + + return 0; +} + +static void recover_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + ret = recover_check(req); + if (ret != 0) { + tevent_req_error(req, ret); + } +} + +static void recover_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct recover_state *state = tevent_req_data( + req, struct recover_state); + struct ctdbd_context *ctdb = state->ctdb; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + ctdb->vnn_map->recmode = CTDB_RECOVERY_NORMAL; + ctdb->recovery_end_time = tevent_timeval_current(); + ctdb->vnn_map->generation = new_generation(ctdb->vnn_map->generation); + + tevent_req_done(req); +} + +static bool recover_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + + return true; +} + +/* + * Routines for ctdb_req_header + */ + +static void header_fix_pnn(struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + if (header->srcnode == CTDB_CURRENT_NODE) { + header->srcnode = ctdb->node_map->pnn; + } + + if (header->destnode == CTDB_CURRENT_NODE) { + header->destnode = ctdb->node_map->pnn; + } +} + +static struct ctdb_req_header header_reply_call( + struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + struct ctdb_req_header reply_header; + + reply_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REPLY_CALL, + .destnode = header->srcnode, + .srcnode = header->destnode, + .reqid = header->reqid, + }; + + return reply_header; +} + +static struct ctdb_req_header header_reply_control( + struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + struct ctdb_req_header reply_header; + + reply_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REPLY_CONTROL, + .destnode = header->srcnode, + .srcnode = header->destnode, + .reqid = header->reqid, + }; + + return reply_header; +} + +static struct ctdb_req_header header_reply_message( + struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + struct ctdb_req_header reply_header; + + reply_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REQ_MESSAGE, + .destnode = header->srcnode, + .srcnode = header->destnode, + .reqid = 0, + }; + + return reply_header; +} + +/* + * Client state + */ + +struct client_state { + struct tevent_context *ev; + int fd; + struct ctdbd_context *ctdb; + int pnn; + pid_t pid; + struct comm_context *comm; + struct srvid_register_state *rstate; + int status; +}; + +/* + * Send replies to call, controls and messages + */ + +static void client_reply_done(struct tevent_req *subreq); + +static void client_send_call(struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_reply_call *reply) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header reply_header; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + reply_header = header_reply_call(header, ctdb); + + datalen = ctdb_reply_call_len(&reply_header, reply); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_call_push(&reply_header, reply, buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, client_reply_done, req); + + talloc_steal(subreq, buf); +} + +static void client_send_message(struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_message_data *message) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header reply_header; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + reply_header = header_reply_message(header, ctdb); + + datalen = ctdb_req_message_data_len(&reply_header, message); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_req_message_data_push(&reply_header, message, + buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + DEBUG(DEBUG_INFO, ("message srvid = 0x%"PRIx64"\n", message->srvid)); + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, client_reply_done, req); + + talloc_steal(subreq, buf); +} + +static void client_send_control(struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_reply_control *reply) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header reply_header; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + reply_header = header_reply_control(header, ctdb); + + datalen = ctdb_reply_control_len(&reply_header, reply); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_control_push(&reply_header, reply, buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + DEBUG(DEBUG_INFO, ("reply opcode = %u\n", reply->rdata.opcode)); + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, client_reply_done, req); + + talloc_steal(subreq, buf); +} + +static void client_reply_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool status; + + status = comm_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + } +} + +/* + * Handling protocol - controls + */ + +static void control_process_exists(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct client_state *cstate; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + cstate = client_find(ctdb, request->rdata.data.pid); + if (cstate == NULL) { + reply.status = -1; + reply.errmsg = "No client for PID"; + } else { + reply.status = kill(request->rdata.data.pid, 0); + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static void control_ping(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = ctdb->num_clients; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_getdbpath(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.db_path = + talloc_strdup(mem_ctx, db->path); + if (reply.rdata.data.db_path == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + } else { + reply.status = 0; + reply.errmsg = NULL; + } + } + + client_send_control(req, header, &reply); +} + +static void control_getvnnmap(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_vnn_map *vnnmap; + + reply.rdata.opcode = request->opcode; + + vnnmap = talloc_zero(mem_ctx, struct ctdb_vnn_map); + if (vnnmap == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + } else { + vnnmap->generation = ctdb->vnn_map->generation; + vnnmap->size = ctdb->vnn_map->size; + vnnmap->map = ctdb->vnn_map->map; + + reply.rdata.data.vnnmap = vnnmap; + reply.status = 0; + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static void control_get_debug(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.rdata.data.loglevel = (uint32_t)ctdb->log_level; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_set_debug(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + ctdb->log_level = (int)request->rdata.data.loglevel; + + reply.rdata.opcode = request->opcode; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_dbmap(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_dbid_map *dbmap; + struct database *db; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + dbmap = talloc_zero(mem_ctx, struct ctdb_dbid_map); + if (dbmap == NULL) { + goto fail; + } + + dbmap->num = database_count(ctdb->db_map); + dbmap->dbs = talloc_zero_array(dbmap, struct ctdb_dbid, dbmap->num); + if (dbmap->dbs == NULL) { + goto fail; + } + + db = ctdb->db_map->db; + for (i = 0; i < dbmap->num; i++) { + dbmap->dbs[i] = (struct ctdb_dbid) { + .db_id = db->id, + .flags = db->flags, + }; + + db = db->next; + } + + reply.rdata.data.dbmap = dbmap; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_get_recmode(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = ctdb->vnn_map->recmode; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +struct set_recmode_state { + struct tevent_req *req; + struct ctdbd_context *ctdb; + struct ctdb_req_header header; + struct ctdb_reply_control reply; +}; + +static void set_recmode_callback(struct tevent_req *subreq) +{ + struct set_recmode_state *substate = tevent_req_callback_data( + subreq, struct set_recmode_state); + bool status; + int ret; + + status = recover_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + substate->reply.status = ret; + substate->reply.errmsg = "recovery failed"; + } else { + substate->reply.status = 0; + substate->reply.errmsg = NULL; + } + + client_send_control(substate->req, &substate->header, &substate->reply); + talloc_free(substate); +} + +static void control_set_recmode(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct tevent_req *subreq; + struct ctdbd_context *ctdb = state->ctdb; + struct set_recmode_state *substate; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + if (request->rdata.data.recmode == CTDB_RECOVERY_NORMAL) { + reply.status = -1; + reply.errmsg = "Client cannot set recmode to NORMAL"; + goto fail; + } + + substate = talloc_zero(ctdb, struct set_recmode_state); + if (substate == NULL) { + reply.status = -1; + reply.errmsg = "Memory error"; + goto fail; + } + + substate->req = req; + substate->ctdb = ctdb; + substate->header = *header; + substate->reply.rdata.opcode = request->opcode; + + subreq = recover_send(substate, state->ev, state->ctdb); + if (subreq == NULL) { + talloc_free(substate); + goto fail; + } + tevent_req_set_callback(subreq, set_recmode_callback, substate); + + ctdb->vnn_map->recmode = CTDB_RECOVERY_ACTIVE; + return; + +fail: + client_send_control(req, header, &reply); + +} + +static void control_db_attach(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, 0); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void srvid_handler_done(struct tevent_req *subreq); + +static void srvid_handler(uint64_t srvid, TDB_DATA data, void *private_data) +{ + struct client_state *state = talloc_get_type_abort( + private_data, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header request_header; + struct ctdb_req_message_data message; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + request_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REQ_MESSAGE, + .destnode = state->pnn, + .srcnode = ctdb->node_map->recmaster, + .reqid = 0, + }; + + message = (struct ctdb_req_message_data) { + .srvid = srvid, + .data = data, + }; + + datalen = ctdb_req_message_data_len(&request_header, &message); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + return; + } + + ret = ctdb_req_message_data_push(&request_header, + &message, + buf, + &buflen); + if (ret != 0) { + talloc_free(buf); + return; + } + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (subreq == NULL) { + talloc_free(buf); + return; + } + tevent_req_set_callback(subreq, srvid_handler_done, state); + + talloc_steal(subreq, buf); +} + +static void srvid_handler_done(struct tevent_req *subreq) +{ + struct client_state *state = tevent_req_callback_data( + subreq, struct client_state); + int ret; + bool ok; + + ok = comm_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (!ok) { + DEBUG(DEBUG_ERR, + ("Failed to dispatch message to client pid=%u, ret=%d\n", + state->pid, + ret)); + } +} + +static void control_register_srvid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + int ret; + + reply.rdata.opcode = request->opcode; + + ret = srvid_register(ctdb->srv, state, request->srvid, + srvid_handler, state); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "Memory error"; + goto fail; + } + + DEBUG(DEBUG_INFO, ("Register srvid 0x%"PRIx64"\n", request->srvid)); + + reply.status = 0; + reply.errmsg = NULL; + +fail: + client_send_control(req, header, &reply); +} + +static void control_deregister_srvid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + int ret; + + reply.rdata.opcode = request->opcode; + + ret = srvid_deregister(ctdb->srv, request->srvid, state); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "srvid not registered"; + goto fail; + } + + DEBUG(DEBUG_INFO, ("Deregister srvid 0x%"PRIx64"\n", request->srvid)); + + reply.status = 0; + reply.errmsg = NULL; + +fail: + client_send_control(req, header, &reply); +} + +static void control_get_dbname(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.db_name = talloc_strdup(mem_ctx, db->name); + if (reply.rdata.data.db_name == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + } else { + reply.status = 0; + reply.errmsg = NULL; + } + } + + client_send_control(req, header, &reply); +} + +static void control_get_pid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = getpid(); + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_pnn(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = header->destnode; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_shutdown(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *hdr, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + + state->status = 99; +} + +static void control_set_tunable(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + bool ret, obsolete; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + ret = ctdb_tunable_set_value(&ctdb->tun_list, + request->rdata.data.tunable->name, + request->rdata.data.tunable->value, + &obsolete); + if (! ret) { + reply.status = -1; + } else if (obsolete) { + reply.status = 1; + } else { + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_get_tunable(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + uint32_t value; + bool ret; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + ret = ctdb_tunable_get_value(&ctdb->tun_list, + request->rdata.data.tun_var, &value); + if (! ret) { + reply.status = -1; + } else { + reply.rdata.data.tun_value = value; + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_list_tunables(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + struct ctdb_var_list *var_list; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + var_list = ctdb_tunable_names(mem_ctx); + if (var_list == NULL) { + reply.status = -1; + } else { + reply.rdata.data.tun_var_list = var_list; + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_modify_flags(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_node_flag_change *change = request->rdata.data.flag_change; + struct ctdb_reply_control reply; + struct node *node; + + reply.rdata.opcode = request->opcode; + + if ((change->old_flags & ~NODE_FLAGS_PERMANENTLY_DISABLED) || + (change->new_flags & ~NODE_FLAGS_PERMANENTLY_DISABLED) != 0) { + DEBUG(DEBUG_INFO, + ("MODIFY_FLAGS control not for PERMANENTLY_DISABLED\n")); + reply.status = EINVAL; + reply.errmsg = "Failed to MODIFY_FLAGS"; + client_send_control(req, header, &reply); + return; + } + + /* There's all sorts of broadcast weirdness here. Only change + * the specified node, not the destination node of the + * control. */ + node = &ctdb->node_map->node[change->pnn]; + + if ((node->flags & + change->old_flags & NODE_FLAGS_PERMANENTLY_DISABLED) == 0 && + (change->new_flags & NODE_FLAGS_PERMANENTLY_DISABLED) != 0) { + DEBUG(DEBUG_INFO,("Disabling node %d\n", header->destnode)); + node->flags |= NODE_FLAGS_PERMANENTLY_DISABLED; + goto done; + } + + if ((node->flags & + change->old_flags & NODE_FLAGS_PERMANENTLY_DISABLED) != 0 && + (change->new_flags & NODE_FLAGS_PERMANENTLY_DISABLED) == 0) { + DEBUG(DEBUG_INFO,("Enabling node %d\n", header->destnode)); + node->flags &= ~NODE_FLAGS_PERMANENTLY_DISABLED; + goto done; + } + + DEBUG(DEBUG_INFO, ("Flags unchanged for node %d\n", header->destnode)); + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_all_tunables(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.rdata.data.tun_list = &ctdb->tun_list; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_db_attach_persistent(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, + CTDB_DB_FLAGS_PERSISTENT); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_uptime(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_uptime *uptime;; + + reply.rdata.opcode = request->opcode; + + uptime = talloc_zero(mem_ctx, struct ctdb_uptime); + if (uptime == NULL) { + goto fail; + } + + uptime->current_time = tevent_timeval_current(); + uptime->ctdbd_start_time = ctdb->start_time; + uptime->last_recovery_started = ctdb->recovery_start_time; + uptime->last_recovery_finished = ctdb->recovery_end_time; + + reply.rdata.data.uptime = uptime; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_reload_nodes_file(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_node_map *nodemap; + struct node_map *node_map = ctdb->node_map; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + nodemap = read_nodes_file(mem_ctx, header->destnode); + if (nodemap == NULL) { + goto fail; + } + + for (i=0; inum; i++) { + struct node *node; + + if (i < node_map->num_nodes && + ctdb_sock_addr_same(&nodemap->node[i].addr, + &node_map->node[i].addr)) { + continue; + } + + if (nodemap->node[i].flags & NODE_FLAGS_DELETED) { + int ret; + + node = &node_map->node[i]; + + node->flags |= NODE_FLAGS_DELETED; + ret = ctdb_sock_addr_from_string("0.0.0.0", &node->addr, + false); + if (ret != 0) { + /* Can't happen, but Coverity... */ + goto fail; + } + + continue; + } + + if (i < node_map->num_nodes && + node_map->node[i].flags & NODE_FLAGS_DELETED) { + node = &node_map->node[i]; + + node->flags &= ~NODE_FLAGS_DELETED; + node->addr = nodemap->node[i].addr; + + continue; + } + + node_map->node = talloc_realloc(node_map, node_map->node, + struct node, + node_map->num_nodes+1); + if (node_map->node == NULL) { + goto fail; + } + node = &node_map->node[node_map->num_nodes]; + + node->addr = nodemap->node[i].addr; + node->pnn = nodemap->node[i].pnn; + node->flags = 0; + node->capabilities = CTDB_CAP_DEFAULT; + node->recovery_disabled = false; + node->recovery_substate = NULL; + + node_map->num_nodes += 1; + } + + talloc_free(nodemap); + + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_get_capabilities(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct node *node; + uint32_t caps = 0; + + reply.rdata.opcode = request->opcode; + + node = &ctdb->node_map->node[header->destnode]; + caps = node->capabilities; + + if (node->flags & NODE_FLAGS_FAKE_TIMEOUT) { + /* Don't send reply */ + return; + } + + reply.rdata.data.caps = caps; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_release_ip(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_public_ip *ip = request->rdata.data.pubip; + struct ctdb_reply_control reply; + struct ctdb_public_ip_list *ips = NULL; + struct ctdb_public_ip *t = NULL; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + D_INFO("RELEASE_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + ips = &ctdb->known_ips[header->destnode]; + + t = NULL; + for (i = 0; i < ips->num; i++) { + if (ctdb_sock_addr_same_ip(&ips->ip[i].addr, &ip->addr)) { + t = &ips->ip[i]; + break; + } + } + if (t == NULL) { + D_INFO("RELEASE_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + if (t->pnn != header->destnode) { + if (header->destnode == ip->pnn) { + D_ERR("error: RELEASE_IP %s - to TAKE_IP node %d\n", + ctdb_sock_addr_to_string(mem_ctx, + &ip->addr, false), + ip->pnn); + reply.status = -1; + reply.errmsg = "RELEASE_IP to TAKE_IP node"; + client_send_control(req, header, &reply); + return; + } + + D_INFO("RELEASE_IP %s - to node %d - redundant\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false), + ip->pnn); + t->pnn = ip->pnn; + } else { + D_NOTICE("RELEASE_IP %s - to node %d\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false), + ip->pnn); + t->pnn = ip->pnn; + } + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_takeover_ip(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_public_ip *ip = request->rdata.data.pubip; + struct ctdb_reply_control reply; + struct ctdb_public_ip_list *ips = NULL; + struct ctdb_public_ip *t = NULL; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + D_INFO("TAKEOVER_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + ips = &ctdb->known_ips[header->destnode]; + + t = NULL; + for (i = 0; i < ips->num; i++) { + if (ctdb_sock_addr_same_ip(&ips->ip[i].addr, &ip->addr)) { + t = &ips->ip[i]; + break; + } + } + if (t == NULL) { + D_INFO("TAKEOVER_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + if (t->pnn == header->destnode) { + D_INFO("TAKEOVER_IP %s - redundant\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + } else { + D_NOTICE("TAKEOVER_IP %s\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + t->pnn = ip->pnn; + } + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_public_ips(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_public_ip_list *ips = NULL; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + /* No IPs defined so create a dummy empty struct and ship it */ + ips = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (ips == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + goto ok; + } + + ips = &ctdb->known_ips[header->destnode]; + + if (request->flags & CTDB_PUBLIC_IP_FLAGS_ONLY_AVAILABLE) { + /* If runstate is not RUNNING or a node is then return + * no available IPs. Don't worry about interface + * states here - we're not faking down to that level. + */ + uint32_t flags = ctdb->node_map->node[header->destnode].flags; + if (ctdb->runstate != CTDB_RUNSTATE_RUNNING || + ((flags & (NODE_FLAGS_INACTIVE|NODE_FLAGS_DISABLED)) != 0)) { + /* No available IPs: return dummy empty struct */ + ips = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (ips == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } + } + +ok: + reply.rdata.data.pubip_list = ips; + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_get_nodemap(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_node_map *nodemap; + struct node *node; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + nodemap = talloc_zero(mem_ctx, struct ctdb_node_map); + if (nodemap == NULL) { + goto fail; + } + + nodemap->num = ctdb->node_map->num_nodes; + nodemap->node = talloc_array(nodemap, struct ctdb_node_and_flags, + nodemap->num); + if (nodemap->node == NULL) { + goto fail; + } + + for (i=0; inum; i++) { + node = &ctdb->node_map->node[i]; + nodemap->node[i] = (struct ctdb_node_and_flags) { + .pnn = node->pnn, + .flags = node->flags, + .addr = node->addr, + }; + } + + reply.rdata.data.nodemap = nodemap; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_get_reclock_file(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + if (ctdb->reclock != NULL) { + reply.rdata.data.reclock_file = + talloc_strdup(mem_ctx, ctdb->reclock); + if (reply.rdata.data.reclock_file == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } else { + reply.rdata.data.reclock_file = NULL; + } + + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_stop_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Stopping node\n")); + ctdb->node_map->node[header->destnode].flags |= NODE_FLAGS_STOPPED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void control_continue_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Continue node\n")); + ctdb->node_map->node[header->destnode].flags &= ~NODE_FLAGS_STOPPED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void set_ban_state_callback(struct tevent_req *subreq) +{ + struct node *node = tevent_req_callback_data( + subreq, struct node); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + DEBUG(DEBUG_INFO, ("tevent_wakeup_recv failed\n")); + } + + node->flags &= ~NODE_FLAGS_BANNED; +} + +static void control_set_ban_state(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct tevent_req *subreq; + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_ban_state *ban = request->rdata.data.ban_state; + struct ctdb_reply_control reply; + struct node *node; + + reply.rdata.opcode = request->opcode; + + if (ban->pnn != header->destnode) { + DEBUG(DEBUG_INFO, + ("SET_BAN_STATE control for PNN %d rejected\n", + ban->pnn)); + reply.status = EINVAL; + goto fail; + } + + node = &ctdb->node_map->node[header->destnode]; + + if (ban->time == 0) { + DEBUG(DEBUG_INFO,("Unbanning this node\n")); + node->flags &= ~NODE_FLAGS_BANNED; + goto done; + } + + subreq = tevent_wakeup_send(ctdb->node_map, state->ev, + tevent_timeval_current_ofs( + ban->time, 0)); + if (subreq == NULL) { + reply.status = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, set_ban_state_callback, node); + + DEBUG(DEBUG_INFO, ("Banning this node for %d seconds\n", ban->time)); + node->flags |= NODE_FLAGS_BANNED; + ctdb->vnn_map->generation = INVALID_GENERATION; + +done: + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; + +fail: + reply.errmsg = "Failed to ban node"; +} + +static void control_trans3_commit(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + int ret; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.recbuf->db_id); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Unknown database"; + client_send_control(req, header, &reply); + return; + } + + if (! (db->flags & + (CTDB_DB_FLAGS_PERSISTENT|CTDB_DB_FLAGS_REPLICATED))) { + reply.status = -1; + reply.errmsg = "Transactions on volatile database"; + client_send_control(req, header, &reply); + return; + } + + ret = ltdb_transaction(db, request->rdata.data.recbuf); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "Transaction failed"; + client_send_control(req, header, &reply); + return; + } + + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_db_seqnum(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + int ret; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + uint64_t seqnum; + + ret = database_seqnum(db, &seqnum); + if (ret == 0) { + reply.rdata.data.seqnum = seqnum; + reply.status = 0; + reply.errmsg = NULL; + } else { + reply.status = ret; + reply.errmsg = "Failed to get seqnum"; + } + } + + client_send_control(req, header, &reply); +} + +static void control_db_get_health(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.reason = NULL; + reply.status = 0; + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static struct ctdb_iface_list *get_ctdb_iface_list(TALLOC_CTX *mem_ctx, + struct ctdbd_context *ctdb) +{ + struct ctdb_iface_list *iface_list; + struct interface *iface; + unsigned int i; + + iface_list = talloc_zero(mem_ctx, struct ctdb_iface_list); + if (iface_list == NULL) { + goto done; + } + + iface_list->num = ctdb->iface_map->num; + iface_list->iface = talloc_array(iface_list, struct ctdb_iface, + iface_list->num); + if (iface_list->iface == NULL) { + TALLOC_FREE(iface_list); + goto done; + } + + for (i=0; inum; i++) { + iface = &ctdb->iface_map->iface[i]; + iface_list->iface[i] = (struct ctdb_iface) { + .link_state = iface->link_up, + .references = iface->references, + }; + strlcpy(iface_list->iface[i].name, iface->name, + sizeof(iface_list->iface[i].name)); + } + +done: + return iface_list; +} + +static void control_get_public_ip_info(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + ctdb_sock_addr *addr = request->rdata.data.addr; + struct ctdb_public_ip_list *known = NULL; + struct ctdb_public_ip_info *info = NULL; + unsigned i; + + reply.rdata.opcode = request->opcode; + + info = talloc_zero(mem_ctx, struct ctdb_public_ip_info); + if (info == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + + reply.rdata.data.ipinfo = info; + + if (ctdb->known_ips != NULL) { + known = &ctdb->known_ips[header->destnode]; + } else { + /* No IPs defined so create a dummy empty struct and + * fall through. The given IP won't be matched + * below... + */ + known = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (known == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } + + for (i = 0; i < known->num; i++) { + if (ctdb_sock_addr_same_ip(&known->ip[i].addr, + addr)) { + break; + } + } + + if (i == known->num) { + D_ERR("GET_PUBLIC_IP_INFO: not known public IP %s\n", + ctdb_sock_addr_to_string(mem_ctx, addr, false)); + reply.status = -1; + reply.errmsg = "Unknown address"; + goto done; + } + + info->ip = known->ip[i]; + + /* The fake PUBLICIPS stanza and resulting known_ips data + * don't know anything about interfaces, so completely fake + * this. + */ + info->active_idx = 0; + + info->ifaces = get_ctdb_iface_list(mem_ctx, ctdb); + if (info->ifaces == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_get_ifaces(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_iface_list *iface_list; + + reply.rdata.opcode = request->opcode; + + iface_list = get_ctdb_iface_list(mem_ctx, ctdb); + if (iface_list == NULL) { + goto fail; + } + + reply.rdata.data.iface_list = iface_list; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_set_iface_link_state(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_iface *in_iface; + struct interface *iface = NULL; + bool link_up = false; + int i; + + reply.rdata.opcode = request->opcode; + + in_iface = request->rdata.data.iface; + + if (in_iface->name[CTDB_IFACE_SIZE] != '\0') { + reply.errmsg = "interface name not terminated"; + goto fail; + } + + switch (in_iface->link_state) { + case 0: + link_up = false; + break; + + case 1: + link_up = true; + break; + + default: + reply.errmsg = "invalid link state"; + goto fail; + } + + if (in_iface->references != 0) { + reply.errmsg = "references should be 0"; + goto fail; + } + + for (i=0; iiface_map->num; i++) { + if (strcmp(ctdb->iface_map->iface[i].name, + in_iface->name) == 0) { + iface = &ctdb->iface_map->iface[i]; + break; + } + } + + if (iface == NULL) { + reply.errmsg = "interface not found"; + goto fail; + } + + iface->link_up = link_up; + + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + client_send_control(req, header, &reply); +} + +static void control_set_db_readonly(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + goto done; + } + + if (db->flags & CTDB_DB_FLAGS_PERSISTENT) { + reply.status = EINVAL; + reply.errmsg = "Can not set READONLY on persistent db"; + goto done; + } + + db->flags |= CTDB_DB_FLAGS_READONLY; + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +struct traverse_start_ext_state { + struct tevent_req *req; + struct ctdb_req_header *header; + uint32_t reqid; + uint64_t srvid; + bool withemptyrecords; + int status; +}; + +static int traverse_start_ext_handler(struct tdb_context *tdb, + TDB_DATA key, TDB_DATA data, + void *private_data) +{ + struct traverse_start_ext_state *state = + (struct traverse_start_ext_state *)private_data; + struct ctdb_rec_data rec; + struct ctdb_req_message_data message; + size_t np; + + if (data.dsize < sizeof(struct ctdb_ltdb_header)) { + return 0; + } + + if ((data.dsize == sizeof(struct ctdb_ltdb_header)) && + (!state->withemptyrecords)) { + return 0; + } + + rec = (struct ctdb_rec_data) { + .reqid = state->reqid, + .header = NULL, + .key = key, + .data = data, + }; + + message.srvid = state->srvid; + message.data.dsize = ctdb_rec_data_len(&rec); + message.data.dptr = talloc_size(state->req, message.data.dsize); + if (message.data.dptr == NULL) { + state->status = ENOMEM; + return 1; + } + + ctdb_rec_data_push(&rec, message.data.dptr, &np); + client_send_message(state->req, state->header, &message); + + talloc_free(message.data.dptr); + + return 0; +} + +static void control_traverse_start_ext(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + struct ctdb_traverse_start_ext *ext; + struct traverse_start_ext_state t_state; + struct ctdb_rec_data rec; + struct ctdb_req_message_data message; + uint8_t buffer[32]; + size_t np; + int ret; + + reply.rdata.opcode = request->opcode; + + ext = request->rdata.data.traverse_start_ext; + + db = database_find(ctdb->db_map, ext->db_id); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Unknown database"; + client_send_control(req, header, &reply); + return; + } + + t_state = (struct traverse_start_ext_state) { + .req = req, + .header = header, + .reqid = ext->reqid, + .srvid = ext->srvid, + .withemptyrecords = ext->withemptyrecords, + }; + + ret = tdb_traverse_read(db->tdb, traverse_start_ext_handler, &t_state); + DEBUG(DEBUG_INFO, ("traversed %d records\n", ret)); + if (t_state.status != 0) { + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); + } + + reply.status = 0; + client_send_control(req, header, &reply); + + rec = (struct ctdb_rec_data) { + .reqid = ext->reqid, + .header = NULL, + .key = tdb_null, + .data = tdb_null, + }; + + message.srvid = ext->srvid; + message.data.dsize = ctdb_rec_data_len(&rec); + ctdb_rec_data_push(&rec, buffer, &np); + message.data.dptr = buffer; + client_send_message(req, header, &message); +} + +static void control_set_db_sticky(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + goto done; + } + + if (db->flags & CTDB_DB_FLAGS_PERSISTENT) { + reply.status = EINVAL; + reply.errmsg = "Can not set STICKY on persistent db"; + goto done; + } + + db->flags |= CTDB_DB_FLAGS_STICKY; + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_ipreallocated(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + /* Always succeed */ + reply.rdata.opcode = request->opcode; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_runstate(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.rdata.data.runstate = ctdb->runstate; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_nodes_file(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + struct ctdb_node_map *nodemap; + + reply.rdata.opcode = request->opcode; + + nodemap = read_nodes_file(mem_ctx, header->destnode); + if (nodemap == NULL) { + goto fail; + } + + reply.rdata.data.nodemap = nodemap; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Failed to read nodes file"; + client_send_control(req, header, &reply); +} + +static void control_db_open_flags(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.tdb_flags = database_flags(db->flags); + reply.status = 0; + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static void control_db_attach_replicated(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, + CTDB_DB_FLAGS_REPLICATED); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_check_pid_srvid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_client *client; + struct client_state *cstate; + struct ctdb_reply_control reply; + bool pid_found, srvid_found; + int ret; + + reply.rdata.opcode = request->opcode; + + pid_found = false; + srvid_found = false; + + for (client=ctdb->client_list; client != NULL; client=client->next) { + if (client->pid == request->rdata.data.pid_srvid->pid) { + pid_found = true; + cstate = (struct client_state *)client->state; + ret = srvid_exists(ctdb->srv, + request->rdata.data.pid_srvid->srvid, + cstate); + if (ret == 0) { + srvid_found = true; + ret = kill(cstate->pid, 0); + if (ret != 0) { + reply.status = ret; + reply.errmsg = strerror(errno); + } else { + reply.status = 0; + reply.errmsg = NULL; + } + } + } + } + + if (! pid_found) { + reply.status = -1; + reply.errmsg = "No client for PID"; + } else if (! srvid_found) { + reply.status = -1; + reply.errmsg = "No client for PID and SRVID"; + } + + client_send_control(req, header, &reply); +} + +static void control_disable_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Disabling node\n")); + ctdb->node_map->node[header->destnode].flags |= + NODE_FLAGS_PERMANENTLY_DISABLED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void control_enable_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Enable node\n")); + ctdb->node_map->node[header->destnode].flags &= + ~NODE_FLAGS_PERMANENTLY_DISABLED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static bool fake_control_failure(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct fake_control_failure *f = NULL; + + D_DEBUG("Checking fake control failure for control %u on node %u\n", + request->opcode, header->destnode); + for (f = ctdb->control_failures; f != NULL; f = f->next) { + if (f->opcode == request->opcode && + (f->pnn == header->destnode || + f->pnn == CTDB_UNKNOWN_PNN)) { + + reply.rdata.opcode = request->opcode; + if (strcmp(f->error, "TIMEOUT") == 0) { + /* Causes no reply */ + D_ERR("Control %u fake timeout on node %u\n", + request->opcode, header->destnode); + return true; + } else if (strcmp(f->error, "ERROR") == 0) { + D_ERR("Control %u fake error on node %u\n", + request->opcode, header->destnode); + reply.status = -1; + reply.errmsg = f->comment; + client_send_control(req, header, &reply); + return true; + } + } + } + + return false; +} + +static void control_error(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + D_DEBUG("Control %u not implemented\n", request->opcode); + + reply.rdata.opcode = request->opcode; + reply.status = -1; + reply.errmsg = "Not implemented"; + + client_send_control(req, header, &reply); +} + +/* + * Handling protocol - messages + */ + +struct disable_recoveries_state { + struct node *node; +}; + +static void disable_recoveries_callback(struct tevent_req *subreq) +{ + struct disable_recoveries_state *substate = tevent_req_callback_data( + subreq, struct disable_recoveries_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + DEBUG(DEBUG_INFO, ("tevent_wakeup_recv failed\n")); + } + + substate->node->recovery_disabled = false; + TALLOC_FREE(substate->node->recovery_substate); +} + +static void message_disable_recoveries(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_message *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct tevent_req *subreq; + struct ctdbd_context *ctdb = state->ctdb; + struct disable_recoveries_state *substate; + struct ctdb_disable_message *disable = request->data.disable; + struct ctdb_req_message_data reply; + struct node *node; + int ret = -1; + TDB_DATA data; + + node = &ctdb->node_map->node[header->destnode]; + + if (disable->timeout == 0) { + TALLOC_FREE(node->recovery_substate); + node->recovery_disabled = false; + DEBUG(DEBUG_INFO, ("Enabled recoveries on node %u\n", + header->destnode)); + goto done; + } + + substate = talloc_zero(ctdb->node_map, + struct disable_recoveries_state); + if (substate == NULL) { + goto fail; + } + + substate->node = node; + + subreq = tevent_wakeup_send(substate, state->ev, + tevent_timeval_current_ofs( + disable->timeout, 0)); + if (subreq == NULL) { + talloc_free(substate); + goto fail; + } + tevent_req_set_callback(subreq, disable_recoveries_callback, substate); + + DEBUG(DEBUG_INFO, ("Disabled recoveries for %d seconds on node %u\n", + disable->timeout, header->destnode)); + node->recovery_substate = substate; + node->recovery_disabled = true; + +done: + ret = header->destnode; + +fail: + reply.srvid = disable->srvid; + data.dptr = (uint8_t *)&ret; + data.dsize = sizeof(int); + reply.data = data; + + client_send_message(req, header, &reply); +} + +static void message_takeover_run(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_message *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_srvid_message *srvid = request->data.msg; + struct ctdb_req_message_data reply; + int ret = -1; + TDB_DATA data; + + if (header->destnode != ctdb->node_map->recmaster) { + /* No reply! Only recmaster replies... */ + return; + } + + DEBUG(DEBUG_INFO, ("IP takover run on node %u\n", + header->destnode)); + ret = header->destnode; + + reply.srvid = srvid->srvid; + data.dptr = (uint8_t *)&ret; + data.dsize = sizeof(int); + reply.data = data; + + client_send_message(req, header, &reply); +} + +/* + * Handle a single client + */ + +static void client_read_handler(uint8_t *buf, size_t buflen, + void *private_data); +static void client_dead_handler(void *private_data); +static void client_process_packet(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_process_call(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_process_message(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_process_control(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_reply_done(struct tevent_req *subreq); + +static struct tevent_req *client_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, struct ctdbd_context *ctdb, + int pnn) +{ + struct tevent_req *req; + struct client_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct client_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->ctdb = ctdb; + state->pnn = pnn; + + (void) ctdb_get_peer_pid(fd, &state->pid); + + ret = comm_setup(state, ev, fd, client_read_handler, req, + client_dead_handler, req, &state->comm); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + ret = client_add(ctdb, state->pid, state); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + DEBUG(DEBUG_INFO, ("New client fd=%d\n", fd)); + + return req; +} + +static void client_read_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_req_header header; + size_t np; + unsigned int i; + int ret; + + ret = ctdb_req_header_pull(buf, buflen, &header, &np); + if (ret != 0) { + return; + } + + if (buflen != header.length) { + return; + } + + ret = ctdb_req_header_verify(&header, 0); + if (ret != 0) { + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode == CTDB_BROADCAST_ALL) { + for (i=0; inode_map->num_nodes; i++) { + header.destnode = i; + + ctdb_req_header_push(&header, buf, &np); + client_process_packet(req, buf, buflen); + } + return; + } + + if (header.destnode == CTDB_BROADCAST_CONNECTED) { + for (i=0; inode_map->num_nodes; i++) { + if (ctdb->node_map->node[i].flags & + NODE_FLAGS_DISCONNECTED) { + continue; + } + + header.destnode = i; + + ctdb_req_header_push(&header, buf, &np); + client_process_packet(req, buf, buflen); + } + return; + } + + if (header.destnode > ctdb->node_map->num_nodes) { + fprintf(stderr, "Invalid destination pnn 0x%x\n", + header.destnode); + return; + } + + + if (ctdb->node_map->node[header.destnode].flags & NODE_FLAGS_DISCONNECTED) { + fprintf(stderr, "Packet for disconnected node pnn %u\n", + header.destnode); + return; + } + + ctdb_req_header_push(&header, buf, &np); + client_process_packet(req, buf, buflen); +} + +static void client_dead_handler(void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + + tevent_req_done(req); +} + +static void client_process_packet(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct ctdb_req_header header; + size_t np; + int ret; + + ret = ctdb_req_header_pull(buf, buflen, &header, &np); + if (ret != 0) { + return; + } + + switch (header.operation) { + case CTDB_REQ_CALL: + client_process_call(req, buf, buflen); + break; + + case CTDB_REQ_MESSAGE: + client_process_message(req, buf, buflen); + break; + + case CTDB_REQ_CONTROL: + client_process_control(req, buf, buflen); + break; + + default: + break; + } +} + +static void client_process_call(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + TALLOC_CTX *mem_ctx; + struct ctdb_req_header header; + struct ctdb_req_call request; + struct ctdb_reply_call reply; + struct database *db; + struct ctdb_ltdb_header hdr; + TDB_DATA data; + int ret; + + mem_ctx = talloc_new(state); + if (tevent_req_nomem(mem_ctx, req)) { + return; + } + + ret = ctdb_req_call_pull(buf, buflen, &header, mem_ctx, &request); + if (ret != 0) { + talloc_free(mem_ctx); + tevent_req_error(req, ret); + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode >= ctdb->node_map->num_nodes) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("call db_id = %u\n", request.db_id)); + + db = database_find(ctdb->db_map, request.db_id); + if (db == NULL) { + goto fail; + } + + ret = ltdb_fetch(db, request.key, &hdr, mem_ctx, &data); + if (ret != 0) { + goto fail; + } + + /* Fake migration */ + if (hdr.dmaster != ctdb->node_map->pnn) { + hdr.dmaster = ctdb->node_map->pnn; + + ret = ltdb_store(db, request.key, &hdr, data); + if (ret != 0) { + goto fail; + } + } + + talloc_free(mem_ctx); + + reply.status = 0; + reply.data = tdb_null; + + client_send_call(req, &header, &reply); + return; + +fail: + talloc_free(mem_ctx); + reply.status = -1; + reply.data = tdb_null; + + client_send_call(req, &header, &reply); +} + +static void client_process_message(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + TALLOC_CTX *mem_ctx; + struct ctdb_req_header header; + struct ctdb_req_message request; + uint64_t srvid; + int ret; + + mem_ctx = talloc_new(state); + if (tevent_req_nomem(mem_ctx, req)) { + return; + } + + ret = ctdb_req_message_pull(buf, buflen, &header, mem_ctx, &request); + if (ret != 0) { + talloc_free(mem_ctx); + tevent_req_error(req, ret); + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode >= ctdb->node_map->num_nodes) { + /* Many messages are not replied to, so just behave as + * though this message was not received */ + fprintf(stderr, "Invalid node %d\n", header.destnode); + talloc_free(mem_ctx); + return; + } + + srvid = request.srvid; + DEBUG(DEBUG_INFO, ("request srvid = 0x%"PRIx64"\n", srvid)); + + if (srvid == CTDB_SRVID_DISABLE_RECOVERIES) { + message_disable_recoveries(mem_ctx, req, &header, &request); + } else if (srvid == CTDB_SRVID_TAKEOVER_RUN) { + message_takeover_run(mem_ctx, req, &header, &request); + } else { + D_DEBUG("Message id 0x%"PRIx64" not implemented\n", srvid); + } + + /* check srvid */ + talloc_free(mem_ctx); +} + +static void client_process_control(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + TALLOC_CTX *mem_ctx; + struct ctdb_req_header header; + struct ctdb_req_control request; + int ret; + + mem_ctx = talloc_new(state); + if (tevent_req_nomem(mem_ctx, req)) { + return; + } + + ret = ctdb_req_control_pull(buf, buflen, &header, mem_ctx, &request); + if (ret != 0) { + talloc_free(mem_ctx); + tevent_req_error(req, ret); + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode >= ctdb->node_map->num_nodes) { + struct ctdb_reply_control reply; + + reply.rdata.opcode = request.opcode; + reply.errmsg = "Invalid node"; + reply.status = -1; + client_send_control(req, &header, &reply); + return; + } + + DEBUG(DEBUG_INFO, ("request opcode = %u, reqid = %u\n", + request.opcode, header.reqid)); + + if (fake_control_failure(mem_ctx, req, &header, &request)) { + goto done; + } + + switch (request.opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + control_process_exists(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_PING: + control_ping(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GETDBPATH: + control_getdbpath(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GETVNNMAP: + control_getvnnmap(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DEBUG: + control_get_debug(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DEBUG: + control_set_debug(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DBMAP: + control_get_dbmap(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_RECMODE: + control_get_recmode(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_RECMODE: + control_set_recmode(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_ATTACH: + control_db_attach(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_REGISTER_SRVID: + control_register_srvid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + control_deregister_srvid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DBNAME: + control_get_dbname(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PID: + control_get_pid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PNN: + control_get_pnn(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SHUTDOWN: + control_shutdown(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_TUNABLE: + control_set_tunable(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_TUNABLE: + control_get_tunable(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + control_list_tunables(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + control_modify_flags(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + control_get_all_tunables(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + control_db_attach_persistent(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_UPTIME: + control_uptime(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + control_reload_nodes_file(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + control_get_capabilities(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_RELEASE_IP: + control_release_ip(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TAKEOVER_IP: + control_takeover_ip(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + control_get_public_ips(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_NODEMAP: + control_get_nodemap(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + control_get_reclock_file(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_STOP_NODE: + control_stop_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_CONTINUE_NODE: + control_continue_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_BAN_STATE: + control_set_ban_state(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + control_trans3_commit(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + control_get_db_seqnum(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + control_db_get_health(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + control_get_public_ip_info(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_IFACES: + control_get_ifaces(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + control_set_iface_link_state(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DB_READONLY: + control_set_db_readonly(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + control_traverse_start_ext(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + control_set_db_sticky(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_IPREALLOCATED: + control_ipreallocated(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_RUNSTATE: + control_get_runstate(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_NODES_FILE: + control_get_nodes_file(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + control_db_open_flags(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + control_db_attach_replicated(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + control_check_pid_srvid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DISABLE_NODE: + control_disable_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_ENABLE_NODE: + control_enable_node(mem_ctx, req, &header, &request); + break; + + default: + if (! (request.flags & CTDB_CTRL_FLAG_NOREPLY)) { + control_error(mem_ctx, req, &header, &request); + } + break; + } + +done: + talloc_free(mem_ctx); +} + +static int client_recv(struct tevent_req *req, int *perr) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + int err; + + DEBUG(DEBUG_INFO, ("Client done fd=%d\n", state->fd)); + close(state->fd); + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return -1; + } + + return state->status; +} + +/* + * Fake CTDB server + */ + +struct server_state { + struct tevent_context *ev; + struct ctdbd_context *ctdb; + struct tevent_timer *leader_broadcast_te; + int fd; +}; + +static void server_leader_broadcast(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data); +static void server_new_client(struct tevent_req *subreq); +static void server_client_done(struct tevent_req *subreq); + +static struct tevent_req *server_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdbd_context *ctdb, + int fd) +{ + struct tevent_req *req, *subreq; + struct server_state *state; + + req = tevent_req_create(mem_ctx, &state, struct server_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ctdb = ctdb; + state->fd = fd; + + state->leader_broadcast_te = tevent_add_timer(state->ev, + state, + timeval_current_ofs(0, 0), + server_leader_broadcast, + state); + if (state->leader_broadcast_te == NULL) { + DBG_WARNING("Failed to set up leader broadcast\n"); + } + + subreq = accept_send(state, ev, fd); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, server_new_client, req); + + return req; +} + +static void server_leader_broadcast(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct server_state *state = talloc_get_type_abort( + private_data, struct server_state); + struct ctdbd_context *ctdb = state->ctdb; + uint32_t leader = ctdb->node_map->recmaster; + TDB_DATA data; + int ret; + + if (leader == CTDB_UNKNOWN_PNN) { + goto done; + } + + data.dptr = (uint8_t *)&leader; + data.dsize = sizeof(leader); + + ret = srvid_dispatch(ctdb->srv, CTDB_SRVID_LEADER, 0, data); + if (ret != 0) { + DBG_WARNING("Failed to send leader broadcast, ret=%d\n", ret); + } + +done: + state->leader_broadcast_te = tevent_add_timer(state->ev, + state, + timeval_current_ofs(1, 0), + server_leader_broadcast, + state); + if (state->leader_broadcast_te == NULL) { + DBG_WARNING("Failed to set up leader broadcast\n"); + } +} + +static void server_new_client(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct server_state *state = tevent_req_data( + req, struct server_state); + struct ctdbd_context *ctdb = state->ctdb; + int client_fd; + int ret = 0; + + client_fd = accept_recv(subreq, NULL, NULL, &ret); + TALLOC_FREE(subreq); + if (client_fd == -1) { + tevent_req_error(req, ret); + return; + } + + subreq = client_send(state, state->ev, client_fd, + ctdb, ctdb->node_map->pnn); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, server_client_done, req); + + ctdb->num_clients += 1; + + subreq = accept_send(state, state->ev, state->fd); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, server_new_client, req); +} + +static void server_client_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct server_state *state = tevent_req_data( + req, struct server_state); + struct ctdbd_context *ctdb = state->ctdb; + int ret = 0; + int status; + + status = client_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (status < 0) { + tevent_req_error(req, ret); + return; + } + + ctdb->num_clients -= 1; + + if (status == 99) { + /* Special status, to shutdown server */ + DEBUG(DEBUG_INFO, ("Shutting down server\n")); + tevent_req_done(req); + } +} + +static bool server_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +/* + * Main functions + */ + +static int socket_init(const char *sockpath) +{ + struct sockaddr_un addr; + size_t len; + int ret, fd; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + if (len >= sizeof(addr.sun_path)) { + fprintf(stderr, "path too long: %s\n", sockpath); + return -1; + } + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + fprintf(stderr, "socket failed - %s\n", sockpath); + return -1; + } + + ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret != 0) { + fprintf(stderr, "bind failed - %s\n", sockpath); + goto fail; + } + + ret = listen(fd, 10); + if (ret != 0) { + fprintf(stderr, "listen failed\n"); + goto fail; + } + + DEBUG(DEBUG_INFO, ("Socket init done\n")); + + return fd; + +fail: + if (fd != -1) { + close(fd); + } + return -1; +} + +static struct options { + const char *dbdir; + const char *sockpath; + const char *pidfile; + const char *debuglevel; +} options; + +static struct poptOption cmdline_options[] = { + POPT_AUTOHELP + { "dbdir", 'D', POPT_ARG_STRING, &options.dbdir, 0, + "Database directory", "directory" }, + { "socket", 's', POPT_ARG_STRING, &options.sockpath, 0, + "Unix domain socket path", "filename" }, + { "pidfile", 'p', POPT_ARG_STRING, &options.pidfile, 0, + "pid file", "filename" } , + { "debug", 'd', POPT_ARG_STRING, &options.debuglevel, 0, + "debug level", "ERR|WARNING|NOTICE|INFO|DEBUG" } , + POPT_TABLEEND +}; + +static void cleanup(void) +{ + unlink(options.sockpath); + unlink(options.pidfile); +} + +static void signal_handler(int sig) +{ + cleanup(); + exit(0); +} + +static void start_server(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct ctdbd_context *ctdb, int fd, int pfd) +{ + struct tevent_req *req; + int ret = 0; + ssize_t len; + + atexit(cleanup); + signal(SIGTERM, signal_handler); + + req = server_send(mem_ctx, ev, ctdb, fd); + if (req == NULL) { + fprintf(stderr, "Memory error\n"); + exit(1); + } + + len = write(pfd, &ret, sizeof(ret)); + if (len != sizeof(ret)) { + fprintf(stderr, "Failed to send message to parent\n"); + exit(1); + } + close(pfd); + + tevent_req_poll(req, ev); + + server_recv(req, &ret); + if (ret != 0) { + exit(1); + } +} + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct ctdbd_context *ctdb; + struct tevent_context *ev; + poptContext pc; + int opt, fd, ret, pfd[2]; + ssize_t len; + pid_t pid; + FILE *fp; + + pc = poptGetContext(argv[0], argc, argv, cmdline_options, + POPT_CONTEXT_KEEP_FIRST); + while ((opt = poptGetNextOpt(pc)) != -1) { + fprintf(stderr, "Invalid option %s\n", poptBadOption(pc, 0)); + exit(1); + } + + if (options.dbdir == NULL) { + fprintf(stderr, "Please specify database directory\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + if (options.sockpath == NULL) { + fprintf(stderr, "Please specify socket path\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + if (options.pidfile == NULL) { + fprintf(stderr, "Please specify pid file\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory error\n"); + exit(1); + } + + ret = logging_init(mem_ctx, "file:", options.debuglevel, "fake-ctdbd"); + if (ret != 0) { + fprintf(stderr, "Invalid debug level\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + ctdb = ctdbd_setup(mem_ctx, options.dbdir); + if (ctdb == NULL) { + exit(1); + } + + if (! ctdbd_verify(ctdb)) { + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory error\n"); + exit(1); + } + + fd = socket_init(options.sockpath); + if (fd == -1) { + exit(1); + } + + ret = pipe(pfd); + if (ret != 0) { + fprintf(stderr, "Failed to create pipe\n"); + cleanup(); + exit(1); + } + + pid = fork(); + if (pid == -1) { + fprintf(stderr, "Failed to fork\n"); + cleanup(); + exit(1); + } + + if (pid == 0) { + /* Child */ + close(pfd[0]); + start_server(mem_ctx, ev, ctdb, fd, pfd[1]); + exit(1); + } + + /* Parent */ + close(pfd[1]); + + len = read(pfd[0], &ret, sizeof(ret)); + close(pfd[0]); + if (len != sizeof(ret)) { + fprintf(stderr, "len = %zi\n", len); + fprintf(stderr, "Failed to get message from child\n"); + kill(pid, SIGTERM); + exit(1); + } + + fp = fopen(options.pidfile, "w"); + if (fp == NULL) { + fprintf(stderr, "Failed to open pid file %s\n", + options.pidfile); + kill(pid, SIGTERM); + exit(1); + } + fprintf(fp, "%d\n", pid); + fclose(fp); + + return 0; +} diff --git a/ctdb/tests/src/fetch_loop.c b/ctdb/tests/src/fetch_loop.c new file mode 100644 index 0000000..0e1d9da --- /dev/null +++ b/ctdb/tests/src/fetch_loop.c @@ -0,0 +1,288 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define TESTDB "fetch_loop.tdb" +#define TESTKEY "testkey" + +struct fetch_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int num_nodes; + int timelimit; + TDB_DATA key; + int locks_count; +}; + +static void fetch_loop_start(struct tevent_req *subreq); +static void fetch_loop_next(struct tevent_req *subreq); +static void fetch_loop_each_second(struct tevent_req *subreq); +static void fetch_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->key.dptr = discard_const(TESTKEY); + state->key.dsize = strlen(TESTKEY); + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_loop_start, req); + + return req; +} + +static void fetch_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_finish, req); +} + +static void fetch_loop_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + struct ctdb_record_handle *h; + TDB_DATA data; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + if (data.dsize == sizeof(uint32_t)) { + state->locks_count = *(uint32_t *)data.dptr; + } + TALLOC_FREE(data.dptr); + + state->locks_count += 1; + data.dsize = sizeof(uint32_t); + data.dptr = (uint8_t *)&state->locks_count; + + ret = ctdb_store_record(h, data); + if (ret != 0) { + talloc_free(h); + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); +} + +static void fetch_loop_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\r", state->locks_count); + fflush(stdout); + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); +} + +static void fetch_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\n", state->locks_count); + + tevent_req_done(req); +} + +static bool fetch_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_loop", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), TESTDB, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", TESTDB); + exit(1); + } + + req = fetch_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "fetch loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_loop_key.c b/ctdb/tests/src/fetch_loop_key.c new file mode 100644 index 0000000..3f41ca7 --- /dev/null +++ b/ctdb/tests/src/fetch_loop_key.c @@ -0,0 +1,217 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct fetch_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int timelimit; + TDB_DATA key; + int locks_count; +}; + +static void fetch_loop_next(struct tevent_req *subreq); + +static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + const char *keystr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->timelimit = timelimit; + state->key.dptr = discard_const(keystr); + state->key.dsize = strlen(keystr); + + subreq = ctdb_fetch_lock_send(state, ev, client, ctdb_db, + state->key, false); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_loop_next, req); + + return req; +} + +static void fetch_loop_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + struct ctdb_record_handle *h; + TDB_DATA data; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + if (data.dsize == sizeof(uint32_t)) { + state->locks_count = *(uint32_t *)data.dptr; + } + TALLOC_FREE(data.dptr); + + state->locks_count += 1; + data.dsize = sizeof(uint32_t); + data.dptr = (uint8_t *)&state->locks_count; + + ret = ctdb_store_record(h, data); + if (ret != 0) { + talloc_free(h); + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); +} + +static bool fetch_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +static struct tevent_req *global_req; + +static void alarm_handler(int sig) +{ + struct fetch_loop_state *state = tevent_req_data( + global_req, struct fetch_loop_state); + static int time_passed = 0; + + time_passed += 1; + + printf("Locks:%d\n", state->locks_count); + fflush(stdout); + + if (time_passed >= state->timelimit) { + tevent_req_done(global_req); + } + + alarm(1); +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int ret; + bool status; + + setup_logging("fetch_loop_key", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, %s\n", + strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname); + exit(1); + } + + global_req = fetch_loop_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->timelimit); + if (global_req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + signal(SIGALRM, alarm_handler); + alarm(1); + + tevent_req_poll(global_req, ev); + + status = fetch_loop_recv(global_req, &ret); + if (! status) { + fprintf(stderr, "fetch loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_readonly.c b/ctdb/tests/src/fetch_readonly.c new file mode 100644 index 0000000..ff126bd --- /dev/null +++ b/ctdb/tests/src/fetch_readonly.c @@ -0,0 +1,166 @@ +/* + Fetch a single record using readonly + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + + +struct fetch_readonly_state { + struct tevent_context *ev; +}; + +static void fetch_readonly_done(struct tevent_req *subreq); + +static struct tevent_req *fetch_readonly_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + const char *keystr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_readonly_state *state; + TDB_DATA key; + + req = tevent_req_create(mem_ctx, &state, struct fetch_readonly_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + + key.dptr = (uint8_t *)discard_const(keystr); + key.dsize = strlen(keystr); + + subreq = ctdb_fetch_lock_send(state, ev, client, db, key, true); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_readonly_done, req); + + return req; +} + +static void fetch_readonly_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_readonly_state *state = tevent_req_data( + req, struct fetch_readonly_state); + struct ctdb_record_handle *h; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, NULL, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + tevent_req_done(req); +} + +static bool fetch_readonly_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_readonly", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, %s\n", + strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname); + exit(1); + } + + req = fetch_readonly_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_readonly_recv(req, &ret); + if (! status) { + fprintf(stderr, "fetch readonly loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_readonly_loop.c b/ctdb/tests/src/fetch_readonly_loop.c new file mode 100644 index 0000000..08cf476 --- /dev/null +++ b/ctdb/tests/src/fetch_readonly_loop.c @@ -0,0 +1,272 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define TESTDB "fetch_readonly_loop.tdb" +#define TESTKEY "testkey" + +struct fetch_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int num_nodes; + int timelimit; + TDB_DATA key; + int locks_count; +}; + +static void fetch_loop_start(struct tevent_req *subreq); +static void fetch_loop_next(struct tevent_req *subreq); +static void fetch_loop_each_second(struct tevent_req *subreq); +static void fetch_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->key.dptr = discard_const(TESTKEY); + state->key.dsize = strlen(TESTKEY); + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_loop_start, req); + + return req; +} + +static void fetch_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, true); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_finish, req); +} + +static void fetch_loop_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + struct ctdb_record_handle *h; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, NULL, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + state->locks_count += 1; + talloc_free(h); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, true); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); +} + +static void fetch_loop_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\r", state->locks_count); + fflush(stdout); + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); +} + +static void fetch_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\n", state->locks_count); + + tevent_req_done(req); +} + +static bool fetch_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_readonly_loop", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), TESTDB, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", TESTDB); + exit(1); + } + + req = fetch_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "fetch readonly loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_ring.c b/ctdb/tests/src/fetch_ring.c new file mode 100644 index 0000000..f1786ef --- /dev/null +++ b/ctdb/tests/src/fetch_ring.c @@ -0,0 +1,398 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/time.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define MSG_ID_FETCH 0 + +static uint32_t next_node(struct ctdb_client_context *client, uint32_t num_nodes) +{ + return (ctdb_client_pnn(client) + 1) % num_nodes; +} + +struct fetch_ring_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + uint32_t num_nodes; + int timelimit; + int interactive; + TDB_DATA key; + int msg_count; + struct timeval start_time; +}; + +static void fetch_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void fetch_ring_wait(struct tevent_req *subreq); +static void fetch_ring_start(struct tevent_req *subreq); +static void fetch_ring_update(struct tevent_req *subreq); +static void fetch_ring_msg_sent(struct tevent_req *subreq); +static void fetch_ring_finish(struct tevent_req *subreq); +static void fetch_ring_final_read(struct tevent_req *subreq); + +static struct tevent_req *fetch_ring_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + const char *keystr, + uint32_t num_nodes, + int timelimit, + int interactive) +{ + struct tevent_req *req, *subreq; + struct fetch_ring_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_ring_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->interactive = interactive; + state->key.dptr = discard_const(keystr); + state->key.dsize = strlen(keystr); + + subreq = ctdb_client_set_message_handler_send( + state, ev, client, MSG_ID_FETCH, + fetch_ring_msg_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_ring_wait, req); + + return req; +} + +static void fetch_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + struct tevent_req *subreq; + + state->msg_count += 1; + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_update, req); +} + +static void fetch_ring_wait(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_start, req); +} + +static void fetch_ring_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->start_time = tevent_timeval_current(); + + if (ctdb_client_pnn(state->client) == state->num_nodes-1) { + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, + false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_update, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_finish, req); + +} + +static void fetch_ring_update(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + struct ctdb_record_handle *h; + struct ctdb_req_message msg; + TDB_DATA data; + uint32_t pnn; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + if (data.dsize > 1000) { + TALLOC_FREE(data.dptr); + data.dsize = 0; + } + + if (data.dsize == 0) { + data.dptr = (uint8_t *)talloc_asprintf(state, "Test data\n"); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + } + + data.dptr = (uint8_t *)talloc_asprintf_append( + (char *)data.dptr, + "msg_count=%d on node %d\n", + state->msg_count, + ctdb_client_pnn(state->client)); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + + data.dsize = strlen((const char *)data.dptr) + 1; + + ret = ctdb_store_record(h, data); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + talloc_free(data.dptr); + talloc_free(h); + + msg.srvid = MSG_ID_FETCH; + msg.data.data = tdb_null; + + pnn = next_node(state->client, state->num_nodes); + + subreq = ctdb_client_message_send(state, state->ev, state->client, + pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_msg_sent, req); +} + +static void fetch_ring_msg_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + } +} + +static void fetch_ring_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + bool status; + double t; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + t = timeval_elapsed(&state->start_time); + + printf("Fetch[%u]: %.2f msgs/sec\n", ctdb_client_pnn(state->client), + state->msg_count / t); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_final_read, req); +} + +static void fetch_ring_final_read(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + struct ctdb_record_handle *h; + TDB_DATA data; + int err; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &err); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, err); + return; + } + + if (state->interactive == 1) { + printf("DATA:\n%s\n", (char *)data.dptr); + } + talloc_free(data.dptr); + talloc_free(h); + + tevent_req_done(req); +} + +static bool fetch_ring_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_ring", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, + client, + tevent_timeval_zero(), + opts->dbname, + 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname); + exit(1); + } + + req = fetch_ring_send(mem_ctx, + ev, + client, + ctdb_db, + opts->keystr, + opts->num_nodes, + opts->timelimit, + opts->interactive); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_ring_recv(req, NULL); + if (! status) { + fprintf(stderr, "fetch ring test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/g_lock_loop.c b/ctdb/tests/src/g_lock_loop.c new file mode 100644 index 0000000..3b84241 --- /dev/null +++ b/ctdb/tests/src/g_lock_loop.c @@ -0,0 +1,270 @@ +/* + simple ctdb benchmark for g_lock operations + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/tevent_unix.h" +#include "lib/util/debug.h" + +#include "protocol/protocol_api.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define TESTKEY "testkey" + +struct glock_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *db; + int num_nodes; + int timelimit; + uint32_t pnn; + uint32_t counter; + struct ctdb_server_id sid; + const char *key; +}; + +static void glock_loop_start(struct tevent_req *subreq); +static void glock_loop_locked(struct tevent_req *subreq); +static void glock_loop_unlocked(struct tevent_req *subreq); +static void glock_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *glock_loop_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct glock_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct glock_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->db = db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->pnn = ctdb_client_pnn(client); + state->counter = 0; + state->sid = ctdb_client_get_server_id(client, 1); + state->key = TESTKEY; + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, glock_loop_start, req); + + return req; +} + +static void glock_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_g_lock_lock_send(state, state->ev, state->client, + state->db, state->key, &state->sid, + false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_locked, req); + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_finish, req); +} + +static void glock_loop_locked(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + int ret; + bool status; + + status = ctdb_g_lock_lock_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + fprintf(stderr, "g_lock_lock failed\n"); + tevent_req_error(req, ret); + return; + } + + state->counter += 1; + + subreq = ctdb_g_lock_unlock_send(state, state->ev, state->client, + state->db, state->key, state->sid); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_unlocked, req); +} + +static void glock_loop_unlocked(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + int ret; + bool status; + + status = ctdb_g_lock_unlock_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + fprintf(stderr, "g_lock_unlock failed\n"); + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_g_lock_lock_send(state, state->ev, state->client, + state->db, state->key, &state->sid, + false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_locked, req); +} + +static void glock_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("PNN:%u counter:%u\n", state->pnn, state->counter); + + tevent_req_done(req); +} + +static bool glock_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("glock_loop", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), "g_lock.tdb", + 0, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to g_lock.tdb\n"); + exit(1); + } + + req = glock_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = glock_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "g_lock loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/hash_count_test.c b/ctdb/tests/src/hash_count_test.c new file mode 100644 index 0000000..6ddde08 --- /dev/null +++ b/ctdb/tests/src/hash_count_test.c @@ -0,0 +1,132 @@ +/* + hash_count tests + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include + +#include "common/db_hash.c" +#include "common/hash_count.c" + +#define KEY "this_is_a_test_key" + +static void test1_handler(TDB_DATA key, uint64_t counter, void *private_data) +{ + int *count = (int *)private_data; + + assert(key.dsize == strlen(KEY)); + assert(strcmp((char *)key.dptr, KEY) == 0); + assert(counter > 0); + + (*count) += 1; +} + +static void do_test1(void) +{ + struct hash_count_context *hc = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct timeval interval = {1, 0}; + TDB_DATA key; + int count = 0; + int ret, i; + + key.dptr = (uint8_t *)discard_const(KEY); + key.dsize = strlen(KEY); + + ret = hash_count_increment(hc, key); + assert(ret == EINVAL); + + ret = hash_count_init(mem_ctx, interval, NULL, NULL, &hc); + assert(ret == EINVAL); + + ret = hash_count_init(mem_ctx, interval, test1_handler, &count, &hc); + assert(ret == 0); + assert(hc != NULL); + + for (i=0; i<10; i++) { + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == i+1); + } + + talloc_free(hc); + ret = talloc_get_size(mem_ctx); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +static void test2_handler(TDB_DATA key, uint64_t counter, void *private_data) +{ + uint64_t *count = (uint64_t *)private_data; + + *count = counter; +} + +static void do_test2(void) +{ + struct hash_count_context *hc; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct timeval interval = {1, 0}; + TDB_DATA key; + uint64_t count = 0; + int ret; + + key.dptr = (uint8_t *)discard_const(KEY); + key.dsize = strlen(KEY); + + ret = hash_count_init(mem_ctx, interval, test2_handler, &count, &hc); + assert(ret == 0); + + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == 1); + + hash_count_expire(hc, &ret); + assert(ret == 0); + + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == 2); + + sleep(2); + + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == 1); + + sleep(2); + + hash_count_expire(hc, &ret); + assert(ret == 1); + + talloc_free(hc); + ret = talloc_get_size(mem_ctx); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +int main(void) +{ + do_test1(); + do_test2(); + + return 0; +} diff --git a/ctdb/tests/src/ipalloc_read_known_ips.c b/ctdb/tests/src/ipalloc_read_known_ips.c new file mode 100644 index 0000000..33d0f94 --- /dev/null +++ b/ctdb/tests/src/ipalloc_read_known_ips.c @@ -0,0 +1,179 @@ +/* + Tests support for CTDB IP allocation + + Copyright (C) Martin Schwenke 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include + +#include "lib/util/debug.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_util.h" +#include "common/logging.h" + +#include "ipalloc_read_known_ips.h" + +static bool add_ip(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list *l, + ctdb_sock_addr *addr, + uint32_t pnn) +{ + + l->ip = talloc_realloc(mem_ctx, l->ip, + struct ctdb_public_ip, l->num + 1); + if (l->ip == NULL) { + D_ERR(__location__ " out of memory\n"); + return false; + } + + l->ip[l->num].addr = *addr; + l->ip[l->num].pnn = pnn; + l->num++; + + return true; +} + +/* Format of each line is "IP CURRENT_PNN [ALLOWED_PNN,...]". + * If multi is true then ALLOWED_PNNs are not allowed. */ +static bool read_ctdb_public_ip_info_node(bool multi, + int numnodes, + struct ctdb_public_ip_list **k, + struct ctdb_public_ip_list *known) +{ + char line[1024]; + ctdb_sock_addr addr; + char *t, *tok; + int pnn, n; + + /* Known public IPs */ + *k = talloc_zero(known, struct ctdb_public_ip_list); + if (*k == NULL) { + goto fail; + } + + while (fgets(line, sizeof(line), stdin) != NULL) { + int ret; + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Exit on an empty line */ + if (line[0] == '\0') { + break; + } + + /* Get the IP address */ + tok = strtok(line, " \t"); + if (tok == NULL) { + D_WARNING("WARNING, bad line ignored :%s\n", line); + continue; + } + + ret = ctdb_sock_addr_from_string(tok, &addr, false); + if (ret != 0) { + D_ERR("ERROR, bad address :%s\n", tok); + continue; + } + + /* Get the PNN */ + pnn = -1; + tok = strtok(NULL, " \t"); + if (tok != NULL) { + pnn = (int) strtol(tok, (char **) NULL, 10); + } + + if (! add_ip(*k, *k, &addr, pnn)) { + goto fail; + } + + tok = strtok(NULL, " \t#"); + if (tok == NULL) { + if (! multi) { + for (n = 0; n < numnodes; n++) { + if (! add_ip(known, &known[n], + &addr, pnn)) { + goto fail; + } + } + } + continue; + } + + /* Handle allowed nodes for addr */ + if (multi) { + D_ERR("ERROR, bad token\n"); + goto fail; + } + t = strtok(tok, ","); + while (t != NULL) { + n = (int) strtol(t, (char **) NULL, 10); + if (! add_ip(known, &known[n], &addr, pnn)) { + goto fail; + } + t = strtok(NULL, ","); + } + } + + return true; + +fail: + TALLOC_FREE(*k); + return false; +} + +struct ctdb_public_ip_list * ipalloc_read_known_ips(TALLOC_CTX *ctx, + int numnodes, + bool multi) +{ + int n; + struct ctdb_public_ip_list *k; + struct ctdb_public_ip_list *known; + + known = talloc_zero_array(ctx, struct ctdb_public_ip_list, + numnodes); + if (known == NULL) { + D_ERR(__location__ " out of memory\n"); + goto fail; + } + + if (multi) { + for (n = 0; n < numnodes; n++) { + if (! read_ctdb_public_ip_info_node(multi, numnodes, + &k, known)) { + goto fail; + } + + known[n] = *k; + } + } else { + if (! read_ctdb_public_ip_info_node(multi, numnodes, + &k, known)) { + goto fail; + } + } + + return known; + +fail: + talloc_free(known); + return NULL; +} diff --git a/ctdb/tests/src/ipalloc_read_known_ips.h b/ctdb/tests/src/ipalloc_read_known_ips.h new file mode 100644 index 0000000..aa6d154 --- /dev/null +++ b/ctdb/tests/src/ipalloc_read_known_ips.h @@ -0,0 +1,32 @@ +/* + Tests support for CTDB IP allocation + + Copyright (C) Martin Schwenke 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#ifndef __IPALLOC_READ_KNOWN_IPS_H__ +#define __IPALLOC_READ_KNOWN_IPS_H__ + +#include +#include + +#include "protocol/protocol.h" + +struct ctdb_public_ip_list * ipalloc_read_known_ips(TALLOC_CTX *ctx, + int numnodes, + bool multi); + +#endif /* __IPALLOC_READ_KNOWN_IPS_H__ */ diff --git a/ctdb/tests/src/line_test.c b/ctdb/tests/src/line_test.c new file mode 100644 index 0000000..806d883 --- /dev/null +++ b/ctdb/tests/src/line_test.c @@ -0,0 +1,102 @@ +/* + Test code for line based I/O over fds + + Copyright (C) Amitay Isaacs 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include +#include + +#include "common/line.c" + +static int line_print(char *line, void *private_data) +{ + printf("%s\n", line); + fflush(stdout); + + return 0; +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + size_t hint = 32; + pid_t pid; + int ret, lines = 0; + int pipefd[2]; + + if (argc < 2 || argc > 3) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + exit(1); + } + + if (argc == 3) { + long value; + + value = atol(argv[2]); + assert(value > 0); + hint = value; + } + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + char buffer[16]; + ssize_t n, n2; + int fd; + + close(pipefd[0]); + + fd = open(argv[1], O_RDONLY); + assert(fd != -1); + + while (1) { + n = read(fd, buffer, sizeof(buffer)); + assert(n >= 0 && (size_t)n <= sizeof(buffer)); + + if (n == 0) { + break; + } + + n2 = write(pipefd[1], buffer, n); + assert(n2 == n); + } + + close(pipefd[1]); + close(fd); + + exit(0); + } + + close(pipefd[1]); + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = line_read(pipefd[0], hint, NULL, line_print, NULL, &lines); + assert(ret == 0); + + talloc_free(mem_ctx); + + return lines; +} diff --git a/ctdb/tests/src/lock_tdb.c b/ctdb/tests/src/lock_tdb.c new file mode 100644 index 0000000..c37f846 --- /dev/null +++ b/ctdb/tests/src/lock_tdb.c @@ -0,0 +1,60 @@ +/* + Lock a tdb and sleep + + Copyright (C) Amitay Isaacs 2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include + +const char *tdb_file; +TDB_CONTEXT *tdb; + +static void signal_handler(int signum) +{ + tdb_close(tdb); +} + +int +main(int argc, char *argv[]) +{ + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + exit(1); + } + + tdb_file = argv[1]; + + tdb = tdb_open(tdb_file, 0, 0, O_RDWR, 0); + if (tdb == NULL) { + fprintf(stderr, "Failed to open TDB file %s\n", tdb_file); + exit(1); + } + + signal(SIGINT, signal_handler); + + if (tdb_lockall(tdb) != 0) { + fprintf(stderr, "Failed to lock database %s\n", tdb_file); + tdb_close(tdb); + exit(1); + } + + sleep(999999); + + return 0; +} diff --git a/ctdb/tests/src/message_ring.c b/ctdb/tests/src/message_ring.c new file mode 100644 index 0000000..d1fcee4 --- /dev/null +++ b/ctdb/tests/src/message_ring.c @@ -0,0 +1,369 @@ +/* + simple ctdb benchmark - send messages in a ring around cluster + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/time.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define MSG_ID_BENCH 0 + +struct message_ring_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + int num_nodes; + int timelimit; + int interactive; + int msg_count; + int msg_plus, msg_minus; + struct timeval start_time; +}; + +static void message_ring_wait(struct tevent_req *subreq); +static void message_ring_start(struct tevent_req *subreq); +static void message_ring_each_second(struct tevent_req *subreq); +static void message_ring_msg_sent(struct tevent_req *subreq); +static void message_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void message_ring_finish(struct tevent_req *subreq); + +static struct tevent_req *message_ring_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + int num_nodes, int timelimit, + int interactive) +{ + struct tevent_req *req, *subreq; + struct message_ring_state *state; + + req = tevent_req_create(mem_ctx, &state, struct message_ring_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->interactive = interactive; + + subreq = ctdb_client_set_message_handler_send( + state, state->ev, state->client, + MSG_ID_BENCH, + message_ring_msg_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, message_ring_wait, req); + + return req; +} + +static void message_ring_wait(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_start, req); +} + +static void message_ring_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->start_time = tevent_timeval_current(); + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_each_second, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_finish, req); +} + +static uint32_t next_node(struct ctdb_client_context *client, + int num_nodes, int incr) +{ + return (ctdb_client_pnn(client) + num_nodes + incr) % num_nodes; +} + +static void message_ring_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + struct ctdb_req_message msg; + uint32_t pnn; + int incr; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + pnn = ctdb_client_pnn(state->client); + if (pnn == 0 && state->interactive == 1) { + double t; + + t = timeval_elapsed(&state->start_time); + printf("Ring[%u]: %.2f msgs/sec (+ve=%d -ve=%d)\n", + pnn, state->msg_count / t, + state->msg_plus, state->msg_minus); + fflush(stdout); + } + + if (state->msg_plus == 0) { + incr = 1; + + msg.srvid = 0; + msg.data.data.dptr = (uint8_t *)&incr; + msg.data.data.dsize = sizeof(incr); + + pnn = next_node(state->client, state->num_nodes, incr); + + subreq = ctdb_client_message_send(state, state->ev, + state->client, pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_msg_sent, req); + } + + if (state->msg_minus == 0) { + incr = -1; + + msg.srvid = 0; + msg.data.data.dptr = (uint8_t *)&incr; + msg.data.data.dsize = sizeof(incr); + + pnn = next_node(state->client, state->num_nodes, incr); + + subreq = ctdb_client_message_send(state, state->ev, + state->client, pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_msg_sent, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_each_second, req); +} + +static void message_ring_msg_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + } +} + +static void message_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + struct ctdb_req_message msg; + struct tevent_req *subreq; + int incr; + uint32_t pnn; + + if (srvid != MSG_ID_BENCH) { + return; + } + + if (data.dsize != sizeof(int)) { + return; + } + incr = *(int *)data.dptr; + + state->msg_count += 1; + if (incr == 1) { + state->msg_plus += 1; + } else { + state->msg_minus += 1; + } + + pnn = next_node(state->client, state->num_nodes, incr); + + msg.srvid = srvid; + msg.data.data = data; + + subreq = ctdb_client_message_send(state, state->ev, state->client, + pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_msg_sent, req); +} + +static void message_ring_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + bool status; + double t; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + t = timeval_elapsed(&state->start_time); + + printf("Ring[%u]: %.2f msgs/sec (+ve=%d -ve=%d)\n", + ctdb_client_pnn(state->client), state->msg_count / t, + state->msg_plus, state->msg_minus); + + tevent_req_done(req); +} + +static bool message_ring_recv(struct tevent_req *req) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("message_ring", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Failed to wait for recovery\n"); + exit(1); + } + + req = message_ring_send(mem_ctx, ev, client, + opts->num_nodes, opts->timelimit, + opts->interactive); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = message_ring_recv(req); + if (! status) { + fprintf(stderr, "message ring test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/pidfile_test.c b/ctdb/tests/src/pidfile_test.c new file mode 100644 index 0000000..592fc2b --- /dev/null +++ b/ctdb/tests/src/pidfile_test.c @@ -0,0 +1,242 @@ +/* + pidfile tests + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/wait.h" + +#include + +#include "common/pidfile.c" + + +/* create pid file, check pid file exists, check pid and remove pid file */ +static void test1(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + int ret; + struct stat st; + FILE *fp; + pid_t pid; + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + ret = stat(pidfile, &st); + assert(ret == 0); + assert(S_ISREG(st.st_mode)); + + fp = fopen(pidfile, "r"); + assert(fp != NULL); + ret = fscanf(fp, "%d", &pid); + assert(ret == 1); + assert(pid == getpid()); + fclose(fp); + + TALLOC_FREE(pid_ctx); + + ret = stat(pidfile, &st); + assert(ret == -1); +} + +/* create pid file in two processes */ +static void test2(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + pid_t pid, pid2; + int fd[2]; + int ret; + size_t nread; + FILE *fp; + struct stat st; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(fd[0]); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + sleep(10); + + TALLOC_FREE(pid_ctx); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + exit(1); + } + + close(fd[1]); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + fp = fopen(pidfile, "r"); + assert(fp != NULL); + ret = fscanf(fp, "%d", &pid2); + assert(ret == 1); + assert(pid == pid2); + fclose(fp); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret != 0); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + TALLOC_FREE(pid_ctx); + + ret = stat(pidfile, &st); + assert(ret == -1); +} + +/* create pid file, fork, try to remove pid file in separate process */ +static void test3(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + pid_t pid; + int fd[2]; + int ret; + size_t nread; + struct stat st; + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(fd[0]); + + TALLOC_FREE(pid_ctx); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + exit(1); + } + + close(fd[1]); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + + ret = stat(pidfile, &st); + assert(ret == 0); + + TALLOC_FREE(pid_ctx); + + ret = stat(pidfile, &st); + assert(ret == -1); +} + +/* create pid file, kill process, overwrite pid file in different process */ +static void test4(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + pid_t pid, pid2; + int fd[2]; + int ret; + size_t nread; + struct stat st; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(fd[0]); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + sleep(99); + exit(1); + } + + close(fd[1]); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + ret = stat(pidfile, &st); + assert(ret == 0); + + ret = kill(pid, SIGKILL); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + ret = stat(pidfile, &st); + assert(ret == 0); + + TALLOC_FREE(pid_ctx); +} + +int main(int argc, const char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + + test1(argv[1]); + test2(argv[1]); + test3(argv[1]); + test4(argv[1]); + + return 0; +} diff --git a/ctdb/tests/src/pkt_read_test.c b/ctdb/tests/src/pkt_read_test.c new file mode 100644 index 0000000..a3ebe0a --- /dev/null +++ b/ctdb/tests/src/pkt_read_test.c @@ -0,0 +1,249 @@ +/* + packet read tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include + +#include "lib/util/blocking.h" + +#include "common/pkt_read.c" + +static void writer(int fd) +{ + uint8_t buf[1024*1024]; + size_t buflen; + size_t pkt_size[4] = { 100, 500, 1024, 1024*1024 }; + int i, j; + int ret; + + for (i=0; i<1024*1024; i++) { + buf[i] = i%256; + } + + for (i=0; i<1000; i++) { + for (j=0; j<4; j++) { + buflen = pkt_size[j]; + memcpy(buf, &buflen, sizeof(buflen)); + + ret = write(fd, buf, buflen); + if (ret < 0) { + printf("write error: %s\n", strerror(errno)); + assert(ret > 0); + } + } + } + + close(fd); +} + +struct reader_state { + struct tevent_context *ev; + int fd; + uint8_t *buf; + size_t buflen; + struct tevent_req *subreq; +}; + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data); +static void reader_done(struct tevent_req *subreq); + +static struct tevent_req *reader_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, uint8_t *buf, + size_t buflen) +{ + struct tevent_req *req, *subreq; + struct reader_state *state; + + req = tevent_req_create(mem_ctx, &state, struct reader_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->buf = buf; + state->buflen = buflen; + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, state->buflen, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + tevent_req_post(req, ev); + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); + return req; +} + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data) +{ + uint32_t pkt_len; + + if (buflen < sizeof(pkt_len)) { + return sizeof(pkt_len) - buflen; + } + + pkt_len = *(uint32_t *)buf; + return pkt_len - buflen; +} + +static void reader_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + ssize_t nread; + uint8_t *buf; + bool free_buf; + int err; + + nread = pkt_read_recv(subreq, state, &buf, &free_buf, &err); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (nread == -1) { + if (err == EPIPE) { + tevent_req_done(req); + } else { + tevent_req_error(req, err); + } + return; + } + + if (free_buf) { + talloc_free(buf); + } + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, state->buflen, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + return; + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); +} + +static void reader_recv(struct tevent_req *req, int *perr) +{ + struct reader_state *state = tevent_req_data( + req, struct reader_state); + int err = 0; + + if (state->subreq != NULL) { + *perr = -1; + } + + if (tevent_req_is_unix_error(req, &err)) { + *perr = err; + return; + } + + *perr = 0; +} + +static void reader_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + + assert(state->subreq != NULL); + pkt_read_handler(ev, fde, flags, state->subreq); +} + +static void reader(int fd, bool fixed) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_fd *fde; + struct tevent_req *req; + int err; + uint8_t *buf = NULL; + size_t buflen = 0; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + if (fixed) { + buflen = 1024; + buf = talloc_size(mem_ctx, buflen); + assert(buf != NULL); + } + + req = reader_send(mem_ctx, ev, fd, buf, buflen); + assert(req != NULL); + + fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_READ, + reader_handler, req); + assert(fde != NULL); + + tevent_req_poll(req, ev); + + reader_recv(req, &err); + assert(err == 0); + + close(fd); + + talloc_free(mem_ctx); +} + +static void reader_test(bool fixed) +{ + int fd[2]; + int ret; + pid_t pid; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Child process */ + close(fd[0]); + writer(fd[1]); + exit(0); + } + + close(fd[1]); + ret = set_blocking(fd[0], false); + if (ret == -1) { + exit(1); + } + + reader(fd[0], fixed); +} + +int main(void) +{ + reader_test(true); + reader_test(false); + + return 0; +} diff --git a/ctdb/tests/src/pkt_write_test.c b/ctdb/tests/src/pkt_write_test.c new file mode 100644 index 0000000..dae92a5 --- /dev/null +++ b/ctdb/tests/src/pkt_write_test.c @@ -0,0 +1,359 @@ +/* + packet write tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include + +#include "lib/util/blocking.h" + +#include "common/pkt_read.c" +#include "common/pkt_write.c" + +struct writer_state { + struct tevent_context *ev; + int fd; + uint8_t *buf; + size_t buflen; + int count; + struct tevent_req *subreq; +}; + +static void writer_next(struct tevent_req *subreq); + +static struct tevent_req *writer_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, uint8_t *buf, size_t buflen) +{ + struct tevent_req *req, *subreq; + struct writer_state *state; + + req = tevent_req_create(mem_ctx, &state, struct writer_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->buf = buf; + state->buflen = buflen; + state->count = 0; + + subreq = pkt_write_send(state, state->ev, state->fd, + state->buf, state->buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, writer_next, req); + return req; +} + +static void writer_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct writer_state *state = tevent_req_data( + req, struct writer_state); + ssize_t nwritten; + int err = 0; + + nwritten = pkt_write_recv(subreq, &err); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (nwritten == -1) { + tevent_req_error(req, err); + return; + } + + if ((size_t)nwritten != state->buflen) { + tevent_req_error(req, EIO); + return; + } + + state->count++; + if (state->count >= 1000) { + tevent_req_done(req); + return; + } + + subreq = pkt_write_send(state, state->ev, state->fd, + state->buf, state->buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, writer_next, req); +} + +static void writer_recv(struct tevent_req *req, int *perr) +{ + struct writer_state *state = tevent_req_data( + req, struct writer_state); + int err = 0; + + if (state->subreq != NULL) { + *perr = -1; + return; + } + + if (tevent_req_is_unix_error(req, &err)) { + *perr = err; + return; + } + + *perr = 0; +} + +static void writer_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct writer_state *state = tevent_req_data( + req, struct writer_state); + + assert(state->subreq != NULL); + pkt_write_handler(ev, fde, flags, state->subreq); +} + +static void writer(int fd) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_fd *fde; + struct tevent_req *req; + uint8_t buf[1024*1024]; + size_t buflen; + size_t pkt_size[4] = { 100, 500, 1024, 1024*1024 }; + int i, err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + for (i=0; i<1024*1024; i++) { + buf[i] = i%256; + } + + for (i=0; i<4; i++) { + buflen = pkt_size[i]; + memcpy(buf, &buflen, sizeof(buflen)); + + req = writer_send(mem_ctx, ev, fd, buf, buflen); + assert(req != NULL); + + fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_WRITE, + writer_handler, req); + assert(fde != NULL); + + tevent_req_poll(req, ev); + + writer_recv(req, &err); + assert(err == 0); + + talloc_free(fde); + talloc_free(req); + } + + close(fd); + + talloc_free(mem_ctx); +} + +struct reader_state { + struct tevent_context *ev; + int fd; + uint8_t buf[1024]; + struct tevent_req *subreq; +}; + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data); +static void reader_done(struct tevent_req *subreq); + +static struct tevent_req *reader_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd) +{ + struct tevent_req *req, *subreq; + struct reader_state *state; + + req = tevent_req_create(mem_ctx, &state, struct reader_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, 1024, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + tevent_req_post(req, ev); + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); + return req; +} + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data) +{ + uint32_t pkt_len; + + if (buflen < sizeof(pkt_len)) { + return sizeof(pkt_len) - buflen; + } + + pkt_len = *(uint32_t *)buf; + return pkt_len - buflen; +} + +static void reader_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + ssize_t nread; + uint8_t *buf; + bool free_buf; + int err = 0; + + nread = pkt_read_recv(subreq, state, &buf, &free_buf, &err); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (nread == -1) { + if (err == EPIPE) { + tevent_req_done(req); + } else { + tevent_req_error(req, err); + } + return; + } + + if (free_buf) { + talloc_free(buf); + } + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, 1024, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + return; + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); +} + +static void reader_recv(struct tevent_req *req, int *perr) +{ + struct reader_state *state = tevent_req_data( + req, struct reader_state); + int err = 0; + + if (state->subreq != NULL) { + *perr = -1; + } + + if (tevent_req_is_unix_error(req, &err)) { + *perr = err; + return; + } + + *perr = 0; +} + +static void reader_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + + assert(state->subreq != NULL); + pkt_read_handler(ev, fde, flags, state->subreq); +} + +static void reader(int fd) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_fd *fde; + struct tevent_req *req; + int err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + req = reader_send(mem_ctx, ev, fd); + assert(req != NULL); + + fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_READ, + reader_handler, req); + assert(fde != NULL); + + tevent_req_poll(req, ev); + + reader_recv(req, &err); + assert(err == 0); + + close(fd); + + talloc_free(mem_ctx); +} + +int main(void) +{ + int fd[2]; + int ret; + pid_t pid; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Child process */ + close(fd[0]); + writer(fd[1]); + exit(0); + } + + close(fd[1]); + ret = set_blocking(fd[0], false); + if (ret == -1) { + exit(1); + } + + reader(fd[0]); + + return 0; +} diff --git a/ctdb/tests/src/porting_tests.c b/ctdb/tests/src/porting_tests.c new file mode 100644 index 0000000..00618d2 --- /dev/null +++ b/ctdb/tests/src/porting_tests.c @@ -0,0 +1,262 @@ +/* + Test porting lib (common/system_*.c) + + Copyright (C) Mathieu Parent 2013 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" + +#include +#include +#include +#include +#include + +#include "lib/util/debug.h" +#include "lib/util/blocking.h" + +#include "protocol/protocol.h" +#include "common/system.h" +#include "common/logging.h" + + +static struct { + const char *socketname; + const char *debuglevel; + pid_t helper_pid; + int socket; +} globals = { + .socketname = "/tmp/test.sock" +}; + + + +/* + Socket functions +*/ +/* + create a unix domain socket and bind it + return a file descriptor open on the socket +*/ +static int socket_server_create(void) +{ + struct sockaddr_un addr; + int ret; + + globals.socket = socket(AF_UNIX, SOCK_STREAM, 0); + assert(globals.socket != -1); + + set_close_on_exec(globals.socket); + //set_blocking(globals.socket, false); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, globals.socketname, sizeof(addr.sun_path)-1); + + ret = bind(globals.socket, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret == 0); + + ret = chown(globals.socketname, geteuid(), getegid()); + assert(ret == 0); + + ret = chmod(globals.socketname, 0700); + assert(ret == 0); + + ret = listen(globals.socket, 100); + assert(ret == 0); + + return 0; +} + +static int socket_server_wait_peer(void) +{ + struct sockaddr_un addr; + socklen_t len; + int fd; + + memset(&addr, 0, sizeof(addr)); + len = sizeof(addr); + fd = accept(globals.socket, (struct sockaddr *)&addr, &len); + assert(fd != -1); + + //set_blocking(fd, false); + set_close_on_exec(fd); + return fd; +} + +static int socket_server_close(void) +{ + int ret; + + ret = close(globals.socket); + assert(ret == 0); + + ret = unlink(globals.socketname); + assert(ret == 0); + + return 0; +} + +static int socket_client_connect(void) +{ + struct sockaddr_un addr; + int client = 0; + int ret; + + client = socket(AF_UNIX, SOCK_STREAM, 0); + assert(client != -1); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, globals.socketname, sizeof(addr.sun_path)-1); + + ret = connect(client, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret == 0); + + return client; +} + +static int socket_client_close(int client) +{ + int ret; + + ret = close(client); + assert(ret == 0); + + return 0; +} + +/* + forked program +*/ +static int fork_helper(void) +{ + pid_t pid; + int client; + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { // Child + pid = getppid(); + client = socket_client_connect(); + while (kill(pid, 0) == 0) { + sleep(1); + } + socket_client_close(client); + exit(0); + } else { + globals.helper_pid = pid; + } + return 0; +} + +/* + tests +*/ +static int test_ctdb_sys_check_iface_exists(void) +{ + bool test1, test2; + + test1 = ctdb_sys_check_iface_exists("unlikely123xyz"); + assert(!test1); + + /* Linux and others */ + test1 = ctdb_sys_check_iface_exists("lo"); + /* FreeBSD */ + test2 = ctdb_sys_check_iface_exists("lo0"); + assert(test1 || test2); + + return 0; +} + +static int test_ctdb_get_peer_pid(void) +{ + int ret; + int fd; + pid_t peer_pid = 0; + + fd = socket_server_wait_peer(); + + ret = ctdb_get_peer_pid(fd, &peer_pid); + assert(ret == 0 || ret == ENOSYS); + + if (ret == 0) { + assert(peer_pid == globals.helper_pid); + + kill(peer_pid, SIGTERM); + } else { + kill(globals.helper_pid, SIGTERM); + } + + close(fd); + return 0; +} + +/* + main program +*/ +int main(int argc, const char *argv[]) +{ + struct poptOption popt_options[] = { + POPT_AUTOHELP + { "socket", 0, POPT_ARG_STRING, &globals.socketname, 0, "local socket name", "filename" }, + POPT_TABLEEND + }; + int opt, ret; + const char **extra_argv; + int extra_argc = 0; + poptContext pc; + + pc = poptGetContext(argv[0], argc, argv, popt_options, POPT_CONTEXT_KEEP_FIRST); + + while ((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + default: + fprintf(stderr, "Invalid option %s: %s\n", + poptBadOption(pc, 0), poptStrerror(opt)); + exit(1); + } + } + + /* setup the remaining options for the main program to use */ + extra_argv = poptGetArgs(pc); + if (extra_argv) { + extra_argv++; + while (extra_argv[extra_argc]) extra_argc++; + } + + assert(globals.socketname != NULL); + + ret = socket_server_create(); + assert(ret == 0); + + /* FIXME: Test tcp_checksum6, tcp_checksum */ + /* FIXME: Test ctdb_sys_send_arp, ctdb_sys_send_tcp */ + /* FIXME: Test ctdb_sys_{open,close}_capture_socket, ctdb_sys_read_tcp_packet */ + test_ctdb_sys_check_iface_exists(); + + ret = fork_helper(); + assert(ret == 0); + test_ctdb_get_peer_pid(); + + ret = socket_server_close(); + assert(ret == 0); + + return 0; +} diff --git a/ctdb/tests/src/protocol_basic_test.c b/ctdb/tests/src/protocol_basic_test.c new file mode 100644 index 0000000..7046718 --- /dev/null +++ b/ctdb/tests/src/protocol_basic_test.c @@ -0,0 +1,106 @@ +/* + protocol types tests + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include + +#include "protocol/protocol_basic.c" + +#include "tests/src/protocol_common_basic.h" + +PROTOCOL_TYPE1_TEST(uint8_t, ctdb_uint8); +PROTOCOL_TYPE1_TEST(uint16_t, ctdb_uint16); +PROTOCOL_TYPE1_TEST(int32_t, ctdb_int32); +PROTOCOL_TYPE1_TEST(uint32_t, ctdb_uint32); +PROTOCOL_TYPE1_TEST(uint64_t, ctdb_uint64); +PROTOCOL_TYPE1_TEST(double, ctdb_double); +PROTOCOL_TYPE1_TEST(bool, ctdb_bool); + +static void test_ctdb_chararray(void) +{ + size_t len = rand_int(1000) + 1; + char p1[len], p2[len]; + size_t buflen, np = 0; + size_t i; + int ret; + + for (i=0; i. +*/ + +#include "replace.h" +#include "system/network.h" + +#include + +#include "protocol/protocol_api.h" + +#include "tests/src/protocol_common_basic.h" +#include "tests/src/protocol_common.h" + +void fill_tdb_data_nonnull(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + p->dsize = rand_int(1024) + 1; + p->dptr = talloc_array(mem_ctx, uint8_t, p->dsize); + assert(p->dptr != NULL); + fill_buffer(p->dptr, p->dsize); +} + +void fill_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + if (rand_int(5) == 0) { + p->dsize = 0; + p->dptr = NULL; + } else { + fill_tdb_data_nonnull(mem_ctx, p); + } +} + +void verify_tdb_data(TDB_DATA *p1, TDB_DATA *p2) +{ + assert(p1->dsize == p2->dsize); + verify_buffer(p1->dptr, p2->dptr, p1->dsize); +} + +void fill_ctdb_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + fill_tdb_data(mem_ctx, p); +} + +void verify_ctdb_tdb_data(TDB_DATA *p1, TDB_DATA *p2) +{ + verify_tdb_data(p1, p2); +} + +void fill_ctdb_tdb_datan(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + fill_tdb_data(mem_ctx, p); +} + +void verify_ctdb_tdb_datan(TDB_DATA *p1, TDB_DATA *p2) +{ + verify_tdb_data(p1, p2); +} + +void fill_ctdb_latency_counter(struct ctdb_latency_counter *p) +{ + p->num = rand32i(); + p->min = rand_double(); + p->max = rand_double(); + p->total = rand_double(); +} + +void verify_ctdb_latency_counter(struct ctdb_latency_counter *p1, + struct ctdb_latency_counter *p2) +{ + assert(p1->num == p2->num); + assert(p1->min == p2->min); + assert(p1->max == p2->max); + assert(p1->total == p2->total); +} + +void fill_ctdb_statistics(TALLOC_CTX *mem_ctx, struct ctdb_statistics *p) +{ + int i; + + p->num_clients = rand32(); + p->frozen = rand32(); + p->recovering = rand32(); + p->client_packets_sent = rand32(); + p->client_packets_recv = rand32(); + p->node_packets_sent = rand32(); + p->node_packets_recv = rand32(); + p->keepalive_packets_sent = rand32(); + p->keepalive_packets_recv = rand32(); + + p->node.req_call = rand32(); + p->node.reply_call = rand32(); + p->node.req_dmaster = rand32(); + p->node.reply_dmaster = rand32(); + p->node.reply_error = rand32(); + p->node.req_message = rand32(); + p->node.req_control = rand32(); + p->node.reply_control = rand32(); + + p->client.req_call = rand32(); + p->client.req_message = rand32(); + p->client.req_control = rand32(); + + p->timeouts.call = rand32(); + p->timeouts.control = rand32(); + p->timeouts.traverse = rand32(); + + fill_ctdb_latency_counter(&p->reclock.ctdbd); + fill_ctdb_latency_counter(&p->reclock.recd); + + p->locks.num_calls = rand32(); + p->locks.num_current = rand32(); + p->locks.num_pending = rand32(); + p->locks.num_failed = rand32(); + fill_ctdb_latency_counter(&p->locks.latency); + for (i=0; ilocks.buckets[i] = rand32(); + } + + p->total_calls = rand32(); + p->pending_calls = rand32(); + p->childwrite_calls = rand32(); + p->pending_childwrite_calls = rand32(); + p->memory_used = rand32(); + p->__last_counter = rand32(); + p->max_hop_count = rand32(); + for (i=0; ihop_count_bucket[i] = rand32(); + } + fill_ctdb_latency_counter(&p->call_latency); + fill_ctdb_latency_counter(&p->childwrite_latency); + p->num_recoveries = rand32(); + fill_ctdb_timeval(&p->statistics_start_time); + fill_ctdb_timeval(&p->statistics_current_time); + p->total_ro_delegations = rand32(); + p->total_ro_revokes = rand32(); +} + +void verify_ctdb_statistics(struct ctdb_statistics *p1, + struct ctdb_statistics *p2) +{ + int i; + + assert(p1->num_clients == p2->num_clients); + assert(p1->frozen == p2->frozen); + assert(p1->recovering == p2->recovering); + assert(p1->client_packets_sent == p2->client_packets_sent); + assert(p1->client_packets_recv == p2->client_packets_recv); + assert(p1->node_packets_sent == p2->node_packets_sent); + assert(p1->node_packets_recv == p2->node_packets_recv); + assert(p1->keepalive_packets_sent == p2->keepalive_packets_sent); + assert(p1->keepalive_packets_recv == p2->keepalive_packets_recv); + + assert(p1->node.req_call == p2->node.req_call); + assert(p1->node.reply_call == p2->node.reply_call); + assert(p1->node.req_dmaster == p2->node.req_dmaster); + assert(p1->node.reply_dmaster == p2->node.reply_dmaster); + assert(p1->node.reply_error == p2->node.reply_error); + assert(p1->node.req_message == p2->node.req_message); + assert(p1->node.req_control == p2->node.req_control); + assert(p1->node.reply_control == p2->node.reply_control); + + assert(p1->client.req_call == p2->client.req_call); + assert(p1->client.req_message == p2->client.req_message); + assert(p1->client.req_control == p2->client.req_control); + + assert(p1->timeouts.call == p2->timeouts.call); + assert(p1->timeouts.control == p2->timeouts.control); + assert(p1->timeouts.traverse == p2->timeouts.traverse); + + verify_ctdb_latency_counter(&p1->reclock.ctdbd, &p2->reclock.ctdbd); + verify_ctdb_latency_counter(&p1->reclock.recd, &p2->reclock.recd); + + assert(p1->locks.num_calls == p2->locks.num_calls); + assert(p1->locks.num_current == p2->locks.num_current); + assert(p1->locks.num_pending == p2->locks.num_pending); + assert(p1->locks.num_failed == p2->locks.num_failed); + verify_ctdb_latency_counter(&p1->locks.latency, &p2->locks.latency); + for (i=0; ilocks.buckets[i] == p2->locks.buckets[i]); + } + + assert(p1->total_calls == p2->total_calls); + assert(p1->pending_calls == p2->pending_calls); + assert(p1->childwrite_calls == p2->childwrite_calls); + assert(p1->pending_childwrite_calls == p2->pending_childwrite_calls); + assert(p1->memory_used == p2->memory_used); + assert(p1->__last_counter == p2->__last_counter); + assert(p1->max_hop_count == p2->max_hop_count); + for (i=0; ihop_count_bucket[i] == p2->hop_count_bucket[i]); + } + verify_ctdb_latency_counter(&p1->call_latency, &p2->call_latency); + verify_ctdb_latency_counter(&p1->childwrite_latency, + &p2->childwrite_latency); + assert(p1->num_recoveries == p2->num_recoveries); + verify_ctdb_timeval(&p1->statistics_start_time, + &p2->statistics_start_time); + verify_ctdb_timeval(&p1->statistics_current_time, + &p2->statistics_current_time); + assert(p1->total_ro_delegations == p2->total_ro_delegations); + assert(p1->total_ro_revokes == p2->total_ro_revokes); +} + +void fill_ctdb_vnn_map(TALLOC_CTX *mem_ctx, struct ctdb_vnn_map *p) +{ + unsigned int i; + + p->generation = rand32(); + p->size = rand_int(20); + if (p->size > 0) { + p->map = talloc_array(mem_ctx, uint32_t, p->size); + assert(p->map != NULL); + + for (i=0; isize; i++) { + p->map[i] = rand32(); + } + } else { + p->map = NULL; + } +} + +void verify_ctdb_vnn_map(struct ctdb_vnn_map *p1, struct ctdb_vnn_map *p2) +{ + unsigned int i; + + assert(p1->generation == p2->generation); + assert(p1->size == p2->size); + for (i=0; isize; i++) { + assert(p1->map[i] == p2->map[i]); + } +} + +void fill_ctdb_dbid(TALLOC_CTX *mem_ctx, struct ctdb_dbid *p) +{ + p->db_id = rand32(); + p->flags = rand8(); +} + +void verify_ctdb_dbid(struct ctdb_dbid *p1, struct ctdb_dbid *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->flags == p2->flags); +} + +void fill_ctdb_dbid_map(TALLOC_CTX *mem_ctx, struct ctdb_dbid_map *p) +{ + unsigned int i; + + p->num = rand_int(40); + if (p->num > 0) { + p->dbs = talloc_zero_array(mem_ctx, struct ctdb_dbid, p->num); + assert(p->dbs != NULL); + for (i=0; inum; i++) { + fill_ctdb_dbid(mem_ctx, &p->dbs[i]); + } + } else { + p->dbs = NULL; + } +} + +void verify_ctdb_dbid_map(struct ctdb_dbid_map *p1, struct ctdb_dbid_map *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; inum; i++) { + verify_ctdb_dbid(&p1->dbs[i], &p2->dbs[i]); + } +} + +void fill_ctdb_pulldb(TALLOC_CTX *mem_ctx, struct ctdb_pulldb *p) +{ + p->db_id = rand32(); + p->lmaster = rand32(); +} + +void verify_ctdb_pulldb(struct ctdb_pulldb *p1, struct ctdb_pulldb *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->lmaster == p2->lmaster); +} + +void fill_ctdb_pulldb_ext(TALLOC_CTX *mem_ctx, struct ctdb_pulldb_ext *p) +{ + p->db_id = rand32(); + p->lmaster = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_pulldb_ext(struct ctdb_pulldb_ext *p1, + struct ctdb_pulldb_ext *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->lmaster == p2->lmaster); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_db_vacuum(TALLOC_CTX *mem_ctx, struct ctdb_db_vacuum *p) +{ + fill_ctdb_uint32(&p->db_id); + fill_ctdb_bool(&p->full_vacuum_run); +} + +void verify_ctdb_db_vacuum(struct ctdb_db_vacuum *p1, + struct ctdb_db_vacuum *p2) +{ + verify_ctdb_uint32(&p1->db_id, &p2->db_id); + verify_ctdb_bool(&p1->full_vacuum_run, &p2->full_vacuum_run); +} + +void fill_ctdb_echo_data(TALLOC_CTX *mem_ctx, struct ctdb_echo_data *p) +{ + fill_ctdb_uint32(&p->timeout); + fill_tdb_data(mem_ctx, &p->buf); +} + +void verify_ctdb_echo_data(struct ctdb_echo_data *p1, + struct ctdb_echo_data *p2) +{ + verify_ctdb_uint32(&p1->timeout, &p2->timeout); + verify_tdb_data(&p1->buf, &p2->buf); +} + +void fill_ctdb_ltdb_header(struct ctdb_ltdb_header *p) +{ + p->rsn = rand64(); + p->dmaster = rand32(); + p->reserved1 = rand32(); + p->flags = rand32(); +} + +void verify_ctdb_ltdb_header(struct ctdb_ltdb_header *p1, + struct ctdb_ltdb_header *p2) +{ + assert(p1->rsn == p2->rsn); + assert(p1->dmaster == p2->dmaster); + assert(p1->reserved1 == p2->reserved1); + assert(p1->flags == p2->flags); +} + +void fill_ctdb_rec_data(TALLOC_CTX *mem_ctx, struct ctdb_rec_data *p) +{ + p->reqid = rand32(); + if (p->reqid % 5 == 0) { + p->header = talloc(mem_ctx, struct ctdb_ltdb_header); + assert(p->header != NULL); + fill_ctdb_ltdb_header(p->header); + } else { + p->header = NULL; + } + fill_tdb_data_nonnull(mem_ctx, &p->key); + fill_tdb_data(mem_ctx, &p->data); +} + +void verify_ctdb_rec_data(struct ctdb_rec_data *p1, struct ctdb_rec_data *p2) +{ + struct ctdb_ltdb_header header; + + assert(p1->reqid == p2->reqid); + if (p1->header != NULL) { + assert(ctdb_ltdb_header_extract(&p2->data, &header) == 0); + verify_ctdb_ltdb_header(p1->header, &header); + } + verify_tdb_data(&p1->key, &p2->key); + verify_tdb_data(&p1->data, &p2->data); +} + +void fill_ctdb_rec_buffer(TALLOC_CTX *mem_ctx, struct ctdb_rec_buffer *p) +{ + struct ctdb_rec_data rec; + int ret, i; + int count; + + p->db_id = rand32(); + p->count = 0; + p->buf = NULL; + p->buflen = 0; + + count = rand_int(100); + if (count > 0) { + for (i=0; idb_id == p2->db_id); + assert(p1->count == p2->count); + assert(p1->buflen == p2->buflen); + verify_buffer(p1->buf, p2->buf, p1->buflen); +} + +void fill_ctdb_traverse_start(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_traverse_start(struct ctdb_traverse_start *p1, + struct ctdb_traverse_start *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_traverse_all(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->pnn = rand32(); + p->client_reqid = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_traverse_all(struct ctdb_traverse_all *p1, + struct ctdb_traverse_all *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->pnn == p2->pnn); + assert(p1->client_reqid == p2->client_reqid); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_traverse_start_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start_ext *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->srvid = rand64(); + p->withemptyrecords = rand_int(2); +} + +void verify_ctdb_traverse_start_ext(struct ctdb_traverse_start_ext *p1, + struct ctdb_traverse_start_ext *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->srvid == p2->srvid); + assert(p1->withemptyrecords == p2->withemptyrecords); +} + +void fill_ctdb_traverse_all_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all_ext *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->pnn = rand32(); + p->client_reqid = rand32(); + p->srvid = rand64(); + p->withemptyrecords = rand_int(2); +} + +void verify_ctdb_traverse_all_ext(struct ctdb_traverse_all_ext *p1, + struct ctdb_traverse_all_ext *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->pnn == p2->pnn); + assert(p1->client_reqid == p2->client_reqid); + assert(p1->srvid == p2->srvid); + assert(p1->withemptyrecords == p2->withemptyrecords); +} + +void fill_ctdb_sock_addr(TALLOC_CTX *mem_ctx, ctdb_sock_addr *p) +{ + if (rand_int(2) == 0) { + p->ip.sin_family = AF_INET; + p->ip.sin_port = rand_int(65535); + fill_buffer(&p->ip.sin_addr, sizeof(struct in_addr)); + } else { + p->ip6.sin6_family = AF_INET6; + p->ip6.sin6_port = rand_int(65535); + fill_buffer(&p->ip6.sin6_addr, sizeof(struct in6_addr)); + } +} + +void verify_ctdb_sock_addr(ctdb_sock_addr *p1, ctdb_sock_addr *p2) +{ + assert(p1->sa.sa_family == p2->sa.sa_family); + if (p1->sa.sa_family == AF_INET) { + assert(p1->ip.sin_port == p2->ip.sin_port); + verify_buffer(&p1->ip.sin_addr, &p2->ip.sin_addr, + sizeof(struct in_addr)); + } else { + assert(p1->ip6.sin6_port == p2->ip6.sin6_port); + verify_buffer(&p1->ip6.sin6_addr, &p2->ip6.sin6_addr, + sizeof(struct in6_addr)); + } +} + +void fill_ctdb_connection(TALLOC_CTX *mem_ctx, struct ctdb_connection *p) +{ + fill_ctdb_sock_addr(mem_ctx, &p->src); + fill_ctdb_sock_addr(mem_ctx, &p->dst); +} + +void verify_ctdb_connection(struct ctdb_connection *p1, + struct ctdb_connection *p2) +{ + verify_ctdb_sock_addr(&p1->src, &p2->src); + verify_ctdb_sock_addr(&p1->dst, &p2->dst); +} + +void fill_ctdb_connection_list(TALLOC_CTX *mem_ctx, + struct ctdb_connection_list *p) +{ + uint32_t i; + + p->num = rand_int(1000); + if (p->num > 0) { + p->conn = talloc_array(mem_ctx, struct ctdb_connection, p->num); + assert(p->conn != NULL); + for (i=0; inum; i++) { + fill_ctdb_connection(mem_ctx, &p->conn[i]); + } + } else { + p->conn = NULL; + } +} + +void verify_ctdb_connection_list(struct ctdb_connection_list *p1, + struct ctdb_connection_list *p2) +{ + uint32_t i; + + assert(p1->num == p2->num); + for (i=0; inum; i++) { + verify_ctdb_connection(&p1->conn[i], &p2->conn[i]); + } +} + +void fill_ctdb_tunable(TALLOC_CTX *mem_ctx, struct ctdb_tunable *p) +{ + fill_ctdb_string(mem_ctx, &p->name); + p->value = rand32(); +} + +void verify_ctdb_tunable(struct ctdb_tunable *p1, struct ctdb_tunable *p2) +{ + verify_ctdb_string(&p1->name, &p2->name); + assert(p1->value == p2->value); +} + +void fill_ctdb_node_flag_change(TALLOC_CTX *mem_ctx, + struct ctdb_node_flag_change *p) +{ + p->pnn = rand32(); + p->new_flags = rand32(); + p->old_flags = rand32(); +} + +void verify_ctdb_node_flag_change(struct ctdb_node_flag_change *p1, + struct ctdb_node_flag_change *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->new_flags == p2->new_flags); + assert(p1->old_flags == p2->old_flags); +} + +void fill_ctdb_var_list(TALLOC_CTX *mem_ctx, struct ctdb_var_list *p) +{ + int i; + + p->count = rand_int(100) + 1; + p->var = talloc_array(mem_ctx, const char *, p->count); + for (i=0; icount; i++) { + fill_ctdb_string(p->var, &p->var[i]); + } +} + +void verify_ctdb_var_list(struct ctdb_var_list *p1, struct ctdb_var_list *p2) +{ + int i; + + assert(p1->count == p2->count); + for (i=0; icount; i++) { + verify_ctdb_string(&p1->var[i], &p2->var[i]); + } +} + +void fill_ctdb_tunable_list(TALLOC_CTX *mem_ctx, struct ctdb_tunable_list *p) +{ + p->max_redirect_count = rand32(); + p->seqnum_interval = rand32(); + p->control_timeout = rand32(); + p->traverse_timeout = rand32(); + p->keepalive_interval = rand32(); + p->keepalive_limit = rand32(); + p->recover_timeout = rand32(); + p->recover_interval = rand32(); + p->election_timeout = rand32(); + p->takeover_timeout = rand32(); + p->monitor_interval = rand32(); + p->tickle_update_interval = rand32(); + p->script_timeout = rand32(); + p->monitor_timeout_count = rand32(); + p->script_unhealthy_on_timeout = rand32(); + p->recovery_grace_period = rand32(); + p->recovery_ban_period = rand32(); + p->database_hash_size = rand32(); + p->database_max_dead = rand32(); + p->rerecovery_timeout = rand32(); + p->enable_bans = rand32(); + p->deterministic_public_ips = rand32(); + p->reclock_ping_period = rand32(); + p->no_ip_failback = rand32(); + p->disable_ip_failover = rand32(); + p->verbose_memory_names = rand32(); + p->recd_ping_timeout = rand32(); + p->recd_ping_failcount = rand32(); + p->log_latency_ms = rand32(); + p->reclock_latency_ms = rand32(); + p->recovery_drop_all_ips = rand32(); + p->verify_recovery_lock = rand32(); + p->vacuum_interval = rand32(); + p->vacuum_max_run_time = rand32(); + p->repack_limit = rand32(); + p->vacuum_limit = rand32(); + p->max_queue_depth_drop_msg = rand32(); + p->allow_unhealthy_db_read = rand32(); + p->stat_history_interval = rand32(); + p->deferred_attach_timeout = rand32(); + p->vacuum_fast_path_count = rand32(); + p->lcp2_public_ip_assignment = rand32(); + p->allow_client_db_attach = rand32(); + p->recover_pdb_by_seqnum = rand32(); + p->deferred_rebalance_on_node_add = rand32(); + p->fetch_collapse = rand32(); + p->hopcount_make_sticky = rand32(); + p->sticky_duration = rand32(); + p->sticky_pindown = rand32(); + p->no_ip_takeover = rand32(); + p->db_record_count_warn = rand32(); + p->db_record_size_warn = rand32(); + p->db_size_warn = rand32(); + p->pulldb_preallocation_size = rand32(); + p->no_ip_host_on_all_disabled = rand32(); + p->samba3_hack = rand32(); + p->mutex_enabled = rand32(); + p->lock_processes_per_db = rand32(); + p->rec_buffer_size_limit = rand32(); + p->queue_buffer_size = rand32(); + p->ip_alloc_algorithm = rand32(); + p->allow_mixed_versions = rand32(); +} + +void verify_ctdb_tunable_list(struct ctdb_tunable_list *p1, + struct ctdb_tunable_list *p2) +{ + assert(p1->max_redirect_count == p2->max_redirect_count); + assert(p1->seqnum_interval == p2->seqnum_interval); + assert(p1->control_timeout == p2->control_timeout); + assert(p1->traverse_timeout == p2->traverse_timeout); + assert(p1->keepalive_interval == p2->keepalive_interval); + assert(p1->keepalive_limit == p2->keepalive_limit); + assert(p1->recover_timeout == p2->recover_timeout); + assert(p1->recover_interval == p2->recover_interval); + assert(p1->election_timeout == p2->election_timeout); + assert(p1->takeover_timeout == p2->takeover_timeout); + assert(p1->monitor_interval == p2->monitor_interval); + assert(p1->tickle_update_interval == p2->tickle_update_interval); + assert(p1->script_timeout == p2->script_timeout); + assert(p1->monitor_timeout_count == p2->monitor_timeout_count); + assert(p1->script_unhealthy_on_timeout == p2->script_unhealthy_on_timeout); + assert(p1->recovery_grace_period == p2->recovery_grace_period); + assert(p1->recovery_ban_period == p2->recovery_ban_period); + assert(p1->database_hash_size == p2->database_hash_size); + assert(p1->database_max_dead == p2->database_max_dead); + assert(p1->rerecovery_timeout == p2->rerecovery_timeout); + assert(p1->enable_bans == p2->enable_bans); + assert(p1->deterministic_public_ips == p2->deterministic_public_ips); + assert(p1->reclock_ping_period == p2->reclock_ping_period); + assert(p1->no_ip_failback == p2->no_ip_failback); + assert(p1->disable_ip_failover == p2->disable_ip_failover); + assert(p1->verbose_memory_names == p2->verbose_memory_names); + assert(p1->recd_ping_timeout == p2->recd_ping_timeout); + assert(p1->recd_ping_failcount == p2->recd_ping_failcount); + assert(p1->log_latency_ms == p2->log_latency_ms); + assert(p1->reclock_latency_ms == p2->reclock_latency_ms); + assert(p1->recovery_drop_all_ips == p2->recovery_drop_all_ips); + assert(p1->verify_recovery_lock == p2->verify_recovery_lock); + assert(p1->vacuum_interval == p2->vacuum_interval); + assert(p1->vacuum_max_run_time == p2->vacuum_max_run_time); + assert(p1->repack_limit == p2->repack_limit); + assert(p1->vacuum_limit == p2->vacuum_limit); + assert(p1->max_queue_depth_drop_msg == p2->max_queue_depth_drop_msg); + assert(p1->allow_unhealthy_db_read == p2->allow_unhealthy_db_read); + assert(p1->stat_history_interval == p2->stat_history_interval); + assert(p1->deferred_attach_timeout == p2->deferred_attach_timeout); + assert(p1->vacuum_fast_path_count == p2->vacuum_fast_path_count); + assert(p1->lcp2_public_ip_assignment == p2->lcp2_public_ip_assignment); + assert(p1->allow_client_db_attach == p2->allow_client_db_attach); + assert(p1->recover_pdb_by_seqnum == p2->recover_pdb_by_seqnum); + assert(p1->deferred_rebalance_on_node_add == p2->deferred_rebalance_on_node_add); + assert(p1->fetch_collapse == p2->fetch_collapse); + assert(p1->hopcount_make_sticky == p2->hopcount_make_sticky); + assert(p1->sticky_duration == p2->sticky_duration); + assert(p1->sticky_pindown == p2->sticky_pindown); + assert(p1->no_ip_takeover == p2->no_ip_takeover); + assert(p1->db_record_count_warn == p2->db_record_count_warn); + assert(p1->db_record_size_warn == p2->db_record_size_warn); + assert(p1->db_size_warn == p2->db_size_warn); + assert(p1->pulldb_preallocation_size == p2->pulldb_preallocation_size); + assert(p1->no_ip_host_on_all_disabled == p2->no_ip_host_on_all_disabled); + assert(p1->samba3_hack == p2->samba3_hack); + assert(p1->mutex_enabled == p2->mutex_enabled); + assert(p1->lock_processes_per_db == p2->lock_processes_per_db); + assert(p1->rec_buffer_size_limit == p2->rec_buffer_size_limit); + assert(p1->queue_buffer_size == p2->queue_buffer_size); + assert(p1->ip_alloc_algorithm == p2->ip_alloc_algorithm); + assert(p1->allow_mixed_versions == p2->allow_mixed_versions); +} + +void fill_ctdb_tickle_list(TALLOC_CTX *mem_ctx, struct ctdb_tickle_list *p) +{ + unsigned int i; + + fill_ctdb_sock_addr(mem_ctx, &p->addr); + p->num = rand_int(1000); + if (p->num > 0) { + p->conn = talloc_array(mem_ctx, struct ctdb_connection, p->num); + assert(p->conn != NULL); + for (i=0; inum; i++) { + fill_ctdb_connection(mem_ctx, &p->conn[i]); + } + } else { + p->conn = NULL; + } +} + +void verify_ctdb_tickle_list(struct ctdb_tickle_list *p1, + struct ctdb_tickle_list *p2) +{ + unsigned int i; + + verify_ctdb_sock_addr(&p1->addr, &p2->addr); + assert(p1->num == p2->num); + for (i=0; inum; i++) { + verify_ctdb_connection(&p1->conn[i], &p2->conn[i]); + } +} + +void fill_ctdb_addr_info(TALLOC_CTX *mem_ctx, struct ctdb_addr_info *p) +{ + fill_ctdb_sock_addr(mem_ctx, &p->addr); + p->mask = rand_int(33); + if (rand_int(2) == 0) { + p->iface = NULL; + } else { + fill_ctdb_string(mem_ctx, &p->iface); + } +} + +void verify_ctdb_addr_info(struct ctdb_addr_info *p1, + struct ctdb_addr_info *p2) +{ + verify_ctdb_sock_addr(&p1->addr, &p2->addr); + assert(p1->mask == p2->mask); + verify_ctdb_string(&p1->iface, &p2->iface); +} + +void fill_ctdb_transdb(TALLOC_CTX *mem_ctx, struct ctdb_transdb *p) +{ + p->db_id = rand32(); + p->tid = rand32(); +} + +void verify_ctdb_transdb(struct ctdb_transdb *p1, struct ctdb_transdb *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->tid == p2->tid); +} + +void fill_ctdb_uptime(TALLOC_CTX *mem_ctx, struct ctdb_uptime *p) +{ + fill_ctdb_timeval(&p->current_time); + fill_ctdb_timeval(&p->ctdbd_start_time); + fill_ctdb_timeval(&p->last_recovery_started); + fill_ctdb_timeval(&p->last_recovery_finished); +} + +void verify_ctdb_uptime(struct ctdb_uptime *p1, struct ctdb_uptime *p2) +{ + verify_ctdb_timeval(&p1->current_time, &p2->current_time); + verify_ctdb_timeval(&p1->ctdbd_start_time, &p2->ctdbd_start_time); + verify_ctdb_timeval(&p1->last_recovery_started, + &p2->last_recovery_started); + verify_ctdb_timeval(&p1->last_recovery_finished, + &p2->last_recovery_finished); +} + +void fill_ctdb_public_ip(TALLOC_CTX *mem_ctx, struct ctdb_public_ip *p) +{ + p->pnn = rand32(); + fill_ctdb_sock_addr(mem_ctx, &p->addr); +} + +void verify_ctdb_public_ip(struct ctdb_public_ip *p1, + struct ctdb_public_ip *p2) +{ + assert(p1->pnn == p2->pnn); + verify_ctdb_sock_addr(&p1->addr, &p2->addr); +} + +void fill_ctdb_public_ip_list(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list *p) +{ + unsigned int i; + + p->num = rand_int(32); + if (p->num > 0) { + p->ip = talloc_array(mem_ctx, struct ctdb_public_ip, p->num); + assert(p->ip != NULL); + for (i=0; inum; i++) { + fill_ctdb_public_ip(mem_ctx, &p->ip[i]); + } + } else { + p->ip = NULL; + } +} + +void verify_ctdb_public_ip_list(struct ctdb_public_ip_list *p1, + struct ctdb_public_ip_list *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; inum; i++) { + verify_ctdb_public_ip(&p1->ip[i], &p2->ip[i]); + } +} + +void fill_ctdb_node_and_flags(TALLOC_CTX *mem_ctx, + struct ctdb_node_and_flags *p) +{ + p->pnn = rand32(); + p->flags = rand32(); + fill_ctdb_sock_addr(mem_ctx, &p->addr); +} + +void verify_ctdb_node_and_flags(struct ctdb_node_and_flags *p1, + struct ctdb_node_and_flags *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->flags == p2->flags); + verify_ctdb_sock_addr(&p1->addr, &p2->addr); +} + +void fill_ctdb_node_map(TALLOC_CTX *mem_ctx, struct ctdb_node_map *p) +{ + unsigned int i; + + p->num = rand_int(32); + if (p->num > 0) { + p->node = talloc_array(mem_ctx, struct ctdb_node_and_flags, + p->num); + assert(p->node != NULL); + for (i=0; inum; i++) { + fill_ctdb_node_and_flags(mem_ctx, &p->node[i]); + } + } else { + p->node = NULL; + } +} + +void verify_ctdb_node_map(struct ctdb_node_map *p1, struct ctdb_node_map *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; inum; i++) { + verify_ctdb_node_and_flags(&p1->node[i], &p2->node[i]); + } +} + +void fill_ctdb_script(TALLOC_CTX *mem_ctx, struct ctdb_script *p) +{ + fill_string(p->name, MAX_SCRIPT_NAME+1); + fill_ctdb_timeval(&p->start); + fill_ctdb_timeval(&p->finished); + p->status = rand32i(); + fill_string(p->output, MAX_SCRIPT_OUTPUT+1); +} + +void verify_ctdb_script(struct ctdb_script *p1, struct ctdb_script *p2) +{ + verify_string(p1->name, p2->name); + verify_ctdb_timeval(&p1->start, &p2->start); + verify_ctdb_timeval(&p1->finished, &p2->finished); + assert(p1->status == p2->status); + verify_string(p1->output, p2->output); +} + +void fill_ctdb_script_list(TALLOC_CTX *mem_ctx, struct ctdb_script_list *p) +{ + unsigned int i; + + p->num_scripts = rand_int(32); + if (p->num_scripts > 0) { + p->script = talloc_zero_array(mem_ctx, struct ctdb_script, + p->num_scripts); + assert(p->script != NULL); + for (i=0; inum_scripts; i++) { + fill_ctdb_script(mem_ctx, &p->script[i]); + } + } else { + p->script = NULL; + } +} + +void verify_ctdb_script_list(struct ctdb_script_list *p1, + struct ctdb_script_list *p2) +{ + unsigned int i; + + assert(p1->num_scripts == p2->num_scripts); + for (i=0; inum_scripts; i++) { + verify_ctdb_script(&p1->script[i], &p2->script[i]); + } +} + +void fill_ctdb_ban_state(TALLOC_CTX *mem_ctx, struct ctdb_ban_state *p) +{ + p->pnn = rand32(); + p->time = rand32(); +} + +void verify_ctdb_ban_state(struct ctdb_ban_state *p1, + struct ctdb_ban_state *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->time == p2->time); +} + +void fill_ctdb_notify_data(TALLOC_CTX *mem_ctx, struct ctdb_notify_data *p) +{ + p->srvid = rand64(); + fill_tdb_data(mem_ctx, &p->data); +} + +void verify_ctdb_notify_data(struct ctdb_notify_data *p1, + struct ctdb_notify_data *p2) +{ + assert(p1->srvid == p2->srvid); + verify_tdb_data(&p1->data, &p2->data); +} + +void fill_ctdb_iface(TALLOC_CTX *mem_ctx, struct ctdb_iface *p) +{ + fill_string(p->name, CTDB_IFACE_SIZE+2); + p->link_state = rand16(); + p->references = rand32(); +} + +void verify_ctdb_iface(struct ctdb_iface *p1, struct ctdb_iface *p2) +{ + verify_string(p1->name, p2->name); + assert(p1->link_state == p2->link_state); + assert(p1->references == p2->references); +} + +void fill_ctdb_iface_list(TALLOC_CTX *mem_ctx, struct ctdb_iface_list *p) +{ + unsigned int i; + + p->num = rand_int(32); + if (p->num > 0) { + p->iface = talloc_array(mem_ctx, struct ctdb_iface, p->num); + assert(p->iface != NULL); + for (i=0; inum; i++) { + fill_ctdb_iface(mem_ctx, &p->iface[i]); + } + } else { + p->iface = NULL; + } +} + +void verify_ctdb_iface_list(struct ctdb_iface_list *p1, + struct ctdb_iface_list *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; inum; i++) { + verify_ctdb_iface(&p1->iface[i], &p2->iface[i]); + } +} + +void fill_ctdb_public_ip_info(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_info *p) +{ + fill_ctdb_public_ip(mem_ctx, &p->ip); + p->active_idx = rand_int(32) + 1; + p->ifaces = talloc(mem_ctx, struct ctdb_iface_list); + assert(p->ifaces != NULL); + fill_ctdb_iface_list(mem_ctx, p->ifaces); +} + +void verify_ctdb_public_ip_info(struct ctdb_public_ip_info *p1, + struct ctdb_public_ip_info *p2) +{ + verify_ctdb_public_ip(&p1->ip, &p2->ip); + assert(p1->active_idx == p2->active_idx); + verify_ctdb_iface_list(p1->ifaces, p2->ifaces); +} + +void fill_ctdb_statistics_list(TALLOC_CTX *mem_ctx, + struct ctdb_statistics_list *p) +{ + int i; + + p->num = rand_int(10); + if (p->num > 0) { + p->stats = talloc_zero_array(mem_ctx, struct ctdb_statistics, + p->num); + assert(p->stats != NULL); + + for (i=0; inum; i++) { + fill_ctdb_statistics(mem_ctx, &p->stats[i]); + } + } else { + p->stats = NULL; + } +} + +void verify_ctdb_statistics_list(struct ctdb_statistics_list *p1, + struct ctdb_statistics_list *p2) +{ + int i; + + assert(p1->num == p2->num); + for (i=0; inum; i++) { + verify_ctdb_statistics(&p1->stats[i], &p2->stats[i]); + } +} + +void fill_ctdb_key_data(TALLOC_CTX *mem_ctx, struct ctdb_key_data *p) +{ + p->db_id = rand32(); + fill_ctdb_ltdb_header(&p->header); + fill_tdb_data_nonnull(mem_ctx, &p->key); +} + +void verify_ctdb_key_data(struct ctdb_key_data *p1, struct ctdb_key_data *p2) +{ + assert(p1->db_id == p2->db_id); + verify_ctdb_ltdb_header(&p1->header, &p2->header); + verify_tdb_data(&p1->key, &p2->key); +} + +void fill_ctdb_db_statistics(TALLOC_CTX *mem_ctx, + struct ctdb_db_statistics *p) +{ + unsigned int i; + + p->locks.num_calls = rand32(); + p->locks.num_current = rand32(); + p->locks.num_pending = rand32(); + p->locks.num_failed = rand32(); + fill_ctdb_latency_counter(&p->locks.latency); + for (i=0; ilocks.buckets[i] = rand32(); + } + + fill_ctdb_latency_counter(&p->vacuum.latency); + + p->db_ro_delegations = rand32(); + p->db_ro_revokes = rand32(); + for (i=0; ihop_count_bucket[i] = rand32(); + } + + p->num_hot_keys = MAX_HOT_KEYS; + for (i=0; inum_hot_keys; i++) { + p->hot_keys[i].count = rand32(); + fill_tdb_data(mem_ctx, &p->hot_keys[i].key); + } +} + +void verify_ctdb_db_statistics(struct ctdb_db_statistics *p1, + struct ctdb_db_statistics *p2) +{ + unsigned int i; + + assert(p1->locks.num_calls == p2->locks.num_calls); + assert(p1->locks.num_current == p2->locks.num_current); + assert(p1->locks.num_pending == p2->locks.num_pending); + assert(p1->locks.num_failed == p2->locks.num_failed); + verify_ctdb_latency_counter(&p1->locks.latency, &p2->locks.latency); + for (i=0; ilocks.buckets[i] == p2->locks.buckets[i]); + } + + verify_ctdb_latency_counter(&p1->vacuum.latency, &p2->vacuum.latency); + + assert(p1->db_ro_delegations == p2->db_ro_delegations); + assert(p1->db_ro_revokes == p2->db_ro_revokes); + for (i=0; ihop_count_bucket[i] == p2->hop_count_bucket[i]); + } + + assert(p1->num_hot_keys == p2->num_hot_keys); + for (i=0; inum_hot_keys; i++) { + assert(p1->hot_keys[i].count == p2->hot_keys[i].count); + verify_tdb_data(&p1->hot_keys[i].key, &p2->hot_keys[i].key); + } +} + +void fill_ctdb_pid_srvid(TALLOC_CTX *mem_ctx, struct ctdb_pid_srvid *p) +{ + p->pid = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_pid_srvid(struct ctdb_pid_srvid *p1, + struct ctdb_pid_srvid *p2) +{ + assert(p1->pid == p2->pid); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_election_message(TALLOC_CTX *mem_ctx, + struct ctdb_election_message *p) +{ + p->num_connected = rand_int(32); + fill_ctdb_timeval(&p->priority_time); + p->pnn = rand_int(32); + p->node_flags = rand32(); +} + +void verify_ctdb_election_message(struct ctdb_election_message *p1, + struct ctdb_election_message *p2) +{ + assert(p1->num_connected == p2->num_connected); + verify_ctdb_timeval(&p1->priority_time, &p2->priority_time); + assert(p1->pnn == p2->pnn); + assert(p1->node_flags == p2->node_flags); +} + +void fill_ctdb_srvid_message(TALLOC_CTX *mem_ctx, + struct ctdb_srvid_message *p) +{ + p->pnn = rand_int(32); + p->srvid = rand64(); +} + +void verify_ctdb_srvid_message(struct ctdb_srvid_message *p1, + struct ctdb_srvid_message *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_disable_message(TALLOC_CTX *mem_ctx, + struct ctdb_disable_message *p) +{ + p->pnn = rand_int(32); + p->srvid = rand64(); + p->timeout = rand32(); +} + +void verify_ctdb_disable_message(struct ctdb_disable_message *p1, + struct ctdb_disable_message *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->srvid == p2->srvid); + assert(p1->timeout == p2->timeout); +} + +void fill_ctdb_server_id(struct ctdb_server_id *p) +{ + p->pid = rand64(); + p->task_id = rand32(); + p->vnn = rand_int(32); + p->unique_id = rand64(); +} + +void verify_ctdb_server_id(struct ctdb_server_id *p1, + struct ctdb_server_id *p2) +{ + assert(p1->pid == p2->pid); + assert(p1->task_id == p2->task_id); + assert(p1->vnn == p2->vnn); + assert(p1->unique_id == p2->unique_id); +} + +void fill_ctdb_g_lock(struct ctdb_g_lock *p) +{ + p->type = rand_int(2); + fill_ctdb_server_id(&p->sid); +} + +void verify_ctdb_g_lock(struct ctdb_g_lock *p1, struct ctdb_g_lock *p2) +{ + assert(p1->type == p2->type); + verify_ctdb_server_id(&p1->sid, &p2->sid); +} + +void fill_ctdb_g_lock_list(TALLOC_CTX *mem_ctx, struct ctdb_g_lock_list *p) +{ + unsigned int i; + + p->num = rand_int(20) + 1; + p->lock = talloc_zero_array(mem_ctx, struct ctdb_g_lock, p->num); + assert(p->lock != NULL); + for (i=0; inum; i++) { + fill_ctdb_g_lock(&p->lock[i]); + } +} + +void verify_ctdb_g_lock_list(struct ctdb_g_lock_list *p1, + struct ctdb_g_lock_list *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; inum; i++) { + verify_ctdb_g_lock(&p1->lock[i], &p2->lock[i]); + } +} + +void fill_sock_packet_header(struct sock_packet_header *p) +{ + p->length = rand32(); + p->reqid = rand32(); +} + +void verify_sock_packet_header(struct sock_packet_header *p1, + struct sock_packet_header *p2) +{ + assert(p1->length == p2->length); + assert(p1->reqid == p2->reqid); +} diff --git a/ctdb/tests/src/protocol_common.h b/ctdb/tests/src/protocol_common.h new file mode 100644 index 0000000..171b19b --- /dev/null +++ b/ctdb/tests/src/protocol_common.h @@ -0,0 +1,238 @@ +/* + protocol tests - common functions + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#ifndef __CTDB_PROTOCOL_COMMON_H__ +#define __CTDB_PROTOCOL_COMMON_H__ + +#include "replace.h" +#include "system/network.h" + +#include +#include + +#include "protocol/protocol.h" + +#include "tests/src/protocol_common_basic.h" + +void fill_tdb_data_nonnull(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void fill_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void verify_tdb_data(TDB_DATA *p1, TDB_DATA *p2); + +void fill_ctdb_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void verify_ctdb_tdb_data(TDB_DATA *p1, TDB_DATA *p2); + +void fill_ctdb_tdb_datan(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void verify_ctdb_tdb_datan(TDB_DATA *p1, TDB_DATA *p2); + +void fill_ctdb_latency_counter(struct ctdb_latency_counter *p); +void verify_ctdb_latency_counter(struct ctdb_latency_counter *p1, + struct ctdb_latency_counter *p2); + +void fill_ctdb_statistics(TALLOC_CTX *mem_ctx, struct ctdb_statistics *p); +void verify_ctdb_statistics(struct ctdb_statistics *p1, + struct ctdb_statistics *p2); + +void fill_ctdb_vnn_map(TALLOC_CTX *mem_ctx, struct ctdb_vnn_map *p); +void verify_ctdb_vnn_map(struct ctdb_vnn_map *p1, struct ctdb_vnn_map *p2); + +void fill_ctdb_dbid(TALLOC_CTX *mem_ctx, struct ctdb_dbid *p); +void verify_ctdb_dbid(struct ctdb_dbid *p1, struct ctdb_dbid *p2); + +void fill_ctdb_dbid_map(TALLOC_CTX *mem_ctx, struct ctdb_dbid_map *p); +void verify_ctdb_dbid_map(struct ctdb_dbid_map *p1, struct ctdb_dbid_map *p2); + +void fill_ctdb_pulldb(TALLOC_CTX *mem_ctx, struct ctdb_pulldb *p); +void verify_ctdb_pulldb(struct ctdb_pulldb *p1, struct ctdb_pulldb *p2); + +void fill_ctdb_pulldb_ext(TALLOC_CTX *mem_ctx, struct ctdb_pulldb_ext *p); +void verify_ctdb_pulldb_ext(struct ctdb_pulldb_ext *p1, + struct ctdb_pulldb_ext *p2); + +void fill_ctdb_db_vacuum(TALLOC_CTX *mem_ctx, struct ctdb_db_vacuum *p); +void verify_ctdb_db_vacuum(struct ctdb_db_vacuum *p1, + struct ctdb_db_vacuum *p2); + +void fill_ctdb_echo_data(TALLOC_CTX *mem_ctx, struct ctdb_echo_data *p); +void verify_ctdb_echo_data(struct ctdb_echo_data *p1, + struct ctdb_echo_data *p2); + +void fill_ctdb_ltdb_header(struct ctdb_ltdb_header *p); +void verify_ctdb_ltdb_header(struct ctdb_ltdb_header *p1, + struct ctdb_ltdb_header *p2); + +void fill_ctdb_rec_data(TALLOC_CTX *mem_ctx, struct ctdb_rec_data *p); +void verify_ctdb_rec_data(struct ctdb_rec_data *p1, struct ctdb_rec_data *p2); + +void fill_ctdb_rec_buffer(TALLOC_CTX *mem_ctx, struct ctdb_rec_buffer *p); +void verify_ctdb_rec_buffer(struct ctdb_rec_buffer *p1, + struct ctdb_rec_buffer *p2); + +void fill_ctdb_traverse_start(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start *p); +void verify_ctdb_traverse_start(struct ctdb_traverse_start *p1, + struct ctdb_traverse_start *p2); + +void fill_ctdb_traverse_all(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all *p); +void verify_ctdb_traverse_all(struct ctdb_traverse_all *p1, + struct ctdb_traverse_all *p2); + +void fill_ctdb_traverse_start_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start_ext *p); +void verify_ctdb_traverse_start_ext(struct ctdb_traverse_start_ext *p1, + struct ctdb_traverse_start_ext *p2); + +void fill_ctdb_traverse_all_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all_ext *p); +void verify_ctdb_traverse_all_ext(struct ctdb_traverse_all_ext *p1, + struct ctdb_traverse_all_ext *p2); + +void fill_ctdb_sock_addr(TALLOC_CTX *mem_ctx, ctdb_sock_addr *p); +void verify_ctdb_sock_addr(ctdb_sock_addr *p1, ctdb_sock_addr *p2); + +void fill_ctdb_connection(TALLOC_CTX *mem_ctx, struct ctdb_connection *p); +void verify_ctdb_connection(struct ctdb_connection *p1, + struct ctdb_connection *p2); + +void fill_ctdb_connection_list(TALLOC_CTX *mem_ctx, + struct ctdb_connection_list *p); +void verify_ctdb_connection_list(struct ctdb_connection_list *p1, + struct ctdb_connection_list *p2); + +void fill_ctdb_tunable(TALLOC_CTX *mem_ctx, struct ctdb_tunable *p); +void verify_ctdb_tunable(struct ctdb_tunable *p1, struct ctdb_tunable *p2); + +void fill_ctdb_node_flag_change(TALLOC_CTX *mem_ctx, + struct ctdb_node_flag_change *p); +void verify_ctdb_node_flag_change(struct ctdb_node_flag_change *p1, + struct ctdb_node_flag_change *p2); + +void fill_ctdb_var_list(TALLOC_CTX *mem_ctx, struct ctdb_var_list *p); +void verify_ctdb_var_list(struct ctdb_var_list *p1, struct ctdb_var_list *p2); + +void fill_ctdb_tunable_list(TALLOC_CTX *mem_ctx, struct ctdb_tunable_list *p); +void verify_ctdb_tunable_list(struct ctdb_tunable_list *p1, + struct ctdb_tunable_list *p2); + +void fill_ctdb_tickle_list(TALLOC_CTX *mem_ctx, struct ctdb_tickle_list *p); +void verify_ctdb_tickle_list(struct ctdb_tickle_list *p1, + struct ctdb_tickle_list *p2); + +void fill_ctdb_addr_info(TALLOC_CTX *mem_ctx, struct ctdb_addr_info *p); +void verify_ctdb_addr_info(struct ctdb_addr_info *p1, + struct ctdb_addr_info *p2); + +void fill_ctdb_transdb(TALLOC_CTX *mem_ctx, struct ctdb_transdb *p); +void verify_ctdb_transdb(struct ctdb_transdb *p1, struct ctdb_transdb *p2); + +void fill_ctdb_uptime(TALLOC_CTX *mem_ctx, struct ctdb_uptime *p); +void verify_ctdb_uptime(struct ctdb_uptime *p1, struct ctdb_uptime *p2); + +void fill_ctdb_public_ip(TALLOC_CTX *mem_ctx, struct ctdb_public_ip *p); +void verify_ctdb_public_ip(struct ctdb_public_ip *p1, + struct ctdb_public_ip *p2); + +void fill_ctdb_public_ip_list(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list *p); +void verify_ctdb_public_ip_list(struct ctdb_public_ip_list *p1, + struct ctdb_public_ip_list *p2); + +void fill_ctdb_node_and_flags(TALLOC_CTX *mem_ctx, + struct ctdb_node_and_flags *p); +void verify_ctdb_node_and_flags(struct ctdb_node_and_flags *p1, + struct ctdb_node_and_flags *p2); + +void fill_ctdb_node_map(TALLOC_CTX *mem_ctx, struct ctdb_node_map *p); +void verify_ctdb_node_map(struct ctdb_node_map *p1, struct ctdb_node_map *p2); + +void fill_ctdb_script(TALLOC_CTX *mem_ctx, struct ctdb_script *p); +void verify_ctdb_script(struct ctdb_script *p1, struct ctdb_script *p2); + +void fill_ctdb_script_list(TALLOC_CTX *mem_ctx, struct ctdb_script_list *p); +void verify_ctdb_script_list(struct ctdb_script_list *p1, + struct ctdb_script_list *p2); + +void fill_ctdb_ban_state(TALLOC_CTX *mem_ctx, struct ctdb_ban_state *p); +void verify_ctdb_ban_state(struct ctdb_ban_state *p1, + struct ctdb_ban_state *p2); + +void fill_ctdb_notify_data(TALLOC_CTX *mem_ctx, struct ctdb_notify_data *p); +void verify_ctdb_notify_data(struct ctdb_notify_data *p1, + struct ctdb_notify_data *p2); + +void fill_ctdb_iface(TALLOC_CTX *mem_ctx, struct ctdb_iface *p); +void verify_ctdb_iface(struct ctdb_iface *p1, struct ctdb_iface *p2); + +void fill_ctdb_iface_list(TALLOC_CTX *mem_ctx, struct ctdb_iface_list *p); +void verify_ctdb_iface_list(struct ctdb_iface_list *p1, + struct ctdb_iface_list *p2); + +void fill_ctdb_public_ip_info(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_info *p); +void verify_ctdb_public_ip_info(struct ctdb_public_ip_info *p1, + struct ctdb_public_ip_info *p2); + +void fill_ctdb_statistics_list(TALLOC_CTX *mem_ctx, + struct ctdb_statistics_list *p); +void verify_ctdb_statistics_list(struct ctdb_statistics_list *p1, + struct ctdb_statistics_list *p2); + +void fill_ctdb_key_data(TALLOC_CTX *mem_ctx, struct ctdb_key_data *p); +void verify_ctdb_key_data(struct ctdb_key_data *p1, struct ctdb_key_data *p2); + +void fill_ctdb_db_statistics(TALLOC_CTX *mem_ctx, + struct ctdb_db_statistics *p); +void verify_ctdb_db_statistics(struct ctdb_db_statistics *p1, + struct ctdb_db_statistics *p2); + +void fill_ctdb_pid_srvid(TALLOC_CTX *mem_ctx, struct ctdb_pid_srvid *p); +void verify_ctdb_pid_srvid(struct ctdb_pid_srvid *p1, + struct ctdb_pid_srvid *p2); + +void fill_ctdb_election_message(TALLOC_CTX *mem_ctx, + struct ctdb_election_message *p); +void verify_ctdb_election_message(struct ctdb_election_message *p1, + struct ctdb_election_message *p2); + +void fill_ctdb_srvid_message(TALLOC_CTX *mem_ctx, + struct ctdb_srvid_message *p); +void verify_ctdb_srvid_message(struct ctdb_srvid_message *p1, + struct ctdb_srvid_message *p2); + +void fill_ctdb_disable_message(TALLOC_CTX *mem_ctx, + struct ctdb_disable_message *p); +void verify_ctdb_disable_message(struct ctdb_disable_message *p1, + struct ctdb_disable_message *p2); + +void fill_ctdb_server_id(struct ctdb_server_id *p); +void verify_ctdb_server_id(struct ctdb_server_id *p1, + struct ctdb_server_id *p2); + +void fill_ctdb_g_lock(struct ctdb_g_lock *p); +void verify_ctdb_g_lock(struct ctdb_g_lock *p1, struct ctdb_g_lock *p2); + +void fill_ctdb_g_lock_list(TALLOC_CTX *mem_ctx, struct ctdb_g_lock_list *p); +void verify_ctdb_g_lock_list(struct ctdb_g_lock_list *p1, + struct ctdb_g_lock_list *p2); + +void fill_sock_packet_header(struct sock_packet_header *p); +void verify_sock_packet_header(struct sock_packet_header *p1, + struct sock_packet_header *p2); + +#endif /* __CTDB_PROTOCOL_COMMON_H__ */ diff --git a/ctdb/tests/src/protocol_common_basic.c b/ctdb/tests/src/protocol_common_basic.c new file mode 100644 index 0000000..7567f7b --- /dev/null +++ b/ctdb/tests/src/protocol_common_basic.c @@ -0,0 +1,305 @@ +/* + protocol tests - common functions - basic types + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/wait.h" + +#include + +#include "lib/util/fault.h" + +#include "tests/src/protocol_common_basic.h" + +uint8_t BUFFER[1024*1024]; + +/* + * Functions to generation random data + */ + +int rand_int(int max) +{ + return random() % max; +} + +uint8_t rand8(void) +{ + uint8_t val = rand_int(256) & 0xff; + return val; +} + +uint16_t rand16(void) +{ + uint16_t val = rand_int(0xffff) & 0xffff; + return val; +} + +int32_t rand32i(void) +{ + return INT_MIN + random(); +} + +uint32_t rand32(void) +{ + return random(); +} + +uint64_t rand64(void) +{ + uint64_t t = random(); + t = (t << 32) | random(); + return t; +} + +double rand_double(void) +{ + return 1.0 / rand64(); +} + +void fill_buffer(void *p, size_t len) +{ + size_t i; + uint8_t *ptr = p; + + for (i=0; i 0) { + assert(memcmp(p1, p2, len) == 0); + } +} + +void fill_string(char *p, size_t len) +{ + size_t i; + + for (i=0; itv_sec = rand32(); + p->tv_usec = rand_int(1000000); +} + +void verify_ctdb_timeval(struct timeval *p1, struct timeval *p2) +{ + assert(p1->tv_sec == p2->tv_sec); + assert(p1->tv_usec == p2->tv_usec); +} + +static unsigned int seed; +static char protocol_test_iterate_buf[1024]; + +static void protocol_test_iterate_abort_handler(int sig) +{ + struct sigaction act = { + .sa_handler = SIG_DFL, + }; + + fprintf(stderr, "Failed with seed: %d\n", seed); + if (protocol_test_iterate_buf[0] != '\0') { + fprintf(stderr, " tag: %s\n", protocol_test_iterate_buf); + } + log_stack_trace(); + sigaction(SIGABRT, &act, NULL); + abort(); +} + +void protocol_test_iterate_tag(const char *fmt, ...) +{ + va_list ap; + int count; + + va_start(ap,fmt); + count = vsnprintf(protocol_test_iterate_buf, + sizeof(protocol_test_iterate_buf), + fmt, + ap); + va_end(ap); + + assert(count >= 0); + protocol_test_iterate_buf[sizeof(protocol_test_iterate_buf) - 1] = '\0'; +} + +void protocol_test_iterate(int argc, + const char *argv[], + void (*test_func)(void)) +{ + struct sigaction act = { + .sa_handler = protocol_test_iterate_abort_handler, + }; + unsigned int min, max; + + if (argc == 2 || argc == 3) { + min = atoi(argv[1]); + + if (argc == 3) { + max = atoi(argv[2]); + if (min >= max) { + fprintf(stderr, + "%s: min must be less than max\n", + argv[0]); + exit(1); + } + + } else { + max = min; + } + } else { + fprintf(stderr, "usage: %s min [max]\n", argv[0]); + exit(1); + } + + sigaction(SIGABRT, &act, NULL); + + for (seed = min; seed <= max ; seed++) { + srandom(seed); + + test_func(); + } +} diff --git a/ctdb/tests/src/protocol_common_basic.h b/ctdb/tests/src/protocol_common_basic.h new file mode 100644 index 0000000..22a11b3 --- /dev/null +++ b/ctdb/tests/src/protocol_common_basic.h @@ -0,0 +1,175 @@ +/* + protocol tests - common functions - basic types + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#ifndef __CTDB_PROTOCOL_COMMON_BASIC_H__ +#define __CTDB_PROTOCOL_COMMON_BASIC_H__ + +#include "replace.h" + +#include + +/* + * Generate test routines + */ + +#define TEST_FUNC(NAME) test_ ##NAME +#define FILL_FUNC(NAME) fill_ ##NAME +#define VERIFY_FUNC(NAME) verify_ ##NAME +#define LEN_FUNC(NAME) NAME## _len +#define PUSH_FUNC(NAME) NAME## _push +#define PULL_FUNC(NAME) NAME## _pull + +/* + * Test for basic data types that do not need memory allocation + * For example - int32_t, uint32_t, uint64_t + */ +#define PROTOCOL_TYPE1_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TYPE p1; \ + TYPE p2; \ + size_t buflen, np = 0; \ + int ret; \ +\ + FILL_FUNC(NAME)(&p1); \ + buflen = LEN_FUNC(NAME)(&p1); \ + assert(buflen < sizeof(BUFFER)); \ + PUSH_FUNC(NAME)(&p1, BUFFER, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(BUFFER, buflen, &p2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ +} + +/* + * Test for container data types that need memory allocation for sub-elements + * For example - TDB_DATA + */ +#define PROTOCOL_TYPE2_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE p1; \ + TYPE p2; \ + size_t buflen, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(mem_ctx, &p1); \ + buflen = LEN_FUNC(NAME)(&p1); \ + assert(buflen < sizeof(BUFFER)); \ + PUSH_FUNC(NAME)(&p1, BUFFER, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(BUFFER, buflen, mem_ctx, &p2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +/* + * Test for derived data types that need memory allocation + * For example - most ctdb structures + */ +#define PROTOCOL_TYPE3_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE *p1, *p2; \ + size_t buflen, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + p1 = talloc_zero(mem_ctx, TYPE); \ + assert(p1 != NULL); \ + FILL_FUNC(NAME)(p1, p1); \ + buflen = LEN_FUNC(NAME)(p1); \ + assert(buflen < sizeof(BUFFER)); \ + PUSH_FUNC(NAME)(p1, BUFFER, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(BUFFER, buflen, mem_ctx, &p2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(p1, p2); \ + talloc_free(mem_ctx); \ +} + +extern uint8_t BUFFER[1024*1024]; + +int rand_int(int max); +uint8_t rand8(void); +uint16_t rand16(void); +int32_t rand32i(void); +uint32_t rand32(void); +uint64_t rand64(void); +double rand_double(void); + +void fill_buffer(void *p, size_t len); +void verify_buffer(void *p1, void *p2, size_t len); + +void fill_string(char *p, size_t len); +void verify_string(const char *p1, const char *p2); + +void fill_ctdb_uint8(uint8_t *p); +void verify_ctdb_uint8(uint8_t *p1, uint8_t *p2); + +void fill_ctdb_uint16(uint16_t *p); +void verify_ctdb_uint16(uint16_t *p1, uint16_t *p2); + +void fill_ctdb_int32(int32_t *p); +void verify_ctdb_int32(int32_t *p1, int32_t *p2); + +void fill_ctdb_uint32(uint32_t *p); +void verify_ctdb_uint32(uint32_t *p1, uint32_t *p2); + +void fill_ctdb_uint64(uint64_t *p); +void verify_ctdb_uint64(uint64_t *p1, uint64_t *p2); + +void fill_ctdb_double(double *p); +void verify_ctdb_double(double *p1, double *p2); + +void fill_ctdb_bool(bool *p); +void verify_ctdb_bool(bool *p1, bool *p2); + +void fill_ctdb_string(TALLOC_CTX *mem_ctx, const char **p); +void verify_ctdb_string(const char **p1, const char **p2); + +void fill_ctdb_stringn(TALLOC_CTX *mem_ctx, const char **p); +void verify_ctdb_stringn(const char **p1, const char **p2); + +void fill_ctdb_pid(pid_t *p); +void verify_ctdb_pid(pid_t *p1, pid_t *p2); + +void fill_ctdb_timeval(struct timeval *p); +void verify_ctdb_timeval(struct timeval *p1, struct timeval *p2); + +void protocol_test_iterate_tag(const char *fmt, ...) PRINTF_ATTRIBUTE(1,0); +void protocol_test_iterate(int argc, + const char *argv[], + void (*test_func)(void)); + +#endif /* __CTDB_PROTOCOL_COMMON_BASIC_H__ */ + + diff --git a/ctdb/tests/src/protocol_common_ctdb.c b/ctdb/tests/src/protocol_common_ctdb.c new file mode 100644 index 0000000..8a8e114 --- /dev/null +++ b/ctdb/tests/src/protocol_common_ctdb.c @@ -0,0 +1,1967 @@ +/* + protocol tests - ctdb protocol + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include + +#include "tests/src/protocol_common.h" +#include "tests/src/protocol_common_ctdb.h" + +/* + * Functions to fill and verify protocol structures + */ + +void fill_ctdb_req_header(struct ctdb_req_header *h) +{ + h->length = rand32(); + h->ctdb_magic = rand32(); + h->ctdb_version = rand32(); + h->generation = rand32(); + h->operation = rand32(); + h->destnode = rand32(); + h->srcnode = rand32(); + h->reqid = rand32(); +} + +void verify_ctdb_req_header(struct ctdb_req_header *h, + struct ctdb_req_header *h2) +{ + assert(h->length == h2->length); + assert(h->ctdb_magic == h2->ctdb_magic); + assert(h->ctdb_version == h2->ctdb_version); + assert(h->generation == h2->generation); + assert(h->operation == h2->operation); + assert(h->destnode == h2->destnode); + assert(h->srcnode == h2->srcnode); + assert(h->reqid == h2->reqid); +} + +void fill_ctdb_req_call(TALLOC_CTX *mem_ctx, struct ctdb_req_call *c) +{ + c->flags = rand32(); + c->db_id = rand32(); + c->callid = rand32(); + c->hopcount = rand32(); + fill_tdb_data_nonnull(mem_ctx, &c->key); + fill_tdb_data(mem_ctx, &c->calldata); +} + +void verify_ctdb_req_call(struct ctdb_req_call *c, struct ctdb_req_call *c2) +{ + assert(c->flags == c2->flags); + assert(c->db_id == c2->db_id); + assert(c->callid == c2->callid); + assert(c->hopcount == c2->hopcount); + verify_tdb_data(&c->key, &c2->key); + verify_tdb_data(&c->calldata, &c2->calldata); +} + +void fill_ctdb_reply_call(TALLOC_CTX *mem_ctx, struct ctdb_reply_call *c) +{ + c->status = rand32(); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_reply_call(struct ctdb_reply_call *c, + struct ctdb_reply_call *c2) +{ + assert(c->status == c2->status); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_reply_error(TALLOC_CTX *mem_ctx, struct ctdb_reply_error *c) +{ + c->status = rand32(); + fill_tdb_data(mem_ctx, &c->msg); +} + +void verify_ctdb_reply_error(struct ctdb_reply_error *c, + struct ctdb_reply_error *c2) +{ + assert(c->status == c2->status); + verify_tdb_data(&c->msg, &c2->msg); +} + +void fill_ctdb_req_dmaster(TALLOC_CTX *mem_ctx, struct ctdb_req_dmaster *c) +{ + c->db_id = rand32(); + c->rsn = rand64(); + c->dmaster = rand32(); + fill_tdb_data_nonnull(mem_ctx, &c->key); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_req_dmaster(struct ctdb_req_dmaster *c, + struct ctdb_req_dmaster *c2) +{ + assert(c->db_id == c2->db_id); + assert(c->rsn == c2->rsn); + assert(c->dmaster == c2->dmaster); + verify_tdb_data(&c->key, &c2->key); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_reply_dmaster(TALLOC_CTX *mem_ctx, + struct ctdb_reply_dmaster *c) +{ + c->db_id = rand32(); + c->rsn = rand64(); + fill_tdb_data_nonnull(mem_ctx, &c->key); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_reply_dmaster(struct ctdb_reply_dmaster *c, + struct ctdb_reply_dmaster *c2) +{ + assert(c->db_id == c2->db_id); + assert(c->rsn == c2->rsn); + verify_tdb_data(&c->key, &c2->key); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_req_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_control_data *cd, + uint32_t opcode) +{ + cd->opcode = opcode; + switch (opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + cd->data.pid = rand32(); + break; + + case CTDB_CONTROL_STATISTICS: + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_GETVNNMAP: + break; + + case CTDB_CONTROL_SETVNNMAP: + cd->data.vnnmap = talloc(mem_ctx, struct ctdb_vnn_map); + assert(cd->data.vnnmap != NULL); + fill_ctdb_vnn_map(mem_ctx, cd->data.vnnmap); + break; + + case CTDB_CONTROL_GET_DEBUG: + break; + + case CTDB_CONTROL_SET_DEBUG: + cd->data.loglevel = rand_int(5); + break; + + case CTDB_CONTROL_GET_DBMAP: + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + cd->data.recmode = rand_int(2); + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name != NULL); + break; + + case CTDB_CONTROL_TRAVERSE_START: + cd->data.traverse_start = talloc(mem_ctx, struct ctdb_traverse_start); + assert(cd->data.traverse_start != NULL); + fill_ctdb_traverse_start(mem_ctx, cd->data.traverse_start); + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + cd->data.traverse_all = talloc(mem_ctx, struct ctdb_traverse_all); + assert(cd->data.traverse_all != NULL); + fill_ctdb_traverse_all(mem_ctx, cd->data.traverse_all); + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + cd->data.rec_data = talloc(mem_ctx, struct ctdb_rec_data); + assert(cd->data.rec_data != NULL); + fill_ctdb_rec_data(mem_ctx, cd->data.rec_data); + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DUMP_MEMORY: + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_TCP_ADD: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_TCP_REMOVE: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + cd->data.tunable = talloc(mem_ctx, struct ctdb_tunable); + assert(cd->data.tunable != NULL); + fill_ctdb_tunable(mem_ctx, cd->data.tunable); + break; + + case CTDB_CONTROL_GET_TUNABLE: + fill_ctdb_string(mem_ctx, &cd->data.tun_var); + assert(cd->data.tun_var != NULL); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + cd->data.flag_change = talloc(mem_ctx, struct ctdb_node_flag_change); + assert(cd->data.flag_change != NULL); + fill_ctdb_node_flag_change(mem_ctx, cd->data.flag_change); + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + cd->data.addr = talloc(mem_ctx, ctdb_sock_addr); + assert(cd->data.addr != NULL); + fill_ctdb_sock_addr(mem_ctx, cd->data.addr); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + cd->data.tickles = talloc(mem_ctx, struct ctdb_tickle_list); + assert(cd->data.tickles != NULL); + fill_ctdb_tickle_list(mem_ctx, cd->data.tickles); + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name != NULL); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info); + assert(cd->data.addr_info != NULL); + fill_ctdb_addr_info(mem_ctx, cd->data.addr_info); + break; + + case CTDB_CONTROL_WIPE_DATABASE: + cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb); + assert(cd->data.transdb != NULL); + fill_ctdb_transdb(mem_ctx, cd->data.transdb); + break; + + case CTDB_CONTROL_UPTIME: + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info); + assert(cd->data.addr_info != NULL); + fill_ctdb_addr_info(mem_ctx, cd->data.addr_info); + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info); + assert(cd->data.addr_info != NULL); + fill_ctdb_addr_info(mem_ctx, cd->data.addr_info); + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + cd->data.pubip = talloc(mem_ctx, struct ctdb_public_ip); + assert(cd->data.pubip != NULL); + fill_ctdb_public_ip(mem_ctx, cd->data.pubip); + break; + + case CTDB_CONTROL_TAKEOVER_IP: + cd->data.pubip = talloc(mem_ctx, struct ctdb_public_ip); + assert(cd->data.pubip != NULL); + fill_ctdb_public_ip(mem_ctx, cd->data.pubip); + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + break; + + case CTDB_CONTROL_GET_NODEMAP: + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + cd->data.traverse_start = talloc(mem_ctx, struct ctdb_traverse_start); + assert(cd->data.traverse_start != NULL); + fill_ctdb_traverse_start(mem_ctx, cd->data.traverse_start); + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + cd->data.reclock_latency = rand_double(); + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + cd->data.role = rand_int(2); + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + cd->data.role = rand_int(2); + break; + + case CTDB_CONTROL_SET_BAN_STATE: + cd->data.ban_state = talloc(mem_ctx, struct ctdb_ban_state); + assert(cd->data.ban_state != NULL); + fill_ctdb_ban_state(mem_ctx, cd->data.ban_state); + break; + + case CTDB_CONTROL_GET_BAN_STATE: + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + cd->data.notify = talloc(mem_ctx, struct ctdb_notify_data); + assert(cd->data.notify != NULL); + fill_ctdb_notify_data(mem_ctx, cd->data.notify); + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + cd->data.srvid = rand64(); + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + cd->data.addr = talloc(mem_ctx, ctdb_sock_addr); + assert(cd->data.addr != NULL); + fill_ctdb_sock_addr(mem_ctx, cd->data.addr); + break; + + case CTDB_CONTROL_GET_IFACES: + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + cd->data.iface = talloc(mem_ctx, struct ctdb_iface); + assert(cd->data.iface != NULL); + fill_ctdb_iface(mem_ctx, cd->data.iface); + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + cd->data.key = talloc(mem_ctx, struct ctdb_key_data); + assert(cd->data.key != NULL); + fill_ctdb_key_data(mem_ctx, cd->data.key); + break; + + case CTDB_CONTROL_SET_DB_READONLY: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + cd->data.traverse_start_ext = talloc(mem_ctx, struct ctdb_traverse_start_ext); + assert(cd->data.traverse_start_ext != NULL); + fill_ctdb_traverse_start_ext(mem_ctx, cd->data.traverse_start_ext); + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + cd->data.traverse_all_ext = talloc(mem_ctx, struct ctdb_traverse_all_ext); + assert(cd->data.traverse_all_ext != NULL); + fill_ctdb_traverse_all_ext(mem_ctx, cd->data.traverse_all_ext); + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + break; + + case CTDB_CONTROL_DB_DETACH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_GET_NODES_FILE: + break; + + case CTDB_CONTROL_DB_FREEZE: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_THAW: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_TRANSACTION_START: + cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb); + assert(cd->data.transdb != NULL); + fill_ctdb_transdb(mem_ctx, cd->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_COMMIT: + cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb); + assert(cd->data.transdb != NULL); + fill_ctdb_transdb(mem_ctx, cd->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_CANCEL: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_PULL: + cd->data.pulldb_ext = talloc(mem_ctx, struct ctdb_pulldb_ext); + assert(cd->data.pulldb_ext != NULL); + fill_ctdb_pulldb_ext(mem_ctx, cd->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_START: + cd->data.pulldb_ext = talloc(mem_ctx, struct ctdb_pulldb_ext); + assert(cd->data.pulldb_ext != NULL); + fill_ctdb_pulldb_ext(mem_ctx, cd->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name != NULL); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + cd->data.pid_srvid = talloc(mem_ctx, struct ctdb_pid_srvid); + assert(cd->data.pid_srvid != NULL); + fill_ctdb_pid_srvid(mem_ctx, cd->data.pid_srvid); + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_DB_VACUUM: + cd->data.db_vacuum = talloc(mem_ctx, struct ctdb_db_vacuum); + assert(cd->data.db_vacuum != NULL); + fill_ctdb_db_vacuum(mem_ctx, cd->data.db_vacuum); + break; + + case CTDB_CONTROL_ECHO_DATA: + cd->data.echo_data = talloc(mem_ctx, struct ctdb_echo_data); + assert(cd->data.echo_data != NULL); + fill_ctdb_echo_data(mem_ctx, cd->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + + case CTDB_CONTROL_TCP_CLIENT_DISCONNECTED: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_TCP_CLIENT_PASSED: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + } +} + +void verify_ctdb_req_control_data(struct ctdb_req_control_data *cd, + struct ctdb_req_control_data *cd2) +{ + assert(cd->opcode == cd2->opcode); + + switch (cd->opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + assert(cd->data.pid == cd2->data.pid); + break; + + case CTDB_CONTROL_STATISTICS: + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_GETVNNMAP: + break; + + case CTDB_CONTROL_SETVNNMAP: + verify_ctdb_vnn_map(cd->data.vnnmap, cd2->data.vnnmap); + break; + + case CTDB_CONTROL_GET_DEBUG: + break; + + case CTDB_CONTROL_SET_DEBUG: + assert(cd->data.loglevel == cd2->data.loglevel); + break; + + case CTDB_CONTROL_GET_DBMAP: + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + assert(cd->data.recmode == cd2->data.recmode); + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_TRAVERSE_START: + verify_ctdb_traverse_start(cd->data.traverse_start, + cd2->data.traverse_start); + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + verify_ctdb_traverse_all(cd->data.traverse_all, + cd2->data.traverse_all); + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + verify_ctdb_rec_data(cd->data.rec_data, cd2->data.rec_data); + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DUMP_MEMORY: + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_TCP_ADD: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_TCP_REMOVE: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + verify_ctdb_tunable(cd->data.tunable, cd2->data.tunable); + break; + + case CTDB_CONTROL_GET_TUNABLE: + verify_ctdb_string(&cd->data.tun_var, &cd2->data.tun_var); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + verify_ctdb_node_flag_change(cd->data.flag_change, + cd2->data.flag_change); + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + verify_ctdb_sock_addr(cd->data.addr, cd2->data.addr); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + verify_ctdb_tickle_list(cd->data.tickles, cd2->data.tickles); + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info); + break; + + case CTDB_CONTROL_WIPE_DATABASE: + verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb); + break; + + case CTDB_CONTROL_UPTIME: + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info); + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info); + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + verify_ctdb_public_ip(cd->data.pubip, cd2->data.pubip); + break; + + case CTDB_CONTROL_TAKEOVER_IP: + verify_ctdb_public_ip(cd->data.pubip, cd2->data.pubip); + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + break; + + case CTDB_CONTROL_GET_NODEMAP: + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + verify_ctdb_traverse_start(cd->data.traverse_start, + cd2->data.traverse_start); + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + assert(cd->data.reclock_latency == cd2->data.reclock_latency); + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + assert(cd->data.role == cd2->data.role); + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + assert(cd->data.role == cd2->data.role); + break; + + case CTDB_CONTROL_SET_BAN_STATE: + verify_ctdb_ban_state(cd->data.ban_state, cd2->data.ban_state); + break; + + case CTDB_CONTROL_GET_BAN_STATE: + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + verify_ctdb_notify_data(cd->data.notify, cd2->data.notify); + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + assert(cd->data.srvid == cd2->data.srvid); + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + verify_ctdb_sock_addr(cd->data.addr, cd2->data.addr); + break; + + case CTDB_CONTROL_GET_IFACES: + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + verify_ctdb_iface(cd->data.iface, cd2->data.iface); + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + verify_ctdb_key_data(cd->data.key, cd2->data.key); + break; + + case CTDB_CONTROL_SET_DB_READONLY: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + verify_ctdb_traverse_start_ext(cd->data.traverse_start_ext, + cd2->data.traverse_start_ext); + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + verify_ctdb_traverse_all_ext(cd->data.traverse_all_ext, + cd2->data.traverse_all_ext); + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + break; + + case CTDB_CONTROL_DB_DETACH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_GET_NODES_FILE: + break; + + case CTDB_CONTROL_DB_FREEZE: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_THAW: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_TRANSACTION_START: + verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_COMMIT: + verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_CANCEL: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_PULL: + verify_ctdb_pulldb_ext(cd->data.pulldb_ext, + cd2->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_START: + verify_ctdb_pulldb_ext(cd->data.pulldb_ext, + cd2->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + verify_ctdb_pid_srvid(cd->data.pid_srvid, cd2->data.pid_srvid); + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_DB_VACUUM: + verify_ctdb_db_vacuum(cd->data.db_vacuum, cd2->data.db_vacuum); + break; + + case CTDB_CONTROL_ECHO_DATA: + verify_ctdb_echo_data(cd->data.echo_data, cd2->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + + case CTDB_CONTROL_TCP_CLIENT_DISCONNECTED: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_TCP_CLIENT_PASSED: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + } +} + +void fill_ctdb_req_control(TALLOC_CTX *mem_ctx, struct ctdb_req_control *c, + uint32_t opcode) +{ + c->opcode = opcode; + c->pad = rand32(); + c->srvid = rand64(); + c->client_id = rand32(); + c->flags = rand32(); + + fill_ctdb_req_control_data(mem_ctx, &c->rdata, opcode); +} + +void verify_ctdb_req_control(struct ctdb_req_control *c, + struct ctdb_req_control *c2) +{ + assert(c->opcode == c2->opcode); + assert(c->pad == c2->pad); + assert(c->srvid == c2->srvid); + assert(c->client_id == c2->client_id); + assert(c->flags == c2->flags); + + verify_ctdb_req_control_data(&c->rdata, &c2->rdata); +} + +void fill_ctdb_reply_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control_data *cd, + uint32_t opcode) +{ + cd->opcode = opcode; + + switch (opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + break; + + case CTDB_CONTROL_STATISTICS: + cd->data.stats = talloc(mem_ctx, struct ctdb_statistics); + assert(cd->data.stats != NULL); + fill_ctdb_statistics(mem_ctx, cd->data.stats); + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + fill_ctdb_string(mem_ctx, &cd->data.db_path); + assert(cd->data.db_path != NULL); + break; + + case CTDB_CONTROL_GETVNNMAP: + cd->data.vnnmap = talloc(mem_ctx, struct ctdb_vnn_map); + assert(cd->data.vnnmap != NULL); + fill_ctdb_vnn_map(mem_ctx, cd->data.vnnmap); + break; + + case CTDB_CONTROL_SETVNNMAP: + break; + + case CTDB_CONTROL_GET_DEBUG: + cd->data.loglevel = rand_int(5); + break; + + case CTDB_CONTROL_SET_DEBUG: + break; + + case CTDB_CONTROL_GET_DBMAP: + cd->data.dbmap = talloc(mem_ctx, struct ctdb_dbid_map); + assert(cd->data.dbmap != NULL); + fill_ctdb_dbid_map(mem_ctx, cd->data.dbmap); + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_TRAVERSE_START: + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + break; + + case CTDB_CONTROL_DUMP_MEMORY: + fill_ctdb_string(mem_ctx, &cd->data.mem_str); + assert(cd->data.mem_str); + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + break; + + case CTDB_CONTROL_TCP_ADD: + break; + + case CTDB_CONTROL_TCP_REMOVE: + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + break; + + case CTDB_CONTROL_GET_TUNABLE: + cd->data.tun_value = rand32(); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + cd->data.tun_var_list = talloc(mem_ctx, struct ctdb_var_list); + assert(cd->data.tun_var_list != NULL); + fill_ctdb_var_list(mem_ctx, cd->data.tun_var_list); + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + cd->data.tun_list = talloc(mem_ctx, struct ctdb_tunable_list); + assert(cd->data.tun_list != NULL); + fill_ctdb_tunable_list(mem_ctx, cd->data.tun_list); + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + cd->data.tickles = talloc(mem_ctx, struct ctdb_tickle_list); + assert(cd->data.tickles != NULL); + fill_ctdb_tickle_list(mem_ctx, cd->data.tickles); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + break; + + case CTDB_CONTROL_WIPE_DATABASE: + break; + + case CTDB_CONTROL_UPTIME: + cd->data.uptime = talloc(mem_ctx, struct ctdb_uptime); + assert(cd->data.uptime != NULL); + fill_ctdb_uptime(mem_ctx, cd->data.uptime); + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + cd->data.caps = rand32(); + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + break; + + case CTDB_CONTROL_TAKEOVER_IP: + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + cd->data.pubip_list = talloc(mem_ctx, struct ctdb_public_ip_list); + assert(cd->data.pubip_list != NULL); + fill_ctdb_public_ip_list(mem_ctx, cd->data.pubip_list); + break; + + case CTDB_CONTROL_GET_NODEMAP: + cd->data.nodemap = talloc(mem_ctx, struct ctdb_node_map); + assert(cd->data.nodemap != NULL); + fill_ctdb_node_map(mem_ctx, cd->data.nodemap); + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + fill_ctdb_string(mem_ctx, &cd->data.reclock_file); + assert(cd->data.reclock_file != NULL); + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + break; + + case CTDB_CONTROL_SET_BAN_STATE: + break; + + case CTDB_CONTROL_GET_BAN_STATE: + cd->data.ban_state = talloc(mem_ctx, struct ctdb_ban_state); + assert(cd->data.ban_state != NULL); + fill_ctdb_ban_state(mem_ctx, cd->data.ban_state); + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + cd->data.seqnum = rand64(); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + fill_ctdb_string(mem_ctx, &cd->data.reason); + assert(cd->data.reason != NULL); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + cd->data.ipinfo = talloc(mem_ctx, struct ctdb_public_ip_info); + assert(cd->data.ipinfo != NULL); + fill_ctdb_public_ip_info(mem_ctx, cd->data.ipinfo); + break; + + case CTDB_CONTROL_GET_IFACES: + cd->data.iface_list = talloc(mem_ctx, struct ctdb_iface_list); + assert(cd->data.iface_list != NULL); + fill_ctdb_iface_list(mem_ctx, cd->data.iface_list); + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + cd->data.stats_list = talloc(mem_ctx, struct ctdb_statistics_list); + assert(cd->data.stats_list != NULL); + fill_ctdb_statistics_list(mem_ctx, cd->data.stats_list); + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + break; + + case CTDB_CONTROL_SET_DB_READONLY: + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + cd->data.dbstats = talloc(mem_ctx, struct ctdb_db_statistics); + assert(cd->data.dbstats != NULL); + fill_ctdb_db_statistics(mem_ctx, cd->data.dbstats); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + cd->data.runstate = rand32(); + break; + + case CTDB_CONTROL_DB_DETACH: + break; + + case CTDB_CONTROL_GET_NODES_FILE: + cd->data.nodemap = talloc(mem_ctx, struct ctdb_node_map); + assert(cd->data.nodemap != NULL); + fill_ctdb_node_map(mem_ctx, cd->data.nodemap); + break; + + case CTDB_CONTROL_DB_PULL: + cd->data.num_records = rand32(); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + cd->data.num_records = rand32(); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + cd->data.tdb_flags = rand32(); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + break; + + case CTDB_CONTROL_DB_VACUUM: + break; + + case CTDB_CONTROL_ECHO_DATA: + cd->data.echo_data = talloc(mem_ctx, struct ctdb_echo_data); + assert(cd->data.echo_data != NULL); + fill_ctdb_echo_data(mem_ctx, cd->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + + case CTDB_CONTROL_TCP_CLIENT_DISCONNECTED: + break; + + case CTDB_CONTROL_TCP_CLIENT_PASSED: + break; + } +} + +void verify_ctdb_reply_control_data(struct ctdb_reply_control_data *cd, + struct ctdb_reply_control_data *cd2) +{ + assert(cd->opcode == cd2->opcode); + + switch (cd->opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + break; + + case CTDB_CONTROL_STATISTICS: + verify_ctdb_statistics(cd->data.stats, cd2->data.stats); + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + verify_ctdb_string(&cd->data.db_path, &cd2->data.db_path); + break; + + case CTDB_CONTROL_GETVNNMAP: + verify_ctdb_vnn_map(cd->data.vnnmap, cd2->data.vnnmap); + break; + + case CTDB_CONTROL_SETVNNMAP: + break; + + case CTDB_CONTROL_GET_DEBUG: + assert(cd->data.loglevel == cd2->data.loglevel); + break; + + case CTDB_CONTROL_SET_DEBUG: + break; + + case CTDB_CONTROL_GET_DBMAP: + verify_ctdb_dbid_map(cd->data.dbmap, cd2->data.dbmap); + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_TRAVERSE_START: + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + break; + + case CTDB_CONTROL_DUMP_MEMORY: + verify_ctdb_string(&cd->data.mem_str, &cd2->data.mem_str); + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + break; + + case CTDB_CONTROL_TCP_ADD: + break; + + case CTDB_CONTROL_TCP_REMOVE: + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + break; + + case CTDB_CONTROL_GET_TUNABLE: + assert(cd->data.tun_value == cd2->data.tun_value); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + verify_ctdb_var_list(cd->data.tun_var_list, + cd2->data.tun_var_list); + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + verify_ctdb_tunable_list(cd->data.tun_list, cd2->data.tun_list); + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + verify_ctdb_tickle_list(cd->data.tickles, cd2->data.tickles); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + break; + + case CTDB_CONTROL_WIPE_DATABASE: + break; + + case CTDB_CONTROL_UPTIME: + verify_ctdb_uptime(cd->data.uptime, cd2->data.uptime); + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + assert(cd->data.caps == cd2->data.caps); + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + break; + + case CTDB_CONTROL_TAKEOVER_IP: + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + verify_ctdb_public_ip_list(cd->data.pubip_list, + cd2->data.pubip_list); + break; + + case CTDB_CONTROL_GET_NODEMAP: + verify_ctdb_node_map(cd->data.nodemap, cd2->data.nodemap); + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + verify_ctdb_string(&cd->data.reclock_file, + &cd2->data.reclock_file); + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + break; + + case CTDB_CONTROL_SET_BAN_STATE: + break; + + case CTDB_CONTROL_GET_BAN_STATE: + verify_ctdb_ban_state(cd->data.ban_state, cd2->data.ban_state); + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + assert(cd->data.seqnum == cd2->data.seqnum); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + verify_ctdb_string(&cd->data.reason, &cd2->data.reason); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + verify_ctdb_public_ip_info(cd->data.ipinfo, cd2->data.ipinfo); + break; + + case CTDB_CONTROL_GET_IFACES: + verify_ctdb_iface_list(cd->data.iface_list, + cd2->data.iface_list); + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + verify_ctdb_statistics_list(cd->data.stats_list, + cd2->data.stats_list); + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + break; + + case CTDB_CONTROL_SET_DB_READONLY: + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + verify_ctdb_db_statistics(cd->data.dbstats, cd2->data.dbstats); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + assert(cd->data.runstate == cd2->data.runstate); + break; + + case CTDB_CONTROL_DB_DETACH: + break; + + case CTDB_CONTROL_GET_NODES_FILE: + verify_ctdb_node_map(cd->data.nodemap, cd2->data.nodemap); + break; + + case CTDB_CONTROL_DB_PULL: + assert(cd->data.num_records == cd2->data.num_records); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + assert(cd->data.num_records == cd2->data.num_records); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + assert(cd->data.tdb_flags == cd2->data.tdb_flags); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + break; + + case CTDB_CONTROL_DB_VACUUM: + break; + + case CTDB_CONTROL_ECHO_DATA: + verify_ctdb_echo_data(cd->data.echo_data, cd2->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + + case CTDB_CONTROL_TCP_CLIENT_DISCONNECTED: + break; + + case CTDB_CONTROL_TCP_CLIENT_PASSED: + break; + } +} + +void fill_ctdb_reply_control(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control *c, uint32_t opcode) +{ + c->status = -rand_int(2); + if (c->status == 0) { + c->errmsg = NULL; + fill_ctdb_reply_control_data(mem_ctx, &c->rdata, opcode); + } else { + fill_ctdb_string(mem_ctx, &c->errmsg); + } +} + +void verify_ctdb_reply_control(struct ctdb_reply_control *c, + struct ctdb_reply_control *c2) +{ + assert(c->status == c2->status); + verify_ctdb_string(&c->errmsg, &c2->errmsg); + if (c->status == 0) { + verify_ctdb_reply_control_data(&c->rdata, &c2->rdata); + } +} + +void fill_ctdb_message_data(TALLOC_CTX *mem_ctx, union ctdb_message_data *md, + uint64_t srvid) +{ + switch (srvid) { + case CTDB_SRVID_RECONFIGURE: + case CTDB_SRVID_GETLOG: + case CTDB_SRVID_CLEARLOG: + case CTDB_SRVID_RELOAD_NODES: + break; + + case CTDB_SRVID_ELECTION: + md->election = talloc(mem_ctx, struct ctdb_election_message); + assert(md->election != NULL); + fill_ctdb_election_message(md->election, md->election); + break; + + case CTDB_SRVID_RELEASE_IP: + case CTDB_SRVID_TAKE_IP: + fill_ctdb_string(mem_ctx, &md->ipaddr); + break; + + case CTDB_SRVID_SET_NODE_FLAGS: + case CTDB_SRVID_PUSH_NODE_FLAGS: + md->flag_change = talloc(mem_ctx, + struct ctdb_node_flag_change); + assert(md->flag_change != NULL); + fill_ctdb_node_flag_change(md->flag_change, md->flag_change); + break; + + case CTDB_SRVID_RECD_UPDATE_IP: + md->pubip = talloc(mem_ctx, struct ctdb_public_ip); + assert(md->pubip != NULL); + fill_ctdb_public_ip(md->pubip, md->pubip); + break; + + case CTDB_SRVID_VACUUM_FETCH: + md->recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(md->recbuf != NULL); + fill_ctdb_rec_buffer(md->recbuf, md->recbuf); + break; + + case CTDB_SRVID_DETACH_DATABASE: + md->db_id = rand32(); + break; + + case CTDB_SRVID_MEM_DUMP: + case CTDB_SRVID_TAKEOVER_RUN: + md->msg = talloc(mem_ctx, struct ctdb_srvid_message); + assert(md->msg != NULL); + fill_ctdb_srvid_message(md->msg, md->msg); + break; + + case CTDB_SRVID_LEADER: + case CTDB_SRVID_BANNING: + case CTDB_SRVID_REBALANCE_NODE: + md->pnn = rand32(); + break; + + case CTDB_SRVID_DISABLE_TAKEOVER_RUNS: + case CTDB_SRVID_DISABLE_RECOVERIES: + md->disable = talloc(mem_ctx, struct ctdb_disable_message); + assert(md->disable != NULL); + fill_ctdb_disable_message(md->disable, md->disable); + break; + + case CTDB_SRVID_DISABLE_IP_CHECK: + md->timeout = rand32(); + break; + + default: + abort(); + } +} + +void verify_ctdb_message_data(union ctdb_message_data *md, + union ctdb_message_data *md2, uint64_t srvid) +{ + switch (srvid) { + case CTDB_SRVID_RECONFIGURE: + case CTDB_SRVID_GETLOG: + case CTDB_SRVID_CLEARLOG: + case CTDB_SRVID_RELOAD_NODES: + break; + + case CTDB_SRVID_ELECTION: + verify_ctdb_election_message(md->election, md2->election); + break; + + case CTDB_SRVID_RELEASE_IP: + case CTDB_SRVID_TAKE_IP: + verify_ctdb_string(&md->ipaddr, &md2->ipaddr); + break; + + case CTDB_SRVID_SET_NODE_FLAGS: + case CTDB_SRVID_PUSH_NODE_FLAGS: + verify_ctdb_node_flag_change(md->flag_change, + md2->flag_change); + break; + + case CTDB_SRVID_RECD_UPDATE_IP: + verify_ctdb_public_ip(md->pubip, md2->pubip); + break; + + case CTDB_SRVID_VACUUM_FETCH: + verify_ctdb_rec_buffer(md->recbuf, md2->recbuf); + break; + + case CTDB_SRVID_DETACH_DATABASE: + assert(md->db_id == md2->db_id); + break; + + case CTDB_SRVID_MEM_DUMP: + case CTDB_SRVID_TAKEOVER_RUN: + verify_ctdb_srvid_message(md->msg, md2->msg); + break; + + case CTDB_SRVID_LEADER: + case CTDB_SRVID_BANNING: + case CTDB_SRVID_REBALANCE_NODE: + assert(md->pnn == md2->pnn); + break; + + case CTDB_SRVID_DISABLE_TAKEOVER_RUNS: + case CTDB_SRVID_DISABLE_RECOVERIES: + verify_ctdb_disable_message(md->disable, md2->disable); + break; + + case CTDB_SRVID_DISABLE_IP_CHECK: + assert(md->timeout == md2->timeout); + break; + + default: + abort(); + } +} + +void fill_ctdb_req_message(TALLOC_CTX *mem_ctx, struct ctdb_req_message *c, + uint64_t srvid) +{ + c->srvid = srvid; + fill_ctdb_message_data(mem_ctx, &c->data, srvid); +} + +void verify_ctdb_req_message(struct ctdb_req_message *c, + struct ctdb_req_message *c2) +{ + assert(c->srvid == c2->srvid); + verify_ctdb_message_data(&c->data, &c2->data, c->srvid); +} + +void fill_ctdb_req_message_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_message_data *c) +{ + c->srvid = rand64(); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_req_message_data(struct ctdb_req_message_data *c, + struct ctdb_req_message_data *c2) +{ + assert(c->srvid == c2->srvid); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_req_keepalive(TALLOC_CTX *mem_ctx, + struct ctdb_req_keepalive *c) +{ + c->version = rand32(); + c->uptime = rand32(); +} + +void verify_ctdb_req_keepalive(struct ctdb_req_keepalive *c, + struct ctdb_req_keepalive *c2) +{ + assert(c->version == c2->version); + assert(c->uptime == c2->uptime); +} + +void fill_ctdb_req_tunnel(TALLOC_CTX *mem_ctx, struct ctdb_req_tunnel *c) +{ + c->tunnel_id = rand64(); + c->flags = rand32(); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_req_tunnel(struct ctdb_req_tunnel *c, + struct ctdb_req_tunnel *c2) +{ + assert(c->tunnel_id == c2->tunnel_id); + assert(c->flags == c2->flags); + verify_tdb_data(&c->data, &c2->data); +} diff --git a/ctdb/tests/src/protocol_common_ctdb.h b/ctdb/tests/src/protocol_common_ctdb.h new file mode 100644 index 0000000..0681089 --- /dev/null +++ b/ctdb/tests/src/protocol_common_ctdb.h @@ -0,0 +1,101 @@ +/* + protocol tests - ctdb protocol + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#ifndef __CTDB_PROTOCOL_COMMON_CTDB_H__ +#define __CTDB_PROTOCOL_COMMON_CTDB_H__ + +#include "replace.h" +#include "system/network.h" + +#include +#include + +#include "protocol/protocol.h" + +void fill_ctdb_req_header(struct ctdb_req_header *h); +void verify_ctdb_req_header(struct ctdb_req_header *h, + struct ctdb_req_header *h2); + +void fill_ctdb_req_call(TALLOC_CTX *mem_ctx, struct ctdb_req_call *c); +void verify_ctdb_req_call(struct ctdb_req_call *c, struct ctdb_req_call *c2); + +void fill_ctdb_reply_call(TALLOC_CTX *mem_ctx, struct ctdb_reply_call *c); +void verify_ctdb_reply_call(struct ctdb_reply_call *c, + struct ctdb_reply_call *c2); + +void fill_ctdb_reply_error(TALLOC_CTX *mem_ctx, struct ctdb_reply_error *c); +void verify_ctdb_reply_error(struct ctdb_reply_error *c, + struct ctdb_reply_error *c2); + +void fill_ctdb_req_dmaster(TALLOC_CTX *mem_ctx, struct ctdb_req_dmaster *c); +void verify_ctdb_req_dmaster(struct ctdb_req_dmaster *c, + struct ctdb_req_dmaster *c2); + +void fill_ctdb_reply_dmaster(TALLOC_CTX *mem_ctx, + struct ctdb_reply_dmaster *c); +void verify_ctdb_reply_dmaster(struct ctdb_reply_dmaster *c, + struct ctdb_reply_dmaster *c2); + +void fill_ctdb_req_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_control_data *cd, + uint32_t opcode); +void verify_ctdb_req_control_data(struct ctdb_req_control_data *cd, + struct ctdb_req_control_data *cd2); + +void fill_ctdb_req_control(TALLOC_CTX *mem_ctx, struct ctdb_req_control *c, + uint32_t opcode); +void verify_ctdb_req_control(struct ctdb_req_control *c, + struct ctdb_req_control *c2); + +void fill_ctdb_reply_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control_data *cd, + uint32_t opcode); +void verify_ctdb_reply_control_data(struct ctdb_reply_control_data *cd, + struct ctdb_reply_control_data *cd2); + +void fill_ctdb_reply_control(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control *c, uint32_t opcode); +void verify_ctdb_reply_control(struct ctdb_reply_control *c, + struct ctdb_reply_control *c2); + +void fill_ctdb_message_data(TALLOC_CTX *mem_ctx, union ctdb_message_data *md, + uint64_t srvid); +void verify_ctdb_message_data(union ctdb_message_data *md, + union ctdb_message_data *md2, uint64_t srvid); + +void fill_ctdb_req_message(TALLOC_CTX *mem_ctx, struct ctdb_req_message *c, + uint64_t srvid); +void verify_ctdb_req_message(struct ctdb_req_message *c, + struct ctdb_req_message *c2); + +void fill_ctdb_req_message_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_message_data *c); +void verify_ctdb_req_message_data(struct ctdb_req_message_data *c, + struct ctdb_req_message_data *c2); + +void fill_ctdb_req_keepalive(TALLOC_CTX *mem_ctx, + struct ctdb_req_keepalive *c); +void verify_ctdb_req_keepalive(struct ctdb_req_keepalive *c, + struct ctdb_req_keepalive *c2); + +void fill_ctdb_req_tunnel(TALLOC_CTX *mem_ctx, struct ctdb_req_tunnel *c); +void verify_ctdb_req_tunnel(struct ctdb_req_tunnel *c, + struct ctdb_req_tunnel *c2); + +#endif /* __CTDB_PROTOCOL_COMMON_CTDB_H__ */ diff --git a/ctdb/tests/src/protocol_ctdb_compat_test.c b/ctdb/tests/src/protocol_ctdb_compat_test.c new file mode 100644 index 0000000..fc9f82e --- /dev/null +++ b/ctdb/tests/src/protocol_ctdb_compat_test.c @@ -0,0 +1,1270 @@ +/* + ctdb protocol backward compatibility test + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_header.c" +#include "protocol/protocol_call.c" +#include "protocol/protocol_control.c" +#include "protocol/protocol_message.c" +#include "protocol/protocol_keepalive.c" +#include "protocol/protocol_tunnel.c" + +#include "tests/src/protocol_common.h" +#include "tests/src/protocol_common_ctdb.h" + +#define COMPAT_TEST_FUNC(NAME) test_ ##NAME## _compat +#define OLD_LEN_FUNC(NAME) NAME## _len_old +#define OLD_PUSH_FUNC(NAME) NAME## _push_old +#define OLD_PULL_FUNC(NAME) NAME## _pull_old + +#define COMPAT_CTDB1_TEST(TYPE, NAME) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(&p); \ + buflen1 = LEN_FUNC(NAME)(&p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + PUSH_FUNC(NAME)(&p, buf1, &np); \ + OLD_PUSH_FUNC(NAME)(&p, buf2); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &p1, &np); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &p2); \ + assert(ret == 0); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB4_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB5_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p, opcode); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB6_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p, opcode); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, opcode, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, opcode, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB7_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(uint64_t srvid) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p, srvid); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + + +static size_t ctdb_req_header_len_old(struct ctdb_req_header *in) +{ + return sizeof(struct ctdb_req_header); +} + +static void ctdb_req_header_push_old(struct ctdb_req_header *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_req_header)); +} + +static int ctdb_req_header_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *out) +{ + if (buflen < sizeof(struct ctdb_req_header)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_req_header)); + return 0; +} + +struct ctdb_req_call_wire { + struct ctdb_req_header hdr; + uint32_t flags; + uint32_t db_id; + uint32_t callid; + uint32_t hopcount; + uint32_t keylen; + uint32_t calldatalen; + uint8_t data[1]; /* key[] followed by calldata[] */ +}; + +static size_t ctdb_req_call_len_old(struct ctdb_req_header *h, + struct ctdb_req_call *c) +{ + return offsetof(struct ctdb_req_call_wire, data) + + ctdb_tdb_data_len(&c->key) + + ctdb_tdb_data_len(&c->calldata); +} + +static int ctdb_req_call_push_old(struct ctdb_req_header *h, + struct ctdb_req_call *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_call_wire *wire = + (struct ctdb_req_call_wire *)buf; + size_t length, np; + + if (c->key.dsize == 0) { + return EINVAL; + } + + length = ctdb_req_call_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->flags = c->flags; + wire->db_id = c->db_id; + wire->callid = c->callid; + wire->hopcount = c->hopcount; + wire->keylen = ctdb_tdb_data_len(&c->key); + wire->calldatalen = ctdb_tdb_data_len(&c->calldata); + ctdb_tdb_data_push(&c->key, wire->data, &np); + ctdb_tdb_data_push(&c->calldata, wire->data + wire->keylen, &np); + + return 0; +} + +static int ctdb_req_call_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_call *c) +{ + struct ctdb_req_call_wire *wire = + (struct ctdb_req_call_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_call_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->calldatalen > buflen) { + return EMSGSIZE; + } + if (length + wire->keylen < length) { + return EMSGSIZE; + } + if (length + wire->keylen + wire->calldatalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->keylen + wire->calldatalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->flags = wire->flags; + c->db_id = wire->db_id; + c->callid = wire->callid; + c->hopcount = wire->hopcount; + + ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key, + &np); + if (ret != 0) { + return ret; + } + + ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->calldatalen, + mem_ctx, &c->calldata, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_call_wire { + struct ctdb_req_header hdr; + uint32_t status; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_reply_call_len_old(struct ctdb_req_header *h, + struct ctdb_reply_call *c) +{ + return offsetof(struct ctdb_reply_call_wire, data) + + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_reply_call_push_old(struct ctdb_req_header *h, + struct ctdb_reply_call *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_call_wire *wire = + (struct ctdb_reply_call_wire *)buf; + size_t length, np; + + length = ctdb_reply_call_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->status = c->status; + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->data, wire->data, &np); + + return 0; +} + +static int ctdb_reply_call_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_call *c) +{ + struct ctdb_reply_call_wire *wire = + (struct ctdb_reply_call_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_call_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->status = wire->status; + + ret = ctdb_tdb_data_pull(wire->data, wire->datalen, mem_ctx, &c->data, + &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_error_wire { + struct ctdb_req_header hdr; + uint32_t status; + uint32_t msglen; + uint8_t msg[1]; +}; + +static size_t ctdb_reply_error_len_old(struct ctdb_req_header *h, + struct ctdb_reply_error *c) +{ + return offsetof(struct ctdb_reply_error_wire, msg) + + ctdb_tdb_data_len(&c->msg); +} + +static int ctdb_reply_error_push_old(struct ctdb_req_header *h, + struct ctdb_reply_error *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_error_wire *wire = + (struct ctdb_reply_error_wire *)buf; + size_t length, np; + + length = ctdb_reply_error_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->status = c->status; + wire->msglen = ctdb_tdb_data_len(&c->msg); + ctdb_tdb_data_push(&c->msg, wire->msg, &np); + + return 0; +} + +static int ctdb_reply_error_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_error *c) +{ + struct ctdb_reply_error_wire *wire = + (struct ctdb_reply_error_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_error_wire, msg); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->msglen > buflen) { + return EMSGSIZE; + } + if (length + wire->msglen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->msglen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->status = wire->status; + + ret = ctdb_tdb_data_pull(wire->msg, wire->msglen, mem_ctx, &c->msg, + &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_dmaster_wire { + struct ctdb_req_header hdr; + uint32_t db_id; + uint64_t rsn; + uint32_t dmaster; + uint32_t keylen; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_dmaster_len_old(struct ctdb_req_header *h, + struct ctdb_req_dmaster *c) +{ + return offsetof(struct ctdb_req_dmaster_wire, data) + + ctdb_tdb_data_len(&c->key) + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_req_dmaster_push_old(struct ctdb_req_header *h, + struct ctdb_req_dmaster *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_dmaster_wire *wire = + (struct ctdb_req_dmaster_wire *)buf; + size_t length, np; + + length = ctdb_req_dmaster_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->db_id = c->db_id; + wire->rsn = c->rsn; + wire->dmaster = c->dmaster; + wire->keylen = ctdb_tdb_data_len(&c->key); + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->key, wire->data, &np); + ctdb_tdb_data_push(&c->data, wire->data + wire->keylen, &np); + + return 0; +} + +static int ctdb_req_dmaster_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_dmaster *c) +{ + struct ctdb_req_dmaster_wire *wire = + (struct ctdb_req_dmaster_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_dmaster_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->keylen < length) { + return EMSGSIZE; + } + if (length + wire->keylen + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->keylen + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->db_id = wire->db_id; + c->rsn = wire->rsn; + c->dmaster = wire->dmaster; + + ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key, + &np); + if (ret != 0) { + return ret; + } + + ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->datalen, + mem_ctx, &c->data, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_dmaster_wire { + struct ctdb_req_header hdr; + uint32_t db_id; + uint64_t rsn; + uint32_t keylen; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_reply_dmaster_len_old(struct ctdb_req_header *h, + struct ctdb_reply_dmaster *c) +{ + return offsetof(struct ctdb_reply_dmaster_wire, data) + + ctdb_tdb_data_len(&c->key) + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_reply_dmaster_push_old(struct ctdb_req_header *h, + struct ctdb_reply_dmaster *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_dmaster_wire *wire = + (struct ctdb_reply_dmaster_wire *)buf; + size_t length, np; + + length = ctdb_reply_dmaster_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->db_id = c->db_id; + wire->rsn = c->rsn; + wire->keylen = ctdb_tdb_data_len(&c->key); + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->key, wire->data, &np); + ctdb_tdb_data_push(&c->data, wire->data + wire->keylen, &np); + + return 0; +} + +static int ctdb_reply_dmaster_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_dmaster *c) +{ + struct ctdb_reply_dmaster_wire *wire = + (struct ctdb_reply_dmaster_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_dmaster_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->keylen < length) { + return EMSGSIZE; + } + if (length + wire->keylen + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->keylen + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->db_id = wire->db_id; + c->rsn = wire->rsn; + + ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key, + &np); + if (ret != 0) { + return ret; + } + + ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->datalen, + mem_ctx, &c->data, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_control_wire { + struct ctdb_req_header hdr; + uint32_t opcode; + uint32_t pad; + uint64_t srvid; + uint32_t client_id; + uint32_t flags; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_control_len_old(struct ctdb_req_header *h, + struct ctdb_req_control *c) +{ + return offsetof(struct ctdb_req_control_wire, data) + + ctdb_req_control_data_len(&c->rdata); +} + +static int ctdb_req_control_push_old(struct ctdb_req_header *h, + struct ctdb_req_control *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_control_wire *wire = + (struct ctdb_req_control_wire *)buf; + size_t length, np; + + length = ctdb_req_control_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->opcode = c->opcode; + wire->pad = c->pad; + wire->srvid = c->srvid; + wire->client_id = c->client_id; + wire->flags = c->flags; + + wire->datalen = ctdb_req_control_data_len(&c->rdata); + ctdb_req_control_data_push(&c->rdata, wire->data, &np); + + return 0; +} + +static int ctdb_req_control_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_control *c) +{ + struct ctdb_req_control_wire *wire = + (struct ctdb_req_control_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_control_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->opcode = wire->opcode; + c->pad = wire->pad; + c->srvid = wire->srvid; + c->client_id = wire->client_id; + c->flags = wire->flags; + + ret = ctdb_req_control_data_pull(wire->data, wire->datalen, + c->opcode, mem_ctx, &c->rdata, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_control_wire { + struct ctdb_req_header hdr; + int32_t status; + uint32_t datalen; + uint32_t errorlen; + uint8_t data[1]; +}; + +static size_t ctdb_reply_control_len_old(struct ctdb_req_header *h, + struct ctdb_reply_control *c) +{ + return offsetof(struct ctdb_reply_control_wire, data) + + (c->status == 0 ? + ctdb_reply_control_data_len(&c->rdata) : + ctdb_string_len(&c->errmsg)); +} + +static int ctdb_reply_control_push_old(struct ctdb_req_header *h, + struct ctdb_reply_control *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_control_wire *wire = + (struct ctdb_reply_control_wire *)buf; + size_t length, np; + + length = ctdb_reply_control_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->status = c->status; + + if (c->status == 0) { + wire->datalen = ctdb_reply_control_data_len(&c->rdata); + wire->errorlen = 0; + ctdb_reply_control_data_push(&c->rdata, wire->data, &np); + } else { + wire->datalen = 0; + wire->errorlen = ctdb_string_len(&c->errmsg); + ctdb_string_push(&c->errmsg, wire->data + wire->datalen, &np); + } + + return 0; +} + +static int ctdb_reply_control_pull_old(uint8_t *buf, size_t buflen, + uint32_t opcode, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_control *c) +{ + struct ctdb_reply_control_wire *wire = + (struct ctdb_reply_control_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_control_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen || wire->errorlen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (length + wire->datalen + wire->errorlen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen + wire->errorlen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->status = wire->status; + + if (c->status != -1) { + ret = ctdb_reply_control_data_pull(wire->data, wire->datalen, + opcode, mem_ctx, + &c->rdata, &np); + if (ret != 0) { + return ret; + } + } + + ret = ctdb_string_pull(wire->data + wire->datalen, wire->errorlen, + mem_ctx, &c->errmsg, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_message_wire { + struct ctdb_req_header hdr; + uint64_t srvid; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_message_len_old(struct ctdb_req_header *h, + struct ctdb_req_message *c) +{ + return offsetof(struct ctdb_req_message_wire, data) + + ctdb_message_data_len(&c->data, c->srvid); +} + +static int ctdb_req_message_push_old(struct ctdb_req_header *h, + struct ctdb_req_message *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + + length = ctdb_req_message_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->srvid = c->srvid; + wire->datalen = ctdb_message_data_len(&c->data, c->srvid); + ctdb_message_data_push(&c->data, c->srvid, wire->data, &np); + + return 0; +} + +static int ctdb_req_message_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_message *c) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_message_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->srvid = wire->srvid; + ret = ctdb_message_data_pull(wire->data, wire->datalen, wire->srvid, + mem_ctx, &c->data, &np); + return ret; +} + +static size_t ctdb_req_message_data_len_old(struct ctdb_req_header *h, + struct ctdb_req_message_data *c) +{ + return offsetof(struct ctdb_req_message_wire, data) + + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_req_message_data_push_old(struct ctdb_req_header *h, + struct ctdb_req_message_data *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + + length = ctdb_req_message_data_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push(h, (uint8_t *)&wire->hdr, &np); + + wire->srvid = c->srvid; + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->data, wire->data, &np); + + return 0; +} + +static int ctdb_req_message_data_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_message_data *c) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_message_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->srvid = wire->srvid; + + ret = ctdb_tdb_data_pull(wire->data, wire->datalen, + mem_ctx, &c->data, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_keepalive_wire { + struct ctdb_req_header hdr; + uint32_t version; + uint32_t uptime; +}; + +static size_t ctdb_req_keepalive_len_old(struct ctdb_req_header *h, + struct ctdb_req_keepalive *c) +{ + return sizeof(struct ctdb_req_keepalive_wire); +} + +static int ctdb_req_keepalive_push_old(struct ctdb_req_header *h, + struct ctdb_req_keepalive *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_keepalive_wire *wire = + (struct ctdb_req_keepalive_wire *)buf; + size_t length; + + length = ctdb_req_keepalive_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->version = c->version; + wire->uptime = c->uptime; + + return 0; +} + +static int ctdb_req_keepalive_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_keepalive *c) +{ + struct ctdb_req_keepalive_wire *wire = + (struct ctdb_req_keepalive_wire *)buf; + size_t length; + int ret; + + length = sizeof(struct ctdb_req_keepalive_wire); + if (buflen < length) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->version = wire->version; + c->uptime = wire->uptime; + + return 0; +} + +struct ctdb_req_tunnel_wire { + struct ctdb_req_header hdr; + uint64_t tunnel_id; + uint32_t flags; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_tunnel_len_old(struct ctdb_req_header *h, + struct ctdb_req_tunnel *c) +{ + return offsetof(struct ctdb_req_tunnel_wire, data) + + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_req_tunnel_push_old(struct ctdb_req_header *h, + struct ctdb_req_tunnel *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_tunnel_wire *wire = + (struct ctdb_req_tunnel_wire *)buf; + size_t length, np; + + length = ctdb_req_tunnel_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->tunnel_id = c->tunnel_id; + wire->flags = c->flags; + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->data, wire->data, &np); + + return 0; +} + +static int ctdb_req_tunnel_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_tunnel *c) +{ + struct ctdb_req_tunnel_wire *wire = + (struct ctdb_req_tunnel_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_tunnel_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->tunnel_id = wire->tunnel_id; + c->flags = wire->flags; + + ret = ctdb_tdb_data_pull(wire->data, wire->datalen, mem_ctx, &c->data, + &np); + if (ret != 0) { + return ret; + } + + return 0; +} + + +COMPAT_CTDB1_TEST(struct ctdb_req_header, ctdb_req_header); + +COMPAT_CTDB4_TEST(struct ctdb_req_call, ctdb_req_call, CTDB_REQ_CALL); +COMPAT_CTDB4_TEST(struct ctdb_reply_call, ctdb_reply_call, CTDB_REPLY_CALL); +COMPAT_CTDB4_TEST(struct ctdb_reply_error, ctdb_reply_error, CTDB_REPLY_ERROR); +COMPAT_CTDB4_TEST(struct ctdb_req_dmaster, ctdb_req_dmaster, CTDB_REQ_DMASTER); +COMPAT_CTDB4_TEST(struct ctdb_reply_dmaster, ctdb_reply_dmaster, CTDB_REPLY_DMASTER); + +COMPAT_CTDB5_TEST(struct ctdb_req_control, ctdb_req_control, CTDB_REQ_CONTROL); +COMPAT_CTDB6_TEST(struct ctdb_reply_control, ctdb_reply_control, CTDB_REPLY_CONTROL); + +COMPAT_CTDB7_TEST(struct ctdb_req_message, ctdb_req_message, CTDB_REQ_MESSAGE); +COMPAT_CTDB4_TEST(struct ctdb_req_message_data, ctdb_req_message_data, CTDB_REQ_MESSAGE); + +COMPAT_CTDB4_TEST(struct ctdb_req_keepalive, ctdb_req_keepalive, CTDB_REQ_KEEPALIVE); +COMPAT_CTDB4_TEST(struct ctdb_req_tunnel, ctdb_req_tunnel, CTDB_REQ_TUNNEL); + +#define NUM_CONTROLS 151 + +static void protocol_ctdb_compat_test(void) +{ + uint32_t opcode; + uint64_t test_srvid[] = { + CTDB_SRVID_BANNING, + CTDB_SRVID_ELECTION, + CTDB_SRVID_LEADER, + CTDB_SRVID_RECONFIGURE, + CTDB_SRVID_RELEASE_IP, + CTDB_SRVID_TAKE_IP, + CTDB_SRVID_SET_NODE_FLAGS, + CTDB_SRVID_RECD_UPDATE_IP, + CTDB_SRVID_VACUUM_FETCH, + CTDB_SRVID_DETACH_DATABASE, + CTDB_SRVID_MEM_DUMP, + CTDB_SRVID_GETLOG, + CTDB_SRVID_CLEARLOG, + CTDB_SRVID_PUSH_NODE_FLAGS, + CTDB_SRVID_RELOAD_NODES, + CTDB_SRVID_TAKEOVER_RUN, + CTDB_SRVID_REBALANCE_NODE, + CTDB_SRVID_DISABLE_TAKEOVER_RUNS, + CTDB_SRVID_DISABLE_RECOVERIES, + CTDB_SRVID_DISABLE_IP_CHECK, + }; + unsigned int i; + + COMPAT_TEST_FUNC(ctdb_req_header)(); + + COMPAT_TEST_FUNC(ctdb_req_call)(); + COMPAT_TEST_FUNC(ctdb_reply_call)(); + COMPAT_TEST_FUNC(ctdb_reply_error)(); + COMPAT_TEST_FUNC(ctdb_req_dmaster)(); + COMPAT_TEST_FUNC(ctdb_reply_dmaster)(); + + for (opcode=0; opcode. +*/ + +#include + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_header.c" +#include "protocol/protocol_call.c" +#include "protocol/protocol_control.c" +#include "protocol/protocol_message.c" +#include "protocol/protocol_keepalive.c" +#include "protocol/protocol_tunnel.c" +#include "protocol/protocol_packet.c" + +#include "tests/src/protocol_common.h" +#include "tests/src/protocol_common_ctdb.h" + +/* + * Functions to test marshalling + */ + +/* for ctdb_req_header */ +#define PROTOCOL_CTDB1_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, np; \ + int ret; \ +\ + protocol_test_iterate_tag("%s\n", #NAME); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(&c1); \ + buflen = LEN_FUNC(NAME)(&c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + np = 0; \ + PUSH_FUNC(NAME)(&c1, pkt, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &c2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_control_data, ctdb_reply_control_data */ +#define PROTOCOL_CTDB2_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, np; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \ + buflen = LEN_FUNC(NAME)(&c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + np = 0; \ + PUSH_FUNC(NAME)(&c1, pkt, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, opcode, mem_ctx, &c2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_message_data */ +#define PROTOCOL_CTDB3_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(uint64_t srvid) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, np; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %"PRIx64"\n", #NAME, srvid); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(mem_ctx, &c1, srvid); \ + buflen = LEN_FUNC(NAME)(&c1, srvid); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + np = 0; \ + PUSH_FUNC(NAME)(&c1, srvid, pkt, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, srvid, mem_ctx, &c2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&c1, &c2, srvid); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_call, ctdb_reply_call, etc. */ +#define PROTOCOL_CTDB4_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s\n", #NAME); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_control */ +#define PROTOCOL_CTDB5_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_reply_control */ +#define PROTOCOL_CTDB6_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, opcode, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_message */ +#define PROTOCOL_CTDB7_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(uint64_t srvid) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %"PRIx64"\n", #NAME, srvid); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1, srvid); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +PROTOCOL_CTDB1_TEST(struct ctdb_req_header, ctdb_req_header); + +PROTOCOL_CTDB4_TEST(struct ctdb_req_call, ctdb_req_call, CTDB_REQ_CALL); +PROTOCOL_CTDB4_TEST(struct ctdb_reply_call, ctdb_reply_call, CTDB_REPLY_CALL); +PROTOCOL_CTDB4_TEST(struct ctdb_reply_error, ctdb_reply_error, + CTDB_REPLY_ERROR); +PROTOCOL_CTDB4_TEST(struct ctdb_req_dmaster, ctdb_req_dmaster, + CTDB_REQ_DMASTER); +PROTOCOL_CTDB4_TEST(struct ctdb_reply_dmaster, ctdb_reply_dmaster, + CTDB_REPLY_DMASTER); + +#define NUM_CONTROLS 161 + +PROTOCOL_CTDB2_TEST(struct ctdb_req_control_data, ctdb_req_control_data); +PROTOCOL_CTDB2_TEST(struct ctdb_reply_control_data, ctdb_reply_control_data); + +PROTOCOL_CTDB5_TEST(struct ctdb_req_control, ctdb_req_control, + CTDB_REQ_CONTROL); +PROTOCOL_CTDB6_TEST(struct ctdb_reply_control, ctdb_reply_control, + CTDB_REPLY_CONTROL); + +PROTOCOL_CTDB3_TEST(union ctdb_message_data, ctdb_message_data); +PROTOCOL_CTDB7_TEST(struct ctdb_req_message, ctdb_req_message, + CTDB_REQ_MESSAGE); +PROTOCOL_CTDB4_TEST(struct ctdb_req_message_data, ctdb_req_message_data, + CTDB_REQ_MESSAGE); + +PROTOCOL_CTDB4_TEST(struct ctdb_req_keepalive, ctdb_req_keepalive, + CTDB_REQ_KEEPALIVE); +PROTOCOL_CTDB4_TEST(struct ctdb_req_tunnel, ctdb_req_tunnel, CTDB_REQ_TUNNEL); + +static void protocol_ctdb_test(void) +{ + uint32_t opcode; + uint64_t test_srvid[] = { + CTDB_SRVID_BANNING, + CTDB_SRVID_ELECTION, + CTDB_SRVID_LEADER, + CTDB_SRVID_RECONFIGURE, + CTDB_SRVID_RELEASE_IP, + CTDB_SRVID_TAKE_IP, + CTDB_SRVID_SET_NODE_FLAGS, + CTDB_SRVID_RECD_UPDATE_IP, + CTDB_SRVID_VACUUM_FETCH, + CTDB_SRVID_DETACH_DATABASE, + CTDB_SRVID_MEM_DUMP, + CTDB_SRVID_GETLOG, + CTDB_SRVID_CLEARLOG, + CTDB_SRVID_PUSH_NODE_FLAGS, + CTDB_SRVID_RELOAD_NODES, + CTDB_SRVID_TAKEOVER_RUN, + CTDB_SRVID_REBALANCE_NODE, + CTDB_SRVID_DISABLE_TAKEOVER_RUNS, + CTDB_SRVID_DISABLE_RECOVERIES, + CTDB_SRVID_DISABLE_IP_CHECK, + }; + size_t i; + + TEST_FUNC(ctdb_req_header)(); + + TEST_FUNC(ctdb_req_call)(); + TEST_FUNC(ctdb_reply_call)(); + TEST_FUNC(ctdb_reply_error)(); + TEST_FUNC(ctdb_req_dmaster)(); + TEST_FUNC(ctdb_reply_dmaster)(); + + for (opcode=0; opcode. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" + +#include "tests/src/protocol_common.h" + +#define COMPAT_TEST_FUNC(NAME) test_ ##NAME## _compat +#define OLD_LEN_FUNC(NAME) NAME## _len_old +#define OLD_PUSH_FUNC(NAME) NAME## _push_old +#define OLD_PULL_FUNC(NAME) NAME## _pull_old + +#define COMPAT_TYPE1_TEST(TYPE, NAME) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(&p); \ + buflen1 = LEN_FUNC(NAME)(&p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + PUSH_FUNC(NAME)(&p, buf1, &np); \ + OLD_PUSH_FUNC(NAME)(&p, buf2); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &p1, &np); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &p2); \ + assert(ret == 0); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_TYPE3_TEST(TYPE, NAME) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + TYPE *p, *p1, *p2; \ + size_t buflen1, buflen2, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + p = talloc_zero(mem_ctx, TYPE); \ + assert(p != NULL); \ + FILL_FUNC(NAME)(p, p); \ + buflen1 = LEN_FUNC(NAME)(p); \ + buflen2 = OLD_LEN_FUNC(NAME)(p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + PUSH_FUNC(NAME)(p, buf1, &np); \ + OLD_PUSH_FUNC(NAME)(p, buf2); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, mem_ctx, &p1, &np); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, mem_ctx, &p2); \ + assert(ret == 0); \ + VERIFY_FUNC(NAME)(p1, p2); \ + talloc_free(mem_ctx); \ +} + + +static size_t ctdb_statistics_len_old(struct ctdb_statistics *in) +{ + return sizeof(struct ctdb_statistics); +} + +static void ctdb_statistics_push_old(struct ctdb_statistics *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_statistics)); +} + +static int ctdb_statistics_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_statistics **out) +{ + struct ctdb_statistics *val; + + if (buflen < sizeof(struct ctdb_statistics)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_statistics); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, buf, sizeof(struct ctdb_statistics)); + + *out = val; + return 0; +} + +struct ctdb_vnn_map_wire { + uint32_t generation; + uint32_t size; + uint32_t map[1]; +}; + +static size_t ctdb_vnn_map_len_old(struct ctdb_vnn_map *in) +{ + return offsetof(struct ctdb_vnn_map, map) + + in->size * sizeof(uint32_t); +} + +static void ctdb_vnn_map_push_old(struct ctdb_vnn_map *in, uint8_t *buf) +{ + struct ctdb_vnn_map_wire *wire = (struct ctdb_vnn_map_wire *)buf; + + memcpy(wire, in, offsetof(struct ctdb_vnn_map, map)); + memcpy(wire->map, in->map, in->size * sizeof(uint32_t)); +} + +static int ctdb_vnn_map_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_vnn_map **out) +{ + struct ctdb_vnn_map *val; + struct ctdb_vnn_map_wire *wire = (struct ctdb_vnn_map_wire *)buf; + + if (buflen < offsetof(struct ctdb_vnn_map_wire, map)) { + return EMSGSIZE; + } + if (wire->size > buflen / sizeof(uint32_t)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_vnn_map_wire, map) + + wire->size * sizeof(uint32_t) < + offsetof(struct ctdb_vnn_map_wire, map)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_vnn_map_wire, map) + + wire->size * sizeof(uint32_t)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_vnn_map); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, offsetof(struct ctdb_vnn_map, map)); + + val->map = talloc_memdup(val, wire->map, + wire->size * sizeof(uint32_t)); + if (val->map == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_dbid_map_wire { + uint32_t num; + struct ctdb_dbid dbs[1]; +}; + +static size_t ctdb_dbid_map_len_old(struct ctdb_dbid_map *in) +{ + return sizeof(uint32_t) + in->num * sizeof(struct ctdb_dbid); +} + +static void ctdb_dbid_map_push_old(struct ctdb_dbid_map *in, uint8_t *buf) +{ + struct ctdb_dbid_map_wire *wire = (struct ctdb_dbid_map_wire *)buf; + + wire->num = in->num; + memcpy(wire->dbs, in->dbs, in->num * sizeof(struct ctdb_dbid)); +} + +static int ctdb_dbid_map_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_dbid_map **out) +{ + struct ctdb_dbid_map *val; + struct ctdb_dbid_map_wire *wire = (struct ctdb_dbid_map_wire *)buf; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_dbid)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_dbid) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + wire->num * sizeof(struct ctdb_dbid)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_dbid_map); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + + val->dbs = talloc_memdup(val, wire->dbs, + wire->num * sizeof(struct ctdb_dbid)); + if (val->dbs == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_pulldb_len_old(struct ctdb_pulldb *in) +{ + return sizeof(struct ctdb_pulldb); +} + +static void ctdb_pulldb_push_old(struct ctdb_pulldb *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_pulldb)); +} + +static int ctdb_pulldb_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_pulldb **out) +{ + struct ctdb_pulldb *val; + + if (buflen < sizeof(struct ctdb_pulldb)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_pulldb)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_pulldb_ext_len_old(struct ctdb_pulldb_ext *in) +{ + return sizeof(struct ctdb_pulldb_ext); +} + +static void ctdb_pulldb_ext_push_old(struct ctdb_pulldb_ext *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_pulldb_ext)); +} + +static int ctdb_pulldb_ext_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_pulldb_ext **out) +{ + struct ctdb_pulldb_ext *val; + + if (buflen < sizeof(struct ctdb_pulldb_ext)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_pulldb_ext)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_ltdb_header_len_old(struct ctdb_ltdb_header *in) +{ + return sizeof(struct ctdb_ltdb_header); +} + +static void ctdb_ltdb_header_push_old(struct ctdb_ltdb_header *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_ltdb_header)); +} + +static int ctdb_ltdb_header_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_ltdb_header *out) +{ + if (buflen < sizeof(struct ctdb_ltdb_header)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_ltdb_header)); + return 0; +} + +struct ctdb_rec_data_wire { + uint32_t length; + uint32_t reqid; + uint32_t keylen; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_rec_data_len_old(struct ctdb_rec_data *in) +{ + return offsetof(struct ctdb_rec_data_wire, data) + + in->key.dsize + in->data.dsize + + (in->header == NULL ? 0 : sizeof(struct ctdb_ltdb_header)); +} + +static void ctdb_rec_data_push_old(struct ctdb_rec_data *in, uint8_t *buf) +{ + struct ctdb_rec_data_wire *wire = (struct ctdb_rec_data_wire *)buf; + size_t offset; + + wire->length = ctdb_rec_data_len(in); + wire->reqid = in->reqid; + wire->keylen = in->key.dsize; + wire->datalen = in->data.dsize; + if (in->header != NULL) { + wire->datalen += sizeof(struct ctdb_ltdb_header); + } + + memcpy(wire->data, in->key.dptr, in->key.dsize); + offset = in->key.dsize; + if (in->header != NULL) { + memcpy(&wire->data[offset], in->header, + sizeof(struct ctdb_ltdb_header)); + offset += sizeof(struct ctdb_ltdb_header); + } + if (in->data.dsize > 0) { + memcpy(&wire->data[offset], in->data.dptr, in->data.dsize); + } +} + +static int ctdb_rec_data_pull_data_old(uint8_t *buf, size_t buflen, + uint32_t *reqid, + struct ctdb_ltdb_header **header, + TDB_DATA *key, TDB_DATA *data, + size_t *reclen) +{ + struct ctdb_rec_data_wire *wire = (struct ctdb_rec_data_wire *)buf; + size_t offset; + + if (buflen < offsetof(struct ctdb_rec_data_wire, data)) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->datalen > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_rec_data_wire, data) + wire->keylen < + offsetof(struct ctdb_rec_data_wire, data)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_rec_data_wire, data) + + wire->keylen + wire->datalen < + offsetof(struct ctdb_rec_data_wire, data)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_rec_data_wire, data) + + wire->keylen + wire->datalen) { + return EMSGSIZE; + } + + *reqid = wire->reqid; + + key->dsize = wire->keylen; + key->dptr = wire->data; + offset = wire->keylen; + + /* Always set header to NULL. If it is required, exact it using + * ctdb_rec_data_extract_header() + */ + *header = NULL; + + data->dsize = wire->datalen; + data->dptr = &wire->data[offset]; + + *reclen = offsetof(struct ctdb_rec_data_wire, data) + + wire->keylen + wire->datalen; + + return 0; +} + +static int ctdb_rec_data_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_rec_data *out) +{ + uint32_t reqid; + struct ctdb_ltdb_header *header; + TDB_DATA key, data; + size_t reclen; + int ret; + + ret = ctdb_rec_data_pull_data_old(buf, buflen, &reqid, &header, + &key, &data, &reclen); + if (ret != 0) { + return ret; + } + + out->reqid = reqid; + out->header = NULL; + + out->key.dsize = key.dsize; + if (key.dsize > 0) { + out->key.dptr = talloc_memdup(mem_ctx, key.dptr, key.dsize); + if (out->key.dptr == NULL) { + return ENOMEM; + } + } + + out->data.dsize = data.dsize; + if (data.dsize > 0) { + out->data.dptr = talloc_memdup(mem_ctx, data.dptr, data.dsize); + if (out->data.dptr == NULL) { + return ENOMEM; + } + } + + return 0; +} + +static int ctdb_rec_data_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_rec_data **out) +{ + struct ctdb_rec_data *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_rec_data); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_rec_data_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_rec_buffer_wire { + uint32_t db_id; + uint32_t count; + uint8_t data[1]; +}; + +static size_t ctdb_rec_buffer_len_old(struct ctdb_rec_buffer *in) +{ + return offsetof(struct ctdb_rec_buffer_wire, data) + in->buflen; +} + +static void ctdb_rec_buffer_push_old(struct ctdb_rec_buffer *in, uint8_t *buf) +{ + struct ctdb_rec_buffer_wire *wire = (struct ctdb_rec_buffer_wire *)buf; + + wire->db_id = in->db_id; + wire->count = in->count; + if (in->buflen > 0) { + memcpy(wire->data, in->buf, in->buflen); + } +} + +static int ctdb_rec_buffer_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_rec_buffer **out) +{ + struct ctdb_rec_buffer *val; + struct ctdb_rec_buffer_wire *wire = (struct ctdb_rec_buffer_wire *)buf; + size_t offset; + + if (buflen < offsetof(struct ctdb_rec_buffer_wire, data)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_rec_buffer); + if (val == NULL) { + return ENOMEM; + } + + val->db_id = wire->db_id; + val->count = wire->count; + + offset = offsetof(struct ctdb_rec_buffer_wire, data); + val->buflen = buflen - offset; + val->buf = talloc_memdup(val, wire->data, val->buflen); + if (val->buf == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_start_len_old(struct ctdb_traverse_start *in) +{ + return sizeof(struct ctdb_traverse_start); +} + +static void ctdb_traverse_start_push_old(struct ctdb_traverse_start *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_start)); +} + +static int ctdb_traverse_start_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start **out) +{ + struct ctdb_traverse_start *val; + + if (buflen < sizeof(struct ctdb_traverse_start)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_traverse_start)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_all_len_old(struct ctdb_traverse_all *in) +{ + return sizeof(struct ctdb_traverse_all); +} + +static void ctdb_traverse_all_push_old(struct ctdb_traverse_all *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_all)); +} + +static int ctdb_traverse_all_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all **out) +{ + struct ctdb_traverse_all *val; + + if (buflen < sizeof(struct ctdb_traverse_all)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_traverse_all)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_start_ext_len_old( + struct ctdb_traverse_start_ext *in) +{ + return sizeof(struct ctdb_traverse_start_ext); +} + +static void ctdb_traverse_start_ext_push_old( + struct ctdb_traverse_start_ext *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_start_ext)); +} + +static int ctdb_traverse_start_ext_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start_ext **out) +{ + struct ctdb_traverse_start_ext *val; + + if (buflen < sizeof(struct ctdb_traverse_start_ext)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_traverse_start_ext)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_all_ext_len_old(struct ctdb_traverse_all_ext *in) +{ + return sizeof(struct ctdb_traverse_all_ext); +} + +static void ctdb_traverse_all_ext_push_old(struct ctdb_traverse_all_ext *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_all_ext)); +} + +static int ctdb_traverse_all_ext_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all_ext **out) +{ + struct ctdb_traverse_all_ext *val; + + if (buflen < sizeof(struct ctdb_traverse_all_ext)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_traverse_all_ext)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_sock_addr_len_old(ctdb_sock_addr *in) +{ + return sizeof(ctdb_sock_addr); +} + +static void ctdb_sock_addr_push_old(ctdb_sock_addr *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(ctdb_sock_addr)); +} + +static int ctdb_sock_addr_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + ctdb_sock_addr *out) +{ + if (buflen < sizeof(ctdb_sock_addr)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(ctdb_sock_addr)); + + return 0; +} + +static int ctdb_sock_addr_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, ctdb_sock_addr **out) +{ + ctdb_sock_addr *val; + int ret; + + val = talloc(mem_ctx, ctdb_sock_addr); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_sock_addr_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +static size_t ctdb_connection_len_old(struct ctdb_connection *in) +{ + return sizeof(struct ctdb_connection); +} + +static void ctdb_connection_push_old(struct ctdb_connection *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_connection)); +} + +static int ctdb_connection_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_connection *out) +{ + if (buflen < sizeof(struct ctdb_connection)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_connection)); + + return 0; +} + +static int ctdb_connection_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_connection **out) +{ + struct ctdb_connection *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_connection); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_connection_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_tunable_wire { + uint32_t value; + uint32_t length; + uint8_t name[1]; +}; + +static size_t ctdb_tunable_len_old(struct ctdb_tunable *in) +{ + return offsetof(struct ctdb_tunable_wire, name) + + strlen(in->name) + 1; +} + +static void ctdb_tunable_push_old(struct ctdb_tunable *in, uint8_t *buf) +{ + struct ctdb_tunable_wire *wire = (struct ctdb_tunable_wire *)buf; + + wire->value = in->value; + wire->length = strlen(in->name) + 1; + memcpy(wire->name, in->name, wire->length); +} + +static int ctdb_tunable_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_tunable **out) +{ + struct ctdb_tunable *val; + struct ctdb_tunable_wire *wire = (struct ctdb_tunable_wire *)buf; + + if (buflen < offsetof(struct ctdb_tunable_wire, name)) { + return EMSGSIZE; + } + if (wire->length > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_tunable_wire, name) + wire->length < + offsetof(struct ctdb_tunable_wire, name)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_tunable_wire, name) + wire->length) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_tunable); + if (val == NULL) { + return ENOMEM; + } + + val->value = wire->value; + val->name = talloc_memdup(val, wire->name, wire->length); + if (val->name == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_node_flag_change_len_old(struct ctdb_node_flag_change *in) +{ + return sizeof(struct ctdb_node_flag_change); +} + +static void ctdb_node_flag_change_push_old(struct ctdb_node_flag_change *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_node_flag_change)); +} + +static int ctdb_node_flag_change_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_node_flag_change **out) +{ + struct ctdb_node_flag_change *val; + + if (buflen < sizeof(struct ctdb_node_flag_change)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_node_flag_change)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_var_list_wire { + uint32_t length; + char list_str[1]; +}; + +static size_t ctdb_var_list_len_old(struct ctdb_var_list *in) +{ + int i; + size_t len = sizeof(uint32_t); + + for (i=0; icount; i++) { + assert(in->var[i] != NULL); + len += strlen(in->var[i]) + 1; + } + return len; +} + +static void ctdb_var_list_push_old(struct ctdb_var_list *in, uint8_t *buf) +{ + struct ctdb_var_list_wire *wire = (struct ctdb_var_list_wire *)buf; + int i, n; + size_t offset = 0; + + if (in->count > 0) { + n = sprintf(wire->list_str, "%s", in->var[0]); + offset += n; + } + for (i=1; icount; i++) { + n = sprintf(&wire->list_str[offset], ":%s", in->var[i]); + offset += n; + } + wire->length = offset + 1; +} + +static int ctdb_var_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_var_list **out) +{ + struct ctdb_var_list *val = NULL; + struct ctdb_var_list_wire *wire = (struct ctdb_var_list_wire *)buf; + char *str, *s, *tok, *ptr; + const char **list; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->length > buflen) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->length < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + wire->length) { + return EMSGSIZE; + } + + str = talloc_strndup(mem_ctx, (char *)wire->list_str, wire->length); + if (str == NULL) { + return ENOMEM; + } + + val = talloc_zero(mem_ctx, struct ctdb_var_list); + if (val == NULL) { + goto fail; + } + + s = str; + while ((tok = strtok_r(s, ":", &ptr)) != NULL) { + s = NULL; + list = talloc_realloc(val, val->var, const char *, + val->count+1); + if (list == NULL) { + goto fail; + } + + val->var = list; + val->var[val->count] = talloc_strdup(val, tok); + if (val->var[val->count] == NULL) { + goto fail; + } + val->count++; + } + + talloc_free(str); + *out = val; + return 0; + +fail: + talloc_free(str); + talloc_free(val); + return ENOMEM; +} + +static size_t ctdb_tunable_list_len_old(struct ctdb_tunable_list *in) +{ + return sizeof(struct ctdb_tunable_list); +} + +static void ctdb_tunable_list_push_old(struct ctdb_tunable_list *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_tunable_list)); +} + +static int ctdb_tunable_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_tunable_list **out) +{ + struct ctdb_tunable_list *val; + + if (buflen < sizeof(struct ctdb_tunable_list)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_tunable_list)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_tickle_list_wire { + ctdb_sock_addr addr; + uint32_t num; + struct ctdb_connection conn[1]; +}; + +static size_t ctdb_tickle_list_len_old(struct ctdb_tickle_list *in) +{ + return offsetof(struct ctdb_tickle_list, conn) + + in->num * sizeof(struct ctdb_connection); +} + +static void ctdb_tickle_list_push_old(struct ctdb_tickle_list *in, + uint8_t *buf) +{ + struct ctdb_tickle_list_wire *wire = + (struct ctdb_tickle_list_wire *)buf; + size_t offset; + unsigned int i; + + memcpy(&wire->addr, &in->addr, sizeof(ctdb_sock_addr)); + wire->num = in->num; + + offset = offsetof(struct ctdb_tickle_list_wire, conn); + for (i=0; inum; i++) { + ctdb_connection_push_old(&in->conn[i], &buf[offset]); + offset += ctdb_connection_len_old(&in->conn[i]); + } +} + +static int ctdb_tickle_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_tickle_list **out) +{ + struct ctdb_tickle_list *val; + struct ctdb_tickle_list_wire *wire = + (struct ctdb_tickle_list_wire *)buf; + size_t offset; + unsigned int i; + int ret; + + if (buflen < offsetof(struct ctdb_tickle_list_wire, conn)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_connection)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_tickle_list_wire, conn) + + wire->num * sizeof(struct ctdb_connection) < + offsetof(struct ctdb_tickle_list_wire, conn)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_tickle_list_wire, conn) + + wire->num * sizeof(struct ctdb_connection)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_tickle_list); + if (val == NULL) { + return ENOMEM; + } + + offset = offsetof(struct ctdb_tickle_list, conn); + memcpy(val, wire, offset); + + val->conn = talloc_array(val, struct ctdb_connection, wire->num); + if (val->conn == NULL) { + talloc_free(val); + return ENOMEM; + } + + for (i=0; inum; i++) { + ret = ctdb_connection_pull_elems_old(&buf[offset], + buflen-offset, + val->conn, + &val->conn[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_connection_len_old(&val->conn[i]); + } + + *out = val; + return 0; +} + +struct ctdb_addr_info_wire { + ctdb_sock_addr addr; + uint32_t mask; + uint32_t len; + char iface[1]; +}; + +static size_t ctdb_addr_info_len_old(struct ctdb_addr_info *in) +{ + uint32_t len; + + len = offsetof(struct ctdb_addr_info_wire, iface); + if (in->iface != NULL) { + len += strlen(in->iface)+1; + } + + return len; +} + +static void ctdb_addr_info_push_old(struct ctdb_addr_info *in, uint8_t *buf) +{ + struct ctdb_addr_info_wire *wire = (struct ctdb_addr_info_wire *)buf; + + wire->addr = in->addr; + wire->mask = in->mask; + if (in->iface == NULL) { + wire->len = 0; + } else { + wire->len = strlen(in->iface)+1; + memcpy(wire->iface, in->iface, wire->len); + } +} + +static int ctdb_addr_info_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_addr_info **out) +{ + struct ctdb_addr_info *val; + struct ctdb_addr_info_wire *wire = (struct ctdb_addr_info_wire *)buf; + + if (buflen < offsetof(struct ctdb_addr_info_wire, iface)) { + return EMSGSIZE; + } + if (wire->len > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_addr_info_wire, iface) + wire->len < + offsetof(struct ctdb_addr_info_wire, iface)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_addr_info_wire, iface) + wire->len) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_addr_info); + if (val == NULL) { + return ENOMEM; + } + + val->addr = wire->addr; + val->mask = wire->mask; + + if (wire->len == 0) { + val->iface = NULL; + } else { + val->iface = talloc_strndup(val, wire->iface, wire->len); + if (val->iface == NULL) { + talloc_free(val); + return ENOMEM; + } + } + + *out = val; + return 0; +} + +static size_t ctdb_transdb_len_old(struct ctdb_transdb *in) +{ + return sizeof(struct ctdb_transdb); +} + +static void ctdb_transdb_push_old(struct ctdb_transdb *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_transdb)); +} + +static int ctdb_transdb_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_transdb **out) +{ + struct ctdb_transdb *val; + + if (buflen < sizeof(struct ctdb_transdb)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_transdb)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_uptime_len_old(struct ctdb_uptime *in) +{ + return sizeof(struct ctdb_uptime); +} + +static void ctdb_uptime_push_old(struct ctdb_uptime *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_uptime)); +} + +static int ctdb_uptime_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_uptime **out) +{ + struct ctdb_uptime *val; + + if (buflen < sizeof(struct ctdb_uptime)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_uptime)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_public_ip_len_old(struct ctdb_public_ip *in) +{ + return sizeof(struct ctdb_public_ip); +} + +static void ctdb_public_ip_push_old(struct ctdb_public_ip *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_public_ip)); +} + +static int ctdb_public_ip_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip *out) +{ + if (buflen < sizeof(struct ctdb_public_ip)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_public_ip)); + + return 0; +} + +static int ctdb_public_ip_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip **out) +{ + struct ctdb_public_ip *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_public_ip); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_public_ip_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_public_ip_list_wire { + uint32_t num; + struct ctdb_public_ip ip[1]; +}; + +static size_t ctdb_public_ip_list_len_old(struct ctdb_public_ip_list *in) +{ + unsigned int i; + size_t len; + + len = sizeof(uint32_t); + for (i=0; inum; i++) { + len += ctdb_public_ip_len_old(&in->ip[i]); + } + return len; +} + +static void ctdb_public_ip_list_push_old(struct ctdb_public_ip_list *in, + uint8_t *buf) +{ + struct ctdb_public_ip_list_wire *wire = + (struct ctdb_public_ip_list_wire *)buf; + size_t offset; + unsigned int i; + + wire->num = in->num; + + offset = offsetof(struct ctdb_public_ip_list_wire, ip); + for (i=0; inum; i++) { + ctdb_public_ip_push_old(&in->ip[i], &buf[offset]); + offset += ctdb_public_ip_len_old(&in->ip[i]); + } +} + +static int ctdb_public_ip_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list **out) +{ + struct ctdb_public_ip_list *val; + struct ctdb_public_ip_list_wire *wire = + (struct ctdb_public_ip_list_wire *)buf; + size_t offset; + unsigned int i; + bool ret; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_public_ip)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_public_ip) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + + wire->num * sizeof(struct ctdb_public_ip)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_public_ip_list); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + if (wire->num == 0) { + val->ip = NULL; + *out = val; + return 0; + } + val->ip = talloc_array(val, struct ctdb_public_ip, wire->num); + if (val->ip == NULL) { + talloc_free(val); + return ENOMEM; + } + + offset = offsetof(struct ctdb_public_ip_list_wire, ip); + for (i=0; inum; i++) { + ret = ctdb_public_ip_pull_elems_old(&buf[offset], + buflen-offset, + val->ip, + &val->ip[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_public_ip_len_old(&val->ip[i]); + } + + *out = val; + return 0; +} + +static size_t ctdb_node_and_flags_len_old(struct ctdb_node_and_flags *in) +{ + return sizeof(struct ctdb_node_and_flags); +} + +static void ctdb_node_and_flags_push_old(struct ctdb_node_and_flags *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_node_and_flags)); +} + +static int ctdb_node_and_flags_pull_elems_old(TALLOC_CTX *mem_ctx, + uint8_t *buf, size_t buflen, + struct ctdb_node_and_flags *out) +{ + if (buflen < sizeof(struct ctdb_node_and_flags)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_node_and_flags)); + + return 0; +} + +static int ctdb_node_and_flags_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_node_and_flags **out) +{ + struct ctdb_node_and_flags *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_node_and_flags); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_node_and_flags_pull_elems_old(val, buf, buflen, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_node_map_wire { + uint32_t num; + struct ctdb_node_and_flags node[1]; +}; + +static size_t ctdb_node_map_len_old(struct ctdb_node_map *in) +{ + return sizeof(uint32_t) + + in->num * sizeof(struct ctdb_node_and_flags); +} + +static void ctdb_node_map_push_old(struct ctdb_node_map *in, uint8_t *buf) +{ + struct ctdb_node_map_wire *wire = (struct ctdb_node_map_wire *)buf; + size_t offset; + unsigned int i; + + wire->num = in->num; + + offset = offsetof(struct ctdb_node_map_wire, node); + for (i=0; inum; i++) { + ctdb_node_and_flags_push_old(&in->node[i], &buf[offset]); + offset += ctdb_node_and_flags_len_old(&in->node[i]); + } +} + +static int ctdb_node_map_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_node_map **out) +{ + struct ctdb_node_map *val; + struct ctdb_node_map_wire *wire = (struct ctdb_node_map_wire *)buf; + size_t offset; + unsigned int i; + bool ret; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_node_and_flags)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_node_and_flags) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + + wire->num * sizeof(struct ctdb_node_and_flags)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_node_map); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + val->node = talloc_array(val, struct ctdb_node_and_flags, wire->num); + if (val->node == NULL) { + talloc_free(val); + return ENOMEM; + } + + offset = offsetof(struct ctdb_node_map_wire, node); + for (i=0; inum; i++) { + ret = ctdb_node_and_flags_pull_elems_old(val->node, + &buf[offset], + buflen-offset, + &val->node[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_node_and_flags_len_old(&val->node[i]); + } + + *out = val; + return 0; +} + +static size_t ctdb_script_len_old(struct ctdb_script *in) +{ + return sizeof(struct ctdb_script); +} + +static void ctdb_script_push_old(struct ctdb_script *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_script)); +} + +static int ctdb_script_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_script *out) +{ + if (buflen < sizeof(struct ctdb_script)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_script)); + + return 0; +} + +static int ctdb_script_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_script **out) +{ + struct ctdb_script *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_script); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_script_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_script_list_wire { + uint32_t num_scripts; + struct ctdb_script script[1]; +}; + +static size_t ctdb_script_list_len_old(struct ctdb_script_list *in) +{ + unsigned int i; + size_t len; + + if (in == NULL) { + return 0; + } + + len = offsetof(struct ctdb_script_list_wire, script); + for (i=0; inum_scripts; i++) { + len += ctdb_script_len_old(&in->script[i]); + } + return len; +} + +static void ctdb_script_list_push_old(struct ctdb_script_list *in, + uint8_t *buf) +{ + struct ctdb_script_list_wire *wire = + (struct ctdb_script_list_wire *)buf; + size_t offset; + unsigned int i; + + if (in == NULL) { + return; + } + + wire->num_scripts = in->num_scripts; + + offset = offsetof(struct ctdb_script_list_wire, script); + for (i=0; inum_scripts; i++) { + ctdb_script_push_old(&in->script[i], &buf[offset]); + offset += ctdb_script_len_old(&in->script[i]); + } +} + +static int ctdb_script_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_script_list **out) +{ + struct ctdb_script_list *val; + struct ctdb_script_list_wire *wire = + (struct ctdb_script_list_wire *)buf; + size_t offset; + unsigned int i; + bool ret; + + /* If event scripts have never been run, the result will be NULL */ + if (buflen == 0) { + *out = NULL; + return 0; + } + + offset = offsetof(struct ctdb_script_list_wire, script); + + if (buflen < offset) { + return EMSGSIZE; + } + if (wire->num_scripts > buflen / sizeof(struct ctdb_script)) { + return EMSGSIZE; + } + if (offset + wire->num_scripts * sizeof(struct ctdb_script) < offset) { + return EMSGSIZE; + } + if (buflen < offset + wire->num_scripts * sizeof(struct ctdb_script)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_script_list); + if (val == NULL) { + return ENOMEM; + + } + + val->num_scripts = wire->num_scripts; + val->script = talloc_array(val, struct ctdb_script, wire->num_scripts); + if (val->script == NULL) { + talloc_free(val); + return ENOMEM; + } + + for (i=0; inum_scripts; i++) { + ret = ctdb_script_pull_elems_old(&buf[offset], buflen-offset, + val->script, + &val->script[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_script_len_old(&val->script[i]); + } + + *out = val; + return 0; +} + +static size_t ctdb_ban_state_len_old(struct ctdb_ban_state *in) +{ + return sizeof(struct ctdb_ban_state); +} + +static void ctdb_ban_state_push_old(struct ctdb_ban_state *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_ban_state)); +} + +static int ctdb_ban_state_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_ban_state **out) +{ + struct ctdb_ban_state *val; + + if (buflen < sizeof(struct ctdb_ban_state)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_ban_state)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_notify_data_wire { + uint64_t srvid; + uint32_t len; + uint8_t data[1]; +}; + +static size_t ctdb_notify_data_len_old(struct ctdb_notify_data *in) +{ + return offsetof(struct ctdb_notify_data_wire, data) + + in->data.dsize; +} + +static void ctdb_notify_data_push_old(struct ctdb_notify_data *in, + uint8_t *buf) +{ + struct ctdb_notify_data_wire *wire = + (struct ctdb_notify_data_wire *)buf; + + wire->srvid = in->srvid; + wire->len = in->data.dsize; + memcpy(wire->data, in->data.dptr, in->data.dsize); +} + +static int ctdb_notify_data_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_notify_data **out) +{ + struct ctdb_notify_data *val; + struct ctdb_notify_data_wire *wire = + (struct ctdb_notify_data_wire *)buf; + + if (buflen < offsetof(struct ctdb_notify_data_wire, data)) { + return EMSGSIZE; + } + if (wire->len > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_notify_data_wire, data) + wire->len < + offsetof(struct ctdb_notify_data_wire, data)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_notify_data_wire, data) + wire->len) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_notify_data); + if (val == NULL) { + return ENOMEM; + } + + val->srvid = wire->srvid; + val->data.dsize = wire->len; + val->data.dptr = talloc_memdup(val, wire->data, wire->len); + if (val->data.dptr == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_iface_len_old(struct ctdb_iface *in) +{ + return sizeof(struct ctdb_iface); +} + +static void ctdb_iface_push_old(struct ctdb_iface *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_iface)); +} + +static int ctdb_iface_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_iface *out) +{ + if (buflen < sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_iface)); + + return 0; +} + +static int ctdb_iface_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_iface **out) +{ + struct ctdb_iface *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_iface); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_iface_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_iface_list_wire { + uint32_t num; + struct ctdb_iface iface[1]; +}; + +static size_t ctdb_iface_list_len_old(struct ctdb_iface_list *in) +{ + return sizeof(uint32_t) + + in->num * sizeof(struct ctdb_iface); +} + +static void ctdb_iface_list_push_old(struct ctdb_iface_list *in, uint8_t *buf) +{ + struct ctdb_iface_list_wire *wire = + (struct ctdb_iface_list_wire *)buf; + + wire->num = in->num; + memcpy(wire->iface, in->iface, in->num * sizeof(struct ctdb_iface)); +} + +static int ctdb_iface_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_iface_list **out) +{ + struct ctdb_iface_list *val; + struct ctdb_iface_list_wire *wire = + (struct ctdb_iface_list_wire *)buf; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_iface) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + wire->num * sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_iface_list); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + val->iface = talloc_array(val, struct ctdb_iface, wire->num); + if (val->iface == NULL) { + talloc_free(val); + return ENOMEM; + } + + memcpy(val->iface, wire->iface, wire->num * sizeof(struct ctdb_iface)); + + *out = val; + return 0; +} + +struct ctdb_public_ip_info_wire { + struct ctdb_public_ip ip; + uint32_t active_idx; + uint32_t num; + struct ctdb_iface ifaces[1]; +}; + +static size_t ctdb_public_ip_info_len_old(struct ctdb_public_ip_info *in) +{ + return offsetof(struct ctdb_public_ip_info_wire, num) + + ctdb_iface_list_len_old(in->ifaces); +} + +static void ctdb_public_ip_info_push_old(struct ctdb_public_ip_info *in, + uint8_t *buf) +{ + struct ctdb_public_ip_info_wire *wire = + (struct ctdb_public_ip_info_wire *)buf; + size_t offset; + + offset = offsetof(struct ctdb_public_ip_info_wire, num); + memcpy(wire, in, offset); + wire->num = in->ifaces->num; + memcpy(wire->ifaces, in->ifaces->iface, + in->ifaces->num * sizeof(struct ctdb_iface)); +} + +static int ctdb_public_ip_info_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_info **out) +{ + struct ctdb_public_ip_info *val; + struct ctdb_public_ip_info_wire *wire = + (struct ctdb_public_ip_info_wire *)buf; + + if (buflen < offsetof(struct ctdb_public_ip_info_wire, ifaces)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_public_ip_info_wire, ifaces) + + wire->num * sizeof(struct ctdb_iface) < + offsetof(struct ctdb_public_ip_info_wire, ifaces)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_public_ip_info_wire, ifaces) + + wire->num * sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_public_ip_info); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, offsetof(struct ctdb_public_ip_info_wire, num)); + + val->ifaces = talloc(val, struct ctdb_iface_list); + if (val->ifaces == NULL) { + talloc_free(val); + return ENOMEM; + } + + val->ifaces->num = wire->num; + val->ifaces->iface = talloc_array(val->ifaces, struct ctdb_iface, + wire->num); + if (val->ifaces->iface == NULL) { + talloc_free(val); + return ENOMEM; + } + + memcpy(val->ifaces->iface, wire->ifaces, + wire->num * sizeof(struct ctdb_iface)); + + *out = val; + return 0; +} + +struct ctdb_statistics_list_wire { + uint32_t num; + struct ctdb_statistics stats[1]; +}; + +static size_t ctdb_statistics_list_len_old(struct ctdb_statistics_list *in) +{ + return offsetof(struct ctdb_statistics_list_wire, stats) + + in->num * sizeof(struct ctdb_statistics); +} + +static void ctdb_statistics_list_push_old(struct ctdb_statistics_list *in, + uint8_t *buf) +{ + struct ctdb_statistics_list_wire *wire = + (struct ctdb_statistics_list_wire *)buf; + + wire->num = in->num; + memcpy(wire->stats, in->stats, + in->num * sizeof(struct ctdb_statistics)); +} + +static int ctdb_statistics_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_statistics_list **out) +{ + struct ctdb_statistics_list *val; + struct ctdb_statistics_list_wire *wire = + (struct ctdb_statistics_list_wire *)buf; + + if (buflen < offsetof(struct ctdb_statistics_list_wire, stats)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_statistics)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_statistics_list_wire, stats) + + wire->num * sizeof(struct ctdb_statistics) < + offsetof(struct ctdb_statistics_list_wire, stats)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_statistics_list_wire, stats) + + wire->num * sizeof(struct ctdb_statistics)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_statistics_list); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + + val->stats = talloc_array(val, struct ctdb_statistics, wire->num); + if (val->stats == NULL) { + talloc_free(val); + return ENOMEM; + } + + memcpy(val->stats, wire->stats, + wire->num * sizeof(struct ctdb_statistics)); + + *out = val; + return 0; +} + +struct ctdb_key_data_wire { + uint32_t db_id; + struct ctdb_ltdb_header header; + uint32_t keylen; + uint8_t key[1]; +}; + +static size_t ctdb_key_data_len_old(struct ctdb_key_data *in) +{ + return offsetof(struct ctdb_key_data_wire, key) + in->key.dsize; +} + +static void ctdb_key_data_push_old(struct ctdb_key_data *in, uint8_t *buf) +{ + struct ctdb_key_data_wire *wire = (struct ctdb_key_data_wire *)buf; + + memcpy(wire, in, offsetof(struct ctdb_key_data, key)); + wire->keylen = in->key.dsize; + memcpy(wire->key, in->key.dptr, in->key.dsize); +} + +static int ctdb_key_data_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_key_data **out) +{ + struct ctdb_key_data *val; + struct ctdb_key_data_wire *wire = (struct ctdb_key_data_wire *)buf; + + if (buflen < offsetof(struct ctdb_key_data_wire, key)) { + return EMSGSIZE; + } + if (wire->keylen > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_key_data_wire, key) + wire->keylen < + offsetof(struct ctdb_key_data_wire, key)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_key_data_wire, key) + wire->keylen) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_key_data); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, offsetof(struct ctdb_key_data, key)); + + val->key.dsize = wire->keylen; + val->key.dptr = talloc_memdup(val, wire->key, wire->keylen); + if (val->key.dptr == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_db_statistics_wire { + struct ctdb_db_statistics dbstats; + char hot_keys_wire[1]; +}; + +static size_t ctdb_db_statistics_len_old(struct ctdb_db_statistics *in) +{ + size_t len; + int i; + + len = sizeof(struct ctdb_db_statistics); + for (i=0; ihot_keys[i].key.dsize; + } + return len; +} + +static void ctdb_db_statistics_push_old(struct ctdb_db_statistics *in, + void *buf) +{ + struct ctdb_db_statistics_wire *wire = + (struct ctdb_db_statistics_wire *)buf; + size_t offset; + int i; + + in->num_hot_keys = MAX_HOT_KEYS; + memcpy(wire, in, sizeof(struct ctdb_db_statistics)); + + offset = 0; + for (i=0; ihot_keys_wire[offset], + in->hot_keys[i].key.dptr, + in->hot_keys[i].key.dsize); + offset += in->hot_keys[i].key.dsize; + } +} + +static int ctdb_db_statistics_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_db_statistics **out) +{ + struct ctdb_db_statistics *val; + struct ctdb_db_statistics_wire *wire = + (struct ctdb_db_statistics_wire *)buf; + size_t offset; + unsigned int i; + + if (buflen < sizeof(struct ctdb_db_statistics)) { + return EMSGSIZE; + } + + offset = 0; + for (i=0; idbstats.num_hot_keys; i++) { + if (wire->dbstats.hot_keys[i].key.dsize > buflen) { + return EMSGSIZE; + } + if (offset + wire->dbstats.hot_keys[i].key.dsize < offset) { + return EMSGSIZE; + } + offset += wire->dbstats.hot_keys[i].key.dsize; + if (offset > buflen) { + return EMSGSIZE; + } + } + if (sizeof(struct ctdb_db_statistics) + offset < + sizeof(struct ctdb_db_statistics)) { + return EMSGSIZE; + } + if (buflen < sizeof(struct ctdb_db_statistics) + offset) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_db_statistics); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, sizeof(struct ctdb_db_statistics)); + + offset = 0; + for (i=0; idbstats.num_hot_keys; i++) { + uint8_t *ptr; + size_t key_size; + + key_size = val->hot_keys[i].key.dsize; + ptr = talloc_memdup(mem_ctx, &wire->hot_keys_wire[offset], + key_size); + if (ptr == NULL) { + talloc_free(val); + return ENOMEM; + } + val->hot_keys[i].key.dptr = ptr; + offset += key_size; + } + + *out = val; + return 0; +} + +static size_t ctdb_election_message_len_old(struct ctdb_election_message *in) +{ + return sizeof(struct ctdb_election_message); +} + +static void ctdb_election_message_push_old(struct ctdb_election_message *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_election_message)); +} + +static int ctdb_election_message_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_election_message **out) +{ + struct ctdb_election_message *val; + + if (buflen < sizeof(struct ctdb_election_message)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_election_message)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_srvid_message_len_old(struct ctdb_srvid_message *in) +{ + return sizeof(struct ctdb_srvid_message); +} + +static void ctdb_srvid_message_push_old(struct ctdb_srvid_message *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_srvid_message)); +} + +static int ctdb_srvid_message_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_srvid_message **out) +{ + struct ctdb_srvid_message *val; + + if (buflen < sizeof(struct ctdb_srvid_message)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_srvid_message)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_disable_message_len_old(struct ctdb_disable_message *in) +{ + return sizeof(struct ctdb_disable_message); +} + +static void ctdb_disable_message_push_old(struct ctdb_disable_message *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_disable_message)); +} + +static int ctdb_disable_message_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_disable_message **out) +{ + struct ctdb_disable_message *val; + + if (buflen < sizeof(struct ctdb_disable_message)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_disable_message)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_server_id_len_old(struct ctdb_server_id *in) +{ + return sizeof(struct ctdb_server_id); +} + +static void ctdb_server_id_push_old(struct ctdb_server_id *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_server_id)); +} + +static int ctdb_server_id_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_server_id *out) +{ + if (buflen < sizeof(struct ctdb_server_id)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_server_id)); + return 0; +} + +static size_t ctdb_g_lock_len_old(struct ctdb_g_lock *in) +{ + return sizeof(struct ctdb_g_lock); +} + +static void ctdb_g_lock_push_old(struct ctdb_g_lock *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_g_lock)); +} + +static int ctdb_g_lock_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_g_lock *out) +{ + if (buflen < sizeof(struct ctdb_g_lock)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_g_lock)); + return 0; +} + +static size_t ctdb_g_lock_list_len_old(struct ctdb_g_lock_list *in) +{ + return in->num * sizeof(struct ctdb_g_lock); +} + +static void ctdb_g_lock_list_push_old(struct ctdb_g_lock_list *in, + uint8_t *buf) +{ + size_t offset = 0; + unsigned int i; + + for (i=0; inum; i++) { + ctdb_g_lock_push_old(&in->lock[i], &buf[offset]); + offset += sizeof(struct ctdb_g_lock); + } +} + +static int ctdb_g_lock_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_g_lock_list **out) +{ + struct ctdb_g_lock_list *val; + unsigned count; + size_t offset; + unsigned int i; + int ret; + + val = talloc_zero(mem_ctx, struct ctdb_g_lock_list); + if (val == NULL) { + return ENOMEM; + } + + count = buflen / sizeof(struct ctdb_g_lock); + val->lock = talloc_array(val, struct ctdb_g_lock, count); + if (val->lock == NULL) { + talloc_free(val); + return ENOMEM; + } + + offset = 0; + for (i=0; ilock[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += sizeof(struct ctdb_g_lock); + } + + val->num = count; + + *out = val; + return 0; +} + +COMPAT_TYPE3_TEST(struct ctdb_statistics, ctdb_statistics); +COMPAT_TYPE3_TEST(struct ctdb_vnn_map, ctdb_vnn_map); +COMPAT_TYPE3_TEST(struct ctdb_dbid_map, ctdb_dbid_map); +COMPAT_TYPE3_TEST(struct ctdb_pulldb, ctdb_pulldb); +COMPAT_TYPE3_TEST(struct ctdb_pulldb_ext, ctdb_pulldb_ext); + +COMPAT_TYPE1_TEST(struct ctdb_ltdb_header, ctdb_ltdb_header); + +COMPAT_TYPE3_TEST(struct ctdb_rec_data, ctdb_rec_data); +COMPAT_TYPE3_TEST(struct ctdb_rec_buffer, ctdb_rec_buffer); +COMPAT_TYPE3_TEST(struct ctdb_traverse_start, ctdb_traverse_start); +COMPAT_TYPE3_TEST(struct ctdb_traverse_all, ctdb_traverse_all); +COMPAT_TYPE3_TEST(struct ctdb_traverse_start_ext, ctdb_traverse_start_ext); +COMPAT_TYPE3_TEST(struct ctdb_traverse_all_ext, ctdb_traverse_all_ext); +COMPAT_TYPE3_TEST(ctdb_sock_addr, ctdb_sock_addr); +COMPAT_TYPE3_TEST(struct ctdb_connection, ctdb_connection); +COMPAT_TYPE3_TEST(struct ctdb_tunable, ctdb_tunable); +COMPAT_TYPE3_TEST(struct ctdb_node_flag_change, ctdb_node_flag_change); +COMPAT_TYPE3_TEST(struct ctdb_var_list, ctdb_var_list); +COMPAT_TYPE3_TEST(struct ctdb_tunable_list, ctdb_tunable_list); +COMPAT_TYPE3_TEST(struct ctdb_tickle_list, ctdb_tickle_list); +COMPAT_TYPE3_TEST(struct ctdb_addr_info, ctdb_addr_info); +COMPAT_TYPE3_TEST(struct ctdb_transdb, ctdb_transdb); +COMPAT_TYPE3_TEST(struct ctdb_uptime, ctdb_uptime); +COMPAT_TYPE3_TEST(struct ctdb_public_ip, ctdb_public_ip); +COMPAT_TYPE3_TEST(struct ctdb_public_ip_list, ctdb_public_ip_list); +COMPAT_TYPE3_TEST(struct ctdb_node_and_flags, ctdb_node_and_flags); +COMPAT_TYPE3_TEST(struct ctdb_node_map, ctdb_node_map); +COMPAT_TYPE3_TEST(struct ctdb_script, ctdb_script); +COMPAT_TYPE3_TEST(struct ctdb_script_list, ctdb_script_list); +COMPAT_TYPE3_TEST(struct ctdb_ban_state, ctdb_ban_state); +COMPAT_TYPE3_TEST(struct ctdb_notify_data, ctdb_notify_data); +COMPAT_TYPE3_TEST(struct ctdb_iface, ctdb_iface); +COMPAT_TYPE3_TEST(struct ctdb_iface_list, ctdb_iface_list); +COMPAT_TYPE3_TEST(struct ctdb_public_ip_info, ctdb_public_ip_info); +COMPAT_TYPE3_TEST(struct ctdb_statistics_list, ctdb_statistics_list); +COMPAT_TYPE3_TEST(struct ctdb_key_data, ctdb_key_data); +COMPAT_TYPE3_TEST(struct ctdb_db_statistics, ctdb_db_statistics); + +COMPAT_TYPE3_TEST(struct ctdb_election_message, ctdb_election_message); +COMPAT_TYPE3_TEST(struct ctdb_srvid_message, ctdb_srvid_message); +COMPAT_TYPE3_TEST(struct ctdb_disable_message, ctdb_disable_message); + +COMPAT_TYPE1_TEST(struct ctdb_server_id, ctdb_server_id); +COMPAT_TYPE1_TEST(struct ctdb_g_lock, ctdb_g_lock); + +COMPAT_TYPE3_TEST(struct ctdb_g_lock_list, ctdb_g_lock_list); + +static void protocol_types_compat_test(void) +{ + COMPAT_TEST_FUNC(ctdb_statistics)(); + COMPAT_TEST_FUNC(ctdb_vnn_map)(); + COMPAT_TEST_FUNC(ctdb_dbid_map)(); + COMPAT_TEST_FUNC(ctdb_pulldb)(); + COMPAT_TEST_FUNC(ctdb_pulldb_ext)(); + COMPAT_TEST_FUNC(ctdb_ltdb_header)(); + COMPAT_TEST_FUNC(ctdb_rec_data)(); + COMPAT_TEST_FUNC(ctdb_rec_buffer)(); + COMPAT_TEST_FUNC(ctdb_traverse_start)(); + COMPAT_TEST_FUNC(ctdb_traverse_all)(); + COMPAT_TEST_FUNC(ctdb_traverse_start_ext)(); + COMPAT_TEST_FUNC(ctdb_traverse_all_ext)(); + COMPAT_TEST_FUNC(ctdb_sock_addr)(); + COMPAT_TEST_FUNC(ctdb_connection)(); + COMPAT_TEST_FUNC(ctdb_tunable)(); + COMPAT_TEST_FUNC(ctdb_node_flag_change)(); + COMPAT_TEST_FUNC(ctdb_var_list)(); + COMPAT_TEST_FUNC(ctdb_tunable_list)(); + COMPAT_TEST_FUNC(ctdb_tickle_list)(); + COMPAT_TEST_FUNC(ctdb_addr_info)(); + COMPAT_TEST_FUNC(ctdb_transdb)(); + COMPAT_TEST_FUNC(ctdb_uptime)(); + COMPAT_TEST_FUNC(ctdb_public_ip)(); + COMPAT_TEST_FUNC(ctdb_public_ip_list)(); + COMPAT_TEST_FUNC(ctdb_node_and_flags)(); + COMPAT_TEST_FUNC(ctdb_node_map)(); + COMPAT_TEST_FUNC(ctdb_script)(); + COMPAT_TEST_FUNC(ctdb_script_list)(); + COMPAT_TEST_FUNC(ctdb_ban_state)(); + COMPAT_TEST_FUNC(ctdb_notify_data)(); + COMPAT_TEST_FUNC(ctdb_iface)(); + COMPAT_TEST_FUNC(ctdb_iface_list)(); + COMPAT_TEST_FUNC(ctdb_public_ip_info)(); + COMPAT_TEST_FUNC(ctdb_statistics_list)(); + COMPAT_TEST_FUNC(ctdb_key_data)(); + COMPAT_TEST_FUNC(ctdb_db_statistics)(); + + COMPAT_TEST_FUNC(ctdb_election_message)(); + COMPAT_TEST_FUNC(ctdb_srvid_message)(); + COMPAT_TEST_FUNC(ctdb_disable_message)(); + COMPAT_TEST_FUNC(ctdb_server_id)(); + COMPAT_TEST_FUNC(ctdb_g_lock)(); + COMPAT_TEST_FUNC(ctdb_g_lock_list)(); +} + +int main(int argc, const char *argv[]) +{ + protocol_test_iterate(argc, argv, protocol_types_compat_test); + return 0; +} diff --git a/ctdb/tests/src/protocol_types_test.c b/ctdb/tests/src/protocol_types_test.c new file mode 100644 index 0000000..f4a3048 --- /dev/null +++ b/ctdb/tests/src/protocol_types_test.c @@ -0,0 +1,194 @@ +/* + protocol types tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_sock.c" + +#include "tests/src/protocol_common.h" + +PROTOCOL_TYPE2_TEST(TDB_DATA, ctdb_tdb_data); +PROTOCOL_TYPE2_TEST(TDB_DATA, ctdb_tdb_datan); +PROTOCOL_TYPE1_TEST(struct ctdb_latency_counter, ctdb_latency_counter); + +PROTOCOL_TYPE3_TEST(struct ctdb_statistics, ctdb_statistics); +PROTOCOL_TYPE3_TEST(struct ctdb_vnn_map, ctdb_vnn_map); +PROTOCOL_TYPE3_TEST(struct ctdb_dbid, ctdb_dbid); +PROTOCOL_TYPE3_TEST(struct ctdb_dbid_map, ctdb_dbid_map); +PROTOCOL_TYPE3_TEST(struct ctdb_pulldb, ctdb_pulldb); +PROTOCOL_TYPE3_TEST(struct ctdb_pulldb_ext, ctdb_pulldb_ext); +PROTOCOL_TYPE3_TEST(struct ctdb_db_vacuum, ctdb_db_vacuum); +PROTOCOL_TYPE3_TEST(struct ctdb_echo_data, ctdb_echo_data); +PROTOCOL_TYPE1_TEST(struct ctdb_ltdb_header, ctdb_ltdb_header); +PROTOCOL_TYPE3_TEST(struct ctdb_rec_data, ctdb_rec_data); +PROTOCOL_TYPE3_TEST(struct ctdb_rec_buffer, ctdb_rec_buffer); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_start, ctdb_traverse_start); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_all, ctdb_traverse_all); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_start_ext, ctdb_traverse_start_ext); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_all_ext, ctdb_traverse_all_ext); +PROTOCOL_TYPE3_TEST(ctdb_sock_addr, ctdb_sock_addr); +PROTOCOL_TYPE3_TEST(struct ctdb_connection, ctdb_connection); +PROTOCOL_TYPE3_TEST(struct ctdb_connection_list, ctdb_connection_list); +PROTOCOL_TYPE3_TEST(struct ctdb_tunable, ctdb_tunable); +PROTOCOL_TYPE3_TEST(struct ctdb_node_flag_change, ctdb_node_flag_change); +PROTOCOL_TYPE3_TEST(struct ctdb_var_list, ctdb_var_list); +PROTOCOL_TYPE3_TEST(struct ctdb_tunable_list, ctdb_tunable_list); +PROTOCOL_TYPE3_TEST(struct ctdb_tickle_list, ctdb_tickle_list); +PROTOCOL_TYPE3_TEST(struct ctdb_addr_info, ctdb_addr_info); +PROTOCOL_TYPE3_TEST(struct ctdb_transdb, ctdb_transdb); +PROTOCOL_TYPE3_TEST(struct ctdb_uptime, ctdb_uptime); +PROTOCOL_TYPE3_TEST(struct ctdb_public_ip, ctdb_public_ip); +PROTOCOL_TYPE3_TEST(struct ctdb_public_ip_list, ctdb_public_ip_list); +PROTOCOL_TYPE3_TEST(struct ctdb_node_and_flags, ctdb_node_and_flags); +PROTOCOL_TYPE3_TEST(struct ctdb_node_map, ctdb_node_map); +PROTOCOL_TYPE3_TEST(struct ctdb_script, ctdb_script); +PROTOCOL_TYPE3_TEST(struct ctdb_script_list, ctdb_script_list); +PROTOCOL_TYPE3_TEST(struct ctdb_ban_state, ctdb_ban_state); +PROTOCOL_TYPE3_TEST(struct ctdb_notify_data, ctdb_notify_data); +PROTOCOL_TYPE3_TEST(struct ctdb_iface, ctdb_iface); +PROTOCOL_TYPE3_TEST(struct ctdb_iface_list, ctdb_iface_list); +PROTOCOL_TYPE3_TEST(struct ctdb_public_ip_info, ctdb_public_ip_info); +PROTOCOL_TYPE3_TEST(struct ctdb_statistics_list, ctdb_statistics_list); +PROTOCOL_TYPE3_TEST(struct ctdb_key_data, ctdb_key_data); +PROTOCOL_TYPE3_TEST(struct ctdb_db_statistics, ctdb_db_statistics); +PROTOCOL_TYPE3_TEST(struct ctdb_pid_srvid, ctdb_pid_srvid); +PROTOCOL_TYPE3_TEST(struct ctdb_election_message, ctdb_election_message); +PROTOCOL_TYPE3_TEST(struct ctdb_srvid_message, ctdb_srvid_message); +PROTOCOL_TYPE3_TEST(struct ctdb_disable_message, ctdb_disable_message); +PROTOCOL_TYPE1_TEST(struct ctdb_server_id, ctdb_server_id); +PROTOCOL_TYPE1_TEST(struct ctdb_g_lock, ctdb_g_lock); +PROTOCOL_TYPE3_TEST(struct ctdb_g_lock_list, ctdb_g_lock_list); + +PROTOCOL_TYPE1_TEST(struct sock_packet_header, sock_packet_header); + +static void test_ctdb_rec_buffer_read_write(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct ctdb_rec_buffer *p1, **p2; + const char *filename = "ctdb_rec_buffer_test.dat"; + int count = 100; + int fd, i, ret; + off_t offset; + + p1 = talloc_array(mem_ctx, struct ctdb_rec_buffer, count); + assert(p1 != NULL); + for (i=0; i. +*/ + +#include "replace.h" +#include "system/network.h" + +#include + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_util.c" + +/* + * Test parsing of IPs, conversion to string + */ + +static void test_sock_addr_to_string(const char *ip, bool with_port) +{ + ctdb_sock_addr sa; + const char *s; + int ret; + + ret = ctdb_sock_addr_from_string(ip, &sa, with_port); + assert(ret == 0); + s = ctdb_sock_addr_to_string(NULL, &sa, with_port); + assert(strcmp(ip, s) == 0); + talloc_free(discard_const(s)); +} + +static void test_sock_addr_from_string_bad(const char *ip, bool with_port) +{ + ctdb_sock_addr sa; + int ret; + + ret = ctdb_sock_addr_from_string(ip, &sa, with_port); + assert(ret == EINVAL); +} + +static void test_sock_addr_from_string_memcmp(const char *ip1, + const char* ip2) +{ + ctdb_sock_addr sa1, sa2; + int ret; + + ret = ctdb_sock_addr_from_string(ip1, &sa1, false); + assert(ret == 0); + ret = ctdb_sock_addr_from_string(ip2, &sa2, false); + assert(ret == 0); + ret = memcmp(&sa1, &sa2, sizeof(ctdb_sock_addr)); + assert(ret == 0); +} + +static void test_sock_addr_cmp(const char *ip1, const char *ip2, + bool with_port, int res) +{ + ctdb_sock_addr sa1, sa2; + int ret; + + ret = ctdb_sock_addr_from_string(ip1, &sa1, with_port); + assert(ret == 0); + ret = ctdb_sock_addr_from_string(ip2, &sa2, with_port); + assert(ret == 0); + ret = ctdb_sock_addr_cmp(&sa1, &sa2); + if (ret < 0) { + ret = -1; + } else if (ret > 0) { + ret = 1; + } + + assert(ret == res); +} + +/* + * Test parsing of IP/mask, conversion to string + */ + +static void test_sock_addr_mask_from_string(const char *ip_mask) +{ + ctdb_sock_addr sa; + unsigned mask; + const char *s, *t; + int ret; + + ret = ctdb_sock_addr_mask_from_string(ip_mask, &sa, &mask); + assert(ret == 0); + s = ctdb_sock_addr_to_string(NULL, &sa, false); + assert(s != NULL); + t = talloc_asprintf(s, "%s/%u", s, mask); + assert(strcmp(ip_mask, t) == 0); + talloc_free(discard_const(s)); +} + +static void test_sock_addr_mask_from_string_bad(const char *ip_mask) +{ + ctdb_sock_addr sa; + unsigned mask; + int ret; + + ret = ctdb_sock_addr_mask_from_string(ip_mask, &sa, &mask); + assert(ret == EINVAL); +} + +/* + * Test parsing of connection, conversion to string + */ + +static void test_connection_to_string(const char *conn_str) +{ + TALLOC_CTX *tmp_ctx; + struct ctdb_connection conn; + const char *s, *r; + int ret; + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + /* + * Test non-reversed parse and render + */ + + ret = ctdb_connection_from_string(conn_str, false, &conn); + assert(ret == 0); + + s = ctdb_connection_to_string(tmp_ctx, &conn, false); + assert(s != NULL); + ret = strcmp(conn_str, s); + assert(ret == 0); + + talloc_free(discard_const(s)); + + /* + * Reversed render + */ + r = ctdb_connection_to_string(tmp_ctx, &conn, true); + assert(r != NULL); + ret = strcmp(conn_str, r); + assert(ret != 0); + + /* + * Reversed parse with forward render + */ + ret = ctdb_connection_from_string(conn_str, true, &conn); + assert(ret == 0); + + s = ctdb_connection_to_string(tmp_ctx, &conn, false); + assert(s != NULL); + ret = strcmp(r, s); + assert(ret == 0); + + talloc_free(discard_const(s)); + + /* + * Reversed parse and render + */ + ret = ctdb_connection_from_string(conn_str, true, &conn); + assert(ret == 0); + + s = ctdb_connection_to_string(tmp_ctx, &conn, true); + assert(s != NULL); + ret = strcmp(conn_str, s); + assert(ret == 0); + + talloc_free(tmp_ctx); +} + +static void test_connection_from_string_bad(const char *conn_str) +{ + struct ctdb_connection conn; + int ret; + + ret = ctdb_connection_from_string(conn_str, false, &conn); + assert(ret == EINVAL); +} + +/* + * Test connection list utilities + */ + +static void test_connection_list_read(const char *s1, const char *s2) +{ + TALLOC_CTX *tmp_ctx; + int pipefd[2]; + pid_t pid; + struct ctdb_connection_list *conn_list = NULL; + const char *t; + int ret; + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + close(pipefd[0]); + + ret = dup2(pipefd[1], STDOUT_FILENO); + assert(ret != -1); + + close(pipefd[1]); + + printf("%s", s1); + fflush(stdout); + + exit(0); + } + + close(pipefd[1]); + + ret = ctdb_connection_list_read(tmp_ctx, pipefd[0], false, &conn_list); + assert(ret == 0); + + close(pipefd[0]); + + ret = ctdb_connection_list_sort(conn_list); + assert(ret == 0); + + t = ctdb_connection_list_to_string(tmp_ctx, conn_list, false); + assert(t != NULL); + ret = strcmp(t, s2); + assert(ret == 0); + + talloc_free(tmp_ctx); +} + +static void test_connection_list_read_bad(const char *s1) +{ + TALLOC_CTX *tmp_ctx; + int pipefd[2]; + pid_t pid; + struct ctdb_connection_list *conn_list = NULL; + int ret; + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + close(pipefd[0]); + + ret = dup2(pipefd[1], STDOUT_FILENO); + assert(ret != -1); + + close(pipefd[1]); + + printf("%s", s1); + fflush(stdout); + + exit(0); + } + + close(pipefd[1]); + + ret = ctdb_connection_list_read(tmp_ctx, pipefd[0], false, &conn_list); + assert(ret == EINVAL); + + close(pipefd[0]); + + talloc_free(tmp_ctx); +} + +/* + * Use macros for these to make them easy to concatenate + */ + +#define CONN4 \ +"\ +127.0.0.1:12345 127.0.0.2:54321\n\ +127.0.0.2:12345 127.0.0.1:54322\n\ +127.0.0.1:12346 127.0.0.2:54323\n\ +127.0.0.2:12345 127.0.0.1:54324\n\ +127.0.0.1:12345 127.0.0.2:54325\n\ +" + +#define CONN4_SORT \ +"\ +127.0.0.1:12345 127.0.0.2:54321\n\ +127.0.0.1:12345 127.0.0.2:54325\n\ +127.0.0.1:12346 127.0.0.2:54323\n\ +127.0.0.2:12345 127.0.0.1:54322\n\ +127.0.0.2:12345 127.0.0.1:54324\n\ +" + +#define CONN6 \ +"\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54321\n\ +[fe80::6af7:28ff:fefa:d138]:12345 [fe80::6af7:28ff:fefa:d137]:54322\n\ +[fe80::6af7:28ff:fefa:d136]:12346 [fe80::6af7:28ff:fefa:d137]:54323\n\ +[fe80::6af7:28ff:fefa:d132]:12345 [fe80::6af7:28ff:fefa:d137]:54324\n\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54325\n\ +" + +#define CONN6_SORT \ +"\ +[fe80::6af7:28ff:fefa:d132]:12345 [fe80::6af7:28ff:fefa:d137]:54324\n\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54321\n\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54325\n\ +[fe80::6af7:28ff:fefa:d136]:12346 [fe80::6af7:28ff:fefa:d137]:54323\n\ +[fe80::6af7:28ff:fefa:d138]:12345 [fe80::6af7:28ff:fefa:d137]:54322\n\ +" + +int main(int argc, char *argv[]) +{ + test_sock_addr_to_string("0.0.0.0", false); + test_sock_addr_to_string("127.0.0.1", false); + test_sock_addr_to_string("::1", false); + test_sock_addr_to_string("192.168.2.1", false); + test_sock_addr_to_string("fe80::6af7:28ff:fefa:d136", false); + + test_sock_addr_to_string("0.0.0.0:0", true); + test_sock_addr_to_string("127.0.0.1:123", true); + test_sock_addr_to_string("[::1]:234", true); + test_sock_addr_to_string("192.168.2.1:123", true); + test_sock_addr_to_string("[fe80::6af7:28ff:fefa:d136]:234", true); + + test_sock_addr_from_string_bad("0.0.0", false); + test_sock_addr_from_string_bad("0.0.0:0", true); + test_sock_addr_from_string_bad("fe80::6af7:28ff:fefa:d136", true); + test_sock_addr_from_string_bad("junk", false); + test_sock_addr_from_string_bad("0.0.0.0:0 trailing junk", true); + + test_sock_addr_from_string_memcmp("127.0.0.1", "127.0.0.1"); + test_sock_addr_from_string_memcmp("fe80::6af7:28ff:fefa:d136", + "fe80::6af7:28ff:fefa:d136"); + test_sock_addr_from_string_memcmp("::ffff:192.0.2.128", "192.0.2.128"); + + test_sock_addr_cmp("127.0.0.1", "127.0.0.1" , false, 0); + test_sock_addr_cmp("127.0.0.1", "127.0.0.2" , false, -1); + test_sock_addr_cmp("127.0.0.2", "127.0.0.1" , false, 1); + test_sock_addr_cmp("127.0.1.2", "127.0.2.1" , false, -1); + test_sock_addr_cmp("127.0.2.1", "127.0.1.2" , false, 1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", "127.0.1.2" , false, 1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", + "fe80::6af7:28ff:fefa:d136" , false, 0); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", + "fe80::6af7:28ff:fefa:d137" , false, -1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", + "fe80:0000:0000:0000:6af7:28ff:fefa:d136" , + false, 0); + test_sock_addr_cmp("::ffff:192.0.2.128", "192.0.2.128", false, 0); + + test_sock_addr_cmp("127.0.0.1:123", "127.0.0.1:124" , true, -1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136:123", + "fe80::6af7:28ff:fefa:d136:122" , true, 1); + + /* + * Confirm equivalence of IPv6 sockets with and without + * square-brackets + */ + test_sock_addr_cmp("[::1]:234", "::1:234", true, 0); + test_sock_addr_cmp("[fe80::6af7:28ff:fefa:d136]:234", + "fe80::6af7:28ff:fefa:d136:234", + true, + 0); + /* Check IPv4-mapped IPv6 addresses */ + test_sock_addr_cmp("::ffff:172.16.0.27:977", + "172.16.0.27:977", + true, + 0); + test_sock_addr_cmp("[::ffff:172.16.0.27]:977", + "172.16.0.27:977", + true, + 0); + + test_sock_addr_mask_from_string("127.0.0.1/8"); + test_sock_addr_mask_from_string("::1/128"); + test_sock_addr_mask_from_string("fe80::6af7:28ff:fefa:d136/64"); + test_sock_addr_mask_from_string_bad("127.0.0.1"); + + test_connection_to_string("127.0.0.1:12345 127.0.0.2:54321"); + test_connection_to_string("[fe80::6af7:28ff:fefa:d137]:12345 " + "[fe80::6af7:28ff:fefa:d138]:54321"); + + test_connection_from_string_bad("127.0.0.1:12345 127.0.0.2:"); + test_connection_from_string_bad("127.0.0.1:12345"); + test_connection_from_string_bad("127.0.0.1:12345 " + "[fe80::6af7:28ff:fefa:d136]:122"); + test_connection_from_string_bad("Junk!"); + test_connection_from_string_bad("More junk"); + + test_connection_list_read(CONN4, CONN4_SORT); + test_connection_list_read(CONN6, CONN6_SORT); + test_connection_list_read(CONN4 CONN6, CONN4_SORT CONN6_SORT); + test_connection_list_read(CONN4 "# Comment\n\n# Comment\n" CONN6, + CONN4_SORT CONN6_SORT); + + test_connection_list_read_bad(CONN4 "# Comment\n\nJunk!!!\n" CONN6); + test_connection_list_read_bad(CONN4 + "# Comment\n\n127.0.0.1: 127.0.0.1:124\n" + CONN6); + + return 0; +} diff --git a/ctdb/tests/src/rb_test.c b/ctdb/tests/src/rb_test.c new file mode 100644 index 0000000..d712c9a --- /dev/null +++ b/ctdb/tests/src/rb_test.c @@ -0,0 +1,336 @@ +/* + simple rb test tool + + Copyright (C) Ronnie Sahlberg 2007 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/time.h" + +#include +#include + +#include "lib/util/dlinklist.h" +#include "lib/util/debug.h" + +#include "common/rb_tree.c" + +static struct timeval tp1,tp2; + +static void start_timer(void) +{ + gettimeofday(&tp1,NULL); +} + +static double end_timer(void) +{ + gettimeofday(&tp2,NULL); + return (tp2.tv_sec + (tp2.tv_usec*1.0e-6)) - + (tp1.tv_sec + (tp1.tv_usec*1.0e-6)); +} + +int num_records=5; + +static void *callback(void *p, void *d) +{ + uint32_t *data = (uint32_t *)d; + + if (d==NULL) { + data = (uint32_t *)p; + } + + (*data)++; + + return data; +} + +static void *random_add(void *p, void *d) +{ + return p; +} + +static int traverse(void *p, void *d) +{ + uint32_t *data = (uint32_t *)d; + + printf("traverse data:%d\n",*data); + return 0; +} + +static int random_traverse(void *p, void *d) +{ + printf("%s ",(char *)d); + return 0; +} + +static uint32_t calc_checksum = 0; +static int traverse_checksum(void *p, void *d) +{ + int i,j,k; + + sscanf(d, "%d.%d.%d", &i, &j, &k); + calc_checksum += i*100+j*10+k; + return 0; +} + +static int count_traverse(void *p, void *d) +{ + int *count = p; + (*count)++; + return 0; +} + +static int count_traverse_abort(void *p, void *d) +{ + int *count = p; + (*count)++; + return -1; +} + +/* + main program +*/ +int main(int argc, const char *argv[]) +{ + int traverse_count; + int i,j,k; + trbt_tree_t *tree; + uint32_t *data; + uint32_t key[3]; + uint32_t key1[3] = {0,10,20}; + uint32_t key2[3] = {0,10,21}; + uint32_t key3[3] = {0,11,20}; + uint32_t key4[3] = {2,10,20}; + TALLOC_CTX *memctx; + uint32_t **u32array; + uint32_t checksum; + + /* testing trbt_insert32_callback for num_records */ + memctx = talloc_new(NULL); + assert(memctx != NULL); + + u32array = talloc_array(memctx, uint32_t *, num_records); + assert(u32array != NULL); + + tree = trbt_create(memctx, 0); + assert(tree != NULL); + + for (i=0; i 0); + + traverse_count = 0; + trbt_traversearray32(tree, 3, count_traverse_abort, &traverse_count); + assert(traverse_count == 1); + + printf("\ndeleting all entries\n"); + for(i=0;i<10;i++){ + for(j=0;j<10;j++){ + for(k=0;k<10;k++){ + key[0]=i; + key[1]=j; + key[2]=k; + talloc_free(trbt_lookuparray32(tree, 3, key)); + } + } + } + trbt_traversearray32(tree, 3, random_traverse, NULL); + + assert(talloc_total_size(memctx) == 16); + + return 0; +} diff --git a/ctdb/tests/src/reqid_test.c b/ctdb/tests/src/reqid_test.c new file mode 100644 index 0000000..2a0828c --- /dev/null +++ b/ctdb/tests/src/reqid_test.c @@ -0,0 +1,89 @@ +/* + reqid tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include +#include + +#include "common/reqid.c" + + +int main(void) +{ + struct reqid_context *reqid_ctx; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + int i, ret; + uint32_t reqid; + int *data, *tmp; + + ret = reqid_init(mem_ctx, INT_MAX-200, &reqid_ctx); + assert(ret == 0); + + data = talloc_zero(mem_ctx, int); + assert(data != 0); + + for (i=0; i<1024*1024; i++) { + reqid = reqid_new(reqid_ctx, data); + assert(reqid != REQID_INVALID); + } + + for (i=0; i<1024; i++) { + tmp = reqid_find(reqid_ctx, i, int); + assert(tmp == data); + } + + for (i=0; i<1024; i++) { + ret = reqid_remove(reqid_ctx, i); + assert(ret == 0); + } + + for (i=0; i<1024; i++) { + tmp = reqid_find(reqid_ctx, i, int); + assert(tmp == NULL); + } + + for (i=0; i<1024; i++) { + ret = reqid_remove(reqid_ctx, i); + assert(ret == ENOENT); + } + + talloc_free(reqid_ctx); + assert(talloc_get_size(mem_ctx) == 0); + + ret = reqid_init(mem_ctx, INT_MAX-1, &reqid_ctx); + assert(ret == 0); + + reqid = reqid_new(reqid_ctx, data); + assert(reqid == INT_MAX); + + reqid = reqid_new(reqid_ctx, data); + assert(reqid == 0); + + reqid_remove(reqid_ctx, 0); + + reqid = reqid_new(reqid_ctx, data); + assert(reqid == 1); + + talloc_free(reqid_ctx); + + talloc_free(mem_ctx); + + return 0; +} diff --git a/ctdb/tests/src/run_event_test.c b/ctdb/tests/src/run_event_test.c new file mode 100644 index 0000000..9454864 --- /dev/null +++ b/ctdb/tests/src/run_event_test.c @@ -0,0 +1,251 @@ +/* + run_event test wrapper + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include +#include + +#include "common/db_hash.c" +#include "common/run_proc.c" +#include "common/event_script.c" +#include "common/run_event.c" + +static void usage(const char *prog) +{ + fprintf(stderr, "Usage: %s run|list|enable|disable \n", prog); + fprintf(stderr, " %s run []\n", prog); + fprintf(stderr, " %s list\n", prog); + fprintf(stderr, " %s enable \n", prog); + fprintf(stderr, " %s disable \n", prog); +} + +static char *compact_args(const char **argv, int argc, int from) +{ + char *arg_str = NULL; + int i; + + for (i = from; i < argc; i++) { + arg_str = talloc_asprintf_append(arg_str, "%s ", argv[i]); + if (arg_str == NULL) { + fprintf(stderr, "talloc_asprintf_append() failed\n"); + exit(1); + } + } + + return arg_str; +} + +static void run_done(struct tevent_req *req) +{ + struct run_event_script_list **script_list = + tevent_req_callback_data_void(req); + bool status; + int ret; + + status = run_event_recv(req, &ret, NULL, script_list); + if (!status) { + fprintf(stderr, "run_event_recv() failed, ret=%d\n", ret); + } +} + +static void do_run(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + struct tevent_req *req; + struct timeval timeout; + struct run_event_script_list *script_list = NULL; + char *arg_str; + unsigned int i; + int t; + bool wait_for_signal = false; + + if (argc < 5) { + usage(argv[0]); + exit(1); + } + + t = atoi(argv[3]); + if (t > 0) { + timeout = tevent_timeval_current_ofs(t, 0); + } else { + timeout = tevent_timeval_zero(); + } + + arg_str = compact_args(argv, argc, 5); + + req = run_event_send(mem_ctx, + ev, + run_ctx, + argv[4], + arg_str, + timeout, + false); + if (req == NULL) { + fprintf(stderr, "run_event_send() failed\n"); + return; + } + + tevent_req_set_callback(req, run_done, &script_list); + + tevent_req_poll(req, ev); + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No event scripts found\n"); + return; + } + + printf("Event %s completed with result=%d\n", + argv[4], script_list->summary); + for (i=0; inum_scripts; i++) { + struct run_event_script *s = &script_list->script[i]; + printf("%s result=%d\n", s->name, s->summary); + + if (s->summary == -ETIMEDOUT) { + wait_for_signal = true; + } + } + + TALLOC_FREE(script_list); + TALLOC_FREE(req); + + if (!wait_for_signal) { + return; + } + + req = tevent_wakeup_send( + ev, ev, tevent_timeval_current_ofs(10, 0)); + if (req == NULL) { + fprintf(stderr, "Could not wait for signal\n"); + return; + } + + tevent_req_poll(req, ev); + TALLOC_FREE(req); +} + +static void do_list(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + struct run_event_script_list *script_list = NULL; + unsigned int i; + int ret; + + ret = run_event_list(run_ctx, mem_ctx, &script_list); + if (ret != 0) { + printf("Script list failed with result=%d\n", ret); + return; + } + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No event scripts found\n"); + return; + } + + for (i=0; inum_scripts; i++) { + printf("%s\n", script_list->script[i].name); + } +} + +static void do_enable(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = run_event_script_enable(run_ctx, argv[3]); + printf("Script enable %s completed with result=%d\n", argv[3], ret); +} + +static void do_disable(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = run_event_script_disable(run_ctx, argv[3]); + printf("Script disable %s completed with result=%d\n", argv[3], ret); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct run_proc_context *run_proc_ctx = NULL; + struct run_event_context *run_ctx = NULL; + int ret; + + if (argc < 3) { + usage(argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "talloc_new() failed\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "tevent_context_init() failed\n"); + exit(1); + } + + ret = run_proc_init(mem_ctx, ev, &run_proc_ctx); + if (ret != 0) { + fprintf(stderr, "run_proc_init() failed, ret=%d\n", ret); + exit(1); + } + + ret = run_event_init(mem_ctx, run_proc_ctx, argv[1], NULL, &run_ctx); + if (ret != 0) { + fprintf(stderr, "run_event_init() failed, ret=%d\n", ret); + exit(1); + } + + if (strcmp(argv[2], "run") == 0) { + do_run(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "list") == 0) { + do_list(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "enable") == 0) { + do_enable(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "disable") == 0) { + do_disable(mem_ctx, ev, run_ctx, argc, argv); + } else { + fprintf(stderr, "Invalid command %s\n", argv[2]); + usage(argv[0]); + } + + talloc_free(mem_ctx); + exit(0); +} + diff --git a/ctdb/tests/src/run_proc_test.c b/ctdb/tests/src/run_proc_test.c new file mode 100644 index 0000000..7cfb870 --- /dev/null +++ b/ctdb/tests/src/run_proc_test.c @@ -0,0 +1,111 @@ +/* + run_proc test wrapper + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include +#include + +#include "common/db_hash.c" +#include "common/run_proc.c" + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + struct run_proc_context *run_ctx; + struct timeval tv; + char *output; + struct run_proc_result result; + pid_t pid; + int timeout, ret, fd; + bool status; + + if (argc < 4) { + fprintf(stderr, + "Usage: %s \n", + argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "talloc_new() failed\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "tevent_context_init() failed\n"); + exit(1); + } + + timeout = atoi(argv[1]); + if (timeout <= 0) { + tv = tevent_timeval_zero(); + } else { + tv = tevent_timeval_current_ofs(timeout, 0); + } + + fd = atoi(argv[2]); + if (fd < 0) { + fd = -1; + } + + ret = run_proc_init(mem_ctx, ev, &run_ctx); + if (ret != 0) { + fprintf(stderr, "run_proc_init() failed, ret=%d\n", ret); + exit(1); + } + + req = run_proc_send(mem_ctx, ev, run_ctx, argv[3], &argv[3], fd, tv); + if (req == NULL) { + fprintf(stderr, "run_proc_send() failed\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = run_proc_recv(req, &ret, &result, &pid, mem_ctx, &output); + if (! status) { + fprintf(stderr, "run_proc_recv() failed, ret=%d\n", ret); + exit(1); + } + + if (result.sig > 0) { + printf("Process exited with signal %d\n", result.sig); + } else if (result.err > 0) { + printf("Process exited with error %d\n", result.err); + } else { + printf("Process exited with status %d\n", result.status); + } + + if (pid != -1) { + printf("Child = %d\n", pid); + } + + if (output != NULL) { + printf("Output = (%s)\n", output); + } + + talloc_free(mem_ctx); + + exit(0); +} diff --git a/ctdb/tests/src/sigcode.c b/ctdb/tests/src/sigcode.c new file mode 100644 index 0000000..9e5ed81 --- /dev/null +++ b/ctdb/tests/src/sigcode.c @@ -0,0 +1,120 @@ +/* + Portability layer for signal codes + + Copyright (C) Amitay Isaacs 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +/* + * These signals are as listed in POSIX standard + * IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008) + */ + +#include "replace.h" +#include "system/wait.h" + +struct { + const char *label; + int code; +} sig_codes[] = { + { "SIGABRT", SIGABRT }, + { "SIGALRM", SIGALRM }, + { "SIBGUS", SIGBUS }, + { "SIGCHLD", SIGCHLD }, + { "SIGCONT", SIGCONT }, + { "SIGFPE", SIGFPE }, + { "SIGHUP", SIGHUP }, + { "SIGILL", SIGILL }, + { "SIGINT", SIGINT }, + { "SIGKILL", SIGKILL }, + { "SIGPIPE", SIGPIPE }, + { "SIGQUIT", SIGQUIT }, + { "SIGSEGV", SIGSEGV }, + { "SIGSTOP", SIGSTOP }, + { "SIGTERM", SIGTERM }, + { "SIGTSTP", SIGTSTP }, + { "SIGTTIN", SIGTTIN }, + { "SIGTTOU", SIGTTOU }, + { "SIGUSR1", SIGUSR1 }, + { "SIGUSR2", SIGUSR2 }, + { "SIGTRAP", SIGTRAP }, + { "SIGURG", SIGURG }, + { "SIGXCPU", SIGXCPU }, + { "SIGXFSZ", SIGXFSZ }, + +}; + +static void dump(void) +{ + size_t i; + + for (i=0; i\n", argv[0]); + exit(1); + } + + if (strcmp(argv[1], "dump") == 0) { + dump(); + } else { + code = strtol(argv[1], &endptr, 0); + if (*endptr == '\0') { + match_code(code); + } else { + match_label(argv[1]); + } + } + + exit(0); +} diff --git a/ctdb/tests/src/sock_daemon_test.c b/ctdb/tests/src/sock_daemon_test.c new file mode 100644 index 0000000..acafc9f --- /dev/null +++ b/ctdb/tests/src/sock_daemon_test.c @@ -0,0 +1,1980 @@ +/* + sock daemon tests + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/wait.h" + +#include + +#include "common/logging.c" +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" +#include "common/pidfile.c" +#include "common/sock_daemon.c" +#include "common/sock_io.c" + +struct dummy_wait_state { +}; + +static struct tevent_req *dummy_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct dummy_wait_state *state; + const char *sockpath = (const char *)private_data; + struct stat st; + int ret; + + ret = stat(sockpath, &st); + assert(ret == 0); + assert(S_ISSOCK(st.st_mode)); + + req = tevent_req_create(mem_ctx, &state, struct dummy_wait_state); + if (req == NULL) { + return NULL; + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static bool dummy_wait_recv(struct tevent_req *req, int *perr) +{ + return true; +} + +static int test1_startup_fail(void *private_data) +{ + return 1; +} + +static int test1_startup(void *private_data) +{ + const char *sockpath = (const char *)private_data; + struct stat st; + int ret; + + ret = stat(sockpath, &st); + assert(ret == -1); + + return 0; +} + +struct test1_startup_state { +}; + +static struct tevent_req *test1_startup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test1_startup_state *state; + + req = tevent_req_create(mem_ctx, &state, struct test1_startup_state); + if (req == NULL) { + return NULL; + } + + tevent_req_error(req, 2); + return tevent_req_post(req, ev); +} + +static bool test1_startup_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static struct tevent_req *dummy_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *client, + uint8_t *buf, size_t buflen, + void *private_data) +{ + return NULL; +} + +static bool dummy_read_recv(struct tevent_req *req, int *perr) +{ + if (perr != NULL) { + *perr = EINVAL; + } + return false; +} + +static struct sock_socket_funcs dummy_socket_funcs = { + .read_send = dummy_read_send, + .read_recv = dummy_read_recv, +}; + +/* + * test1 + * + * Check setup without actually running daemon + */ + +static void test1(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct sock_daemon_funcs test1_funcs; + struct stat st; + int ret; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + test1_funcs = (struct sock_daemon_funcs){ + .startup = test1_startup_fail, + }; + + ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE", + &test1_funcs, NULL, &sockd); + assert(ret == 0); + assert(sockd != NULL); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, -1); + assert(ret == EIO); + talloc_free(sockd); + + test1_funcs = (struct sock_daemon_funcs){ + .startup_send = test1_startup_send, + .startup_recv = test1_startup_recv, + }; + + ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE", + &test1_funcs, NULL, &sockd); + assert(ret == 0); + assert(sockd != NULL); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, -1); + assert(ret == EIO); + talloc_free(sockd); + + test1_funcs = (struct sock_daemon_funcs){ + .startup = test1_startup, + .wait_send = dummy_wait_send, + .wait_recv = dummy_wait_recv, + }; + + ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE", + &test1_funcs, discard_const(sockpath), &sockd); + assert(ret == 0); + assert(sockd != NULL); + + ret = sock_daemon_add_unix(sockd, sockpath, &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = stat(sockpath, &st); + assert(ret == -1); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, -1); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +/* + * test2 + * + * Start daemon, check PID file, sock daemon functions, termination, + * exit code + */ + +static int test2_startup(void *private_data) +{ + int fd = *(int *)private_data; + int ret = 1; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + return 0; +} + +static int test2_reconfigure(void *private_data) +{ + static bool first_time = true; + int fd = *(int *)private_data; + int ret = 2; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (first_time) { + first_time = false; + return 1; + } + + return 0; +} + +struct test2_reconfigure_state { + int fd; +}; + +static struct tevent_req *test2_reconfigure_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test2_reconfigure_state *state; + static bool first_time = true; + + req = tevent_req_create(mem_ctx, &state, + struct test2_reconfigure_state); + if (req == NULL) { + return NULL; + } + + state->fd = *(int *)private_data; + + if (first_time) { + first_time = false; + tevent_req_error(req, 2); + } else { + tevent_req_done(req); + } + + return tevent_req_post(req, ev); +} + +static bool test2_reconfigure_recv(struct tevent_req *req, int *perr) +{ + struct test2_reconfigure_state *state = tevent_req_data( + req, struct test2_reconfigure_state); + int ret = 2; + ssize_t nwritten; + + nwritten = write(state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static int test2_reopen_logs(void *private_data) +{ + static bool first_time = true; + int fd = *(int *)private_data; + int ret = 4; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (first_time) { + first_time = false; + return 1; + } + + return 0; +} + +struct test2_reopen_logs_state { + int fd; +}; + +static struct tevent_req *test2_reopen_logs_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test2_reopen_logs_state *state; + static bool first_time = true; + + req = tevent_req_create(mem_ctx, &state, + struct test2_reopen_logs_state); + if (req == NULL) { + return NULL; + } + + state->fd = *(int *)private_data; + + if (first_time) { + first_time = false; + tevent_req_error(req, 2); + } else { + tevent_req_done(req); + } + + return tevent_req_post(req, ev); +} + +static bool test2_reopen_logs_recv(struct tevent_req *req, int *perr) +{ + struct test2_reopen_logs_state *state = tevent_req_data( + req, struct test2_reopen_logs_state); + int ret = 4; + ssize_t nwritten; + + nwritten = write(state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static void test2_shutdown(void *private_data) +{ + int fd = *(int *)private_data; + int ret = 3; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); +} + +struct test2_shutdown_state { + int fd; +}; + +static struct tevent_req *test2_shutdown_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test2_shutdown_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct test2_shutdown_state); + if (req == NULL) { + return NULL; + } + + state->fd = *(int *)private_data; + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static void test2_shutdown_recv(struct tevent_req *req) +{ + struct test2_shutdown_state *state = tevent_req_data( + req, struct test2_shutdown_state); + int ret = 3; + ssize_t nwritten; + + nwritten = write(state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); +} + +static void test2(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + int fd[2]; + pid_t pid, pid2; + int ret; + ssize_t n; + int pidfile_fd; + char pidstr[20] = { 0 }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct sock_daemon_funcs test2_funcs = { + .startup = test2_startup, + .reconfigure = test2_reconfigure, + .reopen_logs = test2_reopen_logs, + .shutdown = test2_shutdown, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test2", "file:", "NOTICE", + &test2_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + pidfile_fd = open(pidfile, O_RDONLY, 0644); + assert(pidfile_fd != -1); + ret = fstat(pidfile_fd, &st); + assert(ret == 0); + assert(S_ISREG(st.st_mode)); + n = read(pidfile_fd, pidstr, sizeof(pidstr)-1); + assert(n != -1); + pid2 = (pid_t)atoi(pidstr); + assert(pid == pid2); + close(pidfile_fd); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct sock_daemon_funcs test2_funcs = { + .startup = test2_startup, + .reconfigure_send = test2_reconfigure_send, + .reconfigure_recv = test2_reconfigure_recv, + .reopen_logs_send = test2_reopen_logs_send, + .reopen_logs_recv = test2_reopen_logs_recv, + .shutdown_send = test2_shutdown_send, + .shutdown_recv = test2_shutdown_recv, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test2", "file:", "NOTICE", + &test2_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); +} + +/* + * test3 + * + * Start daemon, test watching of (parent) PID + */ + +static void test3(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + pid_t pid_watch, pid, pid2; + int ret; + + pid_watch = fork(); + assert(pid_watch != -1); + + if (pid_watch == 0) { + sleep(10); + exit(0); + } + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test3", "file:", "NOTICE", + NULL, NULL, &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, pid_watch); + assert(ret == ESRCH); + + exit(0); + } + + pid2 = waitpid(pid_watch, &ret, 0); + assert(pid2 == pid_watch); + assert(WEXITSTATUS(ret) == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); +} + +/* + * test4 + * + * Start daemon, test termination via wait_send function + */ + +struct test4_wait_state { +}; + +static void test4_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test4_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req, *subreq; + struct test4_wait_state *state; + + req = tevent_req_create(mem_ctx, &state, struct test4_wait_state); + if (req == NULL) { + return NULL; + } + + subreq = tevent_wakeup_send(state, ev, + tevent_timeval_current_ofs(10,0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test4_wait_done, req); + + return req; +} + +static void test4_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + + if (! status) { + tevent_req_error(req, EIO); + } else { + tevent_req_done(req); + } +} + +static bool test4_wait_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_daemon_funcs test4_funcs = { + .wait_send = test4_wait_send, + .wait_recv = test4_wait_recv, +}; + +static void test4(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + pid_t pid, pid2; + int ret; + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test4", "file:", "NOTICE", + &test4_funcs, NULL, &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == 0); + + exit(0); + } + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); +} + +/* + * test5 + * + * Start daemon, multiple client connects, requests, disconnects + */ + +#define TEST5_VALID_CLIENTS 10 +#define TEST5_MAX_CLIENTS 100 + +struct test5_pkt { + uint32_t len; + int data; +}; + +struct test5_client_state { + int id; + int fd; + bool done; +}; + +static void test5_client_callback(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test5_client_state *state = + (struct test5_client_state *)private_data; + struct test5_pkt *pkt; + ssize_t n; + int ret; + + if (buf == NULL) { + assert(buflen == 0); + + ret = 0; + } else { + assert(buflen == sizeof(struct test5_pkt)); + pkt = (struct test5_pkt *)buf; + assert(pkt->len == sizeof(struct test5_pkt)); + + ret = pkt->data; + } + + assert(state->fd != -1); + + n = write(state->fd, (void *)&ret, sizeof(int)); + assert(n == sizeof(int)); + + state->done = true; +} + +static int test5_client(const char *sockpath, int id, pid_t pid_server, + pid_t *client_pid) +{ + pid_t pid; + int fd[2]; + int ret; + ssize_t n; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct test5_client_state state; + struct sock_queue *queue; + struct test5_pkt pkt; + int conn; + + close(fd[0]); + + ev = tevent_context_init(NULL); + assert(ev != NULL); + + conn = sock_connect(sockpath); + assert(conn != -1); + + state.id = id; + state.fd = fd[1]; + state.done = false; + + queue = sock_queue_setup(ev, ev, conn, + test5_client_callback, &state); + assert(queue != NULL); + + pkt.len = 8; + pkt.data = 0xbaba; + + ret = sock_queue_write(queue, (uint8_t *)&pkt, + sizeof(struct test5_pkt)); + assert(ret == 0); + + while (! state.done) { + tevent_loop_once(ev); + } + + close(fd[1]); + state.fd = -1; + + while (kill(pid_server, 0) == 0 || errno != ESRCH) { + sleep(1); + } + exit(0); + } + + close(fd[1]); + + ret = 0; + n = read(fd[0], &ret, sizeof(ret)); + if (n == 0) { + fprintf(stderr, "client id %d read 0 bytes\n", id); + } + assert(n == 0 || n == sizeof(ret)); + + close(fd[0]); + + *client_pid = pid; + return ret; +} + +struct test5_server_state { + int num_clients; +}; + +static bool test5_connect(struct sock_client_context *client, + pid_t pid, + void *private_data) +{ + struct test5_server_state *state = + (struct test5_server_state *)private_data; + + if (state->num_clients == TEST5_VALID_CLIENTS) { + return false; + } + + state->num_clients += 1; + assert(state->num_clients <= TEST5_VALID_CLIENTS); + return true; +} + +static void test5_disconnect(struct sock_client_context *client, + void *private_data) +{ + struct test5_server_state *state = + (struct test5_server_state *)private_data; + + state->num_clients -= 1; + assert(state->num_clients >= 0); +} + +struct test5_read_state { + struct test5_pkt reply; +}; + +static void test5_read_done(struct tevent_req *subreq); + +static struct tevent_req *test5_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *client, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test5_server_state *server_state = + (struct test5_server_state *)private_data; + struct tevent_req *req, *subreq; + struct test5_read_state *state; + struct test5_pkt *pkt; + + req = tevent_req_create(mem_ctx, &state, struct test5_read_state); + assert(req != NULL); + + assert(buflen == sizeof(struct test5_pkt)); + + pkt = (struct test5_pkt *)buf; + assert(pkt->data == 0xbaba); + + state->reply.len = sizeof(struct test5_pkt); + state->reply.data = server_state->num_clients; + + subreq = sock_socket_write_send(state, ev, client, + (uint8_t *)&state->reply, + state->reply.len); + assert(subreq != NULL); + + tevent_req_set_callback(subreq, test5_read_done, req); + + return req; +} + +static void test5_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool status; + + status = sock_socket_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static bool test5_read_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_socket_funcs test5_client_funcs = { + .connect = test5_connect, + .disconnect = test5_disconnect, + .read_send = test5_read_send, + .read_recv = test5_read_recv, +}; + +struct test5_wait_state { +}; + +static struct tevent_req *test5_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test5_wait_state *state; + int fd = *(int *)private_data; + int ret = 1; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + close(fd); + + req = tevent_req_create(mem_ctx, &state, struct test5_wait_state); + if (req == NULL) { + return NULL; + } + + return req; +} + +static bool test5_wait_recv(struct tevent_req *req, int *perr) +{ + return true; +} + +static struct sock_daemon_funcs test5_funcs = { + .wait_send = test5_wait_send, + .wait_recv = test5_wait_recv, +}; + +static void test5(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + pid_t pid_server, pid; + int fd[2], ret, i; + ssize_t n; + pid_t client_pid[TEST5_MAX_CLIENTS]; + + pid = getpid(); + + ret = pipe(fd); + assert(ret == 0); + + pid_server = fork(); + assert(pid_server != -1); + + if (pid_server == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct test5_server_state state; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test5", "file:", "NOTICE", + &test5_funcs, &fd[1], &sockd); + assert(ret == 0); + + state.num_clients = 0; + + ret = sock_daemon_add_unix(sockd, sockpath, + &test5_client_funcs, &state); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, pid); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + close(fd[0]); + + for (i=0; i=0; i--) { + kill(client_pid[i], SIGKILL); + + pid = wait(&ret); + assert(pid != -1); + } + + ret = kill(pid_server, SIGTERM); + assert(ret == 0); + + pid = waitpid(pid_server, &ret, 0); + assert(pid == pid_server); + assert(WEXITSTATUS(ret) == 0); +} + +/* + * test6 + * + * Start daemon, test client connects, requests, replies, disconnects + */ + +struct test6_pkt { + uint32_t len; + uint32_t data; +}; + +struct test6_client_state { + bool done; +}; + +static void test6_client_callback(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test6_client_state *state = + (struct test6_client_state *)private_data; + struct test6_pkt *pkt; + + assert(buflen == sizeof(struct test6_pkt)); + pkt = (struct test6_pkt *)buf; + assert(pkt->len == sizeof(struct test6_pkt)); + assert(pkt->data == 0xffeeddcc); + + state->done = true; +} + +static void test6_client(const char *sockpath) +{ + struct tevent_context *ev; + struct test6_client_state state; + struct sock_queue *queue; + struct test6_pkt pkt; + int conn, ret; + + ev = tevent_context_init(NULL); + assert(ev != NULL); + + conn = sock_connect(sockpath); + assert(conn != -1); + + state.done = false; + + queue = sock_queue_setup(ev, ev, conn, + test6_client_callback, &state); + assert(queue != NULL); + + pkt.len = 8; + pkt.data = 0xaabbccdd; + + ret = sock_queue_write(queue, (uint8_t *)&pkt, + sizeof(struct test6_pkt)); + assert(ret == 0); + + while (! state.done) { + tevent_loop_once(ev); + } + + talloc_free(ev); +} + +struct test6_server_state { + struct sock_daemon_context *sockd; + int fd, done; +}; + +struct test6_read_state { + struct test6_server_state *server_state; + struct test6_pkt reply; +}; + +static void test6_read_done(struct tevent_req *subreq); + +static struct tevent_req *test6_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *client, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test6_server_state *server_state = + (struct test6_server_state *)private_data; + struct tevent_req *req, *subreq; + struct test6_read_state *state; + struct test6_pkt *pkt; + + req = tevent_req_create(mem_ctx, &state, struct test6_read_state); + assert(req != NULL); + + state->server_state = server_state; + + assert(buflen == sizeof(struct test6_pkt)); + + pkt = (struct test6_pkt *)buf; + assert(pkt->data == 0xaabbccdd); + + state->reply.len = sizeof(struct test6_pkt); + state->reply.data = 0xffeeddcc; + + subreq = sock_socket_write_send(state, ev, client, + (uint8_t *)&state->reply, + state->reply.len); + assert(subreq != NULL); + + tevent_req_set_callback(subreq, test6_read_done, req); + + return req; +} + +static void test6_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test6_read_state *state = tevent_req_data( + req, struct test6_read_state); + int ret; + bool status; + + status = sock_socket_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->server_state->done = 1; + tevent_req_done(req); +} + +static bool test6_read_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_socket_funcs test6_client_funcs = { + .read_send = test6_read_send, + .read_recv = test6_read_recv, +}; + +struct test6_wait_state { + struct test6_server_state *server_state; +}; + +static void test6_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test6_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct test6_server_state *server_state = + (struct test6_server_state *)private_data; + struct tevent_req *req, *subreq; + struct test6_wait_state *state; + ssize_t nwritten; + int ret = 1; + + nwritten = write(server_state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + close(server_state->fd); + server_state->fd = -1; + + req = tevent_req_create(mem_ctx, &state, struct test6_wait_state); + if (req == NULL) { + return NULL; + } + + state->server_state = (struct test6_server_state *)private_data; + + subreq = tevent_wakeup_send(state, ev, + tevent_timeval_current_ofs(10,0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test6_wait_done, req); + + return req; +} + +static void test6_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test6_wait_state *state = tevent_req_data( + req, struct test6_wait_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + if (state->server_state->done == 0) { + tevent_req_error(req, EIO); + return; + } + + tevent_req_done(req); +} + +static bool test6_wait_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_daemon_funcs test6_funcs = { + .wait_send = test6_wait_send, + .wait_recv = test6_wait_recv, +}; + +static void test6(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + pid_t pid_server, pid; + int fd[2], ret; + ssize_t n; + + pid = getpid(); + + ret = pipe(fd); + assert(ret == 0); + + pid_server = fork(); + assert(pid_server != -1); + + if (pid_server == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct test6_server_state server_state = { 0 }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + server_state.fd = fd[1]; + + ret = sock_daemon_setup(mem_ctx, "test6", "file:", "NOTICE", + &test6_funcs, &server_state, + &sockd); + assert(ret == 0); + + server_state.sockd = sockd; + server_state.done = 0; + + ret = sock_daemon_add_unix(sockd, sockpath, + &test6_client_funcs, &server_state); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, pid); + assert(ret == 0); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + close(fd[0]); + + test6_client(sockpath); + + pid = waitpid(pid_server, &ret, 0); + assert(pid == pid_server); + assert(WEXITSTATUS(ret) == 0); +} + +/* + * test7 + * + * Start daemon twice, confirm PID file contention + */ + +static void test7(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct sock_daemon_funcs test7_funcs; + struct stat st; + int fd[2]; + pid_t pid, pid2; + int ret; + struct tevent_context *ev; + struct sock_daemon_context *sockd; + ssize_t n; + + /* Reuse test2 funcs for the startup synchronisation */ + test7_funcs = (struct sock_daemon_funcs) { + .startup = test2_startup, + .reconfigure = test2_reconfigure, + .shutdown = test2_shutdown, + }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test7", "file:", "NOTICE", + &test7_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + ret = stat(pidfile, &st); + assert(ret == 0); + assert(S_ISREG(st.st_mode)); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test7-parent", "file:", "NOTICE", + &test7_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EEXIST); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); +} + +/* + * test8 + * + * Start daemon, confirm that create_session argument works as expected + */ + +static void test8(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + int fd[2]; + pid_t pid, pid2, sid; + int ret; + struct tevent_context *ev; + struct sock_daemon_context *sockd; + ssize_t n; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test8_funcs = { + .startup = test2_startup, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test8", "file:", "NOTICE", + &test8_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* create_session false above, so pid != sid */ + sid = getsid(pid); + assert(pid != sid); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test8_funcs = { + .startup = test2_startup, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test8", "file:", "NOTICE", + &test8_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, true, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* create_session true above, so pid == sid */ + sid = getsid(pid); + assert(pid == sid); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); +} + +/* + * test9 + * + * Confirm that do_fork causes the daemon to be forked as a separate child + */ + +static void test9(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + int fd[2]; + pid_t pid, pid2; + int ret; + struct tevent_context *ev; + struct sock_daemon_context *sockd; + ssize_t n; + int pidfile_fd; + char pidstr[20] = { 0 }; + struct stat st; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test9_funcs = { + .startup = test2_startup, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test9", "file:", "NOTICE", + &test9_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* do_fork false above, so pid should be active */ + ret = kill(pid, 0); + assert(ret == 0); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test9_funcs = { + .startup = test2_startup, + .shutdown = test2_shutdown, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test9", "file:", "NOTICE", + &test9_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, true, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* do_fork true above, so pid should have exited */ + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + pidfile_fd = open(pidfile, O_RDONLY, 0644); + assert(pidfile_fd != -1); + n = read(pidfile_fd, pidstr, sizeof(pidstr)-1); + assert(n != -1); + pid2 = (pid_t)atoi(pidstr); + assert(pid != pid2); + close(pidfile_fd); + + ret = kill(pid2, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + /* + * pid2 isn't our child, so can't call waitpid(). kill(pid2, 0) + * is unreliable - pid2 may have been recycled. Above indicates + * that the shutdown function was called, so just do 1 final + * check to see if pidfile has been removed. + */ + ret = stat(sockpath, &st); + assert(ret == -1); + + close(fd[0]); +} + +static void test10_shutdown(void *private_data) +{ + int fd = *(int *)private_data; + int ret = 3; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); +} + +struct test10_wait_state { +}; + +static void test10_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test10_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + int fd = *(int *)private_data; + struct tevent_req *req, *subreq; + struct test10_wait_state *state; + size_t nwritten; + int ret = 1; + + req = tevent_req_create(mem_ctx, &state, struct test10_wait_state); + if (req == NULL) { + return NULL; + } + + subreq = tevent_wakeup_send(state, ev, + tevent_timeval_current_ofs(10, 0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test10_wait_done, req); + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + return req; +} + +static void test10_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + + status = tevent_wakeup_recv(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + tevent_req_done(req); +} + +static bool test10_wait_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_daemon_funcs test10_funcs = { + .shutdown = test10_shutdown, + .wait_send = test10_wait_send, + .wait_recv = test10_wait_recv, +}; + +/* + * test10 + * + * Confirm that the daemon starts successfully if there is a stale socket + */ + +static void test10(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + int fd[2]; + pid_t pid, pid2; + int ret; + ssize_t n; + int pidfile_fd; + char pidstr[20] = { 0 }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test10", "file:", "NOTICE", + &test10_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* KILL will leave PID file and socket behind */ + ret = kill (pid, SIGKILL); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + ret = stat(sockpath, &st); + assert(ret == 0); + + close(fd[0]); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test10", "file:", "NOTICE", + &test10_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + pidfile_fd = open(pidfile, O_RDONLY, 0644); + assert(pidfile_fd != -1); + n = read(pidfile_fd, pidstr, sizeof(pidstr)-1); + assert(n != -1); + pid2 = (pid_t)atoi(pidstr); + assert(pid == pid2); + close(pidfile_fd); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + const char *pidfile, *sockpath; + int num; + + if (argc != 4) { + fprintf(stderr, "%s \n", argv[0]); + exit(1); + } + + pidfile = argv[1]; + sockpath = argv[2]; + num = atoi(argv[3]); + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + switch (num) { + case 1: + test1(mem_ctx, pidfile, sockpath); + break; + + case 2: + test2(mem_ctx, pidfile, sockpath); + break; + + case 3: + test3(mem_ctx, pidfile, sockpath); + break; + + case 4: + test4(mem_ctx, pidfile, sockpath); + break; + + case 5: + test5(mem_ctx, pidfile, sockpath); + break; + + case 6: + test6(mem_ctx, pidfile, sockpath); + break; + + case 7: + test7(mem_ctx, pidfile, sockpath); + break; + + case 8: + test8(mem_ctx, pidfile, sockpath); + break; + + case 9: + test9(mem_ctx, pidfile, sockpath); + break; + + case 10: + test10(mem_ctx, pidfile, sockpath); + break; + + default: + fprintf(stderr, "Unknown test number %d\n", num); + } + + return 0; +} diff --git a/ctdb/tests/src/sock_io_test.c b/ctdb/tests/src/sock_io_test.c new file mode 100644 index 0000000..ba4b637 --- /dev/null +++ b/ctdb/tests/src/sock_io_test.c @@ -0,0 +1,283 @@ +/* + sock I/O tests + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/wait.h" + +#include + +#include "common/sock_io.c" + +static int socket_init(const char *sockpath) +{ + struct sockaddr_un addr; + int fd, ret; + size_t len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + assert(len < sizeof(addr.sun_path)); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(fd != -1); + + ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret != -1); + + ret = listen(fd, 10); + assert(ret != -1); + + return fd; +} + +static void test1_writer(int fd) +{ + uint8_t buf[1024]; + ssize_t nwritten; + uint32_t len; + + for (len = 10; len < 1000; len += 10) { + int value = len / 10; + uint32_t buflen = len + sizeof(uint32_t); + + memset(buf, value, buflen); + memcpy(buf, &buflen, sizeof(uint32_t)); + + nwritten = sys_write(fd, buf, buflen); + assert(nwritten == buflen); + } +} + +struct test1_reader_state { + size_t pkt_len; + bool done; +}; + +static void test1_reader(uint8_t *buf, size_t buflen, void *private_data) +{ + struct test1_reader_state *state = + (struct test1_reader_state *)private_data; + + if (buflen == 0) { + state->done = true; + return; + } + + assert(buflen == state->pkt_len); + + state->pkt_len += 10; +} + +static void test1(TALLOC_CTX *mem_ctx, const char *sockpath) +{ + struct test1_reader_state state; + struct tevent_context *ev; + struct sock_queue *queue; + pid_t pid; + int pfd[2], fd, ret; + ssize_t n; + + ret = pipe(pfd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + int newfd; + + close(pfd[0]); + + fd = socket_init(sockpath); + assert(fd != -1); + + ret = 1; + n = sys_write(pfd[1], &ret, sizeof(int)); + assert(n == sizeof(int)); + + newfd = accept(fd, NULL, NULL); + assert(newfd != -1); + + test1_writer(newfd); + close(newfd); + unlink(sockpath); + + exit(0); + } + + close(pfd[1]); + + n = sys_read(pfd[0], &ret, sizeof(int)); + assert(n == sizeof(int)); + assert(ret == 1); + + close(pfd[0]); + + fd = sock_connect(sockpath); + assert(fd != -1); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + state.pkt_len = 10 + sizeof(uint32_t); + state.done = false; + + queue = sock_queue_setup(mem_ctx, ev, fd, test1_reader, &state); + assert(queue != NULL); + + while (! state.done) { + tevent_loop_once(ev); + } + + talloc_free(queue); + talloc_free(ev); + + pid = wait(&ret); + assert(pid != -1); +} + +static void test2_reader(int fd) +{ + uint8_t buf[1024]; + size_t pkt_len = 10 + sizeof(uint32_t); + ssize_t n; + + while (1) { + n = sys_read(fd, buf, 1024); + assert(n != -1); + + if (n == 0) { + return; + } + + assert((size_t)n == pkt_len); + pkt_len += 10; + } +} + +static void test2_dummy_reader(uint8_t *buf, size_t buflen, + void *private_data) +{ + abort(); +} + +static void test2_writer(struct sock_queue *queue) +{ + uint8_t buf[1024]; + uint32_t len; + int ret; + + for (len = 10; len < 1000; len += 10) { + int value = len / 10; + uint32_t buflen = len + sizeof(uint32_t); + + memset(buf, value, buflen); + memcpy(buf, &buflen, sizeof(uint32_t)); + + ret = sock_queue_write(queue, buf, buflen); + assert(ret == 0); + } +} + +static void test2(TALLOC_CTX *mem_ctx, const char *sockpath) +{ + struct tevent_context *ev; + struct sock_queue *queue; + pid_t pid; + int pfd[2], fd, ret; + ssize_t n; + + ret = pipe(pfd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + int newfd; + + close(pfd[0]); + + fd = socket_init(sockpath); + assert(fd != -1); + + ret = 1; + n = sys_write(pfd[1], &ret, sizeof(int)); + assert(n == sizeof(int)); + + newfd = accept(fd, NULL, NULL); + assert(newfd != -1); + + test2_reader(newfd); + close(newfd); + unlink(sockpath); + + exit(0); + } + + close(pfd[1]); + + n = sys_read(pfd[0], &ret, sizeof(int)); + assert(n == sizeof(int)); + assert(ret == 1); + + close(pfd[0]); + + fd = sock_connect(sockpath); + assert(fd != -1); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + queue = sock_queue_setup(mem_ctx, ev, fd, test2_dummy_reader, NULL); + assert(queue != NULL); + + test2_writer(queue); + + talloc_free(queue); + talloc_free(ev); + + pid = wait(&ret); + assert(pid != -1); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + const char *sockpath; + + if (argc != 2) { + fprintf(stderr, "%s \n", argv[0]); + exit(1); + } + + sockpath = argv[1]; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + test1(mem_ctx, sockpath); + test2(mem_ctx, sockpath); + + return 0; +} diff --git a/ctdb/tests/src/srvid_test.c b/ctdb/tests/src/srvid_test.c new file mode 100644 index 0000000..2367c6c --- /dev/null +++ b/ctdb/tests/src/srvid_test.c @@ -0,0 +1,105 @@ +/* + srvid tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include + +#include "common/db_hash.c" +#include "common/srvid.c" + +#define TEST_SRVID 0xBE11223344556677 + +static void test_handler(uint64_t srvid, TDB_DATA data, void *private_data) +{ + int *count = (int *)private_data; + (*count)++; +} + +int main(void) +{ + struct srvid_context *srv = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + int ret; + int count = 0; + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == EINVAL); + + ret = srvid_init(mem_ctx, &srv); + assert(ret == 0); + + ret = srvid_deregister(srv, TEST_SRVID, &count); + assert(ret == ENOENT); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == 0); + + ret = srvid_exists(srv, TEST_SRVID, NULL); + assert(ret == 0); + + ret = srvid_exists(srv, TEST_SRVID, &count); + assert(ret == 0); + + ret = srvid_dispatch(srv, TEST_SRVID, 0, tdb_null); + assert(ret == 0); + assert(count == 1); + + ret = srvid_dispatch(srv, 0, TEST_SRVID, tdb_null); + assert(ret == 0); + assert(count == 2); + + ret = srvid_deregister(srv, TEST_SRVID, NULL); + assert(ret == ENOENT); + + ret = srvid_deregister(srv, TEST_SRVID, &count); + assert(ret == 0); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == 0); + + talloc_free(tmp_ctx); + ret = srvid_exists(srv, TEST_SRVID, NULL); + assert(ret == ENOENT); + + ret = srvid_dispatch(srv, TEST_SRVID, 0, tdb_null); + assert(ret == ENOENT); + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, NULL); + assert(ret == 0); + ret = srvid_exists(srv, TEST_SRVID, &count); + assert(ret == ENOENT); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == 0); + ret = srvid_exists(srv, TEST_SRVID, &count); + assert(ret == 0); + + talloc_free(srv); + assert(talloc_get_size(mem_ctx) == 0); + assert(talloc_get_size(tmp_ctx) == 0); + + talloc_free(mem_ctx); + + return 0; +} diff --git a/ctdb/tests/src/system_socket_test.c b/ctdb/tests/src/system_socket_test.c new file mode 100644 index 0000000..436f52a --- /dev/null +++ b/ctdb/tests/src/system_socket_test.c @@ -0,0 +1,266 @@ +/* + Raw socket (un) marshalling tests + + Copyright (C) Martin Schwenke 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include + +/* For ether_aton() */ +#ifdef _AIX +#include +#endif +#ifdef __FreeBSD__ +#include +#endif +#ifdef linux +#include +#endif + +#include "common/system_socket.c" + +#include "protocol/protocol_util.h" + +#include "tests/src/test_backtrace.h" + +static void hexdump(uint8_t *buf, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (i % 16 == 0) { + if (i != 0) { + printf("\n"); + } + printf("%06zx", i); + } + printf(" %02x", buf[i]); + } + + printf("\n%06zx\n", i); +} + +static void test_types(void) +{ + /* + * We use this struct in the code but don't pack it due to + * portability concerns. It should have no padding. + */ + struct { + struct ip ip; + struct tcphdr tcp; + } ip4pkt; + + assert(sizeof(ip4pkt) == sizeof(struct ip) + sizeof(struct tcphdr)); +} + +#ifdef HAVE_PACKETSOCKET + +static void test_arp(const char *addr_str, const char *hwaddr_str, bool reply) +{ + ctdb_sock_addr addr; + struct ether_addr *hw, *dhw; + uint8_t buf[512]; + size_t buflen = sizeof(buf); + size_t len; + int ret; + + ret = ctdb_sock_addr_from_string(addr_str, &addr, false); + assert(ret == 0); + + hw = ether_aton(hwaddr_str); + assert(hw != NULL); + + switch (addr.ip.sin_family) { + case AF_INET: + ret = arp_build(buf, buflen, &addr.ip, hw, reply, &dhw, &len); + break; + case AF_INET6: + ret = ip6_na_build(buf, buflen, &addr.ip6, hw, &dhw, &len); + break; + default: + abort(); + } + + assert(ret == 0); + + hexdump(buf, len); +} + +#else /* HAVE_PACKETSOCKET */ + +static void test_arp(const char *addr_str, const char *hwaddr_str, bool reply) +{ + fprintf(stderr, "PACKETSOCKET not supported\n"); +} + +#endif /* HAVE_PACKETSOCKET */ + +static void test_tcp(const char *src_str, + const char *dst_str, + const char *seq_str, + const char *ack_str, + const char *rst_str) +{ + ctdb_sock_addr src, dst; + uint32_t seq, ack; + int rst; + uint8_t buf[512]; + struct ether_header *eth; + size_t expected_len, len; + char src_str_out[64], dst_str_out[64]; + uint32_t seq_out, ack_out; + int rst_out = 0; + uint16_t window; + int ret; + + ret = ctdb_sock_addr_from_string(src_str, &src, true); + assert(ret == 0); + + ret = ctdb_sock_addr_from_string(dst_str, &dst, true); + assert(ret == 0); + + seq = atoi(seq_str); + ack = atoi(ack_str); + rst = atoi(rst_str); + + /* Need to fake this up */ + eth = (struct ether_header *) buf; + memset(eth, 0, sizeof(*eth)); + + switch (src.ip.sin_family) { + case AF_INET: + eth->ether_type = htons(ETHERTYPE_IP); + expected_len = 40; + ret = tcp4_build(buf + sizeof(struct ether_header), + sizeof(buf) - sizeof(struct ether_header), + &src.ip, + &dst.ip, + seq, + ack, + rst, + &len); + break; + case AF_INET6: + eth->ether_type = htons(ETHERTYPE_IP6); + expected_len = 60; + ret = tcp6_build(buf + sizeof(struct ether_header), + sizeof(buf) - sizeof(struct ether_header), + &src.ip6, + &dst.ip6, + seq, + ack, + rst, + &len); + break; + default: + abort(); + } + + assert(ret == 0); + assert(len == expected_len); + + hexdump(buf + sizeof(struct ether_header), len); + + switch (ntohs(eth->ether_type)) { + case ETHERTYPE_IP: + ret = tcp4_extract(buf + sizeof(struct ether_header), + len, + &src.ip, + &dst.ip, + &ack_out, + &seq_out, + &rst_out, + &window); + break; + case ETHERTYPE_IP6: + ret = tcp6_extract(buf + sizeof(struct ether_header), + len, + &src.ip6, + &dst.ip6, + &ack_out, + &seq_out, + &rst_out, + &window); + break; + default: + abort(); + } + + assert(ret == 0); + + assert(seq == seq_out); + assert(ack == ack_out); + assert((rst != 0) == (rst_out != 0)); + assert(window == htons(1234)); + + ret = ctdb_sock_addr_to_buf(src_str_out, sizeof(src_str_out), + &src, true); + assert(ret == 0); + ret = strcmp(src_str, src_str_out); + assert(ret == 0); + + ret = ctdb_sock_addr_to_buf(dst_str_out, sizeof(dst_str_out), + &dst, true); + assert(ret == 0); + ret = strcmp(dst_str, dst_str_out); + assert(ret == 0); +} + +static void usage(const char *prog) +{ + fprintf(stderr, "usage: %s [ ...]\n", prog); + fprintf(stderr, " commands:\n"); + fprintf(stderr, " types\n"); + fprintf(stderr, " arp [reply]\n"); + fprintf(stderr, " tcp \n"); + + exit(1); +} + +int main(int argc, char **argv) +{ + + if (argc < 2) { + usage(argv[0]); + } + + test_backtrace_setup(); + + if (strcmp(argv[1], "types") == 0) { + test_types(); + } else if (strcmp(argv[1], "arp") == 0) { + /* + * Extra arg indicates that a reply should be + * constructed for IPv4 - value is ignored + */ + if (argc != 4 && argc != 5) { + usage(argv[0]); + } + test_arp(argv[2], argv[3], (argc == 5)); + } else if (strcmp(argv[1], "tcp") == 0) { + if (argc != 7) { + usage(argv[0]); + } + test_tcp(argv[2], argv[3], argv[4], argv[5], argv[6]); + } else { + usage(argv[0]); + } + + return 0; +} diff --git a/ctdb/tests/src/test_backtrace.c b/ctdb/tests/src/test_backtrace.c new file mode 100644 index 0000000..aa3fc0c --- /dev/null +++ b/ctdb/tests/src/test_backtrace.c @@ -0,0 +1,37 @@ +/* + Print a backtrace when a test aborts + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include "lib/util/fault.h" +#include "lib/util/signal.h" + +#include "tests/src/test_backtrace.h" + +static void test_abort_backtrace_handler(int sig) +{ + log_stack_trace(); + CatchSignal(SIGABRT, SIG_DFL); + abort(); +} + +void test_backtrace_setup(void) +{ + CatchSignal(SIGABRT, test_abort_backtrace_handler); +} diff --git a/ctdb/tests/src/test_backtrace.h b/ctdb/tests/src/test_backtrace.h new file mode 100644 index 0000000..a6089c9 --- /dev/null +++ b/ctdb/tests/src/test_backtrace.h @@ -0,0 +1,25 @@ +/* + Print a backtrace when a test aborts + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#ifndef __CTDB_TEST_BACKTRACE_H__ +#define __CTDB_TEST_BACKTRACE_H__ + +void test_backtrace_setup(void); + +#endif /* __CTDB_TEST_BACKTRACE_H__ */ diff --git a/ctdb/tests/src/test_mutex_raw.c b/ctdb/tests/src/test_mutex_raw.c new file mode 100644 index 0000000..8ebf77e --- /dev/null +++ b/ctdb/tests/src/test_mutex_raw.c @@ -0,0 +1,434 @@ +/* + * Test the system robust mutex implementation + * + * Copyright (C) 2016 Amitay Isaacs + * Copyright (C) 2018 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * To run the test do the following: + * + * (a) Compile the test. + * + * gcc -O2 -g3 -o test-robust-mutex test-robust-mutex.c -lpthread + * + * (b) Start the "init" process. + * + * ./test-robust-mutex /tmp/shared-mutex init + * + * (c) Start any number of "worker" instances. + * + * ./test-robust-mutex worker <#> + * + * e.g. /tmp/shared-mutex. + * + * <#> : Number of children processes. + * + * : 0 - Normal, 1 - Realtime, 2 - Nice 20. + * + * For example: + * + * As non-root: + * + * $ while true ; do ./test-robust-mutex /tmp/foo worker 10 0 ; done; + * + * As root: + * + * while true ; do ./test-robust-mutex /tmp/foo worker 10 1 ; done; + * + * This creates 20 processes, 10 at normal priority and 10 at realtime + * priority, all taking the lock, being killed and recovering the lock. + * + * If while running (c) the processes block, it might mean that a futex wakeup + * was lost, or that the handoff of EOWNERDEAD did not happen correctly. In + * either case you can debug the resulting mutex like this: + * + * $ ./test-robust-mutex /tmp/shared-mutex debug + * + * This prints the PID of the process holding the mutex or nothing if + * the value was cleared by the kernel and now no process holds the mutex. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Define DEBUG to 1 to enable verbose debugging. */ +#define DEBUG 0 + +/* Implement the worker. The worker has to do the following things: + + * Succeed at locking the mutex, including possible recovery. + * Kill itself. + + Other workers are attempting exactly the same thing in order to + test the loss and recovery of the robust mutex. */ +static void worker (const char *filename) +{ + pthread_mutex_t *mutex; + void *addr; + int ret, fd; + + /* Open the file and map the shared robust mutex. */ + fd = open(filename, O_RDWR, 0600); + if (fd == -1) { + perror ("FAIL: open"); + exit(EXIT_FAILURE); + } + + addr = mmap(NULL, + sizeof(pthread_mutex_t), + PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_FILE, + fd, + 0); + if (addr == NULL) { + perror ("FAIL: mmap"); + exit(EXIT_FAILURE); + } + + mutex = (pthread_mutex_t *)addr; + + /* Every process will lock once, and die once. */ + printf("INFO: pid %u locking\n", getpid()); + do { + ret = pthread_mutex_lock(mutex); + +#if DEBUG + fprintf(stderr, + "DEBUG: pid %u lock attempt, ret=%d\n", + getpid(), + ret); +#endif + + if (ret == EOWNERDEAD) { + int rc; + + rc = pthread_mutex_consistent(mutex); + if (rc == 0) { + pthread_mutex_unlock(mutex); + } else { + fprintf(stderr, + "FAIL: pthread_mutex_consistent " + "failed\n"); + exit(EXIT_FAILURE); + } +#if DEBUG + fprintf(stderr, + "DEBUG: pid %u recovery lock attempt, ret=%d\n", + getpid(), + ret); +#endif + /* Will loop and try to lock again. */ + } + + } while (ret != 0); + + printf ("INFO: pid %u locked, now killing\n", getpid()); + kill(getpid(), SIGKILL); +} + +/* One of three priority modes. */ +#define PRIO_NORMAL 0 +#define PRIO_REALTIME 1 +#define PRIO_NICE_20 2 + +/* One of three operation modes. */ +#define MODE_INIT 0 +#define MODE_WORKER 1 +#define MODE_DEBUG 2 + +/* Print usage information and exit. */ +static void usage (const char *name) +{ + fprintf(stderr, + "Usage: %s [init|worker|debug] [#] [0|1|2]\n", + name); + exit(EXIT_FAILURE); +} + +/* Set the process priority. */ +static void set_priority (int priority) +{ + struct sched_param p; + int ret; + + switch (priority) { + case PRIO_REALTIME: + p.sched_priority = 1; + ret = sched_setscheduler(0, SCHED_FIFO, &p); + if (ret == -1) + perror("FAIL: sched_setscheduler"); + break; + + case PRIO_NICE_20: + ret = nice(-20); + if (ret == -1) + perror("FAIL: nice"); + break; + + case PRIO_NORMAL: + default: + /* Normal priority is the default. */ + break; + } +} + +int main(int argc, const char **argv) +{ + int i, fd, ret, num_children, mode = -1, priority = PRIO_NORMAL; + const char *mode_str; + const char *file; + char *addr; + pthread_mutex_t *mutex; + pthread_mutexattr_t mattr; + pid_t pid; + + /* One of three modes, init, worker, or debug. */ + if (argc < 3 || argc > 5) + usage (argv[0]); + + /* + * The shared memory file. Care should be taken here because if glibc + * is upgraded between runs the internals of the robust mutex could + * change. See this blog post about the dangers: + * https://developers.redhat.com/blog/2017/03/13/cc-library-upgrades-and-opaque-data-types-in-process-shared-memory/ + * and how to avoid problems inherent in this. + */ + file = argv[1]; + + /* Set the mode. */ + mode_str = argv[2]; + if (strcmp ("init", mode_str) == 0) { + mode = MODE_INIT; + } else if (strcmp ("worker", mode_str) == 0) { + mode = MODE_WORKER; + } else if (strcmp ("debug", mode_str) == 0) { + mode = MODE_DEBUG; + } else { + usage (argv[0]); + } + + /* This is "worker" mode, so set the priority. */ + if (mode == MODE_WORKER) { + priority = atoi(argv[4]); + set_priority(priority); + } + + /* All modes open the file. */ + fd = open(argv[1], O_CREAT|O_RDWR, 0600); + if (fd == -1) { + perror("FAIL: open"); + exit(EXIT_FAILURE); + } + + ret = lseek(fd, 0, SEEK_SET); + if (ret != 0) { + perror("FAIL: lseek"); + exit(EXIT_FAILURE); + } + + /* Truncate the file backing the mutex only in the init phase. */ + if (mode == MODE_INIT) { + ret = ftruncate(fd, sizeof(pthread_mutex_t)); + if (ret != 0) { + perror("FAIL: ftruncate"); + exit(EXIT_FAILURE); + } + } + + /* Map the robust mutex. */ + addr = mmap(NULL, + sizeof(pthread_mutex_t), + PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_FILE, + fd, + 0); + if (addr == NULL) { + perror("FAIL: mmap"); + exit(EXIT_FAILURE); + } + + mutex = (pthread_mutex_t *)(void *)addr; + + /* + * In the debug mode we try to recover the mutex and print it. + * WARNING: All other processes should be stuck, otherwise they may + * change the value of the lock between trylock and the printing after + * EBUSY. + */ + if (mode == MODE_DEBUG) { + ret = pthread_mutex_trylock(mutex); + if (ret == EOWNERDEAD) { + ret = pthread_mutex_consistent(mutex); + if (ret == 0) { + pthread_mutex_unlock(mutex); + } else { + fprintf(stderr, + "FAIL: pthread_mutex_consistent " + "failed\n"); + exit (EXIT_FAILURE); + } + } else if (ret == EBUSY) { + printf("INFO: pid=%u\n", mutex->__data.__owner); + } else if (ret == 0) { + pthread_mutex_unlock(mutex); + } + exit(EXIT_SUCCESS); + } + + /* + * Only the initializing process does initialization because it is + * undefined behaviour to re-initialize an already initialized mutex + * that was not destroyed. + */ + if (mode == MODE_INIT) { + + ret = pthread_mutexattr_init(&mattr); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_init failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutexattr_settype(&mattr, + PTHREAD_MUTEX_ERRORCHECK); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_settype failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutexattr_setpshared(&mattr, + PTHREAD_PROCESS_SHARED); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_setpshared failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_setrobust failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutex_init(mutex, &mattr); + if (ret != 0) { + fprintf(stderr, "FAIL: pthread_mutex_init failed\n"); + exit(EXIT_FAILURE); + } + + printf ("INFO: init: Mutex initialization complete.\n"); + /* Never exit. */ + for (;;) + sleep (1); + } + + /* Acquire the mutext for the first time. Might be dead. + Might also be concurrent with the high-priority threads. */ + fprintf(stderr, + "INFO: parent: Acquiring mutex (pid = %d).\n", + getpid()); + do { + ret = pthread_mutex_lock(mutex); + + /* Not consistent? Try to make it so. */ + if (ret == EOWNERDEAD) { + int rc; + + rc = pthread_mutex_consistent(mutex); + if (rc == 0) { + pthread_mutex_unlock (mutex); + } else { + fprintf(stderr, + "FAIL: pthread_mutex_consistent " + "failed\n"); + exit (EXIT_FAILURE); + } + + /* Will loop and try to lock again. */ + fprintf(stderr, + "INFO: parent: Unlock recovery ret = %d\n", + ret); + } + + } while (ret != 0); + + /* + * Set the parent process into it's own process group (hides the + * children). + */ + setpgid(0, 0); + + /* Create # of children. */ + fprintf(stderr, "INFO: parent: Creating children\n"); + num_children = atoi(argv[3]); + + for (i = 0; i < num_children; i++) { + pid = fork(); + if (pid < 0) { + fprintf(stderr, "FAIL: fork() failed\n"); + exit(EXIT_FAILURE); + } + if (pid == 0) { + close(fd); + worker(file); + exit(EXIT_FAILURE); + } + } + + fprintf(stderr, "INFO: parent: Waiting for children\n"); + + /* Unlock the recently acquired mutex or the old lost mutex. */ + ret = pthread_mutex_unlock(mutex); + if (ret != 0) { + fprintf(stderr, "FAIL: pthread_mutex_unlock failed\n"); + exit(EXIT_FAILURE); + } + + /* + * All threads are running now, and each will take the lock and + * die in turn. When they are all dead we will exit and be started + * again by the caller. + */ + for (i = 0; i < num_children; i++) { + int status; + pid = waitpid(-1, &status, 0); + if (pid <= 0) { + fprintf(stderr, "FAIL: waitpid() failed\n"); + exit(EXIT_FAILURE); + } + fprintf(stderr, + "INFO: parent: Reaped %u\n", + (unsigned int) pid); + } + + /* We never unlink fd. The file must be cleaned up by the caller. */ + close(fd); + + exit(EXIT_SUCCESS); +} diff --git a/ctdb/tests/src/test_options.c b/ctdb/tests/src/test_options.c new file mode 100644 index 0000000..2c64404 --- /dev/null +++ b/ctdb/tests/src/test_options.c @@ -0,0 +1,245 @@ +/* + CTDB tests commandline options + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" + +#include +#include +#include + +#include "lib/util/debug.h" + +#include "common/logging.h" +#include "common/path.h" + +#include "tests/src/test_options.h" + +static struct test_options _values; + +static struct poptOption options_basic[] = { + { + .longName = "socket", + .shortName = 's', + .argInfo = POPT_ARG_STRING, + .arg = &_values.socket, + .descrip = "CTDB socket path", + .argDescrip = "filename", + }, + { + .longName = "timelimit", + .shortName = 't', + .argInfo = POPT_ARG_INT, + .arg = &_values.timelimit, + .descrip = "Time limit (in seconds)", + }, + { + .longName = "num-nodes", + .shortName = 'n', + .argInfo = POPT_ARG_INT, + .arg = &_values.num_nodes, + .descrip = "Number of cluster nodes", + }, + { + .longName = "debug", + .shortName = 'd', + .argInfo = POPT_ARG_STRING, + .arg = &_values.debugstr, + .descrip = "Debug level", + }, + { + .longName = "interactive", + .shortName = 'i', + .argInfo = POPT_ARG_NONE, + .arg = &_values.interactive, + .val = 0, + .descrip = "Interactive output", + }, + POPT_TABLEEND +}; + +#define TEST_OPTIONS_BASIC \ + { \ + .argInfo = POPT_ARG_INCLUDE_TABLE, \ + .arg = options_basic, \ + .descrip = "General options:", \ + }, + +static struct poptOption options_database[] = { + { + .longName = "database", + .shortName = 'D', + .argInfo = POPT_ARG_STRING, + .arg = &_values.dbname, + .descrip = "CTDB database name", + }, + { + .longName = "key", + .shortName = 'k', + .argInfo = POPT_ARG_STRING, + .arg = &_values.keystr, + .descrip = "Name of database key", + }, + { + .longName = "value", + .shortName = 'v', + .argInfo = POPT_ARG_STRING, + .arg = &_values.valuestr, + .descrip = "Value of database key", + }, + { + .longName = "dbtype", + .shortName = 'T', + .argInfo = POPT_ARG_STRING, + .arg = &_values.dbtype, + .descrip = "CTDB database type", + }, + POPT_TABLEEND +}; + +#define TEST_OPTIONS_DATABASE \ + { \ + .argInfo = POPT_ARG_INCLUDE_TABLE, \ + .arg = options_database, \ + .descrip = "Database options:", \ + }, + +static void set_defaults_basic(struct test_options *opts) +{ + /* Set default options */ + opts->socket = path_socket(NULL, "ctdbd"); /* leaked */ + assert(opts->socket != NULL); + + opts->timelimit = 10; + opts->num_nodes = 1; + opts->debugstr = "ERR"; + opts->interactive = 0; +} + +static void set_defaults_database(struct test_options *opts) +{ + opts->dbname = NULL; + opts->keystr = NULL; + opts->valuestr = NULL; + opts->dbtype = "volatile"; +} + +static bool verify_options_basic(struct test_options *opts) +{ + int log_level; + bool status; + + status = debug_level_parse(opts->debugstr, &log_level); + if (! status) { + fprintf(stderr, "Error: Invalid debug string '%s'\n", + opts->debugstr); + return false; + } + + debuglevel_set(log_level); + + return true; +} + +static bool verify_options_database(struct test_options *opts) +{ + if (opts->dbname == NULL) { + fprintf(stderr, "Error: Please specify database\n"); + return false; + } + if (opts->keystr == NULL) { + fprintf(stderr, "Error: Please specify key name\n"); + return false; + } + + if ((strcmp(opts->dbtype, "volatile") != 0) && + (strcmp(opts->dbtype, "persistent") != 0) && + (strcmp(opts->dbtype, "replicated") != 0)) { + return false; + } + + return true; +} + +static bool process_options_common(int argc, const char **argv, + struct poptOption *options) +{ + poptContext pc; + int opt; + + pc = poptGetContext(argv[0], argc, argv, options, + POPT_CONTEXT_KEEP_FIRST); + while ((opt = poptGetNextOpt(pc)) != -1) { + fprintf(stderr, "Invalid option %s: %s\n", + poptBadOption(pc, 0), poptStrerror(opt)); + return false; + } + + return true; +} + +bool process_options_basic(int argc, const char **argv, + const struct test_options **opts) +{ + struct poptOption options[] = { + POPT_AUTOHELP + TEST_OPTIONS_BASIC + POPT_TABLEEND + }; + + set_defaults_basic(&_values); + + if (! process_options_common(argc, argv, options)) { + return false; + } + + if (! verify_options_basic(&_values)) { + return false; + } + + *opts = &_values; + return true; +} + +bool process_options_database(int argc, const char **argv, + const struct test_options **opts) +{ + struct poptOption options[] = { + POPT_AUTOHELP + TEST_OPTIONS_BASIC + TEST_OPTIONS_DATABASE + POPT_TABLEEND + }; + + set_defaults_basic(&_values); + set_defaults_database(&_values); + + if (! process_options_common(argc, argv, options)) { + return false; + } + + if (! verify_options_basic(&_values)) { + return false; + } + if (! verify_options_database(&_values)) { + return false; + } + + *opts = &_values; + return true; +} diff --git a/ctdb/tests/src/test_options.h b/ctdb/tests/src/test_options.h new file mode 100644 index 0000000..1e194c9 --- /dev/null +++ b/ctdb/tests/src/test_options.h @@ -0,0 +1,44 @@ +/* + CTDB tests commandline options + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#ifndef __TEST_OPTIONS_H__ +#define __TEST_OPTIONS_H__ + +struct test_options { + /* Basic options */ + const char *socket; + int timelimit; + int num_nodes; + const char *debugstr; + int interactive; + + /* Database options */ + const char *dbname; + const char *keystr; + const char *valuestr; + const char *dbtype; +}; + +bool process_options_basic(int argc, const char **argv, + const struct test_options **opts); + +bool process_options_database(int argc, const char **argv, + const struct test_options **opts); + +#endif /* __TEST_OPTIONS_H__ */ diff --git a/ctdb/tests/src/tmon_ping_test.c b/ctdb/tests/src/tmon_ping_test.c new file mode 100644 index 0000000..c0c0aae --- /dev/null +++ b/ctdb/tests/src/tmon_ping_test.c @@ -0,0 +1,381 @@ +/* + Test trivial FD monitoring + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/wait.h" + +#include +#include +#include + +#include "lib/util/tevent_unix.h" + +#include "common/tmon.h" + +#include "tests/src/test_backtrace.h" + +struct test_state { + const char *label; + unsigned long async_wait_time; + unsigned long blocking_sleep_time; +}; + +static void test_tmon_ping_done(struct tevent_req *subreq); +static void test_async_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *label, + int fd, + int direction, + unsigned long timeout, + unsigned long interval, + unsigned long async_wait_time, + unsigned long blocking_sleep_time) +{ + struct tevent_req *req, *subreq; + struct test_state *state; + + req = tevent_req_create(mem_ctx, &state, struct test_state); + if (req == NULL) { + return NULL; + } + + state->label = label; + state->async_wait_time = async_wait_time; + state->blocking_sleep_time = blocking_sleep_time; + + subreq = tmon_ping_send(state, + ev, + fd, + direction, + timeout, + interval); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_tmon_ping_done, req); + + if (state->async_wait_time != 0) { + fprintf(stderr, + "%s: async wait start %lu\n", + state->label, + state->async_wait_time); + } + subreq = tevent_wakeup_send(state, + ev, + tevent_timeval_current_ofs( + (uint32_t)async_wait_time, 0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_async_wait_done, req); + + return req; +} + +static void test_tmon_ping_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test_state *state = tevent_req_data(req, struct test_state); + bool status; + int err; + + status = tmon_ping_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!status) { + switch(err) { + case EPIPE: + fprintf(stderr, "%s: pipe closed\n", state->label); + break; + case ETIMEDOUT: + fprintf(stderr, "%s: ping timeout\n", state->label); + break; + default: + fprintf(stderr, "%s: error (%d)\n", state->label, err); + } + tevent_req_error(req, err); + return; + } + + fprintf(stderr, "%s: done\n", state->label); + tevent_req_done(req); +} + +static void test_async_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test_state *state = tevent_req_data(req, struct test_state); + unsigned int left; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (!status) { + fprintf(stderr, + "%s: tevent_wakeup_recv() failed\n", + state->label); + /* Ignore error */ + } + if (state->async_wait_time != 0) { + fprintf(stderr, "%s: async wait end\n", state->label); + } + + if (state->blocking_sleep_time == 0) { + goto done; + } + + fprintf(stderr, + "%s: blocking sleep start %lu\n", + state->label, + state->blocking_sleep_time); + left = sleep((unsigned int)state->blocking_sleep_time); + fprintf(stderr, + "%s: blocking sleep end\n", + state->label); + if (left != 0) { + tevent_req_error(req, EINTR); + return; + } + +done: + tevent_req_done(req); +} + +static bool test_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static int test_one(bool is_parent, + int sync_fd, + int fd, + int direction, + unsigned long timeout, + unsigned long interval, + unsigned long async_wait_time, + unsigned long blocking_sleep_time) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + bool status; + char buf[1] = ""; + ssize_t count; + int err; + int ret; + + if (!is_parent) { + count = read(sync_fd, buf, sizeof(buf)); + assert(count == 1); + assert(buf[0] == '\0'); + close(sync_fd); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + ret = ENOMEM; + goto done; + } + + req = test_send(mem_ctx, + ev, + is_parent ? "parent" : "child", + fd, + direction, + timeout, + interval, + async_wait_time, + blocking_sleep_time); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + if (is_parent) { + count = write(sync_fd, buf, sizeof(buf)); + assert(count == 1); + } + + status = tevent_req_poll(req, ev); + if (!status) { + ret = EIO; + goto done; + } + + status = test_recv(req, &err); + ret = status ? 0 : err; + +done: + return ret; +} + +static void test(unsigned long parent_timeout, + unsigned long parent_interval, + unsigned long parent_async_wait_time, + unsigned long parent_blocking_sleep_time, + int parent_result, + unsigned long child_timeout, + unsigned long child_interval, + unsigned long child_async_wait_time, + unsigned long child_blocking_sleep_time, + int child_result) +{ + int sync[2]; + int fd[2]; + pid_t pid; + int wstatus; + int ret; + + /* Pipe for synchronisation */ + ret = pipe(sync); + assert(ret == 0); + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* child */ + close(sync[1]); + close(fd[0]); + + ret = test_one(false, + sync[0], + fd[1], + TMON_FD_BOTH, + child_timeout, + child_interval, + child_async_wait_time, + child_blocking_sleep_time); + _exit(ret); + } + + /* Parent */ + close(sync[0]); + close(fd[1]); + + ret = test_one(true, + sync[1], + fd[0], + TMON_FD_BOTH, + parent_timeout, + parent_interval, + parent_async_wait_time, + parent_blocking_sleep_time); + assert(ret == parent_result); + + /* Close to mimic exit, so child status can be checked below */ + close(fd[0]); + + /* Abort if child failed */ + waitpid(pid, &wstatus, 0); + if (WIFEXITED(wstatus)) { + assert(WEXITSTATUS(wstatus) == child_result); + } +} + +struct test_inputs { + unsigned int timeout; + unsigned int interval; + unsigned int async_wait_time; + unsigned int blocking_sleep_time; + int expected_result; +}; + +static void get_test_inputs(const char **args, struct test_inputs *inputs) +{ + if (strcmp(args[0], "false") == 0) { + inputs->interval = 0; + } else if (strcmp(args[0], "true") == 0) { + inputs->interval = 1; + } else { + inputs->interval = strtoul(args[0], NULL, 0); + } + + inputs->timeout = strtoul(args[1], NULL, 0); + inputs->async_wait_time = (unsigned int)strtoul(args[2], NULL, 0); + inputs->blocking_sleep_time = (unsigned int)strtoul(args[3], NULL, 0); + inputs->expected_result = (int)strtoul(args[4], NULL, 0); +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "usage: %s " + "\\\n\t" + " " + " " + " " + " " + " " + "\\\n\t" + " " + " " + " " + " " + " " + "\n", + prog); + exit(1); +} + +int main(int argc, const char **argv) +{ + struct test_inputs parent; + struct test_inputs child; + + if (argc != 11) { + usage(argv[0]); + } + + test_backtrace_setup(); + + get_test_inputs(&argv[1], &parent); + get_test_inputs(&argv[6], &child); + + test(parent.timeout, + parent.interval, + parent.async_wait_time, + parent.blocking_sleep_time, + parent.expected_result, + child.timeout, + child.interval, + child.async_wait_time, + child.blocking_sleep_time, + child.expected_result); + + return 0; +} diff --git a/ctdb/tests/src/tmon_test.c b/ctdb/tests/src/tmon_test.c new file mode 100644 index 0000000..10eaa72 --- /dev/null +++ b/ctdb/tests/src/tmon_test.c @@ -0,0 +1,406 @@ +/* + Test trivial FD monitoring + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/wait.h" + +#include +#include +#include +#include + +#include "lib/util/tevent_unix.h" + +#include "common/tmon.h" + +#include "tests/src/test_backtrace.h" + +struct test_write_state { + const char *write_data; + size_t write_data_len; + unsigned int offset; + struct tevent_req *req; +}; + +static int test_write_callback(void *private_data, struct tmon_pkt *pkt) +{ + struct test_write_state *state = talloc_get_type_abort( + private_data, struct test_write_state); + bool status; + size_t len; + char *end; + int err; + char c; + const char *t; + + assert(state->write_data != NULL); + + len = strlen(state->write_data); + if (state->offset >= len) { + return TMON_STATUS_EXIT; + } + + c = state->write_data[state->offset]; + state->offset++; + + if (isdigit(c)) { + err = c - '0'; + + if (err == 0) { + status = tmon_set_exit(pkt); + } else { + status = tmon_set_errno(pkt, err); + } + } else if (ispunct(c)) { + switch (c) { + case '.': + return TMON_STATUS_SKIP; + break; + case '!': + status = tmon_set_ping(pkt); + break; + case '#': + /* Additional errno syntax: #nnn[;] */ + t = &state->write_data[state->offset]; + err = (int)strtol(t, &end, 10); + state->offset += (end - t); + if (state->write_data[state->offset] == ';') { + state->offset++; + } + status = tmon_set_errno(pkt, err); + break; + default: + status = false; + } + } else if (isascii(c) && !isspace(c)) { + status = tmon_set_ascii(pkt, c); + } else { + status = tmon_set_custom(pkt, (uint16_t)c); + } + + if (!status) { + return EDOM; + } + + t = getenv("CTDB_TEST_TMON_WRITE_SKIP_MODE"); + if (t == NULL) { + return 0; + } + + /* + * This is write-skip mode: tmon_write() is called directly + * here in the callback and TMON_WRITE_SKIP is returned. This + * allows tmon_write() to be exercised by reusing test cases + * rather than writing extra test code and test cases. + */ + + status = tmon_write(state->req, pkt); + if (!status) { + return EIO; + } + + return TMON_STATUS_SKIP; +} + +static void test_tmon_done(struct tevent_req *subreq); + +static struct tevent_req *test_write_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, + const char *write_data) +{ + struct tevent_req *req, *subreq; + struct test_write_state *state; + struct tmon_actions actions = { + .write_callback = test_write_callback, + }; + + req = tevent_req_create(mem_ctx, &state, struct test_write_state); + if (req == NULL) { + return NULL; + } + + state->write_data = write_data; + state->offset = 0; + + subreq = tmon_send(state, + ev, + fd, + TMON_FD_WRITE, + 0, + 1, + &actions, + state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_tmon_done, req); + + /* Nasty hack, but OK to cheapen testing - see test_write_callback() */ + state->req = subreq; + + return req; +} + +static void test_tmon_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int err; + + status = tmon_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!status) { + tevent_req_error(req, err); + return; + } + + tevent_req_done(req); +} + +static bool test_write_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static int test_timeout_ok_callback(void *private_data) +{ + return 0; +} + +static int test_read_callback(void *private_data, struct tmon_pkt *pkt) +{ + bool status; + char c; + uint16_t val; + + status = tmon_parse_ping(pkt); + if (status) { + printf("PING\n"); + fflush(stdout); + return 0; + } + + status = tmon_parse_ascii(pkt, &c); + if (status) { + printf("ASCII %c\n", c); + fflush(stdout); + return 0; + } + + status = tmon_parse_custom(pkt, &val); + if (status) { + printf("CUSTOM 0x%"PRIx16"\n", val); + fflush(stdout); + return 0; + } + + return 0; +} + +static int test_close_ok_callback(void *private_data) +{ + return 0; +} + +struct test_read_state { +}; + +static struct tevent_req *test_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, + bool close_ok, + unsigned long timeout, + bool timeout_ok) +{ + struct tevent_req *req, *subreq; + struct test_read_state *state; + struct tmon_actions actions = { + .read_callback = test_read_callback, + }; + + req = tevent_req_create(mem_ctx, &state, struct test_read_state); + if (req == NULL) { + return NULL; + } + + if (timeout_ok) { + actions.timeout_callback = test_timeout_ok_callback; + } + if (close_ok) { + actions.close_callback = test_close_ok_callback; + } + + subreq = tmon_send(state, + ev, + fd, + TMON_FD_READ, + timeout, + 0, + &actions, + state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_tmon_done, req); + + return req; +} + +static bool test_read_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static void test(const char *write_data, + bool close_ok, + unsigned long timeout, + bool timeout_ok) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + int fd[2]; + pid_t pid; + int wstatus; + bool status; + int err; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* child */ + close(fd[1]); + + req = test_read_send(mem_ctx, + ev, + fd[0], + close_ok, + timeout, + timeout_ok); + assert(req != NULL); + + status = tevent_req_poll(req, ev); + assert(status); + + status = test_read_recv(req, &err); + if (status) { + err = 0; + printf("READER OK\n"); + } else { + printf("READER ERR=%d\n", err); + } + fflush(stdout); + + _exit(ret); + } + + /* Parent */ + close(fd[0]); + + req = test_write_send(mem_ctx, + ev, + fd[1], + write_data); + assert(req != NULL); + + status = tevent_req_poll(req, ev); + assert(status); + + status = test_write_recv(req, &err); + if (status) { + err = 0; + printf("WRITER OK\n"); + } else { + printf("WRITER ERR=%d\n", err); + } + fflush(stdout); + + /* Close to mimic exit, so child status can be checked below */ + close(fd[1]); + + waitpid(pid, &wstatus, 0); +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "usage: %s \n\n" + " is processed by test_write_callback(), " + "1 character per second:\n" + " 0: write EXIT\n" + " 1-9: write ERRNO 1-9\n" + " .: skip write\n" + " : write CUSTOM containing \n" + " other : write ASCII containing \n" + " other: write CUSTOM\n" + " See test_write_callback() for more details\n" + , + prog); + exit(1); +} + +int main(int argc, const char **argv) +{ + bool close_ok, timeout_ok; + unsigned long timeout; + + if (argc != 5) { + usage(argv[0]); + } + + test_backtrace_setup(); + + close_ok = (strcmp(argv[2], "true") == 0); + timeout = strtoul(argv[3], NULL, 0); + if (timeout == 0) { + /* + * Default timeout that should not come into play but + * will cause tests to fail after a reasonable amount + * of time, if something unexpected happens. + */ + timeout = 20; + } + timeout_ok = (strcmp(argv[4], "true") == 0); + + test(argv[1], close_ok, timeout, timeout_ok); + + return 0; +} diff --git a/ctdb/tests/src/transaction_loop.c b/ctdb/tests/src/transaction_loop.c new file mode 100644 index 0000000..c6bf35d --- /dev/null +++ b/ctdb/tests/src/transaction_loop.c @@ -0,0 +1,419 @@ +/* + simple ctdb benchmark for persistent databases + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct transaction_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int num_nodes; + int timelimit; + int interactive; + TDB_DATA key; + uint32_t pnn; + struct ctdb_transaction_handle *h; + uint32_t *old_counter, *counter; + struct tevent_req *subreq; + bool done; +}; + +static void transaction_loop_start(struct tevent_req *subreq); +static void transaction_loop_started(struct tevent_req *subreq); +static void transaction_loop_committed(struct tevent_req *subreq); +static void transaction_loop_each_second(struct tevent_req *subreq); +static bool transaction_loop_check_counters(struct tevent_req *req); +static void transaction_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *transaction_loop_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + int num_nodes, int timelimit, int interactive, + const char *keystr) +{ + struct tevent_req *req, *subreq; + struct transaction_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct transaction_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->interactive = interactive; + state->key.dptr = discard_const(keystr); + state->key.dsize = strlen(keystr); + state->pnn = ctdb_client_pnn(client); + state->old_counter = talloc_zero_array(state, uint32_t, num_nodes); + if (tevent_req_nomem(state->old_counter, req)) { + return tevent_req_post(req, ev); + } + state->counter = talloc_zero_array(state, uint32_t, num_nodes); + if (tevent_req_nomem(state->counter, req)) { + return tevent_req_post(req, ev); + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, transaction_loop_start, req); + + return req; +} + +static void transaction_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_transaction_start_send(state, state->ev, state->client, + tevent_timeval_current_ofs( + state->timelimit, 0), + state->ctdb_db, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_started, req); + state->subreq = subreq; + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_each_second, + req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_finish, req); +} + +static void transaction_loop_started(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + TDB_DATA data; + int ret; + uint32_t *counter; + + state->h = ctdb_transaction_start_recv(subreq, &ret); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (state->h == NULL) { + fprintf(stderr, "transaction start failed\n"); + tevent_req_error(req, ret); + return; + } + + ret = ctdb_transaction_fetch_record(state->h, state->key, + state, &data); + if (ret != 0) { + fprintf(stderr, "transaction fetch record failed\n"); + tevent_req_error(req, ret); + return; + } + + if (data.dsize < state->num_nodes * sizeof(uint32_t)) { + TALLOC_FREE(data.dptr); + + data.dsize = state->num_nodes * sizeof(uint32_t); + data.dptr = (uint8_t *)talloc_zero_array(state, uint32_t, + state->num_nodes); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + } + + counter = (uint32_t *)data.dptr; + counter[state->pnn] += 1; + memcpy(state->counter, counter, state->num_nodes * sizeof(uint32_t)); + + ret = ctdb_transaction_store_record(state->h, state->key, data); + if (ret != 0) { + fprintf(stderr, "transaction store failed\n"); + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_transaction_commit_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0), + state->h); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_committed, req); + state->subreq = subreq; +} + +static void transaction_loop_committed(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + int ret; + bool status; + + status = ctdb_transaction_commit_recv(subreq, &ret); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (! status) { + fprintf(stderr, "transaction commit failed - %s\n", + strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->pnn == 0) { + if (! transaction_loop_check_counters(req)) { + return; + } + } + + if (state->done) { + int i; + + printf("Transaction[%u]: ", ctdb_client_pnn(state->client)); + for (i=0; inum_nodes; i++) { + printf("%6u ", state->counter[i]); + } + printf("\n"); + + tevent_req_done(req); + + return; + } + + subreq = ctdb_transaction_start_send(state, state->ev, state->client, + tevent_timeval_current_ofs( + state->timelimit, 0), + state->ctdb_db, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_started, req); +} + +static void transaction_loop_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + bool status; + int i; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + fprintf(stderr, "tevent wakeup failed\n"); + tevent_req_error(req, EIO); + return; + } + + if (state->interactive == 1) { + printf("Transaction[%u]: ", ctdb_client_pnn(state->client)); + for (i=0; inum_nodes; i++) { + printf("%6u ", state->counter[i]); + } + printf("\n"); + fflush(stdout); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_each_second, req); +} + +static bool transaction_loop_check_counters(struct tevent_req *req) +{ + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + int i; + bool monotonous = true; + + for (i=0; inum_nodes; i++) { + if (state->counter[i] < state->old_counter[i]) { + fprintf(stderr, + "Counter reduced for node %d: %u -> %u\n", + i, state->old_counter[i], state->counter[i]); + monotonous = false; + break; + } + } + + if (monotonous) { + memcpy(state->old_counter, state->counter, + state->num_nodes * sizeof(uint32_t)); + } + + return monotonous; +} + +static void transaction_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + + state->done = true; + + if (! status) { + tevent_req_error(req, EIO); + return; + } +} + +static bool transaction_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + uint8_t db_flags; + int ret; + bool status; + + setup_logging("transaction_loop", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + if (strcmp(opts->dbtype, "persistent") == 0) { + db_flags = CTDB_DB_FLAGS_PERSISTENT; + } else if (strcmp(opts->dbtype, "replicated") == 0) { + db_flags = CTDB_DB_FLAGS_REPLICATED; + } else { + fprintf(stderr, "Database must be persistent or replicated\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, + db_flags, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to persistent DB %s\n", + opts->dbname); + exit(1); + } + + req = transaction_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit, + opts->interactive, opts->keystr); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = transaction_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "transaction loop test failed, ret=%d\n", ret); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/tunable_test.c b/ctdb/tests/src/tunable_test.c new file mode 100644 index 0000000..ea94aec --- /dev/null +++ b/ctdb/tests/src/tunable_test.c @@ -0,0 +1,71 @@ +/* + Test tunable handling + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include +#include + +#include "common/tunable.c" + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct ctdb_tunable_list tun_list; + struct ctdb_var_list *list; + bool status; + int ret = 0; + int i; + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + status = ctdb_tunable_load_file(mem_ctx, &tun_list, argv[1]); + if (!status) { + ret = EINVAL; + goto done; + } + + list = ctdb_tunable_names(mem_ctx); + assert(list != NULL); + + for (i = 0; i < list->count; i++) { + const char *var = list->var[i]; + uint32_t val; + + status = ctdb_tunable_get_value(&tun_list, var, &val); + if (!status) { + ret = EIO; + goto done; + } + + printf("%s=%"PRIu32"\n", var, val); + fflush(stdout); + } + +done: + talloc_free(mem_ctx); + return ret; +} diff --git a/ctdb/tests/src/tunnel_cmd.c b/ctdb/tests/src/tunnel_cmd.c new file mode 100644 index 0000000..73a2297 --- /dev/null +++ b/ctdb/tests/src/tunnel_cmd.c @@ -0,0 +1,199 @@ +/* + CTDB tunnel test + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include +#include + +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_private.h" +#include "client/client.h" + +#define TUNNEL_ID (CTDB_TUNNEL_TEST | 0xf0f0f0f0) + +struct listen_state { + TALLOC_CTX *mem_ctx; + bool done; +}; + +static void listen_callback(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct listen_state *state = (struct listen_state *)private_data; + const char *msg; + size_t np; + int ret; + + ret = ctdb_stringn_pull(buf, buflen, state->mem_ctx, &msg, &np); + if (ret != 0) { + fprintf(stderr, "Invalid tunnel message, ret=%d\n", ret); + return; + } + + fprintf(stderr, "%u: %s\n", srcnode, msg); + + if (strcmp(msg, "quit") == 0) { + state->done = true; + } + + talloc_free(discard_const(msg)); +} + +static int cmd_listen(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct ctdb_client_context *client) +{ + struct ctdb_tunnel_context *tunnel; + struct listen_state state; + int ret; + + state.mem_ctx = mem_ctx; + state.done = false; + + ret = ctdb_tunnel_setup(mem_ctx, ev, client, TUNNEL_ID, + listen_callback, &state, &tunnel); + if (ret != 0) { + return ret; + } + + ctdb_client_wait(ev, &state.done); + + ret = ctdb_tunnel_destroy(ev, tunnel); + if (ret != 0) { + return ret; + } + + return 0; +} + +static void send_callback(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, void *private_data) +{ + fprintf(stderr, "send received a message - %u: %zu\n", srcnode, buflen); +} + +static int cmd_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct ctdb_client_context *client, + uint32_t destnode, const char *msg) +{ + struct ctdb_tunnel_context *tunnel; + uint8_t *buf; + size_t buflen, np; + int ret; + + ret = ctdb_tunnel_setup(mem_ctx, ev, client, TUNNEL_ID, + send_callback, NULL, &tunnel); + if (ret != 0) { + return ret; + } + + buflen = ctdb_stringn_len(&msg); + buf = talloc_size(mem_ctx, buflen); + if (buf == NULL) { + return ENOMEM; + } + ctdb_stringn_push(&msg, buf, &np); + + ret = ctdb_tunnel_request(mem_ctx, ev, tunnel, destnode, + tevent_timeval_zero(), buf, buflen, false); + if (ret != 0) { + return ret; + } + + ret = ctdb_tunnel_destroy(ev, tunnel); + if (ret != 0) { + return ret; + } + + return 0; +} + +static void usage(const char *cmd) +{ + fprintf(stderr, "usage: %s listen\n", cmd); + fprintf(stderr, "usage: %s send \n", cmd); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + const char *socket = NULL, *msg = NULL; + uint32_t pnn = CTDB_UNKNOWN_PNN; + int ret; + bool do_listen = false; + bool do_send = false; + + if (argc != 3 && argc != 5) { + usage(argv[0]); + exit(1); + } + + socket = argv[1]; + + if (strcmp(argv[2], "listen") == 0) { + do_listen = true; + } else if (strcmp(argv[2], "send") == 0) { + if (argc != 5) { + usage(argv[0]); + exit(1); + } + + pnn = atol(argv[3]); + msg = argv[4]; + do_send = true; + } else { + usage(argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + talloc_free(mem_ctx); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, socket, &client); + if (ret != 0) { + talloc_free(mem_ctx); + exit(1); + } + + if (do_listen) { + ret = cmd_listen(mem_ctx, ev, client); + } + if (do_send) { + ret = cmd_send(mem_ctx, ev, client, pnn, msg); + } + + talloc_free(mem_ctx); + + return ret; +} diff --git a/ctdb/tests/src/tunnel_test.c b/ctdb/tests/src/tunnel_test.c new file mode 100644 index 0000000..a6d44ba --- /dev/null +++ b/ctdb/tests/src/tunnel_test.c @@ -0,0 +1,480 @@ +/* + CTDB tunnel test + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_private.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct test_data { + uint32_t pnn; + uint32_t count; +}; + +static size_t test_data_len(struct test_data *in) +{ + return ctdb_uint32_len(&in->pnn) + ctdb_uint32_len(&in->count); +} + +static void test_data_push(struct test_data *in, uint8_t *buf, size_t *npush) +{ + size_t offset = 0, np; + + ctdb_uint32_push(&in->pnn, buf+offset, &np); + offset += np; + + ctdb_uint32_push(&in->count, buf+offset, &np); + offset += np; + + *npush = offset; +} + +static int test_data_pull(uint8_t *buf, size_t buflen, struct test_data *out, + size_t *npull) +{ + size_t offset = 0, np; + int ret; + + ret = ctdb_uint32_pull(buf+offset, buflen-offset, &out->pnn, &np); + if (ret != 0) { + return ret; + } + offset += np; + + ret = ctdb_uint32_pull(buf+offset, buflen-offset, &out->count, &np); + if (ret != 0) { + return ret; + } + offset += np; + + *npull = offset; + return 0; +} + +/* + * Set up 2 tunnels from each node - one to the next node and one to the + * previous node. The tunnel to the next node is used for sending data and + * tunnel to the previous node is used for receiving data. + */ + +struct tunnel_test_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + int num_nodes; + int timelimit; + + uint32_t pnn; + uint32_t next_node; + uint32_t prev_node; + bool done; + struct ctdb_tunnel_context *send_tunnel; + struct ctdb_tunnel_context *recv_tunnel; + uint32_t count; + uint8_t *buf; +}; + +static void tunnel_test_send_tunnel_done(struct tevent_req *subreq); +static void tunnel_test_recv_tunnel_done(struct tevent_req *subreq); +static void tunnel_test_start(struct tevent_req *subreq); +static void tunnel_test_msg_send(struct tevent_req *req, + struct test_data *tdata); +static void tunnel_test_msg_send_done(struct tevent_req *subreq); +static void tunnel_test_handler(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, + void *private_data); +static void tunnel_test_done(struct tevent_req *subreq); +static void tunnel_test_finish(struct tevent_req *subreq); +static void tunnel_test_send_tunnel_closed(struct tevent_req *subreq); +static void tunnel_test_recv_tunnel_closed(struct tevent_req *subreq); + +static struct tevent_req *tunnel_test_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct tunnel_test_state *state; + + req = tevent_req_create(mem_ctx, &state, struct tunnel_test_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->pnn = ctdb_client_pnn(client); + state->prev_node = (state->pnn + num_nodes - 1) % num_nodes; + state->next_node = (state->pnn + 1) % num_nodes; + state->done = false; + + subreq = ctdb_tunnel_setup_send(state, state->ev, state->client, + CTDB_TUNNEL_TEST | state->pnn, + tunnel_test_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, tunnel_test_send_tunnel_done, req); + + return req; +} + +static void tunnel_test_send_tunnel_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_setup_recv(subreq, &ret, &state->send_tunnel); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_tunnel_setup_send(state, state->ev, state->client, + CTDB_TUNNEL_TEST | state->prev_node, + tunnel_test_handler, req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_recv_tunnel_done, req); +} + +static void tunnel_test_recv_tunnel_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_setup_recv(subreq, &ret, &state->recv_tunnel); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_start, req); +} + +static void tunnel_test_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + struct test_data tdata; + int ret; + bool status; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_done, req); + + tdata.pnn = state->pnn; + tdata.count = state->count; + tunnel_test_msg_send(req, &tdata); +} + +static void tunnel_test_msg_send(struct tevent_req *req, + struct test_data *tdata) +{ + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + struct tevent_req *subreq; + size_t buflen, np; + + buflen = test_data_len(tdata); + state->buf = talloc_size(state, buflen); + if (tevent_req_nomem(state->buf, req)) { + return; + } + test_data_push(tdata, state->buf, &np); + + subreq = ctdb_tunnel_request_send(state, state->ev, + state->send_tunnel, + state->next_node, + tevent_timeval_zero(), + state->buf, buflen, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_msg_send_done, req); +} + +static void tunnel_test_msg_send_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_request_recv(subreq, &ret, NULL, NULL, NULL); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + TALLOC_FREE(state->buf); +} + +static void tunnel_test_handler(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + struct test_data tdata; + size_t np; + int ret; + + if (state->done) { + return; + } + + if (tctx == state->send_tunnel) { + fprintf(stderr, "pnn:%u Received data on send tunnel\n", + state->pnn); + tevent_req_error(req, EPROTO); + return; + } + + ret = test_data_pull(buf, buflen, &tdata, &np); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + if (tdata.pnn == state->pnn) { + if (tdata.count != state->count) { + tevent_req_error(req, EPROTO); + return; + } + + state->count = tdata.count + 1; + tdata.count = state->count; + } + + tunnel_test_msg_send(req, &tdata); +} + +static void tunnel_test_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("pnn[%u] %.1lf msgs/sec\n", + state->pnn, (double)state->count / state->timelimit); + + state->done = true; + + /* wait few more seconds */ + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(3, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_finish, req); +} + +static void tunnel_test_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + subreq = ctdb_tunnel_destroy_send(state, state->ev, + state->send_tunnel); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_send_tunnel_closed, req); +} + +static void tunnel_test_send_tunnel_closed(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_destroy_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + state->send_tunnel = NULL; + + subreq = ctdb_tunnel_destroy_send(state, state->ev, + state->recv_tunnel); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_recv_tunnel_closed, req); +} + +static void tunnel_test_recv_tunnel_closed(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_destroy_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + state->recv_tunnel = NULL; + + tevent_req_done(req); +} + +static bool tunnel_test_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("tunnel_test", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + req = tunnel_test_send(mem_ctx, ev, client, opts->num_nodes, + opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = tunnel_test_recv(req, &ret); + if (! status) { + fprintf(stderr, "tunnel test failed, ret=%d\n", ret); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/update_record.c b/ctdb/tests/src/update_record.c new file mode 100644 index 0000000..11b6050 --- /dev/null +++ b/ctdb/tests/src/update_record.c @@ -0,0 +1,236 @@ +/* + Update a record and increase it's RSN + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_api.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct update_record_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *db; + int timelimit; + TDB_DATA key; +}; + +static void update_record_fetch_done(struct tevent_req *subreq); +static void update_record_update_done(struct tevent_req *subreq); + +static struct tevent_req *update_record_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + const char *keystr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct update_record_state *state; + + req = tevent_req_create(mem_ctx, &state, struct update_record_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->db = db; + state->timelimit = timelimit; + state->key.dptr = (uint8_t *)discard_const(keystr); + state->key.dsize = strlen(keystr); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, update_record_fetch_done, req); + + return req; +} + +static void update_record_fetch_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct update_record_state *state = tevent_req_data( + req, struct update_record_state); + struct ctdb_record_handle *h; + struct ctdb_ltdb_header header; + struct ctdb_rec_buffer *recbuf; + struct ctdb_req_control request; + TDB_DATA data; + int ret; + + h = ctdb_fetch_lock_recv(subreq, &header, NULL, NULL, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + + header.rsn += 10; + + recbuf = ctdb_rec_buffer_init(state, ctdb_db_id(state->db)); + if (tevent_req_nomem(recbuf, req)) { + return; + } + + data.dptr = (uint8_t *)talloc_asprintf(recbuf, "%"PRIu64, header.rsn); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + data.dsize = strlen((char *)data.dptr); + + ret = ctdb_rec_buffer_add(state, recbuf, 0, &header, state->key, data); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ctdb_req_control_update_record(&request, recbuf); + subreq = ctdb_client_control_send(state, state->ev, state->client, + CTDB_CURRENT_NODE, + tevent_timeval_current_ofs( + state->timelimit, 0), + &request); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, update_record_update_done, req); + + talloc_free(recbuf); +} + +static void update_record_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct update_record_state *state = tevent_req_data( + req, struct update_record_state); + struct ctdb_reply_control *reply; + int ret; + bool status; + + status = ctdb_client_control_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_control_update_record(reply); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + talloc_free(reply); + + tevent_req_done(req); +} + +static bool update_record_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("update_record", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client (%s), %s\n", + opts->socket, strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, + 0, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach DB %s\n", opts->dbname); + exit(1); + } + + req = update_record_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = update_record_recv(req, &ret); + if (! status) { + fprintf(stderr, "update record failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/update_record_persistent.c b/ctdb/tests/src/update_record_persistent.c new file mode 100644 index 0000000..2d6d21e --- /dev/null +++ b/ctdb/tests/src/update_record_persistent.c @@ -0,0 +1,218 @@ +/* + Update a record in persistent database + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_api.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct update_record_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *db; + int timelimit; + TDB_DATA key, data; +}; + +static void update_record_update_done(struct tevent_req *subreq); + +static struct tevent_req *update_record_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + const char *keystr, + const char *valuestr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct update_record_state *state; + struct ctdb_ltdb_header header; + struct ctdb_rec_buffer *recbuf; + struct ctdb_req_control request; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct update_record_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->db = db; + state->timelimit = timelimit; + state->key.dptr = (uint8_t *)discard_const(keystr); + state->key.dsize = strlen(keystr); + state->data.dptr = (uint8_t *)discard_const(valuestr); + state->data.dsize = strlen(valuestr); + + ret = ctdb_ltdb_fetch(state->db, state->key, &header, NULL, NULL); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + header.rsn += 1; + + recbuf = ctdb_rec_buffer_init(state, ctdb_db_id(state->db)); + if (tevent_req_nomem(recbuf, req)) { + return tevent_req_post(req, ev); + } + + ret = ctdb_rec_buffer_add(state, recbuf, 0, &header, + state->key, state->data); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + ctdb_req_control_update_record(&request, recbuf); + subreq = ctdb_client_control_send(state, state->ev, state->client, + CTDB_CURRENT_NODE, + tevent_timeval_current_ofs( + state->timelimit, 0), + &request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, update_record_update_done, req); + + talloc_free(recbuf); + return req; +} + +static void update_record_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct update_record_state *state = tevent_req_data( + req, struct update_record_state); + struct ctdb_reply_control *reply; + int ret; + bool status; + + status = ctdb_client_control_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_control_update_record(reply); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + talloc_free(reply); + + tevent_req_done(req); +} + +static bool update_record_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("update_record_persistene", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + if (opts->valuestr == NULL) { + fprintf(stderr, "Error: please specify key value (-v)\n"); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client (%s), %s\n", + opts->socket, strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, + CTDB_DB_FLAGS_PERSISTENT, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach DB %s\n", opts->dbname); + exit(1); + } + + req = update_record_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->valuestr, + opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = update_record_recv(req, &ret); + if (! status) { + fprintf(stderr, "update record failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/test_check_tcp_ports.sh b/ctdb/tests/test_check_tcp_ports.sh new file mode 100755 index 0000000..1272d88 --- /dev/null +++ b/ctdb/tests/test_check_tcp_ports.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +DIRNAME=$(dirname $0) + +CTDB_BASE="${DIRNAME}/../config" +. "${CTDB_BASE}/functions" + +SERVICE="test-service" + +PORTS="$@" + +if [ "x${PORTS}" = "x" ] ; then + PORTS=139 +fi + +ctdb_check_tcp_ports ${SERVICE} ${PORTS} + +echo "Test for service '${SERVICE}' on tcp ports ${PORTS} succeeded!" -- cgit v1.2.3