[Libguestfs] [PATCH 2/2] Add export to RHEV

Matthew Booth mbooth at redhat.com
Tue Mar 30 16:14:46 UTC 2010


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




More information about the Libguestfs mailing list