blob: c19807c609971f47de94986f7c9d7edc1fb54746 [file] [log] [blame]
#!/usr/bin/perl -w
###############################################################################
# $Id$
###############################################################################
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###############################################################################
=head1 NAME
VCL::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);