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_ResourceAllocationSett..." + xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingDa..." + 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