-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathvmbackup
executable file
·314 lines (269 loc) · 9.81 KB
/
vmbackup
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
#!/bin/bash
#########################################################################
# Copyright Bassu #
# "THE BEER-WARE LICENSE" #
# <[email protected]> wrote this file. As long as you retain this #
# notice you can do whatever you want with this stuff. If we meet #
# some day and this stuff is worth it, you can buy me a beer in return #
#########################################################################
## Some globals
BACKUP_DIR=/ebackup/newton
XL_CONF_DIR=/home/cfgs
SNAPSHOT_PREFIX=snap
SNAPSHOT_SIZE=100G
prog="vmbackup"
pidfile=/tmp/$prog.pid
logfile=/var/log/$prog
tmpfile=/tmp/$prog
## Only one instance should run at a time
if [ -f $pidfile ]; then
if ps -p $(cat $pidfile) > /dev/null 2>&1; then
echo "$0 already running!"
exit
fi
fi
echo $$ > $pidfile
trap 'rm -f "$pidfile" >/dev/null 2>&1' EXIT HUP KILL INT QUIT TERM
# if you want non-fancy output
[[ $@ =~ "-c" ]] && console="yes" || console="no"
## Calculation functions
spinner() {
local pid=$1
local delay=0.5
local spinstr='|/-\'
while [ "$(ps a | awk '{print $1}' | grep $pid 2>> $logfile)" ]; do
local temp=${spinstr#?}
printf " [%c] " "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep $delay
printf "\b\b\b\b\b\b"
done
printf " \b\b\b\b"
}
align() {
awk -F '[' '{printf "%-50s [%-20s\n", $1,$2}'
}
result() {
[[ $? -eq 0 ]] && success || failure
echo
}
timestart() {
start=$(date +%s)
}
timeend() {
eta=$(printf "%.2f" $(awk 'BEGIN { print '$(expr $(date +%s) - $start)'/60 }'))
}
succ_fail() {
if [[ $console == "no" ]]; then
RES_COL=60
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
SETCOLOR_SUCCESS="echo -en \\033[0;31m"
SETCOLOR_FAILURE="echo -en \\033[1;45m"
SETCOLOR_NORMAL="echo -en \\033[0;39m"
else
RES_COL=
MOVE_TO_COL=
SETCOLOR_SUCCESS=
SETCOLOR_FAILURE=
SETCOLOR_NORMAL=
fi
}
success() {
succ_fail
$MOVE_TO_COL
echo -n "["
$SETCOLOR_SUCCESS
echo -n $" OK "
$SETCOLOR_NORMAL
echo -n "]"
return 0
}
failure() {
succ_fail
$MOVE_TO_COL
echo -n "["
$SETCOLOR_FAILURE
echo -n $" FAILED "
$SETCOLOR_NORMAL
echo -n "]"
return 0
}
## Backup process functions
find_vm () {
## Make sure xl honors the vm and config exists
[[ ! "$(xl list $1)" ]] && { echo $1 vm not found; exit 1; }
echo -n $" finding vm $1 "
[[ ! -f "$XL_CONF_DIR/$1.cfg" ]] && { echo $1.cfg config file not found; exit 1; } || vm="$1"
result
}
find_lvm () {
## If vm is lvm based, i.e. count /dev/ in cfg file
echo -n $" checking if $vm is lvm based "
dev=$(awk '/\/dev/{++c} END {print c}' "$XL_CONF_DIR/$vm.cfg")
[[ $dev -gt 0 ]] && lvm_cfg=$(awk -F '[/=,]' '/phy:/ {print "/"$3"/"$4"/"$5}' "$XL_CONF_DIR/$vm.cfg") || { echo "$vm is not lvm based, cannot proceed"; exit 1; }
result
## We could probably go no where if lvm doesn't exit
echo -n $" finding lvm for $vm "
[[ $(lvdisplay $lvm_cfg 2> $logfile | grep "$lvm_cfg" | wc -l) -eq 0 ]] && { echo $lvm_cfg lvm does not exist; exit 1; } || { lvm=$lvm_cfg; vg=$(echo $lvm | cut -d'/' -f3); }
result
}
start_snap () {
## Preexisting snapshots can screw the whole disks; better to have one chain per lvm
count_snaps=$(lvdisplay $lvm | awk '/source/{l++} END {print l}')
[[ $count_snaps -gt 0 ]] && { echo " warning: snapshot already existed, recreating "; remove_snap; result; }
## Snapshots are pretty fuckin slow so set chunk size to max (not needed if not running CFQ)
echo -n $" creating snapshot $SNAPSHOT_PREFIX$vm for $vm "
lvcreate -L$SNAPSHOT_SIZE --snapshot -n "$SNAPSHOT_PREFIX$vm" "$lvm" >> $logfile 2>&1
result
}
remove_snap () {
## Be sure that it's a snapshot that we are removing
echo -n $" removing snapshot $vg/$SNAPSHOT_PREFIX$vm "
is_snap=$(lvdisplay /dev/$vg/$SNAPSHOT_PREFIX$vm | awk '/snapshot status/ {if ($5=="destination") print "yes"; else print "no"}')
[[ $is_snap == "yes" ]] && lvremove -f "$vg/$SNAPSHOT_PREFIX$vm" >> $logfile 2>&1 || { echo "/dev/$vg/$SNAPSHOT_PREFIX$vm does not look like a snapshot, cannot proceed"; exit 1; }
result
}
dump_and_compress() {
suffix=$(date +'%d%b%y-%s' | tr [:upper:] [:lower:])
rawsize=$(lvs -o lv_name,lv_size $lvm | tail -1 | awk '{print $2}' | tr [:lower:] [:upper:] 2>>$logfile)
echo -n $" dumping and compressing $rawsize simultaneously "
echo "executing lzop -c < /dev/$vg/$SNAPSHOT_PREFIX$vm > $BACKUP_DIR/$vm/$vm-$suffix.lzo" >>$logfile 2>&1
if [[ $console == "no" ]]; then
lzop -c < /dev/$vg/$SNAPSHOT_PREFIX$vm > $BACKUP_DIR/$vm/$vm-$suffix.lzo &
spinner $!
else
lzop -c < /dev/$vg/$SNAPSHOT_PREFIX$vm > $BACKUP_DIR/$vm/$vm-$suffix.lzo &
fi
## if lvs reports i/o errors or snapshot fill errors, now is the time to catch and abort
ddpid=$!
while [ "$(ps a | awk '{print $1}' | grep $ddpid 2>> $logfile)" ]; do
## keep checking every fifteen seconds
sleep 15
if [[ $(lvs /dev/$vg/$SNAPSHOT_PREFIX$vm | grep -i error | wc -l) -gt 0 ]]; then
echo "lvs reports errors, quitting"
kill -9 $ddpid
exit 1
fi
done
wait $ddpid
result
}
prepare () {
echo -n $" backing up lvm list"
lvs -o lv_name,lv_size > $BACKUP_DIR/lvms-list
result
if [[ ! -d $BACKUP_DIR/$vm ]]; then
echo -n $" creating backup directory for $vm "
mkdir -p $BACKUP_DIR/$vm
else
echo -n $" backup directory already exists "
fi
result
echo -n $" copying $vm.cfg configuration file "
cp -f "$XL_CONF_DIR/$vm.cfg" $BACKUP_DIR/$vm
result
}
cleanup () {
## Rotate the backup
## recentfile=$(awk '/compressing/ && /into/ && /'$vm'/ {print $4}' $logfile | awk '{gsub(/\/.*\//,"",$1); print}' | head -1)
echo -n $" rotating old backup files for $vm"
find $BACKUP_DIR/$vm -size +2M -type f -not -iname "$vm-$suffix.lzo" -exec rm -f {} +
result
filesize=$(ls -sht $BACKUP_DIR/$vm/$vm-$suffix.lzo | head -1 | awk '{print $1}' 2>> $logfile)
echo -n $" verifying backup of size $filesize " >>$logfile
echo -n $" verifying backup of size $filesize "
result
echo -n $" flushing data out of ram "
sync
result
}
## Greetings. Single quotes prevent bash execution and ^[ is escape key inserted by M-x ucs-insert: ESCAPE
if [[ $console == "yes" ]]; then
cat << 'EOF'
__ __ ____ _
\ \ / / __ ___ | __ ) __ _ ___| | ___ _ _ __
\ \ / / '_ ` _ \| _ \ / _` |/ __| |/ / | | | '_ \
\ V /| | | | | | |_) | (_| | (__| <| |_| | |_) |
\_/ |_| |_| |_|____/ \__,_|\___|_|\_\\__,_| .__/
|_|
Phi9 Xen Backup By: [email protected] Version: 0.3
EOF
else
cat << 'EOF'
[1;44;37m __ __ ____ _ [0m
[1;44;37m \ \ / / __ ___ | __ ) __ _ ___| | ___ _ _ __ [0m
[1;44;37m \ \ / / '_ ` _ \| _ \ / _` |/ __| |/ / | | | '_ \ [0m
[1;44;37m \ V /| | | | | | |_) | (_| | (__| <| |_| | |_) | [0m
[1;44;37m \_/ |_| |_| |_|____/ \__,_|\___|_|\_\\__,_| .__/ [0m
[1;44;37m |_| [0m
[1;45;37m Phi9 Xen Backup By: [email protected] Version: 0.3 [0m
EOF
fi
## Backup a single vm
one () {
[[ $1 == "" ]] && { echo "Usage: $0 [-c] [all| one [vmname]]"; exit 1; }
[[ $console == "no" ]] && echo -e "\e[0;37m\n\r=> Backing up $1... standby\e[0m" || echo -e "\n=> Backing up $1... standby"
if [[ $console == "no" ]]; then
timestart
find_vm $1
find_lvm
start_snap
prepare
dump_and_compress
remove_snap
cleanup
timeend
else
timestart
## Bash's multithreading is shitty (a reason next major version will be in python); it doesn't create real subshells
## and variables on function pipes don't persist; also process substitution screws lvdisplay so doing manual processing
## on initial functions that actually do the job
find_vm $1 > $tmpfile; cat $tmpfile | align
find_lvm > $tmpfile; cat $tmpfile | align
prepare > $tmpfile; cat $tmpfile | align
start_snap > $tmpfile; cat $tmpfile | align
dump_and_compress > $tmpfile; cat $tmpfile | align
remove_snap > $tmpfile; cat $tmpfile | align
cleanup > $tmpfile; cat $tmpfile | align
timeend
fi
[[ $console == "no" ]] && echo -e "\e[0;34m=> Backup time: $eta min\e[0m" || echo -e "=> Backup time: $eta min"
rm -f $lockfile
}
all () {
[[ $console == "no" ]] && echo -e "\e[0;37m\n\r=> Backing up *all* domains ...\e[0m" || echo -e "\n=> Backing up *all* domains ..."
for vm_to_backup in $(xl list | awk '{print $1}' | sed '1d' | grep -i -v 'domain-0' 2>> $pidfile); do
one $vm_to_backup
done
}
if [[ $console == "yes" ]]; then
case "$2" in
all)
all
;;
one)
one $3
;;
*)
echo "Usage: $0 [-c] [all| one [vmname]]"
echo " -c show non-fancy output i.e. for crons"
echo " one backup one vm with name specified after it"
echo " all backup all vms"
exit 2
esac
else
case "$1" in
all)
all
;;
one)
one $2
;;
*)
echo "Usage: $0 [-c] [all| one [vmname]]"
echo " -c show non-fancy output i.e. for crons"
echo " one backup one vm with name specified after it"
echo " all backup all vms"
exit 2
esac
fi