blob: decfb4d2cdb430033615c3c55d3a30629eca7de0 [file] [log] [blame]
#!/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