Added: vcl/sandbox/patches/vSphere_SDK_2.3.2_updated.pm URL: http://svn.apache.org/viewvc/vcl/sandbox/patches/vSphere_SDK_2.3.2_updated.pm?rev=1631760&view=auto ============================================================================== --- vcl/sandbox/patches/vSphere_SDK_2.3.2_updated.pm (added) +++ vcl/sandbox/patches/vSphere_SDK_2.3.2_updated.pm Tue Oct 14 13:45:12 2014 @@ -0,0 +1,3696 @@ +#!/usr/bin/perl -w +############################################################################### +# $Id: vSphere_SDK.pm 1448791 2013-02-21 20:02:20Z jfthomps $ +############################################################################### +# 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::vSphere_SDK; + +=head1 SYNOPSIS + + my $vmhost_datastructure = $self->get_vmhost_datastructure(); + my $vsphere_sdk = VCL::Module::Provisioning::VMware::vSphere_SDK->new({data_structure => $vmhost_datastructure}); + my @registered_vms = $vsphere_sdk->get_registered_vms(); + +=head1 DESCRIPTION + + This module provides support for the vSphere SDK. The vSphere SDK can be used + to manage VMware Server 2.x, ESX 3.0.x, ESX/ESXi 3.5, ESX/ESXi 4.0, vCenter + Server 2.5, and vCenter Server 4.0. + + This patch was created as a temporary fix for deployments running VCL 2.3.2 + which encounter problems where the vSphere function calls die abruptly. The + calls were not wrapped in eval blocks. This modified module adds the following + subroutines: + + _get_service_content + _get_view + _find_entity_view + _find_entity_views + + All calls to the vSphere SDK functions are routed through these subroutines + which include the proper eval blocks. + +=cut + +############################################################################## +package VCL::Module::Provisioning::VMware::vSphere_SDK; + +# Specify the lib path using FindBin +use FindBin; +use lib "$FindBin::Bin/../../../.."; + +# Configure inheritance +use base qw(VCL::Module::Provisioning::VMware::VMware); + +# Specify the version of this module +our $VERSION = '2.3.2'; + +# Specify the version of Perl to use +use 5.008000; + +use strict; +use warnings; +use diagnostics; +use English qw( -no_match_vars ); +use File::Temp qw( tempdir ); +use List::Util qw( max ); + +use VCL::utils; + +############################################################################## + +=head1 API OBJECT METHODS + +=cut + +#///////////////////////////////////////////////////////////////////////////// + +=head2 initialize + + Parameters : none + Returns : boolean + Description : Initializes the vSphere SDK object by establishing a connection + to the VM host. + +=cut + +sub initialize { + 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; + } + + # Newer versions of LWP::Protocol::https have strict SSL checking enabled by default + # The vSphere SDK won't be able to connect if ESXi or vCenter uses a self-signed certificate + # The following setting disables strict checking: + $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0; + + # Override the die handler because process will die if VMware Perl libraries aren't installed + local $SIG{__DIE__} = sub{}; + + eval "use VMware::VIRuntime; use VMware::VILib; use VMware::VIExt"; + if ($EVAL_ERROR) { + notify($ERRORS{'OK'}, 0, "vSphere SDK for Perl does not appear to be installed on this managment node, unable to load VMware vSphere SDK Perl modules, error:\n$EVAL_ERROR"); + return 0; + } + notify($ERRORS{'DEBUG'}, 0, "loaded VMware vSphere SDK modules"); + + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + my $vmhost_username = $self->data->get_vmhost_profile_username(); + my $vmhost_password = $self->data->get_vmhost_profile_password(); + my $vmhost_profile_id = $self->data->get_vmhost_profile_id(); + + if (!$vmhost_hostname) { + notify($ERRORS{'WARNING'}, 0, "VM host name could not be retrieved"); + return; + } + elsif (!$vmhost_username) { + notify($ERRORS{'DEBUG'}, 0, "unable to use vSphere SDK, VM host username is not configured in the database for VM profile: $vmhost_profile_id"); + return; + } + elsif (!$vmhost_password) { + notify($ERRORS{'DEBUG'}, 0, "unable to use vSphere SDK, VM host password is not configured in the database for VM profile: $vmhost_profile_id"); + return; + } + + Opts::set_option('username', $vmhost_username); + Opts::set_option('password', $vmhost_password); + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + # Assemble the URLs to try, URL will vary based on the VMware product + my @possible_vmhost_urls = ( + "https://$vmhost_hostname/sdk", + "https://$vmhost_hostname:8333/sdk", + ); + + # Also add URLs containing the short host name if the VM hostname is a full DNS name + if ($vmhost_hostname =~ /\./) { + my ($vmhost_short_name) = $vmhost_hostname =~ /^([^\.]+)/; + push @possible_vmhost_urls, "https://$vmhost_short_name/sdk"; + push @possible_vmhost_urls, "https://$vmhost_short_name:8333/sdk"; + } + + # Call HostConnect, check how long it takes to connect + my $vim; + for my $host_url (@possible_vmhost_urls) { + Opts::set_option('url', $host_url); + + notify($ERRORS{'DEBUG'}, 0, "attempting to connect to VM host: $host_url ($vmhost_username)"); + eval { $vim = Util::connect(); }; + $vim = 'undefined' if !defined($vim); + my $error_message = $@; + undef $@; + + # It's normal if some connection attempts fail - SSH will be used if the vSphere SDK isn't available + # Don't display a warning unless the error indicates a configuration problem (wrong username or password) + # Possible error messages: + # Cannot complete login due to an incorrect user name or password. + # Error connecting to server at 'https://<VM host>/sdk': Connection refused + if ($error_message && $error_message =~ /incorrect/) { + notify($ERRORS{'WARNING'}, 0, "unable to connect to VM host because username or password is incorrectly configured in the VM profile ($vmhost_username/$vmhost_password), error: $error_message"); + } + elsif (!$vim || $error_message) { + notify($ERRORS{'DEBUG'}, 0, "unable to connect to VM host using URL: $host_url, error:\n$error_message"); + } + else { + notify($ERRORS{'OK'}, 0, "connected to VM host: $host_url, username: '$vmhost_username'"); + last; + } + } + + if (!$vim) { + notify($ERRORS{'DEBUG'}, 0, "failed to connect to VM host $vmhost_hostname"); + return; + } + elsif (!ref($vim)) { + notify($ERRORS{'DEBUG'}, 0, "failed to connect to VM host $vmhost_hostname, Util::connect returned '$vim'"); + return; + } + else { + notify($ERRORS{'DEBUG'}, 0, "connected to $vmhost_hostname, VIM object type: " . ref($vim)); + return 1; + } +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_registered_vms + + Parameters : none + Returns : array + Description : Returns an array containing the vmx file paths of the VMs running + on the VM host. + +=cut + +sub get_registered_vms { + 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; + } + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + my @vms = $self->_find_entity_views('VirtualMachine', + { + begin_entity => $self->_get_datacenter_view() + } + ); + + my @vmx_paths; + for my $vm (@vms) { + push @vmx_paths, $self->_get_normal_path($vm->summary->config->vmPathName) || return; + } + + notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@vmx_paths) . " registered VMs:\n" . join("\n", @vmx_paths)); + return @vmx_paths; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 vm_register + + Parameters : $vmx_file_path + Returns : boolean + Description : Registers the VM specified by vmx file path argument. Returns + true if the VM is already registered or if the VM was + successfully registered. + +=cut + +sub vm_register { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the vmx path argument and convert it to a datastore path + my $vmx_path = $self->_get_datastore_path(shift) || return; + + my $datacenter = $self->_get_datacenter_view() || return; + my $vm_folder = $self->_get_view($datacenter->{vmFolder}) || return; + + my $resource_pool = $self->_get_resource_pool_view() || return; + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + my $vm_mo_ref; + eval { $vm_mo_ref = $vm_folder->RegisterVM(path => $vmx_path, + asTemplate => 'false', + pool => $resource_pool + ); + }; + + if ($@) { + if ($@->isa('SoapFault') && ref($@->detail) eq 'AlreadyExists') { + notify($ERRORS{'DEBUG'}, 0, "VM is already registered: $vmx_path"); + return 1; + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to register VM: $vmx_path, error:\n$@"); + return; + } + } + + if (ref($vm_mo_ref) ne 'ManagedObjectReference' || $vm_mo_ref->type ne 'VirtualMachine') { + notify($ERRORS{'WARNING'}, 0, "RegisterVM did not return a VirtualMachine ManagedObjectReference:\n" . format_data($vm_mo_ref)); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "registered VM: $vmx_path"); + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 vm_unregister + + Parameters : $vmx_file_path or $vm_view or $vm_mo_ref + Returns : boolean + Description : Unregisters the VM specified by vmx file path argument. Returns + true if the VM is not registered or if the VM was successfully + unregistered. + +=cut + +sub vm_unregister { + 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 $argument = shift; + my $vm_view; + my $vm_name; + if (my $type = ref($argument)) { + if ($type eq 'ManagedObjectReference') { + notify($ERRORS{'DEBUG'}, 0, "argument is a ManagedObjectReference, retrieving VM view"); + $vm_view = $self->_get_view($argument) + } + elsif ($type eq 'VirtualMachine') { + $vm_view = $argument; + } + else { + notify($ERRORS{'WARNING'}, 0, "invalid argument reference type: '$type', must be either VirtualMachine or ManagedObjectReference"); + return; + } + + $vm_name = $vm_view->{name}; + if (!$vm_name) { + notify($ERRORS{'WARNING'}, 0, "failed to unregister VM, name could not be determined from VM view:\n" . format_data($vm_view)); + return; + } + } + else { + $vm_name = $argument; + $vm_view = $self->_get_vm_view($argument); + } + + my $vmx_path = $vm_view->{summary}{config}{vmPathName}; + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + notify($ERRORS{'DEBUG'}, 0, "attempting to unregister VM: '$vm_name' ($vmx_path)"); + + eval { $vm_view->UnregisterVM(); }; + if ($@) { + notify($ERRORS{'WARNING'}, 0, "failed to unregister VM: $vm_name, error:\n$@"); + return; + } + + # Delete the cached VM object + delete $self->{vm_view_objects}{$vmx_path}; + + notify($ERRORS{'DEBUG'}, 0, "unregistered VM: $vm_name"); + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 vm_power_on + + Parameters : $vmx_file_path + Returns : boolean + Description : Powers on the VM specified by vmx file path argument. Returns + true if the VM was successfully powered on or if it was already + powered on. + +=cut + +sub vm_power_on { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the vmx path argument and convert it to a datastore path + my $vmx_path = $self->_get_datastore_path(shift) || return; + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + my $vm = $self->_get_vm_view($vmx_path) || return; + + eval { $vm->PowerOnVM(); }; + if ($@) { + if ($@->isa('SoapFault') && ref($@->detail) eq 'InvalidPowerState') { + my $existing_power_state = $@->detail->existingState->val; + if ($existing_power_state =~ /on/i) { + notify($ERRORS{'DEBUG'}, 0, "VM is already powered on: $vmx_path"); + return 1; + } + } + + notify($ERRORS{'WARNING'}, 0, "failed to power on VM: $vmx_path, error:\n$@"); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "powered on VM: $vmx_path"); + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 vm_power_off + + Parameters : $vmx_file_path + Returns : boolean + Description : Powers off the VM specified by vmx file path argument. Returns + true if the VM was successfully powered off or if it was already + powered off. + +=cut + +sub vm_power_off { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the vmx path argument and convert it to a datastore path + my $vmx_path = $self->_get_datastore_path(shift) || return; + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + my $vm = $self->_get_vm_view($vmx_path) || return; + + eval { $vm->PowerOffVM(); }; + if ($@) { + if ($@->isa('SoapFault') && ref($@->detail) eq 'InvalidPowerState') { + my $existing_power_state = $@->detail->existingState->val; + if ($existing_power_state =~ /off/i) { + notify($ERRORS{'DEBUG'}, 0, "VM is already powered off: $vmx_path"); + return 1; + } + } + + notify($ERRORS{'WARNING'}, 0, "failed to power off VM: $vmx_path, error:\n$@"); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "powered off VM: $vmx_path"); + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_vm_power_state + + Parameters : $vmx_file_path + Returns : string + Description : Determines the power state of the VM specified by the vmx file + path argument. A string is returned containing one of the + following values: + -on + -off + -suspended + +=cut + +sub get_vm_power_state { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the vmx path argument and convert it to a datastore path + my $vmx_path = $self->_get_datastore_path(shift) || return; + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + my $vm = $self->_get_vm_view($vmx_path) || return; + + my $power_state = $vm->runtime->powerState->val; + + my $return_power_state; + if ($power_state =~ /on/i) { + $return_power_state = 'on'; + } + elsif ($power_state =~ /off/i) { + $return_power_state = 'off'; + } + elsif ($power_state =~ /suspended/i) { + $return_power_state = 'suspended'; + } + else { + notify($ERRORS{'WARNING'}, 0, "detected unsupported power state: $power_state"); + $return_power_state = '$power_state'; + } + + notify($ERRORS{'DEBUG'}, 0, "power state of VM $vmx_path: $return_power_state"); + return $return_power_state; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 _clean_vm_name + Parameters : $vm_name + Returns : string + Description : VMWare vCenter supports VM Names of up to 80 characters, but if + the name is greater than 29 characters, it will truncate the + corresponding name and enclosing directory of the virtual disks. + +=cut + +sub _clean_vm_name { + 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 $vm_name = shift || return; + + # if the length of the name is > 29, then truncate it in such a way that + # the image name remains unique in the VCL database + my $MAX_VMNAME_LEN = 29; + if (length $vm_name > $MAX_VMNAME_LEN) { + notify($ERRORS{'DEBUG'}, 0, "truncating VM name $vm_name"); + my $newname = ""; + if ($vm_name =~ m/^(\w+)-(\w+?)(\d*)-(v\d+)$/) { + my $base = $1; + my $name = $2; + my $imgid = $3; + my $version = $4; + my $shortened = substr($name, 0, $MAX_VMNAME_LEN - 2 - length($imgid) - length($base) - length($version)); + $newname = $base . "-" . $shortened . $imgid . "-" . $version; + } + else { + my ($pre_len, $post_len) = ($MAX_VMNAME_LEN - 10, 10); + my ($pre, $post) = $vm_name =~ m/^(.{$pre_len}).*(.{$post_len})$/; + $newname = $pre . $post; + } + if (get_image_info($newname, 0, 1)) { + notify($ERRORS{'WARNING'}, 0, "Naming conflict: $newname already exists in the database"); + } + else { + notify($ERRORS{'DEBUG'}, 0, "Changed image name to: $newname"); + $vm_name = $newname; + } + } + return $vm_name; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 copy_virtual_disk + + Parameters : $source_vmdk_file_path, $destination_vmdk_file_path, $disk_type (optional), $adapter_type (optional) + Returns : boolean + Description : Copies a virtual disk (set of vmdk files). This subroutine allows + a virtual disk to be converted to a different disk type or + adapter type. The source and destination vmdk file path arguments + are required. + + The disk type argument is optional and may be one of the + following values: + -eagerZeroedThick + -all space allocated and wiped clean of any previous contents on the physical media at creation time + -may take longer time during creation compared to other disk formats + -flatMonolithic + -preallocated monolithic disk + -disks in this format can be used with other VMware products + -format is only applicable as a destination format in a clone operation + -not usable for disk creation + -since vSphere API 4.0 + -preallocated + -all space allocated at creation time + -space is zeroed on demand as the space is used + -raw + -raw device + -rdm + -virtual compatibility mode raw disk mapping + -grants access to the entire raw disk and the virtual disk can participate in snapshots + -rdmp + -physical compatibility mode (pass-through) raw disk mapping + -passes SCSI commands directly to the hardware + -cannot participate in snapshots + -sparse2Gb, 2Gbsparse + -sparse disk with 2GB maximum extent size + -can be used with other VMware products + -2GB extent size makes these disks easier to burn to dvd or use on filesystems that don't support large files + -only applicable as a destination format in a clone operation + -not usable for disk creation + -sparseMonolithic + -sparse monolithic disk + -can be used with other VMware products + -only applicable as a destination format in a clone operation + -not usable for disk creation + -since vSphere API 4.0 + -thick + -all space allocated at creation time + -space may contain stale data on the physical media + -primarily used for virtual machine clustering + -generally insecure and should not be used + -due to better performance and security properties, the use of the 'preallocated' format is preferred over this format + -thick2Gb + -thick disk with 2GB maximum extent size + -can be used with other VMware products + -2GB extent size makes these disks easier to burn to dvd or use on filesystems that don't support large files + -only applicable as a destination format in a clone operation + -not usable for disk creation + -thin (default) + -space required for thin-provisioned virtual disk is allocated and zeroed on demand as the space is used + + The adapter type argument is optional and may be one of the + following values: + -busLogic + -ide + -lsiLogic + + If the adapter type argument is not specified an attempt will be + made to retrieve it from the source vmdk file. If this fails, + lsiLogic will be used. + +=cut + +sub copy_virtual_disk { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the source and destination path arguments in the datastore path format + my $source_path = $self->_get_datastore_path(shift) || return; + my $destination_path = $self->_get_datastore_path(shift) || return; + + # Make sure the source path ends with .vmdk + if ($source_path !~ /\.vmdk$/i || $destination_path !~ /\.vmdk$/i) { + notify($ERRORS{'WARNING'}, 0, "source and destination path arguments must end with .vmdk:\nsource path argument: $source_path\ndestination path argument: $destination_path"); + return; + } + + + # Get the adapter type and disk type arguments if they were specified + # If not specified, set the default values + my $destination_disk_type = shift || 'thin'; + + # Fix the disk type in case 2gbsparse was passed + if ($destination_disk_type =~ /2gbsparse/i) { + $destination_disk_type = 'sparse2Gb'; + } + + # Check the disk type argument, the string must match exactly or the copy will fail + my @valid_disk_types = qw( eagerZeroedThick flatMonolithic preallocated raw rdm rdmp sparse2Gb sparseMonolithic thick thick2Gb thin ); + if (!grep(/^$destination_disk_type$/, @valid_disk_types)) { + notify($ERRORS{'WARNING'}, 0, "disk type argument is not valid: '$destination_disk_type', it must exactly match (case sensitive) one of the following strings:\n" . join("\n", @valid_disk_types)); + return; + } + + my $vmhost_name = $self->data->get_vmhost_hostname(); + + my $source_datastore_name = $self->_get_datastore_name($source_path) || return; + my $destination_datastore_name = $self->_get_datastore_name($destination_path) || return; + + my $source_datastore = $self->_get_datastore_object($source_datastore_name) || return; + my $destination_datastore = $self->_get_datastore_object($destination_datastore_name) || return; + + my $destination_base_name = $self->_get_file_base_name($destination_path); + + my $datacenter_view = $self->_get_datacenter_view() || return; + my $virtual_disk_manager_view = $self->_get_virtual_disk_manager_view() || return; + + # Get the source vmdk file info so the source adapter and disk type can be displayed + my $source_info = $self->_get_file_info($source_path) || return; + if (scalar(keys %$source_info) != 1) { + notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, multiple source files were found:\n" . format_data($source_info)); + } + + my $source_info_file_path = (keys(%$source_info))[0]; + + my $source_adapter_type = $source_info->{$source_info_file_path}{controllerType} || 'lsiLogic'; + my $source_disk_type = $source_info->{$source_info_file_path}{diskType} || ''; + my $source_file_size_bytes = $source_info->{$source_info_file_path}{fileSize} || '0'; + my $source_file_capacity_kb = $source_info->{$source_info_file_path}{capacityKb} || '0'; + my $source_file_capacity_bytes = ($source_file_capacity_kb * 1024); + + # Set the destination adapter type to the source adapter type if it wasn't specified as an argument + my $destination_adapter_type = shift || $source_adapter_type; + + if ($destination_adapter_type =~ /bus/i) { + $destination_adapter_type = 'busLogic'; + } + elsif ($destination_adapter_type =~ /lsi/) { + $destination_adapter_type = 'lsiLogic'; + } + else { + $destination_adapter_type = 'ide'; + } + + if ($source_adapter_type !~ /\w/ || $source_disk_type !~ /\w/ || $source_file_size_bytes !~ /\d/) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve adapter type, disk type, and file size of source file on VM host $vmhost_name: '$source_path', file info:\n" . format_data($source_info)); + return; + } + + # Get the destination partent directory path and create the directory + my $destination_directory_path = $self->_get_parent_directory_datastore_path($destination_path) || return; + $self->create_directory($destination_directory_path) || return; + + notify($ERRORS{'DEBUG'}, 0, "attempting to copy virtual disk on VM host $vmhost_name: '$source_path' --> '$destination_path'\n" . + "source adapter type: $source_adapter_type\n" . + "destination adapter type: $destination_adapter_type\n" . + "disk type: $source_disk_type\n" . + "source capacity: " . get_file_size_info_string($source_file_capacity_bytes) . "\n" . + "source space used: " . get_file_size_info_string($source_file_size_bytes) + ); + + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + # Create a virtual disk spec object + my $virtual_disk_spec = VirtualDiskSpec->new( + adapterType => $destination_adapter_type, + diskType => $destination_disk_type, + ); + + my $copy_virtual_disk_result; + eval { + $copy_virtual_disk_result = $virtual_disk_manager_view->CopyVirtualDisk( + sourceName => $source_path, + sourceDatacenter => $datacenter_view, + destName => $destination_path, + destDatacenter => $datacenter_view, + destSpec => $virtual_disk_spec, + force => 1 + ); + }; + + # Check if an error occurred + if (my $copy_virtual_disk_fault = $@) { + if ($copy_virtual_disk_fault =~ /No space left/i) { + # Check if the output indicates there is not enough space to copy the vmdk + # Output will contain: + # Fault string: A general system error occurred: No space left on device + # Fault detail: SystemError + notify($ERRORS{'CRITICAL'}, 0, "failed to copy vmdk on VM host $vmhost_name using CopyVirtualDisk function, no space is left on the destination device: '$destination_path'\nerror:\n$copy_virtual_disk_fault"); + return; + } + elsif ($copy_virtual_disk_fault =~ /not implemented/i) { + notify($ERRORS{'DEBUG'}, 0, "unable to copy vmdk using CopyVirtualDisk function, VM host $vmhost_name does not implement the CopyVirtualDisk function"); + + # Delete the destination directory path previously created + $self->delete_file($destination_directory_path); + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to copy vmdk on VM host $vmhost_name using CopyVirtualDisk function: '$source_path' --> '$destination_path'\nerror:\n$copy_virtual_disk_fault"); + return; + } + } + else { + notify($ERRORS{'OK'}, 0, "copied vmdk on VM host $vmhost_name using CopyVirtualDisk function:\n" . format_data($copy_virtual_disk_result)); + return 1; + } + + + my $source_vm_name = $self->_clean_vm_name("source_$destination_base_name"); + my $clone_vm_name = $self->_clean_vm_name($destination_base_name); + + my $source_vm_directory_path = "[$source_datastore_name] $source_vm_name"; + my $clone_vm_directory_path = "[$destination_datastore_name] $clone_vm_name"; + + # Make sure the source and clone directories don't exist + # Otherwise the VM creation/cloning process will create another directory with '_1' appended and the files won't be deleted + if ($self->file_exists($source_vm_directory_path)) { + notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, source VM directory path already exists: $source_vm_directory_path"); + return; + } + if ($self->file_exists($clone_vm_directory_path)) { + notify($ERRORS{'WARNING'}, 0, "unable to copy virtual disk, clone VM directory path already exists: $clone_vm_directory_path"); + return; + } + + + my $file_manager = $self->_get_file_manager_view() || return; + my $resource_pool_view = $self->_get_resource_pool_view() || return; + + my $folder_view = $self->_find_entity_view("Folder", + { + begin_entity => $datacenter_view, + filter => { name => "vm" } + } + ); + + if (!$folder_view){ + notify($ERRORS{'WARNING'}, 0, "failed to retrieve VM folder view"); + return; + } + + # Create a virtual machine on top of this virtual disk + # First, create a controller for the virtual disk + my $controller; + if ($destination_adapter_type eq 'lsiLogic') { + $controller = VirtualLsiLogicController->new( + key => 0, + device => [0], + busNumber => 0, + sharedBus => VirtualSCSISharing->new('noSharing') + ); + } + else { + $controller = VirtualBusLogicController->new( + key => 0, + device => [0], + busNumber => 0, + sharedBus => VirtualSCSISharing->new('noSharing') + ); + } + + # Next create a disk type (it will be the same as the source disk) + my $disk_backing_info = ($source_disk_type)->new( + datastore => $source_datastore, + fileName => $source_path, + diskMode => "independent_persistent" + ); + + # Create the actual virtual disk + my $source_vm_disk = VirtualDisk->new( + key => 0, + backing => $disk_backing_info, + capacityInKB => $source_file_capacity_kb, + controllerKey => 0, + unitNumber => 0 + ); + + # Create the specification for creating a source VM + my $source_vm_config = VirtualMachineConfigSpec->new( + name => $source_vm_name, + deviceChange => [ + VirtualDeviceConfigSpec->new( + operation => VirtualDeviceConfigSpecOperation->new('add'), + device => $controller + ), + VirtualDeviceConfigSpec->new( + operation => VirtualDeviceConfigSpecOperation->new('add'), + device => $source_vm_disk + ) + ], + files => VirtualMachineFileInfo->new( + logDirectory => $source_vm_directory_path, + snapshotDirectory => $source_vm_directory_path, + suspendDirectory => $source_vm_directory_path, + vmPathName => $source_vm_directory_path + ) + ); + + # Create the specification for cloning the VM + my $clone_spec = VirtualMachineCloneSpec->new( + config => VirtualMachineConfigSpec->new( + name => $clone_vm_name, + files => VirtualMachineFileInfo->new( + logDirectory => $clone_vm_directory_path, + snapshotDirectory => $clone_vm_directory_path, + suspendDirectory => $clone_vm_directory_path, + vmPathName => $clone_vm_directory_path + ) + ), + powerOn => 0, + template => 0, + location => VirtualMachineRelocateSpec->new( + datastore => $destination_datastore, + pool => $resource_pool_view, + diskMoveType => 'moveAllDiskBackingsAndDisallowSharing', + transform => VirtualMachineRelocateTransformation->new('sparse'), + ) + ); + + + notify($ERRORS{'DEBUG'}, 0, "attempting to copy virtual disk by cloning temporary VM: '$source_path' --> '$destination_path'\n" . + "adapter type: $source_adapter_type\n" . + "source disk type: $source_disk_type\n" . + "source capacity: " . get_file_size_info_string($source_file_capacity_bytes) . "\n" . + "source space used: " . get_file_size_info_string($source_file_size_bytes) . "\n" . + "source VM name: $source_vm_name\n" . + "clone VM name: $clone_vm_name\n" . + "source VM directory path: $source_vm_directory_path\n" . + "clone VM directory path: $clone_vm_directory_path" + ); + + + my $source_vm_view; + my $clone_vm_view; + eval { + my $source_vm = $folder_view->CreateVM( + config => $source_vm_config, + pool => $resource_pool_view + ); + if ($source_vm) { + notify($ERRORS{'DEBUG'}, 0, "created temporary source VM which will be cloned: $source_vm_name"); + $source_vm_view = $self->_get_view($source_vm); + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to create temporary source VM which will be cloned: $source_vm_name"); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "cloning VM: $source_vm_name --> $clone_vm_name"); + my $clone_vm = $source_vm_view->CloneVM( + folder => $folder_view, + name => $clone_vm_name, + spec => $clone_spec + ); + if ($clone_vm) { + $clone_vm_view = $self->_get_view($clone_vm); + notify($ERRORS{'DEBUG'}, 0, "cloned VM: $source_vm_name --> $clone_vm_name" . format_data($clone_vm_view)); + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to clone VM: $source_vm_name --> $clone_vm_name"); + return; + } + }; + + # Check if an error occurred + if (my $fault = $@) { + if ($fault =~ /No space left/i) { + # Check if the output indicates there is not enough space to copy the vmdk + # Output will contain: + # Fault string: A general system error occurred: No space left on device + # Fault detail: SystemError + notify($ERRORS{'CRITICAL'}, 0, "failed to copy vmdk on VM host $vmhost_name, no space is left on the destination device: '$destination_path'\nerror:\n$fault"); + return; + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to copy vmdk on VM host $vmhost_name: '$source_path' --> '$destination_path'\nerror:\n$fault"); + } + return; + } + + notify($ERRORS{'DEBUG'}, 0, "deleting source VM: $source_vm_name"); + $self->vm_unregister($source_vm_view); + notify($ERRORS{'DEBUG'}, 0, "deleting source VM directory: $source_vm_directory_path"); + $self->delete_file($source_vm_directory_path); + + notify($ERRORS{'DEBUG'}, 0, "deleting cloned VM: $clone_vm_name"); + $self->vm_unregister($clone_vm_view); + my @clone_files = $self->find_files($clone_vm_directory_path, '*', 1); + for my $clone_file_path (grep(!/\.(vmdk)$/i, @clone_files)) { + notify($ERRORS{'DEBUG'}, 0, "deleting cloned VM file: $clone_file_path"); + $self->delete_file($clone_file_path); + } + + # Set this as a class value so that it is retrievable from within + # the calling context, i.e. capture(), routine. This way, in case + # the name changes, it is possible to update the database with the new value. + $self->{new_image_name} = $clone_vm_name; + notify($ERRORS{'OK'}, 0, "copied virtual disk on VM host $vmhost_name: '$source_path' --> '$destination_path'"); + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 move_virtual_disk + + Parameters : $source_path, $destination_path + Returns : boolean + Description : Moves or renames a virtual disk (set of vmdk files). + +=cut + +sub move_virtual_disk { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the source path argument in datastore path format + my $source_path = $self->_get_datastore_path(shift) || return; + my $destination_path = $self->_get_datastore_path(shift) || return; + + my $vmhost_name = $self->data->get_vmhost_hostname(); + + # Make sure the source path ends with .vmdk + if ($source_path !~ /\.vmdk$/i || $destination_path !~ /\.vmdk$/i) { + notify($ERRORS{'WARNING'}, 0, "source and destination path arguments must end with .vmdk:\nsource path argument: $source_path\ndestination path argument: $destination_path"); + return; + } + + # Make sure the source file exists + if (!$self->file_exists($source_path)) { + notify($ERRORS{'WARNING'}, 0, "source file does not exist on VM host $vmhost_name: '$source_path'"); + return; + } + + # Make sure the destination file does not exist + if ($self->file_exists($destination_path)) { + notify($ERRORS{'WARNING'}, 0, "destination file already exists on VM host $vmhost_name: '$destination_path'"); + return; + } + + # Get the destination parent directory path, make sure it exists + my $destination_parent_directory_path = $self->_get_parent_directory_datastore_path($destination_path) || return; + $self->create_directory($destination_parent_directory_path) || return; + + # Check if a virtual disk manager object is available + my $virtual_disk_manager = $self->_get_virtual_disk_manager_view() || return; + + # Create a datacenter object + my $datacenter = $self->_get_datacenter_view() || return; + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + # Attempt to move the virtual disk using MoveVirtualDisk + notify($ERRORS{'DEBUG'}, 0, "attempting to move virtual disk on VM host $vmhost_name: '$source_path' --> '$destination_path'"); + eval { $virtual_disk_manager->MoveVirtualDisk(sourceName => $source_path, + sourceDatacenter => $datacenter, + destName => $destination_path, + destDatacenter => $datacenter, + force => 0); + }; + + # Check if an error occurred + if (my $fault = $@) { + # Get the source file info + my $source_file_info = $self->_get_file_info($source_path)->{$source_path}; + + # A FileNotFound fault will be generated if the source vmdk file exists but there is a problem with it + if ($fault->isa('SoapFault') && ref($fault->detail) eq 'FileNotFound' && defined($source_file_info->{type}) && $source_file_info->{type} !~ /vmdisk/i) { + notify($ERRORS{'WARNING'}, 0, "failed to move virtual disk on VM host $vmhost_name, source file is either not a virtual disk file or there is a problem with its configuration, check the 'Extent description' section of the vmdk file: '$source_path'\nsource file info:\n" . format_data($source_file_info)); + return; + } + elsif ($fault =~ /No space left/i) { + notify($ERRORS{'CRITICAL'}, 0, "failed to move virtual disk on VM host $vmhost_name, no space is left on the destination device: '$destination_path'\nerror:\n$fault"); + return; + } + elsif ($fault =~ /not implemented/i){ + notify($ERRORS{'DEBUG'}, 0, "unable to move vmdk using MoveVirtualDisk function, VM host $vmhost_name does not implement the MoveVirtualDisk function"); + $self->delete_file($destination_parent_directory_path); + } + elsif ($source_file_info) { + notify($ERRORS{'WARNING'}, 0, "failed to move virtual disk on VM host $vmhost_name:\n'$source_path' --> '$destination_path'\nsource file info:\n" . format_data($source_file_info) . "\n$fault"); + return; + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to move virtual disk on VM host $vmhost_name:\n'$source_path' --> '$destination_path'\nsource file info: unavailable\n$fault"); + return; + } + + } else { + notify($ERRORS{'OK'}, 0, "moved virtual disk on VM host $vmhost_name:\n'$source_path' --> '$destination_path'"); + return 1; + } + + # This section should apply only to vCenter hosts, i.e. hosts for which the + # MoveVirtualDisk method is not implemented. Instead, use the copy_virtual_disk + # method (where the CloneVM method is used) and cleanup source files afterward. + if($self->copy_virtual_disk($source_path, $destination_path)){ + my $file_manager = $self->_get_file_manager_view() || return; + my $source_parent_directory_path = $self->_get_parent_directory_datastore_path($source_path) || return; + notify($ERRORS{'DEBUG'}, 0, "Removing source directory: $source_parent_directory_path"); + $file_manager->DeleteDatastoreFile( + name => $source_parent_directory_path, + datacenter => $datacenter); + + notify($ERRORS{'OK'}, 0, "moved virtual disk on VM host $vmhost_name:\n'$source_path' --> '$destination_path'"); + return 1; + } else { + notify($ERRORS{'WARNING'}, 0, "Unable to move virtual disk from $vmhost_name: '$source_path' --> '$destination_path'"); + return 0; + } +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 create_nfs_datastore + + Parameters : $datastore_name, $remote_host, $remote_path + Returns : boolean + Description : Creates an NFS datastore on the VM host. Note: this subroutine is + not currenly being called by anything. + +=cut + +sub create_nfs_datastore { + my $self = shift; + if (ref($self) !~ /module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the arguments + my ($datastore_name, $remote_host, $remote_path) = @_; + if (!$datastore_name || !$remote_host || !$remote_path) { + notify($ERRORS{'WARNING'}, 0, "datastore name, remote host, and remote path arguments were not supplied"); + return; + } + + # Remove trailing slashes from the remote path + $remote_path =~ s/\/+$//g; + + # Assemble a datastore device string, used to check if existing datastore is pointing to the same remote host and path + my $datastore_device = "$remote_host:$remote_path"; + + # Get the existing datastore info + my $datastore_info = $self->_get_datastore_info(); + for my $check_datastore_name (keys(%$datastore_info)) { + my $check_datastore_type = $datastore_info->{$check_datastore_name}{type}; + + # Make sure a non-NFS datastore with the same name doesn't alreay exist + if ($check_datastore_type !~ /nfs/i) { + if ($check_datastore_name eq $datastore_name) { + notify($ERRORS{'WARNING'}, 0, "datastore named $datastore_name already exists on VM host but its type is not NFS:\n" . format_data($datastore_info->{$check_datastore_name})); + return; + } + else { + # Type isn't NFS and name doesn't match + next; + } + } + + # Get the existing datastore device string, format is: + # 10.25.0.245:/install/vmtest/datastore + my $check_datastore_device = $datastore_info->{$check_datastore_name}{datastore}{value}; + if (!$check_datastore_device) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve datastore device string from datastore info:\n" . format_data($datastore_info->{$check_datastore_name})); + next; + } + + # Remove trailing slashes from existing device string + $check_datastore_device =~ s/\/+$//g; + + # Check if datastore already exists pointing to the same remote path + if ($check_datastore_name eq $datastore_name) { + # Datastore names match, check if existing datastore is pointing the the requested device path + if ($check_datastore_device eq $datastore_device) { + notify($ERRORS{'DEBUG'}, 0, "$check_datastore_type datastore '$datastore_name' already exists on VM host, remote path: $check_datastore_device"); + return 1; + } + else { + notify($ERRORS{'WARNING'}, 0, "$check_datastore_type datastore '$datastore_name' already exists on VM host but it is pointing to a different remote path: + requested remote path: $datastore_device + existing remote path: $check_datastore_device"); + return; + } + } + else { + # Datastore names don't match, make sure an existing datastore with a different name isn't pointing to the requested device path + if ($check_datastore_device eq $datastore_device) { + notify($ERRORS{'WARNING'}, 0, "$check_datastore_type datastore with a different name already exists on VM host pointing to '$check_datastore_device': + requested datastore name: $datastore_name + existing datastore name: $check_datastore_name"); + return; + } + else { + # Datastore name doesn't match, datastore remote path doesn't match + next; + } + } + } + + # Get the datastore system object + my $datastore_system = $self->_get_view($self->_get_datastore_view->configManager->datastoreSystem); + if (!$datastore_system) { + notify($ERRORS{'WARNING'}, 0, "failed to retrieve datastore system object"); + return; + } + + # Create a HostNasVolumeSpec object to store the datastore configuration + my $host_nas_volume_spec = HostNasVolumeSpec->new(accessMode => 'readWrite', + localPath => $datastore_name, + remoteHost => $remote_host, + remotePath => $remote_path, + ); + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + # Attempt to cretae the NAS datastore + notify($ERRORS{'DEBUG'}, 0, "attempting to create NAS datastore:\n" . format_data($host_nas_volume_spec)); + eval { $datastore_system->CreateNasDatastore(spec => $host_nas_volume_spec); }; + if (my $fault = $@) { + notify($ERRORS{'WARNING'}, 0, "failed to create NAS datastore on VM host:\ndatastore name: $datastore_name\nremote host: $remote_host\nremote path: $remote_path\nerror:\n$@"); + return; + } + + notify($ERRORS{'OK'}, 0, "created NAS datastore on VM host: $datastore_name"); + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_virtual_disk_controller_type + + Parameters : $vmdk_file_path + Returns : string + Description : Retrieves the disk controller type configured for the virtual + disk specified by the vmdk file path argument. A string is + returned containing one of the following values: + -lsiLogic + -busLogic + -ide + +=cut + +sub get_virtual_disk_controller_type { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the vmdk file path argument + my $vmdk_file_path = $self->_get_datastore_path(shift) || return; + if ($vmdk_file_path !~ /\.vmdk$/) { + notify($ERRORS{'WARNING'}, 0, "file path argument must end with .vmdk: $vmdk_file_path"); + return; + } + + # Get the vmdk file info + my $vmdk_file_info = $self->_get_file_info($vmdk_file_path)->{$vmdk_file_path}; + if (!$vmdk_file_info) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve info for file: $vmdk_file_path"); + return; + } + + # Check if the controllerType key exists in the vmdk file info + if (!defined($vmdk_file_info->{controllerType}) || !$vmdk_file_info->{controllerType}) { + notify($ERRORS{'DEBUG'}, 0, "unable to retrieve controllerType value from file info: $vmdk_file_path\n" . format_data($vmdk_file_info)); + return; + } + + my $controller_type = $vmdk_file_info->{controllerType}; + + my $return_controller_type; + if ($controller_type =~ /lsi/i) { + $return_controller_type = 'lsiLogic'; + } + elsif ($controller_type =~ /bus/i) { + $return_controller_type = 'busLogic'; + } + elsif ($controller_type =~ /ide/i) { + $return_controller_type = 'ide'; + } + else { + $return_controller_type = $controller_type; + } + + notify($ERRORS{'DEBUG'}, 0, "retrieved controllerType value from vmdk file info: $return_controller_type ($controller_type)"); + return $return_controller_type; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_virtual_disk_type + + Parameters : $vmdk_file_path + Returns : string + Description : Retrieves the disk type configured for the virtual + disk specified by the vmdk file path argument. A string is + returned containing one of the following values: + -FlatVer1 + -FlatVer2 + -RawDiskMappingVer1 + -SparseVer1 + -SparseVer2 + +=cut + +sub get_virtual_disk_type { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the vmdk file path argument + my $vmdk_file_path = $self->_get_datastore_path(shift) || return; + if ($vmdk_file_path !~ /\.vmdk$/) { + notify($ERRORS{'WARNING'}, 0, "file path argument must end with .vmdk: $vmdk_file_path"); + return; + } + + # Get the vmdk file info + my $vmdk_file_info = $self->_get_file_info($vmdk_file_path)->{$vmdk_file_path}; + if (!$vmdk_file_info) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve info for file: $vmdk_file_path"); + return; + } + + # Check if the diskType key exists in the vmdk file info + if (!defined($vmdk_file_info->{diskType}) || !$vmdk_file_info->{diskType}) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve diskType value from file info: $vmdk_file_path\n" . format_data($vmdk_file_info)); + return; + } + + my $disk_type = $vmdk_file_info->{diskType}; + + if ($disk_type =~ /VirtualDisk(.+)BackingInfo/) { + $disk_type = $1; + } + notify($ERRORS{'DEBUG'}, 0, "retrieved diskType value from vmdk file info: $disk_type"); + return $disk_type; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_virtual_disk_hardware_version + + Parameters : $vmdk_file_path + Returns : string + Description : Retrieves the virtual disk hardware version configured for the + virtual disk specified by the vmdk file path argument. + +=cut + +sub get_virtual_disk_hardware_version { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the vmdk file path argument + my $vmdk_file_path = $self->_get_datastore_path(shift) || return; + if ($vmdk_file_path !~ /\.vmdk$/) { + notify($ERRORS{'WARNING'}, 0, "file path argument must end with .vmdk: $vmdk_file_path"); + return; + } + + # Get the vmdk file info + my $vmdk_file_info = $self->_get_file_info($vmdk_file_path)->{$vmdk_file_path}; + if (!$vmdk_file_info) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve info for file: $vmdk_file_path"); + return; + } + + # Check if the hardwareVersion key exists in the vmdk file info + my $hardware_version = $vmdk_file_info->{hardwareVersion}; + if (!$hardware_version) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve hardwareVersion value from file info: $vmdk_file_path\n" . format_data($vmdk_file_info)); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "retrieved hardwareVersion value from vmdk file info: $hardware_version"); + return $hardware_version; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_vmware_product_name + + Parameters : none + Returns : string + Description : Returns the full VMware product name installed on the VM host. + Examples: + VMware Server 2.0.2 build-203138 + VMware ESXi 4.0.0 build-208167 + +=cut + +sub get_vmware_product_name { + 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; + } + + return $self->{product_name} if $self->{product_name}; + + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + my $service_content = $self->_get_service_content(); + my $product_name = $service_content->{about}->{fullName}; + if ($product_name) { + notify($ERRORS{'DEBUG'}, 0, "VMware product being used on VM host $vmhost_hostname: '$product_name'"); + $self->{product_name} = $product_name; + return $self->{product_name}; + } + else { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve VMware product name being used on VM host $vmhost_hostname"); + return; + } +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_vmware_product_version + + Parameters : none + Returns : string + Description : Returns the VMware product version installed on the VM host. + Example: '4.0.0' + +=cut + +sub get_vmware_product_version { + 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; + } + + return $self->{product_version} if $self->{product_version}; + + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + my $datacenter_view = $self->_get_datacenter_view(); + my $product_version = $datacenter_view->config->product->version; + + if ($product_version) { + notify($ERRORS{'DEBUG'}, 0, "retrieved product version for VM host $vmhost_hostname: $product_version"); + $self->{product_version} = $product_version; + return $self->{product_version}; + } + else { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve product version for VM host $vmhost_hostname"); + return; + } +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_network_names + + Parameters : none + Returns : array + Description : Retrieves the network names configured on the VM host. + +=cut + +sub get_network_names { + 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 $datacenter_view = $self->_get_datacenter_view(); + + # Retrieve the network info, check if each network is accessible + my @network_names; + for my $network (@{Vim::get_views(mo_ref_array => $datacenter_view->network)}) { + push @network_names, $network->name; + } + + notify($ERRORS{'DEBUG'}, 0, "retrieved network names:\n" . join("\n", @network_names)); + return @network_names; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 is_restricted + + Parameters : none + Returns : boolean + Description : Determines if remote access to the VM host via the vSphere SDK is + restricted due to the type of VMware license being used on the + host. 0 is returned if remote access is not restricted. 1 is + returned if remote access is restricted and the access to the VM + host is read-only. + +=cut + +sub is_restricted { + my $self = shift; + if (ref($self) !~ /module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + my $service_content = $self->_get_service_content(); + if (!$service_content) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve vSphere SDK service content object, assuming access to the VM host via the vSphere SDK is restricted"); + return 1; + } + + # Attempt to get a virtual disk manager object + # This is required to copy virtual disks and perform other operations + if (!$service_content->{virtualDiskManager}) { + notify($ERRORS{'OK'}, 0, "access to the VM host is restricted, virtual disk manager is not available through the vSphere SDK"); + return 1; + } + + # Get a fileManager object + my $file_manager = $self->_get_view($service_content->{fileManager}) || return; + if (!$file_manager) { + notify($ERRORS{'WARNING'}, 0, "unable to determine if access to the VM host via the vSphere SDK is restricted due to the license, failed to retrieve file manager object"); + return 1; + } + + # Override the die handler because MakeDirectory may call it + local $SIG{__DIE__} = sub{}; + + # Attempt to create the test directory, check if RestrictedVersion fault occurs + eval { $file_manager->DeleteDatastoreFile(name => ''); } ; + if (my $fault = $@) { + if ($fault->isa('SoapFault') && ref($fault->detail) eq 'RestrictedVersion') { + notify($ERRORS{'OK'}, 0, "access to the VM host via the vSphere SDK is restricted due to the license: " . $fault->name); + return 1; + } + elsif ($fault->isa('SoapFault') && (ref($fault->detail) eq 'InvalidDatastorePath' || ref($fault->detail) eq 'InvalidArgument')) { + # Do nothing, expected since empty path was passed to DeleteDatastoreFile + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to determine if access to the VM host via the vSphere SDK is restricted due to the license, error:\n$@"); + return 1; + } + } + + notify($ERRORS{'OK'}, 0, "access to the VM host via the vSphere SDK is NOT restricted due to the license"); + + return 0; +} + +############################################################################## + +=head1 OS FUNCTIONALITY OBJECT METHODS + +=cut + +#///////////////////////////////////////////////////////////////////////////// + +=head2 create_directory + + Parameters : $directory_path + Returns : boolean + Description : Creates a directory on a datastore on the VM host using the + vSphere SDK. + +=cut + +sub create_directory { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get and check the directory path argument + my $directory_path = $self->_get_datastore_path(shift) || return; + + # Check if the directory already exists + return 1 if $self->file_exists($directory_path); + + # Get the VM host name + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + # Get a fileManager object + my $file_manager = $self->_get_file_manager_view() || return; + + # Override the die handler because MakeDirectory may call it + local $SIG{__DIE__} = sub{}; + + # Attempt to create the directory + eval { $file_manager->MakeDirectory(name => $directory_path, + datacenter => $self->_get_datacenter_view(), + createParentDirectories => 1); + }; + + if ($@) { + if ($@->isa('SoapFault') && ref($@->detail) eq 'FileAlreadyExists') { + notify($ERRORS{'DEBUG'}, 0, "directory already exists: '$directory_path'"); + return 1; + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to create directory: '$directory_path'\nerror:\n$@"); + return; + } + } + else { + notify($ERRORS{'OK'}, 0, "created directory: '$directory_path'"); + return 1; + } +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 delete_file + + Parameters : $file_path + Returns : boolean + Description : Deletes the file from a datastore on the VM host. Wildcards may + not be used in the file path argument. + +=cut + +sub delete_file { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get and check the file path argument + my $path_argument = shift; + if (!$path_argument) { + notify($ERRORS{'WARNING'}, 0, "path argument was not specified"); + return; + } + + my $datastore_path = $self->_get_datastore_path($path_argument); + if (!$datastore_path) { + notify($ERRORS{'WARNING'}, 0, "failed to convert path argument to datastore path: $path_argument"); + return; + } + + # Sanity check, make sure the file path argument is not the root of a datastore + # Otherwise everything in the datastore would be deleted + if ($datastore_path =~ /^\[.+\]$/) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called with the file path argument pointing to the root of a datastore, this would cause all datastore contents to be deleted\nfile path argument: '$path_argument'\ndatastore path: '$datastore_path'"); + return; + } + + # Get a fileManager object + my $file_manager = $self->_get_file_manager_view() || return; + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + # Attempt to delete the file + notify($ERRORS{'OK'}, 0, "attempting to delete file: $datastore_path"); + eval { $file_manager->DeleteDatastoreFile(name => $datastore_path, datacenter => $self->_get_datacenter_view()); }; + if ($@) { + if ($@->isa('SoapFault') && ref($@->detail) eq 'FileNotFound') { + notify($ERRORS{'DEBUG'}, 0, "file does not exist: $datastore_path"); + return 1; + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to delete file: $datastore_path, error:\n$@"); + return; + } + } + else { + notify($ERRORS{'OK'}, 0, "deleted file: $datastore_path"); + return 1; + } +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 copy_file + + Parameters : $source_file_path, $destination_file_path + Returns : boolean + Description : Copies a file from one datastore location on the VM host to + another datastore location on the VM host. Wildcards may not be + used in the file path argument. + +=cut + +sub copy_file { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get and check the file path arguments + my $source_file_path = $self->_get_datastore_path(shift) || return; + my $destination_file_path = $self->_get_datastore_path(shift) || return; + + # Get the VM host name + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + # Get the destination directory path and create the directory if it doesn't exit + my $destination_directory_path = $self->_get_parent_directory_datastore_path($destination_file_path) || return; + $self->create_directory($destination_directory_path) || return; + + # Get a fileManager object + my $file_manager = $self->_get_file_manager_view() || return; + my $datacenter = $self->_get_datacenter_view() || return; + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + # Attempt to copy the file + notify($ERRORS{'DEBUG'}, 0, "attempting to copy file on VM host $vmhost_hostname: '$source_file_path' --> '$destination_file_path'"); + eval { $file_manager->CopyDatastoreFile(sourceName => $source_file_path, + sourceDatacenter => $datacenter, + destinationName => $destination_file_path, + destinationDatacenter => $datacenter, + force => 0); + }; + + # Check if an error occurred + if ($@) { + if ($@->isa('SoapFault') && ref($@->detail) eq 'FileNotFound') { + notify($ERRORS{'WARNING'}, 0, "source file does not exist on VM host $vmhost_hostname: '$source_file_path'"); + return 0; + } + elsif ($@->isa('SoapFault') && ref($@->detail) eq 'FileAlreadyExists') { + notify($ERRORS{'WARNING'}, 0, "destination file already exists on VM host $vmhost_hostname: '$destination_file_path'"); + return 0; + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to copy file on VM host $vmhost_hostname: '$source_file_path' --> '$destination_file_path'\nerror:\n$@"); + return; + } + } + + notify($ERRORS{'OK'}, 0, "copied file on VM host $vmhost_hostname: '$source_file_path' --> '$destination_file_path'"); + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 copy_file_to + + Parameters : $source_file_path, $destination_file_path + Returns : boolean + Description : Copies a file from the management node to a datastore on the VM + host. The complete source and destination file paths must be + specified. Wildcards may not be used. + +=cut + +sub copy_file_to { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the source and destination arguments + my $source_file_path = normalize_file_path(shift) || return; + my $destination_file_path = $self->_get_datastore_path(shift) || return; + + # Make sure the source file exists on the management node + if (!-f $source_file_path) { + notify($ERRORS{'WARNING'}, 0, "source file does not exist on the management node: '$source_file_path'"); + return; + } + + # Make sure the destination directory path exists + my $destination_directory_path = $self->_get_parent_directory_datastore_path($destination_file_path) || return; + $self->create_directory($destination_directory_path) || return; + sleep 2; + + # Get the VM host name + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + my $datacenter_name = $self->_get_datacenter_name(); + + # Get the destination datastore name and relative datastore path + my $destination_datastore_name = $self->_get_datastore_name($destination_file_path); + my $destination_relative_datastore_path = $self->_get_relative_datastore_path($destination_file_path); + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + # Attempt to copy the file -- make a few attempts since this can sometimes fail + return $self->code_loop_timeout( + sub{ + my $response; + eval { $response = VIExt::http_put_file("folder" , $source_file_path, $destination_relative_datastore_path, $destination_datastore_name, $datacenter_name); }; + if ($response->is_success) { + notify($ERRORS{'DEBUG'}, 0, "copied file from management node to VM host: '$source_file_path' --> $vmhost_hostname:'[$destination_datastore_name] $destination_relative_datastore_path'"); + return 1; + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to copy file from management node to VM host: '$source_file_path' --> $vmhost_hostname($datacenter_name):'$destination_file_path'\nerror: " . $response->message); + return; + } + }, [], "attempting to copy file from management node to VM host: '$source_file_path' --> $vmhost_hostname:'[$destination_datastore_name] $destination_relative_datastore_path'", 50, 5); + +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 copy_file_from + + Parameters : $source_file_path, $destination_file_path + Returns : boolean + Description : Copies file from a datastore on the VM host to the management + node. The complete source and destination file paths must be + specified. Wildcards may not be used. + +=cut + +sub copy_file_from { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the source and destination arguments + my $source_file_path = $self->_get_datastore_path(shift) || return; + my $destination_file_path = normalize_file_path(shift) || return; + + # Get the destination directory path and make sure the directory exists + my $destination_directory_path = $self->_get_parent_directory_normal_path($destination_file_path) || return; + if (!-d $destination_directory_path) { + # Attempt to create the directory + my $command = "mkdir -p -v \"$destination_directory_path\" 2>&1 && ls -1d \"$destination_directory_path\""; + my ($exit_status, $output) = run_command($command, 1); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to run command to create directory on management node: '$destination_directory_path'\ncommand: '$command'"); + return; + } + elsif (grep(/created directory/i, @$output)) { + notify($ERRORS{'OK'}, 0, "created directory on management node: '$destination_directory_path'"); + } + elsif (grep(/mkdir: /i, @$output)) { + notify($ERRORS{'WARNING'}, 0, "error occurred attempting to create directory on management node: '$destination_directory_path':\ncommand: '$command'\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); + return; + } + elsif (grep(/^$destination_directory_path/, @$output)) { + notify($ERRORS{'OK'}, 0, "directory already exists on management node: '$destination_directory_path'"); + } + else { + notify($ERRORS{'WARNING'}, 0, "unexpected output returned from command to create directory on management node: '$destination_directory_path':\ncommand: '$command'\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); + return; + } + } + + # Get the VM host name + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + my $datacenter_name = $self->_get_datacenter_name(); + + # Get the source datastore name + my $source_datastore_name = $self->_get_datastore_name($source_file_path) || return; + + # Get the source file relative datastore path + my $source_file_relative_datastore_path = $self->_get_relative_datastore_path($source_file_path) || return; + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + # Attempt to copy the file -- make a few attempts since this can sometimes fail + return $self->code_loop_timeout( + sub{ + my $response; + eval { $response = VIExt::http_get_file("folder", $source_file_relative_datastore_path, $source_datastore_name, $datacenter_name, $destination_file_path); }; + if ($response->is_success) { + notify($ERRORS{'DEBUG'}, 0, "copied file from VM host to management node: $vmhost_hostname:'[$source_datastore_name] $source_file_relative_datastore_path' --> '$destination_file_path'"); + return 1; + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to copy file from VM host to management node: $vmhost_hostname:'[$source_datastore_name] $source_file_relative_datastore_path' --> '$destination_file_path'\nerror: " . $response->message); + return; + } + }, [], "attempting to copy file from VM host to management node: $vmhost_hostname:'[$source_datastore_name] $source_file_relative_datastore_path' --> '$destination_file_path'", 50, 5); + +} + + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_file_contents + + Parameters : $file_path + Returns : array + Description : Returns an array containing the contents of the file on the VM + host specified by the file path argument. Each array element + contains a line in the file. + +=cut + +sub get_file_contents { + 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; + } + + # TODO: add file size check before retrieving file in case file is huge + + # Get the source and destination arguments + my ($path) = shift; + if (!$path) { + notify($ERRORS{'WARNING'}, 0, "file path argument was not specified"); + return; + } + + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + # Create a temp directory to store the file and construct the temp file path + # The temp directory is automatically deleted then this variable goes out of scope + my $temp_directory_path = tempdir( CLEANUP => 1 ); + my $source_file_name = $self->_get_file_name($path); + my $temp_file_path = "$temp_directory_path/$source_file_name"; + + $self->copy_file_from($path, $temp_file_path) || return; + + # Run cat to retrieve the contents of the file + my $command = "cat \"$temp_file_path\""; + my ($exit_status, $output) = VCL::utils::run_command($command, 1); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to run command to read file: '$temp_file_path'\ncommand: '$command'"); + return; + } + elsif (grep(/^cat: /, @$output)) { + notify($ERRORS{'WARNING'}, 0, "failed to read contents of file: '$temp_file_path', exit status: $exit_status, output:\n" . join("\n", @$output)); + return; + } + else { + notify($ERRORS{'DEBUG'}, 0, "retrieved " . scalar(@$output) . " lines from file: '$temp_file_path'"); + } + + # Output lines contain trailing newlines, remove them + @$output = map { chomp; $_; } @$output; + return @$output; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 move_file + + Parameters : $source_path, $destination_path + Returns : boolean + Description : Moves or renames a file from one datastore location on the VM + host to another datastore location on the VM host. Wildcards may + not be used in the file path argument. + +=cut + +sub move_file { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get and check the file path arguments + my $source_file_path = $self->_get_datastore_path(shift) || return; + my $destination_file_path = $self->_get_datastore_path(shift) || return; + + # Get the VM host name + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + # Get the destination directory path and create the directory if it doesn't exit + my $destination_directory_path = $self->_get_parent_directory_datastore_path($destination_file_path) || return; + $self->create_directory($destination_directory_path) || return; + + # Get a fileManager and Datacenter object + my $file_manager = $self->_get_file_manager_view() || return; + my $datacenter = $self->_get_datacenter_view() || return; + + # Override the die handler + local $SIG{__DIE__} = sub{}; + + # Attempt to copy the file + notify($ERRORS{'DEBUG'}, 0, "attempting to move file on VM host $vmhost_hostname: '$source_file_path' --> '$destination_file_path'"); + eval { $file_manager->MoveDatastoreFile(sourceName => $source_file_path, + sourceDatacenter => $datacenter, + destinationName => $destination_file_path, + destinationDatacenter => $datacenter + ); + }; + + if ($@) { + if ($@->isa('SoapFault') && ref($@->detail) eq 'FileNotFound') { + notify($ERRORS{'WARNING'}, 0, "source file does not exist on VM host $vmhost_hostname: '$source_file_path'"); + return 0; + } + elsif ($@->isa('SoapFault') && ref($@->detail) eq 'FileAlreadyExists') { + notify($ERRORS{'WARNING'}, 0, "destination file already exists on VM host $vmhost_hostname: '$destination_file_path'"); + return 0; + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to move file on VM host $vmhost_hostname: '$source_file_path' --> '$destination_file_path', error:\n$@"); + return; + } + } + + notify($ERRORS{'OK'}, 0, "moved file on VM host $vmhost_hostname: '$source_file_path' --> '$destination_file_path'"); + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 file_exists + + Parameters : $file_path + Returns : boolean + Description : Determines if a file exists on a datastore on the VM host. + +=cut + +sub file_exists { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get and check the file path argument + my $file_path = $self->_get_datastore_path(shift) || return; + + # Check if the path argument is the root of a datastore + if ($file_path =~ /^\[(.+)\]$/) { + my $datastore_name = $1; + (my @datastore_names = $self->_get_datastore_names()) || return; + + if (grep(/^$datastore_name$/, @datastore_names)) { + notify($ERRORS{'DEBUG'}, 0, "file (datastore root) exists: $file_path"); + return 1; + } + else { + notify($ERRORS{'DEBUG'}, 0, "file (datastore root) does not exist: $file_path, datastores on VM host:\n" . join("\n", @datastore_names)); + return 0; + } + } + + # Take the path apart, get the filename and parent directory path + my $base_directory_path = $self->_get_parent_directory_datastore_path($file_path) || return; + my $file_name = $self->_get_file_name($file_path) || return; + + my $result = $self->find_files($base_directory_path, $file_name); + if ($result) { + notify($ERRORS{'DEBUG'}, 0, "file exists: $file_path"); + return 1; + } + elsif (defined($result)) { + notify($ERRORS{'DEBUG'}, 0, "file does not exist: $file_path"); + return 0; + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to determine if file exists: $file_path"); + return; + } +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_file_size + + Parameters : $file_path + Returns : integer + Description : Determines the size of a file of a datastore in bytes. Wildcards + may be used in the file path argument. The total size of all + files found will be returned. Subdirectories are not searched. + +=cut + +sub get_file_size { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get and check the file path argument + my $file_path_argument = shift; + if (!$file_path_argument) { + notify($ERRORS{'WARNING'}, 0, "file path argument was not specified"); + return; + } + + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + # Get the file info + my $file_info = $self->_get_file_info($file_path_argument); + if (!defined($file_info)) { + notify($ERRORS{'WARNING'}, 0, "unable to get file size, failed to get file info for: $file_path_argument"); + return; + } + + # Make sure the file info is not null or else an error occurred + if (!$file_info) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve info for file on $vmhost_hostname: $file_path_argument"); + return; + } + + # Check if there are any keys in the file info hash - no keys indicates no files were found + if (!keys(%{$file_info})) { + notify($ERRORS{'DEBUG'}, 0, "unable to determine size of file on $vmhost_hostname because it does not exist: $file_path_argument"); + return; + } + + # Loop through the files, add their sizes to the total + my $total_size_bytes = 0; + for my $file_path (keys(%{$file_info})) { + my $file_size_bytes = $file_info->{$file_path}{fileSize}; + notify($ERRORS{'DEBUG'}, 0, "size of '$file_path': " . format_number($file_size_bytes) . " bytes"); + $total_size_bytes += $file_size_bytes; + } + + my $total_size_bytes_string = format_number($total_size_bytes); + my $total_size_mb_string = format_number(($total_size_bytes / 1024 / 1024), 2); + my $total_size_gb_string = format_number(($total_size_bytes / 1024 / 1024 /1024), 2); + + notify($ERRORS{'DEBUG'}, 0, "total file size of '$file_path_argument': $total_size_bytes_string bytes ($total_size_mb_string MB, $total_size_gb_string GB)"); + return $total_size_bytes; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 find_files + + Parameters : $base_directory_path, $search_pattern, $search_subdirectories (optional) + Returns : array + Description : Finds files in a datastore on the VM host stored under the base + directory path argument. The search pattern may contain + wildcards. Subdirectories will be searched if the 3rd argument is + not supplied. + +=cut + +sub find_files { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the arguments + my ($base_directory_path, $search_pattern, $search_subdirectories) = @_; + if (!$base_directory_path || !$search_pattern) { + notify($ERRORS{'WARNING'}, 0, "base directory path and search pattern arguments were not specified"); + return; + } + + $search_subdirectories = 1 if !defined($search_subdirectories); + + $base_directory_path = $self->_get_normal_path($base_directory_path) || return; + + # Get the file info + my $file_info = $self->_get_file_info("$base_directory_path/$search_pattern", $search_subdirectories); + if (!defined($file_info)) { + notify($ERRORS{'WARNING'}, 0, "unable to find files, failed to get file info for: $base_directory_path/$search_pattern"); + return; + } + + # Loop through the keys of the file info hash + my @file_paths; + for my $file_path (keys(%{$file_info})) { + # Add the file path to the return array + push @file_paths, $self->_get_normal_path($file_path); + + # vmdk files will have a diskExtents key + # The extents must be added to the return array + if (defined($file_info->{$file_path}->{diskExtents})) { + for my $disk_extent (@{$file_info->{$file_path}->{diskExtents}}) { + # Convert the datastore file paths to normal file paths + $disk_extent = $self->_get_normal_path($disk_extent); + push @file_paths, $self->_get_normal_path($disk_extent); + } + } + } + + @file_paths = sort @file_paths; + notify($ERRORS{'DEBUG'}, 0, "matching file count: " . scalar(@file_paths)); + return @file_paths; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_total_space + + Parameters : $path + Returns : integer + Description : Returns the total size (in bytes) of the volume specified by the + argument. + +=cut + +sub get_total_space { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the path argument + my $path = shift; + if (!$path) { + notify($ERRORS{'WARNING'}, 0, "path argument was not specified"); + return; + } + + # Get the datastore name + my $datastore_name = $self->_get_datastore_name($path) || return; + + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + # Get the datastore info hash + my $datastore_info = $self->_get_datastore_info() || return; + + my $total_bytes = $datastore_info->{$datastore_name}{capacity}; + if (!defined($total_bytes)) { + notify($ERRORS{'WARNING'}, 0, "datastore $datastore_name capacity key does not exist in datastore info:\n" . format_data($datastore_info)); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "capacity of $datastore_name datastore on $vmhost_hostname: " . get_file_size_info_string($total_bytes)); + return $total_bytes; +} + + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_available_space + + Parameters : $path + Returns : integer + Description : Returns the bytes available in the path specified by the + argument. + +=cut + +sub get_available_space { + my $self = shift; + if (ref($self) !~ /module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the path argument + my $path = shift; + if (!$path) { + notify($ERRORS{'WARNING'}, 0, "path argument was not specified"); + return; + } + + # Get the datastore name + my $datastore_name = $self->_get_datastore_name($path) || return; + + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + # Get the datastore info hash + my $datastore_info = $self->_get_datastore_info(1) || return; + + my $available_bytes = $datastore_info->{$datastore_name}{freeSpace}; + if (!defined($available_bytes)) { + notify($ERRORS{'WARNING'}, 0, "datastore $datastore_name freeSpace key does not exist in datastore info:\n" . format_data($datastore_info)); + return; + } + + notify($ERRORS{'DEBUG'}, 0, "space available in $datastore_name datastore on $vmhost_hostname: " . get_file_size_info_string($available_bytes)); + return $available_bytes; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_cpu_core_count + + Parameters : none + Returns : integer + Description : Retrieves the quantitiy of CPU cores the VM host has. + +=cut + +sub get_cpu_core_count { + my $self = shift; + if (ref($self) !~ /module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + return $self->{cpu_core_count} if $self->{cpu_core_count}; + + my $cpu_core_count; + if (my $host_system_view = $self->_get_host_system_view()) { + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + $cpu_core_count = $host_system_view->{hardware}->{cpuInfo}->{numCpuCores}; + notify($ERRORS{'DEBUG'}, 0, "retrieved CPU core count for VM host '$vmhost_hostname': $cpu_core_count"); + } + elsif (my $cluster = $self->_get_cluster_view()) { + # Try to get CPU core count of cluster if cluster is being used + my $cluster_name = $cluster->{name}; + $cpu_core_count = $cluster->{summary}->{numCpuCores}; + notify($ERRORS{'DEBUG'}, 0, "retrieved CPU core count for '$cluster_name' cluster: $cpu_core_count"); + + } + else { + notify($ERRORS{'WARNING'}, 0, "unable to determine CPU core count of VM host"); + return; + } + + $self->{cpu_core_count} = $cpu_core_count; + return $self->{cpu_core_count}; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_cpu_speed + + Parameters : none + Returns : integer + Description : Retrieves the speed of the VM host's CPUs in MHz. + +=cut + +sub get_cpu_speed { + my $self = shift; + if (ref($self) !~ /module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + return $self->{cpu_speed} if $self->{cpu_speed}; + + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + # Try to get CPU speed of resource pool + if (my $resource_pool = $self->_get_resource_pool_view()) { + my $resource_pool_name = $resource_pool->{name}; + + my $mhz = $resource_pool->{runtime}{cpu}{maxUsage}; + + # maxUsage reports sum of all CPUs - divide by core count + # This isn't exact - will be lower than acutal clock rate of CPUs in host + if (my $cpu_core_count = $self->get_cpu_core_count()) { + $mhz = int($mhz / $cpu_core_count); + } + + $self->{cpu_speed} = $mhz; + notify($ERRORS{'DEBUG'}, 0, "retrieved total CPU speed of '$resource_pool_name' resource pool: $self->{cpu_speed} MHz"); + return $self->{cpu_speed}; + } + else { + notify($ERRORS{'WARNING'}, 0, "unable to determine CPU speed of VM host, resource pool view object could not be retrieved"); + return; + } +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_total_memory + + Parameters : none + Returns : integer + Description : Retrieves the VM host's total memory capacity in MB. + +=cut + +sub get_total_memory { + my $self = shift; + if (ref($self) !~ /module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + return $self->{total_memory} if $self->{total_memory}; + + my $vmhost_hostname = $self->data->get_vmhost_hostname(); + + + # Try to get total memory of resource pool + if (my $resource_pool = $self->_get_resource_pool_view()) { + my $resource_pool_name = $resource_pool->{name}; + + my $memory_bytes = $resource_pool->{runtime}{memory}{maxUsage}; + my $memory_mb = int($memory_bytes / 1024 / 1024); + + $self->{total_memory} = $memory_mb; + notify($ERRORS{'DEBUG'}, 0, "retrieved total memory of '$resource_pool_name' resource pool: $self->{total_memory} MB"); + return $self->{total_memory}; + } + else { + notify($ERRORS{'WARNING'}, 0, "unable to determine total memory on VM host, resource pool view object could not be retrieved"); + return; + } +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_license_info + + Parameters : none + Returns : hash reference
[... 1341 lines stripped ...]
