From f4a53bda3f3bf764cc4bde38b9774ee12b71b0b9 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Wed, 16 Sep 2015 18:19:36 +0100 Subject: [PATCH] import-to-ovirt script. --- README | 19 +- import-to-ovirt.pl | 845 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 862 insertions(+), 2 deletions(-) create mode 100755 import-to-ovirt.pl diff --git a/README b/README index b3c32b4..9b0a983 100644 --- a/README +++ b/README @@ -1,5 +1,20 @@ -This script lets you import any virtual machine disk image to RHEV -or oVirt. +This script lets you import a virtual machine disk image to RHEV or +oVirt. + +NB. This script *requires* a 64 bit host. Perl will cause silent +overflows on 32 bit hosts. + +Requirements: + + perl + * perl-Pod-Usage + * perl-Getopt-Long + * perl-File-Temp + * perl-POSIX + perl-XML-Writer + perl-Sys-Guestfs + +* = usually provided as part of base Perl package For instructions, read the script or do: diff --git a/import-to-ovirt.pl b/import-to-ovirt.pl new file mode 100755 index 0000000..cb6c1db --- /dev/null +++ b/import-to-ovirt.pl @@ -0,0 +1,845 @@ +#!/usr/bin/perl -w +# Copyright (C) 2015 Richard W.M. Jones +# Copyright (C) 2015 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use warnings; +use strict; +use English; + +use Pod::Usage; +use Getopt::Long; +use File::Temp qw(tempdir); +use POSIX qw(_exit strftime); +use XML::Writer; + +use Sys::Guestfs; + +=head1 NAME + +import-to-ovirt.pl - Import virtual machine disk image to RHEV or oVirt + +=head1 SYNOPSIS + + sudo ./import-to-ovirt.pl disk.img server:/esd + + sudo ./import-to-ovirt.pl disk.img /esd_mountpoint + +=head1 IMPORTANT NOTE + +This tool should B be used if the guest can already run on KVM. + +B instead.> + +=head1 EXAMPLES + +Import a KVM guest to the Export Storage Domain of your RHEV or oVirt +system. The NFS mount of the Export Storage Domain is C. + + sudo ./import-to-ovirt.pl disk.img server:/esd + +Import a KVM guest to an already-mounted Export Storage Domain: + + sudo ./import-to-ovirt.pl disk.img /esd_mountpoint + +If the single guest has multiple disks, use: + + sudo ./import-to-ovirt.pl disk1.img [disk2.img [...]] server:/esd + +If you are importing multiple guests then you must import each one +separately. Do not use multiple disk parameters if each disk comes +from a different guest. + +=head1 DESCRIPTION + +This is a command line script for importing a KVM virtual machine to +RHEV or oVirt. The script assumes that the guest can already run on +KVM, ie. that it was previously running on KVM and has the required +drivers. If the guest comes from a foreign hypervisor like VMware, +Xen or Hyper-V, use L instead. + +This script only imports the guest into the oVirt "Export Storage +Domain" (ESD). After the import is complete, you must then go to the +oVirt user interface, go to the C tab, select the right ESD, +and use C to take the guest from the ESD to the data +domain. This process is outside the scope of this script, but could +be automated using the oVirt API. + +=head2 Basic usage + +Basic usage is just: + + ./import-to-ovirt.pl [list of disks] server:/esd + +If you are unclear about the C parameter, go to the oVirt +Storage tab, select the C C and look in the +C tab under C. + +If the ESD is already mounted on your machine (or if you are using a +non-NFS ESD), then you can supply a direct path to the mountpoint +instead: + + ./import-to-ovirt.pl [list of disks] /esd_mountpoint + +The list of disks should all belong to a single guest (most guests +will only have a single disk). If you want to import multiple guests, +you must run the script multiple times. + +Importing from OVA etc is not supported. Try C +(if the OVA was exported from oVirt), or L (if the OVA +was exported from VMware). + +=head2 Permissions + +You probably need to run this script as root, because it has to create +files on the ESD as a special C user (UID:GID C<36:36>). + +It may also be possible to run the script as the vdsm user. But if +you run it as some non-root, non-vdsm user, then oVirt won't be able +to read the data from the ESD and will give an error. + +NFS "root squash" should be turned off on the NFS server, since it +stops us from creating files as the vdsm user. Also NFSv4 may not +work unless you have set up idmap correctly (good luck!) + +=head2 Network card and disk model + +(See also L below) + +Currently this script doesn't add a network card to the guest. You +will need to add one yourself in the C tab when importing +the guest. + +Similarly, the script always adds the disks as virtio-blk devices. If +the guest is expecting IDE, SCSI or virtio-scsi, you will need to +change the disk type when importing the guest. + +=head1 OPTIONS + +=over 4 + +=cut + +my $help; + +=item B<--help> + +Display brief help and exit. + +=cut + +my $man; + +=item B<--man> + +Display the manual page and exit. + +=cut + +my $memory_mb = 1024; + +=item B<--memory> MB + +Set the memory size I. The default is 1024. + +=cut + +my $name; + +=item B<--name> name + +Set the guest name. If not present, a name is made up based on +the filename of the first disk. + +=cut + +my $vcpus = 1; + +=item B<--vcpus> N + +Set the number of virtual CPUs. The default is 1. + +=cut + +my $vmtype = "Desktop"; + +=item B<--vmtype> Desktop + +=item B<--vmtype> Server + +Set the VmType field in the OVF. It must be C or +C. The default is C. + +=cut + +=back + +=cut + +$| = 1; + +GetOptions ("help|?" => \$help, + "man" => \$man, + "memory=i" => \$memory_mb, + "name=s" => \$name, + "vmtype=s" => \$vmtype, + ) + or die "$0: unknown command line option\n"; + +pod2usage (1) if $help; +pod2usage (-exitval => 0, -verbose => 2) if $man; + +# Get the parameters. +if (@ARGV < 2) { + die "Use '$0 --man' to display the manual.\n" +} + +my @disks = @ARGV[0 .. $#ARGV-1]; +my $output = $ARGV[$#ARGV]; + +if (!defined $name) { + $name = $disks[0]; + $name =~ s{.*/}{}; + $name =~ s{\.[^.]+}{}; +} + +# Open the guest in libguestfs so we can inspect it. +my $g = Sys::Guestfs->new (); +$g->add_drive_opts ($_, readonly => 1) foreach (@disks); +$g->launch (); +my @roots = $g->inspect_os (); +if (@roots == 0) { + die "$0: no operating system was found on the disk\n" +} +if (@roots > 1) { + die "$0: either this is a multi-OS disk, or you passed multiple unrelated guest disks on the command line\n" +} +my $root = $roots[0]; + +# Save the inspection data. +my $type = $g->inspect_get_type ($root); +my $distro = $g->inspect_get_distro ($root); +my $arch = $g->inspect_get_arch ($root); +my $major_version = $g->inspect_get_major_version ($root); +my $minor_version = $g->inspect_get_major_version ($root); +my $product_name = $g->inspect_get_product_name ($root); +my $product_variant = $g->inspect_get_product_variant ($root); + +# Get the virtual size of each disk. +my @virtual_sizes; +foreach (@disks) { + push @virtual_sizes, $g->disk_virtual_size ($_); +} + +$g->close (); + +# Map inspection data to RHEV ostype. +my $ostype; +if ($type eq "linux" && $distro eq "rhel" && $major_version <= 6) { + if ($arch eq "x86_64") { + $ostype = "RHEL${major_version}x64" + } else { + $ostype = "RHEL$major_version" + } +} +elsif ($type eq "linux" && $distro eq "rhel") { + if ($arch eq "x86_64") { + $ostype = "rhel_${major_version}x64" + } else { + $ostype = "rhel_$major_version" + } +} +elsif ($type eq "linux") { + $ostype = "OtherLinux" +} +elsif ($type eq "windows" && $major_version == 5 && $minor_version == 1) { + $ostype = "WindowsXP" +} +elsif ($type eq "windows" && $major_version == 5 && $minor_version == 2) { + if ($product_name =~ /XP/) { + $ostype = "WindowsXP" + } elsif ($arch eq "x86_64") { + $ostype = "Windows2003x64" + } else { + $ostype = "Windows2003" + } +} +elsif ($type eq "windows" && $major_version == 6 && $minor_version == 0) { + if ($arch eq "x86_64") { + $ostype = "Windows2008x64" + } else { + $ostype = "Windows2008" + } +} +elsif ($type eq "windows" && $major_version == 6 && $minor_version == 1) { + if ($product_variant eq "Client") { + if ($arch eq "x86_64") { + $ostype = "Windows7x64" + } else { + $ostype = "Windows7" + } + } else { + $ostype = "Windows2008R2x64" + } +} +elsif ($type eq "windows" && $major_version == 6 && $minor_version == 2) { + if ($product_variant eq "Client") { + if ($arch eq "x86_64") { + $ostype = "windows_8x64" + } else { + $ostype = "windows_8" + } + } else { + $ostype = "windows_2012x64" + } +} +elsif ($type eq "windows" && $major_version == 6 && $minor_version == 3) { + $ostype = "windows_2012R2x64" +} +else { + $ostype = "Unassigned" +} + +# Mount the ESD if needed (or just check it exists). +my $mountpoint; +if (-d $output) { + $mountpoint = $output; +} elsif ($output =~ m{^.*:.*$}) { + my $umount; + $umount = $mountpoint = tempdir (CLEANUP => 1); + system ("mount", "-t", "nfs", $output, $mountpoint) == 0 + or die "$0: mount $output failed: $?\n"; + END { system ("umount", $umount) if defined $umount } +} else { + die "$0: ESD $output is not a directory or an NFS mountpoint\n" +} + +# Check the ESD looks like an ESD. +my @entries = <$mountpoint/*>; +@entries = + grep { m{/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$}i } + @entries; +if (@entries == 0) { + die "$0: does $output really point to an oVirt Export Storage Domain?\n" +} +if (@entries > 1) { + die "$0: multiple GUIDs found in oVirt Export Storage Domain\n" +} +my $esd_uuid_dir = $entries[0]; +my $esd_uuid = $esd_uuid_dir; +$esd_uuid =~ s{.*/}{}; + +if (! -d $esd_uuid_dir || + ! -d "$esd_uuid_dir/images" || + ! -d "$esd_uuid_dir/master" || + ! -d "$esd_uuid_dir/master/vms") { + die "$0: $output doesn't look like an Export Storage Domain\n" +} + +# Start the import. +print "Importing $product_name to $output...\n"; + +# A helper function that forks and runs some code / a command as +# an alternate UID:GID. +sub run_as_vdsm +{ + my $fn = shift; + + my $pid = fork (); + die "fork: $!" unless defined $pid; + if ($pid == 0) { + # Child process. + if ($EUID == 0) { + $GID = 36; + $UID = 36; + } + eval { &$fn () }; + if ($@) { + print STDERR $@, "\n"; + _exit (1); + } + _exit (0); + } + waitpid ($pid, 0) or die "waitpid: $!"; + if ($? != 0) { + die "$0: run_as_vdsm: child process failed (status $?)\n"; + } +} + +# Generate a UUID. +sub uuidgen +{ + local $_ = `uuidgen -r`; + chomp; + die unless length $_ >= 30; # Sanity check. + $_; +} + +# Generate some random UUIDs. +my $vm_uuid = uuidgen (); +my @image_uuids; +foreach (@disks) { + push @image_uuids, uuidgen (); +} +my @vol_uuids; +foreach (@disks) { + push @vol_uuids, uuidgen (); +} + +# Make sure the output is deleted on unsuccessful exit. We set +# $delete_output_on_exit to false at the end of the script. +my $delete_output_on_exit = 1; +END { + if ($delete_output_on_exit) { + # Can't use run_as_vdsm in an END{} block. + foreach (@image_uuids) { + system ("rm", "-rf", "$esd_uuid_dir/images/$_"); + } + system ("rm", "-rf", "$esd_uuid_dir/master/vms/$vm_uuid"); + } +}; + +# Copy and convert the disk images. +my $i; +my $time = time (); +my $iso_time = strftime ("%Y/%m/%d %H:%M:%S", gmtime ()); +my $imported_by = "Imported by import-to-ovirt.pl"; +my @real_sizes; + +for ($i = 0; $i < @disks; ++$i) { + my $input_file = $disks[$i]; + my $image_uuid = $image_uuids[$i]; + run_as_vdsm (sub { + my $path = "$esd_uuid_dir/images/$image_uuid"; + mkdir ($path, 0755) or die "mkdir: $path: $!"; + }); + my $output_file = "$esd_uuid_dir/images/$image_uuid/".$vol_uuids[$i]; + run_as_vdsm (sub { + open (my $fh, ">", $output_file) or die "open: $output_file: $!"; + # Well done NFS root_squash, you make the world less secure. + chmod (0666, $output_file) or die "chmod: $output_file: $!"; + }); + print "Copying $input_file ...\n"; +# system ("qemu-img", "convert", "-p", +# $input_file, +# "-O", "qcow2", +# "-o", "compat=0.10", # for RHEL 6-based ovirt nodes +# $output_file) == 0 +# or die "qemu-img: $input_file: failed (status $?)"; +# push @real_sizes, -s $output_file; + push @real_sizes, 0; + + my $size_in_sectors = $virtual_sizes[$i] / 512; + + # Create .meta files per disk. + my $meta = <<"EOF"; +DOMAIN=$esd_uuid +VOLTYPE=LEAF +CTIME=$time +MTIME=$time +IMAGE=$image_uuid +DISKTYPE=1 +PUUID=00000000-0000-0000-0000-000000000000 +LEGALITY=LEGAL +POOL_UUID= +SIZE=$size_in_sectors +FORMAT=COW +TYPE=SPARSE +DESCRIPTION=$imported_by +EOF + my $meta_file = $output_file . ".meta"; + run_as_vdsm (sub { + open (my $fh, ">", $meta_file) or die "open: $meta_file: $!"; + print $fh $meta + }); +} + +# Create the OVF. +print "Creating OVF metadata ...\n"; + +my $rasd_ns = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"; +my $vssd_ns = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"; +my $xsi_ns = "http://www.w3.org/2001/XMLSchema-instance"; +my $ovf_ns = "http://schemas.dmtf.org/ovf/envelope/1/"; +my %prefix_map = ( + $rasd_ns => "rasd", + $vssd_ns => "vssd", + $xsi_ns => "xsi", + $ovf_ns => "ovf", +); +my @forced_ns_decls = keys %prefix_map; + +my $w = XML::Writer->new ( + OUTPUT => "self", + NAMESPACES => 1, + PREFIX_MAP => \%prefix_map, + FORCED_NS_DECLS => \@forced_ns_decls, + DATA_MODE => 1, + DATA_INDENT => 4, +); + +$w->startTag ([$ovf_ns, "Envelope"], + [$ovf_ns, "version"] => "0.9"); +$w->comment ($imported_by); + +$w->startTag ("References"); + +for ($i = 0; $i < @disks; ++$i) +{ + my $href = $image_uuids[$i] . "/" . $vol_uuids[$i]; + $w->startTag ("File", + [$ovf_ns, "href"] => $href, + [$ovf_ns, "id"] => $vol_uuids[$i], + [$ovf_ns, "size"] => $virtual_sizes[$i], + [$ovf_ns, "description"] => $imported_by); + $w->endTag (); +} + +$w->endTag (); + +$w->startTag ("Section", + [$xsi_ns, "type"] => "ovf:NetworkSection_Type"); +$w->startTag ("Info"); +$w->characters ("List of networks"); +$w->endTag (); +$w->endTag (); + +$w->startTag ("Section", + [$xsi_ns, "type"] => "ovf:DiskSection_Type"); +$w->startTag ("Info"); +$w->characters ("List of Virtual Disks"); +$w->endTag (); + +for ($i = 0; $i < @disks; ++$i) +{ + my $virtual_size_in_gb = $virtual_sizes[$i]; + $virtual_size_in_gb /= 1024; + $virtual_size_in_gb /= 1024; + $virtual_size_in_gb /= 1024; + my $real_size_in_gb = $real_sizes[$i]; + $real_size_in_gb /= 1024; + $real_size_in_gb /= 1024; + $real_size_in_gb /= 1024; + my $href = $image_uuids[$i] . "/" . $vol_uuids[$i]; + + my $boot_drive; + if ($i == 0) { + $boot_drive = "True"; + } else { + $boot_drive = "False"; + } + + $w->startTag ("Disk", + [$ovf_ns, "diskId" ] => $vol_uuids[$i], + [$ovf_ns, "actual_size"] => $real_size_in_gb, + [$ovf_ns, "size"] => $virtual_size_in_gb, + [$ovf_ns, "fileRef"] => $href, + [$ovf_ns, "parentRef"] => "", + [$ovf_ns, "vm_snapshot_id"] => uuidgen (), + [$ovf_ns, "volume-format"] => "COW", + [$ovf_ns, "volume-type"] => "Sparse", + [$ovf_ns, "format"] => "http://en.wikipedia.org/wiki/Byte", + [$ovf_ns, "disk-interface"] => "VirtIO", + [$ovf_ns, "disk-type"] => "System", + [$ovf_ns, "boot"] => $boot_drive); + $w->endTag (); +} + +$w->endTag (); + +$w->startTag ("Content", + [$ovf_ns, "id"] => "out", + [$xsi_ns, "type"] => "ovf:VirtualSystem_Type"); +$w->startTag ("Name"); +$w->characters ($name); +$w->endTag (); +$w->startTag ("TemplateId"); +$w->characters ("00000000-0000-0000-0000-000000000000"); +$w->endTag (); +$w->startTag ("TemplateName"); +$w->characters ("Blank"); +$w->endTag (); +$w->startTag ("Description"); +$w->characters ($imported_by); +$w->endTag (); +$w->startTag ("Domain"); +$w->endTag (); +$w->startTag ("CreationDate"); +$w->characters ($iso_time); +$w->endTag (); +$w->startTag ("IsInitilized"); # sic +$w->characters ("True"); +$w->endTag (); +$w->startTag ("IsAutoSuspend"); +$w->characters ("False"); +$w->endTag (); +$w->startTag ("TimeZone"); +$w->endTag (); +$w->startTag ("IsStateless"); +$w->characters ("False"); +$w->endTag (); +$w->startTag ("Origin"); +$w->characters ("0"); +$w->endTag (); +$w->startTag ("VmType"); +$w->characters ($vmtype); +$w->endTag (); +$w->startTag ("DefaultDisplayType"); +$w->characters ("1"); # qxl +$w->endTag (); + +$w->startTag ("Section", + [$ovf_ns, "id"] => $vm_uuid, + [$ovf_ns, "required"] => "false", + [$xsi_ns, "type"] => "ovf:OperatingSystemSection_Type"); +$w->startTag ("Info"); +$w->characters ($product_name); +$w->endTag (); +$w->startTag ("Description"); +$w->characters ($ostype); +$w->endTag (); +$w->endTag (); + +$w->startTag ("Section", + [$xsi_ns, "type"] => "ovf:VirtualHardwareSection_Type"); +$w->startTag ("Info"); +$w->characters (sprintf ("%d CPU, %d Memory", $vcpus, $memory_mb)); +$w->endTag (); + +$w->startTag ("Item"); +$w->startTag ([$rasd_ns, "Caption"]); +$w->characters (sprintf ("%d virtual cpu", $vcpus)); +$w->endTag (); +$w->startTag ([$rasd_ns, "Description"]); +$w->characters ("Number of virtual CPU"); +$w->endTag (); +$w->startTag ([$rasd_ns, "InstanceId"]); +$w->characters ("1"); +$w->endTag (); +$w->startTag ([$rasd_ns, "ResourceType"]); +$w->characters ("3"); +$w->endTag (); +$w->startTag ([$rasd_ns, "num_of_sockets"]); +$w->characters ($vcpus); +$w->endTag (); +$w->startTag ([$rasd_ns, "cpu_per_socket"]); +$w->characters (1); +$w->endTag (); +$w->endTag ("Item"); + +$w->startTag ("Item"); +$w->startTag ([$rasd_ns, "Caption"]); +$w->characters (sprintf ("%d MB of memory", $memory_mb)); +$w->endTag (); +$w->startTag ([$rasd_ns, "Description"]); +$w->characters ("Memory Size"); +$w->endTag (); +$w->startTag ([$rasd_ns, "InstanceId"]); +$w->characters ("2"); +$w->endTag (); +$w->startTag ([$rasd_ns, "ResourceType"]); +$w->characters ("4"); +$w->endTag (); +$w->startTag ([$rasd_ns, "AllocationUnits"]); +$w->characters ("MegaBytes"); +$w->endTag (); +$w->startTag ([$rasd_ns, "VirtualQuantity"]); +$w->characters ($memory_mb); +$w->endTag (); +$w->endTag ("Item"); + +$w->startTag ("Item"); +$w->startTag ([$rasd_ns, "Caption"]); +$w->characters ("USB Controller"); +$w->endTag (); +$w->startTag ([$rasd_ns, "InstanceId"]); +$w->characters ("3"); +$w->endTag (); +$w->startTag ([$rasd_ns, "ResourceType"]); +$w->characters ("23"); +$w->endTag (); +$w->startTag ([$rasd_ns, "UsbPolicy"]); +$w->characters ("Disabled"); +$w->endTag (); +$w->endTag ("Item"); + +$w->startTag ("Item"); +$w->startTag ([$rasd_ns, "Caption"]); +$w->characters ("Graphical Controller"); +$w->endTag (); +$w->startTag ([$rasd_ns, "InstanceId"]); +$w->characters (uuidgen ()); +$w->endTag (); +$w->startTag ([$rasd_ns, "ResourceType"]); +$w->characters ("20"); +$w->endTag (); +$w->startTag ("Type"); +$w->characters ("video"); +$w->endTag (); +$w->startTag ([$rasd_ns, "VirtualQuantity"]); +$w->characters ("1"); +$w->endTag (); +$w->startTag ([$rasd_ns, "Device"]); +$w->characters ("qxl"); +$w->endTag (); +$w->endTag ("Item"); + +for ($i = 0; $i < @disks; ++$i) +{ + my $href = $image_uuids[$i] . "/" . $vol_uuids[$i]; + + $w->startTag ("Item"); + + $w->startTag ([$rasd_ns, "Caption"]); + $w->characters ("Drive " . ($i+1)); + $w->endTag (); + $w->startTag ([$rasd_ns, "InstanceId"]); + $w->characters ($vol_uuids[$i]); + $w->endTag (); + $w->startTag ([$rasd_ns, "ResourceType"]); + $w->characters ("17"); + $w->endTag (); + $w->startTag ("Type"); + $w->characters ("disk"); + $w->endTag (); + $w->startTag ([$rasd_ns, "HostResource"]); + $w->characters ($href); + $w->endTag (); + $w->startTag ([$rasd_ns, "Parent"]); + $w->characters ("00000000-0000-0000-0000-000000000000"); + $w->endTag (); + $w->startTag ([$rasd_ns, "Template"]); + $w->characters ("00000000-0000-0000-0000-000000000000"); + $w->endTag (); + $w->startTag ([$rasd_ns, "ApplicationList"]); + $w->endTag (); + $w->startTag ([$rasd_ns, "StorageId"]); + $w->characters ($esd_uuid); + $w->endTag (); + $w->startTag ([$rasd_ns, "StoragePoolId"]); + $w->characters ("00000000-0000-0000-0000-000000000000"); + $w->endTag (); + $w->startTag ([$rasd_ns, "CreationDate"]); + $w->characters ($iso_time); + $w->endTag (); + $w->startTag ([$rasd_ns, "LastModified"]); + $w->characters ($iso_time); + $w->endTag (); + $w->startTag ([$rasd_ns, "last_modified_date"]); + $w->characters ($iso_time); + $w->endTag (); + + $w->endTag ("Item"); +} + +$w->endTag ("Section"); # ovf:VirtualHardwareSection_Type + +$w->endTag ("Content"); + +$w->endTag ([$ovf_ns, "Envelope"]); +$w->end (); + +my $ovf = $w->to_string; + +#print "OVF:\n$ovf\n"; + +my $ovf_dir = "$esd_uuid_dir/master/vms/$vm_uuid"; +run_as_vdsm (sub { + mkdir ($ovf_dir, 0755) or die "mkdir: $ovf_dir: $!"; +}); +my $ovf_file = "$ovf_dir/$vm_uuid.ovf"; +run_as_vdsm (sub { + open (my $fh, ">", $ovf_file) or die "open: $ovf_file: $!"; + print $fh $ovf +}); + +# Finished. +#$delete_output_on_exit = 0; +print "\n"; +print "OVF written to $ovf_file\n"; +print "\n"; +print "Import finished without errors. Now go to the Storage tab ->\n"; +print "Export Storage Domain -> VM Import, and import the guest.\n"; +exit 0; + +__END__ + +=head1 TO DO + +=over 4 + +=item Network + +Add a network card to the OVF. The problem is detecting what +network devices the guest can support. + +=item Disk model + +Detect what disk models (eg. IDE, virtio-blk, virtio-scsi) the +guest can support and add the correct type of disk. + +=back + +=head1 DEBUGGING IMPORT FAILURES + +When you export to the ESD, and then import that guest through the +oVirt / RHEV-M UI, you may encounter an import failure. Diagnosing +these failures is infuriatingly difficult as the UI generally hides +the true reason for the failure. + +There are two log files of interest. The first is stored on the oVirt +engine / RHEV-M server itself, and is called +F + +The second file, which is the most useful, is found on the SPM host +(SPM stands for "Storage Pool Manager"). This is a oVirt node that is +elected to do all metadata modifications in the data center, such as +image or snapshot creation. You can find out which host is the +current SPM from the "Hosts" tab "Spm Status" column. Once you have +located the SPM, log into it and grab the file +F which will contain detailed error messages +from low-level commands. + +=head1 SEE ALSO + +L, +L. + +=head1 AUTHOR + +Richard W.M. Jones + +=head1 COPYRIGHT + +Copyright (C) 2015 Richard W.M. Jones + +Copyright (C) 2015 Red Hat Inc. + +=head1 LICENSE + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2 of the License, or (at your +option) any later version. + +This program 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -- 1.8.3.1