Matthew Booth
2011-Mar-11 13:34 UTC
[Libguestfs] [PATCH 1/2] Allow reading more data than the reported size of a volume
If a volume is not an exact multiple of 512 bytes, qemu-img will report its size rounded down to a 512 byte boundary. However, when copying, the file is still read until EOF, which will return more data than was expected. This change prevents that causing a failure in itself. The situation is still not resolved, however, as there are still situations where this will cause a failure. For example, copying will fail writing to a block device if an attempt is made to write too much data. --- lib/Sys/VirtV2V/Connection/Source.pm | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/lib/Sys/VirtV2V/Connection/Source.pm b/lib/Sys/VirtV2V/Connection/Source.pm index 960aff3..8cbfe25 100644 --- a/lib/Sys/VirtV2V/Connection/Source.pm +++ b/lib/Sys/VirtV2V/Connection/Source.pm @@ -167,7 +167,7 @@ sub _volume_copy v2vdie __x('Didn\'t receive full volume. Received {received} '. 'of {total} bytes.', received => $total, total => $src->get_size()) - if $src->get_format() eq "raw" && $total != $src->get_size(); + if $src->get_format() eq "raw" && $total < $src->get_size(); return $dst; } -- 1.7.4
Matthew Booth
2011-Mar-11 13:34 UTC
[Libguestfs] [PATCH 2/2] Use an internal representation of domain metadata
We previously used libvirt domain XML directly as the internal representation of domain metadata. This change replaces this with a custom representation, described in metadata-format.txt. There are several reasons for this change: * Code to query and modify the new representation is simpler than code to handle XML. * By explicitly parsing only required information from a libvirt source, we can remove a significant amount of code to handle problematic source domain XML which would otherwise have been blindly copied to the output. * By having our own format, we can better serve the needs of multiple source and target hypervisors. --- MANIFEST | 1 + lib/Sys/VirtV2V/Connection/LibVirt.pm | 67 +++++- lib/Sys/VirtV2V/Connection/LibVirtSource.pm | 20 +- lib/Sys/VirtV2V/Connection/LibVirtTarget.pm | 260 +++++++++++------ lib/Sys/VirtV2V/Connection/LibVirtXMLSource.pm | 27 +- lib/Sys/VirtV2V/Connection/RHEVTarget.pm | 92 ++---- lib/Sys/VirtV2V/Connection/Source.pm | 136 ++-------- lib/Sys/VirtV2V/Converter.pm | 361 +----------------------- lib/Sys/VirtV2V/Converter/RedHat.pm | 85 +++---- lib/Sys/VirtV2V/Converter/Windows.pm | 32 +-- metadata-format.txt | 28 ++ v2v/virt-v2v.pl | 36 ++-- 12 files changed, 414 insertions(+), 731 deletions(-) create mode 100644 metadata-format.txt diff --git a/MANIFEST b/MANIFEST index 9c2a5df..71e9a19 100644 --- a/MANIFEST +++ b/MANIFEST @@ -24,6 +24,7 @@ lib/Sys/VirtV2V/Transfer/SSH.pm lib/Sys/VirtV2V/Util.pm MANIFEST.SKIP MANIFEST This list of files +metadata-format.txt META.yml po/es.po po/it.po diff --git a/lib/Sys/VirtV2V/Connection/LibVirt.pm b/lib/Sys/VirtV2V/Connection/LibVirt.pm index 9d322ee..354f227 100644 --- a/lib/Sys/VirtV2V/Connection/LibVirt.pm +++ b/lib/Sys/VirtV2V/Connection/LibVirt.pm @@ -1,5 +1,5 @@ # Sys::VirtV2V::Connection::LibVirt -# Copyright (C) 2009,2010 Red Hat Inc. +# Copyright (C) 2009-2011 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 @@ -151,9 +151,72 @@ sub _get_transfer $format, $is_sparse); } +sub _parse_dom +{ + my ($dom) = @_; + + my %meta; + my $root = $dom->getDocumentElement(); + + $meta{name} = _node_val($root, 'name/text()'); + $meta{memory} = _node_val($root, 'memory/text()') * 1024; + $meta{cpus} = _node_val($root, 'vcpu/text()'); + $meta{arch} = _node_val($root, 'os/type/@arch'); + + $meta{features} = []; + foreach my $feature ($root->findnodes('features/*')) { + push(@{$meta{features}}, $feature->getNodeName()); + } + + $meta{disks} = []; + foreach my $disk ($root->findnodes('devices/disk[@device=\'disk\']')) { + my %info; + + $info{device} = _node_val($disk, 'target/@dev'); + $info{path} = _node_val($disk, 'source/@file | source/@dev'); + $info{is_block} = _node_val($disk, '@type') eq 'file' ? 0 : 1; + $info{format} = _node_val($disk, 'driver/@type'); + + push(@{$meta{disks}}, \%info); + } + + $meta{removables} = []; + foreach my $disk ($root->findnodes('devices/disk[@device=\'cdrom\' or '. + '@device=\'floppy\']')) + { + my %info; + + $info{name} = _node_val($disk, 'target/@dev'); + $info{type} = _node_val($disk, '@device'); + + push(@{$meta{removables}}, \%info); + } + + $meta{nics} = []; + foreach my $nic ($root->findnodes('devices/interface')) { + my %info; + + $info{mac} = _node_val($nic, 'mac/@address'); + $info{vnet} = _node_val($nic, 'source/@network | source/@bridge'); + $info{vnet_type} = _node_val($nic, '@type'); + + push(@{$meta{nics}}, \%info); + } + + return \%meta; +} + +sub _node_val +{ + my ($root, $xpath) = @_; + + my ($node) = $root->findnodes($xpath); + return defined($node) ? $node->getNodeValue() : undef; +} + =head1 COPYRIGHT -Copyright (C) 2009,2010 Red Hat Inc. +Copyright (C) 2009-2011 Red Hat Inc. =head1 LICENSE diff --git a/lib/Sys/VirtV2V/Connection/LibVirtSource.pm b/lib/Sys/VirtV2V/Connection/LibVirtSource.pm index f35feb5..87783e5 100644 --- a/lib/Sys/VirtV2V/Connection/LibVirtSource.pm +++ b/lib/Sys/VirtV2V/Connection/LibVirtSource.pm @@ -1,5 +1,5 @@ -# Sys::VirtV2V::Connection::LibVirt -# Copyright (C) 2009,2010 Red Hat Inc. +# Sys::VirtV2V::Connection::LibVirtSource +# Copyright (C) 2009-2011 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 @@ -21,7 +21,7 @@ use strict; use warnings; use URI; -use XML::DOM; +use XML::DOM::XPath; use Sys::Virt; @@ -49,7 +49,7 @@ Sys::VirtV2V::Connection::LibVirtSource - Get storage and metadata from libvirt $conn = Sys::VirtV2V::Connection::LibVirtSource->new ("xen+ssh://xenserver.example.com/", $name); - $dom = $conn->get_dom(); + $meta = $conn->get_meta(); =head1 DESCRIPTION @@ -78,7 +78,7 @@ sub new $self->{name} = $name; $self->_check_shutdown(); - $self->_get_dom(); + $self->_get_meta(); return $self; } @@ -227,7 +227,7 @@ sub _get_domain return $domain; } -sub _get_dom +sub _get_meta { my $self = shift; @@ -240,17 +240,15 @@ sub _get_dom # Warn and exit if we didn't find it return undef unless(defined($domain)); - my $xml = $domain->get_xml_description(); - - my $dom = new XML::DOM::Parser->parse($xml); - $self->{dom} = $dom; + my $dom = new XML::DOM::Parser->parse($domain->get_xml_description()); + $self->{meta} = Sys::VirtV2V::Connection::LibVirt::_parse_dom($dom); } =back =head1 COPYRIGHT -Copyright (C) 2009,2010 Red Hat Inc. +Copyright (C) 2009-2011 Red Hat Inc. =head1 LICENSE diff --git a/lib/Sys/VirtV2V/Connection/LibVirtTarget.pm b/lib/Sys/VirtV2V/Connection/LibVirtTarget.pm index a74c978..7d00921 100644 --- a/lib/Sys/VirtV2V/Connection/LibVirtTarget.pm +++ b/lib/Sys/VirtV2V/Connection/LibVirtTarget.pm @@ -1,5 +1,5 @@ # Sys::VirtV2V::Connection::LibVirtTarget -# Copyright (C) 2010 Red Hat Inc. +# Copyright (C) 2010-2011 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 @@ -261,7 +261,7 @@ sub guest_exists return 1; } -=item create_guest(desc, dom, guestcaps) +=item create_guest(desc, meta, config, guestcaps) Create the guest in the target @@ -270,19 +270,164 @@ Create the guest in the target sub create_guest { my $self = shift; - my ($desc, $dom, $guestcaps) = @_; + my ($desc, $meta, $config, $guestcaps) = @_; my $vmm = $self->{vmm}; - _unconfigure_incompatible_devices($dom); - _configure_capabilities($vmm, $dom, $guestcaps); + _configure_capabilities($vmm, $meta, $guestcaps); - $vmm->define_domain($dom->toString()); + $vmm->define_domain(_meta_to_domxml($meta, $config, $guestcaps)); # Guest is successfully created, don't remove its volumes @cleanup_vols = (); } +sub _meta_to_domxml +{ + my ($meta, $config, $guestcaps) = @_; + + my $dom = new XML::DOM::Parser->parse(<<DOM); +<domain type='kvm'> + <os> + <type>hvm</type> + <boot dev='hd'/> + </os> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>restart</on_crash> + <devices> + <input type='tablet' bus='usb'/> + <input type='mouse' bus='ps2'/> + <graphics type='vnc' port='-1' listen='127.0.0.1'/> + <video> + <model type='cirrus' vram='9216' heads='1'/> + </video> + <console type='pty'/> + </devices> +</domain> +DOM + + my $root = $dom->getDocumentElement(); + + _append_elem($root, 'name', $meta->{name}); + _append_elem($root, 'memory', $meta->{memory} / 1024); + _append_elem($root, 'vcpu', $meta->{cpus}); + + my ($ostype) = $root->findnodes('os/type'); + $ostype->setAttribute('arch', $guestcaps->{arch}); + + my $features = _append_elem($root, 'features'); + foreach my $feature (@{$meta->{features}}) { + _append_elem($features, $feature); + } + + my $virtio = $guestcaps->{block} eq 'virtio' ? 1 : 0; + my $prefix = $virtio == 1 ? 'vd' : 'hd'; + my $suffix = 'a'; + + my $nide = 0; + + my ($devices) = $root->findnodes('devices'); + foreach my $disk (sort { $a->{device} cmp $b->{device} } @{$meta->{disks}}) + { + my $is_block = $disk->{is_block}; + + my $diskE = _append_elem($devices, 'disk'); + $diskE->setAttribute('device', 'disk'); + $diskE->setAttribute('type', $is_block ? 'block' : 'file'); + + my $driver = _append_elem($diskE, 'driver'); + $driver->setAttribute('name', 'qemu'); + $driver->setAttribute('type', $disk->{format}); + + my $source = _append_elem($diskE, 'source'); + $source->setAttribute($is_block ? 'dev' : 'file', $disk->{path}); + + my $target = _append_elem($diskE, 'target'); + $target->setAttribute('dev', $prefix.$suffix); $suffix++; + $target->setAttribute('bus', $guestcaps->{block}); + + $nide++ unless $virtio; + } + + # Add the correct number of cdrom and floppy drives with appropriate new + # names + $suffix = 'a' if ($virtio); + my $fdn = 0; + foreach my $removable (@{$meta->{removables}}) { + my $name; + my $bus; + if ($removable->{type} eq 'cdrom') { + $bus = 'ide'; + $name = 'hd'.$suffix; $suffix++; + $nide++; + } elsif ($removable->{type} eq 'floppy') { + $bus = 'fdc'; + $name = 'fd'.$fdn; $fdn++; + } else { + logmsg WARN, __x('Ignoring removable device {name} with unknown '. + 'type {type}.', + name => $removable->{name}, + type => $removable->{type}); + next; + } + + my $diskE = _append_elem($devices, 'disk'); + $diskE->setAttribute('device', $removable->{type}); + $diskE->setAttribute('type', 'file'); + + my $driver = _append_elem($diskE, 'driver'); + $driver->setAttribute('name', 'qemu'); + $driver->setAttribute('type', 'raw'); + + my $target = _append_elem($diskE, 'target'); + $target->setAttribute('dev', $name); + $target->setAttribute('bus', $bus); + + _append_elem($diskE, 'readonly') if ($removable->{type} eq 'cdrom'); + } + + logmsg WARN, __x('Only 4 IDE devices are supported, but this guest has '. + '{number}. The guest will not operate correctly without '. + 'manual reconfiguration.', number => $nide) if $nide > 4; + + foreach my $nic (@{$meta->{nics}}) { + # Find an appropriate mapped network + my ($vnet, $vnet_type) + $config->map_network($nic->{vnet}, $nic->{vnet_type}); + $vnet ||= $nic->{vnet}; + $vnet_type ||= $nic->{vnet_type}; + + my $interface = _append_elem($devices, 'interface'); + $interface->setAttribute('type', $vnet_type); + + my $mac = _append_elem($interface, 'mac'); + $mac->setAttribute('address', $nic->{mac}); + + my $source = _append_elem($interface, 'source'); + $source->setAttribute($vnet_type, $vnet); + + my $model = _append_elem($interface, 'model'); + $model->setAttribute('type', $guestcaps->{net}); + } + + return $dom->toString(); +} + +sub _append_elem +{ + my ($parent, $name, $text) = @_; + + my $doc = $parent->getOwnerDocument(); + my $e = $doc->createElement($name); + my $textE = $doc->createTextNode($text) if defined($text); + + $parent->appendChild($e); + $e->appendChild($textE) if defined($text); + + return $e; +} + sub DESTROY { my $self = shift; @@ -303,31 +448,10 @@ sub DESTROY } } -sub _unconfigure_incompatible_devices -{ - my ($dom) = @_; - - foreach my $path ( - # We have replaced the SCSI controller with either VirtIO or IDE. - # Additionally, attempting to start a guest converted from ESX, which - # has an lsilogic SCSI controller, will fail on RHEL 5. - $dom->findnodes("/domain/devices/controller[\@type='scsi']"), - - # XXX: We have no current way of detecting which sound card models are - # supported by the target hypervisor. As an unsupported sound card model - # can prevent the guest from starting, we simply remove sound cards for - # the moment. - $dom->findnodes("/domain/devices/sound") - ) - { - $path->getParentNode()->removeChild($path); - } -} - # Configure guest according to target hypervisor's capabilities sub _configure_capabilities { - my ($vmm, $dom, $guestcaps) = @_; + my ($vmm, $meta, $guestcaps) = @_; # Parse the capabilities of the connected libvirt my $caps = new XML::DOM::Parser->parse($vmm->get_capabilities()); @@ -340,94 +464,48 @@ sub _configure_capabilities v2vdie __x('The connected hypervisor does not support a {arch} kvm guest.', arch => $arch) unless defined($guestcap); - # Ensure that /domain/@type = 'kvm' - my ($type) = $dom->findnodes('/domain/@type'); - $type->setNodeValue('kvm'); - - # Set /domain/os/type to the value taken from capabilities - my ($os_type) = $dom->findnodes('/domain/os/type/text()'); - if(defined($os_type)) { - my ($caps_os_type) = $guestcap->findnodes('os_type/text()'); - $os_type->setNodeValue($caps_os_type->getNodeValue()); - } - - # Check that /domain/os/type/@machine, if set, is listed in capabilities - my ($machine) = $dom->findnodes('/domain/os/type/@machine'); - if(defined($machine)) { - my @machine_caps = $guestcap->findnodes - ("arch[\@name='$arch']/machine/text()"); - - my $found = 0; - foreach my $machine_cap (@machine_caps) { - if($machine eq $machine_cap) { - $found = 1; - last; - } - } - - # If the machine isn't listed as a capability, warn and remove it - if(!$found) { - logmsg WARN, __x('The connected hypervisor does not support '. - 'a machine type of {machine}. It will be '. - 'set to the current default.', - machine => $machine->getValue()); - - my ($type) = $dom->findnodes('/domain/os/type'); - $type->getAttributes()->removeNamedItem('machine'); - } - } - - # Get the domain features node - my ($domfeatures) = $dom->findnodes('/domain/features'); # Check existing features are supported by the hypervisor - if (defined($domfeatures)) { - # Check that /domain/features are listed in capabilities + if (exists($meta->{features})) { + # Check that requested features are listed in capabilities # Get a list of supported features my %features; foreach my $feature ($guestcap->findnodes('features/*')) { $features{$feature->getNodeName()} = 1; } - foreach my $feature ($domfeatures->findnodes('*')) { - my $name = $feature->getNodeName(); - - if (!exists($features{$name})) { + my @new_features = (); + foreach my $feature (@{$meta->{features}}) { + if (!exists($features{$feature})) { logmsg WARN, __x('The connected hypervisor does not '. 'support feature {feature}.', - feature => $name); - $feature->getParentNode()->removeChild($feature); + feature => $feature); } - if ($name eq 'acpi' && !$guestcaps->{acpi}) { + elsif ($feature eq 'acpi' && !$guestcaps->{acpi}) { logmsg WARN, __('The target guest does not support acpi '. 'under KVM. ACPI will be disabled.'); - $feature->getParentNode()->removeChild($feature); } - } - } - # Add a features element if there isn't one already - else { - $domfeatures = $dom->createElement('features'); - my ($root) = $dom->findnodes('/domain'); - $root->appendChild($domfeatures); + else { + push(@new_features, $feature); + } + + $meta->{features} = \@new_features; + } } # Add acpi support if the guest supports it if ($guestcaps->{acpi}) { - $domfeatures->appendChild($dom->createElement('acpi')); + push(@{$meta->{features}}, 'acpi') unless $meta->{features} ~~ 'acpi'; } # Add apic and pae if they're supported by the hypervisor and not already # there foreach my $feature ('apic', 'pae') { - my ($d) = $domfeatures->findnodes($feature); - next if (defined($d)); + next if $meta->{features} ~~ $feature; my ($c) = $guestcap->findnodes("features/$feature"); - if (defined($c)) { - $domfeatures->appendChild($dom->createElement($feature)); - } + push(@{$meta->{features}}, $feature) if defined($c); } } @@ -435,7 +513,7 @@ sub _configure_capabilities =head1 COPYRIGHT -Copyright (C) 2010 Red Hat Inc. +Copyright (C) 2010-2011 Red Hat Inc. =head1 LICENSE diff --git a/lib/Sys/VirtV2V/Connection/LibVirtXMLSource.pm b/lib/Sys/VirtV2V/Connection/LibVirtXMLSource.pm index 596450d..dabebe3 100644 --- a/lib/Sys/VirtV2V/Connection/LibVirtXMLSource.pm +++ b/lib/Sys/VirtV2V/Connection/LibVirtXMLSource.pm @@ -1,5 +1,5 @@ # Sys::VirtV2V::Connection::LibVirtXMLSource -# Copyright (C) 2009,2010 Red Hat Inc. +# Copyright (C) 2009-2011 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 @@ -62,7 +62,7 @@ sub new bless($self, $class); - $self->_get_dom($path); + $self->_get_meta($path); return $self; } @@ -75,13 +75,12 @@ Return the name of the domain. sub get_name { - my $dom = shift->{dom}; + my $meta = shift->{meta}; - my ($name) = $dom->findnodes('/domain/name'); - return $name; + return $meta->{name}; } -sub _get_dom +sub _get_meta { my $self = shift; @@ -92,16 +91,19 @@ sub _get_dom path => $self->{path}, error => $!); # Parse the input file - eval { $self->{dom} = new XML::DOM::Parser->parse ($xml); }; + my $dom; + eval { $dom = new XML::DOM::Parser->parse ($xml); }; # Display any parse errors v2vdie __x('Unable to parse domain from file {path}: {error}', path => $self->{path}, error => $@) if $@; # Check it looks like domain XML - my ($dummy) = $self->{dom}->findnodes('/domain/name'); + my ($dummy) = $dom->findnodes('/domain/name'); v2vdie __x('{path} doesn\'t look like a libvirt domain XML file', path => $self->{path}) unless defined($dummy); + + $self->{meta} = Sys::VirtV2V::Connection::LibVirt::_parse_dom($dom); } =item get_volume(path) @@ -151,11 +153,14 @@ sub get_volume $is_block = 0; my $st = stat($path); $usage = $st->blocks * 512; + + # Usage can be reported greater than size for large files due to the + # requirement for indirect blocks + $usage = $size if $usage > $size; + $is_sparse = $usage < $size ? 1 : 0; } - die("size ($size) < usage ($usage)") if $size < $usage; - my $transfer = new Sys::VirtV2V::Transfer::Local($path, $format, $is_sparse); @@ -169,7 +174,7 @@ sub get_volume =head1 COPYRIGHT -Copyright (C) 2009,2010 Red Hat Inc. +Copyright (C) 2009-2011 Red Hat Inc. =head1 LICENSE diff --git a/lib/Sys/VirtV2V/Connection/RHEVTarget.pm b/lib/Sys/VirtV2V/Connection/RHEVTarget.pm index 5fde58b..6d7b89c 100644 --- a/lib/Sys/VirtV2V/Connection/RHEVTarget.pm +++ b/lib/Sys/VirtV2V/Connection/RHEVTarget.pm @@ -1,5 +1,5 @@ # Sys::VirtV2V::Connection::RHEVTarget -# Copyright (C) 2010 Red Hat Inc. +# Copyright (C) 2010-2011 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 @@ -606,7 +606,7 @@ sub guest_exists return 0; } -=item create_guest(dom) +=item create_guest(desc, meta, config, guestcaps) Create the guest in the target @@ -615,20 +615,16 @@ Create the guest in the target sub create_guest { my $self = shift; - my ($desc, $dom, $guestcaps) = @_; + my ($desc, $meta, $config, $guestcaps) = @_; # Get the name of the guest - my ($name) = $dom->findnodes('/domain/name/text()'); - $name = $name->getNodeValue(); + my $name = $meta->{name}; # Get the number of virtual cpus - my ($ncpus) = $dom->findnodes('/domain/vcpu/text()'); - $ncpus = $ncpus->getNodeValue(); + my $ncpus = $meta->{cpus}; # Get the amount of memory in MB - my ($memsize) = $dom->findnodes('/domain/memory/text()'); - $memsize = $memsize->getNodeValue(); - $memsize = ceil($memsize / 1024); + my $memsize = ceil($meta->{memory}/1024/1024); # Generate a creation date my $vmcreation = _format_time(gmtime()); @@ -710,8 +706,8 @@ sub create_guest </ovf:Envelope> EOF - $self->_disks($ovf, $dom); - $self->_networks($ovf, $dom); + $self->_disks($ovf, $meta, $guestcaps); + $self->_networks($ovf, $meta, $config, $guestcaps); my $mountdir = $self->{mountdir}; my $domainuuid = $self->{domainuuid}; @@ -900,7 +896,7 @@ sub _format_time sub _disks { my $self = shift; - my ($ovf, $dom) = @_; + my ($ovf, $meta, $guestcaps) = @_; my ($references) = $ovf->findnodes('/ovf:Envelope/References'); die("no references") unless (defined($references)); @@ -916,19 +912,12 @@ sub _disks my $driveno = 1; - foreach my $disk - ($dom->findnodes("/domain/devices/disk[\@device='disk']")) - { - my ($path) = $disk->findnodes('source/@file'); - $path = $path->getNodeValue(); + foreach my $disk (@{$meta->{disks}}) { + my $vol = Sys::VirtV2V::Connection::RHEVTarget::Vol->_get_by_path + ($disk->{path}); - my ($bus) = $disk->findnodes('target/@bus'); - $bus = $bus->getNodeValue(); - - my $vol = Sys::VirtV2V::Connection::RHEVTarget::Vol->_get_by_path($path); - - die("dom contains path not written by virt-v2v: $path\n". - $dom->toString()) unless (defined($vol)); + die('metadata contains path not written by virt-v2v: ', $disk->{path}) + unless defined($vol); my $fileref = catdir($vol->_get_imageuuid(), $vol->_get_voluuid()); my $size_gb = ceil($vol->get_size()/1024/1024/1024); @@ -959,7 +948,7 @@ sub _disks $diske->setAttribute('ovf:format', 'http://en.wikipedia.org/wiki/Byte'); # IDE = 0, SCSI = 1, VirtIO = 2 $diske->setAttribute('ovf:disk-interface', - $bus eq 'virtio' ? 'VirtIO' : 'IDE'); + $guestcaps->{block} eq 'virtio' ? 'VirtIO' : 'IDE'); # The libvirt QEMU driver marks the first disk (in document order) as # bootable $diske->setAttribute('ovf:boot', $driveno == 1 ? 'True' : 'False'); @@ -1026,7 +1015,7 @@ sub _disks sub _networks { my $self = shift; - my ($ovf, $dom) = @_; + my ($ovf, $meta, $config, $guestcaps) = @_; my ($networksection) = $ovf->findnodes("/ovf:Envelope/Section". "[\@xsi:type = 'ovf:NetworkSection_Type']"); @@ -1037,41 +1026,22 @@ sub _networks die("no virtualhardware") unless (defined($virtualhardware)); my $i = 0; + foreach my $if (@{$meta->{nics}}) { + my $dev = "eth$i"; $i++; - foreach my $if - ($dom->findnodes('/domain/devices/interface')) - { - # Extract relevant info about this NIC - 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 { - # Should have been picked up in Converter - die("Unknown interface type"); - } - $name = $name->getNodeValue(); - - my ($driver) = $if->findnodes('model/@type'); - $driver &&= $driver->getNodeValue(); - - my ($mac) = $if->findnodes('mac/@address'); - $mac &&= $mac->getNodeValue(); - - my $dev = "eth$i"; + # Find an appropriate mapped network + my ($vnet, undef) = $config->map_network($if->{vnet}, $if->{vnet_type}); + $vnet ||= $if->{vnet}; my $e = $ovf->createElement("Network"); - $e->setAttribute('ovf:name', $name); + $e->setAttribute('ovf:name', $vnet); $networksection->appendChild($e); my $item = $ovf->createElement('Item'); $virtualhardware->appendChild($item); $e = $ovf->createElement('rasd:Caption'); - $e->addText("Ethernet adapter on $name"); + $e->addText('Ethernet adapter on '.$vnet); $item->appendChild($e); $e = $ovf->createElement('rasd:InstanceId'); @@ -1083,16 +1053,16 @@ sub _networks $item->appendChild($e); $e = $ovf->createElement('rasd:ResourceSubType'); - if ($driver eq 'rtl8139') { + if ($guestcaps->{net} eq 'rtl8139') { $e->addText('1'); - } elsif ($driver eq 'e1000') { + } elsif ($guestcaps->{net} eq 'e1000') { $e->addText('2'); - } elsif ($driver eq 'virtio') { + } elsif ($guestcaps->{net} eq 'virtio') { $e->addText('3'); } else { logmsg WARN, __x('Unknown NIC model {driver} for {dev}. '. 'NIC will be {default} when imported.', - driver => $driver, + driver => $guestcaps->{net}, dev => $dev, default => 'rtl8139'); $e->addText('1'); @@ -1100,7 +1070,7 @@ sub _networks $item->appendChild($e); $e = $ovf->createElement('rasd:Connection'); - $e->addText($name); + $e->addText($vnet); $item->appendChild($e); $e = $ovf->createElement('rasd:Name'); @@ -1108,10 +1078,8 @@ sub _networks $item->appendChild($e); $e = $ovf->createElement('rasd:MACAddress'); - $e->addText($mac) if (defined($mac)); + $e->addText($if->{mac}); $item->appendChild($e); - - $i++; } } @@ -1119,7 +1087,7 @@ sub _networks =head1 COPYRIGHT -Copyright (C) 2010 Red Hat Inc. +Copyright (C) 2010-2011 Red Hat Inc. =head1 LICENSE diff --git a/lib/Sys/VirtV2V/Connection/Source.pm b/lib/Sys/VirtV2V/Connection/Source.pm index 8cbfe25..7926822 100644 --- a/lib/Sys/VirtV2V/Connection/Source.pm +++ b/lib/Sys/VirtV2V/Connection/Source.pm @@ -1,5 +1,5 @@ # Sys::VirtV2V::Connection::Source -# Copyright (C) 2009,2010 Red Hat Inc. +# Copyright (C) 2009-2011 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 @@ -31,16 +31,14 @@ use Locale::TextDomain 'virt-v2v'; =head1 NAME -Sys::VirtV2V::Connection - Obtain domain metadata +Sys::VirtV2V::Source - A source connection =head1 SYNOPSIS use Sys::VirtV2V::Connection::LibVirtSource; $conn = Sys::VirtV2V::Connection::LibVirtSource->new($uri, $name, $target); - $dom = $conn->get_dom(); - $storage = $conn->get_storage_paths(); - $devices = $conn->get_storage_devices(); + $meta = $conn->get_meta(); =head1 DESCRIPTION @@ -55,49 +53,19 @@ subclasses: =over -=item get_storage_paths +=item get_meta() -Return an arrayref of local paths to the guest's storage devices. This list is -guaranteed to be in the same order as the list returned by get_storage_devices. +Return guest metadata. -=cut - -sub get_storage_paths -{ - my $self = shift; - - return $self->{paths}; -} - -=item get_storage_devices - -Return an arrayref of libvirt device names for the guest's storage prior to -conversion. This list is guaranteed to be in the same order as the list returned -by get_storage_paths. - -=cut - -sub get_storage_devices -{ - my $self = shift; - - return $self->{devices}; -} - -=item get_dom() - -Returns an XML::DOM::Document describing a libvirt configuration equivalent to -the input. - -Returns undef and displays an error if there was an error +Returns undef and displays an error if there was an error. =cut -sub get_dom +sub get_meta { my $self = shift; - return $self->{dom}; + return $self->{meta}; } sub _volume_copy @@ -172,7 +140,7 @@ sub _volume_copy return $dst; } -=item copy_storage(target) +=item copy_storage(target, format, is_sparse) Copy all of a guests storage devices to I<target>. Update the guest metadata to reflect their new locations and properties. @@ -184,26 +152,10 @@ sub copy_storage my $self = shift; my ($target, $output_format, $output_sparse) = @_; - my $dom = $self->get_dom(); - - # An list of local paths to guest storage - my @paths; - # A list of libvirt target device names - my @devices; - - foreach my $disk ($dom->findnodes("/domain/devices/disk[\@device='disk']")) - { - my ($source_e) = $disk->findnodes('source'); + my $meta = $self->get_meta(); - my ($source) = $source_e->findnodes('@file | @dev'); - defined($source) or die("source element has neither dev nor file: \n". - $dom->toString()); - - my ($dev) = $disk->findnodes('target/@dev'); - defined($dev) or die("disk does not have a target device: \n". - $dom->toString()); - - my $src = $self->get_volume($source->getValue()); + foreach my $disk (@{$meta->{disks}}) { + my $src = $self->get_volume($disk->{path}); my $dst; if ($target->volume_exists($src->get_name())) { logmsg WARN, __x('Storage volume {name} already exists on the '. @@ -219,72 +171,24 @@ sub copy_storage defined($output_sparse) ? $output_sparse : $src->is_sparse() ); - _volume_copy($src, $dst); - } - - # This will die if libguestfs can't use the result directly, so we do it - # before copying all the data. - push(@paths, $dst->get_local_path()); - - # Export the new path - my $path = $dst->get_path(); - - # Find any existing driver element. - my ($driver) = $disk->findnodes('driver'); - - # Create a new driver element if none exists - unless (defined($driver)) { - $driver - $disk->getOwnerDocument()->createElement("driver"); - $disk->appendChild($driver); - } - $driver->setAttribute('name', 'qemu'); - $driver->setAttribute('type', $dst->get_format()); - - # Remove the @file or @dev attribute before adding a new one - $source_e->removeAttributeNode($source); + # This will die if libguestfs can't use the result directly, so we + # do it before copying all the data. + $disk->{local_path} = $dst->get_local_path(); - # Set @file or @dev as appropriate - if ($dst->is_block()) { - $disk->setAttribute('type', 'block'); - $source_e->setAttribute('dev', $path); - } else { - $disk->setAttribute('type', 'file'); - $source_e->setAttribute('file', $path); + _volume_copy($src, $dst); } - push(@devices, $dev->getNodeValue()); + # Update the volume path to point to the copy + $disk->{path} = $dst->get_path(); + $disk->{is_block} = $dst->is_block(); } - - # Blank the source of floppies or cdroms - foreach my $disk ($dom->findnodes('/domain/devices/disk'. - "[\@device='floppy' or \@device='cdrom']")) - { - my ($source_e) = $disk->findnodes('source'); - - # Nothing to do if there's no source element - next unless (defined($source_e)); - - # Blank file or dev as appropriate - my ($source) = $source_e->findnodes('@file | @dev'); - defined($source) or die("source element has neither dev nor file: \n". - $dom->toString()); - - $source_e->setAttribute($source->getName(), ''); - } - - v2vdie __'Guest doesn\'t define any recognised storage devices' - unless @paths > 0; - - $self->{paths} = \@paths; - $self->{devices} = \@devices; } =back =head1 COPYRIGHT -Copyright (C) 2009,2010 Red Hat Inc. +Copyright (C) 2009-2011 Red Hat Inc. =head1 LICENSE diff --git a/lib/Sys/VirtV2V/Converter.pm b/lib/Sys/VirtV2V/Converter.pm index c4adb49..dd0c337 100644 --- a/lib/Sys/VirtV2V/Converter.pm +++ b/lib/Sys/VirtV2V/Converter.pm @@ -1,5 +1,5 @@ # Sys::VirtV2V::Converter -# Copyright (C) 2009 Red Hat Inc. +# Copyright (C) 2009-2011 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 @@ -40,7 +40,7 @@ Sys::VirtV2V::Converter - Convert a guest to run on KVM use Sys::VirtV2V::Converter; - Sys::VirtV2V::Converter->convert($g, $config, $desc, $dom, $devices); + Sys::VirtV2V::Converter->convert($g, $config, $desc, $meta); =head1 DESCRIPTION @@ -51,28 +51,7 @@ OS, and uses it to convert the guest to run on KVM. =over -=cut - -# Default values for a KVM configuration -use constant KVM_DEFAULT_XML => " -<domain type='kvm'> - <os> - <type machine='pc'>hvm</type> - <boot dev='hd'/> - </os> - <devices> - <input type='tablet' bus='usb'/> - <input type='mouse' bus='ps2'/> - <graphics type='vnc' port='-1' listen='127.0.0.1'/> - <video> - <model type='cirrus' vram='9216' heads='1'/> - </video> - <console type='pty'/> - </devices> -</domain> -"; - -=item Sys::VirtV2V::Converter->convert(g, config, desc, dom, devices) +=item Sys::VirtV2V::Converter->convert(g, config, desc, meta) Instantiate an appropriate backend and call convert on it. @@ -90,14 +69,9 @@ An initialised Sys::VirtV2V::Config object. The OS description returned by Sys::Guestfs::Lib. -=item dom - -An XML::DOM object resulting from parsing the guests's libvirt domain XML. - -=item devices +=item meta -An arrayref of libvirt storage device names, in the order they will be presented -to the guest. +Guest metadata. =back @@ -107,19 +81,18 @@ sub convert { my $class = shift; - my ($g, $config, $desc, $dom, $devices) = @_; + my ($g, $config, $desc, $meta) = @_; croak("convert called without g argument") unless defined($g); croak("convert called without config argument") unless defined($config); croak("convert called without desc argument") unless defined($desc); - croak("convert called without dom argument") unless defined($dom); - croak("convert called without devices argument") unless defined($devices); + croak("convert called without meta argument") unless defined($meta); my $guestcaps; # Find a module which can convert the guest and run it foreach my $module ($class->modules()) { if($module->can_handle($desc)) { - $guestcaps = $module->convert($g, $config, $desc, $dom, $devices); + $guestcaps = $module->convert($g, $config, $desc, $meta); last; } } @@ -146,330 +119,14 @@ sub convert }; } - # Map network names from config - _map_networks($dom, $config); - - # Convert the metadata - _convert_metadata($dom, $desc, $devices, $guestcaps); - return $guestcaps; } -sub _convert_metadata -{ - my ($dom, $desc, $devices, $guestcaps) = @_; - - my $default_dom = new XML::DOM::Parser->parse(KVM_DEFAULT_XML); - - # Replace source hypervisor metadata with KVM defaults - _unconfigure_hvs($dom, $default_dom); - - # Remove any configuration related to a PV kernel bootloader - _unconfigure_bootloaders($dom); - - # Update storage devices and drivers - _configure_storage($dom, $devices, $guestcaps->{block}); - - # Configure network drivers - _configure_network($dom, $guestcaps->{net}); - - # Ensure guest has a standard set of default devices - _configure_default_devices($dom, $default_dom); - - # Add a default os section if none exists - _configure_os($dom, $default_dom, $guestcaps->{arch}); - - # Check for weird configs and sanitise them - _sanity_check($dom); -} - -sub _configure_os -{ - my ($dom, $default_dom, $arch) = @_; - - my ($os) = $dom->findnodes('/domain/os'); - - # If there's no os element, copy one from the default - if(!defined($os)) { - ($os) = $default_dom->findnodes('/domain/os'); - $os = $os->cloneNode(1); - $os->setOwnerDocument($dom); - - my ($domain) = $dom->findnodes('/domain'); - $domain->appendChild($os); - } - - my ($type) = $os->findnodes('type'); - - # If there's no type element, copy one from the default - if(!defined($type)) { - ($type) = $default_dom->findnodes('/domain/os/type'); - $type = $type->cloneNode(1); - $type->setOwnerDocument($dom); - - $os->appendChild($type); - } - - # Set type/@arch based on the detected OS architecture - $type->setAttribute('arch', $arch) if (defined($arch)); -} - -sub _configure_default_devices -{ - my ($dom, $default_dom) = @_; - - my ($devices) = $dom->findnodes('/domain/devices'); - - # Remove any existing input, graphics or video devices - foreach my $input ($devices->findnodes('input | video | graphics')) { - $devices->removeChild($input); - } - - my ($input_devices) = $default_dom->findnodes('/domain/devices'); - - # Add new default devices from default XML - foreach my $input ($input_devices->findnodes('input | video | '. - 'graphics | console')) { - my $new = $input->cloneNode(1); - $new->setOwnerDocument($devices->getOwnerDocument()); - $devices->appendChild($new); - } -} - -sub _unconfigure_bootloaders -{ - my ($dom) = @_; - - # A list of paths which relate to assisted booting of a kernel on hvm - my @bootloader_paths = ( - '/domain/os/loader', - '/domain/os/kernel', - '/domain/os/initrd', - '/domain/os/root', - '/domain/os/cmdline', - '/domain/bootloader', - '/domain/bootloader_args' - ); - - foreach my $path (@bootloader_paths) { - my ($node) = $dom->findnodes($path); - $node->getParentNode()->removeChild($node) if defined($node); - } -} - -sub _suffixcmp -{ - my ($a, $b) = @_; - - return 1 if (length($a) > length($b)); - return -1 if (length($a) < length($b)); - - return 1 if ($a gt $b); - return -1 if ($a lt $b); - return 0; -} - -sub _configure_storage -{ - my ($dom, $devices, $block) = @_; - - my $virtio = $block eq 'virtio' ? 1 : 0; - my $prefix = $virtio == 1 ? 'vd' : 'hd'; - - my @removed = (); - - my $suffix = 'a'; - foreach my $device (@$devices) { - my ($target) = $dom->findnodes("/domain/devices/disk[\@device='disk']/". - "target[\@dev='$device']"); - - die("Previously detected drive $device is no longer present in domain ". - "XML: ".$dom->toString()) - unless (defined($target)); - - # Don't add more than 4 IDE disks - if (!$virtio && _suffixcmp($suffix, 'd') > 0) { - push(@removed, "$device(disk)"); - } else { - $target->setAttribute('bus', $block); - $target->setAttribute('dev', $prefix.$suffix); - $suffix++; # Perl magic means 'z'++ == 'aa' - } - } - - # Convert CD-ROM devices to IDE. - $suffix = 'a' if ($virtio); - foreach my $target - ($dom->findnodes("/domain/devices/disk[\@device='cdrom']/target")) - { - if (_suffixcmp($suffix, 'd') <= 0) { - $target->setAttribute('bus', 'ide'); - $target->setAttribute('dev', "hd$suffix"); - $suffix++; - } else { - push(@removed, $target->getAttribute('dev')."(cdrom)"); - - my $disk = $target->getParentNode(); - $disk->getParentNode()->removeChild($disk); - } - } - - if (@removed > 0) { - logmsg WARN, __x('Only 4 IDE devices are supported. The following '. - 'drives have been removed: {list}', - list => join(' ', @removed)); - } - - # As we just changed and unified all their underlying controllers, device - # addresses are no longer relevant - foreach my $address ($dom->findnodes('/domain/devices/disk/address')) { - $address->getParentNode()->removeChild($address); - } -} - -sub _configure_network -{ - my ($dom, $net) = @_; - - # Convert network adapters - # N.B. <interface> is not required to have a <model> element, but <model> - # is required to have a type attribute - - # Convert interfaces which already have a model element - foreach my $type - ($dom->findnodes('/domain/devices/interface/model/@type')) - { - $type->setNodeValue($net); - } - - # Add a model element to interfaces which don't have one - foreach my $interface - ($dom->findnodes('/domain/devices/interface[not(model)]')) - { - my $model = $dom->createElement('model'); - $model->setAttribute('type', $net); - $interface->appendChild($model); - } -} - -sub _unconfigure_hvs -{ - my ($dom, $default_dom) = @_; - die("unconfigure_hvs called without dom argument") - unless defined($dom); - die("unconfigure_hvs called without default_dom argument") - unless defined($default_dom); - - # Remove emulator if it is defined - foreach my $emulator ($dom->findnodes('/domain/devices/emulator')) { - $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); -} - -sub _unconfigure_xen_metadata -{ - my ($dom) = @_; - - # The list of target xen-specific nodes is mostly taken from inspection of - # domain.rng - - # Remove machine if it has a xen-specific value - # We could replace it with the generic 'pc', but 'pc' is a moving target - # across QEMU releases. By removing it entirely, libvirt will automatically - # add the latest machine type (e.g. pc-0.11), which is stable. - foreach my $machine_type ($dom->findnodes('/domain/os/type/@machine')) { - if ($machine_type->getNodeValue() =~ /(xenpv|xenfv|xenner)/) { - my ($type) = $dom->findnodes('/domain/os/type[@machine = "'. - $machine_type->getNodeValue().'"]'); - $type->getAttributes()->removeNamedItem("machine"); - } - } - - # Remove the script element if its path attribute is 'vif-bridge' - foreach my $script ($dom->findnodes('/domain/devices/interface/script[@path = "vif-bridge"]')) - { - $script->getParent()->removeChild($script); - } - - # Other Xen related metadata is handled separately - # /domain/@type - # /domain/devices/input/@bus = xen - # /domain/devices/disk/target/@bus = 'xen' - # /domain/os/loader = 'xen' - # /domain/bootloader - # /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 { - v2vdie __x('Unknown interface type {type} in domain XML: {domain}', - type => $type, domain => $dom->toString()); - } - - my ($newname, $newtype) = $config->map_network($name->getValue(), - $type); - next unless (defined($newname) && defined($newtype)); - - my ($source) = $if->findnodes('source'); - - # Replace @bridge or @network in the source element with the correct - # mapped attribute name and value - $source->removeAttributeNode($name); - $source->setAttribute($newtype, $newname); - - # Update the type of the interface - $if->setAttribute('type', $newtype); - } -} - -sub _sanity_check -{ - my ($dom) = shift; - - # Check for multiple boot devices of the same type, which will cause KVM not - # to start - # Seen on RHEL 5 Xen - my %devs; - foreach my $boot ($dom->findnodes('/domain/os/boot')) { - my $dev = $boot->getAttribute('dev'); - - if (defined($dev) && !exists($devs{$dev})) { - $devs{$dev} = 1; - next; - } - - # Delete nodes with no dev attribute, or that we've seen before - $boot->getParentNode()->removeChild($boot); - } -} - =back =head1 COPYRIGHT -Copyright (C) 2009,2010 Red Hat Inc. +Copyright (C) 2009-2011 Red Hat Inc. =head1 LICENSE diff --git a/lib/Sys/VirtV2V/Converter/RedHat.pm b/lib/Sys/VirtV2V/Converter/RedHat.pm index ed52189..4f8bf2d 100644 --- a/lib/Sys/VirtV2V/Converter/RedHat.pm +++ b/lib/Sys/VirtV2V/Converter/RedHat.pm @@ -1,5 +1,5 @@ # Sys::VirtV2V::Converter::RedHat -# Copyright (C) 2009,2010 Red Hat Inc. +# Copyright (C) 2009-2011 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 @@ -41,7 +41,7 @@ Sys::VirtV2V::Converter::RedHat - Convert a Red Hat based guest to run on KVM use Sys::VirtV2V::Converter; - Sys::VirtV2V::Converter->convert($g, $dom, $os); + Sys::VirtV2V::Converter->convert($g, $meta, $os); =head1 DESCRIPTION @@ -69,7 +69,7 @@ sub can_handle $desc->{distro} =~ /^(rhel|fedora)$/); } -=item Sys::VirtV2V::Converter::RedHat->convert(g, config, dom, desc, $devices) +=item Sys::VirtV2V::Converter::RedHat->convert(g, config, meta, desc) Convert a Red Hat based guest. Assume that can_handle has previously returned 1. @@ -87,14 +87,9 @@ An initialised Sys::VirtV2V::Config A description of the guest OS as returned by Sys::Guestfs::Lib. -=item dom +=item meta -A DOM representation of the guest's libvirt domain metadata - -=item devices - -An arrayref of libvirt storage device names, in the order they will be presented -to the guest. +Guest metadata. =back @@ -104,12 +99,11 @@ sub convert { my $class = shift; - my ($g, $config, $desc, $dom, $devices) = @_; + my ($g, $config, $desc, $meta) = @_; croak("convert called without g argument") unless defined($g); croak("convert called without config argument") unless defined($config); croak("convert called without desc argument") unless defined($desc); - croak("convert called without dom argument") unless defined($dom); - croak("convert called without devices argument") unless defined($devices); + croak("convert called without meta argument") unless defined($meta); _init_selinux($g); _init_augeas($g); @@ -120,15 +114,15 @@ sub convert _unconfigure_hv($g, $desc); # Try to install the virtio capability - my $virtio = _install_capability('virtio', $g, $config, $dom, $desc); + my $virtio = _install_capability('virtio', $g, $config, $meta, $desc); # Get an appropriate kernel, and remove non-bootable kernels - my $kernel = _configure_kernel($virtio, $g, $config, $desc, $dom); + my $kernel = _configure_kernel($virtio, $g, $config, $desc, $meta); # Configure the rest of the system _configure_console($g); _configure_display_driver($g); - _remap_block_devices($devices, $virtio, $g, $desc); + _remap_block_devices($meta, $virtio, $g, $desc); _configure_kernel_modules($g, $desc, $virtio, $modpath); _configure_boot($kernel, $virtio, $g, $desc); @@ -534,7 +528,7 @@ sub _list_kernels sub _configure_kernel { - my ($virtio, $g, $config, $desc, $dom) = @_; + my ($virtio, $g, $config, $desc, $meta) = @_; # Pick first appropriate kernel returned by _list_kernels my $boot_kernel; @@ -555,7 +549,7 @@ sub _configure_kernel # If none of the installed kernels are appropriate, install a new one if(!defined($boot_kernel)) { - $boot_kernel = _install_good_kernel($g, $config, $desc, $dom); + $boot_kernel = _install_good_kernel($g, $config, $desc, $meta); } # Check we have a bootable kernel. @@ -807,7 +801,7 @@ sub _find_xen_kernel_modules sub _install_capability { - my ($name, $g, $config, $dom, $desc) = @_; + my ($name, $g, $config, $meta, $desc) = @_; my $cap; eval { @@ -869,7 +863,7 @@ sub _install_capability # normal kernel replacement if ($kernel_pkg eq "kernel-xen" || $kernel_pkg eq "kernel-xenU") { $kernel_pkg - _get_replacement_kernel_name($kernel_arch, $desc, $dom); + _get_replacement_kernel_name($kernel_arch, $desc, $meta); # Check if we've got already got an appropriate kernel my ($installed) @@ -1331,7 +1325,7 @@ sub _discover_kernel sub _get_replacement_kernel_name { - my ($arch, $desc, $dom) = @_; + my ($arch, $desc, $meta) = @_; # Make an informed choice about a replacement kernel for distros we know # about @@ -1355,24 +1349,14 @@ sub _get_replacement_kernel_name # RHEL 4 elsif ($desc->{distro} eq 'rhel' && $desc->{major_version} eq '4') { - my ($ncpus) = $dom->findnodes('/domain/vcpu/text()'); - if (defined($ncpus)) { - $ncpus = $ncpus->getData() - } else { - $ncpus = 1; - } - if ($arch eq 'i686') { - my ($mem_kb) = $dom->findnodes('/domain/memory/text()'); - $mem_kb = $mem_kb->getData(); - # If the guest has > 10G RAM, give it a hugemem kernel - if ($mem_kb > 10 * 1024 * 1024) { + if ($meta->{memory} > 10 * 1024 * 1024 * 1024) { return 'kernel-hugemem'; } # SMP kernel for guests with >1 CPU - elsif ($ncpus > 1) { + elsif ($meta->{cpus} > 1) { return 'kernel-smp'; } @@ -1382,11 +1366,11 @@ sub _get_replacement_kernel_name } else { - if ($ncpus > 8) { + if ($meta->{cpus} > 8) { return 'kernel-largesmp'; } - elsif ($ncpus > 1) { + elsif ($meta->{cpus} > 1) { return 'kernel-smp'; } else { @@ -1405,14 +1389,14 @@ sub _get_replacement_kernel_name sub _install_good_kernel { - my ($g, $config, $desc, $dom) = @_; + my ($g, $config, $desc, $meta) = @_; my ($kernel_pkg, $kernel_rpmver, $kernel_arch) = _discover_kernel($desc); # 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 = _get_replacement_kernel_name($kernel_arch, $desc, $dom); + $kernel_pkg = _get_replacement_kernel_name($kernel_arch, $desc, $meta); # Check there isn't already one installed my ($kernel) = _get_installed("$kernel_pkg.$kernel_arch", $g); @@ -1718,12 +1702,18 @@ sub _rpmvercmp sub _remap_block_devices { - my ($devices, $virtio, $g, $desc) = @_; + my ($meta, $virtio, $g, $desc) = @_; - # $devices contains an order list of devices, as named by the host. Because + my @devices = map { $_->{device} } @{$meta->{disks}}; + @devices = sort @devices; + + # @devices contains an ordered list of libvirt device names. Because # libvirt uses a similar naming scheme to Linux, these will mostly be the - # same names as used by the guest. However, if the guest is using libata, - # IDE drives could be renamed. + # same names as used by the guest. They are ordered as they were passed to + # libguestfs, which means their device name in the appliance can be + # inferred. + + # If the guest is using libata, IDE drives could be renamed. # Modern distros use libata, and IDE devices are presented as sdX my $libata = 1; @@ -1778,7 +1768,7 @@ sub _remap_block_devices if (exists($guestif{sd})) { # Look for IDE and SCSI devices from the domain definition my %domainif; - foreach my $device (@$devices) { + foreach my $device (@devices) { foreach my $type ('hd', 'sd') { if ($device =~ m{^$type([a-z]+)}) { $domainif{$type} ||= {}; @@ -1806,9 +1796,8 @@ sub _remap_block_devices $letter++; } - # Be careful not to modify the original device list my @newdevices; - foreach my $device (@$devices) { + foreach my $device (@devices) { my $map = $map{$device}; unless (defined($map)) { @@ -1817,11 +1806,11 @@ sub _remap_block_devices } push(@newdevices, $map); } - $devices = \@newdevices; + @devices = @newdevices; } } - # We now assume that $devices contains an ordered list of device names, as + # We now assume that @devices contains an ordered list of device names, as # used by the guest. Create a map of old guest device names to new guest # device names. my %map; @@ -1837,7 +1826,7 @@ sub _remap_block_devices } my $letter = 'a'; - foreach my $device (@$devices) { + foreach my $device (@devices) { $map{$device} = $prefix.$letter; $letter++; } @@ -2067,7 +2056,7 @@ sub _supports_virtio =head1 COPYRIGHT -Copyright (C) 2009,2010 Red Hat Inc. +Copyright (C) 2009-2011 Red Hat Inc. =head1 LICENSE diff --git a/lib/Sys/VirtV2V/Converter/Windows.pm b/lib/Sys/VirtV2V/Converter/Windows.pm index bda40a6..c9499f2 100644 --- a/lib/Sys/VirtV2V/Converter/Windows.pm +++ b/lib/Sys/VirtV2V/Converter/Windows.pm @@ -1,5 +1,5 @@ # Sys::VirtV2V::Converter::Windows -# Copyright (C) 2009-2010 Red Hat Inc. +# Copyright (C) 2009-2011 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 @@ -46,7 +46,7 @@ Sys::VirtV2V::Converter::Windows - Pre-convert a Windows guest to run on KVM use Sys::VirtV2V::Converter; - Sys::VirtV2V::Converter->convert($g, $config, $desc, $dom, $devices); + Sys::VirtV2V::Converter->convert($g, $config, $desc, $meta); =head1 DESCRIPTION @@ -80,7 +80,7 @@ sub can_handle return ($desc->{os} eq 'windows'); } -=item Sys::VirtV2V::Converter::Windows->convert($g, $guestos, $desc, $devices, $config) +=item Sys::VirtV2V::Converter::Windows->convert($g, $guestos, $desc, $config) (Pre-)convert a Windows guest. Assume that can_handle has previously returned 1. @@ -99,14 +99,9 @@ An initialised Sys::VirtV2V::Config object. A description of the guest OS as returned by Sys::Guestfs::Lib. -=item dom +=item meta -A DOM representation of the guest's libvirt domain metadata - -=item devices - -An arrayref of libvirt storage device names, in the order they will be -presented to the guest. +Guest metadata. =back @@ -116,21 +111,20 @@ sub convert { my $class = shift; - my ($g, $config, $desc, undef, $devices) = @_; + my ($g, $config, $desc, undef) = @_; croak("convert called without g argument") unless defined($g); croak("convert called without config argument") unless defined($config); croak("convert called without desc argument") unless defined($desc); - croak("convert called without devices argument") unless defined($devices); my $tmpdir = tempdir (CLEANUP => 1); # Note: disks are already mounted by main virt-v2v script. - _upload_files ($g, $tmpdir, $desc, $devices, $config); - _add_viostor_to_registry ($g, $tmpdir, $desc, $devices, $config); - _add_service_to_registry ($g, $tmpdir, $desc, $devices, $config); + _upload_files ($g, $tmpdir, $desc, $config); + _add_viostor_to_registry ($g, $tmpdir, $desc, $config); + _add_service_to_registry ($g, $tmpdir, $desc, $config); my ($block, $net) - _prepare_virtio_drivers ($g, $tmpdir, $desc, $devices, $config); + _prepare_virtio_drivers ($g, $tmpdir, $desc, $config); # Return guest capabilities. my %guestcaps; @@ -152,7 +146,6 @@ sub _add_viostor_to_registry my $g = shift; my $tmpdir = shift; my $desc = shift; - my $devices = shift; my $config = shift; # Locate and download the system registry. @@ -255,7 +248,6 @@ sub _add_service_to_registry my $g = shift; my $tmpdir = shift; my $desc = shift; - my $devices = shift; my $config = shift; # Locate and download the system registry. @@ -314,7 +306,6 @@ sub _prepare_virtio_drivers my $g = shift; my $tmpdir = shift; my $desc = shift; - my $devices = shift; my $config = shift; # Copy the target VirtIO drivers to the guest @@ -439,7 +430,6 @@ sub _upload_files my $g = shift; my $tmpdir = shift; my $desc = shift; - my $devices = shift; my $config = shift; # Check we have all required files @@ -487,7 +477,7 @@ sub _upload_files =head1 COPYRIGHT -Copyright (C) 2009-2010 Red Hat Inc. +Copyright (C) 2009-2011 Red Hat Inc. =head1 LICENSE diff --git a/metadata-format.txt b/metadata-format.txt new file mode 100644 index 0000000..6ae544c --- /dev/null +++ b/metadata-format.txt @@ -0,0 +1,28 @@ +virt-v2v uses its own representation of a domain's metadata. It is based on, but +differs significantly from, the XML representation used by libvirt. + +% + name The name of the domain + memory The memory assigned to the domain, in bytes + cpus The number of cpus assigned to the domain + arch The architecture of the domain (eg i686,x86_64) + features[] An array containing 'features', as defined by libvirt + disks[] An array containing hashrefs of disk descriptions + device The name of the disk as seen by the guest (eg sda) + path The path to the device's storage, as known to the source + or target hypervisor. This will probably not be a valid + local path + (local_path) A local path to the device's storage, as usable by + libguestfs during conversion. This is populated by + copy_storage() + is_block 1 if the device uses block storage, 0 if it uses a file. + format The file format used by the underlying storage, as known to + qemu (eg raw,qcow) + removables[] An array containing hashrefs of removable media devices + name The name of the device, as seen by the guest (eg fd0) + type The device type, as defined by libvirt (eg floppy,cdrom) + nics[] An array containing hashrefs of NIC descriptions + mac The mac address + vnet The name of the virtual network the NIC will connect to + vnet_type The type of virtual network the NIC will connect to, as + defined by libvirt (eg network,bridge) diff --git a/v2v/virt-v2v.pl b/v2v/virt-v2v.pl index 5212fab..9f603d3 100755 --- a/v2v/virt-v2v.pl +++ b/v2v/virt-v2v.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl # virt-v2v -# Copyright (C) 2009 Red Hat Inc. +# Copyright (C) 2009-2011 Red Hat Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -426,19 +426,23 @@ v2vdie __x('Domain {name} already exists on the target.', $source->copy_storage($target, $output_format, $output_sparse); # Get a libvirt configuration for the guest -my $dom = $source->get_dom(); -exit(1) unless(defined($dom)); +my $meta = $source->get_meta(); +exit(1) unless(defined($meta)); -# Get a list of the guest's transfered storage devices -my $storage = $source->get_storage_paths(); +v2vdie __('Guest doesn\'t define any storage devices') + unless @{$meta->{disks}} > 0; # Create the transfer iso if required my $transferiso; $transferiso = $config->get_transfer_iso(); # Open a libguestfs handle on the guest's storage devices -my $g = new Sys::VirtV2V::GuestfsHandle($storage, $transferiso, - $output_method eq 'rhev'); +my @localpaths = map { $_->{local_path} } @{$meta->{disks}}; +my $g = new Sys::VirtV2V::GuestfsHandle( + \@localpaths, + $transferiso, + $output_method eq 'rhev' +); my $os; my $guestcaps; @@ -447,8 +451,7 @@ eval { $os = inspect_guest($g); # Modify the guest and its metadata - $guestcaps = Sys::VirtV2V::Converter->convert($g, $config, $os, $dom, - $source->get_storage_devices()); + $guestcaps = Sys::VirtV2V::Converter->convert($g, $config, $os, $meta); }; # If any of the above commands result in failure, we need to ensure that the @@ -462,21 +465,20 @@ if ($@) { $g->close(); -$target->create_guest($os, $dom, $guestcaps); +$target->create_guest($os, $meta, $config, $guestcaps); -my ($name) = $dom->findnodes('/domain/name/text()'); -$name = $name->getNodeValue(); if($guestcaps->{block} eq 'virtio' && $guestcaps->{net} eq 'virtio') { - logmsg NOTICE, __x('{name} configured with virtio drivers.', name => $name); + logmsg NOTICE, __x('{name} configured with virtio drivers.', + name => $meta->{name}); } elsif ($guestcaps->{block} eq 'virtio') { logmsg NOTICE, __x('{name} configured with virtio storage only.', - name => $name); + name => $meta->{name}); } elsif ($guestcaps->{net} eq 'virtio') { logmsg NOTICE, __x('{name} configured with virtio networking only.', - name => $name); + name => $meta->{name}); } else { logmsg NOTICE, __x('{name} configured without virtio drivers.', - name => $name); + name => $meta->{name}); } exit(0); @@ -920,7 +922,7 @@ Matthew Booth <mbooth at redhat.com> =head1 COPYRIGHT -Copyright (C) 2009,2010 Red Hat Inc. +Copyright (C) 2009-2011 Red Hat Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by -- 1.7.4
Richard W.M. Jones
2011-Mar-15 11:29 UTC
[Libguestfs] [PATCH 1/2] Allow reading more data than the reported size of a volume
On Fri, Mar 11, 2011 at 01:34:47PM +0000, Matthew Booth wrote:> If a volume is not an exact multiple of 512 bytes, qemu-img will report its size > rounded down to a 512 byte boundary. However, when copying, the file is still > read until EOF, which will return more data than was expected. This change > prevents that causing a failure in itself. > > The situation is still not resolved, however, as there are still situations > where this will cause a failure. For example, copying will fail writing to a > block device if an attempt is made to write too much data. > --- > lib/Sys/VirtV2V/Connection/Source.pm | 2 +- > 1 files changed, 1 insertions(+), 1 deletions(-) > > diff --git a/lib/Sys/VirtV2V/Connection/Source.pm b/lib/Sys/VirtV2V/Connection/Source.pm > index 960aff3..8cbfe25 100644 > --- a/lib/Sys/VirtV2V/Connection/Source.pm > +++ b/lib/Sys/VirtV2V/Connection/Source.pm > @@ -167,7 +167,7 @@ sub _volume_copy > v2vdie __x('Didn\'t receive full volume. Received {received} '. > 'of {total} bytes.', > received => $total, total => $src->get_size()) > - if $src->get_format() eq "raw" && $total != $src->get_size(); > + if $src->get_format() eq "raw" && $total < $src->get_size(); > > return $dst; > } > -- > 1.7.4As discussed on list, ACK. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw
Possibly Parallel Threads
- [PATCH 1/2] Refactor guest and volume creation into Sys::VirtV2V::Target::LibVirt
- Refactor virt-v2v to be more like a 'big script'
- [ESX support] Working ESX conversion for RHEL 5
- [PATCH] Remove v2v-snapshot
- [PATCH 1/6] Convert config file to XML, and translate networks/bridge for all connections