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;