Author: arkurth Date: Thu Mar 29 21:05:08 2012 New Revision: 1307108 URL: http://svn.apache.org/viewvc?rev=1307108&view=rev Log: VCL-569 Updated OS.pm::update_public_ip_address to set the public IP address retrieved from the OS in the Datastructure object. This returns the correct value to future callers of get_computer_ip_address.
Updated new.pm::reserve_computer to call get_computer_ip_address after update_public_ip_address is called. This causes the code to use the correct, updated IP instead of an old value stored in the database. VCL-564 Added OS.pm::get_tools_file_paths. This is used to determine which files under the tools directory on the management node apply to the reservation image. Also added OS.pm::get_file_checksum. This is used to determine whether or a file on the management node is identical to a file on the computer being loaded. If they differ, the file on the computer is replaced. Added optional concatenate argument to OS.pm::create_text_file. By default the text file is overwritten. This argument is used by the run_script subroutines when the logfile is written to. Updated Windows.pm::run_scripts to call OS.pm::get_tools_file_paths. VCL-572 Added subroutines to Windows.pm: install_updates, get_installed_updates, install_exe_update, and install_msu_update. Moved call to get_imagemeta_postoption from the beginning of Windows.pm::post_load to the actual check that determines if the computer needs to be rebooted. This allows any of the subroutines prior to that point to set the reboot flag. VCL-565 Changed call in OS.pm::manage_server_access which was directly referencing $ENV{management_node_info} to use $self->data. Other Removed call to utils.pm::setstaticaddress in OS.pm::update_public_ip_address. This subroutine was removed. Added check to make sure the interface name was retrieved in OS.pm: get_private_network_configuration, get_public_network_configuration Moved copy_file_to and find_files from Linux.pm to OS.pm since they can be used by other OS's. Updated Linux.pm::logoff_user to check for an "invalid user name" error in the output. Modified: 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/OS/Windows.pm incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows/Version_5.pm incubator/vcl/trunk/managementnode/lib/VCL/new.pm incubator/vcl/trunk/managementnode/lib/VCL/utils.pm 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=1307108&r1=1307107&r2=1307108&view=diff ============================================================================== --- incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm (original) +++ incubator/vcl/trunk/managementnode/lib/VCL/Module/OS.pm Thu Mar 29 21:05:08 2012 @@ -840,8 +840,10 @@ sub update_public_ip_address { return; } - # Update the computer table if the retrieved IP address does not match what is in the database + # Update the Datastructure and computer table if the retrieved IP address does not match what is in the database if ($computer_ip_address ne $public_ip_address) { + $self->data->set_computer_ip_address($public_ip_address); + if (update_computer_address($computer_id, $public_ip_address)) { notify($ERRORS{'OK'}, 0, "updated dynamic public IP address in computer table for $computer_node_name, $public_ip_address"); insertloadlog($reservation_id, $computer_id, "dynamicDHCPaddress", "updated dynamic public IP address in computer table for $computer_node_name, $public_ip_address"); @@ -862,13 +864,10 @@ sub update_public_ip_address { notify($ERRORS{'DEBUG'}, 0, "IP configuration is set to $public_ip_configuration, attempting to set public IP address"); # Try to set the static public IP address using the OS module - if ($self->can("set_static_public_address") && $self->set_static_public_address()) { - notify($ERRORS{'DEBUG'}, 0, "set static public IP address on $computer_node_name using OS module's set_static_public_address() method"); - } - else { - # Unable to set the static address using the OS module, try using utils.pm - if (setstaticaddress($computer_node_name, $image_os_name, $computer_ip_address, $image_os_type)) { - notify($ERRORS{'DEBUG'}, 0, "set static public IP address on $computer_node_name using utils.pm::setstaticaddress()"); + if ($self->can("set_static_public_address")) { + if ($self->set_static_public_address()) { + notify($ERRORS{'DEBUG'}, 0, "set static public IP address on $computer_node_name using OS module's set_static_public_address() method"); + insertloadlog($reservation_id, $computer_id, "staticIPaddress", "set static public IP address on $computer_node_name"); } else { notify($ERRORS{'WARNING'}, 0, "failed to set static public IP address on $computer_node_name"); @@ -876,7 +875,9 @@ sub update_public_ip_address { return; } } - insertloadlog($reservation_id, $computer_id, "staticIPaddress", "set static public IP address on $computer_node_name"); + else { + notify($ERRORS{'WARNING'}, 0, "unable to set static public IP address on $computer_node_name, " . ref($self) . " module does not implement a set_static_public_address subroutine"); + } } else { @@ -1270,7 +1271,13 @@ sub get_private_network_configuration { return; } - return $self->get_network_configuration()->{$self->get_private_interface_name()}; + my $private_interface_name = $self->get_private_interface_name(); + if (!$private_interface_name) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve private network configuration, private interface name could not be determined"); + return; + } + + return $self->get_network_configuration()->{$private_interface_name}; } #///////////////////////////////////////////////////////////////////////////// @@ -1290,7 +1297,13 @@ sub get_public_network_configuration { return; } - return $self->get_network_configuration()->{$self->get_public_interface_name()}; + my $public_interface_name = $self->get_public_interface_name(); + if (!$public_interface_name) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve public network configuration, public interface name could not be determined"); + return; + } + + return $self->get_network_configuration()->{$public_interface_name}; } #///////////////////////////////////////////////////////////////////////////// @@ -1670,7 +1683,7 @@ sub create_text_file { return; } - my ($file_path, $file_contents_string) = @_; + my ($file_path, $file_contents_string, $concatenate) = @_; if (!$file_contents_string) { notify($ERRORS{'WARNING'}, 0, "file contents argument was not supplied"); return; @@ -1682,7 +1695,7 @@ sub create_text_file { # Attempt to create the parent directory if it does not exist if ($self->can('create_directory')) { my $parent_directory_path = parent_directory_path($file_path); - $self->create_directory($parent_directory_path); + $self->create_directory($parent_directory_path) if $parent_directory_path; } # Remove Windows-style carriage returns if the image OS isn't Windows @@ -1707,14 +1720,21 @@ sub create_text_file { # Create a command to echo the hex string to the file # Use -e to enable interpretation of backslash escapes - my $command .= "echo -n -e \"$hex_string\" > $file_path"; + my $command .= "echo -n -e \"$hex_string\""; + if ($concatenate) { + $command .= " >> \"$file_path\""; + } + else { + $command .= " > \"$file_path\""; + } + my ($exit_status, $output) = $self->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to execute ssh command to create file on $computer_node_name: $file_path"); return; } elsif ($exit_status != 0 || grep(/^\w+:/i, @$output)) { - notify($ERRORS{'WARNING'}, 0, "failed to execute command to create a file on $computer_node_name: $file_path, exit status: $exit_status, output:\n" . join("\n", @$output)); + notify($ERRORS{'WARNING'}, 0, "failed to execute command to create a file on $computer_node_name:\ncommand: '$command', exit status: $exit_status, output:\n" . join("\n", @$output)); return; } else { @@ -1896,6 +1916,7 @@ sub execute_new { if ($attempt > 0) { $attempt_string = "attempt $attempt/$max_attempts: "; $ssh->close() if $ssh; + delete $ENV{net_ssh_expect}{$computer_name}; notify($ERRORS{'DEBUG'}, 0, $attempt_string . "sleeping for $attempt_delay seconds before making next attempt"); sleep $attempt_delay; @@ -1976,7 +1997,8 @@ sub execute_new { delete $ENV{net_ssh_expect}{$computer_name}; } - notify($ERRORS{'DEBUG'}, 0, $attempt_string . "executing command on $computer_name: '$command', timeout: $timeout_seconds seconds") if ($display_output); + (my $command_formatted = $command) =~ s/\s+(;|&|&&)\s+/\n$1 /g; + notify($ERRORS{'DEBUG'}, 0, $attempt_string . "executing command on $computer_name (timeout: $timeout_seconds seconds):\n$command_formatted") if ($display_output); $ssh->send($command . ' 2>&1 ; echo exitstatus:$?'); my $ssh_wait_status; @@ -2002,7 +2024,7 @@ sub execute_new { $output =~ s/(^\s+)|(\s+$)//g; my $exit_status_string = $ssh->match() || ''; - my ($exit_status) = $exit_status_string =~ /(\d)+/; + my ($exit_status) = $exit_status_string =~ /(\d+)/; if (!$exit_status_string || !defined($exit_status)) { my $all_output = $ssh->read_all() || ''; notify($ERRORS{'WARNING'}, 0, $attempt_string . "failed to determine exit status from string: '$exit_status_string', output:\n$all_output"); @@ -2149,11 +2171,7 @@ sub manage_server_access { # Collect users in reservationaccounts table my %res_accounts = get_reservation_accounts($reservation_id); - notify($ERRORS{'DEBUG'}, 0, "res_accounts:". format_data(%res_accounts)); - my $not_standalone_list = ""; - if(defined($ENV{management_node_info}{NOT_STANDALONE}) && $ENV{management_node_info}{NOT_STANDALONE}){ - $not_standalone_list = $ENV{management_node_info}{NOT_STANDALONE}; - } + my $not_standalone_list = $self->data->get_management_node_not_standalone(); #Add users foreach my $userid (sort keys %user_hash) { @@ -2204,7 +2222,7 @@ sub manage_server_access { } } - + #Remove anyone listed in reservationaccounts list that is not in user_hash foreach my $res_userid (sort keys %res_accounts) { notify($ERRORS{'OK'}, 0, "res_userid= $res_userid username= $res_accounts{$res_userid}{username}"); @@ -2228,7 +2246,7 @@ sub manage_server_access { $allow_list .= " $res_accounts{$res_userid}{username}"; } notify($ERRORS{'OK'}, 0, "allow_list= $allow_list"); - + $self->data->set_server_allow_users($allow_list); if ($self->can("update_server_access") ) { @@ -2476,6 +2494,322 @@ sub is_user_connected { return $ret_val; } +#///////////////////////////////////////////////////////////////////////////// + +=head2 copy_file_to + + Parameters : $source_path, $destination_path + Returns : boolean + Description : Copies file(s) from the management node to the computer. + Wildcards are allowed in the source path. + +=cut + +sub copy_file_to { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the source and destination arguments + my ($source_path, $destination_path) = @_; + if (!$source_path || !$destination_path) { + notify($ERRORS{'WARNING'}, 0, "source and destination path arguments were not specified"); + return; + } + + # Get the computer short and hostname + my $computer_node_name = $self->data->get_computer_node_name() || return; + + # Get the destination parent directory path and create the directory + my $destination_directory_path = parent_directory_path($destination_path); + if (!$destination_directory_path) { + notify($ERRORS{'WARNING'}, 0, "unable to determine destination parent directory path: $destination_path"); + return; + } + $self->create_directory($destination_directory_path) || return; + + # Get the identity keys used by the management node + my $management_node_keys = $self->data->get_management_node_keys() || ''; + + # Run the SCP command + if (run_scp_command($source_path, "$computer_node_name:\"$destination_path\"", $management_node_keys)) { + notify($ERRORS{'DEBUG'}, 0, "copied file from management node to $computer_node_name: '$source_path' --> $computer_node_name:'$destination_path'"); + } + else { + notify($ERRORS{'WARNING'}, 0, "failed to copy file from management node to $computer_node_name: '$source_path' --> $computer_node_name:'$destination_path'"); + return; + } + + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 find_files + + Parameters : $base_directory_path, $file_pattern, $search_type (optional) + Returns : array + Description : Finds files under the base directory and any subdirectories path + matching the file pattern. The search is not case sensitive. An + array is returned containing matching file paths. + + A third argument can be supplied specifying the search type. + + If 'regex' is supplied, the $file_pattern argument is assumed to + be a regular expression. + +=cut + +sub find_files { + my $self = shift; + if (ref($self) !~ /VCL::Module/i) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + # Get the arguments + my ($base_directory_path, $file_pattern, $search_type) = @_; + if (!$base_directory_path || !$file_pattern) { + notify($ERRORS{'WARNING'}, 0, "base directory path and file pattern arguments were not specified"); + return; + } + + # Normalize the arguments + $base_directory_path = normalize_file_path($base_directory_path); + $file_pattern = normalize_file_path($file_pattern); + + # The base directory path must have a trailing slash or find won't work + $base_directory_path .= '/'; + + # Get the computer short and hostname + my $computer_node_name = $self->data->get_computer_node_name() || return; + + # Run the find command + my $command = "/usr/bin/find \"$base_directory_path\""; + if ($search_type) { + if ($search_type =~ /regex/i) { + $command .= " -type f -iregex \"$file_pattern\""; + } + else { + notify($ERRORS{'WARNING'}, 0, "invalid search type argument was specified: '$search_type'"); + return; + } + } + else { + $command .= " -type f -iname \"$file_pattern\""; + } + + notify($ERRORS{'DEBUG'}, 0, "attempting to find files on $computer_node_name, base directory path: '$base_directory_path', pattern: $file_pattern, command: $command"); + + my ($exit_status, $output) = $self->execute($command, 0); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to run command to find files on $computer_node_name, base directory path: '$base_directory_path', pattern: $file_pattern, command:\n$command"); + return; + } + elsif (grep(/^find:.*No such file or directory/i, @$output)) { + notify($ERRORS{'DEBUG'}, 0, "base directory does not exist on $computer_node_name: $base_directory_path"); + @$output = (); + } + elsif (grep(/^find: /i, @$output)) { + notify($ERRORS{'WARNING'}, 0, "error occurred attempting to find files on $computer_node_name\nbase directory path: $base_directory_path\npattern: $file_pattern\ncommand: $command\noutput:\n" . join("\n", @$output)); + return; + } + + my @files; + LINE: for my $line (@$output) { + push @files, $line; + } + + my $file_count = scalar(@files); + + notify($ERRORS{'DEBUG'}, 0, "files found: $file_count, base directory: '$base_directory_path', pattern: '$file_pattern'"); + return @files; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_file_checksum + + Parameters : $file_path + Returns : integer + Description : Runs chsum on the file specified by the argument and returns the + checksum of the file. + +=cut + +sub get_file_checksum { + 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 $file_path = shift; + if (!$file_path) { + notify($ERRORS{'WARNING'}, 0, "file path argument was not supplied"); + return; + } + + # Escape $ characters + $file_path =~ s/([\$])/\\$1/g; + + my $command = "cksum \"$file_path\""; + my ($exit_status, $output) = $self->execute($command); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to execute command to determine checksum of file: $file_path"); + return; + } + elsif (my ($checksum_line) = grep(/^\d+\s+/, @$output)) { + my ($checksum) = $checksum_line =~ /^(\d+)/; + #notify($ERRORS{'DEBUG'}, 0, "determined checksum of file '$file_path': $checksum"); + return $checksum; + } + else { + notify($ERRORS{'WARNING'}, 0, "unexpected output in cksum output, command: '$command', output:\n" . join("\n", @$output)); + return; + } +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 get_tools_file_paths + + Parameters : $pattern + Returns : boolean + Description : Scans the tools directory on the management node for any files + which are intended for the OS of the reservation image. The OS + name and architecture are considered. A list of file paths on the + reservation computer is returned. + + Files intended for the reservation image are synchronized from + the management node. Any files which don't exist on the + reservation computer are copied. Files which exist on the + computer but are different than the file on the management node + are replaced. Files which exist on the computer but not on the + management node are ignored. + + A pattern argument can be supplied to limit the results. For + example, to only return driver files supply '/Drivers/' as the + argument. To only return script files intended to for the + post_load stage, supply '/Scripts/post_load' as the argument. + + The list of files returned is sorted by the names of the files, + regardless of the directory where they reside. Files can be named + beginning with a number. This list returned is sorted numerically + from the lowest number to the highest: + -1.cmd + -50.cmd + -100.cmd + + File names which do not begin with a number are sorted + alphabetically and listed after any files beginning with a + number: + -1.cmd + -50.cmd + -100.cmd + -Blah.cmd + -foo.cmd + +=cut + +sub get_tools_file_paths { + 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; + } + + my $pattern = shift || '.*'; + + my $computer_node_name = $self->data->get_computer_node_name(); + + my @source_configuration_directories = $self->get_source_configuration_directories(); + if (!@source_configuration_directories) { + notify($ERRORS{'WARNING'}, 0, "unable to retrieve source configuration directories"); + return; + } + + my $architecture = $self->is_64_bit() ? 'x86_64' : 'x86'; + my $other_architecture = $self->is_64_bit() ? 'x86' : 'x86_64'; + + notify($ERRORS{'DEBUG'}, 0, "attempting for find tools files:\npattern: $pattern\narchitecture: $architecture\nother architecture: $other_architecture"); + + # Find files already on the computer + my $computer_directory_path = "$NODE_CONFIGURATION_DIRECTORY"; + my @existing_computer_file_array = $self->find_files($computer_directory_path, '*'); + my %existing_computer_files = map { $_ => 1 } @existing_computer_file_array; + + my %computer_tools_file_paths; + + # Loop through the directories on the management node + DIRECTORY: for my $source_configuration_directory (@source_configuration_directories) { + # Find script files on the managment node intended for the computer + my $mn_directory_path = "$source_configuration_directory"; + my @mn_directory_files = $self->mn_os->find_files($mn_directory_path, '*'); + + # Loop through the files found on the management node + MN_FILE: for my $mn_file_path (@mn_directory_files) { + + # Ignore files not matching the pattern argument, Subversion files, and files intended for another architecture + if ($pattern && $mn_file_path !~ /$pattern/i) { + #notify($ERRORS{'DEBUG'}, 0, "ignoring file, it does not match pattern '$pattern': $mn_file_path"); + next MN_FILE; + } + elsif ($mn_file_path =~ /\/\.svn\//i) { + notify($ERRORS{'DEBUG'}, 0, "ignoring Subversion file: $mn_file_path"); + next MN_FILE; + } + elsif ($mn_file_path =~ /\/$other_architecture\//) { + notify($ERRORS{'DEBUG'}, 0, "ignoring file intended for different computer architecture: $mn_file_path"); + next MN_FILE; + } + + my ($relative_file_path) = $mn_file_path =~ /$mn_directory_path\/(.+)/; + my $computer_file_path = "$computer_directory_path/$relative_file_path"; + + # Add the computer file path to the list that will be returned + $computer_tools_file_paths{$computer_file_path} = 1; + + # Check if the file already exists on the computer + notify($ERRORS{'DEBUG'}, 0, "checking if file on management node needs to be copied to $computer_node_name: $mn_file_path"); + if ($existing_computer_files{$computer_file_path}) { + + # Check if existing file on computer is identical to file on managment node + # Retrieve the checksums + my $mn_file_checksum = $self->mn_os->get_file_checksum($mn_file_path); + my $computer_file_checksum = $self->get_file_checksum($computer_file_path); + + # Check if the file already on the computer is exactly the same as the one on the MN by comparing checksums + if ($mn_file_checksum && $computer_file_checksum && $computer_file_checksum eq $mn_file_checksum) { + notify($ERRORS{'DEBUG'}, 0, "identical file exists on $computer_node_name: $computer_file_path"); + next MN_FILE; + } + else { + notify($ERRORS{'DEBUG'}, 0, "file exists on $computer_node_name but checksum is different: $computer_file_path\n" . + "MN file checksum: " . ($mn_file_checksum || '<unknown>') . "\n" . + "computer file checksum: " . ($computer_file_checksum || '<unknown>') + ); + } + } + else { + notify($ERRORS{'DEBUG'}, 0, "file does not exist on $computer_node_name: $computer_file_path"); + } + + # File either doesn't already exist on the computer or file on computer is different than file on MN + if (!$self->copy_file_to($mn_file_path, $computer_file_path)) { + notify($ERRORS{'WARNING'}, 0, "file could not be copied from management node to $computer_node_name: $mn_file_path --> $computer_file_path"); + return; + } + } + } + + my @return_files = sort_by_file_name(keys %computer_tools_file_paths); + notify($ERRORS{'DEBUG'}, 0, "determined list of tools files intended for $computer_node_name, pattern: $pattern, architecture: $architecture:\n" . join("\n", @return_files)); + return @return_files; +} + #/////////////////////////////////////////////////////////////////////////// 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=1307108&r1=1307107&r2=1307108&view=diff ============================================================================== --- incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm (original) +++ incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm Thu Mar 29 21:05:08 2012 @@ -732,35 +732,35 @@ sub logoff_user { } # Make sure the user login ID was passed - my $user_login_id = shift; - $user_login_id = $self->data->get_user_login_id() if (!$user_login_id); + my $user_login_id = shift || $self->data->get_user_login_id(); if (!$user_login_id) { notify($ERRORS{'WARNING'}, 0, "user could not be determined"); return 0; } # Make sure the user login ID was passed - my $computer_node_name = shift; - $computer_node_name = $self->data->get_computer_node_name() if (!$computer_node_name); + my $computer_node_name = shift || $self->data->get_computer_node_name(); if (!$computer_node_name) { notify($ERRORS{'WARNING'}, 0, "computer node name could not be determined"); return 0; } - #Make sure the identity key was passed - my $image_identity = shift; - $image_identity = $self->data->get_image_identity() if (!$image_identity); - if (!$image_identity) { - notify($ERRORS{'WARNING'}, 0, "image identity keys could not be determined"); - return 0; - } - my $logoff_cmd = "pkill -KILL -u $user_login_id"; - if (run_ssh_command($computer_node_name, $image_identity, $logoff_cmd, "root")) { - notify($ERRORS{'DEBUG'}, 0, "logged off $user_login_id from $computer_node_name"); + my ($exit_status, $output) = $self->execute($logoff_cmd); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to execute command to log off $user_login_id from $computer_node_name"); + return; + } + elsif (grep(/invalid user name/i, @$output)) { + notify($ERRORS{'DEBUG'}, 0, "user $user_login_id does not exist on $computer_node_name"); + return 1; + } + elsif ($exit_status ne '0') { + notify($ERRORS{'WARNING'}, 0, "error occurred attempting to log off $user_login_id from $computer_node_name, exit status: $exit_status, output:\n" . join("\n", @$output)); + return; } else { - notify($ERRORS{'DEBUG'}, 0, "failed to log off $user_login_id from $computer_node_name"); + notify($ERRORS{'OK'}, 0, "logged off $user_login_id from $computer_node_name"); } return 1; @@ -1773,57 +1773,6 @@ sub copy_file_from { #///////////////////////////////////////////////////////////////////////////// -=head2 copy_file_to - - Parameters : $source_path, $destination_path - Returns : boolean - Description : Copies file(s) from the management node to the Linux computer. - Wildcards are allowed in the source path. - -=cut - -sub copy_file_to { - my $self = shift; - if (ref($self) !~ /VCL::Module/i) { - notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); - return; - } - - # Get the source and destination arguments - my ($source_path, $destination_path) = @_; - if (!$source_path || !$destination_path) { - notify($ERRORS{'WARNING'}, 0, "source and destination path arguments were not specified"); - return; - } - - # Get the computer short and hostname - my $computer_node_name = $self->data->get_computer_node_name() || return; - - # Get the destination parent directory path and create the directory - my $destination_directory_path = parent_directory_path($destination_path); - if (!$destination_directory_path) { - notify($ERRORS{'WARNING'}, 0, "unable to determine destination parent directory path: $destination_path"); - return; - } - $self->create_directory($destination_directory_path) || return; - - # Get the identity keys used by the management node - my $management_node_keys = $self->data->get_management_node_keys() || ''; - - # Run the SCP command - if (run_scp_command($source_path, "$computer_node_name:\"$destination_path\"", $management_node_keys)) { - notify($ERRORS{'DEBUG'}, 0, "copied file from management node to $computer_node_name: '$source_path' --> $computer_node_name:'$destination_path'"); - } - else { - notify($ERRORS{'WARNING'}, 0, "failed to copy file from management node to $computer_node_name: '$source_path' --> $computer_node_name:'$destination_path'"); - return; - } - - return 1; -} - -#///////////////////////////////////////////////////////////////////////////// - =head2 copy_file Parameters : $source_file_path, $destination_file_path @@ -1901,7 +1850,7 @@ sub copy_file { =head2 get_file_size - Parameters : $file_path + Parameters : @file_paths Returns : integer or array Description : Determines the size of the file specified by the file path argument in bytes. The file path argument may be a directory or @@ -2027,77 +1976,6 @@ sub get_file_size { #///////////////////////////////////////////////////////////////////////////// -=head2 find_files - - Parameters : $base_directory_path, $file_pattern, $search_type (optional) - Returns : array - Description : Finds files under the base directory and any subdirectories path - matching the file pattern. The search is not case sensitive. An - array is returned containing matching file paths. - - A third argument can be supplied specifying the search type. - 'regex' is currently the only supported type. If supplied, the - $file_pattern argument is assumed to be a regular expression. - Otherwise it is assumed to be a normal search pattern. - -=cut - -sub find_files { - my $self = shift; - if (ref($self) !~ /VCL::Module/i) { - notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); - return; - } - - # Get the arguments - my ($base_directory_path, $file_pattern, $search_type) = @_; - if (!$base_directory_path || !$file_pattern) { - notify($ERRORS{'WARNING'}, 0, "base directory path and file pattern arguments were not specified"); - return; - } - - # Normalize the arguments - $base_directory_path = normalize_file_path($base_directory_path); - $file_pattern = normalize_file_path($file_pattern); - - # The base directory path must have a trailing slash or find won't work - $base_directory_path .= '/'; - - # Get the computer short and hostname - my $computer_node_name = $self->data->get_computer_node_name() || return; - - # Run the find command - my $command; - if ($search_type && $search_type =~ /regex/i) { - $command = "find \"$base_directory_path\" -iregex \"$file_pattern\""; - } - else { - $command = "find \"$base_directory_path\" -iname \"$file_pattern\""; - } - notify($ERRORS{'DEBUG'}, 0, "attempting to find files on $computer_node_name, base directory path: '$base_directory_path', pattern: $file_pattern, command: $command"); - - my ($exit_status, $output) = $self->execute($command); - if (!defined($output)) { - notify($ERRORS{'WARNING'}, 0, "failed to run command to find files on $computer_node_name, base directory path: '$base_directory_path', pattern: $file_pattern, command:\n$command"); - return; - } - elsif (grep(/^find:.*No such file or directory/i, @$output)) { - notify($ERRORS{'DEBUG'}, 0, "base directory does not exist on $computer_node_name: $base_directory_path"); - @$output = (); - } - elsif (grep(/^find: /i, @$output)) { - notify($ERRORS{'WARNING'}, 0, "error occurred attempting to find files on $computer_node_name\nbase directory path: $base_directory_path\npattern: $file_pattern\ncommand: $command\noutput:\n" . join("\n", @$output)); - return; - } - - # Return the file list - my @file_paths = @$output; - notify($ERRORS{'DEBUG'}, 0, "matching file count: " . scalar(@file_paths)); - return sort @file_paths; -} - -#///////////////////////////////////////////////////////////////////////////// - =head2 set_file_permissions Parameters : $file_path, $chmod_mode, $recursive (optional) Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm?rev=1307108&r1=1307107&r2=1307108&view=diff ============================================================================== --- incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm (original) +++ incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm Thu Mar 29 21:05:08 2012 @@ -616,7 +616,6 @@ sub post_load { } my $computer_node_name = $self->data->get_computer_node_name(); - my $imagemeta_postoption = $self->data->get_imagemeta_postoption(); notify($ERRORS{'OK'}, 0, "beginning Windows post-load tasks on $computer_node_name"); @@ -814,7 +813,7 @@ sub post_load { =cut - if ($imagemeta_postoption =~ /reboot/i) { + if ($self->data->get_imagemeta_postoption() =~ /reboot/i) { notify($ERRORS{'OK'}, 0, "imagemeta postoption reboot is set for image, rebooting computer"); if (!$self->reboot()) { notify($ERRORS{'WARNING'}, 0, "failed to reboot the computer"); @@ -10903,10 +10902,19 @@ sub run_script { (my $script_path_escaped = $script_path) =~ s/( )/\^$1/g; # Get the node configuration directory, make sure it exists, create if necessary - my $node_log_directory = $self->get_node_configuration_directory() . '/Logs'; + my $node_configuration_directory = $self->get_node_configuration_directory(); + my $node_log_directory = "$node_configuration_directory/Logs"; # Assemble the log file path - my $log_file_path = $node_log_directory . "/$script_name.log"; + my $log_file_path; + + # If the script resides in the VCL node configuration directory, append the intermediate directory paths to the logfile path + if ($script_directory_path =~ /$node_configuration_directory[\\\/](.+)/) { + $log_file_path = "$node_log_directory/$1$script_name.log"; + } + else { + $log_file_path = "$node_log_directory/$script_name.log"; + } my $timestamp = makedatestring(); @@ -10914,7 +10922,7 @@ sub run_script { my $command = "cmd.exe /c \"$script_path_escaped & exit %ERRORLEVEL%\""; # Execute the command - notify($ERRORS{'DEBUG'}, 0, "executing command: '$command'"); + notify($ERRORS{'DEBUG'}, 0, "executing script on $computer_node_name:\nscript path: $script_path\nlog file path: $log_file_path"); my ($exit_status, $output) = $self->execute($command); if (!defined($output)) { notify($ERRORS{'WARNING'}, 0, "failed to execute script on $computer_node_name: '$script_path', command: '$command'"); @@ -10984,120 +10992,316 @@ sub run_script { sub run_scripts { 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"); + if (ref($self) !~ /VCL::Module/) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } # Get the stage argument my $stage = shift; if (!$stage) { - notify($ERRORS{'WANING'}, 0, "unable to run scripts, stage argument was not supplied"); + notify($ERRORS{'WARNING'}, 0, "unable to run scripts, stage argument was not supplied"); return; } elsif ($stage !~ /(pre_capture|post_load|post_reserve)/) { - notify($ERRORS{'WANING'}, 0, "invalid stage argument was supplied: $stage"); + notify($ERRORS{'WARNING'}, 0, "invalid stage argument was supplied: $stage"); return; } my $computer_node_name = $self->data->get_computer_node_name(); - my @source_configuration_directories = $self->get_source_configuration_directories(); - if (!@source_configuration_directories) { - notify($ERRORS{'WARNING'}, 0, "unable to retrieve source configuration directories"); + my @computer_tools_files = $self->get_tools_file_paths("/Scripts/$stage/"); + + # Loop through all tools files on the computer + for my $computer_tools_file_path (@computer_tools_files) { + if ($computer_tools_file_path !~ /\.(cmd|bat)$/i) { + notify($ERRORS{'DEBUG'}, 0, "file on $computer_node_name not executed because extension is not .cmd or .bat: $computer_tools_file_path"); + next; + } + + notify($ERRORS{'DEBUG'}, 0, "executing script on $computer_node_name: $computer_tools_file_path"); + + $self->run_script($computer_tools_file_path); + } + + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 install_updates + + Parameters : none + Returns : boolean + Description : Installs Windows update files stored in under the tools directory + on the management node. Update files which exist on the + management node but not on the computer are copied. Files which + are named the same but differ are replaced. + +=cut + +sub install_updates { + my $self = shift; + if (ref($self) !~ /VCL::Module/) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); return; } - my $architecture = $self->is_64_bit() ? 'x86_64' : 'x86'; + my $computer_node_name = $self->data->get_computer_node_name(); + my $system32_path = $self->get_system32_path() || return; - # Find any script files already on the computer residing in the Scripts/<stage> directory - my $computer_directory_path = "$NODE_CONFIGURATION_DIRECTORY/Scripts/$stage"; - my $computer_files = $self->find_files($computer_directory_path, '*'); - - my $mn_files = {}; - - # Loop through the directories on the management node - DIRECTORY: for my $source_configuration_directory (@source_configuration_directories) { - # Find script files on the managment node intended for the computer - my $mn_directory_path = "$source_configuration_directory/Scripts/$stage"; - my $mn_directory_files = $self->mn_os->find_files($mn_directory_path, '*'); - - # Loop through the files found on the management node - MN_FILE: for my $mn_file_path (keys(%$mn_directory_files)) { - # Determine the relative path - this is used to match up files on the MN and files on the computer - # The base directories differ but the path proceeding 'Scripts' is the same - # Example: - # MN file path: /usr/local/vcl/tools/Windows_Version_5/Scripts/post_load/x86/myscript.cmd - # Relative file path: x86/myscript.cmd - # Computer file path: C:/cygwin/home/root/VCL/Scripts/post_load/x86/myscript.cmd - my ($relative_file_path) = $mn_file_path =~ /$mn_directory_path\/(.+)/; - - # Add the file to the hash containing all script files found in all directories - # This is used later to determine if any extra files exist on the computer but not on the MN - $mn_files->{$relative_file_path} = $mn_directory_files->{$mn_file_path}; - - # Check if the script file resides in an architecture-specific directory (/x86*/) not matching the architecture of the computer - if ($relative_file_path !~ /^$architecture\// && $relative_file_path =~ /^x86/) { - notify($ERRORS{'DEBUG'}, 0, "ignoring file intended for different computer architecture: $mn_file_path"); - next MN_FILE; - } - - # Retrieve the checksum of the file on the management node from the file info returned by find_files - my $mn_file_checksum = $mn_directory_files->{$mn_file_path}{checksum}; - - notify($ERRORS{'DEBUG'}, 0, "checking if file on management node needs to be copied to $computer_node_name: $mn_file_path"); - - # Assemble the script file path on the computer - my $computer_file_path = "$computer_directory_path/$relative_file_path"; - - # Check if the file already exists on the computer - if ($computer_files->{$computer_file_path}) { - # Check if the file already on the computer is exactly the same as the one on the MN by comparing checksums - my $computer_file_checksum = $computer_files->{$computer_file_path}{checksum}; - if ($computer_file_checksum eq $mn_file_checksum) { - notify($ERRORS{'DEBUG'}, 0, "identical file exists on $computer_node_name: $computer_file_path"); - next MN_FILE; - } - else { - notify($ERRORS{'DEBUG'}, 0, "file exists on $computer_node_name but checksum is different: $computer_file_path\n" . - "MN file checksum: $mn_file_checksum\n" . - "computer file checksum: $computer_file_checksum" - ); - } + # Get the node configuration directory, make sure it exists, create if necessary + my $node_configuration_directory = $self->get_node_configuration_directory(); + + my @computer_tools_files = $self->get_tools_file_paths("/Updates/"); + + my $logfile_directory_path = "$node_configuration_directory/Logs/Updates"; + $self->create_directory($logfile_directory_path); + + my %installed_updates = map { $_ => 1 } $self->get_installed_updates(); + + my @update_ids; + + # Loop through all update files on the computer + for my $file_path (@computer_tools_files) { + my ($file_name, $directory_path, $file_extension) = fileparse($file_path, qr/\.[^.]*/); + + if ($file_path !~ /\.(msu|exe)$/i) { + notify($ERRORS{'DEBUG'}, 0, "file on $computer_node_name not installed because file extension is not .exe or .msu: $file_path"); + next; + } + + # Get the update ID (KBxxxxxx) from the file name + my ($update_id) = uc($file_name) =~ /(kb\d+)/i; + $update_id = $file_name if !$update_id; + + # Check if the update is already installed based on the list returned from the OS + if ($installed_updates{$update_id}) { + notify($ERRORS{'DEBUG'}, 0, "update $update_id is already installed on $computer_node_name"); + next; + } + else { + # Add ID to @update_ids array, this list will be checked after all updates are installed to verify update is installed on computer + push @update_ids, $update_id; + } + + if ($file_path =~ /\.msu$/i) { + $self->install_msu_update($file_path); + } + elsif ($file_path =~ /\.exe$/i) { + $self->install_exe_update($file_path); + } + } + + # If any updates were installed, verify they appear on the OS + if (@update_ids) { + # Retrieve the installed updated from the OS to check if the update was installed + %installed_updates = map { $_ => 1 } $self->get_installed_updates(1); + for my $update_id (@update_ids) { + if ($installed_updates{$update_id}) { + notify($ERRORS{'DEBUG'}, 0, "verified update $update_id is installed on $computer_node_name"); } else { - notify($ERRORS{'DEBUG'}, 0, "file does not exist on $computer_node_name: $computer_file_path"); - } - - # File either doesn't already exist on the computer or file on computer is different than file on MN - if (!$self->copy_file_to($mn_file_path, $computer_file_path)) { - notify($ERRORS{'WARNING'}, 0, "file could not be copied from management node to $computer_node_name: $mn_file_path --> $computer_file_path"); - next MN_FILE; + notify($ERRORS{'WARNING'}, 0, "update $update_id does not appear in the list of updates installed on $computer_node_name"); } - - # Add the file to the hash of files on the computer - $computer_files->{$computer_file_path} = $mn_directory_files->{$mn_file_path}; } } - # Loop through all files found and/or copied to the computer - COMPUTER_FILE: for my $computer_file_path (sort_by_file_name(keys(%$computer_files))) { - my ($relative_file_path) = $computer_file_path =~ /$computer_directory_path\/(.+)/; + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 install_exe_update + + Parameters : $update_file_path + Returns : boolean + Description : Installs a Windows Update .exe update package. + +=cut + +sub install_exe_update { + my $self = shift; + if (ref($self) !~ /VCL::Module/) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + my $file_path = shift; + if (!$file_path) { + notify($ERRORS{'WARNING'}, 0, "path to .msu update file was not supplied"); + return; + } + + my $computer_node_name = $self->data->get_computer_node_name(); + my $system32_path = $self->get_system32_path() || return; + + my ($file_name, $directory_path, $file_extension) = fileparse($file_path, qr/\.[^.]*/); + + my ($update_id) = uc($file_name) =~ /(kb\d+)/i; + $update_id = $file_name if !$update_id; + + # Assemble the log file path + # wusa.exe creates log files in the Event Log format - not plain text + my $node_configuration_directory = $self->get_node_configuration_directory(); + my $logfile_directory_path = "$node_configuration_directory/Logs/Updates"; + my $log_file_path = "$logfile_directory_path/$file_name.log"; + + # Delete old log files for the update being installed so log output can be parsed without including old data + $self->delete_file("$logfile_directory_path/*$update_id*"); + + my $command; + $command .= "chmod -Rv 755 \"$file_path\" ; "; + $command .= "\"$file_path\" /quiet /norestart /log:\"$log_file_path\""; + + notify($ERRORS{'DEBUG'}, 0, "installing update on $computer_node_name\ncommand: $command"); + my ($exit_status, $output) = $self->execute($command, 1, 180); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to execute command to install update on $computer_node_name: $command"); + return; + } + elsif ($exit_status eq 194) { + # Exit status 194 - installed but reboot required + notify($ERRORS{'DEBUG'}, 0, "installed update on $computer_node_name, exit status $exit_status indicates a reboot is required"); + $self->data->set_imagemeta_postoption('reboot'); + return 1; + } + elsif ($exit_status eq 0) { + notify($ERRORS{'DEBUG'}, 0, "installed update on $computer_node_name"); + } + else { + notify($ERRORS{'WARNING'}, 0, "command to install update on $computer_node_name returned exit status: $exit_status\ncommand: $command\noutput:\n" . join("\n", @$output)); + } + + # Check the log file to determine if a reboot is required, skip if exit status was 194 + my @log_file_lines = $self->get_file_contents($log_file_path); + for my $line (@log_file_lines) { + if ($line =~ /RebootNecessary = 1|reboot is required/i) { + $self->data->set_imagemeta_postoption('reboot'); + } + } + + return 1; +} + +#///////////////////////////////////////////////////////////////////////////// + +=head2 install_msu_update + + Parameters : $msu_file_path + Returns : boolean + Description : Installs a Windows Update Stand-alone Installer .msu update + package. +=cut + +sub install_msu_update { + my $self = shift; + if (ref($self) !~ /VCL::Module/) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + my $file_path = shift; + if (!$file_path) { + notify($ERRORS{'WARNING'}, 0, "path to .msu update file was not supplied"); + return; + } + + my $computer_node_name = $self->data->get_computer_node_name(); + my $system32_path = $self->get_system32_path() || return; + + my ($file_name, $directory_path, $file_extension) = fileparse($file_path, qr/\.[^.]*/); + + my ($update_id) = uc($file_name) =~ /(kb\d+)/i; + $update_id = $file_name if !$update_id; + + # Assemble the log file path + # wusa.exe creates log files in the Event Log format - not plain text + my $node_configuration_directory = $self->get_node_configuration_directory(); + my $logfile_directory_path = "$node_configuration_directory/Logs/Updates"; + my $event_log_file_path = "$logfile_directory_path/$file_name.evtx"; + my $log_file_path = "$logfile_directory_path/$file_name.log"; + + # Delete old log files for the update being installed so log output can be parsed without including old data + $self->delete_file("$logfile_directory_path/*$update_id*"); + + my $wusa_command; + $wusa_command .= "chmod -Rv 755 \"$file_path\" ; "; + $wusa_command .= "$system32_path/wusa.exe \"$file_path\" /quiet /norestart /log:\"$event_log_file_path\""; + + notify($ERRORS{'DEBUG'}, 0, "installing update on $computer_node_name\ncommand: $wusa_command"); + my ($wusa_exit_status, $wusa_output) = $self->execute($wusa_command, 1, 180); + if (!defined($wusa_output)) { + notify($ERRORS{'WARNING'}, 0, "failed to execute command to install update on $computer_node_name: $wusa_command"); + return; + } + else { + notify($ERRORS{'DEBUG'}, 0, "executed command to install update on $computer_node_name, exit status: $wusa_exit_status\ncommand: $wusa_command\noutput:\n" . join("\n", @$wusa_output)); + } + + # Convert Event Log format log file to plain text + # Use the wevtutil.exe - the Windows Events Command Line Utility + my $wevtutil_command = "$system32_path/wevtutil.exe qe \"$event_log_file_path\" /lf:true /f:XML /e:root > \"$log_file_path\""; + + my ($wevtutil_exit_status, $wevtutil_output) = $self->execute($wevtutil_command); + if (!defined($wevtutil_output)) { + notify($ERRORS{'WARNING'}, 0, "failed to execute command to convert event log file to plain text on $computer_node_name: $wevtutil_command"); + return; + } + else { + #notify($ERRORS{'DEBUG'}, 0, "executed command to convert event log file to plain text $computer_node_name, exit status: $wevtutil_exit_status\ncommand: $wevtutil_command\noutput:\n" . join("\n", @$wevtutil_output)); + } + + my @log_file_lines = $self->get_file_contents($log_file_path); + #notify($ERRORS{'DEBUG'}, 0, "log file contents from installation of $file_path:\n" . join("\n", @log_file_lines)); + + my $log_xml_hash = xml_string_to_hash(@log_file_lines); + #notify($ERRORS{'DEBUG'}, 0, "XML hash:\n" . format_data($log_xml_hash)); + + my @events = @{$log_xml_hash->{Event}}; + for my $event (@events) { + my $event_record_id = $event->{System}[0]->{EventRecordID}[0]; + my %event_data = map { $_->{Name} => $_->{content} } @{$event->{EventData}[0]->{Data}}; + + #notify($ERRORS{'DEBUG'}, 0, "event $event_record_id:\n" . format_data(\%event_data)); - # Check if file on the computer was not found on the management node - # If so, delete it from the computer - if (!defined($mn_files->{$relative_file_path})) { - notify($ERRORS{'DEBUG'}, 0, "file exists on $computer_node_name but not on management node: $computer_file_path"); - $self->delete_file($computer_file_path); - delete($computer_files->{$computer_file_path}); - next COMPUTER_FILE; + if (my $error_code = $event_data{ErrorCode}) { + my $error_string = $event_data{ErrorString} || '<none>'; + + if ($error_code eq '2359302') { + # Already installed but reboot is required + notify($ERRORS{'DEBUG'}, 0, "update $update_id is already installed but a reboot is required:\n" . format_data(\%event_data)); + $self->data->set_imagemeta_postoption('reboot'); + } + else { + notify($ERRORS{'WARNING'}, 0, "error occurred installing update $update_id:\n" . format_data(\%event_data)); + } } - elsif ($computer_file_path !~ /\.(cmd|bat)$/i) { - notify($ERRORS{'DEBUG'}, 0, "file on $computer_node_name not executed because extension is not .cmd or .bat: $computer_file_path"); - next COMPUTER_FILE; + elsif (my $debug_message = $event_data{DebugMessage}) { + if ($debug_message =~ /IsRebootRequired: 1/i) { + # RebootIfRequested.01446: Reboot is not scheduled. IsRunWizardStarted: 0, IsRebootRequired: 0, RestartMode: 1 + notify($ERRORS{'DEBUG'}, 0, "installed update $update_id, reboot is required:\n$debug_message"); + $self->data->set_imagemeta_postoption('reboot'); + } + elsif ($debug_message =~ /Update is already installed/i) { + # InstallWorker.01051: Update is already installed + notify($ERRORS{'DEBUG'}, 0, "update $update_id is already installed:\n$debug_message"); + } + elsif ($debug_message =~ /0X240006/i) { + # InstallWorker.01051: Update is already installed + notify($ERRORS{'DEBUG'}, 0, "error 0X240006 indicates that update $update_id is already installed:\n$debug_message"); + } + elsif ($debug_message =~ /0X80240017/i) { + notify($ERRORS{'WARNING'}, 0, "update is not intended for OS installed on $computer_node_name:\n$debug_message"); + return; + } + elsif ($debug_message !~ /((start|end) of search|Failed to get message for error)/i) { + notify($ERRORS{'DEBUG'}, 0, "debug message for installation of update $update_id:\n$debug_message"); + } + } + else { + notify($ERRORS{'DEBUG'}, 0, "event generated while installing update $update_id:\n" . format_data(\%event_data)); } - - $self->run_script($computer_file_path); } return 1; @@ -11105,6 +11309,53 @@ sub run_scripts { #///////////////////////////////////////////////////////////////////////////// +=head2 get_installed_updates + + Parameters : $no_cache (optional) + Returns : array + Description : Retrieves the list of updates installed on the computer. +=cut + +sub get_installed_updates { + my $self = shift; + if (ref($self) !~ /VCL::Module/) { + notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); + return; + } + + my $no_cache = shift; + + return $self->{update_ids} if (!$no_cache && $self->{update_ids}); + + my $computer_node_name = $self->data->get_computer_node_name(); + my $system32_path = $self->get_system32_path() || return; + + # wmic.exe will hang if it is called by itself. It has something to do with TTY/PTY + # Piping the echo command seems to prevent it from hanging + my $command = "echo | $system32_path/Wbem/wmic.exe QFE LIST BRIEF"; + notify($ERRORS{'DEBUG'}, 0, "retrieving list of installed updates on $computer_node_name, command: $command"); + my ($exit_status, $output) = $self->execute($command); + if (!defined($output)) { + notify($ERRORS{'WARNING'}, 0, "failed to execute command to list updates installed on $computer_node_name: $command"); + return; + } + + # Add update IDs found to a hash and then convert it to an array to eliminate duplicates + my %update_id_hash; + for my $line (@$output) { + # Parse the update ID from the line, may be in the form KB000000 + my ($update_id) = $line =~ /(kb\d+)/i; + $update_id_hash{$update_id} = 1 if $update_id; + } + + my @update_ids = sort keys %update_id_hash; + $self->{update_ids} = \@update_ids; + notify($ERRORS{'DEBUG'}, 0, "retrieved list updates installed on $computer_node_name(" . scalar(@update_ids) . "): " . join(", ", @update_ids)); + return @update_ids; +} + +#///////////////////////////////////////////////////////////////////////////// + =head2 format_text_file Parameters : $file_path Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows/Version_5.pm URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows/Version_5.pm?rev=1307108&r1=1307107&r2=1307108&view=diff ============================================================================== --- incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows/Version_5.pm (original) +++ incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows/Version_5.pm Thu Mar 29 21:05:08 2012 @@ -111,7 +111,7 @@ sub pre_capture { } notify($ERRORS{'OK'}, 0, "beginning Windows version 5 image capture preparation tasks"); - + # Check if Sysprep should be used if ($self->data->get_imagemeta_sysprep()) { if (!$self->run_sysprep()) { Modified: incubator/vcl/trunk/managementnode/lib/VCL/new.pm URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/new.pm?rev=1307108&r1=1307107&r2=1307108&view=diff ============================================================================== --- incubator/vcl/trunk/managementnode/lib/VCL/new.pm (original) +++ incubator/vcl/trunk/managementnode/lib/VCL/new.pm Thu Mar 29 21:05:08 2012 @@ -934,6 +934,9 @@ sub reserve_computer { $self->reservation_failed("failed to update public IP address"); } + # Update the $computer_ip_address varible in case the IP address was different than what was originally in the database + $computer_ip_address = $self->data->get_computer_ip_address(); + insertloadlog($reservation_id, $computer_id, "info", "node ready adding user account"); # Only generate new password if: Modified: incubator/vcl/trunk/managementnode/lib/VCL/utils.pm URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/utils.pm?rev=1307108&r1=1307107&r2=1307108&view=diff ============================================================================== --- incubator/vcl/trunk/managementnode/lib/VCL/utils.pm (original) +++ incubator/vcl/trunk/managementnode/lib/VCL/utils.pm Thu Mar 29 21:05:08 2012 @@ -10890,7 +10890,7 @@ sub sort_by_file_name { if (!defined($a) && !defined($b)) { my @file_paths = @_; if (scalar(@file_paths)) { - notify($ERRORS{'DEBUG'}, 0, "not called by sort, \$a and \$b are not defined, array argument was passed"); + #notify($ERRORS{'DEBUG'}, 0, "not called by sort, \$a and \$b are not defined, array argument was passed"); return sort sort_by_file_name @file_paths; } else {