~ara/ubuntu-qa-tools/iso_tracker_tools

« back to all changes in this revision

Viewing changes to vm-tools/vm-clone

  • Committer: Jamie Strandboge
  • Date: 2009-03-30 20:57:59 UTC
  • Revision ID: jamie@canonical.com-20090330205759-8rcymuk8ywgsfgk7
migrate vm-tools in from security-tools

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/bin/sh -e
 
2
#
 
3
# Copyright (C) 2007-2009 Canonical, Ltd.
 
4
# Author: Jamie Strandboge <jamie@canonical.com>
 
5
# License: GPLv3
 
6
#
 
7
# Tested on Jaunty
 
8
#
 
9
# apt-get install kpartx nbd-client
 
10
#
 
11
 
 
12
ustconf="$HOME/.uqt-vm-tools.conf"
 
13
if [ -s "$ustconf" ]; then
 
14
    . "$ustconf"
 
15
else
 
16
    echo "Could not find '$ustconf'"
 
17
    exit 1
 
18
fi
 
19
 
 
20
. $UQT_VM_TOOLS/libvm.sh
 
21
abort_if_root
 
22
 
 
23
original=""
 
24
clone=""
 
25
script=""
 
26
ssh_opt="-o StrictHostKeyChecking=no"
 
27
convert=""
 
28
nbd_device=""
 
29
update_errors=""
 
30
 
 
31
help() {
 
32
    cat << EOM
 
33
USAGE:
 
34
  vm-clone <original> <new> [<script>]
 
35
  vm-clone -p PREFIX [-c] [-a ARCH] NEWPREFIX [<script>]
 
36
 
 
37
Specifying a 'PREFIX' will clone all machines created with vm-new with that
 
38
PREFIX that are in '$vm_release_list'. Eg:
 
39
$ vm-clone -p clean sec
 
40
 
 
41
will clone sec-<release>-<arch> VMs for each clean-<release>-<arch>.
 
42
 
 
43
Specifying '-c' will convert a qcow to raw instead of using nbd.
 
44
 
 
45
WARNING: vm-clone does NOT check to make sure you have enough space on disk,
 
46
so make sure you do.
 
47
EOM
 
48
}
 
49
 
 
50
tmpdir=`mktemp -d`
 
51
trap "rmdir $tmpdir" EXIT HUP INT QUIT TERM
 
52
 
 
53
mount_qemu() {
 
54
    max=16
 
55
    count=
 
56
 
 
57
    for i in `seq 0 $max` ; do
 
58
        if ! nbd-client -c /dev/nbd$i ; then
 
59
            count=$i
 
60
            break
 
61
        fi
 
62
    done
 
63
 
 
64
    if [ -z "$count" ]; then
 
65
        echo "Could not find available nbd device. Aborting" >&2
 
66
        exit 1
 
67
    fi
 
68
    nbd_device=/dev/nbd$count
 
69
 
 
70
    disk="$1"
 
71
    mountpoint="$2"
 
72
    if [ -e "/sys/module/nbd/parameters/max_part" ]; then
 
73
        cur_max_part=`cat /sys/module/nbd/parameters/max_part`
 
74
        if [ "$cur_max_part" = "0" ]; then
 
75
            sudo rmmod nbd || {
 
76
                cat << EOM
 
77
/sys/module/nbd/parameters/max_part is $cur_max_part. This setting should be
 
78
$max or higher. I tried to unload the module, but could not. Please
 
79
release any nbd devices with:
 
80
$ for i in `seq 0 $max`; do sudo nbd-client -c /dev/nbd\$i && sudo nbd-client -d /dev/nbd\$i ; done
 
81
 
 
82
Then unload the nbd module and reload it:
 
83
$ sudo rmmod nbd
 
84
$ sudo modprobe nbd max_part=$max
 
85
 
 
86
Aborting
 
87
EOM
 
88
                exit 1
 
89
            }
 
90
        fi
 
91
    fi
 
92
 
 
93
    lsmod | grep -q '^nbd ' || {
 
94
        echo "Inserting nbd module" >&2
 
95
        sudo modprobe nbd max_part=$max
 
96
    }
 
97
 
 
98
    sudo qemu-nbd -n --connect=$nbd_device "$disk" || {
 
99
        echo "Could not mount '$disk' (qemu-nbd failed). Skipping" >&2
 
100
        return
 
101
    }
 
102
    sleep 2
 
103
 
 
104
    # mount the non-swap partitions
 
105
    sudo fdisk -l $nbd_device | while read line ; do
 
106
        if ! echo "$line" | grep -q "83  Linux" || echo "$line" | grep -q "swap" ; then
 
107
            #echo "Not mounting '$line'" >&2
 
108
            continue
 
109
        fi
 
110
        partition=`echo "$line" | cut -d ' ' -f 1`
 
111
        device=`echo $partition | cut -d '/' -f 3`
 
112
        mp="$mountpoint/$device"
 
113
        mkdir -p "$mp" || {
 
114
            echo "Could not create '$mp'" >&2
 
115
            continue
 
116
        }
 
117
        sudo mount $partition $mp
 
118
    done
 
119
 
 
120
}
 
121
 
 
122
 
 
123
mount_raw() {
 
124
    disk="$1"
 
125
    mountpoint="$2"
 
126
 
 
127
    loopdev=`sudo kpartx -av "$disk" | head -1 | cut -d ' ' -f 8 | cut -d '/' -f 3`
 
128
    sleep 2
 
129
 
 
130
    # mount the non-swap partitions
 
131
    count=0
 
132
    sudo fdisk -l "$disk" 2>/dev/null | while read line ; do
 
133
        echo "$line" | grep -q "8[23]  Linux" && count=$((count+1))
 
134
        if ! echo "$line" | grep -q "83  Linux" || echo "$line" | grep -q "swap" ; then
 
135
            #echo "Not mounting '$line'"
 
136
            continue
 
137
        fi
 
138
        partition="/dev/mapper/${loopdev}p$count"
 
139
        device=`basename $partition`
 
140
        mp="$mountpoint/$device"
 
141
        mkdir -p "$mp" || {
 
142
            echo "Could not create '$mp'"
 
143
            continue
 
144
        }
 
145
        sudo mount $partition $mp
 
146
    done
 
147
 
 
148
}
 
149
 
 
150
mount_disks() {
 
151
    for d in "$@" ; do
 
152
        if [ "$d" = "--file" ]; then
 
153
            continue
 
154
        fi
 
155
 
 
156
        mountpoint=$tmpdir/`basename $d`
 
157
        tmpdisk="$d"
 
158
 
 
159
        if [ -z "$convert" ] && file $d | grep -q 'Qcow'; then
 
160
            echo "Mounting image..."
 
161
            mount_qemu "$tmpdisk" $mountpoint
 
162
        else
 
163
            if file $d | grep -q 'Qcow' ; then
 
164
                # convert to raw
 
165
                echo "Converting to raw..."
 
166
                tmpdisk="${d}.converted_to_raw"
 
167
                qemu-img convert "$d" -O raw "$tmpdisk"
 
168
            fi
 
169
 
 
170
            echo "Mounting image..."
 
171
            mount_raw "$tmpdisk" $mountpoint
 
172
        fi
 
173
    done
 
174
}
 
175
 
 
176
umount_disks() {
 
177
    tmp_args="$@"
 
178
    for i in `ls -1 $tmpdir` ; do
 
179
        diskdir="$tmpdir/$i"
 
180
        if [ ! -d "$diskdir" ]; then
 
181
            continue
 
182
        fi
 
183
        for j in `ls -1 $diskdir` ; do
 
184
            mp="$diskdir/$j"
 
185
            if [ -d "$mp" ]; then
 
186
                echo "Unmounting image..."
 
187
                sudo umount $mp && rmdir $mp || {
 
188
                    echo "Could not umount '$tmpdir/$i'"
 
189
                    continue
 
190
                }
 
191
            fi
 
192
        done
 
193
        rmdir $diskdir || {
 
194
            echo "Could not remove '$diskdir'"
 
195
            continue
 
196
        }
 
197
    done
 
198
 
 
199
    for d in $tmp_args ; do
 
200
        if [ "$d" = "--file" ]; then
 
201
            continue
 
202
        fi
 
203
        tmpimg="${d}"
 
204
 
 
205
        if [ ! -z "$nbd_device" ]; then
 
206
            sudo qemu-nbd --disconnect "${tmpimg}"
 
207
            sudo nbd-client -d $nbd_device || {
 
208
                echo "ERROR: Could not disconnect $nbd_device! Aborting" >&2
 
209
                exit 1
 
210
            }
 
211
        else
 
212
            if [ -s "${d}.converted_to_raw" ]; then
 
213
                tmpimg="${d}.converted_to_raw"
 
214
            fi
 
215
            sudo kpartx -dv "${tmpimg}"
 
216
 
 
217
            if [ -s "${d}.converted_to_raw" ]; then
 
218
                # convert back to qcow
 
219
                echo "Converting to qcow2..."
 
220
                qemu-img convert -f raw "${d}.converted_to_raw" -O qcow2 "${d}"
 
221
                rm -f "${d}.converted_to_raw"
 
222
            fi
 
223
        fi
 
224
    done
 
225
}
 
226
 
 
227
arch=
 
228
prefix=
 
229
while getopts "ca:p:" opt
 
230
do
 
231
    case "$opt" in
 
232
        a) arch="$OPTARG";;
 
233
        c) convert="yes";;
 
234
        p) prefix="$OPTARG";;
 
235
        ?) help
 
236
           exit 1
 
237
           ;;
 
238
    esac
 
239
done
 
240
shift $(($OPTIND - 1))
 
241
 
 
242
which qemu-img >/dev/null || {
 
243
    echo "Could not find qemu-img. Aborting" >&2
 
244
    exit 1
 
245
}
 
246
 
 
247
if [ -z "$1" ]; then
 
248
    help
 
249
    exit 1
 
250
elif [ -z "$prefix" ]; then
 
251
    original="$1"
 
252
    shift
 
253
else
 
254
    original="$prefix"
 
255
fi
 
256
 
 
257
if [ -z "$1" ]; then
 
258
    help
 
259
    exit 1
 
260
else
 
261
    clone="$1"
 
262
    shift
 
263
fi
 
264
 
 
265
if [ ! -z "$1" ]; then
 
266
    script="$1"
 
267
    shift
 
268
    if [ ! -x "$script" ]; then
 
269
        echo "Specified script '$script' is not executable"
 
270
        exit 1
 
271
    fi
 
272
fi
 
273
 
 
274
update_machine() {
 
275
    local original="$1"
 
276
    local clone="$2"
 
277
    local release="$3"
 
278
    # check if original and clone exists
 
279
    if ! vm_exists $original ; then
 
280
        echo "'$original' does not exist. Skipping" >&2
 
281
        return
 
282
    fi
 
283
 
 
284
    if vm_exists $clone ; then
 
285
        echo "'$clone' already exists. Skipping" >&2
 
286
        return
 
287
    fi
 
288
 
 
289
    disks=`get_disks $original`
 
290
    if [ -z "$disks" ]; then
 
291
        echo "Could not find any disks for '$original'. Skipping" >&2
 
292
        return
 
293
    fi
 
294
 
 
295
    # just grab the first disk for the path
 
296
    tmp=`echo "$disks" | awk '{ print $1 }' | sed "s/$original/$clone"/g`
 
297
    newpath=`dirname "$tmp"`
 
298
    mkdir "$newpath" || return
 
299
 
 
300
    # compose file args
 
301
    file_args=""
 
302
    for d in $disks ; do
 
303
        tmp=`echo "$d" | cut -d ' ' -f 1 | sed "s/$original/$clone"/g`
 
304
        file_args="$file_args --file $tmp"
 
305
    done
 
306
 
 
307
    if [ -z "$file_args" ]; then
 
308
        echo "Could not create '--file' arguments. Skipping" >&2
 
309
        return
 
310
    fi
 
311
 
 
312
    virt-clone --connect ${vm_connect} --original "$original" --name "$clone" $file_args || {
 
313
        rm -rf "$newpath"
 
314
        return
 
315
    }
 
316
 
 
317
    mount_disks $file_args
 
318
 
 
319
    prevdir=`pwd`
 
320
    for i in `ls -1 $tmpdir` ; do
 
321
        diskdir="$tmpdir/$i"
 
322
        if [ ! -d "$diskdir" ]; then
 
323
            continue
 
324
        fi
 
325
        for j in `ls -1 $diskdir` ; do
 
326
            mp="$diskdir/$j"
 
327
            if [ ! -d "$mp/etc" ]; then
 
328
                continue
 
329
            fi
 
330
            echo "Updating files..."
 
331
            sudo sh -c "echo $clone > $mp/etc/hostname" || echo "Couldn't update '$mp/etc/hostname'" >&2
 
332
            sudo sed -i "s/$original/$clone/g" $mp/etc/dhcp3/dhclient.conf || echo "Couldn't update '$mp/etc/dhcp3/dhclient.conf'" >&2
 
333
            sudo sed -i "s/$original/$clone/g" $mp/etc/hosts || echo "Couldn't update '$mp/etc/hosts'" >&2
 
334
 
 
335
            if [ "$release" = "gutsy" ] || [ "$release" = "hardy" ]; then
 
336
                echo "Updating udev..."
 
337
                mac=`virsh dumpxml $clone 2>/dev/null | grep 'mac address' | cut -d "'" -f 2`
 
338
                sudo sh -c "echo SUBSYSTEM==\"net\", DRIVERS==\"?*\", ATTRS{address}==\"$mac\", NAME=\"eth0\" > $mp/etc/udev/rules.d/70-persistent-net.rules"
 
339
            fi
 
340
 
 
341
            if [ ! -z "$script" ]; then
 
342
                sudo cp $script $mp/$script
 
343
            fi
 
344
        done
 
345
    done
 
346
 
 
347
    umount_disks $file_args
 
348
 
 
349
    # Get rid of old ssh key as it may be wrong
 
350
    ssh-keygen -R $clone
 
351
    ssh-keygen -R $clone.
 
352
 
 
353
    # start it up and try to login
 
354
    virsh --connect ${vm_connect} start "$clone"
 
355
    clone_hostname=`vm_wait $clone`
 
356
    if [ -z "$clone_hostname" ]; then
 
357
        update_errors="${update_errors}'$clone' is not pingable'\n"
 
358
    elif ssh $ssh_opt -t root@${clone_hostname} cat /etc/hostname | grep -q "$clone"; then
 
359
        echo "/etc/hostname updated"
 
360
        if [ ! -z "$script" ]; then
 
361
            if ssh $ssh_opt -t root@${clone_hostname} /${script} ; then
 
362
                echo "Script successfully executed"
 
363
            else
 
364
                echo "Script not successful"
 
365
                update_errors="${update_errors}'$script' on '$clone' not successful\n"
 
366
            fi
 
367
        fi
 
368
    else
 
369
        update_errors="${update_errors}Could not ssh into '$clone'\n"
 
370
    fi
 
371
 
 
372
    virsh --connect ${vm_connect} shutdown "$clone"
 
373
}
 
374
 
 
375
# check that we have all the needed binaries
 
376
error=
 
377
for b in qemu-nbd nbd-client kpartx ; do
 
378
    which $b >/dev/null || {
 
379
        echo "Could not find '$b'" >&2
 
380
        error="yes"
 
381
    }
 
382
done
 
383
if [ "$error" = "yes" ]; then
 
384
    echo "Required binaries not found. Aborting" >&2
 
385
    exit 1
 
386
fi
 
387
 
 
388
ionice -c 2 -n 7 -p $$
 
389
if [ -z "$prefix" ]; then
 
390
    update_machine $original $clone
 
391
else
 
392
    archs=$vm_archs
 
393
    if [ ! -z "$arch" ]; then
 
394
        archs="$arch"
 
395
    fi
 
396
 
 
397
    for release in $vm_release_list
 
398
    do
 
399
        for arch in $archs ; do
 
400
            echo "Cloning ${original}-${release}-${arch} to ${clone}-${release}-${arch}"
 
401
            update_machine ${original}-${release}-${arch} ${clone}-${release}-${arch} $release
 
402
        done
 
403
    done
 
404
fi
 
405
 
 
406
if [ -z "$update_errors" ]; then
 
407
    echo "SUCCESS"
 
408
else
 
409
    echo "Completed with errors:"
 
410
    echo -e "$update_errors"
 
411
fi
 
412
 
 
413
exit