Matthew Booth
2010-Feb-09 16:01 UTC
[Libguestfs] [PATCH 1/6] Convert config file to XML, and translate networks/bridge for all connections
Previously, only the LibVirtXML connection translated network and bridge names in imported metadata. This change moves this functionality in Converter, making it available to LibVirt connections as well. At the same time, the format of the config file is switched to XML. The primary driver for this is that the allowable syntax of a foreign network/bridge name is not known. Rather than create a new format which deals with this, I have switched to an existing one. Note that this change doesn't update GuestOS's use of the config file. Until this is restored it is not possible to install software in a guest, and therefore not possible to convert a PV xen guest. --- MANIFEST | 2 +- lib/Sys/VirtV2V/Connection/LibVirtXML.pm | 73 +++----------------------- lib/Sys/VirtV2V/Converter.pm | 81 +++++++++++++++++++++++++++++- v2v/virt-v2v.conf | 35 ------------- v2v/virt-v2v.pl | 29 ++++++---- v2v/virt-v2v.xml | 62 +++++++++++++++++++++++ 6 files changed, 169 insertions(+), 113 deletions(-) delete mode 100644 v2v/virt-v2v.conf create mode 100644 v2v/virt-v2v.xml diff --git a/MANIFEST b/MANIFEST index 3d6bf00..2513714 100644 --- a/MANIFEST +++ b/MANIFEST @@ -35,6 +35,6 @@ t/003-syntax.t t/004-ExecHelper.t TODO v2v/run-v2v-locally -v2v/virt-v2v.conf +v2v/virt-v2v.xml v2v/virt-v2v.conf.pod v2v/virt-v2v.pl diff --git a/lib/Sys/VirtV2V/Connection/LibVirtXML.pm b/lib/Sys/VirtV2V/Connection/LibVirtXML.pm index 6867a9b..5d0ebbc 100644 --- a/lib/Sys/VirtV2V/Connection/LibVirtXML.pm +++ b/lib/Sys/VirtV2V/Connection/LibVirtXML.pm @@ -39,7 +39,7 @@ Sys::VirtV2V::Connection::LibVirtXML - Read libvirt XML from a file use Sys::VirtV2V::Connection::LibVirtXML; - $conn = Sys::VirtV2V::Connection::LibVirtXML->new($config, $path); + $conn = Sys::VirtV2V::Connection::LibVirtXML->new($path); $dom = $conn->get_dom(); =head1 DESCRIPTION @@ -52,10 +52,9 @@ file. =over -=item new(config, path) +=item new(path) -Create a new LibVirtXML connection. Configuration for transforming the metadata -is taken from I<config>, and the metadata itself is read from I<path>. +Create a new LibVirtXML connection. The metadata itself is read from I<path>. =cut @@ -63,38 +62,13 @@ sub new { my $class = shift; - my ($config, $path) = @_; + my ($path) = @_; my %obj = (); my $self = \%obj; bless($self, $class); - if(defined($config)) { - my %bridges; - my %networks; - - $self->{bridges} = \%bridges; - $self->{networks} = \%networks; - - # Split bridges and networks into separate hashes - foreach my $directive (keys(%$config)) { - if($directive =~ /^bridge\.(.*)$/) { - $bridges{$1} = $config->{$directive}; - } - - elsif($directive =~ /^network\.(.*)$/) { - $networks{$1} = $config->{$directive}; - } - - else { - die(__x("WARNING: unknown configuration directive ". - "{directive} in {name} section.", - directive => $directive, name => 'libvirtxml')); - } - } - } - $self->_get_dom($path); # No transfer methods defined yet @@ -109,12 +83,9 @@ sub _get_dom # Open the input file my $xml; # Implicitly closed on function exit - if(!open($xml, '<', $self->{path})) { - print STDERR user_message - (__x("Failed to open {path}: {error}", - path => $self->{path}, error => $!)); - return undef; - } + open($xml, '<', $self->{path}) + or die(user_message(__x("Failed to open {path}: {error}", + path => $self->{path}, error => $!))); # Parse the input file my $parser = new XML::DOM::Parser; @@ -122,34 +93,8 @@ sub _get_dom eval { $dom = $parser->parse ($xml); }; # Display any parse errors - if ($@) { - print STDERR user_message - (__x("Unable to parse {path}: {error}", - path => $self->{path}, error => $@)); - return undef; - } - - # Rewrite bridge names - foreach my $bridge - ($dom->findnodes("/domain/devices/interface[\@type='bridge']/". - "source/\@bridge")) - { - my $name = $bridge->getNodeValue(); - if(exists($self->{bridges}->{$name})) { - $bridge->setNodeValue($self->{bridges}->{$name}); - } - } - - # Rewrite network names - foreach my $network - ($dom->findnodes("/domain/devices/interface[\@type='network']/". - "source/\@network")) - { - my $name = $network->getNodeValue(); - if(exists($self->{networks}->{$name})) { - $network->setNodeValue($self->{networks}->{$name}); - } - } + die(user_message(__x("Unable to parse {path}: {error}", + path => $self->{path}, error => $@))) if ($@); return $dom; } diff --git a/lib/Sys/VirtV2V/Converter.pm b/lib/Sys/VirtV2V/Converter.pm index 81abb02..4b11efd 100644 --- a/lib/Sys/VirtV2V/Converter.pm +++ b/lib/Sys/VirtV2V/Converter.pm @@ -130,9 +130,10 @@ sub convert { my $class = shift; - my ($vmm, $guestos, $dom, $desc) = @_; + my ($vmm, $guestos, $config, $dom, $desc) = @_; carp("convert called without vmm argument") unless defined($vmm); carp("convert called without guestos argument") unless defined($guestos); + carp("convert called without config argument") unless defined($config); carp("convert called without dom argument") unless defined($dom); carp("convert called without desc argument") unless defined($desc); @@ -149,6 +150,9 @@ sub convert die(user_message(__"Unable to find a module to convert this guest")) unless (defined($guestcaps)); + # Map network names from config + _map_networks($dom, $config); + # Convert the metadata _convert_metadata($vmm, $dom, $desc, $guestcaps); @@ -469,6 +473,81 @@ sub _unconfigure_xen_metadata # /domain/bootloader_args } +sub _map_networks +{ + my ($dom, $config) = @_; + + # Iterate over interfaces + foreach my $if ($dom->findnodes('/domain/devices/interface')) + { + my $type = $if->getAttribute('type'); + + my $name; + if ($type eq 'bridge') { + ($name) = $if->findnodes('source/@bridge'); + } elsif ($type eq 'network') { + ($name) = $if->findnodes('source/@network'); + } else { + print STDERR user_message (__x("Unknown interface type {type} in ". + "domain XML: {domain}", + type => $type, + domain => $dom->toString())); + exit(1); + } + + _update_interface($if, $name, $type, $config); + } +} + +sub _update_interface +{ + my ($if, $oldname, $oldtype, $config) = @_; + + my $oldnameval = $oldname->getValue(); + my ($mapping) = $config->findnodes + ("/virt-v2v/network[\@type='$oldtype' and \@name='$oldnameval']". + "/network"); + + unless (defined($mapping)) { + print STDERR user_message(__x("No mapping found for '{type}' ". + "interface: {name}", + type => $oldtype, + name => $oldnameval)); + next; + } + + my $newtype = $mapping->getAttributeNode('type'); + $newtype &&= $newtype->getValue(); + my $newname = $mapping->getAttributeNode('name'); + $newname &&= $newname->getValue(); + + # Check type and name are defined for the mapping + unless (defined($newtype) && defined($newname)) { + print STDERR user_message(__x("WARNING: Invalid network ". + "mapping in config: {config}", + config => $mapping->toString())); + return; + } + + # Check type is something we recognise + unless ($newtype eq 'network' || $newtype eq 'bridge') { + print STDERR user_message(__x("WARNING: Unknown interface type ". + "{type} in network mapping: {config}", + type => $newtype, + config => $mapping->toString())); + } + + my ($source) = $if->findnodes('source'); + + # Replace @bridge or @network in the source element with the correct mapped + # attribute name and value + $source->removeAttributeNode($oldname); + $source->setAttribute($newtype, $newname); + + # Update the type of the interface + $if->setAttribute('type', $newtype); +} + =back =head1 COPYRIGHT diff --git a/v2v/virt-v2v.conf b/v2v/virt-v2v.conf deleted file mode 100644 index 86e0b02..0000000 --- a/v2v/virt-v2v.conf +++ /dev/null @@ -1,35 +0,0 @@ -[aliases] -#### RHEL 5 -# Install a regular kernel in place of a xen kernel -rhel.5.kernel-xen=kernel -rhel.4.kernel-xenU=kernel - -[files] -rhel.5.i686.kernel=/var/lib/virt-v2v/kernel-2.6.18-128.1.14.el5.i686.rpm -rhel.5.x86_64.kernel=/var/lib/virt-v2v/kernel-2.6.18-128.4.1.el5.x86_64.rpm - -# The RHEL 5.3 kernel conflicts with older versions of ecryptfs-utils -rhel.5.i386.ecryptfs-utils=/var/lib/virt-v2v/ecryptfs-utils-56-8.el5.i386.rpm - -# The following userspace packages are required on RHEL 5 prior to RHEL 5.3 to -# suport virtio -rhel.5.i386.lvm2=/var/lib/virt-v2v/lvm2-2.02.40-6.el5.i386.rpm -rhel.5.i386.device-mapper=/var/lib/virt-v2v/device-mapper-1.02.28-2.el5.i386.rpm -rhel.5.i386.device-mapper-event=/var/lib/virt-v2v/device-mapper-event-1.02.28-2.el5.i386.rpm - -#### RHEL 4 -rhel.4.i686.kernel=/var/lib/virt-v2v/kernel-2.6.9-89.EL.i686.rpm -rhel.4.x86_64.kernel=/var/lib/virt-v2v/kernel-2.6.9-89.0.3.EL.x86_64.rpm - -[deps] -# Only update userspace on RHEL 5 prior to RHEL 5.3 -rhel.5.2.kernel=ecryptfs-utils lvm2 -rhel.5.1.kernel=ecryptfs-utils lvm2 -rhel.5.0.kernel=ecryptfs-utils lvm2 - -# RPM version dependencies -rhel.5.lvm2=device-mapper -rhel.5.device-mapper=device-mapper-event - -[libvirtxml] -bridge.xenbr1=virbr0 diff --git a/v2v/virt-v2v.pl b/v2v/virt-v2v.pl index 091dc49..e475098 100755 --- a/v2v/virt-v2v.pl +++ b/v2v/virt-v2v.pl @@ -212,17 +212,21 @@ GetOptions ("help|?" => sub { ) or pod2usage(2); # Read the config file if one was given -my $config = {}; +my $config; if(defined($config_file)) { - $config = Config::Tiny->read($config_file); + # Check we can access the config file + die(user_message(__x("Config file {path} doesn't exist", + path => $config_file))) unless (-e $config_file); - # Check we were able to read it - if(!defined($config)) { - print STDERR user_message(__x("Unable to parse {file}: {error}", - file => $config_file, - error => Config::Tiny->errstr)); - exit(1); - } + die(user_message(__x("Don't have permissions to read {path}", + path => $config_file))) unless (-r $config_file); + + eval { + $config = new XML::DOM::Parser->parsefile($config_file); + }; + + die(user_message(__x("Unable to parse config file {path}: {error}", + path => $config_file, error => $@))) if ($@); } # Connect to target libvirt @@ -246,7 +250,7 @@ eval { modulename => 'libvirtxml')); } - $conn = Sys::VirtV2V::Connection::LibVirtXML->new($config, $path); + $conn = Sys::VirtV2V::Connection::LibVirtXML->new($path); } elsif ($input_method eq "libvirt") { @@ -293,7 +297,8 @@ if ($@) { } # Configure GuestOS ([files] and [deps] sections) -Sys::VirtV2V::GuestOS->configure($config); +# Need to fix GuestOS's usage of config for installing applications +Sys::VirtV2V::GuestOS->configure({}); ############################################################################### @@ -316,7 +321,7 @@ my $os = inspect_guest($g); my $guestos = Sys::VirtV2V::GuestOS->instantiate($g, $os); # Modify the guest and its metadata for the target hypervisor -Sys::VirtV2V::Converter->convert($vmm, $guestos, $dom, $os); +Sys::VirtV2V::Converter->convert($vmm, $guestos, $config, $dom, $os); $g->umount_all(); $g->sync(); diff --git a/v2v/virt-v2v.xml b/v2v/virt-v2v.xml new file mode 100644 index 0000000..9d9a38b --- /dev/null +++ b/v2v/virt-v2v.xml @@ -0,0 +1,62 @@ +<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>kernel-2.6.18-128.el5.i686.rpm</path> + <dep>ecryptfs-utils-56-8.el5.i386.rpm</dep> + <dep>lvm2-2.02.40-6.el5.i386.rpm</dep> + <dep>device-mapper-1.02.28-2.el5.i386.rpm</dep> + <dep>device-mapper-event-1.02.28-2.el5.i386.rpm</dep> + </app> + <app os='rhel' major='5' arch='i686' name='kernel-PAE'> + <path>kernel-PAE-2.6.18-128.el5.i686.rpm</path> + <dep>ecryptfs-utils-56-8.el5.i386.rpm</dep> + <dep>lvm2-2.02.40-6.el5.i386.rpm</dep> + <dep>device-mapper-1.02.28-2.el5.i386.rpm</dep> + <dep>device-mapper-event-1.02.28-2.el5.i386.rpm</dep> + </app> + <app os='rhel' major='5' arch='x86_64' name='kernel'> + <path>kernel-2.6.18-128.el5.x86_64.rpm</path> + <dep>ecryptfs-utils-56-8.el5.x86_64.rpm</dep> + <dep>lvm2-2.02.40-6.el5.x86_64.rpm</dep> + <dep>device-mapper-1.02.28-2.el5.x86_64.rpm</dep> + <dep>device-mapper-event-1.02.28-2.el5.x86_64.rpm</dep> + </app> + + <!-- RHEL 4 + All of these RPMs are from RHEL 4.8, which was the first version of RHEL + 4 to support VirtIO --> + <app os='rhel' major='4' arch='i686' name='kernel'> + <path>kernel-2.6.9-89.EL.i686.rpm</path> + </app> + <app os='rhel' major='4' arch='i686' name='kernel-smp'> + <path>kernel-smp-2.6.9-89.EL.i686.rpm</path> + </app> + <app os='rhel' major='4' arch='i686' name='kernel-hugemem'> + <path>kernel-hugemem-2.6.9-89.EL.i686.rpm</path> + </app> + <app os='rhel' major='4' arch='x86_64' name='kernel'> + <path>kernel-2.6.9-89.EL.x86_64.rpm</path> + </app> + <app os='rhel' major='4' arch='x86_64' name='kernel-smp'> + <path>kernel-smp-2.6.9-89.EL.x86_64.rpm</path> + </app> + <app os='rhel' major='4' arch='x86_64' name='kernel-largesmp'> + <path>kernel-largesmp-2.6.9-89.EL.x86_64.rpm</path> + </app> + + <!-- Networks --> + <!-- The default Xen bridge name --> + <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> +</virt-v2v> -- 1.6.6
Matthew Booth
2010-Feb-09 16:01 UTC
[Libguestfs] [PATCH 2/6] Try hard to clean up on unclean shutdown
Set autosync on the libguestfs handle, install signal handlers for SIGINT and SIGQUIT which explicitly close the handle, and add an END { } block which closes the handle in virt-v2v.pl. Note that these measures still don't work when the user kills virt-v2v with Ctrl-C. We suppose this is because qemu is also receiving the SIGINT and shutting down before it can be shut down cleanly by libguestfs. However, the above changes should improve matters if this can be fixed. --- v2v/virt-v2v.pl | 24 ++++++++++++++++++++---- 1 files changed, 20 insertions(+), 4 deletions(-) diff --git a/v2v/virt-v2v.pl b/v2v/virt-v2v.pl index e475098..ee2210f 100755 --- a/v2v/virt-v2v.pl +++ b/v2v/virt-v2v.pl @@ -312,7 +312,10 @@ exit(1) unless(defined($dom)); my @storage = $conn->get_local_storage(); # Open a libguestfs handle on the guest's storage devices -my $g = get_guestfs_handle(@storage); +my $g = get_guestfs_handle(\@storage, $transferiso); + +$SIG{'INT'} = \&close_guest_handle; +$SIG{'QUIT'} = \&close_guest_handle; # Inspect the guest my $os = inspect_guest($g); @@ -323,16 +326,26 @@ my $guestos = Sys::VirtV2V::GuestOS->instantiate($g, $os); # Modify the guest and its metadata for the target hypervisor Sys::VirtV2V::Converter->convert($vmm, $guestos, $config, $dom, $os); -$g->umount_all(); -$g->sync(); - $vmm->define_domain($dom->toString()); exit(0); +# We should always attempt to shut down the guest gracefully +END { + close_guest_handle(); +} + ############################################################################### ## Helper functions +sub close_guest_handle +{ + if (defined($g)) { + $g->umount_all(); + $g->sync(); + } +} + sub get_guestfs_handle { my $g = open_guest(\@_, rw => 1); @@ -344,6 +357,9 @@ sub get_guestfs_handle # Enable selinux in the guest $g->set_selinux(1); + # Enable autosync to defend against data corruption on unclean shutdown + $g->set_autosync(1); + $g->launch (); return $g; -- 1.6.6
Matthew Booth
2010-Feb-09 16:01 UTC
[Libguestfs] [PATCH 3/6] Use add_drive instead of add_cdrom for transfer iso image
There's a current bug in libguestfs which means the order of devices returned by list-devices is unpredictable if there are a mix of drive types. Presenting the transfer iso as a hard disk rather than a cdrom drive works fine, and works around this issue. --- v2v/virt-v2v.pl | 5 ++--- 1 files changed, 2 insertions(+), 3 deletions(-) diff --git a/v2v/virt-v2v.pl b/v2v/virt-v2v.pl index ee2210f..cf78523 100755 --- a/v2v/virt-v2v.pl +++ b/v2v/virt-v2v.pl @@ -350,9 +350,8 @@ sub get_guestfs_handle { my $g = open_guest(\@_, rw => 1); - # Mount the transfer iso if GuestOS needs it - my $transferiso = Sys::VirtV2V::GuestOS->get_transfer_iso(); - $g->add_cdrom($transferiso) if(defined($transferiso)); + # Add the transfer iso if there is one + $g->add_drive($transferiso) if(defined($transferiso)); # Enable selinux in the guest $g->set_selinux(1); -- 1.6.6
Matthew Booth
2010-Feb-09 16:01 UTC
[Libguestfs] [PATCH 4/6] LibVirtXML: Fix errors introduced by 78f88208
dom and path weren't being stored in the newly constructed object. --- lib/Sys/VirtV2V/Connection/LibVirtXML.pm | 12 ++++-------- 1 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/Sys/VirtV2V/Connection/LibVirtXML.pm b/lib/Sys/VirtV2V/Connection/LibVirtXML.pm index 5d0ebbc..874946a 100644 --- a/lib/Sys/VirtV2V/Connection/LibVirtXML.pm +++ b/lib/Sys/VirtV2V/Connection/LibVirtXML.pm @@ -64,8 +64,8 @@ sub new my ($path) = @_; - my %obj = (); - my $self = \%obj; + my $self = {}; + $self->{path} = $path; bless($self, $class); @@ -88,15 +88,11 @@ sub _get_dom path => $self->{path}, error => $!))); # Parse the input file - my $parser = new XML::DOM::Parser; - my $dom; - eval { $dom = $parser->parse ($xml); }; + eval { $self->{dom} = new XML::DOM::Parser->parse ($xml); }; # Display any parse errors - die(user_message(__x("Unable to parse {path}: {error}", + die(user_message(__x("Unable to parse domain from file {path}: {error}", path => $self->{path}, error => $@))) if ($@); - - return $dom; } =back -- 1.6.6
Matthew Booth
2010-Feb-09 16:01 UTC
[Libguestfs] [PATCH 5/6] Converter: Remove disk driver elements other than 'qemu'
QEMU only currently supports the qemu storage driver in libvirt. Remove any other driver element. --- lib/Sys/VirtV2V/Converter.pm | 7 +++++++ 1 files changed, 7 insertions(+), 0 deletions(-) diff --git a/lib/Sys/VirtV2V/Converter.pm b/lib/Sys/VirtV2V/Converter.pm index 4b11efd..edc6589 100644 --- a/lib/Sys/VirtV2V/Converter.pm +++ b/lib/Sys/VirtV2V/Converter.pm @@ -436,6 +436,13 @@ sub _unconfigure_hvs $emulator->getParent()->removeChild($emulator); } + # Remove any disk driver element other than 'qemu' + foreach my $driver + ($dom->findnodes('/domain/devices/disk/driver[@name != \'qemu\']')) + { + $driver->getParentNode()->removeChild($driver); + } + _unconfigure_xen_metadata($dom, $default_dom); } -- 1.6.6
Matthew Booth
2010-Feb-09 16:01 UTC
[Libguestfs] [PATCH 6/6] Fix application installation by GuestOS
Rewrite the code to create the transfer ISO to use the new XML config file format, and move it out of GuestOS in to virt-v2v.pl --- lib/Sys/VirtV2V/GuestOS.pm | 217 +++++++++++++++------------- lib/Sys/VirtV2V/GuestOS/RedHat.pm | 294 ++++++++++++++++--------------------- v2v/virt-v2v.pl | 94 +++++++++++- 3 files changed, 328 insertions(+), 277 deletions(-) diff --git a/lib/Sys/VirtV2V/GuestOS.pm b/lib/Sys/VirtV2V/GuestOS.pm index 1db7590..539659e 100644 --- a/lib/Sys/VirtV2V/GuestOS.pm +++ b/lib/Sys/VirtV2V/GuestOS.pm @@ -43,7 +43,7 @@ Sys::VirtV2V::GuestOS - Manipulate and query a Guest OS use Sys::VirtV2V::GuestOS; - $guestos = Sys::VirtV2V::GuestOS->instantiate($g, $desc); + $guestos = Sys::VirtV2V::GuestOS->new($g, $desc, $dom, $config); =head1 DESCRIPTION @@ -57,25 +57,11 @@ implements methods to access backends. Sys::VirtV2V::GuestOS uses L<Module::Pluggable> to automatically discover backends under Sys::VirtV2V::GuestOS. -=cut - -# A map of file labels to their paths relative to the transfer device -my %files; - -# A map of file labels to their dependencies -my %deps; - -# A map of file labels aliases -my %aliases; - -# The path (on the host) to the transfer iso -my $transferiso; - =head1 METHODS =over -=item instantiate(g, desc) +=item new(g, desc, dom, config) Instantiate a GuestOS object capable of manipulating the target OS. @@ -89,123 +75,162 @@ A L<Sys::Guestfs> handle. An OS description created by L<Sys::Guestfs::Lib>. +=item dom + +An XML::DOM object containing the guest's libvirt domain XML prior to +conversion. + +=item config + +An XML::DOM object containing the virt-v2v configuration. + =back -Returns a capable Sys::VirtV2V::GuestOS backend if one is found. +Returns a capable Sys::VirtV2V::GuestOS if one is found. Returns undef otherwise. =cut -sub instantiate +sub new { my $class = shift; - my ($g, $desc) = @_; - defined($g) or carp("get_instance called without g argument"); - defined($desc) or carp("get_instance called without desc argument"); + my ($g, $desc, $dom, $config) = @_; + defined($g) or carp("instantiate called without g argument"); + defined($desc) or carp("instantiate called without desc argument"); + defined($dom) or carp("instantiate called without dom argument"); + defined($config) or carp("instantiate called without config argument"); + + my $self = {}; + + $self->{g} = $g; + $self->{desc} = $desc; + $self->{dom} = $dom; + $self->{config} = $config; foreach my $module ($class->modules()) { - return $module->new($g, $desc, \%files, \%deps, \%aliases) - if($module->can_handle($desc)); + return $module->new($self) + if($module->can_handle($desc)); } return undef; } -=item configure(config) +=item get_ncpus -=over +Return the number of CPUS which are available to this guest -=item config +=cut -The parsed virt-v2v config file, as returned by Config::Tiny. +sub get_ncpus +{ + my $self = shift; -=back + my ($ncpus) = $self->{dom}->findnodes('/domain/vcpu/text()'); + if (defined($ncpus)) { + return $ncpus->getData(); + } else { + return 1; + } +} + +=item get_memory_kb -Read the [files], [deps] and [aliases] sections of the virt-v2v config file. -Create the transfer iso from the contents of [files]. +Return the amount of memory, in KB, which is available to this guest =cut -sub configure +sub get_memory_kb { - my $class = shift; + my $self = shift; - my $config = shift; + my ($mem_kb) = $self->{dom}->findnodes('/domain/memory/text()'); - carp("configure called without config argument") unless(defined($config)); + return $mem_kb->getData(); +} - # Lookup the [files] config section - my $files_conf = $config->{files}; +=item match_app - # Do nothing if there is no [files] config section - return unless(defined($files_conf)); +Return a matching app entry from the virt-v2v configuration. The entry is +returned as a hashref containing 2 entries. I<path> contains the path to the +application itself. I<deps> contains an arrayref containing the paths of all the +app's listed dependencies. - # A hash, whose labels are filenames to be added to the transfer iso. We use - # a hash here to remove duplicates. - my %paths = (); - foreach my $label (keys(%$files_conf)) { - my $path = $files_conf->{$label}; +=cut - unless(-f $path && -r $path) { - print STDERR user_message(__x("WARNING: unable to access {path}.", - path => $path)); - next; - } +sub match_app +{ + my $self = shift; - $paths{$path} = 1; + my ($name, $arch) = @_; - # As transfer directory hierarchy is flat, remove all directory - # components from paths - my (undef, undef, $filename) = File::Spec->splitpath($path); - $files{$label} = $filename; - } + my $config = $self->{config}; - # Do nothing if there are no files defined - return if(keys(%paths) == 0); - - $transferiso = File::Temp->new(UNLINK => 1, SUFFIX => '.iso'); - my $eh = Sys::VirtV2V::ExecHelper->run - ('mkisofs', '-o', $transferiso, '-r', '-J', - '-V', '__virt-v2v_transfer__', keys(%paths)); - if($eh->status() != 0) { - print STDERR user_message(__x("Failed to create transfer iso. Command ". - "output was:\n{output}", - output => $eh->output())); - } + my $desc = $self->{desc}; + my $distro = $desc->{distro}; + my $major = $desc->{major_version}; + my $minor = $desc->{minor_version}; + + # Check we've got at least a distro from OS detection + die(user_message(__"Didn't detect OS distribution")) + unless (defined($distro)); + + # Create a list of xpath queries against the config which look for a + # matching <app> config entry in descending order of specificity - # Populate deps from the [deps] config section - my $deps_conf = $config->{deps}; + my $prefix = "/virt-v2v/app[\@os='$distro' and \@name='$name'"; - if(defined($deps_conf)) { - # Copy the deps_conf hash into %deps - foreach my $label (keys(%$deps_conf)) { - $deps{$label} = $deps_conf->{$label}; + my @queries; + if (defined($major)) { + if (defined($minor)) { + push(@queries, $prefix." and \@major='$major' ". + "and \@minor='$minor' and \@arch='$arch']"); + push(@queries, $prefix." and \@major='$major' ". + "and \@minor='$minor']"); } + + push(@queries, $prefix." and \@major='$major' and \@arch='$arch']"); + push(@queries, $prefix." and \@major='$major']"); } - # Populate aliases from the [aliases] config section - my $aliases_conf = $config->{aliases}; + push(@queries, $prefix." and \@arch='$arch']"); + push(@queries, $prefix."]"); - if(defined($aliases_conf)) { - # Copy the aliases_conf hash into %aliases - foreach my $label (keys(%$aliases_conf)) { - $aliases{$label} = $aliases_conf->{$label}; - } + # Use the results of the first query which returns a result + my $app; + foreach my $query (@queries) { + ($app) = $config->findnodes($query); + last if (defined($app)); } -} -=item get_transfer_iso + unless (defined($app)) { + my $search = "distro='$distro' name='$name'"; + $search .= " major='$major'" if (defined($major)); + $search .= " minor='$minor'" if (defined($minor)); + $search .= " arch='$arch'"; -Return the path (on the host) to the transfer iso image. L</configure> must have -been called first. + die(user_message(__x("No app in config matches {search}", + search => $search))); + } -=cut + my %app; + my ($path) = $app->findnodes('path/text()'); + die(user_message(__x("app entry in config doesn't contain a path: {xml}", + xml => $app->toString()))) unless (defined($path)); + $path = $path->getData(); -sub get_transfer_iso -{ - return $transferiso; + my @deps; + foreach my $dep ($app->findnodes('dep/text()')) { + push(@deps, $dep->getData()); + } + + # Return a hash containing the application path and its dependencies + my %ret; + $ret{path} = $path; + $ret{deps} = \@deps; + + return \%ret; } =back @@ -242,21 +267,15 @@ A L<Sys::Guestfs> handle. An OS description created by L<Sys::Guestfs::Lib>. -=item files - -A hash containing 'label => filename' mappings. These mappings are consulted -when a guest needs to install a specific application. +=item dom -=item deps +A parsed XML::DOM containing the libvirt domain XML for this guest prior to any +conversion. -A hash containing 'label => C<space separated dependency list>'. The -dependencies are given as labels rather than specific files. This is used to -install dependencies when installing an application in the guest. - -=item aliases +=item config -A hack containing 'label => alias'. Aliases are given as labels rather than -specific files. This is used to substitute packages during installation. +A parsed XML::DOM containing the virt-v2v configuration, or undef if there is +no config. =back diff --git a/lib/Sys/VirtV2V/GuestOS/RedHat.pm b/lib/Sys/VirtV2V/GuestOS/RedHat.pm index 920eea2..d6abe9b 100644 --- a/lib/Sys/VirtV2V/GuestOS/RedHat.pm +++ b/lib/Sys/VirtV2V/GuestOS/RedHat.pm @@ -17,9 +17,13 @@ package Sys::VirtV2V::GuestOS::RedHat; +our @ISA = ('Sys::VirtV2V::GuestOS'); + use strict; use warnings; +use File::Spec; + use Sys::Guestfs::Lib qw(inspect_linux_kernel); use Sys::VirtV2V::UserMessage qw(user_message); @@ -65,7 +69,7 @@ sub can_handle return ($desc->{os} eq 'linux') && ($desc->{package_format} eq 'rpm'); } -=item Sys::VirtV2V::GuestOS::RedHat->new(g, desc, files, deps, aliases) +=item Sys::VirtV2V::GuestOS::RedHat->new(self) See BACKEND INTERFACE in L<Sys::VirtV2V::GuestOS> for details. @@ -75,27 +79,9 @@ sub new { my $class = shift; - my $self = {}; - - # Guest handle - my $g = $self->{g} = shift; - carp("new called without guest handle") unless defined($g); - - # Guest description - $self->{desc} = shift; - carp("new called without guest description") unless defined($self->{desc}); - - # Guest file map - $self->{files} = shift; - carp("new called without files description") unless defined($self->{files}); - - # Guest dependency map - $self->{deps} = shift; - carp("new called without dependencies") unless defined($self->{deps}); - - # Guest alias map - $self->{aliases} = shift; - carp("new called without aliases") unless defined($self->{aliases}); + # Self object + my $self = shift; + carp("new called without self object") unless defined($self); bless($self, $class); @@ -475,32 +461,113 @@ sub add_kernel my ($kernel_pkg, $kernel_arch) = $self->_discover_kernel(); - # Install the kernel's dependencies - $self->_install_rpms(1, $self->_resolve_deps($kernel_pkg)); + # 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'; + } + + # There's only 1 kernel package on RHEL 5 x86_64 + else { + $kernel_pkg = 'kernel'; + } + } + + # RHEL 4 + elsif ($desc->{distro} eq 'rhel' && $desc->{major_version} eq '4') { + my $ncpus = $self->get_ncpus(); + + 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'; + } + + # SMP kernel for guests with >1 CPU + elsif ($ncpus > 1) { + $kernel_pkg = 'kernel-smp'; + } + + else { + $kernel_pkg = 'kernel'; + } + } + + else { + if ($ncpus > 8) { + $kernel_pkg = 'kernel-largesmp'; + } + + elsif ($ncpus > 1) { + $kernel_pkg = 'kernel-smp'; + } + + else { + $kernel_pkg = 'kernel'; + } + } + } - my $filename; + # RHEL 3 didn't have a xen kernel + + # XXX: Could do with a history of Fedora kernels in here + + # For other distros, be conservative and just return 'kernel' + else { + $kernel_pkg = 'kernel'; + } + } + + my $app; eval { - # Get a matching rpm - $filename = $self->_match_file($kernel_pkg, $kernel_arch); + $app = $self->match_app($kernel_pkg, $kernel_arch); }; - # Return undef if we didn't find a kernel + if ($@) { + print STDERR $@; + return undef; + } + + my $path = $app->{path}; + + my @install; + # Install any kernel dependencies which aren't already installed + foreach my $dep (@{$app->{deps}}) { + push(@install, $dep) unless($self->_is_installed($dep)); + } + $self->_install_rpms(1, @install); + return undef if($@); # 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", $filename])) { + foreach my $file ($g->command_lines + (["rpm", "-qlp", $self->_transfer_path($path)])) + { if($file =~ m{^/boot/vmlinuz-(.*)$}) { $version = $1; last; } } - die(user_message(__x("{filename} doesn't contain a valid kernel", - filename => $filename))) if(!defined($version)); + die(user_message(__x("{path} doesn't contain a valid kernel", + path => $path))) if(!defined($version)); - $self->_install_rpms(0, ($filename)); + $self->_install_rpms(0, ($path)); # Make augeas reload so it'll find the new kernel $g->aug_load(); @@ -508,7 +575,7 @@ sub add_kernel return $version; } -# Inspect the guest description to work out what kernel should be installed. +# Inspect the guest description to work out what kernel package is in use # Returns ($kernel_pkg, $kernel_arch) sub _discover_kernel { @@ -602,63 +669,21 @@ sub add_application my $user_arch = $self->{desc}->{arch}; - # Get the rpm for this label - my $rpm = $self->_match_file($label, $user_arch); + my $app = $self->match_app($label, $user_arch); # Nothing to do if it's already installed - return if(_is_installed($rpm)); + return if($self->_is_installed($app->{path})); - my @install = ($rpm); + my @install = ($app->{path}); - # Add the dependencies to the install set - push(@install, $self->_resolve_deps($label)); + # Add any dependencies which aren't already installed to the install set + foreach my $dep (@{$app->{deps}}) { + push(@install, $dep) unless ($self->_is_installed($dep)); + } $self->_install_rpms(1, @install); } -# Return a list of dependencies which must be installed before $label can be -# installed. The list contains paths of rpm files. It does not contain the rpm -# for $label itself. This is so _resolve_deps can be used to install kernel -# dependencies with -U before the kernel itself is installed with -i. -sub _resolve_deps -{ - my $self = shift; - - my ($label, @path) = @_; - - my $user_arch = $self->{desc}->{arch}; - - # Check for an alias for $label - $label = $self->_resolve_alias($label, $user_arch); - - # Check that the dependency path doesn't include the given label. If it - # does, that's a dependency loop. - if(grep(/\Q$label\E/, @path) > 0) { - die(user_message(__x("Found dependency loop installing {label}: {path}", - label => $label, path => join(' ', @path)))); - } - push(@path, $label); - - my $g = $self->{g}; - - my @depfiles = (); - - # Find dependencies for $label - foreach my $dep ($self->_match_deps($label, $user_arch)) { - my $rpm = $self->_match_file($dep, $user_arch); - - # Don't add the dependency if it's already installed - next if($self->_is_installed($rpm)); - - # Add the dependency - push(@depfiles, $rpm); - - # Recursively add dependencies - push(@depfiles, $self->_resolve_deps($dep, @path)); - } - - return @depfiles; -} # Return 1 if the requested rpm, or a newer version, is installed # Return 0 otherwise @@ -669,6 +694,8 @@ sub _is_installed my $g = $self->{g}; + $rpm = $self->_transfer_path($rpm); + # Get NEVRA for the rpm to be installed my $nevra = $g->command(['rpm', '-qp', '--qf', '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}', @@ -828,94 +855,6 @@ sub get_application_owner die($@) if($@); } -# Lookup a guest specific match for the given label -sub _match -{ - my $self = shift; - my ($object, $label, $arch, $hash) = @_; - - my $desc = $self->{desc}; - my $distro = $desc->{distro}; - my $major = $desc->{major_version}; - my $minor = $desc->{minor_version}; - - if(values(%$hash) > 0) { - # Search for a matching entry in the file map, in descending order of - # specificity - for my $name ("$distro.$major.$minor.$arch.$label", - "$distro.$major.$minor.$label", - "$distro.$major.$arch.$label", - "$distro.$major.$label", - "$distro.$arch.$label", - "$distro.$label") { - return $name if(defined($hash->{$name})); - } - } - - die(user_message(__x("No {object} given matching {label}", - object => $object, - label => "$distro.$major.$minor.$arch.$label"))); -} - -# Return the path to an rpm for <label>.<arch> -# Dies if no match is found -sub _match_file -{ - my $self = shift; - my ($label, $arch) = @_; - - # Check for an alias for $label - $label = $self->_resolve_alias($label, $arch); - - my $files = $self->{files}; - - my $name = $self->_match(__"file", $label, $arch, $files); - - # Ensure that whatever file is returned is accessible - $self->_ensure_transfer_mounted(); - - return $self->{transfer_mount}.'/'.$files->{$name}; -} - -# Look for an alias for this label -sub _resolve_alias -{ - my $self = shift; - my ($label, $arch) = @_; - - my $aliases = $self->{aliases}; - - my $alias; - eval { - $alias = $self->_match(__"alias", $label, $arch, $aliases); - }; - - return $aliases->{$alias} if(defined($alias)); - return $label; -} - -# Return a list of labels listed as dependencies of the given label. -# Returns an empty list if no dependencies were specified. -sub _match_deps -{ - my $self = shift; - my ($label, $arch) = @_; - - my $deps = $self->{deps}; - - my $name; - eval { - $name = $self->_match(__"dependencies", $label, $arch, $deps); - }; - - # Return an empty list if there were no dependencies defined - if($@) { - return (); - } else { - return split(/\s+/, $deps->{$name}); - } -} - # Install a set of rpms sub _install_rpms { @@ -926,6 +865,9 @@ sub _install_rpms # Nothing to do if we got an empty set return if(scalar(@rpms) == 0); + # All paths are relative to the transfer mount. Need to make them absolute. + @rpms = map { $_ = $self->_transfer_path($_) } @rpms; + my $g = $self->{g}; eval { $g->command(['rpm', $upgrade == 1 ? '-U' : '-i', @rpms]); @@ -935,6 +877,18 @@ sub _install_rpms die($@) if($@); } +# Get full, local path of a file on the transfer mount +sub _transfer_path +{ + my $self = shift; + + my ($path) = @_; + + $self->_ensure_transfer_mounted(); + + return File::Spec->catfile($self->{transfer_mount}, $path); +} + # Ensure that the transfer device is mounted. If not, mount it. sub _ensure_transfer_mounted { diff --git a/v2v/virt-v2v.pl b/v2v/virt-v2v.pl index cf78523..a0559e9 100755 --- a/v2v/virt-v2v.pl +++ b/v2v/virt-v2v.pl @@ -22,7 +22,9 @@ use strict; use Pod::Usage; use Getopt::Long; #use Data::Dumper; -use Config::Tiny; +use File::Spec; +use File::stat; + use Locale::TextDomain 'virt-v2v'; use Sys::Guestfs; @@ -31,10 +33,11 @@ use Sys::Guestfs::Lib qw(open_guest get_partitions inspect_all_partitions inspect_in_detail); use Sys::VirtV2V; -use Sys::VirtV2V::GuestOS; use Sys::VirtV2V::Converter; use Sys::VirtV2V::Connection::LibVirt; use Sys::VirtV2V::Connection::LibVirtXML; +use Sys::VirtV2V::ExecHelper; +use Sys::VirtV2V::GuestOS; use Sys::VirtV2V::UserMessage qw(user_message); =encoding utf8 @@ -296,10 +299,6 @@ if ($@) { exit(1); } -# Configure GuestOS ([files] and [deps] sections) -# Need to fix GuestOS's usage of config for installing applications -Sys::VirtV2V::GuestOS->configure({}); - ############################################################################### ## Start of processing @@ -311,6 +310,9 @@ exit(1) unless(defined($dom)); # Get a list of the guest's transfered storage devices my @storage = $conn->get_local_storage(); +# Create the transfer iso if required +my $transferiso = get_transfer_iso($config, $config_file); + # Open a libguestfs handle on the guest's storage devices my $g = get_guestfs_handle(\@storage, $transferiso); @@ -321,7 +323,7 @@ $SIG{'QUIT'} = \&close_guest_handle; my $os = inspect_guest($g); # Instantiate a GuestOS instance to manipulate the guest -my $guestos = Sys::VirtV2V::GuestOS->instantiate($g, $os); +my $guestos = Sys::VirtV2V::GuestOS->new($g, $os, $dom, $config); # Modify the guest and its metadata for the target hypervisor Sys::VirtV2V::Converter->convert($vmm, $guestos, $config, $dom, $os); @@ -346,9 +348,85 @@ sub close_guest_handle } } +sub get_transfer_iso +{ + my ($config, $config_file) = @_; + + # Nothing to do if there's no config + return undef unless (defined($config)); + + # path-root doesn't have to be defined + my ($root) = $config->findnodes('/virt-v2v/path-root/text()'); + $root = $root->getData() if (defined($root)); + + # Construct a list of path arguments to mkisofs from paths referenced in the + # config file + # We actually use a hash here to avoid duplicates + my %path_args; + foreach my $path ($config->findnodes('/virt-v2v/app/path/text() | '. + '/virt-v2v/app/dep/text()')) { + $path = $path->getData(); + + # Get the absolute path if iso-root was defined + my $abs; + if (defined($root)) { + $abs = File::Spec->catfile($root, $path); + } else { + $abs = $path; + } + + # Check the referenced path is accessible + die(user_message(__x("Unable to access {path} referenced in ". + "the config file", + path => $path))) unless (-r $abs); + + $path_args{"$path=$abs"} = 1; + } + + # Nothing further to do if there are no paths + return if (keys(%path_args) == 0); + + # Get the path of the transfer iso + my ($iso_path) = $config->findnodes('/virt-v2v/iso-path/text()'); + + # We need this + die(user_message(__"<iso-path> must be specified in the configuration ". + "file")) unless (defined($iso_path)); + $iso_path = $iso_path->getData(); + + # Check that the transfer iso exists, and is newer than the config file + if (-e $iso_path) { + my $iso_st = stat($iso_path) + or die(user_message(__x("Unable to stat iso file {path}: {error}", + path => $iso_path, error => $!))); + + my $config_st = stat($config_file) + or die(user_message(__x("Unable to stat config file {path}: ". + "{error}", + path => $config_file, error => $!))); + + # Don't need to re-create if the iso file is newer than the config file + return $iso_path if ($iso_st->mtime > $config_st->mtime); + } + + # Re-create the transfer iso + my $eh = Sys::VirtV2V::ExecHelper->run + ('mkisofs', '-o', $iso_path, + '-r', '-J', + '-V', '__virt-v2v_transfer__', + '-graft-points', keys(%path_args)); + die(user_message(__x("Failed to create transfer iso. ". + "Command output was:\n{output}", + output => $eh->output()))) unless ($eh->status() == 0); + + return $iso_path; +} + sub get_guestfs_handle { - my $g = open_guest(\@_, rw => 1); + my ($storage, $transferiso) = @_; + + my $g = open_guest($storage, rw => 1); # Add the transfer iso if there is one $g->add_drive($transferiso) if(defined($transferiso)); -- 1.6.6
Reasonably Related Threads
- [PATCH 1/9] Convert config file to XML, and translate networks/bridge for all connections
- [PATCH 1/2] Refactor guest and volume creation into Sys::VirtV2V::Target::LibVirt
- [PATCH] Move all interaction with the config file into Sys::VirtV2V::Config
- [PATCH 1/2] Config: NFC: always create and pass round a Config object
- RHN support and capabilities