Up to network configuration.
[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 # $Id$
10
11 export PATH=/usr/sbin:/sbin:/usr/local/bin:/usr/kerberos/bin:/usr/bin:/bin
12
13 # The defaults here make a generic virt-p2v.sh script, but if you want
14 # to build a partially-/fully-automatic P2V solution, then you can set
15 # these variables to something, and the script won't ask the user for
16 # input.
17
18 # Use 'greeting=no' to suppress greeting and final verification screens.
19 greeting=
20
21 override_remote_host=
22 override_remote_port=
23
24 # can be 'ssh' or 'tcp'
25 override_remote_transport=
26
27 # eg. override_remote_directory=/var/lib/xen/images
28 # (only if override_remote_transport is 'ssh')
29 override_remote_directory=
30
31 # list of local physical devices to examine, separated by spaces,
32 # if empty ask user.
33 # this is usually a list of whole disk devices (eg. "sda")
34 override_physical_devices=""
35
36 # list of filesystems to send (eg. "sda1 sda2 VolGroup00/LogVol00")
37 override_filesystems_to_send=""
38
39 # list of root filesystems to examine (eg. "sda3" or
40 # "VolGroup00/LogVol00")
41 override_root_filesystems=""
42
43 # network configuration
44 #  - if empty, ask user
45 #  - if contains a filesystem name (eg. "VolGroup00/LogVol00") try to
46 #    auto-configure network from that
47 # (XXX needs to contain more ways to override in future)
48 override_network=""
49
50 #----------------------------------------------------------------------
51 # Logging.
52
53 function log {
54     echo "$@" >> /tmp/virt-p2v.log
55 }
56
57 #----------------------------------------------------------------------
58 # Helper functions.
59
60 # 'matches_regexp regexp string' returns true if string matches regexp.
61 # It uses egrep for extended regexps.
62 function matches_regexp {
63     local re="$1"; shift
64     echo "$@" | grep -Esq "$re"
65 }
66
67 # 'string_contains needle haystack' returns true if needle in haystack.
68 function string_contains {
69     echo "$2" | grep -Fsq "$1"
70 }
71
72 # 'word_in_list word ...'.  '...' (list of parameters) is a list of
73 # words.  Returns true if 'word' is in the list.
74 function word_in_list {
75     local word="$1"; shift
76     local w
77     for w in "$@"; do
78         [ "$w" = "$word" ] && return 0
79     done
80     return 1
81 }
82
83 #----------------------------------------------------------------------
84 # Device mapper snapshotting.
85
86 # Create a device-mapper snapshot of a device with ramdisk overlay.
87 # Example:
88 #   snapshot sda1 snap
89 # creates a snapshot of /dev/sda1 called /dev/mapper/snap
90 function snapshot {
91     local dev=$1 name=$2
92
93     # Get size of the device in sectors.
94     local sectors=`blockdev --getsize /dev/$dev`
95
96     dmsetup create ${name}_org \
97         --table="0 $sectors snapshot-origin /dev/$dev"
98     if [ $? -ne 0 ]; then exit 1; fi
99     dmsetup create $name \
100         --table="0 $sectors snapshot /dev/mapper/${name}_org /dev/ram1 n 64"
101     if [ $? -ne 0 ]; then exit 1; fi
102 }
103
104 # Drop an existing snapshot created by snapshot function.
105 # Example:
106 #   drop_snapshot snap
107 # drops a snapshot called /dev/mapper/snap
108 function drop_snapshot {
109     dmsetup remove /dev/mapper/$name
110     dmsetup remove /dev/mapper/${name}_org
111 }
112
113 #----------------------------------------------------------------------
114 # Searching for devices, partitions and LVs.
115
116 # Convert "/dev/foo" into "foo".  Returns $device.
117 function strip_slash_dev {
118     device=$(echo "$1" | sed 's|^/dev/||')
119 }
120
121 # The 'lvs' utility returns devices like '/dev/sda2(0)'.  I've no
122 # idea what the number in parentheses is, but remove /dev/ and the
123 # strange number.
124 function device_of_lvs_device {
125     strip_slash_dev "$1"
126     device=$(echo "$device" | sed 's|(.*)$||')
127 }
128
129 # All functions in this section assume that physical block devices
130 # (things corresponding to hard disks in the system) are called
131 # hd[a-z]+ or sd[a-z]+
132
133 # Get list of physical block devices.  Sets variable $devices
134 # to something like "sda sdb".
135 function search_devices {
136     devices=$(cd /sys/block && /bin/ls -d [hs]d*)
137 }
138
139 # Get list of partitions from a physical block device.  Sets
140 # variable $partitions to something like "sda1 sda2 sda3".
141 # See also: search_parts
142 function get_partitions {
143     partitions=$(cd /sys/block/"$1" && /bin/ls -d "$1"*)
144 }
145
146 # Given a partition (eg. "sda1" or "VolGroup00/LogVol00") return
147 # the physical block device which contains it.  In the case where
148 # we are given an LV, this is supposed to do the right thing and
149 # go back through the layers until it gets to the physical block
150 # device.  Returns $device set to something like "sda".
151 function block_device_of_part {
152     local part="$1" vg_name lv_name pv
153
154     if matches_regexp '^[hs]d[a-z]+[0-9]*$' "$part"; then
155         device=$(echo "$part" | sed 's|[0-9]*$||')
156         return 0
157     fi
158
159     # Not a partition name, so it's a LV name.  Ask lvs to
160     # do the hard work.
161     lvs --noheadings -o vg_name,lv_name,devices > lvs
162     while read vg_name lv_name pv; do
163         if [ "$vg_name/$lv_name" = "$part" \
164             -o "mapper/$vg_name-$lv_name" = "$part" ]; then
165             device_of_lvs_device "$pv"  ;# sets $device to (eg.) "sda1"
166             block_device_of_part "$device"
167             return 0
168         fi
169     done < lvs
170
171     # Help ... block device not found.
172     log block_device_of_part: block device cannot be resolved: $part
173     device="$part"
174 }
175
176 # search_parts $devices examines the list of devices and looks for
177 # partitions or LVs on just those devices.  Sets variable $parts to
178 # something like "sda1 VolGroup00/LogVol00".
179 function search_parts {
180     local vg_name lv_name pv pvs="" device partition partitions
181
182     parts=""
183
184     lvs --noheadings -o vg_name,lv_name,devices > lvs
185     while read vg_name lv_name pv; do
186         # Get just the partition name (eg. "sda2").
187         device_of_lvs_device "$pv"; pv="$device"
188         # Get just the block device name (eg. "sda").
189         block_device_of_part "$pv"
190
191         log search_parts: pv $pv device $device lv $vg_name/$lv_name
192
193         # A device in our list of devices to consider?
194         if word_in_list $device "$@"; then
195             log search_parts: adding $vg_name/$lv_name
196             parts="$parts $vg_name/$lv_name"
197             pvs="$pvs $pv"
198         fi
199     done < lvs
200
201     log search_parts: after lvs, parts $parts pvs $pvs
202
203     # Look for non-LVM partitions, but ignore any which are PVs
204     # as identified above.
205     for device in "$@"; do
206         get_partitions "$device"
207         for partition in $partitions; do
208             if ! word_in_list $partition $pvs; then
209                 log search_parts: adding $partition
210                 parts="$parts $partition"
211             fi
212         done
213     done
214
215     # $parts is returned.
216 }
217
218 #----------------------------------------------------------------------
219 # Network configuration functions.
220
221 # `auto_network $fs' mounts /dev/$fs read-only on /mnt/root and then
222 # tries to configure the network from it.  Returns true or false.
223 function auto_network {
224     if ! mount -o ro /dev/$1 /mnt; then return 1; fi
225
226     pushd /etc/sysconfig
227
228     mv network network.saved
229     mv networking networking.saved
230     mv network-scripts network-scripts.saved
231
232     ln -s /mnt/etc/sysconfig/network
233     ln -s /mnt/etc/sysconfig/networking
234     ln -s /mnt/etc/sysconfig/network-scripts
235
236     /etc/init.d/network start
237     local status=$?
238
239     rm network networking network-scripts
240     mv network.saved network
241     mv networking.saved networking
242     mv network-scripts.saved network-scripts
243     umount /mnt
244
245     if [ "$greeting" != "no" ]; then
246         echo "Did automatic network configuration work?"
247         echo "(Hint: if not sure, there is a shell on console [ALT] [F2])"
248         echo -n "    (y/n) "
249         local line
250         read line
251         if [ "$line" = "y" -o "$line" = "yes" ]; then return 0; fi
252         return 1
253     fi
254
255     # In non-interactive mode, return the status of /etc/init.d/network.
256     return $status
257 }
258
259
260 #----------------------------------------------------------------------
261 # General script setup.
262
263 # We can safely write files into /tmp without modifying anything.
264 cd /tmp
265
266 #----------------------------------------------------------------------
267 # Dialog with the user.
268
269 log
270 log virt-p2v starting up at `date`
271
272 if [ "$greeting" != "no" ]; then
273     dialog \
274         --title "virt-p2v" \
275         --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
276 fi
277
278 # Get configuration from the user.
279
280 # To make the [Back] button work, we make this into a looping state
281 # machine.  Each state asks a question and jumps to the next state
282 # (unless [Back] is pressed, in which case it jumps back to the previous
283 # state).  Finally the 'exit' state causes us to quit the loop.
284
285 remote_port=22
286 remote_directory=/var/lib/xen/images
287 state=transport
288
289 while [ "$state" != "exit" ]; do
290     log next state = $state
291     case "$state" in
292         transport)
293             if [ -n "$override_remote_transport" ]; then
294                 remote_transport="$override_remote_transport"
295                 state=hostname
296             else
297                 case "$remote_transport" in
298                     tcp) ssh_on=off; tcp_on=on;;
299                     *) ssh_on=on; tcp_on=off;;
300                 esac
301                 dialog \
302                     --nocancel \
303                     --radiolist "Connection type" 10 50 2 \
304                     "ssh" "SSH (secure shell - recommended)" $ssh_on \
305                     "tcp" "TCP socket" $tcp_on \
306                     2> line
307                 remote_transport=`cat line`
308                 state=hostname
309             fi
310             ;;
311         hostname)
312             if [ -n "$override_remote_host" ]; then
313                 remote_host="$override_remote_host"
314                 state=port
315             else
316                 dialog \
317                     --extra-button --extra-label "Back" --nocancel \
318                     --inputbox "Remote host" 10 50 "$remote_host" \
319                     2> line
320                 if [ $? -eq 3 ]; then state=transport
321                 else
322                     remote_host=`cat line`
323                     if [ -n "$remote_host" ]; then state=port; fi
324                     # else stay in same state and demand a hostname!
325                 fi
326             fi
327             ;;
328         port)
329             if [ -n "$override_remote_port" ]; then
330                 remote_port="$override_remote_port"
331                 state=directory
332             else
333                 dialog \
334                     --extra-button --extra-label "Back" --nocancel \
335                     --inputbox "Remote port" 10 50 "$remote_port" \
336                     2> line
337                 if [ $? -eq 3 ]; then state=hostname
338                 else
339                     remote_port=`cat line`
340                     state=directory
341                 fi
342             fi
343             ;;
344         directory)
345             if [ "$remote_transport" = "tcp" ]; then
346                 state=devices
347             elif [ -n "$override_remote_directory" ]; then
348                 remote_directory="$override_remote_directory"
349                 state=devices
350             else
351                 dialog \
352                     --extra-button --extra-label "Back" --nocancel \
353                     --inputbox "Remote directory containing guest images" \
354                     10 50 \
355                     "$remote_directory" \
356                     2> line
357                 if [ $? -eq 3 ]; then state=port
358                 else
359                     remote_directory=`cat line`
360                     state=devices
361                 fi
362             fi
363             ;;
364
365         # Block devices configuration.
366         devices)
367             if [ -n "$override_physical_devices" ]; then
368                 physical_devices="$override_physical_devices"
369                 state=filesystems
370             else
371                 # Returns the list of physical devices in $devices
372                 search_devices
373
374                 log devices returned by search_devices: $devices
375
376                 deviceslist=""
377                 for d in $devices; do
378                     if word_in_list $d $physical_devices; then
379                         stat=on
380                     else
381                         stat=off
382                     fi
383                     gigs=$(($(blockdev --getsize /dev/$d)/2/1024/1024))
384                     deviceslist="$deviceslist $d /dev/$d(${gigs}GB) $stat"
385                 done
386
387                 dialog \
388                     --extra-button --extra-label "Back" --nocancel \
389                     --single-quoted \
390                     --checklist "Pick local disks to examine" 15 50 8 \
391                     $deviceslist \
392                     2> line
393                 if [ $? -eq 3 ]; then state=directory
394                 else
395                     physical_devices=`cat line`
396                     state=filesystems
397                 fi
398             fi
399             ;;
400         filesystems)
401             if [ -n "$override_filesystems_to_send" ]; then
402                 filesystems_to_send="$override_filesystems_to_send"
403                 state=roots
404             else
405                 # Returns the list of possible partitions / LVs in $parts
406                 search_parts $physical_devices
407
408                 log partitions returned by search_parts: $parts
409
410                 partslist=""
411                 for p in $parts; do
412                     if word_in_list $p $filesystems_to_send; then
413                         stat=on
414                     else
415                         stat=off
416                     fi
417                     gigs=$(($(blockdev --getsize /dev/$p)/2/1024/1024))
418                     partslist="$partslist $p /dev/$p(${gigs}GB) $stat"
419                 done
420
421                 dialog \
422                     --extra-button --extra-label "Back" --nocancel \
423                     --single-quoted \
424                     --checklist "Pick filesystems to send" 15 70 8 \
425                     $partslist \
426                     2> line
427                 if [ $? -eq 3 ]; then state=devices
428                 else
429                     filesystems_to_send=`cat line`
430                     state=roots
431                 fi
432             fi
433             ;;
434         roots)
435             if [ -n "$override_root_filesystems" ]; then
436                 root_filesystems="$override_root_filesystems"
437                 state=network
438             else
439                 partslist=""
440                 for r in $filesystems_to_send; do
441                     if word_in_list $r $root_filesystems; then
442                         stat=on
443                     else
444                         stat=off
445                     fi
446                     partslist="$partslist $r /dev/$r $stat"
447                 done
448
449                 dialog \
450                     --extra-button --extra-label "Back" --nocancel \
451                     --single-quoted \
452                     --checklist "Pick partition(s) containing a root (/) filesystem" 10 70 5 \
453                     $partslist \
454                     2> line
455                 if [ $? -eq 3 ]; then state=filesystems
456                 else
457                     root_filesystems=`cat line`
458                     state=network
459                 fi
460             fi
461             ;;
462
463         # Network configuration.
464         network)
465             if [ -n "$override_network" ]; then
466                 network="$override_network"
467                 state=verify
468             else
469                 partslist=""
470                 for r in $root_filesystems; do
471                     if [ "$r" = "$network" ]; then
472                         stat=on
473                     else
474                         stat=off
475                     fi
476                     partslist="$partslist $r Auto-configure $stat"
477                 done
478
479                 dialog \
480                     --extra-button --extra-label "Back" --nocancel \
481                     --radiolist "Network configuration" 10 70 5 \
482                     $partslist \
483                     "ask" "Manual configuration" off \
484                     "sh" "Configure from the shell" off \
485                     2> line
486                 if [ $? -eq 3 ]; then state=roots
487                 else
488                     network=`cat line`
489                     state=verify
490                 fi
491             fi
492             ;;
493
494         # Verify configuration.
495         verify)
496             if [ "$greeting" = "no" ]; then
497                 state=exit
498             else
499                 dialog \
500                     --title "Summary" \
501                     --yesno "Transport: $remote_transport\nRemote host: $remote_host\nRemote port: $remote_port\nRemote directory (ssh only): $remote_directory\nFilesystems to send: $filesystems_to_send\nRoot filesystem(s): $root_filesystems\nNetwork configuration: $network\n\nProceed with these settings?" \
502                     23 70
503                 if [ $? -eq 1 ]; then
504                     state=transport
505                 else
506                     state=exit
507                 fi
508             fi
509             ;;
510         *)
511             echo "Invalid state: $state"
512             state=transport
513             ;;
514     esac
515 done
516
517 clear
518
519 # Now see if we can get a network configuration.
520 log network configuration $network
521
522 case "$network" in
523     sh)
524         echo "Network configuration"
525         echo
526         echo "Please configure the network from this shell."
527         echo
528         echo "You can modify any file under /etc (especially /etc/sysconfig)."
529         echo "No changes are made to the system disks unless you mount them"
530         echo "and explicitly modify them."
531         echo
532         echo "When finished, exit with ^D or exit"
533         echo
534         bash
535         ;;
536
537     ask)
538         # XXX Not implemented
539         echo "Sorry, we didn't implement this one yet."
540         bash
541         ;;
542
543     *)
544         echo "Trying to auto-configure network from /dev/$network ..."
545         echo
546         if ! auto_network "$network"; then
547             echo "Auto-configuration failed.  Starting a shell."
548             echo
549             bash
550         fi
551 esac
552
553 # Mount the root filesystem(s) using device-mapper snapshots.
554 # The snapshots are called snap0, snap1, etc. with the number
555 # corresponding to its index in $root_filesystems array.
556
557
558
559
560
561
562
563
564
565
566
567
568 # This file must end with a newline
569