ganeti-reboot-cluster: describe what it does, and a license
[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
40 nodelist="node-list"
41 newmaster=""
42 force="0"
43
44 while getopts "fhn:M:" OPTION; do
45   case "$OPTION" in
46     f)
47       force="1"
48       ;;
49     h)
50       usage
51       exit 0
52       ;;
53     M)
54       newmaster="$OPTARG"
55       ;;
56     n)
57       nodelist="$OPTARG"
58       if ! [  -e "$nodelist" ]; then
59         echo >&2 "nodelist $nodelist not found."
60         exit 1
61       fi
62       ;;
63     *)
64       error_usage
65   esac
66 done
67 shift $(($OPTIND - 1))
68
69 direction="${1:-up}"
70 [ "$#" -ge 1 ] && shift
71 case "$direction" in
72   up)   print_list=tac;;
73   down) print_list=cat;;
74   *) error_usage;;
75 esac
76
77 [ "$#" -gt 0 ] && error_usage
78
79 count_instances() {
80   gnt-instance list --no-headers -o status --filter '(pnode == "'"$1"'")' | grep -c -v ADMIN_down
81 }
82 has_instances() {
83   if [ "$(count_instances "$1")" != 0 ]; then
84     return 0
85   else
86     return 1
87   fi
88 }
89
90 reboot_host() {
91   local tgt
92   local max_wait
93   local wait_until
94   local sleep_time
95
96   tgt="$1"
97
98   if has_instances "$tgt"; then
99     echo >&2 "$tgt not empty."
100     exit 1
101   fi
102
103   ssh -n -l root "$tgt" shutdown -r 1 "'reboot requested by $0 on $(hostname -f)'"
104
105   # wait for target to go down:
106   max_wait='300 seconds'
107   wait_until=$(date -d "now +$max_wait" +%s)
108   while ping -c 5 -q "$tgt" > /dev/null; do
109     echo "[$(date)] $tgt is still up (will wait until $(date -d "@$wait_until")."
110     sleep 10
111     if [ "$(date +%s)" -gt "$wait_until" ]; then
112       echo >&2 "Giving up on waiting for $tgt to go down."
113       exit 1
114     fi
115   done
116
117   sleep_time=30
118   echo "[$(date)] $tgt is down.  Pausing for $sleep_time seconds"
119   sleep "$sleep_time"
120
121   max_wait='15 minutes'
122   wait_until=$(date -d "now +$max_wait" +%s)
123   while ! ping -c 5 -q "$tgt" > /dev/null; do
124     echo "[$(date)] $tgt is still down (will wait until $(date -d "@$wait_until")."
125     if [ "$(date +%s)" -gt "$wait_until" ]; then
126       echo >&2 "Giving up on waiting for $tgt to come back."
127       exit 1
128     fi
129     sleep 10
130   done
131
132   sleep_time=30
133   echo "[$(date)] $tgt is up.  Pausing for $sleep_time seconds"
134   sleep "$sleep_time"
135
136   max_wait='180 minutes'
137   wait_until=$(date -d "now +$max_wait" +%s)
138   while ! ssh -n -l root "$tgt" systemctl is-system-running; do
139     echo "[$(date)] $tgt is still booting up (will wait until $(date -d "@$wait_until")."
140     if [ "$(date +%s)" -gt "$wait_until" ]; then
141       echo >&2 "Giving up on waiting for $tgt to come back."
142       exit 1
143     fi
144     sleep 10
145   done
146
147   sleep_time=30
148   echo "[$(date)] $tgt has finished booting.  Pausing for $sleep_time seconds"
149   sleep "$sleep_time"
150 }
151
152 # move down, i.e. from 2 to 1, ..., 14 to 13.
153 moveupdown() {
154   first_tgt="$(${print_list} "$nodelist" | head -n1 | awk '{print $1}')"
155   last_node="$(${print_list} "$nodelist" | tail -n1 | awk '{print $1}')"
156   me=$(hostname -f)
157
158   if has_instances "$first_tgt"; then
159     echo "$first_tgt not empty."
160     exit 1
161   fi
162
163   if [ "$me" != "$last_node" ]; then
164     echo "Making $last_node the new master"
165     ssh -n -l root "$last_node" gnt-cluster master-failover
166     echo "relaunching reboot-cluster on $last_node"
167     tmp="$(ssh -n -l root -t "$last_node" tempfile)"
168     scp "$nodelist" "$last_node:$tmp"
169     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\""
170     echo >&1 "fell through!"
171     exit 1
172   fi
173
174   ${print_list} "$nodelist" | (
175     read tgt dummy
176     while read src dummy; do
177       if has_instances "$tgt"; then
178         echo "$tgt not empty."
179         exit 1
180       fi
181       reboot_host "$tgt"
182
183       if has_instances "$src"; then
184         echo "Migrating from $src to $tgt."
185         if ! gnt-node migrate -f -n "$tgt" "$src"; then
186           echo >&2 "gnt-node migrate exited with an error.  Bailing out."
187           exit 1
188         fi
189       else
190         echo "nothing to migrate from $src to $tgt"
191       fi
192       tgt="$src"
193     done
194
195     if has_instances "$tgt"; then
196       echo "$tgt not empty."
197       exit 1
198     fi
199
200     if ! [ "$tgt" = "$me" ]; then
201       echo >&2 "I was expecting $tgt to be me ($me) here."
202       exit 1
203     fi
204
205     if [ "$newmaster" != "" ]; then
206       echo "Making $newmaster the new master"
207       ssh -n -l root "$newmaster" gnt-cluster master-failover
208     fi
209     shutdown -r 1 "reboot requested by $0"
210     exit
211   )
212 }
213
214 crossmigrate() {
215   me=$(hostname -f)
216   if ! grep -q -F "$me" "$nodelist"; then
217     echo >&2 "my hostname ($me) not found in nodelist"
218     exit 1
219   fi
220   them="$(grep -v -F "$me" "$nodelist")"
221
222   echo "Migrating from $them to $me."
223   if ! gnt-node migrate -f -n "$me" "$them"; then
224     echo >&2 "gnt-node migrate exited with an error.  Bailing out."
225     exit 1
226   fi
227   reboot_host "$them"
228
229   echo "Activating disks.."
230   for instance in $( gnt-instance list -o name --no-headers --filter 'status == "running"' ); do
231     echo " - $instance ..."
232     if ! gnt-instance activate-disks "$instance"; then
233       echo >&2 "gnt-instance activate-disks $instance failed.  Bailing out."
234       exit 1
235     fi
236   done
237
238   if [ -e /proc/drbd ]; then
239     echo "Waiting for drbd to be consistent."
240     sleep 5
241     while egrep -C2 --color -i 'iconsistent|finish' /proc/drbd || ! /usr/lib/nagios/plugins/dsa-check-drbd -d All ; do
242       echo "Still waiting.."
243       sleep 5
244     done
245   fi
246
247   echo "Migrating from $me to $them."
248   if ! gnt-node migrate -f -n "$them" "$me"; then
249     echo >&2 "gnt-node migrate exited with an error.  Bailing out."
250     exit 1
251   fi
252
253   at 'now + 30 min' << 'EOF'
254 screen -S hbal -d -m sh -c '
255   echo "Activating disks.."
256   for instance in $( gnt-instance list -o name --no-headers --filter "status == \"running\"" ); do
257     echo " - $instance ..."
258     if ! gnt-instance activate-disks "$instance"; then
259       echo >&2 "Warning: gnt-instance activate-disks $instance failed."
260     fi
261   done
262
263   hbal -L -C -v -X
264   echo "done."
265   sleep 1h
266 '
267 EOF
268   reboot_host "$me"
269 }
270
271 reboot_byrd() {
272   /sbin/shutdown -k 30 < /dev/null
273   sleep 15m
274   gnt-cluster watcher pause 30m
275
276   for i in $(gnt-instance list --no-headers -o name); do
277     gnt-instance shutdown --no-remember --submit $i
278   done
279
280   while pgrep -c '^qemu-|^kvm$' -u root ; do
281     sleep 15;
282     gnt-cluster watcher pause 30m
283   done
284
285   at 'now + 5 min' << EOF
286 sleep 4m;
287 gnt-cluster watcher continue
288 EOF
289
290   /sbin/shutdown -c
291   sleep 5
292   /sbin/shutdown -r 1 </dev/null
293 }
294
295 if [ "${TMUX:-}" = "" ] && [ "${STY:-}" = "" ] ; then
296   echo >&2 "Might want to launch me in a screen or tmux."
297   exit 1
298 fi
299
300 if ! [ "$force" = 1 ]; then
301   echo -n 'really? '
302   read really
303   [ "$really" = "y" ]
304 fi
305
306 ### ensure_nodelist
307 ###################
308 if ! [ -e "$nodelist" ]; then
309   tmp="$(tempfile)"
310   trap "rm -f '$tmp'" EXIT
311   gnt-node list --no-headers -o name > "$tmp"
312   nodelist="$tmp"
313 fi
314
315 lines=$(wc -l < "$nodelist")
316 case "$lines" in
317   0) 
318     echo >&2 "nodelist $nodelist empty."
319     exit 1
320     ;;
321   1)
322     case "$(hostname -f)" in
323       byrd.debian.org)
324         reboot_byrd
325         ;;
326       *)
327         echo >&2 "Only one node."
328         exit 1
329     esac
330     ;;
331   2)
332     crossmigrate
333     ;;
334   *)
335     moveupdown
336     ;;
337 esac
338