2 # Copyright (C) 2015 Richard W.M. Jones <rjones@redhat.com>
3 # Copyright (C) 2015 Red Hat Inc.
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.
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.
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.
25 use File::Temp qw(tempdir);
26 use POSIX qw(_exit setgid setuid strftime);
33 import-to-ovirt.pl - Import virtual machine disk image to RHEV or oVirt
37 sudo ./import-to-ovirt.pl disk.img server:/esd
39 sudo ./import-to-ovirt.pl disk.img /esd_mountpoint
43 This tool should B<only> be used if the guest can already run on KVM.
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.>
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>.
52 sudo ./import-to-ovirt.pl disk.img server:/esd
54 Import a KVM guest to an already-mounted Export Storage Domain:
56 sudo ./import-to-ovirt.pl disk.img /esd_mountpoint
58 If the single guest has multiple disks, use:
60 sudo ./import-to-ovirt.pl disk1.img [disk2.img [...]] server:/esd
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.
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.
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.
85 ./import-to-ovirt.pl [list of disks] server:/esd
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>.
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
95 ./import-to-ovirt.pl [list of disks] /esd_mountpoint
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.
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).
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>).
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.
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!)
118 =head2 Network card and disk model
120 (See also L</TO DO> below)
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
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.
140 Display brief help and exit.
148 Display the manual page and exit.
152 my $memory_mb = 1024;
156 Set the memory size I<in megabytes>. The default is 1024.
164 Set the guest name. If not present, a name is made up based on
165 the filename of the first disk.
173 Set the number of virtual CPUs. The default is 1.
177 my $vmtype = "Desktop";
179 =item B<--vmtype> Desktop
181 =item B<--vmtype> Server
183 Set the VmType field in the OVF. It must be C<Desktop> or
184 C<Server>. The default is C<Desktop>.
194 GetOptions ("help|?" => \$help,
196 "memory=i" => \$memory_mb,
198 "vcpus=i" => \$vcpus,
199 "vmtype=s" => \$vmtype,
201 or die "$0: unknown command line option\n";
203 pod2usage (1) if $help;
204 pod2usage (-exitval => 0, -verbose => 2) if $man;
206 # Get the parameters.
208 die "Use '$0 --man' to display the manual.\n"
211 my @disks = @ARGV[0 .. $#ARGV-1];
212 my $output = $ARGV[$#ARGV];
214 if (!defined $name) {
217 $name =~ s{\.[^.]+}{};
220 if ($vmtype =~ /^Desktop$/i) {
222 } elsif ($vmtype =~ /^Server$/i) {
225 die "$0: --vmtype parameter must be 'Desktop' or 'Server'\n"
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);
233 my @roots = $g->inspect_os ();
235 die "$0: no operating system was found on the disk\n"
238 die "$0: either this is a multi-OS disk, or you passed multiple unrelated guest disks on the command line\n"
240 my $root = $roots[0];
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);
251 # Get the virtual size of each disk.
254 push @virtual_sizes, $g->disk_virtual_size ($_);
259 # Map inspection data to RHEV ostype.
261 if ($type eq "linux" && $distro eq "rhel" && $major_version <= 6) {
262 if ($arch eq "x86_64") {
263 $ostype = "RHEL${major_version}x64"
265 $ostype = "RHEL$major_version"
268 elsif ($type eq "linux" && $distro eq "rhel") {
269 if ($arch eq "x86_64") {
270 $ostype = "rhel_${major_version}x64"
272 $ostype = "rhel_$major_version"
275 elsif ($type eq "linux") {
276 $ostype = "OtherLinux"
278 elsif ($type eq "windows" && $major_version == 5 && $minor_version == 1) {
279 $ostype = "WindowsXP"
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"
287 $ostype = "Windows2003"
290 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 0) {
291 if ($arch eq "x86_64") {
292 $ostype = "Windows2008x64"
294 $ostype = "Windows2008"
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"
305 $ostype = "Windows2008R2x64"
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"
313 $ostype = "windows_8"
316 $ostype = "windows_2012x64"
319 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 3) {
320 $ostype = "windows_2012R2x64"
323 $ostype = "Unassigned"
326 # Mount the ESD if needed (or just check it exists).
329 $mountpoint = $output;
330 } elsif ($output =~ m{^.*:.*$}) {
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 }
337 die "$0: ESD $output is not a directory or an NFS mountpoint\n"
340 # Check the ESD looks like an ESD.
341 my @entries = <$mountpoint/*>;
343 grep { m{/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$}i }
346 die "$0: does $output really point to an oVirt Export Storage Domain?\n"
349 die "$0: multiple GUIDs found in oVirt Export Storage Domain\n"
351 my $esd_uuid_dir = $entries[0];
352 my $esd_uuid = $esd_uuid_dir;
353 $esd_uuid =~ s{.*/}{};
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"
363 print "Importing $product_name to $output...\n";
365 # A helper function that forks and runs some code / a command as
366 # an alternate UID:GID.
372 die "fork: $!" unless defined $pid;
386 waitpid ($pid, 0) or die "waitpid: $!";
388 die "$0: run_as_vdsm: child process failed (status $?)\n";
395 local $_ = `uuidgen -r`;
397 die unless length $_ >= 30; # Sanity check.
401 # Generate some random UUIDs.
402 my $vm_uuid = uuidgen ();
405 push @image_uuids, uuidgen ();
409 push @vol_uuids, uuidgen ();
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;
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/$_");
421 system ("rm", "-rf", "$esd_uuid_dir/master/vms/$vm_uuid");
425 # Copy and convert the disk images.
428 my $iso_time = strftime ("%Y/%m/%d %H:%M:%S", gmtime ());
429 my $imported_by = "Imported by import-to-ovirt.pl";
432 for ($i = 0; $i < @disks; ++$i) {
433 my $input_file = $disks[$i];
434 my $image_uuid = $image_uuids[$i];
436 my $path = "$esd_uuid_dir/images/$image_uuid";
437 mkdir ($path, 0755) or die "mkdir: $path: $!";
439 my $output_file = "$esd_uuid_dir/images/$image_uuid/".$vol_uuids[$i];
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: $!";
445 print "Copying $input_file ...\n";
446 system ("qemu-img", "convert", "-p",
449 "-o", "compat=0.10", # for RHEL 6-based ovirt nodes
451 or die "qemu-img: $input_file: failed (status $?)";
452 push @real_sizes, -s $output_file;
454 my $size_in_sectors = $virtual_sizes[$i] / 512;
456 # Create .meta files per disk.
464 PUUID=00000000-0000-0000-0000-000000000000
467 SIZE=$size_in_sectors
470 DESCRIPTION=$imported_by
472 my $meta_file = $output_file . ".meta";
474 open (my $fh, ">", $meta_file) or die "open: $meta_file: $!";
480 print "Creating OVF metadata ...\n";
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/";
492 my @forced_ns_decls = keys %prefix_map;
494 my $w = XML::Writer->new (
497 PREFIX_MAP => \%prefix_map,
498 FORCED_NS_DECLS => \@forced_ns_decls,
503 $w->startTag ([$ovf_ns, "Envelope"],
504 [$ovf_ns, "version"] => "0.9");
505 $w->comment ($imported_by);
507 $w->startTag ("References");
509 for ($i = 0; $i < @disks; ++$i)
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);
522 $w->startTag ("Section",
523 [$xsi_ns, "type"] => "ovf:NetworkSection_Type");
524 $w->startTag ("Info");
525 $w->characters ("List of networks");
529 $w->startTag ("Section",
530 [$xsi_ns, "type"] => "ovf:DiskSection_Type");
531 $w->startTag ("Info");
532 $w->characters ("List of Virtual Disks");
535 for ($i = 0; $i < @disks; ++$i)
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];
549 $boot_drive = "True";
551 $boot_drive = "False";
554 $w->startTag ("Disk",
555 [$ovf_ns, "diskId" ] => $vol_uuids[$i],
556 [$ovf_ns, "actual_size"] =>
557 sprintf ("%.0f", $real_size_in_gb),
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);
574 $w->startTag ("Content",
575 [$ovf_ns, "id"] => "out",
576 [$xsi_ns, "type"] => "ovf:VirtualSystem_Type");
577 $w->startTag ("Name");
578 $w->characters ($name);
580 $w->startTag ("TemplateId");
581 $w->characters ("00000000-0000-0000-0000-000000000000");
583 $w->startTag ("TemplateName");
584 $w->characters ("Blank");
586 $w->startTag ("Description");
587 $w->characters ($imported_by);
589 $w->startTag ("Domain");
591 $w->startTag ("CreationDate");
592 $w->characters ($iso_time);
594 $w->startTag ("IsInitilized"); # sic
595 $w->characters ("True");
597 $w->startTag ("IsAutoSuspend");
598 $w->characters ("False");
600 $w->startTag ("TimeZone");
602 $w->startTag ("IsStateless");
603 $w->characters ("False");
605 $w->startTag ("Origin");
606 $w->characters ("0");
608 $w->startTag ("VmType");
609 $w->characters ($vmtype);
611 $w->startTag ("DefaultDisplayType");
612 $w->characters ("1"); # qxl
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);
622 $w->startTag ("Description");
623 $w->characters ($ostype);
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));
633 $w->startTag ("Item");
634 $w->startTag ([$rasd_ns, "Caption"]);
635 $w->characters (sprintf ("%d virtual cpu", $vcpus));
637 $w->startTag ([$rasd_ns, "Description"]);
638 $w->characters ("Number of virtual CPU");
640 $w->startTag ([$rasd_ns, "InstanceId"]);
641 $w->characters ("1");
643 $w->startTag ([$rasd_ns, "ResourceType"]);
644 $w->characters ("3");
646 $w->startTag ([$rasd_ns, "num_of_sockets"]);
647 $w->characters ($vcpus);
649 $w->startTag ([$rasd_ns, "cpu_per_socket"]);
654 $w->startTag ("Item");
655 $w->startTag ([$rasd_ns, "Caption"]);
656 $w->characters (sprintf ("%d MB of memory", $memory_mb));
658 $w->startTag ([$rasd_ns, "Description"]);
659 $w->characters ("Memory Size");
661 $w->startTag ([$rasd_ns, "InstanceId"]);
662 $w->characters ("2");
664 $w->startTag ([$rasd_ns, "ResourceType"]);
665 $w->characters ("4");
667 $w->startTag ([$rasd_ns, "AllocationUnits"]);
668 $w->characters ("MegaBytes");
670 $w->startTag ([$rasd_ns, "VirtualQuantity"]);
671 $w->characters ($memory_mb);
675 $w->startTag ("Item");
676 $w->startTag ([$rasd_ns, "Caption"]);
677 $w->characters ("USB Controller");
679 $w->startTag ([$rasd_ns, "InstanceId"]);
680 $w->characters ("3");
682 $w->startTag ([$rasd_ns, "ResourceType"]);
683 $w->characters ("23");
685 $w->startTag ([$rasd_ns, "UsbPolicy"]);
686 $w->characters ("Disabled");
690 $w->startTag ("Item");
691 $w->startTag ([$rasd_ns, "Caption"]);
692 $w->characters ("Graphical Controller");
694 $w->startTag ([$rasd_ns, "InstanceId"]);
695 $w->characters (uuidgen ());
697 $w->startTag ([$rasd_ns, "ResourceType"]);
698 $w->characters ("20");
700 $w->startTag ("Type");
701 $w->characters ("video");
703 $w->startTag ([$rasd_ns, "VirtualQuantity"]);
704 $w->characters ("1");
706 $w->startTag ([$rasd_ns, "Device"]);
707 $w->characters ("qxl");
711 for ($i = 0; $i < @disks; ++$i)
713 my $href = $image_uuids[$i] . "/" . $vol_uuids[$i];
715 $w->startTag ("Item");
717 $w->startTag ([$rasd_ns, "Caption"]);
718 $w->characters ("Drive " . ($i+1));
720 $w->startTag ([$rasd_ns, "InstanceId"]);
721 $w->characters ($vol_uuids[$i]);
723 $w->startTag ([$rasd_ns, "ResourceType"]);
724 $w->characters ("17");
726 $w->startTag ("Type");
727 $w->characters ("disk");
729 $w->startTag ([$rasd_ns, "HostResource"]);
730 $w->characters ($href);
732 $w->startTag ([$rasd_ns, "Parent"]);
733 $w->characters ("00000000-0000-0000-0000-000000000000");
735 $w->startTag ([$rasd_ns, "Template"]);
736 $w->characters ("00000000-0000-0000-0000-000000000000");
738 $w->startTag ([$rasd_ns, "ApplicationList"]);
740 $w->startTag ([$rasd_ns, "StorageId"]);
741 $w->characters ($esd_uuid);
743 $w->startTag ([$rasd_ns, "StoragePoolId"]);
744 $w->characters ("00000000-0000-0000-0000-000000000000");
746 $w->startTag ([$rasd_ns, "CreationDate"]);
747 $w->characters ($iso_time);
749 $w->startTag ([$rasd_ns, "LastModified"]);
750 $w->characters ($iso_time);
752 $w->startTag ([$rasd_ns, "last_modified_date"]);
753 $w->characters ($iso_time);
759 $w->endTag ("Section"); # ovf:VirtualHardwareSection_Type
761 $w->endTag ("Content");
763 $w->endTag ([$ovf_ns, "Envelope"]);
766 my $ovf = $w->to_string;
768 #print "OVF:\n$ovf\n";
770 my $ovf_dir = "$esd_uuid_dir/master/vms/$vm_uuid";
772 mkdir ($ovf_dir, 0755) or die "mkdir: $ovf_dir: $!";
774 my $ovf_file = "$ovf_dir/$vm_uuid.ovf";
776 open (my $fh, ">", $ovf_file) or die "open: $ovf_file: $!";
781 $delete_output_on_exit = 0;
783 print "OVF written to $ovf_file\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";
797 Add a network card to the OVF. The problem is detecting what
798 network devices the guest can support.
802 Detect what disk models (eg. IDE, virtio-blk, virtio-scsi) the
803 guest can support and add the correct type of disk.
807 =head1 DEBUGGING IMPORT FAILURES
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.
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>
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.
829 L<https://bugzilla.redhat.com/show_bug.cgi?id=998279>,
830 L<https://bugzilla.redhat.com/show_bug.cgi?id=1049604>,
832 L<engine-image-uploader(8)>.
836 Richard W.M. Jones <rjones@redhat.com>
840 Copyright (C) 2015 Richard W.M. Jones <rjones@redhat.com>
842 Copyright (C) 2015 Red Hat Inc.
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.
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.
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.