[Libguestfs] [PATCH 7/8] Add capabilities

Matthew Booth mbooth at redhat.com
Wed May 19 16:50:39 UTC 2010


Before RHN support was added, the only way to install software was by specifying
it in the config file. To configure VirtIO support, Converter::Linux called
add_kernel(). We setup the config file such that the kernel version in there
supported VirtIO, and everything else which needed to be installed was a
dependency of the kernel. This meant that the RHN support had no good way to
determine from configuration what needed to be installed, and at what versions.

Capabilities describe a set of requirements for a feature, which means they can
be used cleanly for both RHN support, and installation from local sources. They
will also allow VirtIO to be configured in RHEL 3 guests by installing the
kmod-virtio package rather than a new kernel, and are intended to be usable for
installing spice.

This change principally adds install_capability to GuestOS::RedHat, replacing
add_kernel. It also adds a new function, install_good_kernel, which will
specifically attempt to install a bootable kernel from any source. It also
refactors the RHN support to make it usable by both these functions, and adds
additional error checking.
---
 lib/Sys/VirtV2V/Config.pm          |   54 +++
 lib/Sys/VirtV2V/Converter/Linux.pm |   80 ++---
 lib/Sys/VirtV2V/GuestOS.pm         |   30 --
 lib/Sys/VirtV2V/GuestOS/RedHat.pm  |  816 ++++++++++++++++++++++++------------
 v2v/virt-v2v.conf                  |  104 +++--
 v2v/virt-v2v.conf.pod              |  160 +++++---
 v2v/virt-v2v.pl                    |   41 +--
 7 files changed, 814 insertions(+), 471 deletions(-)

diff --git a/lib/Sys/VirtV2V/Config.pm b/lib/Sys/VirtV2V/Config.pm
index 46ce0c8..776c359 100644
--- a/lib/Sys/VirtV2V/Config.pm
+++ b/lib/Sys/VirtV2V/Config.pm
@@ -262,6 +262,60 @@ sub _match_query
     return $query;
 }
 
+=item match_capability
+
+Match a capability from the configuration. Returned as a hashref containing
+dependencies, where each dependency is a hashref containing:
+
+  {capability} ->
+    {name} ->       : package name
+      {minversion}  : minimum required version
+      {ifinstalled} : 1 if the package should be upgraded if necessary, but
+                      not installed if it is not already, 0 otherwise
+
+Returns undef if the capability was not found.
+
+=cut
+
+sub match_capability
+{
+    my $self = shift;
+    my ($desc, $name, $arch) = @_;
+
+    my $cap = $self->_match_element('capability', $desc, $name, $arch);
+
+    my %out;
+    foreach my $dep ($cap->findnodes('dep')) {
+        my %props;
+        foreach my $prop ('name', 'minversion') {
+            my ($val) = $dep->findnodes('@'.$prop);
+            $val &&= $val->getData();
+            die(user_message(__x("Capability in config contains a dependency ".
+                                 "with no {property} attribute: {xml}",
+                                 property => $prop,
+                                 xml => $cap->toString())))
+                if (!defined($val));
+            $props{$prop} = $val;
+        }
+
+        my ($ifinstalled) = $dep->findnodes('@ifinstalled');
+        $ifinstalled &&= $ifinstalled->getData();
+        if (defined($ifinstalled) &&
+            ($ifinstalled eq "1" || $ifinstalled eq "yes"))
+        {
+            $props{ifinstalled} = 1;
+        } else {
+            $props{ifinstalled} = 0;
+        }
+
+        my $depname = $props{name};
+        delete($props{name});
+
+        $out{$depname} = \%props;
+    }
+    return \%out;
+}
+
 sub _match_element
 {
     my $self = shift;
diff --git a/lib/Sys/VirtV2V/Converter/Linux.pm b/lib/Sys/VirtV2V/Converter/Linux.pm
index 3bcba9f..768a3ab 100644
--- a/lib/Sys/VirtV2V/Converter/Linux.pm
+++ b/lib/Sys/VirtV2V/Converter/Linux.pm
@@ -105,11 +105,11 @@ sub convert
     # replacement
     _unconfigure_hv($g, $guestos, $desc);
 
-    # Get the best available kernel
-    my $kernel = _configure_kernel($guestos, $desc);
+    # Try to install the virtio capability
+    my $virtio = $guestos->install_capability('virtio');
 
-    # Check if the resulting kernel will support virtio
-    my $virtio = $guestos->supports_virtio($kernel);
+    # Get an appropriate kernel, and remove non-bootable kernels
+    my $kernel = _configure_kernel($guestos, $desc, $virtio);
 
     # Configure the rest of the system
     _configure_console($g);
@@ -248,72 +248,45 @@ sub _configure_display_driver
 
 sub _configure_kernel
 {
-    my ($guestos, $desc) = @_;
-
-    my %kernels;
-
-    # Look for installed kernels with virtio support
-    foreach my $kernel (@{$desc->{kernels}}) {
-        my %checklist = (
-            "virtio_blk" => undef,
-            "virtio_pci" => undef,
-            "virtio_net" => undef
-        );
-
-        foreach my $module (@{$kernel->{modules}}) {
-            if(exists($checklist{$module})) {
-                $checklist{$module} = 1;
-            }
-        }
-
-        my $virtio = 1;
-        foreach my $module (keys(%checklist)) {
-            if(!defined($checklist{$module})) {
-                $virtio = 0;
-                last;
-            }
-        }
-
-        if($virtio) {
-            $kernels{$kernel->{version}} = 1;
-        } else {
-            $kernels{$kernel->{version}} = 0;
-        }
-    }
+    my ($guestos, $desc, $virtio) = @_;
 
     my @remove_kernels = ();
 
     # Remove foreign hypervisor specific kernels from the list of available
     # kernels
     foreach my $kernel (_find_hv_kernels($desc)) {
-        # Remove the kernel from our cache
-        delete($kernels{$kernel});
-
         # Don't actually try to remove them yet in case we remove them all. This
-        # would make your dependency checker unhappy.
+        # might make your dependency checker unhappy.
         push(@remove_kernels, $kernel);
     }
 
-    # Find the highest versioned, virtio capable, installed kernel
+    # Pick first appropriate kernel returned by list_kernels()
     my $boot_kernel;
-    foreach my $kernel (sort {$b cmp $a} (keys(%kernels))) {
-        if($kernels{$kernel}) {
-            $boot_kernel = $kernel;
-            last;
-        }
+    foreach my $kernel ($guestos->list_kernels()) {
+        # Skip kernels we're going to remove
+        next if (grep(/^$kernel$/, @remove_kernels));
+
+        # If we're configuring virtio, check this kernel supports it
+        next if ($virtio && !$guestos->supports_virtio($kernel));
+
+        $boot_kernel = $kernel;
+        last;
     }
 
+    # There should be an installed virtio capable kernel if virtio was installed
+    die("virtio configured, but no virtio kernel found")
+        if ($virtio && !defined($boot_kernel));
+
     # If none of the installed kernels are appropriate, install a new one
     if(!defined($boot_kernel)) {
-        $boot_kernel = $guestos->add_kernel();
+        $boot_kernel = $guestos->install_good_kernel();
     }
 
-    # Check that either there are still kernels in the cache, or we just added a
-    # kernel. If neither of these is the case, we're about to try to remove all
-    # kernels, which will fail unpleasantly. Fail nicely instead.
+    # Check we have a bootable kernel. If we don't, we're probably about to
+    # remove all kernels, which will fail unpleasantly. Fail nicely instead.
     die(user_message(__"No bootable kernels installed, and no replacement ".
-                       "specified in configuration.\nUnable to continue."))
-        unless(keys(%kernels) > 0 || defined($boot_kernel));
+                       "is available.\nUnable to continue."))
+        unless(defined($boot_kernel));
 
     # It's safe to remove kernels now
     foreach my $kernel (@remove_kernels) {
@@ -321,9 +294,6 @@ sub _configure_kernel
         $guestos->remove_kernel($kernel);
     }
 
-    # If we didn't install a new kernel, pick the default kernel
-    $boot_kernel ||= $guestos->get_default_kernel();
-
     return $boot_kernel;
 }
 
diff --git a/lib/Sys/VirtV2V/GuestOS.pm b/lib/Sys/VirtV2V/GuestOS.pm
index 7d3f1c7..57311ec 100644
--- a/lib/Sys/VirtV2V/GuestOS.pm
+++ b/lib/Sys/VirtV2V/GuestOS.pm
@@ -242,23 +242,6 @@ The name of the new disply driver. An example is I<cirrus>.
 
 Update the display driver, if defined, to the given driver.
 
-=item get_default_kernel
-
-get_default_kernel returns the version number of the kernel which will be booted
-according to the current configuration. It examines the guest directly rather
-than relying on the output from Sys::Guestfs::Lib, which may be out of date.
-
-=item add_kernel
-
-add_kernel installs a new kernel. It chooses a kernel label based on the name of
-the default kernel installed in the guest. See L<virt-v2v(5)> for details of how
-files are selected for installation.
-
-add_kernel will also install dependencies of the chosen kernel.
-
-add_kernel returns the version number of the kernel it installed, or undef if it
-did not find a kernel to install.
-
 =item remove_kernel(version)
 
 =over
@@ -271,19 +254,6 @@ The version number of the kernel to be removed.
 
 remove_kernel uninstalls a kernel from the guest.
 
-=item remove_application(name)
-
-=over
-
-=item name
-
-The name of the the application, as it is known to the underlying package
-manager.
-
-=back
-
-remove an application from the guest.
-
 =item get_application_owner(file)
 
 =over
diff --git a/lib/Sys/VirtV2V/GuestOS/RedHat.pm b/lib/Sys/VirtV2V/GuestOS/RedHat.pm
index a5dc1cc..99dcb12 100644
--- a/lib/Sys/VirtV2V/GuestOS/RedHat.pm
+++ b/lib/Sys/VirtV2V/GuestOS/RedHat.pm
@@ -399,13 +399,14 @@ sub _check_augeas_device
     return $augeas;
 }
 
-=item get_default_kernel()
+=item list_kernels()
 
-See BACKEND INTERFACE in L<Sys::VirtV2V::GuestOS> for details.
+List all installed kernels. List the default kernel first, followed by the
+remaining kernels in the order they are listed in grub.
 
 =cut
 
-sub get_default_kernel
+sub list_kernels
 {
     my $self = shift;
 
@@ -429,11 +430,15 @@ sub get_default_kernel
             if defined($default);
         push(@paths, $g->aug_match('/files/boot/grub/menu.lst/title/kernel'));
     };
-
     $self->_augeas_error($@) if ($@);
 
-    my $kernel;
+    my @kernels;
+    my %checked;
     foreach my $path (@paths) {
+        next if ($checked{$path});
+        $checked{$path} = 1;
+
+        my $kernel;
         eval {
             $kernel = $g->aug_get($path);
         };
@@ -443,249 +448,628 @@ sub get_default_kernel
         $kernel = "$grub$kernel" if(defined($grub));
 
         # Check the kernel exists
-        last if($g->exists($kernel));
+        if ($g->exists($kernel)) {
+            # Work out it's version number
+            my $kernel_desc = inspect_linux_kernel($g, $kernel, 'rpm');
 
-        print STDERR user_message(__x("WARNING: grub refers to ".
-                                      "{path}, which doesn't exist.",
-                                      path => $kernel));
-        $kernel = undef;
+            push(@kernels, $kernel_desc->{version});
+        }
+
+        else {
+            warn user_message(__x("WARNING: grub refers to {path}, which ".
+                                  "doesn't exist.",
+                                  path => $kernel));
+        }
     }
 
-    # If we got here, grub doesn't contain any kernels. Give up.
-    die(user_message(__"Unable to find a default kernel"))
-        unless(defined($kernel));
+    return @kernels;
+}
 
-    # Work out it's version number
-    my $kernel_desc = inspect_linux_kernel ($g, $kernel, 'rpm');
+sub _parse_evr
+{
+    my ($evr) = @_;
+
+    $evr =~ /^(?:(\d+):)?([^-]+)(?:-(\S+))?$/ or die();
 
-    return $kernel_desc->{version};
+    my $epoch = $1;
+    my $version = $2;
+    my $release = $3;
+
+    return ($epoch, $version, $release);
 }
 
-=item add_kernel()
+=item install_capability(name)
 
-See BACKEND INTERFACE in L<Sys::VirtV2V::GuestOS> for details.
+Install a capability specified in the configuration file.
 
 =cut
 
-sub add_kernel
+sub install_capability
 {
     my $self = shift;
+    my ($name) = @_;
 
-    my ($kernel_pkg, $kernel_ver, $kernel_arch) = $self->_discover_kernel();
+    my $desc = $self->{desc};
+    my $config = $self->{config};
 
-    # If the guest is using a Xen PV kernel, choose an appropriate normal kernel
-    # replacement
-    if ($kernel_pkg eq "kernel-xen" || $kernel_pkg eq "kernel-xenU") {
-        my $desc = $self->{desc};
-
-        # Make an informed choice about a replacement kernel for distros we know
-        # about
-
-        # RHEL 5
-        if ($desc->{distro} eq 'rhel' && $desc->{major_version} eq '5') {
-            if ($kernel_arch eq 'i686') {
-                # XXX: This assumes that PAE will be available in the
-                # hypervisor. While this is almost certainly true, it's
-                # theoretically possible that it isn't. The information we need
-                # is available in the capabilities XML.
-                # If PAE isn't available, we should choose 'kernel'.
-                $kernel_pkg = 'kernel-PAE';
-            }
+    my $cap;
+    eval {
+        $cap = $config->match_capability($desc, $name);
+    };
+    if ($@) {
+        warn($@);
+        return 0;
+    }
 
-            # There's only 1 kernel package on RHEL 5 x86_64
-            else {
-                $kernel_pkg = 'kernel';
-            }
-        }
+    if (!defined($cap)) {
+        warn(user_message(__x("{name} capability not found in configuration",
+                               name => $name)));
+        return 0;
+    }
 
-        # RHEL 4
-        elsif ($desc->{distro} eq 'rhel' && $desc->{major_version} eq '4') {
-            my $ncpus = $self->get_ncpus();
+    my @install;
+    my @upgrade;
+    my $kernel;
+    foreach my $name (keys(%$cap)) {
+        my $props = $cap->{$name};
+        my $ifinstalled = $props->{ifinstalled};
 
-            if ($kernel_arch eq 'i686') {
-                # If the guest has > 10G RAM, give it a hugemem kernel
-                if ($self->get_memory_kb() > 10 * 1024 * 1024) {
-                    $kernel_pkg = 'kernel-hugemem';
-                }
+        # Parse epoch, version and release from minversion
+        my ($min_epoch, $min_version, $min_release);
+        eval {
+            ($min_epoch, $min_version, $min_release) =
+                _parse_evr($props->{minversion});
+        };
+        if ($@) {
+            die(user_message(__x("Unrecognised format for {field} in config: ".
+                                 "{value}. {field} must be in the format ".
+                                 "[epoch:]version[-release].",
+                                 field => 'minversion',
+                                 value => $props->{minversion})));
+        }
 
-                # SMP kernel for guests with >1 CPU
-                elsif ($ncpus > 1) {
-                    $kernel_pkg = 'kernel-smp';
-                }
+        # Kernels are special
+        if ($name eq 'kernel') {
+            my ($kernel_pkg, $kernel_rpmver, $kernel_arch) =
+                $self->_discover_kernel();
 
-                else {
-                    $kernel_pkg = 'kernel';
-                }
+            my ($kernel_epoch, $kernel_ver, $kernel_release);
+            eval {
+                ($kernel_epoch, $kernel_ver, $kernel_release) =
+                    _parse_evr($kernel_rpmver);
+            };
+            if ($@) {
+                # Don't die here, just make best effort to do a version
+                # comparison by directly comparing the full strings
+                $kernel_epoch = undef;
+                $kernel_ver = $kernel_rpmver;
+                $kernel_release = undef;
+
+                $min_epoch = undef;
+                $min_version = $props->{minversion};
+                $min_release = undef;
             }
 
-            else {
-                if ($ncpus > 8) {
-                    $kernel_pkg = 'kernel-largesmp';
+            # If the guest is using a Xen PV kernel, choose an appropriate
+            # normal kernel replacement
+            if ($kernel_pkg eq "kernel-xen" || $kernel_pkg eq "kernel-xenU") {
+                $kernel_pkg = $self->_get_replacement_kernel_name($kernel_arch);
+
+                # filter out xen/xenU from release field
+                if (defined($kernel_release) &&
+                    $kernel_release =~ /^(\S+?)(xen)?(U)?$/)
+                {
+                    $kernel_release = $1;
                 }
 
-                elsif ($ncpus > 1) {
-                    $kernel_pkg = 'kernel-smp';
+                # If the guest kernel is new enough, but PV, try to replace it
+                # with an equivalent version FV kernel
+                if (_evr_cmp($kernel_epoch, $kernel_ver, $kernel_release,
+                             $min_epoch, $min_version, $min_release) >= 0) {
+                    $kernel = [$kernel_pkg, $kernel_arch,
+                               $kernel_epoch, $kernel_ver, $kernel_release];
                 }
 
+                # Otherwise, just grab the latest
                 else {
-                    $kernel_pkg = 'kernel';
+                    $kernel = [$kernel_pkg, $kernel_arch];
                 }
             }
+
+            # If the kernel is too old, grab the latest replacement
+            elsif (_evr_cmp($kernel_epoch, $kernel_ver, $kernel_release,
+                            $min_epoch, $min_version, $min_release) < 0) {
+                $kernel = [$kernel_pkg, $kernel_arch];
+            }
         }
 
-        # RHEL 3 didn't have a xen kernel
+        else {
+            my @installed = $self->_get_installed($name);
+
+            # Ignore an 'ifinstalled' dep if it's not currently installed
+            next if (@installed == 0 && $ifinstalled);
 
-        # XXX: Could do with a history of Fedora kernels in here
+            # Check if any installed version meets the minimum version
+            my $found = 0;
+            foreach my $app (@installed) {
+                my ($epoch, $version, $release) = @$app;
 
-        # For other distros, be conservative and just return 'kernel'
-        else {
-            $kernel_pkg = 'kernel';
+                if (_evr_cmp($app->[0], $app->[1], $app->[2],
+                             $min_epoch, $min_version, $min_release) >= 0) {
+                    $found = 1;
+                    last;
+                }
+            }
+
+            # Install the latest available version of the dep if it wasn't found
+            if (!$found) {
+                if (@installed == 0) {
+                    push(@install, [$name]);
+                } else {
+                    push(@upgrade, [$name]);
+                }
+            }
         }
     }
 
-    my $version;
+    # Capability is already installed
+    if (!defined($kernel) && @install == 0 && @upgrade == 0) {
+        return 1;
+    }
+
     my $g = $self->{g};
-    my $update_fail = 0;
 
-    # try using up2date / yum if available
-    if ($g->exists('/usr/sbin/up2date') or $g->exists('/usr/bin/yum')) {
+    # List of kernels before the new kernel installation
+    my @k_before = $g->glob_expand('/boot/vmlinuz-*');
+
+    my $success = $self->_install_any($kernel, \@install, \@upgrade);
+
+    # Check to see if we installed a new kernel, and check grub if we did
+    $self->_find_new_kernel(@k_before);
+
+    return $success;
+}
+
+sub _get_replacement_kernel_name
+{
+    my $self = shift;
+    my ($arch) = @_;
 
-        my $desc = $self->{desc};
+    my $desc = $self->{desc};
 
-        my ($min_virtio_ver, @kern_vr, @preinst_cmd, @inst_cmd, $inst_fmt);
+    # Make an informed choice about a replacement kernel for distros we know
+    # about
+
+    # RHEL 5
+    if ($desc->{distro} eq 'rhel' && $desc->{major_version} eq '5') {
+        if ($arch eq 'i686') {
+            # XXX: This assumes that PAE will be available in the hypervisor.
+            # While this is almost certainly true, it's theoretically possible
+            # that it isn't. The information we need is available in the
+            # capabilities XML.  If PAE isn't available, we should choose
+            # 'kernel'.
+            return 'kernel-PAE';
+        }
 
-        # filter out xen/xenU from release field
-        if ($kernel_ver =~ /^(\S+)-(\S+?)(xen)?(U)?$/) {
-            @kern_vr = ($1, $2);
-            $kernel_ver = join('-', @kern_vr);
+        # There's only 1 kernel package on RHEL 5 x86_64
+        else {
+            return 'kernel';
         }
+    }
 
-        # We need to upgrade kernel deps. first to avoid possible conflicts
-        my $deps = ($self->{config}->match_app($desc, $kernel_pkg, $kernel_arch))[1];
-
-        # use up2date when available (RHEL-4 and earlier)
-        if ($g->exists('/usr/sbin/up2date')) {
-             if (_rpmvercmp($kernel_ver, '2.6.9-89.EL') >= 0) {
-                # Install matching kernel version
-                @inst_cmd = ('/usr/bin/python', '-c',
-                    "import sys; sys.path.append('/usr/share/rhn');" .
-                    "import actions.packages;" .
-                    "actions.packages.cfg['forceInstall'] = 1;" .
-                    "actions.packages.update([['$kernel_pkg', " .
-                    "'$kern_vr[0]', '$kern_vr[1]', '']])");
-            } else {
-                # Install latest available kernel version
-                @inst_cmd = ('/usr/sbin/up2date', '-fi', $kernel_pkg);
+    # RHEL 4
+    elsif ($desc->{distro} eq 'rhel' && $desc->{major_version} eq '4') {
+        my $ncpus = $self->get_ncpus();
+
+        if ($arch eq 'i686') {
+            # If the guest has > 10G RAM, give it a hugemem kernel
+            if ($self->get_memory_kb() > 10 * 1024 * 1024) {
+                return 'kernel-hugemem';
             }
 
-            if (@$deps) {
-                use Data::Dumper; print Dumper($deps);
-                @preinst_cmd = ('/usr/sbin/up2date', '-fu', @$deps);
+            # SMP kernel for guests with >1 CPU
+            elsif ($ncpus > 1) {
+                return 'kernel-smp';
+            }
+
+            else {
+                return 'kernel';
             }
         }
-        # use yum when available (RHEL-5 and beyond)
-        elsif ($g->exists('/usr/bin/yum')) {
-            if (_rpmvercmp($kernel_ver, '2.6.18-128.el5') >= 0) {
-                # Install matching kernel version
-                @inst_cmd = ('/usr/bin/yum', '-y', 'install',
-                             "$kernel_pkg-$kernel_ver");
-            } else {
-                # Install latest available kernel version
-                @inst_cmd = ('/usr/bin/yum', '-y', 'install', $kernel_pkg);
+
+        else {
+            if ($ncpus > 8) {
+                return 'kernel-largesmp';
+            }
+
+            elsif ($ncpus > 1) {
+                return 'kernel-smp';
             }
 
-            if ($deps) {
-                @preinst_cmd = ('/usr/bin/yum', '-y', 'upgrade', @$deps);
+            else {
+                return 'kernel';
             }
         }
+    }
 
-        my (@k_before, @k_new);
+    # RHEL 3 didn't have a xen kernel
 
-        # List of kernels before the new kernel installation
-        @k_before = $self->{g}->glob_expand('/boot/vmlinuz-*');
+    # XXX: Could do with a history of Fedora kernels in here
 
-        eval {
-            # Upgrade dependencies if needed
-            if (@preinst_cmd) {
-                $g->command(\@preinst_cmd);
-            }
-            # Install new kernel
-            $g->command(\@inst_cmd);
-        };
+    # For other distros, be conservative and just return 'kernel'
+    return 'kernel';
+}
 
-        if ($@) {
-            $update_fail = 1;
+sub _install_any
+{
+    my $self = shift;
+    my ($kernel, $install, $upgrade) = @_;
+
+    my $g = $self->{g};
+
+    my $resolv_bak = $g->exists('/etc/resolv.conf');
+    $g->mv('/etc/resolv.conf', '/etc/resolv.conf.v2vtmp') if ($resolv_bak);
+
+    # XXX We should get the nameserver from the appliance here. However,
+    # there's no current api other than debug to do this, and in any case
+    # resolv.conf in the appliance is both hardcoded and currently wrong.
+    $g->write_file('/etc/resolv.conf', "nameserver 169.254.2.3", 0);
+
+    my $success;
+    eval {
+        # Try to fetch these dependencies using the guest's native update tool
+        $success = $self->_install_up2date($kernel, $install, $upgrade);
+        $success = $self->_install_yum($kernel, $install, $upgrade)
+            unless ($success);
+
+        # Fall back to local config if the above didn't work
+        $success = $self->_install_config($kernel, $install, $upgrade)
+            unless ($success);
+    };
+    if ($@) {
+        warn($@);
+        $success = 0;
+    }
+
+    $g->mv('/etc/resolv.conf.v2vtmp', '/etc/resolv.conf') if ($resolv_bak);
+
+    # Make augeas reload to pick up any altered configuration
+    eval {
+        $g->aug_load();
+    };
+    $self->_augeas_error($@) if ($@);
+
+    return $success;
+}
+
+sub _install_up2date
+{
+    my $self = shift;
+    my ($kernel, $install, $upgrade) = @_;
+
+    my $g = $self->{g};
+
+    # Check this system has actions.packages
+    return 0 unless ($g->exists('/usr/bin/up2date'));
+
+    # Check this system is registered to rhn
+    return 0 unless ($g->exists('/etc/sysconfig/rhn/systemid'));
+
+    my @pkgs;
+    foreach my $pkg ($kernel, @$install, @$upgrade) {
+        next unless defined($pkg);
+
+        # up2date doesn't do arch
+        my ($name, undef, $epoch, $version, $release) = @$pkg;
+
+        $epoch   ||= "";
+        $version ||= "";
+        $release ||= "";
+
+        push(@pkgs, "['$name', '$version', '$release', '$epoch']");
+    }
+
+    eval {
+         $g->command(['/usr/bin/python', '-c',
+                     "import sys; sys.path.append('/usr/share/rhn'); ".
+                     "import actions.packages;                       ".
+                     "actions.packages.cfg['forceInstall'] = 1;      ".
+                     "ret = actions.packages.update([".join(',', @pkgs)."]); ".
+                     "sys.exit(ret[0]);                              "]);
+    };
+    if ($@) {
+        warn(user_message(__x("Failed to install packages using up2date. ".
+                              "Error message was: {error}",
+                              error => $@)));
+        return 0;
+    }
+
+    return 1;
+}
+
+sub _install_yum
+{
+    my $self = shift;
+    my ($kernel, $install, $upgrade) = @_;
+
+    my $g = $self->{g};
+
+    # Check this system has yum installed
+    return 0 unless ($g->exists('/usr/bin/yum'));
+
+    # Install or upgrade the kernel?
+    # If it isn't installed (because we're replacing a PV kernel), we need to
+    # install
+    # If we're installing a specific version, we need to install
+    # If the kernel package we're installing is already installed and we're
+    # just upgrading to the latest version, we need to upgrade
+    if (defined($kernel)) {
+        my @installed = $self->_get_installed($kernel->[0]);
+
+        # Don't modify the contents of $install and $upgrade in case we fall
+        # through and they're reused in another function
+        if (@installed == 0 || defined($kernel->[2])) {
+            my @tmp = @$install;
+            push(@tmp, $kernel);
+            $install = \@tmp;
+        } else {
+            my @tmp = @$upgrade;
+            push(@tmp, $kernel);
+            $upgrade = \@tmp;
         }
-        else {
+    }
+
+    my $success = 1;
+    YUM: foreach my $task (
+        [ "install", $install, qr/(^No package|already installed)/ ],
+        [ "upgrade", $upgrade, qr/^No Packages/ ]
+    ) {
+        my ($action, $list, $failure) = @$task;
+
+        # We can't do these all in a single transaction, because yum offers us
+        # no way to tell if a transaction partially succeeded
+        foreach my $entry (@$list) {
+            next unless (defined($entry));
 
-            # Figure out which kernel has just been installed
-            foreach my $k ($g->glob_expand('/boot/vmlinuz-*')) {
-                grep(/^$k$/, @k_before) or push(@k_new, $k);
+            # You can't specify epoch without architecture to yum, so we just
+            # ignore epoch and hope
+            my ($name, undef, undef, $version, $release) = @$entry;
+
+            # Construct n-v-r
+            my $pkg = $name;
+            $pkg .= "-$version" if (defined($version));
+            $pkg .= "-$release" if (defined($release));
+
+            my @output;
+            eval {
+                @output = $g->sh_lines("LANG=C /usr/bin/yum -y $action $pkg");
+            };
+            if ($@) {
+                warn(user_message(__x("Failed to install packages using yum. ".
+                                      "Output was: {output}",
+                                      error => $@)));
+                $success = 0;
+                last YUM;
             }
 
-            # version-release of the new kernel package
-            $version = ($g->command_lines(
-                ['rpm', '-qf', '--qf=%{VERSION}-%{RELEASE}', $k_new[0]]))[0];
+            foreach my $line (@output) {
+                # Yum probably just isn't configured. Don't bother with an error
+                # message
+                if ($line =~ /$failure/) {
+                    $success = 0;
+                    last YUM;
+                }
+            }
         }
     }
 
-    if ($update_fail) {
+    return $success;
+}
 
-        my ($app, $depnames);
-        eval {
-            my $desc = $self->{desc};
+sub _install_config
+{
+    my $self = shift;
+    my ($kernel_naevr, $install, $upgrade) = @_;
 
-            ($app, $depnames) =
-                $self->{config}->match_app($desc, $kernel_pkg, $kernel_arch);
-        };
-        # Return undef if we didn't find a kernel
-        if ($@) {
-            print STDERR $@;
-            return undef;
-        }
+    my $g = $self->{g};
+    my $desc = $self->{desc};
 
-        my @missing;
-        if (!$g->exists($self->_transfer_path($app))) {
-            push(@missing, $app);
-        } else {
-            return undef if ($self->_newer_installed($app));
-        }
+    my ($kernel, $user);
+    if (defined($kernel_naevr)) {
+        my ($kernel_pkg, $kernel_arch) = @$kernel_naevr;
 
-        my $user_arch = $kernel_arch eq 'i686' ? 'i386' : $kernel_arch;
+        ($kernel, $user) =
+            $self->{config}->match_app($desc, $kernel_pkg, $kernel_arch);
+    } else {
+        $user = [];
+    }
+
+    foreach my $pkg (@$install, @$upgrade) {
+        push(@$user, $pkg->[0]);
+    }
 
-        my @deps = $self->_get_deppaths(\@missing, $user_arch, @$depnames);
+    my @missing;
+    if (defined($kernel) && !$g->exists($self->_transfer_path($kernel))) {
+        push(@missing, $kernel);
+    }
 
-        # We can't proceed if there are any files missing
-        _die_missing(@missing) if (@missing > 0);
+    my @user_paths = $self->_get_deppaths(\@missing, $desc->{arch}, @$user);
 
-        # Install any required kernel dependencies
-        $self->_install_rpms(1, @deps);
+    # We can't proceed if there are any files missing
+    _die_missing(@missing) if (@missing > 0);
 
-        # Inspect the rpm to work out what kernel version it contains
-        foreach my $file ($g->command_lines
-            (["rpm", "-qlp", $self->_transfer_path($app)]))
-        {
-            if($file =~ m{^/boot/vmlinuz-(.*)$}) {
-                $version = $1;
-                last;
+    # Install any non-kernel requirements
+    $self->_install_rpms(1, @user_paths);
+
+    if (defined($kernel)) {
+        $self->_install_rpms(0, ($kernel));
+    }
+
+    return 1;
+}
+
+=item install_good_kernel
+
+Attempt to install a known-good kernel
+
+=cut
+
+sub install_good_kernel
+{
+    my $self = shift;
+
+    my $g = $self->{g};
+
+    my ($kernel_pkg, $kernel_rpmver, $kernel_arch) = $self->_discover_kernel();
+
+    # If the guest is using a Xen PV kernel, choose an appropriate
+    # normal kernel replacement
+    if ($kernel_pkg eq "kernel-xen" || $kernel_pkg eq "kernel-xenU") {
+        $kernel_pkg = $self->_get_replacement_kernel_name($kernel_arch);
+    }
+
+    # List of kernels before the new kernel installation
+    my @k_before = $g->glob_expand('/boot/vmlinuz-*');
+
+    return undef unless ($self->_install_any([$kernel_pkg, $kernel_arch]));
+
+    my $version = $self->_find_new_kernel(@k_before);
+    die("Couldn't determine version of installed kernel")
+        unless (defined($version));
+
+    return $version;
+}
+
+sub _find_new_kernel
+{
+    my $self = shift;
+
+    my $g = $self->{g};
+
+    # Figure out which kernel has just been installed
+    foreach my $k ($g->glob_expand('/boot/vmlinuz-*')) {
+        if (!grep(/^$k$/, @_)) {
+            # Check which directory in /lib/modules the kernel rpm creates
+            foreach my $file ($g->command_lines (['rpm', '-qlf', $k])) {
+                next unless ($file =~ m{^/lib/modules/([^/]+)$});
+
+                my $version = $1;
+                if ($g->is_dir("/lib/modules/$version")) {
+                    $self->_check_grub($version, $k);
+                    return $version;
+                }
             }
         }
+    }
+    return undef;
+}
 
-        die(user_message(__x("{path} doesn't contain a valid kernel",
-                             path => $app))) if(!defined($version));
+# grubby can sometimes fail to correctly update grub.conf when run from
+# libguestfs. If it looks like this happened, install a new grub config here.
+sub _check_grub
+{
+    my $self = shift;
+    my ($version, $kernel) = @_;
 
-        $self->_install_rpms(0, ($app));
+    my $g = $self->{g};
 
+    # Nothing to do if there's already a grub entry
+    eval {
+        foreach my $augpath
+            ($g->aug_match('/files/boot/grub/menu.lst/title/kernel'))
+        {
+            return if ($g->aug_get($augpath) eq $kernel);
+        }
+    };
+    $self->_augeas_error($@) if ($@);
+
+    my $prefix;
+    if ($self->{desc}->{boot}->{grub_fs} eq "/boot") {
+        $prefix = '';
+    } else {
+        $prefix = '/boot';
     }
 
-    # Make augeas reload so it'll find the new kernel
+    my $initrd = "$prefix/initrd-$version.img";
+    $kernel =~ m{^/boot/(.*)$} or die("kernel in unexpected location: $kernel");
+    my $vmlinuz = "$prefix/$1";
+
+    my $title;
+    # No point in dying if /etc/redhat-release can't be read
     eval {
-        $g->aug_load();
+        ($title) = $g->read_lines('/etc/redhat-release');
     };
+    $title ||= 'Linux';
 
-    $self->_augeas_error($@) if ($@);
+    # This is how new-kernel-pkg does it
+    $title =~ s/ release.*//;
+    $title .= " ($version)";
 
-    return $version;
+    my $default;
+    # Doesn't matter if there's no default
+    eval {
+        $default = $g->aug_get('/files/boot/grub/menu.lst/default');
+    };
+
+    eval {
+        if (defined($default)) {
+            $g->aug_defvar('template',
+                 '/files/boot/grub/menu.lst/title['.($default + 1).']');
+        }
+
+        # If there's no default, take the first entry with a kernel
+        else {
+            my ($match) =
+                $g->aug_match('/files/boot/grub/menu.lst/title/kernel');
+
+            die("No template kernel found in grub") unless(defined($match));
+
+            $match =~ s/\/kernel$//;
+            $g->aug_defvar('template', $match);
+        }
+
+        # Add a new title node at the end
+        $g->aug_defnode('new',
+                        '/files/boot/grub/menu.lst/title[last()+1]',
+                        $title);
+
+        # N.B. Don't change the order of root, kernel and initrd below, or the
+        # guest will not boot.
+
+        # Copy root from the template
+        $g->aug_set('$new/root', $g->aug_get('$template/root'));
+
+        # Set kernel and initrd to the new values
+        $g->aug_set('$new/kernel', $vmlinuz);
+        $g->aug_set('$new/initrd', $initrd);
+
+        # Copy all kernel command-line arguments
+        foreach my $arg ($g->aug_match('$template/kernel/*')) {
+            # kernel arguments don't necessarily have values
+            my $val;
+            eval {
+                $val = $g->aug_get($arg);
+            };
+
+            $arg =~ /([^\/]*)$/;
+            $arg = $1;
+
+            if (defined($val)) {
+                $g->aug_set('$new/kernel/'.$arg, $val);
+            } else {
+                $g->aug_clear('$new/kernel/'.$arg);
+            }
+        }
+
+        my ($new) = $g->aug_match('$new');
+        $new =~ /\[(\d+)\]$/;
+
+        $g->aug_set('/files/boot/grub/menu.lst/default',
+                    defined($1) ? $1 - 1 : 0);
+
+        $g->aug_save();
+    };
+    $self->_augeas_error($@) if ($@);
 }
 
 sub _die_missing
@@ -809,12 +1193,12 @@ sub _get_nevra
 sub _get_installed
 {
     my $self = shift;
-    my ($name, $arch) = @_;
+    my ($name) = @_;
 
     my $g = $self->{g};
 
     my $rpmcmd = ['rpm', '-q', '--qf', '%{EPOCH} %{VERSION} %{RELEASE}\n',
-                  "$name.$arch"];
+                  $name];
     my @output;
     eval {
         @output = $g->command_lines($rpmcmd);
@@ -887,7 +1271,7 @@ sub _newer_installed
 
     my ($name, $epoch, $version, $release, $arch) = $self->_get_nevra($rpm);
 
-    my @installed = $self->_get_installed($name, $arch);
+    my @installed = $self->_get_installed("$name.$arch");
 
     # Search installed rpms matching <name>.<arch>
     my $found = 0;
@@ -955,7 +1339,7 @@ sub _get_deppaths
                     my ($name, undef, undef, undef, $arch) =
                         $self->_get_nevra($path);
 
-                    my @installed = $self->_get_installed($name, $arch);
+                    my @installed = $self->_get_installed("$name.$arch");
 
                     if (@installed > 0) {
                         $required{$path} = 1;
@@ -1056,7 +1440,7 @@ sub _rpmvercmp
 
 =item remove_application(name)
 
-See BACKEND INTERFACE in L<Sys::VirtV2V::GuestOS> for details.
+Uninstall an application.
 
 =cut
 
@@ -1350,94 +1734,6 @@ sub prepare_bootable
             }
         }
 
-        # grubby can sometimes fail to correctly update grub.conf when run from
-        # libguestfs. If it looks like this happened, install a new grub config
-        # here.
-        if (!$found) {
-            # Check that an appropriately named kernel and initrd exist
-            if ($g->exists("/boot/vmlinuz-$version") &&
-                $g->exists("/boot/initrd-$version.img"))
-            {
-                $initrd = "$prefix/initrd-$version.img";
-
-                my $title;
-                # No point in dying if /etc/redhat-release can't be read
-                eval {
-                    ($title) = $g->read_lines('/etc/redhat-release');
-                };
-                $title ||= 'Linux';
-
-                # This is how new-kernel-pkg does it
-                $title =~ s/ release.*//;
-                $title .= " ($version)";
-
-                my $default;
-                eval {
-                    $default = $g->aug_get('/files/boot/grub/menu.lst/default');
-                };
-
-                if (defined($default)) {
-                    $g->aug_defvar('template',
-                         '/files/boot/grub/menu.lst/title['.($default + 1).']');
-                }
-
-                # If there's no default, take the first entry with a kernel
-                else {
-                    my ($match) =
-                        $g->aug_match('/files/boot/grub/menu.lst/title/kernel');
-
-                    die("No template kernel found in grub")
-                        unless(defined($match));
-
-                    $match =~ s/\/kernel$//;
-                    $g->aug_defvar('template', $match);
-                }
-
-                # Add a new title node at the end
-                $g->aug_defnode('new',
-                                '/files/boot/grub/menu.lst/title[last()+1]',
-                                $title);
-
-                # N.B. Don't change the order of root, kernel and initrd below,
-                # or the guest will not boot.
-
-                # Copy root from the template
-                $g->aug_set('$new/root', $g->aug_get('$template/root'));
-
-                # Set kernel and initrd to the new values
-                $g->aug_set('$new/kernel', "$prefix/vmlinuz-$version");
-                $g->aug_set('$new/initrd', "$prefix/initrd-$version.img");
-
-                # Copy all kernel command-line arguments
-                foreach my $arg ($g->aug_match('$template/kernel/*')) {
-                    # kernel arguments don't necessarily have values
-                    my $val;
-                    eval {
-                        $val = $g->aug_get($arg);
-                    };
-
-                    $arg =~ /([^\/]*)$/;
-                    $arg = $1;
-
-                    if (defined($val)) {
-                        $g->aug_set('$new/kernel/'.$arg, $val);
-                    } else {
-                        $g->aug_clear('$new/kernel/'.$arg);
-                    }
-                }
-
-                my ($new) = $g->aug_match('$new');
-                $new =~ /\[(\d+)\]$/;
-
-                $g->aug_set('/files/boot/grub/menu.lst/default',
-                            defined($1) ? $1 - 1 : 0);
-            }
-
-            else {
-                die("Didn't find a grub entry for kernel version $version");
-            }
-        }
-
         $g->aug_save();
     };
 
diff --git a/v2v/virt-v2v.conf b/v2v/virt-v2v.conf
index 3d33918..fa2293e 100644
--- a/v2v/virt-v2v.conf
+++ b/v2v/virt-v2v.conf
@@ -1,6 +1,66 @@
 <virt-v2v>
-  <path-root>/var/lib/virt-v2v/software</path-root>
-  <iso-path>/var/lib/virt-v2v/transfer.iso</iso-path>
+  <!-- Networks -->
+  <!-- Mappings for the defaults in Xen, ESX and libvirt/KVM
+       to the default in libvirt/KVM -->
+  <!--
+  <network type='bridge' name='xenbr1'>
+    <network type='network' name='default'/>
+  </network>
+
+  <network type='bridge' name='VM Network'>
+    <network type='network' name='default'/>
+  </network>
+
+  <network type='network' name='default'>
+    <network type='network' name='default'/>
+  </network>
+  -->
+
+  <!-- If importing to RHEV, you may want to use the default network name
+       'rhevm' instead -->
+  <!--
+  <network type='bridge' name='xenbr1'>
+    <network type='network' name='rhevm'/>
+  </network>
+
+  <network type='bridge' name='VM Network'>
+    <network type='network' name='rhevm'/>
+  </network>
+
+  <network type='network' name='default'>
+    <network type='network' name='rhevm'/>
+  </network>
+  -->
+
+  <!--
+    Capabilities
+
+    You shouldn't need to modify these.
+  -->
+  <capability os='linux' distro='rhel' major='5' name='virtio'>
+    <dep name='kernel' minversion='2.6.18-128.el5'/>
+    <dep name='lvm2' minversion='2.02.40-6.el5'/>
+  </capability>
+
+  <capability os='linux' distro='rhel' major='4' name='virtio'>
+    <dep name='kernel' minversion='2.6.9-89.EL'/>
+  </capability>
+
+  <!--
+    Local applications
+
+    The applications below are required for updating software in a guest which
+    it is not possible to obtain via the network. Note that the software itself
+    is not provided with virt-v2v. virt-v2v will give an error if any of the
+    software listed below is required but not available. In this case, you
+    should obtain the software and copy it locally to the correct location.
+
+    The default set of packages listed below are the oldest packages which
+    supported VirtIO for each OS. They also have a relatively minimal dependency
+    set, which makes them simpler to install during conversion. If you rely on
+    these packages rather than online update, you MUST apply all relevant
+    security patches immediately after conversion.
+  -->
 
   <!-- RHEL 5
        All of these RPMS are from RHEL 5.3, which was the first version of RHEL
@@ -8,20 +68,15 @@
   <app os='linux' distro='rhel' major='5' arch='i686' name='kernel'>
     <path>rhel/5/kernel-2.6.18-128.el5.i686.rpm</path>
     <dep>ecryptfs-utils</dep>
-    <dep>lvm2</dep>
   </app>
   <app os='linux' distro='rhel' major='5' arch='i686' name='kernel-PAE'>
     <path>rhel/5/kernel-PAE-2.6.18-128.el5.i686.rpm</path>
     <dep>ecryptfs-utils</dep>
-    <dep>lvm2</dep>
   </app>
   <app os='linux' distro='rhel' major='5' arch='x86_64' name='kernel'>
     <path>rhel/5/kernel-2.6.18-128.el5.x86_64.rpm</path>
     <dep>ecryptfs-utils</dep>
-    <dep>lvm2</dep>
   </app>
-
-  <!-- RHEL 5 Kernel dependencies -->
   <app os='linux' distro='rhel' major='5' arch='x86_64' name='ecryptfs-utils'>
     <path>rhel/5/ecryptfs-utils-56-8.el5.x86_64.rpm</path>
   </app>
@@ -107,36 +162,7 @@
     <path>windows/rhev-apt.exe</path>
   </app>
 
-  <!-- Networks -->
-  <!-- Mappings for the defaults in Xen, ESX and libvirt/KVM
-       to the default in libvirt/KVM -->
-  <!--
-  <network type='bridge' name='xenbr1'>
-    <network type='network' name='default'/>
-  </network>
-
-  <network type='bridge' name='VM Network'>
-    <network type='network' name='default'/>
-  </network>
-
-  <network type='network' name='default'>
-    <network type='network' name='default'/>
-  </network>
-  -->
-
-  <!-- If importing to RHEV, you may want to use the default network name
-       'rhevm' instead -->
-  <!--
-  <network type='bridge' name='xenbr1'>
-    <network type='network' name='rhevm'/>
-  </network>
-
-  <network type='bridge' name='VM Network'>
-    <network type='network' name='rhevm'/>
-  </network>
-
-  <network type='network' name='default'>
-    <network type='network' name='rhevm'/>
-  </network>
-  -->
+  <!-- Default file locations -->
+  <path-root>/var/lib/virt-v2v/software</path-root>
+  <iso-path>/var/lib/virt-v2v/transfer.iso</iso-path>
 </virt-v2v>
diff --git a/v2v/virt-v2v.conf.pod b/v2v/virt-v2v.conf.pod
index b9bd893..61aea5d 100644
--- a/v2v/virt-v2v.conf.pod
+++ b/v2v/virt-v2v.conf.pod
@@ -68,9 +68,102 @@ virt-v2v may have to install software in a guest during the conversion process
 to ensure it boots. An example is replacing a Xen paravirtualised kernel with a
 normal kernel. This software will be specific to the guest operating system.
 
-Software to be installed is specified in the E<lt>appE<gt> element, which is a
+=head3 Capabilities
+
+A capability describes the set of software required for a specific goal, for
+example VirtIO support.  A capability describes only direct dependencies.
+Transitive dependencies will be resolved by the installation method, for example
+yum or L</"Local Installation">.
+
+E<lt>capabilityE<gt> is a child of the root element. There can be any number of
+E<lt>capabilityE<gt> elements. See L</Searching> for a description of the
+attributes of E<lt>capabilityE<gt> and how they are matched.
+
+Dependencies are specified in the E<lt>depE<gt> element, which has the following
+attributes:
+
+=over
+
+=item name
+
+The symbolic name of a dependency. On an rpm-based system this will be the
+package name. This attribute is required.
+
+=item minversion
+
+The minimum required version of the software. For rpm-based systems this must be
+specified as [epoch:]version[-release]. This attribute is required.
+
+=item ifinstalled
+
+A dependency must normally be installed if it is not present, or upgraded if it
+present but too old. If I<ifinstalled> is 'yes', the dependency will be upgraded
+if is present but too old, but not installed if it is not already present.
+
+=back
+
+=head3 Local Installation
+
+If it is not possible to install required software using the guest's update
+agent, the software can be installed from the conversion host. In this case, it
+must be specified in the E<lt>appE<gt> element. E<lt>appE<gt> is a
 child of the root element. The configuration can specify any number of
-E<lt>appE<gt> elements. E<lt>appE<gt> can have these attributes:
+E<lt>appE<gt> elements. See L</Searching> for a description of the attribute of
+E<lt>appE<gt> and how they are matched.
+
+The E<lt>appE<gt> element must contain a E<lt>pathE<gt> element, which specifies
+the path to the software. It may also contain any number of E<lt>depE<gt>
+elements, which specify the names of additional applications which may need to
+be installed. Each dependency will be resolved in the same way as its parent, by
+looking for a match based on os, distro, major, minor and arch.
+
+virt-v2v will attempt to install dependencies first. A dependency will only be
+installed if it is not already installed, or the installed version is older than
+the specified version. On x86_64, virt-v2v will additionally check if an i386
+version need to by updated, but only if any i386 version of the package is
+already installed.
+
+Paths given in E<lt>pathE<gt> must be absolute, unless there is a top level
+E<lt>path-rootE<gt> element. If it exists, all E<lt>pathE<gt> elements will be
+relative to E<lt>path-rootE<gt>.
+
+virt-v2v passes software to the guest by creating an iso image and passing it to
+the guest as a cd-rom drive. The path to this iso image must be specified in a
+top level E<lt>iso-pathE<gt> element.
+
+The following example specifies the location of 'kernel' for RHEL 5, all minor
+versions, on i686:
+
+ <app os='linux' distro='rhel' major='5' arch='i686' name='kernel'>
+   <path>rhel/5/kernel-2.6.18-128.el5.i686.rpm</path>
+   <dep>ecryptfs-utils</dep>
+   <dep>lvm2</dep>
+ </app>
+ <app os='linux' distro='rhel' major='5' arch='i386' name='ecryptfs-utils'>
+   <path>rhel/5/ecryptfs-utils-56-8.el5.i386.rpm</path>
+ </app>
+ <app os='linux' distro='rhel' major='5' arch='i386' name='lvm2'>
+   <path>rhel/5/lvm2-2.02.40-6.el5.i386.rpm</path>
+   <dep>device-mapper</dep>
+   <dep>device-mapper-event</dep>
+ </app>
+
+ <path-root>/var/lib/virt-v2v/software</path-root>
+ <iso-path>/var/lib/virt-v2v/transfer.iso</iso-path>
+
+The kernel can be found at
+/var/lib/virt-v2v/software/rhel/5/kernel-2.6.18-128.el5.i686.rpm. It has 2
+direct dependencies: ecryptfs and lvm2. ecryptfs-utils has no additional
+dependencies, but lvm2 has 2 further dependencies (not shown for brevity). All
+dependencies will also be installed if they are not present, or are too old. All
+dependency paths are also relative to /var/lib/virt-v2v/software.  virt-v2v will
+create a transfer iso image containing all paths and dependencies at
+/var/lib/virt-v2v/transfer.iso.
+
+=head3 Searching
+
+Both E<lt>capabilityE<gt> and E<lt>appE<gt> are matched in the same way, based
+on the following attributes:
 
 =over
 
@@ -105,11 +198,11 @@ The guest architecture, as returned by virt-inspector.
 
 =back
 
-virt-v2v requests a file from the configuration by its symbolic name, searching
-based on its additional attributes.  If an attribute is missing from the
-E<lt>appE<gt> element, it will match any value. If multiple E<lt>appE<gt>
-elements would match a given search, virt-v2v will choose the most specific
-match. Specifically, it searches in the following order:
+virt-v2v searches for an E<lt>appE<gt> or E<lt>capabilityE<gt> by symbolic name,
+matching based on its additional attributes. If an attribute is missing it will
+match any value. If multiple elements would match a given search, virt-v2v will
+choose the most specific match. Specifically, it searches in the following
+order:
 
 =over
 
@@ -139,57 +232,8 @@ os
 
 =back
 
-If virt-v2v doesn't find a matching E<lt>appE<gt>, it will quit with an error
-describing what it was looking for.
-
-The E<lt>appE<gt> element must contain a E<lt>pathE<gt> element, which specifies
-the path to the software. It may also contain any number of E<lt>depE<gt>
-elements, which specify the names of additional applications which may need to
-be installed. Each dependency will be resolved in the same way as its parent, by
-looking for a match based on os, major, minor and arch.
-
-virt-v2v will attempt to install dependencies first. A dependency will only be
-installed if it is not already installed, or the installed version is older than
-the specified version. On x86_64, virt-v2v will additionally check if an i386
-version need to by updated, but only if any i386 version of the package is
-already installed.
-
-Paths given in E<lt>pathE<gt> must be absolute, unless there is a top level
-E<lt>path-rootE<gt> element. If it exists, all E<lt>pathE<gt> elements will be
-relative to E<lt>path-rootE<gt>.
-
-virt-v2v passes software to the guest by creating an iso image and passing it to
-the guest as a cd-rom drive. The path to this iso image must be specified in a
-top level E<lt>iso-pathE<gt> element.
-
-The following example specifies the location of 'kernel' for RHEL 5, all minor
-versions, on i686:
-
- <path-root>/var/lib/virt-v2v/software</path-root>
- <iso-path>/var/lib/virt-v2v/transfer.iso</iso-path>
-
- <app os='linux' distro='rhel' major='5' arch='i686' name='kernel'>
-   <path>rhel/5/kernel-2.6.18-128.el5.i686.rpm</path>
-   <dep>ecryptfs-utils</dep>
-   <dep>lvm2</dep>
- </app>
- <app os='linux' distro='rhel' major='5' arch='i386' name='ecryptfs-utils'>
-   <path>rhel/5/ecryptfs-utils-56-8.el5.i386.rpm</path>
- </app>
- <app os='linux' distro='rhel' major='5' arch='i386' name='lvm2'>
-   <path>rhel/5/lvm2-2.02.40-6.el5.i386.rpm</path>
-   <dep>device-mapper</dep>
-   <dep>device-mapper-event</dep>
- </app>
-
-The kernel can be found at
-/var/lib/virt-v2v/software/rhel/5/kernel-2.6.18-128.el5.i686.rpm. It has 2
-direct dependencies: ecryptfs and lvm2. ecryptfs-utils has no additional
-dependencies, but lvm2 has 2 further dependencies (not shown for brevity). All
-dependencies will also be installed if they are not present, or are too old. All
-dependency paths are also relative to /var/lib/virt-v2v/software.  virt-v2v will
-create a transfer iso image containing all paths and dependencies at
-/var/lib/virt-v2v/transfer.iso.
+If virt-v2v doesn't find a match it will quit with an error describing what it
+was looking for.
 
 =head1 COPYRIGHT
 
diff --git a/v2v/virt-v2v.pl b/v2v/virt-v2v.pl
index 8395d93..685ade8 100755
--- a/v2v/virt-v2v.pl
+++ b/v2v/virt-v2v.pl
@@ -547,30 +547,16 @@ To perform the conversion, run:
 
 where C<< <domain>.xml >> is the path to the exported guest domain's xml, and
 C<< <pool> >> is the local storage pool where copies of the guest's disks will
-be created. virt-v2v.conf should specify:
+be created. See L<virt-v2v.conf(5)> for a details of virt-v2v.conf.
 
-=over
-
-=item *
-
-a mapping for the guest's network configuration, unless a default was specified
-on the command line with I<--bridge> or I<--network>.
-
-=item *
-
-app definitions for any required replacement kernels.
-
-=back
-
-See L<virt-v2v.conf(5)> for details.
-
-It is possible to avoid specifying replacement kernels in the virt-v2v config
-file by ensuring that the guest has an appropriate kernel installed prior to
-conversion. If your guest uses a Xen paravirtualised kernel (it would be called
-something like kernel-xen or kernel-xenU), you can install a regular kernel,
-which won't reference a hypervisor in its name, alongside it. You shouldn't make
-this newly installed kernel your default kernel because Xen may not boot it.
-virt-v2v will make it the default during conversion.
+If it is not possible to provide software updates over the network in your
+environment, it is still possible to avoid specifying replacement kernels in the
+virt-v2v config file by ensuring that the guest has an appropriate kernel
+installed prior to conversion. If your guest uses a Xen paravirtualised kernel
+(it would be called something like kernel-xen or kernel-xenU), you can install a
+regular kernel, which won't reference a hypervisor in its name, alongside it.
+You shouldn't make this newly installed kernel your default kernel because Xen
+may not boot it.  virt-v2v will make it the default during conversion.
 
 =head2 CONVERTING A GUEST FROM VMWARE ESX
 
@@ -611,9 +597,7 @@ converted.
 
 =back
 
-virt-v2v.conf should specify a mapping for the guest's network configuration,
-unless a default was specified on the command line with I<--bridge> or
-I<--network>. See L<virt-v2v.conf(5)> for details.
+See L<virt-v2v.conf(5)> for a details of virt-v2v.conf.
 
 =head3 Authenticating to the ESX server
 
@@ -677,9 +661,7 @@ virt-v2v -f virt-v2v.conf -o rhev -osd <export_sd> <domain>
 
 =back
 
-Ensure that I<virt-v2v.conf> contains a correct network mapping for your target
-RHEV configuration, or that you have specified a default mapping on the command
-line with either I<--bridge> or I<--network>.
+See L<virt-v2v.conf(5)> for details of virt-v2v.conf.
 
 =head1 RUNNING THE CONVERTED GUEST
 
@@ -806,6 +788,7 @@ Describe the bug accurately, and give a way to reproduce it.
 
 =head1 SEE ALSO
 
+L<virt-v2v.conf(5)>,
 L<virt-manager(1)>,
 L<http://libguestfs.org/>.
 
-- 
1.6.6.1




More information about the Libguestfs mailing list