| #!/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::Module::Provisioning::VMware::VMware |
| |
| =head1 SYNOPSIS |
| |
| use VCL::Module::Provisioning::VMware::VMware; |
| my $provisioner = VCL::Module::Provisioning::VMware::VMware->new({data_structure => $self->data}); |
| |
| =head1 DESCRIPTION |
| |
| This module provides VCL support for the following VMware products: |
| -VMware Server 1.x |
| -VMware Server 2.x |
| -VMware ESX 3.x |
| -VMware ESX 4.x |
| -VMware ESXi 4.x |
| |
| =cut |
| |
| ############################################################################### |
| package VCL::Module::Provisioning::VMware::VMware; |
| |
| # Specify the lib path using FindBin |
| use FindBin; |
| use lib "$FindBin::Bin/../../../.."; |
| |
| # Configure inheritance |
| use base qw(VCL::Module::Provisioning); |
| |
| # 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; |
| no warnings 'redefine'; |
| |
| use English qw(-no_match_vars); |
| use IO::File; |
| use Fcntl qw(:DEFAULT :flock); |
| use File::Temp qw(tempfile); |
| use List::Util qw(max); |
| use Storable qw(dclone); |
| use Term::ANSIColor 2.00 qw(:constants colored); |
| |
| use VCL::utils; |
| |
| ############################################################################### |
| |
| =head1 CLASS VARIABLES |
| |
| =cut |
| |
| =head2 %VM_OS_CONFIGURATION |
| |
| Data type : hash |
| Description : Maps OS names to the appropriate guestOS, Ethernet, and SCSI |
| virtualDev values to be used in the vmx file. |
| |
| =cut |
| |
| our %VM_OS_CONFIGURATION = ( |
| # Linux configurations: |
| "linux-x86" => { |
| "guestOS" => "otherlinux", |
| "ethernet-virtualDev" => "vlance", |
| "scsi-virtualDev" => "busLogic", |
| }, |
| "linux-x86_64" => { |
| "guestOS" => "otherlinux-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| }, |
| # OSX configuration |
| "osx-x86" => { |
| "guestOS" => "darwin10-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| }, |
| "osx-x86_64" => { |
| "guestOS" => "darwin10-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| }, |
| # Windows configurations: |
| "winxp-x86" => { |
| "guestOS" => "winXPPro", |
| "ethernet-virtualDev" => "vlance", |
| "scsi-virtualDev" => "busLogic", |
| "cpu_socket_limit" => 2, |
| }, |
| "winxp-x86_64" => { |
| "guestOS" => "winXPPro-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| "cpu_socket_limit" => 2, |
| }, |
| "winvista-x86" => { |
| "guestOS" => "winvista", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| "cpu_socket_limit" => 2, |
| }, |
| "vista-x86_64" => { |
| "guestOS" => "winvista-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| "cpu_socket_limit" => 2, |
| }, |
| "win7-x86" => { |
| "guestOS" => "windows7", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| "cpu_socket_limit" => 2, |
| }, |
| "win7-x86_64" => { |
| "guestOS" => "windows7-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| "cpu_socket_limit" => 2, |
| }, |
| "win8-x86" => { |
| "guestOS" => "windows8", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| "cpu_socket_limit" => 2, |
| }, |
| "win8-x86_64" => { |
| "guestOS" => "windows8-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| "cpu_socket_limit" => 2, |
| }, |
| "win10-x86" => { |
| "guestOS" => "windows9", |
| "ethernet-virtualDev" => "e1000e", |
| "scsi-virtualDev" => "lsisas1068", |
| "cpu_socket_limit" => 2, |
| }, |
| "win10-x86_64" => { |
| "guestOS" => "windows9-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsisas1068", |
| "cpu_socket_limit" => 2, |
| }, |
| "win2003-x86" => { |
| "guestOS" => "winNetEnterprise", |
| "ethernet-virtualDev" => "vlance", |
| "scsi-virtualDev" => "lsiLogic", |
| "cpu_socket_limit" => 64, |
| }, |
| "win2003-x86_64" => { |
| "guestOS" => "winNetEnterprise-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| "cpu_socket_limit" => 64, |
| }, |
| "win2008-x86" => { |
| "guestOS" => "winServer2008Enterprise-32", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| "cpu_socket_limit" => 64, |
| }, |
| "win2008-x86_64" => { |
| "guestOS" => "winServer2008Enterprise-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| "cpu_socket_limit" => 64, |
| }, |
| "win2012-x86_64" => { |
| "guestOS" => "windows8srv-64", |
| "ethernet-virtualDev" => "e1000e", |
| "scsi-virtualDev" => "lsisas1068", |
| "cpu_socket_limit" => 64, |
| }, |
| "win2016-x86_64" => { |
| "guestOS" => "windows9srv-64", |
| "ethernet-virtualDev" => "e1000e", |
| "scsi-virtualDev" => "lsisas1068", |
| "cpu_socket_limit" => 64, |
| }, |
| |
| # Default Windows configuration if Windows version isn't found above: |
| "windows-x86" => { |
| "guestOS" => "windows7", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsisas1068", |
| }, |
| "windows-x86_64" => { |
| "guestOS" => "windows7-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsisas1068", |
| }, |
| |
| # Default configuration if OS is not Windows or Linux: |
| "default-x86" => { |
| "guestOS" => "otherlinux", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| }, |
| "default-x86_64" => { |
| "guestOS" => "otherlinux-64", |
| "ethernet-virtualDev" => "e1000", |
| "scsi-virtualDev" => "lsiLogic", |
| }, |
| ); |
| |
| =head2 $VSPHERE_SDK_PACKAGE |
| |
| Data type : string |
| Description : Perl package name for the vSphere SDK module. |
| |
| =cut |
| |
| our $VSPHERE_SDK_PACKAGE = 'VCL::Module::Provisioning::VMware::vSphere_SDK'; |
| |
| =head2 $VIX_API_PACKAGE |
| |
| Data type : string |
| Description : Perl package name for the VIX API module. |
| |
| =cut |
| |
| our $VIX_API_PACKAGE = 'VCL::Module::Provisioning::VMware::VIX_API'; |
| |
| =head2 $VIM_SSH_PACKAGE |
| |
| Data type : string |
| Description : Perl package name for the VIM SSH command module. |
| |
| =cut |
| |
| our $VIM_SSH_PACKAGE = 'VCL::Module::Provisioning::VMware::VIM_SSH'; |
| |
| =head2 $VMWARE_CMD_PACKAGE |
| |
| Data type : string |
| Description : Perl package name for the vmware-cmd module. |
| |
| =cut |
| |
| our $VMWARE_CMD_PACKAGE = 'VCL::Module::Provisioning::VMware::vmware_cmd'; |
| |
| ############################################################################### |
| |
| =head1 OBJECT METHODS |
| |
| =cut |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 initialize |
| |
| Parameters : none |
| Returns : boolean |
| Description : Determines how the VM and VM host can be contolled. Creates an |
| API object which is used to control the VM throughout the |
| reservation. Creates a VM host OS object to be used to control |
| the VM host throughout the reservation. |
| |
| =cut |
| |
| sub initialize { |
| 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; |
| } |
| |
| # Get a DataStructure object containing data for the VM host computer |
| my $vmhost_data = $self->get_vmhost_datastructure(); |
| if (!$vmhost_data) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create VM host DataStructure object"); |
| return; |
| } |
| |
| my $request_state_name = $self->data->get_request_state_name(); |
| |
| # Used only for development/testing |
| # If request state is 'test', full initialization is bypassed by default to speed things up |
| # Passing '1' as the argument causes full initialization |
| if ($request_state_name eq 'test') { |
| my $argument = shift; |
| unless (defined($argument) && !ref($argument) && $argument eq '1') { |
| notify($ERRORS{'DEBUG'}, 0, "request state is '$request_state_name', bypassing full " . ref($self) . " object initialization"); |
| return 1; |
| } |
| } |
| |
| my $vmhost_computer_name = $vmhost_data->get_computer_node_name(); |
| my $vmhost_image_name = $vmhost_data->get_image_name(); |
| my $vmhost_os_module_package = $vmhost_data->get_image_os_module_perl_package(); |
| my $vmhost_lastcheck_time = $vmhost_data->get_computer_lastcheck_time(0); |
| my $vmhost_computer_id = $self->data->get_vmhost_computer_id(); |
| my $vmprofile_name = $self->data->get_vmhost_profile_name(); |
| my $vmprofile_password = $self->data->get_vmhost_profile_password(0); |
| |
| notify($ERRORS{'DEBUG'}, 0, "initializing " . ref($self) . " object"); |
| |
| my $vmware_api; |
| |
| notify($ERRORS{'DEBUG'}, 0, "VM profile assigned to $vmhost_computer_name: $vmprofile_name"); |
| |
| # Create an API object which will be used to control the VM (register, power on, etc.) |
| if ($vmprofile_password && ($vmware_api = $self->get_vmhost_api_object($VSPHERE_SDK_PACKAGE)) && !$vmware_api->is_restricted()) { |
| notify($ERRORS{'DEBUG'}, 0, "vSphere SDK object will be used to control VM host $vmhost_computer_name"); |
| |
| $self->set_vmhost_os($vmware_api); |
| $vmware_api->set_vmhost_os($vmware_api); |
| } |
| else { |
| # SSH access to the VM host OS is required if the vSphere SDK can't be used |
| if (!$self->vmhost_os) { |
| notify($ERRORS{'WARNING'}, 0, "unable to control VM host $vmhost_computer_name, vSphere SDK cannot be used and the VM host OS object is not available"); |
| return; |
| } |
| |
| # Check if SSH is responding |
| if ($self->vmhost_os->is_ssh_responding(3)) { |
| notify($ERRORS{'OK'}, 0, "OS on VM host $vmhost_computer_name will be controlled using " . ref($self->vmhost_os) . " OS object"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "unable to control OS of VM host $vmhost_computer_name using $vmhost_os_module_package OS object because VM host is not responding to SSH"); |
| return; |
| } |
| |
| # Try to create one of the other types of objects to control the VM host |
| if ($vmware_api = $self->get_vmhost_api_object($VIM_SSH_PACKAGE)) { |
| notify($ERRORS{'DEBUG'}, 0, "VM host $vmhost_computer_name will be controlled using vim-cmd via SSH"); |
| } |
| elsif ($vmware_api = $self->get_vmhost_api_object($VMWARE_CMD_PACKAGE)) { |
| notify($ERRORS{'DEBUG'}, 0, "VM host $vmhost_computer_name will be controlled using vmware-cmd via SSH"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to create an object to control VM host: $vmhost_computer_name"); |
| return; |
| } |
| } |
| |
| if ($SETUP_MODE) { |
| $vmware_api->initialize() || return; |
| } |
| |
| # Store the VM host API object in this object |
| $self->{api} = $vmware_api; |
| |
| notify($ERRORS{'DEBUG'}, 0, "VMware OS and API objects created for VM host $vmhost_computer_name:\n" . |
| "VM host OS object type: " . ref($self->vmhost_os) . "\n" . |
| "VMware API object type: " . ref($self->api) . "\n" |
| ); |
| |
| # Make sure the VMware product name can be retrieved |
| my $vmhost_product_name = $self->get_vmhost_product_name(); |
| if (!$vmhost_product_name) { |
| notify($ERRORS{'WARNING'}, 0, "VMware module initialization failed, unable to determine VMware product installed on VM host $vmhost_computer_name"); |
| return; |
| } |
| |
| if ($SETUP_MODE) { |
| return 1; |
| } |
| |
| # Make sure the vmx and vmdk base directories can be accessed |
| my $vmx_base_directory_path = $self->get_vmx_base_directory_path(); |
| if (!$vmx_base_directory_path) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine vmx base directory path on VM host $vmhost_computer_name"); |
| return; |
| } |
| elsif (!$self->vmhost_os->file_exists($vmx_base_directory_path)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to access vmx base directory path on VM host $vmhost_computer_name: $vmx_base_directory_path"); |
| return; |
| } |
| |
| my $vmdk_base_directory_path = $self->get_vmdk_base_directory_path(); |
| if (!$vmdk_base_directory_path) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine vmdk base directory path on VM host $vmhost_computer_name"); |
| return; |
| } |
| elsif ($vmx_base_directory_path eq $vmdk_base_directory_path) { |
| notify($ERRORS{'DEBUG'}, 0, "not checking if vmdk base directory exists because it is the same as the vmx base directory: $vmdk_base_directory_path"); |
| } |
| elsif (!$self->vmhost_os->file_exists($vmdk_base_directory_path)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to access vmdk base directory path: $vmdk_base_directory_path"); |
| return; |
| } |
| |
| # Retrieve the VM host's hardware info if: |
| # -request state is 'timeout', don't slow down user reservations |
| # -VM host computer.lastcheck is NULL or more than 30 days old |
| if ($request_state_name eq 'timeout' && (!$vmhost_lastcheck_time || (time - convert_to_epoch_seconds($vmhost_lastcheck_time)) > (60 * 60 * 24 * 30))) { |
| # Configure the SSH authorized_keys file to persist through reboots if the VM host is running VMware ESXi |
| # This shouldn't need to be done more than once, only call this if the state is 'reclaim' |
| if (ref($self->vmhost_os) =~ /Linux/i && $vmhost_product_name =~ /ESXi 4/) { |
| $self->configure_vmhost_dedicated_ssh_key(); |
| } |
| |
| # Retrieve the CPU core count, update the database if necessary |
| my $cpu_core_count; |
| if ($self->api->can('get_cpu_core_count')) { |
| $cpu_core_count = $self->api->get_cpu_core_count(); |
| } |
| elsif (!$cpu_core_count && $self->vmhost_os->can('get_cpu_core_count')) { |
| $cpu_core_count = $self->vmhost_os->get_cpu_core_count(); |
| } |
| |
| if (!$cpu_core_count) { |
| notify($ERRORS{'DEBUG'}, 0, "VM host computer.procnumber not updated, CPU core count could not be retrieved from the API or VM host OS object"); |
| } |
| elsif ($cpu_core_count eq $vmhost_data->get_computer_processor_count()) { |
| notify($ERRORS{'DEBUG'}, 0, "VM host computer.procnumber is already correct in the database"); |
| } |
| else { |
| update_computer_procnumber($vmhost_computer_id, $cpu_core_count); |
| } |
| |
| # Retrieve the CPU speed, update the database if necessary |
| my $cpu_speed; |
| if ($self->api->can('get_cpu_speed')) { |
| $cpu_speed = $self->api->get_cpu_speed(); |
| } |
| elsif (!$cpu_speed && $self->vmhost_os->can('get_cpu_speed')) { |
| $cpu_speed = $self->vmhost_os->get_cpu_speed(); |
| } |
| |
| if (!$cpu_speed) { |
| notify($ERRORS{'DEBUG'}, 0, "VM host computer.procspeed not updated, CPU speed could not be retrieved from the API or VM host OS object"); |
| } |
| elsif ($cpu_speed eq $vmhost_data->get_computer_processor_speed()) { |
| notify($ERRORS{'DEBUG'}, 0, "VM host computer.procspeed is already correct in the database"); |
| } |
| else { |
| update_computer_procspeed($vmhost_computer_id, $cpu_speed); |
| } |
| |
| # Retrieve the RAM, update the database if necessary |
| my $ram_mb; |
| if ($self->api->can('get_total_memory')) { |
| $ram_mb = $self->api->get_total_memory(); |
| } |
| elsif (!$ram_mb && $self->vmhost_os->can('get_total_memory')) { |
| $ram_mb = $self->vmhost_os->get_total_memory(); |
| } |
| |
| if (!$ram_mb) { |
| notify($ERRORS{'DEBUG'}, 0, "VM host computer.RAM not updated, total memory could not be retrieved from the API or VM host OS object"); |
| } |
| elsif ($ram_mb eq $vmhost_data->get_computer_ram()) { |
| notify($ERRORS{'DEBUG'}, 0, "VM host computer.RAM is already correct in the database"); |
| } |
| else { |
| update_computer_ram($vmhost_computer_id, $ram_mb); |
| } |
| |
| # Update the VM host computer lastcheck time to now |
| update_computer_lastcheck($vmhost_computer_id); |
| } |
| elsif ($request_state_name eq 'timeout') { |
| notify($ERRORS{'DEBUG'}, 0, "VM host hardware parameters not updated in the database, last check is less than 30 days ago: $vmhost_lastcheck_time"); |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 node_status |
| |
| Parameters : none |
| Returns : string |
| Description : Checks the status of the computer in order to determine if the |
| computer is ready to be reserved or needs to be reloaded. A |
| string is returned depending on the status of the computer: |
| 'READY': |
| * Computer is ready to be reserved |
| * It is accessible |
| * It is loaded with the correct image |
| * OS module's post-load tasks have run |
| 'POST_LOAD': |
| * Computer is loaded with the correct image |
| * OS module's post-load tasks have not run |
| 'RELOAD': |
| * Computer is not accessible or not loaded with the correct |
| image |
| |
| =cut |
| |
| sub node_status { |
| 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 $request_state_name = $self->data->get_request_state_name(); |
| my $is_server_request = $self->data->is_server_request(); |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| # Fist perform the normal checks using the subroutine in Provisioning.pm |
| my $result = $self->SUPER::node_status(); |
| |
| # If normal checks require a reload, return it |
| if ($result =~ /reload/i) { |
| notify($ERRORS{'OK'}, 0, "skipping VMware node status checks, parent node_status subroutine returned $result"); |
| return $result; |
| } |
| |
| # VM is loaded with the correct image and responding, result is either READY or POST_LOAD |
| # If this is a reload request, no additional checks are necessary |
| if ($request_state_name =~ /reload/) { |
| notify($ERRORS{'DEBUG'}, 0, "request state is '$request_state_name', returning result from normal node_status checks: '$result'"); |
| return $result; |
| } |
| |
| my $vmx_file_path = $self->get_vmx_file_path(); |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine vmx file path, returning 'RELOAD'"); |
| return 'RELOAD'; |
| } |
| |
| #>>>>>>>>>> |
| # Experimental - Support for VMware ESXi's built in VNC server functionality |
| #my $reservation_id = $self->data->get_reservation_id(); |
| #my $nathost_id = $self->data->get_nathost_id(0); |
| #if ($nathost_id) { |
| # my $connect_method_info = get_reservation_connect_method_info($reservation_id); |
| # |
| # my $vnc_connect_method_port_id; |
| # |
| # for my $connect_method_id (keys %$connect_method_info) { |
| # my $connect_method_name = $connect_method_info->{$connect_method_id}{name}; |
| # if ($connect_method_name =~ /(VMWare|ESX)/i && $connect_method_name =~ /VNC/i) { |
| # my @vnc_connect_method_port_ids = keys(%{$connect_method_info->{$connect_method_id}{connectmethodport}}); |
| # $vnc_connect_method_port_id = $vnc_connect_method_port_ids[0]; |
| # last; |
| # } |
| # } |
| # |
| # if ($vnc_connect_method_port_id) { |
| # my $vmx_info = $self->get_vmx_info($vmx_file_path); |
| # if (!$vmx_info) { |
| # notify($ERRORS{'WARNING'}, 0, "unable to retrieve vmx file info, returning 'RELOAD'"); |
| # return 'RELOAD'; |
| # } |
| # |
| # my $vnc_password = $vmx_info->{'remotedisplay.vnc.password'}; |
| # if (!defined($vnc_password)) { |
| # notify($ERRORS{'WARNING'}, 0, "VMware VNC connect method enabled, vmx file does NOT contain VNC password, returning 'RELOAD'"); |
| # return 'RELOAD'; |
| # } |
| # |
| # my $vnc_port = $vmx_info->{'remotedisplay.vnc.port'}; |
| # if (!defined($vnc_port)) { |
| # notify($ERRORS{'WARNING'}, 0, "VMware VNC connect method enabled, vmx file does NOT contain VNC port, returning 'RELOAD'"); |
| # return 'RELOAD'; |
| # } |
| # |
| # if (!$self->data->set_reservation_password($vnc_password) || !update_reservation_password($reservation_id, $vnc_password)) { |
| # notify($ERRORS{'WARNING'}, 0, "VMware VNC connect method enabled, failed to override reservation password, returning 'RELOAD'"); |
| # return 'RELOAD'; |
| # } |
| # |
| # if (!insert_natport($reservation_id, $nathost_id, $vnc_connect_method_port_id, $vnc_port)) { |
| # notify($ERRORS{'WARNING'}, 0, "VMware VNC connect method enabled, failed to override NAT port, returning 'RELOAD'"); |
| # return 'RELOAD'; |
| # } |
| # } |
| #} |
| #<<<<<<<<<< |
| |
| # If this is not a server request, no additional checks are necessary |
| if (!$is_server_request) { |
| notify($ERRORS{'DEBUG'}, 0, "this is not a server request, returning result from normal node_status checks: '$result'"); |
| return $result; |
| } |
| |
| # Server request |
| notify($ERRORS{'DEBUG'}, 0, "normal node_status checks returned $result, this is a server request, checking if $computer_name is using a dedicated or shared virtual disk"); |
| |
| my $vmdk_file_path_dedicated = $self->get_vmdk_file_path_dedicated(); |
| if (!$vmdk_file_path_dedicated) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine dedicated vmdk file path, returning 'RELOAD'"); |
| return 'RELOAD'; |
| } |
| |
| |
| |
| my @vm_virtual_disk_file_paths = $self->api->get_vm_virtual_disk_file_paths($vmx_file_path); |
| if (!@vm_virtual_disk_file_paths) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve virtual disk files paths of $computer_name, returning 'RELOAD'"); |
| return 'RELOAD'; |
| } |
| |
| # Check if any of the vmdk files used by the VM match the dedicated vmdk file path |
| for my $virtual_disk_array_ref (@vm_virtual_disk_file_paths) { |
| for my $file_path (@$virtual_disk_array_ref) { |
| if ($file_path eq $vmdk_file_path_dedicated) { |
| notify($ERRORS{'DEBUG'}, 0, "$computer_name is using a dedicated virtual disk: $file_path, returning '$result'"); |
| return $result; |
| } |
| } |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "$computer_name is NOT using a dedicated virtual disk, returning 'RELOAD'\ndedicated vmdk file path: $vmdk_file_path_dedicated\nvmdk files used by VM:\n" . format_data(\@vm_virtual_disk_file_paths)); |
| return 'RELOAD'; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 unload |
| |
| Parameters : none |
| Returns : boolean |
| Description : Unloads a VM with the requested image. |
| |
| =cut |
| |
| sub unload { |
| 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 $computer_name = $self->data->get_computer_short_name() || return; |
| my $vmhost_name = $self->data->get_vmhost_short_name() || return; |
| |
| # Remove existing VMs which were created for the reservation computer |
| if (!$self->remove_existing_vms()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to remove existing VMs created for computer $computer_name on VM host: $vmhost_name"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 load |
| |
| Parameters : none |
| Returns : boolean |
| Description : Loads a VM with the requested image. |
| |
| =cut |
| |
| sub load { |
| 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 $reservation_id = $self->data->get_reservation_id() || return; |
| my $vmx_file_path = $self->get_vmx_file_path() || return; |
| my $computer_id = $self->data->get_computer_id() || return; |
| my $computer_name = $self->data->get_computer_short_name() || return; |
| my $image_name = $self->data->get_image_name() || return; |
| my $vmhost_name = $self->data->get_vmhost_short_name() || return; |
| |
| |
| insertloadlog($reservation_id, $computer_id, "startload", "$computer_name $image_name"); |
| |
| # Remove existing VMs which were created for the reservation computer |
| if (!$self->remove_existing_vms()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to remove existing VMs created for computer $computer_name on VM host: $vmhost_name"); |
| return; |
| } |
| |
| # Check if enough disk space is available |
| my $enough_disk_space = $self->check_vmhost_disk_space(); |
| if (!defined($enough_disk_space)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine if enough disk space is available on VM host $vmhost_name"); |
| return; |
| } |
| elsif (!$enough_disk_space) { |
| if (!$self->reclaim_vmhost_disk_space()) { |
| notify($ERRORS{'CRITICAL'}, 0, "not enough space is available on VM host $vmhost_name to accomodate the reservation"); |
| return; |
| } |
| } |
| |
| # Check if the .vmdk files exist, copy them if necessary |
| if (!$self->prepare_vmdk()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to prepare vmdk file for $computer_name on VM host: $vmhost_name"); |
| return; |
| } |
| insertloadlog($reservation_id, $computer_id, "transfervm", "copied $image_name to $computer_name"); |
| |
| # Generate the .vmx file |
| if (!$self->prepare_vmx()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to prepare vmx file for $computer_name on VM host: $vmhost_name"); |
| return; |
| } |
| insertloadlog($reservation_id, $computer_id, "vmsetupconfig", "prepared vmx file"); |
| |
| # Register the VM |
| if (!$self->api->vm_register($vmx_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to register VM $computer_name on VM host: $vmhost_name"); |
| return; |
| } |
| |
| # If API implements 'add_ethernet_adapter' the adapters were not added to the vmx, add them now |
| if ($self->api->can('add_ethernet_adapter')) { |
| (my @vm_ethernet_adapter_configuration = $self->get_vm_ethernet_adapter_configuration()) || return; |
| for my $adapter (@vm_ethernet_adapter_configuration) { |
| if (!$self->api->add_ethernet_adapter($vmx_file_path, $adapter)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to add ethernet adapter to VM $computer_name on VM host: $vmhost_name\n" . format_data($adapter)); |
| return; |
| } |
| } |
| } |
| |
| # Create a snapshot of the VM |
| if (!$self->snapshot('register')) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create snapshot before powering on VM $computer_name on VM host: $vmhost_name, attempting to delete VM to prevent the possibility of writing to the shared vmdk if the VM is powered on"); |
| |
| # Snapshot failed. If the VM is powered on, changes will be written directly to the shared vmdk |
| # Attempt to delete the VM to prevent the shared vmdk from being written to |
| if (!$self->delete_vm($vmx_file_path)) { |
| notify($ERRORS{'CRITICAL'}, 0, "failed to delete VM $computer_name on VM host $vmhost_name after failing to create snapshot, changes may be written to shared vmdk if the VM is powered on"); |
| } |
| return; |
| } |
| |
| |
| # Power on the VM |
| if (!$self->power_on($vmx_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to power on VM $computer_name on VM host: $vmhost_name"); |
| return; |
| } |
| insertloadlog($reservation_id, $computer_id, "startvm", "registered and powered on $computer_name"); |
| |
| # Call the OS module's post_load() subroutine if implemented |
| if ($self->os->can("post_load")) { |
| if ($self->os->post_load()) { |
| notify($ERRORS{'OK'}, 0, "performed OS post-load tasks on $computer_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to perform OS post-load tasks on VM $computer_name on VM host: $vmhost_name"); |
| return; |
| } |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "OS post-load tasks not necessary on $computer_name"); |
| } |
| |
| # Check if the VM has the expected number of CPUs |
| # Some OS's don't recognize additional CPUs when the VM is first loaded if the computer used to capture the image had fewer CPUs |
| # Reboot the computer if it has fewer CPUs than expected |
| my $vm_os_reported_cpu_count = $self->os->get_cpu_core_count(); |
| my $vm_expected_cpu_count = $self->get_vm_cpu_configuration(); |
| |
| if (!$vm_os_reported_cpu_count) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if VM OS recognized all CPUs, CPU count could not be retrieved from the VM OS"); |
| } |
| elsif (!$vm_expected_cpu_count) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if VM OS recognized all CPUs, expected CPU count could not be determined"); |
| } |
| elsif ($vm_os_reported_cpu_count == $vm_expected_cpu_count) { |
| notify($ERRORS{'DEBUG'}, 0, "verified VM OS recognized correct number of CPUs: $vm_os_reported_cpu_count"); |
| } |
| elsif ($vm_os_reported_cpu_count > $vm_expected_cpu_count) { |
| notify($ERRORS{'WARNING'}, 0, "VM OS recognized more CPUs than expected:\nrecognized CPU count: $vm_os_reported_cpu_count\nexpected CPU count: $vm_expected_cpu_count"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "VM OS did not recognize all CPUs, rebooting VM:\nrecognized CPU count: $vm_os_reported_cpu_count\nexpected CPU count: $vm_expected_cpu_count"); |
| $self->os->reboot(240, 4, 1, 0) || return; |
| |
| $vm_os_reported_cpu_count = $self->os->get_cpu_core_count(); |
| if ($vm_os_reported_cpu_count) { |
| if ($vm_os_reported_cpu_count < $vm_expected_cpu_count) { |
| notify($ERRORS{'WARNING'}, 0, "VM OS did not recognize all CPUs after rebooting VM:\nrecognized CPU count: $vm_os_reported_cpu_count\nexpected CPU count: $vm_expected_cpu_count"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "VM OS recognized correct number of CPUs after rebooting: $vm_os_reported_cpu_count"); |
| } |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if VM OS recognized all CPUs, CPU count could not be retrieved from the VM OS after rebooting"); |
| } |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 capture |
| |
| Parameters : none |
| Returns : boolean |
| Description : Captures a VM image. |
| |
| =cut |
| |
| sub capture { |
| 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; |
| } |
| |
| my $request_state_name = $self->data->get_request_state_name(); |
| my $computer_name = $self->data->get_computer_short_name(); |
| my $image_name = $self->data->get_image_name(); |
| my $vmhost_name = $self->data->get_vmhost_short_name(); |
| my $vmprofile_name = $self->data->get_vmhost_profile_name(); |
| my $vmprofile_vmdisk = $self->data->get_vmhost_profile_vmdisk(); |
| my $vmdk_base_directory_path_shared = $self->get_vmdk_base_directory_path_shared(); |
| my $repository_mounted_on_vmhost = $self->is_repository_mounted_on_vmhost(); |
| |
| # Determine the vmx file path actively being used by the VM |
| my $vmx_file_path_original = $self->get_active_vmx_file_path(); |
| if (!$vmx_file_path_original) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine the vmx file path actively being used by VM $computer_name"); |
| return; |
| } |
| |
| # Set the vmx file path in this object so that it overrides the default value that would normally be constructed |
| if (!$self->set_vmx_file_path($vmx_file_path_original)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the vmx file to the path that was determined to be in use by VM $computer_name being captured: $vmx_file_path_original"); |
| return; |
| } |
| |
| # Get the vmx directory path of the VM being captured |
| my $vmx_directory_path_original = $self->get_vmx_directory_path(); |
| if (!$vmx_directory_path_original) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine the vmx directory path of VM $computer_name being captured"); |
| return; |
| } |
| |
| # Get the information contained within the vmx file |
| my $vmx_info = $self->get_vmx_info($vmx_file_path_original); |
| notify($ERRORS{'DEBUG'}, 0, "vmx info for VM $computer_name being captured:\n" . format_data($vmx_info)); |
| |
| # Get the vmdk info from the vmx info |
| my @vmdk_identifiers = keys %{$vmx_info->{vmdk}}; |
| if (!@vmdk_identifiers) { |
| notify($ERRORS{'WARNING'}, 0, "did not find vmdk file path ({vmdk} key is missing) in vmx info for VM $computer_name being captured:\n" . format_data($vmx_info)); |
| return; |
| } |
| elsif (scalar(@vmdk_identifiers) > 1) { |
| notify($ERRORS{'WARNING'}, 0, "found multiple vmdk file paths ({vmdk} keys) in vmx info for VM $computer_name being captured:\n" . format_data($vmx_info)); |
| return; |
| } |
| |
| # Get the vmdk file path to be captured from the vmx information |
| my $vmdk_file_path_original = $vmx_info->{vmdk}{$vmdk_identifiers[0]}{vmdk_file_path}; |
| if (!$vmdk_file_path_original) { |
| notify($ERRORS{'WARNING'}, 0, "vmdk file path to be captured was not found in the vmx info for VM $computer_name being captured:\n" . format_data($vmx_info)); |
| return; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "vmdk file path configured for VM $computer_name being captured: $vmdk_file_path_original"); |
| |
| # Set the vmdk file path in this object so that it overrides the default value that would normally be constructed |
| if (!$self->set_vmdk_file_path($vmdk_file_path_original)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the vmdk file to the path that is configured for VM $computer_name being captured: $vmdk_file_path_original"); |
| return; |
| } |
| |
| # Get the vmdk directory path |
| my $vmdk_directory_path_original = $self->get_vmdk_directory_path(); |
| |
| # NOTE! Don't change $vmx_file_path_original, $vmx_directory_path_original, $vmdk_file_path_original, or $vmdk_directory_path_original after this point |
| # They should not be changed in order to check later on whether the original VM can be deleted |
| |
| # Get the vmdk mode from the vmx information and make sure it is not nonpersistent |
| my $vmdk_mode = $vmx_info->{vmdk}{$vmdk_identifiers[0]}{mode}; |
| if (!$vmdk_mode) { |
| notify($ERRORS{'WARNING'}, 0, "vmdk mode was not found in the vmx info for VM $computer_name being captured:\n" . format_data($vmx_info)); |
| return; |
| } |
| elsif ($vmdk_mode =~ /nonpersistent/i) { |
| notify($ERRORS{'WARNING'}, 0, "mode of vmdk: $vmdk_mode, the mode must be persistent or independent-persistent in order to be captured"); |
| return; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "mode of vmdk to be captured is valid: $vmdk_mode"); |
| |
| |
| # Construct the vmdk directory and file path where the captured image will be saved |
| my $vmdk_directory_path_renamed = "$vmdk_base_directory_path_shared/$image_name"; |
| my $vmdk_file_path_renamed = "$vmdk_directory_path_renamed/$image_name.vmdk"; |
| |
| # Construct the path of the reference vmx file to be saved with the vmdk |
| # The .vmx file is only saved so that it can be referenced later |
| my $reference_vmx_file_name = $self->get_reference_vmx_file_name(); |
| my $vmx_file_path_renamed = "$vmdk_directory_path_renamed/$reference_vmx_file_name"; |
| |
| # Make sure the vmdk file path for the captured image doesn't already exist |
| # Do this before calling pre_capture and shutting down the VM |
| if ($vmdk_file_path_original ne $vmdk_file_path_renamed && $self->vmhost_os->file_exists($vmdk_file_path_renamed)) { |
| notify($ERRORS{'WARNING'}, 0, "vmdk file that captured image will be renamed to already exists: $vmdk_file_path_renamed"); |
| return; |
| } |
| |
| # Set the imagemeta Sysprep value to 0 to prevent Sysprep from being used |
| $self->data->set_imagemeta_sysprep(0); |
| |
| # Attempt to get the name of the OS running on the VM and tag the .vmx file with it |
| # The name will be saved in the reference .vmx file and can be used to select the most appropriate guest OS when loading future VMs |
| my $os_product_name = $self->os->get_product_name() if $self->os->can("get_product_name"); |
| my $os_is_64_bit = $self->os->is_64_bit() if $self->os->can("is_64_bit"); |
| |
| # Call the OS module's pre_capture() subroutine if implemented |
| if ($self->os->can("pre_capture") && !$self->os->pre_capture({end_state => 'off'})) { |
| notify($ERRORS{'WARNING'}, 0, "failed to complete OS module's pre_capture tasks"); |
| return; |
| } |
| |
| # Wait for the VM to power off |
| # This OS module may initiate a shutdown and immediately return |
| if (!$self->wait_for_power_off(600)) { |
| notify($ERRORS{'WARNING'}, 0, "VM $computer_name has not powered off after the OS module's pre_capture tasks were completed, powering off VM forcefully"); |
| |
| if ($self->api->vm_power_off($vmx_file_path_original)) { |
| # Sleep for 10 seconds to make sure the power off is complete |
| sleep 10; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to power off the VM being captured after the OS module's pre_capture tasks were completed"); |
| return; |
| } |
| } |
| |
| # Tag the .vmx with the OS product name and architecture |
| $self->vmhost_os->append_text_file($vmx_file_path_original, "#os_product_name = \"$os_product_name\"") if (defined($os_product_name)); |
| $self->vmhost_os->append_text_file($vmx_file_path_original, "#os_64_bit = \"$os_is_64_bit\"") if (defined($os_is_64_bit)); |
| |
| |
| if ($vmprofile_vmdisk =~ /(local|dedicated)/ && $repository_mounted_on_vmhost) { |
| # See https://issues.apache.org/jira/browse/VCL-633 |
| # Don't save copy on VM host's datastore if dedicated, datastore may run out of space |
| notify($ERRORS{'DEBUG'}, 0, "vmx and vmdk files will not be copied or renamed directly on the host, the VM profile disk type is $vmprofile_vmdisk and the image repository is mounted on the host"); |
| $vmdk_file_path_renamed = $vmdk_file_path_original; |
| |
| # Need to copy the original vmx to vmx.reference using the base name of the vmdk |
| # copy_vmdk expects a file with this name to exist in order for the reference file to be created in the target directory |
| my $vmdk_file_base_name_renamed = $self->_get_file_base_name($vmdk_file_path_renamed); |
| $vmx_file_path_renamed = "$vmx_directory_path_original/$vmdk_file_base_name_renamed.vmx.reference"; |
| } |
| else { |
| # Rename the vmdk to the new image directory and file name |
| # First check if vmdk file path already matches the destination file path |
| if ($vmdk_file_path_original eq $vmdk_file_path_renamed) { |
| notify($ERRORS{'DEBUG'}, 0, "vmdk files will not be renamed, vmdk file path being captured is already named as the image being captured: '$vmdk_file_path_original'"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "vmdk files will be renamed: '$vmdk_file_path_original' --> '$vmdk_file_path_renamed'"); |
| if (!$self->copy_vmdk($vmdk_file_path_original, $vmdk_file_path_renamed)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to copy the vmdk files after the VM was powered off: '$vmdk_file_path_original' --> '$vmdk_file_path_renamed'"); |
| return; |
| } |
| } |
| } |
| |
| # Copy the vmx file to the new image directory for later reference |
| # First check if vmx file already exists (could happen if base image VM was manually created) |
| if ($vmx_file_path_original eq $vmx_file_path_renamed) { |
| notify($ERRORS{'DEBUG'}, 0, "vmx file will not be copied, vmx file path being captured is already named as the image being captured: '$vmx_file_path_original'"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "vmx file will be copied: '$vmx_file_path_original' --> '$vmx_file_path_renamed'"); |
| if (!$self->vmhost_os->copy_file($vmx_file_path_original, $vmx_file_path_renamed)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to copy the reference vmx file after the VM was powered off: '$vmx_file_path_original' --> '$vmx_file_path_renamed'"); |
| return; |
| } |
| } |
| |
| # Copy the vmdk to the image repository if the repository path is defined in the VM profile |
| my $repository_directory_path = $self->get_repository_vmdk_directory_path(); |
| if ($repository_directory_path) { |
| my $repository_copy_successful = 0; |
| |
| # Check if the image repository path configured in the VM profile is mounted on the host or on the management node |
| if ($repository_mounted_on_vmhost) { |
| # Files can be copied directly to the image repository and converted while they are copied |
| my $repository_vmdk_file_path = $self->get_repository_vmdk_file_path(); |
| notify($ERRORS{'DEBUG'}, 0, "vmdk will be copied directly from VM host $vmhost_name to the image repository in the 2gbsparse disk format: '$vmdk_file_path_renamed' --> '$repository_vmdk_file_path'"); |
| if ($self->copy_vmdk($vmdk_file_path_renamed, $repository_vmdk_file_path, '2gbsparse')) { |
| $repository_copy_successful = 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to copy the vmdk files to the repository mounted on the VM host after the VM was powered off: '$vmdk_file_path_renamed' --> '$repository_vmdk_file_path'"); |
| } |
| } |
| else { |
| # Repository is not mounted on the VM host |
| # Check if virtual disk type is sparse - vmdk can't be converted to 2gb sparse while it is copied |
| # If the virtual disk isn't sparse a sparse copy is created on the datastore |
| my $vmdk_directory_path_sparse; |
| my $vmdk_file_path_sparse; |
| my @vmdk_copy_paths; |
| |
| my $virtual_disk_type = $self->api->get_virtual_disk_type($vmdk_file_path_renamed); |
| if (!$virtual_disk_type) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine the virtual disk type of the vmdk being captured: $vmdk_file_path_renamed"); |
| } |
| elsif ($virtual_disk_type =~ /sparse/i) { |
| # Virtual disk is sparse, get a list of the vmdk file paths |
| notify($ERRORS{'DEBUG'}, 0, "vmdk can be copied directly from VM host $vmhost_name to the image repository because the virtual disk type is sparse: $virtual_disk_type"); |
| @vmdk_copy_paths = $self->find_datastore_files($vmdk_directory_path_renamed, '*.vmdk'); |
| } |
| else { |
| # Virtual disk is NOT sparse - a sparse copy must first be created before being copied to the repository |
| notify($ERRORS{'DEBUG'}, 0, "vmdk disk type: $virtual_disk_type, a temporary 2gbsparse copy of the vmdk will be made on VM host $vmhost_name, copied to the image repository, and then deleted from the VM host"); |
| |
| # Construct the vmdk file path where the 2gbsparse copy will be created |
| # The vmdk files are copied to a directory with the same name but with '_2gbsparse' appended to the directory name |
| # The vmdk files in the '_2gbsparse' are named the same as the original non-sparse directory |
| $vmdk_directory_path_sparse = "$vmdk_directory_path_renamed\_2gbsparse"; |
| $vmdk_file_path_sparse = "$vmdk_directory_path_sparse/$image_name.vmdk"; |
| |
| # Create a sparse copy of the virtual disk |
| if ($self->copy_vmdk($vmdk_file_path_renamed, $vmdk_file_path_sparse, '2gbsparse')) { |
| # Get a list of the 2gbsparse vmdk file paths |
| @vmdk_copy_paths = $self->find_datastore_files($vmdk_directory_path_sparse, '*.vmdk'); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to create a temporary 2gbsparse copy of the vmdk file: '$vmdk_file_path_renamed' --> '$vmdk_file_path_sparse'"); |
| } |
| } |
| |
| # Copy the vmdk directory from the VM host to the image repository |
| if (@vmdk_copy_paths) { |
| # Add the reference vmx file path to the array so that the vmx is copied to the repository |
| push @vmdk_copy_paths, $vmx_file_path_renamed; |
| |
| # Loop through the files, copy each to the management node's repository directory |
| notify($ERRORS{'DEBUG'}, 0, "vmdk files will be copied from VM host $vmhost_name to the image repository on the management node:\n" . join("\n", sort @vmdk_copy_paths)); |
| VMDK_COPY_PATH: for my $vmdk_copy_path (@vmdk_copy_paths) { |
| my ($vmdk_copy_name) = $vmdk_copy_path =~ /([^\/]+)$/; |
| |
| # Set the flag to 1 before copying, set it back to 0 if any files fail to be copied |
| $repository_copy_successful = 1; |
| |
| if (!$self->vmhost_os->copy_file_from($vmdk_copy_path, "$repository_directory_path/$vmdk_copy_name")) { |
| notify($ERRORS{'WARNING'}, 0, "failed to copy vmdk file from VM host $vmhost_name to the management node:\n '$vmdk_copy_path' --> '$repository_directory_path/$vmdk_copy_name'"); |
| $repository_copy_successful = 0; |
| last VMDK_COPY_PATH; |
| } |
| } |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to find the vmdk files on VM host $vmhost_name to copy back to the managment node's image repository"); |
| } |
| |
| # Check if the $vmdk_directory_path_sparse variable has been set |
| # If set, a sparse copy of the vmdk files had to be created |
| # The sparse directory should be deleted |
| if ($vmdk_directory_path_sparse) { |
| notify($ERRORS{'DEBUG'}, 0, "deleting the directory containing the temporary 2gbsparse copy of the vmdk files which were copied to the image repository: $vmdk_directory_path_sparse"); |
| |
| if (!$self->vmhost_os->delete_file($vmdk_directory_path_sparse)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete the directory containing the 2gbsparse copy of the vmdk files: $vmdk_directory_path_sparse"); |
| } |
| } |
| } |
| |
| # The $repository_copy_successful flag should be set to 1 by this point if the copy was successful |
| if (!$repository_copy_successful) { |
| # Rename the vmdk back to the original file name |
| # This is necessary to power the VM back on in order to fix the problem because the VM's vmx file still contains the path to the original vmdk |
| # First check if vmdk file path already matches the destination file path |
| if ($vmdk_file_path_original eq $vmdk_file_path_renamed) { |
| notify($ERRORS{'DEBUG'}, 0, "vmdk file does not need to be renamed back to the original name, vmdk file path being captured is already named as the image being captured: '$vmdk_file_path_original'"); |
| |
| # Attempt to power the VM back on |
| # This saves a step when troubleshooting the problem |
| notify($ERRORS{'DEBUG'}, 0, "attempting to power the VM back on so that it can be captured again"); |
| $self->power_on($vmx_file_path_original); |
| } |
| else { |
| # Delete the directory where the vmdk was copied |
| if ($vmdk_directory_path_original ne $vmdk_directory_path_renamed) { |
| notify($ERRORS{'DEBUG'}, 0, "attempting to delete directory where moved vmdk resided before reverting the name back to the original: $vmdk_directory_path_renamed"); |
| $self->vmhost_os->delete_file($vmdk_directory_path_renamed); |
| } |
| |
| # Attempt to power the VM back on |
| # This saves a step when troubleshooting the problem |
| notify($ERRORS{'DEBUG'}, 0, "attempting to power the VM back on so that it can be captured again"); |
| $self->power_on($vmx_file_path_original); |
| } |
| return; |
| } |
| |
| # Attempt to set permissions on the image repository directory |
| # Don't fail the capture if this fails, it only affects image retrieval from another managment node |
| $self->set_image_repository_permissions(); |
| } |
| else { |
| # The repository path isn't set in the VM profile |
| notify($ERRORS{'OK'}, 0, "vmdk files NOT copied to the image repository because the repository path is not configured in VM profile '$vmprofile_name'"); |
| } |
| |
| # Delete the VM that was captured |
| # Make sure the VM's vmx and vmdk path don't match the path of the captured image |
| if ($vmx_directory_path_original eq $vmdk_directory_path_renamed) { |
| notify($ERRORS{'WARNING'}, 0, "VM will NOT be deleted because the VM's vmx directory path matches the captured vmdk directory path: '$vmdk_directory_path_renamed'"); |
| } |
| elsif ($vmdk_directory_path_original eq $vmdk_directory_path_renamed) { |
| notify($ERRORS{'WARNING'}, 0, "VM will NOT be deleted because the VM's vmdk directory path configured in the vmx file matches the captured vmdk directory path: '$vmdk_directory_path_renamed'"); |
| } |
| elsif ($request_state_name !~ /^(image)$/) { |
| notify($ERRORS{'OK'}, 0, "VM will NOT be deleted because the request state is '$request_state_name'"); |
| } |
| else { |
| # Delete the VM |
| if (!$self->delete_vm($vmx_file_path_original)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete the VM after the image was captured: $vmx_file_path_original"); |
| } |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_active_vmx_file_path |
| |
| Parameters : none |
| Returns : string |
| Description : Determines the path to the vmx file being used by the VM assigned |
| to the reservation. It essentually does a reverse lookup to |
| locate the VM's vmx file given a running VM. This is accomplished |
| by retrieving the MAC addresses being used by the VM according to |
| the OS. The MAC addresses configured within the vmx files on the |
| host are then checked to locate a match. This allows an image |
| capture to work if the VM was created by hand with a different |
| vmx directory name or file name. This is useful to make base |
| image capture easier with fewer restrictions. |
| |
| If the MAC addresses cannot be retrieved from the VM's OS, the |
| MAC addresses in the database are used. |
| |
| =cut |
| |
| sub get_active_vmx_file_path { |
| 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; |
| } |
| |
| my $os_type = $self->data->get_image_os_type(); |
| my $computer_name = $self->data->get_computer_short_name(); |
| my $computer_eth0_mac_address = $self->data->get_computer_eth0_mac_address(); |
| my $computer_eth1_mac_address = $self->data->get_computer_eth1_mac_address(); |
| |
| my @vm_mac_addresses; |
| |
| if (!$self->os->is_ssh_responding()) { |
| notify($ERRORS{'WARNING'}, 0, "$computer_name is not responding, unable to verify MAC addresses reported by OS match MAC addresses in vmx file"); |
| @vm_mac_addresses = ($computer_eth0_mac_address, $computer_eth1_mac_address); |
| } |
| else { |
| my $active_os; |
| my $active_os_type = $self->os->get_os_type(); |
| if (!$active_os_type) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine OS type currently installed on $computer_name"); |
| $active_os = $self->os(); |
| } |
| elsif ($active_os_type ne $os_type) { |
| notify($ERRORS{'DEBUG'}, 0, "OS type currently installed on $computer_name does not match the OS type of the reservation image:\nOS type installed on $computer_name: $active_os_type\nreservation image OS type: $os_type"); |
| |
| my $active_os_perl_package; |
| if ($active_os_type =~ /linux/i) { |
| $active_os_perl_package = 'VCL::Module::OS::Linux'; |
| } |
| else { |
| $active_os_perl_package = 'VCL::Module::OS::Windows'; |
| } |
| |
| if ($active_os = $self->create_os_object($active_os_perl_package)) { |
| notify($ERRORS{'DEBUG'}, 0, "created a '$active_os_perl_package' OS object for the '$active_os_type' OS type currently installed on $computer_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine active vmx file path, failed to create a '$active_os_perl_package' OS object for the '$active_os_type' OS type currently installed on $computer_name"); |
| return; |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "'$active_os_type' OS type currently installed on $computer_name matches the OS type of the image assigned to this reservation"); |
| $active_os = $self->os; |
| } |
| |
| # Make sure the active OS object implements the required subroutines called below |
| if (!$active_os->can('get_private_mac_address') || !$active_os->can('get_public_mac_address')) { |
| notify($ERRORS{'WARNING'}, 0, ref($active_os) . " OS object does not implement 'get_private_mac_address' and 'get_public_mac_address' subroutines, unable to verify MAC addresses reported by OS match MAC addresses in vmx file"); |
| @vm_mac_addresses = ($computer_eth0_mac_address, $computer_eth1_mac_address); |
| } |
| else { |
| # Get the MAC addresses being used by the running VM for this reservation |
| my $active_private_mac_address = $active_os->get_private_mac_address(); |
| my $active_public_mac_address = $active_os->get_public_mac_address(); |
| push @vm_mac_addresses, $active_private_mac_address if $active_private_mac_address; |
| push @vm_mac_addresses, $active_public_mac_address if $active_public_mac_address; |
| } |
| } |
| |
| # Remove the colons from the MAC addresses and convert to lower case so they can be compared |
| map { s/[^\w]//g; $_ = lc($_) } (@vm_mac_addresses); |
| |
| # Get an array containing the existing vmx file paths on the VM host |
| my @host_vmx_file_paths = $self->get_vmx_file_paths(); |
| notify($ERRORS{'DEBUG'}, 0, "retrieved vmx file paths currently residing on the VM host:\n" . join("\n", @host_vmx_file_paths)); |
| |
| # Sort the vmx file path list so that paths containing the computer name are checked first |
| my @ordered_host_vmx_file_paths; |
| push @ordered_host_vmx_file_paths, grep(/$computer_name\_/, @host_vmx_file_paths); |
| push @ordered_host_vmx_file_paths, grep(!/$computer_name\_/, @host_vmx_file_paths); |
| @host_vmx_file_paths = @ordered_host_vmx_file_paths; |
| notify($ERRORS{'DEBUG'}, 0, "sorted vmx file paths so that directories containing $computer_name are checked first:\n" . join("\n", @host_vmx_file_paths)); |
| |
| # Loop through the vmx files found on the VM host |
| # Check if the MAC addresses in the vmx file match the MAC addresses currently in use on the VM to be captured |
| my @matching_host_vmx_paths_powered_on; |
| my @matching_host_vmx_paths_powered_off; |
| for my $host_vmx_path (@host_vmx_file_paths) { |
| # Quit checking if a match has already been found and the vmx path being checked doesn't contain the computer name |
| last if ((@matching_host_vmx_paths_powered_on || @matching_host_vmx_paths_powered_off) && $host_vmx_path !~ /$computer_name/); |
| |
| # Get the info from the existing vmx file on the VM host |
| my $host_vmx_info = $self->get_vmx_info($host_vmx_path); |
| if (!$host_vmx_info) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve the info from existing vmx file on VM host: $host_vmx_path"); |
| next; |
| } |
| |
| my $vmx_file_name = $host_vmx_info->{"vmx_file_name"} || ''; |
| |
| # Create an array containing the values of any ethernetx.address or ethernetx.generatedaddress lines |
| my @vmx_mac_addresses; |
| for my $vmx_property (keys %{$host_vmx_info}) { |
| if ($vmx_property =~ /^ethernet\d+\.(generated)?address$/i) { |
| push @vmx_mac_addresses, $host_vmx_info->{$vmx_property}; |
| } |
| } |
| |
| # Remove the colons from the MAC addresses and convert to lowercase so they can be compared |
| map { s/[^\w]//g; $_ = lc($_) } (@vmx_mac_addresses); |
| |
| # Check if any elements of the VM MAC address array intersect with the vmx MAC address array |
| notify($ERRORS{'DEBUG'}, 0, "comparing MAC addresses\nused by $computer_name:\n" . join("\n", sort(@vm_mac_addresses)) . "\nconfigured in $vmx_file_name:\n" . join("\n", sort(@vmx_mac_addresses))); |
| my @matching_mac_addresses = map { my $vm_mac_address = $_; grep(/$vm_mac_address/i, @vmx_mac_addresses) } @vm_mac_addresses; |
| |
| if (!@matching_mac_addresses) { |
| notify($ERRORS{'DEBUG'}, 0, "ignoring $vmx_file_name because MAC addresses do not match the ones being used by $computer_name"); |
| next; |
| } |
| |
| # Ignore the vmx file if it is not registered |
| if (!$self->is_vm_registered($host_vmx_path)) { |
| notify($ERRORS{'DEBUG'}, 0, "ignoring $vmx_file_name because the VM is not registered"); |
| next; |
| } |
| |
| # Ignore the vmx file if the VM is powered on |
| my $power_state = $self->api->get_vm_power_state($host_vmx_path) || 'unknown'; |
| if ($power_state !~ /on/i) { |
| notify($ERRORS{'DEBUG'}, 0, "found matching MAC address between $computer_name (powered off) and $vmx_file_name:\n" . join("\n", sort(@matching_mac_addresses))); |
| push @matching_host_vmx_paths_powered_off, $host_vmx_path; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "found matching MAC address between $computer_name (powered on) and $vmx_file_name:\n" . join("\n", sort(@matching_mac_addresses))); |
| push @matching_host_vmx_paths_powered_on, $host_vmx_path; |
| } |
| } |
| |
| # Check if any matching vmx files were found |
| if (@matching_host_vmx_paths_powered_on) { |
| if (scalar(@matching_host_vmx_paths_powered_on) > 1) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine active vmx file path, found multiple vmx files of powered on VMs on the VM host containing a MAC address matching $computer_name:\n" . join("\n", @matching_host_vmx_paths_powered_on)); |
| return; |
| } |
| my $matching_vmx_file_path = $matching_host_vmx_paths_powered_on[0]; |
| notify($ERRORS{'OK'}, 0, "found vmx file being used by $computer_name (powered on): $matching_vmx_file_path"); |
| return $matching_vmx_file_path; |
| } |
| elsif (@matching_host_vmx_paths_powered_off) { |
| if (scalar(@matching_host_vmx_paths_powered_off) > 1) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine active vmx file path, found no vmx files of powered on VMs, found multiple vmx files of powered off VMs on the VM host containing a MAC address matching $computer_name:\n" . join("\n", @matching_host_vmx_paths_powered_off)); |
| return; |
| } |
| my $matching_vmx_file_path = $matching_host_vmx_paths_powered_off[0]; |
| notify($ERRORS{'OK'}, 0, "found vmx file being used by $computer_name (powered off): $matching_vmx_file_path"); |
| return $matching_vmx_file_path; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine active vmx file path, did not find any vmx files on the VM host containing a MAC address matching $computer_name"); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 vmhost_data |
| |
| Parameters : none |
| Returns : DataStructure object reference |
| Description : Returns the DataStructure object containing the VM host data. |
| |
| =cut |
| |
| sub vmhost_data { |
| 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; |
| } |
| |
| if (!$self->{vmhost_data}) { |
| notify($ERRORS{'WARNING'}, 0, "VM host DataStructure object is not defined as \$self->{vmhost_data}"); |
| return; |
| } |
| |
| return $self->{vmhost_data}; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 api |
| |
| Parameters : none |
| Returns : API object reference |
| Description : Returns the VMware API object that is used to control VMs on the |
| VM host. |
| |
| =cut |
| |
| sub api { |
| 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; |
| } |
| |
| if (!$self->{api}) { |
| notify($ERRORS{'DEBUG'}, 0, "attempting to initialize"); |
| if (!$self->initialize()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to initialize"); |
| return; |
| } |
| elsif (!$self->{api}) { |
| notify($ERRORS{'WARNING'}, 0, "api object is not defined"); |
| return; |
| } |
| } |
| |
| return $self->{api}; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_vmhost_datastructure |
| |
| Parameters : none |
| Returns : DataStructure object reference |
| Description : Returns a DataStructure object containing the data for the VM |
| host. The computer and image data stored in the object describe |
| the VM host computer, not the VM. All of the other data in the |
| DataStore object matches the data for the regular reservation |
| DataStructure object. |
| |
| =cut |
| |
| sub get_vmhost_datastructure { |
| 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 $request_data = $self->data->get_request_data(); |
| my $reservation_id = $self->data->get_reservation_id(); |
| my $vmhost_computer_id = $self->data->get_vmhost_computer_id(); |
| my $vmhost_profile_image_id = $self->data->get_vmhost_profile_image_id(); |
| |
| # Create a DataStructure object containing computer data for the VM host |
| my $vmhost_data; |
| eval { |
| $vmhost_data= new VCL::DataStructure({request_data => $request_data, |
| reservation_id => $reservation_id, |
| computer_identifier => $vmhost_computer_id, |
| image_identifier => $vmhost_profile_image_id}); |
| }; |
| |
| if ($EVAL_ERROR) { |
| notify($ERRORS{'WARNING'}, 0, "unable to create DataStructure object for VM host, exception thrown, error: $EVAL_ERROR"); |
| return; |
| } |
| elsif (!$vmhost_data) { |
| notify($ERRORS{'WARNING'}, 0, "unable to create DataStructure object for VM host, DataStructure object is not defined"); |
| return; |
| } |
| |
| # Get the VM host nodename from the DataStructure object which was created for it |
| # This acts as a test to make sure the DataStructure object is working |
| my $vmhost_computer_node_name = $vmhost_data->get_computer_node_name(); |
| if (!$vmhost_computer_node_name) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine VM host node name from DataStructure object created for VM host\n"); |
| return; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "created DataStructure object for VM host: $vmhost_computer_node_name"); |
| $self->{vmhost_data} = $vmhost_data; |
| return $vmhost_data; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_vmhost_os_object |
| |
| Parameters : $vmhost_os_perl_package (optional) |
| Returns : OS object reference |
| Description : Creates an OS object to be used to control the VM host OS. An |
| optional argument may be specified containing the Perl package to |
| instantiate. If an argument is not specified, the Perl package of |
| the image currently installed on the VM host computer is used. |
| |
| =cut |
| |
| sub get_vmhost_os_object { |
| 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; |
| } |
| |
| # Get the VM host OS object type argument |
| my $vmhost_os_perl_package = shift; |
| |
| # Get a DataStructure object containing the VM host's data |
| my $vmhost_data = $self->get_vmhost_datastructure() || return; |
| |
| # Check if the VM host OS object type was specified as an argument |
| if (!$vmhost_os_perl_package) { |
| # Get the VM host OS module Perl package name |
| $vmhost_os_perl_package = $vmhost_data->get_image_os_module_perl_package(); |
| if (!$vmhost_os_perl_package) { |
| notify($ERRORS{'WARNING'}, 0, "unable to create DataStructure or OS object for VM host, failed to retrieve VM host image OS module Perl package name"); |
| return; |
| } |
| } |
| |
| # Load the VM host OS module if it is different than the one already loaded for the reservation image OS |
| notify($ERRORS{'DEBUG'}, 0, "attempting to load VM host OS module: $vmhost_os_perl_package"); |
| eval "use $vmhost_os_perl_package"; |
| if ($EVAL_ERROR) { |
| notify($ERRORS{'WARNING'}, 0, "VM host OS module could NOT be loaded: $vmhost_os_perl_package, error: $EVAL_ERROR"); |
| return; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "VM host OS module loaded: $vmhost_os_perl_package"); |
| |
| # Create an OS object for the VM host |
| my $vmhost_os; |
| eval { $vmhost_os = ($vmhost_os_perl_package)->new({data_structure => $vmhost_data}) }; |
| if ($vmhost_os) { |
| notify($ERRORS{'DEBUG'}, 0, "VM host OS object created: " . ref($vmhost_os)); |
| return $vmhost_os; |
| } |
| elsif ($EVAL_ERROR) { |
| notify($ERRORS{'WARNING'}, 0, "VM host OS object could not be created: type: $vmhost_os_perl_package, error:\n$EVAL_ERROR"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "VM host OS object could not be created, type: $vmhost_os_perl_package, no eval error"); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_vmhost_api_object |
| |
| Parameters : none |
| Returns : VMware API object |
| Description : |
| |
| =cut |
| |
| sub get_vmhost_api_object { |
| 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; |
| } |
| |
| # Get the Perl package argument |
| my $api_perl_package = shift; |
| if (!$api_perl_package) { |
| notify($ERRORS{'WARNING'}, 0, "API Perl package argument was not specified"); |
| return; |
| } |
| |
| # Get a DataStructure object containing the VM host's data |
| my $vmhost_datastructure = $self->get_vmhost_datastructure() || return; |
| |
| # Get the VM host nodename from the DataStructure object which was created for it |
| # This acts as a test to make sure the DataStructure object is working |
| my $vmhost_nodename = $vmhost_datastructure->get_computer_node_name(); |
| if (!$vmhost_nodename) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine VM host node name from DataStructure object created for VM host"); |
| return; |
| } |
| |
| # Load the VMware control module |
| notify($ERRORS{'DEBUG'}, 0, "attempting to load VMware control module: $api_perl_package"); |
| eval "use $api_perl_package"; |
| if ($EVAL_ERROR) { |
| notify($ERRORS{'WARNING'}, 0, "failed to load VMware control module: $api_perl_package"); |
| return 0; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "loaded VMware control module: $api_perl_package"); |
| |
| # Create an API object to control the VM host and VMs |
| my $api; |
| eval { $api = ($api_perl_package)->new({ |
| data_structure => $self->data, |
| vmhost_data => $vmhost_datastructure, |
| vmhost_os => $self->vmhost_os |
| })}; |
| if (!$api) { |
| if ($EVAL_ERROR) { |
| notify($ERRORS{'WARNING'}, 0, "API object could not be created: $api_perl_package, error:\n$EVAL_ERROR"); |
| return; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "API object could not be created: $api_perl_package"); |
| return; |
| } |
| } |
| |
| $api->{api} = $api; |
| |
| notify($ERRORS{'DEBUG'}, 0, "created API object: $api_perl_package"); |
| return $api; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 remove_existing_vms |
| |
| Parameters : none |
| Returns : boolean |
| Description : Removes VMs from a VMware host which were previously created for |
| the VM. It only removes VMs created for the VM assigned to the |
| reservation. It does not delete all VM's from the host. |
| |
| =cut |
| |
| sub remove_existing_vms { |
| 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 $computer_name = $self->data->get_computer_short_name() || return; |
| my $computer_id = $self->data->get_computer_id() || return; |
| my $vmx_base_directory_path = $self->get_vmx_base_directory_path(); |
| my $vmdk_base_directory_path = $self->get_vmdk_base_directory_path(); |
| |
| # Get an array containing the existing vmx file paths on the VM host |
| my @vmx_file_paths = $self->get_vmx_file_paths(); |
| my %vmx_file_path_hash = map { $_ => 1 } (@vmx_file_paths); |
| |
| # Loop through the existing vmx file paths found, check if it matches the VM for this reservation |
| for my $vmx_file_path (@vmx_file_paths) { |
| my $vmx_file_name = $self->_get_file_name($vmx_file_path); |
| notify($ERRORS{'DEBUG'}, 0, "checking existing vmx file: '$vmx_file_path'"); |
| |
| # Section commented out because it may prevent VM from being deleted if datastores change |
| # Ignore file if it does not begin with the base directory path |
| # get_vmx_file_paths() will return all vmx files it finds under the base directory path and all registered vmx files |
| # It's possible for a vmx file to be registered that resided on some other datastore |
| #if ($vmx_file_path !~ /^$vmx_base_directory_path/) { |
| # notify($ERRORS{'DEBUG'}, 0, "ignoring existing vmx file '$vmx_file_path' because it does not begin with the base directory path: '$vmx_base_directory_path'"); |
| # next; |
| #} |
| |
| # Check if the vmx directory name matches the naming convention VCL would use for the computer |
| 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; |
| } |
| elsif ($vmx_file_path_computer_name ne $computer_name) { |
| #notify($ERRORS{'DEBUG'}, 0, "ignoring existing vmx file: $vmx_file_name, the directory computer name '$vmx_file_path_computer_name' does not match the reservation computer name '$computer_name'"); |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "found existing vmx file $vmx_file_name with matching computer name $computer_name: $vmx_file_path"); |
| |
| # Delete the existing VM from the VM host |
| if (!$self->delete_vm($vmx_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete existing VM: $vmx_file_path"); |
| } |
| } |
| } |
| |
| # Delete orphaned vmx or vmdk directories previously created by VCL for the computer |
| # Find any files under the vmx or vmdk base directories matching the computer name |
| my @orphaned_vmx_file_paths = $self->find_datastore_files($vmx_base_directory_path, "*$computer_name*\.vmx"); |
| |
| # 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_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; |
| } |
| elsif ($orphaned_computer_name ne $computer_name) { |
| #notify($ERRORS{'DEBUG'}, 0, "ignoring existing file: '$orphaned_vmx_file_path', the directory computer name '$orphaned_computer_name' does not match the reservation computer name '$computer_name'"); |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "found orphaned file '$orphaned_vmx_file_path' with matching computer name $computer_name"); |
| |
| # Delete the orphaned VM from the VM host |
| if (!$self->delete_vm($orphaned_vmx_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete orphaned VM: $orphaned_vmx_file_path"); |
| } |
| } |
| } |
| |
| # Make sure the computer assigned to this reservation isn't still responding |
| # This could occur if a VM was configured to use the IP address but the directory where the VM resides doesn't match the name VCL would have given it |
| my $remote_connection_target = determine_remote_connection_target($computer_name); |
| if (_pingnode($computer_name)) { |
| notify($ERRORS{'WARNING'}, 0, "$computer_name ($remote_connection_target) is still responding to ping after deleting deleting matching VMs"); |
| return 0; |
| } |
| elsif ($self->os->is_ssh_responding()) { |
| notify($ERRORS{'WARNING'}, 0, "$computer_name ($remote_connection_target) is still responding to SSH after deleting deleting matching VMs"); |
| return 0; |
| } |
| else { |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 prepare_vmx |
| |
| Parameters : none |
| Returns : boolean |
| Description : Creates a .vmx file on the VM host configured for the |
| reservation. Checks if a VM for the same VCL computer entry is |
| already registered. If the VM is already registered, it is |
| unregistered and the files for the existing VM are deleted. |
| |
| =cut |
| |
| sub prepare_vmx { |
| 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; |
| } |
| |
| # Get the required data to configure the .vmx file |
| my $image_id = $self->data->get_image_id() || return; |
| my $imagerevision_id = $self->data->get_imagerevision_id() || return; |
| my $image_project = $self->data->get_image_project() || return; |
| my $computer_id = $self->data->get_computer_id() || return; |
| my $vmx_file_name = $self->get_vmx_file_name() || return; |
| my $vmx_file_path = $self->get_vmx_file_path() || return; |
| my $vmx_directory_name = $self->get_vmx_directory_name() || return; |
| my $vmx_directory_path = $self->get_vmx_directory_path() || return; |
| my $vmdk_file_path = $self->get_vmdk_file_path() || return; |
| my $computer_name = $self->data->get_computer_short_name() || return; |
| my $image_name = $self->data->get_image_name() || return; |
| my $vm_ram = $self->get_vm_ram() || return; |
| my $vm_ethernet_adapter_type = $self->get_vm_ethernet_adapter_type() || return; |
| my $vm_eth0_generated = $self->data->get_vmhost_profile_eth0generated(0); |
| my $vm_eth1_generated = $self->data->get_vmhost_profile_eth1generated(0); |
| my $virtual_switch_0 = $self->data->get_vmhost_profile_virtualswitch0(0) || ''; |
| my $virtual_switch_1 = $self->data->get_vmhost_profile_virtualswitch1(0) || ''; |
| my $virtual_switch_2 = $self->data->get_vmhost_profile_virtualswitch2(0) || ''; |
| my $virtual_switch_3 = $self->data->get_vmhost_profile_virtualswitch3(0) || ''; |
| my $vm_disk_adapter_type = $self->get_vm_disk_adapter_type() || return; |
| my $vm_hardware_version = $self->get_vm_virtual_hardware_version() || return; |
| my $is_vm_dedicated = $self->is_vm_dedicated(); |
| my $guest_os = $self->get_vm_guest_os() || return; |
| my $vmware_product_name = $self->get_vmhost_product_name(); |
| my $image_os_type = $self->data->get_image_os_type(); |
| |
| (my ($vm_cpu_count, $vm_cores_per_socket) = $self->get_vm_cpu_configuration()) || return; |
| |
| # Create the .vmx directory on the host |
| if (!$self->vmhost_os->create_directory($vmx_directory_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create .vmx directory on VM host: $vmx_directory_path"); |
| return; |
| } |
| |
| # Set the disk parameters based on whether or not the VM has a dedicated virtual disk |
| # Also set the display name to indicate if the VM has a shared or dedicated virtual disk |
| my $display_name = "$computer_name\_$image_name"; |
| |
| my $vm_disk_mode = 'persistent'; |
| my $vm_disk_write_through = "TRUE"; |
| my $vm_disk_shared_bus = "none"; |
| |
| # Determine which parameter to use in the vmx file for the network name |
| # VMware Server 1.x uses 'vnet', newer VMware products use 'networkName' |
| my $network_parameter; |
| if ($vmware_product_name =~ /VMware Server 1/i) { |
| $network_parameter = 'vnet'; |
| } |
| else { |
| $network_parameter = 'networkName'; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "vm info: |
| display name: $display_name |
| vmx file path: $vmx_file_path |
| vmdk file path: $vmdk_file_path" |
| ); |
| |
| # Create a hash containing the vmx parameter names and values |
| my %vmx_parameters = ( |
| "#image_id" => "$image_id", |
| "#imagerevision_id" => "$imagerevision_id", |
| "#computer_id" => "$computer_id", |
| ".encoding" => "UTF-8", |
| #"bios.bootDelay" => "1000", |
| "config.version" => "8", |
| "cpuid.coresPerSocket" => "$vm_cores_per_socket", |
| "displayName" => "$display_name", |
| "floppy0.present" => "FALSE", |
| "guestOS" => "$guest_os", |
| "gui.exitOnCLIHLT" => "TRUE", # causes the virtual machine to power off automatically when you choose Start > Shut Down from the Windows guest |
| "memsize" => "$vm_ram", |
| "mem.hotadd" => "TRUE", |
| "msg.autoAnswer" => "TRUE", # tries to automatically answer all questions that may occur at boot-time. |
| #"mks.enable3d" => "TRUE", |
| #"mks.gl.allowBlacklistedDrivers" => "TRUE", |
| "numvcpus" => "$vm_cpu_count", |
| "powerType.powerOff" => "soft", |
| "powerType.powerOn" => "hard", |
| "powerType.reset" => "soft", |
| "powerType.suspend" => "hard", |
| "sched.swap.dir" => "$vmx_directory_path/", |
| "snapshot.redoNotWithParent" => "TRUE", |
| "svga.autodetect" => "TRUE", |
| #"svga.yes3d" => "TRUE", |
| "tools.remindInstall" => "FALSE", |
| "tools.syncTime" => "FALSE", |
| "toolScripts.afterPowerOn" => "FALSE", |
| "toolScripts.afterResume" => "FALSE", |
| "toolScripts.beforeSuspend" => "FALSE", |
| "toolScripts.beforePowerOff" => "FALSE", |
| #"tools.upgrade.policy" => "upgradeAtPowerCycle", |
| "usb.present" => "TRUE", |
| "uuid.action" => "keep", # Keep the VM's uuid, keeps existing MAC |
| "vcpu.hotadd" => "TRUE", |
| "virtualHW.version" => "$vm_hardware_version", |
| "workingDir" => "$vmx_directory_path", |
| ); |
| |
| if ($self->api->is_nested_virtualization_supported()) { |
| %vmx_parameters = (%vmx_parameters, ( |
| "cpuid.1.ecx" => "--------------------------H-----", |
| "featMask.vm.hv.capable" => "Min:1", |
| "hypervisor.cpuid.v0" => "FALSE", |
| "monitor.virtual_mmu" => "hardware", |
| "monitor.virtual_exec" => "hardware", |
| "vhv.enable" => "TRUE", |
| )); |
| } |
| |
| #>>>>>>>>>> |
| ## Experimental - Support for VMware ESXi's built in VNC server functionality |
| #my $reservation_id = $self->data->get_reservation_id(); |
| #my $vnc_password = $self->data->get_reservation_password(0); |
| #if (!defined($vnc_password)) { |
| # $vnc_password = getpw(); |
| # update_reservation_password($reservation_id, $vnc_password); |
| # $self->data->set_reservation_password($vnc_password); |
| #} |
| # |
| #my $vnc_port = ($computer_id + 10000); |
| #notify($ERRORS{'DEBUG'}, 0, "vnc access will be enabled, port: $vnc_port, password: $vnc_password"); |
| #%vmx_parameters = (%vmx_parameters, ( |
| # "RemoteDisplay.vnc.enabled" => "TRUE", |
| # "RemoteDisplay.vnc.password" => $vnc_password, |
| # "RemoteDisplay.vnc.port" => $vnc_port, |
| #)); |
| #<<<<<<<<<< |
| |
| # Add the disk adapter parameters to the hash |
| if ($vm_disk_adapter_type =~ /ide/i) { |
| %vmx_parameters = (%vmx_parameters, ( |
| "ide0:0.fileName" => "$vmdk_file_path", |
| "ide0:0.mode" => "$vm_disk_mode", |
| "ide0:0.present" => "TRUE", |
| "ide0:0.writeThrough" => "$vm_disk_write_through", |
| "ide0:0.sharedBus" => "$vm_disk_shared_bus", |
| |
| "ide0:1.startConnected" => "FALSE", |
| "ide0:1.deviceType" => "cdrom-raw", |
| "ide0:1.clientDevice" => "TRUE", |
| "ide0:1.fileName" => "emptyBackingString", |
| "ide0:1.present" => "TRUE", |
| )); |
| } |
| else { |
| %vmx_parameters = (%vmx_parameters, ( |
| "scsi0.present" => "TRUE", |
| "scsi0.virtualDev" => "$vm_disk_adapter_type", |
| "scsi0:0.fileName" => "$vmdk_file_path", |
| "scsi0:0.mode" => "$vm_disk_mode", |
| "scsi0:0.present" => "TRUE", |
| "scsi0:0.writeThrough" => "$vm_disk_write_through", |
| "scsi0:0.sharedBus" => "$vm_disk_shared_bus", |
| |
| "ide0:0.startConnected" => "FALSE", |
| "ide0:0.deviceType" => "cdrom-raw", |
| "ide0:0.clientDevice" => "TRUE", |
| "ide0:0.fileName" => "emptyBackingString", |
| "ide0:0.present" => "TRUE", |
| )); |
| } |
| |
| if ($vm_hardware_version >= 7) { |
| %vmx_parameters = (%vmx_parameters, ( |
| "pciBridge0.present" => "TRUE", |
| "pciBridge4.present" => "TRUE", |
| "pciBridge4.virtualDev" => "pcieRootPort", |
| "pciBridge4.functions" => "8", |
| "pciBridge5.present" => "TRUE", |
| "pciBridge5.virtualDev" => "pcieRootPort", |
| "pciBridge5.functions" => "8", |
| "pciBridge6.present" => "TRUE", |
| "pciBridge6.virtualDev" => "pcieRootPort", |
| "pciBridge6.functions" => "8", |
| "pciBridge7.present" => "TRUE", |
| "pciBridge7.virtualDev" => "pcieRootPort", |
| "pciBridge7.functions" => "8", |
| "vmci0.present" => "TRUE", |
| )); |
| } |
| |
| # ide needed for boot |
| # usb needed for mouse |
| # monitor, ich7m, smc for darwin |
| if ($image_os_type =~ /osx/i) { |
| %vmx_parameters = (%vmx_parameters, ( |
| "ide1:0.clientDevice" => "TRUE", |
| "ide1:0.deviceType" => "atapi-cdrom", |
| "ide1:0.fileName" => "", |
| "ide1:0.present" => "TRUE", |
| "ide1:0.startConnected" => "FALSE", |
| "usb.present" => "TRUE", |
| "usb:1.deviceType" => "hub", |
| "usb:1.present" => "TRUE", |
| "usb:2.deviceType" => "mouse", |
| "usb:2.present" => "TRUE", |
| "monitor.virtual_exec" => "hardware", |
| "monitor.virtual_mmu" => "software", |
| "ich7m.present" => "TRUE", |
| "smc.present" => "FALSE", |
| "keyboard.vusb.enable" => "TRUE", |
| "mouse.vusb.enable" => "TRUE", |
| )); |
| } |
| |
| # Check if the API implements 'add_ethernet_adapter' |
| # This is necessary if the host is using dvSwitches/dvPorts |
| # Adding the info to the vmx before it is registered will not work |
| if ($self->api->can('add_ethernet_adapter')) { |
| notify($ERRORS{'DEBUG'}, 0, "ethernet adapters not added to vmx file, they will be added after the VM is registered"); |
| } |
| else { |
| (my @vm_ethernet_adapter_configuration = $self->get_vm_ethernet_adapter_configuration()) || return; |
| |
| my $interface_index = 0; |
| for my $adapter (@vm_ethernet_adapter_configuration) { |
| if ($adapter->{address_type} =~ /Manual/i) { |
| %vmx_parameters = (%vmx_parameters, ( |
| "ethernet$interface_index.present" => "TRUE", |
| "ethernet$interface_index.connectionType" => "custom", |
| "ethernet$interface_index.virtualDev" => $adapter->{adapter_type}, |
| "ethernet$interface_index.networkName" => $adapter->{network_name}, |
| "ethernet$interface_index.addressType" => "static", |
| "ethernet$interface_index.address" => $adapter->{address}, |
| )); |
| } |
| else { |
| %vmx_parameters = (%vmx_parameters, ( |
| "ethernet$interface_index.present" => "TRUE", |
| "ethernet$interface_index.connectionType" => "custom", |
| "ethernet$interface_index.virtualDev" => $adapter->{adapter_type}, |
| "ethernet$interface_index.networkName" => $adapter->{network_name}, |
| "ethernet$interface_index.addressType" => "generated", |
| )); |
| } |
| $interface_index++; |
| } |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "vmx parameters:\n" . format_data(\%vmx_parameters)); |
| |
| # Create a string from the hash |
| my $vmx_contents = "#!/usr/bin/vmware\n"; |
| map { $vmx_contents .= "$_ = \"$vmx_parameters{$_}\"\n" } sort keys %vmx_parameters; |
| |
| # Create a temporary vmx file on this managment node in /tmp |
| my $temp_vmx_file_path = "/tmp/$vmx_file_name"; |
| if (open VMX_TEMP, ">", $temp_vmx_file_path) { |
| print VMX_TEMP $vmx_contents; |
| close VMX_TEMP; |
| notify($ERRORS{'DEBUG'}, 0, "created temporary vmx file: $temp_vmx_file_path"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to create temporary vmx file: $temp_vmx_file_path, error: @!"); |
| return; |
| } |
| |
| # The vmx file should be set to executable |
| chmod(0755, "/tmp/$vmx_file_name"); |
| |
| # Copy the temporary vmx file the the VM host |
| $self->vmhost_os->copy_file_to($temp_vmx_file_path, $vmx_file_path) || return; |
| notify($ERRORS{'OK'}, 0, "created vmx file on VM host: $vmx_file_path"); |
| |
| # Delete the temporary vmx file |
| if (unlink $temp_vmx_file_path) { |
| notify($ERRORS{'DEBUG'}, 0, "deleted temporary vmx file: $temp_vmx_file_path"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete temporary vmx file: $temp_vmx_file_path, error: $!"); |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_vm_ethernet_adapter_configuration |
| |
| Parameters : none |
| Returns : array of hashes |
| Description : Assembles a data structure containing the ethernet adapter |
| configuration for the VM. |
| |
| =cut |
| |
| sub get_vm_ethernet_adapter_configuration { |
| 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 $image_project = $self->data->get_image_project(); |
| my $virtual_switch_0 = $self->data->get_vmhost_profile_virtualswitch0(0); |
| my $virtual_switch_1 = $self->data->get_vmhost_profile_virtualswitch1(0); |
| my $virtual_switch_2 = $self->data->get_vmhost_profile_virtualswitch2(0); |
| my $virtual_switch_3 = $self->data->get_vmhost_profile_virtualswitch3(0); |
| my $vm_ethernet_adapter_type = $self->get_vm_ethernet_adapter_type() || return; |
| |
| # Get a list of all the network names configured on the VMware host |
| my @network_names = $self->api->get_network_names(); |
| if (!@network_names) { |
| notify($ERRORS{'WARNING'}, 0, "unable to assemble ethernet adapter configuration, network names could not be retrieved from the VM host"); |
| return; |
| } |
| |
| # Make sure all network names configured in the VM host profile actually exist on the host |
| for my $network_name ($virtual_switch_0, $virtual_switch_1, $virtual_switch_2, $virtual_switch_3) { |
| if ($network_name && !grep(/^$network_name$/, @network_names)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to assemble ethernet adapter configuration, network name '$network_name' configured in the VM profile does not match any network names on the VM host:\n" . join("\n", sort @network_names)); |
| return; |
| } |
| } |
| |
| my @adapters; |
| |
| if ($virtual_switch_0) { |
| my $adapter_configuration = { |
| network_name => $virtual_switch_0, |
| adapter_type => $vm_ethernet_adapter_type, |
| }; |
| if ($self->data->get_vmhost_profile_eth0generated(0)) { |
| $adapter_configuration->{address_type} = 'Generated', |
| } |
| else { |
| $adapter_configuration->{address_type} = 'Manual'; |
| $adapter_configuration->{address} = $self->data->get_computer_eth0_mac_address(); |
| } |
| push @adapters, $adapter_configuration; |
| } |
| |
| if ($virtual_switch_1) { |
| my $adapter_configuration = { |
| network_name => $virtual_switch_1, |
| adapter_type => $vm_ethernet_adapter_type, |
| }; |
| if ($self->data->get_vmhost_profile_eth1generated(0)) { |
| $adapter_configuration->{address_type} = 'Generated', |
| } |
| else { |
| $adapter_configuration->{address_type} = 'Manual'; |
| $adapter_configuration->{address} = $self->data->get_computer_eth1_mac_address(); |
| } |
| push @adapters, $adapter_configuration; |
| } |
| |
| my @additional_network_names; |
| push @additional_network_names, $virtual_switch_2 if $virtual_switch_2; |
| push @additional_network_names, $virtual_switch_3 if $virtual_switch_3; |
| |
| # Add additional Ethernet interfaces if the image project name is not vcl |
| if ($image_project !~ /^vcl$/i) { |
| notify($ERRORS{'DEBUG'}, 0, "image project is: $image_project, checking if additional network adapters should be configured"); |
| |
| # Check each network name |
| for my $network_name (@network_names) { |
| if ($network_name =~ /$image_project/i || $image_project =~ /$network_name/i) { |
| notify($ERRORS{'DEBUG'}, 0, "network name ($network_name) and image project name ($image_project) intersect, adding network interface to VM for network $network_name"); |
| push @additional_network_names, $network_name; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "network name ($network_name) and image project name ($image_project) do not intersect, network interface will not be added to VM for network $network_name"); |
| } |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "image project is: $image_project, no additional network adapters will be configured"); |
| } |
| |
| for my $network_name (@additional_network_names) { |
| my $adapter_configuration = { |
| network_name => $network_name, |
| adapter_type => $vm_ethernet_adapter_type, |
| address_type => 'Generated', |
| }; |
| push @adapters, $adapter_configuration; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "VM ethernet adapter configuration:\n" . format_data(\@adapters)); |
| return @adapters; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 prepare_vmdk |
| |
| Parameters : none |
| Returns : boolean |
| Description : Prepares the .vmdk files on the VM host. This subroutine |
| determines whether or not the vmdk files need to be copied to the |
| VM host. |
| |
| =cut |
| |
| sub prepare_vmdk { |
| 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 $host_vmdk_base_directory_path = $self->get_vmdk_base_directory_path() || return; |
| my $host_vmdk_directory_path = $self->get_vmdk_directory_path() || return; |
| my $host_vmdk_file_path = $self->get_vmdk_file_path() || return; |
| my $host_vmdk_file_path_shared = $self->get_vmdk_file_path_shared() || return; |
| my $host_vmdk_directory_path_shared = $self->get_vmdk_directory_path_shared() || return; |
| |
| my $request_state_name = $self->data->get_request_state_name(0) || 'unknown'; |
| my $image_name = $self->data->get_image_name() || return; |
| my $vm_computer_name = $self->data->get_computer_short_name() || return; |
| my $vmhost_name = $self->data->get_vmhost_short_name() || return; |
| |
| my $is_vm_dedicated = $self->is_vm_dedicated(); |
| |
| # Semaphores are created when exclusive access to a file/directory is needed to avoid conflicts |
| # A semaphore ID is a string identifying a semaphore object when created |
| # Only 1 process at a time may create a semaphore with a given ID - other processes must wait if they attempt to do so |
| |
| # Establish a semaphore for the shared vmdk directory before checking if it exists |
| # This causes this process to wait if another process is copying to the shared directory |
| # Wait a long time to create the semaphore in case another process is copying a large vmdk to the directory |
| |
| my $shared_vmdk_semaphore = $self->get_datastore_directory_semaphore($host_vmdk_directory_path_shared, (60 * 30)) || return; |
| |
| my $dedicated_vmdk_semaphore; |
| if ($host_vmdk_directory_path_shared ne $host_vmdk_directory_path) { |
| $dedicated_vmdk_semaphore = $self->get_datastore_directory_semaphore($host_vmdk_directory_path, (60 * 30)) || return; |
| } |
| |
| # Return if the VM is not dedicated and the shared vmdk already exists on the host |
| my $shared_vmdk_exists = $self->vmhost_os->file_exists($host_vmdk_file_path_shared); |
| if ($shared_vmdk_exists) { |
| # Release the shared vmdk semaphore - image should be completely copied to correct location |
| undef $shared_vmdk_semaphore; |
| |
| if (!$is_vm_dedicated) { |
| notify($ERRORS{'DEBUG'}, 0, "VM is not dedicated and shared vmdk file already exists on VM host $vmhost_name: $host_vmdk_file_path"); |
| return 1; |
| } |
| } |
| |
| # VM is either: |
| # -dedicated |
| # -vmdk directory should be deleted if it already exists |
| # -vmdk directory should be created and vmdk files copied to it |
| # -shared and the directory doesn't exist |
| # -shared vmdk directory should be retrieved from the image repository |
| |
| |
| # If the VM is dedicated, check if the dedicated vmdk already exists on the host, delete it if necessary |
| if ($is_vm_dedicated) { |
| if ($self->vmhost_os->file_exists($host_vmdk_directory_path)) { |
| if ($request_state_name =~ /(new|reload)/) { |
| notify($ERRORS{'WARNING'}, 0, "VM is dedicated and vmdk directory already exists on VM host $vmhost_name: $host_vmdk_directory_path, existing directory will be deleted"); |
| if (!$self->vmhost_os->delete_file($host_vmdk_directory_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete existing dedicated vmdk directory on VM host $vmhost_name: $host_vmdk_directory_path"); |
| return; |
| } |
| } |
| else { |
| # Don't delete the directory, it may be in use by a VM |
| # Attempting to delete it will likely delete some files but not all, leaving a mess to reconstruct |
| notify($ERRORS{'OK'}, 0, "VM is dedicated and vmdk directory already exists on VM host $vmhost_name: $host_vmdk_directory_path, request state is not new or reload, directory will not be deleted, returning true"); |
| return 1; |
| } |
| } |
| |
| # Attempt to copy files from the shared vmdk directory if it exists |
| if ($shared_vmdk_exists) { |
| notify($ERRORS{'DEBUG'}, 0, "VM is dedicated and shared vmdk exists on the VM host $vmhost_name, attempting to make a copy"); |
| if ($self->copy_vmdk($host_vmdk_file_path_shared, $host_vmdk_file_path)) { |
| notify($ERRORS{'OK'}, 0, "copied vmdk from shared to dedicated directory on VM host $vmhost_name"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to copy vmdk from shared to dedicated directory on VM host $vmhost_name"); |
| return; |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "VM is dedicated, shared vmdk does not exist on the VM host $vmhost_name: $host_vmdk_file_path_shared"); |
| } |
| } |
| |
| # Check if the image repository is mounted on the VM host |
| # Copy vmdk files from repository datastore if it's mounted on the host |
| # Attempt this before attempting to copy from the shared datastore to reduce load on shared datastore |
| # Also - vmdk's are stored in 2gb sparse format in the repository. Copying from here may result in less space being used by the resulting copied vmdk. |
| if ($self->is_repository_mounted_on_vmhost()) { |
| notify($ERRORS{'DEBUG'}, 0, "files will be copied from this image repository directory mounted on the VM host"); |
| |
| # Check if the vmdk file exists in the mounted repository |
| my $repository_vmdk_file_path = $self->get_repository_vmdk_file_path(); |
| if ($self->vmhost_os->file_exists($repository_vmdk_file_path)) { |
| # Attempt to copy the vmdk file from the mounted repository to the VM host datastore |
| if ($self->copy_vmdk($repository_vmdk_file_path, $host_vmdk_file_path)) { |
| notify($ERRORS{'OK'}, 0, "copied vmdk from image repository to VM host $vmhost_name"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to copy vmdk from image repository to VM host $vmhost_name"); |
| return; |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "vmdk file does not exist in image repository directory mounted on VM host $vmhost_name: $repository_vmdk_file_path"); |
| } |
| } |
| |
| # Copy the vmdk files from the image repository on the management node to the vmdk directory |
| my $repository_vmdk_directory_path = $self->get_repository_vmdk_directory_path() || return; |
| my $start_time = time; |
| |
| # Find the vmdk file paths in the image repository directory |
| my @vmdk_repository_file_paths; |
| my $command = "find \"$repository_vmdk_directory_path\" -type f -iname \"$image_name*\""; |
| my ($exit_status, $output) = run_command($command, 1); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to find files in image repository directory: '$repository_vmdk_directory_path', pattern: '*.vmdk', command:\n$command"); |
| return; |
| } |
| elsif (grep(/(^find:.*no such file)/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "directory does not exist in image repository: '$repository_vmdk_directory_path'"); |
| return; |
| } |
| elsif (grep(/(^find: |syntax error|unexpected EOF)/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred attempting to find files in image repository directory: '$repository_vmdk_directory_path', pattern: '*.vmdk', command: $command, output:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| @vmdk_repository_file_paths = @$output; |
| map { chomp $_ } @vmdk_repository_file_paths; |
| notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@vmdk_repository_file_paths) . " vmdk files in image repository directory: '$repository_vmdk_directory_path':\n" . join("\n", sort @vmdk_repository_file_paths)); |
| } |
| |
| # Loop through the files, copy each from the management node's repository directory to the VM host |
| for my $vmdk_repository_file_path (sort @vmdk_repository_file_paths) { |
| my ($vmdk_copy_name) = $vmdk_repository_file_path =~ /([^\/]+)$/g; |
| if (!$self->vmhost_os->copy_file_to($vmdk_repository_file_path, "$host_vmdk_directory_path/$vmdk_copy_name")) { |
| notify($ERRORS{'WARNING'}, 0, "failed to copy vmdk file from the repository to the VM host: '$vmdk_repository_file_path' --> '$host_vmdk_directory_path/$vmdk_copy_name'"); |
| return; |
| } |
| } |
| my $duration = (time - $start_time); |
| notify($ERRORS{'OK'}, 0, "copied vmdk files from management node image repository to the VM host, took " . format_number($duration) . " seconds"); |
| |
| # If SCP is used, the names of the vmdk files will be the image name |
| if ("$host_vmdk_directory_path/$image_name.vmdk" ne $host_vmdk_file_path && !$self->move_vmdk("$host_vmdk_directory_path/$image_name.vmdk", $host_vmdk_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to rename the vmdk that was copied via SCP to the VM host $vmhost_name: '$host_vmdk_directory_path/$image_name.vmdk' --> '$host_vmdk_file_path'"); |
| return; |
| } |
| |
| # Check if the vmdk disk type is compatible with the VMware product installed on the host |
| return 1 if $self->is_vmdk_compatible(); |
| |
| # Disk type is not compatible with the VMware product installed on the host |
| # Attempt to make a copy - copy_vmdk should create a copy in a compatible format |
| # The destination copy is stored in a directory with the same name as the normal vmdk directory followed by a ~ |
| # Once the copy is done, delete the original vmdk directory and rename the copied directory |
| my $vmdk_file_name = $self->get_vmdk_file_name(); |
| my $temp_vmdk_file_path = "$host_vmdk_directory_path~/$vmdk_file_name"; |
| notify($ERRORS{'DEBUG'}, 0, "attempting to copy the vmdk using a compatible disk type on VM host $vmhost_name: '$host_vmdk_file_path' --> '$temp_vmdk_file_path'"); |
| |
| if (!$self->copy_vmdk($host_vmdk_file_path, $temp_vmdk_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to copy the vmdk using a compatible disk type on VM host $vmhost_name: '$host_vmdk_file_path' --> '$temp_vmdk_file_path'"); |
| return; |
| } |
| |
| if (!$self->vmhost_os->delete_file($host_vmdk_directory_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete the directory containing the incompatible vmdk on VM host $vmhost_name: '$host_vmdk_directory_path'"); |
| return; |
| } |
| |
| if (!$self->vmhost_os->move_file("$host_vmdk_directory_path~", $host_vmdk_directory_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to rename the directory containing the compatible vmdk on VM host $vmhost_name: '$host_vmdk_directory_path~' --> '$host_vmdk_directory_path'"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 is_vmx_vmdk_volume_shared |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub is_vmx_vmdk_volume_shared { |
| 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; |
| } |
| |
| return $self->{vmx_vmdk_volume_shared} if defined($self->{vmx_vmdk_volume_shared}); |
| |
| my $vmx_base_directory_path = $self->get_vmx_base_directory_path(); |
| my $vmdk_base_directory_path = $self->get_vmdk_base_directory_path(); |
| |
| # Check if the vmx and vmdk base directory paths are identical |
| if ($vmx_base_directory_path eq $vmdk_base_directory_path) { |
| notify($ERRORS{'DEBUG'}, 0, "vmx and vmdk base directory paths are identical: '$vmx_base_directory_path', they are on the same volume"); |
| $self->{vmx_vmdk_volume_shared} = 1; |
| return $self->{vmx_vmdk_volume_shared}; |
| } |
| |
| my $vmx_volume_total_space = $self->get_vmx_volume_total_space(); |
| my $vmdk_volume_total_space = $self->get_vmdk_volume_total_space(); |
| my $vmx_volume_available_space = $self->vmhost_os->get_available_space($vmx_base_directory_path); |
| my $vmdk_volume_available_space = $self->vmhost_os->get_available_space($vmdk_base_directory_path); |
| unless (defined($vmx_volume_total_space) && defined($vmdk_volume_total_space) && defined($vmx_volume_available_space) && defined($vmdk_volume_available_space)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if vmx and vmdk base directory paths are on the same volume, vmx and vmdk total and available space could not be determined"); |
| return; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "checking if vmx and vmdk base directory paths appear to be on the same volume:\n" . |
| "vmx base directory path: '$vmx_base_directory_path'\n" . |
| "vmdk base directory path: '$vmdk_base_directory_path'\n" . |
| "vmx volume total space: " . get_file_size_info_string($vmx_volume_total_space) . "\n" . |
| "vmdk volume total space: " . get_file_size_info_string($vmdk_volume_total_space) . "\n" . |
| "vmx volume available space: " . get_file_size_info_string($vmx_volume_available_space) . "\n" . |
| "vmdk volume available space: " . get_file_size_info_string($vmdk_volume_available_space) |
| ); |
| |
| if ($vmx_base_directory_path eq $vmdk_base_directory_path || ($vmx_volume_total_space == $vmdk_volume_total_space && abs($vmx_volume_available_space - $vmdk_volume_available_space) < ($vmdk_volume_total_space * .01))) { |
| notify($ERRORS{'DEBUG'}, 0, "vmx and vmdk base directory paths appear to be on the same volume based on the total and available space"); |
| $self->{vmx_vmdk_volume_shared} = 1; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "vmx and vmdk base directory paths do not appear to be on the same volume based on the total and available space"); |
| $self->{vmx_vmdk_volume_shared} = 0; |
| } |
| |
| return $self->{vmx_vmdk_volume_shared}; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_vmx_volume_total_space |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub get_vmx_volume_total_space { |
| 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; |
| } |
| |
| return $self->{vmx_total_space} if defined($self->{vmx_total_space}); |
| my $vmx_base_directory_path = $self->get_vmx_base_directory_path(); |
| $self->{vmx_total_space} = $self->vmhost_os->get_total_space($vmx_base_directory_path); |
| return $self->{vmx_total_space}; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_vmdk_volume_total_space |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub get_vmdk_volume_total_space { |
| 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; |
| } |
| |
| return $self->{vmdk_total_space} if defined($self->{vmdk_total_space}); |
| my $vmdk_base_directory_path = $self->get_vmdk_base_directory_path(); |
| $self->{vmdk_total_space} = $self->vmhost_os->get_total_space($vmdk_base_directory_path); |
| return $self->{vmdk_total_space}; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 check_vmhost_disk_space |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub check_vmhost_disk_space { |
| 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 $vmhost_name = $self->data->get_vmhost_short_name() || return; |
| notify($ERRORS{'DEBUG'}, 0, "checking if enough space is available on VM host $vmhost_name"); |
| |
| my $shared_vmx_vmdk_volume = $self->is_vmx_vmdk_volume_shared(); |
| |
| my $vmx_base_directory_path = $self->get_vmx_base_directory_path(); |
| my $vmdk_base_directory_path = $self->get_vmdk_base_directory_path(); |
| |
| my $vmx_volume_available_space = $self->vmhost_os->get_available_space($vmx_base_directory_path); |
| if (!defined($vmx_volume_available_space)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine available space for the vmx directory on VM host $vmhost_name"); |
| return; |
| } |
| |
| # Figure out how much additional space is required for the vmx and vmdk directories |
| my $vmx_additional_bytes_required = $self->get_vm_additional_vmx_bytes_required(); |
| my $vmdk_additional_bytes_required = $self->get_vm_additional_vmdk_bytes_required(); |
| if (!defined($vmx_additional_bytes_required) || !defined($vmdk_additional_bytes_required)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine additional bytes required for the vmx and vmdk directories on VM host $vmhost_name"); |
| return; |
| } |
| |
| my $space_message; |
| |
| if ($shared_vmx_vmdk_volume) { |
| my $additional_bytes_required = ($vmx_additional_bytes_required + $vmdk_additional_bytes_required); |
| |
| my $space_message; |
| $space_message .= "vmx additional space required: " . get_file_size_info_string($vmx_additional_bytes_required) . "\n"; |
| $space_message .= "vmdk additional space required: " . get_file_size_info_string($vmdk_additional_bytes_required) . "\n"; |
| $space_message .= "total additional space required: " . get_file_size_info_string($additional_bytes_required) . "\n"; |
| $space_message .= "shared vmx/vmdk volume available space: " . get_file_size_info_string($vmx_volume_available_space); |
| |
| if ($additional_bytes_required <= $vmx_volume_available_space) { |
| notify($ERRORS{'DEBUG'}, 0, "enough space is available on shared vmx/vmdk volume on VM host $vmhost_name: '$vmx_base_directory_path'\n$space_message"); |
| return 1; |
| } |
| else { |
| my $deficit_space = ($additional_bytes_required - $vmx_volume_available_space); |
| $space_message .= "\nshared vmx/vmdk volume space deficit: " . get_file_size_info_string($deficit_space); |
| notify($ERRORS{'DEBUG'}, 0, "not enough space is available on shared vmx/vmdk volume on VM host $vmhost_name: '$vmx_base_directory_path'\n$space_message"); |
| return 0; |
| } |
| } |
| else { |
| my $vmdk_volume_available_space = $self->vmhost_os->get_available_space($vmdk_base_directory_path); |
| if (!defined($vmdk_volume_available_space)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine available space for the vmdk directory on VM host $vmhost_name"); |
| return; |
| } |
| |
| $space_message .= "vmx additional space required: " . get_file_size_info_string($vmx_additional_bytes_required) . "\n"; |
| $space_message .= "vmx volume available space: " . get_file_size_info_string($vmx_volume_available_space) . "\n"; |
| $space_message .= "vmdk additional space required: " . get_file_size_info_string($vmdk_additional_bytes_required) . "\n"; |
| $space_message .= "vmdk volume available space: " . get_file_size_info_string($vmdk_volume_available_space); |
| |
| if ($vmx_additional_bytes_required <= $vmx_volume_available_space && $vmdk_additional_bytes_required <= $vmdk_volume_available_space) { |
| notify($ERRORS{'DEBUG'}, 0, "enough space is available on vmx and vmdk volumes on VM host $vmhost_name:\n$space_message"); |
| return 1; |
| } |
| |
| if ($vmdk_additional_bytes_required <= $vmdk_volume_available_space) { |
| $space_message = "enough space is available on vmdk volume on VM host $vmhost_name:\n$space_message"; |
| } |
| else { |
| my $vmdk_deficit_space = ($vmdk_additional_bytes_required - $vmdk_volume_available_space); |
| $space_message .= "\nvmdk volume space deficit: " . get_file_size_info_string($vmdk_deficit_space); |
| $space_message = "not enough space is available on vmdk volume on VM host $vmhost_name:\n$space_message"; |
| } |
| |
| if ($vmx_additional_bytes_required <= $vmx_volume_available_space) { |
| $space_message = "enough space is available on vmx volume on VM host $vmhost_name:\n$space_message"; |
| } |
| else { |
| my $vmx_deficit_space = ($vmx_additional_bytes_required - $vmx_volume_available_space); |
| $space_message .= "\nvmx volume space deficit: " . get_file_size_info_string($vmx_deficit_space); |
| $space_message = "not enough space is available on vmx volume on VM host $vmhost_name:\n$space_message"; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "$space_message"); |
| return 0; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reclaim_vmhost_disk_space |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub reclaim_vmhost_disk_space { |
| 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 $request_id = $self->data->get_request_id(); |
| my $reservation_computer_id = $self->data->get_computer_id(); |
| my $vmhost_profile_vmdisk = $self->data->get_vmhost_profile_vmdisk(); |
| |
| my $is_vm_dedicated = $self->is_vm_dedicated(); |
| my $reservation_vmdk_directory_path = $self->get_vmdk_directory_path(); |
| |
| my $vmx_base_directory_path = $self->get_vmx_base_directory_path(); |
| my $vmdk_base_directory_path = $self->get_vmdk_base_directory_path(); |
| |
| # Figure out how much additional space is required for the vmx and vmdk directories |
| my $vmx_additional_bytes_required = $self->get_vm_additional_vmx_bytes_required(); |
| my $vmdk_additional_bytes_required = $self->get_vm_additional_vmdk_bytes_required(); |
| if (!defined($vmx_additional_bytes_required) || !defined($vmdk_additional_bytes_required)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine the additional bytes required for the vmx and vmdk directories"); |
| return; |
| } |
| |
| my $shared_vmx_vmdk_volume = $self->is_vmx_vmdk_volume_shared(); |
| |
| my $vmx_files = {}; |
| my $deletable_vmx_files = {}; |
| my $vmdk_directories = {}; |
| my $deletable_vmdk_directories = {}; |
| |
| my $total_deletable_vmx_size = 0; |
| my $total_deletable_vmdk_size = 0; |
| |
| # Retrieve a list of existing vmdk directories matching the VCL naming convention |
| 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"} = {} |
| } |
| |
| 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(); |
| for my $vmx_file_path (@vmx_file_paths) { |
| $vmx_files->{$vmx_file_path} = {}; |
| |
| my $vmx_info = $self->get_vmx_info($vmx_file_path); |
| if (!$vmx_info) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve info from vmx file: $vmx_file_path"); |
| next; |
| } |
| |
| # Retrieve the vmx_file_name value from the vmx info - this should exist if VCL created the vmx file |
| my $vmx_file_name = $vmx_info->{vmx_file_name}; |
| if (!$vmx_file_name) { |
| notify($ERRORS{'DEBUG'}, 0, "$vmx_file_name can't be deleted because the vmx file does not contain a vmx_file_name value"); |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| next; |
| } |
| $vmx_files->{$vmx_file_path}{file_name} = $vmx_file_name; |
| |
| # Retrieve the vmx_directory_path value from the vmx info - this should exist if VCL created the vmx file |
| my $vmx_directory_path = $vmx_info->{vmx_directory_path}; |
| if (!$vmx_directory_path) { |
| notify($ERRORS{'DEBUG'}, 0, "$vmx_file_name can't be deleted because the vmx file does not contain a vmx_directory_path value"); |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| next; |
| } |
| |
| # Retrieve the computer_id value from the vmx info - this should exist if VCL created the vmx file |
| my $check_computer_id = $vmx_info->{computer_id}; |
| if (!defined($check_computer_id)) { |
| notify($ERRORS{'DEBUG'}, 0, "$vmx_file_name can't be deleted, vmx file does not contain a computer_id value and ID could not be determined from the directory name"); |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| next; |
| } |
| |
| # Check if the vmx file was created for the same computer assigned to this reservation |
| # If true, delete the VM and remove it from the $vmx_files hash |
| if ($check_computer_id eq $reservation_computer_id) { |
| notify($ERRORS{'DEBUG'}, 0, "attempting to delete VM $vmx_file_path because vmx file contains the computer ID assigned to this reservation"); |
| if ($self->delete_vm($vmx_file_path)) { |
| notify($ERRORS{'DEBUG'}, 0, "deleted VM containing the computer ID assigned to this reservation: $vmx_file_path"); |
| delete $vmx_files->{$vmx_file_path}; |
| next; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete VM containing the computer ID assigned to this reservation: $vmx_file_path"); |
| return; |
| } |
| } |
| |
| # Retrieve the vmdk directory paths from the vmx info and add them to the $vmdk_directories hash |
| for my $storage_identifier (keys %{$vmx_info->{vmdk}}) { |
| my $vmdk_file_path = $vmx_info->{vmdk}{$storage_identifier}{vmdk_file_path}; |
| |
| if ($self->is_vmdk_file_shared($vmdk_file_path)) { |
| $vmx_files->{$vmx_file_path}{vmdk_shared} = 1; |
| } |
| else { |
| $vmx_files->{$vmx_file_path}{vmdk_shared} = 0; |
| } |
| } |
| |
| # Create a DataStructure object for the computer |
| my $check_computer_data; |
| eval { $check_computer_data = new VCL::DataStructure({computer_identifier => $check_computer_id}); }; |
| if (!$check_computer_data) { |
| notify($ERRORS{'WARNING'}, 0, "$vmx_file_name can't be deleted, failed to create a DataStructure object for computer $check_computer_id"); |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| next; |
| } |
| |
| # Retrieve the computer name from the DataStructure object |
| my $check_computer_name = $check_computer_data->get_computer_short_name(); |
| if (!$check_computer_name) { |
| notify($ERRORS{'WARNING'}, 0, "$vmx_file_name can't be deleted, failed to retrieve the computer name from the DataStructure object"); |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "name of computer ID $check_computer_id: $check_computer_name"); |
| } |
| |
| # Check the computer state |
| # Don't remove computers in the maintenance state |
| my $check_computer_state = $check_computer_data->get_computer_state_name(); |
| if (!$check_computer_state) { |
| notify($ERRORS{'WARNING'}, 0, "$vmx_file_name can't be deleted, failed to retrieve the computer state from the DataStructure object"); |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| next; |
| } |
| $vmx_files->{$vmx_file_path}{computer_state} = $check_computer_state; |
| if ($check_computer_state =~ /maintenance/i) { |
| notify($ERRORS{'DEBUG'}, 0, "$vmx_file_name can't be deleted because its current state is '$check_computer_state'"); |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "state of $check_computer_name: $check_computer_state"); |
| } |
| |
| |
| # Check if the computer has been assigned to a block allocation |
| if (is_inblockrequest($check_computer_id)) { |
| notify($ERRORS{'DEBUG'}, 0, "$vmx_file_name can't be deleted because it has been assigned to a block allocation"); |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| $vmx_files->{$vmx_file_path}{block_allocation} = 1; |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "$check_computer_name has not been assigned to a block allocation"); |
| $vmx_files->{$vmx_file_path}{block_allocation} = 0; |
| } |
| |
| |
| # Check if any other requests have been assigned to the computer |
| my $computer_requests = get_request_by_computerid($check_computer_id); |
| |
| # Remove the ID for the current request |
| delete $computer_requests->{$request_id}; |
| if (keys(%$computer_requests)) { |
| notify($ERRORS{'DEBUG'}, 0, "$vmx_file_name can't be deleted because it is assigned to another request: " . join(", ", sort keys(%$computer_requests))); |
| $vmx_files->{$vmx_file_path}{requests} = [sort keys(%$computer_requests)]; |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "$check_computer_name has not been assigned to any other requests"); |
| $vmx_files->{$vmx_file_path}{requests} = []; |
| } |
| |
| # Get the amount of space being used by the vmx directory |
| my $vmx_directory_size = $self->vmhost_os->get_file_size($vmx_directory_path); |
| if (!defined($vmx_directory_size)) { |
| notify($ERRORS{'WARNING'}, 0, "$vmx_file_name can't be deleted because the size of the vmx directory could not be determined: '$vmx_directory_path'"); |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved size of vmx directory '$vmx_directory_path': " . format_number($vmx_directory_size, 0) . " bytes"); |
| $vmx_files->{$vmx_file_path}{vmx_directory_size} = $vmx_directory_size; |
| } |
| |
| # Check if the VM is registered |
| my $registered = $self->is_vm_registered($vmx_file_path); |
| if (!defined($registered)) { |
| notify($ERRORS{'DEBUG'}, 0, "$vmx_file_name can't be deleted because failed to determine if the VM is registered"); |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved registered status of $vmx_file_name: $registered"); |
| $vmx_files->{$vmx_file_path}{registered} = $registered; |
| |
| if ($registered) { |
| # Get the power status of the VM |
| my $power_status = $self->power_status($vmx_file_path); |
| if (!defined($power_status)) { |
| notify($ERRORS{'WARNING'}, 0, "$vmx_file_name can't be deleted because the power status of the VM could not be determined"); |
| $vmx_files->{$vmx_file_path}{deletable} = 0; |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved power status of $vmx_file_name: $power_status"); |
| $vmx_files->{$vmx_file_path}{power_status} = $power_status; |
| } |
| } |
| } |
| |
| $vmx_files->{$vmx_file_path}{deletable} = 1; |
| $deletable_vmx_files->{$vmx_file_path} = $vmx_files->{$vmx_file_path}; |
| $total_deletable_vmx_size += $vmx_directory_size; |
| notify($ERRORS{'DEBUG'}, 0, "VM $vmx_file_name can be deleted"); |
| } |
| |
| if ($vmhost_profile_vmdisk !~ /(local|dedicated)/) { |
| notify($ERRORS{'OK'}, 0, "VM disk mode is '$vmhost_profile_vmdisk', no image directories will be deleted from $vmdk_base_directory_path"); |
| } |
| elsif (!$self->get_repository_vmdk_base_directory_path()) { |
| notify($ERRORS{'OK'}, 0, "VM disk mode is '$vmhost_profile_vmdisk' but repository path is NOT configured, no image directories will be deleted from $vmdk_base_directory_path"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "VM disk mode is '$vmhost_profile_vmdisk' and repository path is configured, checking if any image directories can be deleted from $vmdk_base_directory_path"); |
| for my $vmdk_directory_path (sort keys %$vmdk_directories) { |
| $vmdk_directories->{$vmdk_directory_path}{deletable} = 1; |
| for my $vmx_file_path (keys %{$vmdk_directories->{$vmdk_directory_path}{vmx_file_paths}}) { |
| $vmdk_directories->{$vmdk_directory_path}{deletable} &= $vmx_files->{$vmx_file_path}{deletable}; |
| } |
| |
| my $vmx_file_path_count = scalar(keys %{$vmdk_directories->{$vmdk_directory_path}{vmx_file_paths}}); |
| $vmdk_directories->{$vmdk_directory_path}{vmx_file_path_count} = $vmx_file_path_count; |
| |
| # Retrieve additional information if the vmdk is deletable |
| if ($vmdk_directories->{$vmdk_directory_path}{deletable}) { |
| # Check if the vmdk directory matches the vmdk directory that will be used for this reservation |
| # Don't delete this directory because it will just have to be copied back |
| if (!$is_vm_dedicated && $vmdk_directory_path eq $reservation_vmdk_directory_path) { |
| notify($ERRORS{'DEBUG'}, 0, "vmdk directory can't be deleted because it will be used for this reservation: $vmdk_directory_path"); |
| $vmdk_directories->{$vmdk_directory_path}{deletable} = 0; |
| next; |
| } |
| |
| # Get the vmdk directory name so that the image info for that directory can be retrieved |
| # _get_file_name returns the last part of a file path |
| my $vmdk_directory_name = $self->_get_file_name($vmdk_directory_path); |
| $vmdk_directories->{$vmdk_directory_path}{directory_name} = $vmdk_directory_name; |
| |
| my $imagerevision_info = get_imagerevision_info($vmdk_directory_name); |
| if (!$imagerevision_info) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve info for the image revision matching the vmdk directory name: '$vmdk_directory_name'"); |
| $vmdk_directories->{$vmdk_directory_path}{deletable} = 0; |
| next; |
| } |
| else { |
| #notify($ERRORS{'DEBUG'}, 0, "retrieved info for the image revision matching the vmdk directory name: '$vmdk_directory_name'\n" . format_data(\%imagerevision_info)); |
| |
| $vmdk_directories->{$vmdk_directory_path}{image_id} = $imagerevision_info->{imageid}; |
| $vmdk_directories->{$vmdk_directory_path}{imagerevision_id} = $imagerevision_info->{id}; |
| $vmdk_directories->{$vmdk_directory_path}{image_deleted} = $imagerevision_info->{deleted}; |
| $vmdk_directories->{$vmdk_directory_path}{imagerevision_production} = $imagerevision_info->{production}; |
| |
| my $image_info = get_image_info($imagerevision_info->{imageid}); |
| if (!$image_info) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve info for the image ID contained in the image revision info: $imagerevision_info->{imageid}"); |
| } |
| else { |
| #notify($ERRORS{'DEBUG'}, 0, "retrieved info for the image ID contained in the image revision info: $imagerevision_info->{imageid}\n" . format_data($image_info)); |
| # Use the 'or' operator to set the 'deleted' key so this value is set to 1 if either the image revision or image has deleted=1 |
| $vmdk_directories->{$vmdk_directory_path}{image_deleted} |= $image_info->{deleted}; |
| } |
| } |
| |
| my $vmdk_directory_size = $self->vmhost_os->get_file_size($vmdk_directory_path); |
| if (!defined($vmdk_directory_size)) { |
| notify($ERRORS{'WARNING'}, 0, "$vmdk_directory_path can't be deleted because the size of the directory could not be determined"); |
| $vmdk_directories->{$vmdk_directory_path}{deletable} = 0; |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved size of vmdk directory '$vmdk_directory_path': " . format_number($vmdk_directory_size, 0) . " bytes"); |
| $vmdk_directories->{$vmdk_directory_path}{vmdk_directory_size} = $vmdk_directory_size; |
| } |
| |
| $deletable_vmdk_directories->{$vmdk_directory_path} = $vmdk_directories->{$vmdk_directory_path}; |
| $total_deletable_vmdk_size += $vmdk_directory_size; |
| } |
| } |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "all VMs:\n" . format_data($vmx_files)); |
| notify($ERRORS{'DEBUG'}, 0, "all vmdk directories:\n" . format_data($vmdk_directories)); |
| |
| notify($ERRORS{'DEBUG'}, 0, "deletable VMs:\n" . format_data($deletable_vmx_files)); |
| notify($ERRORS{'DEBUG'}, 0, "deletable vmdk directories:\n" . format_data($deletable_vmdk_directories)); |
| |
| if ($shared_vmx_vmdk_volume) { |
| my $additional_space_required = ($vmx_additional_bytes_required + $vmdk_additional_bytes_required); |
| |
| my $available_space = $self->vmhost_os->get_available_space($vmx_base_directory_path); |
| my $deletable_space = ($total_deletable_vmx_size + $total_deletable_vmdk_size); |
| my $potential_available_space = ($available_space + $deletable_space); |
| |
| if ($available_space >= $additional_space_required) { |
| notify($ERRORS{'DEBUG'}, 0, "enough space is already available to accomodate the VM:\n" . |
| "currently available space: " . get_file_size_info_string($available_space) . "\n" . |
| "space required for the VM: " . get_file_size_info_string($additional_space_required) |
| ); |
| return 1; |
| } |
| elsif ($potential_available_space < $additional_space_required) { |
| notify($ERRORS{'WARNING'}, 0, "not enough space can be reclaimed to accomodate the VM:\n" . |
| "deletable space: " . get_file_size_info_string($deletable_space) . "\n" . |
| "currently available space: " . get_file_size_info_string($available_space) . "\n" . |
| "potential available space: " . get_file_size_info_string($potential_available_space) . "\n" . |
| "space required for the VM: " . get_file_size_info_string($additional_space_required) |
| ); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "enough space can be reclaimed to accomodate the VM:\n" . |
| "deletable space: " . get_file_size_info_string($deletable_space) . "\n" . |
| "currently available space: " . get_file_size_info_string($available_space) . "\n" . |
| "potential available space: " . get_file_size_info_string($potential_available_space) . "\n" . |
| "space required for the VM: " . get_file_size_info_string($additional_space_required) |
| ); |
| } |
| } |
| else { |
| my $vmx_available_space = $self->vmhost_os->get_available_space($vmx_base_directory_path); |
| my $vmdk_available_space = $self->vmhost_os->get_available_space($vmdk_base_directory_path); |
| |
| my $vmx_potential_available_space = ($vmx_available_space + $total_deletable_vmx_size); |
| my $vmdk_potential_available_space = ($vmdk_available_space + $total_deletable_vmdk_size); |
| |
|