6b51b05662b14b39bd842e3f6d397ebf4107618c
[metze/ctdb/wip.git] / config / events.d / 13.per_ip_routing
1 #!/bin/sh
2
3 . $CTDB_BASE/functions
4 loadconfig
5
6 [ -z "$CTDB_PER_IP_ROUTING_STATE" ] && {
7         CTDB_PER_IP_ROUTING_STATE="$CTDB_BASE/state/per_ip_routing"
8 }
9
10 AUTO_LINK_LOCAL="no"
11
12 case "$CTDB_PER_IP_ROUTING_CONF" in
13         __auto_link_local__)
14                 AUTO_LINK_LOCAL="yes"
15                 CTDB_PER_IP_ROUTING_CONF="$CTDB_PER_IP_ROUTING_STATE/auto_link_local.conf"
16                 ;;
17         *)
18                 [ -z "$CTDB_PER_IP_ROUTING_CONF" ] && {
19                         #echo "No config file found. Nothing to do for 13.per_ip_routing"
20                         exit 0;
21                 }
22                 ;;
23 esac
24
25 _low=$CTDB_PER_IP_ROUTING_TABLE_ID_LOW
26 _high=$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH
27
28 test -z "$_low" && {
29         echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_LOW not configured";
30         exit 1;
31 }
32 test -z "$_high" && {
33         echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_HIGH not configured";
34         exit 1;
35 }
36 test "$_low" -ge "$_high" && {
37         echo "$0: CTDB_PER_IP_ROUTING_TABLE_ID_LOW[$_low] needs to be below CTDB_PER_IP_ROUTING_TABLE_ID_HIGH[$_high]";
38         exit 1;
39 }
40
41 test -z "$CTDB_PER_IP_ROUTING_RULE_PREF" && {
42         echo "$0: CTDB_PER_IP_ROUTING_RULE_PREF not configured";
43         exit 1;
44 }
45
46 locknesting=0
47 lock_root="$CTDB_PER_IP_ROUTING_STATE"
48 host=`hostname`
49
50 lock_debug()
51 {
52         echo -n ""
53 }
54
55 ############################
56 # grab a lock file. Not atomic, but close :)
57 # tries to cope with NFS
58 lock_file() {
59         if [ -z "$lock_root" ]; then
60                 lock_root=`pwd`;
61         fi
62         lckf="$lock_root/$1"
63         machine=`cat "$lckf" 2> /dev/null | cut -d: -f1`
64         pid=`cat "$lckf" 2> /dev/null | cut -d: -f2`
65
66         if [ "$pid" = "$$" ]; then
67                 locknesting=`expr $locknesting + 1`
68                 lock_debug "lock nesting now $locknesting"
69                 return 0
70         fi
71
72         if test -f "$lckf"; then
73                 test $machine = $host || {
74                         lock_debug "lock file $lckf is valid for other machine $machine"
75                         stat -c%y "$lckf"
76                         return 1
77                 }
78                 /bin/kill -0 $pid && {
79                         lock_debug "lock file $lckf is valid for process $pid"
80                         stat -c%y "$lckf"
81                         return 1
82                 }
83                 lock_debug "stale lock file $lckf for $machine:$pid"
84                 cat "$lckf"
85                 /bin/rm -f "$lckf"
86         fi
87         echo "$host:$$" > "$lckf"
88         return 0
89 }
90
91 ############################
92 # unlock a lock file
93 unlock_file() {
94         if [ -z "$lock_root" ]; then
95                 lock_root=`pwd`;
96         fi
97         if [ "$locknesting" != "0" ]; then
98                 locknesting=`expr $locknesting - 1`
99                 lock_debug "lock nesting now $locknesting"
100         else
101                 lckf="$lock_root/$1"
102                 /bin/rm -f "$lckf"
103         fi
104 }
105
106 generate_table_id () {
107         local _ip=$1
108         local _ipsdir="$CTDB_PER_IP_ROUTING_STATE/ips"
109         local _ipdir="$_ipsdir/$_ip"
110
111         mkdir -p $_ipdir
112
113         #echo "generate_table_id $_ip"
114
115         local _id=`cat $_ipdir/table_id 2>/dev/null| xargs`
116         test -n "$_id" && {
117                 #echo "IP: $_ip => OLD TABLE: $_id"
118                 table_id=$_id
119                 return 0;
120         }
121
122         local _low="$CTDB_PER_IP_ROUTING_TABLE_ID_LOW"
123         local _high="$CTDB_PER_IP_ROUTING_TABLE_ID_HIGH"
124
125         local _newid=""
126         for _id in `seq $_low $_high | xargs`; do
127                 local _table_lck="table_id_$_id.lock"
128                 lock_file $_table_lck 2>/dev/null || {
129                         continue;
130                 }
131                 local _taken=`grep "^$_id$" $_ipsdir/*/table_id 2>/dev/null| wc -l | xargs`
132                 test x"$_taken" != x"0" && {
133                         unlock_file $_table_lck
134                         #echo "tableid: $_id taken"
135                         continue
136                 }
137                 _newid=$_id;
138                 echo "$_newid" > $_ipdir/table_id
139                 unlock_file $_table_lck
140                 break;
141         done
142
143         test -z "$_newid" && {
144                 echo "generate_table_id: out of table ids: $_low - $_high"
145                 exit 1;
146         }
147
148         #echo "IP: $_ip => NEW TABLE: $_newid"
149         table_id=$_newid
150         return 0;
151 }
152
153 run_release_script_once()
154 {
155         local _script=$1
156
157         #echo "run_release_script_once[$_script]"
158
159         test -x "$_script" && {
160                 #echo "run it: start"
161                 $_script || {
162                         echo "release_script: $_script - failed $?"
163                         return $?;
164                 }
165                 #echo "run it: end"
166         }
167
168         echo '#!/bin/sh' > $_script
169         echo '#' >> $_script
170         echo >> $_script
171
172         chmod +x $_script
173
174         return 0;
175 }
176
177 generate_auto_link_local()
178 {
179         local _ip=$1
180         local _maskbits=$2
181
182         #echo "generate_auto_link_local $_ip $_maskbits"
183
184         local _netip=`ipv4_host_addr_to_net_addr $_ip $_maskbits`
185
186         local _line="$_ip $_netip/$_maskbits"
187
188         local _lockfile="$CTDB_PER_IP_ROUTING_CONF.lock"
189         local _script="$CTDB_PER_IP_ROUTING_CONF.$$.sh"
190
191         echo "#!/bin/sh" > $_script
192         echo "#" >> $_script
193         echo "" >> $_script
194         echo "_config=\`cat $CTDB_PER_IP_ROUTING_CONF 2>/dev/null\`" >> $_script
195         echo "_exact=\`echo -n \"\$_config\" | grep \"^$_line\$\" | wc -l | xargs\`" >> $_script
196         echo "" >> $_script
197
198         echo "test x\"\$_exact\" = x\"1\" && {" >> $_script
199         echo "    exit 0;" >> $_script
200         echo "}" >> $_script
201         echo "" >> $_script
202
203         echo "_tmp=\"$CTDB_PER_IP_ROUTING_CONF.$$.tmp\"" >> $_script
204         echo "echo -n \"\$_config\" | grep -v \"^$_ip \" | cat > \$_tmp || {" >> $_script
205         echo "    echo \"echo -n \\\"\$_config\\\" | grep -v \\\"^$_ip \\\" > \$_tmp - failed\"" >> $_script
206         echo "    exit 1;" >> $_script
207         echo "}" >> $_script
208         echo "echo \"$_line\" >> \$_tmp || {" >> $_script
209         echo "    echo \"echo \\\"$_line\\\" >> \$_tmp - failed\"" >> $_script
210         echo "    exit 1;" >> $_script
211         echo "}" >> $_script
212         echo "" >> $_script
213
214         echo "mv \$_tmp $CTDB_PER_IP_ROUTING_CONF || {" >> $_script
215         echo "    echo \"mv \$_tmp $CTDB_PER_IP_ROUTING_CONF - failed\"" >> $_script
216         echo "    exit 1;" >> $_script
217         echo "}" >> $_script
218         echo "" >> $_script
219
220         echo "echo \"Added '$_line' to $CTDB_PER_IP_ROUTING_CONF\"">> $_script
221         echo "exit 0" >> $_script
222
223         chmod +x $_script
224
225         test -f $_lockfile || {
226                 touch $_lockfile
227         }
228
229         flock --timeout 30 $_lockfile $_script
230         ret=$?
231         rm $_script
232         return $ret
233 }
234
235 generate_per_ip_routing()
236 {
237         local _ip=$1
238         local _maskbits=$2
239         local _iface=$3
240         local _readonly=$4
241         local _ipdir="$CTDB_PER_IP_ROUTING_STATE/ips/$_ip"
242
243         table_id=""
244         release_script="$_ipdir/per_ip_routing_release.sh"
245         setup_script="$_ipdir/per_ip_routing_setup.sh"
246
247         test x"$_readonly" = x"yes" && {
248                 test -d $_ipdir || {
249                         return 1;
250                 }
251                 return 0;
252         }
253
254         mkdir -p $_ipdir || {
255                 echo "mkdir -p $_ipdir failed"
256                 return 1;
257         }
258         echo "$_ip" > $_ipdir/ip
259
260         generate_table_id $_ip
261
262         test x"$AUTO_LINK_LOCAL" = x"yes" && {
263                 generate_auto_link_local $_ip $_maskbits
264         }
265
266         run_release_script_once $release_script
267
268         echo '#!/bin/sh' > $setup_script
269         echo '#' >> $setup_script
270         echo >> $setup_script
271         chmod +x $setup_script
272
273         return 0;
274 }
275
276 setup_per_ip_routing()
277 {
278         local _ip=$1
279         local _iface=$2
280         local _table_id=$3
281         local _release_script=$4
282         local _setup_script=$5
283
284         local _config=`cat $CTDB_PER_IP_ROUTING_CONF`
285         local _lines=`echo -n "$_config" | grep -n "^$_ip " | cut -d ':' -f1 | xargs`
286
287         local _pref="$CTDB_PER_IP_ROUTING_RULE_PREF"
288
289         test -n "$_lines" && {
290                 echo "ip rule del from $_ip pref $_pref table $_table_id" >> $_release_script
291                 echo "ip route flush table $_table_id 2>/dev/null" >> $_release_script
292
293                 cmd="ip rule del from $_ip pref $_pref 2>/dev/null"
294                 echo "$cmd" >> $_setup_script
295
296                 cmd="ip route flush table $_table_id 2>/dev/null"
297                 echo "$cmd" >> $_setup_script
298
299                 cmd="ip rule add from $_ip pref $_pref table $_table_id"
300                 echo "$cmd || {" >> $_setup_script
301                 echo "    echo \"$cmd - failed \$ret\"" >> $_setup_script
302                 echo "    exit \$ret" >> $_setup_script
303                 echo "}" >> $_setup_script
304         }
305         local _l
306         for _l in $_lines; do
307                 local _line=`echo -n "$_config" | head -n $_l | tail -n 1`
308                 local _dest=`echo -n "$_line" | cut -d ' ' -f 2`
309                 local _gw=`echo -n "$_line" | cut -d ' ' -f 3`
310
311                 local _via=""
312                 test -n "$_gw" && {
313                         _via="via $_gw"
314                 }
315
316                 cmd="ip route add $_dest $_via dev $_iface table $_table_id"
317                 echo "$cmd || {" >> $_setup_script
318                 echo "    echo \"$cmd - failed \$ret\"" >> $_setup_script
319                 echo "    exit \$ret" >> $_setup_script
320                 echo "}" >> $_setup_script
321         done
322
323         $_setup_script
324         return $?;
325 }
326
327 case "$1" in
328      #############################
329      # called when ctdbd starts up
330      startup)
331         # cleanup old rules
332         pref=$CTDB_PER_IP_ROUTING_RULE_PREF
333         rules=`ip rule show | grep "^$pref:" | sed -e 's/.*from \([^ ][^ ]*\) lookup \([^ ][^ ]*\)/\2;\1/' | xargs`
334         for r in $rules; do
335                 table_id=`echo -n "$r" | cut -d ';' -f1`
336                 ip=`echo -n "$r" | cut -d ';' -f2-`
337
338                 echo "Removing ip rule for public address $ip for routing table $table_id"
339                 cmd="ip rule del from $ip table $table_id pref $pref"
340                 #echo $cmd
341                 eval $cmd
342                 cmd="ip route flush table $table_id"
343                 #echo $cmd
344                 eval $cmd 2>/dev/null
345         done
346
347         # make sure that we only respond to ARP messages from the NIC where
348         # a particular ip address is associated.
349         [ -f /proc/sys/net/ipv4/conf/all/arp_filter ] && {
350             echo 1 > /proc/sys/net/ipv4/conf/all/arp_filter
351         }
352
353         mkdir -p $CTDB_PER_IP_ROUTING_STATE
354
355         ;;
356
357      shutdown)
358
359         for s in $CTDB_PER_IP_ROUTING_STATE/ips/*/per_ip_routing_release.sh; do
360                 run_release_script_once "$s"
361         done
362         rm -rf $CTDB_PER_IP_ROUTING_STATE
363
364         ;;
365
366      ################################################
367      # called when ctdbd wants to claim an IP address
368      takeip)
369         if [ $# != 4 ]; then
370            echo "must supply interface, IP and maskbits"
371            exit 1
372         fi
373         iface=$2
374         ip=$3
375         maskbits=$4
376
377         ipv4_is_valid_addr $ip || {
378                 echo "$0: $1 not an ipv4 address skipping IP:$ip"
379                 exit 0;
380         }
381
382         [ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
383                 echo "$0: $1 No state directory found, waiting for startup."
384                 exit 0;
385         }
386
387         generate_per_ip_routing $ip $maskbits $iface "no" || {
388                 echo "$0: $1: generate_per_ip_routing $ip $maskbits $iface no - failed"
389                 exit 1;
390         }
391
392         setup_per_ip_routing $ip $iface $table_id $release_script $setup_script || {
393                 echo "$0: $1: setup_per_ip_routing $ip $iface $table_id $release_script $setup_script - failed"
394                 exit 1;
395         }
396
397         setup_iface_ip_readd_script $iface $ip $maskbits $setup_script || {
398                 echo "$0: $1: setup_iface_ip_readd_script $iface $ip $maskbits $setup_script - failed"
399                 exit 1;
400         }
401
402         # flush our route cache
403         echo 1 > /proc/sys/net/ipv4/route/flush
404         ctdb gratiousarp $ip $iface
405
406         ;;
407
408      ################################################
409      # called when ctdbd wants to claim an IP address
410      updateip)
411         if [ $# != 5 ]; then
412            echo "must supply old interface, new interface, IP and maskbits"
413            exit 1
414         fi
415         oiface=$2
416         niface=$3
417         ip=$4
418         maskbits=$5
419
420         ipv4_is_valid_addr $ip || {
421                 echo "$0: $1 not an ipv4 address skipping IP:$ip"
422                 exit 0;
423         }
424
425         [ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
426                 echo "$0: $1 No state directory found, waiting for startup."
427                 exit 0;
428         }
429
430         generate_per_ip_routing $ip $maskbits $niface "no" || {
431                 echo "$0: $1: generate_per_ip_routing $ip $maskbits $niface no - failed"
432                 exit 1;
433         }
434
435         setup_per_ip_routing $ip $niface $table_id $release_script $setup_script || {
436                 echo "$0: $1: setup_per_ip_routing $ip $niface $table_id $release_script $setup_script - failed"
437                 exit 1;
438         }
439
440         setup_iface_ip_readd_script $niface $ip $maskbits $setup_script || {
441                 echo "$0: $1: setup_iface_ip_readd_script $niface $ip $maskbits $setup_script - failed"
442                 exit 1;
443         }
444
445         # flush our route cache
446         echo 1 > /proc/sys/net/ipv4/route/flush
447
448         ctdb gratiousarp $ip $niface
449         tickle_tcp_connections $ip
450
451         ;;
452
453      ##################################################
454      # called when ctdbd wants to release an IP address
455      releaseip)
456         if [ $# != 4 ]; then
457            echo "must supply interface, IP and maskbits"
458            exit 1
459         fi
460
461         iface=$2
462         ip=$3
463         maskbits=$4
464
465         ipv4_is_valid_addr $ip || {
466                 echo "$0: $1 not an ipv4 address skipping IP:$ip"
467                 exit 0;
468         }
469
470         [ ! -d "$CTDB_PER_IP_ROUTING_STATE" ] && {
471                 echo "$0: $1 No state directory found, waiting for startup."
472                 exit 0;
473         }
474
475         generate_per_ip_routing $ip $maskbits $iface "yes" || {
476                 echo "$0: $1: generate_per_ip_routing $ip $maskbits $iface yes - failed"
477                 exit 1;
478         }
479
480         run_release_script_once "$release_script"
481
482         ;;
483
484
485      ###########################################
486      # called when ctdbd has finished a recovery
487      recovered)
488         ;;
489
490      ####################################
491      # called when ctdbd is shutting down
492      shutdown)
493         ;;
494
495      monitor)
496         ;;
497     *)
498         ctdb_standard_event_handler "$@"
499         ;;
500 esac
501
502 exit 0
503