Daily update.
[virt-p2v.git] / virt-p2v.sh
1 #!/bin/bash
2 #
3 # virt-p2v.sh is a shell script which performs a physical to
4 # virtual conversion of local disks.
5 #
6 # Copyright (C) 2007 Red Hat Inc.
7 # Written by Richard W.M. Jones <rjones@redhat.com>
8 #
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 #
23 # $Id$
24
25 # Because we're running from a start-up script, we don't have much
26 # of a login environment, so set one up.
27 export PATH=/usr/sbin:/sbin:/usr/local/bin:/usr/kerberos/bin:/usr/bin:/bin
28 export HOME=/root
29 export LOGNAME=root
30
31 # The defaults here make a generic virt-p2v.sh script, but if you want
32 # to build a partially-/fully-automatic P2V solution, then you can set
33 # these variables to something, and the script won't ask the user for
34 # input.
35
36 # Use 'greeting=no' to suppress greeting and final verification screens.
37 greeting=
38
39 override_remote_host=
40 override_remote_port=
41
42 # can be 'ssh' or 'tcp'
43 override_remote_transport=
44
45 # eg. override_remote_directory=/var/lib/xen/images
46 # (only if override_remote_transport is 'ssh')
47 override_remote_directory=
48
49 # list of local physical devices to send, separated by spaces,
50 # if empty ask user.
51 # this is usually a list of whole disk devices (eg. "sda")
52 override_devices_to_send=""
53
54 # the root filesystem containing /etc/fstab (eg. "sda3" or
55 # "VolGroup00/LogVol00")
56 override_root_filesystem=""
57
58 # network configuration
59 #  - if empty, ask user
60 #  - "auto" means try to autoconfigure from root filesystem
61 # (XXX needs to contain more ways to override in future)
62 override_network=""
63
64 #----------------------------------------------------------------------
65 # General script setup and logging.
66
67 exec 2>> /tmp/virt-p2v.log
68
69 function log {
70     echo "$@" 1>&2
71 }
72
73 log
74 log virt-p2v starting up at `date`
75
76 # The first and only parameter must be the tty.  Connect
77 # stdin/stdout to this device.
78 if [ -n "$1" ]; then
79     log connecting to /dev/$1
80     exec </dev/$1 >/dev/$1
81 fi
82
83 # We can safely write files into /tmp without modifying anything.
84 cd /tmp
85
86 #----------------------------------------------------------------------
87 # Helper functions.
88
89 # 'matches_regexp regexp string' returns true if string matches regexp.
90 # It uses egrep for extended regexps.
91 function matches_regexp {
92     local re="$1"; shift
93     echo "$@" | grep -Esq "$re"
94 }
95
96 # 'string_contains needle haystack' returns true if needle in haystack.
97 function string_contains {
98     echo "$2" | grep -Fsq "$1"
99 }
100
101 # 'word_in_list word ...'.  '...' (list of parameters) is a list of
102 # words.  Returns true if 'word' is in the list.
103 function word_in_list {
104     local word="$1"; shift
105     local w
106     for w in "$@"; do
107         [ "$w" = "$word" ] && return 0
108     done
109     return 1
110 }
111
112 #----------------------------------------------------------------------
113 # I/O functions
114
115 # Use the function read_line instead of the shell built-in read.
116 # It reads from the console.
117 function read_line {
118     read "$*"
119 }
120
121 # Launch a bash subshell connected to the console.
122 function shell {
123     PS1='\u@\h:\w\$ ' bash
124 }
125
126 #----------------------------------------------------------------------
127 # Device mapper snapshotting.
128
129 next_ramdisk=1
130
131 # Create a device-mapper snapshot of a device with ramdisk overlay.
132 # Example:
133 #   snapshot sda1 snap
134 # creates a snapshot of /dev/sda1 called /dev/mapper/snap
135 function snapshot {
136     local dev=$1 name=$2
137
138     # Next free ramdisk (/dev/ram$i)
139     local i=$next_ramdisk
140     next_ramdisk=$(($next_ramdisk+1))
141
142     # Get size of the device in sectors.
143     local sectors=`blockdev --getsize /dev/$dev`
144
145     dmsetup create ${name}_org \
146         --table="0 $sectors snapshot-origin /dev/$dev"
147     if [ $? -ne 0 ]; then exit 1; fi
148     dmsetup create $name \
149         --table="0 $sectors snapshot /dev/mapper/${name}_org /dev/ram$i n 64"
150     if [ $? -ne 0 ]; then exit 1; fi
151 }
152
153 # Drop an existing snapshot created by snapshot function.
154 # Example:
155 #   drop_snapshot snap
156 # drops a snapshot called /dev/mapper/snap
157 function drop_snapshot {
158     local name=$1
159
160     dmsetup remove /dev/mapper/$name
161     dmsetup remove /dev/mapper/${name}_org
162 }
163
164 #----------------------------------------------------------------------
165 # Searching for devices, partitions and LVs.
166
167 # Convert "/dev/foo" into "foo".  Returns $device.
168 function strip_slash_dev {
169     device=$(echo "$1" | sed 's|^/dev/||')
170 }
171
172 # The 'lvs' utility returns devices like '/dev/sda2(0)'.  I've no
173 # idea what the number in parentheses is, but remove /dev/ and the
174 # strange number.
175 function device_of_lvs_device {
176     strip_slash_dev "$1"
177     device=$(echo "$device" | sed 's|(.*)$||')
178 }
179
180 # All functions in this section assume that physical block devices
181 # (things corresponding to hard disks in the system) are called
182 # hd[a-z]+ or sd[a-z]+
183
184 # Get list of physical block devices.  Sets variable $devices
185 # to something like "sda sdb".
186 function search_devices {
187     devices1=$(cd /sys/block && /bin/ls -d [hs]d*)
188     log search_devices: devices1: $devices1
189     # Ignore devices which fail 'blockdev --getsize' - probably
190     # removable devices or other strange ones.
191     devices=""
192     for d in $devices1; do
193         if blockdev --getsize /dev/$d > /dev/null; then
194             devices="$devices${devices:+ }$d"
195         fi
196     done
197     log search_devices: devices: $devices
198 }
199
200 # Get list of partitions from a physical block device.  Sets
201 # variable $partitions to something like "sda1 sda2 sda3".
202 # See also: search_parts
203 function get_partitions {
204     partitions=$(cd /sys/block/"$1" && /bin/ls -d "$1"*)
205 }
206
207 # Given a partition (eg. "sda1" or "VolGroup00/LogVol00") return
208 # the physical block device which contains it.  In the case where
209 # we are given an LV, this is supposed to do the right thing and
210 # go back through the layers until it gets to the physical block
211 # device.  Returns $device set to something like "sda".
212 function block_device_of_part {
213     local part="$1" vg_name lv_name pv
214
215     if matches_regexp '^[hs]d[a-z]+[0-9]*$' "$part"; then
216         device=$(echo "$part" | sed 's|[0-9]*$||')
217         return 0
218     fi
219
220     # Not a partition name, so it's a LV name.  Ask lvs to
221     # do the hard work.
222     lvs --noheadings -o vg_name,lv_name,devices > lvs
223     while read vg_name lv_name pv; do
224         if [ "$vg_name/$lv_name" = "$part" \
225             -o "mapper/$vg_name-$lv_name" = "$part" ]; then
226             device_of_lvs_device "$pv"  ;# sets $device to (eg.) "sda1"
227             block_device_of_part "$device"
228             return 0
229         fi
230     done < lvs
231
232     # Help ... block device not found.
233     log block_device_of_part: block device cannot be resolved: $part
234     device="$part"
235 }
236
237 # search_parts $devices examines the list of devices and looks for
238 # partitions or LVs on just those devices.  Sets variable $parts to
239 # something like "sda1 VolGroup00/LogVol00".
240 function search_parts {
241     local vg_name lv_name pv pvs="" device partition partitions
242
243     parts=""
244
245     lvs --noheadings -o vg_name,lv_name,devices > lvs
246     while read vg_name lv_name pv; do
247         # Get just the partition name (eg. "sda2").
248         device_of_lvs_device "$pv"; pv="$device"
249         # Get just the block device name (eg. "sda").
250         block_device_of_part "$pv"
251
252         log search_parts: pv $pv device $device lv $vg_name/$lv_name
253
254         # A device in our list of devices to consider?
255         if word_in_list $device "$@"; then
256             log search_parts: adding $vg_name/$lv_name
257             parts="$parts $vg_name/$lv_name"
258             pvs="$pvs $pv"
259         fi
260     done < lvs
261
262     log search_parts: after lvs, parts $parts pvs $pvs
263
264     # Look for non-LVM partitions, but ignore any which are PVs
265     # as identified above.
266     for device in "$@"; do
267         get_partitions "$device"
268         for partition in $partitions; do
269             if ! word_in_list $partition $pvs; then
270                 log search_parts: adding $partition
271                 parts="$parts $partition"
272             fi
273         done
274     done
275
276     # $parts is returned.
277 }
278
279 # This generates a snapshot device name from a device name.
280 # eg. sda -> snap_sda
281 # Sets $dname.
282 function snap_name {
283     dname=`echo -n snap_"$1" | tr -cs '[:alnum:]' _`
284 }
285
286 #----------------------------------------------------------------------
287 # Network configuration functions.
288
289 # `auto_network' tries to configure the network from the
290 # root filesystem.  Returns true or false.
291 function auto_network {
292     # Make sure this file exists, otherwise Fedora gives a warning.
293     touch /etc/resolv.conf
294
295     pushd /etc/sysconfig
296
297     mv network network.saved
298     mv networking networking.saved
299     mv network-scripts network-scripts.saved
300
301     # Originally I symlinked these, but that causes dhclient to
302     # keep open /mnt/root (as its cwd is in network-scripts subdir).
303     # So now we will copy them recursively instead.
304     cp -r /mnt/root/etc/sysconfig/network .
305     cp -r /mnt/root/etc/sysconfig/networking .
306     cp -r /mnt/root/etc/sysconfig/network-scripts .
307
308     /etc/init.d/network start
309     local status=$?
310
311     rm -rf network networking network-scripts
312     mv network.saved network
313     mv networking.saved networking
314     mv network-scripts.saved network-scripts
315
316     popd
317
318     ping -c 3 $remote_host
319
320     if [ "$greeting" != "no" ]; then
321         echo "Did automatic network configuration work?"
322         echo "(Hint: if not sure, there is a shell on console [ALT] [F2])"
323         echo -n "    (y/n) "
324         local line
325         read_line line
326         if [ "$line" = "y" -o "$line" = "yes" ]; then return 0; fi
327         return 1
328     fi
329
330     # In non-interactive mode, return the status of /etc/init.d/network.
331     return $status
332 }
333
334 #----------------------------------------------------------------------
335 # Dialog with the user.
336
337 if [ "$greeting" != "no" ]; then
338     dialog \
339         --title "virt-p2v" \
340         --msgbox "\nWelcome to virt-p2v, a live CD for migrating a physical machine to a virtualized host.\n\nTo continue press the Return key.\n\nTo get a shell you can use [ALT] [F2] and log in as root with no password." 17 50
341 fi
342
343 # Get configuration from the user.
344
345 # To make the [Back] button work, we make this into a looping state
346 # machine.  Each state asks a question and jumps to the next state
347 # (unless [Back] is pressed, in which case it jumps back to the previous
348 # state).  Finally the 'exit' state causes us to quit the loop.
349
350 remote_port=22
351 remote_directory=/var/lib/xen/images
352 state=transport
353
354 while [ "$state" != "exit" ]; do
355     log next state = $state
356     case "$state" in
357         transport)
358             if [ -n "$override_remote_transport" ]; then
359                 remote_transport="$override_remote_transport"
360                 state=hostname
361             else
362                 case "$remote_transport" in
363                     tcp) ssh_on=off; tcp_on=on;;
364                     *) ssh_on=on; tcp_on=off;;
365                 esac
366                 dialog \
367                     --nocancel \
368                     --radiolist "Connection type" 10 50 2 \
369                     "ssh" "SSH (secure shell - recommended)" $ssh_on \
370                     "tcp" "TCP socket" $tcp_on \
371                     2> line
372                 remote_transport=`cat line`
373                 state=hostname
374             fi
375             ;;
376         hostname)
377             if [ -n "$override_remote_host" ]; then
378                 remote_host="$override_remote_host"
379                 state=port
380             else
381                 dialog \
382                     --extra-button --extra-label "Back" --nocancel \
383                     --inputbox "Remote host" 10 50 "$remote_host" \
384                     2> line
385                 if [ $? -eq 3 ]; then state=transport
386                 else
387                     remote_host=`cat line`
388                     if [ -n "$remote_host" ]; then state=port; fi
389                     # else stay in same state and demand a hostname!
390                 fi
391             fi
392             ;;
393         port)
394             if [ -n "$override_remote_port" ]; then
395                 remote_port="$override_remote_port"
396                 state=directory
397             else
398                 dialog \
399                     --extra-button --extra-label "Back" --nocancel \
400                     --inputbox "Remote port" 10 50 "$remote_port" \
401                     2> line
402                 if [ $? -eq 3 ]; then state=hostname
403                 else
404                     remote_port=`cat line`
405                     state=directory
406                 fi
407             fi
408             ;;
409         directory)
410             if [ "$remote_transport" = "tcp" ]; then
411                 state=devices
412             elif [ -n "$override_remote_directory" ]; then
413                 remote_directory="$override_remote_directory"
414                 state=devices
415             else
416                 dialog \
417                     --extra-button --extra-label "Back" --nocancel \
418                     --inputbox "Remote directory containing guest images" \
419                     10 50 \
420                     "$remote_directory" \
421                     2> line
422                 if [ $? -eq 3 ]; then state=port
423                 else
424                     remote_directory=`cat line`
425                     state=devices
426                 fi
427             fi
428             ;;
429
430         # Block devices configuration.
431         devices)
432             if [ -n "$override_devices_to_send" ]; then
433                 devices_to_send="$override_devices_to_send"
434                 state=root
435             else
436                 # Returns the list of physical devices in $devices
437                 search_devices
438
439                 log devices returned by search_devices: $devices
440
441                 deviceslist=""
442                 for d in $devices; do
443                     if word_in_list $d $devices_to_send; then
444                         stat=on
445                     else
446                         stat=off
447                     fi
448                     log running blockdev --getsize /dev/$d
449                     gigs=$(($(blockdev --getsize /dev/$d)/2/1024/1024))
450                     log /dev/$d has size $gigs
451                     deviceslist="$deviceslist $d /dev/$d(${gigs}GB) $stat"
452                 done
453                 log deviceslist="$deviceslist"
454
455                 dialog \
456                     --extra-button --extra-label "Back" --nocancel \
457                     --single-quoted \
458                     --checklist "Pick disks to send" 15 50 8 \
459                     $deviceslist \
460                     2> line
461                 if [ $? -eq 3 ]; then state=directory
462                 else
463                     devices_to_send=`cat line`
464                     state=root
465                 fi
466             fi
467             ;;
468
469         # Root filesystem.
470         root)
471             if [ -n "$override_root_filesystem" ]; then
472                 root_filesystem="$override_root_filesystem"
473                 state=network
474             else
475                 # Returns the list of possible partitions / LVs in $parts
476                 search_parts $devices_to_send
477
478                 log partitions returned by search_parts: $parts
479
480                 partslist=""
481                 for r in $parts; do
482                     if word_in_list $r $root_filesystem; then
483                         stat=on
484                     else
485                         stat=off
486                     fi
487                     partslist="$partslist $r /dev/$r $stat"
488                 done
489
490                 dialog \
491                     --extra-button --extra-label "Back" --nocancel \
492                     --single-quoted \
493                     --radiolist "Pick partition containing the root (/) filesystem" 10 70 5 \
494                     $partslist \
495                     2> line
496                 if [ $? -eq 3 ]; then state=devices
497                 else
498                     root_filesystem=`cat line`
499                     state=network
500                 fi
501             fi
502             ;;
503
504         # Network configuration.
505         network)
506             if [ -n "$override_network" ]; then
507                 network="$override_network"
508                 state=verify
509             else
510                 dialog \
511                     --extra-button --extra-label "Back" --nocancel \
512                     --radiolist "Network configuration" 10 70 5 \
513                     "auto" "Auto-configure from root filesystem" on \
514                     "ask" "Manual configuration" off \
515                     "sh" "Configure from the shell" off \
516                     2> line
517                 if [ $? -eq 3 ]; then state=root
518                 else
519                     network=`cat line`
520                     state=verify
521                 fi
522             fi
523             ;;
524
525         # Verify configuration.
526         verify)
527             if [ "$greeting" = "no" ]; then
528                 state=exit
529             else
530                 dialog \
531                     --title "Summary" \
532                     --yesno "Transport: $remote_transport\nRemote host: $remote_host\nRemote port: $remote_port\nRemote directory (ssh only): $remote_directory\nDisks to send: $devices_to_send\nRoot filesystem: $root_filesystem\nNetwork configuration: $network\n\nProceed with these settings?" \
533                     18 70
534                 if [ $? -eq 1 ]; then
535                     state=transport
536                 else
537                     state=exit
538                 fi
539             fi
540             ;;
541         *)
542             echo "Invalid state: $state"
543             state=transport
544             ;;
545     esac
546 done
547
548 clear
549
550 #----------------------------------------------------------------------
551 # De-activate all volume groups and switch to new dm-only LVM config.
552 log deactivating volume groups
553
554 vgchange -a n
555 mv /etc/lvm/lvm.conf /etc/lvm/lvm.conf.old
556 mv /etc/lvm/lvm.conf.new /etc/lvm/lvm.conf
557 rm -f /etc/lvm/cache/.cache
558
559 # Snapshot the block devices.
560 for d in $devices_to_send; do
561     snap_name $d
562     log snapshotting block device /dev/$d to $dname ...
563
564     snapshot $d $dname
565
566     # The block devices are whole disks.  Use kpartx to repartition them.
567     log running kpartx -a /dev/mapper/$dname ...
568     kpartx -a /dev/mapper/$dname
569 done
570
571 # Rescan for LVs.
572 log running vgscan
573 vgscan
574 vgchange -a y
575
576 # Mount the root filesystem on /mnt/root.  If it's a physical
577 # device then we want to mount (eg) /dev/mapper/snap_sda2.
578 # If it's a LVM device then we can just mount the LVM partition.
579
580 log mount $root_filesystem as /mnt/root
581
582 snap_name $root_filesystem
583 root_filesystem_dname="$dname"
584
585 if [ -b /dev/mapper/$root_filesystem_dname ]; then
586     mount /dev/mapper/$root_filesystem_dname /mnt/root
587 else
588     mount /dev/$root_filesystem /mnt/root
589 fi
590
591 #----------------------------------------------------------------------
592 # Now see if we can get a network configuration.
593 log network configuration $network
594
595 case "$network" in
596     sh)
597         echo "Network configuration"
598         echo
599         echo "Please configure the network from this shell."
600         echo
601         echo "When finished, exit with ^D or exit"
602         echo
603         shell
604         ;;
605
606     ask)
607         # XXX Not implemented
608         echo "Sorry, we didn't implement this one yet."
609         shell
610         ;;
611
612     auto)
613         echo "Trying to auto-configure network from root filesystem ..."
614         echo
615         if ! auto_network; then
616             echo "Auto-configuration failed.  Starting a shell."
617             echo
618             shell
619         fi
620 esac
621
622 #----------------------------------------------------------------------
623 # Rewrite /mnt/root/etc/fstab
624
625 log rewriting /etc/fstab
626
627 cp /mnt/root/etc/fstab /mnt/root/etc/fstab.p2vsaved
628 while read dev mountpoint fstype options freq passno; do
629     # If device is /dev/sd* then on the target fullvirt machine
630     # it will be /dev/hd*
631     if matches_regexp "^/dev/sd[[:alpha:]]+[[:digit:]]+$" "$dev"; then
632         dev=`echo $dev | sed 's|^/dev/sd|/dev/hd|'`
633     fi
634
635     # Print out again with all the fields nicely lined up.
636     printf "%-23s %-23s %-7s %-15s %d %d\n" \
637         "$dev" "$mountpoint" "$fstype" "$options" "$freq" "$passno"
638 done < /mnt/root/etc/fstab.p2vsaved > /mnt/root/etc/fstab
639
640 #----------------------------------------------------------------------
641 # XXX Look for other files which might need to be changed here ...
642
643
644
645 # We've now finished with /mnt/root (the real root filesystem),
646 # so unmount it and synch everything.
647 umount /mnt/root
648 sync
649
650 #----------------------------------------------------------------------
651 # Send the device snapshots (underlying device + changes in ramdisk)
652 # to the remote server.
653
654 log sending disks
655
656 # XXX Effectively this is using the hostname derived from network
657 # configuration, but we might want to ask the user instead.
658 # XXX How do we ensure that we won't overwrite target files?  Currently
659 # tries to use the current date as a uniquifier.
660
661 # Names will be something like
662 # p2v-oirase-200709011249-hda.img
663 basename=p2v-`hostname -s|tr -cd '[0-9a-zA-Z]'`-`date +'%Y%m%d%H%M'`
664
665 for dev in $devices_to_send; do
666     rdev=`echo $dev | sed 's|^sd|hd|'`
667     name="$basename-$rdev.img"
668     log sending $dev to $name
669
670     snap_name $dev
671
672     sectors=`blockdev --getsize /dev/mapper/$dname`
673
674     gigs=$(($sectors/2/1024/1024))
675     echo "Sending /dev/$dev (${gigs} GB) to remote machine"
676
677     dd if=/dev/mapper/$dname | gzip --best |
678     case "$remote_transport" in
679         ssh)
680             ssh -p "$remote_port" "$remote_host" \
681                 "zcat > $remote_directory/$name"
682             ;;
683         tcp)
684             echo "p2v $name $sectors" > header
685             echo > newline
686             cat header - newline | nc "$remote_host" "$remote_port"
687             ;;
688     esac
689 done
690
691
692 #----------------------------------------------------------------------
693 # Clean up.
694
695 #for d in $devices_to_send; do
696 #    snap_name $d
697 #    kpartx -d /dev/mapper/$dname
698 #    drop_snapshot $dname
699 #done
700
701 # This file must end with a newline
702