1#!@BASH_SHELL@
2#
3# Copyright (c) 2014 David Vossel <davidvossel@gmail.com>
4#                    All Rights Reserved.
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of version 2 of the GNU General Public License as
8# published by the Free Software Foundation.
9#
10# This program is distributed in the hope that it would be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13#
14# Further, this software is distributed without any warranty that it is
15# free of the rightful claim of any third person regarding infringement
16# or the like.  Any license provided herein, whether implied or
17# otherwise, applies only to this software file.  Patent licenses, if
18# any, provided herein do not apply to combinations of this program with
19# other software, or any other product whatsoever.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write the Free Software Foundation,
23# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
24#
25
26#######################################################################
27# Initialization:
28
29: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
30. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
31. ${OCF_FUNCTIONS_DIR}/ocf-directories
32
33# Parameter defaults
34
35OCF_RESKEY_with_cmirrord_default="false"
36OCF_RESKEY_daemon_options_default="-d0"
37OCF_RESKEY_activate_vgs_default="true"
38OCF_RESKEY_exclusive_default="false"
39
40: ${OCF_RESKEY_with_cmirrord=${OCF_RESKEY_with_cmirrord_default}}
41: ${OCF_RESKEY_daemon_options=${OCF_RESKEY_daemon_options_default}}
42: ${OCF_RESKEY_activate_vgs=${OCF_RESKEY_activate_vgs_default}}
43: ${OCF_RESKEY_exclusive=${OCF_RESKEY_exclusive_default}}
44
45#######################################################################
46
47meta_data() {
48	cat <<END
49<?xml version="1.0"?>
50<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
51<resource-agent name="clvm">
52<version>1.0</version>
53
54<longdesc lang="en">
55This agent manages the clvmd daemon.
56</longdesc>
57<shortdesc lang="en">clvmd</shortdesc>
58
59<parameters>
60<parameter name="with_cmirrord" unique="0" required="0">
61<longdesc lang="en">
62Start with cmirrord (cluster mirror log daemon).
63</longdesc>
64<shortdesc lang="en">activate cmirrord</shortdesc>
65<content type="boolean" default="${OCF_RESKEY_with_cmirrord_default}" />
66</parameter>
67
68<parameter name="daemon_options" unique="0">
69<longdesc lang="en">
70Options to clvmd. Refer to clvmd.8 for detailed descriptions.
71</longdesc>
72<shortdesc lang="en">Daemon Options</shortdesc>
73<content type="string" default="${OCF_RESKEY_daemon_options_default}"/>
74</parameter>
75
76<parameter name="activate_vgs" unique="0">
77<longdesc lang="en">
78Whether or not to activate all cluster volume groups after starting
79the clvmd or not. Note that clustered volume groups will always be
80deactivated before the clvmd stops regardless of what this option
81is set to.
82</longdesc>
83<shortdesc lang="en">Activate volume groups</shortdesc>
84<content type="boolean" default="${OCF_RESKEY_activate_vgs_default}"/>
85</parameter>
86
87<parameter name="exclusive" unique="0" required="0">
88<longdesc lang="en">
89If set, only exclusive volume groups will be monitored.
90</longdesc>
91<shortdesc lang="en">Only monitor exclusive volume groups</shortdesc>
92<content type="boolean" default="${OCF_RESKEY_exclusive_default}" />
93</parameter>
94
95
96</parameters>
97
98<actions>
99<action name="start"        timeout="90s" />
100<action name="stop"         timeout="90s" />
101<action name="monitor"      timeout="90s" interval="30s" depth="0" />
102<action name="meta-data"    timeout="10s" />
103<action name="validate-all"   timeout="20s" />
104</actions>
105</resource-agent>
106END
107}
108
109#######################################################################
110
111sbindir=$HA_SBIN_DIR
112if [ -z $sbindir ]; then
113	sbindir=/usr/sbin
114fi
115DAEMON="clvmd"
116CMIRROR="cmirrord"
117DAEMON_PATH="${sbindir}/clvmd"
118CMIRROR_PATH="${sbindir}/cmirrord"
119LVMCONF="${sbindir}/lvmconf"
120LOCK_FILE="/var/lock/subsys/$DAEMON"
121
122# attempt to detect where the vg tools are located
123# for some reason this isn't consistent with sbindir
124# in some distros.
125vgtoolsdir=$(dirname $(which vgchange 2> /dev/null) 2> /dev/null)
126if [ -z "$vgtoolsdir" ]; then
127       vgtoolsdir="$sbindir"
128fi
129
130LVM_VGCHANGE=${vgtoolsdir}/vgchange
131LVM_VGDISPLAY=${vgtoolsdir}/vgdisplay
132LVM_VGSCAN=${vgtoolsdir}/vgscan
133
134# Leaving this in for legacy. We do not want to advertize
135# the abilty to set options in the systconfig exists, we want
136# to expand the OCF style options as necessary instead.
137[ -f /etc/sysconfig/cluster ] && . /etc/sysconfig/cluster
138[ -f /etc/sysconfig/$DAEMON ] && . /etc/sysconfig/$DAEMON
139
140CLVMD_TIMEOUT="90"
141if [ -n "$OCF_RESKEY_CRM_meta_timeout" ]; then
142	CLVMD_TIMEOUT=$(($OCF_RESKEY_CRM_meta_timeout/1000))
143fi
144
145clvmd_usage()
146{
147	cat <<END
148usage: $0 {start|stop|monitor|validate-all|meta-data}
149
150Expects to have a fully populated OCF RA-compliant environment set.
151END
152}
153
154clvmd_validate()
155{
156	# check_binary will exit with OCF_ERR_INSTALLED
157	# when binary is missing
158	check_binary "pgrep"
159	check_binary $DAEMON_PATH
160	if ocf_is_true $OCF_RESKEY_with_cmirrord; then
161		check_binary $CMIRROR_PATH
162	fi
163
164	if [ "$__OCF_ACTION" != "monitor" ]; then
165		check_binary "killall"
166		check_binary $LVM_VGCHANGE
167		check_binary $LVM_VGDISPLAY
168		check_binary $LVM_VGSCAN
169	fi
170
171	# Future validation checks here.
172	return $OCF_SUCCESS
173}
174
175check_process()
176{
177	local binary=$1
178	local pidfile="${HA_RSCTMP}/${binary}-${OCF_RESOURCE_INSTANCE}.pid"
179	local pid
180
181	ocf_log debug "Checking status for ${binary}."
182	if [ -e "$pidfile" ]; then
183		cat /proc/$(cat $pidfile)/cmdline 2>/dev/null | grep -a "${binary}" > /dev/null 2>&1
184		if [ $? -eq 0 ];then
185			# shortcut without requiring pgrep to search through all procs
186			return $OCF_SUCCESS
187		fi
188	fi
189
190	pid=$(pgrep ${binary})
191	case $? in
192		0)
193			ocf_log info "PID file (pid:${pid} at $pidfile) created for ${binary}."
194			echo "$pid" > $pidfile
195			return $OCF_SUCCESS;;
196		1)
197			rm -f "$pidfile" > /dev/null 2>&1
198			ocf_log info "$binary is not running"
199			return $OCF_NOT_RUNNING;;
200		*)
201			rm -f "$pidfile" > /dev/null 2>&1
202			ocf_exit_reason "Error encountered detecting pid status of $binary"
203			return $OCF_ERR_GENERIC;;
204	esac
205}
206
207clvmd_status()
208{
209	local rc
210	local mirror_rc
211	clvmd_validate
212	if [ $? -ne $OCF_SUCCESS ]; then
213		ocf_exit_reason "Unable to monitor, Environment validation failed."
214		return $?
215	fi
216
217	check_process $DAEMON
218	rc=$?
219	mirror_rc=$rc
220
221	if ocf_is_true $OCF_RESKEY_with_cmirrord; then
222		check_process $CMIRROR
223		mirror_rc=$?
224	fi
225
226	# If these ever don't match, return error to force recovery
227	if [ $mirror_rc -ne $rc ]; then
228		return $OCF_ERR_GENERIC
229	fi
230
231	return $rc
232}
233
234# NOTE: replace this with vgs, once display filter per attr is implemented.
235clustered_vgs() {
236	if ! ocf_is_true "$OCF_RESKEY_exclusive"; then
237		${LVM_VGDISPLAY} 2>/dev/null | awk 'BEGIN {RS="VG Name"} {if (/Clustered/) print $1;}'
238	else
239		for vg in $(vgs --select "clustered=yes" -o name --noheadings); do
240			lvs --select lv_active=~'local.*exclusive' -o vg_name --noheadings $vg 2> /dev/null | awk '!seen[$1]++ {print $1}'
241		done
242	fi
243}
244
245wait_for_process()
246{
247	local binary=$1
248	local timeout=$2
249	local count=0
250
251	ocf_log info "Waiting for $binary to exit"
252	while [ $count -le $timeout ]; do
253		check_process $binary
254		if [ $? -eq $OCF_NOT_RUNNING ]; then
255			ocf_log info "$binary terminated"
256			return $OCF_SUCCESS
257		fi
258		sleep 1
259		count=$((count+1))
260	done
261
262	return $OCF_ERR_GENERIC
263}
264
265time_left()
266{
267	local end=$1
268	local default=$2
269	local now=$SECONDS
270	local result=0
271
272	result=$(( $end - $now ))
273	if [ $result -lt $default ]; then
274		return $default
275	fi
276	return $result
277}
278
279clvmd_stop()
280{
281	local LVM_VGS
282	local rc=$OCF_SUCCESS
283	local end=$(( $SECONDS + $CLVMD_TIMEOUT ))
284
285	clvmd_status
286	if [ $? -eq $OCF_NOT_RUNNING ]; then
287		return $OCF_SUCCESS
288	fi
289
290	check_process $DAEMON
291	if [ $? -ne $OCF_NOT_RUNNING ]; then
292		LVM_VGS="$(clustered_vgs)"
293
294		if [ -n "$LVM_VGS" ]; then
295			ocf_log info "Deactivating clustered VG(s):"
296			ocf_run ${LVM_VGCHANGE} -anl $LVM_VGS
297			if [ $? -ne 0 ]; then
298				ocf_exit_reason "Failed to deactivate volume groups, cluster vglist = $LVM_VGS"
299				return $OCF_ERR_GENERIC
300			fi
301		fi
302
303		ocf_log info "Signaling $DAEMON to exit"
304		killall -TERM $DAEMON
305		if [ $? != 0 ]; then
306			ocf_exit_reason "Failed to signal -TERM to $DAEMON"
307			return $OCF_ERR_GENERIC
308		fi
309
310		wait_for_process $DAEMON $CLVMD_TIMEOUT
311		rc=$?
312		if [ $rc -ne $OCF_SUCCESS ]; then
313			ocf_exit_reason "$DAEMON failed to exit"
314			return $rc
315		fi
316
317		rm -f $LOCK_FILE
318	fi
319
320	check_process $CMIRROR
321	if [ $? -ne $OCF_NOT_RUNNING ] && ocf_is_true $OCF_RESKEY_with_cmirrord; then
322		local timeout
323		ocf_log info "Signaling $CMIRROR to exit"
324		killall -INT $CMIRROR
325
326		time_left $end 10; timeout=$?
327		wait_for_process $CMIRROR $timeout
328		rc=$?
329		if [ $rc -ne $OCF_SUCCESS ]; then
330			killall -KILL $CMIRROR
331			time_left $end 10; timeout=$?
332			wait_for_process $CMIRROR $(time_left $end 10)
333			rc=$?
334		fi
335	fi
336
337	return $rc
338}
339
340start_process()
341{
342	local binary_path=$1
343	local opts=$2
344
345	check_process "$(basename $binary_path)"
346	if [ $? -ne $OCF_SUCCESS ]; then
347		ocf_log info "Starting $binary_path: "
348		ocf_run $binary_path $opts
349		rc=$?
350		if [ $rc -ne 0 ]; then
351			ocf_exit_reason "Failed to launch $binary_path, exit code $rc"
352			exit $OCF_ERR_GENERIC
353		fi
354	fi
355
356	return $OCF_SUCCESS
357}
358
359clvmd_activate_all()
360{
361
362	if ! ocf_is_true "$OCF_RESKEY_activate_vgs"; then
363		ocf_log info "skipping vg activation, activate_vgs is set to $OCF_RESKEY_activate_vgs"
364		return $OCF_SUCCESS
365	fi
366	# Activate all volume groups by leaving the
367	# "volume group name" parameter empty
368	ocf_run ${LVM_VGCHANGE} -aay
369	if [ $? -ne 0 ]; then
370		ocf_log info "Failed to activate VG(s):"
371		clvmd_stop
372		return $OCF_ERR_GENERIC
373	fi
374	return $OCF_SUCCESS
375}
376
377clvmd_start()
378{
379	local rc=0
380	local CLVMDOPTS="-T${CLVMD_TIMEOUT} $OCF_RESKEY_daemon_options"
381
382	clvmd_validate
383	if [ $? -ne $OCF_SUCCESS ]; then
384		ocf_exit_reason "Unable to start, Environment validation failed."
385		return $?
386	fi
387
388	# systemd drop-in to stop process before storage services during
389	# shutdown/reboot
390	if systemd_is_running ; then
391		systemd_drop_in "99-clvmd" "After" "blk-availability.service"
392	fi
393
394	clvmd_status
395	if [ $? -eq $OCF_SUCCESS ]; then
396		ocf_log debug "$DAEMON already started"
397		clvmd_activate_all
398		return $?;
399	fi
400
401	# autoset locking type to clustered when lvmconf tool is available
402	if [ -x "$LVMCONF"  ]; then
403		$LVMCONF --enable-cluster > /dev/null 2>&1
404	fi
405
406	# if either of these fail, script will exit OCF_ERR_GENERIC
407	if ocf_is_true $OCF_RESKEY_with_cmirrord; then
408		start_process $CMIRROR_PATH
409	fi
410	start_process $DAEMON_PATH "$CLVMDOPTS"
411
412	# Refresh local cache.
413	#
414	# It's possible that new PVs were added to this, or other VGs
415	# while this node was down. So we run vgscan here to avoid
416	# any potential "Missing UUID" messages with subsequent
417	# LVM commands.
418
419	# The following step would be better and more informative to the user:
420	# 'action "Refreshing VG(s) local cache:" ${LVM_VGSCAN}'
421	# but it could show warnings such as:
422	# 'clvmd not running on node x-y-z  Unable to obtain global lock.'
423	# and the action would be shown as FAILED when in reality it didn't.
424	# Ideally vgscan should have a startup mode that would not print
425	# unnecessary warnings.
426
427	${LVM_VGSCAN} > /dev/null 2>&1
428	touch $LOCK_FILE
429
430	clvmd_activate_all
431
432	clvmd_status
433	return $?
434}
435
436case $__OCF_ACTION in
437	meta-data)   meta_data
438		exit $OCF_SUCCESS;;
439
440	start)         clvmd_start;;
441
442	stop)          clvmd_stop;;
443
444	monitor)       clvmd_status;;
445
446	validate-all)  clvmd_validate;;
447
448	usage|help)    clvmd_usage;;
449
450	*)             clvmd_usage
451	               exit $OCF_ERR_UNIMPLEMENTED;;
452esac
453
454rc=$?
455ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
456exit $rc
457
458