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