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