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