[Libguestfs] [PATCH] Config: Change config to lookup dependencies by name

Matthew Booth mbooth at redhat.com
Fri Apr 23 09:26:47 UTC 2010


Conversion would fail if it was necessary to install a package, and multiple
architectures of that package were already installed. This was happening
specifically with device-mapper on RHEL 5 conversions.

Unfortunately the flat dependency list in the config file didn't really allow
this to be fixed. The best that could be done is to specify both i386 and x86_64
dependencies, but would mean attempted installation of the i386 version
regardless of whether it was already installed. This would fail if the guest
didn't have i386 dependencies installed, for example glibc.

By making the dependency tree more explicit, we can not only more easily test
for both i386 and x86_64 packages, but also more effectively prune what needs to
be installed.

This fixes RHBZ#583008
---
 lib/Sys/VirtV2V/Config.pm         |    3 +-
 lib/Sys/VirtV2V/GuestOS/RedHat.pm |  134 +++++++++++++++++++++++++++++--------
 v2v/virt-v2v.conf                 |   48 ++++++++++---
 v2v/virt-v2v.conf.pod             |  133 +++++++++++++++++++++++++++----------
 4 files changed, 242 insertions(+), 76 deletions(-)

diff --git a/lib/Sys/VirtV2V/Config.pm b/lib/Sys/VirtV2V/Config.pm
index d90c869..117b55b 100644
--- a/lib/Sys/VirtV2V/Config.pm
+++ b/lib/Sys/VirtV2V/Config.pm
@@ -107,8 +107,7 @@ sub get_transfer_iso
     # config file
     # We use a hash here to avoid duplicates
     my %path_args;
-    foreach my $path ($dom->findnodes('/virt-v2v/app/path/text() | '.
-                                      '/virt-v2v/app/dep/text()')) {
+    foreach my $path ($dom->findnodes('/virt-v2v/app/path/text()')) {
         $path = $path->getData();
 
         # Get the absolute path if iso-root was defined
diff --git a/lib/Sys/VirtV2V/GuestOS/RedHat.pm b/lib/Sys/VirtV2V/GuestOS/RedHat.pm
index b0ef775..a973c19 100644
--- a/lib/Sys/VirtV2V/GuestOS/RedHat.pm
+++ b/lib/Sys/VirtV2V/GuestOS/RedHat.pm
@@ -462,7 +462,7 @@ sub add_kernel
         }
     }
 
-    my ($app, $deps);
+    my ($app, $depnames);
     eval {
         my $desc = $self->{desc};
 
@@ -475,7 +475,8 @@ sub add_kernel
                                  search => $search)));
         }
 
-        ($app, $deps) = $config->match_app($desc, $kernel_pkg, $kernel_arch);
+        ($app, $depnames) =
+            $config->match_app($desc, $kernel_pkg, $kernel_arch);
     };
     # Return undef if we didn't find a kernel
     if ($@) {
@@ -483,14 +484,12 @@ sub add_kernel
         return undef;
     }
 
-    return undef if($self->_is_installed($app));
+    return undef if ($self->_newer_installed($app));
 
-    my @install;
-    # Install any kernel dependencies which aren't already installed
-    foreach my $dep (@$deps) {
-        push(@install, $dep) unless($self->_is_installed($dep));
-    }
-    $self->_install_rpms(1, @install);
+    my $user_arch = $kernel_arch eq 'i686' ? 'i386' : $kernel_arch;
+
+    # Install any required kernel dependencies
+    $self->_install_rpms(1, $self->_get_deppaths($user_arch, @$depnames));
 
     # Inspect the rpm to work out what kernel version it contains
     my $version;
@@ -621,22 +620,17 @@ sub add_application
     my ($app, $deps) = $config->match_app($self->{desc}, $label, $user_arch);
 
     # Nothing to do if it's already installed
-    return if($self->_is_installed($app));
+    return if ($self->_newer_installed($app));
 
     my @install = ($app);
 
     # Add any dependencies which aren't already installed to the install set
-    foreach my $dep (@$deps) {
-        push(@install, $dep) unless ($self->_is_installed($dep));
-    }
+    push(@install, $self->_get_deppaths($user_arch, @$deps));
 
     $self->_install_rpms(1, @install);
 }
 
-
-# Return 1 if the requested rpm, or a newer version, is installed
-# Return 0 otherwise
-sub _is_installed
+sub _get_nevra
 {
     my $self = shift;
     my ($rpm) = @_;
@@ -657,8 +651,15 @@ sub _is_installed
     # Ensure epoch is always numeric
     $epoch = 0 if('(none)' eq $epoch);
 
-    # Search installed rpms matching <name>.<arch>
-    my $found = 0;
+    return ($name, $epoch, $version, $release, $arch);
+}
+
+sub _get_installed
+{
+    my $self = shift;
+    my ($name, $arch) = @_;
+
+    my $g = $self->{g};
 
     my $rpmcmd = ['rpm', '-q', '--qf', '%{EPOCH} %{VERSION} %{RELEASE}\n',
                   "$name.$arch"];
@@ -678,32 +679,60 @@ sub _is_installed
         # a real error.
         my $error = $g->sh("LANG=C '".join("' '", @$rpmcmd)."' 2>&1 ||:");
 
-        return 0 if ($error =~ /not installed/);
+        return () if ($error =~ /not installed/);
 
         die(user_message(__x("Error running {command}: {error}",
                              command => join(' ', @$rpmcmd),
                              error => $error)));
     }
 
+    my @installed;
     foreach my $installed (@output) {
         $installed =~ /^(\S+)\s+(\S+)\s+(\S+)$/
             or die("Unexpected return from rpm command: $installed");
-        my ($iepoch, $iversion, $irelease) = ($1, $2, $3);
+        my ($epoch, $version, $release) = ($1, $2, $3);
 
         # Ensure iepoch is always numeric
-        $iepoch = 0 if('(none)' eq $iepoch);
+        $epoch = 0 if('(none)' eq $epoch);
+
+        push(@installed, [$epoch, $version, $release]);
+    }
+
+    return @installed;
+}
+
+
+# Return 1 if the requested rpm, or a newer version, is installed
+# Return 0 otherwise
+sub _newer_installed
+{
+    my $self = shift;
+    my ($rpm) = @_;
+
+    my $g = $self->{g};
+
+    my ($name, $epoch, $version, $release, $arch) = $self->_get_nevra($rpm);
+
+    my @installed = $self->_get_installed($name, $arch);
+
+    # Search installed rpms matching <name>.<arch>
+    my $found = 0;
+    foreach my $pkg (@installed) {
+        my $iepoch   = $pkg->[0];
+        my $iversion = $pkg->[1];
+        my $irelease = $pkg->[2];
 
         # Skip if installed epoch less than requested version
-        next if($iepoch < $epoch);
+        next if ($iepoch < $epoch);
 
-        if($iepoch eq $epoch) {
+        if ($iepoch eq $epoch) {
             # Skip if installed version less than requested version
-            next if(_rpmvercmp($iversion, $version) < 0);
+            next if (_rpmvercmp($iversion, $version) < 0);
 
             # Skip if install version == requested version, but release less
             # than requested release
-            if($iversion eq $version) {
-                next if(_rpmvercmp($irelease,$release) < 0);
+            if ($iversion eq $version) {
+                next if (_rpmvercmp($irelease, $release) < 0);
             }
         }
 
@@ -713,6 +742,57 @@ sub _is_installed
     return $found;
 }
 
+# Return a list of dependency paths which need to be installed for the given
+# apps
+sub _get_deppaths
+{
+    my $self = shift;
+    my ($arch, @apps) = @_;
+
+    my $desc = $self->{desc};
+    my $config = $self->{config};
+
+    my %required;
+    foreach my $app (@apps) {
+        my ($path, $deps) = $config->match_app($desc, $app, $arch);
+
+        if (!$self->_newer_installed($path)) {
+            $required{$path} = 1;
+
+            foreach my $deppath ($self->_get_deppaths($arch, @$deps)) {
+                $required{$deppath} = 1;
+            }
+        }
+
+        # For x86_64, also check if there is any i386 version installed. If
+        # there is, check if it needs to be upgraded.
+        if ($arch eq 'x86_64') {
+            $path = undef;
+            $deps = undef;
+
+            # It's not an error if no i386 package is available
+            eval {
+                ($path, $deps) = $config->match_app($desc, $app, 'i386');
+            };
+
+            if (defined($path) && !$self->_newer_installed($path)) {
+                my ($name, undef, undef, undef, $arch) =
+                    $self->_get_nevra($path);
+
+                if ($self->_get_installed($name, $arch) > 0) {
+                    $required{$path} = 1;
+
+                    foreach my $deppath ($self->_get_deppaths('i386', @$deps)) {
+                        $required{$deppath} = 1;
+                    }
+                }
+            }
+        }
+    }
+
+    return keys(%required);
+}
+
 # An implementation of rpmvercmp. Compares two rpm version/release numbers and
 # returns -1/0/1 as appropriate.
 # Note that this is intended to have the exact same behaviour as the real
diff --git a/v2v/virt-v2v.conf b/v2v/virt-v2v.conf
index 6b76f79..0ea3c9f 100644
--- a/v2v/virt-v2v.conf
+++ b/v2v/virt-v2v.conf
@@ -7,24 +7,48 @@
        5 to support VirtIO -->
   <app os='rhel' major='5' arch='i686' name='kernel'>
     <path>rhel/5/kernel-2.6.18-128.el5.i686.rpm</path>
-    <dep>rhel/5/ecryptfs-utils-56-8.el5.i386.rpm</dep>
-    <dep>rhel/5/lvm2-2.02.40-6.el5.i386.rpm</dep>
-    <dep>rhel/5/device-mapper-1.02.28-2.el5.i386.rpm</dep>
-    <dep>rhel/5/device-mapper-event-1.02.28-2.el5.i386.rpm</dep>
+    <dep>ecryptfs-utils</dep>
+    <dep>lvm2</dep>
   </app>
   <app os='rhel' major='5' arch='i686' name='kernel-PAE'>
     <path>rhel/5/kernel-PAE-2.6.18-128.el5.i686.rpm</path>
-    <dep>rhel/5/ecryptfs-utils-56-8.el5.i386.rpm</dep>
-    <dep>rhel/5/lvm2-2.02.40-6.el5.i386.rpm</dep>
-    <dep>rhel/5/device-mapper-1.02.28-2.el5.i386.rpm</dep>
-    <dep>rhel/5/device-mapper-event-1.02.28-2.el5.i386.rpm</dep>
+    <dep>ecryptfs-utils</dep>
+    <dep>lvm2</dep>
   </app>
   <app os='rhel' major='5' arch='x86_64' name='kernel'>
     <path>rhel/5/kernel-2.6.18-128.el5.x86_64.rpm</path>
-    <dep>rhel/5/ecryptfs-utils-56-8.el5.x86_64.rpm</dep>
-    <dep>rhel/5/lvm2-2.02.40-6.el5.x86_64.rpm</dep>
-    <dep>rhel/5/device-mapper-1.02.28-2.el5.x86_64.rpm</dep>
-    <dep>rhel/5/device-mapper-event-1.02.28-2.el5.x86_64.rpm</dep>
+    <dep>ecryptfs-utils</dep>
+    <dep>lvm2</dep>
+  </app>
+
+  <!-- RHEL 5 Kernel dependencies -->
+  <app os='rhel' major='5' arch='x86_64' name='ecryptfs-utils'>
+    <path>rhel/5/ecryptfs-utils-56-8.el5.x86_64.rpm</path>
+  </app>
+  <app os='rhel' major='5' arch='i386' name='ecryptfs-utils'>
+    <path>rhel/5/ecryptfs-utils-56-8.el5.i386.rpm</path>
+  </app>
+  <app os='rhel' major='5' arch='x86_64' name='lvm2'>
+    <path>rhel/5/lvm2-2.02.40-6.el5.x86_64.rpm</path>
+    <dep>device-mapper</dep>
+    <dep>device-mapper-event</dep>
+  </app>
+  <app os='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>
+  <app os='rhel' major='5' arch='x86_64' name='device-mapper'>
+    <path>rhel/5/device-mapper-1.02.28-2.el5.x86_64.rpm</path>
+  </app>
+  <app os='rhel' major='5' arch='i386' name='device-mapper'>
+    <path>rhel/5/device-mapper-1.02.28-2.el5.i386.rpm</path>
+  </app>
+  <app os='rhel' major='5' arch='x86_64' name='device-mapper-event'>
+    <path>rhel/5/device-mapper-event-1.02.28-2.el5.x86_64.rpm</path>
+  </app>
+  <app os='rhel' major='5' arch='i386' name='device-mapper-event'>
+    <path>rhel/5/device-mapper-event-1.02.28-2.el5.i386.rpm</path>
   </app>
 
   <!-- RHEL 4
diff --git a/v2v/virt-v2v.conf.pod b/v2v/virt-v2v.conf.pod
index 20df3ae..ec470db 100644
--- a/v2v/virt-v2v.conf.pod
+++ b/v2v/virt-v2v.conf.pod
@@ -133,16 +133,19 @@ 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 paths to dependencies of the application.
+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.
+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 both the E<lt>pathE<gt> and E<lt>depE<gt> must be absolute,
-unless there is a top level E<lt>path-rootE<gt> element. If it exists, all
-E<lt>pathE<gt> and E<lt>depE<gt> elements will be relative to
-E<lt>path-rootE<gt>.
+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
@@ -156,17 +159,25 @@ versions, on i686:
 
  <app os='rhel' major='5' arch='i686' name='kernel'>
    <path>rhel/5/kernel-2.6.18-128.el5.i686.rpm</path>
-   <dep>rhel/5/ecryptfs-utils-56-8.el5.i386.rpm</dep>
-   <dep>rhel/5/lvm2-2.02.40-6.el5.i386.rpm</dep>
-   <dep>rhel/5/device-mapper-1.02.28-2.el5.i386.rpm</dep>
-   <dep>rhel/5/device-mapper-event-1.02.28-2.el5.i386.rpm</dep>
+   <dep>ecryptfs-utils</dep>
+   <dep>lvm2</dep>
+ </app>
+ <app os='rhel' major='5' arch='i386' name='ecryptfs-utils'>
+   <path>rhel/5/ecryptfs-utils-56-8.el5.i386.rpm</path>
+ </app>
+ <app os='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 4
-dependencies, which will 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/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.
 
 =head1 EXAMPLE
@@ -176,39 +187,69 @@ virt-v2v distribution. The majority of the file specifies the locations of
 replacement FV kernels for RHEL 4 and RHEL 5. These will be required when
 converting Xen guests which use a PV kernel.
 
-It also specifies that guests using a bridge called either 'xenbr1' (the Xen
-default) or 'VM Network' (the VMware ESX default) should be mapped to the local
-managed network called 'default'.
+It also contains 2 example network mappings, both commented out, which provide
+mappings for:
+
+* a bridge called 'xenbr1' (the Xen default)
+* a network called 'VM Network' (the VMware ESX default)
+* a network called 'default' (the libvirt default)
+
+The first commented out section will map all of the above to the libvirt
+default, the second to the RHEV default.
 
  <virt-v2v>
    <path-root>/var/lib/virt-v2v/software</path-root>
    <iso-path>/var/lib/virt-v2v/transfer.iso</iso-path>
-
+ 
    <!-- RHEL 5
         All of these RPMS are from RHEL 5.3, which was the first version of RHEL
         5 to support VirtIO -->
    <app os='rhel' major='5' arch='i686' name='kernel'>
      <path>rhel/5/kernel-2.6.18-128.el5.i686.rpm</path>
-     <dep>rhel/5/ecryptfs-utils-56-8.el5.i386.rpm</dep>
-     <dep>rhel/5/lvm2-2.02.40-6.el5.i386.rpm</dep>
-     <dep>rhel/5/device-mapper-1.02.28-2.el5.i386.rpm</dep>
-     <dep>rhel/5/device-mapper-event-1.02.28-2.el5.i386.rpm</dep>
+     <dep>ecryptfs-utils</dep>
+     <dep>lvm2</dep>
    </app>
    <app os='rhel' major='5' arch='i686' name='kernel-PAE'>
      <path>rhel/5/kernel-PAE-2.6.18-128.el5.i686.rpm</path>
-     <dep>rhel/5/ecryptfs-utils-56-8.el5.i386.rpm</dep>
-     <dep>rhel/5/lvm2-2.02.40-6.el5.i386.rpm</dep>
-     <dep>rhel/5/device-mapper-1.02.28-2.el5.i386.rpm</dep>
-     <dep>rhel/5/device-mapper-event-1.02.28-2.el5.i386.rpm</dep>
+     <dep>ecryptfs-utils</dep>
+     <dep>lvm2</dep>
    </app>
    <app os='rhel' major='5' arch='x86_64' name='kernel'>
      <path>rhel/5/kernel-2.6.18-128.el5.x86_64.rpm</path>
-     <dep>rhel/5/ecryptfs-utils-56-8.el5.x86_64.rpm</dep>
-     <dep>rhel/5/lvm2-2.02.40-6.el5.x86_64.rpm</dep>
-     <dep>rhel/5/device-mapper-1.02.28-2.el5.x86_64.rpm</dep>
-     <dep>rhel/5/device-mapper-event-1.02.28-2.el5.x86_64.rpm</dep>
+     <dep>ecryptfs-utils</dep>
+     <dep>lvm2</dep>
    </app>
-
+ 
+   <!-- RHEL 5 Kernel dependencies -->
+   <app os='rhel' major='5' arch='x86_64' name='ecryptfs-utils'>
+     <path>rhel/5/ecryptfs-utils-56-8.el5.x86_64.rpm</path>
+   </app>
+   <app os='rhel' major='5' arch='i386' name='ecryptfs-utils'>
+     <path>rhel/5/ecryptfs-utils-56-8.el5.i386.rpm</path>
+   </app>
+   <app os='rhel' major='5' arch='x86_64' name='lvm2'>
+     <path>rhel/5/lvm2-2.02.40-6.el5.x86_64.rpm</path>
+     <dep>device-mapper</dep>
+     <dep>device-mapper-event</dep>
+   </app>
+   <app os='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>
+   <app os='rhel' major='5' arch='x86_64' name='device-mapper'>
+     <path>rhel/5/device-mapper-1.02.28-2.el5.x86_64.rpm</path>
+   </app>
+   <app os='rhel' major='5' arch='i386' name='device-mapper'>
+     <path>rhel/5/device-mapper-1.02.28-2.el5.i386.rpm</path>
+   </app>
+   <app os='rhel' major='5' arch='x86_64' name='device-mapper-event'>
+     <path>rhel/5/device-mapper-event-1.02.28-2.el5.x86_64.rpm</path>
+   </app>
+   <app os='rhel' major='5' arch='i386' name='device-mapper-event'>
+     <path>rhel/5/device-mapper-event-1.02.28-2.el5.i386.rpm</path>
+   </app>
+ 
    <!-- RHEL 4
         All of these RPMs are from RHEL 4.8, which was the first version of RHEL
         4 to support VirtIO -->
@@ -230,17 +271,39 @@ managed network called 'default'.
    <app os='rhel' major='4' arch='x86_64' name='kernel-largesmp'>
      <path>rhel/4/kernel-largesmp-2.6.9-89.EL.x86_64.rpm</path>
    </app>
-
+ 
    <!-- Networks -->
-   <!-- The default Xen bridge name -->
+   <!-- 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>
-
-   <!-- The default ESX bridge name -->
+ 
    <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>
+   -->
  </virt-v2v>
 
 =head1 COPYRIGHT
-- 
1.6.6.1




More information about the Libguestfs mailing list