Note that exim contains tracker-specific configuration
[mirror/dsa-puppet.git] / modules / ganeti2 / files / ganeti-reboot-cluster
1 #!/bin/bash
2
3 # reboot a ganeti cluster, making sure instances are moved around before and after
4
5 # Copyright 2018, 2019 Peter Palfrader
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be
16 # included in all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
26
27 set -e
28 set -o pipefail
29 set -u
30
31 usage() {
32   echo "Usage: $0 [-n <node-list>] [-f] [ -M <newmaster> ] [up|down]"
33   echo " -M is for internal use only (used in n>2 clusters if we want to reboot the master first)"
34 }
35 error_usage() {
36   usage >&2
37   exit 1
38 }
39 do_cleanup() {
40     local cnt
41     cnt=$((${#cleanup[*]}-1))
42     for i in $(seq ${cnt} -1 0); do
43         ${cleanup[$i]} || true
44     done
45 }
46 declare -a cleanup
47 cleanup+=(":")
48 trap do_cleanup EXIT
49
50
51 nodelist="node-list"
52 newmaster=""
53 force="0"
54
55 while getopts "fhn:M:" OPTION; do
56   case "$OPTION" in
57     f)
58       force="1"
59       ;;
60     h)
61       usage
62       exit 0
63       ;;
64     M)
65       newmaster="$OPTARG"
66       ;;
67     n)
68       nodelist="$OPTARG"
69       if ! [  -e "$nodelist" ]; then
70         echo >&2 "nodelist $nodelist not found."
71         exit 1
72       fi
73       ;;
74     *)
75       error_usage
76   esac
77 done
78 shift $(($OPTIND - 1))
79
80 direction="${1:-up}"
81 [ "$#" -ge 1 ] && shift
82 case "$direction" in
83   up)   print_list=tac;;
84   down) print_list=cat;;
85   *) error_usage;;
86 esac
87
88 [ "$#" -gt 0 ] && error_usage
89
90 count_instances() {
91   gnt-instance list --no-headers -o status --filter '(pnode == "'"$1"'")' | grep -c -v ADMIN_down
92 }
93 has_instances() {
94   if [ "$(count_instances "$1")" != 0 ]; then
95     return 0
96   else
97     return 1
98   fi
99 }
100
101 reboot_host() {
102   local tgt
103   local max_wait
104   local wait_until
105   local sleep_time
106
107   tgt="$1"
108
109   if has_instances "$tgt"; then
110     echo >&2 "$tgt not empty."
111     exit 1
112   fi
113
114   ssh -n -l root "$tgt" shutdown -r 1 "'reboot requested by $0 on $(hostname -f)'"
115
116   # wait for target to go down:
117   max_wait='300 seconds'
118   wait_until=$(date -d "now +$max_wait" +%s)
119   while ping -c 5 -q "$tgt" > /dev/null; do
120     echo "[$(date)] $tgt is still up (will wait until $(date -d "@$wait_until")."
121     sleep 10
122     if [ "$(date +%s)" -gt "$wait_until" ]; then
123       echo >&2 "Giving up on waiting for $tgt to go down."
124       exit 1
125     fi
126   done
127
128   sleep_time=30
129   echo "[$(date)] $tgt is down.  Pausing for $sleep_time seconds"
130   sleep "$sleep_time"
131
132   max_wait='15 minutes'
133   wait_until=$(date -d "now +$max_wait" +%s)
134   while ! ping -c 5 -q "$tgt" > /dev/null; do
135     echo "[$(date)] $tgt is still down (will wait until $(date -d "@$wait_until")."
136     if [ "$(date +%s)" -gt "$wait_until" ]; then
137       echo >&2 "Giving up on waiting for $tgt to come back."
138       exit 1
139     fi
140     sleep 10
141   done
142
143   sleep_time=30
144   echo "[$(date)] $tgt is up.  Pausing for $sleep_time seconds"
145   sleep "$sleep_time"
146
147   max_wait='180 minutes'
148   wait_until=$(date -d "now +$max_wait" +%s)
149   while ! ssh -n -l root "$tgt" systemctl is-system-running; do
150     echo "[$(date)] $tgt is still booting up (will wait until $(date -d "@$wait_until")."
151     if [ "$(date +%s)" -gt "$wait_until" ]; then
152       echo >&2 "Giving up on waiting for $tgt to come back."
153       exit 1
154     fi
155     sleep 10
156   done
157
158   sleep_time=30
159   echo "[$(date)] $tgt has finished booting.  Pausing for $sleep_time seconds"
160   sleep "$sleep_time"
161 }
162
163 # move down, i.e. from 2 to 1, ..., 14 to 13.
164 moveupdown() {
165   first_tgt="$(${print_list} "$nodelist" | head -n1 | awk '{print $1}')"
166   last_node="$(${print_list} "$nodelist" | tail -n1 | awk '{print $1}')"
167   me=$(hostname -f)
168
169   if has_instances "$first_tgt"; then
170     echo "$first_tgt not empty."
171     exit 1
172   fi
173
174   if [ "$me" != "$last_node" ]; then
175     echo "Making $last_node the new master"
176     ssh -n -l root "$last_node" gnt-cluster master-failover
177     echo "relaunching reboot-cluster on $last_node"
178     tmp="$(ssh -n -l root -t "$last_node" tempfile)"
179     scp "$nodelist" "$last_node:$tmp"
180     ssh -l root -t "$last_node" screen -S reboot-cluster -m sh -c "\"echo Relaunched on $last_node; ganeti-reboot-cluster -f -n '$tmp' -M '$me' '$direction'; echo ganeti-reboot-cluster exited with \$?.; sleep 12h\""
181     echo >&1 "fell through!"
182     exit 1
183   fi
184
185   ${print_list} "$nodelist" | (
186     read tgt dummy
187     while read src dummy; do
188       reboot_host "$tgt"
189
190       if has_instances "$src"; then
191         echo "Migrating from $src to $tgt."
192         if ! gnt-node migrate -f -n "$tgt" "$src"; then
193           echo >&2 "gnt-node migrate exited with an error.  Bailing out."
194           exit 1
195         fi
196       else
197         echo "nothing to migrate from $src to $tgt"
198       fi
199       tgt="$src"
200     done
201
202     if has_instances "$tgt"; then
203       echo "$tgt not empty."
204       exit 1
205     fi
206
207     if ! [ "$tgt" = "$me" ]; then
208       echo >&2 "I was expecting $tgt to be me ($me) here."
209       exit 1
210     fi
211
212     if [ "$newmaster" != "" ]; then
213       echo "Making $newmaster the new master"
214       ssh -n -l root "$newmaster" gnt-cluster master-failover
215     fi
216     shutdown -r 1 "reboot requested by $0"
217     exit
218   )
219 }
220
221 crossmigratemany() {
222   me=$(hostname -f)
223   if ! grep -q --line-regexp --fixed-strings  "$me" "$nodelist"; then
224     echo >&2 "my hostname ($me) not found in nodelist"
225     exit 1
226   fi
227
228   # move ourselves last
229   newlist="$(tempfile)"
230   cleanup+=("rm -f '$newlist'")
231   grep -v --line-regexp --fixed-strings  "$me" "$nodelist" > "$newlist"
232   echo "$me" >> "$newlist"
233
234   while read node ; do
235     if ! hbal -L -C -v -v --no-disk-moves --offline="$node" -X; then
236       echo >&2 "hbal failed at node $node.  Bailing out."
237       exit 1
238     fi
239     if ! gnt-node migrate -f "$node"; then
240       echo >&2 "gnt-node migrate failed for node $node.  Bailing out."
241       exit 1
242     fi
243     if [ "$node" = "$me" ] ; then
244       break
245     fi
246     reboot_host "$node"
247     # bring back disks
248     echo "Bringing back disks using the watcher"
249     ganeti-watcher
250     # wait for a cron-launched ganeti-watcher to finish
251     while pgrep ganeti-watcher > /dev/null ; do
252       echo -n "."
253       sleep 5
254     done
255     echo
256   done < "$newlist"
257
258   at 'now + 5 min' << 'EOF'
259 screen -S hbal -d -m sh -c '
260   echo "Activating disks using the watcher.."
261   ganeti-watcher
262   while pgrep ganeti-watcher > /dev/null ; do
263     sleep 5
264   done
265   hbal -L -C -v -v --no-disk-moves -X
266   echo "done."
267   sleep 1h
268 '
269 EOF
270   reboot_host "$me"
271 }
272
273 crossmigrate() {
274   me=$(hostname -f)
275   if ! grep -q --line-regexp --fixed-strings  "$me" "$nodelist"; then
276     echo >&2 "my hostname ($me) not found in nodelist"
277     exit 1
278   fi
279   them="$(grep -v --line-regexp --fixed-strings  "$me" "$nodelist")"
280
281   echo "Migrating from $them to $me."
282   if ! gnt-node migrate -f -n "$me" "$them"; then
283     echo >&2 "gnt-node migrate exited with an error.  Bailing out."
284     exit 1
285   fi
286   reboot_host "$them"
287
288   echo "Activating disks.."
289   for instance in $( gnt-instance list -o name --no-headers --filter 'status == "running"' ); do
290     echo " - $instance ..."
291     if ! gnt-instance activate-disks "$instance"; then
292       echo >&2 "gnt-instance activate-disks $instance failed.  Bailing out."
293       exit 1
294     fi
295   done
296
297   if [ -e /proc/drbd ]; then
298     echo "Waiting for drbd to be consistent."
299     sleep 5
300     while egrep -C2 --color -i 'iconsistent|finish' /proc/drbd || ! /usr/lib/nagios/plugins/dsa-check-drbd -d All ; do
301       echo "Still waiting.."
302       sleep 5
303     done
304   fi
305
306   echo "Migrating from $me to $them."
307   if ! gnt-node migrate -f -n "$them" "$me"; then
308     echo >&2 "gnt-node migrate exited with an error.  Bailing out."
309     exit 1
310   fi
311
312   at 'now + 30 min' << 'EOF'
313 screen -S hbal -d -m sh -c '
314   echo "Activating disks.."
315   for instance in $( gnt-instance list -o name --no-headers --filter "status == \"running\"" ); do
316     echo " - $instance ..."
317     if ! gnt-instance activate-disks "$instance"; then
318       echo >&2 "Warning: gnt-instance activate-disks $instance failed."
319     fi
320   done
321
322   hbal -L -C -v -X
323   echo "done."
324   sleep 1h
325 '
326 EOF
327   reboot_host "$me"
328 }
329
330 reboot_byrd() {
331   /sbin/shutdown -k 30 < /dev/null
332   sleep 15m
333   gnt-cluster watcher pause 30m
334
335   for i in $(gnt-instance list --no-headers -o name); do
336     gnt-instance shutdown --no-remember --submit $i
337   done
338
339   while pgrep -c '^qemu-|^kvm$' -u root ; do
340     sleep 15;
341     gnt-cluster watcher pause 30m
342   done
343
344   at 'now + 5 min' << EOF
345 sleep 4m;
346 gnt-cluster watcher continue
347 EOF
348
349   /sbin/shutdown -c
350   sleep 5
351   /sbin/shutdown -r 1 </dev/null
352 }
353
354 if [ "${TMUX:-}" = "" ] && [ "${STY:-}" = "" ] ; then
355   echo >&2 "Might want to launch me in a screen or tmux."
356   exit 1
357 fi
358
359 if ! [ "$force" = 1 ]; then
360   echo -n 'really? '
361   read really
362   [ "$really" = "y" ]
363 fi
364
365 ### ensure_nodelist
366 ###################
367 if ! [ -e "$nodelist" ]; then
368   tmp="$(tempfile)"
369   cleanup+=("rm -f '$tmp'")
370   gnt-node list --no-headers -o name > "$tmp"
371   nodelist="$tmp"
372 fi
373
374 lines=$(wc -l < "$nodelist")
375 case "$lines" in
376   0) 
377     echo >&2 "nodelist $nodelist empty."
378     exit 1
379     ;;
380   1)
381     case "$(hostname -f)" in
382       byrd.debian.org)
383         reboot_byrd
384         ;;
385       *)
386         echo >&2 "Only one node."
387         exit 1
388     esac
389     ;;
390   2)
391     crossmigrate
392     ;;
393   3)
394     echo "WARNING: this is untested.  ^C now if you want to stop"
395     read dummy
396     crossmigratemany
397     ;;
398   *)
399     moveupdown
400     ;;
401 esac
402