[Libguestfs] [PATCH 2/2] Use an internal representation of domain metadata

Matthew Booth mbooth at redhat.com
Fri Mar 11 13:34:48 UTC 2011


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




More information about the Libguestfs mailing list