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 "vmtype=s" => \$vmtype,
200 or die "$0: unknown command line option\n";
202 pod2usage (1) if $help;
203 pod2usage (-exitval => 0, -verbose => 2) if $man;
205 # Get the parameters.
207 die "Use '$0 --man' to display the manual.\n"
210 my @disks = @ARGV[0 .. $#ARGV-1];
211 my $output = $ARGV[$#ARGV];
213 if (!defined $name) {
216 $name =~ s{\.[^.]+}{};
219 if ($vmtype =~ /^Desktop$/i) {
221 } elsif ($vmtype =~ /^Server$/i) {
224 die "$0: --vmtype parameter must be 'Desktop' or 'Server'\n"
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);
231 my @roots = $g->inspect_os ();
233 die "$0: no operating system was found on the disk\n"
236 die "$0: either this is a multi-OS disk, or you passed multiple unrelated guest disks on the command line\n"
238 my $root = $roots[0];
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);
249 # Get the virtual size of each disk.
252 push @virtual_sizes, $g->disk_virtual_size ($_);
257 # Map inspection data to RHEV ostype.
259 if ($type eq "linux" && $distro eq "rhel" && $major_version <= 6) {
260 if ($arch eq "x86_64") {
261 $ostype = "RHEL${major_version}x64"
263 $ostype = "RHEL$major_version"
266 elsif ($type eq "linux" && $distro eq "rhel") {
267 if ($arch eq "x86_64") {
268 $ostype = "rhel_${major_version}x64"
270 $ostype = "rhel_$major_version"
273 elsif ($type eq "linux") {
274 $ostype = "OtherLinux"
276 elsif ($type eq "windows" && $major_version == 5 && $minor_version == 1) {
277 $ostype = "WindowsXP"
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"
285 $ostype = "Windows2003"
288 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 0) {
289 if ($arch eq "x86_64") {
290 $ostype = "Windows2008x64"
292 $ostype = "Windows2008"
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"
303 $ostype = "Windows2008R2x64"
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"
311 $ostype = "windows_8"
314 $ostype = "windows_2012x64"
317 elsif ($type eq "windows" && $major_version == 6 && $minor_version == 3) {
318 $ostype = "windows_2012R2x64"
321 $ostype = "Unassigned"
324 # Mount the ESD if needed (or just check it exists).
327 $mountpoint = $output;
328 } elsif ($output =~ m{^.*:.*$}) {
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 }
335 die "$0: ESD $output is not a directory or an NFS mountpoint\n"
338 # Check the ESD looks like an ESD.
339 my @entries = <$mountpoint/*>;
341 grep { m{/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$}i }
344 die "$0: does $output really point to an oVirt Export Storage Domain?\n"
347 die "$0: multiple GUIDs found in oVirt Export Storage Domain\n"
349 my $esd_uuid_dir = $entries[0];
350 my $esd_uuid = $esd_uuid_dir;
351 $esd_uuid =~ s{.*/}{};
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"
361 print "Importing $product_name to $output...\n";
363 # A helper function that forks and runs some code / a command as
364 # an alternate UID:GID.
370 die "fork: $!" unless defined $pid;
384 waitpid ($pid, 0) or die "waitpid: $!";
386 die "$0: run_as_vdsm: child process failed (status $?)\n";
393 local $_ = `uuidgen -r`;
395 die unless length $_ >= 30; # Sanity check.
399 # Generate some random UUIDs.
400 my $vm_uuid = uuidgen ();
403 push @image_uuids, uuidgen ();
407 push @vol_uuids, uuidgen ();
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;
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/$_");
419 system ("rm", "-rf", "$esd_uuid_dir/master/vms/$vm_uuid");
423 # Copy and convert the disk images.
426 my $iso_time = strftime ("%Y/%m/%d %H:%M:%S", gmtime ());
427 my $imported_by = "Imported by import-to-ovirt.pl";
430 for ($i = 0; $i < @disks; ++$i) {
431 my $input_file = $disks[$i];
432 my $image_uuid = $image_uuids[$i];
434 my $path = "$esd_uuid_dir/images/$image_uuid";
435 mkdir ($path, 0755) or die "mkdir: $path: $!";
437 my $output_file = "$esd_uuid_dir/images/$image_uuid/".$vol_uuids[$i];
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: $!";
443 print "Copying $input_file ...\n";
444 system ("qemu-img", "convert", "-p",
447 "-o", "compat=0.10", # for RHEL 6-based ovirt nodes
449 or die "qemu-img: $input_file: failed (status $?)";
450 push @real_sizes, -s $output_file;
452 my $size_in_sectors = $virtual_sizes[$i] / 512;
454 # Create .meta files per disk.
462 PUUID=00000000-0000-0000-0000-000000000000
465 SIZE=$size_in_sectors
468 DESCRIPTION=$imported_by
470 my $meta_file = $output_file . ".meta";
472 open (my $fh, ">", $meta_file) or die "open: $meta_file: $!";
478 print "Creating OVF metadata ...\n";
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/";
490 my @forced_ns_decls = keys %prefix_map;
492 my $w = XML::Writer->new (
495 PREFIX_MAP => \%prefix_map,
496 FORCED_NS_DECLS => \@forced_ns_decls,
501 $w->startTag ([$ovf_ns, "Envelope"],
502 [$ovf_ns, "version"] => "0.9");
503 $w->comment ($imported_by);
505 $w->startTag ("References");
507 for ($i = 0; $i < @disks; ++$i)
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);
520 $w->startTag ("Section",
521 [$xsi_ns, "type"] => "ovf:NetworkSection_Type");
522 $w->startTag ("Info");
523 $w->characters ("List of networks");
527 $w->startTag ("Section",
528 [$xsi_ns, "type"] => "ovf:DiskSection_Type");
529 $w->startTag ("Info");
530 $w->characters ("List of Virtual Disks");
533 for ($i = 0; $i < @disks; ++$i)
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];
547 $boot_drive = "True";
549 $boot_drive = "False";
552 $w->startTag ("Disk",
553 [$ovf_ns, "diskId" ] => $vol_uuids[$i],
554 [$ovf_ns, "actual_size"] =>
555 sprintf ("%.0f", $real_size_in_gb),
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);
572 $w->startTag ("Content",
573 [$ovf_ns, "id"] => "out",
574 [$xsi_ns, "type"] => "ovf:VirtualSystem_Type");
575 $w->startTag ("Name");
576 $w->characters ($name);
578 $w->startTag ("TemplateId");
579 $w->characters ("00000000-0000-0000-0000-000000000000");
581 $w->startTag ("TemplateName");
582 $w->characters ("Blank");
584 $w->startTag ("Description");
585 $w->characters ($imported_by);
587 $w->startTag ("Domain");
589 $w->startTag ("CreationDate");
590 $w->characters ($iso_time);
592 $w->startTag ("IsInitilized"); # sic
593 $w->characters ("True");
595 $w->startTag ("IsAutoSuspend");
596 $w->characters ("False");
598 $w->startTag ("TimeZone");
600 $w->startTag ("IsStateless");
601 $w->characters ("False");
603 $w->startTag ("Origin");
604 $w->characters ("0");
606 $w->startTag ("VmType");
607 $w->characters ($vmtype);
609 $w->startTag ("DefaultDisplayType");
610 $w->characters ("1"); # qxl
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);
620 $w->startTag ("Description");
621 $w->characters ($ostype);
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));
631 $w->startTag ("Item");
632 $w->startTag ([$rasd_ns, "Caption"]);
633 $w->characters (sprintf ("%d virtual cpu", $vcpus));
635 $w->startTag ([$rasd_ns, "Description"]);
636 $w->characters ("Number of virtual CPU");
638 $w->startTag ([$rasd_ns, "InstanceId"]);
639 $w->characters ("1");
641 $w->startTag ([$rasd_ns, "ResourceType"]);
642 $w->characters ("3");
644 $w->startTag ([$rasd_ns, "num_of_sockets"]);
645 $w->characters ($vcpus);
647 $w->startTag ([$rasd_ns, "cpu_per_socket"]);
652 $w->startTag ("Item");
653 $w->startTag ([$rasd_ns, "Caption"]);
654 $w->characters (sprintf ("%d MB of memory", $memory_mb));
656 $w->startTag ([$rasd_ns, "Description"]);
657 $w->characters ("Memory Size");
659 $w->startTag ([$rasd_ns, "InstanceId"]);
660 $w->characters ("2");
662 $w->startTag ([$rasd_ns, "ResourceType"]);
663 $w->characters ("4");
665 $w->startTag ([$rasd_ns, "AllocationUnits"]);
666 $w->characters ("MegaBytes");
668 $w->startTag ([$rasd_ns, "VirtualQuantity"]);
669 $w->characters ($memory_mb);
673 $w->startTag ("Item");
674 $w->startTag ([$rasd_ns, "Caption"]);
675 $w->characters ("USB Controller");
677 $w->startTag ([$rasd_ns, "InstanceId"]);
678 $w->characters ("3");
680 $w->startTag ([$rasd_ns, "ResourceType"]);
681 $w->characters ("23");
683 $w->startTag ([$rasd_ns, "UsbPolicy"]);
684 $w->characters ("Disabled");
688 $w->startTag ("Item");
689 $w->startTag ([$rasd_ns, "Caption"]);
690 $w->characters ("Graphical Controller");
692 $w->startTag ([$rasd_ns, "InstanceId"]);
693 $w->characters (uuidgen ());
695 $w->startTag ([$rasd_ns, "ResourceType"]);
696 $w->characters ("20");
698 $w->startTag ("Type");
699 $w->characters ("video");
701 $w->startTag ([$rasd_ns, "VirtualQuantity"]);
702 $w->characters ("1");
704 $w->startTag ([$rasd_ns, "Device"]);
705 $w->characters ("qxl");
709 for ($i = 0; $i < @disks; ++$i)
711 my $href = $image_uuids[$i] . "/" . $vol_uuids[$i];
713 $w->startTag ("Item");
715 $w->startTag ([$rasd_ns, "Caption"]);
716 $w->characters ("Drive " . ($i+1));
718 $w->startTag ([$rasd_ns, "InstanceId"]);
719 $w->characters ($vol_uuids[$i]);
721 $w->startTag ([$rasd_ns, "ResourceType"]);
722 $w->characters ("17");
724 $w->startTag ("Type");
725 $w->characters ("disk");
727 $w->startTag ([$rasd_ns, "HostResource"]);
728 $w->characters ($href);
730 $w->startTag ([$rasd_ns, "Parent"]);
731 $w->characters ("00000000-0000-0000-0000-000000000000");
733 $w->startTag ([$rasd_ns, "Template"]);
734 $w->characters ("00000000-0000-0000-0000-000000000000");
736 $w->startTag ([$rasd_ns, "ApplicationList"]);
738 $w->startTag ([$rasd_ns, "StorageId"]);
739 $w->characters ($esd_uuid);
741 $w->startTag ([$rasd_ns, "StoragePoolId"]);
742 $w->characters ("00000000-0000-0000-0000-000000000000");
744 $w->startTag ([$rasd_ns, "CreationDate"]);
745 $w->characters ($iso_time);
747 $w->startTag ([$rasd_ns, "LastModified"]);
748 $w->characters ($iso_time);
750 $w->startTag ([$rasd_ns, "last_modified_date"]);
751 $w->characters ($iso_time);
757 $w->endTag ("Section"); # ovf:VirtualHardwareSection_Type
759 $w->endTag ("Content");
761 $w->endTag ([$ovf_ns, "Envelope"]);
764 my $ovf = $w->to_string;
766 #print "OVF:\n$ovf\n";
768 my $ovf_dir = "$esd_uuid_dir/master/vms/$vm_uuid";
770 mkdir ($ovf_dir, 0755) or die "mkdir: $ovf_dir: $!";
772 my $ovf_file = "$ovf_dir/$vm_uuid.ovf";
774 open (my $fh, ">", $ovf_file) or die "open: $ovf_file: $!";
779 $delete_output_on_exit = 0;
781 print "OVF written to $ovf_file\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";
795 Add a network card to the OVF. The problem is detecting what
796 network devices the guest can support.
800 Detect what disk models (eg. IDE, virtio-blk, virtio-scsi) the
801 guest can support and add the correct type of disk.
805 =head1 DEBUGGING IMPORT FAILURES
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.
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>
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.
828 L<engine-image-uploader(8)>.
832 Richard W.M. Jones <rjones@redhat.com>
836 Copyright (C) 2015 Richard W.M. Jones <rjones@redhat.com>
838 Copyright (C) 2015 Red Hat Inc.
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.
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.
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.