Fixes to import-to-ovirt.pl
[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->add_drive_opts ($_, readonly => 1) foreach (@disks);
230 $g->launch ();
231 my @roots = $g->inspect_os ();
232 if (@roots == 0) {
233     die "$0: no operating system was found on the disk\n"
234 }
235 if (@roots > 1) {
236     die "$0: either this is a multi-OS disk, or you passed multiple unrelated guest disks on the command line\n"
237 }
238 my $root = $roots[0];
239
240 # Save the inspection data.
241 my $type = $g->inspect_get_type ($root);
242 my $distro = $g->inspect_get_distro ($root);
243 my $arch = $g->inspect_get_arch ($root);
244 my $major_version = $g->inspect_get_major_version ($root);
245 my $minor_version = $g->inspect_get_major_version ($root);
246 my $product_name = $g->inspect_get_product_name ($root);
247 my $product_variant = $g->inspect_get_product_variant ($root);
248
249 # Get the virtual size of each disk.
250 my @virtual_sizes;
251 foreach (@disks) {
252     push @virtual_sizes, $g->disk_virtual_size ($_);
253 }
254
255 $g->close ();
256
257 # Map inspection data to RHEV ostype.
258 my $ostype;
259 if ($type eq "linux" && $distro eq "rhel" && $major_version <= 6) {
260     if ($arch eq "x86_64") {
261         $ostype = "RHEL${major_version}x64"
262     } else {
263         $ostype = "RHEL$major_version"
264     }
265 }
266 elsif ($type eq "linux" && $distro eq "rhel") {
267     if ($arch eq "x86_64") {
268         $ostype = "rhel_${major_version}x64"
269     } else {
270         $ostype = "rhel_$major_version"
271     }
272 }
273 elsif ($type eq "linux") {
274     $ostype = "OtherLinux"
275 }
276 elsif ($type eq "windows" && $major_version == 5 && $minor_version == 1) {
277     $ostype = "WindowsXP"
278 }
279 elsif ($type eq "windows" && $major_version == 5 && $minor_version == 2) {
280     if ($product_name =~ /XP/) {
281         $ostype = "WindowsXP"
282     } elsif ($arch eq "x86_64") {
283         $ostype = "Windows2003x64"
284     } else {
285         $ostype = "Windows2003"
286     }
287 }
288 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 0) {
289     if ($arch eq "x86_64") {
290         $ostype = "Windows2008x64"
291     } else {
292         $ostype = "Windows2008"
293     }
294 }
295 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 1) {
296     if ($product_variant eq "Client") {
297         if ($arch eq "x86_64") {
298             $ostype = "Windows7x64"
299         } else {
300             $ostype = "Windows7"
301         }
302     } else {
303         $ostype = "Windows2008R2x64"
304     }
305 }
306 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 2) {
307    if ($product_variant eq "Client") {
308        if ($arch eq "x86_64") {
309            $ostype = "windows_8x64"
310        } else {
311            $ostype = "windows_8"
312        }
313    } else {
314        $ostype = "windows_2012x64"
315    }
316 }
317 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 3) {
318     $ostype = "windows_2012R2x64"
319 }
320 else {
321     $ostype = "Unassigned"
322 }
323
324 # Mount the ESD if needed (or just check it exists).
325 my $mountpoint;
326 if (-d $output) {
327     $mountpoint = $output;
328 } elsif ($output =~ m{^.*:.*$}) {
329     my $umount;
330     $umount = $mountpoint = tempdir (CLEANUP => 1);
331     system ("mount", "-t", "nfs", $output, $mountpoint) == 0
332         or die "$0: mount $output failed: $?\n";
333     END { system ("umount", $umount) if defined $umount }
334 } else {
335     die "$0: ESD $output is not a directory or an NFS mountpoint\n"
336 }
337
338 # Check the ESD looks like an ESD.
339 my @entries = <$mountpoint/*>;
340 @entries =
341     grep { m{/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$}i }
342     @entries;
343 if (@entries == 0) {
344     die "$0: does $output really point to an oVirt Export Storage Domain?\n"
345 }
346 if (@entries > 1) {
347     die "$0: multiple GUIDs found in oVirt Export Storage Domain\n"
348 }
349 my $esd_uuid_dir = $entries[0];
350 my $esd_uuid = $esd_uuid_dir;
351 $esd_uuid =~ s{.*/}{};
352
353 if (! -d $esd_uuid_dir ||
354     ! -d "$esd_uuid_dir/images" ||
355     ! -d "$esd_uuid_dir/master" ||
356     ! -d "$esd_uuid_dir/master/vms") {
357     die "$0: $output doesn't look like an Export Storage Domain\n"
358 }
359
360 # Start the import.
361 print "Importing $product_name to $output...\n";
362
363 # A helper function that forks and runs some code / a command as
364 # an alternate UID:GID.
365 sub run_as_vdsm
366 {
367     my $fn = shift;
368
369     my $pid = fork ();
370     die "fork: $!" unless defined $pid;
371     if ($pid == 0) {
372         # Child process.
373         if ($EUID == 0) {
374             setgid (36);
375             setuid (36);
376         }
377         eval { &$fn () };
378         if ($@) {
379             print STDERR "$@\n";
380             _exit (1);
381         }
382         _exit (0);
383     }
384     waitpid ($pid, 0) or die "waitpid: $!";
385     if ($? != 0) {
386         die "$0: run_as_vdsm: child process failed (status $?)\n";
387     }
388 }
389
390 # Generate a UUID.
391 sub uuidgen
392 {
393     local $_ = `uuidgen -r`;
394     chomp;
395     die unless length $_ >= 30; # Sanity check.
396     $_;
397 }
398
399 # Generate some random UUIDs.
400 my $vm_uuid = uuidgen ();
401 my @image_uuids;
402 foreach (@disks) {
403     push @image_uuids, uuidgen ();
404 }
405 my @vol_uuids;
406 foreach (@disks) {
407     push @vol_uuids, uuidgen ();
408 }
409
410 # Make sure the output is deleted on unsuccessful exit.  We set
411 # $delete_output_on_exit to false at the end of the script.
412 my $delete_output_on_exit = 1;
413 END {
414     if ($delete_output_on_exit) {
415         # Can't use run_as_vdsm in an END{} block.
416         foreach (@image_uuids) {
417             system ("rm", "-rf", "$esd_uuid_dir/images/$_");
418         }
419         system ("rm", "-rf", "$esd_uuid_dir/master/vms/$vm_uuid");
420     }
421 };
422
423 # Copy and convert the disk images.
424 my $i;
425 my $time = time ();
426 my $iso_time = strftime ("%Y/%m/%d %H:%M:%S", gmtime ());
427 my $imported_by = "Imported by import-to-ovirt.pl";
428 my @real_sizes;
429
430 for ($i = 0; $i < @disks; ++$i) {
431     my $input_file = $disks[$i];
432     my $image_uuid = $image_uuids[$i];
433     run_as_vdsm (sub {
434         my $path = "$esd_uuid_dir/images/$image_uuid";
435         mkdir ($path, 0755) or die "mkdir: $path: $!";
436     });
437     my $output_file = "$esd_uuid_dir/images/$image_uuid/".$vol_uuids[$i];
438     run_as_vdsm (sub {
439         open (my $fh, ">", $output_file) or die "open: $output_file: $!";
440         # Well done NFS root_squash, you make the world less secure.
441         chmod (0666, $output_file) or die "chmod: $output_file: $!";
442     });
443     print "Copying $input_file ...\n";
444     system ("qemu-img", "convert", "-p",
445             $input_file,
446             "-O", "qcow2",
447             "-o", "compat=0.10", # for RHEL 6-based ovirt nodes
448             $output_file) == 0
449                 or die "qemu-img: $input_file: failed (status $?)";
450     push @real_sizes, -s $output_file;
451
452     my $size_in_sectors = $virtual_sizes[$i] / 512;
453
454     # Create .meta files per disk.
455     my $meta = <<"EOF";
456 DOMAIN=$esd_uuid
457 VOLTYPE=LEAF
458 CTIME=$time
459 MTIME=$time
460 IMAGE=$image_uuid
461 DISKTYPE=1
462 PUUID=00000000-0000-0000-0000-000000000000
463 LEGALITY=LEGAL
464 POOL_UUID=
465 SIZE=$size_in_sectors
466 FORMAT=COW
467 TYPE=SPARSE
468 DESCRIPTION=$imported_by
469 EOF
470     my $meta_file = $output_file . ".meta";
471     run_as_vdsm (sub {
472         open (my $fh, ">", $meta_file) or die "open: $meta_file: $!";
473         print $fh $meta
474     });
475 }
476
477 # Create the OVF.
478 print "Creating OVF metadata ...\n";
479
480 my $rasd_ns = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData";
481 my $vssd_ns = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData";
482 my $xsi_ns = "http://www.w3.org/2001/XMLSchema-instance";
483 my $ovf_ns = "http://schemas.dmtf.org/ovf/envelope/1/";
484 my %prefix_map = (
485     $rasd_ns => "rasd",
486     $vssd_ns => "vssd",
487     $xsi_ns => "xsi",
488     $ovf_ns => "ovf",
489 );
490 my @forced_ns_decls = keys %prefix_map;
491
492 my $w = XML::Writer->new (
493     OUTPUT => "self",
494     NAMESPACES => 1,
495     PREFIX_MAP => \%prefix_map,
496     FORCED_NS_DECLS => \@forced_ns_decls,
497     DATA_MODE => 1,
498     DATA_INDENT => 4,
499 );
500
501 $w->startTag ([$ovf_ns, "Envelope"],
502               [$ovf_ns, "version"] => "0.9");
503 $w->comment ($imported_by);
504
505 $w->startTag ("References");
506
507 for ($i = 0; $i < @disks; ++$i)
508 {
509     my $href = $image_uuids[$i] . "/" . $vol_uuids[$i];
510     $w->startTag ("File",
511                   [$ovf_ns, "href"] => $href,
512                   [$ovf_ns, "id"] => $vol_uuids[$i],
513                   [$ovf_ns, "size"] => $virtual_sizes[$i],
514                   [$ovf_ns, "description"] => $imported_by);
515     $w->endTag ();
516 }
517
518 $w->endTag ();
519
520 $w->startTag ("Section",
521               [$xsi_ns, "type"] => "ovf:NetworkSection_Type");
522 $w->startTag ("Info");
523 $w->characters ("List of networks");
524 $w->endTag ();
525 $w->endTag ();
526
527 $w->startTag ("Section",
528               [$xsi_ns, "type"] => "ovf:DiskSection_Type");
529 $w->startTag ("Info");
530 $w->characters ("List of Virtual Disks");
531 $w->endTag ();
532
533 for ($i = 0; $i < @disks; ++$i)
534 {
535     my $virtual_size_in_gb = $virtual_sizes[$i];
536     $virtual_size_in_gb /= 1024;
537     $virtual_size_in_gb /= 1024;
538     $virtual_size_in_gb /= 1024;
539     my $real_size_in_gb = $real_sizes[$i];
540     $real_size_in_gb /= 1024;
541     $real_size_in_gb /= 1024;
542     $real_size_in_gb /= 1024;
543     my $href = $image_uuids[$i] . "/" . $vol_uuids[$i];
544
545     my $boot_drive;
546     if ($i == 0) {
547         $boot_drive = "True";
548     } else {
549         $boot_drive = "False";
550     }
551
552     $w->startTag ("Disk",
553                   [$ovf_ns, "diskId" ] => $vol_uuids[$i],
554                   [$ovf_ns, "actual_size"] =>
555                       sprintf ("%.0f", $real_size_in_gb),
556                   [$ovf_ns, "size"] =>
557                       sprintf ("%.0f", $virtual_size_in_gb),
558                   [$ovf_ns, "fileRef"] => $href,
559                   [$ovf_ns, "parentRef"] => "",
560                   [$ovf_ns, "vm_snapshot_id"] => uuidgen (),
561                   [$ovf_ns, "volume-format"] => "COW",
562                   [$ovf_ns, "volume-type"] => "Sparse",
563                   [$ovf_ns, "format"] => "http://en.wikipedia.org/wiki/Byte",
564                   [$ovf_ns, "disk-interface"] => "VirtIO",
565                   [$ovf_ns, "disk-type"] => "System",
566                   [$ovf_ns, "boot"] => $boot_drive);
567     $w->endTag ();
568 }
569
570 $w->endTag ();
571
572 $w->startTag ("Content",
573               [$ovf_ns, "id"] => "out",
574               [$xsi_ns, "type"] => "ovf:VirtualSystem_Type");
575 $w->startTag ("Name");
576 $w->characters ($name);
577 $w->endTag ();
578 $w->startTag ("TemplateId");
579 $w->characters ("00000000-0000-0000-0000-000000000000");
580 $w->endTag ();
581 $w->startTag ("TemplateName");
582 $w->characters ("Blank");
583 $w->endTag ();
584 $w->startTag ("Description");
585 $w->characters ($imported_by);
586 $w->endTag ();
587 $w->startTag ("Domain");
588 $w->endTag ();
589 $w->startTag ("CreationDate");
590 $w->characters ($iso_time);
591 $w->endTag ();
592 $w->startTag ("IsInitilized"); # sic
593 $w->characters ("True");
594 $w->endTag ();
595 $w->startTag ("IsAutoSuspend");
596 $w->characters ("False");
597 $w->endTag ();
598 $w->startTag ("TimeZone");
599 $w->endTag ();
600 $w->startTag ("IsStateless");
601 $w->characters ("False");
602 $w->endTag ();
603 $w->startTag ("Origin");
604 $w->characters ("0");
605 $w->endTag ();
606 $w->startTag ("VmType");
607 $w->characters ($vmtype);
608 $w->endTag ();
609 $w->startTag ("DefaultDisplayType");
610 $w->characters ("1"); # qxl
611 $w->endTag ();
612
613 $w->startTag ("Section",
614               [$ovf_ns, "id"] => $vm_uuid,
615               [$ovf_ns, "required"] => "false",
616               [$xsi_ns, "type"] => "ovf:OperatingSystemSection_Type");
617 $w->startTag ("Info");
618 $w->characters ($product_name);
619 $w->endTag ();
620 $w->startTag ("Description");
621 $w->characters ($ostype);
622 $w->endTag ();
623 $w->endTag ();
624
625 $w->startTag ("Section",
626               [$xsi_ns, "type"] => "ovf:VirtualHardwareSection_Type");
627 $w->startTag ("Info");
628 $w->characters (sprintf ("%d CPU, %d Memory", $vcpus, $memory_mb));
629 $w->endTag ();
630
631 $w->startTag ("Item");
632 $w->startTag ([$rasd_ns, "Caption"]);
633 $w->characters (sprintf ("%d virtual cpu", $vcpus));
634 $w->endTag ();
635 $w->startTag ([$rasd_ns, "Description"]);
636 $w->characters ("Number of virtual CPU");
637 $w->endTag ();
638 $w->startTag ([$rasd_ns, "InstanceId"]);
639 $w->characters ("1");
640 $w->endTag ();
641 $w->startTag ([$rasd_ns, "ResourceType"]);
642 $w->characters ("3");
643 $w->endTag ();
644 $w->startTag ([$rasd_ns, "num_of_sockets"]);
645 $w->characters ($vcpus);
646 $w->endTag ();
647 $w->startTag ([$rasd_ns, "cpu_per_socket"]);
648 $w->characters (1);
649 $w->endTag ();
650 $w->endTag ("Item");
651
652 $w->startTag ("Item");
653 $w->startTag ([$rasd_ns, "Caption"]);
654 $w->characters (sprintf ("%d MB of memory", $memory_mb));
655 $w->endTag ();
656 $w->startTag ([$rasd_ns, "Description"]);
657 $w->characters ("Memory Size");
658 $w->endTag ();
659 $w->startTag ([$rasd_ns, "InstanceId"]);
660 $w->characters ("2");
661 $w->endTag ();
662 $w->startTag ([$rasd_ns, "ResourceType"]);
663 $w->characters ("4");
664 $w->endTag ();
665 $w->startTag ([$rasd_ns, "AllocationUnits"]);
666 $w->characters ("MegaBytes");
667 $w->endTag ();
668 $w->startTag ([$rasd_ns, "VirtualQuantity"]);
669 $w->characters ($memory_mb);
670 $w->endTag ();
671 $w->endTag ("Item");
672
673 $w->startTag ("Item");
674 $w->startTag ([$rasd_ns, "Caption"]);
675 $w->characters ("USB Controller");
676 $w->endTag ();
677 $w->startTag ([$rasd_ns, "InstanceId"]);
678 $w->characters ("3");
679 $w->endTag ();
680 $w->startTag ([$rasd_ns, "ResourceType"]);
681 $w->characters ("23");
682 $w->endTag ();
683 $w->startTag ([$rasd_ns, "UsbPolicy"]);
684 $w->characters ("Disabled");
685 $w->endTag ();
686 $w->endTag ("Item");
687
688 $w->startTag ("Item");
689 $w->startTag ([$rasd_ns, "Caption"]);
690 $w->characters ("Graphical Controller");
691 $w->endTag ();
692 $w->startTag ([$rasd_ns, "InstanceId"]);
693 $w->characters (uuidgen ());
694 $w->endTag ();
695 $w->startTag ([$rasd_ns, "ResourceType"]);
696 $w->characters ("20");
697 $w->endTag ();
698 $w->startTag ("Type");
699 $w->characters ("video");
700 $w->endTag ();
701 $w->startTag ([$rasd_ns, "VirtualQuantity"]);
702 $w->characters ("1");
703 $w->endTag ();
704 $w->startTag ([$rasd_ns, "Device"]);
705 $w->characters ("qxl");
706 $w->endTag ();
707 $w->endTag ("Item");
708
709 for ($i = 0; $i < @disks; ++$i)
710 {
711     my $href = $image_uuids[$i] . "/" . $vol_uuids[$i];
712
713     $w->startTag ("Item");
714
715     $w->startTag ([$rasd_ns, "Caption"]);
716     $w->characters ("Drive " . ($i+1));
717     $w->endTag ();
718     $w->startTag ([$rasd_ns, "InstanceId"]);
719     $w->characters ($vol_uuids[$i]);
720     $w->endTag ();
721     $w->startTag ([$rasd_ns, "ResourceType"]);
722     $w->characters ("17");
723     $w->endTag ();
724     $w->startTag ("Type");
725     $w->characters ("disk");
726     $w->endTag ();
727     $w->startTag ([$rasd_ns, "HostResource"]);
728     $w->characters ($href);
729     $w->endTag ();
730     $w->startTag ([$rasd_ns, "Parent"]);
731     $w->characters ("00000000-0000-0000-0000-000000000000");
732     $w->endTag ();
733     $w->startTag ([$rasd_ns, "Template"]);
734     $w->characters ("00000000-0000-0000-0000-000000000000");
735     $w->endTag ();
736     $w->startTag ([$rasd_ns, "ApplicationList"]);
737     $w->endTag ();
738     $w->startTag ([$rasd_ns, "StorageId"]);
739     $w->characters ($esd_uuid);
740     $w->endTag ();
741     $w->startTag ([$rasd_ns, "StoragePoolId"]);
742     $w->characters ("00000000-0000-0000-0000-000000000000");
743     $w->endTag ();
744     $w->startTag ([$rasd_ns, "CreationDate"]);
745     $w->characters ($iso_time);
746     $w->endTag ();
747     $w->startTag ([$rasd_ns, "LastModified"]);
748     $w->characters ($iso_time);
749     $w->endTag ();
750     $w->startTag ([$rasd_ns, "last_modified_date"]);
751     $w->characters ($iso_time);
752     $w->endTag ();
753
754     $w->endTag ("Item");
755 }
756
757 $w->endTag ("Section"); # ovf:VirtualHardwareSection_Type
758
759 $w->endTag ("Content");
760
761 $w->endTag ([$ovf_ns, "Envelope"]);
762 $w->end ();
763
764 my $ovf = $w->to_string;
765
766 #print "OVF:\n$ovf\n";
767
768 my $ovf_dir = "$esd_uuid_dir/master/vms/$vm_uuid";
769 run_as_vdsm (sub {
770     mkdir ($ovf_dir, 0755) or die "mkdir: $ovf_dir: $!";
771 });
772 my $ovf_file = "$ovf_dir/$vm_uuid.ovf";
773 run_as_vdsm (sub {
774     open (my $fh, ">", $ovf_file) or die "open: $ovf_file: $!";
775     print $fh $ovf
776 });
777
778 # Finished.
779 $delete_output_on_exit = 0;
780 print "\n";
781 print "OVF written to $ovf_file\n";
782 print "\n";
783 print "Import finished without errors.  Now go to the Storage tab ->\n";
784 print "Export Storage Domain -> VM Import, and import the guest.\n";
785 exit 0;
786
787 __END__
788
789 =head1 TO DO
790
791 =over 4
792
793 =item Network
794
795 Add a network card to the OVF.  The problem is detecting what
796 network devices the guest can support.
797
798 =item Disk model
799
800 Detect what disk models (eg. IDE, virtio-blk, virtio-scsi) the
801 guest can support and add the correct type of disk.
802
803 =back
804
805 =head1 DEBUGGING IMPORT FAILURES
806
807 When you export to the ESD, and then import that guest through the
808 oVirt / RHEV-M UI, you may encounter an import failure.  Diagnosing
809 these failures is infuriatingly difficult as the UI generally hides
810 the true reason for the failure.
811
812 There are two log files of interest.  The first is stored on the oVirt
813 engine / RHEV-M server itself, and is called
814 F</var/log/ovirt-engine/engine.log>
815
816 The second file, which is the most useful, is found on the SPM host
817 (SPM stands for "Storage Pool Manager").  This is a oVirt node that is
818 elected to do all metadata modifications in the data center, such as
819 image or snapshot creation.  You can find out which host is the
820 current SPM from the "Hosts" tab "Spm Status" column.  Once you have
821 located the SPM, log into it and grab the file
822 F</var/log/vdsm/vdsm.log> which will contain detailed error messages
823 from low-level commands.
824
825 =head1 SEE ALSO
826
827 L<virt-v2v(1)>,
828 L<engine-image-uploader(8)>.
829
830 =head1 AUTHOR
831
832 Richard W.M. Jones <rjones@redhat.com>
833
834 =head1 COPYRIGHT
835
836 Copyright (C) 2015 Richard W.M. Jones <rjones@redhat.com>
837
838 Copyright (C) 2015 Red Hat Inc.
839
840 =head1 LICENSE
841
842 This program is free software; you can redistribute it and/or modify it
843 under the terms of the GNU General Public License as published by the
844 Free Software Foundation; either version 2 of the License, or (at your
845 option) any later version.
846
847 This program is distributed in the hope that it will be useful, but
848 WITHOUT ANY WARRANTY; without even the implied warranty of
849 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
850 General Public License for more details.
851
852 You should have received a copy of the GNU General Public License along
853 with this program; if not, write to the Free Software Foundation, Inc.,
854 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.