Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / tools / rbd_recover_tool / database_h
1 #!/bin/bash
2 # file: database_h
3 #
4 # Copyright (C) 2015 Ubuntu Kylin
5 #
6 # Author: Min Chen <minchen@ubuntukylin.com>
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU Library Public License as published by
10 # the Free Software Foundation; either version 2, or (at your option)
11 # any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU Library Public License for more details.
17 #
18
19 my_dir=$(dirname "$0")
20
21 . $my_dir/common_h
22 . $my_dir/metadata_h
23 . $my_dir/epoch_h
24
25 db_image_prefix=
26 db_image_size=
27 db_order=
28 db_snap_id=
29 db_snap_image_size=
30 found=0
31
32 #init osd_data and get all objects path
33 function gen_database()
34 {
35   local func="gen_database"
36   rm -rf $database/*
37   rm -rf $images
38   rm -rf $raw
39   mkdir -p $database
40   local host=
41   local data_path=
42
43   trap 'echo $func failed; exit;' INT HUP
44   while read line
45   do
46     {
47       host=`echo $line|awk '{print $1}'`
48       data_path=`echo $line|awk '{print $2}'`
49       if [ "$host"x = ""x ] || [ "$data_path"x = ""x ];then
50         continue
51       fi
52       local cmds="find $data_path/current -type f"
53       ssh $ssh_option $host $cmds > $database/$host
54     } &
55   done < $osd_host_path
56   wait
57   echo "$func: finish"
58 }
59
60 # collect hobjects from database 
61 # and choose the object whose epoch is latest
62 # then, sort the objects by their offsets in image 
63 function gather_hobject_common()
64 {
65   func="gather_hobject_common"
66
67   trap 'echo $func failed; exit;' INT HUP
68   if [ $# -lt 2 ];then
69     echo "$func: parameters: <pool_id> <image_prefix> [<snap_id>]"  
70     exit
71   fi
72
73   local pool_id=$1
74   local image_prefix=$2
75   pool_id=$(($pool_id))
76   local hex_pool_id=`printf "%x" $pool_id`
77   # NOSNAP = uint64(-2)
78   local snap_id=`printf "%u" -2`
79   local hex_snap_id="head"
80   local psuffix=
81   local fsuffix="_head"
82   if [ $# = 3 ];then
83     snap_id=$(($3))
84     hex_snap_id=`printf "%x" $snap_id`
85     psuffix="_"$snap_id
86     fsuffix="_"$snap_id
87   fi
88   local underline_image_prefix=`convert_underline $image_prefix`
89   local dump_image_prefix=`dump_backslash $underline_image_prefix`
90   local ddump_image_prefix=`dump_dump_backslash $underline_image_prefix`
91   local images_raw_dir=$rbd_image/raw
92   local image_hobjects_dir=$images/pool_$pool_id/$image_prefix
93   # $images/raw/$image_prefix"_head"
94   local image_hobjects_raw=$images_raw_dir/$image_prefix"$fsuffix"
95   # $images/$image_prefix/$image_prefix"_head"
96   local image_hobjects_stable=$image_hobjects_dir/$image_prefix"$fsuffix"
97
98   if [ ! -e $images_raw_dir ];then
99     mkdir -p $images_raw_dir
100   fi
101   if [ ! -e $image_hobjects_dir ];then
102   local image_metadata=$images_meta/$image_name_in
103     mkdir -p $image_hobjects_dir
104   fi
105
106   pushd $database >/dev/null
107   local  pattern="\.[0-9a-f]+__"$hex_snap_id"_[0-9A-F]{8}__"$hex_pool_id
108   >$image_hobjects_raw
109   grep -r -E $dump_image_prefix""$pattern * >$image_hobjects_raw
110   if [ ! -s $image_hobjects_raw ];then
111     echo "$func: image snap [ $image_prefix"$psuffix" ] is empty"
112     return 1 #no data available
113   fi
114   popd >/dev/null
115
116   local offset_dir_temp=$images_raw_dir/$image_prefix"$fsuffix""_dir_temp"
117   rm -rf $offset_dir_temp
118   mkdir -p $offset_dir_temp
119
120   echo "gather hobjects from database: snapid=$snap_id ..."
121
122   # format: ceph2:/var/lib/ceph/osd/ceph-1/current/2.d3_head/rb.0.1293.6b8b4567.000000000002__head_FB425CD3__2
123   local tmp_image=$offset_dir_temp/tmpimage.$$$$
124   >$tmp_image
125   cat $image_hobjects_raw | 
126   awk -F ':' '
127   BEGIN {
128     pg_coll="'$pg_coll'"
129     tmp_image="'$tmp_image'"
130     osd_host_mapping="'$osd_host_mapping'"
131     snapid="'$snap_id'"
132   }{ 
133       # $2 = /var/lib/ceph/osd/ceph-1/current/2.d3_head/rb.0.1293.6b8b4567.000000000002__head_FB425CD3__2
134
135       split($2, arr1, "/current/");   # {/var/lib/ceph/osd/ceph-1/, 2.d3_head/rb.0.1293.6b8b4567.000000000002__head_FB425CD3__2}
136       split(arr1[2], arr2, "/");     # {2.d3_head, rb.0.1293.6b8b4567.000000000002__head_FB425CD3__2} 
137       split(arr2[1], arr3, "_head"); # {2.d3,} 
138
139       hobject=$2;
140       data_path=arr1[1];
141       gsub(/\\u/, "\\\\\\\\u", hobject); # dump backslash to delay escape (\ -> \\)
142       "awk \"\\$1 == \\\""$1"\\\" {print \\$2}\" "osd_host_mapping" | head -n 1" | getline node
143       pgid = arr3[1];
144
145       len=length(arr2);
146       offset_hobject=arr2[len]             # rb.0.1293.6b8b4567.000000000002__head_FB425CD3__2
147       split(offset_hobject, offarr1, "."); # {rb, 0, 1293, 6b8b4567, 000000000002__head_FB425CD3__2}
148       len1=length(offarr1) 
149       offset_p=offarr1[len1]               # 000000000002__head_FB425CD3__2
150       split(offset_p, offarr2, "__");      # {000000000002, head_FB425CD3, 2}
151       offset=offarr2[1];                   # 000000000002
152
153       system("echo -n \""node" "pgid" "hobject" "offset" "snapid" \" >>"tmp_image);
154       #system("echo -n \""node" "pgid" "hobject" "offset" "snapid" \"");
155       #print node" "pgid" "hobject" "offset" "snapid
156       
157       # find pg_epoch from pg_coll database
158       system("awk  \"\\$1 == \\\""node"\\\" && \\$2 == \\\""pgid"\\\" && \\$4 == \\\""data_path"\\\" {print \\$3}\" "pg_coll" >>"tmp_image);
159       #system("awk  \"\\$1 == \\\""node"\\\" && \\$2 == \\\""pgid"\\\" && \\$4 == \\\""data_path"\\\" {print \\$3}\" "pg_coll);
160   }'
161
162   local sort_image=$offset_dir_temp/sortimage.$$$$
163   >$sort_image
164   sort -t ' ' -k 4.1,4 -k 6.1nr -k 1.1,1 $tmp_image >$sort_image
165   sort -t ' ' -k 4.1,4 -u $sort_image > $image_hobjects_stable
166   
167   #rm -rf $offset_dir_temp
168   return 0
169 }
170
171 function gather_hobject_nosnap()
172 {
173   gather_hobject_common $1 $2
174 }
175
176 function gather_hobject_snap()
177 {
178   gather_hobject_common $1 $2 $3
179 }
180
181 # select the max pg_epoch item of the same $field
182 # if no same $field, choose the first 
183 # format : "node $field pg_epoch"
184 function choose_epoch()
185 {
186   cat $1|sort -t ' ' -k 3.1,3nr -k 2.1,2n |head -n 1;
187 }
188
189 # lookup image info , after scatter_node_jobs & gather_node_infos
190 function lookup_image()
191 {
192   local func="lookup_image"
193   if [ $# -lt 2 ];then
194     echo "$func: parameters error <pool_id> <image_name> [<snap_name>]"
195   fi
196   local pool_id=$1
197   local image_name=$2
198   local snap_name=$3
199   pool_id=$((pool_id))
200   echo -e "$func: pool_id = $pool_id\timage_name = $image_name\tsnap_name = $snap_name"
201   if [ $pool_id -lt 0 ];then
202     echo "$func: pool_id must great than zero"
203     exit
204   fi
205   local hex_pool_id=`printf "%x" $pool_id`
206   input_image $image_name
207   local node=
208   local item=/tmp/item.$$$$
209   local img_name=`dump_backslash $image_name`
210
211   local image_format=0
212   local image_id_hobject=
213   local image_header_hobject=
214   local result=/tmp/tmp_result.$$$$
215   local res1=/tmp/tmp_res1.$$$$
216   local res2=/tmp/tmp_res2.$$$$
217   local data_path=
218
219   # image format v1
220   {
221     cat $image_coll_v1|grep -E "/$img_name\.rbd__head_[0-9A-F]{8}__$hex_pool_id" >$res1
222     if [ -s $res1 ];then
223       echo -n "$func: rbd_header_hobject = "
224       choose_epoch $res1| tee $item
225       #choose_epoch $res1 > $item
226       
227       if [ -e $item ];then
228         node=`cat $item|awk '{print $1}'`
229         image_header_hobject=`cat $item|awk '{print $2}'`
230         if [ "$node"x = ""x ];then
231           echo "$func: v1 node is NULL"
232           exit
233         fi
234         if [ "$image_header_hobject"x = ""x ];then
235           echo "$func: v1 image_header_hobject is NULL"
236           exit
237         fi
238         rm -f $item
239       fi
240
241       image_format=1
242       echo -e "image_name:\t$image_name_in"
243       echo -e "image_format:\t$image_format"
244       data_path=`echo $image_header_hobject|awk -F "/current" '{print $1}'`
245  
246       >$result
247       cmds="bash $job_path/osd_job do_image_metadata_v1 $data_path `dump_backslash $image_header_hobject` $snap_name" 
248       ssh $ssh_option $node $cmds | tee $result
249     fi
250   }
251
252   # image format v2
253   {
254     cat $image_coll_v2|grep -E "/rbd\\\\uid\."$img_name"__head_[0-9A-F]{8}__$hex_pool_id" >$res2
255     if [ -s $res2 ];then
256       echo -n "$func: rbd_id_hobject = "
257       choose_epoch $res2 | tee $item
258       #choose_epoch $res2 > $item
259
260       if [ -e $item ];then
261         node=`cat $item|awk '{print $1}'`
262         image_id_hobject=`cat $item|awk '{print $2}'`
263         if [ "$node"x = ""x ];then
264           echo "$func: v2 node is NULL(to get image_id_hobject)"
265           exit
266         fi
267         if [ "$image_id_hobject"x = ""x ];then
268           echo "$func: v2 image_id_hobject is NULL"
269           exit
270         fi
271         rm -f $item
272       fi
273
274       check_osd_process $node
275       image_format=2
276       
277       local tid=/tmp/image_id.$$$$
278       data_path=`echo $image_id_hobject|awk -F "/current" '{print $1}'`
279       >$tid
280       cmds="bash $job_path/osd_job do_image_id $data_path `dump_backslash $image_id_hobject`" 
281       ssh $ssh_option $node $cmds > $tid
282
283       local image_id=`cat $tid`
284       rm -f $tid
285
286       #get image_header_hobject
287       pushd $database >/dev/null
288       local pattern="header\."$image_id"__head_[0-9A-F]{8}__$hex_pool_id"
289       local tcoll=/tmp/tmp_image_head_coll.$$$$
290
291       # hostname(by command hostname) in $pg_coll  maybe different from hostname in tcoll(input by user) 
292       # t_host:        hostname read from config file ($tcoll)
293       # t_host_remote: $(hostname) on osd node ($pg_coll)
294       grep -r -E $pattern * >$tcoll
295       popd >/dev/null
296
297       local t_host=(`cat $tcoll|awk -F ":" '{print $1}'`)
298       local t_pgid=(`cat $tcoll|awk -F ":" '{print $2}'|sed -n 's/.*\/\([0-9a-fA-F]\+\.[0-9a-fA-F]\+\)_head\/.*/\1/p'`)
299       local t_hobject=(`cat $tcoll|awk -F ":" '{print $2}'`)
300       local t_data_path=(`cat $tcoll|awk -F ":" '{split($2, arr, "/current/"); print arr[1];}'`)
301       rm -f $tcoll
302       declare -a t_host_remote
303
304       #if there is no failed pg migration, number of t_host is replica num
305       #replica num : 3, 4, 5 ...
306       local t_hostname=/tmp/t_hostname.$$$$
307       for ((i=0; i<${#t_host[*]}; i++))
308       do
309         ssh $ssh_option ${t_host[$i]} "hostname" >$t_hostname
310         if [ $? != 0 ];then
311           echo "$func: ${t_host[$i]} get host_remote failed"
312           exit
313         fi
314         t_host_remote[$i]=`cat $t_hostname`     
315       done
316       rm -f $t_hostname
317
318       local t_item=/tmp/tmp_item.$$$$
319       local tmp_item=/tmp/tmp_tmp_item.$$$$
320       
321       >$tmp_item
322       for ((i=0; i<${#t_host_remote[*]}; i++ ))
323       do
324         local node=${t_host_remote[$i]}
325         local pgid=${t_pgid[$i]}
326         awk '$1 == "'"$node"'" && $2 == "'"$pgid"'" {print}' $pg_coll >>$tmp_item
327       done
328
329       # t_item: <remote_hostname> <pgid> <epoch> <data_path>
330       sort -u $tmp_item >$t_item
331       rm -f $tmp_item
332
333       local entry=`choose_epoch $t_item` #t_host_remote
334       rm -f $t_item
335
336       node=`echo $entry|awk '{print $1}'`
337       data_path=`echo $entry|awk '{print $4}'`
338       if [ "$node"x = ""x ];then
339         echo "$func: v2 node is NULL (to get image_header_hobject)"
340         exit
341       fi
342
343       for ((i=0; i<${#t_host_remote[*]}; i++))
344       do
345         if [ "${t_host_remote[$i]}"x = "$node"x ] && [ "${t_data_path[$i]}"x = "$data_path"x ];then
346           image_header_hobject=${t_hobject[$i]}
347           break
348         fi
349       done
350       
351       if [ "$image_id_hobject"x = ""x ];then
352         echo "$func: v2 image_header_hobject is NULL"
353         exit
354       fi
355
356       check_osd_process $node
357      
358       echo "$func: rbd_header_hobject = $node $image_header_hobject"
359       echo -e "image_name:\t$image_name_in"
360       echo -e "image_format:\t$image_format"
361
362       #data_path=`echo $image_header_hobject|awk -F "/current" '{print $1}'`
363       >$result
364       cmds="bash $job_path/osd_job do_image_metadata_v2 $data_path $image_id `dump_backslash $image_header_hobject` $snap_name" 
365       ssh $ssh_option $node $cmds | tee $result
366     fi
367   }
368
369   if [ ! -s $result ];then
370     echo "$func: $image_name_in not exists" 
371     exit
372   fi
373   
374   # to assign value to global variable
375   db_image_prefix=`cat $result|awk '/^(object_prefix|block_name):/{print $2}'`
376   if [ "$db_image_prefix"x = ""x ];then
377     echo "$func: image_prefix is NULL"
378     exit
379   fi
380
381   db_image_size=`cat $result|awk '/^image_size:/{print $2}'`
382   db_order=`cat $result|awk '/^order:/{print $2}'`
383   if [ "$snap_name"x != ""x ];then
384     db_snap_id=`cat $result|awk '/^snapshot:/{print $2}'`
385     if [ "$db_snap_id"x = ""x ];then
386       echo "$func: $image_name_in@$snap_name NOT EXISTS"
387       exit
388     fi
389     db_snap_image_size=`cat $result|awk '/^snapshot:/{print $4}'`
390   else
391     #save snaplist
392     local image_snaplist=$images/pool_$pool_id/$image_name_in/@snaplist
393     local image_dir=$images/pool_$pool_id/$image_name_in
394     if [ ! -e $image_dir ];then
395       mkdir -p $image_dir
396     fi
397     cat $result|awk '/^snapshot:/{print $2" "$3" "$4}' >$image_snaplist
398   fi
399   found=1
400   rm -f $result
401 }
402
403 function list_images()
404 {
405    echo "=============== format =============="
406    echo "format: <pool_id>/<image_name>"
407    echo "================ v1: ================"
408    #sed -n 's/\(.*\)\/\(.*\)\.rbd__\(.*\)/\2/p' $image_coll_v1|sort -u|sed -e 's/\\u/_/g'
409    sed -n 's/.*\/\(.*\)\.rbd__head_[0-9A-F]\{8\}__\([0-9a-f]\+\).*/\2 \1/p' $image_coll_v1|sort -u|awk '{print strtonum("0x"$1)"/"$2;}'|sed -e 's/\\u/_/g'
410    echo "================ v2: ================"
411    #sed -n 's/\(.*\)\/rbd\\uid.\(.*\)__\(head.*\)/\2/p' $image_coll_v2|sort -u|sed 's/\\u/_/g'
412    sed -n 's/.*\/rbd\\uid.\(.*\)__head_[0-9A-F]\{8\}__\([0-9a-f]\+\).*/\2 \1/p' $image_coll_v2|sort -u|awk '{print strtonum("0x"$1)"/"$2}'|sed 's/\\u/_/g'
413 }
414
415 # lookup image metadata
416 # and 
417 # collect hobjects of image with the latest pg epoch
418 function discover_image_nosnap()
419 {
420   local func="discover_image_nosnap"
421   echo "$func ..."
422   local pool_id=$1
423   local image_name=$2
424   pool_id=$(($pool_id))
425   lookup_image $pool_id $image_name # assign $image_prefix
426   gather_hobject_nosnap $pool_id $db_image_prefix 
427   if [ $? -ne 0 ];then
428     exit
429   fi
430   local image_hobjects_stable_nosnap=$images/pool_$pool_id/$db_image_prefix/$db_image_prefix"_head"
431   local image_hobjects_dir=$images/pool_$pool_id/$image_name_in
432   if [ ! -e $image_hobjects_dir ];then
433     mkdir -p $image_hobjects_dir
434   fi
435   # mv image_prefix to image_name
436   mv $image_hobjects_stable_nosnap $image_hobjects_dir/$image_name_in
437   rm -rf $images/pool_$pool_id/$db_image_prefix
438 }
439
440 # get the offset snapid object 
441 # if there is no object, choose the smallest snapid which is greater than current snapid
442 function get_object_clone()
443 {
444   local func="get_object_clone"
445   if [ $# -lt 4 ];then
446     exit
447   fi
448
449   local object_offset_string=$1
450   local snapid=$2
451   local snaplist_path=$3
452   local snapset_output_dir=$4
453
454   # snapid in desc
455   local snap_coll_arr=(`
456   cat $snaplist_path|awk '{ if ($1 >= '"$snapid"') print "'"$snapset_output_dir"'/@"$1}'`) 
457
458   local hex_snapid=`printf "%x" $snapid`
459   pushd $snapset_output_dir >/dev/null
460   # get object with the smallest snapid greater than current snapid
461   awk '$4 == "'"$object_offset_string"'" && $5 >= '$snapid' {print}' `echo ${snap_coll_arr[@]}` |tail -n 1
462   popd >/dev/null
463 }
464
465 # gather hobject for each snapid
466 function gen_snapset_hobject()
467 {
468   local func="gen_image_snapset"
469   echo "$func ..."
470   if [ $# -lt 4 ];then
471     echo "$func: parameters: <pool_id> <image_prefix> <snaplist_path> <snapset_output_dir>"
472     exit
473   fi
474   local pool_id=$1
475   local image_prefix=$2
476   local snaplist_path=$3
477   local snapset_output_dir=$4
478   pool_id=$(($pool_id))
479   OIFS=$IFS
480   IFS=$'\n'
481   local snaparr=(`cat $snaplist_path`) 
482   # gather hobject for each snapshot
483   trap 'echo $func failed; exit;' INT HUP
484   for line in ${snaparr[@]}
485   do
486     OOIFS=$IFS
487     IFS=$' '
488     local field=(`echo $line`)
489     local snapid=${field[0]}
490     local image_hobjects_stable_snap=$images/pool_$pool_id/$image_prefix/$image_prefix"_"$snapid
491     local image_snap=$snapset_output_dir/@$snapid
492     gather_hobject_snap $pool_id $image_prefix $snapid 
493     local res=$?
494     if [ $res -ne 0 ];then
495       touch $image_snap
496     else 
497       mv $image_hobjects_stable_snap $image_snap
498     fi
499     IFS=$OOIFS
500   done
501   IFS=$OIFS
502 }
503
504 # lookup image metadata and get snapid hobjects
505 function discover_image_snap()
506 {
507   local func="discover_image_snap"
508   echo "$func ..."
509   if [ $# -lt 3 ];then
510     echo "$func: paramters: <pool_id> <image_name> [<snap_name>]"
511     exit
512   fi
513   local pool_id=$1
514   local image_name=$2
515   local snap_name=$3
516   pool_id=$(($pool_id))
517   #mkdir -p $images/$image_prefix
518   lookup_image $pool_id $image_name $snap_name # input image_name and snap_name to lookup metadata and snap_id
519   if [ "$db_snap_id"x = ""x ];then
520     echo "$func: lookup image failed to gen snapid"
521     exit
522   fi
523   local image_hobjects_dir_prefix=$images/pool_$pool_id/$db_image_prefix
524   local image_nosnap=$images/pool_$pool_id/$image_name_in 
525   #check if image nosnap recovered
526   if [ ! -s $image_nosnap ];then
527     echo "$func: please recover image nosnap before recover with snap"
528     rm -rf $image_hobjects_dir_prefix
529     exit
530   fi
531   local image_hobject_dir=$images/pool_$pool_id/$image_name_in
532   local image_snap_hobject=$image_hobject_dir/$image_name_in@$db_snap_id
533   local image_snap_hobject_head=$image_hobject_dir/$image_name_in@$db_snap_id@head
534   local image_snaplist=$image_hobject_dir/@snaplist
535   local image_snapset_dir=$image_hobject_dir/@snapset_dir
536   local image_head=$image_hobject_dir/$image_name_in
537   if [ ! -e $image_hobject_dir ];then
538     mkdir -p $image_hobject_dir
539   fi
540   # only gen snapset one time
541   if [ ! -e $image_snapset_dir ];then
542     mkdir -p $image_snapset_dir
543     gen_snapset_hobject $pool_id $db_image_prefix $image_snaplist $image_snapset_dir  
544    
545   fi
546
547   echo "$func: will get object clone ..."
548   >$image_snap_hobject
549   >$image_snap_hobject_head
550
551   trap 'echo $func failed; exit;' INT HUP
552   # get each offset 's snapid hobject
553   while read line
554   do
555     #echo $line
556     OOIFS=$IFS
557     IFS=$' '
558     local field=(`echo $line`)
559     local offset_string=${field[3]}
560     IFS=$OOIFS
561     local entry=`get_object_clone $offset_string $db_snap_id $image_snaplist $image_snapset_dir` 
562     if [ "$entry"x != ""x ];then
563       echo $entry >> $image_snap_hobject
564       echo `dump_backslash $line` >> $image_snap_hobject_head
565     fi
566   done < $image_head
567   rm -rf $image_hobjects_dir_prefix
568 }
569
570 # after discover_image_nosnap
571 # collect objects from osds one by one in sequence
572 function copy_image_nosnap_single_thread()
573 {
574   local func="copy_image_nosnap_single_thread"
575   echo "$func ..."
576   if [ $# -lt 3 ];then
577     echo "$func: parameters: <pool_id> <image_hobjects> <backup_dir>"
578     exit
579   fi
580   local pool_id=$1
581   local image_hobjects=$2
582   local backup_dir=$3
583   pool_id=$(($pool_id))
584
585   # make sure lookup_image first
586   if [ $found = 0 ];then
587     echo "$func: image not found, maybe forget to discover_image"
588     exit
589   fi
590   if [ ! -e $backup_dir ];then
591     mkdir -p $backup_dir
592   fi
593
594   local image_dir=$backup_dir/pool_$pool_id/$image_name_in
595   local image_file=$image_dir/$image_name_in
596   local CURRENT=$image_dir/@CURRENT
597   local LOCK=$image_dir/@LOCK
598   if [ ! -e $image_dir ];then
599     mkdir -p $image_dir
600   fi
601   if [ -e $LOCK ];then
602     echo "$func: $LOCK is locked by other process"
603     exit
604   else
605     touch $LOCK
606   fi
607
608   >$image_file
609   truncate -s $db_image_size $image_file 
610   echo "head">$CURRENT
611
612   local count=$(($db_image_size >> $db_order))
613   local start=`cat $image_hobjects|head -n 1|awk '{print $4}'`
614   local end=`cat $image_hobjects|tail -n 1|awk '{print $4}'`
615   local entry_count=`cat $image_hobjects|wc -l`
616
617   local char_bits=$((`echo $start|wc -c` -1 ))
618   local format="%0"$char_bits"x"
619   
620   local expect_start=`printf $format 0`
621   local expect_end=`printf $format $(($count -1 ))`  
622
623   echo -e "object_count\t$entry_count"
624   echo -e "expect\t\t[$expect_start ~ $expect_end] count:$count"
625   echo -e "range\t\t[$start ~ $end] count:$entry_count"
626
627   local icount=0
628   local istart=
629   local iend=
630   local percent=
631   
632   trap 'echo $func failed; exit;' INT HUP
633   local unit=$((1<<$db_order))
634   while read line
635   do
636   {
637     icount=$(($icount+1))
638     node=`echo $line|awk '{print $1}'` 
639     hobject=`echo $line|awk '{print $3}'` 
640     offset=`echo $line|awk '{print $4}'`
641     off=$((16#$offset))
642     if [ $icount = 1 ];then
643       istart=$offset
644     fi
645     hobject=`dump_backslash $hobject`
646     iend=$offset
647     sshcmd="cat $hobject"
648     ssh $ssh_option $node $sshcmd < /dev/null | dd of=$image_file bs=$unit seek=$off conv=notrunc 2>/dev/null
649     percent=`echo "scale=3; 100*$icount/$entry_count"|bc`
650     tput sc  #record current cursor
651     echo -n -e "complete\t[$istart ~ $iend] $icount/$entry_count ==> "$percent"%"
652     if [ $icount != $entry_count ];then
653       tput rc # backport most recent cursor
654     fi
655   }
656   done < $image_hobjects
657
658   echo
659   echo -n "size: "
660   ls -lh $image_file|awk '{print  $5"\t"$9}'
661   echo -n "du:   "
662   du -h $image_file
663   #unlock
664   rm -f $LOCK
665 }
666
667
668 # ssh copy snap_object & head_object from osd to admin node
669 # copy all snapshot objects 
670 # and 
671 # all head objects which have the same offset as snapshot objects 
672 function collect_image_snap_objects()
673 {
674   local func="collect_image_snap_objects"
675   #$1=backup_dir, $2=snap_name, $3=snap_hobjects, $4=head_hobjects
676   if [ $# -lt 6 ];then
677     echo "$func: parameters: <pool_id> <image_name> <snap_id> <snap_hobjects> <head_hobjects> <backup_dir>"
678     exit
679   fi  
680
681   local pool_id=$1
682   local image_name=$2
683   local snap_id=$3
684   local snap_hobjects=$4 #snap hobjects info
685   local head_hobjects=$5 #head hobjects info
686   local backup_dir=$6
687   pool_id=$(($pool_id))
688
689   local head_dir=$backup_dir/pool_$pool_id/$image_name/@head
690   local snap_dir=$backup_dir/pool_$pool_id/$image_name/@$snap_id
691   local CURRENT=$backup_dir/pool_$pool_id/$image_name/@CURRENT
692  
693   if [ ! -e $head_dir ];then
694     mkdir -p $head_dir
695   fi
696   if [ ! -e $snap_dir ];then
697     mkdir -p $snap_dir
698   fi
699
700   local snap_node= #osd node
701   local snap_hobject= #hobject path with snapid on osd
702   local snap_offset=
703   local snap_filename=
704
705   local head_node=
706   local head_hobject=
707   local head_offset=
708   local head_filename=
709
710   # ignore if there is no object in snapshot(empty )
711   if [ ! -s $snap_hobjects ];then
712     echo "$func: $snap_hobjects is empty"
713     return 0
714   fi
715   local start=`head -n 1 $snap_hobjects|awk '{print $4}'`
716   local end=`tail -n 1 $snap_hobjects|awk '{print $4}'`
717   local entry_count=`cat $snap_hobjects|wc -l`
718   if [ $((16#$first_offset)) -gt $((16#$last_offset)) ];then
719     echo "$func: $snap_hobjects not sorted"
720     return 1
721   fi
722
723   # just assert if ignored empty snapshot
724   if [ "$start"x = ""x ] || [ "$end"x = ""x ];then
725     return 1
726   fi
727  
728   # speed up copy snapshot
729   # lookup the coresponding head hobject of snap hobject
730   # use command: grep <offset> <head hobjects>
731   # 
732   # eg.
733   # head hobjects: (32 objects, snapid = uint64(-2) = 18446744073709551614)
734   # ceph1 29.4d /var/lib/ceph/osd/ceph-0/current/29.4d_head/rb.0.1c414.6b8b4567.000000000000__head_EC2C1C4D__1d 000000000000 18446744073709551614 869
735   # ceph1 29.8c /var/lib/ceph/osd/ceph-0/current/29.8c_head/rb.0.1c414.6b8b4567.000000000001__head_0F439A8C__1d 000000000001 18446744073709551614 867
736   # ceph1 29.6a /var/lib/ceph/osd/ceph-0/current/29.6a_head/rb.0.1c414.6b8b4567.000000000002__head_FC55706A__1d 000000000002 18446744073709551614 869
737   # ceph1 29.8b /var/lib/ceph/osd/ceph-0/current/29.8b_head/rb.0.1c414.6b8b4567.000000000003__head_20A6328B__1d 000000000003 18446744073709551614 869
738   # ceph2 29.75 /var/lib/ceph/osd/ceph-1/current/29.75_head/rb.0.1c414.6b8b4567.000000000004__head_AC5ADB75__1d 000000000004 18446744073709551614 867
739   # ceph2 29.23 /var/lib/ceph/osd/ceph-1/current/29.23_head/rb.0.1c414.6b8b4567.000000000005__head_1FDEA823__1d 000000000005 18446744073709551614 867
740   # ......
741   # ceph1 29.34 /var/lib/ceph/osd/ceph-0/current/29.34_head/rb.0.1c414.6b8b4567.00000000001f__head_52373734__1d 00000000001f 18446744073709551614 869
742   #
743   # snap hobjects: (3 objects, snapid >= 29)
744   # ceph1 29.8c /var/lib/ceph/osd/ceph-0/current/29.8c_head/rb.0.1c414.6b8b4567.000000000001__1f_0F439A8C__1d 000000000001 31 867
745   # ceph1 29.6a /var/lib/ceph/osd/ceph-0/current/29.6a_head/rb.0.1c414.6b8b4567.000000000002__1e_FC55706A__1d 000000000002 30 869
746   # ceph1 29.8b /var/lib/ceph/osd/ceph-0/current/29.8b_head/rb.0.1c414.6b8b4567.000000000003__1d_20A6328B__1d 000000000003 29 869
747   #
748   # so find out offset in head hobjects line number:
749   # snap hobjects: 000000000001 ---> head hobjects: 2 (n1)
750   # snap hobjects: 000000000003 ---> head hobjects: 4 (n2)
751   # 
752   # finally , grep range from the whole file [1 ~ N] shranked to part of file [n1 ~ n2]
753   # the worst case : [n1 ~ n2] = [1 ~ N], means no shranking
754
755   # get the line number of the start offset in head hobjects
756   local n1=`grep -n $start $head_hobjects|head -n 1|cut -d ":" -f 1`
757   # get the line number of the end offset in head hobjects
758   local n2=`grep -n $end $head_hobjects|head -n 1|cut -d ":" -f 1`
759  
760   local icount=0
761   local istart=
762   local iend=
763   local percent=
764
765   OIFS=$IFS
766   IFS=$'\n'
767
768   #assume file:snap_hobjects is not very large, and can be loaded into memory
769   local snap_arr=(`cat $snap_hobjects`)
770   local snap_tmp=/tmp/snaptmp.$$$$
771
772   # snap_tmp: 
773   # consists of snap hobject or head hobject
774   # select lineno range: [n1 ~ n2]
775   head -n $n2 $head_hobjects|tail -n $(($n2-$n1+1)) >$snap_tmp 
776
777   echo "copy image snap/head objects from osd ..."
778   echo -e "object_count\t$entry_count"
779   echo -e "range\t\t[$start ~ $end] count:$entry_count"
780
781   trap 'echo $func failed; exit;' INT HUP
782   for line in ${snap_arr[*]}
783   do
784     icount=$(($icount+1))    
785
786     OOIFS=$IFS
787     IFS=$' '
788
789     local arr=(`echo $line`)
790     snap_node=${arr[0]}
791     snap_hobject=${arr[2]}
792     snap_offset=${arr[3]}
793     snap_filename=$snap_dir/$snap_offset
794
795     if [ $icount = 1 ];then
796       istart=$snap_offset
797     fi
798     iend=$snap_offset
799
800     #lookup corresponding head hobject of snap hobject
801     local res=`grep $snap_offset $snap_tmp|head -n 1` 
802     if [ "$res"x = ""x ];then
803       echo "$func: image object[ $snap_offset ] missing"
804       exit
805     fi
806     
807     local arr2=(`echo $res`)
808     head_node=${arr2[0]}
809     head_hobject=${arr2[2]}
810     head_offset=${arr2[3]}
811     head_filename=$head_dir/$head_offset
812
813     # just copy object(snap/head) if it does not exist
814     if [ ! -e $snap_filename ];then
815       ssh $ssh_option $snap_node "cat $snap_hobject" > $snap_filename 
816     fi
817     if [ ! -e $head_filename ];then
818       ssh $ssh_option $head_node "cat $head_hobject" > $head_filename 
819     fi
820     IFS=$OOIFS
821
822     percent=`echo "scale=3; 100*$icount/$entry_count"|bc`
823     tput sc  #record current cursor
824     echo -n -e "complete\t[$istart ~ $iend] $icount/$entry_count ==> "$percent"%"
825     if [ $icount != $entry_count ];then
826       tput rc # backport most recent cursor
827     fi
828   done
829   echo
830   IFS=$OIFS 
831   rm -f $snap_tmp
832   return 0
833 }
834
835 # copy all snap objects and corresponding head objects from osds
836 # in single process
837 function copy_image_snap_single_thread()
838 {
839   local func="copy_image_snap_single_thread"
840   if [ $# -lt 6 ];then
841     echo "$func: parameters: <pool_id> <image_name> <snap_id> <snap_hobjects> <head_hobjects> <backup_dir>" 
842     exit
843   fi
844   local pool_id=$1
845   local image_name=$2
846   local snap_id=$3
847   local snap_hobjects=$4
848   local head_hobjects=$5
849   local backup_dir=$6
850   pool_id=$(($pool_id))
851
852   local CURRENT=$backup_dir/pool_$pool_id/$image_name/@CURRENT
853   local LOCK=$backup_dir/pool_$pool_id/$image_name/@LOCK
854   #lock
855   if [ -e $LOCK ];then
856     echo "$func: $LOCK is locked by other process"
857     exit
858   else
859     touch $LOCK
860   fi
861   collect_image_snap_objects $pool_id $image_name $snap_id $snap_hobjects $head_hobjects $backup_dir
862   #unlock
863   rm -f $LOCK
864 }
865
866 # after all snap objects and necessary head objects are copied,
867 # just pick appropriate head objects and snap objects and write them to image
868 # in order to rollback image to snapshot
869 #
870 # init: image is created by copy_image_nosnap_single_thread firstly
871 #
872 # all output include 3 parts:
873 # <image>  <head objects>      <snap objects>
874
875 #          head objects1  ---  snap1 objects
876 #          head objects2  ---  snap2 objects
877 #  image   head objects3  ---  snap3 objects
878 #          ......
879 #          head objectsN  ---  snapN objects
880 #
881 # how to rollback:
882 # firstly rollback to head, secondly write <snapX objects>
883 # head  = <image> + <head objects>
884 # snap1 = <image> + <head objects> + <snap1 objects>
885 # snap2 = <image> + <head objects> + <snap2 objects>
886 # snap3 = <image> + <head objects> + <snap3 objects>
887 # ......
888 # snapN = <image> + <head objects> + <snapN objects>
889
890 # improve rollback:
891 # there is intersection of head objects and snapX objects, if snapX objects are not empty
892 # and need to deduplicate the intersection.
893 # deduplicate steps:
894 # - get difference set of head objects and snapX objects
895 # - write the difference set objects to image
896 # - write the snapX objects to image
897 function rollback_image_snap()
898 {
899   local func="rollback_image_snap"
900   
901   echo "$func ..."
902   
903   trap 'echo $func failed; exit;' INT HUP
904   if [ $# -lt 6 ];then
905     echo "$func: parameters <pool_id> <image_name> <snap_id> <snap_object_dir> <backup_dir> <image_unit>"
906     exit
907   fi
908   local pool_id=$1
909   local image_name=$2
910   local snap_id=$3
911   local snap_object_dir=$4
912   local backup_dir=$5
913   local image_unit=$6
914
915   local need_diff_set=0
916
917   local image_path=$backup_dir/pool_$pool_id/$image_name/$image_name
918   local head_object_dir=$backup_dir/pool_$pool_id/$image_name/@head
919   local CURRENT=$backup_dir/pool_$pool_id/$image_name/@CURRENT
920   local LOCK=$backup_dir/pool_$pool_id/$image_name/@LOCK
921   if [ -e $LOCK ];then
922     echo "$func: $LOCK is locked by other process"
923     exit
924   else
925     touch $LOCK
926   fi
927   if [ $snap_id -ne -2 ];then
928     echo $snap_id > $CURRENT
929   else
930     echo "head" > $CURRENT
931   fi 
932
933   if [ ! -e $snap_object_dir ];then
934     return 0
935   fi
936
937   if [ "$snap_object_dir"x != "$head_object_dir"x ];then
938     echo "$func: need to compute diff_set of head"
939     need_diff_set=1
940   else
941     echo "$func: NO diff_set"
942     need_diff_set=0
943   fi
944
945   local entry_count=0
946   local start=
947   local end=
948   local offset=
949   local icount=0
950   local istart=
951   local iend=
952   local percent=
953
954   local snap_objects=
955   local head_objects=
956   local diff_set=
957
958   snap_objects=(`ls $snap_object_dir`)
959
960   # if need to compute difference set of head_objects and snap_objects
961   if [ $need_diff_set -ne 0 ];then
962     head_objects=(`ls $head_object_dir`) 
963
964     #get the difference set: ( head_objects - snap_objects )
965     diff_set=(`
966     sort -m <(echo ${head_objects[@]}|xargs -n 1 echo) <(echo ${snap_objects[@]}|xargs -n 1 echo) \
967         <(echo ${snap_objects[@]}|xargs -n 1 echo) |uniq -u`) 
968
969     # copy diff_set of head object to image
970     pushd $head_object_dir >/dev/null
971
972     echo "$func: copy diff_set head objects ..."
973     entry_count=${#diff_set[@]}  
974     start=${diff_set[0]}
975     end=
976     if [ $entry_count -gt 0 ];then
977       end=${diff_set[$(($entry_count - 1))]}
978     fi
979     offset=
980     icount=0
981     istart=
982     iend=
983     percent=
984
985     echo -e "object_count\t$entry_count"
986     echo -e "range\t\t[$start ~ $end] count:$entry_count"
987
988     for object in ${diff_set[@]}
989     do
990       icount=$(($icount+1))
991       if [ $icount = 1 ];then
992         istart=$object
993       fi
994       iend=$object
995
996       local offset=$((16#$object))
997       dd if=$object of=$image_path bs=$image_unit seek=$offset conv=notrunc 2>/dev/null
998
999       percent=`echo "scale=3; 100*$icount/$entry_count"|bc`
1000       tput sc  #record current cursor
1001       echo -n -e "complete\t[$istart ~ $iend] $icount/$entry_count ==> "$percent"%"
1002       if [ $icount != $entry_count ];then
1003         tput rc # backport most recent cursor
1004       fi
1005     done
1006     if [ $entry_count -gt 0 ];then
1007       echo
1008     fi
1009     popd >/dev/null
1010
1011     if [ $snap_id -ne -2 ];then
1012       echo -e "$image_name already rollback diff_set: (head - snap)"
1013     fi
1014   fi
1015   
1016   # copy snap object to image
1017   pushd $snap_object_dir >/dev/null 
1018
1019   if [ $need_diff_set -ne 0 ];then
1020     echo "$func: copy snap objects ..."
1021   else
1022     echo "$func: copy head objects ..."
1023   fi
1024   entry_count=${#snap_objects[@]}  
1025   start=${snap_objects[0]}
1026   end=
1027   if [ $entry_count -gt 0 ];then
1028     end=${snap_objects[$(($entry_count - 1))]}
1029   fi
1030   offset=
1031   icount=0
1032   istart=
1033   iend=
1034   percent=
1035
1036   echo -e "object_count\t$entry_count"
1037   echo -e "range\t\t[$start ~ $end] count:$entry_count"
1038
1039   for object in ${snap_objects[@]}
1040   do
1041     icount=$(($icount+1))
1042     if [ $icount = 1 ];then
1043       istart=$object
1044     fi
1045     iend=$object
1046
1047     local offset=$((16#$object))
1048     dd if=$object of=$image_path bs=$image_unit seek=$offset conv=notrunc 2>/dev/null
1049
1050     percent=`echo "scale=3; 100*$icount/$entry_count"|bc`
1051     tput sc  #record current cursor
1052     echo -n -e "complete\t[$istart ~ $iend] $icount/$entry_count ==> "$percent"%"
1053     if [ $icount != $entry_count ];then
1054       tput rc # backport most recent cursor
1055     fi
1056   done
1057   if [ $entry_count -gt 0 ];then
1058     echo
1059   fi
1060   popd >/dev/null
1061
1062   rm -f $LOCK
1063   if [ $snap_id -ne -2 ];then
1064     echo "$image_name rollback to snapid: $snap_id"
1065   else
1066     echo "$image_name rollback to head"
1067   fi
1068 }
1069
1070 function recover_image()
1071 {
1072   local func="recover_image"
1073   echo "$func ..."
1074   
1075   if [ $# -lt 3 ];then
1076     echo "$func: paramters: <pool_id> <image_name> <snap_name> [<backup_dir>]"
1077     exit
1078   fi
1079
1080   local pool_id=$1
1081   local img_name=$2
1082   local snap_name=$3
1083   local backup_dir=$4
1084   pool_id=$(($pool_id))
1085   if [ "$snap_name"x = "@"x ];then
1086     snap_name=
1087   fi
1088   if [ "$backup_dir"x = ""x ];then
1089     backup_dir=$default_backup_dir
1090   fi
1091
1092   #recover image with nosnap
1093   if [ "$snap_name"x = ""x ];then
1094     discover_image_nosnap $pool_id $img_name #input image_name 
1095     local image_hobjects=$images/pool_$pool_id/$image_name_in/$image_name_in
1096     copy_image_nosnap_single_thread $pool_id $image_hobjects $backup_dir
1097
1098   #recover image with snap
1099   else
1100
1101     # check if recovered head already
1102     local img_hobjects_path=$images/pool_$pool_id/$img_name/$img_name
1103     local img_file_path=$backup_dir/pool_$pool_id/$img_name/$img_name
1104     if [ ! -e $img_hobjects_path ] || [ ! -e $img_file_path ];then
1105       echo "$func: $img_name@$snap_name : can not rollback to snapshot, please recover image head first"
1106       exit
1107     fi
1108
1109     # rollback to head
1110     if [ "$snap_name"x = "@@"x ];then
1111       local head_dir=$backup_dir/pool_$pool_id/$img_name/@head
1112       if [ -e $head_dir ];then
1113         local unit=`pushd $head_dir >/dev/null; ls|head -n 1|xargs -n 1 stat|awk '/Size:/{print $2}'`
1114         # rollback to head
1115         rollback_image_snap $pool_id $img_name -2 $backup_dir/$img_name/@head $backup_dir $unit
1116         echo "$image_name_in head : $backup_dir/$img_name/$img_name"
1117       else
1118         echo "$func: no need to rollback to head"
1119       fi
1120       return 0
1121     fi
1122     
1123     # rollback to snap
1124     discover_image_snap $pool_id $img_name $snap_name # get image meta & get snapid object
1125     local snap_hobjects=$images/pool_$pool_id/$image_name_in/$image_name_in@$db_snap_id
1126     local head_hobjects=$images/pool_$pool_id/$image_name_in/$image_name_in@$db_snap_id@head
1127     local snap_object_dir=$backup_dir/pool_$pool_id/$image_name_in/@$db_snap_id
1128     local image_path=$backup_dir/pool_$pool_id/$image_name_in/$image_name_in
1129     local image_unit=$((1<<$db_order))
1130     copy_image_snap_single_thread $pool_id $image_name_in $db_snap_id $snap_hobjects $head_hobjects $backup_dir
1131     rollback_image_snap $pool_id $image_name_in $db_snap_id $snap_object_dir $backup_dir $image_unit 
1132     echo "$image_name_in@$snap_name : $image_path"
1133   fi
1134 }