| #!/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::VIM_SSH; |
| |
| =head1 SYNOPSIS |
| |
| my $vmhost_datastructure = $self->get_vmhost_datastructure(); |
| my $VIM_SSH = VCL::Module::Provisioning::VMware::VIM_SSH->new({data_structure => $vmhost_datastructure}); |
| my @registered_vms = $VIM_SSH->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. |
| |
| =cut |
| |
| ############################################################################### |
| package VCL::Module::Provisioning::VMware::VIM_SSH; |
| |
| # 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.5.1'; |
| |
| # Specify the version of Perl to use |
| use 5.008000; |
| |
| use strict; |
| use warnings; |
| use diagnostics; |
| |
| use English '-no_match_vars'; |
| use VCL::utils; |
| |
| ############################################################################### |
| |
| =head1 PRIVATE 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; |
| } |
| |
| my $args = shift; |
| |
| if (!defined($self->{vmhost_os})) { |
| # |
| if (!defined $args->{vmhost_os}) { |
| notify($ERRORS{'WARNING'}, 0, "required 'vmhost_os' argument was not passed"); |
| return; |
| } |
| |
| # |
| if (ref $args->{vmhost_os} !~ /VCL::Module::OS/) { |
| notify($ERRORS{'CRITICAL'}, 0, "'vmhost_os' argument passed is not a reference to a VCL::Module::OS object, type: " . ref($args->{vmhost_os})); |
| return; |
| } |
| |
| # |
| $self->{vmhost_os} = $args->{vmhost_os}; |
| } |
| |
| if (!$self->vmhost_os) { |
| return; |
| } |
| |
| my @required_vmhost_os_subroutines = ( |
| 'execute', |
| ); |
| |
| for my $required_vmhost_os_subroutine (@required_vmhost_os_subroutines) { |
| if (!$self->vmhost_os->can($required_vmhost_os_subroutine)) { |
| notify($ERRORS{'WARNING'}, 0, "required VM host OS subroutine is not implemented: $required_vmhost_os_subroutine"); |
| return; |
| } |
| } |
| |
| # Determine which VIM executable is installed on the VM host |
| my $command = 'vim-cmd ; vmware-vim-cmd'; |
| my ($exit_status, $output) = $self->vmhost_os->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'OK'}, 0, "VIM executable is not available on the VM host"); |
| return; |
| } |
| elsif (!grep(/vmsvc/, @$output)) { |
| # String 'vmsvc' does not exist in the output, neither of the commands worked |
| notify($ERRORS{'DEBUG'}, 0, "VIM executable is not available on the VM host, output:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif (grep(/: vim-cmd:.*not found/i, @$output)) { |
| # Output contains the line: 'vim-cmd: command not found' |
| $self->{vim_cmd} = 'vmware-vim-cmd'; |
| } |
| else { |
| # Output contains the line: 'vmware-vim-cmd: command not found' |
| # Note: VMware ESX 4.1 has BOTH vim-cmd and vmware-vim-cmd |
| $self->{vim_cmd} = 'vim-cmd'; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "VIM executable available on VM host: $self->{vim_cmd}"); |
| |
| notify($ERRORS{'DEBUG'}, 0, ref($self) . " object initialized"); |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _run_vim_cmd |
| |
| Parameters : $vim_arguments, $timeout_seconds (optional), $attempt_limit (optional) |
| Returns : array ($exit_status, $output) |
| Description : Runs vim-cmd command on the VMware host. This was designed to |
| allow it to handle most of the error checking. |
| |
| By default, 5 attempts are made. |
| |
| If the exit status of the vim-cmd command is 0 after any attempt, |
| $exit_status and $output are returned to the calling subroutine. |
| |
| If the exit $attempt_limit > 1 and the status is not 0 after all |
| attempts are made, undefined is returned. This allows the calling |
| subroutine to simply check if result is true if it does not care |
| about the output. |
| |
| There is a special condition if the $attempt_limit is 1 and the |
| exit status is not 0. $exit_status and $output are always |
| returned so calling subroutine can handle the logic. |
| |
| =cut |
| |
| sub _run_vim_cmd { |
| 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 $vim_arguments = shift; |
| if (!$vim_arguments) { |
| notify($ERRORS{'WARNING'}, 0, "VIM command arguments were not specified"); |
| return; |
| } |
| |
| my $timeout_seconds = shift || 60; |
| my $attempt_limit = shift || 5; |
| |
| my $request_state_name = $self->data->get_request_state_name(); |
| my $vmhost_computer_name = $self->vmhost_os->data->get_computer_short_name(); |
| |
| my $command = "$self->{vim_cmd} $vim_arguments"; |
| |
| my $attempt = 0; |
| my $wait_seconds = 5; |
| |
| my $connection_reset_errors = 0; |
| |
| ATTEMPT: while ($attempt++ < $attempt_limit) { |
| |
| my $semaphore; |
| if ($attempt > 1) { |
| # Wait before making next attempt |
| notify($ERRORS{'OK'}, 0, "sleeping $wait_seconds seconds before making attempt $attempt/$attempt_limit"); |
| sleep_uninterrupted($wait_seconds); |
| $semaphore = $self->get_semaphore($vmhost_computer_name, 120, 1) || next ATTEMPT; |
| } |
| |
| # The following error is somewhat common if several processes are adding/removing VMs at the same time: |
| # (vmodl.fault.ManagedObjectNotFound) { |
| # dynamicType = <unset>, |
| # faultCause = (vmodl.MethodFault) null, |
| # obj = 'vim.VirtualMachine:672', |
| # msg = "The object has already been deleted or has not been completely created", |
| # } |
| |
| # Keep a count of the number of times vim-cmd is executed for the entire vcld state process |
| # This will be used to improve performance by reducing the number of calls necessary |
| $self->{vim_cmd_calls}++; |
| #notify($ERRORS{'DEBUG'}, 0, "vim-cmd call count: $self->{vim_cmd_calls} ($vim_arguments)"); |
| |
| #my $register_semaphore; |
| #if ($command =~ /(getallvms|register)/) { |
| # $register_semaphore = $self->get_semaphore($vmhost_computer_name, 120, 1); |
| # if (!$register_semaphore) { |
| # next ATTEMPT; |
| # } |
| #} |
| |
| my ($exit_status, $output) = $self->vmhost_os->execute({ |
| 'command' => $command, |
| 'display_output' => 0, |
| 'timeout_seconds' => $timeout_seconds, |
| #'max_attempts' => 1 |
| }); |
| |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "attempt $attempt/$attempt_limit: failed to run VIM command on VM host $vmhost_computer_name: $command"); |
| } |
| elsif (grep(/already been deleted/i, @$output)) { |
| notify($ERRORS{'OK'}, 0, "attempt $attempt/$attempt_limit: fault occurred attempting to run command on VM host $vmhost_computer_name: $command, output:\n" . join("\n", @$output)); |
| } |
| elsif (grep(/(Failed to login|connection reset|SSL Exception)/i, @$output)) { |
| # Try to catch these errors: |
| # Failed to login: Connection reset by peer |
| # Failed to login: SSL Exception: The SSL handshake timed out local: 127.0.0.1:52713 peer: 127.0.0.1:443. |
| $connection_reset_errors++; |
| notify($ERRORS{'OK'}, 0, "attempt $attempt/$attempt_limit: connection reset while attempting to run command on VM host $vmhost_computer_name: $command, output:\n" . join("\n", @$output)); |
| |
| # If 2 connection reset errors occured, attempt to run services.sh restart |
| if ($connection_reset_errors == 2) { |
| if ($self->{services_restarted}) { |
| notify($ERRORS{'WARNING'}, 0, "encountered $connection_reset_errors connection reset errors on VM host $vmhost_computer_name, not calling 'services.sh restart', it was already attempted"); |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "calling 'services.sh restart', encountered $connection_reset_errors connection reset errors on VM host $vmhost_computer_name"); |
| $self->_services_restart(); |
| $self->{services_restarted} = 1; |
| next ATTEMPT; |
| } |
| } |
| elsif ($connection_reset_errors > 2) { |
| notify($ERRORS{'WARNING'}, 0, "encountered $connection_reset_errors connection reset errors on VM host $vmhost_computer_name"); |
| } |
| else { |
| next ATTEMPT; |
| } |
| |
| # Problem probably won't correct itself |
| # If request state is 'inuse', set the reservation.lastcheck value to 20 minutes before request.end |
| # This avoids 'inuse' processes from being created over and over again which will fail |
| if ($request_state_name eq 'inuse') { |
| my $reservation_id = $self->data->get_reservation_id(); |
| my $request_end_time_epoch = convert_to_epoch_seconds($self->data->get_request_end_time()); |
| my $current_time_epoch = time; |
| my $reservation_lastcheck_epoch = ($request_end_time_epoch-(20*60)); |
| set_reservation_lastcheck($reservation_lastcheck_epoch, $reservation_id); |
| } |
| return; |
| } |
| elsif (grep(/^(vim-cmd:|Killed|terminate called|Aborted|what\()/i, @$output)) { |
| # terminate called after throwing an instance of 'std::bad_alloc' |
| # what(): std::bad_alloc |
| # Aborted |
| notify($ERRORS{'WARNING'}, 0, "attempt $attempt/$attempt_limit: failed to execute command on VM host $vmhost_computer_name: $command, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| next ATTEMPT; |
| } |
| elsif ($exit_status != 0) { |
| if ($attempt_limit == 1) { |
| notify($ERRORS{'DEBUG'}, 0, "command failed on VM host $vmhost_computer_name, not making another attempt because attempt limit argument is set to $attempt_limit, error checking will be done by calling subroutine, command: $command, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| return ($exit_status, $output); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "attempt $attempt/$attempt_limit: command failed on VM host $vmhost_computer_name: $command, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| next ATTEMPT; |
| } |
| } |
| else { |
| # VIM command command was executed |
| if ($attempt > 1) { |
| notify($ERRORS{'DEBUG'}, 0, "attempt $attempt/$attempt_limit: executed command on VM host $vmhost_computer_name: $command, exit status: $exit_status"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "executed command on VM host $vmhost_computer_name: $command, exit status: $exit_status"); |
| } |
| return ($exit_status, $output); |
| } |
| } |
| |
| notify($ERRORS{'WARNING'}, 0, "failed to run VIM command on VM host $vmhost_computer_name: '$command', made $attempt_limit attempts"); |
| return; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _services_restart |
| |
| Parameters : none |
| Returns : boolean |
| Description : Calls 'services.sh restart' on the VM host. This may resolve |
| problems where the host is not responding due to a problem with |
| one or more services. This should rarely be called. |
| |
| =cut |
| |
| sub _services_restart { |
| 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 $vmhost_computer_name = $self->vmhost_os->data->get_computer_short_name(); |
| |
| my $semaphore = $self->get_semaphore("$vmhost_computer_name-vmware_services_restart", 0); |
| if (!$semaphore) { |
| notify($ERRORS{'OK'}, 0, "unable to obtain semaphore, another process is likely restarting services on $vmhost_computer_name, sleeping for 30 seconds and then proceeding"); |
| sleep_uninterrupted(30); |
| return 1; |
| } |
| |
| my $check_services = { |
| 'hostd-worker' => '/var/run/vmware/vmware-hostd.PID', |
| 'sfcb-vmware_bas' => '/var/run/vmware/vicimprovider.PID', |
| 'vmkdevmgr' => '/var/run/vmware/vmkdevmgr.pid', |
| 'vmkeventd' => '/var/run/vmware/vmkeventd.pid', |
| 'vmsyslogd' => '/var/run/vmware/vmsyslogd.pid', |
| 'rhttpproxy-work' => '/var/run/vmware/vmware-rhttpproxy.PID', |
| 'vpxa-worker' => '/var/run/vmware/vmware-vpxa.PID', |
| }; |
| |
| # Check if the PID files for the following services are correct |
| for my $service_name (keys %$check_services) { |
| my $pid_file_path = $check_services->{$service_name}; |
| $self->_check_service_pid($service_name, $pid_file_path); |
| } |
| |
| my $services_command = "services.sh restart"; |
| notify($ERRORS{'DEBUG'}, 0, "restarting VMware services on $vmhost_computer_name"); |
| my ($services_exit_status, $services_output) = $self->vmhost_os->execute($services_command, 0, 120); |
| if (!defined($services_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command on VM host $vmhost_computer_name: $services_command"); |
| return; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "executed command to restart VMware services on $vmhost_computer_name, command: '$services_command', output:\n" . join("\n", @$services_output)); |
| } |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _check_service_pid |
| |
| Parameters : $process_name, $pid_file_path |
| Returns : boolean |
| Description : Checks if the PID stored in the PID file matches the parent PID |
| of the running service process. Problems occur if the file does |
| not match the running process PID. Most often, vim-cmd commands |
| fail with an error such as: |
| Connect to localhost failed: Connection failure |
| |
| The PID file is updated with the correct PID if the PID file |
| contents cannot be retrieved and parsed or if the PID stored in |
| the file does not match the running process. |
| |
| =cut |
| |
| sub _check_service_pid { |
| 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 ($process_name, $pid_file_path) = @_; |
| if (!defined($process_name) || !defined($pid_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "process name and PID file path arguments were not supplied"); |
| return; |
| } |
| |
| my $vmhost_computer_name = $self->vmhost_os->data->get_computer_short_name(); |
| |
| # Retrieve the running PID |
| my $running_pid; |
| my $ps_command = "ps |grep $process_name |awk '{print \$2}'"; |
| my ($ps_exit_status, $ps_output) = $self->vmhost_os->execute($ps_command); |
| if (!defined($ps_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to determine main $process_name PID on $vmhost_computer_name"); |
| } |
| else { |
| ($running_pid) = "@$ps_output" =~ /(\d+)/g; |
| if ($running_pid && $running_pid > 1) { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved parent $process_name PID: $running_pid"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "parent $process_name process is not running"); |
| } |
| } |
| |
| # Check if the .pid file exists |
| my $pid_file_exists = $self->vmhost_os->file_exists($pid_file_path); |
| if (!$running_pid) { |
| if ($pid_file_exists) { |
| notify($ERRORS{'DEBUG'}, 0, "running $process_name process was not detected but PID file exists: $pid_file_path, deleting file"); |
| if ($self->vmhost_os->delete_file($pid_file_path)) { |
| notify($ERRORS{'DEBUG'}, 0, "deleted file on $vmhost_computer_name: $pid_file_path"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete file on $vmhost_computer_name: $pid_file_path"); |
| } |
| return 1; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "running $process_name process was not detected and PID file does not exist: $pid_file_path"); |
| return 1; |
| } |
| } |
| else { |
| if ($pid_file_exists) { |
| # Retrieve the PID stored in the PID file |
| my @pid_file_contents = $self->vmhost_os->get_file_contents($pid_file_path); |
| if (@pid_file_contents) { |
| my ($file_pid) = "@pid_file_contents" =~ /(\d+)/g; |
| if ($file_pid) { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved PID stored in $pid_file_path: $file_pid"); |
| if ($file_pid eq $running_pid) { |
| notify($ERRORS{'OK'}, 0, "PID in $pid_file_path ($file_pid) matches PID of parent $process_name process ($running_pid), update not necessary"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "PID in $pid_file_path ($file_pid) does not match PID of parent $process_name process ($running_pid), updating $pid_file_path to contain $running_pid"); |
| } |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine PID stored in $pid_file_path, contents:\n" . join("\n", @pid_file_contents)); |
| } |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve contents of $pid_file_path"); |
| } |
| } |
| |
| # Update the PID file with the correct PID |
| my $echo_command = "echo -n $running_pid > $pid_file_path"; |
| my ($echo_exit_status, $echo_output) = $self->vmhost_os->execute($echo_command); |
| if (!defined($echo_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to update $pid_file_path on $vmhost_computer_name"); |
| return; |
| } |
| elsif (grep(/(ash:|echo:)/, @$echo_output)) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred updating $pid_file_path on $vmhost_computer_name, command: '$echo_command', output:\n" . joini("\n", @$echo_output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "updated $pid_file_path on $vmhost_computer_name to contain the correct PID: $running_pid"); |
| } |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _get_vm_list |
| |
| Parameters : none |
| Returns : hash |
| Description : Returns an hash with keys containing the IDs of the VMs running |
| on the VM host. The values are the vmx file paths. |
| |
| =cut |
| |
| sub _get_vm_list { |
| 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 $vim_cmd_arguments = "vmsvc/getallvms"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # Check the vim-cmd output |
| # Format of the output from "vim-cmd vmsvc/getallvms": |
| # Vmid Name File Guest OS Version Annotation |
| # 496 vm-ark-mcnc-9 (nonpersistent: vmwarewinxp-base234-v12) [nfs-datastore] vm-ark-mcnc-9_234-v12/vm-ark-mcnc-9_234-v12.vmx winXPProGuest vmx-04 |
| # 512 vm-ark-mcnc-10 (nonpersistent: vmwarelinux-centosbase1617-v1) [nfs-datastore] vm-ark-mcnc-10_1617-v1/vm-ark-mcnc-10_1617-v1.vmx otherLinuxGuest vmx-04 |
| if (!grep(/Vmid\s+Name/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine VM IDs, unexpected output returned, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| my %vms; |
| for my $line (@$output) { |
| my ($vm_id, $vmx_file_path) = $line =~ /^(\d+).*(\[.+\.vmx)/; |
| |
| # Skip lines that don't begin with a number |
| next if !defined($vm_id); |
| |
| # Make sure the vmx file path was parsed |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine vmx file path, VIM command arguments: '$vim_cmd_arguments', output line: $line"); |
| return; |
| } |
| |
| # Get the normal path |
| my $vmx_normal_path = $self->_get_normal_path($vmx_file_path); |
| if (!$vmx_normal_path) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine normal path: $vmx_file_path"); |
| #return; |
| } |
| |
| $vms{$vm_id} = $vmx_normal_path; |
| } |
| |
| #notify($ERRORS{'DEBUG'}, 0, "registered VMs IDs found: " . keys(%vms)); |
| return \%vms; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _get_vm_id |
| |
| Parameters : $vmx_file_path |
| Returns : integer |
| Description : |
| |
| =cut |
| |
| sub _get_vm_id { |
| 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 $vmx_file_path = shift; |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not specified"); |
| return; |
| } |
| |
| return $self->{vm_id}{$vmx_file_path} if $self->{vm_id}{$vmx_file_path}; |
| |
| # Get the VM IDs and vmx paths |
| my $vm_list = $self->_get_vm_list(); |
| if (!defined($vm_list)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine VM ID, failed to retrieve list of registered VMs and their IDs"); |
| return; |
| } |
| |
| for my $vm_id (keys %$vm_list) { |
| if ($vm_list->{$vm_id} && $vmx_file_path eq $vm_list->{$vm_id}) { |
| $self->{vm_id}{$vmx_file_path} = $vm_id; |
| return $vm_id; |
| } |
| } |
| |
| notify($ERRORS{'WARNING'}, 0, "unable to determine VM ID, vmx file is not registered: $vmx_file_path, registered VMs:\n" . format_data($vm_list)); |
| return; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _get_vm_summary |
| |
| Parameters : $vm_id |
| Returns : string |
| Description : Runs "vim-cmd vmsvc/get.summary <VM ID>" to retrive a summary |
| of the configuration of a VM. |
| |
| =cut |
| |
| sub _get_vm_summary { |
| 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 file path argument |
| my $vmx_file_path = shift || $self->get_vmx_file_path(); |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument could not be determined"); |
| return; |
| } |
| |
| my $vm_id = $self->_get_vm_id($vmx_file_path) || return; |
| |
| my $vim_cmd_arguments = "vmsvc/get.summary $vm_id"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # The output should look like this: |
| # Listsummary: |
| # (vim.vm.Summary) { |
| # dynamicType = <unset>, |
| # vm = 'vim.VirtualMachine:496', |
| # runtime = (vim.vm.RuntimeInfo) { |
| # dynamicType = <unset>, |
| # host = 'vim.HostSystem:ha-host', |
| # connectionState = "connected", |
| # powerState = "poweredOn", |
| # faultToleranceState = "notConfigured", |
| # toolsInstallerMounted = false, |
| # suspendTime = <unset>, |
| # bootTime = "2010-06-08T14:26:48.658743Z", |
| # suspendInterval = 0, |
| # question = (vim.vm.QuestionInfo) null, |
| # memoryOverhead = 119189504, |
| # maxCpuUsage = 2000, |
| # maxMemoryUsage = 1024, |
| # numMksConnections = 0, |
| # recordReplayState = "inactive", |
| # cleanPowerOff = <unset>, |
| # needSecondaryReason = <unset>, |
| # }, |
| # guest = (vim.vm.Summary.GuestSummary) { |
| # dynamicType = <unset>, |
| # guestId = "winXPProGuest", |
| # guestFullName = "Microsoft Windows XP Professional (32-bit)", |
| # toolsStatus = "toolsOld", |
| # toolsVersionStatus = "guestToolsNeedUpgrade", |
| # toolsRunningStatus = "guestToolsRunning", |
| # hostName = "APACHE-44896D77.dcs.mcnc.org", |
| # ipAddress = "152.46.16.235", |
| # }, |
| # config = (vim.vm.Summary.ConfigSummary) { |
| # dynamicType = <unset>, |
| # name = "vm-ark-mcnc-9 (nonpersistent: vmwarewinxp-base234-v12)", |
| # template = false, |
| # vmPathName = "[nfs-datastore] vm-ark-mcnc-9_234-v12/vm-ark-mcnc-9_234-v12.vmx", |
| # memorySizeMB = 1024, |
| # cpuReservation = <unset>, |
| # memoryReservation = <unset>, |
| # numCpu = 1, |
| # numEthernetCards = 2, |
| # numVirtualDisks = 1, |
| # uuid = "564d36cf-6988-c91d-0f5f-a62628d46553", |
| # instanceUuid = "", |
| # guestId = "winXPProGuest", |
| # guestFullName = "Microsoft Windows XP Professional (32-bit)", |
| # annotation = "", |
| # product = (vim.vApp.ProductInfo) null, |
| # installBootRequired = <unset>, |
| # ftInfo = (vim.vm.FaultToleranceConfigInfo) null, |
| # }, |
| # storage = (vim.vm.Summary.StorageSummary) { |
| # dynamicType = <unset>, |
| # committed = 4408509391, |
| # uncommitted = 11697668096, |
| # unshared = 4408509391, |
| # timestamp = "2010-06-08T14:26:30.312473Z", |
| # }, |
| # quickStats = (vim.vm.Summary.QuickStats) { |
| # dynamicType = <unset>, |
| # overallCpuUsage = 20, |
| # overallCpuDemand = <unset>, |
| # guestMemoryUsage = 40, |
| # hostMemoryUsage = 652, |
| # guestHeartbeatStatus = "yellow", |
| # distributedCpuEntitlement = <unset>, |
| # distributedMemoryEntitlement = <unset>, |
| # staticCpuEntitlement = <unset>, |
| # staticMemoryEntitlement = <unset>, |
| # privateMemory = <unset>, |
| # sharedMemory = <unset>, |
| # swappedMemory = <unset>, |
| # balloonedMemory = <unset>, |
| # consumedOverheadMemory = <unset>, |
| # ftLogBandwidth = <unset>, |
| # ftSecondaryLatency = <unset>, |
| # ftLatencyStatus = <unset>, |
| # }, |
| # overallStatus = "green", |
| # } |
| if (!grep(/vim\.vm\.Summary/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve VM summary, unexpected output returned, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| my $vm_summary_info = $self->_parse_vim_cmd_output($output); |
| if (defined($vm_summary_info->{'vim.vm.Summary'})) { |
| return $vm_summary_info->{'vim.vm.Summary'}; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve summary of VM: $vmx_file_path, parsed output does not contain a 'vim.vm.Summary' key:\n" . format_data($vm_summary_info)); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _get_datastore_info |
| |
| Parameters : none |
| Returns : hash reference |
| Description : |
| |
| =cut |
| |
| sub _get_datastore_info { |
| 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 previously retrieved datastore name array if it is defined in this object |
| if ($self->{datastore}) { |
| return $self->{datastore}; |
| } |
| |
| my $vmhost_hostname = $self->data->get_vmhost_hostname(); |
| |
| my $vim_cmd_arguments = "hostsvc/datastore/listsummary"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # The output should look like this: |
| # (vim.Datastore.Summary) [ |
| # (vim.Datastore.Summary) { |
| # dynamicType = <unset>, |
| # datastore = 'vim.Datastore:4bcf0efe-c426acc4-c7e1-001a644d1cc0', |
| # name = "local-datastore", |
| # url = "/vmfs/volumes/4bcf0efe-c426acc4-c7e1-001a644d1cc0", |
| # capacity = 31138512896, |
| # freeSpace = 26277314560, |
| # uncommitted = 0, |
| # accessible = true, |
| # multipleHostAccess = <unset>, |
| # type = "VMFS", |
| # }, |
| # (vim.Datastore.Summary) { |
| # dynamicType = <unset>, |
| # datastore = 'vim.Datastore:10.25.0.245:/vmfs/volumes/nfs-datastore', |
| # name = "nfs-datastore", |
| # url = "/vmfs/volumes/95e378c2-863dd2b4", |
| # capacity = 975027175424, |
| # freeSpace = 108854874112, |
| # uncommitted = 0, |
| # accessible = true, |
| # multipleHostAccess = <unset>, |
| # type = "NFS", |
| # }, |
| # ] |
| if (!grep(/vim\.Datastore\.Summary/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine datastore names, unexpected output returned, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| my $datastore_info; |
| |
| # Split the output into sections for each datastore |
| my @output_sections = split(/vim\.Datastore\.Summary/i, join("\n", @$output)); |
| |
| for my $output_section (@output_sections) { |
| my ($datastore_name) = $output_section =~ /name\s*=\s*"(.+)"/; |
| next if (!defined($datastore_name)); |
| |
| for my $line (split(/[\r\n]+/, $output_section)) { |
| # Skip lines which don't contain a '=' |
| next if $line !~ /=/; |
| |
| # Parse the line |
| my ($parameter, $value) = $line =~ /^\s*(\w+)\s*=[\s"']*([^"',]+)/g; |
| if (defined($parameter) && defined($value)) { |
| $datastore_info->{$datastore_name}{$parameter} = $value if ($parameter ne 'name'); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to parse parameter and value from line: '$line'"); |
| } |
| } |
| |
| # Check if the accessible value was retrieved and is not false |
| my $datastore_accessible = $datastore_info->{$datastore_name}{accessible}; |
| if (!$datastore_accessible || $datastore_accessible =~ /false/i) { |
| notify($ERRORS{'DEBUG'}, 0, "datastore '$datastore_name' is mounted on $vmhost_hostname but not accessible"); |
| delete $datastore_info->{$datastore_name}; |
| next; |
| } |
| |
| # Add a 'normal_path' key to the hash based on the datastore url |
| my $datastore_url = $datastore_info->{$datastore_name}{url}; |
| if (!defined($datastore_url)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine datastore url from 'vim-cmd $vim_cmd_arguments' output section, datastore name: $datastore_name:\n$output_section"); |
| delete $datastore_info->{$datastore_name}; |
| next; |
| } |
| |
| my $datastore_normal_path; |
| if ($datastore_url =~ /^\/vmfs\/volumes/i) { |
| $datastore_normal_path = "/vmfs/volumes/$datastore_name"; |
| } |
| else { |
| $datastore_normal_path = $datastore_url; |
| } |
| $datastore_info->{$datastore_name}{normal_path} = $datastore_normal_path; |
| } |
| |
| return $datastore_info; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _get_task_ids |
| |
| Parameters : $vmx_file_path, $task_type |
| Returns : array |
| Description : Returns an array containing the task IDs recently executed on |
| the VM indicated by the $vm_id argument. The task type argument |
| must be specified. Example task type values: |
| powerOn |
| powerOff |
| registerVm |
| unregisterVm |
| |
| =cut |
| |
| sub _get_task_ids { |
| 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 file path argument |
| my $vmx_file_path = shift || $self->get_vmx_file_path(); |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument could not be determined"); |
| return; |
| } |
| |
| # Get the task type argument |
| my $task_type = shift; |
| |
| my $vm_id = $self->_get_vm_id($vmx_file_path) || return; |
| |
| my $vim_cmd_arguments = "vmsvc/get.tasklist $vm_id"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # Expected output: |
| # (ManagedObjectReference) [ |
| # 'vim.Task:haTask-512-vim.VirtualMachine.powerOn-2826', |
| # 'vim.Task:haTask-512-vim.VirtualMachine.powerOn-2843', |
| # 'vim.Task:haTask-512-vim.VirtualMachine.powerOn-2856' |
| # ] |
| |
| # Expected output if there are no recent tasks: |
| # (ManagedObjectReference) [] |
| |
| if (!grep(/ManagedObjectReference/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to retrieve task list, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif (grep(/\(ManagedObjectReference\)\s*\[\]/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "there are no recent tasks for VM $vm_id"); |
| return (); |
| } |
| |
| #notify($ERRORS{'DEBUG'}, 0, "task list output:\n" . join("\n", @$output)); |
| |
| # Reverse the output array so the newest tasks are listed first |
| my @reversed_output = reverse(@$output); |
| |
| #notify($ERRORS{'DEBUG'}, 0, "reversed task list output:\n" . join("\n", @reversed_output)); |
| |
| #my (@task_ids) = grep(/haTask-$vm_id-.+$task_type-/, @reversed_output); |
| my @task_ids; |
| if ($task_type) { |
| @task_ids = map { /(haTask-$vm_id-.+$task_type-\d+)/ } @reversed_output; |
| } |
| else { |
| @task_ids = map { /(haTask-$vm_id-.+-\d+)/ } @reversed_output; |
| $task_type = 'all'; |
| } |
| |
| # Check if a matching task was found |
| if (!@task_ids) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine task IDs from output for VM $vm_id, task type: $task_type, output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| #notify($ERRORS{'DEBUG'}, 0, "retrieved task IDs for VM $vm_id, task type: $task_type:\n" . join("\n", @task_ids)); |
| return @task_ids; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _get_task_info |
| |
| Parameters : $task_id |
| Returns : hash reference |
| Description : |
| |
| =cut |
| |
| sub _get_task_info { |
| 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 task ID path argument |
| my (@task_ids) = @_; |
| @task_ids = $self->_get_task_ids() if (!@task_ids); |
| |
| my $task_info = {}; |
| |
| for my $task_id (@task_ids) { |
| my $vim_cmd_arguments = "vimsvc/task_info $task_id"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| |
| # Expected output: |
| # (vim.TaskInfo) { |
| # dynamicType = <unset>, |
| # key = "haTask-496-vim.VirtualMachine.powerOn-3072", |
| # task = 'vim.Task:haTask-496-vim.VirtualMachine.powerOn-3072', |
| # description = (vmodl.LocalizableMessage) null, |
| # name = "vim.VirtualMachine.powerOn", |
| # descriptionId = "VirtualMachine.powerOn", |
| # entity = 'vim.VirtualMachine:496', |
| # entityName = "vm-ark-mcnc-9 (nonpersistent: vmwarewinxp-base234-v12)", |
| # state = "error", |
| # cancelled = false, |
| # cancelable = false, |
| # error = (vmodl.fault.RequestCanceled) { |
| # dynamicType = <unset>, |
| # faultCause = (vmodl.MethodFault) null, |
| # msg = "The task was canceled by a user.", |
| # }, |
| # result = <unset>, |
| # progress = 100, |
| # reason = (vim.TaskReasonUser) { |
| # dynamicType = <unset>, |
| # userName = "root", |
| # }, |
| # queueTime = "2010-06-30T08:48:44.187347Z", |
| # startTime = "2010-06-30T08:48:44.187347Z", |
| # completeTime = "2010-06-30T08:49:26.381383Z", |
| # eventChainId = 3072, |
| # changeTag = <unset>, |
| # parentTaskKey = <unset>, |
| # rootTaskKey = <unset>, |
| # } |
| |
| # Expected output if the task is not found: |
| # (vmodl.fault.ManagedObjectNotFound) { |
| # dynamicType = <unset>, |
| # faultCause = (vmodl.MethodFault) null, |
| # obj = 'vim.Task:haTask-496-vim.VirtualMachine.powerOn-3072x', |
| # msg = "The object has already been deleted or has not been completely created", |
| # } |
| |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to retrieve info for task ID: $task_id"); |
| next; |
| } |
| elsif (grep(/ManagedObjectNotFound/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "task was not found, task ID: $task_id, output:\n" . join("\n", @$output)); |
| next; |
| } |
| elsif (!grep(/vim.TaskInfo/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to retrieve task list, VIM command arguments: '$vim_cmd_arguments' output:\n" . join("\n", @$output)); |
| next; |
| } |
| else { |
| #notify($ERRORS{'DEBUG'}, 0, "retrieved info for task $task_id"); |
| $task_info->{$task_id} = join("\n", @$output); |
| } |
| } |
| |
| return $task_info; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _wait_for_task |
| |
| Parameters : $task_id, $timeout_seconds (optional) |
| Returns : boolean |
| Description : Waits for the vim task to complete. Returns true if the task |
| completes successfully. |
| |
| =cut |
| |
| sub _wait_for_task { |
| 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 task ID path argument |
| my $task_id = shift; |
| if (!$task_id) { |
| notify($ERRORS{'WARNING'}, 0, "task ID argument was not supplied"); |
| return; |
| } |
| |
| my $timeout_seconds = shift || 30; |
| |
| my $start_time = time(); |
| |
| while (time() - $start_time < $timeout_seconds) { |
| notify($ERRORS{'DEBUG'}, 0, "checking status of task: $task_id"); |
| |
| # Get the task info |
| my $task_info = $self->_get_task_info($task_id); |
| my $task_info_output = $task_info->{$task_id}; |
| if (!$task_info || !$task_info_output) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if task $task_id has completed, task info could not be retrieved"); |
| return; |
| } |
| |
| # Parse the output to get the task state and progress |
| my ($task_state) = $task_info_output =~ /state\s*=\s*"([^"]+)/is; |
| if (!$task_state) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine task state from task info output:\n$task_info_output"); |
| return; |
| } |
| |
| my ($error_message) = $task_info_output =~ /msg\s*=\s*"([^"]+)/; |
| $error_message = $task_info_output if !$error_message; |
| |
| my ($progress)= $task_info_output =~ /progress\s*=\s*(\d+)/; |
| $progress = 'unknown' if !defined($progress); |
| |
| if ($task_state =~ /success/) { |
| notify($ERRORS{'DEBUG'}, 0, "task completed successfully: $task_id"); |
| return 1; |
| } |
| elsif ($task_state =~ /error|cancelled/) { |
| |
| # Check if the task failed with the message: 'Operation failed since another task is in progress.' |
| if ($error_message =~ /another task is in progress/i) { |
| # Retrieve info for all of the VMs recent tasks |
| my $task_info_all = $self->_get_task_info(); |
| notify($ERRORS{'WARNING'}, 0, "task $task_id did not complete successfully, state: $task_state, error message: $error_message, task info:\n" . format_data($task_info_all)); |
| } |
| elsif ($error_message =~ /state of the virtual machine has not changed since the last snapshot/i) { |
| # Snapshot may fail if VM is suspended and snapshot was already taken after suspension, message will be: |
| # message = "An error occurred while taking a snapshot: The state of the virtual machine has not changed since the last snapshot operation." |
| notify($ERRORS{'DEBUG'}, 0, "snapshot task is not necessary: $task_id, message: $error_message"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "task $task_id did not complete successfully, state: $task_state, error message: $error_message"); |
| } |
| return; |
| } |
| elsif ($task_state =~ /running/) { |
| notify($ERRORS{'DEBUG'}, 0, "task state: $task_state, progress: $progress, sleeping for 3 seconds before checking task state again"); |
| sleep 3; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "task state: $task_state, progress: $progress, sleeping for 3 seconds before checking task state again, output:\n$task_info_output"); |
| sleep 3; |
| } |
| } |
| |
| notify($ERRORS{'WARNING'}, 0, "timeout was reached: $timeout_seconds seconds, task never completed"); |
| return; |
| } |
| |
| ############################################################################### |
| |
| =head1 API OBJECT METHODS |
| |
| =cut |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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; |
| } |
| |
| # Get the VM IDs |
| my $vm_list = $self->_get_vm_list(); |
| if (!defined($vm_list)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve registered VMs, failed to retrieve list of registered VMs and their IDs"); |
| return; |
| } |
| |
| # Get the vmx path values for each VM |
| my @vmx_paths = sort { lc($a) cmp lc($b) } values(%$vm_list); |
| |
| notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@vmx_paths) . " registered VMs"); |
| return @vmx_paths; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_vm_power_state |
| |
| Parameters : $vmx_file_path |
| Returns : string |
| Description : Returns a string containing the power state of the VM indicated |
| by the vmx file path argument. The string returned may be 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 file path argument |
| my $vmx_file_path = shift; |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not supplied"); |
| return; |
| } |
| |
| my $vm_id = $self->_get_vm_id($vmx_file_path) || return; |
| |
| my $vim_cmd_arguments = "vmsvc/power.getstate $vm_id"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # The output should look like this: |
| # Retrieved runtime info |
| # Powered on |
| |
| # Retrieved runtime info |
| # Powered off |
| |
| # Retrieved runtime info |
| # Suspended |
| |
| notify($ERRORS{'DEBUG'}, 0, "$vim_cmd_arguments:\n" . join("\n", @$output)); |
| |
| if (grep(/powered on/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "VM is powered on: $vmx_file_path"); |
| return 'on'; |
| } |
| elsif (grep(/powered off/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "VM is powered off: $vmx_file_path"); |
| return 'off'; |
| } |
| elsif (grep(/suspended/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "VM is suspended: $vmx_file_path"); |
| return 'suspended'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to determine power state of $vmx_file_path, VIM command arguments: '$vim_cmd_arguments' output:\n" . join("\n", @$output)); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 vm_power_on |
| |
| Parameters : $vmx_file_path |
| Returns : boolean |
| Description : Powers on the VM indicated by the vmx file path argument. |
| |
| =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 file path argument |
| my ($vmx_file_path, $is_retry_attempt) = @_; |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not supplied"); |
| return; |
| } |
| |
| # Get the VM ID |
| my $vm_id = $self->_get_vm_id($vmx_file_path); |
| if (!defined($vm_id)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to power on VM because VM ID could not be determined"); |
| return; |
| } |
| |
| my $vim_cmd_arguments = "vmsvc/power.on $vm_id"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments, 360, 1); |
| return if !$output; |
| |
| # Expected output if the VM was not previously powered on: |
| # Powering on VM: |
| |
| # Expected output if the VM was previously powered on: |
| |
| # Old versions of ESXi? (unsure about when the output changed) |
| # Powering on VM: |
| # (vim.fault.InvalidPowerState) { |
| # dynamicType = <unset>, |
| # faultCause = (vmodl.MethodFault) null, |
| # requestedState = "poweredOn", |
| # existingState = "poweredOn", |
| # msg = "The attempted operation cannot be performed in the current state (Powered On).", |
| # } |
| |
| # ESXi 6.0, 6.5: |
| # Powering on VM: |
| # Power on failed |
| |
| if (grep(/existingState = "poweredOn"/i, @$output)) { |
| notify($ERRORS{'OK'}, 0, "VM is already powered on: $vmx_file_path"); |
| return 1; |
| } |
| elsif (!grep(/Powering on VM/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to power on VM $vmx_file_path, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif (grep(/failed/i, @$output)) { |
| # Power on failed but no indication that VM is already powered on from the output, check the power state |
| # Command will occasionally incorrectly report that it failed but the VM is actually powered on |
| my $power_state = $self->get_vm_power_state($vmx_file_path); |
| if ($power_state && $power_state =~ /on/i) { |
| notify($ERRORS{'OK'}, 0, "power on failed because VM is already powered on: $vmx_file_path"); |
| return 1; |
| } |
| elsif (!$is_retry_attempt) { |
| # Make one more attempt, pass it the $is_retry_attempt argument to avoid an endless loop |
| return $self->vm_power_on($vmx_file_path, 1); |
| } |
| } |
| |
| # Get the task ID |
| my @task_ids = $self->_get_task_ids($vmx_file_path, 'powerOn'); |
| if (!@task_ids) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve the ID of the task created to power on the VM"); |
| return; |
| } |
| |
| # Wait for the task to complete |
| if ($self->_wait_for_task($task_ids[0])) { |
| notify($ERRORS{'OK'}, 0, "powered on VM: $vmx_file_path"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to power on VM: $vmx_file_path, the vim power on task did not complete successfully, vim-cmd $vim_cmd_arguments output:\n" . join("\n", @$output)); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 vm_power_off |
| |
| Parameters : $vmx_file_path |
| Returns : boolean |
| Description : Powers off the VM indicated by the vmx file path argument. |
| |
| =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 file path argument |
| my $vmx_file_path = shift; |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not supplied"); |
| return; |
| } |
| |
| # Check if the VM is already powered off |
| my $vm_power_state = $self->get_vm_power_state($vmx_file_path); |
| if ($vm_power_state && $vm_power_state =~ /off/i) { |
| notify($ERRORS{'DEBUG'}, 0, "VM is already powered off: $vmx_file_path"); |
| return 1; |
| } |
| |
| # Get the VM ID |
| my $vm_id = $self->_get_vm_id($vmx_file_path); |
| if (!defined($vm_id)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to power off VM because VM ID could not be determined"); |
| return; |
| } |
| |
| my $vim_cmd_arguments = "vmsvc/power.off $vm_id"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # Expected output if the VM was not previously powered off: |
| # Powering off VM: |
| |
| # Expected output if the VM was previously powered off: |
| # Powering off VM: |
| # (vim.fault.InvalidPowerState) { |
| # dynamicType = <unset>, |
| # faultCause = (vmodl.MethodFault) null, |
| # requestedState = "poweredOff", |
| # existingState = "poweredOff", |
| # msg = "The attempted operation cannot be performed in the current state (Powered Off).", |
| # } |
| |
| # ESXi 6.0, 6.5: |
| # Powering off VM: |
| # Power off failed |
| |
| if (grep(/existingState = "poweredOff"/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "VM is already powered off: $vmx_file_path"); |
| return 1; |
| } |
| elsif (!grep(/Powering off VM/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to power off VM $vmx_file_path, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Get the task ID |
| my @task_ids = $self->_get_task_ids($vmx_file_path, 'powerOff'); |
| if (!@task_ids) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve the ID of the task created to power off the VM"); |
| return; |
| } |
| |
| # Wait for the task to complete |
| if ($self->_wait_for_task($task_ids[0])) { |
| notify($ERRORS{'OK'}, 0, "powered off VM: $vmx_file_path"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to power off VM: $vmx_file_path, the vim power off task did not complete successfully, vim-cmd $vim_cmd_arguments output:\n" . join("\n", @$output)); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 vm_suspend |
| |
| Parameters : $vmx_file_path |
| Returns : boolean |
| Description : Suspends the VM indicated by the vmx file path argument. |
| |
| =cut |
| |
| sub vm_suspend { |
| 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 file path argument |
| my $vmx_file_path = shift; |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not supplied"); |
| return; |
| } |
| |
| # Check if the VM is already powered off |
| my $vm_power_state = $self->get_vm_power_state($vmx_file_path); |
| if ($vm_power_state) { |
| if ($vm_power_state =~ /off/i) { |
| notify($ERRORS{'DEBUG'}, 0, "VM is already powered off: $vmx_file_path"); |
| return 1; |
| } |
| elsif ($vm_power_state =~ /suspend/i) { |
| notify($ERRORS{'DEBUG'}, 0, "VM is already suspended: $vmx_file_path"); |
| return 1; |
| } |
| } |
| |
| # Get the VM ID |
| my $vm_id = $self->_get_vm_id($vmx_file_path); |
| if (!defined($vm_id)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to power off VM because VM ID could not be determined"); |
| return; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "suspending VM: $vmx_file_path ($vm_id)"); |
| my $start_time = time; |
| my $vim_cmd_arguments = "vmsvc/power.suspend $vm_id"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments, 400); |
| return if !$output; |
| |
| # Expected output if the VM was not previously suspended: |
| # Suspending VM: |
| |
| # Expected output if the VM was previously suspended or powered off: |
| # Suspending VM: |
| # Suspend failed |
| |
| if (!grep(/Suspending VM/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to suspend VM $vmx_file_path, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Get the task ID |
| my @task_ids = $self->_get_task_ids($vmx_file_path, 'suspend'); |
| if (!@task_ids) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve the ID of the task created to suspend the VM"); |
| return; |
| } |
| |
| # Wait for the task to complete |
| if ($self->_wait_for_task($task_ids[0])) { |
| my $duration = (time - $start_time); |
| notify($ERRORS{'OK'}, 0, "suspended VM: $vmx_file_path, took $duration seconds"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to suspend VM: $vmx_file_path, the vim power off task did not complete successfully, vim-cmd $vim_cmd_arguments output:\n" . join("\n", @$output)); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 vm_register |
| |
| Parameters : $vmx_file_path |
| Returns : boolean |
| Description : Registers the VM indicated by the vmx file path argument. |
| |
| =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 file path argument |
| my $vmx_file_path = shift; |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not supplied"); |
| return; |
| } |
| |
| # Check if the VM is already registered |
| if ($self->is_vm_registered($vmx_file_path)) { |
| notify($ERRORS{'OK'}, 0, "VM is already registered: $vmx_file_path"); |
| return 1; |
| } |
| |
| $vmx_file_path =~ s/\\* /\\ /g; |
| my $vim_cmd_arguments = "solo/registervm \"$vmx_file_path\""; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments, 60, 1); |
| return if !$output; |
| |
| # Note: registervm does not produce any output if it was successful |
| |
| # Expected output if the vmx file path does not exist: |
| # (vim.fault.NotFound) { |
| # dynamicType = <unset>, |
| # faultCause = (vmodl.MethodFault) null, |
| # msg = "The object or item referred to could not be found.", |
| # } |
| |
| if (grep(/vim.fault.NotFound/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to register VM, vmx file was not found: $vmx_file_path, output:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif (grep(/vim.fault.AlreadyExists/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to register VM on the 1st attempt, an existing invalid VM using the same vmx file path may already already be registered, output:\n" . join("\n", @$output)); |
| |
| # If an "invalid" VM exists using the same .vmx path, this fault will be generated: |
| # (vim.fault.AlreadyExists) { |
| # faultCause = (vmodl.MethodFault) null, |
| # name = "51", |
| # msg = "The specified key, name, or identifier '51' already exists." |
| my ($vm_id) = join("\n", @$output) =~ /name\s*=\s*"(\d+)"/; |
| if ($vm_id) { |
| if ($self->vm_unregister($vm_id)) { |
| notify($ERRORS{'DEBUG'}, 0, "unregistered existing invalid VM $vm_id, making another attempt to register VM: $vmx_file_path"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to register VM: $vmx_file_path, unable to unregister existing invalid VM $vm_id"); |
| return; |
| } |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to register VM: $vmx_file_path, ID of existing invalid VM could not be determined, was expecting a line beginning with 'name = \"<ID>\"' in output:\n" . join("\n", @$output)); |
| return; |
| } |
| } |
| |
| if (grep(/fault/i, @$output)) { |
| # Only made 1 attempt so far, try again if fault occurred, allow 4 more attempts |
| ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments, 60, 4); |
| return if !$output; |
| } |
| |
| if (grep(/fault/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to register VM: $vmx_file_path, vim-cmd $vim_cmd_arguments output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Check to make sure the VM is registered |
| if ($self->is_vm_registered($vmx_file_path)) { |
| notify($ERRORS{'OK'}, 0, "registered VM: '$vmx_file_path'"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to register VM: '$vmx_file_path'"); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 vm_unregister |
| |
| Parameters : $vm_identifier |
| Returns : boolean |
| Description : Unregisters the VM indicated by the argument which may either be |
| the .vmx file path or VM ID. |
| |
| =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; |
| } |
| |
| # Note: allow the VM ID to be passed in case the .vmx file path cannot be determined |
| # This allows an invalid VM with a missing .vmx file to be unregistered |
| |
| my $vm_identifier = shift; |
| if (!$vm_identifier) { |
| notify($ERRORS{'WARNING'}, 0, "VM identifier argument was not supplied"); |
| return; |
| } |
| |
| my $vm_id; |
| my $vmx_file_path; |
| if ($vm_identifier =~ /^\d+$/) { |
| $vm_id = $vm_identifier; |
| } |
| else { |
| # Argument should be the vmx file path |
| $vmx_file_path = $vm_identifier; |
| |
| # Check if the VM is not registered |
| if (!$self->is_vm_registered($vmx_file_path)) { |
| notify($ERRORS{'OK'}, 0, "VM not unregistered because it is not registered: $vmx_file_path"); |
| return 1; |
| } |
| |
| # Power off the VM if it is powered on or the unregister command will fail |
| my $vm_power_state = $self->get_vm_power_state($vmx_file_path); |
| if ($vm_power_state && $vm_power_state !~ /off/i) { |
| if (!$self->vm_power_off($vmx_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to unregister VM because it could not be powered off: $vmx_file_path"); |
| return; |
| } |
| } |
| |
| $vm_id = $self->_get_vm_id($vmx_file_path); |
| if (!defined($vm_id)) { |
| notify($ERRORS{'OK'}, 0, "unable to unregister VM because VM ID could not be determined for vmx path argument: $vmx_file_path"); |
| return; |
| } |
| } |
| |
| my $vim_cmd_arguments = "vmsvc/unregister $vm_id"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| |
| # Delete cached .vmx - VM ID mapping if previously retrieved |
| delete $self->{vm_id}{$vm_identifier}; |
| |
| return if !$output; |
| |
| # Expected output if the VM is not registered: |
| # (vim.fault.NotFound) { |
| # dynamicType = <unset>, |
| # faultCause = (vmodl.MethodFault) null, |
| # msg = "Unable to find a VM corresponding to "/vmfs/volumes/nfs-datastore/vm-ark-mcnc-9_234-v12/vm-ark-mcnc-9_234-v12.vmx"", |
| # } |
| |
| if (grep(/fault/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to unregister VM, VIM command arguments: '$vim_cmd_arguments'\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Check to make sure the VM is not registered |
| if ($vmx_file_path && $self->is_vm_registered($vmx_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to unregister VM: $vmx_file_path (ID: $vm_id), it still appears to be registered"); |
| return; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "unregistered VM: $vm_identifier"); |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_virtual_disk_type |
| |
| Parameters : $vmdk_file_path |
| Returns : |
| 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 = shift; |
| if (!$vmdk_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmdk file path argument was not supplied"); |
| return; |
| } |
| |
| my ($vmdk_directory_path, $vmdk_file_name) = $vmdk_file_path =~ /^(.+)\/([^\/]+\.vmdk)/; |
| if (!$vmdk_directory_path || !$vmdk_file_name) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine directory path and file name from vmdk file path: $vmdk_directory_path"); |
| return; |
| } |
| |
| my $vmdk_directory_datastore_path = $self->_get_datastore_path($vmdk_directory_path); |
| if (!$vmdk_directory_datastore_path) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine vmdk directory datastore path from vmdk directory path: $vmdk_directory_path"); |
| return; |
| } |
| |
| # The output of 'vim-cmd hostsvc/datastorebrowser/disksearch' differs for VMware Server 2.x and ESXi |
| # The value of 'thin' is not returned if disksearch is run under ESXi |
| my $vmware_product_name = $self->get_vmhost_product_name(); |
| my $vim_cmd_arguments; |
| if ($vmware_product_name =~ /esx/i) { |
| $vim_cmd_arguments = "hostsvc/datastorebrowser/search 0 \"$vmdk_directory_datastore_path\""; |
| } |
| else { |
| $vim_cmd_arguments = "hostsvc/datastorebrowser/disksearch \"$vmdk_directory_datastore_path\""; |
| } |
| |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # Expected output: |
| #(vim.host.DatastoreBrowser.SearchResults) { |
| # dynamicType = <unset>, |
| # datastore = 'vim.Datastore:10.10.14.20:/nfs-datastore1', |
| # folderPath = "[nfs-datastore1] vclv17-149_234-v14", |
| # file = (vim.host.DatastoreBrowser.FileInfo) [ |
| # (vim.host.DatastoreBrowser.VmDiskInfo) { |
| # dynamicType = <unset>, |
| # path = "vmwarewinxp-base234-v14.vmdk", |
| # fileSize = 5926510592, |
| # modification = "2010-11-24T17:06:44Z", |
| # owner = <unset>, |
| # diskType = "vim.vm.device.VirtualDisk.FlatVer2BackingInfo", |
| # capacityKb = 14680064, |
| # hardwareVersion = 4, |
| # controllerType = "vim.vm.device.VirtualIDEController", |
| # diskExtents = (string) [ |
| # "[nfs-datastore1] vclv17-149_234-v14/vmwarewinxp-base234-v14-flat.vmdk" |
| # ], |
| # thin = true, |
| # } |
| # ], |
| #} |
| |
| |
| my $output_string = join("\n", @$output); |
| my (@disk_info_sections) = split(/vim.host.DatastoreBrowser.VmDiskInfo/, $output_string); |
| |
| for my $disk_info (@disk_info_sections) { |
| my ($disk_path) = $disk_info =~ /\spath = "(.+)"/i; |
| |
| if (!$disk_path || $disk_path ne $vmdk_file_name) { |
| next; |
| } |
| |
| my ($disk_type) = $disk_info =~ /\sdiskType = "(.+)"/i; |
| if (!$disk_type) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine disk type, disk path: $disk_path, disk info section from vim-cmd $vim_cmd_arguments output:\n$disk_info"); |
| next; |
| } |
| |
| # Disk type format: vim.vm.device.VirtualDisk.FlatVer2BackingInfo |
| # Remove everything but "FlatVer2" |
| $disk_type =~ s/(^.*\.|BackingInfo$)//g; |
| |
| # Return 'thin' if thin is set to true |
| my ($thin) = $disk_info =~ /\sthin\s*=\s*(.+)/i; |
| if (defined($thin) && $thin =~ /true/) { |
| $disk_type = 'thin'; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "$disk_path disk type: $disk_type"); |
| return $disk_type; |
| } |
| |
| notify($ERRORS{'WARNING'}, 0, "unable to determine disk type for disk: $vmdk_file_path, vim-cmd $vim_cmd_arguments output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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. False is returned |
| if the controller type cannot be retrieved. A string is returned |
| containing one of the following values: |
| -IDE |
| -lsiLogic |
| -busLogic |
| |
| =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 = shift; |
| if (!$vmdk_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmdk file path argument was not supplied"); |
| return; |
| } |
| |
| my ($vmdk_directory_path, $vmdk_file_name) = $vmdk_file_path =~ /^(.+)\/([^\/]+\.vmdk)/; |
| if (!$vmdk_directory_path || !$vmdk_file_name) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine directory path and file name from vmdk file path: $vmdk_directory_path"); |
| return; |
| } |
| |
| my $vmdk_directory_datastore_path = $self->_get_datastore_path($vmdk_directory_path); |
| if (!$vmdk_directory_datastore_path) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine vmdk directory datastore path from vmdk directory path: $vmdk_directory_path"); |
| return; |
| } |
| |
| my $vim_cmd_arguments = "hostsvc/datastorebrowser/searchsubfolders 0 \"$vmdk_directory_datastore_path\""; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # Expected output: |
| # (vim.host.DatastoreBrowser.SearchResults) { |
| # dynamicType = <unset>, |
| # datastore = 'vim.Datastore:10.25.0.245:/vmfs/volumes/nfs-datastore', |
| # folderPath = "[nfs-datastore] vmwarewinxp-base234-v12", |
| # file = (vim.host.DatastoreBrowser.FileInfo) [ |
| # (vim.host.DatastoreBrowser.VmDiskInfo) { |
| # dynamicType = <unset>, |
| # path = "vmwarewinxp-base234-v12.vmdk", |
| # fileSize = 4774187008, |
| # modification = "2010-06-30T21:03:45Z", |
| # owner = <unset>, |
| # diskType = "vim.vm.device.VirtualDisk.SparseVer2BackingInfo", |
| # capacityKb = 14680064, |
| # hardwareVersion = 4, |
| # controllerType = "vim.vm.device.VirtualBusLogicController", |
| # diskExtents = (string) [ |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s001.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s002.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s003.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s004.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s005.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s006.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s007.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s008.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s009.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s010.vmdk" |
| # ], |
| # thin = false, |
| # }, |
| # (vim.host.DatastoreBrowser.VmDiskInfo) { |
| # dynamicType = <unset>, |
| # path = "esx_2gb_sparse.vmdk", |
| # fileSize = 4410286080, |
| # modification = "2010-07-01T18:38:04Z", |
| # owner = <unset>, |
| # diskType = "vim.vm.device.VirtualDisk.SparseVer2BackingInfo", |
| # capacityKb = 14680064, |
| # hardwareVersion = 7, |
| # controllerType = "vim.vm.device.VirtualIDEController", |
| # diskExtents = (string) [ |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s001.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s002.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s003.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s004.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s005.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s006.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s007.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s008.vmdk" |
| # ], |
| # thin = true, |
| # }, |
| # (vim.host.DatastoreBrowser.VmDiskInfo) { |
| # dynamicType = <unset>, |
| # path = "thin_vmwarewinxp-base234-v12.vmdk", |
| # fileSize = 4408459264, |
| # modification = "2010-06-30T20:53:51Z", |
| # owner = <unset>, |
| # diskType = "vim.vm.device.VirtualDisk.FlatVer2BackingInfo", |
| # capacityKb = 14680064, |
| # hardwareVersion = 4, |
| # controllerType = <unset>, |
| # diskExtents = (string) [ |
| # "[nfs-datastore] vmwarewinxp-base234-v12/thin_vmwarewinxp-base234-v12-flat.vmdk" |
| # ], |
| # thin = true, |
| # } |
| # ], |
| # } |
| |
| my $output_string = join("\n", @$output); |
| my (@disk_info_sections) = split(/vim.host.DatastoreBrowser.VmDiskInfo/, $output_string); |
| |
| for my $disk_info (@disk_info_sections) { |
| my ($disk_path) = $disk_info =~ /\spath = "(.+)"/i; |
| |
| if (!$disk_path) { |
| next; |
| } |
| elsif ($disk_path ne $vmdk_file_name) { |
| #notify($ERRORS{'DEBUG'}, 0, "ignoring disk because the file name does not match $vmdk_file_name: $disk_path"); |
| next; |
| } |
| |
| my ($controller_type) = $disk_info =~ /\scontrollerType\s*=\s*(.+)/i; |
| if (!$controller_type) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine disk controller type, disk path: $disk_path, disk info section from vim-cmd $vim_cmd_arguments output:\n$disk_info"); |
| next; |
| } |
| |
| if ($controller_type =~ /unset/i) { |
| notify($ERRORS{'DEBUG'}, 0, "disk controller type is not set in the vmdk file: $disk_path"); |
| return 0; |
| } |
| else { |
| # Extract just the controller type name from the value: vim.vm.device.VirtualIDEController --> IDE |
| $controller_type =~ s/(.*vim.vm.device.Virtual|Controller.*)//ig; |
| notify($ERRORS{'DEBUG'}, 0, "retrieved controller type for $disk_path: '$controller_type'"); |
| return $controller_type; |
| } |
| } |
| |
| notify($ERRORS{'WARNING'}, 0, "unable to determine disk controller type for disk: $vmdk_file_path, vim-cmd $vim_cmd_arguments output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_virtual_disk_hardware_version |
| |
| Parameters : $vmdk_file_path |
| Returns : integer |
| Description : Retrieves the hardware version configured for the virtual |
| disk specified by the vmdk file path argument. False is returned |
| if the hardware version cannot be retrieved. |
| |
| =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 = shift; |
| if (!$vmdk_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmdk file path argument was not supplied"); |
| return; |
| } |
| |
| my ($vmdk_directory_path, $vmdk_file_name) = $vmdk_file_path =~ /^(.+)\/([^\/]+\.vmdk)/; |
| if (!$vmdk_directory_path || !$vmdk_file_name) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine directory path and file name from vmdk file path: $vmdk_directory_path"); |
| return; |
| } |
| |
| my $vmdk_directory_datastore_path = $self->_get_datastore_path($vmdk_directory_path); |
| if (!$vmdk_directory_datastore_path) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine vmdk directory datastore path from vmdk directory path: $vmdk_directory_path"); |
| return; |
| } |
| |
| my $vim_cmd_arguments = "hostsvc/datastorebrowser/searchsubfolders 0 \"$vmdk_directory_datastore_path\""; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # Expected output: |
| # (vim.host.DatastoreBrowser.SearchResults) { |
| # dynamicType = <unset>, |
| # datastore = 'vim.Datastore:10.25.0.245:/vmfs/volumes/nfs-datastore', |
| # folderPath = "[nfs-datastore] vmwarewinxp-base234-v12", |
| # file = (vim.host.DatastoreBrowser.FileInfo) [ |
| # (vim.host.DatastoreBrowser.VmDiskInfo) { |
| # dynamicType = <unset>, |
| # path = "vmwarewinxp-base234-v12.vmdk", |
| # fileSize = 4774187008, |
| # modification = "2010-06-30T21:03:45Z", |
| # owner = <unset>, |
| # diskType = "vim.vm.device.VirtualDisk.SparseVer2BackingInfo", |
| # capacityKb = 14680064, |
| # hardwareVersion = 4, |
| # controllerType = "vim.vm.device.VirtualBusLogicController", |
| # diskExtents = (string) [ |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s001.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s002.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s003.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s004.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s005.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s006.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s007.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s008.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s009.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/vmwarewinxp-base234-v12-s010.vmdk" |
| # ], |
| # thin = false, |
| # }, |
| # (vim.host.DatastoreBrowser.VmDiskInfo) { |
| # dynamicType = <unset>, |
| # path = "esx_2gb_sparse.vmdk", |
| # fileSize = 4410286080, |
| # modification = "2010-07-01T18:38:04Z", |
| # owner = <unset>, |
| # diskType = "vim.vm.device.VirtualDisk.SparseVer2BackingInfo", |
| # capacityKb = 14680064, |
| # hardwareVersion = 7, |
| # controllerType = "vim.vm.device.VirtualIDEController", |
| # diskExtents = (string) [ |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s001.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s002.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s003.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s004.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s005.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s006.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s007.vmdk", |
| # "[nfs-datastore] vmwarewinxp-base234-v12/esx_2gb_sparse-s008.vmdk" |
| # ], |
| # thin = true, |
| # }, |
| # (vim.host.DatastoreBrowser.VmDiskInfo) { |
| # dynamicType = <unset>, |
| # path = "thin_vmwarewinxp-base234-v12.vmdk", |
| # fileSize = 4408459264, |
| # modification = "2010-06-30T20:53:51Z", |
| # owner = <unset>, |
| # diskType = "vim.vm.device.VirtualDisk.FlatVer2BackingInfo", |
| # capacityKb = 14680064, |
| # hardwareVersion = 4, |
| # controllerType = <unset>, |
| # diskExtents = (string) [ |
| # "[nfs-datastore] vmwarewinxp-base234-v12/thin_vmwarewinxp-base234-v12-flat.vmdk" |
| # ], |
| # thin = true, |
| # } |
| # ], |
| # } |
| |
| my $output_string = join("\n", @$output); |
| my (@disk_info_sections) = split(/vim.host.DatastoreBrowser.VmDiskInfo/, $output_string); |
| |
| for my $disk_info (@disk_info_sections) { |
| my ($disk_path) = $disk_info =~ /\spath = "(.+)"/i; |
| |
| if (!$disk_path) { |
| next; |
| } |
| elsif ($disk_path ne $vmdk_file_name) { |
| notify($ERRORS{'DEBUG'}, 0, "ignoring disk because the file name does not match $vmdk_file_name: $disk_path"); |
| next; |
| } |
| |
| my ($hardware_version) = $disk_info =~ /\shardwareVersion\s*=\s*(\d+)/ig; |
| if (!$hardware_version) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine disk hardware version, disk path: $disk_path, disk info section from vim-cmd $vim_cmd_arguments output:\n$disk_info"); |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved hardware version for $disk_path: '$hardware_version'"); |
| return $hardware_version; |
| } |
| } |
| |
| notify($ERRORS{'WARNING'}, 0, "unable to determine hardware version for disk: $vmdk_file_path, vim-cmd $vim_cmd_arguments output:\n" . join("\n", @$output)); |
| 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 $vim_cmd_arguments = "solo/environment"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # The output should contain a network section: |
| #network = (vim.vm.NetworkInfo) [ |
| # (vim.vm.NetworkInfo) { |
| # dynamicType = <unset>, |
| # name = "Private", |
| # network = (vim.Network.Summary) { |
| # dynamicType = <unset>, |
| # network = 'vim.Network:HaNetwork-Private', |
| # name = "Private", |
| # accessible = true, |
| # ipPoolName = "", |
| # }, |
| # }, |
| # (vim.vm.NetworkInfo) { |
| # dynamicType = <unset>, |
| # name = "Public", |
| # network = (vim.Network.Summary) { |
| # dynamicType = <unset>, |
| # network = 'vim.Network:HaNetwork-Public', |
| # name = "Public", |
| # accessible = true, |
| # ipPoolName = "", |
| # }, |
| # }, |
| #], |
| |
| # Convert the output line array to a string then split it by network sections |
| my ($network_info) = join("\n", @$output) =~ /(vim\.vm\.NetworkInfo[^\]]+)/; |
| if ($network_info) { |
| notify($ERRORS{'DEBUG'}, 0, "network info:\n$network_info"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve network info, vim-cmd arguments: '$vim_cmd_arguments', $exit_status: $exit_status, output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| my (@network_sections) = split(/vim.vm.NetworkInfo/, $network_info); |
| |
| # Extract the network names from the network sections |
| my @network_names; |
| for my $network_info (@network_sections) { |
| my ($network_name) = $network_info =~ /\sname = "(.+)"/i; |
| next if !$network_name; |
| push @network_names, $network_name; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "retrieved network names:\n" . join("\n", @network_names)); |
| return @network_names; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 create_snapshot |
| |
| Parameters : $vmx_file_path, $name (optional) |
| Returns : boolean |
| Description : Creates a snapshot of the VM. |
| |
| =cut |
| |
| sub create_snapshot { |
| 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 file path argument |
| my $vmx_file_path = shift; |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not supplied"); |
| return; |
| } |
| |
| my $snapshot_name = shift || ("VCL: " . convert_to_datetime()); |
| |
| # Get the VM ID |
| my $vm_id = $self->_get_vm_id($vmx_file_path); |
| if (!defined($vm_id)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to create snapshot because VM ID could not be determined"); |
| return; |
| } |
| |
| my $vim_cmd_arguments = "vmsvc/snapshot.create $vm_id '$snapshot_name'"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments, 60, 1); |
| return if !$output; |
| |
| notify($ERRORS{'DEBUG'}, 0, "create snapshot output:\n" . join("\n", @$output)); |
| |
| # IMPORTANT: Don't check for 'failed' in the output, it may contain failed but the snapshot is not necessary: |
| # Snapshot not taken since the state of the virtual machine has not changed since the last snapshot operation. |
| #if (grep(/failed|invalid/i, @$output)) { |
| # notify($ERRORS{'WARNING'}, 0, "failed to create snapshot of VM $vmx_file_path, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output)); |
| # return; |
| #} |
| |
| # Get the task ID |
| my @task_ids = $self->_get_task_ids($vmx_file_path, 'createSnapshot'); |
| if (!@task_ids) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve the ID of the task created to create snapshot"); |
| return; |
| } |
| |
| # Wait for the task to complete |
| if ($self->_wait_for_task($task_ids[0])) { |
| notify($ERRORS{'OK'}, 0, "created snapshot of VM: $vmx_file_path, snapshot name: $snapshot_name"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to create snapshot VM: $vmx_file_path, the vim task did not complete successfully, vim-cmd $vim_cmd_arguments output:\n" . join("\n", @$output)); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 remove_snapshots |
| |
| Parameters : $vmx_file_path |
| Returns : boolean |
| Description : Removes all snapshots for a VM. |
| |
| =cut |
| |
| sub remove_snapshots { |
| 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 file path argument |
| my $vmx_file_path = shift; |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not supplied"); |
| return; |
| } |
| |
| # Get the VM ID |
| my $vm_id = $self->_get_vm_id($vmx_file_path); |
| if (!defined($vm_id)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to create snapshot because VM ID could not be determined"); |
| return; |
| } |
| |
| my $vim_cmd_arguments = "vmsvc/snapshot.removeall $vm_id"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments, 7200); |
| return if !$output; |
| |
| notify($ERRORS{'DEBUG'}, 0, "remove snapshots output:\n" . join("\n", @$output)); |
| |
| if (grep(/failed|invalid/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to remove snapshots for VM $vmx_file_path, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Get the task ID |
| my @task_ids = $self->_get_task_ids($vmx_file_path, 'removeAllSnapshots'); |
| if (!@task_ids) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve the ID of the task created to remove snapshots"); |
| return; |
| } |
| |
| # Wait for the task to complete |
| if ($self->_wait_for_task($task_ids[0], 7200)) { |
| notify($ERRORS{'OK'}, 0, "removed snapshots for VM: $vmx_file_path"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to remove snapshots for VM: $vmx_file_path, the vim task did not complete successfully, vim-cmd $vim_cmd_arguments output:\n" . join("\n", @$output)); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 snapshot_exists |
| |
| Parameters : $vmx_file_path |
| Returns : boolean |
| Description : Determines if a snapshot exists for the VM. |
| |
| =cut |
| |
| sub snapshot_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 the vmx file path argument |
| my $vmx_file_path = shift; |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not supplied"); |
| return; |
| } |
| |
| # Get the VM ID |
| my $vm_id = $self->_get_vm_id($vmx_file_path); |
| if (!defined($vm_id)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if snapshot exists because VM ID could not be determined"); |
| return; |
| } |
| |
| my $vim_cmd_arguments = "vmsvc/snapshot.get $vm_id"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| notify($ERRORS{'DEBUG'}, 0, "snapshot.get output:\n" . join("\n", @$output)); |
| |
| if (grep(/failed|invalid/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine if snapshot exists for VM $vmx_file_path, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Expected output if shapshot exists: |
| # Get Snapshot: |
| # |-ROOT |
| # --Snapshot Name : 1311966951 |
| # --Snapshot Desciption : |
| # --Snapshot Created On : 7/29/2011 19:15:59 |
| # --Snapshot State : powered off |
| |
| # Expected output if snapshot does not exist: |
| # Get Snapshot: |
| |
| if (grep(/-ROOT/, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "snapshot exists for VM $vmx_file_path"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "snapshot does NOT exist for VM $vmx_file_path"); |
| return 0; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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) !~ /VCL::Module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $vmhost_hostname = $self->data->get_vmhost_hostname(); |
| |
| my $vim_cmd_arguments = "hostsvc/hosthardware"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # The CPU info should be contained in the output: |
| # cpuInfo = (vim.host.CpuInfo) { |
| # dynamicType = <unset>, |
| # numCpuPackages = 2, |
| # numCpuCores = 8, |
| # numCpuThreads = 8, |
| # hz = 2000070804, |
| # }, |
| |
| my ($cpu_cores_line) = grep(/^\s*numCpuCores\s*=/i, @$output); |
| if (!$cpu_cores_line) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine VM host $vmhost_hostname CPU core count, output does not contain a 'numCpuCores =' line:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif ($cpu_cores_line =~ /(\d+)/) { |
| my $cpu_core_count = $1; |
| notify($ERRORS{'DEBUG'}, 0, "retrieved VM host $vmhost_hostname CPU core count: $cpu_core_count"); |
| return $cpu_core_count; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine VM host $vmhost_hostname CPU core count from line: $cpu_cores_line"); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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) !~ /VCL::Module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $vmhost_hostname = $self->data->get_vmhost_hostname(); |
| |
| my $vim_cmd_arguments = "hostsvc/hosthardware"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # The CPU info should be contained in the output: |
| # cpuInfo = (vim.host.CpuInfo) { |
| # dynamicType = <unset>, |
| # numCpuPackages = 2, |
| # numCpuCores = 8, |
| # numCpuThreads = 8, |
| # hz = 2000070804, |
| # }, |
| |
| my ($hz_line) = grep(/^\s*hz\s*=/i, @$output); |
| if (!$hz_line) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine VM host $vmhost_hostname CPU speed, output does not contain a 'hz =' line:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif ($hz_line =~ /(\d+)/) { |
| my $mhz = int($1 / 1000000); |
| notify($ERRORS{'DEBUG'}, 0, "retrieved VM host $vmhost_hostname CPU speed: $mhz MHz"); |
| return $mhz; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine VM host $vmhost_hostname CPU speed from line: $hz_line"); |
| 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) !~ /VCL::Module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $vmhost_hostname = $self->data->get_vmhost_hostname(); |
| |
| my $vim_cmd_arguments = "hostsvc/hosthardware"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # The following line should be contained in the output: |
| # memorySize = 17178869760, |
| |
| my ($memory_size_line) = grep(/^\s*memorySize\s*=/i, @$output); |
| if (!$memory_size_line) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine VM host $vmhost_hostname total memory capacity, output does not contain a 'memorySize =' line:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif ($memory_size_line =~ /(\d+)/) { |
| my $memory_mb = int($1 / 1024 / 1024); |
| notify($ERRORS{'DEBUG'}, 0, "retrieved VM host $vmhost_hostname total memory capacity: $memory_mb MB"); |
| return $memory_mb; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine VM host $vmhost_hostname total memory capacity from line: $memory_size_line"); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_license_info |
| |
| Parameters : none |
| Returns : hash reference |
| Description : Retrieves the license information from the host. A hash reference |
| is returned: |
| { |
| "name" => "vSphere 4 Hypervisor", |
| "properties" => { |
| "FileVersion" => "4.1.1.0", |
| "LicenseFilePath" => [ |
| "/usr/lib/vmware/licenses/site/license-esx-40-e1-4core-200803", |
| ... |
| "/usr/lib/vmware/licenses/site/license-esx-40-e9-vm-200803" |
| ], |
| "ProductName" => "VMware ESX Server", |
| "ProductVersion" => "4.0", |
| "count_disabled" => "This license is unlimited", |
| "feature" => { |
| "maxRAM:256g" => "Up to 256 GB of memory", |
| "vsmp:4" => "Up to 4-way virtual SMP" |
| } |
| }, |
| "serial" => "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", |
| "total" => 0, |
| "unit" => "cpuPackage:6core", |
| "used" => 2 |
| } |
| |
| =cut |
| |
| sub get_license_info { |
| 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->{license_info} if $self->{license_info}; |
| |
| my $vmhost_hostname = $self->data->get_vmhost_hostname(); |
| |
| my $vim_cmd_arguments = "vimsvc/license --show"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # Typical output: |
| # [200] Sending request for installed licenses...[200] Complete, result is: |
| # serial: XXXXX-XXXXX-XXXXX-XXXXX-XXXXX |
| # vmodl key: esxBasic |
| # name: vSphere 4 Hypervisor |
| # total: 0 |
| # used: 2 |
| # unit: cpuPackage:6core |
| # Properties: |
| # [ProductName] = VMware ESX Server |
| # [ProductVersion] = 4.0 |
| # [count_disabled] = This license is unlimited |
| # [feature] = maxRAM:256g ("Up to 256 GB of memory") |
| # [feature] = vsmp:4 ("Up to 4-way virtual SMP") |
| # [FileVersion] = 4.1.1.0 |
| # [LicenseFilePath] = /usr/lib/vmware/licenses/site/license-esx-40-e1-4core-200803 |
| # ... |
| # [200] End of report. |
| |
| my $license_info; |
| for my $line (@$output) { |
| |
| # Find lines formatted as 'property: value' |
| if ($line =~ /^\s*(\w+):\s+(.+)$/) { |
| $license_info->{$1} = $2; |
| next; |
| } |
| |
| # Find '[feature] = ' lines |
| elsif ($line =~ /\[feature\]\s*=\s*([^\s]+)\s+\(\"(.+)\"\)/) { |
| my $feature_name = $1; |
| my $feature_description = $2; |
| $license_info->{properties}{feature}{$feature_name} = $feature_description; |
| } |
| |
| # Find '[LicenseFilePath] = ' lines |
| elsif ($line =~ /\[LicenseFilePath\]\s*=\s*(.+)/) { |
| # Leave this out of data for now, not used anywhere, clutters display of license info |
| #push @{$license_info->{properties}{LicenseFilePath}}, $1; |
| } |
| |
| # Find '[xxx] = ' lines |
| elsif ($line =~ /\[(\w+)\]\s*=\s*(.+)$/) { |
| my $property = $1; |
| my $value = $2; |
| $license_info->{properties}{$property} = $value; |
| } |
| } |
| |
| # Make sure something was found |
| if (!$license_info) { |
| notify($ERRORS{'WARNING'}, 0, "failed to parse 'vim-cmd $vim_cmd_arguments' output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| $self->{license_info} = $license_info; |
| notify($ERRORS{'DEBUG'}, 0, "retrieved VM host license info:\n" . format_data($license_info)); |
| return $license_info; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_config_option_descriptor_info |
| |
| Parameters : none |
| Returns : hash reference |
| Description : Retrieves information about the VM configuration options that are |
| supported on the host. |
| } |
| "vmx-09" => { |
| "createSupported" => "true", |
| "defaultConfigOption" => "false", |
| "description" => "ESXi 5.1 virtual machine", |
| "dynamicType" => "<unset>", |
| "key" => "vmx-09", |
| "runSupported" => "true", |
| "upgradeSupported" => "true" |
| }, |
| "vmx-10" => { |
| "createSupported" => "true", |
| "defaultConfigOption" => "true", |
| "description" => "ESXi 5.5 virtual machine", |
| "dynamicType" => "<unset>", |
| "key" => "vmx-10", |
| "runSupported" => "true", |
| "upgradeSupported" => "true" |
| } |
| } |
| |
| =cut |
| |
| sub get_config_option_descriptor_info { |
| 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->{config_option_descriptor_info} if $self->{config_option_descriptor_info}; |
| |
| my $vim_cmd_arguments = "solo/querycfgoptdesc"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| my $result = $self->_parse_vim_cmd_output($output); |
| if (!$result) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve config option descriptor info"); |
| return; |
| } |
| |
| my $type = ref($result); |
| if (!$type) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve config option descriptor info, parsed result is not a reference:\n" . $result); |
| return; |
| } |
| |
| # If a single entry is returned a hash reference may be returned instead of an array, convert it to an array |
| if ($type eq 'HASH') { |
| $result = [$result]; |
| } |
| |
| my $config_option_descriptor_info = {}; |
| for my $config_option_descriptor (@$result) { |
| my $key = $config_option_descriptor->{key}; |
| if (!defined($key)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve config option descriptor info, result does not contain a 'key' element:\n" . format_data($config_option_descriptor)); |
| return; |
| } |
| $config_option_descriptor_info->{$key} = $config_option_descriptor; |
| } |
| |
| #notify($ERRORS{'DEBUG'}, 0, "retrieved config option descriptor info:\n" . format_data($config_option_descriptor_info)); |
| $self->{config_option_descriptor_info} = $config_option_descriptor_info; |
| return $config_option_descriptor_info; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_highest_vm_hardware_version_key |
| |
| Parameters : none |
| Returns : string |
| Description : Each VMware VM has a hardware version. The versions supported on |
| the host depends on the version of VMware. This subroutine |
| returns the highest supported version and returns an integer. For |
| example vmx-11 is returned for ESXi 6.0. |
| |
| =cut |
| |
| sub get_highest_vm_hardware_version_key { |
| 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->{highest_vm_hardware_version_key} if defined($self->{highest_vm_hardware_version_key}); |
| |
| my $vmhost_hostname = $self->data->get_vmhost_hostname(); |
| |
| my $config_option_descriptor_info = $self->get_config_option_descriptor_info(); |
| |
| my $highest_vm_hardware_version_number; |
| my $highest_vm_hardware_version_key; |
| for my $version_key (sort keys %$config_option_descriptor_info) { |
| my ($version_number) = $version_key =~ /-(\d+)$/g; |
| if (!$highest_vm_hardware_version_number || $highest_vm_hardware_version_number < $version_number) { |
| $highest_vm_hardware_version_number = $version_number; |
| $highest_vm_hardware_version_key = $version_key; |
| } |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "determined highest VM hardware version supported on $vmhost_hostname: $highest_vm_hardware_version_key"); |
| $self->{highest_vm_hardware_version_key} = $highest_vm_hardware_version_key; |
| return $highest_vm_hardware_version_key; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_config_option_info |
| |
| Parameters : $key |
| Returns : hash reference |
| Description : Retrieves info about the VM configuration options available for a |
| particular hardware version key (ex: vmx-09). A hash reference is |
| returned with the following keys: |
| { |
| capabilities = {}, |
| datastore = {}, |
| defaultDevice = [], |
| description = '', |
| guestOSDefaultIndex = '', |
| guestOSDescriptor = [], |
| hardwareOptions = {}, |
| supportedMonitorType = [], |
| supportedOvfEnvironmentTransport = '', |
| supportedOvfInstallTransport = '', |
| version = '', |
| }, |
| |
| =cut |
| |
| sub get_config_option_info { |
| 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 ($key) = @_; |
| if (!defined($key)) { |
| notify($ERRORS{'WARNING'}, 0, "key argument was not provided"); |
| return; |
| } |
| |
| return $self->{config_option_info}{$key} if defined($self->{config_option_info}{$key}); |
| |
| my $vim_cmd_arguments = "solo/querycfgopt $key"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| my $result = $self->_parse_vim_cmd_output($output); |
| if (!$result) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve config option info for $key"); |
| return; |
| } |
| |
| if (!defined($result->{'vim.vm.ConfigOption'})) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve config option info for $key, 'vim.vm.ConfigOption' key does not exist:\n" . format_hash_keys($result)); |
| return; |
| } |
| |
| $self->{config_option_info}{$key} = $result->{'vim.vm.ConfigOption'}; |
| return $result->{'vim.vm.ConfigOption'}; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_config_option_guest_os_info |
| |
| Parameters : $key |
| Returns : hash reference |
| Description : Retrieves info about the guest OS's supported for the given key |
| (ex: vmx-09). |
| { |
| "windows8_64Guest" => { |
| "family" => "windowsGuest", |
| "fullName" => "Microsoft Windows 8 (64-bit)", |
| "id" => "windows8_64Guest", |
| ... |
| }, |
| "windows8Server64Guest" => { |
| ... |
| }, |
| ... |
| } |
| |
| =cut |
| |
| sub get_config_option_guest_os_info { |
| 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 ($key) = @_; |
| if (!defined($key)) { |
| notify($ERRORS{'WARNING'}, 0, "key argument was not provided"); |
| return; |
| } |
| |
| return $self->{config_option_guest_os_info}{$key} if defined($self->{config_option_guest_os_info}{$key}); |
| |
| my $config_option_info = $self->get_config_option_info($key) || return; |
| |
| my $guest_os_descriptor_array_ref = $config_option_info->{guestOSDescriptor}; |
| if (!defined($guest_os_descriptor_array_ref)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve config option guest OS info, config option info does not contain a 'guestOSDescriptor' key:\n" . format_hash_keys($config_option_info)); |
| return; |
| } |
| |
| my $type = ref($guest_os_descriptor_array_ref); |
| if (!$type || $type ne 'ARRAY') { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve config option guest OS info for '$key', guestOSDescriptor value is not an array reference:\n" . format_data($guest_os_descriptor_array_ref)); |
| return; |
| } |
| |
| my $config_option_guest_os_info = {}; |
| for my $guest_os_descriptor (@$guest_os_descriptor_array_ref) { |
| my $id = $guest_os_descriptor->{id}; |
| if (!defined($id)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve config option guest OS info for '$key', guest OS descriptor does not contain an 'id' key:\n" . format_data($guest_os_descriptor)); |
| return; |
| } |
| $config_option_guest_os_info->{$id} = $guest_os_descriptor; |
| } |
| |
| #notify($ERRORS{'DEBUG'}, 0, "retrieved config option guest OS info:\n" . format_data($config_option_guest_os_info)); |
| $self->{config_option_guest_os_info}{$key} = $config_option_guest_os_info; |
| return $config_option_guest_os_info; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_supported_guest_os_ids |
| |
| Parameters : $vm_hardware_version_key (optional) |
| Returns : array |
| Description : Retrieves the names of the supported guestOS values for the VM |
| hardware version specified by the argument (example: vmx-11). If |
| no argument is supplied, the host's highest supported hardware |
| version is used. |
| |
| =cut |
| |
| sub get_supported_guest_os_ids { |
| 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 $vmhost_hostname = $self->data->get_vmhost_hostname(); |
| |
| my $vm_hardware_version_key = shift; |
| if (!defined($vm_hardware_version_key)) { |
| $vm_hardware_version_key = $self->get_highest_vm_hardware_version_key(); |
| if (!defined($vm_hardware_version_key)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine supported guest OS names on $vmhost_hostname, VM hardware version key argument was not provided and highest supported VM hardware version could not be determiend"); |
| return; |
| } |
| } |
| |
| return @{$self->{supported_guest_os_ids}{$vm_hardware_version_key}} if defined($self->{supported_guest_os_ids}{$vm_hardware_version_key}); |
| |
| my $config_option_info = $self->get_config_option_info($vm_hardware_version_key) || return; |
| |
| my $guest_os_descriptor_array_ref = $config_option_info->{guestOSDescriptor}; |
| if (!defined($guest_os_descriptor_array_ref)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve config option guest OS info, config option info does not contain a 'guestOSDescriptor' key:\n" . format_hash_keys($config_option_info)); |
| return; |
| } |
| |
| my $type = ref($guest_os_descriptor_array_ref); |
| if (!$type || $type ne 'ARRAY') { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve config option guest OS info for '$vm_hardware_version_key', guestOSDescriptor value is not an array reference:\n" . format_data($guest_os_descriptor_array_ref)); |
| return; |
| } |
| |
| my @supported_guest_os_ids; |
| for my $guest_os_descriptor (@$guest_os_descriptor_array_ref) { |
| my $guest_os_id = $guest_os_descriptor->{id}; |
| |
| # Every name includes "Guest" at the end but this is not in the valid guestOS values |
| $guest_os_id =~ s/Guest//; |
| |
| # Windows server OS's: windows7Server --> windows7srv |
| $guest_os_id =~ s/(windows.+)Server/$1srv/; |
| |
| # windows7_64 --> windows7-64 |
| # windows8srv64 --> windows8srv-64 |
| $guest_os_id =~ s/_?(64)/-$1/g; |
| |
| push @supported_guest_os_ids, $guest_os_id; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "retrieved supported guest OS names on $vmhost_hostname, VM hardware version: $vm_hardware_version_key: " . join(",", @supported_guest_os_ids)); |
| $self->{supported_guest_os_ids}{$vm_hardware_version_key} = \@supported_guest_os_ids; |
| return @supported_guest_os_ids; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _print_compatible_guest_os_hardware_versions |
| |
| Parameters : $print_code (optional) |
| Returns : true |
| Description : Used for development/testing only. Prints list of possible |
| guestOS values. |
| asianux3 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| asianux3-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| asianux4 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| asianux4-64 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| asianux7-64 vmx-13 |
| centos vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| centos-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| centos6 vmx-13 |
| centos6-64 vmx-13 |
| centos7-64 vmx-13 |
| coreos-64 vmx-11 vmx-12 vmx-13 |
| darwin vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| darwin-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| darwin10 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| darwin10-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| darwin11 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| darwin11-64 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| darwin12-64 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| darwin13-64 vmx-10 vmx-11 vmx-12 vmx-13 |
| darwin14-64 vmx-11 vmx-12 vmx-13 |
| darwin15-64 vmx-12 vmx-13 |
| darwin16-64 vmx-13 |
| debian10 vmx-13 |
| debian10-64 vmx-13 |
| debian4 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| debian4-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| debian5 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| debian5-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| debian6 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| debian6-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| debian7 vmx-10 vmx-11 vmx-12 vmx-13 |
| debian7-64 vmx-10 vmx-11 vmx-12 vmx-13 |
| debian8 vmx-11 vmx-12 vmx-13 |
| debian8-64 vmx-11 vmx-12 vmx-13 |
| debian9 vmx-13 |
| debian9-64 vmx-13 |
| dos vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| eComStation vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| eComStation2 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| fedora vmx-11 vmx-12 vmx-13 |
| fedora-64 vmx-11 vmx-12 vmx-13 |
| freebsd vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| freebsd-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| netware5 vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| netware6 vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| oes vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| openServer5 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| openServer6 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| opensuse vmx-11 vmx-12 vmx-13 |
| opensuse-64 vmx-11 vmx-12 vmx-13 |
| oracleLinux vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| oracleLinux-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| oracleLinux6 vmx-13 |
| oracleLinux6-64 vmx-13 |
| oracleLinux7-64 vmx-13 |
| os2 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| other vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| other-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| other24xLinux vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| other24xLinux-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| other26xLinux vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| other26xLinux-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| other3xLinux vmx-10 vmx-11 vmx-12 vmx-13 |
| other3xLinux-64 vmx-10 vmx-11 vmx-12 vmx-13 |
| otherLinux vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| otherLinux-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| rhel2 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| rhel3 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| rhel3-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| rhel4 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| rhel4-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| rhel5 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| rhel5-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| rhel6 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| rhel6-64 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| rhel7 vmx-09 vmx-10 |
| rhel7-64 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| sles vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| sles-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| sles10 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| sles10-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| sles11 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| sles11-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| sles12 vmx-09 vmx-10 |
| sles12-64 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| solaris10 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| solaris10-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| solaris11-64 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| solaris8 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| solaris9 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| ubuntu vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| ubuntu-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| unixWare7 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| vmkernel vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| vmkernel5 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| vmkernel6 vmx-11 vmx-12 vmx-13 |
| vmkernel65 vmx-11 vmx-12 vmx-13 |
| vmwarePhoton-64 vmx-13 |
| win2000AdvServ vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| win2000Pro vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| win2000Serv vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| win31 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| win95 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| win98 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| windows7 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| windows7-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| windows7srv-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| windows8 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| windows8-64 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| windows8srv-64 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| windows9 vmx-10 vmx-11 vmx-12 vmx-13 |
| windows9-64 vmx-10 vmx-11 vmx-12 vmx-13 |
| windows9srv-64 vmx-10 vmx-11 vmx-12 vmx-13 |
| winLonghorn vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winLonghorn-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winNetBusiness vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winNetDatacenter vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winNetDatacenter-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winNetEnterprise vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winNetEnterprise-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winNetStandard vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winNetStandard-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winNetWeb vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winNT vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winVista vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winVista-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winXPPro vmx-03 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| winXPPro-64 vmx-04 vmx-07 vmx-08 vmx-09 vmx-10 vmx-11 vmx-12 vmx-13 |
| |
| =cut |
| |
| sub _print_compatible_guest_os_hardware_versions { |
| 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 $print_code = shift; |
| |
| my $guest_os_info = {}; |
| my $config_option_descriptor_info = $self->get_config_option_descriptor_info(); |
| |
| for my $version_key (sort keys %$config_option_descriptor_info) { |
| my @guest_os_ids = $self->get_supported_guest_os_ids($version_key); |
| for my $guest_os_id (@guest_os_ids) { |
| $guest_os_info->{$guest_os_id}{$version_key} = 1; |
| } |
| } |
| |
| my $version_key_count = scalar(keys %$config_option_descriptor_info); |
| |
| for my $guest_os (sort {lc($a) cmp lc($b)} keys %$guest_os_info) { |
| if ($print_code) { |
| print "'$guest_os' => { "; |
| for my $version_key (sort keys %{$guest_os_info->{$guest_os}}) { |
| $version_key =~ s/vmx-0?//; |
| print "$version_key => 1, "; |
| } |
| print "},\n"; |
| } |
| else { |
| my $length = length($guest_os); |
| my $guest_os_version_key_count = scalar(keys %{$guest_os_info->{$guest_os}}); |
| print "$guest_os "; |
| print (' ' x (25-$length)); |
| |
| for my $version_key (sort keys %$config_option_descriptor_info) { |
| print " "; |
| if ($guest_os_info->{$guest_os}{$version_key}) { |
| print "$version_key"; |
| } |
| else { |
| print " "; |
| } |
| } |
| print "\n"; |
| } |
| } |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_host_capability_info |
| |
| Parameters : none |
| Returns : hash reference |
| Description : Retrieves information about the capabilities of the VMware host. |
| A hash reference is returned similar to: |
| { |
| "bootOptionsSupported" => "true", |
| "bootRetryOptionsSupported" => "true", |
| "canConnectUSBDevices" => "<unset>", |
| "changeTrackingSupported" => "false", |
| "consolePreferencesSupported" => "true", |
| "cpuFeatureMaskSupported" => "true", |
| "disableSnapshotsSupported" => "false", |
| "diskSharesSupported" => "true", |
| "featureRequirementSupported" => "true", |
| "guestAutoLockSupported" => "true", |
| "hostBasedReplicationSupported" => "true", |
| "lockSnapshotsSupported" => "false", |
| "memoryReservationLockSupported" => "true", |
| "memorySnapshotsSupported" => "true", |
| "messageBusSupported" => "true", |
| "multipleCoresPerSocketSupported" => "true", |
| "multipleSnapshotsSupported" => "true", |
| "nestedHVSupported" => "true", |
| "npivWwnOnNonRdmVmSupported" => "true", |
| "perVmEvcSupported" => "<unset>", |
| "poweredOffSnapshotsSupported" => "true", |
| "poweredOnMonitorTypeChangeSupported" => "true", |
| "quiescedSnapshotsSupported" => "true", |
| "recordReplaySupported" => "true", |
| "revertToSnapshotSupported" => "true", |
| "s1AcpiManagementSupported" => "true", |
| "seSparseDiskSupported" => "true", |
| "secureBootSupported" => "<unset>", |
| "settingDisplayTopologyModesSupported" => "true", |
| "settingDisplayTopologySupported" => "false", |
| "settingScreenResolutionSupported" => "true", |
| "settingVideoRamSizeSupported" => "true", |
| "snapshotConfigSupported" => "true", |
| "snapshotOperationsSupported" => "true", |
| "swapPlacementSupported" => "true", |
| "toolsAutoUpdateSupported" => "false", |
| "toolsRebootPredictSupported" => "<unset>", |
| "toolsSyncTimeSupported" => "true", |
| "vPMCSupported" => "true", |
| "virtualMmuUsageSupported" => "true", |
| "vmNpivWwnDisableSupported" => "true", |
| "vmNpivWwnSupported" => "true", |
| "vmNpivWwnUpdateSupported" => "true", |
| "vmfsNativeSnapshotSupported" => "false" |
| } |
| |
| =cut |
| |
| sub get_host_capability_info { |
| 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; |
| } |
| |
| if (defined($self->{host_capability_info})) { |
| return $self->{host_capability_info}; |
| } |
| |
| my $vmhost_computer_name = $self->data->get_vmhost_short_name(); |
| |
| my $version_key = $self->get_highest_vm_hardware_version_key(); |
| if (!$version_key) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve host capability info from $vmhost_computer_name, failed to retrieve highest supported virtual machine hardware"); |
| return; |
| } |
| |
| my $config_option_info = $self->get_config_option_info($version_key); |
| if (!$config_option_info) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve host capability info from $vmhost_computer_name, failed to retrieve host config option info"); |
| return; |
| } |
| |
| if ($config_option_info->{capabilities}) { |
| $self->{host_capability_info} = $config_option_info->{capabilities}; |
| return $self->{host_capability_info}; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve host capability info from $vmhost_computer_name, config option info does not contain a 'capabilities' key:\n" . format_hash_keys($config_option_info)); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 is_nested_virtualization_supported |
| |
| Parameters : none |
| Returns : boolean |
| Description : Determines whether or not the VMware host supports nested |
| hardware-assisted virtualization. |
| |
| =cut |
| |
| sub is_nested_virtualization_supported { |
| 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 $vmhost_computer_name = $self->data->get_vmhost_short_name(); |
| |
| my $host_capability_info = $self->get_host_capability_info(); |
| if (!$host_capability_info) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if nested virtualization is supported on $vmhost_computer_name, failed to retrieve host capability info"); |
| return; |
| } |
| |
| if (!defined($host_capability_info->{nestedHVSupported})) { |
| notify($ERRORS{'DEBUG'}, 0, "nested virtualization is NOT supported on $vmhost_computer_name, host capability info does not contain a 'nestedHVSupported' key:\n" . format_hash_keys($host_capability_info)); |
| return 0; |
| } |
| elsif ($host_capability_info->{nestedHVSupported} !~ /true/i) { |
| notify($ERRORS{'DEBUG'}, 0, "nested virtualization is NOT supported on $vmhost_computer_name, nestedHVSupported value: $host_capability_info->{nestedHVSupported}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "nested virtualization is supported on $vmhost_computer_name, nestedHVSupported value: $host_capability_info->{nestedHVSupported}"); |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _parse_vim_cmd_output |
| |
| Parameters : $vim_cmd_output |
| Returns : varies |
| Description : Parses the Data::Dumper-like output returned for some vim-cmd |
| commands and attempts to parse the output into a data structure. |
| |
| =cut |
| |
| sub _parse_vim_cmd_output { |
| 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, $debug) = @_; |
| if (!defined($argument)) { |
| notify($ERRORS{'WARNING'}, 0, "vim-cmd output argument was not supplied"); |
| return; |
| } |
| my @lines; |
| if (my $type = ref($argument)) { |
| if ($type eq 'ARRAY') { |
| @lines = @$argument; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "argument is a $type reference, only an ARRAY reference or string are supported"); |
| return; |
| } |
| } |
| elsif (scalar(@_) > 1) { |
| @lines = @_; |
| } |
| else { |
| @lines = split("\n", $argument); |
| } |
| |
| my $statement; |
| my $numbered_statement; |
| my $line_number = 0; |
| for my $line (@lines) { |
| # Skip blank lines |
| if ($line !~ /\S/) { |
| next; |
| } |
| |
| # Some commands such as 'vim-cmd vmsvc/get.summary' add this to the beginning: |
| # Listsummary: |
| if ($line =~ /:$/) { |
| notify($ERRORS{'DEBUG'}, 0, "skipping line: $line"); |
| next; |
| } |
| |
| $line_number++; |
| |
| my $original_line = $line; |
| |
| # Remove trailing newlines |
| $line =~ s/\n+$//g; |
| |
| # Remove class names in parenthesis from beginning class name of indented lines |
| # ' (vim.vm.device.xxx) {' --> ' {' |
| $line =~ s/^(\s+)\([^\)]*\)\s*/$1/g; |
| |
| # Remove class names at beginning of a line surrounded by parenthesis |
| # '(vim.vm.device.VirtualPointingDevice) {' --> 'vim.vm.device.VirtualPointingDevice => {' |
| $line =~ s/^\(([^\)]+)\)\s*{/$1 => {/gx; |
| |
| # Remove class names |
| # '(vim.vm.ConfigOptionDescriptor) [' |
| $line =~ s/^\([^\)]+\)\s*(\[)/$1/gx; |
| |
| # Add comma to lines containing a closing curly bracket |
| # ' }' --> ' },' |
| $line =~ s/^(\s*)}\s*$/$1},/g; |
| |
| # Remove class names after an equals sign |
| # 'backing = (vim.vm.device.VirtualDevice.BackingInfo) null,' --> 'backing = null' |
| $line =~ s/(=\s+)\([^\)]+\)\s*/$1/g; |
| |
| # Add comma to lines containing = sign which don't end with a comma |
| # 'value = xxx' --> 'value = xxx,' |
| $line =~ s/(=\s+[^,]+[\w>])$/$1,/g; |
| |
| # Surround values after equals sign in single quotes |
| # value = xxx, --> value = 'xxx', |
| # value = "xxx", --> value = 'xxx', |
| $line =~ s/(=\s+)["']?([^"']+)["']?,/$1'$2',/g; |
| |
| # Surround values before equals sign in single quotes |
| $line =~ s/^(\s*)["']?([^\s"']+)["']?(\s*=)/$1'$2'$3/g; |
| |
| # Change 'null' to undef and add => |
| # 'busSlotOption null,' --> 'busSlotOption => undef,' |
| $line =~ s/(\w\s+)null,/$1 => undef,/g; |
| |
| # Change = to => |
| $line =~ s/=(\s+.+)/=>$1/g; |
| |
| # Add => before array and hash references |
| # 'guestOSDescriptor [' --> 'guestOSDescriptor => [' |
| $line =~ s/(\w\s+)([\[{])/$1=>$2/g; |
| |
| $statement .= "$line\n"; |
| $numbered_statement .= "$line_number:\n"; |
| $numbered_statement .= "$original_line\n"; |
| $numbered_statement .= "$line\n"; |
| } |
| |
| # Enclose the entire statement in curly brackets |
| if ($statement =~ /^[^\n]+{/) { |
| $statement = "{\n$statement\n}"; |
| } |
| |
| if ($debug) { |
| print "\n"; |
| print '.' x 200 . "\n"; |
| print "Statement:\n$statement\n"; |
| print '.' x 200 . "\n"; |
| } |
| |
| # The statement variable should contain a valid definition |
| my $result = eval($statement); |
| if ($EVAL_ERROR) { |
| notify($ERRORS{'WARNING'}, 0, "failed to parse vim-cmd output, error:\n$EVAL_ERROR\n$numbered_statement"); |
| return; |
| } |
| elsif (!defined($result)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to parse vim-cmd output:\n$numbered_statement"); |
| return; |
| } |
| else { |
| #notify($ERRORS{'DEBUG'}, 0, "parsed vim-cmd output:\n" . format_data($result)); |
| return $result; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _get_vm_virtual_disk_file_layout |
| |
| Parameters : $vmx_file_path |
| Returns : hash reference |
| Description : Retrieves a VM's virtual disk file layout as reported by: |
| vim-cmd vmsvc/get.filelayout <VM ID> |
| A hash reference is returned: |
| { |
| "vim.vm.FileInfo" => { |
| "dynamicType" => "<unset>", |
| "ftMetadataDirectory" => "<unset>", |
| "logDirectory" => "[blade1e1-10-vmpath] arkvmm160_3868-v0", |
| "snapshotDirectory" => "[blade1e1-10-vmpath] arkvmm160_3868-v0", |
| "suspendDirectory" => "[blade1e1-10-vmpath] arkvmm160_3868-v0", |
| "vmPathName" => "[blade1e1-10-vmpath] arkvmm160_3868-v0/arkvmm160_3868-v0.vmx" |
| }, |
| "vim.vm.FileLayout" => { |
| "configFile" => [ |
| "arkvmm160_3868-v0.vmxf", |
| "nvram", |
| "arkvmm160_3868-v0.vmsd" |
| ], |
| "disk" => [ |
| { |
| "diskFile" => [ |
| "[datastore-compressed] vmwarewinxp-xpsp33868-v0/vmwarewinxp-xpsp33868-v0.vmdk", |
| "[blade1e1-10-vmpath] arkvmm160_3868-v0/vmwarewinxp-xpsp33868-v0-000001.vmdk" |
| ], |
| "dynamicType" => "<unset>", |
| "key" => 3000 |
| }, |
| { |
| "diskFile" => [ |
| "[blade1e1-10-vmpath] arkvmm160_3868-v0/arkvmm160_3868-v0.vmdk" |
| ], |
| "dynamicType" => "<unset>", |
| "key" => 2000 |
| } |
| ], |
| "dynamicType" => "<unset>", |
| "logFile" => [ |
| "vmware-1.log", |
| "vmware-2.log", |
| "vmware-3.log", |
| "vmware.log" |
| ], |
| "snapshot" => [ |
| { |
| "dynamicType" => "<unset>", |
| "key" => "'vim.vm.Snapshot:576-snapshot-1'", |
| "snapshotFile" => [ |
| "[blade1e1-10-vmpath] arkvmm160_3868-v0/arkvmm160_3868-v0-Snapshot1.vmsn", |
| "[datastore-compressed] vmwarewinxp-xpsp33868-v0/vmwarewinxp-xpsp33868-v0.vmdk" |
| ] |
| } |
| ], |
| "swapFile" => "<unset>" |
| } |
| } |
| |
| =cut |
| |
| sub _get_vm_virtual_disk_file_layout { |
| 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 $vmx_file_path = shift; |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not specified"); |
| return; |
| } |
| |
| # Get the VM ID |
| my $vm_id = $self->_get_vm_id($vmx_file_path); |
| if (!defined($vm_id)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to power off VM because VM ID could not be determined"); |
| return; |
| } |
| |
| my $vim_cmd_arguments = "vmsvc/get.filelayout $vm_id"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| my $virtual_disk_file_layout = $self->_parse_vim_cmd_output($output); |
| if ($virtual_disk_file_layout) { |
| #notify($ERRORS{'DEBUG'}, 0, "retrieved virtual disk file layout for VM $vm_id ($vmx_file_path)\n" . format_data($virtual_disk_file_layout)); |
| return $virtual_disk_file_layout; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve virtual disk file layout for VM $vm_id ($vmx_file_path)"); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_vm_virtual_disk_file_paths |
| |
| Parameters : $vmx_file_path |
| Returns : array |
| Description : Retrieves a VM's virtual disk file layout and returns an array |
| reference. Each top-level array element represent entire virtual |
| disk and contains an array reference containing the virtual |
| disk's files: |
| [ |
| [ |
| "/vmfs/volumes/datastore/vmwarewin7-bare3844-v1/vmwarewin7-bare3844-v1.vmdk", |
| "/vmfs/volumes/blade-vmpath/vm170_3844-v1/vmwarewin7-bare3844-v1-000001.vmdk", |
| "/vmfs/volumes/blade-vmpath/vm170_3844-v1/vmwarewin7-bare3844-v1-000002.vmdk", |
| "/vmfs/volumes/blade-vmpath/vm170_3844-v1/vmwarewin7-bare3844-v1-000003.vmdk" |
| ], |
| [ |
| "/vmfs/volumes/blade-vmpath/vm170_3844-v1/vm170_3844-v1.vmdk" |
| ] |
| ] |
| |
| =cut |
| |
| sub get_vm_virtual_disk_file_paths { |
| my $self = shift; |
| if (ref($self) !~ /VCL::Module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $vmx_file_path = shift; |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument was not specified"); |
| return; |
| } |
| |
| my $virtual_disk_file_layout = $self->_get_vm_virtual_disk_file_layout($vmx_file_path) || return; |
| |
| my $virtual_disk_array_ref = $virtual_disk_file_layout->{'vim.vm.FileLayout'}{'disk'}; |
| if (!$virtual_disk_array_ref) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine virtual disk file paths, failed to retrieve {'vim.vm.FileLayout'}{'disk'} array reference from virtual disk file layout:\n" . format_data($virtual_disk_file_layout)); |
| return; |
| } |
| elsif (!ref($virtual_disk_array_ref) || ref($virtual_disk_array_ref) ne 'ARRAY') { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine virtual disk file paths, virtual disk file layout {'vim.vm.FileLayout'}{'disk'} key does not contain an array reference:\n" . format_data($virtual_disk_array_ref)); |
| return; |
| } |
| |
| my @virtual_disks; |
| for my $virtual_disk_ref (@$virtual_disk_array_ref) { |
| my $disk_file_array_ref = $virtual_disk_ref->{'diskFile'}; |
| my @virtual_disk_file_paths; |
| for my $virtual_disk_file_path (@$disk_file_array_ref) { |
| push @virtual_disk_file_paths, $self->_get_normal_path($virtual_disk_file_path); |
| } |
| push @virtual_disks, \@virtual_disk_file_paths; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "retrieved virtual disk file paths for $vmx_file_path:\n" . format_data(\@virtual_disks)); |
| return @virtual_disks; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_vm_cpu_usage |
| |
| Parameters : $vmx_file_path |
| Returns : integer (percent) |
| Description : Retrieves the most recent overall CPU usage for a VM. This is |
| calculated based on the values returned from: |
| vim-cmd vmsvc/get.summary |
| {quickStats}{overallCpuUsage} / {runtime}{maxCpuUsage} = %usage |
| |
| =cut |
| |
| sub get_vm_cpu_usage { |
| 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 file path argument |
| my $vmx_file_path = shift || $self->get_vmx_file_path(); |
| if (!$vmx_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "vmx file path argument could not be determined"); |
| return; |
| } |
| |
| my $vm_summary = $self->_get_vm_summary($vmx_file_path) || return; |
| |
| my $max_cpu_usage = $vm_summary->{runtime}{maxCpuUsage}; |
| if (!defined($max_cpu_usage)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine CPU usage for VM $vmx_file_path, VM summary information does not contain a {runtime}{maxCpuUsage} key:\n" . format_data($vm_summary)); |
| return; |
| } |
| elsif ($max_cpu_usage !~ /^\d+$/ || !$max_cpu_usage) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine CPU usage for VM $vmx_file_path, maxCpuUsage value is not valid: $max_cpu_usage"); |
| return; |
| } |
| |
| my $overall_cpu_usage = $vm_summary->{quickStats}{overallCpuUsage}; |
| if (!defined($overall_cpu_usage)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine CPU usage for VM $vmx_file_path, VM summary information does not contain a {quickStats}{overallCpuUsage} key:\n" . format_data($vm_summary)); |
| return; |
| } |
| elsif ($overall_cpu_usage !~ /^\d+$/) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine CPU usage for VM $vmx_file_path, $overall_cpu_usage value is not valid: $overall_cpu_usage"); |
| return; |
| } |
| |
| my $cpu_usage_percent = format_number(($overall_cpu_usage / $max_cpu_usage), 2) * 100; |
| |
| notify($ERRORS{'DEBUG'}, 0, "retrieved CPU usage for VM $vmx_file_path: $cpu_usage_percent\% (overall CPU usage: $overall_cpu_usage MHz / max CPU usage: $max_cpu_usage MHz)"); |
| return $cpu_usage_percent; |
| |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 firewall_ruleset_allow_ip |
| |
| Parameters : $ruleset_name, $ip_address |
| Returns : boolean |
| Description : Adds an IP address to a firewall ruleset to allow traffic. |
| |
| =cut |
| |
| sub firewall_ruleset_allow_ip { |
| 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 ($ruleset_name, $ip_address) = @_; |
| if (!defined($ruleset_name)) { |
| notify($ERRORS{'WARNING'}, 0, "ruleset name argument was not supplied"); |
| return; |
| } |
| elsif (!defined($ip_address)) { |
| notify($ERRORS{'WARNING'}, 0, "IP address argument was not supplied"); |
| return; |
| } |
| |
| my $vmhost_computer_name = $self->data->get_vmhost_hostname(); |
| |
| my $command; |
| if ($ip_address =~ /all/i) { |
| $command = "esxcli network firewall ruleset set --ruleset-id=$ruleset_name --allowed-all true"; |
| } |
| else { |
| $command = "esxcli network firewall ruleset allowedip add --ruleset-id=$ruleset_name --ip-address=$ip_address"; |
| } |
| |
| my ($exit_status, $output) = $self->vmhost_os->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command on VM host $vmhost_computer_name: $command"); |
| return; |
| } |
| elsif (grep(/already (exist|allowed)/i, @$output)) { |
| notify($ERRORS{'OK'}, 0, "$ip_address is already allowed for $ruleset_name ruleset on VM host $vmhost_computer_name"); |
| return 1; |
| } |
| elsif (grep(/allowed-all/i, @$output)) { |
| # Couldn't update allowed ip list when allowed-all flag is true. |
| notify($ERRORS{'OK'}, 0, "all IP addresses are already allowed for $ruleset_name ruleset on VM host $vmhost_computer_name"); |
| return 1; |
| } |
| elsif ($exit_status ne 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to add $ip_address to $ruleset_name ruleset on VM host $vmhost_computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output)); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "added $ip_address to $ruleset_name ruleset on VM host $vmhost_computer_name"); |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 firewall_ruleset_enable |
| |
| Parameters : $ruleset_name |
| Returns : boolean |
| Description : Enables a firewall ruleset. |
| |
| =cut |
| |
| sub firewall_ruleset_enable { |
| 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 ($ruleset_name) = @_; |
| if (!defined($ruleset_name)) { |
| notify($ERRORS{'WARNING'}, 0, "ruleset name argument was not supplied"); |
| return; |
| } |
| |
| my $vmhost_computer_name = $self->data->get_vmhost_hostname(); |
| |
| my $command = "esxcli network firewall ruleset set --ruleset-id=$ruleset_name --enabled true"; |
| my ($exit_status, $output) = $self->vmhost_os->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command on VM host $vmhost_computer_name: $command"); |
| return; |
| } |
| elsif ($exit_status ne 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to enable $ruleset_name ruleset on VM host $vmhost_computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output)); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "enabled $ruleset_name ruleset on VM host $vmhost_computer_name"); |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_firewall_ruleset_info |
| |
| Parameters : none |
| Returns : array |
| Description : Retrieves information about all of the firewall rulesets from the |
| VM host. A hash reference is returned. Hash keys are the ruleset |
| names: |
| "ipfam" => { |
| "enabled" => 1, |
| "rules" => [ |
| { |
| "Direction" => "Inbound", |
| "PortBegin" => 6999, |
| "PortEnd" => 6999, |
| "PortType" => "Dst", |
| "Protocol" => "UDP" |
| }, |
| { |
| "Direction" => "Outbound", |
| "PortBegin" => 6999, |
| "PortEnd" => 6999, |
| "PortType" => "Dst", |
| "Protocol" => "UDP" |
| } |
| ] |
| }, |
| "nfs41Client" => { |
| "enabled" => 0, |
| "rules" => [ |
| { |
| "Direction" => "Outbound", |
| "PortBegin" => 2049, |
| "PortEnd" => 2049, |
| "PortType" => "Dst", |
| "Protocol" => "TCP" |
| } |
| ] |
| }, |
| |
| =cut |
| |
| sub get_firewall_ruleset_info { |
| 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 $ENV{firewall_ruleset_info} if defined($ENV{firewall_ruleset_info}); |
| |
| my $vmhost_computer_name = $self->data->get_vmhost_hostname(); |
| |
| my $ruleset_info = {}; |
| |
| # Get the enabled/disabled status of each ruleset |
| my $ruleset_list_command = "esxcli --formatter=csv network firewall ruleset list"; |
| my ($ruleset_list_exit_status, $ruleset_list_output) = $self->vmhost_os->execute($ruleset_list_command); |
| if (!defined($ruleset_list_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command on VM host $vmhost_computer_name: $ruleset_list_command"); |
| return; |
| } |
| elsif ($ruleset_list_exit_status ne 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve firewall ruleset info from VM host $vmhost_computer_name, exit status: $ruleset_list_exit_status, command:\n$ruleset_list_command\noutput:\n" . join("\n", @$ruleset_list_output)); |
| return 0; |
| } |
| |
| # Enabled,Name, |
| # true,sshServer, |
| # true,sshClient, |
| # true,nfsClient, |
| # false,nfs41Client, |
| # ... |
| for my $line (@$ruleset_list_output) { |
| if ($line !~ /(true|false)/) { |
| next; |
| } |
| my ($enabled, $ruleset_name) = split(/,/, $line); |
| if ($enabled =~ /true/i) { |
| $ruleset_info->{$ruleset_name}{enabled} = 1; |
| } |
| else { |
| $ruleset_info->{$ruleset_name}{enabled} = 0; |
| } |
| } |
| |
| |
| |
| |
| # Get the allowed IPs of each ruleset |
| my $ruleset_allowed_ip_command = "esxcli --formatter=csv network firewall ruleset allowedip list"; |
| my ($ruleset_allowed_ip_exit_status, $ruleset_allowed_ip_output) = $self->vmhost_os->execute($ruleset_allowed_ip_command); |
| if (!defined($ruleset_allowed_ip_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command on VM host $vmhost_computer_name: $ruleset_allowed_ip_command"); |
| return; |
| } |
| elsif ($ruleset_allowed_ip_exit_status ne 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve firewall ruleset allowed IP info from VM host $vmhost_computer_name, exit status: $ruleset_allowed_ip_exit_status, command:\n$ruleset_allowed_ip_command\noutput:\n" . join("\n", @$ruleset_allowed_ip_output)); |
| return 0; |
| } |
| |
| # AllowedIPAddresses,Ruleset, |
| # "152.1.4.152,10.25.7.2,10.25.11.104,10.25.0.241,10.25.0.242,10.25.0.243,10.25.0.244,10.25.0.245,10.25.0.246,10.25.1.178,",sshServer, |
| # "All,",sshClient, |
| # ... |
| for my $line (@$ruleset_allowed_ip_output) { |
| if ($line =~ /Ruleset/) { |
| next; |
| } |
| |
| my ($ip_address_string, $ruleset_name) = $line =~ /^"?(.+),"?,([^,]+),/g; |
| if (!defined($ruleset_name)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve firewall ruleset allowed IP info from VM host $vmhost_computer_name, failed to parse line:\n$line"); |
| return; |
| } |
| |
| my @ip_addresses = split(/,/, $ip_address_string); |
| $ruleset_info->{$ruleset_name}{allowedip} = \@ip_addresses; |
| } |
| |
| # Get the rule port information |
| my $rule_list_command = "esxcli --formatter=csv network firewall ruleset rule list"; |
| my ($rule_list_exit_status, $rule_list_output) = $self->vmhost_os->execute($rule_list_command); |
| if (!defined($rule_list_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command on VM host $vmhost_computer_name: $rule_list_command"); |
| return; |
| } |
| elsif ($rule_list_exit_status ne 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve firewall rule info from VM host $vmhost_computer_name, exit status: $rule_list_exit_status, command:\n$rule_list_command\noutput:\n" . join("\n", @$rule_list_output)); |
| return 0; |
| } |
| |
| # Parse the header line |
| # Direction,PortBegin,PortEnd,PortType,Protocol,Ruleset, |
| my $rule_header_line = shift @$rule_list_output; |
| my @rule_fields = split(/,/, $rule_header_line); |
| my $rule_field_count = scalar(@rule_fields); |
| |
| # Inbound,22,22,Dst,TCP,sshServer, |
| # Outbound,22,22,Dst,TCP,sshClient, |
| # Outbound,0,65535,Dst,TCP,nfsClient, |
| # Outbound,2049,2049,Dst,TCP,nfs41Client, |
| for my $line (@$rule_list_output) { |
| if ($line !~ /bound/) { |
| next; |
| } |
| my @values = split(/,/, $line); |
| my $rule = {}; |
| for (my $i = 0; $i < $rule_field_count; $i++) { |
| my $field = $rule_fields[$i]; |
| my $value = $values[$i]; |
| $rule->{$field} = $value; |
| } |
| my $ruleset_name = $rule->{Ruleset}; |
| delete $rule->{Ruleset}; |
| |
| push @{$ruleset_info->{$ruleset_name}{rules}}, $rule; |
| } |
| |
| notify($ERRORS{'OK'}, 0, "retrieved firewall ruleset info from VM host $vmhost_computer_name:\n" . format_data($ruleset_info)); |
| $ENV{firewall_ruleset_info} = $ruleset_info; |
| return $ruleset_info; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_matching_firewall_ruleset_info |
| |
| Parameters : $port (optional), $direction (optional), $include_disabled (optional) |
| Returns : hash reference |
| Description : |
| |
| =cut |
| |
| sub get_matching_firewall_ruleset_info { |
| 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 ($direction_argument, $port_argument, $exclude_disabled) = @_; |
| if ($port_argument) { |
| if ($port_argument =~ /^(\*|any)$/) { |
| $port_argument = 'any'; |
| } |
| elsif ($port_argument !~ /^\d+$/) { |
| notify($ERRORS{'WARNING'}, 0, "port argument was specified but the value is not an integer: $port_argument"); |
| return; |
| } |
| } |
| else { |
| $port_argument = 'any'; |
| } |
| |
| if ($direction_argument) { |
| if ($direction_argument =~ /^(\*|any)$/) { |
| $direction_argument = 'any'; |
| } |
| elsif ($direction_argument =~ /in/i) { |
| $direction_argument = 'inbound'; |
| } |
| elsif ($direction_argument =~ /out/i) { |
| $direction_argument = 'outbound'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "direction argument is not valid: $direction_argument"); |
| return; |
| } |
| } |
| else { |
| $direction_argument = 'any'; |
| } |
| |
| my $vmhost_computer_name = $self->data->get_vmhost_short_name(); |
| |
| my $ruleset_info = $self->get_firewall_ruleset_info() || return; |
| |
| my $matching_ruleset_info = {}; |
| |
| RULESET: for my $ruleset_name (sort {lc($a) cmp lc($b)} keys %$ruleset_info) { |
| my $ruleset = $ruleset_info->{$ruleset_name}; |
| |
| # Ignore disabled rulesets if argument was supplied |
| my $enabled = $ruleset->{enabled}; |
| if (!$enabled && $exclude_disabled) { |
| next RULESET; |
| } |
| |
| RULE: for my $rule (@{$ruleset->{rules}}) { |
| if ($direction_argument ne 'any') { |
| my $direction = $rule->{Direction}; |
| if ($direction !~ /$direction_argument/i) { |
| #notify($ERRORS{'DEBUG'}, 0, "$ruleset_name direction does not match: argument: $direction_argument, rule: $direction"); |
| next RULE; |
| } |
| } |
| |
| if ($port_argument ne 'any') { |
| my $port_begin = $rule->{PortBegin}; |
| my $port_end = $rule->{PortEnd}; |
| if ($port_argument < $port_begin || $port_argument > $port_end) { |
| #notify($ERRORS{'DEBUG'}, 0, "$ruleset_name port does not match: argument: $port_argument, port begin: $port_begin, port end: $port_end"); |
| next RULE; |
| } |
| } |
| |
| $matching_ruleset_info->{$ruleset_name} = $ruleset; |
| } |
| } |
| |
| my $ruleset_count = scalar(keys %$matching_ruleset_info); |
| notify($ERRORS{'DEBUG'}, 0, "retrieved $ruleset_count matching firewall ruleset from VM host $vmhost_computer_name matching port: $port_argument, direction: $direction_argument, exclude disabled: " . ($exclude_disabled ? 'yes' : 'no') . "\n" . join("\n", sort keys %$matching_ruleset_info)); |
| return $matching_ruleset_info; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 is_firewall_port_allowed |
| |
| Parameters : $direction, $port, $remote_ip_address |
| Returns : boolean |
| Description : Checks if an enabled firewall ruleset exists which matches the |
| arguments. |
| |
| *** WARNING *** |
| There seems to be no reliable way to determine if a port is truly |
| open if a custom rule exists with identical port and definitions |
| as a standard service. The IBMIMM is an example. It defines |
| outbound port 22 as does sshClient. If both of these services are |
| enabled with different allowed IP address lists, the allowed IP |
| address list of the service which started last prevails. |
| Example: |
| * sshClient allows only 10.1.1.1 |
| * IBMIMM allows only 10.2.2.2 |
| * Restart sshClient : 10.1.1.1 allowed, 10.2.2.2 blocked |
| * Restart IBMIMM : 10.2.2.2 allowed, 10.1.1.1 blocked |
| |
| =cut |
| |
| sub is_firewall_port_allowed { |
| 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 ($direction_argument, $port_argument, $remote_ip_address) = @_; |
| |
| if (!defined($direction_argument)) { |
| notify($ERRORS{'WARNING'}, 0, "direction argument was not specified"); |
| return; |
| } |
| elsif ($direction_argument =~ /in/i) { |
| $direction_argument = 'inbound'; |
| } |
| elsif ($direction_argument =~ /out/i) { |
| $direction_argument = 'outbound'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "direction argument is not valid: $direction_argument"); |
| return; |
| } |
| |
| if (!defined($remote_ip_address)) { |
| notify($ERRORS{'WARNING'}, 0, "remote IP address argument was not specified"); |
| return; |
| } |
| elsif (!is_valid_ip_address($remote_ip_address)) { |
| notify($ERRORS{'WARNING'}, 0, "remote IP address argument is not valid: $remote_ip_address"); |
| return; |
| } |
| |
| my $vmhost_computer_name = $self->data->get_vmhost_short_name(); |
| |
| my $ruleset_info = $self->get_firewall_ruleset_info() || return; |
| |
| my $matching_ruleset_info = {}; |
| |
| RULESET: for my $ruleset_name (keys %$ruleset_info) { |
| my $ruleset = $ruleset_info->{$ruleset_name}; |
| |
| # Ignore disabled rulesets |
| if (!$ruleset->{enabled}) { |
| #notify($ERRORS{'DEBUG'}, 0, "$ruleset_name ruleset ignored because it is not enabled"); |
| next RULESET; |
| } |
| |
| my $direction_port_match = 0; |
| RULE: for my $rule (@{$ruleset->{rules}}) { |
| if ($rule->{Direction} !~ /$direction_argument/i) { |
| next RULE; |
| } |
| |
| if ($port_argument >= $rule->{PortBegin} && $port_argument <= $rule->{PortEnd}) { |
| $direction_port_match = 1; |
| #notify($ERRORS{'DEBUG'}, 0, "$ruleset_name ruleset rule matches direction: $direction_argument, port: $port_argument\n" . format_data($rule)); |
| last RULE; |
| } |
| } |
| if (!$direction_port_match) { |
| next RULESET; |
| } |
| |
| my @allowed_ip_addresses = @{$ruleset->{allowedip}}; |
| if ($allowed_ip_addresses[0] =~ /all/i) { |
| notify($ERRORS{'DEBUG'}, 0, "$ruleset_name ruleset on VM host $vmhost_computer_name allows $direction_argument port $port_argument " . ($direction_argument =~ /in/i ? 'from' : 'to') . " all:\n" . format_data($ruleset)); |
| next RULESET; |
| #return 1; |
| } |
| elsif (grep { $_ eq $remote_ip_address } @allowed_ip_addresses) { |
| notify($ERRORS{'DEBUG'}, 0, "$ruleset_name ruleset on VM host $vmhost_computer_name allows $direction_argument port $port_argument " . ($direction_argument =~ /in/i ? 'from' : 'to') . " $remote_ip_address:\n" . format_data($ruleset)); |
| next RULESET; |
| #return 1; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "$ruleset_name ruleset on VM host $vmhost_computer_name does NOT allow $direction_argument port $port_argument " . ($direction_argument =~ /in/i ? 'from' : 'to') . " $remote_ip_address:\n" . format_data($ruleset)); |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "$direction_argument firewall port $port_argument is NOT allowed for $remote_ip_address on VM host $vmhost_computer_name"); |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 _get_host_summary |
| |
| Parameters : none |
| Returns : hash reference |
| Description : Runs "vim-cmd hostsvc/hostsummary" to retrive various information |
| about the VMware host. |
| |
| =cut |
| |
| sub _get_host_summary { |
| 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->{host_summary} if $self->{host_summary}; |
| |
| my $vim_cmd_arguments = "hostsvc/hostsummary"; |
| my ($exit_status, $output) = $self->_run_vim_cmd($vim_cmd_arguments); |
| return if !$output; |
| |
| # The output should look like this: |
| #(vim.host.Summary) { |
| # host = 'vim.HostSystem:ha-host', |
| # hardware = (vim.host.Summary.HardwareSummary) { |
| # vendor = "IBM", |
| # model = "BladeCenter HS22 -[7870AC1]-", |
| # ... |
| |
| # The hash keys are: |
| # { |
| # config = { |
| # agentVmDatastore = '', |
| # agentVmNetwork = '', |
| # faultToleranceEnabled = '', |
| # featureVersion = '', |
| # name = '', |
| # port = '', |
| # product = { |
| # apiType = '', |
| # apiVersion = '', |
| # build = '', |
| # fullName = '', |
| # instanceUuid = '', |
| # licenseProductName = '', |
| # licenseProductVersion = '', |
| # localeBuild = '', |
| # localeVersion = '', |
| # name = '', |
| # osType = '', |
| # productLineId = '', |
| # vendor = '', |
| # version = '', |
| # }, |
| # sslThumbprint = '', |
| # vmotionEnabled = '', |
| # }, |
| # currentEVCModeKey = '', |
| # customValue = '', |
| # gateway = '', |
| # hardware = { |
| # cpuMhz = '', |
| # cpuModel = '', |
| # memorySize = '', |
| # model = '', |
| # numCpuCores = '', |
| # numCpuPkgs = '', |
| # numCpuThreads = '', |
| # numHBAs = '', |
| # numNics = '', |
| # otherIdentifyingInfo = '', |
| # uuid = '', |
| # vendor = '', |
| # }, |
| # host = '', |
| # managementServerIp = '', |
| # maxEVCModeKey = '', |
| # overallStatus = '', |
| # quickStats = { |
| # distributedCpuFairness = '', |
| # distributedMemoryFairness = '', |
| # overallCpuUsage = '', |
| # overallMemoryUsage = '', |
| # uptime = '', |
| # }, |
| # rebootRequired = '', |
| # runtime = { |
| # bootTime = '', |
| # connectionState = '', |
| # cpuCapacityForVm = '', |
| # cryptoKeyId = '', |
| # cryptoState = '', |
| # dasHostState = '', |
| # healthSystemRuntime = { |
| # hardwareStatusInfo = { |
| # cpuStatusInfo => [], |
| # memoryStatusInfo => [], |
| # storageStatusInfo = '', |
| # }, |
| # systemHealthInfo = { |
| # numericSensorInfo => [], |
| # }, |
| # }, |
| # hostMaxVirtualDiskCapacity = '', |
| # inMaintenanceMode = '', |
| # inQuarantineMode = '', |
| # memoryCapacityForVm = '', |
| # networkRuntimeInfo = { |
| # netStackInstanceRuntimeInfo => [], |
| # networkResourceRuntime = '', |
| # }, |
| # powerState = '', |
| # standbyMode = '', |
| # tpmPcrValues = '', |
| # vFlashResourceRuntimeInfo = '', |
| # vsanRuntimeInfo = { |
| # accessGenNo = '', |
| # diskIssues = '', |
| # membershipList = '', |
| # }, |
| # }, |
| # } |
| |
| |
| if (!grep(/vim\.host\.Summary/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve host summary, unexpected output returned, VIM command arguments: '$vim_cmd_arguments', output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| my $host_summary_info = $self->_parse_vim_cmd_output($output); |
| if (defined($host_summary_info->{'vim.host.Summary'})) { |
| $self->{host_summary} = $host_summary_info->{'vim.host.Summary'}; |
| return $self->{host_summary}; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve host summary, parsed output does not contain a 'vim.host.Summary' key:\n" . format_data($host_summary_info)); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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 |
|