3
# Copyright (C) 2007-2009 Canonical, Ltd.
4
# Author: Jamie Strandboge <jamie@canonical.com>
9
# apt-get install kpartx nbd-client
12
ustconf="$HOME/.uqt-vm-tools.conf"
13
if [ -s "$ustconf" ]; then
16
echo "Could not find '$ustconf'"
20
. $UQT_VM_TOOLS/libvm.sh
26
ssh_opt="-o StrictHostKeyChecking=no"
34
vm-clone <original> <new> [<script>]
35
vm-clone -p PREFIX [-c] [-a ARCH] NEWPREFIX [<script>]
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
41
will clone sec-<release>-<arch> VMs for each clean-<release>-<arch>.
43
Specifying '-c' will convert a qcow to raw instead of using nbd.
45
WARNING: vm-clone does NOT check to make sure you have enough space on disk,
51
trap "rmdir $tmpdir" EXIT HUP INT QUIT TERM
57
for i in `seq 0 $max` ; do
58
if ! nbd-client -c /dev/nbd$i ; then
64
if [ -z "$count" ]; then
65
echo "Could not find available nbd device. Aborting" >&2
68
nbd_device=/dev/nbd$count
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
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
82
Then unload the nbd module and reload it:
84
$ sudo modprobe nbd max_part=$max
93
lsmod | grep -q '^nbd ' || {
94
echo "Inserting nbd module" >&2
95
sudo modprobe nbd max_part=$max
98
sudo qemu-nbd -n --connect=$nbd_device "$disk" || {
99
echo "Could not mount '$disk' (qemu-nbd failed). Skipping" >&2
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
110
partition=`echo "$line" | cut -d ' ' -f 1`
111
device=`echo $partition | cut -d '/' -f 3`
112
mp="$mountpoint/$device"
114
echo "Could not create '$mp'" >&2
117
sudo mount $partition $mp
127
loopdev=`sudo kpartx -av "$disk" | head -1 | cut -d ' ' -f 8 | cut -d '/' -f 3`
130
# mount the non-swap partitions
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'"
138
partition="/dev/mapper/${loopdev}p$count"
139
device=`basename $partition`
140
mp="$mountpoint/$device"
142
echo "Could not create '$mp'"
145
sudo mount $partition $mp
152
if [ "$d" = "--file" ]; then
156
mountpoint=$tmpdir/`basename $d`
159
if [ -z "$convert" ] && file $d | grep -q 'Qcow'; then
160
echo "Mounting image..."
161
mount_qemu "$tmpdisk" $mountpoint
163
if file $d | grep -q 'Qcow' ; then
165
echo "Converting to raw..."
166
tmpdisk="${d}.converted_to_raw"
167
qemu-img convert "$d" -O raw "$tmpdisk"
170
echo "Mounting image..."
171
mount_raw "$tmpdisk" $mountpoint
178
for i in `ls -1 $tmpdir` ; do
180
if [ ! -d "$diskdir" ]; then
183
for j in `ls -1 $diskdir` ; do
185
if [ -d "$mp" ]; then
186
echo "Unmounting image..."
187
sudo umount $mp && rmdir $mp || {
188
echo "Could not umount '$tmpdir/$i'"
194
echo "Could not remove '$diskdir'"
199
for d in $tmp_args ; do
200
if [ "$d" = "--file" ]; then
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
212
if [ -s "${d}.converted_to_raw" ]; then
213
tmpimg="${d}.converted_to_raw"
215
sudo kpartx -dv "${tmpimg}"
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"
229
while getopts "ca:p:" opt
234
p) prefix="$OPTARG";;
240
shift $(($OPTIND - 1))
242
which qemu-img >/dev/null || {
243
echo "Could not find qemu-img. Aborting" >&2
250
elif [ -z "$prefix" ]; then
265
if [ ! -z "$1" ]; then
268
if [ ! -x "$script" ]; then
269
echo "Specified script '$script' is not executable"
278
# check if original and clone exists
279
if ! vm_exists $original ; then
280
echo "'$original' does not exist. Skipping" >&2
284
if vm_exists $clone ; then
285
echo "'$clone' already exists. Skipping" >&2
289
disks=`get_disks $original`
290
if [ -z "$disks" ]; then
291
echo "Could not find any disks for '$original'. Skipping" >&2
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
303
tmp=`echo "$d" | cut -d ' ' -f 1 | sed "s/$original/$clone"/g`
304
file_args="$file_args --file $tmp"
307
if [ -z "$file_args" ]; then
308
echo "Could not create '--file' arguments. Skipping" >&2
312
virt-clone --connect ${vm_connect} --original "$original" --name "$clone" $file_args || {
317
mount_disks $file_args
320
for i in `ls -1 $tmpdir` ; do
322
if [ ! -d "$diskdir" ]; then
325
for j in `ls -1 $diskdir` ; do
327
if [ ! -d "$mp/etc" ]; then
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
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"
341
if [ ! -z "$script" ]; then
342
sudo cp $script $mp/$script
347
umount_disks $file_args
349
# Get rid of old ssh key as it may be wrong
351
ssh-keygen -R $clone.
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"
364
echo "Script not successful"
365
update_errors="${update_errors}'$script' on '$clone' not successful\n"
369
update_errors="${update_errors}Could not ssh into '$clone'\n"
372
virsh --connect ${vm_connect} shutdown "$clone"
375
# check that we have all the needed binaries
377
for b in qemu-nbd nbd-client kpartx ; do
378
which $b >/dev/null || {
379
echo "Could not find '$b'" >&2
383
if [ "$error" = "yes" ]; then
384
echo "Required binaries not found. Aborting" >&2
388
ionice -c 2 -n 7 -p $$
389
if [ -z "$prefix" ]; then
390
update_machine $original $clone
393
if [ ! -z "$arch" ]; then
397
for release in $vm_release_list
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
406
if [ -z "$update_errors" ]; then
409
echo "Completed with errors:"
410
echo -e "$update_errors"