Matthew Booth
2010-Mar-30 16:14 UTC
[Libguestfs] [PATCH 1/2] Refactor guest and volume creation into Sys::VirtV2V::Target::LibVirt
Move all target-specific functionality into its own module in preparation for
output to RHEV.
---
MANIFEST | 1 +
lib/Sys/VirtV2V/Connection.pm | 46 ++---
lib/Sys/VirtV2V/Converter.pm | 138 +------------
lib/Sys/VirtV2V/Target/LibVirt.pm | 419 +++++++++++++++++++++++++++++++++++++
lib/Sys/VirtV2V/Transfer/ESX.pm | 91 +++------
po/POTFILES.in | 1 +
v2v/virt-v2v.pl | 69 ++++---
7 files changed, 511 insertions(+), 254 deletions(-)
create mode 100644 lib/Sys/VirtV2V/Target/LibVirt.pm
diff --git a/MANIFEST b/MANIFEST
index cc6dd92..d4dd140 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -12,6 +12,7 @@ lib/Sys/VirtV2V/Connection.pm
lib/Sys/VirtV2V/Connection/LibVirt.pm
lib/Sys/VirtV2V/Connection/LibVirtXML.pm
lib/Sys/VirtV2V/UserMessage.pm
+lib/Sys/VirtV2V/Target/LibVirt.pm
lib/Sys/VirtV2V/Transfer/ESX.pm
MANIFEST This list of files
MANIFEST.SKIP
diff --git a/lib/Sys/VirtV2V/Connection.pm b/lib/Sys/VirtV2V/Connection.pm
index 46bd020..5ecc7e3 100644
--- a/lib/Sys/VirtV2V/Connection.pm
+++ b/lib/Sys/VirtV2V/Connection.pm
@@ -36,7 +36,7 @@ Sys::VirtV2V::Connection - Obtain domain metadata
use Sys::VirtV2V::Connection::LibVirt;
- $conn = Sys::VirtV2V::Connection::LibVirt->new($uri, $name, $pool);
+ $conn = Sys::VirtV2V::Connection::LibVirt->new($uri, $name, $target);
$dom = $conn->get_dom();
$storage = $conn->get_storage_paths();
$devices = $conn->get_storage_devices();
@@ -106,7 +106,7 @@ sub _storage_iterate
{
my $self = shift;
- my ($transfer, $pool) = @_;
+ my ($transfer, $target) = @_;
my $dom = $self->get_dom();
@@ -121,8 +121,8 @@ sub _storage_iterate
defined($source) or die("source element has neither dev nor file:
\n".
$dom->toString());
- my ($target) = $disk->findnodes('target/@dev');
- defined($target) or die("disk does not have a target device:
\n".
+ my ($dev) = $disk->findnodes('target/@dev');
+ defined($dev) or die("disk does not have a target device:
\n".
$dom->toString());
# If the disk is a floppy or a cdrom, blank its source
@@ -135,16 +135,15 @@ sub _storage_iterate
my $path = $source->getValue();
if (defined($transfer)) {
- # Die if transfer required and no output pool
- die (user_message(__"No output pool was specified"))
- unless (defined($pool));
+ # Die if transfer required and no output target
+ die (user_message(__"No output target was
specified"))
+ unless (defined($target));
# Fetch the remote storage
- my $vol = $transfer->transfer($self, $path, $pool);
+ my $vol = $transfer->transfer($self, $path, $target);
- # Parse the XML description of the returned volume
- my $voldom - new
XML::DOM::Parser->parse($vol->get_xml_description());
+ # Export the new path
+ $path = $vol->get_path();
# Find any existing driver element.
my ($driver) = $disk->findnodes('driver');
@@ -156,37 +155,24 @@ sub _storage_iterate
$disk->appendChild($driver);
}
$driver->setAttribute('name', 'qemu');
-
- # Get the volume format for passing to the qemu driver
- my ($format) -
$voldom->findnodes('/volume/target/format/@type');
-
- $format = $format->getValue() if (defined($format));
-
- # Auto-detect if no format is specified explicitly
- $format ||= 'auto';
-
- $driver->setAttribute('type', $format);
+ $driver->setAttribute('type',
$vol->get_format());
# Remove the @file or @dev attribute before adding a new one
$source_e->removeAttributeNode($source);
- $path = $vol->get_path();
-
# Set @file or @dev as appropriate
- if ($vol->get_info()->{type} =-
Sys::Virt::StorageVol::TYPE_FILE)
+ if ($vol->is_block())
{
- $disk->setAttribute('type', 'file');
- $source_e->setAttribute('file', $path);
- } else {
$disk->setAttribute('type', 'block');
$source_e->setAttribute('dev', $path);
+ } else {
+ $disk->setAttribute('type', 'file');
+ $source_e->setAttribute('file', $path);
}
}
push(@paths, $path);
- push(@devices, $target->getNodeValue());
+ push(@devices, $dev->getNodeValue());
}
}
diff --git a/lib/Sys/VirtV2V/Converter.pm b/lib/Sys/VirtV2V/Converter.pm
index b817f49..fbcaa51 100644
--- a/lib/Sys/VirtV2V/Converter.pm
+++ b/lib/Sys/VirtV2V/Converter.pm
@@ -42,7 +42,7 @@ Sys::VirtV2V::Converter - Convert a guest to run on KVM
use Sys::VirtV2V::Converter;
my $guestos = Sys::VirtV2V::GuestOS->new($g, $os, $dom, $config);
- Sys::VirtV2V::Converter->convert($vmm, $guestos, $config, $dom, $os,
$devices);
+ Sys::VirtV2V::Converter->convert($guestos, $config, $dom, $os, $devices);
=head1 DESCRIPTION
@@ -102,16 +102,12 @@ use constant KVM_XML_NOVIRTIO => "
</domain>
";
-=item Sys::VirtV2V::Converter->convert(vmm, guestos, dom, desc)
+=item Sys::VirtV2V::Converter->convert(guestos, dom, desc)
Instantiate an appropriate backend and call convert on it.
=over
-=item vmm
-
-A Sys::Virt connection.
-
=item guestos
An initialised Sys::VirtV2V::GuestOS object for the guest.
@@ -137,8 +133,7 @@ sub convert
{
my $class = shift;
- my ($vmm, $guestos, $config, $dom, $desc, $devices) = @_;
- carp("convert called without vmm argument") unless defined($vmm);
+ my ($guestos, $config, $dom, $desc, $devices) = @_;
carp("convert called without guestos argument") unless
defined($guestos);
# config will be undefined if no config was specified
carp("convert called without dom argument") unless defined($dom);
@@ -162,23 +157,14 @@ sub convert
_map_networks($dom, $config) if (defined($config));
# Convert the metadata
- _convert_metadata($vmm, $dom, $desc, $devices, $guestcaps);
-
- my ($name) = $dom->findnodes('/domain/name/text()');
- $name = $name->getNodeValue();
+ _convert_metadata($dom, $desc, $devices, $guestcaps);
- if($guestcaps->{virtio}) {
- print user_message
- (__x("{name} configured with virtio drivers", name =>
$name));
- } else {
- print user_message
- (__x("{name} configured without virtio drivers", name
=> $name));
- }
+ return $guestcaps;
}
sub _convert_metadata
{
- my ($vmm, $dom, $desc, $devices, $guestcaps) = @_;
+ my ($dom, $desc, $devices, $guestcaps) = @_;
my $arch = $guestcaps->{arch};
my $virtio = $guestcaps->{virtio};
@@ -193,9 +179,6 @@ sub _convert_metadata
# Replace source hypervisor metadata with KVM defaults
_unconfigure_hvs($dom, $default_dom);
- # Configure guest according to local hypervisor's capabilities
- _configure_capabilities($dom, $vmm, $guestcaps);
-
# Remove any configuration related to a PV kernel bootloader
_unconfigure_bootloaders($dom);
@@ -264,115 +247,6 @@ sub _configure_default_devices
}
}
-sub _configure_capabilities
-{
- my ($dom, $vmm, $guestcaps) = @_;
-
- # Parse the capabilities of the connected libvirt
- my $caps = new XML::DOM::Parser->parse($vmm->get_capabilities());
-
- my $arch = $guestcaps->{arch};
-
- (my $guestcap) = $caps->findnodes
-
("/capabilities/guest[arch[\@name='$arch']/domain/\@type='kvm']");
-
- die(__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) {
- print STDERR user_message
- (__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
- # 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})) {
- print STDERR user_message
- (__x("The connected hypervisor does not support
".
- "feature {feature}", feature => $name));
- $feature->getParentNode()->removeChild($feature);
- }
-
- if ($name eq 'acpi' && !$guestcaps->{acpi}) {
- print STDERR user_message
- (__"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);
- }
-
- # Add acpi support if the guest supports it
- if ($guestcaps->{acpi}) {
- $domfeatures->appendChild($dom->createElement('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));
-
- my ($c) = $guestcap->findnodes("features/$feature");
- if (defined($c)) {
- $domfeatures->appendChild($dom->createElement($feature));
- }
- }
-}
-
sub _unconfigure_bootloaders
{
my ($dom) = @_;
diff --git a/lib/Sys/VirtV2V/Target/LibVirt.pm
b/lib/Sys/VirtV2V/Target/LibVirt.pm
new file mode 100644
index 0000000..96ed513
--- /dev/null
+++ b/lib/Sys/VirtV2V/Target/LibVirt.pm
@@ -0,0 +1,419 @@
+# Sys::VirtV2V::Target::LibVirt
+# Copyright (C) 2010 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+use strict;
+use warnings;
+
+package Sys::VirtV2V::Target::LibVirt::Vol;
+
+sub _new
+{
+ my $class = shift;
+ my ($vol) = @_;
+
+ my $self = {};
+ bless($self, $class);
+
+ $self->{vol} = $vol;
+
+ return $self;
+}
+
+sub _create
+{
+ my $class = shift;
+ my ($pool, $name, $size) = @_;
+
+ my $vol_xml = "
+ <volume>
+ <name>$name</name>
+ <capacity>$size</capacity>
+ </volume>
+ ";
+
+ my $vol;
+ eval {
+ $vol = $pool->create_volume($vol_xml);
+ };
+ die(user_message(__x("Failed to create storage volume: {error}",
+ error => $@->stringify()))) if ($@);
+
+ return $class->_new($vol);
+}
+
+sub _get
+{
+ my $class = shift;
+ my ($pool, $name) = @_;
+
+ my $vol;
+ eval {
+ $vol = $pool->get_volume_by_name($name);
+ };
+ die(user_message(__x("Failed to get storage volume: {error}",
+ error => $@->stringify()))) if ($@);
+
+ return $class->_new($vol);
+}
+
+sub get_path
+{
+ my $self = shift;
+
+ return $self->{vol}->get_path();
+}
+
+sub get_format
+{
+ my $self = shift;
+
+ my $vol = $self->{vol};
+ my $voldom = new
XML::DOM::Parser->parse($vol->get_xml_description());
+
+ my ($format) =
$voldom->findnodes('/volume/target/format/@type');
+ $format = $format->getValue() if (defined($format));
+ $format ||= 'auto';
+
+ return $format;
+}
+
+sub is_block
+{
+ my $self = shift;
+
+ my $type = $self->{vol}->get_info()->{type};
+ return $type == Sys::Virt::StorageVol::TYPE_BLOCK;
+}
+
+sub open
+{
+ my $self = shift;
+
+ my $path = $self->get_path();
+ open(my $fd, '>', $path)
+ or die(user_message(__x("Error opening storage volume {path}
".
+ "for writing: {error}", error =>
$!)));
+
+ $self->{fd} = $fd;
+}
+
+sub write
+{
+ my $self = shift;
+ my ($data) = @_;
+
+ defined($self->{fd}) or die("write called without open");
+
+ syswrite($self->{fd}, $data)
+ or die(user_message(__x("Error writing to {path}: {error}",
+ path => $self->get_path(),
+ error => $!)));
+}
+
+sub close
+{
+ my $self = shift;
+
+ close($self->{fd})
+ or die(user_message(__x("Error closing volume handle:
{error}",
+ error => $!)));
+
+ delete($self->{fd});
+}
+
+package Sys::VirtV2V::Target::LibVirt;
+
+use Locale::TextDomain 'virt-v2v';
+
+=head1 NAME
+
+Sys::VirtV2V::Target::LibVirt - Output to libvirt
+
+=head1 SYNOPSIS
+
+ use Sys::VirtV2V::Target::LibVirt;
+
+ my $target = new Sys::VirtV2V::Target::LibVirt($uri, $poolname);
+
+=head1 DESCRIPTION
+
+Sys::VirtV2V::Target::LibVirt creates a new libvirt domain using the given
+target URI. New storage will be created in the target pool.
+
+=head1 METHODS
+
+=over
+
+=item Sys::VirtV2V::Target::LibVirt->new(uri, poolname)
+
+Create a new Sys::VirtV2V::Target::LibVirt object.
+
+=over
+
+=item uri
+
+A libvirt connection URI
+
+=item poolname
+
+The name of a storage pool managed by the target libvirt daemon.
+
+=back
+
+=cut
+
+sub new
+{
+ my $class = shift;
+ my ($uri, $poolname) = @_;
+
+ my $self = {};
+ bless($self, $class);
+
+ $self->{vmm} = Sys::Virt->new(auth => 1, uri => $uri);
+
+ if (defined($poolname)) {
+ eval {
+ $self->{pool} =
$self->{vmm}->get_storage_pool_by_name($poolname);
+ };
+
+ if ($@) {
+ die(user_message(__x("Output pool {poolname} is not a valid
".
+ "storage pool",
+ poolname => $poolname)));
+ }
+ }
+
+ return $self;
+}
+
+=item create_volume(name, size)
+
+Create a new volume in the pool whose name was passed to new().
+
+=over
+
+=item name
+
+The name of the volume which is being created.
+
+=item size
+
+The size of the volume which is being created in bytes.
+
+=back
+
+create_volume() returns a Sys::VirtV2V::Target::LibVirt::Vol object.
+
+=cut
+
+sub create_volume
+{
+ my $self = shift;
+ my ($name, $size) = @_;
+
+ return Sys::VirtV2V::Target::LibVirt::Vol->_create($self->{pool},
+ $name, $size);
+}
+
+=item volume_exists (name)
+
+Check if volume I<name> exists in the target pool.
+
+Returns 1 if it exists, 0 otherwise.
+
+=cut
+
+sub volume_exists
+{
+ my $self = shift;
+ my ($name) = @_;
+
+ my $vol;
+ eval {
+ $vol = $self->{pool}->get_volume_by_name($name);
+ };
+
+ # The above command will generate VIR_ERR_NO_STORAGE_VOL if the
+ # volume doesn't exist
+ if ($@ && $@->code == Sys::Virt::Error::ERR_NO_STORAGE_VOL) {
+ return 0;
+ }
+
+ if ($@) {
+ # We got an error, but not the one we expected
+ die(user_message(__x("Unexpected error accessing storage pool:
",
+ "{error}", error =>
$@->stringify())));
+ }
+
+ return 1;
+}
+
+=item get_volume (name)
+
+Get a reference to an existing volume. See L<create_volume> for return
value.
+
+=cut
+
+sub get_volume
+{
+ my $self = shift;
+ my ($name) = @_;
+
+ return Sys::VirtV2V::Target::LibVirt::Vol->_get($self->{pool},
$name);
+}
+
+=item create_guest(dom)
+
+Create the guest in the target
+
+=cut
+
+sub create_guest
+{
+ my $self = shift;
+ my ($dom, $guestcaps) = @_;
+
+ my $vmm = $self->{vmm};
+
+ _configure_capabilities($vmm, $dom, $guestcaps);
+
+ $vmm->define_domain($dom->toString());
+}
+
+# Configure guest according to target hypervisor's capabilities
+sub _configure_capabilities
+{
+ my ($vmm, $dom, $guestcaps) = @_;
+
+ # Parse the capabilities of the connected libvirt
+ my $caps = new XML::DOM::Parser->parse($vmm->get_capabilities());
+
+ my $arch = $guestcaps->{arch};
+
+ (my $guestcap) = $caps->findnodes
+
("/capabilities/guest[arch[\@name='$arch']/domain/\@type='kvm']");
+
+ die(__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) {
+ print STDERR user_message
+ (__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
+ # 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})) {
+ print STDERR user_message
+ (__x("The connected hypervisor does not support
".
+ "feature {feature}", feature => $name));
+ $feature->getParentNode()->removeChild($feature);
+ }
+
+ if ($name eq 'acpi' && !$guestcaps->{acpi}) {
+ print STDERR user_message
+ (__"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);
+ }
+
+ # Add acpi support if the guest supports it
+ if ($guestcaps->{acpi}) {
+ $domfeatures->appendChild($dom->createElement('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));
+
+ my ($c) = $guestcap->findnodes("features/$feature");
+ if (defined($c)) {
+ $domfeatures->appendChild($dom->createElement($feature));
+ }
+ }
+}
+
+=back
+
+=head1 COPYRIGHT
+
+Copyright (C) 2010 Red Hat Inc.
+
+=head1 LICENSE
+
+Please see the file COPYING for the full license.
+
+=cut
+
+1;
diff --git a/lib/Sys/VirtV2V/Transfer/ESX.pm b/lib/Sys/VirtV2V/Transfer/ESX.pm
index 66ba515..2d15732 100644
--- a/lib/Sys/VirtV2V/Transfer/ESX.pm
+++ b/lib/Sys/VirtV2V/Transfer/ESX.pm
@@ -71,7 +71,7 @@ sub new {
});
$self->{_v2v_server} = $server;
- $self->{_v2v_pool} = $pool;
+ $self->{_v2v_target} = $target;
$self->{_v2v_username} = $username;
$self->{_v2v_password} = $password;
@@ -113,31 +113,19 @@ sub get_volume
$url->query_form(dcPath => "ha-datacenter", dsName =>
$datastore);
# Replace / with _ so the vmdk name can be used as a volume name
- $self->{_v2v_volname} = $vmdk;
- $self->{_v2v_volname} =~ s,/,_,g;
-
- # Check to see if this volume already exists
- eval {
- my $pool = $self->{_v2v_pool};
- $self->{_v2v_vol} =
$pool->get_volume_by_name($self->{_v2v_volname});
- };
-
- # The above command should generate VIR_ERR_NO_STORAGE_VOL because the
- # volume doesn't exist
- unless($@ && $@->code == Sys::Virt::Error::ERR_NO_STORAGE_VOL) {
- unless ($@) {
- print STDERR user_message(__x("WARNING: storage volume {name}
".
- "already exists in the target
".
- "pool. NOT fetching it again.
".
- "Delete the volume and retry to
".
- "download again.",
- name =>
$self->{_v2v_volname}));
- return $self->{_v2v_vol};
- }
-
- # We got an error, but not the one we expected
- die(user_message(__x("Unexpected error accessing storage pool:
",
- "{error}", error =>
$@->stringify())));
+ my $volname = $vmdk;
+ $volname =~ s,/,_,g;
+ $self->{_v2v_volname} = $volname;
+
+ my $target = $self->{_v2v_target};
+ if ($target->volume_exists($volname)) {
+ print STDERR user_message(__x("WARNING: storage volume {name}
".
+ "already exists in the target
".
+ "pool. NOT fetching it again.
".
+ "Delete the volume and retry to
".
+ "download again.",
+ name => $volname));
+ return $target->get_volume($volname);
}
$self->{_v2v_received} = 0;
@@ -150,11 +138,9 @@ sub get_volume
my $died = $r->header('X-Died');
die($died) if (defined($died));
- # Close the volume file descriptor
- close($self->{_v2v_volfh})
- or die(user_message(__x("Error closing volume handle:
{error}",
- error => $!)));
- return $self->{_v2v_vol};
+ my $vol = $self->{_v2v_vol};
+ $vol->close();
+ return $vol;
}
die(user_message(__x("Didn't receive full volume. Received
{received} of ".
@@ -192,14 +178,8 @@ sub handle_data
my ($data, $response) = @_;
- my $volfh = $self->{_v2v_volfh};
-
$self->{_v2v_received} += length($data);
-
- syswrite($volfh, $data)
- or die(user_message(__x("Error writing to {path}: {error}",
- path => $self->{_v2v_volpath},
- error => $!)));
+ $self->{_v2v_vol}->write($data);
}
sub create_volume
@@ -208,9 +188,8 @@ sub create_volume
my ($response) = @_;
- my $pool = $self->{_v2v_pool};
+ my $target = $self->{_v2v_target};
- # Create a volume in the target storage pool of the correct size
my $name = $self->{_v2v_volname};
die("create_volume called, but _v2v_volname is not set")
unless (defined($name));
@@ -218,27 +197,9 @@ sub create_volume
my $size = $response->content_length();
$self->{_v2v_volsize} = $size;
- my $vol_xml = "
- <volume>
- <name>$name</name>
- <capacity>$size</capacity>
- </volume>
- ";
-
- my $volume;
- eval {
- $volume = $pool->create_volume($vol_xml);
- };
- die(user_message(__x("Failed to create storage volume: {error}",
- error => $@->stringify()))) if ($@);
- $self->{_v2v_vol} = $volume;
-
- # Open the volume for writing
- open(my $volfh, '>', $volume->get_path())
- or die(user_message(__x("Error opening storage volume {path}
".
- "for writing: {error}", error =>
$!)));
-
- $self->{_v2v_volfh} = $volfh;
+ my $vol = $target->create_volume($name, $size);
+ $vol->open();
+ $self->{_v2v_vol} = $vol;
}
sub verify_certificate
@@ -295,10 +256,10 @@ Sys::VirtV2V::Transfer::ESX retrieves guest storage
devices from an ESX server.
=over
-=item transfer(conn, path, pool)
+=item transfer(conn, path, target)
Transfer <path> from a remote ESX server. Server and authentication
details will
-be taken from <conn>. Storage will be copied to a new volume created in
<pool>.
+be taken from <conn>. Storage will be created using <target>.
=cut
@@ -306,7 +267,7 @@ sub transfer
{
my $class = shift;
- my ($conn, $path, $pool) = @_;
+ my ($conn, $path, $target) = @_;
my $uri = $conn->{uri};
my $username = $conn->{username};
@@ -330,7 +291,7 @@ sub transfer
my $ua = Sys::VirtV2V::Transfer::ESX::UA->new($conn->{hostname},
$username,
$password,
- $pool,
+ $target,
$noverify);
return $ua->get_volume($path);
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3874426..ffdf250 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -7,6 +7,7 @@
../lib/Sys/VirtV2V/GuestOS.pm
../lib/Sys/VirtV2V/GuestOS/RedHat.pm
../lib/Sys/VirtV2V.pm
+../lib/Sys/VirtV2V/Target/LibVirt.pm
../lib/Sys/VirtV2V/Transfer/ESX.pm
../lib/Sys/VirtV2V/UserMessage.pm
../snapshot/v2v-snapshot.pl
diff --git a/v2v/virt-v2v.pl b/v2v/virt-v2v.pl
index 33e65f3..a9e834f 100755
--- a/v2v/virt-v2v.pl
+++ b/v2v/virt-v2v.pl
@@ -36,6 +36,7 @@ use Sys::VirtV2V;
use Sys::VirtV2V::Converter;
use Sys::VirtV2V::Connection::LibVirt;
use Sys::VirtV2V::Connection::LibVirtXML;
+use Sys::VirtV2V::Target::LibVirt;
use Sys::VirtV2V::ExecHelper;
use Sys::VirtV2V::GuestOS;
use Sys::VirtV2V::UserMessage qw(user_message);
@@ -99,11 +100,29 @@ my $input_transport;
=item B<-it method>
-Species the transport method used to obtain raw storage from the source guest.
+Specifies the transport method used to obtain raw storage from the source
guest.
This is currently only a placeholder, and does nothing.
=cut
+my $output_method = "libvirt";
+
+=item B<-o method>
+
+Specifies the output method. Supported output methods are:
+
+=over
+
+=item libvirt
+
+Create a libvirt guest. See the I<-oc> and I<-op> options.
+
+=back
+
+If no output type is specified, it defaults to libvirt.
+
+=cut
+
my $output_uri = "qemu:///system";
=item B<-oc URI>
@@ -186,11 +205,13 @@ if(defined($config_file)) {
path => $config_file, error => $@))) if ($@);
}
-# Connect to target libvirt
-my $vmm = Sys::Virt->new(
- auth => 1,
- uri => $output_uri
-);
+my $target;
+if ($output_method eq "libvirt") {
+ $target = new Sys::VirtV2V::Target::LibVirt($output_uri, $output_pool);
+} else {
+ die(user_message(__x("{output} is not a valid output method",
+ output => $output_method)));
+}
# Get an appropriate Connection
my $conn;
@@ -215,24 +236,8 @@ eval {
pod2usage({ -message => user_message(__"You must specify a
guest"),
-exitval => 1 });
- # Get a handle to the output pool if one is defined
- my $pool;
- if (defined($output_pool)) {
- eval {
- $pool = $vmm->get_storage_pool_by_name($output_pool);
- };
-
- if ($@) {
- print STDERR user_message
- (__x("Output pool {poolname} is not a valid local
".
- "storage pool",
- poolname => $output_pool));
- exit(1);
- }
- }
-
$conn = Sys::VirtV2V::Connection::LibVirt->new($input_uri, $name,
- $pool);
+ $target);
# Warn if we were given more than 1 argument
if(scalar(@_) > 0) {
@@ -280,10 +285,20 @@ my $os = inspect_guest($g);
my $guestos = Sys::VirtV2V::GuestOS->new($g, $os, $dom, $config);
# Modify the guest and its metadata for the target hypervisor
-Sys::VirtV2V::Converter->convert($vmm, $guestos, $config, $dom, $os,
- $conn->get_storage_devices());
-
-$vmm->define_domain($dom->toString());
+my $guestcaps = Sys::VirtV2V::Converter->convert($guestos, $config, $dom,
$os,
+
$conn->get_storage_devices());
+
+$target->create_guest($dom, $guestcaps);
+
+my ($name) = $dom->findnodes('/domain/name/text()');
+$name = $name->getNodeValue();
+if($guestcaps->{virtio}) {
+ print user_message
+ (__x("{name} configured with virtio drivers", name =>
$name));
+} else {
+ print user_message
+ (__x("{name} configured without virtio drivers", name =>
$name));
+}
exit(0);
--
1.6.6.1
Allow guests to be written to a RHEV NFS export storage domain.
Add 'rhev' output method and -osd command-line option.
Example command line:
virt-v2v -f virt-v2v.conf -ic 'esx://yellow.rhev.marston/' \
-o rhev -osd blue:/export/export RHEL3-32
This will connect to an ESX server and write the guest 'RHEL3-32' to
blue:/export/export, which is a pre-initialised RHEV export storage domain.
---
MANIFEST | 1 +
lib/Sys/VirtV2V/Target/RHEV.pm | 900 ++++++++++++++++++++++++++++++++++++++++
v2v/virt-v2v.conf | 12 +
v2v/virt-v2v.pl | 50 +++-
4 files changed, 961 insertions(+), 2 deletions(-)
create mode 100644 lib/Sys/VirtV2V/Target/RHEV.pm
diff --git a/MANIFEST b/MANIFEST
index d4dd140..1bc6018 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -13,6 +13,7 @@ lib/Sys/VirtV2V/Connection/LibVirt.pm
lib/Sys/VirtV2V/Connection/LibVirtXML.pm
lib/Sys/VirtV2V/UserMessage.pm
lib/Sys/VirtV2V/Target/LibVirt.pm
+lib/Sys/VirtV2V/Target/RHEV.pm
lib/Sys/VirtV2V/Transfer/ESX.pm
MANIFEST This list of files
MANIFEST.SKIP
diff --git a/lib/Sys/VirtV2V/Target/RHEV.pm b/lib/Sys/VirtV2V/Target/RHEV.pm
new file mode 100644
index 0000000..0251ab8
--- /dev/null
+++ b/lib/Sys/VirtV2V/Target/RHEV.pm
@@ -0,0 +1,900 @@
+# Sys::VirtV2V::Target::RHEV
+# Copyright (C) 2010 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+use strict;
+use warnings;
+
+package Sys::VirtV2V::Target::RHEV::UUIDHelper;
+
+sub get_uuid
+{
+ my $uuidgen;
+ open($uuidgen, '<', '/proc/sys/kernel/random/uuid')
+ or die("Unable to open /proc/sys/kernel/random/uuid: $!");
+
+ my $uuid;
+ while(<$uuidgen>) {
+ chomp;
+ $uuid = $_;
+ }
+ close($uuidgen) or die("Error closing /proc/sys/kernel/random/uuid:
$!");
+
+ return $uuid;
+}
+
+package Sys::VirtV2V::Target::RHEV::NFSHelper;
+
+use Carp;
+use File::Temp qw(tempfile);
+use POSIX qw(:sys_wait_h setuid setgid);
+
+use Sys::VirtV2V::UserMessage qw(user_message);
+
+use Locale::TextDomain 'virt-v2v';
+
+sub new
+{
+ my $class = shift;
+ my ($sub) = @_;
+
+ my $self = {};
+ bless($self, $class);
+
+ my ($tochild_read, $tochild_write);
+ my ($fromchild_read, $fromchild_write);
+
+ pipe($tochild_read, $tochild_write);
+ pipe($fromchild_read, $fromchild_write);
+
+ # Capture stderr to a file
+ my ($stderr, undef) = tempfile(UNLINK => 1, SUFFIX =>
'.virt-v2v');
+ $self->{stderr} = $stderr;
+
+ my $pid = fork();
+ if ($pid == 0) {
+ # Close the ends of the pipes we don't need
+ close($tochild_write);
+ close($fromchild_read);
+
+ # dup2() stdin and stdout with the communication pipes
+ open(STDIN, "<&".fileno($tochild_read))
+ or die("dup stdin failed: $!");
+ open(STDOUT, ">&".fileno($fromchild_write))
+ or die("dup stdout failed: $!");
+
+ # Write stderr to our temp file
+ open(STDERR, ">&".fileno($stderr))
+ or die("dup stderr failed: $!");
+
+ # Close the original file handles
+ close($tochild_read);
+ close($fromchild_write);
+
+ # Set EUID and EGID to RHEV magic values 36:36
+ # execute the wrapped function, trapping errors
+ eval {
+ setgid(36) or die("setgid failed: $!");
+ setuid(36) or die("setuid failed: $!");
+
+print STDERR "EUID: $>\n";
+print STDERR "EGID: $)\n";
+
+ &$sub();
+ };
+
+ # Don't exit, which would cause destructors to be called in the
child.
+ # Instead exec /bin/true or /bin/false as appropriate
+ if ($@) {
+ print $stderr $@;
+ close($stderr);
+ exec('/bin/false');
+ }
+
+ exec('/bin/true');
+ } else {
+ close($tochild_read);
+ close($fromchild_write);
+ }
+
+ $self->{tochild} = $tochild_write;
+ $self->{fromchild} = $fromchild_read;
+
+ $self->{pid} = $pid;
+ return $self;
+}
+
+sub check_exit
+{
+ my $self = shift;
+
+ my $ret = waitpid($self->{pid}, 0);
+
+ # If the process terminated normally, check the exit status and stderr
+ if ($ret == $self->{pid}) {
+ delete($self->{pid});
+
+ # No error if the exit status was 0
+ return if ($? == 0);
+
+ # Otherwise return whatever went to stderr
+ my $stderr = $self->{stderr};
+ my $error = "";
+ seek($stderr, 0, 0);
+ while(<$stderr>) {
+ $error .= $_;
+ }
+ die($error);
+ }
+
+ confess("Error waiting for child process");
+}
+
+sub DESTROY
+{
+ my $self = shift;
+
+ # Make certain the child process dies with the object
+ if (defined($self->{pid})) {
+ kill(9, $self->{pid});
+ waitpid($self->{pid}, WNOHANG);
+ }
+}
+
+package Sys::VirtV2V::Target::RHEV::Vol;
+
+use POSIX;
+
+use Sys::VirtV2V::UserMessage qw(user_message);
+
+use Locale::TextDomain 'virt-v2v';
+
+our %vols_by_path;
+
+sub _new
+{
+ my $class = shift;
+ my ($mountdir, $domainuuid, $size) = @_;
+
+ my $self = {};
+ bless($self, $class);
+
+ $self->{size} = $size;
+
+ my $imageuuid = Sys::VirtV2V::Target::RHEV::UUIDHelper::get_uuid();
+ my $voluuid = Sys::VirtV2V::Target::RHEV::UUIDHelper::get_uuid();
+ $self->{imageuuid} = $imageuuid;
+ $self->{voluuid} = $voluuid;
+ $self->{domainuuid} = $domainuuid;
+
+ $self->{dir} = "$mountdir/$domainuuid/images/$imageuuid";
+ $self->{path} = $self->{dir}."/$voluuid";
+
+ $self->{creation} = time();
+
+ $vols_by_path{$self->{path}} = $self;
+
+ return $self;
+}
+
+sub _get_by_path
+{
+ my $class = shift;
+ my ($path) = @_;
+
+ return $vols_by_path{$path};
+}
+
+sub _get_size
+{
+ my $self = shift;
+
+ return $self->{size};
+}
+
+sub _get_imageuuid
+{
+ my $self = shift;
+
+ return $self->{imageuuid};
+}
+
+sub _get_voluuid
+{
+ my $self = shift;
+
+ return $self->{voluuid};
+}
+
+sub _get_creation
+{
+ my $self = shift;
+
+ return $self->{creation};
+}
+
+sub get_path
+{
+ my $self = shift;
+
+ return $self->{path};
+}
+
+sub get_format
+{
+ my $self = shift;
+
+ return "raw";
+}
+
+sub is_block
+{
+ my $self = shift;
+
+ return 0;
+}
+
+sub open
+{
+ my $self = shift;
+
+ my $now = $self->{creation};
+
+ $self->{writer} = Sys::VirtV2V::Target::RHEV::NFSHelper->new(sub {
+ my $dir = $self->{dir};
+ my $path = $self->{path};
+
+ mkdir($dir)
+ or die(user_message(__x("Failed to create directory {dir}:
{error}",
+ dir => $dir,
+ error => $!)));
+
+ # Write out the .meta file
+ my $meta;
+ open($meta, '>', "$path.meta")
+ or die(__x("Unable to open {path} for writing: {error}",
+ path => "$path.meta",
+ error => $!));
+
+ print $meta "DOMAIN=".$self->{domainuuid}."\n";
+ print $meta "VOLTYPE=LEAF\n";
+ print $meta "CTIME=$now\n";
+ print $meta "FORMAT=RAW\n";
+ print $meta "IMAGE=".$self->{imageuuid}."\n";
+ print $meta "DISKTYPE=1\n";
+ print $meta "PUUID=00000000-0000-0000-0000-000000000000\n";
+ print $meta "LEGALITY=LEGAL\n";
+ print $meta "MTIME=$now\n";
+ print $meta
"POOL_UUID=00000000-0000-0000-0000-000000000000\n";
+ print $meta
"SIZE=".ceil($self->{size}/1024)."\n";
+ print $meta "TYPE=SPARSE\n";
+ print $meta "DESCRIPTION=Exported by virt-v2v\n";
+ print $meta "EOF\n";
+
+ close($meta)
+ or die(user_message(__x("Error closing {path}: {error}",
+ path => "$path.meta",
+ error => $!)));
+
+ # Open the data file for writing
+ my $data;
+ open($data, '>', $path)
+ or die(__x("Unable to open {path} for writing: {error}",
+ path => "$path",
+ error => $!));
+
+ # Write all data received to the data file
+ my $buffer;
+
+ for(;;) {
+ my $ret = sysread(STDIN, $buffer, 64*1024);
+ die("Error in NFSHelper reading from stdin: $!")
+ unless (defined($ret));
+ last if ($ret == 0);
+
+ print $data $buffer;
+ }
+
+ close($data)
+ or die(user_message(__x("Error closing {path}: {error}",
+ path => "$path",
+ error => $!)));
+ });
+}
+
+sub write
+{
+ my $self = shift;
+ my ($data) = @_;
+
+ defined($self->{writer}) or die("write called without open");
+
+ unless(print {$self->{writer}->{tochild}} $data) {
+ # This should only have failed if there was an error from the helper
+ $self->{writer}->check_exit();
+
+ # die() explicitly in case the above didn't
+ die("Error writing to helper: $!");
+ }
+}
+
+sub close
+{
+ my $self = shift;
+
+ # Close the writer pipe, which will cause the child to exit
+ close($self->{writer}->{tochild})
+ or die("Error closing tochild pipe");
+
+ # Wait for the child to exit
+ $self->{writer}->check_exit();
+
+ delete($self->{writer});
+}
+
+package Sys::VirtV2V::Target::RHEV;
+
+use File::Temp qw(tempdir);
+use Time::gmtime;
+
+use Sys::VirtV2V::ExecHelper;
+use Sys::VirtV2V::UserMessage qw(user_message);
+
+use Locale::TextDomain 'virt-v2v';
+
+=head1 NAME
+
+Sys::VirtV2V::Target::RHEV - Output to a RHEV Export storage domain
+
+=head1 SYNOPSIS
+
+ use Sys::VirtV2V::Target::RHEV;
+
+ my $target = new Sys::VirtV2V::Target::RHEV($domain_path);
+
+=head1 DESCRIPTION
+
+Sys::VirtV2V::Target::RHEV write the converted guest to a RHEV Export storage
+domain. This can later be imported to RHEV by the user.
+
+=head1 METHODS
+
+=over
+
+=item Sys::VirtV2V::Target::RHEV->new(domain_path)
+
+Create a new Sys::VirtV2V::Target::RHEV object.
+
+=over
+
+=item domain_path
+
+The NFS path to an initialised RHEV Export storage domain.
+
+=back
+
+=cut
+
+sub new
+{
+ my $class = shift;
+ my ($domain_path) = @_;
+
+ my $self = {};
+ bless($self, $class);
+
+ die(user_message(__"You must be root to output to RHEV"))
+ unless ($> == 0);
+
+ my $mountdir = tempdir();
+
+ # Needs to be read by 36:36
+ chown(36, 36, $mountdir)
+ or die(user_message(__x("Unable to change ownership of {mountdir}
to ".
+ "36:36",
+ mountdir => $mountdir)));
+
+ $self->{mountdir} = $mountdir;
+ $self->{domain_path} = $domain_path;
+
+ my $eh = Sys::VirtV2V::ExecHelper->run('mount', $domain_path,
$mountdir);
+ if ($eh->status() != 0) {
+ die(user_message(__x("Failed to mount {path}. Command exited with
".
+ "status {status}. Output was: {output}",
+ path => $domain_path,
+ status => $eh->status(),
+ output => $eh->output())));
+ }
+
+ my $nfs = Sys::VirtV2V::Target::RHEV::NFSHelper->new(sub {
+ opendir(my $dir, $mountdir)
+ or die(user_message(__x("Unable to open {mountdir}:
{error}",
+ mountdir => $mountdir,
+ error => $!)));
+
+ foreach my $entry (readdir($dir)) {
+ # return entries which look like uuids
+ print "$entry\n"
+ if ($entry =~ /^[0-9a-z]{8}-(?:[0-9a-z]{4}-){3}[0-9a-z]{12}$/);
+ }
+ });
+
+ # Get the UUID of the storage domain
+ my $domainuuid;
+ my $fromchild = $nfs->{fromchild};
+ while (<$fromchild>) {
+ if (defined($domainuuid)) {
+ die(user_message(__x("{mountdir} contains multiple possible
".
+ "domains. It may only contain one.",
+ mountdir => $mountdir)));
+ }
+ chomp;
+ $domainuuid = $_;
+ }
+
+ $nfs->check_exit();
+
+ if (!defined($domainuuid)) {
+ die(user_message(__x("{mountdir} does not contain an initialised
".
+ "storage domain",
+ mountdir => $mountdir)));
+ }
+
+ $self->{domainuuid} = $domainuuid;
+
+ return $self;
+}
+
+sub DESTROY
+{
+ my $self = shift;
+
+ my $eh = Sys::VirtV2V::ExecHelper->run('umount',
$self->{mountdir});
+ if ($eh->status() != 0) {
+ print STDERR user_message(__x("Failed to unmount {path}. Command
".
+ "exited with status {status}. Output
".
+ "was: {output}",
+ path => $self->{domain_path},
+ status => $eh->status(),
+ output => $eh->output()));
+ }
+
+ rmdir($self->{mountdir})
+ or print STDERR user_message(__x("Failed to remove mount directory
".
+ "{dir}: {error}",
+ dir => $self->{mountdir},
+ error => $!));
+}
+
+=item create_volume(name, size)
+
+Create a new volume in the export storage domain
+
+=over
+
+=item name
+
+The name of the volume which is being created.
+
+=item size
+
+The size of the volume which is being created in bytes.
+
+=back
+
+create_volume() returns a Sys::VirtV2V::Target::RHEV::Vol object.
+
+=cut
+
+sub create_volume
+{
+ my $self = shift;
+ my ($name, $size) = @_;
+
+ return Sys::VirtV2V::Target::RHEV::Vol->_new($self->{mountdir},
+ $self->{domainuuid},
+ $size);
+}
+
+=item volume_exists (name)
+
+Check if volume I<name> exists in the target storage domain.
+
+Always returns 0, as RHEV storage domains don't have names
+
+=cut
+
+sub volume_exists
+{
+ my $self = shift;
+ my ($name) = @_;
+
+ return 0;
+}
+
+=item get_volume (name)
+
+Not defined for RHEV output
+
+=cut
+
+sub get_volume
+{
+ my $self = shift;
+ my ($name) = @_;
+
+ die("Cannot retrieve an existing RHEV storage volume by name");
+}
+
+=item create_guest(dom)
+
+Create the guest in the target
+
+=cut
+
+sub create_guest
+{
+ my $self = shift;
+ my ($dom, $guestcaps) = @_;
+
+ # Get the name of the guest
+ my ($name) = $dom->findnodes('/domain/name/text()');
+ $name = $name->getNodeValue();
+
+ # Get the number of virtual cpus
+ my ($ncpus) = $dom->findnodes('/domain/vcpu/text()');
+ $ncpus = $ncpus->getNodeValue();
+
+ # Get the amount of memory in MB
+ my ($memsize) = $dom->findnodes('/domain/memory/text()');
+ $memsize = $memsize->getNodeValue();
+ $memsize = int($memsize / 1024);
+
+ # Generate a creation date
+ my $now = gmtime();
+ my $vmcreation = sprintf("%02d/%02d/%d %02d:%02d:%02d",
+ $now->mday(), $now->mon() + 1,
$now->year() + 1900,
+ $now->hour(), $now->min(), $now->sec());
+
+ my $osuuid = Sys::VirtV2V::Target::RHEV::UUIDHelper::get_uuid();
+
+ my $ovf = new XML::DOM::Parser->parse(<<EOF);
+<ovf:Envelope
+
xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
+
xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1/"
+ ovf:version="0.9">
+
+ <References/>
+
+ <Section xsi:type="ovf:NetworkSection_Type">
+ <Info>List of networks</Info>
+ </Section>
+
+ <Section xsi:type="ovf:DiskSection_Type">
+ <Info>List of Virtual Disks</Info>
+ </Section>
+
+ <Content ovf:id="out"
xsi:type="ovf:VirtualSystem_Type">
+ <Name>$name</Name>
+
<TemplateId>00000000-0000-0000-0000-000000000000</TemplateId>
+ <TemplateName>Blank</TemplateName>
+ <Description>Imported with virt-v2v</Description>
+ <Domain/>
+ <CreationDate>$vmcreation</CreationDate>
+ <IsInitilized>True</IsInitilized>
+ <IsAutoSuspend>False</IsAutoSuspend>
+ <TimeZone/>
+ <IsStateless>False</IsStateless>
+ <Origin>0</Origin>
+ <VmType>1</VmType>
+ <DefaultDisplayType>0</DefaultDisplayType>
+
+ <Section ovf:id="$osuuid" ovf:required="false"
xsi:type="ovf:OperatingSystemSection_Type">
+ <Info>Guest Operating System</Info>
+ <Description>Unassigned</Description>
+ </Section>
+
+ <Section xsi:type="ovf:VirtualHardwareSection_Type">
+ <Info>$ncpus CPU, $memsize Memory</Info>
+ <Item>
+ <rasd:Caption>$ncpus virtual cpu</rasd:Caption>
+ <rasd:Description>Number of virtual
CPU</rasd:Description>
+ <rasd:InstanceId>1</rasd:InstanceId>
+ <rasd:ResourceType>3</rasd:ResourceType>
+ <rasd:num_of_sockets>$ncpus</rasd:num_of_sockets>
+ <rasd:cpu_per_socket>1</rasd:cpu_per_socket>
+ </Item>
+ <Item>
+ <rasd:Caption>$memsize MB of memory</rasd:Caption>
+ <rasd:Description>Memory Size</rasd:Description>
+ <rasd:InstanceId>2</rasd:InstanceId>
+ <rasd:ResourceType>4</rasd:ResourceType>
+
<rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>
+
<rasd:VirtualQuantity>$memsize</rasd:VirtualQuantity>
+ </Item>
+ <Item>
+ <rasd:Caption>USB Controller</rasd:Caption>
+ <rasd:InstanceId>4</rasd:InstanceId>
+ <rasd:ResourceType>23</rasd:ResourceType>
+ <rasd:UsbPolicy>Disabled</rasd:UsbPolicy>
+ </Item>
+ <Item>
+ <rasd:Caption>Graphical Controller</rasd:Caption>
+ <rasd:InstanceId>5</rasd:InstanceId>
+ <rasd:ResourceType>20</rasd:ResourceType>
+ <rasd:VirtualQuantity>1</rasd:VirtualQuantity>
+ </Item>
+ </Section>
+ </Content>
+</ovf:Envelope>
+EOF
+
+ $self->_disks($ovf, $dom);
+ $self->_networks($ovf, $dom);
+
+ my $nfs = Sys::VirtV2V::Target::RHEV::NFSHelper->new(sub {
+ my $mountdir = $self->{mountdir};
+ my $domainuuid = $self->{domainuuid};
+
+ my $vmuuid = Sys::VirtV2V::Target::RHEV::UUIDHelper::get_uuid();
+
+ my $dir =
$mountdir.'/'.$domainuuid.'/master/vms/'.$vmuuid;
+ mkdir($dir)
+ or die(user_message(__x("Failed to create directory {dir}:
{error}",
+ dir => $dir,
+ error => $!)));
+
+ my $vm;
+ my $ovfpath = $dir.'/'.$vmuuid.'.ovf';
+ open($vm, '>', $ovfpath)
+ or die(user_message(__x("Unable to open {path} for writing:
".
+ "{error}",
+ path => $ovfpath,
+ error => $!)));
+
+ print $vm $ovf->toString();
+ });
+ $nfs->check_exit();
+}
+
+sub _disks
+{
+ my $self = shift;
+ my ($ovf, $dom) = @_;
+
+ my ($references) = $ovf->findnodes('/ovf:Envelope/References');
+ die("no references") unless (defined($references));
+
+ my ($disksection) = $ovf->findnodes("/ovf:Envelope/Section".
+ "[\@xsi:type =
'ovf:DiskSection_Type']");
+ die("no disksection") unless (defined($disksection));
+
+ my ($virtualhardware) +
$ovf->findnodes("/ovf:Envelope/Content/Section".
+ "[\@xsi:type =
'ovf:VirtualHardwareSection_Type'");
+ die("no virtualhardware") unless (defined($virtualhardware));
+
+ my $driveno = 1;
+
+ foreach my $disk
+
($dom->findnodes("/domain/devices/disk[\@device='disk']"))
+ {
+ my ($path) = $disk->findnodes('source/@file');
+ $path = $path->getNodeValue();
+
+ my $vol = Sys::VirtV2V::Target::RHEV::Vol->_get_by_path($path);
+
+ die("dom contains path not written by virt-v2v: $path\n".
+ $dom->toString()) unless (defined($vol));
+
+ my $fileref =
$vol->_get_imageuuid().'/'.$vol->_get_voluuid();
+ my $size_gb = int($vol->_get_size()/1024/1024/1024);
+
+ # Add disk to References
+ my $file = $ovf->createElement("File");
+ $references->appendChild($file);
+
+ $file->setAttribute('ovf:href', $fileref);
+ $file->setAttribute('ovf:id', $vol->_get_voluuid());
+ $file->setAttribute('ovf:size', $vol->_get_size());
+ $file->setAttribute('ovf:description', 'imported by
virt-v2v');
+
+ # Add disk to DiskSection
+ my $diske = $ovf->createElement("Disk");
+ $disksection->appendChild($diske);
+
+ $diske->setAttribute('ovf:diskId', $vol->_get_voluuid());
+ $diske->setAttribute('ovf:size', $size_gb);
+ $diske->setAttribute('ovf:actual_size', $size_gb);
+ $diske->setAttribute('ovf:fileRef', $fileref);
+ $diske->setAttribute('ovf:parentRef', '');
+ $diske->setAttribute('ovf:vm_snapshot_id',
+ '00000000-0000-0000-0000-000000000000');
+ $diske->setAttribute('ovf:volume-format', 'RAW');
+ $diske->setAttribute('ovf:volume-type', 'Sparse');
+ $diske->setAttribute('ovf:format',
'http://en.wikipedia.org/wiki/Byte');
+
+ # Add disk to VirtualHardware
+ my $item = $ovf->createElement('Item');
+ $virtualhardware->appendChild($item);
+
+ my $e;
+ $e = $ovf->createElement('rasd:Caption');
+ $e->addText("Drive $driveno"); # This text MUST begin with
the string
+ # 'Drive ' or the file will not
parse
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:InstanceId');
+ $e->addText($vol->_get_voluuid());
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:ResourceType');
+ $e->addText('17');
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:HostResource');
+ $e->addText($fileref);
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:Parent');
+ $e->addText('00000000-0000-0000-0000-000000000000');
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:Template');
+ $e->addText('00000000-0000-0000-0000-000000000000');
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:ApplicationList');
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:StorageId');
+ $e->addText($self->{domainuuid});
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:StoragePoolId');
+ $e->addText('00000000-0000-0000-0000-000000000000');
+ $item->appendChild($e);
+
+ my $volcreation = gmtime($vol->_get_creation());
+ my $voldate = sprintf("%02d/%02d/%d %02d:%02d:%02d",
+ $volcreation->mday(), $volcreation->mon() +
1,
+ $volcreation->year() + 1900,
$volcreation->hour(),
+ $volcreation->min(), $volcreation->sec());
+
+ $e = $ovf->createElement('rasd:CreationDate');
+ $e->addText($voldate);
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:LastModified');
+ $e->addText($voldate);
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:last_modified_date');
+ $e->addText($voldate);
+ $item->appendChild($e);
+
+ $driveno++;
+ }
+}
+
+sub _networks
+{
+ my $self = shift;
+ my ($ovf, $dom) = @_;
+
+ my ($networksection) =
$ovf->findnodes("/ovf:Envelope/Section".
+ "[\@xsi:type =
'ovf:NetworkSection_Type']");
+
+ my ($virtualhardware) +
$ovf->findnodes("/ovf:Envelope/Content/Section".
+ "[\@xsi:type =
'ovf:VirtualHardwareSection_Type'");
+ die("no virtualhardware") unless (defined($virtualhardware));
+
+ my $i = 0;
+
+ 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";
+
+ my $e = $ovf->createElement("Network");
+ $e->setAttribute('ovf:name', $name);
+ $networksection->appendChild($e);
+
+ my $item = $ovf->createElement('Item');
+ $virtualhardware->appendChild($item);
+
+ $e = $ovf->createElement('rasd:Caption');
+ $e->addText("Ethernet adapter on $name");
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:InstanceId');
+ $e->addText('3');
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:ResourceType');
+ $e->addText('10');
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:ResourceSubType');
+ if ($driver eq 'rtl8139') {
+ $e->addText('1');
+ } elsif ($driver eq 'e1000') {
+ $e->addText('2');
+ } elsif ($driver eq 'virtio') {
+ $e->addText('3');
+ } else {
+ print STDERR (user_message(__x("Unknown NIC model {driver} for
".
+ "{dev}. NIC will be {default}
".
+ "when imported",
+ driver => $driver,
+ dev => $dev,
+ default => 'e1000')));
+ $e->addText('1');
+ }
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:Connection');
+ $e->addText($name);
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:Name');
+ $e->addText($dev);
+ $item->appendChild($e);
+
+ $e = $ovf->createElement('rasd:MACAddress');
+ $e->addText($mac) if (defined($mac));
+ $item->appendChild($e);
+
+ $i++;
+ }
+}
+
+=back
+
+=head1 COPYRIGHT
+
+Copyright (C) 2010 Red Hat Inc.
+
+=head1 LICENSE
+
+Please see the file COPYING for the full license.
+
+=cut
+
+1;
diff --git a/v2v/virt-v2v.conf b/v2v/virt-v2v.conf
index 7101e1e..4597adc 100644
--- a/v2v/virt-v2v.conf
+++ b/v2v/virt-v2v.conf
@@ -59,4 +59,16 @@
<network type='bridge' name='VM Network'>
<network type='network' name='default'/>
</network>
+
+ <!-- If importing to RHEV, you may want to use the default network name
+ 'rhevm' instead -->
+ <!--
+ <network type='bridge' name='xenbr1'>
+ <network type='network' name='rhevm'/>
+ </network>
+
+ <network type='bridge' name='VM Network'>
+ <network type='network' name='rhevm'/>
+ </network>
+ -->
</virt-v2v>
diff --git a/v2v/virt-v2v.pl b/v2v/virt-v2v.pl
index a9e834f..351caf1 100755
--- a/v2v/virt-v2v.pl
+++ b/v2v/virt-v2v.pl
@@ -37,6 +37,7 @@ use Sys::VirtV2V::Converter;
use Sys::VirtV2V::Connection::LibVirt;
use Sys::VirtV2V::Connection::LibVirtXML;
use Sys::VirtV2V::Target::LibVirt;
+use Sys::VirtV2V::Target::RHEV;
use Sys::VirtV2V::ExecHelper;
use Sys::VirtV2V::GuestOS;
use Sys::VirtV2V::UserMessage qw(user_message);
@@ -145,6 +146,21 @@ guest.
=cut
+my $output_storage_domain;
+
+=item B<-osd domain>
+
+Specifies the NFS path to a RHEV Export storage domain. Note that the storage
+domain must have been previously initialised by RHEV.
+
+The domain must be in the format <host>:<path>, eg:
+
+ rhev-storage.example.com:/rhev/export
+
+The nfs export must be mountable and writable by the machine running virt-v2v.
+
+=cut
+
my $config_file;
=item B<-f file> | B<--config file>
@@ -182,8 +198,10 @@ GetOptions ("help|?" => sub {
},
"i=s" => \$input_method,
"ic=s" => \$input_uri,
+ "o=s" => \$output_method,
"oc=s" => \$output_uri,
"op=s" => \$output_pool,
+ "osd=s" => \$output_storage_domain,
"f|config=s" => \$config_file
) or pod2usage(2);
@@ -208,7 +226,18 @@ if(defined($config_file)) {
my $target;
if ($output_method eq "libvirt") {
$target = new Sys::VirtV2V::Target::LibVirt($output_uri, $output_pool);
-} else {
+}
+
+elsif ($output_method eq "rhev") {
+ pod2usage({ -message => __("You must specify an output storage
domain ".
+ "when using the rhev output method"),
+ -exitval => 1 })
+ unless (defined($output_storage_domain));
+
+ $target = new Sys::VirtV2V::Target::RHEV($output_storage_domain);
+}
+
+else {
die(user_message(__x("{output} is not a valid output method",
output => $output_method)));
}
@@ -272,9 +301,19 @@ my $storage = $conn->get_storage_paths();
# Create the transfer iso if required
my $transferiso = get_transfer_iso($config, $config_file);
+if ($output_method eq 'rhev') {
+ $) = "36 36";
+ $> = "36";
+}
+
# Open a libguestfs handle on the guest's storage devices
my $g = get_guestfs_handle($storage, $transferiso);
+if ($output_method eq 'rhev') {
+ $) = "0";
+ $> = "0";
+}
+
$SIG{'INT'} = \&close_guest_handle;
$SIG{'QUIT'} = \&close_guest_handle;
@@ -284,10 +323,12 @@ my $os = inspect_guest($g);
# Instantiate a GuestOS instance to manipulate the guest
my $guestos = Sys::VirtV2V::GuestOS->new($g, $os, $dom, $config);
-# Modify the guest and its metadata for the target hypervisor
+# Modify the guest and its metadata
my $guestcaps = Sys::VirtV2V::Converter->convert($guestos, $config, $dom,
$os,
$conn->get_storage_devices());
+close_guest_handle();
+
$target->create_guest($dom, $guestcaps);
my ($name) = $dom->findnodes('/domain/name/text()');
@@ -315,6 +356,11 @@ sub close_guest_handle
if (defined($g)) {
$g->umount_all();
$g->sync();
+
+ # Note that this undef is what actually causes the underlying handle to
+ # be closed. This is required to allow the RHEV target's temporary
mount
+ # directory to be unmounted and deleted prior to exit.
+ $g = undef;
}
}
--
1.6.6.1
Richard W.M. Jones
2010-Apr-01 13:20 UTC
[Libguestfs] [PATCH 1/2] Refactor guest and volume creation into Sys::VirtV2V::Target::LibVirt
On Tue, Mar 30, 2010 at 05:14:45PM +0100, Matthew Booth wrote:> Move all target-specific functionality into its own module in preparation for > output to RHEV.There seem to be a few other mixed bugfixes in this patch. Nevertheless, obviously adding a separate target for libvirt versus other targets like RHEV-M makes complete sense, so ACK. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top
On Tue, Mar 30, 2010 at 05:14:46PM +0100, Matthew Booth wrote:> Allow guests to be written to a RHEV NFS export storage domain. > > Add 'rhev' output method and -osd command-line option. > Example command line: > > virt-v2v -f virt-v2v.conf -ic 'esx://yellow.rhev.marston/' \ > -o rhev -osd blue:/export/export RHEL3-32 > > This will connect to an ESX server and write the guest 'RHEL3-32' to > blue:/export/export, which is a pre-initialised RHEV export storage domain.> + open($uuidgen, '<', '/proc/sys/kernel/random/uuid') > + or die("Unable to open /proc/sys/kernel/random/uuid: $!");This really exists? I'd be inclined to use the uuidgen command. It probably does the same thing, but I bet it's much more portable.> +package Sys::VirtV2V::Target::RHEV::NFSHelper;What's the purpose of this module? It forks and changes EUID/EGID, then runs a command, then execs /bin/true. I couldn't understand why it doesn't just run the command directly, with a wrapper around that to change EUID/EGID, ie. what is the reason for forking? Anyhow, the OVF may be hideous, but the general outline of this patch is fine, so 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
Seemingly Similar Threads
- [PATCH 1/2] Allow reading more data than the reported size of a volume
- [PATCH 1/4] Check that we're not overwriting an existing Libvirt domain
- [PATCH 1/3] Fix RHEV cleanup on unclean shutdown
- [PREVIEW ONLY] Refactor data transfer code
- [PATCH] Remove v2v-snapshot