Fix for libguestfs-winsupport on RHEL 7.2.
[import-to-ovirt.git] / import-to-ovirt.pl
1 #!/usr/bin/perl -w
2 # Copyright (C) 2015 Richard W.M. Jones <rjones@redhat.com>
3 # Copyright (C) 2015 Red Hat Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19 use warnings;
20 use strict;
21 use English;
22
23 use Pod::Usage;
24 use Getopt::Long;
25 use File::Temp qw(tempdir);
26 use POSIX qw(_exit setgid setuid strftime);
27 use XML::Writer;
28
29 use Sys::Guestfs;
30
31 =head1 NAME
32
33 import-to-ovirt.pl - Import virtual machine disk image to RHEV or oVirt
34
35 =head1 SYNOPSIS
36
37  sudo ./import-to-ovirt.pl disk.img server:/esd
38
39  sudo ./import-to-ovirt.pl disk.img /esd_mountpoint
40
41 =head1 IMPORTANT NOTE
42
43 This tool should B<only> be used if the guest can already run on KVM.
44
45 B<If you need to convert the guest from some foreign hypervisor, like VMware, Xen or Hyper-V, you should use L<virt-v2v(1)> instead.>
46
47 =head1 EXAMPLES
48
49 Import a KVM guest to the Export Storage Domain of your RHEV or oVirt
50 system.  The NFS mount of the Export Storage Domain is C<server:/esd>.
51
52  sudo ./import-to-ovirt.pl disk.img server:/esd
53
54 Import a KVM guest to an already-mounted Export Storage Domain:
55
56  sudo ./import-to-ovirt.pl disk.img /esd_mountpoint
57
58 If the single guest has multiple disks, use:
59
60  sudo ./import-to-ovirt.pl disk1.img [disk2.img [...]] server:/esd
61
62 If you are importing multiple guests then you must import each one
63 separately.  Do not use multiple disk parameters if each disk comes
64 from a different guest.
65
66 =head1 DESCRIPTION
67
68 This is a command line script for importing a KVM virtual machine to
69 RHEV or oVirt.  The script assumes that the guest can already run on
70 KVM, ie. that it was previously running on KVM and has the required
71 drivers.  If the guest comes from a foreign hypervisor like VMware,
72 Xen or Hyper-V, use L<virt-v2v(1)> instead.
73
74 This script only imports the guest into the oVirt "Export Storage
75 Domain" (ESD).  After the import is complete, you must then go to the
76 oVirt user interface, go to the C<Storage> tab, select the right ESD,
77 and use C<VM Import> to take the guest from the ESD to the data
78 domain.  This process is outside the scope of this script, but could
79 be automated using the oVirt API.
80
81 =head2 Basic usage
82
83 Basic usage is just:
84
85  ./import-to-ovirt.pl [list of disks] server:/esd
86
87 If you are unclear about the C<server:/esd> parameter, go to the oVirt
88 Storage tab, select the C<Domain Type> C<Export> and look in the
89 C<General> tab under C<Path>.
90
91 If the ESD is already mounted on your machine (or if you are using a
92 non-NFS ESD), then you can supply a direct path to the mountpoint
93 instead:
94
95  ./import-to-ovirt.pl [list of disks] /esd_mountpoint
96
97 The list of disks should all belong to a single guest (most guests
98 will only have a single disk).  If you want to import multiple guests,
99 you must run the script multiple times.
100
101 Importing from OVA etc is not supported.  Try C<ovirt-image-uploader>
102 (if the OVA was exported from oVirt), or L<virt-v2v(1)> (if the OVA
103 was exported from VMware).
104
105 =head2 Permissions
106
107 You probably need to run this script as root, because it has to create
108 files on the ESD as a special C<vdsm> user (UID:GID C<36:36>).
109
110 It may also be possible to run the script as the vdsm user.  But if
111 you run it as some non-root, non-vdsm user, then oVirt won't be able
112 to read the data from the ESD and will give an error.
113
114 NFS "root squash" should be turned off on the NFS server, since it
115 stops us from creating files as the vdsm user.  Also NFSv4 may not
116 work unless you have set up idmap correctly (good luck!)
117
118 =head2 Network card and disk model
119
120 (See also L</TO DO> below)
121
122 Currently this script doesn't add a network card to the guest.  You
123 will need to add one yourself in the C<VM Import> tab when importing
124 the guest.
125
126 Similarly, the script always adds the disks as virtio-blk devices.  If
127 the guest is expecting IDE, SCSI or virtio-scsi, you will need to
128 change the disk type when importing the guest.
129
130 =head1 OPTIONS
131
132 =over 4
133
134 =cut
135
136 my $help;
137
138 =item B<--help>
139
140 Display brief help and exit.
141
142 =cut
143
144 my $man;
145
146 =item B<--man>
147
148 Display the manual page and exit.
149
150 =cut
151
152 my $memory_mb = 1024;
153
154 =item B<--memory> MB
155
156 Set the memory size I<in megabytes>.  The default is 1024.
157
158 =cut
159
160 my $name;
161
162 =item B<--name> name
163
164 Set the guest name.  If not present, a name is made up based on
165 the filename of the first disk.
166
167 =cut
168
169 my $vcpus = 1;
170
171 =item B<--vcpus> N
172
173 Set the number of virtual CPUs.  The default is 1.
174
175 =cut
176
177 my $vmtype = "Desktop";
178
179 =item B<--vmtype> Desktop
180
181 =item B<--vmtype> Server
182
183 Set the VmType field in the OVF.  It must be C<Desktop> or
184 C<Server>.  The default is C<Desktop>.
185
186 =cut
187
188 =back
189
190 =cut
191
192 $| = 1;
193
194 GetOptions ("help|?" => \$help,
195             "man" => \$man,
196             "memory=i" => \$memory_mb,
197             "name=s" => \$name,
198             "vmtype=s" => \$vmtype,
199     )
200     or die "$0: unknown command line option\n";
201
202 pod2usage (1) if $help;
203 pod2usage (-exitval => 0, -verbose => 2) if $man;
204
205 # Get the parameters.
206 if (@ARGV < 2) {
207     die "Use '$0 --man' to display the manual.\n"
208 }
209
210 my @disks = @ARGV[0 .. $#ARGV-1];
211 my $output = $ARGV[$#ARGV];
212
213 if (!defined $name) {
214     $name = $disks[0];
215     $name =~ s{.*/}{};
216     $name =~ s{\.[^.]+}{};
217 }
218
219 if ($vmtype =~ /^Desktop$/i) {
220     $vmtype = 0;
221 } elsif ($vmtype =~ /^Server$/i) {
222     $vmtype = 1;
223 } else {
224     die "$0: --vmtype parameter must be 'Desktop' or 'Server'\n"
225 }
226
227 # Open the guest in libguestfs so we can inspect it.
228 my $g = Sys::Guestfs->new ();
229 $g->set_program ("virt-import-to-ovirt");
230 $g->add_drive_opts ($_, readonly => 1) foreach (@disks);
231 $g->launch ();
232 my @roots = $g->inspect_os ();
233 if (@roots == 0) {
234     die "$0: no operating system was found on the disk\n"
235 }
236 if (@roots > 1) {
237     die "$0: either this is a multi-OS disk, or you passed multiple unrelated guest disks on the command line\n"
238 }
239 my $root = $roots[0];
240
241 # Save the inspection data.
242 my $type = $g->inspect_get_type ($root);
243 my $distro = $g->inspect_get_distro ($root);
244 my $arch = $g->inspect_get_arch ($root);
245 my $major_version = $g->inspect_get_major_version ($root);
246 my $minor_version = $g->inspect_get_major_version ($root);
247 my $product_name = $g->inspect_get_product_name ($root);
248 my $product_variant = $g->inspect_get_product_variant ($root);
249
250 # Get the virtual size of each disk.
251 my @virtual_sizes;
252 foreach (@disks) {
253     push @virtual_sizes, $g->disk_virtual_size ($_);
254 }
255
256 $g->close ();
257
258 # Map inspection data to RHEV ostype.
259 my $ostype;
260 if ($type eq "linux" && $distro eq "rhel" && $major_version <= 6) {
261     if ($arch eq "x86_64") {
262         $ostype = "RHEL${major_version}x64"
263     } else {
264         $ostype = "RHEL$major_version"
265     }
266 }
267 elsif ($type eq "linux" && $distro eq "rhel") {
268     if ($arch eq "x86_64") {
269         $ostype = "rhel_${major_version}x64"
270     } else {
271         $ostype = "rhel_$major_version"
272     }
273 }
274 elsif ($type eq "linux") {
275     $ostype = "OtherLinux"
276 }
277 elsif ($type eq "windows" && $major_version == 5 && $minor_version == 1) {
278     $ostype = "WindowsXP"
279 }
280 elsif ($type eq "windows" && $major_version == 5 && $minor_version == 2) {
281     if ($product_name =~ /XP/) {
282         $ostype = "WindowsXP"
283     } elsif ($arch eq "x86_64") {
284         $ostype = "Windows2003x64"
285     } else {
286         $ostype = "Windows2003"
287     }
288 }
289 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 0) {
290     if ($arch eq "x86_64") {
291         $ostype = "Windows2008x64"
292     } else {
293         $ostype = "Windows2008"
294     }
295 }
296 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 1) {
297     if ($product_variant eq "Client") {
298         if ($arch eq "x86_64") {
299             $ostype = "Windows7x64"
300         } else {
301             $ostype = "Windows7"
302         }
303     } else {
304         $ostype = "Windows2008R2x64"
305     }
306 }
307 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 2) {
308    if ($product_variant eq "Client") {
309        if ($arch eq "x86_64") {
310            $ostype = "windows_8x64"
311        } else {
312            $ostype = "windows_8"
313        }
314    } else {
315        $ostype = "windows_2012x64"
316    }
317 }
318 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 3) {
319     $ostype = "windows_2012R2x64"
320 }
321 else {
322     $ostype = "Unassigned"
323 }
324
325 # Mount the ESD if needed (or just check it exists).
326 my $mountpoint;
327 if (-d $output) {
328     $mountpoint = $output;
329 } elsif ($output =~ m{^.*:.*$}) {
330     my $umount;
331     $umount = $mountpoint = tempdir (CLEANUP => 1);
332     system ("mount", "-t", "nfs", $output, $mountpoint) == 0
333         or die "$0: mount $output failed: $?\n";
334     END { system ("umount", $umount) if defined $umount }
335 } else {
336     die "$0: ESD $output is not a directory or an NFS mountpoint\n"
337 }
338
339 # Check the ESD looks like an ESD.
340 my @entries = <$mountpoint/*>;
341 @entries =
342     grep { m{/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$}i }
343     @entries;
344 if (@entries == 0) {
345     die "$0: does $output really point to an oVirt Export Storage Domain?\n"
346 }
347 if (@entries > 1) {
348     die "$0: multiple GUIDs found in oVirt Export Storage Domain\n"
349 }
350 my $esd_uuid_dir = $entries[0];
351 my $esd_uuid = $esd_uuid_dir;
352 $esd_uuid =~ s{.*/}{};
353
354 if (! -d $esd_uuid_dir ||
355     ! -d "$esd_uuid_dir/images" ||
356     ! -d "$esd_uuid_dir/master" ||
357     ! -d "$esd_uuid_dir/master/vms") {
358     die "$0: $output doesn't look like an Export Storage Domain\n"
359 }
360
361 # Start the import.
362 print "Importing $product_name to $output...\n";
363
364 # A helper function that forks and runs some code / a command as
365 # an alternate UID:GID.
366 sub run_as_vdsm
367 {
368     my $fn = shift;
369
370     my $pid = fork ();
371     die "fork: $!" unless defined $pid;
372     if ($pid == 0) {
373         # Child process.
374         if ($EUID == 0) {
375             setgid (36);
376             setuid (36);
377         }
378         eval { &$fn () };
379         if ($@) {
380             print STDERR "$@\n";
381             _exit (1);
382         }
383         _exit (0);
384     }
385     waitpid ($pid, 0) or die "waitpid: $!";
386     if ($? != 0) {
387         die "$0: run_as_vdsm: child process failed (status $?)\n";
388     }
389 }
390
391 # Generate a UUID.
392 sub uuidgen
393 {
394     local $_ = `uuidgen -r`;
395     chomp;
396     die unless length $_ >= 30; # Sanity check.
397     $_;
398 }
399
400 # Generate some random UUIDs.
401 my $vm_uuid = uuidgen ();
402 my @image_uuids;
403 foreach (@disks) {
404     push @image_uuids, uuidgen ();
405 }
406 my @vol_uuids;
407 foreach (@disks) {
408     push @vol_uuids, uuidgen ();
409 }
410
411 # Make sure the output is deleted on unsuccessful exit.  We set
412 # $delete_output_on_exit to false at the end of the script.
413 my $delete_output_on_exit = 1;
414 END {
415     if ($delete_output_on_exit) {
416         # Can't use run_as_vdsm in an END{} block.
417         foreach (@image_uuids) {
418             system ("rm", "-rf", "$esd_uuid_dir/images/$_");
419         }
420         system ("rm", "-rf", "$esd_uuid_dir/master/vms/$vm_uuid");
421     }
422 };
423
424 # Copy and convert the disk images.
425 my $i;
426 my $time = time ();
427 my $iso_time = strftime ("%Y/%m/%d %H:%M:%S", gmtime ());
428 my $imported_by = "Imported by import-to-ovirt.pl";
429 my @real_sizes;
430
431 for ($i = 0; $i < @disks; ++$i) {
432     my $input_file = $disks[$i];
433     my $image_uuid = $image_uuids[$i];
434     run_as_vdsm (sub {
435         my $path = "$esd_uuid_dir/images/$image_uuid";
436         mkdir ($path, 0755) or die "mkdir: $path: $!";
437     });
438     my $output_file = "$esd_uuid_dir/images/$image_uuid/".$vol_uuids[$i];
439     run_as_vdsm (sub {
440         open (my $fh, ">", $output_file) or die "open: $output_file: $!";
441         # Well done NFS root_squash, you make the world less secure.
442         chmod (0666, $output_file) or die "chmod: $output_file: $!";
443     });
444     print "Copying $input_file ...\n";
445     system ("qemu-img", "convert", "-p",
446             $input_file,
447             "-O", "qcow2",
448             "-o", "compat=0.10", # for RHEL 6-based ovirt nodes
449             $output_file) == 0
450                 or die "qemu-img: $input_file: failed (status $?)";
451     push @real_sizes, -s $output_file;
452
453     my $size_in_sectors = $virtual_sizes[$i] / 512;
454
455     # Create .meta files per disk.
456     my $meta = <<"EOF";
457 DOMAIN=$esd_uuid
458 VOLTYPE=LEAF
459 CTIME=$time
460 MTIME=$time
461 IMAGE=$image_uuid
462 DISKTYPE=1
463 PUUID=00000000-0000-0000-0000-000000000000
464 LEGALITY=LEGAL
465 POOL_UUID=
466 SIZE=$size_in_sectors
467 FORMAT=COW
468 TYPE=SPARSE
469 DESCRIPTION=$imported_by
470 EOF
471     my $meta_file = $output_file . ".meta";
472     run_as_vdsm (sub {
473         open (my $fh, ">", $meta_file) or die "open: $meta_file: $!";
474         print $fh $meta
475     });
476 }
477
478 # Create the OVF.
479 print "Creating OVF metadata ...\n";
480
481 my $rasd_ns = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData";
482 my $vssd_ns = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData";
483 my $xsi_ns = "http://www.w3.org/2001/XMLSchema-instance";
484 my $ovf_ns = "http://schemas.dmtf.org/ovf/envelope/1/";
485 my %prefix_map = (
486     $rasd_ns => "rasd",
487     $vssd_ns => "vssd",
488     $xsi_ns => "xsi",
489     $ovf_ns => "ovf",
490 );
491 my @forced_ns_decls = keys %prefix_map;
492
493 my $w = XML::Writer->new (
494     OUTPUT => "self",
495     NAMESPACES => 1,
496     PREFIX_MAP => \%prefix_map,
497     FORCED_NS_DECLS => \@forced_ns_decls,
498     DATA_MODE => 1,
499     DATA_INDENT => 4,
500 );
501
502 $w->startTag ([$ovf_ns, "Envelope"],
503               [$ovf_ns, "version"] => "0.9");
504 $w->comment ($imported_by);
505
506 $w->startTag ("References");
507
508 for ($i = 0; $i < @disks; ++$i)
509 {
510     my $href = $image_uuids[$i] . "/" . $vol_uuids[$i];
511     $w->startTag ("File",
512                   [$ovf_ns, "href"] => $href,
513                   [$ovf_ns, "id"] => $vol_uuids[$i],
514                   [$ovf_ns, "size"] => $virtual_sizes[$i],
515                   [$ovf_ns, "description"] => $imported_by);
516     $w->endTag ();
517 }
518
519 $w->endTag ();
520
521 $w->startTag ("Section",
522               [$xsi_ns, "type"] => "ovf:NetworkSection_Type");
523 $w->startTag ("Info");
524 $w->characters ("List of networks");
525 $w->endTag ();
526 $w->endTag ();
527
528 $w->startTag ("Section",
529               [$xsi_ns, "type"] => "ovf:DiskSection_Type");
530 $w->startTag ("Info");
531 $w->characters ("List of Virtual Disks");
532 $w->endTag ();
533
534 for ($i = 0; $i < @disks; ++$i)
535 {
536     my $virtual_size_in_gb = $virtual_sizes[$i];
537     $virtual_size_in_gb /= 1024;
538     $virtual_size_in_gb /= 1024;
539     $virtual_size_in_gb /= 1024;
540     my $real_size_in_gb = $real_sizes[$i];
541     $real_size_in_gb /= 1024;
542     $real_size_in_gb /= 1024;
543     $real_size_in_gb /= 1024;
544     my $href = $image_uuids[$i] . "/" . $vol_uuids[$i];
545
546     my $boot_drive;
547     if ($i == 0) {
548         $boot_drive = "True";
549     } else {
550         $boot_drive = "False";
551     }
552
553     $w->startTag ("Disk",
554                   [$ovf_ns, "diskId" ] => $vol_uuids[$i],
555                   [$ovf_ns, "actual_size"] =>
556                       sprintf ("%.0f", $real_size_in_gb),
557                   [$ovf_ns, "size"] =>
558                       sprintf ("%.0f", $virtual_size_in_gb),
559                   [$ovf_ns, "fileRef"] => $href,
560                   [$ovf_ns, "parentRef"] => "",
561                   [$ovf_ns, "vm_snapshot_id"] => uuidgen (),
562                   [$ovf_ns, "volume-format"] => "COW",
563                   [$ovf_ns, "volume-type"] => "Sparse",
564                   [$ovf_ns, "format"] => "http://en.wikipedia.org/wiki/Byte",
565                   [$ovf_ns, "disk-interface"] => "VirtIO",
566                   [$ovf_ns, "disk-type"] => "System",
567                   [$ovf_ns, "boot"] => $boot_drive);
568     $w->endTag ();
569 }
570
571 $w->endTag ();
572
573 $w->startTag ("Content",
574               [$ovf_ns, "id"] => "out",
575               [$xsi_ns, "type"] => "ovf:VirtualSystem_Type");
576 $w->startTag ("Name");
577 $w->characters ($name);
578 $w->endTag ();
579 $w->startTag ("TemplateId");
580 $w->characters ("00000000-0000-0000-0000-000000000000");
581 $w->endTag ();
582 $w->startTag ("TemplateName");
583 $w->characters ("Blank");
584 $w->endTag ();
585 $w->startTag ("Description");
586 $w->characters ($imported_by);
587 $w->endTag ();
588 $w->startTag ("Domain");
589 $w->endTag ();
590 $w->startTag ("CreationDate");
591 $w->characters ($iso_time);
592 $w->endTag ();
593 $w->startTag ("IsInitilized"); # sic
594 $w->characters ("True");
595 $w->endTag ();
596 $w->startTag ("IsAutoSuspend");
597 $w->characters ("False");
598 $w->endTag ();
599 $w->startTag ("TimeZone");
600 $w->endTag ();
601 $w->startTag ("IsStateless");
602 $w->characters ("False");
603 $w->endTag ();
604 $w->startTag ("Origin");
605 $w->characters ("0");
606 $w->endTag ();
607 $w->startTag ("VmType");
608 $w->characters ($vmtype);
609 $w->endTag ();
610 $w->startTag ("DefaultDisplayType");
611 $w->characters ("1"); # qxl
612 $w->endTag ();
613
614 $w->startTag ("Section",
615               [$ovf_ns, "id"] => $vm_uuid,
616               [$ovf_ns, "required"] => "false",
617               [$xsi_ns, "type"] => "ovf:OperatingSystemSection_Type");
618 $w->startTag ("Info");
619 $w->characters ($product_name);
620 $w->endTag ();
621 $w->startTag ("Description");
622 $w->characters ($ostype);
623 $w->endTag ();
624 $w->endTag ();
625
626 $w->startTag ("Section",
627               [$xsi_ns, "type"] => "ovf:VirtualHardwareSection_Type");
628 $w->startTag ("Info");
629 $w->characters (sprintf ("%d CPU, %d Memory", $vcpus, $memory_mb));
630 $w->endTag ();
631
632 $w->startTag ("Item");
633 $w->startTag ([$rasd_ns, "Caption"]);
634 $w->characters (sprintf ("%d virtual cpu", $vcpus));
635 $w->endTag ();
636 $w->startTag ([$rasd_ns, "Description"]);
637 $w->characters ("Number of virtual CPU");
638 $w->endTag ();
639 $w->startTag ([$rasd_ns, "InstanceId"]);
640 $w->characters ("1");
641 $w->endTag ();
642 $w->startTag ([$rasd_ns, "ResourceType"]);
643 $w->characters ("3");
644 $w->endTag ();
645 $w->startTag ([$rasd_ns, "num_of_sockets"]);
646 $w->characters ($vcpus);
647 $w->endTag ();
648 $w->startTag ([$rasd_ns, "cpu_per_socket"]);
649 $w->characters (1);
650 $w->endTag ();
651 $w->endTag ("Item");
652
653 $w->startTag ("Item");
654 $w->startTag ([$rasd_ns, "Caption"]);
655 $w->characters (sprintf ("%d MB of memory", $memory_mb));
656 $w->endTag ();
657 $w->startTag ([$rasd_ns, "Description"]);
658 $w->characters ("Memory Size");
659 $w->endTag ();
660 $w->startTag ([$rasd_ns, "InstanceId"]);
661 $w->characters ("2");
662 $w->endTag ();
663 $w->startTag ([$rasd_ns, "ResourceType"]);
664 $w->characters ("4");
665 $w->endTag ();
666 $w->startTag ([$rasd_ns, "AllocationUnits"]);
667 $w->characters ("MegaBytes");
668 $w->endTag ();
669 $w->startTag ([$rasd_ns, "VirtualQuantity"]);
670 $w->characters ($memory_mb);
671 $w->endTag ();
672 $w->endTag ("Item");
673
674 $w->startTag ("Item");
675 $w->startTag ([$rasd_ns, "Caption"]);
676 $w->characters ("USB Controller");
677 $w->endTag ();
678 $w->startTag ([$rasd_ns, "InstanceId"]);
679 $w->characters ("3");
680 $w->endTag ();
681 $w->startTag ([$rasd_ns, "ResourceType"]);
682 $w->characters ("23");
683 $w->endTag ();
684 $w->startTag ([$rasd_ns, "UsbPolicy"]);
685 $w->characters ("Disabled");
686 $w->endTag ();
687 $w->endTag ("Item");
688
689 $w->startTag ("Item");
690 $w->startTag ([$rasd_ns, "Caption"]);
691 $w->characters ("Graphical Controller");
692 $w->endTag ();
693 $w->startTag ([$rasd_ns, "InstanceId"]);
694 $w->characters (uuidgen ());
695 $w->endTag ();
696 $w->startTag ([$rasd_ns, "ResourceType"]);
697 $w->characters ("20");
698 $w->endTag ();
699 $w->startTag ("Type");
700 $w->characters ("video");
701 $w->endTag ();
702 $w->startTag ([$rasd_ns, "VirtualQuantity"]);
703 $w->characters ("1");
704 $w->endTag ();
705 $w->startTag ([$rasd_ns, "Device"]);
706 $w->characters ("qxl");
707 $w->endTag ();
708 $w->endTag ("Item");
709
710 for ($i = 0; $i < @disks; ++$i)
711 {
712     my $href = $image_uuids[$i] . "/" . $vol_uuids[$i];
713
714     $w->startTag ("Item");
715
716     $w->startTag ([$rasd_ns, "Caption"]);
717     $w->characters ("Drive " . ($i+1));
718     $w->endTag ();
719     $w->startTag ([$rasd_ns, "InstanceId"]);
720     $w->characters ($vol_uuids[$i]);
721     $w->endTag ();
722     $w->startTag ([$rasd_ns, "ResourceType"]);
723     $w->characters ("17");
724     $w->endTag ();
725     $w->startTag ("Type");
726     $w->characters ("disk");
727     $w->endTag ();
728     $w->startTag ([$rasd_ns, "HostResource"]);
729     $w->characters ($href);
730     $w->endTag ();
731     $w->startTag ([$rasd_ns, "Parent"]);
732     $w->characters ("00000000-0000-0000-0000-000000000000");
733     $w->endTag ();
734     $w->startTag ([$rasd_ns, "Template"]);
735     $w->characters ("00000000-0000-0000-0000-000000000000");
736     $w->endTag ();
737     $w->startTag ([$rasd_ns, "ApplicationList"]);
738     $w->endTag ();
739     $w->startTag ([$rasd_ns, "StorageId"]);
740     $w->characters ($esd_uuid);
741     $w->endTag ();
742     $w->startTag ([$rasd_ns, "StoragePoolId"]);
743     $w->characters ("00000000-0000-0000-0000-000000000000");
744     $w->endTag ();
745     $w->startTag ([$rasd_ns, "CreationDate"]);
746     $w->characters ($iso_time);
747     $w->endTag ();
748     $w->startTag ([$rasd_ns, "LastModified"]);
749     $w->characters ($iso_time);
750     $w->endTag ();
751     $w->startTag ([$rasd_ns, "last_modified_date"]);
752     $w->characters ($iso_time);
753     $w->endTag ();
754
755     $w->endTag ("Item");
756 }
757
758 $w->endTag ("Section"); # ovf:VirtualHardwareSection_Type
759
760 $w->endTag ("Content");
761
762 $w->endTag ([$ovf_ns, "Envelope"]);
763 $w->end ();
764
765 my $ovf = $w->to_string;
766
767 #print "OVF:\n$ovf\n";
768
769 my $ovf_dir = "$esd_uuid_dir/master/vms/$vm_uuid";
770 run_as_vdsm (sub {
771     mkdir ($ovf_dir, 0755) or die "mkdir: $ovf_dir: $!";
772 });
773 my $ovf_file = "$ovf_dir/$vm_uuid.ovf";
774 run_as_vdsm (sub {
775     open (my $fh, ">", $ovf_file) or die "open: $ovf_file: $!";
776     print $fh $ovf
777 });
778
779 # Finished.
780 $delete_output_on_exit = 0;
781 print "\n";
782 print "OVF written to $ovf_file\n";
783 print "\n";
784 print "Import finished without errors.  Now go to the Storage tab ->\n";
785 print "Export Storage Domain -> VM Import, and import the guest.\n";
786 exit 0;
787
788 __END__
789
790 =head1 TO DO
791
792 =over 4
793
794 =item Network
795
796 Add a network card to the OVF.  The problem is detecting what
797 network devices the guest can support.
798
799 =item Disk model
800
801 Detect what disk models (eg. IDE, virtio-blk, virtio-scsi) the
802 guest can support and add the correct type of disk.
803
804 =back
805
806 =head1 DEBUGGING IMPORT FAILURES
807
808 When you export to the ESD, and then import that guest through the
809 oVirt / RHEV-M UI, you may encounter an import failure.  Diagnosing
810 these failures is infuriatingly difficult as the UI generally hides
811 the true reason for the failure.
812
813 There are two log files of interest.  The first is stored on the oVirt
814 engine / RHEV-M server itself, and is called
815 F</var/log/ovirt-engine/engine.log>
816
817 The second file, which is the most useful, is found on the SPM host
818 (SPM stands for "Storage Pool Manager").  This is a oVirt node that is
819 elected to do all metadata modifications in the data center, such as
820 image or snapshot creation.  You can find out which host is the
821 current SPM from the "Hosts" tab "Spm Status" column.  Once you have
822 located the SPM, log into it and grab the file
823 F</var/log/vdsm/vdsm.log> which will contain detailed error messages
824 from low-level commands.
825
826 =head1 SEE ALSO
827
828 L<https://bugzilla.redhat.com/show_bug.cgi?id=998279>,
829 L<https://bugzilla.redhat.com/show_bug.cgi?id=1049604>,
830 L<virt-v2v(1)>,
831 L<engine-image-uploader(8)>.
832
833 =head1 AUTHOR
834
835 Richard W.M. Jones <rjones@redhat.com>
836
837 =head1 COPYRIGHT
838
839 Copyright (C) 2015 Richard W.M. Jones <rjones@redhat.com>
840
841 Copyright (C) 2015 Red Hat Inc.
842
843 =head1 LICENSE
844
845 This program is free software; you can redistribute it and/or modify it
846 under the terms of the GNU General Public License as published by the
847 Free Software Foundation; either version 2 of the License, or (at your
848 option) any later version.
849
850 This program is distributed in the hope that it will be useful, but
851 WITHOUT ANY WARRANTY; without even the implied warranty of
852 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
853 General Public License for more details.
854
855 You should have received a copy of the GNU General Public License along
856 with this program; if not, write to the Free Software Foundation, Inc.,
857 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.