This patch series includes a repost of Milan's unmodified RHN support patch because I haven't pushed it yet. On top of that patch, it includes the capabilities patch in as many bits as I could make it into. The big one is 7/8. I've tested all of the following guests both with and without RHN registration: Xen RHEL 54 PV Xen RHEL 51 PV Xen RHEL 48 PV ESX RHEL 54 FV
Matthew Booth
2010-May-19 16:50 UTC
[Libguestfs] [PATCH 1/8] Use up2date / yum to retrieve replacement packages
From: Milan Zazrivec <mzazrivec at redhat.com> During the conversion process, use up2date or yum to download appropriate kernel package and its dependencies from Red Hat Network or previously setup repositories. Install matching kernel version whenever possible, latest kernel version otherwise. _discover_kernel routine has been extended to return version-release of the default kernel found. --- lib/Sys/VirtV2V/GuestOS/RedHat.pm | 166 +++++++++++++++++++++++++++++-------- 1 files changed, 130 insertions(+), 36 deletions(-) diff --git a/lib/Sys/VirtV2V/GuestOS/RedHat.pm b/lib/Sys/VirtV2V/GuestOS/RedHat.pm index 1a7afbd..ffa85be 100644 --- a/lib/Sys/VirtV2V/GuestOS/RedHat.pm +++ b/lib/Sys/VirtV2V/GuestOS/RedHat.pm @@ -471,7 +471,7 @@ sub add_kernel { my $self = shift; - my ($kernel_pkg, $kernel_arch) = $self->_discover_kernel(); + my ($kernel_pkg, $kernel_ver, $kernel_arch) = $self->_discover_kernel(); # If the guest is using a Xen PV kernel, choose an appropriate normal kernel # replacement @@ -543,52 +543,140 @@ sub add_kernel } } - my ($app, $depnames); - eval { + my $version; + 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')) { + my $desc = $self->{desc}; - ($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 ($min_virtio_ver, @kern_vr, @preinst_cmd, @inst_cmd, $inst_fmt); - my @missing; - if (!$self->{g}->exists($self->_transfer_path($app))) { - push(@missing, $app); - } else { - return undef if ($self->_newer_installed($app)); - } + # filter out xen/xenU from release field + if ($kernel_ver =~ /^(\S+)-(\S+?)(xen)?(U)?$/) { + @kern_vr = ($1, $2); + $kernel_ver = join('-', @kern_vr); + } - my $user_arch = $kernel_arch eq 'i686' ? 'i386' : $kernel_arch; + # 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); + } - my @deps = $self->_get_deppaths(\@missing, $user_arch, @$depnames); + if (@$deps) { + use Data::Dumper; print Dumper($deps); + @preinst_cmd = ('/usr/sbin/up2date', '-fu', @$deps); + } + } + # 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); + } - # We can't proceed if there are any files missing - _die_missing(@missing) if (@missing > 0); + if ($deps) { + @preinst_cmd = ('/usr/bin/yum', '-y', 'upgrade', @$deps); + } + } - # Install any required kernel dependencies - $self->_install_rpms(1, @deps); + my (@k_before, @k_new); - # Inspect the rpm to work out what kernel version it contains - my $version; - my $g = $self->{g}; - foreach my $file ($g->command_lines - (["rpm", "-qlp", $self->_transfer_path($app)])) - { - if($file =~ m{^/boot/vmlinuz-(.*)$}) { - $version = $1; - last; + # List of kernels before the new kernel installation + @k_before = $self->{g}->glob_expand('/boot/vmlinuz-*'); + + eval { + # Upgrade dependencies if needed + if (@preinst_cmd) { + $g->command(\@preinst_cmd); + } + # Install new kernel + $g->command(\@inst_cmd); + }; + + if ($@) { + $update_fail = 1; + } + else { + + # 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); + } + + # version-release of the new kernel package + $version = ($g->command_lines( + ['rpm', '-qf', '--qf=%{VERSION}-%{RELEASE}', $k_new[0]]))[0]; } } - die(user_message(__x("{path} doesn't contain a valid kernel", - path => $app))) if(!defined($version)); + if ($update_fail) { + + my ($app, $depnames); + eval { + my $desc = $self->{desc}; + + ($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 @missing; + if (!$g->exists($self->_transfer_path($app))) { + push(@missing, $app); + } else { + return undef if ($self->_newer_installed($app)); + } - $self->_install_rpms(0, ($app)); + my $user_arch = $kernel_arch eq 'i686' ? 'i386' : $kernel_arch; + + my @deps = $self->_get_deppaths(\@missing, $user_arch, @$depnames); + + # We can't proceed if there are any files missing + _die_missing(@missing) if (@missing > 0); + + # Install any required kernel dependencies + $self->_install_rpms(1, @deps); + + # 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; + } + } + + die(user_message(__x("{path} doesn't contain a valid kernel", + path => $app))) if(!defined($version)); + + $self->_install_rpms(0, ($app)); + + } # Make augeas reload so it'll find the new kernel eval { @@ -628,6 +716,7 @@ sub _discover_kernel # Get a current bootable kernel, preferrably the default my $kernel_pkg; + my $kernel_ver; my $kernel_arch; foreach my $i (@configs) { @@ -643,6 +732,11 @@ sub _discover_kernel # Get the kernel package name $kernel_pkg = $kernel->{package}; + + # Get the kernel package version + $kernel_ver = $kernel->{version}; + + last; } # Default to 'kernel' if package name wasn't discovered @@ -660,7 +754,7 @@ sub _discover_kernel # a very long time. $kernel_arch = 'i686' if('i386' eq $kernel_arch); - return ($kernel_pkg, $kernel_arch); + return ($kernel_pkg, $kernel_ver, $kernel_arch); } =item remove_kernel(version) -- 1.6.6.1
Matthew Booth
2010-May-19 16:50 UTC
[Libguestfs] [PATCH 2/8] GuestOS: Remove unused add_application function
--- lib/Sys/VirtV2V/GuestOS.pm | 13 ------------- lib/Sys/VirtV2V/GuestOS/RedHat.pm | 34 ---------------------------------- 2 files changed, 0 insertions(+), 47 deletions(-) diff --git a/lib/Sys/VirtV2V/GuestOS.pm b/lib/Sys/VirtV2V/GuestOS.pm index e5b1704..7d3f1c7 100644 --- a/lib/Sys/VirtV2V/GuestOS.pm +++ b/lib/Sys/VirtV2V/GuestOS.pm @@ -271,19 +271,6 @@ The version number of the kernel to be removed. remove_kernel uninstalls a kernel from the guest. -=item add_application(label) - -=over - -=item label - -The label of the application to be installed. See L<virt-v2v.conf(5)> for more -details. - -=back - -add_application installs an application and its dependencies into the guest. - =item remove_application(name) =over diff --git a/lib/Sys/VirtV2V/GuestOS/RedHat.pm b/lib/Sys/VirtV2V/GuestOS/RedHat.pm index ffa85be..a51b082 100644 --- a/lib/Sys/VirtV2V/GuestOS/RedHat.pm +++ b/lib/Sys/VirtV2V/GuestOS/RedHat.pm @@ -782,40 +782,6 @@ sub remove_kernel $self->_augeas_error($@) if ($@); } -=item add_application(label) - -See BACKEND INTERFACE in L<Sys::VirtV2V::GuestOS> for details. - -=cut - -sub add_application -{ - my $self = shift; - my $label = shift; - - my $desc = $self->{desc}; - my $user_arch = $desc->{arch}; - - my $config = $self->{config}; - my ($app, $deps) = $config->match_app($self->{desc}, $label, $user_arch); - - my @missing; - if (!$self->{g}->exists($self->_transfer_path($app))) { - push(@missing, $app); - } else { - return if ($self->_newer_installed($app)); - } - - my @install = ($app); - - # Add any dependencies which aren't already installed to the install set - push(@install, $self->_get_deppaths(\@missing, $user_arch, @$deps)); - - _die_missing(@missing) if (@missing > 0); - - $self->_install_rpms(1, @install); -} - sub _get_nevra { my $self = shift; -- 1.6.6.1
Matthew Booth
2010-May-19 16:50 UTC
[Libguestfs] [PATCH 3/8] GuestOS: Add _evr_cmp, and update _newer_installed to use it
--- lib/Sys/VirtV2V/GuestOS/RedHat.pm | 42 +++++++++++++++++++++--------------- 1 files changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/Sys/VirtV2V/GuestOS/RedHat.pm b/lib/Sys/VirtV2V/GuestOS/RedHat.pm index a51b082..2fe4640 100644 --- a/lib/Sys/VirtV2V/GuestOS/RedHat.pm +++ b/lib/Sys/VirtV2V/GuestOS/RedHat.pm @@ -853,6 +853,28 @@ sub _get_installed return @installed; } +sub _evr_cmp +{ + my ($e1, $v1, $r1, $e2, $v2, $r2) = @_; + + # Treat epoch as zero if undefined + $e1 ||= 0; + $e2 ||= 0; + + return -1 if ($e1 < $e2); + return 1 if ($e1 > $e2); + + # version must be defined + my $cmp = _rpmvercmp($v1, $v2); + return $cmp if ($cmp != 0); + + # Treat release as the empty string if undefined + $r1 ||= ""; + $r2 ||= ""; + + return _rpmvercmp($r1, $r2); +} + # Return 1 if the requested rpm, or a newer version, is installed # Return 0 otherwise @@ -870,24 +892,8 @@ sub _newer_installed # 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); - - if ($iepoch eq $epoch) { - # Skip if installed version less than requested version - 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); - } - } - + next if _evr_cmp($pkg->[0], $pkg->[1], $pkg->[2], + $epoch, $version, $release) < 0; $found = 1; } -- 1.6.6.1
Matthew Booth
2010-May-19 16:50 UTC
[Libguestfs] [PATCH 4/8] GuestOS: Don't error if initrd doesn't exist before running mkinitrd
--- lib/Sys/VirtV2V/GuestOS/RedHat.pm | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/lib/Sys/VirtV2V/GuestOS/RedHat.pm b/lib/Sys/VirtV2V/GuestOS/RedHat.pm index 2fe4640..a5dc1cc 100644 --- a/lib/Sys/VirtV2V/GuestOS/RedHat.pm +++ b/lib/Sys/VirtV2V/GuestOS/RedHat.pm @@ -1453,7 +1453,7 @@ sub prepare_bootable $initrd = $self->{desc}->{boot}->{grub_fs}.$initrd; # Backup the original initrd - $g->mv("$initrd", "$initrd.pre-v2v"); + $g->mv($initrd, "$initrd.pre-v2v") if ($g->exists($initrd)); # Create a new initrd which probes the required kernel modules my @module_args = (); -- 1.6.6.1
Matthew Booth
2010-May-19 16:50 UTC
[Libguestfs] [PATCH 5/8] Config: Move matching code into _match_element and update match_app to use it
--- lib/Sys/VirtV2V/Config.pm | 108 ++++++++++++++++++++++++++------------------- 1 files changed, 63 insertions(+), 45 deletions(-) diff --git a/lib/Sys/VirtV2V/Config.pm b/lib/Sys/VirtV2V/Config.pm index 95fffb9..46ce0c8 100644 --- a/lib/Sys/VirtV2V/Config.pm +++ b/lib/Sys/VirtV2V/Config.pm @@ -194,7 +194,7 @@ sub get_transfer_iso return $iso_path; } -sub _get_app_search +sub _get_search { my ($desc, $name, $arch) = @_; @@ -208,7 +208,7 @@ sub _get_app_search $search .= " distro='$distro'" if (defined ($distro)); $search .= " major='$major'" if (defined($major)); $search .= " minor='$minor'" if (defined($minor)); - $search .= " arch='$arch'"; + $search .= " arch='$arch'" if (defined($arch)); return $search; } @@ -225,50 +225,11 @@ the app's listed dependencies. sub match_app { my $self = shift; - my ($desc, $name, $arch) = @_; my $dom = $self->{dom}; - die(user_message(__x("No config specified. No app match for {search}", - search => _get_app_search($desc, $name, $arch)))) - unless (defined($dom)); - - my $os = $desc->{os}; - my $distro = $desc->{distro}; - my $major = $desc->{major_version}; - my $minor = $desc->{minor_version}; - - # Check we've got at least the {os} field from OS detection. - die(user_message(__"Didn't detect operating system")) - unless defined $os; - - # Create a list of xpath queries against the config which look for a - # matching <app> config entry in descending order of specificity - my @queries; - if (defined($major)) { - if (defined($minor)) { - push(@queries, _app_query($name, $os, $distro, $major, $minor, $arch)); - push(@queries, _app_query($name, $os, $distro, $major, $minor, undef)); - } - - push(@queries, _app_query($name, $os, $distro, $major, undef, $arch)); - push(@queries, _app_query($name, $os, $distro, $major, undef, undef)); - } - - push(@queries, _app_query($name, $os, $distro, undef, undef, $arch)); - push(@queries, _app_query($name, $os, $distro, undef, undef, undef)); - - # Use the results of the first query which returns a result - my $app; - foreach my $query (@queries) { - ($app) = $dom->findnodes($query); - last if (defined($app)); - } - - die(user_message(__x("No app in config matches {search}", - search => _get_app_search($desc, $name, $arch)))) - unless (defined($app)); + my $app = $self->_match_element('app', $desc, $name, $arch); my %app; my ($path) = $app->findnodes('path/text()'); @@ -284,11 +245,11 @@ sub match_app return ($path, \@deps); } -sub _app_query +sub _match_query { - my ($name, $os, $distro, $major, $minor, $arch) = @_; + my ($type, $name, $os, $distro, $major, $minor, $arch) = @_; - my $query = "/virt-v2v/app[\@name='$name' and \@os='$os' and "; + my $query = "/virt-v2v/".$type."[\@name='$name' and \@os='$os' and "; $query .= defined($distro) ? "\@distro='$distro'" : 'not(@distro)'; $query .= ' and '; $query .= defined($major) ? "\@major='$major'" : 'not(@major)'; @@ -301,6 +262,63 @@ sub _app_query return $query; } +sub _match_element +{ + my $self = shift; + my ($type, $desc, $name, $arch) = @_; + + my $dom = $self->{dom}; + + die(user_message(__x("No config specified. No {type} match for {search}", + type => $type, + search => _get_search($desc, $name, $arch)))) + unless (defined($dom)); + + my $os = $desc->{os}; + my $distro = $desc->{distro}; + my $major = $desc->{major_version}; + my $minor = $desc->{minor_version}; + + # Check we've got at least the {os} field from OS detection. + die(user_message(__"Didn't detect operating system")) + unless (defined $os); + + # Create a list of xpath queries against the config which look for a + # matching <app> config entry in descending order of specificity + my @queries; + if (defined($major)) { + if (defined($minor)) { + push(@queries, _match_query($type, $name, $os, $distro, + $major, $minor, $arch)) + if (defined($arch)); + push(@queries, _match_query($type, $name, $os, $distro, + $major, $minor, undef)); + } + + push(@queries, _match_query($type, $name, $os, $distro, + $major, undef, $arch)) + if (defined($arch)); + push(@queries, _match_query($type, $name, $os, $distro, + $major, undef, undef)); + } + + push(@queries, _match_query($type, $name, $os, $distro, + undef, undef, $arch)) + if (defined($arch)); + push(@queries, _match_query($type, $name, $os, $distro, + undef, undef, undef)); + + # Use the results of the first query which returns a result + foreach my $query (@queries) { + my ($element) = $dom->findnodes($query); + return $element if (defined($element)); + } + + die(user_message(__x("No {type} in config matches {search}", + type => $type, + search => _get_search($desc, $name, $arch)))); +} + =item map_network(oldname, oldtype) Return a new network name/type for I<oldname> and I<oldtype> from the config. -- 1.6.6.1
Matthew Booth
2010-May-19 16:50 UTC
[Libguestfs] [PATCH 6/8] Converter::Linux: Don't pass $virtio to _configure_display_driver
--- lib/Sys/VirtV2V/Converter/Linux.pm | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Sys/VirtV2V/Converter/Linux.pm b/lib/Sys/VirtV2V/Converter/Linux.pm index 6c50cd1..3bcba9f 100644 --- a/lib/Sys/VirtV2V/Converter/Linux.pm +++ b/lib/Sys/VirtV2V/Converter/Linux.pm @@ -113,7 +113,7 @@ sub convert # Configure the rest of the system _configure_console($g); - _configure_display_driver($guestos, $virtio); + _configure_display_driver($guestos); $guestos->remap_block_devices($devices, $virtio); _configure_kernel_modules($guestos, $desc, $virtio); _configure_boot($guestos, $kernel, $virtio); @@ -241,7 +241,7 @@ sub _configure_console sub _configure_display_driver { - my ($guestos, $virtio) = @_; + my ($guestos) = @_; $guestos->update_display_driver("cirrus"); } -- 1.6.6.1
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
Matthew Booth
2010-May-19 16:50 UTC
[Libguestfs] [PATCH 8/8] Update selinux-policy-targeted on RHEL 5 guests if required
--- v2v/virt-v2v.conf | 9 +++++++++ 1 files changed, 9 insertions(+), 0 deletions(-) diff --git a/v2v/virt-v2v.conf b/v2v/virt-v2v.conf index fa2293e..9fbb1cc 100644 --- a/v2v/virt-v2v.conf +++ b/v2v/virt-v2v.conf @@ -40,6 +40,8 @@ <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'/> + <dep name='selinux-policy-targeted' minversion='2.4.6-203.el5' + ifinstalled='yes'/> </capability> <capability os='linux' distro='rhel' major='4' name='virtio'> @@ -105,6 +107,13 @@ <app os='linux' distro='rhel' major='5' arch='i386' name='device-mapper-event'> <path>rhel/5/device-mapper-event-1.02.28-2.el5.i386.rpm</path> </app> + <app os='linux' distro='rhel' major='5' name='selinux-policy-targeted'> + <path>rhel/5/selinux-policy-targeted-2.4.6-203.el5.noarch.rpm</path> + <dep>selinux-policy</dep> + </app> + <app os='linux' distro='rhel' major='5' name='selinux-policy'> + <path>rhel/5/selinux-policy-2.4.6-203.el5.noarch.rpm</path> + </app> <!-- RHEL 4 All of these RPMs are from RHEL 4.8, which was the first version of RHEL -- 1.6.6.1
Maybe Matching Threads
- [PATCH] Use RHN to retrieve replacement packages
- [PATCH 1/6] Convert config file to XML, and translate networks/bridge for all connections
- [PATCH 0/4] Add SUSE guest converter to virt-v2v
- [PATCH 1/9] Convert config file to XML, and translate networks/bridge for all connections
- [PATCH 1/7] Push $desc creation into Sys::VirtConvert::Converter->convert