in managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm [611:1027]
sub copy_virtual_disk {
my $self = shift;
if (ref($self) !~ /VCL::Module/i) {
notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
return;
}
# Get the source and destination path arguments in the datastore path format
my $source_path = $self->_get_datastore_path(shift) || return;
my $destination_path = $self->_get_datastore_path(shift) || return;
# Make sure the source path ends with .vmdk
if ($source_path !~ /\.vmdk$/i || $destination_path !~ /\.vmdk$/i) {
notify($ERRORS{'WARNING'}, 0, "source and destination path arguments must end with .vmdk:\nsource path argument: $source_path\ndestination path argument: $destination_path");
return;
}
# Get the adapter type and disk type arguments if they were specified
# If not specified, set the default values
my $destination_disk_type = shift || 'thin';
# Fix the disk type in case 2gbsparse was passed
if ($destination_disk_type =~ /2gbsparse/i) {
$destination_disk_type = 'sparse2Gb';
}
# Check the disk type argument, the string must match exactly or the copy will fail
my @valid_disk_types = qw(eagerZeroedThick flatMonolithic preallocated raw rdm rdmp sparse2Gb sparseMonolithic thick thick2Gb thin);
if (!grep(/^$destination_disk_type$/, @valid_disk_types)) {
notify($ERRORS{'WARNING'}, 0, "disk type argument is not valid: '$destination_disk_type', it must exactly match (case sensitive) one of the following strings:\n" . join("\n", @valid_disk_types));
return;
}
my $vmhost_name = $self->data->get_vmhost_hostname();
my $datacenter_view = $self->_get_datacenter_view() || return;
my $virtual_disk_manager_view = $self->_get_virtual_disk_manager_view() || return;
my $file_manager = $self->_get_file_manager_view() || return;
my $source_datastore_name = $self->_get_datastore_name($source_path) || return;
my $destination_datastore_name = $self->_get_datastore_name($destination_path) || return;
my $source_datastore = $self->_get_datastore_object($source_datastore_name) || return;
my $destination_datastore = $self->_get_datastore_object($destination_datastore_name) || return;
my $destination_file_name = $self->_get_file_name($destination_path);
my $destination_base_name = $self->_get_file_base_name($destination_path);
# Get the source vmdk file info so the source adapter and disk type can be displayed
my $source_info = $self->_get_file_info($source_path) || return;
if (scalar(keys %$source_info) != 1) {
notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, multiple source files were found:\n" . format_data($source_info));
}
my $source_info_file_path = (keys(%$source_info))[0];
my $source_adapter_type = $source_info->{$source_info_file_path}{controllerType} || 'lsiLogic';
my $source_disk_type = $source_info->{$source_info_file_path}{diskType} || '';
my $source_file_size_bytes = $source_info->{$source_info_file_path}{fileSize} || '0';
my $source_file_capacity_kb = $source_info->{$source_info_file_path}{capacityKb} || '0';
my $source_file_capacity_bytes = ($source_file_capacity_kb * 1024);
# Set the destination adapter type to the source adapter type if it wasn't specified as an argument
my $destination_adapter_type = shift || $source_adapter_type;
if ($destination_adapter_type =~ /bus/i) {
$destination_adapter_type = 'busLogic';
}
elsif ($destination_adapter_type =~ /lsi/) {
$destination_adapter_type = 'lsiLogic';
}
else {
$destination_adapter_type = 'ide';
}
if ($source_adapter_type !~ /\w/ || $source_disk_type !~ /\w/ || $source_file_size_bytes !~ /\d/) {
notify($ERRORS{'WARNING'}, 0, "unable to retrieve adapter type, disk type, and file size of source file on VM host $vmhost_name: '$source_path', file info:\n" . format_data($source_info));
return;
}
# Get the destination partent directory path and create the directory
my $destination_directory_path = $self->_get_parent_directory_datastore_path($destination_path) || return;
$self->create_directory($destination_directory_path) || return;
# Override the die handler
local $SIG{__DIE__} = sub{};
# Create a virtual disk spec object
my $virtual_disk_spec = VirtualDiskSpec->new(
adapterType => $destination_adapter_type,
diskType => $destination_disk_type,
);
notify($ERRORS{'DEBUG'}, 0, "attempting to copy virtual disk on VM host $vmhost_name: '$source_path' --> '$destination_path'\n" .
"source adapter type: $source_adapter_type\n" .
"destination adapter type: $destination_adapter_type\n" .
"source disk type: $source_disk_type\n" .
"destination disk type: $destination_disk_type\n" .
"source capacity: " . get_file_size_info_string($source_file_capacity_bytes) . "\n" .
"source space used: " . get_file_size_info_string($source_file_size_bytes)
);
my $copy_virtual_disk_result;
eval {
$copy_virtual_disk_result = $virtual_disk_manager_view->CopyVirtualDisk(
sourceName => $source_path,
sourceDatacenter => $datacenter_view,
destName => $destination_path,
destDatacenter => $datacenter_view,
destSpec => $virtual_disk_spec,
force => 1
);
};
# Check if an error occurred
if (my $copy_virtual_disk_fault = $@) {
# Delete the destination directory path previously created
$self->delete_file($destination_directory_path);
if ($source_disk_type =~ /sparse/i && $copy_virtual_disk_fault =~ /FileNotFound/ && $self->is_multiextent_disabled()) {
notify($ERRORS{'WARNING'}, 0, "failed to copy vmdk on VM host $vmhost_name using CopyVirtualDisk function, likely because multiextent kernel module is disabled");
return;
}
elsif ($copy_virtual_disk_fault =~ /No space left/i) {
# Check if the output indicates there is not enough space to copy the vmdk
# Output will contain:
# Fault string: A general system error occurred: No space left on device
# Fault detail: SystemError
notify($ERRORS{'CRITICAL'}, 0, "failed to copy vmdk on VM host $vmhost_name using CopyVirtualDisk function, no space is left on the destination device: '$destination_path'\nerror:\n$copy_virtual_disk_fault");
return;
}
elsif ($copy_virtual_disk_fault =~ /not implemented/i) {
notify($ERRORS{'DEBUG'}, 0, "unable to copy vmdk using CopyVirtualDisk function, VM host $vmhost_name does not implement the CopyVirtualDisk function");
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to copy vmdk on VM host $vmhost_name using CopyVirtualDisk function: '$source_path' --> '$destination_path'\nerror:\n$copy_virtual_disk_fault");
#return;
}
}
else {
notify($ERRORS{'OK'}, 0, "copied vmdk on VM host $vmhost_name using CopyVirtualDisk function:\n" . format_data($copy_virtual_disk_result));
return $destination_path;
}
my $resource_pool_view = $self->_get_resource_pool_view() || return;
my $resource_pool_path = $self->_get_managed_object_path($resource_pool_view->{mo_ref});
my $vm_folder_view = $self->_get_vm_folder_view() || return;
my $vm_folder_path = $self->_get_managed_object_path($vm_folder_view->{mo_ref});
my $request_id = $self->data->get_request_id();
my $source_vm_name = $self->_clean_vm_name("source-$request_id\_$destination_base_name");
my $clone_vm_name = $self->_clean_vm_name($destination_base_name);
if ($clone_vm_name ne $destination_base_name) {
notify($ERRORS{'OK'}, 0, "virtual disk name shortened:\noriginal: $destination_base_name --> modified: $clone_vm_name");
$destination_base_name = $clone_vm_name;
$destination_path = "[$destination_datastore_name] $clone_vm_name/$clone_vm_name.vmdk";
}
my $source_vm_directory_path = "[$source_datastore_name] $source_vm_name";
my $clone_vm_directory_path = "[$destination_datastore_name] $clone_vm_name";
# Make sure the "source-..." directory doesn't exist or else the clone will fail
$self->delete_file($source_vm_directory_path);
# Check if VMs already exist using the source/clone directories
my @existing_vmx_file_paths = $self->get_vmx_file_paths();
for my $existing_vmx_file_path (@existing_vmx_file_paths) {
my $existing_vmx_directory_path = $self->_get_parent_directory_datastore_path($existing_vmx_file_path);
if ($existing_vmx_directory_path eq $source_vm_directory_path) {
notify($ERRORS{'WARNING'}, 0, "existing VM using the directory of the source VM will be deleted:\n" .
"source VM directory path: $source_vm_directory_path\n" .
"existing vmx file path: $existing_vmx_file_path"
);
# Unregister the VM, don't attempt to delete it or else the source vmdk may be deleted
# Don't fail if VM can't be unregistered, it may not be registered
$self->vm_unregister($existing_vmx_file_path);
}
elsif ($existing_vmx_directory_path eq $clone_vm_directory_path) {
notify($ERRORS{'WARNING'}, 0, "existing VM using the directory of the VM clone will be deleted:\n" .
"clone VM directory path: $clone_vm_directory_path\n" .
"existing vmx file path: $existing_vmx_file_path"
);
return unless $self->delete_vm($existing_vmx_file_path);
}
}
# Make sure the source and clone directories don't exist
# Otherwise the VM creation/cloning process will create another directory with '_1' appended and the files won't be deleted
if ($self->file_exists($source_vm_directory_path)) {
notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, source VM directory path already exists: $source_vm_directory_path");
return;
}
if ($self->file_exists($clone_vm_directory_path)) {
notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, clone VM directory path already exists: $clone_vm_directory_path");
return;
}
# Create a virtual machine on top of this virtual disk
# First, create a controller for the virtual disk
my $controller;
if ($destination_adapter_type eq 'lsiLogic') {
$controller = VirtualLsiLogicController->new(
key => 0,
device => [0],
busNumber => 0,
sharedBus => VirtualSCSISharing->new('noSharing')
);
}
else {
$controller = VirtualBusLogicController->new(
key => 0,
device => [0],
busNumber => 0,
sharedBus => VirtualSCSISharing->new('noSharing')
);
}
# Next create a disk type (it will be the same as the source disk)
my $disk_backing_info = ($source_disk_type)->new(
datastore => $source_datastore,
fileName => $source_path,
diskMode => "independent_persistent"
);
# Create the actual virtual disk
my $source_vm_disk = VirtualDisk->new(
key => 0,
backing => $disk_backing_info,
capacityInKB => $source_file_capacity_kb,
controllerKey => 0,
unitNumber => 0
);
# Create the specification for creating a source VM
my $source_vm_config = VirtualMachineConfigSpec->new(
name => $source_vm_name,
deviceChange => [
VirtualDeviceConfigSpec->new(
operation => VirtualDeviceConfigSpecOperation->new('add'),
device => $controller
),
VirtualDeviceConfigSpec->new(
operation => VirtualDeviceConfigSpecOperation->new('add'),
device => $source_vm_disk
)
],
files => VirtualMachineFileInfo->new(
logDirectory => $source_vm_directory_path,
snapshotDirectory => $source_vm_directory_path,
suspendDirectory => $source_vm_directory_path,
vmPathName => $source_vm_directory_path
)
);
notify($ERRORS{'DEBUG'}, 0, <<EOF
attempting to create temporary VM to be cloned:
VM name: '$source_vm_name'
VM directory path: '$source_vm_directory_path'
VM folder: '$vm_folder_path'
resource pool: '$resource_pool_path'
source disk path: '$source_path'
source adapter type: '$source_adapter_type'
source disk type: '$source_disk_type'
EOF
);
# Create a temporary VM which will be cloned
my $source_vm_view;
notify($ERRORS{'DEBUG'}, 0, "creating temporary source VM which will be cloned in order to copy its virtual disk: $source_vm_name");
eval {
my $source_vm = $vm_folder_view->CreateVM(
config => $source_vm_config,
pool => $resource_pool_view
);
if ($source_vm) {
notify($ERRORS{'DEBUG'}, 0, "created temporary source VM which will be cloned: $source_vm_name");
$source_vm_view = $self->_get_view($source_vm);
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to create temporary source VM which will be cloned: $source_vm_name");
return;
}
};
if (my $fault = $@) {
notify($ERRORS{'WARNING'}, 0, "failed to create temporary source VM which will be cloned on VM host $vmhost_name: $source_vm_name\nerror:\n$fault");
return;
}
my $source_vm_vmx_path = $source_vm_view->config->files->vmPathName;
# Create the specification for cloning the VM
my $clone_spec = VirtualMachineCloneSpec->new(
config => VirtualMachineConfigSpec->new(
name => $clone_vm_name,
files => VirtualMachineFileInfo->new(
logDirectory => $clone_vm_directory_path,
snapshotDirectory => $clone_vm_directory_path,
suspendDirectory => $clone_vm_directory_path,
vmPathName => $clone_vm_directory_path
)
),
powerOn => 0,
template => 0,
location => VirtualMachineRelocateSpec->new(
datastore => $destination_datastore,
pool => $resource_pool_view,
diskMoveType => 'moveAllDiskBackingsAndDisallowSharing',
transform => VirtualMachineRelocateTransformation->new('sparse'),
)
);
notify($ERRORS{'DEBUG'}, 0, "attempting to create clone of temporary VM:\n" .
"clone VM name: $source_vm_name\n" .
"clone VM directory path: $clone_vm_directory_path"
);
# Clone the temporary VM, thus creating a copy of its virtual disk
notify($ERRORS{'DEBUG'}, 0, "attempting to clone VM: $source_vm_name --> $clone_vm_name\nclone VM directory path: '$clone_vm_directory_path'");
my $clone_vm;
my $clone_vm_view;
eval {
$clone_vm = $source_vm_view->CloneVM(
folder => $vm_folder_view,
name => $clone_vm_name,
spec => $clone_spec
);
};
if ($clone_vm) {
$clone_vm_view = $self->_get_view($clone_vm);
notify($ERRORS{'DEBUG'}, 0, "cloned VM: $source_vm_name --> $clone_vm_name");
}
else {
if (my $fault = $@) {
if ($source_disk_type =~ /sparse/i && $fault =~ /FileNotFound/ && $self->is_multiextent_disabled()) {
notify($ERRORS{'WARNING'}, 0, "failed to clone VM on VM host $vmhost_name, likely because multiextent kernel module is disabled");
}
elsif ($fault =~ /No space left/i) {
# Check if the output indicates there is not enough space to copy the vmdk
# Output will contain:
# Fault string: A general system error occurred: No space left on device
# Fault detail: SystemError
notify($ERRORS{'CRITICAL'}, 0, "failed to clone VM on VM host $vmhost_name, no space is left on the destination device: '$destination_path'\nerror:\n$fault");
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to clone VM on VM host $vmhost_name: '$source_path' --> '$destination_path'\nerror:\n$fault");
}
}
else {
notify($ERRORS{'WARNING'}, 0, "failed to clone VM: $source_vm_name --> $clone_vm_name");
}
# Delete the source VM which could not be cloned
if (!$source_vm_vmx_path) {
notify($ERRORS{'WARNING'}, 0, "source VM not deleted, unable to determine vmx file path");
}
elsif ($source_vm_vmx_path !~ /\.vmx$/i) {
notify($ERRORS{'WARNING'}, 0, "source VM not deleted, vmPathName does not end with '.vmx': $source_vm_vmx_path");
}
else {
$self->delete_vm($source_vm_vmx_path);
}
return;
}
notify($ERRORS{'DEBUG'}, 0, "deleting source VM: $source_vm_name");
$self->vm_unregister($source_vm_view);
notify($ERRORS{'DEBUG'}, 0, "deleting source VM directory: $source_vm_directory_path");
$self->delete_file($source_vm_directory_path);
notify($ERRORS{'DEBUG'}, 0, "deleting cloned VM: $clone_vm_name");
$self->vm_unregister($clone_vm_view);
# Get all files created for cloned VM
my @clone_files = $self->find_files($clone_vm_directory_path, '*', 1);
# Delete all non-vmdk files
for my $clone_file_path (grep(!/\.(vmdk)$/i, @clone_files)) {
notify($ERRORS{'DEBUG'}, 0, "deleting clone VM file: $clone_file_path");
$self->delete_file($clone_file_path);
}
# Get the clone vmdk file names
my (@clone_vmdk_file_paths) = grep(/\.(vmdk)$/i, @clone_files);
if (@clone_vmdk_file_paths) {
my ($clone_vmdk_file_path) = grep(/$clone_vm_name-\d+\.vmdk$/, @clone_vmdk_file_paths);
if ($clone_vmdk_file_path) {
my $clone_vmdk_file_name = $self->_get_file_name($clone_vmdk_file_path);
notify($ERRORS{'OK'}, 0, "clone vmdk name is different than requested, attempting to rename clone vmdk: $clone_vmdk_file_name --> $destination_file_name");
if (!$self->move_virtual_disk($clone_vmdk_file_path, $destination_path)) {
$self->delete_file($clone_vm_directory_path);
return;
}
}
else {
notify($ERRORS{'DEBUG'}, 0, "clone vmdk name matches requested:\n" . join("\n", @clone_vmdk_file_paths));
}
}
else {
notify($ERRORS{'WARNING'}, 0, "no file created for clone VM ends with .vmdk:\n" . join("\n", @clone_files));
return;
}
# Set this as a class value so that it is retrievable from within
# the calling context, i.e. capture(), routine. This way, in case
# the name changes, it is possible to update the database with the new value.
notify($ERRORS{'OK'}, 0, "copied virtual disk on VM host $vmhost_name: '$source_path' --> '$destination_path'");
return $destination_path;
}