this stuff is just so fragile that it will enter infinite recovery and fail loops
[metze/ctdb/wip.git] / config / events.d / 10.interface
1 #!/bin/sh
2
3 #################################
4 # interface event script for ctdb
5 # this adds/removes IPs from your 
6 # public interface
7
8 . $CTDB_BASE/functions
9 loadconfig
10
11 [ -z "$CTDB_PUBLIC_ADDRESSES" ] && {
12         CTDB_PUBLIC_ADDRESSES=$CTDB_BASE/public_addresses
13 }
14
15 [ ! -f "$CTDB_PUBLIC_ADDRESSES" ] && {
16         echo "No public addresses file found. Nothing to do for 10.interfaces"
17         exit 0
18 }
19
20 monitor_interfaces()
21 {
22         local INTERFACES=`cat $CTDB_PUBLIC_ADDRESSES |
23                 sed -e "s/^[^\t ]*[\t ]*//" -e "s/,/ /g" -e "s/[\t ]*$//"`
24
25         [ "$CTDB_PUBLIC_INTERFACE" ] && INTERFACES="$CTDB_PUBLIC_INTERFACE $INTERFACES"
26         [ "$CTDB_NATGW_PUBLIC_IFACE" ] && INTERFACES="$CTDB_NATGW_PUBLIC_IFACE $INTERFACES"
27
28         local IFACES=`ctdb ifaces -Y | grep -v '^:Name:LinkStatus:References:'`
29
30         local I
31         local IFACE
32
33         for I in $IFACES; do
34                 IFACE=`echo -n "$I" | cut -d ':' -f2`
35                 INTERFACES="$IFACE $INTERFACES"
36         done
37
38         INTERFACES=`for IFACE in $INTERFACES ; do echo $IFACE ; done | sort | uniq`
39
40         local fail=0
41         local force_fail=0
42         local ok=0
43         for IFACE in $INTERFACES ; do
44
45             local OLDLINK=`echo -n "$IFACES" | grep "^:$IFACE:" | cut -d ':' -f3 | xargs`
46             test -z "$OLDLINK" && {
47                 force_fail=1
48             }
49
50             # These interfaces are sometimes bond devices
51             # When we use VLANs for bond interfaces, there will only
52             # be an entry in /proc for the underlying real interface
53             local REALIFACE=`echo $IFACE |sed -e 's/\..*$//'`
54             [ -f /proc/net/bonding/$REALIFACE ] && {
55                 grep -q 'Currently Active Slave: None' /proc/net/bonding/$REALIFACE && {
56                         echo "ERROR: No active slaves for bond device $REALIFACE"
57                         fail=1
58                         test -n "$OLDLINK" && {
59                                 ctdb setifacelink $IFACE down
60                         }
61                         continue;
62                 }
63                 grep -q '^MII Status: up' /proc/net/bonding/$REALIFACE || {
64                         echo "ERROR: public network interface $REALIFACE is down"
65                         fail=1
66                         test -n "$OLDLINK" && {
67                                 ctdb setifacelink $IFACE down
68                         }
69                         continue;
70                 }
71                 test -n "$OLDLINK" && {
72                         ok=1 # we only set ok for interfaces known to ctdbd
73                         ctdb setifacelink $IFACE up
74                 }
75                 continue;
76             }
77
78             case $IFACE in
79             lo*)
80                 # loopback is always working
81                 test -n "$OLDLINK" && {
82                     ok=1 # we only set ok for interfaces known to ctdbd
83                     ctdb setifacelink $IFACE up
84                 }
85                 ;;
86             ib*)
87                 # we dont know how to test ib links
88                 test -n "$OLDLINK" && {
89                     ok=1 # we only set ok for interfaces known to ctdbd
90                     ctdb setifacelink $IFACE up
91                 }
92                 ;;
93             *)
94                 [ -z "$IFACE" ] || {
95                     [ "$(basename $(readlink /sys/class/net/$IFACE/device/driver))" = virtio_net ] ||
96                     ethtool $IFACE | grep -q 'Link detected: yes' || {
97                         # On some systems, this is not successful when a
98                         # cable is plugged but the interface has not been
99                         # brought up previously. Bring the interface up and
100                         # try again...
101                         /sbin/ip link set $IFACE up
102                         ethtool $IFACE | grep -q 'Link detected: yes' || {
103                             echo "ERROR: No link on the public network interface $IFACE"
104                             fail=1
105                             test -n "$OLDLINK" && {
106                                 ctdb setifacelink $IFACE down
107                             }
108                             continue
109                         }
110                     }
111                     test -n "$OLDLINK" && {
112                         ok=1 # we only set ok for interfaces known to ctdbd
113                         ctdb setifacelink $IFACE up
114                     }
115                 }
116                 ;;
117             esac
118
119         done
120
121         test x"$fail" = x"0" && {
122                 return 0;
123         }
124
125         test x"$force_fail" != x"0" && {
126                 return 1;
127         }
128
129         test x"$ok" = x"1" && {
130                 return 2;
131         }
132
133         return 1;
134 }
135
136 case "$1" in 
137      #############################
138      # called when ctdbd starts up
139      init)
140         # make sure that we only respond to ARP messages from the NIC where
141         # a particular ip address is associated.
142         [ -f /proc/sys/net/ipv4/conf/all/arp_filter ] && {
143             echo 1 > /proc/sys/net/ipv4/conf/all/arp_filter
144         }
145         ;;
146
147      #############################
148      # called after ctdbd has done its initial recovery
149      # and we start the services to become healthy
150      startup)
151         monitor_interfaces
152
153         ;;
154
155
156      ################################################
157      # called when ctdbd wants to claim an IP address
158      takeip)
159         if [ $# != 4 ]; then
160            echo "must supply interface, IP and maskbits"
161            exit 1
162         fi
163         iface=$2
164         ip=$3
165         maskbits=$4
166
167         add_ip_to_iface $iface $ip $maskbits || {
168                 exit 1;
169         }
170
171         # cope with the script being killed while we have the interface blocked
172         iptables -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
173
174         # flush our route cache
175         echo 1 > /proc/sys/net/ipv4/route/flush
176         ;;
177
178
179      ##################################################
180      # called when ctdbd wants to release an IP address
181      releaseip)
182         if [ $# != 4 ]; then
183            echo "must supply interface, IP and maskbits"
184            exit 1
185         fi
186
187         # releasing an IP is a bit more complex than it seems. Once the IP
188         # is released, any open tcp connections to that IP on this host will end
189         # up being stuck. Some of them (such as NFS connections) will be unkillable
190         # so we need to use the killtcp ctdb function to kill them off. We also
191         # need to make sure that no new connections get established while we are 
192         # doing this! So what we do is this:
193         # 1) firewall this IP, so no new external packets arrive for it
194         # 2) use netstat -tn to find existing connections, and kill them 
195         # 3) remove the IP from the interface
196         # 4) remove the firewall rule
197         iface=$2
198         ip=$3
199         maskbits=$4
200
201         failed=0
202         # we do an extra delete to cope with the script being killed
203         iptables -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
204         iptables -I INPUT -i $iface -d $ip -j DROP
205         kill_tcp_connections $ip
206
207         delete_ip_from_iface $iface $ip $maskbits || {
208                 iptables -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
209                 exit 1;
210         }
211
212         iptables -D INPUT -i $iface -d $ip -j DROP 2> /dev/null
213
214         # flush our route cache
215         echo 1 > /proc/sys/net/ipv4/route/flush
216         ;;
217
218      ##################################################
219      # called when ctdbd wants to update an IP address
220      updateip)
221         if [ $# != 5 ]; then
222            echo "must supply old interface, new interface, IP and maskbits"
223            exit 1
224         fi
225
226         # moving an IP is a bit more complex than it seems.
227         # First we drop all traffic on the old interface.
228         # Then we try to add the ip to the new interface and before
229         # we finally remove it from the old interface.
230         #
231         # 1) firewall this IP, so no new external packets arrive for it
232         # 2) add the IP to the new interface
233         # 3) remove the IP from the old interface
234         # 4) remove the firewall rule
235         # 5) use ctdb gratiousarp to propagate the new mac address
236         # 6) use netstat -tn to find existing connections, and tickle them
237         oiface=$2
238         niface=$3
239         ip=$4
240         maskbits=$5
241
242         failed=0
243         # we do an extra delete to cope with the script being killed
244         iptables -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
245         iptables -I INPUT -i $oiface -d $ip -j DROP
246
247         delete_ip_from_iface $oiface $ip $maskbits 2>/dev/null
248         delete_ip_from_iface $niface $ip $maskbits 2>/dev/null
249
250         add_ip_to_iface $niface $ip $maskbits || {
251                 iptables -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
252                 exit 1;
253         }
254
255         # cope with the script being killed while we have the interface blocked
256         iptables -D INPUT -i $oiface -d $ip -j DROP 2> /dev/null
257
258         # flush our route cache
259         echo 1 > /proc/sys/net/ipv4/route/flush
260
261         # propagate the new mac address
262         ctdb gratiousarp $ip $niface
263
264         # tickle all existing connections, so that dropped packets
265         # are retransmited and the tcp streams work
266
267         tickle_tcp_connections $ip
268
269         ;;
270
271
272      ###########################################
273      # called when ctdbd has finished a recovery
274      recovered)
275         ;;
276
277      ####################################
278      # called when ctdbd is shutting down
279      shutdown)
280         ;;
281
282      monitor)
283         monitor_interfaces
284         ret=$?
285
286         test x"$ret" = x"2" && {
287                 test x"$CTDB_PARTIALLY_ONLINE_INTERFACES" != x"yes" && {
288                         exit 1;
289                 }
290                 # as long as we have one interface available don't become
291                 # unhealthy
292                 ret=0
293         }
294
295         test x"$ret" != x"0" && {
296                 exit 1;
297         }
298         ;;
299     *)
300         ctdb_standard_event_handler "$@"
301         ;;
302 esac
303
304 exit 0
305