Author: arkurth
Date: Fri Aug 6 19:38:53 2010
New Revision: 983104
URL: http://svn.apache.org/viewvc?rev=983104&view=rev
Log:
VCL-289
Created Provisioning.pm::retrieve_image subroutine based on the code from the
existing xCAT and VMware modules. Added Semaphore.pm which contains code to
create an exclusively locked file and Module.pm::get_semaphore for easy
access/creation. The retrieve_image subroutine creates a Semaphore object
before retrieving an image from another management node. This ensures that
only 1 retrieval for a given image happens at a time. Added
get_image_repository_path and get_image_repository_search_paths subroutines to
VMware.pm allowing it to use the new retrieve_image subroutine. Also removed
get_lockfile and release_lockfile from VMware.pm. They were put there during
development.
VCL-100
Updated Linux.pm::get_file_size to return the bytes actually used rather than
the apparent size of a file. This allows an accurate size to be returned when
checking the size of thin vmdk files whose different size values may differ
widely.
VCL-298
Updated VMware.pm:
Modified is_vm_persistent to return true if the reservation end time is more
than 24 hours in the future.
Updated in get_image_size so that it works correctly if an image name is passed
as an argument. This is done by image.pm.
Fixed bug in get_vm_disk_adapter_type. It was returning IDE if using ESX in
some situations which doesn't work.
Other
Made minor change to OS.pm::is_ssh_responding so that it doesn't rely on the
exit status of the SSH command.
In utils.pm::run_ssh_command, added '2>&1' so it is applied to both the remote
command being run and the local SSH command. This should allow STDOUT and
STDERR output to be captured and synchronized.
Added:
incubator/vcl/trunk/managementnode/lib/VCL/Module/Semaphore.pm (with
props)
Modified:
incubator/vcl/trunk/managementnode/lib/VCL/Module.pm
incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm
incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm
incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning.pm
incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm
incubator/vcl/trunk/managementnode/lib/VCL/utils.pm
Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module.pm
URL:
http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module.pm?rev=983104&r1=983103&r2=983104&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module.pm Fri Aug 6 19:38:53
2010
@@ -86,6 +86,7 @@ use English '-no_match_vars';
use VCL::utils qw($SETUP_MODE $VERBOSE %ERRORS ¬ify &getnewdbh format_data);
use VCL::DataStructure;
+use VCL::Module::Semaphore;
##############################################################################
@@ -130,10 +131,10 @@ sub new {
my $args = shift;
notify($ERRORS{'DEBUG'}, 0, "constructor called, class=$class");
-
+
# Create a variable to store the newly created class object
my $class_object;
-
+
# Make sure the data structure was passed as an argument called
'data_structure'
if (!defined $args->{data_structure}) {
notify($ERRORS{'CRITICAL'}, 0, "required 'data_structure'
argument was not passed");
@@ -145,7 +146,7 @@ sub new {
notify($ERRORS{'CRITICAL'}, 0, "'data_structure' argument
passed is not a reference to a VCL::DataStructure object");
return;
}
-
+
# Add the DataStructure reference to the class object
$class_object->{data} = $args->{data_structure};
@@ -334,7 +335,7 @@ sub get_package_hierarchy {
sub code_loop_timeout {
my $self = shift;
- if (ref($self) !~ /VCL::Module/i) {
+ unless (ref($self) && $self->isa('VCL::Module')) {
notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a
function, it must be called as a class method");
return;
}
@@ -422,6 +423,82 @@ sub code_loop_timeout {
#/////////////////////////////////////////////////////////////////////////////
+=head2 get_semaphore
+
+ Parameters : $file_path, $total_wait_seconds (optional),
$attempt_delay_seconds (optional)
+ Returns : VCL::Module::Semaphore object
+ Description : This subroutine is used to ensure that only 1 process performs a
+ particular task at a time. An example would be the retrieval of
+ an image from another management node. If multiple reservations
+ are being processed for the same image, each reservation may
+ attempt to retrieve it via SCP at the same time. This subroutine
+ can be used to only allow 1 process to retrieve the image. The
+ others will wait until the semaphore is released by the
+ retrieving process.
+
+ Attempts to open and obtain an exclusive lock on the file
+ specified by the file path argument. If unable to obtain an
+ exclusive lock, it will wait up to the value specified by the
+ total wait seconds argument (default: 30 seconds). The number of
+ seconds to wait in between retries can be specified (default: 15
+ seconds).
+
+ A semaphore object is returned. The exclusive lock will be
+ retained as long as the semaphore object remains defined. Once
+ undefined, the exclusive lock is released and the file is
+ deleted.
+
+ Examples:
+
+ Semaphore is released when it is undefined:
+ my $semaphore = $self->get_semaphore('/tmp/test.lock');
+ ... <exclusive lock is in place>
+ undef $semaphore;
+ ... <exclusive lock released>
+
+ Semaphore is released when it goes out of scope:
+ if (blah) {
+ my $semaphore = $self->get_semaphore('/tmp/test.lock');
+ ... <exclusive lock is in place>
+ }
+ ... <exclusive lock released>
+
+=cut
+
+sub get_semaphore {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a
function, it must be called as a class method");
+ return;
+ }
+
+ # Get the file path argument
+ my ($file_path, $total_wait_seconds, $attempt_delay_seconds) = @_;
+ if (!$file_path) {
+ notify($ERRORS{'WARNING'}, 0, "file path argument was not
supplied");
+ return;
+ }
+
+ # Attempt to create a new semaphore object
+ my $semaphore = VCL::Module::Semaphore->new({'data_structure' =>
$self->data});
+ if (!$semaphore) {
+ notify($ERRORS{'WARNING'}, 0, "failed to create semaphore
object");
+ return;
+ }
+
+ # Attempt to open and exclusively lock the file
+ if ($semaphore->get_lockfile($file_path, $total_wait_seconds,
$attempt_delay_seconds)) {
+ # Return the semaphore object
+ return $semaphore;
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "failed to open and optain
exclusive lock on file: $file_path");
+ return;
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
1;
__END__
Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm
URL:
http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm?rev=983104&r1=983103&r2=983104&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm Fri Aug 6 19:38:53
2010
@@ -426,7 +426,7 @@ sub is_ssh_responding {
});
# The exit status will be 0 if the command succeeded
- if (defined($exit_status) && $exit_status == 0) {
+ if (defined($output) && grep(/testing/, @$output)) {
notify($ERRORS{'DEBUG'}, 0, "$computer_node_name is responding
to SSH, port 22: $port_22_status, port 24: $port_24_status");
return 1;
}
Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm
URL:
http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm?rev=983104&r1=983103&r2=983104&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm Fri Aug 6
19:38:53 2010
@@ -1859,7 +1859,7 @@ sub get_file_size {
my $computer_node_name = $self->data->get_computer_node_name() ||
return;
# Run stat rather than du because du is not available on VMware ESX
- my $command = 'stat -c "%F:%s:%n" ' . $escaped_file_path;
+ my $command = 'stat -c "%F:%s:%b:%B:%n" ' . $escaped_file_path;
my ($exit_status, $output) = $self->execute($command);
if (!defined($output)) {
notify($ERRORS{'WARNING'}, 0, "failed to run command to
determine file size on $computer_node_name: $file_path\ncommand: $command");
@@ -1871,23 +1871,25 @@ sub get_file_size {
}
# Loop through the stat output lines
- my $total_bytes = 0;
+ my $total_bytes_used = 0;
+ my $total_bytes_allocated = 0;
for my $line (@$output) {
# Take the stat output line apart
- my ($type, $file_bytes, $path) = split(/:/, $line);
- if (!defined($type) || !defined($file_bytes) ||
!defined($path)) {
+ my ($type, $file_bytes, $file_blocks, $block_size, $path) =
split(/:/, $line);
+ if (!defined($type) || !defined($file_bytes) ||
!defined($file_blocks) || !defined($block_size) || !defined($path)) {
notify($ERRORS{'WARNING'}, 0, "unexpected output
returned from stat, line: $line\ncommand: $command\noutput:\n" . join("\n",
@$output));
return;
}
# Add the size to the total if the type is file
if ($type =~ /file/) {
- $total_bytes += $file_bytes;
+ $total_bytes_used += ($file_blocks * $block_size);
+ $total_bytes_allocated += $file_bytes;
}
}
- notify($ERRORS{'DEBUG'}, 0, "size of $file_path on $computer_node_name:
" . format_number($total_bytes) . " bytes");
- return $total_bytes;
+ notify($ERRORS{'DEBUG'}, 0, "size of $file_path on $computer_node_name:
" . format_number($total_bytes_used) . " bytes, (" .
format_number($total_bytes_allocated) . " bytes allocated)");
+ return $total_bytes_used;
}
#/////////////////////////////////////////////////////////////////////////////
Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning.pm
URL:
http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning.pm?rev=983104&r1=983103&r2=983104&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning.pm Fri Aug
6 19:38:53 2010
@@ -140,6 +140,182 @@ sub vmhost_os {
#/////////////////////////////////////////////////////////////////////////////
+=head2 retrieve_image
+
+ Parameters : none
+ Returns : boolean
+ Description : Retrieves an image from another management node.
+
+=cut
+
+sub retrieve_image {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called
as a VCL::Module module object method");
+ return;
+ }
+
+ # Make sure image library functions are enabled
+ my $image_lib_enable =
$self->data->get_management_node_image_lib_enable();
+ if (!$image_lib_enable) {
+ notify($ERRORS{'OK'}, 0, "image retrieval skipped, image
library functions are disabled for this management node");
+ return;
+ }
+
+ # Get the image name from the reservation data
+ my $image_name = $self->data->get_image_name();
+ if (!$image_name) {
+ notify($ERRORS{'WARNING'}, 0, "failed to determine image name
from reservation data");
+ return;
+ }
+
+ # Get the last digit of the reservation ID and sleep that number of
seconds
+ # This is done in case 2 reservations for the same image were started
at the same time
+ # Both may attempt to retrieve an image and execute the SCP command at
nearly the same time
+ # does_image_exist() may not catch this and allow 2 SCP retrieval
processes to start
+ # It's likely that the reservation IDs are consecutive and the the last
digits will be different
+ my ($pre_retrieval_sleep) = $self->data->get_reservation_id() =~
/(\d)$/;
+ notify($ERRORS{'DEBUG'}, 0, "sleeping for $pre_retrieval_sleep seconds
to prevent multiple SCP image retrieval processes");
+ sleep $pre_retrieval_sleep;
+
+ # Get a semaphore so only 1 process is able to retrieve the image at a
time
+ # Do this before checking if the image exists in case another process
is retrieving the image
+ my $semaphore = $self->get_semaphore("/tmp/retrieve_$image_name.lock",
(60 * 15)) || return;
+
+ # Make sure image does not already exist on this management node
+ if ($self->does_image_exist($image_name)) {
+ notify($ERRORS{'OK'}, 0, "$image_name already exists on this
management node");
+ return 1;
+ }
+
+ # Get the image library partner string
+ my $image_lib_partners =
$self->data->get_management_node_image_lib_partners();
+ if (!$image_lib_partners) {
+ notify($ERRORS{'WARNING'}, 0, "image library partners could not
be determined");
+ return;
+ }
+
+ # Split up the partner list
+ my @partner_list = split(/,/, $image_lib_partners);
+ if ((scalar @partner_list) == 0) {
+ notify($ERRORS{'WARNING'}, 0, "image lib partners variable is
not listed correctly or does not contain any information: $image_lib_partners");
+ return;
+ }
+
+ # Get the local image repository path
+ my $image_repository_path_local = $self->get_image_repository_path();
+ if (!$image_repository_path_local) {
+ notify($ERRORS{'WARNING'}, 0, "unable to determine the local
image repository path");
+ return;
+ }
+
+ # Loop through the partners
+ # Find partners which have the image
+ # Check size for each partner
+ # Retrieve image from partner with largest image
+ # It's possible that another partner (management node) is currently
copying the image from another managment node
+ # This should prevent copying a partial image
+ my %partner_info;
+ my $largest_partner_image_size = 0;
+ my @partners_with_image;
+
+ foreach my $partner (@partner_list) {
+ # Get the connection information for the partner management node
+ $partner_info{$partner}{hostname} =
$self->data->get_management_node_hostname($partner);
+ $partner_info{$partner}{user} =
$self->data->get_management_node_image_lib_user($partner) || 'root';
+ $partner_info{$partner}{identity_key} =
$self->data->get_management_node_image_lib_key($partner) || '';
+ $partner_info{$partner}{port} =
$self->data->get_management_node_ssh_port($partner) || '22';
+
+ # Call the provisioning module's
get_image_repository_search_paths() subroutine
+ # This returns an array of strings to pass to du
+ $partner_info{$partner}{search_paths} =
[$self->get_image_repository_search_paths($partner)];
+ if (!$partner_info{$partner}{search_paths}) {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieve image
repository search paths for partner: $partner");
+ next;
+ }
+
+ # Run du to get the size of the image files on the partner if
the image exists in any of the search paths
+ my $du_command = "du -b " . join(" ",
@{$partner_info{$partner}{search_paths}});
+ my ($du_exit_status, $du_output) = run_ssh_command($partner,
$partner_info{$partner}{identity_key}, $du_command,
$partner_info{$partner}{user}, $partner_info{$partner}{port}, 0);
+ if (!defined($du_output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to run SSH
command to determine if image $image_name exists on
$partner_info{$partner}{hostname}");
+ next;
+ }
+
+ # Loop through the du output lines, parse lines beginning with
a number followed by a '/'
+ for my $line (@$du_output) {
+ my ($file_size, $file_path) = $line =~
/^(\d+)\s+(\/.+)/;
+ next if (!defined($file_size) || !defined($file_path));
+ $partner_info{$partner}{file_paths}{$file_path} =
$file_size;
+ $partner_info{$partner}{image_size} += $file_size;
+ }
+
+ # Display the image size if any files were found
+ if ($partner_info{$partner}{image_size}) {
+ notify($ERRORS{'OK'}, 0, "$image_name exists on
$partner_info{$partner}{hostname}, size: " .
format_number($partner_info{$partner}{image_size}) . " bytes");
+ }
+ else {
+ notify($ERRORS{'OK'}, 0, "$image_name does NOT exist on
$partner_info{$partner}{hostname}");
+ next;
+ }
+
+ # Check if the image size is larger than any previously found
on other partners
+ if ($partner_info{$partner}{image_size} >
$largest_partner_image_size) {
+ @partners_with_image = ();
+ }
+
+ # Check if the image size is larger than any previously found
on other partners
+ if ($partner_info{$partner}{image_size} >=
$largest_partner_image_size) {
+ push @partners_with_image, $partner;
+ $largest_partner_image_size =
$partner_info{$partner}{image_size};
+ }
+ }
+
+ # Check if any partner was found
+ if (!...@partners_with_image) {
+ notify($ERRORS{'WARNING'}, 0, "unable to find $image_name on
other management nodes");
+ return;
+ }
+
+ notify($ERRORS{'OK'}, 0, "found $image_name on partner management
nodes:\n" . join("\n", map { $partner_info{$_}{hostname} } (sort
@partners_with_image)));
+
+ # Choose a random partner so that the same management node isn't used
for most transfers
+ my $random_index = int(rand(scalar(@partners_with_image)));
+ my $retrieval_partner = $partners_with_image[$random_index];
+ notify($ERRORS{'OK'}, 0, "selected random retrieval partner:
$partner_info{$retrieval_partner}{hostname}");
+
+ # Create the directory in the image repository
+ my $mkdir_command = "mkdir -pv $image_repository_path_local";
+ my ($mkdir_exit_status, $mkdir_output) = run_command($mkdir_command);
+ if (!defined($mkdir_output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to run command to create
image repository directory: $mkdir_command");
+ return;
+ }
+
+ # Copy each file path to the image repository directory
+ notify($ERRORS{'OK'}, 0, "attempting to retrieve $image_name from
$partner_info{$retrieval_partner}{hostname}");
+ for my $partner_file_path (sort keys
%{$partner_info{$retrieval_partner}{file_paths}}) {
+ my ($file_name) = $partner_file_path =~ /([^\/]+)$/;
+ if
(run_scp_command("$partner_info{$retrieval_partner}{use...@$retrieval_partner:$partner_file_path",
"$image_repository_path_local/$file_name",
$partner_info{$retrieval_partner}{key},
$partner_info{$retrieval_partner}{port})) {
+ notify($ERRORS{'OK'}, 0, "image $image_name was copied from
$partner_info{$retrieval_partner}{hostname}");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to copy image
$image_name from $partner_info{$retrieval_partner}{hostname}");
+ return;
+ }
+ }
+
+ # Make sure image was retrieved
+ if (!$self->does_image_exist($image_name)) {
+ notify($ERRORS{'WARNING'}, 0, "does_image_exist subroutine
returned false for $image_name after it should have been retrieved");
+ return;
+ }
+
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
1;
__END__
Modified:
incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm
URL:
http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm?rev=983104&r1=983103&r2=983104&view=diff
==============================================================================
---
incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm
(original)
+++
incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm
Fri Aug 6 19:38:53 2010
@@ -276,6 +276,9 @@ sub initialize {
# Store the VM host API object in this object
$self->{api} = $vmware_api;
+ # Make sure the VMware product name can be retrieved
+ $self->get_vmhost_product_name() || return;
+
# Make sure the vmx and vmdk base directories can be accessed
my $vmx_base_directory_path = $self->get_vmx_base_directory_path() ||
return;
if (!$vmhost_os->file_exists($vmx_base_directory_path)) {
@@ -461,9 +464,6 @@ sub capture {
sleep 5;
}
- ## Get a lockfile so that only 1 process is operating on VM host files
at any one time
- #my $lockfile = $self->get_lockfile("/tmp/$vmhost_hostname.lock", (60 *
10)) || return;
-
# Rename the vmdk files on the VM host and change the vmdk directory
name to the image name
# This ensures that the vmdk and vmx files now reside in different
directories
# so that the vmdk files can't be deleted when the vmx directory is
deleted later on
@@ -1997,13 +1997,77 @@ sub get_vmdk_file_path {
#/////////////////////////////////////////////////////////////////////////////
+=head2 get_image_repository_path
+
+ Parameters : $management_node_identifier (optional)
+ Returns :
+ Description :
+
+=cut
+
+sub get_image_repository_path {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called
as a VCL::Module module object method");
+ return;
+ }
+
+ return $self->get_repository_vmdk_directory_path();
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_image_repository_search_paths
+
+ Parameters :
+ Returns :
+ Description :
+
+=cut
+
+sub get_image_repository_search_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 $management_node_identifier = shift ||
$self->data->get_management_node_hostname();
+
+ my $image_name = $self->data->get_image_name();
+
+ my @repository_search_paths;
+
+ if (my $vmhost_profile_repository_path =
$self->data->get_vmhost_profile_repository_path()) {
+ push @repository_search_paths,
"$vmhost_profile_repository_path/$image_name/$image_name*.vmdk";
+ }
+
+ if (my $management_node_install_path =
$self->data->get_management_node_install_path($management_node_identifier)) {
+ push @repository_search_paths,
"$management_node_install_path/vmware_images/$image_name/$image_name*.vmdk";
+ push @repository_search_paths,
"$management_node_install_path/$image_name/$image_name*.vmdk";
+ }
+
+ push @repository_search_paths,
"/install/vmware_images/$image_name/$image_name*.vmdk";
+
+ my %seen;
+ @repository_search_paths = grep { !$seen{$_}++ }
@repository_search_paths;
+ #notify($ERRORS{'DEBUG'}, 0, "repository search paths on
$management_node_identifier:\n" . join("\n", @repository_search_paths));
+ return @repository_search_paths;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
=head2 get_repository_vmdk_base_directory_path
Parameters : none
Returns : string
Description : Returns the image repository directory path on the management
- node under which the vmdk directories for all of the images
- reside. The preferred database value to use is
vmprofile.repositorypath. If this is not available, managementnode.installpath
is retrieved and "/vmware_images" is appended. If this is not available,
"/install/vmware
+ node under which the vmdk directories
for all of the images
+ reside. The preferred database value to
use is
+ vmprofile.repositorypath. If this is
not available,
+ managementnode.installpath is retrieved
and "/vmware_images" is
+ appended. If this is not available,
"/install/vmware_images" is
+ returned.
Example:
repository vmdk file path:
/install/vmware_images/vmwarewinxp-base234-v12/vmwarewinxp-base234-v12.vmdk
repository vmdk base directory path: /install/vmware_images
@@ -2018,9 +2082,6 @@ sub get_repository_vmdk_base_directory_p
return;
}
- # Return the path stored in this object if it has already been
determined
- return $self->{repository_base_directory_path} if (defined
$self->{repository_base_directory_path});
-
my $repository_vmdk_base_directory_path;
# Attempt the retrieve vmhost.repositorypath
@@ -2037,9 +2098,7 @@ sub get_repository_vmdk_base_directory_p
notify($ERRORS{'WARNING'}, 0, "unable to retrieve repository
path from VM profile or management node install path, returning
'/install/vmware_images'");
}
- # Set a value in this object so this doesn't have to be retrieved more
than once
- $self->{repository_base_directory_path} =
$repository_vmdk_base_directory_path;
- return $self->{repository_base_directory_path};
+ return $repository_vmdk_base_directory_path;
}
#/////////////////////////////////////////////////////////////////////////////
@@ -2098,7 +2157,8 @@ sub get_repository_vmdk_file_path {
Parameters : none
Returns : boolean
Description : Determines if a VM should be persistent or not based on whether
- or not the reservation is an imaging reservation.
+ or not the reservation is an imaging reservation or if the end
+ time is more than 24 hours in the future.
=cut
@@ -2113,9 +2173,17 @@ sub is_vm_persistent {
if ($request_forimaging) {
return 1;
}
- else {
- return 0;
+
+ # Return true if the request end time is more than 24 hours in the
future
+ my $end_epoch =
convert_to_epoch_seconds($self->data->get_request_end_time());
+ my $now_epoch = time();
+ my $end_hours = (($end_epoch - $now_epoch) / 60 / 60);
+ if ($end_hours >= 24) {
+ notify($ERRORS{'DEBUG'}, 0, "request end time is " .
format_number($end_hours, 1) . " hours in the future, returning true");
+ return 1;
}
+
+ return 0;
}
#/////////////////////////////////////////////////////////////////////////////
@@ -2158,7 +2226,7 @@ sub is_vm_registered {
=head2 get_image_size
- Parameters : $vmdk_file_path (optional)
+ Parameters : $image_name (optional)
Returns : integer
Description : Returns the size of the image in bytes. If the vmdk file path
argument is not supplied and the VM disk type in the VM profile
@@ -2176,73 +2244,67 @@ sub get_image_size {
}
my $vmhost_hostname = $self->data->get_vmhost_hostname() || return;
- my $vmprofile_vmdisk = $self->data->get_vmhost_profile_vmdisk() ||
return;
- my $image_name = $self->data->get_image_name() || return;
- # Attempt to get the vmdk file path argument
- # If not supplied, use the default vmdk file path for this reservation
- my $vmdk_file_path = shift;
-
- # Try to retrieve the image size from the repository if an argument was
not supplied and localdisk is being used
- if (!$vmdk_file_path && $vmprofile_vmdisk eq "localdisk") {
- my $repository_vmdk_directory_path =
$self->get_repository_vmdk_directory_path() || return;
- notify($ERRORS{'DEBUG'}, 0, "vm disk type is $vmprofile_vmdisk,
checking size of vmdk directory in image repository:
$repository_vmdk_directory_path");
+ # Attempt to get the image name argument
+ my $image_name = shift;
+ if (!$image_name) {
+ $image_name = $self->data->get_image_name() || return;
+ }
+
+ my $image_size_bytes;
+
+ # Try to retrieve the image size from the repository if localdisk is
being used
+ if (my $repository_vmdk_base_directory_path =
$self->get_repository_vmdk_base_directory_path()) {
+
+ my $search_path =
"$repository_vmdk_base_directory_path/$image_name/$image_name*.vmdk";
+
+ notify($ERRORS{'DEBUG'}, 0, "checking size of image in image
repository: $search_path");
# Run du specifying image repository directory as an argument
- my ($exit_status, $output) = run_command("du -bc
\"$repository_vmdk_directory_path\"", 1);
+ my ($exit_status, $output) = run_command("du -bc $search_path",
1);
if (!defined($output)) {
- notify($ERRORS{'WARNING'}, 0, "failed to run command to
determine size of vmdk directory in image repository:
$repository_vmdk_directory_path");
+ notify($ERRORS{'WARNING'}, 0, "failed to run command to
determine size of image in image repository: $search_path");
+ }
+ elsif (grep(/no such file/i, @$output)) {
+ notify($ERRORS{'DEBUG'}, 0, "image does not exist in
image repository");
}
elsif (grep(/du: /i, @$output)) {
- notify($ERRORS{'WARNING'}, 0, "error occurred
attempting to determine size of vmdk directory in image repository:
$repository_vmdk_directory_path, output:\n" . join("\n", @$output));
+ notify($ERRORS{'WARNING'}, 0, "error occurred
attempting to determine size of image in image repository: $search_path,
output:\n" . join("\n", @$output));
}
- else {
- my ($total_line) = grep(/total/, @$output);
- if (!$total_line) {
- notify($ERRORS{'WARNING'}, 0, "unable to locate
'total' line in du output while attempting to determine size of vmdk directory
in image repository: $repository_vmdk_directory_path, output:\n" . join("\n",
@$output));
+ elsif (my ($total_line) = grep(/total/, @$output)) {
+ ($image_size_bytes) = $total_line =~ /(\d+)/;
+ if (defined($image_size_bytes)) {
+ notify($ERRORS{'DEBUG'}, 0, "retrieved size of
image in image repository: $image_size_bytes");
}
else {
- my ($bytes_used) = $total_line =~ /(\d+)/;
- if ($bytes_used =~ /^\d+$/) {
- my $mb_used =
format_number(($bytes_used / 1024 / 1024), 1);
- my $gb_used =
format_number(($bytes_used / 1024 / 1024 / 1024), 2);
- notify($ERRORS{'DEBUG'}, 0, "size of
$image_name image in image repository: " . format_number($bytes_used) . " bytes
($mb_used MB, $gb_used GB)");
- return $bytes_used;
- }
- else {
- notify($ERRORS{'WARNING'}, 0, "failed
to parse du output to determine size of vmdk directory in image repository:
$repository_vmdk_directory_path, output:\n" . join("\n", @$output));
- return;
- }
+ notify($ERRORS{'WARNING'}, 0, "failed to parse
du output to determine size of vmdk directory in image repository:
$search_path, output:\n" . join("\n", @$output));
}
}
+ else {
+ notify($ERRORS{'WARNING'}, 0, "unable to locate 'total'
line in du output while attempting to determine size of vmdk directory in image
repository: $search_path, output:\n" . join("\n", @$output));
+ }
}
- # Get the vmdk file path if not specified as an argument
- if (!$vmdk_file_path) {
- $vmdk_file_path = $self->get_vmdk_file_path() || return;
- }
-
- # Extract the directory path and file prefix from the vmdk file path
- my ($vmdk_directory_path, $vmdk_file_prefix) = $vmdk_file_path =~
/^(.+)\/([^\/]+)\.vmdk$/;
- if (!$vmdk_directory_path || !$vmdk_file_prefix) {
- notify($ERRORS{'WARNING'}, 0, "unable to determine vmdk
directory path and vmdk file prefix from vmdk file path: $vmdk_file_path");
- return;
+ # Unable to determine the image size from the image repository, attempt
to retrieve size from VM host
+ if (!defined($image_size_bytes)) {
+ # Assemble a search path
+ my $vmdk_base_directory_path =
$self->get_vmdk_base_directory_path() || return;
+ my $search_path =
"$vmdk_base_directory_path/$image_name/$image_name*.vmdk";
+
+ # Get the size of the files on the VM host
+ $image_size_bytes =
$self->vmhost_os->get_file_size($search_path);
}
- # Assemble a search path
- my $vmdk_search_path = "$vmdk_directory_path/$vmdk_file_prefix*.vmdk";
- # Get the size of the files on the VM host
- my $vmdk_size = $self->vmhost_os->get_file_size($vmdk_search_path);
- if (!defined($vmdk_size)) {
- notify($ERRORS{'WARNING'}, 0, "unable to determine the size of
vmdk file on VM host $vmhost_hostname:
- vmdk file path: $vmdk_file_path
- search path: $vmdk_search_path");
+ if (!defined($image_size_bytes)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to determine the size of
image in image repository or on VM host");
return;
}
- notify($ERRORS{'DEBUG'}, 0, "size of $image_name image on VM host
$vmhost_hostname: " . format_number($vmdk_size) . " bytes");
- return $vmdk_size;
+ my $mb_used = format_number(($image_size_bytes / 1024 / 1024));
+ my $gb_used = format_number(($image_size_bytes / 1024 / 1024 / 1024),
2);
+ notify($ERRORS{'DEBUG'}, 0, "size of $image_name image: " .
format_number($image_size_bytes) . " bytes ($mb_used MB, $gb_used GB)");
+ return $image_size_bytes;
}
#/////////////////////////////////////////////////////////////////////////////
@@ -2414,15 +2476,15 @@ sub get_vm_disk_adapter_type {
my $vm_host_product_name = $self->get_vmhost_product_name() || return;
my $vmdk_controller_type;
- if ($self->api->can("get_virtual_disk_controller_type")) {
- $vmdk_controller_type =
$self->api->get_virtual_disk_controller_type($self->get_vmdk_file_path());
+
+ if ($self->api->can("get_virtual_disk_controller_type") &&
($vmdk_controller_type =
$self->api->get_virtual_disk_controller_type($self->get_vmdk_file_path()))) {
notify($ERRORS{'DEBUG'}, 0, "retrieved VM disk adapter type
from api object: $vmdk_controller_type");
}
elsif ($vmdk_controller_type =
$self->get_vmdk_parameter_value('adapterType')) {
notify($ERRORS{'DEBUG'}, 0, "retrieved VM disk adapter type
from vmdk file: $vmdk_controller_type");
}
- elsif (!$vmdk_controller_type || ($vm_host_product_name =~ /esx/i &&
$vmdk_controller_type =~ /ide/i)) {
-
+
+ if (!$vmdk_controller_type || ($vm_host_product_name =~ /esx/i &&
$vmdk_controller_type =~ /ide/i)) {
my $vm_os_configuration = $self->get_vm_os_configuration();
if (!$vm_os_configuration) {
notify($ERRORS{'WARNING'}, 0, "unable to determine VM
disk adapter type because unable to retrieve default VM OS configuration");
@@ -2622,113 +2684,6 @@ sub get_vm_ram {
#/////////////////////////////////////////////////////////////////////////////
-=head2 get_lockfile
-
- Parameters : $file_path, $total_wait_seconds (optional),
$attempt_delay_seconds (optional)
- Returns : filehandle
- Description : Attempts to open and obtain an exclusive lock on the file
- specified by the file path argument. If unable to obtain an
- exclusive lock, it will wait up to the value specified by the
- total wait seconds argument (default: 30 seconds). The number of
- seconds to wait in between retries can be specified (default: 15
- seconds).
-
-=cut
-
-sub get_lockfile {
- my $self = shift;
- if (ref($self) !~ /Module/i) {
- notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a
function, it must be called as a class method");
- return;
- }
-
- # Get the file path argument
- my ($file_path, $total_wait_seconds, $attempt_delay_seconds) = @_;
- if (!$file_path) {
- notify($ERRORS{'WARNING'}, 0, "file path argument was not
supplied");
- return;
- }
-
- # Set the wait defaults if not supplied as arguments
- $total_wait_seconds = 30 if !$total_wait_seconds;
- $attempt_delay_seconds = 5 if !$attempt_delay_seconds;
-
- # Attempt to open the file
- notify($ERRORS{'DEBUG'}, 0, "attempting to open file to be exclusively
locked: $file_path");
- my $file_handle = new IO::File($file_path, O_RDONLY | O_CREAT);
- if (!$file_handle) {
- notify($ERRORS{'WARNING'}, 0, "failed to open file to be
exclusively locked: $file_path, reason: $!");
- return;
- }
- my $fileno = $file_handle->fileno;
- notify($ERRORS{'DEBUG'}, 0, "opened file to be exclusively locked:
$file_path");
-
- # Store the fileno and path in %ENV so we can retrieve the path later on
- $ENV{fileno}{$fileno} = $file_path;
-
- # Attempt to lock the file
- my $wait_message = "attempting to obtain lock on file: $file_path";
- if ($self->code_loop_timeout(sub{flock($file_handle, LOCK_EX|LOCK_NB)},
[], $wait_message, $total_wait_seconds, $attempt_delay_seconds)) {
- notify($ERRORS{'DEBUG'}, 0, "obtained an exclusive lock on
file: $file_path");
- }
- else {
- notify($ERRORS{'DEBUG'}, 0, "failed to obtain lock on file:
$file_path");
- return;
- }
-
- # Store the file handle as a variable and return it
- return $file_handle;
-}
-
-#/////////////////////////////////////////////////////////////////////////////
-
-=head2 release_lockfile
-
- Parameters : $file_handle
- Returns : boolean
- Description : Closes the lockfile handle specified by the argument.
-
-=cut
-
-sub release_lockfile {
- my $self = shift;
- if (ref($self) !~ /Module/i) {
- notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a
function, it must be called as a class method");
- return;
- }
-
- # Get the file handle argument
- my ($file_handle) = @_;
- if (!$file_handle) {
- notify($ERRORS{'WARNING'}, 0, "file handle argument was not
supplied");
- return;
- }
-
- # Make sure the file handle is opened
- my $fileno = $file_handle->fileno;
- if (!$fileno) {
- notify($ERRORS{'WARNING'}, 0, "file handle is not opened");
- return;
- }
-
- # Get the file path previously stored in %ENV
- my $file_path = $ENV{fileno}{$fileno} || 'unknown';
-
- # Close the file
- if (close($file_handle)) {
- notify($ERRORS{'DEBUG'}, 0, "closed file handle: $file_path");
- }
- else {
- notify($ERRORS{'WARNING'}, 0, "failed to close file handle:
$file_path, reason: $!");
- return;
- }
-
- delete $ENV{fileno}{$fileno};
- return 1;
-}
-
-#/////////////////////////////////////////////////////////////////////////////
-
=head2 get_vmx_file_paths
Parameters : none
Added: incubator/vcl/trunk/managementnode/lib/VCL/Module/Semaphore.pm
URL:
http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/Semaphore.pm?rev=983104&view=auto
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/Semaphore.pm (added)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/Semaphore.pm Fri Aug 6
19:38:53 2010
@@ -0,0 +1,281 @@
+#!/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::Semaphore - VCL module to control semaphores
+
+=head1 SYNOPSIS
+
+ my $semaphore = VCL::Module::Semaphore->new({data_structure => $self->data});
+ $semaphore->get_lockfile($file_path, $total_wait_seconds,
$attempt_delay_seconds);
+
+=head1 DESCRIPTION
+
+ A semaphore is used to ensure that only 1 process performs a particular task
at
+ a time. An example would be the retrieval of an image from another management
+ node. If multiple reservations are being processed for the same image, each
+ reservation may attempt to retrieve it via SCP at the same time. A
+ VCL::Module::Semaphore can be used to only allow 1 process to retrieve the
+ image. The others will wait until the semaphore is released by the retrieving
+ process.
+
+=cut
+
+##############################################################################
+package VCL::Module::Semaphore;
+
+# Specify the lib path using FindBin
+use FindBin;
+use lib "$FindBin::Bin/../..";
+
+# Configure inheritance
+use base qw(VCL::Module);
+
+# Specify the version of this module
+our $VERSION = '2.00';
+
+# Specify the version of Perl to use
+use 5.008000;
+
+use strict;
+use warnings;
+use diagnostics;
+use English qw( -no_match_vars );
+use IO::File;
+use Fcntl qw(:DEFAULT :flock);
+
+use VCL::utils;
+
+##############################################################################
+
+=head1 OBJECT METHODS
+
+=cut
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_lockfile
+
+ Parameters : $file_path, $total_wait_seconds (optional),
$attempt_delay_seconds (optional)
+ Returns : filehandle
+ Description : Attempts to open and obtain an exclusive lock on the file
+ specified by the file path argument. If unable to obtain an
+ exclusive lock, it will wait up to the value specified by the
+ total wait seconds argument (default: 30 seconds). The number of
+ seconds to wait in between retries can be specified (default: 15
+ seconds).
+
+=cut
+
+sub get_lockfile {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a
function, it must be called as a class method");
+ return;
+ }
+
+ # Get the file path argument
+ my ($file_path, $total_wait_seconds, $attempt_delay_seconds) = @_;
+ if (!$file_path) {
+ notify($ERRORS{'WARNING'}, 0, "file path argument was not
supplied");
+ return;
+ }
+
+ # Set the wait defaults if not supplied as arguments
+ $total_wait_seconds = 30 if !$total_wait_seconds;
+ $attempt_delay_seconds = 5 if !$attempt_delay_seconds;
+
+ # Attempt to lock the file
+ my $wait_message = "attempting to open lockfile";
+ if ($self->code_loop_timeout(\&open_lockfile, [$self, $file_path],
$wait_message, $total_wait_seconds, $attempt_delay_seconds)) {
+ return $file_path;
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "failed to open lockfile:
$file_path");
+ return;
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 open_lockfile
+
+ Parameters : $file_path
+ Returns : If successful: IO::File file handle object
+ If failed: false
+ Description : Opens and obtains an exclusive lock on the file specified by the
+ argument.
+
+=cut
+
+sub open_lockfile {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a
function, it must be called as a class method");
+ return;
+ }
+
+ # Get the file path argument
+ my ($file_path) = @_;
+ if (!$file_path) {
+ notify($ERRORS{'WARNING'}, 0, "file path argument was not
supplied");
+ return;
+ }
+
+ # Attempt to open and lock the file
+ if (my $file_handle = new IO::File($file_path, O_WRONLY|O_CREAT)) {
+ if (flock($file_handle, LOCK_EX | LOCK_NB)) {
+ notify($ERRORS{'DEBUG'}, 0, "opened and obtained an
exclusive lock on file: $file_path");
+
+ # Truncate and print the process information to the file
+ $file_handle->truncate(0);
+ print $file_handle "$$ $0\n";
+
+ $self->{file_handles}{$file_path} = $file_handle;
+ return $file_handle;
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "unable to obtain exclusive
lock on file: $file_path");
+ $file_handle->close;
+ }
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to open file: $file_path,
error:\n$!");
+ return;
+ }
+
+ # Run lsof to determine which process is locking the file
+ my ($exit_status, $output) = run_command("lsof -Fp $file_path", 1);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to run losf command to
determine which process is locking the file: $file_path");
+ return;
+ }
+ elsif (grep(/no such file/i, @$output)) {
+ notify($ERRORS{'WARNING'}, 0, "losf command reports that the
file does not exist: $file_path");
+ return;
+ }
+
+ # Parse the lsof output to determine the PID
+ my @locking_pids = map { /^p(\d+)/ } @$output;
+ if (@locking_pids && grep { $_ eq $PID } @locking_pids) {
+ # The current process already has an exclusive lock on the file
+ # This could happen if open_lockfile is called more than once
for the same file in the same scope
+ notify($ERRORS{'WARNING'}, 0, "file is already locked by this
process: @locking_pids");
+ return;
+ }
+ elsif (@locking_pids) {
+ notify($ERRORS{'DEBUG'}, 0, "file is locked by another process:
@locking_pids");
+ return;
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "unable to determine PIDs from lsof
output\n:" . join("\n", @$output));
+ }
+
+ return 0;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 release_lockfile
+
+ Parameters : $file_path
+ Returns : boolean
+ Description : Releases the exclusive lock and closes the lockfile handle
+ specified by the argument.
+
+=cut
+
+sub release_lockfile {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a
function, it must be called as a class method");
+ return;
+ }
+
+ # Get the file path argument
+ my $file_path = shift;
+ if (!$file_path) {
+ notify($ERRORS{'WARNING'}, 0, "file path argument was not
supplied");
+ return;
+ }
+
+ my $file_handle = $self->{file_handles}{$file_path};
+ if (!$file_handle) {
+ notify($ERRORS{'WARNING'}, 0, "file handle is not saved in this
object for file path: $file_path");
+ return;
+ }
+
+ # Make sure the file handle is opened
+ my $fileno = $file_handle->fileno;
+ if (!$fileno) {
+ notify($ERRORS{'WARNING'}, 0, "file is not opened: $file_path");
+ }
+
+ # Close the file
+ if (!close($file_handle)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to close file:
$file_path, reason: $!");
+ }
+
+ # Delete the file
+ if (unlink($file_path)) {
+ notify($ERRORS{'DEBUG'}, 0, "deleted file: $file_path");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to delete file:
$file_path, reason: $!");
+ }
+
+ delete $self->{file_handles}{$file_path};
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 DESTROY
+
+ Parameters : none
+ Returns : nothing
+ Description : Destroys the semaphore object. The files opened and exclusively
+ locked by the semaphore object are closed and deleted.
+
+=cut
+
+sub DESTROY {
+ my $self = shift;
+ notify($ERRORS{'DEBUG'}, 0, "destructor called: $self");
+
+ for my $file_path (keys %{$self->{file_handles}}) {
+ $self->release_lockfile($file_path);
+ }
+
+ # Check for an overridden destructor
+ $self->SUPER::DESTROY if $self->can("SUPER::DESTROY");
+} ## end sub DESTROY
+
+#/////////////////////////////////////////////////////////////////////////////
+
+1;
+__END__
+
+=head1 SEE ALSO
+
+L<http://cwiki.apache.org/VCL/>
+
+=cut
Propchange: incubator/vcl/trunk/managementnode/lib/VCL/Module/Semaphore.pm
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: incubator/vcl/trunk/managementnode/lib/VCL/Module/Semaphore.pm
------------------------------------------------------------------------------
svn:keywords = Date Revision Author HeadURL Id
Modified: incubator/vcl/trunk/managementnode/lib/VCL/utils.pm
URL:
http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/utils.pm?rev=983104&r1=983103&r2=983104&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/utils.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/utils.pm Fri Aug 6 19:38:53 2010
@@ -5404,11 +5404,8 @@ sub run_ssh_command {
# -p <port>, Port to connect to on the remote host.
# -x, Disables X11 forwarding.
# Dont use: -q, Quiet mode. Causes all warning and diagnostic messages
to be suppressed.
- my $ssh_command = "$ssh_path $identity_paths -l $user -p $port -x $node
'$command'";
-
- # Redirect standard output and error output so all messages are captured
- $ssh_command .= ' 2>&1';
-
+ my $ssh_command = "$ssh_path $identity_paths -l $user -p $port -x $node
'$command 2>&1' 2>&1";
+
# Execute the command
my $ssh_output;
my $ssh_output_formatted;
@@ -5443,10 +5440,10 @@ sub run_ssh_command {
else {
notify($ERRORS{'DEBUG'}, 0, "attempt
$attempts/$max_attempts: executing SSH command on $node:\n$ssh_command") if
$output_level;
}
-
+
# Execute the command
$ssh_output = `$ssh_command`;
-
+
# Bits 0-7 of $? are set to the signal the child process
received that caused it to die
my $signal_number = $? & 127;