Initial commit
[ta/monitoring.git] / src / dbwatchdog.sh
1 #!/bin/bash
2
3 # Copyright 2019 Nokia
4
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 DBAGENT_LOG=/var/log/dbwatchdog.log
18 DOMAIN=galera
19 OWNNODE=$(hostname)
20 DSSCLI=/usr/local/bin/dsscli
21 BECOMEMASTERATTR=become-master
22 LOCKNAME=galera
23 LOCKTIMEOUT=60
24 LOCKCLI=/usr/local/bin/lockcli
25 LOCKNAME=galera
26 LOCKHOLDER=$OWNNODE
27 LOCKUUID=0
28 LOCKUUID_FILE=/var/run/.$DOMAIN.lock.uuid
29
30 declare -a dbnodes
31 dbnodes_count=1
32
33 function get_db_nodes() 
34 {
35     IFS=',' read -a dbnodes <<< $1
36     dbnodes_count=${#dbnodes[@]}
37 }
38
39
40
41 function log()
42 {
43     local priority=$1
44     shift
45     local message=$1
46
47     logger $priority "${FUNCNAME[2]} ${message}"
48     echo "$(date) ($priority) ${FUNCNAME[2]} ${message}" >> $DBAGENT_LOG
49 }
50
51 function log_info()
52 {
53     log info "$@"
54 }
55
56 function log_error()
57 {
58     log error "$@"
59 }
60
61 function run_cmd()
62 {
63     local result
64     local ret
65     log_info "Running $*"
66     result=$(eval "$*" 2>&1)
67     ret=$?
68     if [ $ret -ne 0 ]; then
69            log_error "Failed with error $result"
70        else
71            log_info "Command succeeded: $result"
72        fi
73     echo "$result"
74     return $ret
75 }
76
77 function is_db_instance_running()
78 {
79     output=$(/usr/bin/mysql -h $node -e "select 1" 2>&1)
80     if [ $? -eq 0 ]; then
81         log_info "DB instance in $node is up"
82         return 1
83     fi
84
85     echo $output | grep "Access denied"
86     if [ $? -eq 0 ]; then
87         log_info "DB instance in $node is up"
88         return 1
89     fi
90
91     return 0
92 }
93
94 function is_single_node()
95 {
96     log_info "Checking if we are running in single-node environment"
97     if [ $dbnodes_count -gt 1 ]; then
98         return 0
99     fi
100
101     return 1
102 }
103
104 function lock()
105 {
106     log_info "Acquiring lock"
107     while [ 1 ]; do
108         output=$($LOCKCLI lock --id $LOCKNAME --timeout $LOCKTIMEOUT)
109         if [ $? -eq 0 ]; then
110             LOCKUUID=$(echo $output | grep "uuid=" | /bin/awk -F= '{print $2}')
111             break
112         fi
113         log_info "Cannot acquire lock, waiting..."
114         sleep 5
115     done
116 }
117
118 function unlock()
119 {
120     log_info "Releasing lock"
121     uuid=$(cat $LOCKUUID_FILE)
122     run_cmd "$LOCKCLI unlock --id $LOCKNAME --uuid $uuid"
123     return 0
124 }
125
126 function set_becoming_master()
127 {
128     log_info "Setting becoming master"
129     run_cmd "$DSSCLI set --domain $DOMAIN --name $BECOMEMASTERATTR --value $OWNNODE"
130
131     ret=$?
132
133     if [ $ret -eq 0 ]; then
134         while [ 1 ]; do
135             log_info "Waiting for become master to be set"
136             is_becoming_master_set
137             if [ $? -eq 1 ]; then
138                 break
139             fi
140             sleep 1
141         done
142     fi
143
144     return $ret
145 }
146
147 function is_becoming_master_set()
148 {
149     log_info "Checking if becoming master is set"
150     value=$(run_cmd "$DSSCLI get --domain $DOMAIN --name $BECOMEMASTERATTR")
151     if [ $? -ne 0 ]; then
152         value=none
153     fi
154     if [ "z$value" != "znone" ]; then
155         return 1
156     fi
157     return 0
158 }
159
160 function get_becoming_master_node()
161 {
162     log_info "Getting the node trying to become master"
163     value=$(run_cmd "$DSSCLI get --domain $DOMAIN --name $BECOMEMASTERATTR")
164     ret=$?
165     if [ $ret -ne 0 ]; then
166         value=none
167     fi
168     echo $value
169     return $ret
170 }
171
172 function unset_becoming_master()
173 {
174     log_info "Unsetting becoming master"
175     run_cmd "$DSSCLI set --domain $DOMAIN --name $BECOMEMASTERATTR --value none"
176 }
177
178
179 function set_wsrep_new_cluster()
180 {
181     log_info "Setting new cluster and safe to bootstrap"
182     run_cmd "sed -i 's/^safe_to_bootstrap: 0/safe_to_bootstrap: 1/g' /var/lib/mysql/grastate.dat"
183     run_cmd "systemctl set-environment _WSREP_NEW_CLUSTER='--wsrep-new-cluster'"
184 }
185
186 function unset_wsrep_new_cluster()
187 {
188     log_info "Clearing new cluster flag and safe to bootstrap"
189     run_cmd "sed -i 's/^safe_to_bootstrap: 1/safe_to_bootstrap: 0/g' /var/lib/mysql/grastate.dat"
190     run_cmd "systemctl set-environment _WSREP_NEW_CLUSTER=''"
191 }
192
193 ### own attributes
194 function set_running()
195 {
196     log_info "Setting running flag to true"
197     run_cmd "$DSSCLI set --domain $DOMAIN --name ${OWNNODE}.running --value true"
198 }
199
200 function unset_running()
201 {
202     log_info "Setting running flag to false"
203     run_cmd "$DSSCLI set --domain $DOMAIN --name ${OWNNODE}.running --value false"
204     
205 }
206
207 function write_state()
208 {
209     uuid=$(grep uuid /var/lib/mysql/grastate.dat  | awk '{print $2}')
210     seqno=$(grep seqno /var/lib/mysql/grastate.dat  | awk '{print $2}')
211     run_cmd "$DSSCLI set --domain $DOMAIN --name ${OWNNODE}.uuid --value $uuid"
212     run_cmd "$DSSCLI set --domain $DOMAIN --name ${OWNNODE}.seqno --value $seqno"
213 }
214
215 ### query functions
216 function get_node_uuid()
217 {   
218     node=$1
219     log_info "Getting uuid of node $node"
220     uuid=$(run_cmd "$DSSCLI get --domain $DOMAIN --name ${node}.uuid")
221     ret=$?
222     if [ $ret -ne 0 ]; then
223         uuid=0
224     fi
225     echo $uuid
226     return $ret
227 }
228
229 function get_node_seqno()
230 {   
231     node=$1
232     log_info "Getting seqno of node $node"
233     seqno=$(run_cmd "$DSSCLI get --domain $DOMAIN --name ${node}.seqno")
234     ret=$?
235     if [ $ret -ne 0 ]; then
236         seqno=-1
237     fi
238     echo $seqno
239     return $ret
240 }
241
242 function do_others_have_good_seqno()
243 {   
244     node=$1
245     log_info "Checking if any node have a valid seqno"
246     for no in $($DSSCLI get-domain --domain $DOMAIN | grep seqno | awk '{print $3}'); do
247         if [ $no -gt 0 ]; then
248             log_info "Some node have a valid seqno"
249             return 1
250         fi
251     done
252     log_info "No node with valid seqno found"
253     return 0
254 }
255
256 function get_node_running()
257 {
258     node=$1
259     log_info "Getting if $node is running"
260     running=$(run_cmd "$DSSCLI get --domain $DOMAIN --name ${node}.running")
261     if [ $? -ne 0 ]; then
262         log_info "command failed with error $running"
263         running='false'
264     fi
265     log_info "Total running $running"
266     if [ "z$running" == "ztrue" ]; then
267         return 1
268     fi
269     return 0
270 }
271
272
273 function is_any_db_instance_running()
274 {
275     log_info "Getting nodes in which the db is running"
276     total_initializing=0
277     for node in "${dbnodes[@]}"; do
278         if [ "x$node" == "x$OWNNODE" ]; then
279             continue
280         fi
281
282         is_db_instance_running $node
283         if [ $? -eq 1 ]; then
284             log_info "DB instance in $node is up"
285             return 1
286         fi
287     done
288
289     return 0
290 }
291 function is_cluster_running()
292 {
293     log_info "Checking if an existing galera cluster is running"
294
295     #check if any instance of the db is up and running
296     is_any_db_instance_running
297     if [ $? -eq 1 ]; then
298         return 1
299     fi
300
301     return 0
302 }
303
304 function wait_cluster_running()
305 {
306     log_info "Waiting for cluster to become running"
307     while [ 1 ]; do
308         lock
309         is_cluster_running
310         cluster_running=$?
311         if [ $cluster_running -eq 1 ]; then
312             log_info "cluster is running"
313             unlock
314             return 0
315         fi
316         unlock
317         sleep 5
318     done
319 }
320 function start_pre()
321 {
322     log_info "start_pre called"
323     #check for single node case
324     is_single_node
325     single_node=$?
326     if [ $single_node -eq 1 ]; then
327         echo "Doing nothing as we are running in a single-node environment"
328         return 0
329     fi
330     #acquire lock
331     lock
332     is_cluster_running
333     cluster_running=$?
334     if [ $cluster_running -eq 1 ]; then
335         log_info "starting normally as a galera cluster is already running"
336         return 0
337     fi
338
339     #check if we have good seqno, if not then we need to wait for the active
340     #as we cannot become master
341     log_info "checking own sequence number"
342     seqno=$(get_node_seqno $OWNNODE)
343     if [ $seqno -le 0 ]; then
344         #check the seqno of others
345         do_others_have_good_seqno
346         if [ $? -eq 1 ]; then
347             log_info "bad seqno $seqno we need to wait for cluster to become running"
348             unlock
349             wait_cluster_running
350             lock
351             return 0
352         fi
353     fi
354
355     if [ $seqno -le 0 ]; then
356         log_info "no one seems to have a good seqno"
357     else
358         log_info "no running galera cluster found and we have good seqno"
359     fi
360
361     log_info "check if someone is trying to become master"
362     is_becoming_master_set
363     becoming_master=$?
364     if [ $becoming_master -eq 1 ]; then
365         log_info "someone is trying to become master, backing off"
366         unlock
367         wait_cluster_running
368         lock
369         return 0
370     fi
371
372     log_info "no one is trying to become master, let us become master"
373     set_becoming_master
374     set_wsrep_new_cluster
375     return 0
376 }
377
378 function start_post()
379 {
380     log_info "start_post setting running state to true"
381     #check for single node case
382     is_single_node
383     single_node=$?
384     if [ $single_node -eq 1 ]; then
385         echo "Doing nothing as we are running in a single-node environment"
386         return 0
387     fi
388     is_in_quorum
389     qm=$?
390     if [ $qm -eq 1 ]; then
391         become_master_node=$(get_becoming_master_node)
392         if [ "x$become_master_node" == "x$OWNNODE" ]; then
393             unset_becoming_master
394         fi
395     fi
396
397     set_running
398     unset_wsrep_new_cluster
399     unlock
400
401     return 0
402 }
403
404 function stop_post()
405 {
406     log_info "stop_post setting running state to false"
407     #check for single node case
408     is_single_node
409     single_node=$?
410     if [ $single_node -eq 1 ]; then
411         echo "Doing nothing as we are running in a single-node environment"
412         return 0
413     fi
414     is_in_quorum
415     qm=$?
416     if [ $qm -eq 1 ]; then
417         become_master_node=$(get_becoming_master_node)
418         if [ "x$become_master_node" == "x$OWNNODE" ]; then
419             unset_becoming_master
420         fi
421     fi
422     
423     unset_wsrep_new_cluster
424     if [ $qm -eq 1 ]; then
425         write_state
426         unset_running
427         for ((i=0; i<10; i++)); do
428             log_info "Waiting for own state to become not running"
429             get_node_running $OWNNODE
430             if [ $? -eq 0 ]; then
431                 log_info "Own state is updated"
432                 break
433             fi
434             sleep 2
435         done
436     fi
437     unlock
438     return 0
439 }
440
441 function stop()
442 {
443     log_info "waiting until clustercheck is ok"
444     is_single_node
445     single_node=$?
446     if [ $single_node -eq 1 ]; then
447         log_info "Doing nothing as we are running in a single-node environment"
448         return 0
449     fi
450
451     while true; do
452         /usr/local/bin/clustercheck
453         if [ $? -eq 0 ]; then
454             log_info "clustercheck is ok"
455             break
456         fi
457         sleep 2
458     done
459 }
460
461 function get_states()
462 {
463     log_info "Getting states"
464     run_cmd "$DSSCLI get-domain --domain $DOMAIN"
465     run_cmd "$DSSCLI get-domain --domain _locks"
466     is_in_quorum
467     if [ $? -eq 1 ]; then
468         echo "Nodes have quorum"
469     else
470         echo "Nodes don't have quorum"
471     fi
472 }
473
474 function is_in_quorum()
475 {
476     log_info "Checking if peer nodes are running"
477     nodes=$($DSSCLI get-domain --domain galera | grep running | awk -F. '{print $1}')
478     if [ $? -ne 0 ]; then
479         return 0
480     fi
481
482     count=0
483     down=0
484     up=0
485     for node in "${dbnodes[@]}"; do
486         let count=$count+1
487         is_db_instance_running $node
488         if [ $? -eq 1 ]; then
489             let up=$up+1
490         else
491             let down=$down+1
492         fi
493     done
494
495     log_info "Total $count, up $up, down $down"
496
497     if [ $count -eq 1 ]; then
498         return 1
499     fi
500
501     if [ $up -gt $down ]; then
502         return 1
503     fi
504
505     return 0
506 }
507
508
509 function kill_old()
510 {
511     log_info "Checking for hanging mysqld services"
512     mysqlpid=$(/usr/sbin/pidof mysqld)
513     if [ "x$mysqlpid" == "x" ]; then
514         return
515     fi
516     kill -9 $mysqlpid
517 }
518
519 if [ $# -ne 2 ]; then
520     echo "Usage:$0 start-pre|start-post|stop|stop-post|get-states|set-running|kill-old|do-others-have-good-seqno <comma separted list of db node names>"
521     exit 1
522 fi
523
524 get_db_nodes $2
525
526 if [ $1 == "start-pre" ]; then
527     start_pre
528 elif [ $1 == "start-post" ]; then
529     start_post
530 elif [ $1 == "stop" ]; then
531     stop
532 elif [ $1 == "stop-post" ]; then
533     stop_post
534 elif [ $1 == "get-states" ]; then
535     get_states
536 elif [ $1 == "set-running" ]; then
537     set_running
538 elif [ $1 == "kill-old" ]; then
539     kill_old
540 elif [ $1 == "do-others-have-good-seqno" ]; then
541     do_others_have_good_seqno
542 elif [ $1 == "is-any-db-instance-running" ]; then
543     is_any_db_instance_running
544     result=$?
545     echo "Result is $result"
546 else
547     echo "Invalid option provided"
548     echo "Usage:$0 start-pre|start-post|stop|stop-post|get-states|set-running|kill-old|do-others-have-good-seqno|is-any-db-instance-running"
549     exit 1
550 fi