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 {


Reply via email to