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