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
Possibly Parallel 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