1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
|
#! /bin/bash
# Usage: block-dmmd [add args | remove args]
#
# the dmmd device syntax (in xl commands/configs) is something like:
# script=block-dmmd,md;/dev/md0;md;/dev/md1;lvm;/dev/vg1/lv1
# or
# script=block-dmmd,lvm;/dev/vg1/lv1;lvm;/dev/vg1/lv2;md;/dev/md0
# device pairs (type;dev) are processed in order, with the last device
# assigned to the VM
#
# Note - When using the libxl stack, the "script=block-dmmd" option
# is required. See man xl-disk-configuration(5) for more information.
#
# md devices can optionally:
# specify a config file through:
# md;/dev/md100(/var/xen/config/mdadm.conf)
# use an array name (mdadm -N option):
# md;My-MD-name;lvm;/dev/vg1/lv1
#
# Completely expressive syntax should be similar to:
# "format=raw, vdev=xvdb, access=rw, script=block-dmmd, \
# target=md;/dev/md0(/etc/mdadm.conf);lvm;/dev/vg1/lv1"
#
##
# History:
# 2017-07-10, mlatimer@suse.com:
# Modification to use syslog for progress messages by ldevulder@suse.com
# 2017-06-12, mlatimer@suse.com:
# Merge LVM improvements by loic.devulder@mpsa.com
# Document libxl "script=block-dmmd" syntax in examples
# Remove xm/xend references (e.g. parsed_timeout from xend-config.sxp)
# 2016-05-27, mlatimer@suse.com:
# Merge improvements by loic.devulder@mpsa.com. Highlights include:
# - Re-write and simplification to speed up the script!
# - Add some (useful) logging messages and comments
# Minor tweaks and logging improvements
# 2016-05-26, mlatimer@suse.com:
# Verify MD activation if mdadm returns 2
# 2016-05-20, mlatimer@suse.com:
# Strip leading "dmmd:" if present in xenstore params value
# 2013-07-03, loic.devulder@mpsa.com:
# Partial rewrite of the script for supporting MD activation by name
# 2009-06-09, mh@novell.com:
# Emit debugging messages into a temporary file; if no longer needed,
# just comment the exec I/O redirection below
# Make variables used in functions local to avoid global overridings
# Use vgscan and vgchange where required
# Use the C locale to avoid dealing with localized messages
# Assign output from assembling an MD device to a variable to aid
# debugging
# We do not want to deal with localized messages
# We use LC_ALL because LC_ALL superse LANG
# But we also use LANG because some applications may still use LANG...
export LC_ALL=C
export LANG=${LC_ALL}
# Loading common libraries
. $(dirname $0)/block-common.sh
# Constants
typeset -rx MDADM_BIN=/sbin/mdadm
typeset -rx LVCHANGE_BIN=/sbin/lvchange
typeset -rx PVSCAN_BIN=/sbin/pvscan
typeset -rx VGSCAN_BIN=/sbin/vgscan
typeset -rx VGCHANGE_BIN=/sbin/vgchange
typeset -rx CLVMD_BIN=/usr/sbin/clvmd
typeset -rx DATE_SEC="date +%s"
# We check for errors ourselves
set +e
function reload_clvm()
{
# If we are in cluster mode
if ps -e | grep -q [c]lvmd 2>/dev/null; then
# Logging message
log info "Synchronizing cLVM..."
# Synchronize cLVM
${CLVMD_BIN} -R > /dev/null 2>&1 \
|| return 1
fi
return 0
}
function run_mdadm()
{
local mdadm_cmd=$1
local msg
local rc
msg="$(${MDADM_BIN} ${mdadm_cmd} 2>&1)"
rc=$?
case "${msg}" in
*"has been started"* | *"already active"*)
return 0
;;
*"is already in use"*)
# Hmm, might be used by another device in this domU
# Leave it to upper layers to detect a real error
return 2
;;
*)
return ${rc}
;;
esac
# Normally we should not get here, but if this happens
# we have to return an error
return 1
}
function activate_md()
{
# Make it explicitly local
local par=$1
local cfg dev dev_path rc t mdadm_opts
if [[ ${par} == ${par%%(*} ]]; then
# No configuration file specified
dev=${par}
cfg=""
else
dev=${par%%(*}
t=${par#*(}
cfg="-c ${t%%)*}"
fi
# Looking for device name or aliase
if [[ ${dev:0:1} == / ]]; then
dev_path=${dev%/*}
mdadm_opts=""
else
dev_path=/dev/md
mdadm_opts="-s -N"
fi
# Logging message
log info "Activating MD device ${dev}..."
# Is MD device already active?
# We need to use full path name, aliase is not possible...
if [ -e ${dev_path}/${dev##*/} ]; then
${MDADM_BIN} -Q -D ${dev_path}/${dev##*/} 2>/dev/null \
| grep -iq state.*\:.*inactive || return 0
fi
# Activate MD device
run_mdadm "-A ${mdadm_opts} ${dev} ${cfg}"
rc=$?
# A return code of 2 can indicate the array configuration was incorrect
if [[ ${rc} == 2 ]]; then
# Logging message
log info "Verifying MD device ${dev} activation..."
# If the array is active, return 0, otherwise return an error
${MDADM_BIN} -Q -D ${dev_path}/${dev##*/} &>/dev/null && return 0 \
|| return 1
fi
return ${rc}
}
function deactivate_md()
{
local par=$1
local dev
if [[ ${par} == ${par%%(*} ]]; then
# No configuration file specified
dev=${par}
else
dev=${par%%(*}
fi
# Looking for device name or aliase
if [[ ${dev:0:1} == / ]]; then
dev_path=${dev%/*}
else
dev_path=/dev/md
fi
# Logging message
log info "Deactivating MD device ${dev}..."
# We need the device name only while deactivating
${MDADM_BIN} -S ${dev_path}/${dev##*/} > /dev/null 2>&1
return $?
}
function lvm_action()
{
local action=$1
local dev=$2
local run_timeout=90
local end_time
# Logging message
log info "${action} LVM device ${dev}..."
# Set end_time for the loop
(( end_time = $(${DATE_SEC}) + run_timeout ))
while true; do
# Action depends of what the user asks
if [[ ${action} == activate ]]; then
# First scan for PVs and VGs
# We need this for using MD device as PV
${PVSCAN_BIN} > /dev/null 2>&1
${LVCHANGE_BIN} -aey ${dev} > /dev/null 2>&1 \
&& [[ -e ${dev} ]] \
&& return 0
elif [[ ${action} == deactivate ]]; then
${LVCHANGE_BIN} -aen ${dev} > /dev/null 2>&1 \
&& return 0
# If the LV is already deactivated we may be in an infinite loop
# So we need to test if the LV is still present
[[ -e ${dev} ]] || return 0
fi
# It seems that we had a problem during lvchange
# If we are in a cluster the problem may be due to a cLVM locking bug,
# so try to reload it
reload_clvm
# If it takes too long we need to return an error
if (( $(${DATE_SEC}) >= end_time )); then
log err "Failed to ${action} $1 within ${run_timeout} seconds"
return 1
fi
# Briefly sleep before restarting the loop
sleep 0.1
done
# Normally we should not get here, but if this happens
# we have to return an error
return 1
}
# Variables
typeset command=$1
typeset BP=100
typeset SP=${BP}
typeset VBD
typeset -a stack
function push()
{
local value="$1"
[[ -n "${value}" ]] \
&& stack[$((--SP))]="${value}"
return 0
}
function pop()
{
[[ "${SP}" != "${BP}" ]] \
&& VBD=${stack[$((SP++))]} \
|| VBD=""
return 0
}
function activate_dmmd()
{
case "$1" in
"md")
activate_md $2
return $?
;;
"lvm")
lvm_action activate $2
return $?
;;
esac
# Normally we should not get here, but if this happens
# we have to return an error
return 1
}
function deactivate_dmmd()
{
case "$1" in
"md")
deactivate_md $2
return $?
;;
"lvm")
lvm_action deactivate $2
return $?
;;
esac
# Normally we should not get here, but if this happens
# we have to return an error
return 1
}
function cleanup_stack()
{
while true; do
pop
[[ -z "${VBD}" ]] && break
deactivate_dmmd ${VBD}
done
}
function parse_par()
{
# Make these vars explicitly local
local ac par rc s t
ac=$1
par="$2"
par="${par};"
while true; do
t=${par%%;*}
[[ -z "${t}" ]] && return 0
par=${par#*;}
s=${par%%;*}
[[ -z "${s}" ]] && return 1
par=${par#*;}
if [[ "${ac}" == "activate" ]]; then
activate_dmmd ${t} ${s} \
|| return 1
fi
push "${t} ${s}"
done
}
case "${command}" in
"add")
p=$(xenstore-read ${XENBUS_PATH}/params) || true
claim_lock "dmmd"
dmmd=${p#dmmd:}
if ! parse_par activate "${dmmd}"; then
cleanup_stack
release_lock "dmmd"
exit 1
fi
lastparam=${dmmd##*;}
usedevice=${lastparam%(*}
xenstore-write ${XENBUS_PATH}/node "${usedevice}"
write_dev "${usedevice}"
release_lock "dmmd"
exit 0
;;
"remove")
p=$(xenstore-read ${XENBUS_PATH}/params) || true
claim_lock "dmmd"
dmmd=${p#dmmd:}
parse_par noactivate "${dmmd}"
cleanup_stack
release_lock "dmmd"
exit 0
;;
esac
# Normally we should not get here, but if this happens
# we have to return an error
return 1
|