Modified: vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm?rev=1677997&r1=1677996&r2=1677997&view=diff ============================================================================== --- vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm (original) +++ vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm Wed May 6 14:07:44 2015 @@ -1410,7 +1410,7 @@ sub remove_existing_vms { } # Check if the vmx directory name matches the naming convention VCL would use for the computer - my $vmx_file_path_computer_name = $self->_get_vmx_file_path_computer_name($vmx_file_path); + my $vmx_file_path_computer_name = $self->_get_file_path_computer_name($vmx_file_path); if (!$vmx_file_path_computer_name) { #notify($ERRORS{'DEBUG'}, 0, "ignoring existing vmx file $vmx_file_name, the computer name could not be determined from the directory name"); next; @@ -1436,7 +1436,7 @@ sub remove_existing_vms { # Check if any of the paths match the format of a directory VCL would have created for the computer for my $orphaned_vmx_file_path (@orphaned_vmx_file_paths) { # Check if the directory name matches the naming convention VCL would use for the computer - my $orphaned_computer_name = $self->_get_vmx_file_path_computer_name($orphaned_vmx_file_path); + my $orphaned_computer_name = $self->_get_file_path_computer_name($orphaned_vmx_file_path); if (!$orphaned_computer_name) { #notify($ERRORS{'DEBUG'}, 0, "ignoring existing file path '$orphaned_vmx_file_path', the computer name could not be determined from the directory name"); next; @@ -2326,16 +2326,13 @@ sub reclaim_vmhost_disk_space { my $total_deletable_vmdk_size = 0; # Retrieve a list of existing vmdk directories matching the VCL naming convention - # Get a list of the files and directories under the vmdk base directory - my @vmdk_base_directory_contents = $self->vmhost_os->find_files($vmdk_base_directory_path, '*'); - for my $vmdk_base_directory_entry (@vmdk_base_directory_contents) { - if ($vmdk_base_directory_entry =~ /^$vmdk_base_directory_path\/(vmware\w+-[^\/]+\d+-v\d+)$/) { - my $vmdk_directory_name = $1; - notify($ERRORS{'DEBUG'}, 0, "found existing vmdk directory: '$vmdk_directory_name'"); - $vmdk_directories->{$vmdk_base_directory_entry} = {} - } + my @vmdk_base_directory_contents = $self->get_datastore_imagerevision_names($vmdk_base_directory_path); + for my $vmdk_directory_name (@vmdk_base_directory_contents) { + $vmdk_directories->{"$vmdk_base_directory_path/$vmdk_directory_name"} = {} } - notify($ERRORS{'DEBUG'}, 0, "retrieved list of existing vmdk directories under '$vmdk_base_directory_path':\n" . join("\n", sort keys %$vmdk_directories)); + + my $vmdk_directory_count = scalar(keys %$vmdk_directories); + notify($ERRORS{'DEBUG'}, 0, "retrieved list of existing vmdk directories under '$vmdk_base_directory_path' ($vmdk_directory_count):\n" . join("\n", sort keys %$vmdk_directories)); # Find VMs that can can be deleted my @vmx_file_paths = $self->get_vmx_file_paths(); @@ -4267,9 +4264,18 @@ sub is_vmdk_directory_shared { return 1; } else { - notify($ERRORS{'DEBUG'}, 0, "vmdk directory does NOT appear to be shared: '$vmdk_directory_path', directory name '$vmdk_directory_name' does NOT begin with the name of an OS in the database:\n" . join("\n", @os_names)); + notify($ERRORS{'DEBUG'}, 0, "vmdk directory name '$vmdk_directory_name' does NOT begin with the name of an OS in the database"); + } + + my $file_path_computer_name = $self->_get_file_path_computer_name($vmdk_directory_path); + if ($file_path_computer_name) { + notify($ERRORS{'DEBUG'}, 0, "vmdk directory does NOT appear to be shared: '$vmdk_directory_path', it contains a computer name: $file_path_computer_name"); return 0; } + else { + notify($ERRORS{'DEBUG'}, 0, "vmdk directory may be shared: '$vmdk_directory_path', it does NOT appear to contain a computer name"); + return 1; + } } #///////////////////////////////////////////////////////////////////////////// @@ -5271,7 +5277,7 @@ sub get_vmx_info { if (!defined($vmx_info{computer_id})) { notify($ERRORS{'DEBUG'}, 0, "vmx file does not contain a computer_id value, attempting to determine matching computer"); - my $computer_name = $self->_get_vmx_file_path_computer_name($vmx_file_path); + my $computer_name = $self->_get_file_path_computer_name($vmx_file_path); if ($computer_name) { my @computer_ids = get_computer_ids($computer_name); if ((scalar(@computer_ids) == 1)) { @@ -5450,51 +5456,30 @@ sub delete_vm { return; } - my $vmdk_base_directory_path_shared = $self->get_vmdk_base_directory_path_shared(); + my $vmx_file_base_name = $self->_get_file_base_name($vmx_file_path); + my $vmx_directory_path = $self->_get_parent_directory_normal_path($vmx_file_path); notify($ERRORS{'DEBUG'}, 0, "attempting to delete VM: $vmx_file_path"); - delete $self->{vmx_info}{$vmx_file_path}; + + my $vmx_info; + if ($self->vmhost_os->file_exists($vmx_file_path)) { + $vmx_info = $self->get_vmx_info($vmx_file_path); + } + + my @virtual_disks; if ($self->is_vm_registered($vmx_file_path)) { - # Make sure the VM is powered off - $self->api->power_off($vmx_file_path); + # The VM needs to be registered for get_vm_virtual_disk_file_paths to work + @virtual_disks = $self->api->get_vm_virtual_disk_file_paths($vmx_file_path); - # Unregister the VM if (!$self->api->vm_unregister($vmx_file_path)) { notify($ERRORS{'WARNING'}, 0, "failed to unregister VM: $vmx_file_path, VM not deleted"); return; } } - # Get the vmx info - my $vmx_info = $self->get_vmx_info($vmx_file_path); - my $vmx_directory_path; - if ($vmx_info) { - $vmx_directory_path = $vmx_info->{vmx_directory_path}; - } - else { - # Failed to retrieve vmx info - # VM may have been registered but the vmx file/directory was deleted - # Check if the vmx file exists - # Display a warning and return false if it does exist - # Return true if it doesn't exist - if ($self->vmhost_os->file_exists($vmx_file_path)) { - notify($ERRORS{'WARNING'}, 0, "failed to delete VM, vmx info could not be retrieved: $vmx_file_path"); - return; - } - - # Check if the vmx parent directory is named after the vmx file - # If so, it's safe to delete the directory - my $vmx_file_base_name = $self->_get_file_base_name($vmx_file_path); - $vmx_directory_path = $self->_get_parent_directory_normal_path($vmx_file_path) || ''; - if ($vmx_directory_path && $vmx_directory_path =~ /\/$vmx_file_base_name$/) { - notify($ERRORS{'OK'}, 0, "deleted VM, successfully unregistered VM, vmx file does not exist: $vmx_file_path, deleting vmx parent directory: $vmx_directory_path, directory name matches the vmx file base name: $vmx_file_base_name"); - } - else { - notify($ERRORS{'OK'}, 0, "deleted VM, successfully unregistered VM but it vmx directory was not deleted because the vmx file does not exist: $vmx_file_path, vmx parent directory name ($vmx_directory_path) does not match the vmx file base name: $vmx_file_base_name"); - return 1; - } - } + delete $self->{vmx_info}{$vmx_file_path}; + my $deleted_directories = {}; # Delete the vmx directory my $attempt = 0; @@ -5511,6 +5496,7 @@ sub delete_vm { if ($self->vmhost_os->delete_file($vmx_directory_path)) { notify($ERRORS{'DEBUG'}, 0, "deleted vmx directory: $vmx_directory_path"); + $deleted_directories->{$vmx_directory_path} = 1; last DELETE_VMX_ATTEMPT; } else { @@ -5521,52 +5507,46 @@ sub delete_vm { } } - # Delete the vmdk files belonging to the VM - for my $storage_identifier (keys %{$vmx_info->{vmdk}}) { - my $vmdk_directory_path = $vmx_info->{vmdk}{$storage_identifier}{vmdk_directory_path}; - if ($vmdk_directory_path eq $vmx_directory_path) { - notify($ERRORS{'DEBUG'}, 0, "vmdk directory matches the vmx directory path which has already been deleted: $vmdk_directory_path"); + my $vmdk_file_paths = {}; + for my $virtual_disk_array_ref (@virtual_disks) { + for my $vmdk_file_path (@$virtual_disk_array_ref) { + $vmdk_file_paths->{$self->_get_normal_path($vmdk_file_path)} = 1; + } + } + for my $controller (keys %{$vmx_info->{vmdk}}) { + my $vmdk_file_path = $vmx_info->{vmdk}{$controller}{vmdk_file_path} || next; + $vmdk_file_paths->{$self->_get_normal_path($vmdk_file_path)} = 1; + } + + for my $vmdk_file_path (keys %$vmdk_file_paths) { + my $vmdk_directory_path = $self->_get_parent_directory_normal_path($vmdk_file_path) || next; + if ($deleted_directories->{$vmdk_directory_path}) { + notify($ERRORS{'DEBUG'}, 0, "directory containing vmdk file was already deleted: $vmdk_file_path"); next; } # Check if the directory containing the vmdk is shared among different VMs or dedicated to the VM being deleted my $vmdk_directory_shared = $self->is_vmdk_directory_shared($vmdk_directory_path); if (!defined($vmdk_directory_shared)) { - notify($ERRORS{'DEBUG'}, 0, "vmdk directory will NOT be deleted, unable to determine if vmdk appears to be shared: $vmdk_directory_path"); + notify($ERRORS{'DEBUG'}, 0, "vmdk directory will NOT be deleted, unable to determine if vmdk directory is shared: $vmdk_directory_path"); + next; } elsif ($vmdk_directory_shared) { - # Directory is shared, entire directory can't be deleted - notify($ERRORS{'DEBUG'}, 0, "vmdk directory will NOT be deleted because the vmdk appears to be shared: $vmdk_directory_path"); - - # Directory can't be deleted, check if individual files can be deleted - my $vmdk_file_path = $vmx_info->{vmdk}{$storage_identifier}{vmdk_file_path}; - if ($self->is_vmdk_file_shared($vmdk_file_path)) { - notify($ERRORS{'DEBUG'}, 0, "vmdk file will NOT be deleted because the vmdk appears to be shared: $vmdk_file_path"); - } - else { - # vmdk file not shared, can be deleted - # Find all files matching pattern in order to remove all files - # For snapshots, the vmdk will in the format: - # vmwarewin7-Windows764bit1846-v3-000001.vmdk - # Also have to delete the delta file: - # vmwarewin7-Windows764bit1846-v3-000001-delta.vmd - my $vmdk_file_base_name = $vmx_info->{vmdk}{$storage_identifier}{vmdk_file_base_name}; - my $vmdk_search_path = "$vmdk_directory_path/$vmdk_file_base_name*.vmdk"; - if (!$self->vmhost_os->delete_file($vmdk_search_path)) { - notify($ERRORS{'WARNING'}, 0, "failed to delete vmdk files: $vmdk_search_path"); - return; - } - } + notify($ERRORS{'DEBUG'}, 0, "vmdk directory will NOT be deleted because it may be shared with other VMs: $vmdk_directory_path"); + next; + } + + if ($self->vmhost_os->delete_file($vmdk_directory_path)) { + notify($ERRORS{'OK'}, 0, "directory containing vmdk file deleted: $vmdk_file_path"); + $deleted_directories->{$vmdk_directory_path} = 1; } else { - notify($ERRORS{'DEBUG'}, 0, "vmdk directory does NOT appear to be shared, attempting to delete directory: $vmdk_directory_path"); - if (!$self->vmhost_os->delete_file($vmdk_directory_path)) { - notify($ERRORS{'WARNING'}, 0, "failed to delete vmdk directory: $vmdk_directory_path"); - return; - } + notify($ERRORS{'WARNING'}, 0, "failed to delete directory containing vmdk file: $vmdk_file_path"); } } + # TODO: delete orphaned directories named after the VM + notify($ERRORS{'OK'}, 0, "deleted VM: $vmx_file_path"); return 1; } @@ -7340,7 +7320,7 @@ sub _get_parent_directory_name { notify($ERRORS{'WARNING'}, 0, "unable to determine parent directory name, path argument could not be converted to a datastore path: '$path_argument'"); return; } - + if ($datastore_path =~ /^\[.+\]$/) { notify($ERRORS{'WARNING'}, 0, "unable to determine parent directory name, path argument is the root path of a datastore: '$path_argument'"); return; @@ -7348,7 +7328,7 @@ sub _get_parent_directory_name { my ($parent_directory_name) = $datastore_path =~ /[\s\/]([^\/]+)\/[^\/]+$/g; if (!$parent_directory_name) { - notify($ERRORS{'WARNING'}, 0, "unable to determine parent directory name from path: '$path_argument'"); + notify($ERRORS{'WARNING'}, 0, "unable to determine parent directory name from path: '$path_argument' ($datastore_path)"); return; } @@ -7496,19 +7476,18 @@ sub _get_relative_datastore_path { #///////////////////////////////////////////////////////////////////////////// -=head2 _get_vmx_file_path_computer_name +=head2 _get_file_path_computer_name - Parameters : $vmx_file_path + Parameters : $file_path Returns : string - Description : Attempts to determine the computer name from the vmx file path - argument. Undefined is returned if the computer name cannot be - determined. - '/vmfs/volumes/vmpath/vmwarewinxp-ArcGIS93Desktop1301-v15ve1-71/vmwarewinxp-ArcGIS93Desktop1301-v15ve1-72.vmx' --> 've1-72' + Description : Attempts to determine the computer name from the path argument. + Undefined is returned if the computer name cannot be determined. '/vmfs/volumes/vmpath/ve1-72_1036-v2/ve1-72_1036-v2.vmx' --> 've1-72' + '/vmfs/volumes/vmpath/ve1-72_1036-v2' --> 've1-72' =cut -sub _get_vmx_file_path_computer_name { +sub _get_file_path_computer_name { my $self = shift; if (ref($self) !~ /module/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); @@ -7522,29 +7501,36 @@ sub _get_vmx_file_path_computer_name { return; } - my $directory_name = $self->_get_parent_directory_name($path_argument); - if (!$directory_name) { - notify($ERRORS{'WARNING'}, 0, "unable to determine computer name from path '$path_argument', directory name could not be determined from path"); - return; - } + my $computer_name; + my $regex_pattern = '^([\w\-]+)\_\d+-v\d+([_]|$)'; my $file_name = $self->_get_file_name($path_argument); - if (!$directory_name) { + if (!$file_name) { notify($ERRORS{'WARNING'}, 0, "unable to determine computer name from path '$path_argument', file name could not be determined from path"); return; } - my ($computer_name) = $directory_name =~ /^([\w\-]+)\_\d+-v\d+$/; - if (!$computer_name) { - ($computer_name) = $directory_name =~ /^vmware.*\d+-v\d+([\w\-]+)$/i; + if ($file_name =~ /\./) { + my $directory_name = $self->_get_parent_directory_name($path_argument); + if ($directory_name) { + ($computer_name) = $directory_name =~ /$regex_pattern/; + if ($computer_name) { + notify($ERRORS{'DEBUG'}, 0, "determined computer name '$computer_name' from directory name: $directory_name, argument: $path_argument"); + return $computer_name; + } + else { + notify($ERRORS{'DEBUG'}, 0, "directory name '$directory_name' does not appear to contain a computer name, argument: $path_argument"); + } + } } + ($computer_name) = $file_name =~ /$regex_pattern/; if ($computer_name) { - #notify($ERRORS{'DEBUG'}, 0, "determined computer name '$computer_name' from directory name: '$directory_name'"); + notify($ERRORS{'DEBUG'}, 0, "determined computer name '$computer_name' from file/directory name: $file_name, argument: $path_argument"); return $computer_name; } else { - notify($ERRORS{'DEBUG'}, 0, "computer name could not be determined from directory name: '$directory_name'"); + notify($ERRORS{'DEBUG'}, 0, "computer name could not be determined from path: $path_argument"); return; } } @@ -8586,45 +8572,41 @@ sub get_datastore_imagerevision_names { #///////////////////////////////////////////////////////////////////////////// -=head2 configure_vmhost_ssh_keys +=head2 configure_root_ssh_key Parameters : none Returns : boolean Description : Creates SSH private and public key files on the VM host to allow - the root account on the host to authenticate to other hosts. This - subroutine does not configure SSH keys related to the management - node. It is used to configure SSH for VM host to VM host - communication. + the root account on the host to authenticate to other hosts. This + subroutine does not configure SSH keys related to the management + node. It is used to configure SSH for VM host to VM host + communication. =cut -sub configure_vmhost_ssh_keys { +sub configure_root_ssh_key { my $self = shift; if (ref($self) !~ /VMware/i) { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } - my $management_node_name = $self->data->get_management_node_short_name(); my $vmhost_name = $self->data->get_vmhost_short_name(); - my $local_private_key_file_path = "/tmp/$vmhost_name.key"; - my $local_public_key_file_path = "$local_private_key_file_path.pub"; - my $vmhost_private_key_file_path = "/.ssh/id_rsa"; my $vmhost_public_key_file_path = "$vmhost_private_key_file_path.pub"; - # Delete local files previously created - $self->mn_os->delete_file("$local_private_key_file_path*"); - my $vmware_product_name = $self->get_vmhost_product_name(); - - if ($self->vmhost_os->file_exists($vmhost_private_key_file_path)) { - notify($ERRORS{'DEBUG'}, 0, "private key file already exists on $vmhost_name: $vmhost_private_key_file_path"); + my $bits; + if ($vmware_product_name =~ /4\./) { + $bits = 768; } else { - notify($ERRORS{'DEBUG'}, 0, "private key file does not already exist on $vmhost_name: $vmhost_private_key_file_path"); - + $bits = 1024; + } + + # Check if the private key already exists + if (!$self->vmhost_os->file_exists($vmhost_private_key_file_path)) { # Private key does not exist on VM host if ($self->vmhost_os->file_exists($vmhost_public_key_file_path)) { notify($ERRORS{'WARNING'}, 0, "public key file exists on $vmhost_name but private key file does not, deleting public key file: $vmhost_public_key_file_path"); @@ -8634,199 +8616,207 @@ sub configure_vmhost_ssh_keys { } } - if ($vmware_product_name =~ /4\./) { - # Create the private key - my $command = "/bin/dropbearkey -t rsa -f $vmhost_private_key_file_path -s 768"; - my ($exit_status, $output) = $self->vmhost_os->execute($command); - if (!defined($output)) { - notify($ERRORS{'WARNING'}, 0, "failed to execute command to create SSH private key on VM host $vmhost_name: $command"); - return; - } - elsif ($exit_status ne 0) { - notify($ERRORS{'WARNING'}, 0, "failed to create SSH private key on VM host, exit status: $exit_status, command: $command, output:\n" . join("\n", @$output)); - return; - } - # Make sure the newly created file exists - if (!$self->vmhost_os->file_exists($vmhost_private_key_file_path)) { - notify($ERRORS{'WARNING'}, 0, "attempted to create SSH private key file on VM host $vmhost_name but file still does not exist: $vmhost_private_key_file_path, command: $command, output:\n" . join("\n", @$output)); - return; - } - } - else { - # Generate a new SSH key for the VM host - if (!$self->mn_os->generate_ssh_key_files($local_private_key_file_path, 'rsa', 1024, $vmhost_name)) { - notify($ERRORS{'WARNING'}, 0, "failed to generate private SSH key file for VM host $vmhost_name"); - $self->mn_os->delete_file("$local_private_key_file_path*"); - return; - } - - # Copy files to the VM host - if (!$self->vmhost_os->copy_file_to($local_private_key_file_path, $vmhost_private_key_file_path)) { - notify($ERRORS{'WARNING'}, 0, "failed to copy private key file from $management_node_name to VM host $vmhost_name: $local_private_key_file_path --> $vmhost_private_key_file_path"); - $self->mn_os->delete_file("$local_private_key_file_path*"); - return; - } - if (!$self->vmhost_os->copy_file_to($local_public_key_file_path, $vmhost_public_key_file_path)) { - notify($ERRORS{'WARNING'}, 0, "failed to copy public key file from $management_node_name to VM host $vmhost_name: $local_public_key_file_path --> $vmhost_public_key_file_path"); - $self->mn_os->delete_file("$local_private_key_file_path*"); - return; - } - } - - notify($ERRORS{'DEBUG'}, 0, "created SSH private key file on VM host $vmhost_name: $vmhost_private_key_file_path"); - $self->mn_os->delete_file("$local_private_key_file_path*"); - } - - # Check if the public key exists - if ($self->vmhost_os->file_exists($vmhost_public_key_file_path)) { - notify($ERRORS{'DEBUG'}, 0, "private and public SSH key files already exist on VM host $vmhost_name"); - return 1; - } - - # Attempt to retrieve the private key file from the VM host - notify($ERRORS{'DEBUG'}, 0, "public key file does not exist on VM host $vmhost_name: $vmhost_public_key_file_path"); - - my $public_key_string; - if ($vmware_product_name =~ /4\./) { - # Retrieve the public key from the private key - my $command = "/bin/dropbearkey -f $vmhost_private_key_file_path -y"; - my ($exit_status, $output) = $self->vmhost_os->execute($command); - if (!defined($output)) { - notify($ERRORS{'WARNING'}, 0, "failed to execute command to retrieve SSH public key from VM host $vmhost_name: $command"); - return; - } - elsif ($exit_status ne 0) { - notify($ERRORS{'WARNING'}, 0, "failed to retrieve SSH public key from VM host $vmhost_name, exit status: $exit_status, command: $command, output:\n" . join("\n", @$output)); - return; - } - - # Locate the output line beginning with 'ssh-' - ($public_key_string) = grep(/^ssh-/, @$output); - if ($public_key_string) { - notify($ERRORS{'DEBUG'}, 0, "retrieved SSH public key from VM host $vmhost_name: $public_key_string"); - } - else { - notify($ERRORS{'WARNING'}, 0, "failed to retrieve SSH public key from VM host $vmhost_name, command: $command, output does not contain a line beginning with 'ssh-':\n" . join("\n", @$output)); - return; - } - - if ($self->vmhost_os->create_text_file($vmhost_public_key_file_path, $public_key_string)) { - notify($ERRORS{'OK'}, 0, "created SSH public key file on VM host $vmhost_name: $vmhost_public_key_file_path"); - } - else { - notify($ERRORS{'WARNING'}, 0, "failed to create SSH public key file on VM host $vmhost_name: $vmhost_public_key_file_path"); + # Create the private key file + if (!$self->vmhost_os->generate_ssh_private_key_file($vmhost_private_key_file_path, 'rsa', $bits, $vmhost_name)) { return; } } - else { - if (!$self->vmhost_os->copy_file_from($vmhost_private_key_file_path, $local_private_key_file_path)) { - notify($ERRORS{'WARNING'}, 0, "failed to retrieve private key file from VM host $vmhost_name: $vmhost_private_key_file_path"); - return; - } - - # Generate local public key file from private key retrieved from VM host - if (!$self->mn_os->create_ssh_public_key_file($local_private_key_file_path, $local_public_key_file_path, $vmhost_name)) { - notify($ERRORS{'WARNING'}, 0, "failed to create public key file on $management_node_name from VM host $vmhost_name private key file: $local_public_key_file_path"); - $self->mn_os->delete_file("$local_private_key_file_path*"); - return; - } - - # Copy the public key file to the VM host - if (!$self->vmhost_os->copy_file_to($local_public_key_file_path, $vmhost_public_key_file_path)) { - notify($ERRORS{'WARNING'}, 0, "failed to copy newly created public key file from $management_node_name to VM host $vmhost_name: $local_public_key_file_path --> $vmhost_public_key_file_path"); - $self->mn_os->delete_file("$local_private_key_file_path*"); + + # Create the public key file if it wasn't created when the private key was created + if (!$self->vmhost_os->file_exists($vmhost_public_key_file_path)) { + if (!$self->vmhost_os->generate_ssh_public_key_file($vmhost_private_key_file_path, $vmhost_public_key_file_path, $vmhost_name)) { return; } - - $self->mn_os->delete_file("$local_private_key_file_path*"); } - notify($ERRORS{'OK'}, 0, "configured SSH keys on VM host $vmhost_name"); + notify($ERRORS{'OK'}, 0, "configured SSH key for the root user on VM host $vmhost_name"); return 1; } #///////////////////////////////////////////////////////////////////////////// -=head2 copy_vmhost_ssh_public_key_to_another_host +=head2 add_ssh_root_key_to_authorized_keys - Parameters : $destination_vmhost_os + Parameters : $destination_vmware_object Returns : boolean - Description : Retrieves the public key from the VM host (/.ssh/id_rsa.pub) and - adds it to the authorized_keys file on the destination VM host. + Description : Retrieves the root user's public SSH key from the VM host + represented by the object which this subroutine is called from + ($self) and adds it to the authorized_keys file on the + destination VM host. + + "Destination" means the remote VM host which the source will SSH + into or send a file to via SCP. =cut -sub copy_vmhost_ssh_public_key_to_another_host { - my ($self, $destination) = @_; - if (ref($self) !~ /VMware/i) { - notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); +sub add_ssh_root_key_to_authorized_keys { + my ($source, $destination) = @_; + if (!$source) { + notify($ERRORS{'WARNING'}, 0, "no arguments were provided"); + return; + } + elsif (!ref($source) || ref($source) !~ /VMware/i) { + notify($ERRORS{'WARNING'}, 0, "1st argument is not a reference to a VMware provisioning object"); return; } elsif (!$destination) { - notify($ERRORS{'WARNING'}, 0, "destination VM host OS object argument was not supplied"); + notify($ERRORS{'WARNING'}, 0, "2nd destination VMware provisioning object argument was not supplied"); return; } - elsif (ref($destination) !~ /VMware/i) { - notify($ERRORS{'WARNING'}, 0, "destination VM host OS object is not a VMware module: " . ref($destination)); + elsif (!ref($destination) || ref($destination) !~ /VMware/i) { + notify($ERRORS{'WARNING'}, 0, "2nd argument is not a reference to a VMware provisioning object"); return; } - my $management_node_name = $self->data->get_management_node_short_name(); - my $vmhost_computer_name = $self->data->get_vmhost_short_name(); + my $source_vmhost_computer_name = $source->data->get_vmhost_short_name(); my $destination_vmhost_computer_name = $destination->vmhost_os->data->get_computer_short_name(); + my $source_private_key_file_path = "/.ssh/id_rsa"; + + # Determine the authorized_keys file location based on the VMware version my $destination_vmware_product_name = $destination->get_vmhost_product_name(); - my $vmhost_authorized_keys_file_path; + my $destination_authorized_keys_file_path; if ($destination_vmware_product_name =~ /4\./) { - $vmhost_authorized_keys_file_path = "/.ssh/authorized_keys"; + $destination_authorized_keys_file_path = "/.ssh/authorized_keys"; } else { - $vmhost_authorized_keys_file_path = "/etc/ssh/keys-root/authorized_keys"; + $destination_authorized_keys_file_path = "/etc/ssh/keys-root/authorized_keys"; } - my $public_key_file_path = "/.ssh/id_rsa.pub"; - - # Get the VM host's public key - my $public_key_string = $self->vmhost_os->get_file_contents($public_key_file_path); - if ($public_key_string) { - notify($ERRORS{'DEBUG'}, 0, "retrieved VM host $vmhost_computer_name public SSH key:\n$public_key_string"); + # Get the source VM host's public key string + my $source_public_key_string = $source->vmhost_os->get_ssh_public_key_string($source_private_key_file_path, $source_vmhost_computer_name); + if ($source_public_key_string) { + notify($ERRORS{'DEBUG'}, 0, "retrieved source VM host $source_vmhost_computer_name public SSH key:\n$source_public_key_string"); } else { - notify($ERRORS{'WARNING'}, 0, "failed to retrieve contents of public SSH key file from VM host $vmhost_computer_name"); + notify($ERRORS{'WARNING'}, 0, "failed to retrieve contents of public SSH key file from source VM host $source_vmhost_computer_name"); return; } # Get the contents of the destination VM host's authorized_keys file - my $destination_authorized_keys_file_contents = $destination->vmhost_os->get_file_contents($vmhost_authorized_keys_file_path); + my $destination_authorized_keys_file_contents = $destination->vmhost_os->get_file_contents($destination_authorized_keys_file_path); if ($destination_authorized_keys_file_contents) { - notify($ERRORS{'DEBUG'}, 0, "retrieved contents of $vmhost_authorized_keys_file_path from destination VM host $destination_vmhost_computer_name:\n$destination_authorized_keys_file_contents"); + #notify($ERRORS{'DEBUG'}, 0, "retrieved contents of $destination_authorized_keys_file_path from destination VM host $destination_vmhost_computer_name:\n$destination_authorized_keys_file_contents"); } else { - notify($ERRORS{'WARNING'}, 0, "failed to retrieve contents of $vmhost_authorized_keys_file_path from destination VM host $destination_vmhost_computer_name"); + notify($ERRORS{'WARNING'}, 0, "failed to retrieve contents of $destination_authorized_keys_file_path from destination VM host $destination_vmhost_computer_name"); return; } # Remove comments from public key for comparison - my $public_key_string_cleaned = $public_key_string; - $public_key_string_cleaned =~ s/^\s*(ssh-\w+\s+[^\s=]+).*/$1/g; + my $source_public_key_string_cleaned = $source_public_key_string; + $source_public_key_string_cleaned =~ s/^\s*(ssh-\w+\s+[^\s=]+).*/$1/g; my @destination_authorized_keys_file_lines = split(/\n+/, $destination_authorized_keys_file_contents); for my $destination_authorized_keys_file_line (@destination_authorized_keys_file_lines) { $destination_authorized_keys_file_line =~ s/^(ssh-\w+\s+[^\s=]+).*/$1/g; - if ($destination_authorized_keys_file_line eq $public_key_string_cleaned) { - notify($ERRORS{'DEBUG'}, 0, "$vmhost_authorized_keys_file_path on destination VM host $destination_vmhost_computer_name already contains the VM host's $vmhost_computer_name public SSH key:\n$public_key_string"); + if ($destination_authorized_keys_file_line eq $source_public_key_string_cleaned) { + notify($ERRORS{'DEBUG'}, 0, "$destination_authorized_keys_file_path on destination VM host $destination_vmhost_computer_name already contains the VM host's $source_vmhost_computer_name public SSH key:\n$source_public_key_string"); return 1; } } # Public key was not found in destination's authorized_keys file, attempt to add it - if ($destination->vmhost_os->append_text_file($vmhost_authorized_keys_file_path, $public_key_string)) { - notify($ERRORS{'OK'}, 0, "added VM host $vmhost_computer_name public SSH key to $vmhost_authorized_keys_file_path on destination VM host $destination_vmhost_computer_name:\n$public_key_string"); + if ($destination->vmhost_os->append_text_file($destination_authorized_keys_file_path, $source_public_key_string)) { + notify($ERRORS{'OK'}, 0, "added VM host $source_vmhost_computer_name public SSH key to $destination_authorized_keys_file_path on destination VM host $destination_vmhost_computer_name:\n$source_public_key_string"); return 1; } else { - notify($ERRORS{'WARNING'}, 0, "failed to add VM host $vmhost_computer_name public SSH key to $vmhost_authorized_keys_file_path on destination VM host $destination_vmhost_computer_name"); + notify($ERRORS{'WARNING'}, 0, "failed to add VM host $source_vmhost_computer_name public SSH key to $destination_authorized_keys_file_path on destination VM host $destination_vmhost_computer_name"); + return; + } +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 add_ssh_host_key_to_known_hosts + + Parameters : $destination_vmware_object + Returns : boolean + Description : Retrieves the public SSH host key from the destination VMware + host and adds and entry to known_hosts on the VM host represented + by the object which this subroutine is called from ($self). + + "Destination" means the remote VM host which the source will SSH + into or send a file to via SCP. "Destination" does not mean the + VM host whose known_hosts file will be modified. + +=cut + +sub add_ssh_host_key_to_known_hosts { + my ($source, $destination) = @_; + if (!$source) { + notify($ERRORS{'WARNING'}, 0, "no arguments were provided"); + return; + } + elsif (!ref($source) || ref($source) !~ /VMware/i) { + notify($ERRORS{'WARNING'}, 0, "1st argument is not a reference to a VMware provisioning object"); + return; + } + elsif (!$destination) { + notify($ERRORS{'WARNING'}, 0, "2nd destination VMware provisioning object argument was not supplied"); + return; + } + elsif (!ref($destination) || ref($destination) !~ /VMware/i) { + notify($ERRORS{'WARNING'}, 0, "2nd argument is not a reference to a VMware provisioning object"); + return; + } + + my $source_vmhost_computer_name = $source->vmhost_os->data->get_computer_hostname(); + my $destination_vmhost_computer_name = $destination->vmhost_os->data->get_computer_hostname(); + + # Add extra checking here - easy for objects to not be created correctly + if (!$source_vmhost_computer_name) { + notify($ERRORS{'WARNING'}, 0, "failed to add host SSH key to known_hosts, source VM host computer name could not be determined"); + return; + } + if (!$destination_vmhost_computer_name) { + notify($ERRORS{'WARNING'}, 0, "failed to add host SSH key to known_hosts on $source_vmhost_computer_name, destination VM host computer name could not be determined"); + return; + } + + my $source_known_hosts_file_path = "/.ssh/known_hosts"; + + # Figure out the IP address to use to connect to the destination + my $destination_remote_connection_target = determine_remote_connection_target($destination_vmhost_computer_name); + if (!$destination_remote_connection_target) { + notify($ERRORS{'WARNING'}, 0, "failed to add host SSH key from $destination_vmhost_computer_name to known_hosts on $source_vmhost_computer_name, failed to determine the remote connection target (IP address) to use for connecting to $destination_vmhost_computer_name"); + return; + } + + # Determine the host SSH key file path based on the VMware version + my $destination_vmware_product_name = $destination->get_vmhost_product_name(); + my $destination_private_host_key_file_path; + if ($destination_vmware_product_name =~ /4\./) { + $destination_private_host_key_file_path = "/etc/dropbear/dropbear_rsa_host_key"; + } + else { + $destination_private_host_key_file_path = "/etc/ssh/ssh_host_rsa_key"; + } + + # Extract the public key from the destination's private SSH host key + my $destination_public_key_string = $destination->vmhost_os->get_ssh_public_key_string($destination_private_host_key_file_path); + if (!$destination_public_key_string) { + notify($ERRORS{'WARNING'}, 0, "failed to add host SSH key from $destination_vmhost_computer_name to known_hosts on $source_vmhost_computer_name, failed to determine public SSH key from $destination_private_host_key_file_path on $destination_vmhost_computer_name"); return; } + + # Make sure there is no comment in the public key or things won't work + $destination_public_key_string =~ s/(ssh-\w+\s+[^=\s]+).*/$1/; + + # Remove any existing entries from the known_hosts file on the source + $source->vmhost_os->execute("sed -i -e \"/^$destination_remote_connection_target /d\" $source_known_hosts_file_path"); + + # Assemble the known_hosts line + my $source_known_hosts_string = "$destination_remote_connection_target $destination_public_key_string"; + + if ($source->vmhost_os->append_text_file($source_known_hosts_file_path, $source_known_hosts_string)) { + notify($ERRORS{'OK'}, 0, "added host SSH key from $destination_vmhost_computer_name to known_hosts on $source_vmhost_computer_name:\n$source_known_hosts_string"); + return 1; + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to append known_hosts on $source_vmhost_computer_name:\n$source_known_hosts_string"); + return 0; + } } #///////////////////////////////////////////////////////////////////////////// @@ -8902,6 +8892,10 @@ sub setup_migrate_vm { my $source_vmhost_id = $self->data->get_vmhost_id(); my $source_assigned_vm_info = get_vmhost_assigned_vm_info($source_vmhost_id); + if (!keys %$source_assigned_vm_info) { + print "No VMs are assigned to $source_vmhost_computer_name\n"; + return; + } #print "\nSelect VMs to be migrated off of $source_vmhost_computer_name:\n"; #my $vm_computer_id = setup_get_hash_multiple_choice($source_assigned_vm_info, 'SHORTNAME', 'currentimagerevision-imagename') || return; @@ -8949,10 +8943,15 @@ sub setup_migrate_vm { =head2 migrate_vm - Parameters : $vm_identifier, $destination_vmhost_identifier + Parameters : $vm_identifier, $destination_vmhost_identifier, $options (optional) Returns : boolean Description : Migrates a VM from the host the VM is assigned to to another - host. + host. An optional $options hash reference may be supplied which + may contain the following keys: + * revert_destination_on_error - Used for development and + troubleshooting. If a migration fails, the destination VM is + removed by default. If this is set to true the destination VM + will be left on the VM host and powered off. =cut @@ -8962,7 +8961,7 @@ sub migrate_vm { notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } - my ($vm_identifier, $destination_vmhost_identifier) = @_; + my ($vm_identifier, $destination_vmhost_identifier, $options) = @_; if (!defined($vm_identifier)) { notify($ERRORS{'WARNING'}, 0, "VM identifier argument was not supplied"); return; @@ -8972,6 +8971,11 @@ sub migrate_vm { return; } + my $revert_destination_on_error = 1; + if (defined($options->{revert_destination_on_error})) { + $revert_destination_on_error = $options->{revert_destination_on_error}; + } + if ($SETUP_MODE) { no warnings 'redefine'; *notify = sub { @@ -9150,9 +9154,10 @@ sub migrate_vm { } # Configure host to host SSH - $source->configure_vmhost_ssh_keys() || return; - $source->copy_vmhost_ssh_public_key_to_another_host($destination) || return; - + if (!($source->configure_root_ssh_key() && $source->add_ssh_root_key_to_authorized_keys($destination) && $source->add_ssh_host_key_to_known_hosts($destination))) { + notify($ERRORS{'WARNING'}, 0, "unable to migrate VM: $vm_computer_name, failed to configure SSH access between $source_vmhost_computer_name and $destination_vmhost_computer_name"); + return; + } # Find the .vmx file on the source VM host my @source_vmx_file_paths = $source->api->get_registered_vms(); @@ -9169,6 +9174,14 @@ sub migrate_vm { notify($ERRORS{'DEBUG'}, 0, "found matching .vmx file on source VM host $source_vmhost_computer_name: $source_vmx_file_path"); $source->set_vmx_file_path($source_vmx_file_path); + my $source_vmx_file_name = $source->get_vmx_file_name(); + my $source_vmx_directory_name = $source->get_vmx_directory_name(); + my $source_vmx_directory_path = $source->get_vmx_directory_path(); + my $source_vmx_directory_url_path = $source->_get_url_path($source_vmx_directory_path); + my $source_vmx_base_directory_path = $source->get_vmx_base_directory_path(); + my $source_vmx_base_directory_url_path = $source->_get_url_path($source_vmx_base_directory_path); + + # Possible TODO: if problems occur using VMware's suspend/resume, try OS's hibernate # Figure out if VMware's suspend or the guest OS's hibernate should be used # Check if source vmx contains any values known to cause problems with VMware's suspend/resume @@ -9194,7 +9207,6 @@ sub migrate_vm { # last; # } #} - ## Perform additional checks if VMware's suspend/resume can't be used #if ($suspend_method eq 'os') { # # Check if the VM OS object implements a hibernate subroutine @@ -9205,36 +9217,46 @@ sub migrate_vm { #} #notify($ERRORS{'DEBUG'}, 0, "source VM suspend/hibernate method: " . ($suspend_method eq 'vmware' ? 'VMware suspend' : 'guest OS hibernate')); - # Construct destination vmx file path - my $source_vmx_directory_name = $source->get_vmx_directory_name(); - my $source_vmx_file_name = $source->get_vmx_file_name(); - my $destination_vmx_base_directory_path = $destination->get_vmx_base_directory_path(); - my $destination_vmx_file_path = "$destination_vmx_base_directory_path/$source_vmx_directory_name/$source_vmx_file_name"; - $destination->set_vmx_file_path($destination_vmx_file_path); - # Needed for search/replace - my $source_vmx_base_directory_path = $source->get_vmx_base_directory_path(); - my $source_vmx_base_directory_url_path = $source->_get_url_path($source_vmx_base_directory_path); + # Figure out the destination vmx file path + my $destination_vmx_directory_name = $source_vmx_directory_name; + my $destination_vmx_base_directory_path = $destination->get_vmx_base_directory_path(); my $destination_vmx_base_directory_url_path = $destination->_get_url_path($destination_vmx_base_directory_path); - # Needed to locate files to copy - my $source_vmx_directory_path = $source->get_vmx_directory_path(); + # Check if source and destination were to use the same vmx directory + # This causes problems where the destination VM won't power on - not sure why + # The vmware.log file will contain information related to the VM hosts having different CPU features + # For some reason, using a different directory seems to solve the problem + # 015-04-29T18:32:21.581Z| vmx| I120: evcMasksInCpt = 0 + # 015-04-29T18:32:21.581Z| vmx| I120: evcCompatibilityMode = 0 + # 015-04-29T18:32:21.581Z| vmx| I120: Error: This host has a usermode feature set incompatible with the host on which it was suspended. + # 015-04-29T18:32:21.581Z| vmx| I120: Host: 0x02982203, Checkpoint: 0x00982201 + # 015-04-29T18:32:21.581Z| vmx| I120: CPUID test failed: 1.ecx. + # 015-04-29T18:32:21.582Z| vmx| I120: Msg_Question: + # 015-04-29T18:32:21.582Z| vmx| I120: [msg.checkpoint.cpucheck.fail.feature] The features supported by the processors in this machine are different from the features supported by the processors in the machine on which the virtual machine state was saved. + # 015-04-29T18:32:21.582Z| vmx| I120: [msg.checkpoint.cpucheck.fail.hard] Resume on a machine with similar processors. + # 015-04-29T18:32:21.582Z| vmx| I120: [msg.checkpoint.restore.cpufail] An error occurred while restoring the CPU state from file "/vmfs/volumes/55410273-6261f90c-8568-00053348d88e/shared/arkvmm194_3081-v5/arkvmm194_3081-v5-9478be0b.vmss". + # 015-04-29T18:32:21.582Z| vmx| I120: [msg.checkpoint.resume.softError] Your virtual machine did not resume because of a correctable error. Preserve the suspended state and correct the error, or discard the suspended state. + if ($source_vmx_directory_url_path eq "$destination_vmx_base_directory_url_path/$source_vmx_directory_name") { + $destination_vmx_directory_name .= "_$destination_vmhost_computer_name"; + notify($ERRORS{'DEBUG'}, 0, "source and destination VM use the same vmx directory: $source_vmx_directory_url_path, changing destination vmx directory name to avoid problems powering on the destination VM: $destination_vmx_directory_name"); + } + else { + notify($ERRORS{'DEBUG'}, 0, "source and destination VM use different vmx directories:\nsource: $source_vmx_directory_url_path\ndestination: $destination_vmx_base_directory_url_path/$source_vmx_directory_name"); + } - # Needed to verify destination VM doesn't exist - my $destination_vmx_directory_path = $destination->get_vmx_directory_path(); + # Construct destination vmx file path + my $destination_vmx_file_path = "$destination_vmx_base_directory_path/$destination_vmx_directory_name/$source_vmx_file_name"; + $destination->set_vmx_file_path($destination_vmx_file_path); - # Check if sure source and destination directories are different - my $source_vmx_directory_url_path = $source->_get_url_path($source_vmx_directory_path); + my $destination_vmx_directory_path = $destination->get_vmx_directory_path(); my $destination_vmx_directory_url_path = $destination->_get_url_path($destination_vmx_directory_path); - my $same_vmx_directory = ($source_vmx_directory_url_path eq $destination_vmx_directory_url_path ? 1 : 0); # Create a snapshot of the source # This is to reduce the amount of data to copy while the VM is hibernating - if (!$same_vmx_directory) { - notify($ERRORS{'DEBUG'}, 0, "attempting to create snapshot of $vm_computer_name on $source_vmhost_computer_name"); - $source->snapshot() || return; - notify($ERRORS{'OK'}, 0, "created snapshot of $vm_computer_name on $source_vmhost_computer_name"); - } + notify($ERRORS{'DEBUG'}, 0, "attempting to create snapshot of $vm_computer_name on $source_vmhost_computer_name"); + $source->snapshot() || return; + notify($ERRORS{'OK'}, 0, "created snapshot of $vm_computer_name on $source_vmhost_computer_name"); # Figure out the parent .vmdk file being used by the source VM my @source_vmdk_file_paths = $self->api->get_vm_virtual_disk_file_paths($source_vmx_file_path); @@ -9256,6 +9278,7 @@ sub migrate_vm { # Set the vmdk file path in the source VMware object $source->set_vmdk_file_path($source_master_vmdk_file_path); + my $source_vmdk_file_path = $source->get_vmdk_file_path(); # The last file path is actively being used by the VM my $source_active_vmdk_file_path = $source_primary_vmdk_file_paths[-1]; @@ -9283,6 +9306,31 @@ sub migrate_vm { my $destination_vmdk_directory_url_path = $destination->_get_url_path($destination_vmdk_directory_path); my $same_vmdk_directory = ($source_vmdk_directory_url_path eq $destination_vmdk_directory_url_path ? 1 : 0); + # If the source and destination are using the same vmdk directory, check if the vmdk is dedicated + if ($same_vmdk_directory) { + notify($ERRORS{'DEBUG'}, 0, "source and destination VMs use the same vmdk directory: $source_vmdk_directory_url_path"); + my $source_vmdk_directory_path_dedicated = $source->get_vmdk_directory_path_dedicated(); + my $source_vmdk_directory_url_path_dedicated = $source->_get_url_path($source_vmdk_directory_path_dedicated); + if ($source_vmdk_directory_url_path_dedicated eq $destination_vmdk_directory_url_path) { + notify($ERRORS{'DEBUG'}, 0, "vmdk directory is dedicated: $source_vmdk_directory_url_path_dedicated"); + + # Override the destination vmdk file path + $destination->set_vmdk_file_path($destination->get_vmdk_file_path_dedicated()); + $destination_vmdk_file_path = $destination->get_vmdk_file_path(); + $destination_vmdk_directory_path = $destination->get_vmdk_directory_path(); + $destination_vmdk_directory_url_path = $destination->_get_url_path($destination_vmdk_directory_path); + $destination->{vm_dedicated} = 1; + $same_vmdk_directory = 0; + } + else { + notify($ERRORS{'DEBUG'}, 0, "vmdk directory is NOT dedicated: $source_vmdk_directory_url_path_dedicated"); + } + } + + my $source_vmdk_file_url_path = $source->_get_url_path($source_vmdk_file_path); + my $destination_vmdk_file_url_path = $destination->_get_url_path($destination_vmdk_file_path); + + # Copy the parent vmdk to the correct location on the destination # This may fail if vmdk doesn't exist on destination datastore or repository notify($ERRORS{'DEBUG'}, 0, "copying destination master vmdk if necessary: $destination_vmdk_file_path"); @@ -9292,22 +9340,17 @@ sub migrate_vm { } # Create the destination directory - if ($same_vmx_directory) { - notify($ERRORS{'DEBUG'}, 0, "directory does not need to be created on destination VM host $destination_vmhost_computer_name because source VM host is using the same directory: $destination_vmx_directory_path"); + if ($destination->vmhost_os->file_exists($destination_vmx_directory_path)) { + notify($ERRORS{'WARNING'}, 0, "directory already exists on destination VM host $destination_vmhost_computer_name: $destination_vmx_directory_path, attempting to delete directory"); + $destination->vmhost_os->delete_file($destination_vmx_directory_path) || return; + } + + if ($destination->vmhost_os->create_directory($destination_vmx_directory_path)) { + notify($ERRORS{'OK'}, 0, "created directory on destination VM host $destination_vmhost_computer_name: $destination_vmx_directory_path"); } else { - if ($destination->vmhost_os->file_exists($destination_vmx_directory_path)) { - notify($ERRORS{'WARNING'}, 0, "directory already exists on destination VM host $destination_vmhost_computer_name: $destination_vmx_directory_path, attempting to delete directory"); - $destination->vmhost_os->delete_file($destination_vmx_directory_path) || return; - } - - if ($destination->vmhost_os->create_directory($destination_vmx_directory_path)) { - notify($ERRORS{'OK'}, 0, "created directory on destination VM host $destination_vmhost_computer_name: $destination_vmx_directory_path"); - } - else { - notify($ERRORS{'WARNING'}, 0, "failed to create directory on destination VM host $destination_vmhost_computer_name: $destination_vmx_directory_path"); - return; - } + notify($ERRORS{'WARNING'}, 0, "failed to create directory on destination VM host $destination_vmhost_computer_name: $destination_vmx_directory_path"); + return; } # Get a list of all files in the source VM directory @@ -9319,7 +9362,7 @@ sub migrate_vm { my @destination_edit_file_paths; if (!@source_vmx_directory_file_paths) { notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, no files were found in source vmx directory on $source_vmhost_computer_name: $source_vmx_directory_path"); - $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory); + $destination->vmhost_os->delete_file($destination_vmx_directory_path); return; } for my $source_file_path (@source_vmx_directory_file_paths) { @@ -9338,31 +9381,29 @@ sub migrate_vm { push @destination_edit_file_paths, $destination_file_path; } - if (!$same_vmx_directory) { - # Keep list of files in use by the source VM which is still running - # These will be copied after the VM hibernates - if ($source_file_path =~ /$source_active_vmdk_file_base_name[\.-].*vmdk$/) { - push @source_active_file_paths, $source_file_path; - notify($ERRORS{'DEBUG'}, 0, "file is actively being used by the source VM, will be copied after source VM is suspended: $source_file_name"); - next; - } - - # Attempt to retrieve the source file size - useful info to present because copy may take a long time - my $source_file_size_bytes = $source->vmhost_os->get_file_size($source_file_path); - my $file_size_string = ''; - if ($source_file_size_bytes) { - $file_size_string = ' (' . get_file_size_info_string($source_file_size_bytes) . ')'; - } - - notify($ERRORS{'DEBUG'}, 0, "copying file to destination: $destination_vmhost_computer_name:$destination_file_path" . $file_size_string); - if ($source->copy_file_to_another_host($source_file_path, $destination, $destination_file_path)) { - #notify($ERRORS{'OK'}, 0, "copied file to destination: $destination_vmhost_computer_name:$destination_file_path"); - } - else { - notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to copy file from source to destination VM host: $source_vmhost_computer_name:$source_file_path --> $destination_vmhost_computer_name:$destination_file_path"); - $destination->vmhost_os->delete_file($destination_vmx_directory_path); - return; - } + # Keep list of files in use by the source VM which is still running + # These will be copied after the VM hibernates + if ($source_file_path =~ /$source_active_vmdk_file_base_name[\.-].*vmdk$/) { + push @source_active_file_paths, $source_file_path; + notify($ERRORS{'DEBUG'}, 0, "file is actively being used by the source VM, will be copied after source VM is suspended: $source_file_name"); + next; + } + + # Attempt to retrieve the source file size - useful info to present because copy may take a long time + my $source_file_size_bytes = $source->vmhost_os->get_file_size($source_file_path); + my $file_size_string = ''; + if ($source_file_size_bytes) { + $file_size_string = ' (' . get_file_size_info_string($source_file_size_bytes) . ')'; + } + + notify($ERRORS{'DEBUG'}, 0, "copying file to destination: $destination_vmhost_computer_name:$destination_file_path" . $file_size_string); + if ($source->copy_file_to_another_host($source_file_path, $destination, $destination_file_path)) { + #notify($ERRORS{'OK'}, 0, "copied file to destination: $destination_vmhost_computer_name:$destination_file_path"); + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to copy file from source to destination VM host: $source_vmhost_computer_name:$source_file_path --> $destination_vmhost_computer_name:$destination_file_path"); + $destination->vmhost_os->delete_file($destination_vmx_directory_path); + return; } } @@ -9378,7 +9419,7 @@ sub migrate_vm { } else { notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to suspend source VM"); - $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory); + $destination->vmhost_os->delete_file($destination_vmx_directory_path); return; } } @@ -9389,7 +9430,7 @@ sub migrate_vm { } else { notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to hibernate VM's guest OS"); - $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory); + $destination->vmhost_os->delete_file($destination_vmx_directory_path); return; } } @@ -9407,53 +9448,59 @@ sub migrate_vm { } # Copy the files that were actively being used by the source VM - if (!$same_vmx_directory) { - for my $source_file_path (@source_active_file_paths) { - my $file_name = $self->_get_file_name($source_file_path); - my $destination_file_path = "$destination_vmx_directory_path/$file_name"; - if ($source->copy_file_to_another_host($source_file_path, $destination, $destination_file_path)) { - notify($ERRORS{'OK'}, 0, "copied file to destination VM host: $destination_vmhost_computer_name:$destination_file_path"); - } - else { - notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to copy file which was actively being used by the VM from source to destination VM host after the VM hibernated: $source_vmhost_computer_name:$source_file_path --> $destination_vmhost_computer_name:$destination_file_path"); - migrate_revert_source($source, $vm_os); - $destination->vmhost_os->delete_file($destination_vmx_directory_path); - return; - } + for my $source_file_path (@source_active_file_paths) { + my $file_name = $self->_get_file_name($source_file_path); + my $destination_file_path = "$destination_vmx_directory_path/$file_name"; + if ($source->copy_file_to_another_host($source_file_path, $destination, $destination_file_path)) { + notify($ERRORS{'OK'}, 0, "copied file to destination VM host: $destination_vmhost_computer_name:$destination_file_path"); + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to copy file which was actively being used by the VM from source to destination VM host after the VM hibernated: $source_vmhost_computer_name:$source_file_path --> $destination_vmhost_computer_name:$destination_file_path"); + migrate_revert_source($source, $vm_os); + $destination->vmhost_os->delete_file($destination_vmx_directory_path); + return; } } # Update files on destination which have paths specific to the source VM host - my $file_replacements = {}; - if (!$same_vmx_directory) { - $file_replacements->{$source_vmx_base_directory_path} = $destination_vmx_base_directory_path; - $file_replacements->{$source_vmx_base_directory_url_path} = $destination_vmx_base_directory_url_path; - } - if (!$same_vmdk_directory) { - $file_replacements->{$source_vmdk_base_directory_path} = $destination_vmdk_base_directory_path; - $file_replacements->{$source_vmdk_base_directory_url_path} = $destination_vmdk_base_directory_url_path; - }; - SOURCE_PATTERN: for my $source_pattern (sort keys %$file_replacements) { - my $destination_pattern = $file_replacements->{$source_pattern}; - next if ($source_pattern eq $destination_pattern); - notify($ERRORS{'DEBUG'}, 0, "updating files on destination VM host $destination_vmhost_computer_name, pattern: $source_pattern --> $destination_pattern"); - for my $destination_file_path (@destination_edit_file_paths) { - my $sed_command = "sed -i -e \"s|$source_vmx_base_directory_path/|$destination_vmx_base_directory_path/|g\" $destination_file_path"; - my ($sed_exit_status, $sed_output) = $destination->vmhost_os->execute($sed_command); + my @file_replacements; + + push @file_replacements, [$source_vmdk_file_path, $destination_vmdk_file_path]; + push @file_replacements, [$source_vmdk_file_url_path, $destination_vmdk_file_url_path]; + + push @file_replacements, ["$source_vmx_directory_path/", "$destination_vmx_directory_path/"]; + push @file_replacements, ["$source_vmx_directory_url_path/", "$destination_vmx_directory_url_path/"]; + + push @file_replacements, ["$source_vmdk_directory_path/", "$destination_vmdk_directory_path/"]; + push @file_replacements, ["$source_vmdk_directory_url_path/", "$destination_vmdk_directory_url_path/"]; + + push @file_replacements, ["$source_vmx_base_directory_path/", "$destination_vmx_base_directory_path/"]; + push @file_replacements, ["$source_vmx_base_directory_url_path/", "$destination_vmx_base_directory_url_path/"]; + + push @file_replacements, ["$source_vmdk_base_directory_path/", "$destination_vmdk_base_directory_path/"]; + push @file_replacements, ["$source_vmdk_base_directory_url_path/", "$destination_vmdk_base_directory_url_path/"]; + + for my $destination_file_path (@destination_edit_file_paths) { + SOURCE_PATTERN: for my $file_replacement (@file_replacements) { + my ($source_pattern, $destination_pattern) = @$file_replacement; + next if ($source_pattern eq $destination_pattern); + + my $sed_command = "sed -i -e \"s|$source_pattern|$destination_pattern|g\" $destination_file_path"; + my ($sed_exit_status, $sed_output) = $destination->vmhost_os->execute($sed_command, 0); if (!defined($sed_output)) { notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to execute command on destination VM host $destination_vmhost_computer_name: $sed_command"); migrate_revert_source($source, $vm_os); - $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory); + $destination->vmhost_os->delete_file($destination_vmx_directory_path); return; } elsif (grep(/sed:/, @$sed_output)) { notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to update file on destination VM host $destination_vmhost_computer_name, exit status: $sed_exit_status\ncommand:\n$sed_command\noutput:\n" . join("\n", @$sed_output)); migrate_revert_source($source, $vm_os); - $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory); + $destination->vmhost_os->delete_file($destination_vmx_directory_path); return; } else { - #notify($ERRORS{'OK'}, 0, "updated file on $destination_vmhost_computer_name: $destination_file_path"); + notify($ERRORS{'OK'}, 0, "updated file on $destination_vmhost_computer_name: $destination_file_path, pattern: $source_pattern --> $destination_pattern"); } } } @@ -9466,7 +9513,9 @@ sub migrate_vm { else { notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to register VM on destination VM host $destination_vmhost_computer_name: $destination_vmx_file_path"); migrate_revert_source($source, $vm_os); - $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory); + if ($revert_destination_on_error) { + $destination->vmhost_os->delete_file($destination_vmx_directory_path); + } return; } @@ -9478,8 +9527,10 @@ sub migrate_vm { else { notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, failed to power on VM on destination VM host $destination_vmhost_computer_name: $destination_vmx_file_path"); migrate_revert_source($source, $vm_os); - $destination->api->vm_unregister($destination_vmx_file_path); - $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory); + if ($revert_destination_on_error) { + $destination->api->vm_unregister($destination_vmx_file_path); + $destination->vmhost_os->delete_file($destination_vmx_directory_path); + } return; } @@ -9491,22 +9542,19 @@ sub migrate_vm { else { notify($ERRORS{'WARNING'}, 0, "failed to migrate $vm_computer_name, VM never responded on destination VM host $destination_vmhost_computer_name"); migrate_revert_source($source, $vm_os); - $destination->api->vm_unregister($destination_vmx_file_path); - $destination->vmhost_os->delete_file($destination_vmx_directory_path) if (!$same_vmx_directory); + if ($revert_destination_on_error) { + $destination->api->vm_unregister($destination_vmx_file_path); + $destination->vmhost_os->delete_file($destination_vmx_directory_path); + } return; } my $hibernate_duration = (time - $hibernate_start_time); # Remove the original VM from the source VM host - if ($same_vmx_directory) { - $source->api->vm_unregister($source_vmx_file_path); - } - else { - notify($ERRORS{'DEBUG'}, 0, "deleting original VM from $source_vmhost_computer_name: $source_vmx_file_path"); - $source->delete_vm($source_vmx_file_path); - notify($ERRORS{'OK'}, 0, "deleted original VM from $source_vmhost_computer_name: $source_vmx_file_path"); - } + notify($ERRORS{'DEBUG'}, 0, "deleting original VM from $source_vmhost_computer_name: $source_vmx_file_path"); + $source->delete_vm($source_vmx_file_path); + notify($ERRORS{'OK'}, 0, "deleted original VM from $source_vmhost_computer_name: $source_vmx_file_path"); notify($ERRORS{'OK'}, 0, "migration of $vm_computer_name complete: $source_vmhost_computer_name --> $destination_vmhost_computer_name, hibernation duration: $hibernate_duration seconds"); return 1;
Modified: vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm?rev=1677997&r1=1677996&r2=1677997&view=diff ============================================================================== --- vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm (original) +++ vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm Wed May 6 14:07:44 2015 @@ -93,13 +93,6 @@ sub initialize { # Override the die handler because process will die if VMware Perl libraries aren't installed local $SIG{__DIE__} = sub{}; - eval "use VMware::VIRuntime; use VMware::VILib; use VMware::VIExt"; - if ($EVAL_ERROR) { - notify($ERRORS{'DEBUG'}, 0, "vSphere SDK for Perl does not appear to be installed on this managment node, unable to load VMware vSphere SDK Perl modules, error:\n$EVAL_ERROR"); - return 0; - } - notify($ERRORS{'DEBUG'}, 0, "loaded VMware vSphere SDK modules"); - my $vmhost_hostname = $self->data->get_vmhost_hostname(); my $vmhost_username = $self->data->get_vmhost_profile_username(); my $vmhost_password = $self->data->get_vmhost_profile_password(); @@ -118,25 +111,41 @@ sub initialize { return; } + eval "use VMware::VIRuntime; use VMware::VILib; use VMware::VIExt"; + if ($EVAL_ERROR) { + notify($ERRORS{'DEBUG'}, 0, "vSphere SDK for Perl does not appear to be installed on this managment node, unable to load VMware vSphere SDK Perl modules, error:\n$EVAL_ERROR"); + return 0; + } + notify($ERRORS{'DEBUG'}, 0, "loaded VMware vSphere SDK modules"); + Opts::set_option('username', $vmhost_username); Opts::set_option('password', $vmhost_password); # Override the die handler local $SIG{__DIE__} = sub{}; - - # Assemble the URLs to try, URL will vary based on the VMware product - my @possible_vmhost_urls = ( - "https://$vmhost_hostname/sdk", - "https://$vmhost_hostname:8333/sdk", - ); - # Also add URLs containing the short host name if the VM hostname is a full DNS name + my @possible_vmhost_urls; + + # Determine the VM host's IP address + require VCL::utils; + my $remote_connection_target = determine_remote_connection_target($vmhost_hostname); + if ($remote_connection_target) { + push @possible_vmhost_urls, "https://$remote_connection_target/sdk"; + } + + push @possible_vmhost_urls, "https://$vmhost_hostname/sdk"; if ($vmhost_hostname =~ /[a-z]\./i) { my ($vmhost_short_name) = $vmhost_hostname =~ /^([^\.]+)/; push @possible_vmhost_urls, "https://$vmhost_short_name/sdk"; - push @possible_vmhost_urls, "https://$vmhost_short_name:8333/sdk"; } + my @possible_vmhost_urls_with_port; + for my $host_url (@possible_vmhost_urls) { + (my $host_url_with_port = $host_url) =~ s/^(.+)(\/sdk)$/$1:8333$2/g; + push @possible_vmhost_urls_with_port, $host_url_with_port; + } + push @possible_vmhost_urls, @possible_vmhost_urls_with_port; + # Call HostConnect, check how long it takes to connect my $vim; for my $host_url (@possible_vmhost_urls) { @@ -1986,7 +1995,9 @@ sub file_exists { } # Get and check the file path argument - my $file_path_argument = shift; + my ($file_path_argument, $type) = @_; + + $type = '*' unless defined($type); my $file_path = $self->_get_datastore_path($file_path_argument); if (!$file_path) { @@ -2013,7 +2024,7 @@ sub file_exists { my $base_directory_path = $self->_get_parent_directory_datastore_path($file_path) || return; my $file_name = $self->_get_file_name($file_path) || return; - my $result = $self->find_files($base_directory_path, $file_name); + my $result = $self->find_files($base_directory_path, $file_name, 0, $type); if ($result) { notify($ERRORS{'DEBUG'}, 0, "file exists: $file_path"); return 1; @@ -2095,7 +2106,7 @@ sub get_file_size { =head2 find_files - Parameters : $base_directory_path, $search_pattern, $search_subdirectories (optional) + Parameters : $base_directory_path, $search_pattern, $search_subdirectories (optional), $type (optional) Returns : array Description : Finds files in a datastore on the VM host stored under the base directory path argument. The search pattern may contain @@ -2112,7 +2123,7 @@ sub find_files { } # Get the arguments - my ($base_directory_path, $search_pattern, $search_subdirectories) = @_; + my ($base_directory_path, $search_pattern, $search_subdirectories, $type) = @_; if (!$base_directory_path || !$search_pattern) { notify($ERRORS{'WARNING'}, 0, "base directory path and search pattern arguments were not specified"); return; @@ -2120,18 +2131,46 @@ sub find_files { $search_subdirectories = 1 if !defined($search_subdirectories); + my $type_string; + $type = 'f' unless defined $type; + if ($type =~ /^f$/i) { + $type = 'f'; + $type_string = 'files'; + } + elsif ($type =~ /^d$/i) { + $type = 'd'; + $type_string = 'directories'; + } + elsif ($type =~ /^\*$/i) { + $type = '*'; + $type_string = 'files and directories'; + } + else { + notify($ERRORS{'WARNING'}, 0, "unsupported type argument: '$type'"); + return; + } + $base_directory_path = $self->_get_normal_path($base_directory_path) || return; # Get the file info - my $file_info = $self->_get_file_info("$base_directory_path/$search_pattern", $search_subdirectories); + my $file_info = $self->_get_file_info("$base_directory_path/$search_pattern", $search_subdirectories, 1); if (!defined($file_info)) { - notify($ERRORS{'WARNING'}, 0, "unable to find files, failed to get file info for: $base_directory_path/$search_pattern"); + notify($ERRORS{'WARNING'}, 0, "unable to find $type_string, failed to get file info for: $base_directory_path/$search_pattern"); return; } # Loop through the keys of the file info hash my @file_paths; for my $file_path (keys(%{$file_info})) { + my $file_type = $file_info->{$file_path}{type}; + + if ($type eq 'f' && $file_type =~ /Folder/i) { + next; + } + elsif ($type eq 'd' && $file_type !~ /Folder/i) { + next; + } + # Add the file path to the return array push @file_paths, $self->_get_normal_path($file_path); @@ -2147,7 +2186,7 @@ sub find_files { } @file_paths = sort @file_paths; - notify($ERRORS{'DEBUG'}, 0, "matching file count: " . scalar(@file_paths)); + notify($ERRORS{'DEBUG'}, 0, "$type_string found under $base_directory_path matching pattern '$search_pattern': " . scalar(@file_paths)); return @file_paths; } @@ -2790,7 +2829,7 @@ sub _get_file_info { } # Get the arguments - my ($path_argument, $search_subfolders) = @_; + my ($path_argument, $search_subfolders, $include_directories) = @_; if (!$path_argument) { notify($ERRORS{'WARNING'}, 0, "file path argument was not specified"); return; @@ -2898,7 +2937,7 @@ sub _get_file_info { my $file_path = $self->_get_datastore_path("$directory_normal_path/" . $file->path); # Check the file type - if (ref($file) eq 'FolderFileInfo') { + if (ref($file) eq 'FolderFileInfo' && !$include_directories) { # Don't include folders in the results next; } @@ -2910,7 +2949,8 @@ sub _get_file_info { } $self->{_get_file_info}{"$base_directory_path/$search_pattern"} = \%file_info; - notify($ERRORS{'DEBUG'}, 0, "retrieved info for " . scalar(keys(%file_info)) . " matching files:\n" . format_data(\%file_info)); + #notify($ERRORS{'DEBUG'}, 0, "retrieved info for " . scalar(keys(%file_info)) . " matching files:\n" . format_data(\%file_info)); + notify($ERRORS{'DEBUG'}, 0, "retrieved info for " . scalar(keys(%file_info)) . " matching files"); return \%file_info; } @@ -3139,7 +3179,7 @@ sub _get_resource_pool_view { } my $resource_pools; - + my $root_resource_pool_path; for my $resource_pool_view (@resource_pool_views) { # Assemble the full path to the resource view - including Datacenters, folders, clusters... my $resource_pool_path = $self->_get_managed_object_path($resource_pool_view->{mo_ref}); @@ -3152,6 +3192,11 @@ sub _get_resource_pool_view { $resource_pool_path_fixed =~ s/\/host\//\//g; $resource_pool_path_fixed =~ s/\/Resources($|\/?)/$1/g; $resource_pools->{$resource_pool_path_fixed} = $resource_pool_view; + + # Save the top-level default resource pool path - use this if resource path is not configured + if ($resource_pool_path =~ /\/Resources$/) { + $root_resource_pool_path = $resource_pool_path_fixed; + } } if (!$resource_pools) { @@ -3160,8 +3205,16 @@ sub _get_resource_pool_view { } elsif (!$vmhost_profile_resource_path) { if (scalar(keys %$resource_pools) > 1) { - notify($ERRORS{'WARNING'}, 0, "unable to determine which resource pool to use, resource path not configured in VM profile and multiple resource paths exist on host $vmhost_name:\n" . join("\n", sort keys %$resource_pools)); - return; + if ($root_resource_pool_path) { + notify($ERRORS{'DEBUG'}, 0, "resource path not configured in VM profile, using root resource pool: $root_resource_pool_path"); + my $resource_pool = $resource_pools->{$root_resource_pool_path}; + $self->{resource_pool_view_object} = $resource_pool; + return $resource_pool; + } + else { + notify($ERRORS{'WARNING'}, 0, "unable to determine which resource pool to use, resource path not configured in VM profile and multiple resource paths exist on host $vmhost_name:\n" . join("\n", sort keys %$resource_pools)); + return; + } } else { my $resource_pool_name = (keys %$resource_pools)[0]; @@ -4208,6 +4261,60 @@ sub is_multiextent_disabled { } #///////////////////////////////////////////////////////////////////////////// + +=head2 get_vm_virtual_disk_file_paths + + Parameters : $vmx_file_path + Returns : array + Description : Retrieves a VM's virtual disk file layout and returns an array + reference. Each top-level array element represent entire virtual + disk and contains an array reference containing the virtual + disk's files: + [ + [ + "/vmfs/volumes/datastore/vmwarewin7-bare3844-v1/vmwarewin7-bare3844-v1.vmdk", + "/vmfs/volumes/blade-vmpath/vm170_3844-v1/vmwarewin7-bare3844-v1-000001.vmdk", + "/vmfs/volumes/blade-vmpath/vm170_3844-v1/vmwarewin7-bare3844-v1-000002.vmdk", + "/vmfs/volumes/blade-vmpath/vm170_3844-v1/vmwarewin7-bare3844-v1-000003.vmdk" + ], + [ + "/vmfs/volumes/blade-vmpath/vm170_3844-v1/vm170_3844-v1.vmdk" + ] + ] + +=cut + +sub get_vm_virtual_disk_file_paths { + 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 vmx path argument and convert it to a datastore path + my $vmx_file_path = $self->_get_datastore_path(shift) || return; + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + my $vm = $self->_get_vm_view($vmx_file_path) || return; + + my $virtual_disk_array_ref = $vm->{layout}->{disk}; + my @virtual_disks; + for my $virtual_disk_ref (@$virtual_disk_array_ref) { + my $disk_file_array_ref = $virtual_disk_ref->{'diskFile'}; + my @virtual_disk_file_paths; + for my $virtual_disk_file_path (@$disk_file_array_ref) { + push @virtual_disk_file_paths, $self->_get_normal_path($virtual_disk_file_path); + } + push @virtual_disks, \@virtual_disk_file_paths; + } + + notify($ERRORS{'DEBUG'}, 0, "retrieved virtual disk file paths for $vmx_file_path:\n" . format_data(\@virtual_disks)); + return @virtual_disks; +} + +#///////////////////////////////////////////////////////////////////////////// =head2 DESTROY
