ctdb-scripts: Split CTDB configuration loading
[samba.git] / ctdb / config / statd-callout
1 #!/bin/sh
2
3 # This must run as root as CTDB tool commands need to access CTDB socket
4 [ "$(id -u)" -eq 0 ] || exec sudo "$0" "$@"
5
6 # this script needs to be installed so that statd points to it with the -H 
7 # command line argument. The easiest way to do that is to put something like this in 
8 # /etc/sysconfig/nfs:
9 #   STATD_HOSTNAME="myhostname -H /etc/ctdb/statd-callout"
10
11 [ -n "$CTDB_BASE" ] || \
12     CTDB_BASE=$(d=$(dirname "$0") ; cd -P "$d" ; echo "$PWD")
13
14 . "${CTDB_BASE}/functions"
15
16 # Overwrite this so we get some logging
17 die ()
18 {
19     script_log "statd-callout" "$@"
20     exit 1
21 }
22
23 load_system_config "nfs"
24
25 loadconfig
26
27 [ -n "$NFS_HOSTNAME" ] || \
28     die "NFS_HOSTNAME is not configured. statd-callout failed"
29
30 ############################################################
31
32 ctdb_setup_state_dir "service" "nfs"
33
34 # script_state_dir set by ctdb_setup_state_dir()
35 # shellcheck disable=SC2154
36 d="${script_state_dir}/statd-callout"
37
38 mkdir -p "$d" || die "Failed to create directory \"${d}\""
39 cd "$d" || die "Failed to change directory to \"${d}\""
40
41 pnn=$(ctdb_get_pnn)
42
43 ############################################################
44
45 send_notifies ()
46 {
47         _smnotify="${CTDB_HELPER_BINDIR}/smnotify"
48
49         # State must monotonically increase, across the entire
50         # cluster.  Use seconds since epoch and hope the time is in
51         # sync across nodes.  Even numbers mean service is shut down,
52         # odd numbers mean service is started.
53
54         # Intentionally round to an even number
55         # shellcheck disable=SC2017
56         _state_even=$(( $(date '+%s') / 2 * 2))
57
58         _prev=""
59         while read _sip _cip ; do
60                 # NOTE: Consider optimising smnotify to read all the
61                 # data from stdin and then run it in the background.
62
63                 # Reset stateval for each serverip
64                 if [ "$_sip" != "$_prev" ] ; then
65                         _stateval="$_state_even"
66                 fi
67
68                 # Send notifies for server shutdown
69                 "$_smnotify" --client="$_cip" --ip="$_sip" \
70                              --server="$_sip" --stateval="$_stateval"
71                 "$_smnotify" --client="$_cip" --ip="$_sip" \
72                              --server="$NFS_HOSTNAME" --stateval="$_stateval"
73
74                 # Send notifies for server startup
75                 _stateval=$((_stateval + 1))
76                 "$_smnotify" --client="$_cip" --ip="$_sip" \
77                              --server="$_sip" --stateval="$_stateval"
78                 "$_smnotify" --client="$_cip" --ip="$_sip" \
79                              --server="$NFS_HOSTNAME" --stateval="$_stateval"
80         done
81 }
82
83 delete_records ()
84 {
85         while read _sip _cip ; do
86                 _key="statd-state@${_sip}@${_cip}"
87                 echo "\"${_key}\" \"\""
88         done | $CTDB ptrans "ctdb.tdb"
89 }
90
91 ############################################################
92
93 case "$1" in
94     # Keep a single file to keep track of the last "add-client" or
95     # "del-client'.  These get pushed to ctdb.tdb during "update",
96     # which will generally be run once each "monitor" cycle.  In this
97     # way we avoid scalability problems with flood of persistent
98     # transactions after a "notify" when all the clients re-take their
99     # locks.
100
101     add-client)
102         # statd does not tell us to which IP the client connected so
103         # we must add it to all the IPs that we serve
104         cip="$2"
105         date=$(date '+%s')
106         # x is intentionally ignored
107         # shellcheck disable=SC2034
108         $CTDB ip -X |
109         tail -n +2 |
110         while IFS="|" read x sip node x ; do
111             [ "$node" = "$pnn" ] || continue # not us
112             key="statd-state@${sip}@${cip}"
113             echo "\"${key}\" \"${date}\"" >"$key"
114         done
115         ;;
116
117     del-client)
118         # statd does not tell us from which IP the client disconnected
119         # so we must add it to all the IPs that we serve
120         cip="$2"
121         # x is intentionally ignored
122         # shellcheck disable=SC2034
123         $CTDB ip -X |
124         tail -n +2 |
125         while IFS="|" read x sip node x ; do
126             [ "$node" = "$pnn" ] || continue # not us
127             key="statd-state@${sip}@${cip}"
128             echo "\"${key}\" \"\"" >"$key"
129         done
130         ;;
131
132     update)
133         files=$(echo statd-state@*)
134         if [ "$files" = "statd-state@*" ] ; then
135             # No files!
136             exit 0
137         fi
138         # Filter out lines for any IP addresses that are not currently
139         # hosted public IP addresses.
140         ctdb_ips=$($CTDB ip | tail -n +2)
141         sed_expr=$(echo "$ctdb_ips" |
142             awk -v pnn="$pnn" 'pnn == $2 {
143                 ip = $1; gsub(/\./, "\\.", ip);
144                 printf "/statd-state@%s@/p\n", ip }')
145         # Intentional multi-word expansion for multiple files
146         # shellcheck disable=SC2086
147         items=$(sed -n "$sed_expr" $files)
148         if [ -n "$items" ] ; then
149                 if echo "$items"  | $CTDB ptrans "ctdb.tdb" ; then
150                         # shellcheck disable=SC2086
151                         rm $files
152                 fi
153         fi
154         ;;
155
156     notify)
157         # we must restart the lockmanager (on all nodes) so that we get
158         # a clusterwide grace period (so other clients don't take out
159         # conflicting locks through other nodes before all locks have been
160         # reclaimed)
161
162         # we need these settings to make sure that no tcp connections survive
163         # across a very fast failover/failback
164         #echo 10 > /proc/sys/net/ipv4/tcp_fin_timeout
165         #echo 0 > /proc/sys/net/ipv4/tcp_max_tw_buckets
166         #echo 0 > /proc/sys/net/ipv4/tcp_max_orphans
167
168         # Delete the notification list for statd, we don't want it to 
169         # ping any clients
170         rm -f /var/lib/nfs/statd/sm/*
171         rm -f /var/lib/nfs/statd/sm.bak/*
172
173         # We must also let some time pass between stopping and
174         # restarting the lock manager.  Otherwise there is a window
175         # where the lock manager will respond "strangely" immediately
176         # after restarting it, which causes clients to fail to reclaim
177         # their locks.
178         nfs_callout_init
179         "$CTDB_NFS_CALLOUT" "stop" "nlockmgr" >/dev/null 2>&1
180         sleep 2
181         "$CTDB_NFS_CALLOUT" "start" "nlockmgr" >/dev/null 2>&1
182
183         # we now need to send out additional statd notifications to ensure
184         # that clients understand that the lockmanager has restarted.
185         # we have three cases:
186         # 1, clients that ignore the ip address the stat notification came from
187         #    and ONLY care about the 'name' in the notify packet.
188         #    these clients ONLY work with lock failover IFF that name
189         #    can be resolved into an ipaddress that matches the one used
190         #    to mount the share.  (==linux clients)
191         #    This is handled when starting lockmanager above,  but those
192         #    packets are sent from the "wrong" ip address, something linux
193         #    clients are ok with, buth other clients will barf at.
194         # 2, Some clients only accept statd packets IFF they come from the
195         #    'correct' ip address.
196         # 2a,Send out the notification using the 'correct' ip address and also
197         #    specify the 'correct' hostname in the statd packet.
198         #    Some clients require both the correct source address and also the
199         #    correct name. (these clients also ONLY work if the ip addresses
200         #    used to map the share can be resolved into the name returned in
201         #    the notify packet.)
202         # 2b,Other clients require that the source ip address of the notify
203         #    packet matches the ip address used to take out the lock.
204         #    I.e. that the correct source address is used.
205         #    These clients also require that the statd notify packet contains
206         #    the name as the ip address used when the lock was taken out.
207         #
208         # Both 2a and 2b are commonly used in lockmanagers since they maximize
209         # probability that the client will accept the statd notify packet and
210         # not just ignore it.
211         # For all IPs we serve, collect info and push to the config database
212
213         # Construct a sed expression to take catdb output and produce pairs of:
214         #   server-IP client-IP
215         # but only for the server-IPs that are hosted on this node.
216         ctdb_all_ips=$($CTDB ip all | tail -n +2)
217         sed_expr=$(echo "$ctdb_all_ips" |
218             awk -v pnn="$pnn" 'pnn == $2 {
219                 ip = $1; gsub(/\./, "\\.", ip);
220                 printf "s/^key.*=.*statd-state@\\(%s\\)@\\([^\"]*\\).*/\\1 \\2/p\n", ip }')
221
222         statd_state=$($CTDB catdb ctdb.tdb | sed -n "$sed_expr" | sort)
223         [ -n "$statd_state" ] || exit 0
224
225         echo "$statd_state" | send_notifies
226         echo "$statd_state" | delete_records
227
228         # Remove any stale touch files (i.e. for IPs not currently
229         # hosted on this node and created since the last "update").
230         # There's nothing else we can do with them at this stage.
231         echo "$ctdb_all_ips" |
232             awk -v pnn="$pnn" 'pnn != $2 { print $1 }' |
233             while read sip ; do
234                 rm -f "statd-state@${sip}@"*
235             done
236         ;;
237 esac