Matthew Booth
2010-Apr-08 15:58 UTC
[Libguestfs] [PATCH] Move all interaction with the config file into Sys::VirtV2V::Config
This removes another chunk of functionality from GuestOS. --- MANIFEST | 1 + lib/Sys/VirtV2V/Config.pm | 339 +++++++++++++++++++++++++++++++++++++ lib/Sys/VirtV2V/Converter.pm | 56 +----- lib/Sys/VirtV2V/GuestOS.pm | 94 ---------- lib/Sys/VirtV2V/GuestOS/RedHat.pm | 46 ++++-- v2v/virt-v2v.pl | 96 +---------- 6 files changed, 385 insertions(+), 247 deletions(-) create mode 100644 lib/Sys/VirtV2V/Config.pm diff --git a/MANIFEST b/MANIFEST index a83d682..65b5a8e 100644 --- a/MANIFEST +++ b/MANIFEST @@ -6,6 +6,7 @@ lib/Sys/VirtV2V.pm lib/Sys/VirtV2V/ExecHelper.pm lib/Sys/VirtV2V/GuestOS.pm lib/Sys/VirtV2V/GuestOS/RedHat.pm +lib/Sys/VirtV2V/Config.pm lib/Sys/VirtV2V/Converter.pm lib/Sys/VirtV2V/Converter/Linux.pm lib/Sys/VirtV2V/Connection.pm diff --git a/lib/Sys/VirtV2V/Config.pm b/lib/Sys/VirtV2V/Config.pm new file mode 100644 index 0000000..b9e4d37 --- /dev/null +++ b/lib/Sys/VirtV2V/Config.pm @@ -0,0 +1,339 @@ +# Sys::VirtV2V::Config +# Copyright (C) 2009 Red Hat Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +package Sys::VirtV2V::Config; + +use strict; +use warnings; + +use File::Spec; +use File::stat; +use XML::DOM; +use XML::DOM::XPath; + +use Sys::VirtV2V::ExecHelper; +use Sys::VirtV2V::UserMessage qw(user_message); + +use Locale::TextDomain 'virt-v2v'; + +=pod + +=head1 NAME + +Sys::VirtV2V::Config - Manage virt-v2v's configuration file + +=head1 SYNOPSIS + + use Sys::VirtV2V::Config; + + $eh = Sys::VirtV2V::Config->new($config_path); + + my $isopath = $config->get_transfer_iso(); + my ($path, $deps) = $config->match_app($desc, $name, $arch); + my ($name, $type) = $config->map_network($oldname, $oldtype); + +=head1 DESCRIPTION + +Sys::VirtV2V::Config parses and queries the virt-v2v config file. + +=head1 METHODS + +=over + +=item new(path) + +Create a new Sys::VirtV2V::Config object to operate on the config file at +I<path>. + +=cut + +sub new +{ + my $class = shift; + my ($path) = @_; + + my $self = {}; + bless($self, $class); + + die(user_message(__x("Config file {path} doesn't exist", + path => $path))) unless (-e $path); + + die(user_message(__x("Don't have permissions to read {path}", + path => $path))) unless (-r $path); + + eval { + $self->{dom} = new XML::DOM::Parser->parsefile($path); + }; + + die(user_message(__x("Unable to parse config file {path}: {error}", + path => $path, error => $@))) if ($@); + + $self->{path} = $path; + return $self; +} + +=item get_transfer_iso + +Return the path to an iso image containing all software defined in the config +file. Returns undef if no transfer iso is required. + +=cut + +sub get_transfer_iso +{ + my $self = shift; + + my $dom = $self->{dom}; + + # path-root doesn't have to be defined + my ($root) = $dom->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 use a hash here to avoid duplicates + my %path_args; + foreach my $path ($dom->findnodes('/virt-v2v/app/path/text() | '. + '/virt-v2v/app/dep/text()')) { + $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) = $dom->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 if 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($self->{path}) + or die(user_message(__x("Unable to stat config file {path}: ". + "{error}", + path => $self->{path}, 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; +} + +=item get_app_search(desc, name, arch) + +Return a string describing what v2v is looking for in the config file. The +string is intended to be presented to the user to help improve the configuration +file. + +=cut + +sub get_app_search +{ + my ($desc, $name, $arch) = @_; + + my $distro = $desc->{distro}; + my $major = $desc->{major_version}; + my $minor = $desc->{minor_version}; + + my $search = "distro='$distro' name='$name'"; + $search .= " major='$major'" if (defined($major)); + $search .= " minor='$minor'" if (defined($minor)); + $search .= " arch='$arch'"; + + return $search; +} + +=item match_app + +Return a matching app entry from the virt-v2v configuration. The entry is +returned as a list containing 2 values. The first contains the path to the +application itself. The second contains an arrayref containing the paths of all +the app's listed dependencies. + +=cut + +sub match_app +{ + my $self = shift; + + my ($desc, $name, $arch) = @_; + + my $dom = $self->{dom}; + + 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 + my @queries; + if (defined($major)) { + if (defined($minor)) { + push(@queries, _app_query($name, $distro, $major, $minor, $arch)); + push(@queries, _app_query($name, $distro, $major, $minor, undef)); + } + + push(@queries, _app_query($name, $distro, $major, undef, $arch)); + push(@queries, _app_query($name, $distro, $major, undef, undef)); + } + + push(@queries, _app_query($name, $distro, undef, undef, $arch)); + push(@queries, _app_query($name, $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; + 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(); + + my @deps; + foreach my $dep ($app->findnodes('dep/text()')) { + push(@deps, $dep->getData()); + } + + return ($path, \@deps); +} + +sub _app_query +{ + my ($name, $distro, $major, $minor, $arch) = @_; + + my $query = "/virt-v2v/app[\@name='$name' and \@os='$distro' and "; + $query .= defined($major) ? "\@major='$major'" : 'not(@major)'; + $query .= ' and '; + $query .= defined($minor) ? "\@minor='$minor'" : 'not(@minor)'; + $query .= ' and '; + $query .= defined($arch) ? "\@arch='$arch'" : 'not(@arch)'; + $query .= ']'; + + return $query; +} + +=item map_network(oldname, oldtype) + +Return a new network name/type for I<oldname> and I<oldtype> from the config. +Returns a list of 2 values: (I<name>, I<type>) + +=cut + +sub map_network +{ + my $self = shift; + my ($oldname, $oldtype) = @_; + + my ($mapping) = $self->{dom}->findnodes + ("/virt-v2v/network[\@type='$oldtype' and \@name='$oldname']". + "/network"); + + unless (defined($mapping)) { + print STDERR user_message(__x("No mapping found for '{type}' ". + "interface: {name}", + type => $oldtype, + name => $oldname)); + return; + } + + 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())); + } + + return ($newname, $newtype); +} + +=back + +=head1 COPYRIGHT + +Copyright (C) 2010 Red Hat Inc. + +=head1 LICENSE + +Please see the file COPYING.LIB for the full license. + +=head1 SEE ALSO + +L<virt-v2v(1)>, +L<http://libguestfs.org/>. + +=cut + +1; diff --git a/lib/Sys/VirtV2V/Converter.pm b/lib/Sys/VirtV2V/Converter.pm index 5dc8550..08f1e8f 100644 --- a/lib/Sys/VirtV2V/Converter.pm +++ b/lib/Sys/VirtV2V/Converter.pm @@ -442,57 +442,19 @@ sub _map_networks exit(1); } - _update_interface($if, $name, $type, $config); - } -} + my ($newname, $newtype) = $config->map_network($name->getValue(), + $type); -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)); - return; - } + my ($source) = $if->findnodes('source'); - 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; - } + # Replace @bridge or @network in the source element with the correct + # mapped attribute name and value + $source->removeAttributeNode($name); + $source->setAttribute($newtype, $newname); - # 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())); + # Update the type of the interface + $if->setAttribute('type', $newtype); } - - 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 diff --git a/lib/Sys/VirtV2V/GuestOS.pm b/lib/Sys/VirtV2V/GuestOS.pm index 3fb59ce..6165edd 100644 --- a/lib/Sys/VirtV2V/GuestOS.pm +++ b/lib/Sys/VirtV2V/GuestOS.pm @@ -150,100 +150,6 @@ sub get_memory_kb return $mem_kb->getData(); } -=item match_app - -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. - -=cut - -sub match_app -{ - my $self = shift; - - my ($name, $arch) = @_; - - my $config = $self->{config}; - - 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)); - - my $search = "distro='$distro' name='$name'"; - $search .= " major='$major'" if (defined($major)); - $search .= " minor='$minor'" if (defined($minor)); - $search .= " arch='$arch'"; - - die(user_message(__x("No config specified. No app match for {search}", - search => $search))) unless (defined($config)); - - # 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, $distro, $major, $minor, $arch)); - push(@queries, _app_query($name, $distro, $major, $minor, undef)); - } - - push(@queries, _app_query($name, $distro, $major, undef, $arch)); - push(@queries, _app_query($name, $distro, $major, undef, undef)); - } - - push(@queries, _app_query($name, $distro, undef, undef, $arch)); - push(@queries, _app_query($name, $distro, undef, undef, undef)); - - # 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)); - } - - die(user_message(__x("No app in config matches {search}", - search => $search))) unless (defined($app)); - - 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(); - - 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; -} - -sub _app_query -{ - my ($name, $distro, $major, $minor, $arch) = @_; - - my $query = "/virt-v2v/app[\@name='$name' and \@os='$distro' and "; - $query .= defined($major) ? "\@major='$major'" : 'not(@major)'; - $query .= ' and '; - $query .= defined($minor) ? "\@minor='$minor'" : 'not(@minor)'; - $query .= ' and '; - $query .= defined($arch) ? "\@arch='$arch'" : 'not(@arch)'; - $query .= ']'; - - return $query; -} - =back =head1 BACKEND INTERFACE diff --git a/lib/Sys/VirtV2V/GuestOS/RedHat.pm b/lib/Sys/VirtV2V/GuestOS/RedHat.pm index d062da1..f945847 100644 --- a/lib/Sys/VirtV2V/GuestOS/RedHat.pm +++ b/lib/Sys/VirtV2V/GuestOS/RedHat.pm @@ -475,9 +475,20 @@ sub add_kernel } } - my $app; + my ($app, $deps); eval { - $app = $self->match_app($kernel_pkg, $kernel_arch); + my $desc = $self->{desc}; + + my $config = $self->{config}; + unless (defined($config)) { + my $search = Sys::VirtV2V::Config::get_app_search + ($desc, $kernel_pkg, $kernel_arch); + die(user_message(__x("No config specified. No app match for ". + "{search}", + search => $search))); + } + + ($app, $deps) = $config->match_app($desc, $kernel_pkg, $kernel_arch); }; # Return undef if we didn't find a kernel if ($@) { @@ -485,13 +496,11 @@ sub add_kernel return undef; } - my $path = $app->{path}; - - return undef if($self->_is_installed($path)); + return undef if($self->_is_installed($app)); my @install; # Install any kernel dependencies which aren't already installed - foreach my $dep (@{$app->{deps}}) { + foreach my $dep (@$deps) { push(@install, $dep) unless($self->_is_installed($dep)); } $self->_install_rpms(1, @install); @@ -500,7 +509,7 @@ sub add_kernel my $version; my $g = $self->{g}; foreach my $file ($g->command_lines - (["rpm", "-qlp", $self->_transfer_path($path)])) + (["rpm", "-qlp", $self->_transfer_path($app)])) { if($file =~ m{^/boot/vmlinuz-(.*)$}) { $version = $1; @@ -509,9 +518,9 @@ sub add_kernel } die(user_message(__x("{path} doesn't contain a valid kernel", - path => $path))) if(!defined($version)); + path => $app))) if(!defined($version)); - $self->_install_rpms(0, ($path)); + $self->_install_rpms(0, ($app)); # Make augeas reload so it'll find the new kernel $g->aug_load(); @@ -611,17 +620,26 @@ sub add_application my $self = shift; my $label = shift; - my $user_arch = $self->{desc}->{arch}; + my $desc = $self->{desc}; + my $user_arch = $desc->{arch}; + + my $config = $self->{config}; + unless (defined($config)) { + my $search = Sys::VirtV2V::Config::get_app_search($desc, $label, + $user_arch); + die(user_message(__x("No config specified. No app match for {search}", + search => $search))); + } - my $app = $self->match_app($label, $user_arch); + my ($app, $deps) = $config->match_app($self->{desc}, $label, $user_arch); # Nothing to do if it's already installed - return if($self->_is_installed($app->{path})); + return if($self->_is_installed($app)); - my @install = ($app->{path}); + my @install = ($app); # Add any dependencies which aren't already installed to the install set - foreach my $dep (@{$app->{deps}}) { + foreach my $dep (@$deps) { push(@install, $dep) unless ($self->_is_installed($dep)); } diff --git a/v2v/virt-v2v.pl b/v2v/virt-v2v.pl index c7ebad3..66b1844 100755 --- a/v2v/virt-v2v.pl +++ b/v2v/virt-v2v.pl @@ -21,8 +21,6 @@ use strict; use Pod::Usage; use Getopt::Long; -use File::Spec; -use File::stat; use Locale::TextDomain 'virt-v2v'; @@ -32,6 +30,7 @@ use Sys::Guestfs::Lib qw(open_guest get_partitions inspect_all_partitions inspect_in_detail); use Sys::VirtV2V; +use Sys::VirtV2V::Config; use Sys::VirtV2V::Converter; use Sys::VirtV2V::Connection::LibVirt; use Sys::VirtV2V::Connection::LibVirtXML; @@ -215,21 +214,7 @@ GetOptions ("help|?" => sub { # Read the config file if one was given my $config; -if(defined($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); - - 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 ($@); -} +$config = Sys::VirtV2V::Config->new($config_file) if defined($config_file); my $target; if ($output_method eq "libvirt") { @@ -312,7 +297,8 @@ exit(1) unless(defined($dom)); my $storage = $conn->get_storage_paths(); # Create the transfer iso if required -my $transferiso = get_transfer_iso($config, $config_file); +my $transferiso; +$transferiso = $config->get_transfer_iso() if (defined($config)); if ($output_method eq 'rhev') { $) = "36 36"; @@ -380,80 +366,6 @@ 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 ($storage, $transferiso) = @_; -- 1.6.6.1
Richard W.M. Jones
2010-Apr-08 21:42 UTC
[Libguestfs] [PATCH] Move all interaction with the config file into Sys::VirtV2V::Config
On Thu, Apr 08, 2010 at 04:58:27PM +0100, Matthew Booth wrote:> This removes another chunk of functionality from GuestOS.Much as I dislike XML configuration, moving this into a separate module makes sense, ACK. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v
Possibly Parallel Threads
- [PATCH 1/2] Config: NFC: always create and pass round a Config object
- [PATCH 1/6] Convert config file to XML, and translate networks/bridge for all connections
- [PATCH] Config: Don't require all referenced software to be available
- [PATCH 1/9] Convert config file to XML, and translate networks/bridge for all connections
- [PATCH] List all missing dependencies at once