managementnode/lib/VCL/Module/Provisioning/libvirt/KVM.pm (598 lines of code) (raw):

#!/usr/bin/perl -w ############################################################################### # $Id$ ############################################################################### # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ############################################################################### =head1 NAME VCL::Provisioning::libvirt::KVM - Libvirt hypervisor driver module to allow support for the KVM hypervisor =head1 DESCRIPTION This is a driver module to allow the main libvirt.pm provisioning module to support KVM hosts. It performs the KVM-specific tasks not handled by libvirt itself. =cut ############################################################################### package VCL::Module::Provisioning::libvirt::KVM; # Specify the lib path using FindBin use FindBin; use lib "$FindBin::Bin/../../../.."; # Configure inheritance use base qw(VCL::Module::Provisioning::libvirt); # Specify the version of this module our $VERSION = '2.5.1'; # Specify the version of Perl to use use 5.008000; use strict; use warnings; use diagnostics; use English qw(-no_match_vars); use File::Basename; use VCL::utils; ############################################################################### =head1 OBJECT METHODS =cut #////////////////////////////////////////////////////////////////////////////// =head2 initialize Parameters : none Returns : boolean Description : Checks if the node has KVM installed by checking if /usr/bin/qemu exists. Returns true if the file exists, false otherwise. =cut sub initialize { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $node_name = $self->data->get_vmhost_short_name(); my ($driver_name) = ref($self) =~ /::([^:]+)$/; # Check to see if required commands exist on the VM host my @test_commands = ( 'virsh', 'qemu-img', 'virt-win-reg', ); my @missing_commands; for my $command (@test_commands) { my ($exit_status, $output) = $self->vmhost_os->execute("which $command"); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "unable to initialize $driver_name driver module to control $node_name, failed to execute command to determine if the '$command' command is available"); return; } elsif (grep(/(which:|no $command)/, @$output)) { notify($ERRORS{'DEBUG'}, 0, "'$command' command is NOT available on $node_name"); push @missing_commands, $command; } else { notify($ERRORS{'DEBUG'}, 0, "verified '$command' command is available on $node_name"); } } if (@missing_commands) { notify($ERRORS{'DEBUG'}, 0, "unable to initialize $driver_name driver module to control $node_name, the following commands are not available:\n" . join("\n", @missing_commands)); return; } else { notify($ERRORS{'DEBUG'}, 0, "$driver_name driver module successfully initialized to control $node_name"); return 1; } } #////////////////////////////////////////////////////////////////////////////// =head2 get_domain_type Parameters : none Returns : string Description : Returns 'kvm'. This is specified in the domain XML definition: <domain type='kvm'> =cut sub get_domain_type { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } return 'kvm'; } #////////////////////////////////////////////////////////////////////////////// =head2 get_disk_driver_name Parameters : none Returns : string Description : Returns 'qemu'. The disk driver name is specified in the domain XML definition: <domain ...> <devices> <disk ...> <driver name='qemu' ...> =cut sub get_disk_driver_name { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } return 'qemu'; } #////////////////////////////////////////////////////////////////////////////// =head2 pre_define Parameters : none Returns : boolean Description : Performs the KVM-specific steps prior to defining a domain: * Checks if the master image file exists on the node, If it does not exist, attempts to copy image from repository to the node * Creates a copy on write image which will be used by the domain being loaded =cut sub pre_define { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $image_name = $self->data->get_image_name(); my $image_os_type = $self->data->get_image_os_type(); my $node_name = $self->data->get_vmhost_short_name(); my $copy_on_write_file_path = $self->get_copy_on_write_file_path(); my $master_image_file_path = $self->get_master_image_file_path(); my $datastore_image_type = $self->data->get_vmhost_datastore_imagetype_name(); if ($self->vmhost_os->file_exists($master_image_file_path)) { notify($ERRORS{'DEBUG'}, 0, "master image file exists in the datastore on $node_name: $master_image_file_path"); } else { notify($ERRORS{'DEBUG'}, 0, "master image file does NOT exist in the datastore on $node_name: $master_image_file_path"); # Check the files found in the repository # Attempt to determine which files are actual virtual disk files my @repository_image_file_paths = $self->find_repository_image_file_paths(); if (@repository_image_file_paths) { # Get a semaphore so that no other process can access this master image until the copy is complete # Don't need a repository image semaphore - impossible that another process is copying it to the repository # find_repository_image_file_paths must have successfully obtained one if (my $semaphore = $self->get_master_image_semaphore()) { # Attempt to copy the virtual disk from the repository to the datastore if ($self->copy_virtual_disk(\@repository_image_file_paths, $master_image_file_path, $datastore_image_type)) { notify($ERRORS{'DEBUG'}, 0, "copied master image from repository to datastore"); } else { notify($ERRORS{'WARNING'}, 0, "failed to copy master image from repository:\n" . join("\n", @repository_image_file_paths) . " --> $master_image_file_path"); return; } } else { notify($ERRORS{'WARNING'}, 0, "unable to prepare virtual disk, failed to obtain repository image semaphore before creating master image from repository image:\n" . join("\n", @repository_image_file_paths) . " --> $master_image_file_path"); return; } } else { notify($ERRORS{'WARNING'}, 0, "unable to prepare virtual disk, master image file could NOT be located, it does not exist in the datastore and node $node_name is not configured to use an image repository"); return; } # Update the registry if this is a Windows image # This allows VMware images to run on KVM using an IDE disk if ($image_os_type =~ /windows/i && !$self->update_windows_image($master_image_file_path)) { notify($ERRORS{'WARNING'}, 0, "failed to make Windows-specific changes to $master_image_file_path after it was copied/converted"); return; } } if ($datastore_image_type =~ /^qcow2?$/) { # Create a copy on write image which will be used by the VM being loaded # This effectively makes the master image read only, all changes are written to the copy on write image if (!$self->create_copy_on_write_image($master_image_file_path, $copy_on_write_file_path)) { notify($ERRORS{'WARNING'}, 0, "failed to prepare virtual disk, unable to create copy on write image"); return; } } else { notify($ERRORS{'DEBUG'}, 0, "copy on write virtual disk is not supported for the datastore image type: $datastore_image_type, creating full copy of master image file"); if (!$self->copy_virtual_disk($master_image_file_path, $copy_on_write_file_path, $datastore_image_type)) { notify($ERRORS{'WARNING'}, 0, "failed to prepare virtual disk, unable to create $datastore_image_type copy of master image: $master_image_file_path --> $copy_on_write_file_path"); return; } } return 1; } ############################################################################### =head1 PRIVATE METHODS =cut #////////////////////////////////////////////////////////////////////////////// =head2 get_virtual_disk_file_info Parameters : $virtual_disk_file_path Returns : hash reference Description : Calls 'qemu-img info' to retrieve the virtual disk information. Builds a hash based on the output. Example: "backing_file" => "/var/lib/libvirt/images/vmwarewinxp-base234-v23.qcow2 (actual path: /var/lib/libvirt/images/vmwarewinxp-base234-v23.qcow2)", "backing_file_actual_path" => "/var/lib/libvirt/images/vmwarewinxp-base234-v23.qcow2", "cluster_size" => 65536, "disk_size" => "423M", "disk_size_bytes" => 443547648, "file_format" => "qcow2", "image" => "/var/lib/libvirt/images/vclv99-37_234-v23.qcow2", "snapshot" => { 1 => { "date" => "2011-12-07 14:43:12", "tag" => "snap1", "vm_clock" => "00:00:00.000", "vm_size" => 0 } }, "virtual_size" => "20G (21474836480 bytes)", "virtual_size_bytes" => "21474836480" =cut sub get_virtual_disk_file_info { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $virtual_disk_file_path = shift; if (!$virtual_disk_file_path) { notify($ERRORS{'WARNING'}, 0, "unable to retrieve image info, file path argument was not supplied"); return; } # Return cached copy of virtual disk file info if it exists return $self->{virtual_disk_file_info}{$virtual_disk_file_path} if defined($self->{virtual_disk_file_info}{$virtual_disk_file_path}); my $node_name = $self->data->get_vmhost_short_name(); my $command = "qemu-img info \"$virtual_disk_file_path\""; my ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($exit_status)) { notify($ERRORS{'WARNING'}, 0, "failed to execute command to retrieve image info on $node_name"); return; } elsif ($exit_status) { notify($ERRORS{'DEBUG'}, 0, "unable to retrieve image info on $node_name, output:\n" . join("\n", @$output)); return; } else { #notify($ERRORS{'DEBUG'}, 0, "retrieved image info, output:\n" . join("\n", @$output)); my $virtual_disk_file_info; for my $line (@$output) { # Output example: # image: vclv99-37_234-v23.qcow2 # file format: qcow2 # virtual size: 20G (21474836480 bytes) # disk size: 423M # cluster_size: 65536 # backing file: /var/lib/libvirt/images/vmwarewinxp-base234-v23.qcow2 (actual path: /var/lib/libvirt/images/vmwarewinxp-base234-v23.qcow2) # Snapshot list: # ID TAG VM SIZE DATE VM CLOCK # 1 snap1 0 2011-12-07 14:43:12 00:00:00.000 # Skip the 'Snapshot list:' and snapshot header lines if ($line =~ /^(Snapshot list|ID)/i) { next; } # ID TAG SIZE DATE CLOCK elsif ($line =~ /^(\d+)\s+(.+)\s+(\d+)\s+([\d\-:\.]+ [\d:]+)\s+([\d:\.]+)/g) { my $id = $1; my $tag = $2; my $vm_size = $3; my $date = $4; my $vm_clock = $5; # Remove trailing spaces from the tag $tag =~ s/\s+$//; $virtual_disk_file_info->{snapshot}{$id} = { 'tag' => $tag, 'vm_size' => $vm_size, 'date' => $date, 'vm_clock' => $vm_clock, }; } elsif ($line =~ /([\w_ ]+):\s*(.+)/) { my $property = $1; my $value = $2; if ($property =~ /disk size/i) { # Calculate the number of bytes from the "disk size" line: # "disk_size" => "16K", # "disk_size" => "2.7M", my $disk_size_bytes; my ($disk_size, $units) = $value =~ /([\d\.]+)(\w)/; if ($units =~ /K/) { $disk_size_bytes = ($disk_size * 1024 ** 1); } elsif ($units =~ /M/) { $disk_size_bytes = ($disk_size * 1024 ** 2); } elsif ($units =~ /G/) { $disk_size_bytes = ($disk_size * 1024 ** 3); } elsif ($units =~ /T/) { $disk_size_bytes = ($disk_size * 1024 ** 4); } else { $disk_size_bytes = $disk_size; } $virtual_disk_file_info->{disk_size_bytes} = int($disk_size_bytes); } elsif ($property =~ /virtual size/i) { # Extract the number of bytes from the "virtual size" line: # "virtual_size" => "15M (15728640 bytes)" my ($virtual_size_bytes) = $value =~ /(\d+) bytes/; $virtual_disk_file_info->{virtual_size_bytes} = $virtual_size_bytes; } elsif ($property =~ /backing file/i) { # Extract the actual path from the "backing file" line: my ($actual_path) = $value =~ /actual path: ([^\)]+)/; $virtual_disk_file_info->{backing_file_actual_path} = $actual_path; } $property = lc($property); $property =~ s/\s+/_/g; $virtual_disk_file_info->{$property} = $value; } } #notify($ERRORS{'DEBUG'}, 0, "retrieved virtual disk file info:\n" . format_data($virtual_disk_file_info)); $self->{virtual_disk_file_info}{$virtual_disk_file_path} = $virtual_disk_file_info; return $virtual_disk_file_info; } } #////////////////////////////////////////////////////////////////////////////// =head2 get_virtual_disk_size_bytes Parameters : @virtual_disk_file_paths Returns : integer Description : Returns the size of the virtual disk in bytes. =cut sub get_virtual_disk_size_bytes { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } # Attempt to get the argument my @virtual_disk_file_paths = @_; if (!@virtual_disk_file_paths) { notify($ERRORS{'WARNING'}, 0, "virtual disk file paths argument was not supplied"); return; } my $node_name = $self->data->get_vmhost_short_name(); my $virtual_disk_size_bytes = 0; for my $virtual_disk_file_path (@virtual_disk_file_paths) { # Attempt to retrieve the virtual disk file info my $virtual_disk_file_info = $self->get_virtual_disk_file_info($virtual_disk_file_path); if (!$virtual_disk_file_info) { notify($ERRORS{'WARNING'}, 0, "unable to determine virtual disk size, information could not be retrieved for virtual disk file: $virtual_disk_file_path"); return; } $virtual_disk_size_bytes += $virtual_disk_file_info->{disk_size_bytes}; # Check if virtual disk has a backing file, size of both must be added if ($virtual_disk_file_info->{backing_file_actual_path}) { notify($ERRORS{'DEBUG'}, 0, "attempting to retrieve size of virtual disk backing file: $virtual_disk_file_info->{backing_file_actual_path}"); my $backing_file_size_bytes = $self->get_virtual_disk_size_bytes($virtual_disk_file_info->{backing_file_actual_path}); if (!$backing_file_size_bytes) { notify($ERRORS{'WARNING'}, 0, "unable to determine size of virtual disk: $virtual_disk_file_path, failed to determine size of backing file: $virtual_disk_file_info->{backing_file_actual_path}"); return; } # Note: added total size is not accurate, it is larger than the actual size $virtual_disk_size_bytes += $backing_file_size_bytes; } } notify($ERRORS{'DEBUG'}, 0, "retrieved size of virtual disk:\n" . join("\n", @virtual_disk_file_paths) . "\n" . get_file_size_info_string($virtual_disk_size_bytes)); return $virtual_disk_size_bytes; } ## end sub get_virtual_disk_size_bytes #////////////////////////////////////////////////////////////////////////////// =head2 copy_virtual_disk Parameters : $source_file_paths, $destination_file_path, $disk_format (optional) Returns : boolean Description : Calls qemu-img to copy a virtual disk image. The destination disk format can be specified as an argument. If omitted, qcow2 is used. =cut sub copy_virtual_disk { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my $source_file_path_argument = shift; my $destination_file_path = shift; if (!$source_file_path_argument || !$destination_file_path) { notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, source and destination file path arguments were not passed"); return; } my @source_file_paths; if (!ref($source_file_path_argument)) { push @source_file_paths, $source_file_path_argument; } elsif (ref($source_file_path_argument) eq 'ARRAY') { @source_file_paths = @$source_file_path_argument; } else { notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, source file path argument was passed as a reference and type is not ARRAY"); return; } # Get the disk format argument my $disk_format = shift || $self->data->get_vmhost_datastore_imagetype_name(); my $node_name = $self->data->get_vmhost_short_name(); # Get the size of all of the source files my $source_size_bytes = $self->get_virtual_disk_size_bytes(@source_file_paths) || 0; # Make sure the destination file extension matches the disk format my ($destination_file_name, $destination_directory_path, $destination_file_extension) = fileparse($destination_file_path, qr/\.[^.]*/); if (!$destination_file_extension) { notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, file extension could not be determined from destination file path: $destination_file_path"); return; } elsif ($destination_file_extension !~ /^\.?$disk_format$/i) { notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, extension of destination file '$destination_file_extension' is not '$disk_format': $destination_file_path"); return; } # Remove trailing space from directory path $destination_directory_path =~ s/\/+$//; # Attempt to create the parent directory if (!$self->vmhost_os->create_directory($destination_directory_path)) { notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, failed to create destination parent directory: $destination_directory_path"); return; } # Copy the XML file if it exists (saved 'virsh dumpxml' from image capture) my ($source_file_name, $source_directory_path, $source_file_extension) = fileparse($source_file_paths[0], qr/\.[^.]*/); my $source_xml_file_path = "$source_directory_path/$source_file_name.xml"; if ($self->vmhost_os->file_exists($source_xml_file_path)) { my $destination_xml_file_path = "$destination_directory_path/$destination_file_name.xml"; $self->vmhost_os->copy_file($source_xml_file_path, $destination_xml_file_path) } my $source_file_count = scalar(@source_file_paths); my $source_file_paths_string; my $raw_file_directory_path; # Check if the source file paths appear to be in the 2GB sparse vmdk format # qemu-img included in anything earlier than Fedora 16 doesn't handle this properly #if ($source_file_count > 1 && $source_file_paths[0] =~ /-s\d+\.vmdk$/i) { # my $image_name = $self->data->get_image_name(); # $raw_file_directory_path = "$destination_directory_path/raw_$image_name"; # # # Attempt to create the directory where the raw files will be stored # if (!$self->vmhost_os->create_directory($raw_file_directory_path)) { # notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, failed to create temporary directory to store raw files: $raw_file_directory_path"); # return; # } # # for my $source_file_path (@source_file_paths) { # my ($source_file_name, $source_directory_path, $source_file_extension) = fileparse($source_file_path, qr/\.[^.]*/); # # my $raw_file_path = "$raw_file_directory_path/$source_file_name.raw"; # $source_file_paths_string .= "\"$raw_file_path\" "; # # ## Convert from raw to raw # ## There seems to be a bug in qemu-img if you specify "-f vmdk", it results in a empty file # ## Leaving the -f option off also results in an empty file # #my $command = "qemu-img convert -f raw \"$source_file_path\" -O raw \"$raw_file_path\" && qemu-img info \"$raw_file_path\""; # #notify($ERRORS{'DEBUG'}, 0, "attempting to convert vmdk file to raw format: $source_file_path --> $raw_file_path, command:\n$command"); # #my ($exit_status, $output) = $self->vmhost_os->execute($command, 0, 7200); # #if (!defined($exit_status)) { # # notify($ERRORS{'WARNING'}, 0, "failed to execute command to convert vmdk file to raw format:\n$command"); # # return; # #} # #elsif ($exit_status) { # # notify($ERRORS{'WARNING'}, 0, "failed to convert vmdk file to raw format on $node_name\ncommand: '$command'\noutput:\n" . join("\n", @$output)); # # return; # #} # #else { # # notify($ERRORS{'DEBUG'}, 0, "converted vmdk file to raw format on $node_name: $source_file_path --> $raw_file_path\ncommand: '$command'\noutput:\n" . join("\n", @$output)); # #} # } # # # Remove trailing last space # $source_file_paths_string =~ s/\s+$//; # # #my $raw_file_path_merged = "$raw_file_directory_path/$image_name.raw"; # #my $cat_command = "cat $source_file_paths_string > \"$raw_file_path_merged\""; # #notify($ERRORS{'DEBUG'}, 0, "attempting to merge split raw files into $raw_file_path_merged, command:\n$cat_command"); # #my ($cat_exit_status, $cat_output) = $self->vmhost_os->execute($cat_command, 0, 7200); # #if (!defined($cat_exit_status)) { # # notify($ERRORS{'WARNING'}, 0, "failed to execute command to merge split raw files into $raw_file_path_merged, command: $cat_command"); # # return; # #} # #elsif ($cat_exit_status) { # # notify($ERRORS{'WARNING'}, 0, "failed to convert merge split raw files into $raw_file_path_merged\ncommand: '$cat_command'\noutput:\n" . join("\n", @$cat_output)); # # return; # #} # #else { # # notify($ERRORS{'DEBUG'}, 0, "merged split raw files into $raw_file_path_merged\ncommand: '$cat_command'\noutput:\n" . join("\n", @$cat_output)); # # $source_file_paths_string = "\"$raw_file_path_merged\""; # #} #} #else { # Join the array of file paths into a string $source_file_paths_string = '"' . join('" "', @source_file_paths) . '"'; #} my $parent_file = $source_file_name; $parent_file =~ s/-s[0-9]{3}//; $parent_file = $parent_file . ".vmdk"; if ($self->vmhost_os->file_exists("$source_directory_path$parent_file")) { $source_file_paths_string = "$source_directory_path$parent_file"; notify($ERRORS{'DEBUG'}, 0, "changing source_file_paths_string from multiple files to $source_directory_path$parent_file"); } my $options = ''; # VCL-911: If copying to the repository, save the image qcow2 version 0.10, the traditional image format that can be read by any QEMU since 0.10 my $repository_image_file_path = $self->get_repository_image_file_path(); if ($destination_file_path eq $repository_image_file_path) { $options .= ' -o compat=0.10'; } #my $command = "qemu-img convert -f vmdk -O $disk_format $source_file_paths_string \"$destination_file_path\" && qemu-img info \"$destination_file_path\""; my $command = "qemu-img convert $source_file_paths_string -O $disk_format"; $command .= $options; $command .= " \"$destination_file_path\""; $command .= " && qemu-img info \"$destination_file_path\""; ## If the image had to be converted to raw format first, add command to delete raw files #if ($raw_file_directory_path) { # $command .= " ; rm -f $raw_file_directory_path"; #} notify($ERRORS{'DEBUG'}, 0, "attempting to copy/convert virtual disk to $disk_format format --> $destination_file_path, command:\n$command"); my $start_time = time; my ($exit_status, $output) = $self->vmhost_os->execute($command, 0, 7200); if (defined($output) && (grep(/Unknown option.*compat/, @$output) || grep(/Invalid parameter.*compat/, @$output))) { # Check for older versions which don't support '-o compat=': # Unknown option 'compat' # qemu-img: Invalid options for file format 'qcow2'. # Remove the option from the command and try again $command =~ s/ -o compat=0.10//; notify($ERRORS{'DEBUG'}, 0, "version of qemu-img on $node_name does not appear to support the '-o compat=' option, trying again without it, output from first attempt:\n" . join("\n", @$output)); ($exit_status, $output) = $self->vmhost_os->execute($command, 0, 7200); } if (!defined($exit_status)) { notify($ERRORS{'WARNING'}, 0, "failed to execute command to copy/convert virtual disk on $node_name:\n$command"); return; } elsif ($exit_status) { notify($ERRORS{'WARNING'}, 0, "failed to copy/convert virtual disk on $node_name\ncommand: '$command'\noutput:\n" . join("\n", @$output)); return; } # Calculate how long it took to copy my $duration_seconds = (time - $start_time); my $minutes = ($duration_seconds / 60); $minutes =~ s/\..*//g; my $seconds = ($duration_seconds - ($minutes * 60)); if (length($seconds) == 0) { $seconds = "00"; } elsif (length($seconds) == 1) { $seconds = "0$seconds"; } my $destination_size_bytes = $self->get_virtual_disk_size_bytes($destination_file_path) || 0; # Get a string which displays various copy rate information my $copy_speed_info_string = get_copy_speed_info_string($destination_size_bytes, $duration_seconds); notify($ERRORS{'OK'}, 0, "copied virtual disk on $node_name, output:\n" . join("\n", @$output) . "\n---\n$copy_speed_info_string"); return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 create_copy_on_write_image Parameters : $master_image_file_path, $copy_on_write_file_path Returns : boolean Description : Calls qemu-img to create a copy on write virtual disk image based on the master image. The resulting image is written to by the VM when it makes changes to its hard disk. Multiple VMs may utilize the master image file. Each writes to its own copy on write image file. The master image file is not altered. =cut sub create_copy_on_write_image { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } my ($master_image_file_path, $copy_on_write_file_path, $disk_format) = @_; if (!$master_image_file_path || !$copy_on_write_file_path) { notify($ERRORS{'WARNING'}, 0, "unable to create copy on write image, master and copy on write image file path arguments were not passed"); return; } my $node_name = $self->data->get_vmhost_short_name(); if (!$disk_format) { $disk_format = $self->data->get_vmhost_datastore_imagetype_name(); } notify($ERRORS{'DEBUG'}, 0, "creating copy on write image on $node_name\nmaster disk image: $master_image_file_path\ncopy on write image: $copy_on_write_file_path\nformat: $disk_format"); my $command = "qemu-img create -f $disk_format -b \"$master_image_file_path\" \"$copy_on_write_file_path\""; my ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($exit_status)) { notify($ERRORS{'WARNING'}, 0, "failed to execute command to create copy on write image on $node_name: '$command'"); return; } elsif ($exit_status) { notify($ERRORS{'WARNING'}, 0, "failed to create copy on write image on $node_name, command: '$command', output:\n" . join("\n", @$output)); return; } else { notify($ERRORS{'DEBUG'}, 0, "created copy on write image: $copy_on_write_file_path, output:\n" . join("\n", @$output)); return 1; } } #////////////////////////////////////////////////////////////////////////////// =head2 update_windows_image Parameters : $virtual_disk_file_path Returns : boolean Description : Runs virt-win-reg to update the registry of the image specified by the $virtual_disk_file_path argument. The virt-win-reg utility is provided by libguestfs-tools. This subroutine returns true if virt-win-reg isn't installed. Adds registry keys to disable VMware services. If the image is Windows 5.x, registry keys are added to enable the builtin IDE drivers. This allows Windows images converted from VMware using a SCSI virtual disk to be loaded on KVM. =cut sub update_windows_image { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); return; } my $virtual_disk_file_path = shift; if (!$virtual_disk_file_path) { notify($ERRORS{'WARNING'}, 0, "virtual disk file path argument was not supplied"); return; } my $node_name = $self->data->get_vmhost_short_name(); # Construct a string containing .reg file contents # Add keys to disable VMware services if they are installed my $registry_contents .= <<'EOF'; Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\VClone] "Start"=dword:00000004 [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\vmci] "Start"=dword:00000004 [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\vmmouse] "Start"=dword:00000004 [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\vmscsi] "Start"=dword:00000004 [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\VMTools] "Start"=dword:00000004 [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\vmx_svga] "Start"=dword:00000004 [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\vmxnet] "Start"=dword:00000004 EOF # Check if the guest OS module is for Windows 5.x # Add registry entries to enable the Windows IDE drivers if ($self->os->isa('VCL::Module::OS::Windows::Version_5')) { notify($ERRORS{'DEBUG'}, 0, "guest OS is Windows 5.x, adding registry keys to enable IDE drivers"); $registry_contents .= <<'EOF'; [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\primary_ide_channel] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="atapi" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\secondary_ide_channel] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="atapi" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\*pnp0600] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="atapi" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\gendisk] "ClassGUID"="{4D36E967-E325-11CE-BFC1-08002BE10318}" "Service"="disk" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#cc_0101] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_0e11&dev_ae33] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_1039&dev_0601] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_1039&dev_5513] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_1042&dev_1000] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_105a&dev_4d33] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_1095&dev_0640] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_1095&dev_0646] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_1095&dev_0646&REV_05] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_1095&dev_0646&REV_07] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_1095&dev_0648] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_1095&dev_0649] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_1097&dev_0038] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_10ad&dev_0001] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_10ad&dev_0150] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_10b9&dev_5215] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_10b9&dev_5219] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_10b9&dev_5229] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="pciide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_1106&dev_0571] "Service"="pciide" "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_8086&dev_1222] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="intelide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_8086&dev_1230] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="intelide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_8086&dev_2411] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="intelide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_8086&dev_2421] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="intelide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_8086&dev_7010] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="intelide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_8086&dev_7111] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="intelide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\CriticalDeviceDatabase\pci#ven_8086&dev_7199] "ClassGUID"="{4D36E96A-E325-11CE-BFC1-08002BE10318}" "Service"="intelide" [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\atapi] "ErrorControl"=dword:00000001 "Group"="SCSI miniport" "Start"=dword:00000000 "Tag"=dword:00000019 "Type"=dword:00000001 "DisplayName"="Standard IDE/ESDI Hard Disk Controller" "ImagePath"=hex(2):53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,44,00,\ 52,00,49,00,56,00,45,00,52,00,53,00,5c,00,61,00,74,00,61,00,70,00,69,00,2e,\ 00,73,00,79,00,73,00,00,00 [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\IntelIde] "ErrorControl"=dword:00000001 "Group"="System Bus Extender" "Start"=dword:00000000 "Tag"=dword:00000004 "Type"=dword:00000001 "ImagePath"=hex(2):53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,44,00,\ 52,00,49,00,56,00,45,00,52,00,53,00,5c,00,69,00,6e,00,74,00,65,00,6c,00,69,\ 00,64,00,65,00,2e,00,73,00,79,00,73,00,00,00 [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\PCIIde] "ErrorControl"=dword:00000001 "Group"="System Bus Extender" "Start"=dword:00000000 "Tag"=dword:00000003 "Type"=dword:00000001 "ImagePath"=hex(2):53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,44,00,\ 52,00,49,00,56,00,45,00,52,00,53,00,5c,00,70,00,63,00,69,00,69,00,64,00,65,\ 00,2e,00,73,00,79,00,73,00,00,00 EOF } # Create a text file on the VM host containing the registry contents my $virtual_disk_file_base_name = fileparse($virtual_disk_file_path, qr/\.[^\.]*$/i); my $temp_reg_file_path = "/tmp/$virtual_disk_file_base_name.reg"; if (!$self->vmhost_os->create_text_file($temp_reg_file_path, $registry_contents)) { return; } # Attempt to run virt-win-reg to merge the registry contents into the registry on the virtual disk notify($ERRORS{'DEBUG'}, 0, "attempting to merge $temp_reg_file_path into $virtual_disk_file_path"); my $command = "virt-win-reg --merge $virtual_disk_file_path $temp_reg_file_path"; my ($exit_status, $output) = $self->vmhost_os->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to execute command to merge $temp_reg_file_path into $virtual_disk_file_path"); return; } elsif (grep(/command not found/i, @$output)) { notify($ERRORS{'OK'}, 0, "unable to merge $temp_reg_file_path into $virtual_disk_file_path, virt-win-reg is not installed on $node_name"); return 1; } elsif ($exit_status ne '0') { notify($ERRORS{'WARNING'}, 0, "failed to merge $temp_reg_file_path into $virtual_disk_file_path, exit status: $exit_status, command: '$command', output:\n" . join("\n", @$output)); return; } else { notify($ERRORS{'OK'}, 0, "merged $temp_reg_file_path into $virtual_disk_file_path"); } # Delete the temporary registry file on the VM host $self->vmhost_os->delete_file($temp_reg_file_path); return 1; } #////////////////////////////////////////////////////////////////////////////// =head2 query_windows_image_registry Parameters : $virtual_disk_file_path Returns : boolean Description : =cut sub query_windows_image_registry { my $self = shift; unless (ref($self) && $self->isa('VCL::Module')) { notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); return; } my $virtual_disk_file_path = shift; if (!$virtual_disk_file_path) { notify($ERRORS{'WARNING'}, 0, "virtual disk file path argument was not supplied"); return; } my $registry_key = shift; if (!$registry_key) { notify($ERRORS{'WARNING'}, 0, "registry key argument was not supplied"); return; } my $node_name = $self->data->get_vmhost_short_name(); # notify($ERRORS{'DEBUG'}, 0, "attempting to query registry key '$registry_key' in image '$virtual_disk_file_path'"); my $command = "virt-win-reg $virtual_disk_file_path \"$registry_key\""; my ($exit_status, $output) = $self->vmhost_os->execute("virt-win-reg $virtual_disk_file_path \"$registry_key\""); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to execute command to query registry key '$registry_key' in image '$virtual_disk_file_path'"); return; } elsif (grep(/command not found/i, @$output)) { notify($ERRORS{'OK'}, 0, "unable to query registry key in $virtual_disk_file_path, virt-win-reg is not installed on $node_name"); return 1; } elsif ($exit_status ne '0') { notify($ERRORS{'WARNING'}, 0, "failed to query registry key '$registry_key' in image '$virtual_disk_file_path', exit status: $exit_status\ncommand: $command\noutput:\n" . join("\n", @$output)); return; } my $registry_data = {}; my $current_key; LINE: for my $line (@$output) { if ($line =~ /^\[(.+)\]$/) { $current_key = $1; next LINE; } elsif ($line =~ /^"([^"]+)"=([^:]+):(.*)$/) { my $value = $1; my $type = $2; my $data = $3; my $converted_data = $self->os->reg_query_convert_data($type, $data); $registry_data->{$current_key}{$value} = $converted_data; next LINE; } else { notify($ERRORS{'WARNING'}, 0, "unable to parse virt-win-reg registry query output line: '$line'"); } } notify($ERRORS{'OK'}, 0, "queried registry key '$registry_key' in image '$virtual_disk_file_path':\n" . format_data($registry_data)); return $registry_data; } #////////////////////////////////////////////////////////////////////////////// 1; __END__ =head1 SEE ALSO L<http://cwiki.apache.org/VCL/> =cut