git-svn-id: https://svn.code.sf.net/p/xcat/code/xcat-core/trunk@16147 8638fb3e-16cb-4fca-ae20-7b5d299a9bcd
		
			
				
	
	
		
			6469 lines
		
	
	
		
			210 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			6469 lines
		
	
	
		
			210 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| #!/usr/bin/env perl
 | |
| # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
 | |
| 
 | |
| package xCAT::DSHCLI;
 | |
| BEGIN
 | |
| {
 | |
|     $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
 | |
| }
 | |
| use lib "$::XCATROOT/lib/perl";
 | |
| 
 | |
| use File::Basename;
 | |
| 
 | |
| use locale;
 | |
| use strict;
 | |
| use File::Path;
 | |
| use POSIX;
 | |
| use Socket;
 | |
| use Getopt::Long;
 | |
| require xCAT::DSHCore;
 | |
| use xCAT::MsgUtils;
 | |
| use xCAT::Utils;
 | |
| use xCAT::TableUtils;
 | |
| use lib '/opt/xcat/xdsh';
 | |
| our @dsh_available_contexts = ();
 | |
| our @dsh_valid_contexts     = ();
 | |
| 
 | |
| our $dsh_exec_state         = 0;
 | |
| our $dsh_forked_process     = undef;
 | |
| our $dsh_options            = undef;
 | |
| our $dsh_resolved_targets   = undef;
 | |
| our $dsh_unresolved_targets = undef;
 | |
| our %dsh_target_status      = undef;
 | |
| our $dsh_trace              = undef;
 | |
| our %dsh_stats              = ();
 | |
| our $signal_interrupt_flag  = 0;
 | |
| our $dsh_cmd_background     = 0;
 | |
| $::CONTEXT_DIR = "/opt/xcat/xdsh/Context/";
 | |
| $::__DCP_DELIM = 'Xcat,DELIMITER,Xcat';
 | |
| 
 | |
| our @dsh_valid_env = (
 | |
|                       'DCP_NODE_OPTS',       'DCP_NODE_RCP',
 | |
|                       'DSH_ENVIRONMENT',     'DSH_FANOUT',
 | |
|                       'DSH_LOG',             'DSH_NODEGROUP_PATH',
 | |
|                       'DSH_NODE_LIST',       'DSH_NODE_OPTS',
 | |
|                       'DSH_NODE_RCP',        'DSH_NODE_RSH',
 | |
|                       'DSH_OUTPUT',          'DSH_PATH',
 | |
|                       'DSH_SYNTAX',          'DSH_TIMEOUT',
 | |
|                       'DSH_REMOTE_PASSWORD', 'DSH_TO_USERID',
 | |
|                       'DSH_FROM_USERID',     'DEVICETYPE',
 | |
|                       'RSYNCSN',             'DSH_RSYNC_FILE',
 | |
|                       'RSYNCSNONLY',
 | |
|                       );
 | |
| select(STDERR);
 | |
| $| = 1;
 | |
| select(STDOUT);
 | |
| $| = 1;
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         execute_dcp
 | |
| 
 | |
|         This is the main driver routine for an instance of the dcp command.
 | |
|         Given the options configured in the $options hash table, the routine
 | |
|         configures the execution of the dcp instance, executes the remote copy
 | |
|         commands and processes the output from each target.
 | |
| 
 | |
|         Arguments:
 | |
| 			$options - options hash table describing dcp configuration options
 | |
| 
 | |
|         Returns:
 | |
|         	The number of targets that failed to execute a remote copy command
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub execute_dcp
 | |
| {
 | |
|     my ($class, $options) = @_;
 | |
| 
 | |
|     $::dsh_command = 'dcp';
 | |
| 
 | |
|     my $rsp = {};
 | |
|     $rsp->{data}->[0] = "dsh>  Dcp_process_id $$";
 | |
|     $$options{'monitor'} && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|     my $result = xCAT::DSHCLI->config_dcp($options);
 | |
|     $result && (return $result);
 | |
| 
 | |
|     my %resolved_targets   = ();
 | |
|     my %unresolved_targets = ();
 | |
|     my %context_targets    = ();
 | |
|     xCAT::DSHCLI->resolve_targets($options, \%resolved_targets,
 | |
|                                   \%unresolved_targets, \%context_targets);
 | |
| 
 | |
|     if (!scalar(%resolved_targets))
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] = "No hosts in node list 1";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return ++$result;
 | |
|     }
 | |
| 
 | |
|     $$options{'verify'}
 | |
|       && xCAT::DSHCLI->verify_targets($options, \%resolved_targets);
 | |
| 
 | |
|     #check if file descriptor number exceeds the max number in ulimit
 | |
|     #if (
 | |
|     #     xCAT::DSHCLI->isFdNumExceed(
 | |
|     #                          2, scalar(keys(%resolved_targets)),
 | |
|     #                          $$options{'fanout'}
 | |
|     #    )
 | |
|     # )
 | |
|     #{
 | |
|     #    my $rsp ={};
 | |
|     #    $rsp->{error}->[0] = " The DSH fanout value has exceeded the system file descriptor upper limit. Please either reduce the fanout value, or increase max file descriptor number by running ulimit.";
 | |
|     #    xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|     #   return ++$result;
 | |
|     #}
 | |
| 
 | |
|     my @targets_waiting  = (sort keys(%resolved_targets));
 | |
|     my %targets_active   = ();
 | |
|     my @targets_finished = ();
 | |
|     my @targets_failed   = ();
 | |
|     my %targets_buffered = ();
 | |
| 
 | |
|     my %output_buffers = ();
 | |
|     my %error_buffers  = ();
 | |
|     my %pid_targets    = ();
 | |
|     my %outfh_targets  = ();
 | |
|     my %errfh_targets  = ();
 | |
|     my %forked_process = ();
 | |
| 
 | |
|     my @output_files = ();
 | |
|     !$$options{'silent'} && push @output_files, *STDOUT;
 | |
|     my @error_files = ();
 | |
|     !$$options{'silent'} && push @error_files, *STDERR;
 | |
| 
 | |
|     xCAT::DSHCLI->fork_fanout_dcp(
 | |
|                                   $options,          \%resolved_targets,
 | |
|                                   \%forked_process,  \%pid_targets,
 | |
|                                   \%outfh_targets,   \%errfh_targets,
 | |
|                                   \@targets_waiting, \%targets_active
 | |
|                                   );
 | |
| 
 | |
|     while (keys(%targets_active))
 | |
|     {
 | |
|         my $rin = $outfh_targets{'bitmap'} | $errfh_targets{'bitmap'};
 | |
| 
 | |
|         my $fh_count =
 | |
|           select(my $rout = $rin, undef, undef, $$options{'timeout'});
 | |
| 
 | |
|         if ($fh_count == 0)
 | |
|         {
 | |
|             my @active_list = keys(%targets_active);
 | |
|             my $rsp         = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               " Timed out waiting for response from child processes for the following nodes.";
 | |
|             $rsp->{error}->[1] = " @active_list";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             kill 'INT', keys(%pid_targets);
 | |
|             $result++;
 | |
|             last;
 | |
|         }
 | |
| 
 | |
|         my @select_out_fhs =
 | |
|           xCAT::DSHCLI->util_bit_indexes($rout & $outfh_targets{'bitmap'}, 1);
 | |
| 
 | |
|         (@select_out_fhs)
 | |
|           && xCAT::DSHCLI->buffer_output(
 | |
|                      $options,           \%resolved_targets, \%targets_active,
 | |
|                      \@targets_finished, \@targets_failed,   \%targets_buffered,
 | |
|                      \%pid_targets,      \%forked_process,   \%outfh_targets,
 | |
|                      \%output_buffers,   \%error_buffers,    \@output_files,
 | |
|                      \@error_files,      \@select_out_fhs
 | |
|                      );
 | |
| 
 | |
|         my @select_err_fhs =
 | |
|           xCAT::DSHCLI->util_bit_indexes($rout & $errfh_targets{'bitmap'}, 1);
 | |
| 
 | |
|         (@select_err_fhs)
 | |
|           && xCAT::DSHCLI->buffer_error(
 | |
|                                         $options,         \%resolved_targets,
 | |
|                                         \%targets_active, \@targets_finished,
 | |
|                                         \@targets_failed, \%targets_buffered,
 | |
|                                         \%pid_targets,    \%forked_process,
 | |
|                                         \%errfh_targets,  \%output_buffers,
 | |
|                                         \%error_buffers,  \@output_files,
 | |
|                                         \@error_files,    \@select_err_fhs
 | |
|                                         );
 | |
| 
 | |
|         my @targets_buffered_keys = sort keys(%targets_buffered);
 | |
| 
 | |
|         foreach my $user_target (@targets_buffered_keys)
 | |
|         {
 | |
|             my $target_properties = $resolved_targets{$user_target};
 | |
| 
 | |
|             if (!$$options{'silent'})
 | |
|             {
 | |
|                 if ($::DCP_API)
 | |
|                 {
 | |
|                     $::DCP_API_MESSAGE .=
 | |
|                         join("", @{$output_buffers{$user_target}})
 | |
|                       . join("", @{$error_buffers{$user_target}});
 | |
|                     if ($$options{'display_output'})
 | |
|                     {
 | |
|                         print STDOUT @{$output_buffers{$user_target}};
 | |
|                         print STDERR @{$error_buffers{$user_target}};
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     print STDOUT @{$output_buffers{$user_target}};
 | |
|                     print STDERR @{$error_buffers{$user_target}};
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             delete $output_buffers{$user_target};
 | |
|             delete $error_buffers{$user_target};
 | |
| 
 | |
|             my $exit_code = $targets_buffered{$user_target}{'exit-code'};
 | |
|             
 | |
|             if ($exit_code != 0)
 | |
|             {
 | |
|                 # report error status  --nodestatus
 | |
|                 # Note the message below for node status must
 | |
|                 # not be NLS translated.  Code depends on the English. 
 | |
|                 if ($$options{'nodestatus'}) {
 | |
|                    my $rsp={};
 | |
|                   $rsp->{data}->[0] =
 | |
|                   "$user_target: Remote_command_failed, error_code=$exit_code";
 | |
|                    xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                 }
 | |
|                 push @targets_failed, $user_target;
 | |
|                 push @{$dsh_target_status{'failed'}}, $user_target;
 | |
| 
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 # Note the message below for node status must
 | |
|                 # not be NLS translated.  Code depends on the English. 
 | |
|                 if ($$options{'nodestatus'}) {
 | |
|                    my $rsp={};
 | |
|                   $rsp->{data}->[0] =
 | |
|                   "$user_target: Remote_command_successful";
 | |
|                    xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                 }
 | |
|                 push @targets_finished, $user_target;
 | |
|             }
 | |
|             
 | |
|             # return list of   badnodes and goodnodes
 | |
|             if ($$options{'monitor'}) {
 | |
|               foreach my $badnode (@targets_failed) {
 | |
|                  my $rsp={};
 | |
|                  $rsp->{data}->[0] =
 | |
|                  "dsh>  Remote_command_failed $badnode";
 | |
|                  xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|               }
 | |
|               foreach my $goodnode (@targets_finished) {
 | |
|                  my $rsp={};
 | |
|                  $rsp->{data}->[0] =
 | |
|                  "dsh>  Remote_command_successful $goodnode";
 | |
|                  xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|               }
 | |
|             }
 | |
|             delete $targets_buffered{$user_target};
 | |
|         }
 | |
| 
 | |
|         (@targets_waiting)
 | |
|           && xCAT::DSHCLI->fork_fanout_dcp(
 | |
|                         $options,          \%resolved_targets, \%forked_process,
 | |
|                         \%pid_targets,     \%outfh_targets,    \%errfh_targets,
 | |
|                         \@targets_waiting, \%targets_active
 | |
|                         );
 | |
|     }
 | |
|     if (scalar(@targets_failed) > 0)
 | |
|     {
 | |
|           #$::DCP_NODES_FAILED = join ",", @targets_failed;
 | |
|           @::DCP_NODES_FAILED =  @targets_failed;
 | |
|     }
 | |
| 
 | |
|     return (scalar(@targets_failed) + scalar(keys(%unresolved_targets)));
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         execute_dsh
 | |
| 
 | |
|         This is the main driver routine for an instance of the dsh command.
 | |
|         Given the options configured in the $options hash table, the routine
 | |
|         configures the execution of the dsh instance, executes the remote shell
 | |
|         commands and processes the output from each target.
 | |
| 
 | |
|         Arguments:
 | |
| 			$options - options hash table describing dsh configuration options
 | |
| 
 | |
|         Returns:
 | |
|         	The number of targets that failed to execute a remote shell command
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub execute_dsh
 | |
| {
 | |
|     my ($class, $options) = @_;
 | |
| 
 | |
|     $::dsh_command = 'dsh';
 | |
|     $dsh_options   = $options;
 | |
| 
 | |
|     xCAT::DSHCLI->config_signals_dsh($options);
 | |
| 
 | |
|     my $rsp = {};
 | |
|     $rsp->{data}->[0] = "dsh>  Dsh_process_id $$";
 | |
|     $$options{'monitor'} && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|     $dsh_exec_state++;
 | |
| 
 | |
|     my $result = xCAT::DSHCLI->config_dsh($options);
 | |
|     $result && (return $result);
 | |
| 
 | |
|     my $rsp = {};
 | |
|     $rsp->{data}->[0] = "dsh>  Dsh_initialization_completed";
 | |
|     $$options{'monitor'} && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|     $dsh_exec_state++;
 | |
| 
 | |
|     my %resolved_targets = ();
 | |
|     $dsh_resolved_targets = \%resolved_targets;
 | |
|     my %unresolved_targets = ();
 | |
|     $dsh_unresolved_targets = \%unresolved_targets;
 | |
|     my %context_targets = ();
 | |
|     xCAT::DSHCLI->resolve_targets($options, \%resolved_targets,
 | |
|                                   \%unresolved_targets, \%context_targets);
 | |
|     my @canceled_targets = ();
 | |
|     $dsh_target_status{'canceled'} = \@canceled_targets;
 | |
|     my $rsp = {};
 | |
| 
 | |
|     if (scalar(%unresolved_targets))
 | |
|     {
 | |
|         foreach my $target (sort keys(%unresolved_targets))
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             $rsp->{data}->[0] = "dsh>  Remote_command_cancelled $target";
 | |
|             $$dsh_options{'monitor'}
 | |
|               && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (!scalar(%resolved_targets))
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] = " No hosts in node list 2";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return ++$result;
 | |
|     }
 | |
|     $dsh_exec_state++;
 | |
| 
 | |
|     if ($$options{'verify'})
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = "dsh>  Dsh_verifying_hosts";
 | |
|         $$options{'monitor'} && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|         xCAT::DSHCLI->verify_targets($options, \%resolved_targets);
 | |
|     }
 | |
| 
 | |
|     #check if file descriptor number exceeds the max number in ulimit
 | |
|     #if (
 | |
|     #    xCAT::DSHCLI->isFdNumExceed(
 | |
|     #                        2, scalar(keys(%resolved_targets)),
 | |
|     #                       $$options{'fanout'}
 | |
|     # )
 | |
|     #)
 | |
|     #{
 | |
|     #    $rsp->{data}->[0] = " The DSH fanout value has exceeded the system file descriptor upper limit. Please either reduce the fanout value, or increase max file descriptor number by running ulimit.";
 | |
|     #    xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|     #   return ++$result;
 | |
|     #}
 | |
| 
 | |
|     my @targets_failed = ();
 | |
|     $dsh_target_status{'failed'} = \@targets_failed;
 | |
|     @targets_failed =
 | |
|       xCAT::DSHCLI->_execute_dsh($options, \%resolved_targets,
 | |
|                                  \%unresolved_targets, \%context_targets);
 | |
|     if (scalar(@targets_failed) > 0)
 | |
|     {
 | |
|          # $::DSH_NODES_FAILED = join ",", @targets_failed;
 | |
|           @::DSH_NODES_FAILED = @targets_failed;
 | |
|     }
 | |
| 
 | |
|     return (scalar(@targets_failed) + scalar(keys(%unresolved_targets)));
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         _execute_dsh
 | |
| 
 | |
|         Wrapper routine for execute_dsh 
 | |
|         to execute actual dsh call
 | |
| 
 | |
|         Arguments:
 | |
|         	$options - options hash table describing dsh configuration options
 | |
|         	$resolved_targets - hash table of resolved targets and target properties
 | |
|         	$unresolved_targets - hash table of unresolved targets and relevant properties
 | |
| 			$context_targets - hash table of targets grouped by context name
 | |
| 
 | |
|         Returns:
 | |
|         	@targets_failed - a list of those targets that failed execution
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
|         	Internal Routine only
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub _execute_dsh
 | |
| {
 | |
|     my ($class, $options, $resolved_targets, $unresolved_targets,
 | |
|         $context_targets)
 | |
|       = @_;
 | |
| 
 | |
|     my @output_files = ();
 | |
|     !$$options{'silent'} && push @output_files, *STDOUT;
 | |
| 
 | |
|     my @error_files = ();
 | |
|     !$$options{'silent'} && push @error_files, *STDERR;
 | |
|     my @targets_waiting = ();
 | |
|     @targets_waiting = (sort keys(%$resolved_targets));
 | |
|     my @dsh_target_status = ();
 | |
|     $dsh_target_status{'waiting'} = \@targets_waiting;
 | |
|     my %targets_active = ();
 | |
|     $dsh_target_status{'active'} = \%targets_active;
 | |
|     my @targets_finished = ();
 | |
|     $dsh_target_status{'finished'} = \@targets_finished;
 | |
|     my @targets_failed   = ();
 | |
|     my %targets_buffered = ();
 | |
| 
 | |
|     my %output_buffers = ();
 | |
|     my %error_buffers  = ();
 | |
|     my %pid_targets    = ();
 | |
|     my %outfh_targets  = ();
 | |
|     my %errfh_targets  = ();
 | |
|     my %forked_process = ();
 | |
|     $dsh_forked_process = \%forked_process;
 | |
| 
 | |
|     my $rsp = {};
 | |
|     my $result;
 | |
|     $rsp->{data}->[0] = "dsh>  Dsh_remote_execution_started";
 | |
|     $$options{'monitor'} && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|     $dsh_exec_state++;
 | |
| 
 | |
|     xCAT::DSHCLI->fork_fanout_dsh(
 | |
|                                   $options,          $resolved_targets,
 | |
|                                   \%forked_process,  \%pid_targets,
 | |
|                                   \%outfh_targets,   \%errfh_targets,
 | |
|                                   \@targets_waiting, \%targets_active
 | |
|                                   );
 | |
| 
 | |
|     while (keys(%targets_active))
 | |
|     {
 | |
| 
 | |
|         my $rin = $outfh_targets{'bitmap'} | $errfh_targets{'bitmap'};
 | |
| 
 | |
|         my $fh_count =
 | |
|           select(my $rout = $rin, undef, undef, $$options{'timeout'});
 | |
| 
 | |
|         if ($fh_count == 0)
 | |
|         {
 | |
|             my @active_list = keys(%targets_active);
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               " Timed out waiting for response from child processes for the following nodes. Terminating the child processes. ";
 | |
|             $rsp->{error}->[1] = " @active_list";
 | |
|             xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|             @targets_failed = keys(%targets_active);
 | |
| 
 | |
|             &handle_signal_dsh('INT', 2);
 | |
|             $result++;
 | |
| 
 | |
|             #last;
 | |
|         }
 | |
| 
 | |
|         my @select_out_fhs =
 | |
|           xCAT::DSHCLI->util_bit_indexes($rout & $outfh_targets{'bitmap'}, 1);
 | |
| 
 | |
|         if (@select_out_fhs)
 | |
|         {
 | |
|             if ($$options{'streaming'})
 | |
|             {
 | |
|                 xCAT::DSHCLI->stream_output(
 | |
|                         $options,           $resolved_targets, \%targets_active,
 | |
|                         \@targets_finished, \@targets_failed,  \%pid_targets,
 | |
|                         \%forked_process,   \%outfh_targets,   \%output_buffers,
 | |
|                         \@output_files,     \@select_out_fhs
 | |
|                         );
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 xCAT::DSHCLI->buffer_output(
 | |
|                       $options,           $resolved_targets, \%targets_active,
 | |
|                       \@targets_finished, \@targets_failed,  \%targets_buffered,
 | |
|                       \%pid_targets,      \%forked_process,  \%outfh_targets,
 | |
|                       \%output_buffers,   \%error_buffers,   \@output_files,
 | |
|                       \@error_files,      \@select_out_fhs
 | |
|                       );
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         my @select_err_fhs =
 | |
|           xCAT::DSHCLI->util_bit_indexes($rout & $errfh_targets{'bitmap'}, 1);
 | |
| 
 | |
|         if (@select_err_fhs)
 | |
|         {
 | |
|             if ($$options{'streaming'})
 | |
|             {
 | |
|                 xCAT::DSHCLI->stream_error(
 | |
|                         $options,           $resolved_targets, \%targets_active,
 | |
|                         \@targets_finished, \@targets_failed,  \%pid_targets,
 | |
|                         \%forked_process,   \%errfh_targets,   \%error_buffers,
 | |
|                         \@error_files,      \@select_err_fhs
 | |
|                         );
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 xCAT::DSHCLI->buffer_error(
 | |
|                       $options,           $resolved_targets, \%targets_active,
 | |
|                       \@targets_finished, \@targets_failed,  \%targets_buffered,
 | |
|                       \%pid_targets,      \%forked_process,  \%errfh_targets,
 | |
|                       \%output_buffers,   \%error_buffers,   \@output_files,
 | |
|                       \@error_files,      \@select_err_fhs
 | |
|                       );
 | |
|             }
 | |
|         }
 | |
|         # This is only if you are not streaming ( -s)
 | |
|         my @targets_buffered_keys = sort keys(%targets_buffered);
 | |
| 
 | |
|         foreach my $user_target (@targets_buffered_keys)
 | |
|         {
 | |
|             my $target_properties = $$resolved_targets{$user_target};
 | |
| 
 | |
|             my $output_file     = undef;
 | |
|             my $output_filename = undef;
 | |
|             my $error_file      = undef;
 | |
|             my $error_filename  = undef;
 | |
| 
 | |
|             if (!$$options{'silent'})
 | |
|             {
 | |
|                 if ($::DSH_API)
 | |
|                 {
 | |
|                     $::DSH_API_MESSAGE =
 | |
|                         $::DSH_API_MESSAGE
 | |
|                       . join("", @{$output_buffers{$user_target}})
 | |
|                       . join("", @{$error_buffers{$user_target}});
 | |
|                     if ($$options{'display_output'})
 | |
|                     {
 | |
| 
 | |
|                         # print STDOUT @{$output_buffers{$user_target}};
 | |
|                         # print STDERR @{$error_buffers{$user_target}};
 | |
|                         chomp(@{$output_buffers{$user_target}});
 | |
|                         chomp(@{$error_buffers{$user_target}});
 | |
|                         my $rsp = {};
 | |
|                         push @{$rsp->{data}}, @{$output_buffers{$user_target}};
 | |
|                         xCAT::MsgUtils->message("D", $rsp, $::CALLBACK);
 | |
|                         $rsp = {};
 | |
|                         push @{$rsp->{error}}, @{$error_buffers{$user_target}};
 | |
|                         $rsp->{NoErrorPrefix} = 1;
 | |
|                         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK,0);
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     # LKV: This is where the output shows up
 | |
|                     #print STDOUT @{$output_buffers{$user_target}};
 | |
|                     #print STDERR @{$error_buffers{$user_target}};
 | |
|                     chomp(@{$output_buffers{$user_target}});
 | |
|                     chomp(@{$error_buffers{$user_target}});
 | |
|                     my $rsp = {};
 | |
|                     push @{$rsp->{data}}, @{$output_buffers{$user_target}};
 | |
|                     xCAT::MsgUtils->message("D", $rsp, $::CALLBACK);
 | |
|                     $rsp = {};
 | |
|                     push @{$rsp->{error}}, @{$error_buffers{$user_target}};
 | |
|                     $rsp->{NoErrorPrefix} = 1;
 | |
|                     xCAT::MsgUtils->message("E", $rsp, $::CALLBACK,0);
 | |
| 
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             delete $output_buffers{$user_target};
 | |
|             delete $error_buffers{$user_target};
 | |
| 
 | |
|             my $exit_code = $targets_buffered{$user_target}{'exit-code'};
 | |
|             my $target_rc = $targets_buffered{$user_target}{'target-rc'};
 | |
|             my $rsp       = {};
 | |
| 
 | |
|             if ($exit_code != 0)
 | |
|             {
 | |
| 
 | |
|                 # Note the message below for node status must
 | |
|                 # not be NLS translated.  Code depends on the English. 
 | |
|                 # report error status  --nodestatus
 | |
|                 if ($$options{'nodestatus'}) {
 | |
|                    my $rsp={};
 | |
|                   $rsp->{data}->[0] =
 | |
|                   "$user_target: Remote_command_failed, error_code=$exit_code";
 | |
|                    xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                 }
 | |
|                 # for monitoring -m
 | |
|                 my $rsp = {};
 | |
|                 $rsp->{error}->[0] = "dsh>  Remote_command_failed $user_target";
 | |
|                 $$options{'monitor'}
 | |
|                   && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                 if (!grep(/$user_target/, @targets_failed))
 | |
|                 {    # not already in list
 | |
| 
 | |
|                     push @targets_failed, $user_target;
 | |
|                 }
 | |
|                 push @{$dsh_target_status{'failed'}}, $user_target
 | |
|                   if !$signal_interrupt_flag;
 | |
| 
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 if ($target_rc != 0)
 | |
|                 {
 | |
|                   # Note the message below for node status must
 | |
|                   # not be NLS translated.  Code depends on the English. 
 | |
|                    # report error status  --nodestatus
 | |
|                    if ($$options{'nodestatus'}) {
 | |
|                      my $rsp={};
 | |
|                      $rsp->{data}->[0] =
 | |
|                    "$user_target: Remote_command_failed, error_code=$target_rc";
 | |
|                      xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                    }
 | |
|                     # if monitoring
 | |
|                     $rsp={};
 | |
|                     $rsp->{data}->[0] =
 | |
|                       "dsh>  Remote_command_failed $user_target";
 | |
|                     $$options{'monitor'}
 | |
|                       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK, 1);
 | |
| 
 | |
|                     push @targets_failed, $user_target;
 | |
|                     push @{$dsh_target_status{'failed'}}, $user_target
 | |
|                       if !$signal_interrupt_flag;
 | |
|                 }
 | |
| 
 | |
|                 elsif (!defined($target_rc) && !$dsh_cmd_background && ($::DSH_MELLANOX_SWITCH==0) && ($$options{'devicetype'}!~ /EthSwitch/))
 | |
|                 {
 | |
|                    # report error status  --nodestatus
 | |
|                   # Note the message below for node status must
 | |
|                   # not be NLS translated.  Code depends on the English. 
 | |
|                    if ($$options{'nodestatus'}) {
 | |
|                      my $rsp={};
 | |
|                      $rsp->{data}->[0] =
 | |
|                    "$user_target: Remote_command_failed, error_code=???";
 | |
|                      xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                    }
 | |
| 
 | |
|                     $rsp={};
 | |
|                     $rsp->{error}->[0] =
 | |
|                       " A return code for the command run on the host $user_target was not received.";
 | |
|                     xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
| 
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{error}->[0] =
 | |
|                       "dsh>  Remote_command_failed $user_target";
 | |
|                     $$options{'monitor'}
 | |
|                       && xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|                     push @targets_failed, $user_target;
 | |
|                     push @{$dsh_target_status{'failed'}}, $user_target
 | |
|                       if !$signal_interrupt_flag;
 | |
|                 }
 | |
| 
 | |
|                 else   # it worked
 | |
|                 {
 | |
|                   # Note the message below for node status must
 | |
|                   # not be NLS translated.  Code depends on the English. 
 | |
|                    if ($$options{'nodestatus'}) {
 | |
|                     my $rsp={};
 | |
|                     $rsp->{data}->[0] =
 | |
|                     "$user_target: Remote_command_successful";
 | |
|                      xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                     }
 | |
|                     $rsp={};
 | |
|                     $rsp->{data}->[0] =
 | |
|                       "dsh>  Remote_command_successful $user_target";
 | |
|                     $$options{'monitor'}
 | |
|                       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|                     push @targets_finished, $user_target;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             delete $targets_buffered{$user_target};
 | |
|         }
 | |
| 
 | |
|         (@targets_waiting)
 | |
|           && xCAT::DSHCLI->fork_fanout_dsh(
 | |
|                          $options,          $resolved_targets, \%forked_process,
 | |
|                          \%pid_targets,     \%outfh_targets,   \%errfh_targets,
 | |
|                          \@targets_waiting, \%targets_active
 | |
|                          );
 | |
| 
 | |
|     }
 | |
| 
 | |
|     $dsh_exec_state++;
 | |
| 
 | |
|     if ($$options{'stats'})
 | |
|     {
 | |
|         $dsh_stats{'end-time'} = localtime();
 | |
| 
 | |
|         scalar(@targets_finished)
 | |
|           && ($dsh_stats{'successful-targets'} = \@targets_finished);
 | |
|         if (scalar(@targets_failed))
 | |
|         {
 | |
|             if (scalar(@{$dsh_target_status{'failed'}}))
 | |
|             {
 | |
|                 $dsh_stats{'failed-targets'} = $dsh_target_status{'failed'};
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 $dsh_stats{'failed-targets'} = \@targets_failed;
 | |
|             }
 | |
|         }
 | |
|         scalar(@{$dsh_target_status{'canceled'}})
 | |
|           && ($dsh_stats{'canceled-targets'} = $dsh_target_status{'canceled'});
 | |
|     }
 | |
|     my $rsp = {};
 | |
|     $rsp->{data}->[0] = "dsh>  Remote_command_execution_completed";
 | |
|     $$options{'monitor'} && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|     return @targets_failed;
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         fork_fanout_dcp
 | |
| 
 | |
|         Main process forking routine for an instance of the dcp command.
 | |
|         The routine creates forked processes of a remote copy command up to
 | |
|         the configured fanout value.  Then output from each process is
 | |
|         processed.  The number of currently forked processes is consistently
 | |
|         maintained up to the configured fanout value
 | |
| 
 | |
|         Arguments:
 | |
| 			$options - options hash table describing dsh configuration options
 | |
|         	$resolved_targets - hash table of resolved targets and target properties
 | |
|         	$forked_process - hash table of process information keyed by target name
 | |
|         	$pid_targets - hash table of target names keyed by process ID
 | |
|         	$outfh_targets - hash table of STDOUT pipe handles keyed by target name
 | |
|         	$errfh_targets - hash table of STDERR pipe handles keyed by target name
 | |
|         	$targets_waiting - array of targets pending remote execution
 | |
|         	$targets_active - hash table of currently active targets with output possibly available
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub fork_fanout_dcp
 | |
| {
 | |
|     my (
 | |
|         $class,          $options,         $resolved_targets,
 | |
|         $forked_process, $pid_targets,     $outfh_targets,
 | |
|         $errfh_targets,  $targets_waiting, $targets_active
 | |
|       )
 | |
|       = @_;
 | |
| 
 | |
|     while (@$targets_waiting
 | |
|            && (keys(%$targets_active) < $$options{'fanout'}))
 | |
|     {
 | |
|         my $user_target       = shift @$targets_waiting;
 | |
|         my $target_properties = $$resolved_targets{$user_target};
 | |
| 
 | |
|         my @dcp_command;
 | |
|         my $rsyncfile;
 | |
| 
 | |
|         if (!$$target_properties{'localhost'})  # this is to a remote host
 | |
|         {
 | |
|             my $target_type = $$target_properties{'type'};
 | |
| 
 | |
|             my %rcp_config = ();
 | |
| 
 | |
|             my $remote_copy;
 | |
|             my $rsh_extension = 'RSH';
 | |
| 
 | |
|             if ($target_type eq 'node')
 | |
|             {
 | |
|                 $remote_copy =
 | |
|                      $$options{'node-rcp'}{$$target_properties{'context'}}
 | |
|                   || $$target_properties{'remote-copy'};
 | |
|                 ($remote_copy =~ /\/scp$/)   && ($rsh_extension = 'SSH');
 | |
|                 ($remote_copy =~ /\/rsync$/) && ($rsh_extension = 'RSYNC');
 | |
|                 $rcp_config{'options'} =
 | |
|                   $$options{'node-options'}{$$target_properties{'context'}};
 | |
|             }
 | |
| 
 | |
|             $rcp_config{'preserve'}  = $$options{'preserve'};
 | |
|             $rcp_config{'recursive'} = $$options{'recursive'};
 | |
|             $rcp_config{'sudo'} = $$options{'sudo'};
 | |
| 
 | |
|             if ($$options{'pull'})
 | |
|             {
 | |
|                 $rcp_config{'src-user'} = $$target_properties{'user'}
 | |
|                   || $$options{'user'};
 | |
|                 $rcp_config{'src-host'} = $$target_properties{'hostname'};
 | |
|                 $rcp_config{'src-file'} = $$options{'source'};
 | |
| 
 | |
|                 my @target_file = split '/', $$options{'source'};
 | |
|                 $rcp_config{'dest-file'} =
 | |
|                   "$$options{'target'}/$target_file[$#target_file]._$$target_properties{'hostname'}";
 | |
| 
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 $rcp_config{'src-file'}  = $$options{'source'};
 | |
|                 $rcp_config{'dest-host'} = $$target_properties{'hostname'};
 | |
|                 $rcp_config{'dest-file'} = $$options{'target'};
 | |
|                 $rcp_config{'dest-user'} = $$target_properties{'user'}
 | |
|                   || $$options{'user'};
 | |
|                 $rcp_config{'destDir_srcFile'} =
 | |
|                   $$options{'destDir_srcFile'}{$user_target};
 | |
|             }
 | |
| 
 | |
|             #eval "require RemoteShell::$rsh_extension";
 | |
|             eval "require xCAT::$rsh_extension";
 | |
|             my $remoteshell = "xCAT::$rsh_extension";
 | |
|             # HERE: Build the dcp command based on the arguments
 | |
|             my $localhost=0;   # this is from the MN to another node 
 | |
|             @dcp_command =
 | |
|               $remoteshell->remote_copy_command(\%rcp_config, $remote_copy);
 | |
|             # add sudo  for non-root users
 | |
|             if ($$options{'sudo'}) {
 | |
|                   unshift (@dcp_command,'sudo'); 
 | |
|             }
 | |
| 
 | |
|         }
 | |
|         else  # this is the local host ( running on the Management Node)
 | |
|         {
 | |
|             if ($$options{'destDir_srcFile'}{$user_target})
 | |
|             {
 | |
|              my $target_type = $$target_properties{'type'};
 | |
| 
 | |
|              my %rcp_config = ();
 | |
| 
 | |
|              my $remote_copy;
 | |
|              my $rsh_extension = 'RSH';
 | |
| 
 | |
|              if ($target_type eq 'node')
 | |
|              {
 | |
|                 $remote_copy =
 | |
|                      $$options{'node-rcp'}{$$target_properties{'context'}}
 | |
|                   || $$target_properties{'remote-copy'};
 | |
|                 ($remote_copy =~ /\/scp$/)   && ($rsh_extension = 'SSH');
 | |
|                 ($remote_copy =~ /\/rsync$/) && ($rsh_extension = 'RSYNC');
 | |
|                 $rcp_config{'options'} =
 | |
|                   $$options{'node-options'}{$$target_properties{'context'}};
 | |
|              }
 | |
| 
 | |
|              $rcp_config{'preserve'}  = $$options{'preserve'};
 | |
|              $rcp_config{'recursive'} = $$options{'recursive'};
 | |
| 
 | |
| 
 | |
|              $rcp_config{'src-file'}  = $$options{'source'};
 | |
|              $rcp_config{'dest-host'} = $$target_properties{'hostname'};
 | |
|              $rcp_config{'dest-file'} = $$options{'target'};
 | |
|              $rcp_config{'dest-user'} = $$target_properties{'user'}
 | |
|                   || $$options{'user'};
 | |
|              $rcp_config{'destDir_srcFile'} =
 | |
|                   $$options{'destDir_srcFile'}{$user_target};
 | |
| 
 | |
|              eval "require xCAT::$rsh_extension";
 | |
|              my $remoteshell = "xCAT::$rsh_extension";
 | |
|              # HERE: Build the dcp command based on the arguments
 | |
|              my $localhost=1;   # this is on the MN to the MN
 | |
|              @dcp_command =
 | |
|               $remoteshell->remote_copy_command(\%rcp_config, $remote_copy,$localhost);
 | |
|                 
 | |
|              }
 | |
|              else    # just a copy not a sync
 | |
|              {
 | |
|                 @dcp_command =
 | |
|                   ('/bin/cp', '-r', $$options{'source'}, $$options{'target'});
 | |
|              }
 | |
|         }
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = " TRACE: Executing Command:@dcp_command";
 | |
|         $dsh_trace
 | |
|           && (xCAT::MsgUtils->message("I", $rsp, $::CALLBACK));
 | |
| 
 | |
|         my @process_info =
 | |
|           xCAT::DSHCore->fork_output($user_target, @dcp_command);
 | |
|         vec($$outfh_targets{'bitmap'}, fileno($process_info[1]), 1) = 1;
 | |
|         vec($$errfh_targets{'bitmap'}, fileno($process_info[2]), 1) = 1;
 | |
|         $$outfh_targets{fileno($process_info[1])} = $user_target;
 | |
|         $$errfh_targets{fileno($process_info[2])} = $user_target;
 | |
| 
 | |
|         $$forked_process{$user_target} = \@process_info;
 | |
|         $$targets_active{$user_target}++;
 | |
|         $$pid_targets{$process_info[0]} = $user_target;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         fork_fanout_dsh
 | |
| 
 | |
|         Main process forking routine for an instance of the dsh command.
 | |
|         The routine creates forked processes of a remote shell command up to
 | |
|         the configured fanout value.  Then output from each process is
 | |
|         processed.  The number of currently forked processes is consistently
 | |
|         maintained up to the configured fanout value
 | |
| 
 | |
|         Arguments:
 | |
| 			$options - options hash table describing dsh configuration options
 | |
|         	$resolved_targets - hash table of resolved targets and target properties
 | |
|         	$forked_process - hash table of process information keyed by target name
 | |
|         	$pid_targets - hash table of target names keyed by process ID
 | |
|         	$outfh_targets - hash table of STDOUT pipe handles keyed by target name
 | |
|         	$errfh_targets - hash table of STDERR pipe handles keyed by target name
 | |
|         	$targets_waiting - array of targets pending remote execution
 | |
|         	$targets_active - hash table of currently active targets with output possibly available
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub fork_fanout_dsh
 | |
| {
 | |
|     my (
 | |
|         $class,          $options,         $resolved_targets,
 | |
|         $forked_process, $pid_targets,     $outfh_targets,
 | |
|         $errfh_targets,  $targets_waiting, $targets_active
 | |
|       )
 | |
|       = @_;
 | |
| 
 | |
|     #get username and passeword for ether net switches (EthSwitch type)
 | |
|     if ($$options{'devicetype'} =~ /EthSwitch/) {
 | |
|         if (@$targets_waiting > 0) {
 | |
|             if ($ENV{'DSH_REMOTE_PASSWORD'}) {
 | |
|                 foreach my $t (keys(%$resolved_targets)) {
 | |
|                     $resolved_targets->{$t}->{'password'}=$ENV{'DSH_REMOTE_PASSWORD'};
 | |
|                     $resolved_targets->{$t}->{'user'}=$$options{'user'};
 | |
|                 }
 | |
|             } else {
 | |
|                 #get user name and password  from the switches table
 | |
|                  my $switchestab=xCAT::Table->new('switches',-create=>0);
 | |
| 
 | |
|                  my $switchents = $switchestab->getNodesAttribs($targets_waiting,[qw/switch sshusername sshpassword protocol/]);
 | |
|                  foreach my $entry (values %$switchents) {
 | |
|                     my $switch=$entry->[0]->{switch};
 | |
|                     if (defined($entry->[0]->{sshusername})) {
 | |
|                         $resolved_targets->{$switch}->{'user'}=$entry->[0]->{sshusername};
 | |
|                     }
 | |
|                     if (defined($entry->[0]->{sshpassword})) {
 | |
|                         $resolved_targets->{$switch}->{'password'}=$entry->[0]->{sshpassword};
 | |
|                     }
 | |
|                     if (defined($entry->[0]->{protocol})) {
 | |
|                         $resolved_targets->{$switch}->{'remotecmdproto'}=$entry->[0]->{protocol};
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     # save the original exports,  we are going to add the unique node name below
 | |
|     my $origprecommand=$$options{'pre-command'};
 | |
|     while (@$targets_waiting
 | |
|            && (keys(%$targets_active) < $$options{'fanout'}))
 | |
|     {
 | |
|         my $user_target       = shift @$targets_waiting;
 | |
|         # now add export NODE=nodename to the pre-command;
 | |
|         my $exportnode="export NODE=$user_target;";
 | |
|         $$options{'pre-command'} = $exportnode . $origprecommand;
 | |
|         my $target_properties = $$resolved_targets{$user_target};
 | |
|         my @commands;
 | |
|         my $localShell        =
 | |
|           ($$options{'syntax'} eq 'csh') ? '/bin/csh' : '/bin/sh';
 | |
|         my @dsh_command = ($localShell, '-c');
 | |
|         $$options{'command'} =~ s/\s*$//;
 | |
|         $$options{'command'} =~ s/;$//;
 | |
| 
 | |
|         if ($$options{'command'} =~ /\&$/)
 | |
|         {
 | |
|             $$options{'post-command'} = "";
 | |
|             $dsh_cmd_background = 1;
 | |
|         }
 | |
|         if ($$options{'environment'})
 | |
|         {
 | |
|             # if we are on a servicenode need to get the environment file
 | |
|             # from the SNsyncfiledir, not local
 | |
|             if (xCAT::Utils->isServiceNode()) {
 | |
|               my $newenvfile;
 | |
|               my $synfiledir = "/var/xcat/syncfiles"; #default
 | |
| 
 | |
|               # get the directory on the servicenode to and add to filepath 
 | |
|               my @syndir= xCAT::TableUtils->get_site_attribute("SNsyncfiledir");
 | |
|               if ($syndir[0])
 | |
|               {
 | |
|                 $synfiledir = $syndir[0];
 | |
|               }
 | |
|               $newenvfile = $synfiledir;
 | |
|               $newenvfile .= $$options{'environment'};
 | |
|               $$options{'environment'} =  $newenvfile;
 | |
|             }
 | |
|             if (!(-e $$options{'environment'}))
 | |
|             {
 | |
|               my $rsp={};
 | |
|                $rsp->{error}->[0] = "File $$options{'environment'} does not exist";
 | |
|                xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|             }
 | |
|             push @dsh_command,
 | |
|               "$$options{'pre-command'} . $$options{'environment'} ; $$options{'command'}$$options{'post-command'}";
 | |
|         }
 | |
| 
 | |
|         else
 | |
|         {
 | |
|             push @dsh_command,
 | |
|               "$$options{'pre-command'}$$options{'command'}$$options{'post-command'}";
 | |
|         }
 | |
| 
 | |
|         if ($$target_properties{'localhost'})
 | |
|         {
 | |
|             if (my $specified_usr =
 | |
|                 ($$target_properties{'user'} || $$options{'user'}))
 | |
|             {
 | |
|                 my $current_usr = getpwuid($>);
 | |
|                 if ($specified_usr ne $current_usr)
 | |
|                 {
 | |
|                     delete $$target_properties{'localhost'};
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!$$target_properties{'localhost'})
 | |
|         {
 | |
|             @dsh_command = ();
 | |
| 
 | |
|             my $target_type = $$target_properties{'type'};
 | |
| 
 | |
|             my %rsh_config = ();
 | |
|             $rsh_config{'hostname'} = $$target_properties{'hostname'};
 | |
|             $rsh_config{'user'}     = $$target_properties{'user'}
 | |
|               || $$options{'user'};
 | |
| 
 | |
|             my $remote_shell;
 | |
|             my $rsh_extension = 'RSH';
 | |
| 
 | |
|             if ($target_type eq 'node')
 | |
|             {
 | |
|                 my $context = $$target_properties{'context'};
 | |
|                      $remote_shell = $$options{'node-rsh'}{$context}
 | |
|                   || $$options{'node-rsh'}{'none'}
 | |
|                   || $$target_properties{'remote-shell'}
 | |
|                   || $$options{'node-rsh-defaults'}{$context};
 | |
|                 ($remote_shell =~ /\/ssh$/) && ($rsh_extension = 'SSH');
 | |
| 
 | |
|                 if ($$options{'devicetype'} =~ /EthSwitch/) {
 | |
|                     $remote_shell = "$::XCATROOT/sbin/rshell_api";
 | |
|                     $rsh_extension='RShellAPI';
 | |
|                     $rsh_config{'password'}=$$target_properties{'password'};
 | |
| 		            if ($$target_properties{'remotecmdproto'}) {
 | |
| 			            $rsh_config{'remotecmdproto'}=$$target_properties{'remotecmdproto'};
 | |
| 		            }
 | |
|                   if ($$options{'trace'}) {
 | |
| 	         		  $rsh_config{'trace'}=1;
 | |
| 		            }
 | |
|                 }
 | |
| 
 | |
|                 # will not set -n for any command,  causing problems
 | |
|                 # with running xdsh to install rpms and start xcatd on AIX
 | |
|                 # if IB switch device, do not set -n, causes no returncode
 | |
|                 #if  (($$options{'devicetype'})) {
 | |
| 
 | |
|                 $rsh_config{'options'} =
 | |
|                   $$options{'node-options'}{$$target_properties{'context'}};
 | |
| 
 | |
|                 #} else { # not a device
 | |
|                 #  $rsh_config{'options'} = "-n "
 | |
|                 #. $$options{'node-options'}{$$target_properties{'context'}};
 | |
|                 #}
 | |
|             }
 | |
| 
 | |
|             #eval "require RemoteShell::$rsh_extension";
 | |
|             eval "require xCAT::$rsh_extension";
 | |
| 
 | |
|             $rsh_config{'command'} = "$$options{'pre-command'}";
 | |
|             my $tmp_env_file;
 | |
|             # for the -E flag here we build and copy the -E env variable
 | |
|             # file to the nodes
 | |
|             if ($$options{'environment'})
 | |
|             {
 | |
|                 
 | |
|                 my $rsp = {};
 | |
|                 $rsp->{data}->[0] = "TRACE: Environment option specified";
 | |
|                 $dsh_trace && (xCAT::MsgUtils->message("I", $rsp, $::CALLBACK));
 | |
|                 my %env_rcp_config = ();
 | |
|                 $tmp_env_file = POSIX::tmpnam . '.dsh';
 | |
|                 $rsh_config{'command'} .= ". $tmp_env_file ; ";
 | |
| 
 | |
|                 $env_rcp_config{'src-file'}  = $$options{'environment'};
 | |
|                 $env_rcp_config{'dest-host'} = $$target_properties{'hostname'};
 | |
|                 $env_rcp_config{'dest-file'} = $tmp_env_file;
 | |
|                 $env_rcp_config{'dest-user'} = $$target_properties{'user'}
 | |
|                   || $$options{'user'};
 | |
| 
 | |
|                 my $env_rcp_command   = undef;
 | |
|                 my $env_rcp_extension = $rsh_extension;
 | |
| 
 | |
|                 ($$target_properties{'type'} eq 'node')
 | |
|                   && ($env_rcp_command = $ENV{'DSH_NODE_RCP'});
 | |
| 
 | |
|                 if ($env_rcp_command)
 | |
|                 {
 | |
|                     ($env_rcp_command =~ /\/rcp$/)
 | |
|                       && ($env_rcp_extension = 'RSH');
 | |
|                     ($env_rcp_command =~ /\/scp$/)
 | |
|                       && ($env_rcp_extension = 'SSH');
 | |
|                 }
 | |
| 
 | |
|                 #eval "require RemoteShell::$env_rcp_extension";
 | |
|                 eval "require xCAT::$env_rcp_extension";
 | |
|                 my $rcp             = "xCAT::$env_rcp_extension";
 | |
|                 my @env_rcp_command =
 | |
|                   $rcp->remote_copy_command(\%env_rcp_config);
 | |
| 
 | |
|                 my $rsp = {};
 | |
|                 $rsp->{data}->[0] =
 | |
|                   "TRACE:Environment: Exporting File.@env_rcp_command ";
 | |
|                 $dsh_trace && (xCAT::MsgUtils->message("I", $rsp, $::CALLBACK));
 | |
|                 # copy the Env Variable input file to the nodes
 | |
|                 my @env_rcp_process =
 | |
|                   xCAT::DSHCore->fork_no_output($user_target, @env_rcp_command);
 | |
|                 waitpid($env_rcp_process[0], undef);
 | |
|                 #push @commands, \@env_rcp_command;
 | |
|             }
 | |
|             my $tmp_cmd_file;
 | |
|             if ($$options{'execute'})
 | |
|             {
 | |
|                 # first build the scp command to copy the file to execute
 | |
|                 # down to the node into /tmp/*.dsh 
 | |
|                 my $rsp = {};
 | |
|                 $rsp->{data}->[0] = "TRACE: Execute option specified.";
 | |
|                 $dsh_trace && (xCAT::MsgUtils->message("I", $rsp, $::CALLBACK));
 | |
| 
 | |
|                 my %exe_rcp_config = ();
 | |
|                 $tmp_cmd_file = POSIX::tmpnam . ".dsh"; 
 | |
| 
 | |
|                 my ($exe_cmd, @args) = @{$$options{'execute'}};
 | |
|                 my $chmod_cmd = "";
 | |
|                 # now build the xdsh of the file buffer
 | |
|                 # add sudo  for non-root users
 | |
|                 if ($$options{'sudo'}) {
 | |
|                   $rsh_config{'command'} .= "sudo ";
 | |
|                   $rsh_config{'sudo'} = "sudo ";
 | |
|                 }
 | |
|                 $rsh_config{'command'} .=
 | |
|                   "$chmod_cmd $tmp_cmd_file @args$$options{'post-command'}";
 | |
|                 $exe_rcp_config{'src-file'}  = $exe_cmd;
 | |
|                 $exe_rcp_config{'dest-host'} = $$target_properties{'hostname'};
 | |
|                 $exe_rcp_config{'dest-file'} = $tmp_cmd_file;
 | |
|                 $exe_rcp_config{'dest-user'} = $$target_properties{'user'}
 | |
|                   || $$options{'user'};
 | |
| 
 | |
|                 my $exe_rcp_command   = undef;
 | |
|                 my $exe_rcp_extension = $rsh_extension;
 | |
| 
 | |
|                 ($$target_properties{'type'} eq 'node')
 | |
|                   && ($exe_rcp_command = $ENV{'DSH_NODE_RCP'});
 | |
| 
 | |
|                 if ($exe_rcp_command)
 | |
|                 {
 | |
|                     ($exe_rcp_command =~ /\/rcp$/)
 | |
|                       && ($exe_rcp_extension = 'RSH');
 | |
|                     ($exe_rcp_command =~ /\/scp$/)
 | |
|                       && ($exe_rcp_extension = 'SSH');
 | |
|                 }
 | |
| 
 | |
|                 #eval "require RemoteShell::$exe_rcp_extension";
 | |
|                 eval "require xCAT::$exe_rcp_extension";
 | |
|                 my $rcp             = "xCAT::$exe_rcp_extension";
 | |
|                 my @exe_rcp_command =
 | |
|                   $rcp->remote_copy_command(\%exe_rcp_config);
 | |
| 
 | |
|                 my $rsp = {};
 | |
|                 $rsp->{data}->[0] =
 | |
|                   "TRACE:Execute: Exporting File:@exe_rcp_command";
 | |
|                 $dsh_trace && (xCAT::MsgUtils->message("I", $rsp, $::CALLBACK));
 | |
|                 #my @exe_rcp_process =
 | |
|                 #  xCAT::DSHCore->fork_no_output($user_target, @exe_rcp_command);
 | |
|                 #waitpid($exe_rcp_process[0], undef);
 | |
|                 push @commands, \@exe_rcp_command;
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 # add sudo  for non-root users
 | |
|                if ($$options{'sudo'}) {
 | |
|                 $rsh_config{'command'} .= "sudo ";
 | |
|                 $rsh_config{'sudo'} = "sudo ";
 | |
|                }
 | |
|                 $rsh_config{'command'} .=
 | |
|                   "$$options{'command'}$$options{'post-command'}";
 | |
|             }
 | |
|             if ($$options{'environment'})
 | |
|             {
 | |
|                 $rsh_config{'command'} .= ";rm $tmp_env_file";
 | |
|             }
 | |
|             if ($$options{'execute'})
 | |
|             {
 | |
|                 $rsh_config{'command'} .= ";rm $tmp_cmd_file"; 
 | |
|             }
 | |
| 
 | |
|             #eval "require RemoteShell::$rsh_extension";
 | |
|             eval "require xCAT::$rsh_extension";
 | |
|             my $remoteshell = "xCAT::$rsh_extension";
 | |
|             push @dsh_command,
 | |
|               $remoteshell->remote_shell_command(\%rsh_config, $remote_shell);
 | |
| 
 | |
|         }
 | |
| 
 | |
|         my @process_info;
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = "Command name: @dsh_command";
 | |
|         $dsh_trace && (xCAT::MsgUtils->message("I", $rsp, $::CALLBACK));
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = "dsh>  Remote_command_started $user_target";
 | |
|         $$options{'monitor'} && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|         
 | |
|         #  execute and remove the /tmp file build which is a copy of the
 | |
|         # input -E file
 | |
|         #print "Command=@dsh_command\n";
 | |
| 
 | |
|         #@process_info = xCAT::DSHCore->fork_output($user_target, @dsh_command);
 | |
|         push (@commands, \@dsh_command);  #print Dumper(\@commands);
 | |
|         @process_info = xCAT::DSHCore->fork_output_for_commands($user_target, @commands); 
 | |
|         if ($process_info[0] == -2)
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "$user_target could not execute this command $dsh_command[0] - $$options{'command'} ,  $! ";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         }
 | |
| 
 | |
|         ($process_info[0] == -3) && (&handle_signal_dsh('INT', 1));
 | |
| 
 | |
|         if ($process_info[0] == -4)
 | |
|         {
 | |
| 
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] = "Cannot redirect STDOUT, error= $!";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         }
 | |
| 
 | |
|         if ($process_info[0] == -5)
 | |
|         {
 | |
| 
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] = "Cannot redirect STDERR, error= $!";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         }
 | |
| 
 | |
|         vec($$outfh_targets{'bitmap'}, fileno($process_info[1]), 1) = 1;
 | |
|         vec($$errfh_targets{'bitmap'}, fileno($process_info[2]), 1) = 1;
 | |
|         $$outfh_targets{fileno($process_info[1])} = $user_target;
 | |
|         $$errfh_targets{fileno($process_info[2])} = $user_target;
 | |
| 
 | |
|         $$forked_process{$user_target} = \@process_info;
 | |
|         $$targets_active{$user_target}++;
 | |
|         $$pid_targets{$process_info[0]} = $user_target;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         buffer_output
 | |
| 
 | |
|         For a given list of targets with output available, this routine buffers
 | |
|         output from the targets STDOUT pipe handles into buffers grouped by
 | |
|         target name
 | |
| 
 | |
|         Arguments:
 | |
| 			$options - options hash table describing dsh configuration options
 | |
|         	$resolved_targets - hash table of resolved targets and target properties
 | |
|         	$targets_active - hash table of currently active targets with output possibly available
 | |
|         	$targets_finished - list of targets with all output processed and have completed execution
 | |
|         	$targets_failed - list of targets that have unsuccessfully executed
 | |
|         	$targets_buffered - hash table of buffers with output from active targets
 | |
|         	$pid_targets - hash table of target names keyed by process ID
 | |
|         	$forked_process - hash table of process information keyed by target name
 | |
|         	$errfh_targets - hash table of STDERR pipe handles keyed by target name
 | |
|         	$output_buffers - hash table of STDOUT buffers keyed by target name
 | |
|         	$error_buffers - hash table of STDERR buffers keyed by target name
 | |
| 			$output_files - list of output file handles where output is to be written
 | |
| 			$error_files - list of error file handles where error output is to be written
 | |
| 			$select_err_fhs - list of currently available STDERR pipe handles
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub buffer_output
 | |
| {
 | |
|     my (
 | |
|         $class,            $options,          $resolved_targets,
 | |
|         $targets_active,   $targets_finished, $targets_failed,
 | |
|         $targets_buffered, $pid_targets,      $forked_process,
 | |
|         $outfh_targets,    $output_buffers,   $error_buffers,
 | |
|         $output_files,     $error_files,      $select_out_fhs
 | |
|       )
 | |
|       = @_;
 | |
| 
 | |
|     foreach my $select_out_fh (@$select_out_fhs)
 | |
|     {
 | |
| 
 | |
|         my $user_target       = $$outfh_targets{$select_out_fh};
 | |
|         my $output_fh         = $$forked_process{$user_target}[1];
 | |
|         my $error_fh          = $$forked_process{$user_target}[2];
 | |
|         my $target_properties = $$resolved_targets{$user_target};
 | |
| 
 | |
|         if (!$$output_buffers{$user_target})
 | |
|         {
 | |
|             my @buffer = ();
 | |
|             $$output_buffers{$user_target} = \@buffer;
 | |
|         }
 | |
| 
 | |
|         if (!$$output_buffers{"${user_target}_tmp"})
 | |
|         {
 | |
|             my @buffer_tmp = ();
 | |
|             $$output_buffers{"${user_target}_tmp"} = \@buffer_tmp;
 | |
|         }
 | |
| 
 | |
|         my $eof_output =
 | |
|           xCAT::DSHCore->pipe_handler_buffer(
 | |
|                                          $target_properties, $output_fh, 4096,
 | |
|                                          "$user_target: ",
 | |
|                                          $$output_buffers{"${user_target}_tmp"},
 | |
|                                          $$output_buffers{$user_target}
 | |
|                                          );
 | |
| 
 | |
|         if ($eof_output)
 | |
|         {
 | |
|             vec($$outfh_targets{'bitmap'}, fileno($output_fh), 1) = 0;
 | |
|             delete $$outfh_targets{$user_target};
 | |
| 
 | |
|             if (++$$targets_active{$user_target} == 3)
 | |
| 
 | |
|             {
 | |
|                 my $exit_code;
 | |
|                 my $pid = waitpid($$forked_process{$user_target}[0], 0);
 | |
|                 if ($pid == -1)
 | |
|                 {    # no child waiting ignore
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{data}->[0] = "waitpid call PID=$pid. Ignore.";
 | |
|                     $$options{'monitor'}
 | |
|                       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                 }
 | |
|                 else
 | |
|                 {    # check return code
 | |
|                     $exit_code = $? >> 8;
 | |
|                 }
 | |
|                 if (scalar(@{$$output_buffers{$user_target}}) == 1)
 | |
|                 {
 | |
|                     ($$output_buffers{$user_target}[0] eq '')
 | |
|                       && (@{$$output_buffers{$user_target}} = ());
 | |
|                 }
 | |
| 
 | |
|                 if (scalar(@{$$error_buffers{$user_target}}) == 1)
 | |
|                 {
 | |
|                     ($$error_buffers{$user_target}[0] eq '')
 | |
|                       && (@{$$error_buffers{$user_target}} = ());
 | |
|                 }
 | |
| 
 | |
|                 my %exit_status = (
 | |
|                                  'exit-code' => $exit_code,
 | |
|                                  'target-rc' => $$target_properties{'target-rc'}
 | |
|                                  );
 | |
|                 $$targets_buffered{$user_target} = \%exit_status;
 | |
| 
 | |
|                 delete $$targets_active{$user_target};
 | |
|                 delete $$pid_targets{$$forked_process{$user_target}[0]};
 | |
| 
 | |
|                 close $output_fh;
 | |
|                 close $error_fh;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         buffer_error
 | |
| 
 | |
|         For a given list of targets with output available, this routine buffers
 | |
|         output from the targets STDERR pipe handles into buffers grouped by
 | |
|         target name
 | |
| 
 | |
|         Arguments:
 | |
|             $options - options hash table describing dsh configuration options
 | |
|         	$resolved_targets - hash table of resolved targets and target properties
 | |
|         	$targets_active - hash table of currently active targets with output possibly available
 | |
|         	$targets_finished - list of targets with all output processed and have completed execution
 | |
|         	$targets_failed - list of targets that have unsuccessfully executed
 | |
|         	$targets_buffered - hash table of buffers with output from active targets
 | |
|         	$pid_targets - hash table of target names keyed by process ID
 | |
|         	$forked_process - hash table of process information keyed by target name
 | |
|         	$errfh_targets - hash table of STDERR pipe handles keyed by target name
 | |
|         	$output_buffers - hash table of STDOUT buffers keyed by target name
 | |
|         	$error_buffers - hash table of STDERR buffers keyed by target name
 | |
| 			$output_files - list of output file handles where output is to be written
 | |
| 			$error_files - list of error file handles where error output is to be written
 | |
| 			$select_err_fhs - list of currently available STDERR pipe handles
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub buffer_error
 | |
| {
 | |
|     my (
 | |
|         $class,            $options,          $resolved_targets,
 | |
|         $targets_active,   $targets_finished, $targets_failed,
 | |
|         $targets_buffered, $pid_targets,      $forked_process,
 | |
|         $errfh_targets,    $output_buffers,   $error_buffers,
 | |
|         $output_files,     $error_files,      $select_err_fhs
 | |
|       )
 | |
|       = @_;
 | |
| 
 | |
|     foreach my $select_err_fh (@$select_err_fhs)
 | |
|     {
 | |
| 
 | |
|         my $user_target       = $$errfh_targets{$select_err_fh};
 | |
|         my $output_fh         = $$forked_process{$user_target}[1];
 | |
|         my $error_fh          = $$forked_process{$user_target}[2];
 | |
|         my $target_properties = $$resolved_targets{$user_target};
 | |
| 
 | |
|         if (!$$error_buffers{$user_target})
 | |
|         {
 | |
|             my @buffer = ();
 | |
|             $$error_buffers{$user_target} = \@buffer;
 | |
|         }
 | |
| 
 | |
|         if (!$$error_buffers{"${user_target}_tmp"})
 | |
|         {
 | |
|             my @buffer_tmp = ();
 | |
|             $$error_buffers{"${user_target}_tmp"} = \@buffer_tmp;
 | |
|         }
 | |
| 
 | |
|         my $eof_error =
 | |
|           xCAT::DSHCore->pipe_handler_buffer(
 | |
|                                           $target_properties, $error_fh, 4096,
 | |
|                                           "$user_target: ",
 | |
|                                           $$error_buffers{"${user_target}_tmp"},
 | |
|                                           $$error_buffers{$user_target}
 | |
|                                           );
 | |
| 
 | |
|         if ($eof_error)
 | |
|         {
 | |
|             vec($$errfh_targets{'bitmap'}, fileno($error_fh), 1) = 0;
 | |
|             delete $$errfh_targets{$user_target};
 | |
| 
 | |
|             if (++$$targets_active{$user_target} == 3)
 | |
|             {
 | |
|                 my $exit_code;
 | |
|                 my $pid = waitpid($$forked_process{$user_target}[0], 0);
 | |
|                 if ($pid == -1)
 | |
|                 {    # no child waiting
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{data}->[0] = "waitpid call PID=$pid. Ignore.";
 | |
|                     $$options{'monitor'}
 | |
|                       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                 }
 | |
|                 else
 | |
|                 {    # check return code
 | |
|                     $exit_code = $? >> 8;
 | |
|                 }
 | |
| 
 | |
|                 if (scalar(@{$$output_buffers{$user_target}}) == 1)
 | |
|                 {
 | |
|                     ($$output_buffers{$user_target}[0] eq '')
 | |
|                       && (@{$$output_buffers{$user_target}} = ());
 | |
|                 }
 | |
| 
 | |
|                 if (scalar(@{$$error_buffers{$user_target}}) == 1)
 | |
|                 {
 | |
|                     ($$error_buffers{$user_target}[0] eq '')
 | |
|                       && (@{$$error_buffers{$user_target}} = ());
 | |
|                 }
 | |
| 
 | |
|                 my %exit_status = (
 | |
|                                  'exit-code' => $exit_code,
 | |
|                                  'target-rc' => $$target_properties{'target-rc'}
 | |
|                                  );
 | |
|                 $$targets_buffered{$user_target} = \%exit_status;
 | |
| 
 | |
|                 delete $$targets_active{$user_target};
 | |
|                 delete $$pid_targets{$$forked_process{$user_target}[0]};
 | |
| 
 | |
|                 close $output_fh;
 | |
|                 close $error_fh;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         stream_output - Running xdsh with the -s option
 | |
| 
 | |
|         For a given list of targets with output available, this routine writes
 | |
|         output from the targets STDOUT pipe handles directly to STDOUT as soon
 | |
|         as it is available from the target
 | |
| 
 | |
|         Arguments:
 | |
|             $options - options hash table describing dsh configuration options
 | |
|         	$resolved_targets - hash table of resolved targets and target properties
 | |
|         	$targets_active - hash table of currently active targets with output possibly available
 | |
|         	$targets_finished - list of targets with all output processed and have completed execution
 | |
|         	$targets_failed - list of targets that have unsuccessfully executed
 | |
|         	$targets_buffered - hash table of buffers with output from active targets
 | |
|         	$pid_targets - hash table of target names keyed by process ID
 | |
|         	$forked_process - hash table of process information keyed by target name
 | |
|         	$outfh_targets - hash table of STDOUT pipe handles keyed by target name
 | |
|         	$output_buffers - hash table of STDOUT buffers keyed by target name
 | |
| 			$output_files - list of error file handles where error output is to be written
 | |
| 			$select_out_fhs - list of currently available STDOUT pipe handles
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub stream_output
 | |
| {
 | |
|     my (
 | |
|         $class,          $options,          $resolved_targets,
 | |
|         $targets_active, $targets_finished, $targets_failed,
 | |
|         $pid_targets,    $forked_process,   $outfh_targets,
 | |
|         $output_buffers, $output_files,     $select_out_fhs
 | |
|       )
 | |
|       = @_;
 | |
| 
 | |
|     foreach my $select_out_fh (@$select_out_fhs)
 | |
|     {
 | |
| 
 | |
|         my $user_target       = $$outfh_targets{$select_out_fh};
 | |
|         my $output_fh         = $$forked_process{$user_target}[1];
 | |
|         my $target_properties = $$resolved_targets{$user_target};
 | |
| 
 | |
|         if (!$$output_buffers{$user_target})
 | |
|         {
 | |
|             my @buffer = ();
 | |
|             $$output_buffers{$user_target} = \@buffer;
 | |
|         }
 | |
| 
 | |
|         my $eof_output =
 | |
|           xCAT::DSHCore->pipe_handler(
 | |
|                                       $options,
 | |
|                                       $target_properties,
 | |
|                                       $output_fh,
 | |
|                                       4096,
 | |
|                                       "$user_target: ",
 | |
|                                       $$output_buffers{$user_target},
 | |
|                                       @$output_files
 | |
|                                       );
 | |
| 
 | |
|         if ($eof_output)
 | |
|         {
 | |
|             vec($$outfh_targets{'bitmap'}, fileno($output_fh), 1) = 0;
 | |
|             delete $$outfh_targets{$user_target};
 | |
| 
 | |
|             if (++$$targets_active{$user_target} == 3)
 | |
|             {
 | |
|                 my $exit_code;
 | |
|                 my $pid = waitpid($$forked_process{$user_target}[0], 0);
 | |
|                 if ($pid == -1)
 | |
|                 {    # no child waiting
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{data}->[0] = "waitpid call PID=$pid. Ignore.";
 | |
|                     $$options{'monitor'}
 | |
|                       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                 }
 | |
|                 else
 | |
|                 {    # check return code
 | |
|                     $exit_code = $? >> 8;
 | |
|                 }
 | |
| 
 | |
|                 my $target_rc = $$target_properties{'target-rc'};
 | |
| 
 | |
|                 if ($exit_code != 0)
 | |
|                 {
 | |
|                     # report error status  --nodestatus
 | |
|                     # Note the message below for node status must
 | |
|                     # not be NLS translated.  Code depends on the English. 
 | |
|                     if ($$options{'nodestatus'}) {
 | |
|                          my $rsp={};
 | |
|                          $rsp->{data}->[0] =
 | |
|                          "$user_target: Remote_command_failed, error_code=$exit_code";
 | |
|                           xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                     }
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{error}->[0] =
 | |
|                       "$user_target remote shell had error code: $exit_code";
 | |
|                     !$$options{'silent'}
 | |
|                       && (xCAT::MsgUtils->message("E", $rsp, $::CALLBACK));
 | |
| 
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{data}->[0] =
 | |
|                       "dsh>  Remote_command_failed $user_target";
 | |
|                     $$options{'monitor'}
 | |
|                       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|                     push @$targets_failed, $user_target;
 | |
|                     push @{$dsh_target_status{'failed'}}, $user_target
 | |
|                       if !$signal_interrupt_flag;
 | |
| 
 | |
|                 }
 | |
| 
 | |
|                 else
 | |
|                 {
 | |
|                     if ($target_rc != 0)
 | |
|                     {
 | |
|                        # report error status  --nodestatus
 | |
|                        # Note the message below for node status must
 | |
|                        # not be NLS translated.  Code depends on the English. 
 | |
|                        if ($$options{'nodestatus'}) {
 | |
|                          my $rsp={};
 | |
|                          $rsp->{data}->[0] =
 | |
|                          "$user_target: Remote_command_failed, error_code=$$target_properties{'target-rc'}";
 | |
|                           xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                        }
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{error}->[0] =
 | |
|                           " $user_target remote Command had return code: $$target_properties{'target-rc'} ";
 | |
|                         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{error}->[0] =
 | |
|                           "dsh>  Remote_command_failed $user_target";
 | |
|                         $$options{'monitor'}
 | |
|                           && xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|                         push @$targets_failed, $user_target;
 | |
|                     }
 | |
| 
 | |
|                     elsif (!defined($target_rc))
 | |
|                     {
 | |
|                        # report error status  --nodestatus
 | |
|                        # Note the message below for node status must
 | |
|                        # not be NLS translated.  Code depends on the English. 
 | |
|                        if ($$options{'nodestatus'}) {
 | |
|                          my $rsp={};
 | |
|                          $rsp->{data}->[0] =
 | |
|                          "$user_target: Remote_command_failed, error_code=???";
 | |
|                           xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                        }
 | |
| 
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{error}->[0] =
 | |
|                           " $user_target a return code run on this host was not received. ";
 | |
|                         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{data}->[0] =
 | |
|                           "dsh>  Remote_command_failed $user_target";
 | |
|                         $$options{'monitor'}
 | |
|                           && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|                         push @$targets_failed, $user_target;
 | |
|                     }
 | |
| 
 | |
|                     else   #  xdsh -s worked
 | |
|                     {
 | |
|                        # report error status  --nodestatus
 | |
|                        # Note the message below for node status must
 | |
|                        # not be NLS translated.  Code depends on the English. 
 | |
|                        if ($$options{'nodestatus'}) {
 | |
|                          my $rsp={};
 | |
|                          $rsp->{data}->[0] =
 | |
|                          "$user_target: Remote_command_successful";
 | |
|                           xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                        }
 | |
| 
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{data}->[0] =
 | |
|                           "dsh>  Remote_command_successful $user_target";
 | |
|                         $$options{'monitor'}
 | |
|                           && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|                         push @$targets_finished, $user_target;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 delete $$targets_active{$user_target};
 | |
|                 delete $$pid_targets{$$forked_process{$user_target}[0]};
 | |
|             }
 | |
| 
 | |
|             close $output_fh;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         stream_error
 | |
| 
 | |
|         For a given list of targets with output available, this routine writes
 | |
|         output from the targets STDERR pipe handles directly to STDERR as soon
 | |
|         as it is available from the target
 | |
| 
 | |
|         Arguments:
 | |
|             $options - options hash table describing dsh configuration options
 | |
|         	$resolved_targets - hash table of resolved targets and target properties
 | |
|         	$targets_active - hash table of currently active targets with output possibly available
 | |
|         	$targets_finished - list of targets with all output processed and have completed execution
 | |
|         	$targets_failed - list of targets that have unsuccessfully executed
 | |
|         	$targets_buffered - hash table of buffers with output from active targets
 | |
|         	$pid_targets - hash table of target names keyed by process ID
 | |
|         	$forked_process - hash table of process information keyed by target name
 | |
|         	$errfh_targets - hash table of STDERR pipe handles keyed by target name
 | |
|         	$error_buffers - hash table of STDERR buffers keyed by target name
 | |
| 			$error_files - list of error file handles where error output is to be written
 | |
| 			$select_err_fhs - list of currently available STDERR pipe handles
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub stream_error
 | |
| {
 | |
|     my (
 | |
|         $class,          $options,          $resolved_targets,
 | |
|         $targets_active, $targets_finished, $targets_failed,
 | |
|         $pid_targets,    $forked_process,   $errfh_targets,
 | |
|         $error_buffers,  $error_files,      $select_err_fhs
 | |
|       )
 | |
|       = @_;
 | |
| 
 | |
|     foreach my $select_err_fh (@$select_err_fhs)
 | |
|     {
 | |
| 
 | |
|         my $user_target       = $$errfh_targets{$select_err_fh};
 | |
|         my $error_fh          = $$forked_process{$user_target}[2];
 | |
|         my $target_properties = $$resolved_targets{$user_target};
 | |
| 
 | |
|         if (!$$error_buffers{$user_target})
 | |
|         {
 | |
|             my @buffer = ();
 | |
|             $$error_buffers{$user_target} = \@buffer;
 | |
|         }
 | |
| 
 | |
|         my $eof_error =
 | |
|           xCAT::DSHCore->pipe_handler(
 | |
|                                       $options,
 | |
|                                       $target_properties,
 | |
|                                       $error_fh,
 | |
|                                       4096,
 | |
|                                       "$user_target: ",
 | |
|                                       $$error_buffers{$user_target},
 | |
|                                       @$error_files
 | |
|                                       );
 | |
| 
 | |
|         if ($eof_error)
 | |
|         {
 | |
|             vec($$errfh_targets{'bitmap'}, fileno($error_fh), 1) = 0;
 | |
|             delete $$errfh_targets{$user_target};
 | |
| 
 | |
|             my $rsp = {};
 | |
|             if (++$$targets_active{$user_target} == 3)
 | |
|             {
 | |
|                 my $exit_code;
 | |
|                 my $pid = waitpid($$forked_process{$user_target}[0], 0);
 | |
|                 if ($pid == -1)
 | |
|                 {    # no child waiting
 | |
|                     $rsp->{data}->[0] = "waitpid call PID=$pid. Ignore.";
 | |
|                     $$options{'monitor'}
 | |
|                       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                 }
 | |
|                 else
 | |
|                 {    # check return code
 | |
|                     $exit_code = $? >> 8;
 | |
|                 }
 | |
| 
 | |
|                 my $target_rc = $$target_properties{'target-rc'};
 | |
| 
 | |
|                 if ($exit_code != 0)
 | |
|                 {
 | |
|                    # report error status  --nodestatus
 | |
|                    # Note the message below for node status must
 | |
|                    # not be NLS translated.  Code depends on the English. 
 | |
|                    if ($$options{'nodestatus'}) {
 | |
|                       my $rsp={};
 | |
|                       $rsp->{data}->[0] =
 | |
|                       "$user_target: Remote_command_failed, error_code=$exit_code";
 | |
|                       xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                     }
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{error}->[0] =
 | |
|                       " $user_target remote shell had exit code $exit_code.";
 | |
|                     !$$options{'silent'}
 | |
|                       && (xCAT::MsgUtils->message("E", $rsp, $::CALLBACK));
 | |
| 
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{error}->[0] =
 | |
|                       "dsh>  Remote_command_failed $user_target";
 | |
|                     $$options{'monitor'}
 | |
|                       && xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|                     push @$targets_failed, $user_target;
 | |
|                     push @{$dsh_target_status{'failed'}}, $user_target
 | |
|                       if !$signal_interrupt_flag;
 | |
| 
 | |
|                 }
 | |
| 
 | |
|                 else
 | |
|                 {
 | |
|                     if ($target_rc != 0)
 | |
|                     {
 | |
|                       # report error status  --nodestatus
 | |
|                       # Note the message below for node status must
 | |
|                       # not be NLS translated.  Code depends on the English. 
 | |
|                       if ($$options{'nodestatus'}) {
 | |
|                          my $rsp={};
 | |
|                          $rsp->{data}->[0] =
 | |
|                          "$user_target: Remote_command_failed, error_code=$$target_properties{'target-rc'}";
 | |
|                          xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                        }
 | |
| 
 | |
|                         #my $rsp = {};
 | |
|                         #$rsp->{error}->[0] =
 | |
|                         #  "$user_target remote command had return code $$target_properties{'target-rc'}";
 | |
|                         #xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{error}->[0] =
 | |
|                           "dsh>  Remote_command_failed $user_target";
 | |
|                         $$options{'monitor'}
 | |
|                           && xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|                         push @$targets_failed, $user_target;
 | |
|                     }
 | |
| 
 | |
|                     elsif (!defined($target_rc) && ($::DSH_MELLANOX_SWITCH==0))
 | |
|                     {
 | |
|                       # report error status  --nodestatus
 | |
|                       # Note the message below for node status must
 | |
|                       # not be NLS translated.  Code depends on the English. 
 | |
|                       if ($$options{'nodestatus'}) {
 | |
|                          my $rsp={};
 | |
|                          $rsp->{data}->[0] =
 | |
|                          "$user_target: Remote_command_failed, error_code=???}";
 | |
|                          xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                        }
 | |
| 
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{data}->[0] =
 | |
|                           "A return code for the command run on $user_target was not received.";
 | |
|                         xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{error}->[0] =
 | |
|                           "dsh>  Remote_command_failed $user_target";
 | |
|                         $$options{'monitor'}
 | |
|                           && xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|                         push @$targets_failed, $user_target;
 | |
|                     }
 | |
| 
 | |
|                     else
 | |
|                     {
 | |
|                       # report error status  --nodestatus
 | |
|                       # Note the message below for node status must
 | |
|                       # not be NLS translated.  Code depends on the English. 
 | |
|                       if ($$options{'nodestatus'}) {
 | |
|                          my $rsp={};
 | |
|                          $rsp->{data}->[0] =
 | |
|                          "$user_target: Remote_command_successful";
 | |
|                          xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                       }
 | |
| 
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{data}->[0] =
 | |
|                           "dsh>  Remote_command_successful $user_target";
 | |
|                         $$options{'monitor'}
 | |
|                           && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|                         push @$targets_finished, $user_target;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 delete $$targets_active{$user_target};
 | |
|                 delete $$pid_targets{$$forked_process{$user_target}[0]};
 | |
|             }
 | |
| 
 | |
|             close $error_fh;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         config_default_context
 | |
| 
 | |
|         Return the name of the default context to the caller ( always XCAT)
 | |
| 
 | |
|         Arguments:
 | |
| 			$options - options hash table describing dsh configuration options
 | |
| 
 | |
|         Returns:
 | |
|         	The name of the default context  which is always XCAT
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         If context file not found 	
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub config_default_context
 | |
| {
 | |
|     my ($class, $options) = @_;
 | |
|     my $contextdir = $::CONTEXT_DIR;
 | |
|     $contextdir .= "XCAT.pm";
 | |
|     if (-e "$contextdir")
 | |
|     {
 | |
|         require Context::XCAT;
 | |
|         (XCAT->valid_context) && ($$options{'context'} = 'XCAT');
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         config_dcp
 | |
| 
 | |
|         This routine configures the command environment for an instance of the
 | |
|         dcp command based on the configuration of the DSH Utilities environment
 | |
|         defined in $options.
 | |
| 
 | |
|         Arguments:
 | |
|             $options - options hash table describing dsh configuration options
 | |
| 
 | |
|         Returns:
 | |
|         	Number of configuration errors
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub config_dcp
 | |
| {
 | |
|     my ($class, $options) = @_;
 | |
| 
 | |
|     my $result = 0;
 | |
| 
 | |
|     $$options{'trace'} && $dsh_trace++;
 | |
| 
 | |
|     $dsh_trace && xCAT::DSHCLI->show_dsh_config($options);
 | |
| 
 | |
|     xCAT::DSHCLI->config_default_context($options);
 | |
| 
 | |
|     if (!(-e "$::CONTEXT_DIR$$options{'context'}.pm"))
 | |
|     {
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Context file XCAT.pm does not exist.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         return ++$result;
 | |
|     }
 | |
| 
 | |
|     my $rsp = {};
 | |
|     $rsp->{data}->[0] = "TRACE:Default context is $$options{'context'}.";
 | |
|     $dsh_trace
 | |
|       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|     !$$options{'node-rcp'}
 | |
|       && (   $$options{'node-rcp'} = $ENV{'DCP_NODE_RCP'}
 | |
|           || $ENV{'DCP_COPY_CMD'}
 | |
|           || undef);
 | |
| 
 | |
|     if ($$options{'node-rcp'})
 | |
|     {
 | |
|         my %node_rcp        = ();
 | |
|         my @remotecopy_list = split ',', $$options{'node-rcp'};
 | |
| 
 | |
|         foreach my $context_remotecopy (@remotecopy_list)
 | |
|         {
 | |
|             my ($context, $remotecopy) = split ':', $context_remotecopy;
 | |
| 
 | |
|             if (!$remotecopy)
 | |
|             {
 | |
|                 $remotecopy = $context;
 | |
|                 scalar(@dsh_valid_contexts) || xCAT::DSHCLI->get_valid_contexts;
 | |
| 
 | |
|                 foreach my $context (@dsh_valid_contexts)
 | |
|                 {
 | |
|                     !$node_rcp{$context}
 | |
|                       && ($node_rcp{$context} = $remotecopy);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 $node_rcp{$context} = $remotecopy;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $$options{'node-rcp'} = \%node_rcp;
 | |
|     }
 | |
| 
 | |
|     $$options{'fanout'} = $$options{'fanout'} || $ENV{'DSH_FANOUT'} || 64;
 | |
| 
 | |
|     my $rsp = {};
 | |
|     $rsp->{data}->[0] = "TRACE:Fanout Value is $$options{'fanout'}.";
 | |
|     $dsh_trace
 | |
|       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|     $$options{'timeout'} = $$options{'timeout'} || $ENV{'DSH_TIMEOUT'} || undef;
 | |
| 
 | |
|     my $rsp = {};
 | |
|     $rsp->{data}->[0] = "TRACE:Timeout Value is $$options{'timeout'}.";
 | |
|     $dsh_trace
 | |
|       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|     if (   (!$$options{'nodes'})
 | |
|         && ($ENV{'DSH_NODE_LIST'} || $ENV{'DSH_LIST'}))
 | |
|     {
 | |
|         require Context::DSH;
 | |
| 
 | |
|         my $node_list_file = $ENV{'DSH_NODE_LIST'}
 | |
|           || $ENV{'DSH_LIST'}
 | |
|           || $ENV{'WCOLL'};
 | |
|         my $node_list = DSH->read_target_file($node_list_file);
 | |
|         $$options{'nodes'} = join ',', @$node_list;
 | |
|     }
 | |
| 
 | |
|     elsif (!$$options{'nodes'} && $ENV{'DSH_NODE_LIST'})
 | |
|     {
 | |
|         require Context::DSH;
 | |
| 
 | |
|         my $node_list_file = $ENV{'DSH_NODE_LIST'};
 | |
|         my $node_list      = DSH->read_target_file($node_list_file);
 | |
|         $$options{'nodes'} = join ',', @$node_list;
 | |
|     }
 | |
| 
 | |
|     $$options{'node-options'} = $$options{'node-options'}
 | |
|       || $ENV{'DCP_NODE_OPTS'}
 | |
|       || $ENV{'DSH_REMOTE_OPTS'};
 | |
| 
 | |
|     if ($$options{'node-options'})
 | |
|     {
 | |
|         my %node_options    = ();
 | |
|         my @remoteopts_list = split ',', $$options{'node-options'};
 | |
| 
 | |
|         foreach my $context_remoteopts (@remoteopts_list)
 | |
|         {
 | |
|             my ($context, $remoteopts) = split ':', $context_remoteopts;
 | |
| 
 | |
|             if (!$remoteopts)
 | |
|             {
 | |
|                 $remoteopts = $context;
 | |
|                 scalar(@dsh_valid_contexts) || xCAT::DSHCLI->get_valid_contexts;
 | |
| 
 | |
|                 foreach my $context (@dsh_valid_contexts)
 | |
|                 {
 | |
|                     !$node_options{$context}
 | |
|                       && ($node_options{$context} = $remoteopts);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 $node_options{$context} = $remoteopts;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $$options{'node-options'} = \%node_options;
 | |
|     }
 | |
| 
 | |
|     if ($$options{'pull'} && !(-d $$options{'target'}))
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Cannot copy to target $$options{'target'}. Directory does not exist.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         return ++$result;
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         config_dsh
 | |
| 
 | |
|         This routine configures the command environment for an instance of the
 | |
|         dsh command based on the configuration of the DSH Utilities environment
 | |
|         defined in $options.
 | |
| 
 | |
|         Arguments:
 | |
|             $options - options hash table describing dsh configuration options
 | |
| 
 | |
|         Returns:
 | |
|         	Number of configuration errors
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub config_dsh
 | |
| {
 | |
|     my ($class, $options) = @_;
 | |
| 
 | |
|     my $result = 0;
 | |
| 
 | |
|     $$options{'stats'} = $$options{'monitor'};
 | |
| 
 | |
|     if ($$options{'stats'})
 | |
|     {
 | |
|         $dsh_stats{'start-time'} = localtime();
 | |
| 
 | |
|         $dsh_stats{'user'} = $$options{'user'} || `whoami`;
 | |
|         chomp($dsh_stats{'user'});
 | |
|         $dsh_stats{'successful-targets'}     = ();
 | |
|         $dsh_stats{'failed-targets'}         = ();
 | |
|         $dsh_stats{'report-status-messages'} = ();
 | |
|         $dsh_stats{'specified-targets'}      = ();
 | |
|         scalar(@dsh_valid_contexts) || xCAT::DSHCLI->get_valid_contexts;
 | |
|         push @{$dsh_stats{'valid-contexts'}}, @dsh_valid_contexts;
 | |
| 
 | |
|         foreach my $context (@dsh_valid_contexts)
 | |
|         {
 | |
|             $dsh_stats{'specified-targets'}{$context} = ();
 | |
|             $dsh_stats{'specified-targets'}{$context}{'nodes'} = ();
 | |
|         }
 | |
| 
 | |
|         $$options{'command-name'} = $$options{'command-name'} || 'Unspecified';
 | |
|         $$options{'command-description'} = $$options{'command-description'}
 | |
|           || '';
 | |
| 
 | |
|     }
 | |
| 
 | |
|     $$options{'trace'} && $dsh_trace++;
 | |
| 
 | |
|     $dsh_trace && xCAT::DSHCLI->show_dsh_config;
 | |
| 
 | |
|     xCAT::DSHCLI->config_default_context($options);
 | |
|     my $test = " $::CONTEXT_DIR$$options{'context'}.pm";
 | |
|     if (!(-e "$::CONTEXT_DIR$$options{'context'}.pm"))
 | |
|     {
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Context file $::CONTEXT_DIR$$options{'context'}.pm does not exist.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         return ++$result;
 | |
|     }
 | |
| 
 | |
|     my $rsp = {};
 | |
|     $rsp->{data}->[0] = "TRACE:Default context is $$options{'context'}";
 | |
|     $dsh_trace
 | |
|       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|     # Check devicetype attr and build command based on type 
 | |
|     $$options{'devicetype'} = $$options{'devicetype'}
 | |
|       || $ENV{'DEVICETYPE'}
 | |
|       || undef;
 | |
|     if ($$options{'devicetype'})
 | |
|     {
 | |
|         $ENV{'DEVICETYPE'} = $$options{'devicetype'};
 | |
|         my $devicename = $$options{'devicetype'};
 | |
|         $devicename =~ s/::/\//g;
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = "Processing $devicename device type";
 | |
|         $dsh_trace && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|         # process the config file. check /var/opt/xcat/... first, if the config
 | |
|         # file is not found, goto /opt/xcat/share/devicetype
 | |
| 	my $devicepath = "/var/opt/xcat/" . $devicename . "/config"; 
 | |
| 	if (! -e $devicepath) {
 | |
| 	    $devicepath="$::XCATROOT/share/xcat/devicetype/" . $devicename . "/config";
 | |
| 	}
 | |
| 	# Get configuration from $::XCATDEVCFGDIR
 | |
| 	# used for QLogic and Mellanox
 | |
| 	if (-e $devicepath)
 | |
| 	{
 | |
| 	    my $deviceconf = get_config($devicepath);
 | |
| 	    
 | |
| 	    # Get all dsh section configuration
 | |
| 	    foreach my $entry (keys %{$$deviceconf{'xdsh'}})
 | |
| 	    {
 | |
|                 my $value = $$deviceconf{'xdsh'}{$entry};
 | |
|                 if ($value)
 | |
|                 {
 | |
|                     $$options{$entry} = $value;
 | |
|                 }
 | |
| 		
 | |
| 	    }
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] = "EMsgMISSING_DEV_CFG";
 | |
|             xCAT::MsgUtils->message('E', $rsp, $::CALLBACK);
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     !$$options{'node-rsh'}
 | |
|       && (   $$options{'node-rsh'} = $ENV{'DSH_NODE_RSH'}
 | |
|           || $ENV{'DSH_REMOTE_CMD'}
 | |
|           || undef);
 | |
| 
 | |
|     my $rsp = {};
 | |
|     $rsp->{data}->[0] = "TRACE:Node RSH is $$options{'node-rsh'}";
 | |
|     $dsh_trace
 | |
|       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|     my %node_rsh_defaults = ();
 | |
| 
 | |
|     scalar(@dsh_valid_contexts) || xCAT::DSHCLI->get_valid_contexts;
 | |
| 
 | |
|     foreach my $context (@dsh_valid_contexts)
 | |
|     {
 | |
|         eval "require Context::$context";
 | |
|         my $defaults = $context->context_defaults;
 | |
|         $node_rsh_defaults{$context} = $$defaults{'NodeRemoteShell'}
 | |
|           || $$defaults{'RemoteShell'};
 | |
|     }
 | |
| 
 | |
|     $$options{'node-rsh-defaults'} = \%node_rsh_defaults;
 | |
| 
 | |
|     my %node_rsh = ();
 | |
| 
 | |
|     if ($$options{'node-rsh'})
 | |
|     {
 | |
| 
 | |
|         my @remoteshell_list = split ',', $$options{'node-rsh'};
 | |
| 
 | |
|         foreach my $context_remoteshell (@remoteshell_list)
 | |
|         {
 | |
|             my ($context, $remoteshell) = split ':', $context_remoteshell;
 | |
| 
 | |
|             if (!$remoteshell)
 | |
|             {
 | |
|                 $remoteshell = $context;
 | |
|                 !$node_rsh{'none'} && ($node_rsh{'none'} = $remoteshell);
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 !$node_rsh{$context} && ($node_rsh{$context} = $remoteshell);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     $$options{'node-rsh'} = \%node_rsh;
 | |
| 
 | |
|     $$options{'environment'} = $$options{'environment'}
 | |
|       || $ENV{'DSH_ENVIRONMENT'}
 | |
|       || undef;
 | |
| 
 | |
|     if ($$options{'environment'} && (-z $$options{'environment'}))
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = "File: $$options{'environment'} is empty.";
 | |
|         xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|         $$options{'environment'} = undef;
 | |
|     }
 | |
| 
 | |
|     $$options{'fanout'} = $$options{'fanout'} || $ENV{'DSH_FANOUT'} || 64;
 | |
|     $rsp={};
 | |
|     $rsp->{data}->[0] = "TRACE: Fanout value is $$options{'fanout'}.";
 | |
|     $dsh_trace
 | |
|       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|     $$options{'syntax'} = $$options{'syntax'} || $ENV{'DSH_SYNTAX'} || undef;
 | |
| 
 | |
|     if (
 | |
|         defined($$options{'syntax'})
 | |
|         && (   ($$options{'syntax'} ne 'csh')
 | |
|             && ($$options{'syntax'} ne 'ksh'))
 | |
|       )
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Incorrect argument \"$$options{'syntax'}\" specified on -S flag. ";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         return ++$result;
 | |
|     }
 | |
| 
 | |
|     my $env_set    = 'export';
 | |
|     my $env_assign = '=';
 | |
| 
 | |
|     if ($$options{'syntax'} eq 'csh')
 | |
|     {
 | |
|         $env_set    = 'setenv';
 | |
|         $env_assign = ' ';
 | |
|     }
 | |
| 
 | |
|     my $path_set;
 | |
|     $ENV{'DSH_PATH'}
 | |
|       && ($path_set = "$env_set PATH$env_assign$ENV{'DSH_PATH'};");
 | |
| 
 | |
|     $$options{'timeout'} = $$options{'timeout'} || $ENV{'DSH_TIMEOUT'} || undef;
 | |
|     my $rsp = {};
 | |
|     $rsp->{data}->[0] = "TRACE: Timeout value is $$options{'timeout'} ";
 | |
|     $dsh_trace
 | |
|       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|     # Check if $$options{'pre-command'} has been overwritten
 | |
|     # Mellanox uses pre-command = cli
 | |
|     if (!$$options{'pre-command'})
 | |
|     {
 | |
| 
 | |
|         # Set a default PATH
 | |
|         $$options{'pre-command'} = $path_set;
 | |
| 
 | |
|         if (!$$options{'no-locale'})
 | |
|         {
 | |
|             my @output = `/usr/bin/locale`;
 | |
|             chomp(@output);
 | |
| 
 | |
|             my @settings = ();
 | |
|             !($$options{'syntax'} eq 'csh') && (push @settings, $env_set);
 | |
| 
 | |
|             foreach my $line (@output)
 | |
|             {
 | |
|                 $line =~ s/=/$env_assign/;
 | |
| 
 | |
|                 if ($$options{'syntax'} eq 'csh')
 | |
|                 {
 | |
|                     push @settings, "$env_set $line;";
 | |
|                 }
 | |
| 
 | |
|                 else
 | |
|                 {
 | |
|                     push @settings, $line;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if ($$options{'syntax'} eq 'csh')
 | |
|             {
 | |
|                 push @settings, "$env_set PERL_BADLANG${env_assign}0;";
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 push @settings, "PERL_BADLANG${env_assign}0";
 | |
|             }
 | |
| 
 | |
|             my $locale_settings = join ' ', @settings;
 | |
|             !($$options{'syntax'} eq 'csh') && ($locale_settings .= ' ; ');
 | |
| 
 | |
|             $$options{'pre-command'} .= $locale_settings;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|        # if entry NULL then remove
 | |
|        if ($$options{'pre-command'} =~ /NULL/i) {
 | |
|           $$options{'pre-command'} = '';
 | |
|        } else {   #add space between pre-command and command
 | |
|           $$options{'pre-command'} .=" ";
 | |
|        } 
 | |
|     }
 | |
| 
 | |
|     # Check if $$options{'post-command'} has been overwritten.
 | |
|     if (!$$options{'post-command'})
 | |
|     {
 | |
|         if ($$options{'syntax'} eq 'csh')
 | |
|         {
 | |
|             $$options{'post-command'} =
 | |
|               "; $env_set DSH_TARGET_RC$env_assign\$status; echo \":DSH_TARGET_RC=\${DSH_TARGET_RC}:\"";
 | |
|         }
 | |
| 
 | |
|         else
 | |
|         {
 | |
|             $$options{'post-command'} =
 | |
|               "; $env_set DSH_TARGET_RC$env_assign\$?; echo \":DSH_TARGET_RC=\${DSH_TARGET_RC}:\"";
 | |
|         }
 | |
| 
 | |
|         $$options{'exit-status'}
 | |
|           && ($$options{'post-command'} .=
 | |
|               ' ; echo "Remote_command_rc = $DSH_TARGET_RC"');
 | |
|     }
 | |
|     else
 | |
|     {
 | |
| 
 | |
|         # post-command is overwritten by user , set env $::USER_POST_CMD
 | |
|         $::USER_POST_CMD = 1;
 | |
|         if ($$options{'post-command'} =~ /NULL/i)
 | |
|         {
 | |
|             $$options{'post-command'} = '';
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             #  Build the user post command, for example for QLogic
 | |
|             # $::DSH_EXIT_STATUS ony can be used in DSHCore::pipe_handler_buffer
 | |
|             # and DSHCore::pipe_handler
 | |
|             $$options{'exit-status'}
 | |
|               && ($::DSH_EXIT_STATUS = 1);
 | |
|             $$options{'post-command'} = ";$$options{'post-command'}";
 | |
| 
 | |
|             # Append "DSH_RC" keyword to mark output
 | |
|             $$options{'post-command'} = "$$options{'post-command'};echo DSH_RC";
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (
 | |
|         !$$options{'nodes'}
 | |
|         && (   $ENV{'DSH_NODE_LIST'}
 | |
|             || $ENV{'DSH_LIST'})
 | |
|       )
 | |
|     {
 | |
|         require Context::DSH;
 | |
| 
 | |
|         my $node_list_file = $ENV{'DSH_NODE_LIST'}
 | |
|           || $ENV{'DSH_LIST'}
 | |
|           || $ENV{'WCOLL'};
 | |
|         my $node_list = DSH->read_target_file($node_list_file);
 | |
|         $$options{'nodes'} = join ',', @$node_list;
 | |
|     }
 | |
| 
 | |
|     elsif (!$$options{'nodes'} && $ENV{'DSH_NODE_LIST'})
 | |
|     {
 | |
|         require Context::DSH;
 | |
| 
 | |
|         my $node_list_file = $ENV{'DSH_NODE_LIST'};
 | |
|         my $node_list      = DSH->read_target_file($node_list_file);
 | |
|         $$options{'nodes'} = join ',', @$node_list;
 | |
|     }
 | |
| 
 | |
|     $$options{'node-options'} = $$options{'node-options'}
 | |
|       || $ENV{'DSH_NODE_OPTS'}
 | |
|       || $ENV{'DSH_REMOTE_OPTS'};
 | |
| 
 | |
|     if ($$options{'node-options'})
 | |
|     {
 | |
|         my %node_options    = ();
 | |
|         my @remoteopts_list = split ',', $$options{'node-options'};
 | |
| 
 | |
|         foreach my $context_remoteopts (@remoteopts_list)
 | |
|         {
 | |
|             my ($context, $remoteopts) = split ':', $context_remoteopts;
 | |
| 
 | |
|             if (!$remoteopts)
 | |
|             {
 | |
|                 $remoteopts = $context;
 | |
|                 scalar(@dsh_valid_contexts) || xCAT::DSHCLI->get_valid_contexts;
 | |
| 
 | |
|                 foreach my $context (@dsh_valid_contexts)
 | |
|                 {
 | |
|                     !$node_options{$context}
 | |
|                       && ($node_options{$context} = $remoteopts);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 $node_options{$context} = $remoteopts;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $$options{'node-options'} = \%node_options;
 | |
|     }
 | |
| 
 | |
|     if ($$options{'execute'})
 | |
|     {
 | |
|         my @exe_command = split ' ', $$options{'command'};
 | |
|         $$options{'execute'} = \@exe_command;
 | |
| 
 | |
|         if (!(-e $exe_command[0]))
 | |
|         {
 | |
|             my $rsp={};
 | |
|             $rsp->{error}->[0] = "File $exe_command[0] does not exist";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|             return ++$result;
 | |
|         }
 | |
| 
 | |
|         if (-z $exe_command[0])
 | |
|         {
 | |
| 
 | |
|             my $rsp={};
 | |
|             $rsp->{error}->[0] = "File $exe_command[0] is empty.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|             return ++$result;
 | |
|         }
 | |
| 
 | |
|         if (!(-x $exe_command[0]))
 | |
|         {
 | |
| 
 | |
|             my $rsp={};
 | |
|             $rsp->{error}->[0] = "File $exe_command[0] is not executable.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|             return ++$result;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         config_signals_dsh
 | |
| 
 | |
|         Configures the signal handling routines for each system signal
 | |
|         and configures which signals should be restored as define in the
 | |
|         DSH environment options.
 | |
| 
 | |
|         Arguments:
 | |
|         	$options - options hash table describing dsh configuration options
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub config_signals_dsh
 | |
| {
 | |
|     my ($class, $options) = @_;
 | |
| 
 | |
|     $SIG{'STOP'} = 'DEFAULT';
 | |
|     $SIG{'CONT'} = 'DEFAULT';
 | |
|     $SIG{'TSTP'} = 'DEFAULT';
 | |
| 
 | |
|     $SIG{'TERM'} = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'QUIT'} = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'INT'}  = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'ABRT'} = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'ALRM'} = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'FPE'}  = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'ILL'}  = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'PIPE'} = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'SEGV'} = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'USR1'} = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'USR2'} = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'TTIN'} = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'TTOU'} = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
|     $SIG{'BUS'}  = 'xCAT::DSHCLI::handle_signal_dsh';
 | |
| 
 | |
|     my @ignore_signals = split /,/, $$options{'ignore-signal'};
 | |
| 
 | |
|     foreach my $signal (@ignore_signals)
 | |
|     {
 | |
| 
 | |
|         if (   ($signal ne 'STOP')
 | |
|             && ($signal ne 'CONT')
 | |
|             && ($signal ne 'TSTP'))
 | |
|         {
 | |
| 
 | |
|             $SIG{$signal} = 'IGNORE';
 | |
|         }
 | |
| 
 | |
|     }
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         handle_signal_dsh
 | |
| 
 | |
|         Signal handling routine for an instance of the dsh command.  Depending
 | |
|         on the state of dsh execution and report configuraion, this routine
 | |
|         properly writes reports and output files if a termination signal is recieved
 | |
| 
 | |
|         Arguments:
 | |
|         	$signal - termination signal to handle
 | |
|         	$fatal - 1 if signal is fatal, 0 otherwise
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	$dsh_execution_state - current state of dsh execution
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub handle_signal_dsh
 | |
| {
 | |
|     my ($signal, $fatal_error) = @_;
 | |
| 
 | |
|     my $DSH_STATE_BEGIN                   = 0;
 | |
|     my $DSH_STATE_INIT_STARTED            = 1;
 | |
|     my $DSH_STATE_INIT_COMPLETE           = 2;
 | |
|     my $DSH_STATE_TARGET_RESOLVE_COMPLETE = 3;
 | |
|     my $DSH_STATE_REMOTE_EXEC_STARTED     = 4;
 | |
|     my $DSH_STATE_REMOTE_EXEC_COMPLETE    = 5;
 | |
| 
 | |
|     if ($dsh_exec_state == $DSH_STATE_BEGIN)
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Command execution ended prematurely due to a previous error or stop request from the user.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         exit(1);
 | |
|     }
 | |
| 
 | |
|     elsif ($dsh_exec_state == $DSH_STATE_INIT_STARTED)
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Command execution ended prematurely due to a previous error or stop request from the user.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|         exit(1);
 | |
|     }
 | |
| 
 | |
|     elsif ($dsh_exec_state == $DSH_STATE_INIT_COMPLETE)
 | |
|     {
 | |
|         if ($$dsh_options{'stats'})
 | |
|         {
 | |
|             $dsh_stats{'exec-state'} = $dsh_exec_state;
 | |
|             $dsh_stats{'end-time'}   = localtime();
 | |
|         }
 | |
| 
 | |
|         if (@{$dsh_target_status{'waiting'}})
 | |
|         {
 | |
|             foreach my $user_target (@{$dsh_target_status{'waiting'}})
 | |
|             {
 | |
|                 if ($fatal_error)
 | |
|                 {
 | |
| 
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{error}->[0] =
 | |
|                       "Running the command on $user_target has been cancelled due to unrecoverable error.  The command was never sent to the host.";
 | |
|                     xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|                 }
 | |
| 
 | |
|                 else
 | |
|                 {
 | |
| 
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{data}->[0] =
 | |
|                       "xdsh>  Remote_command_cancelled $user_target";
 | |
|                     $$dsh_options{'monitor'}
 | |
|                       && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                 }
 | |
| 
 | |
|                 push @{$dsh_target_status{'canceled'}}, $user_target;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Running commands have been cancelled due to unrecoverable error or stop request by user.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|         if ($$dsh_options{'stats'})
 | |
|         {
 | |
|             my @empty_targets = ();
 | |
| 
 | |
|             $dsh_stats{'successful-targets'} = \@empty_targets;
 | |
|             $dsh_stats{'failed-targets'}     = \@empty_targets;
 | |
|             $dsh_stats{'canceled-targets'}   = $dsh_target_status{'canceled'};
 | |
|         }
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = "dsh>  Dsh_remote_execution_completed.";
 | |
|         $$dsh_options{'monitor'}
 | |
|           && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|         exit(1);
 | |
| 
 | |
|     }
 | |
| 
 | |
|     elsif ($dsh_exec_state == $DSH_STATE_TARGET_RESOLVE_COMPLETE)
 | |
|     {
 | |
|         if ($$dsh_options{'stats'})
 | |
|         {
 | |
|             $dsh_stats{'exec-state'} = $dsh_exec_state;
 | |
|             $dsh_stats{'end-time'}   = localtime();
 | |
|         }
 | |
| 
 | |
|         if (@{$dsh_target_status{'waiting'}})
 | |
|         {
 | |
|             foreach my $user_target (@{$dsh_target_status{'waiting'}})
 | |
|             {
 | |
|                 if ($fatal_error)
 | |
|                 {
 | |
| 
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{error}->[0] =
 | |
|                       "$user_target: running of the command on this host has been cancelled due to unrecoverable error.\n The command was never sent to the host.";
 | |
|                     xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|                 }
 | |
| 
 | |
|                 else
 | |
|                 {
 | |
| 
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{error}->[0] =
 | |
|                       "$user_target: running of the command on this host has been cancelled due to unrecoverable error or stop request by user.\n The command was never sent to the host.";
 | |
|                     xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{error}->[0] =
 | |
|                       "xdsh>  Remote_command_cancelled $user_target";
 | |
|                     $$dsh_options{'monitor'}
 | |
|                       && xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|                 }
 | |
| 
 | |
|                 push @{$dsh_target_status{'canceled'}}, $user_target;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         @{$dsh_target_status{'waiting'}} = ();
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] =
 | |
|           "Command execution ended prematurely due to a previous unrecoverable error or stop by user.\n No commands were executed on any host.";
 | |
| 
 | |
|         if ($$dsh_options{'stats'})
 | |
|         {
 | |
|             my @empty_targets = ();
 | |
| 
 | |
|             $dsh_stats{'successful-targets'} = \@empty_targets;
 | |
|             $dsh_stats{'failed-targets'}     = \@empty_targets;
 | |
|             $dsh_stats{'canceled-targets'}   = $dsh_target_status{'canceled'};
 | |
|         }
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = "xdsh>  Dsh_remote_execution_completed";
 | |
|         $$dsh_options{'monitor'}
 | |
|           && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
| 
 | |
|         exit(1);
 | |
|     }
 | |
| 
 | |
|     elsif ($dsh_exec_state == $DSH_STATE_REMOTE_EXEC_STARTED)
 | |
|     {
 | |
|         if ($$dsh_options{'stats'})
 | |
|         {
 | |
|             $dsh_stats{'exec-state'} = $dsh_exec_state;
 | |
|             $dsh_stats{'end-time'}   = localtime();
 | |
|         }
 | |
| 
 | |
|         my $targets_active      = $dsh_target_status{'active'};
 | |
|         my @targets_active_list = keys(%$targets_active);
 | |
| 
 | |
|         if (@targets_active_list)
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "Caught SIG$signal - terminating the child processes.";
 | |
|             !$$dsh_options{'stats'}
 | |
|               && xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|             my $target_signal = $signal;
 | |
|             my $last_error    = $!;
 | |
| 
 | |
|             if ($signal ne 'QUIT' && $signal ne 'INT' && $signal ne 'TERM')
 | |
|             {
 | |
|                 $target_signal = 'TERM';
 | |
|                 $SIG{'TERM'} = 'IGNORE';
 | |
|             }
 | |
| 
 | |
|             $SIG{$signal} = 'DEFAULT';
 | |
| 
 | |
|             foreach my $user_target (@targets_active_list)
 | |
|             {
 | |
|                 if ($$dsh_options{'stats'})
 | |
|                 {
 | |
|                     if ($fatal_error)
 | |
|                     {
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{error}->[0] =
 | |
|                           "Running the command on $user_target has been interrupted due to unrecoverable error.  The command may not have completed successfully.";
 | |
|                         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|                     }
 | |
| 
 | |
|                     else
 | |
|                     {
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{error}->[0] =
 | |
|                           "Running the command on $user_target has been interrupted due to unrecoverable error or stop request by the user.  The command may not have completed successfully.";
 | |
|                         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 my $target_pid = $$dsh_forked_process{$user_target}[0];
 | |
|                 kill $target_signal, $target_pid;
 | |
|                 push @{$dsh_target_status{'failed'}}, $user_target;
 | |
|                 $signal_interrupt_flag = 1;
 | |
|             }
 | |
| 
 | |
|             $! = $last_error;
 | |
|         }
 | |
| 
 | |
|         # if 2 input then this was a timeout on one process, we do
 | |
|         # not want to remove all the rest
 | |
|         if ($fatal_error != 2)
 | |
|         {    # remove the waiting processes
 | |
|             if (@{$dsh_target_status{'waiting'}})
 | |
|             {
 | |
|                 foreach my $user_target (@{$dsh_target_status{'waiting'}})
 | |
|                 {
 | |
|                     if ($fatal_error)
 | |
|                     {
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{error}->[0] =
 | |
|                           "Running the command on $user_target has been cancelled due to unrecoverable error.  The command was never sent to the host.";
 | |
|                         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|                     }
 | |
| 
 | |
|                     else
 | |
|                     {
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{error}->[0] =
 | |
|                           "Running the command on $user_target has been cancelled due to unrecoverable error or stop request by the user.  The command was never sent to the host.";
 | |
|                         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|                         my $rsp = {};
 | |
|                         $rsp->{error}->[0] =
 | |
|                           "dsh>  Remote_command_cancelled $user_target";
 | |
|                         $$dsh_options{'monitor'}
 | |
|                           && xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|                     }
 | |
| 
 | |
|                     push @{$dsh_target_status{'canceled'}}, $user_target;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             @{$dsh_target_status{'waiting'}} = ();
 | |
| 
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "Command execution ended prematurely due to a previous unrecoverable error or stop request by the user.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         }    #end fatal_error != 2
 | |
|         if ($$dsh_options{'stats'})
 | |
|         {
 | |
|             my @empty_targets = ();
 | |
| 
 | |
|             $dsh_stats{'successful-targets'} = $dsh_target_status{'finished'};
 | |
|             $dsh_stats{'failed-targets'}     = $dsh_target_status{'failed'};
 | |
|             $dsh_stats{'canceled-targets'}   = $dsh_target_status{'canceled'};
 | |
|         }
 | |
| 
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     elsif ($dsh_exec_state == $DSH_STATE_REMOTE_EXEC_COMPLETE)
 | |
|     {
 | |
|         if ($$dsh_options{'stats'})
 | |
|         {
 | |
|             $dsh_stats{'exec-state'} = $dsh_exec_state;
 | |
|             $dsh_stats{'end-time'}   = localtime();
 | |
|         }
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Running the command  stopped due to unrecoverable error or stop request by the user.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     else
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Running the command  stopped due to unrecoverable error or stop request by the user.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         exit(1);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         resolve_targets
 | |
| 
 | |
|         Main target resolution routine.  This routine calls calls all appropriate
 | |
|         target resolution routines to resolve target information in the
 | |
|         dsh command environment.  Currently the routine delegates resolution to
 | |
|         resolve_nodes.
 | |
| 
 | |
|         Arguments:
 | |
|         	$options - options hash table describing dsh configuration options
 | |
|         	$resolved_targets - hash table of resolved targets and target properties
 | |
|         	$unresolved_targets - hash table of unresolved targets and target properties
 | |
|         	$context_targets - hash table of targets grouped by context name
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub resolve_targets
 | |
| {
 | |
|     my ($class, $options, $resolved_targets, $unresolved_targets,
 | |
|         $context_targets)
 | |
|       = @_;
 | |
| 
 | |
|     $$options{'nodes'}
 | |
|       && xCAT::DSHCLI->resolve_nodes($options,            $resolved_targets,
 | |
|                                      $unresolved_targets, $context_targets);
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         resolve_nodes
 | |
| 
 | |
|         For a given list of contexts and node names, build a list of
 | |
|         nodes defined and augment the list with context node information.
 | |
| 
 | |
|         Arguments:
 | |
|         	$options - options hash table describing dsh configuration options
 | |
|         	$resolved_targets - hash table of resolved targets and target properties
 | |
|         	$unresolved_targets - hash table of unresolved targets and target properties
 | |
|         	$context_targets - hash table of targets grouped by context name
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub resolve_nodes
 | |
| {
 | |
|     my ($class, $options, $resolved_targets, $unresolved_targets,
 | |
|         $context_targets)
 | |
|       = @_;
 | |
| 
 | |
|     my @node_list = ();
 | |
|     @node_list = split ',', $$options{'nodes'};
 | |
| 
 | |
|     foreach my $context_node (@node_list)
 | |
|     {
 | |
|         my ($context, $node) = split ':', $context_node;
 | |
|         !$node
 | |
|           && (($node = $context) && ($context = $$options{'context'}));
 | |
| 
 | |
|         push @{$dsh_stats{'specified-targets'}{$context}{'nodes'}}, $node;
 | |
|     }
 | |
| 
 | |
|     xCAT::DSHCLI->_resolve_nodes($options, $resolved_targets,
 | |
|                                  $unresolved_targets, $context_targets,
 | |
|                                  @node_list);
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         _resolve_nodes
 | |
| 
 | |
|         Wrapper routine for resolve_all_nodes, resolve_nodes and
 | |
| 
 | |
| 
 | |
|         Arguments:
 | |
|             $options - options hash table describing dsh configuration options
 | |
|         	$resolved_targets - hash table of resolved targets and target properties
 | |
|         	$unresolved_targets - hash table of unresolved targets and relevant properties
 | |
| 			$context_targets - hash table of targets grouped by context name
 | |
| 			@nodes - a list of nodes to resolve
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub _resolve_nodes
 | |
| {
 | |
| 
 | |
|     my ($class, $options, $resolved_targets, $unresolved_targets,
 | |
|         $context_targets, @nodes)
 | |
|       = @_;
 | |
| 
 | |
|     my %resolved_nodes   = ();
 | |
|     my %unresolved_nodes = ();
 | |
| 
 | |
|     # this build the resolved nodes hash
 | |
|     # bypasses the old dsh resolution code
 | |
|     # unresolved nodes will be determined when the remote shell runs
 | |
|     xCAT::DSHCLI->bld_resolve_nodes_hash($options, \%resolved_nodes, @nodes);
 | |
| 
 | |
|     foreach my $user_node (keys(%resolved_nodes))
 | |
|     {
 | |
|         my $node_properties = $resolved_nodes{$user_node};
 | |
| 
 | |
|         $$node_properties{'type'} = 'node';
 | |
| 
 | |
|         eval "require Context::$$node_properties{'context'}";
 | |
|         my $result =
 | |
|           $$node_properties{'context'}->resolve_node($node_properties);
 | |
| 
 | |
|         $result && ($$resolved_targets{$user_node} = $node_properties);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #---------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         resolve_nodes_hash
 | |
| 
 | |
|         Builds the resolved nodes hash.
 | |
|         Does not do much anymore because we removed address resolution from
 | |
|         xdsh and just let ssh do it.  Stayed to preserve the logic of the old
 | |
|         code.
 | |
|         Does check if the Management Node is part of the node range and sets
 | |
|         localhost if it is.  The MN must be defined in nodelist and with 
 | |
|         nodetype.nodetype=mn.
 | |
| 
 | |
|         Arguments:
 | |
|        	$options - options hash table describing dsh configuration options
 | |
|        	$resolved_targets - hash table of resolved properties, keyed by target name
 | |
|        	@target_list - input list of target names to resolve
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
|                 
 | |
|         Globals:
 | |
|         	None
 | |
|     
 | |
|         Error:
 | |
|         	None
 | |
|     
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #---------------------------------------------------------------------------
 | |
| 
 | |
| sub bld_resolve_nodes_hash
 | |
| {
 | |
|     my ($class, $options, $resolved_targets, @target_list) = @_;
 | |
| 
 | |
|     # find out if we have an MN in the list, local cp and sh will be used
 | |
|     # not remote shell
 | |
|     my $mname = xCAT::Utils->noderangecontainsMn(@target_list);
 | |
|     foreach my $target (@target_list)
 | |
|     {
 | |
| 
 | |
|         my $hostname = $target;
 | |
|         my $ip_address;
 | |
|         my $localhost;
 | |
|         my $user;
 | |
|         my $context = "XCAT";
 | |
|         # check to see if this node is the Management Node
 | |
|         if ($mname) {
 | |
|           if ($mname eq $target) {
 | |
|             $localhost=$target;
 | |
|           }
 | |
|         }
 | |
|         my %properties = (
 | |
|                           'hostname'   => $hostname,
 | |
|                           'ip-address' => $ip_address,
 | |
|                           'localhost'  => $localhost,
 | |
|                           'user'       => $user,
 | |
|                           'context'    => $context,
 | |
|                           'unresolved' => $target
 | |
|                           );
 | |
| 
 | |
|         $$resolved_targets{"$target"} = \%properties;
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         verify_targets
 | |
| 
 | |
|         Executes a verification test for all targets across all
 | |
|         available contexts.  If a target cannot be verified it is removed
 | |
|         from the $resolved_targets list
 | |
| 
 | |
|         Arguments:
 | |
|         	$options - options hash table describing dsh configuration options
 | |
|         	$resolved_targets - hash table of resolved targets and target properties
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub verify_targets
 | |
| {
 | |
|     my ($class, $options, $resolved_targets) = @_;
 | |
|     my @ping_list;
 | |
|     foreach my $user_target (keys(%$resolved_targets))
 | |
|     {
 | |
| 
 | |
|         my $hostname = $$resolved_targets{$user_target}{'hostname'};
 | |
|         push @ping_list, $hostname;
 | |
|     }
 | |
| 
 | |
|     if (@ping_list)
 | |
|     {
 | |
| 
 | |
|         my @no_response = ();
 | |
|         my $rsp         = {};
 | |
|         $rsp->{data}->[0] =
 | |
|           "TRACE:Verifying remaining targets with pping command.";
 | |
|         $dsh_trace && xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|         @no_response = xCAT::DSHCore->pping_hostnames(@ping_list);
 | |
| 
 | |
|         foreach my $hostname (@no_response)
 | |
|         {
 | |
|             my @targets = grep /$hostname/, keys(%$resolved_targets);
 | |
| 
 | |
|             foreach my $user_target (@targets)
 | |
|             {
 | |
|                 my $rsp = {};
 | |
|                 $rsp->{error}->[0] =
 | |
|                   "$user_target is not responding. No command will be issued to this host.";
 | |
|                 xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|                 # report error status  --nodestatus
 | |
|                 # Note the message below for node status must
 | |
|                 # not be NLS translated.  Code depends on the English.
 | |
|                 if ($$options{'nodestatus'}) {
 | |
|                    my $rsp={};
 | |
|                   $rsp->{data}->[0] =
 | |
|                   "$user_target: Remote_command_failed";
 | |
|                    xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|                 }
 | |
| 
 | |
|                 my $rsp = {};
 | |
|                 $rsp->{error}->[0] =
 | |
|                   "dsh>  Remote_command_cancelled $user_target";
 | |
|                 $$dsh_options{'monitor'}
 | |
|                   && xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
| 
 | |
|                 push @{$dsh_target_status{'canceled'}}, $user_target;
 | |
|                 $$dsh_unresolved_targets{$user_target} =
 | |
|                   $$resolved_targets{$user_target};
 | |
|                 delete $$resolved_targets{$user_target};
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         get_available_contexts
 | |
| 
 | |
|         Returns a list of available contexts in the DSH environment
 | |
| 
 | |
|         Arguments:
 | |
|         	None
 | |
| 
 | |
|         Returns:
 | |
|         	A list of available contexts in the DSH environment
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub get_available_contexts
 | |
| {
 | |
|     opendir(DIR, $::CONTEXT_DIR);
 | |
|     my @contexts =
 | |
|       grep { ($_ ne '.') && ($_ ne '..') && ($_ =~ /\.pm$/) } readdir DIR;
 | |
|     closedir DIR;
 | |
| 
 | |
|     chomp(@contexts);
 | |
| 
 | |
|     foreach my $context (@contexts)
 | |
|     {
 | |
|         $context =~ s/\.pm$//;
 | |
| 
 | |
|         push @dsh_available_contexts, $context;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         get_dsh_config
 | |
| 
 | |
|         Get the initial DSH configuration for all available contexts
 | |
| 
 | |
|         Arguments:
 | |
|         	None
 | |
| 
 | |
|         Returns:
 | |
|         	A hash table of configuration properties grouped by context
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub get_dsh_config
 | |
| {
 | |
|     scalar(@dsh_valid_contexts) || xCAT::DSHCLI->get_valid_contexts;
 | |
| 
 | |
|     my %dsh_config = ();
 | |
|     foreach my $context (@dsh_valid_contexts)
 | |
|     {
 | |
|         $dsh_config{$context} = $context->context_properties;
 | |
|     }
 | |
| 
 | |
|     return \%dsh_config;
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         get_dsh_defaults
 | |
| 
 | |
|         Get the default properties for all available contexts
 | |
| 
 | |
|         Arguments:
 | |
|         	None
 | |
| 
 | |
|         Returns:
 | |
|         	A hash table of default properties for each context
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| sub get_dsh_defaults
 | |
| {
 | |
|     scalar(@dsh_valid_contexts) || xCAT::DSHCLI->get_valid_contexts;
 | |
| 
 | |
|     my %dsh_defaults = ();
 | |
|     foreach my $context (@dsh_valid_contexts)
 | |
|     {
 | |
|         $dsh_defaults{$context} = $context->context_defaults;
 | |
|     }
 | |
| 
 | |
|     return \%dsh_defaults;
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         get_valid_contexts
 | |
| 
 | |
|         Returns a list of valid contexts in the DSH environment.
 | |
|         A valid context is one that is available in the DSH environment
 | |
|         and whose valid_context routine returns 1
 | |
| 
 | |
|         Arguments:
 | |
|         	None
 | |
| 
 | |
|         Returns:
 | |
|         	A list of valid contexts
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| sub get_valid_contexts
 | |
| {
 | |
|     scalar(@dsh_available_contexts) || xCAT::DSHCLI->get_available_contexts;
 | |
|     @dsh_valid_contexts = ();
 | |
| 
 | |
|     foreach my $context (@dsh_available_contexts)
 | |
|     {
 | |
|         eval "require Context::$context";
 | |
|         ($context->valid_context) && (push @dsh_valid_contexts, $context);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         util_bit_indexes
 | |
| 
 | |
|         Utility routine that converts a bit vector to a corresponding array
 | |
|         of 1s and 0s
 | |
| 
 | |
|         Arguments:
 | |
|         	None
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| sub util_bit_indexes
 | |
| {
 | |
|     my ($class, $vector, $bit) = @_;
 | |
| 
 | |
|     my @bit_indexes = ();
 | |
|     my @bits        = split(//, unpack("b*", $vector));
 | |
| 
 | |
|     my $index = 0;
 | |
|     while (@bits)
 | |
|     {
 | |
|         ((shift @bits) == $bit) && (push @bit_indexes, $index);
 | |
|         $index++;
 | |
|     }
 | |
| 
 | |
|     return @bit_indexes;
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|    check_valid_options
 | |
| 
 | |
|     Arguments:
 | |
|         1 - %options
 | |
|     Returns:
 | |
|         1 - there are invalid options
 | |
|         0 - options are all okay
 | |
|     Globals:
 | |
|         none
 | |
|     Error:
 | |
|         none
 | |
|     Comments:
 | |
|         none
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #--------------------------------------------------------------------------------
 | |
| 
 | |
| sub check_valid_options
 | |
| {
 | |
|     my ($pkg, $options) = @_;
 | |
| 
 | |
|     if (scalar(@$options) > 0)
 | |
|     {
 | |
|         my @invalid_opts;
 | |
|         my @valid_longnames = (
 | |
|                                "continue",           "execute",
 | |
|                                "fanout",             "help",
 | |
|                                "user",               "monitor",
 | |
|                                "nodes",              "node-options",
 | |
|                                "node-rsh",           "stream",
 | |
|                                "timeout",            "verify",
 | |
|                                "exit-status",        "context",
 | |
|                                "environment",        "ignore-sig",
 | |
|                                "ignoresig",          "no-locale",
 | |
|                                "nodegroups",         "silent",
 | |
|                                "syntax",             "trace",
 | |
|                                "version",            "command-name",
 | |
|                                "commandName",        "command-description",
 | |
|                                "commandDescription", "noFileWriting",
 | |
|                                "preserve",           "node-rcp",
 | |
|                                "pull",               "recursive"
 | |
|                                );
 | |
| 
 | |
|         foreach my $opt (@$options)
 | |
|         {
 | |
|             my $tmp_opt = $opt;
 | |
| 
 | |
|             # remove any leading dash first
 | |
|             $tmp_opt =~ s/^-*//;
 | |
| 
 | |
|             # if this is a long keyword...
 | |
|             if (grep /^\Q$tmp_opt\E$/, @valid_longnames)
 | |
|             {
 | |
| 
 | |
|                 # there should be two leading dashs
 | |
|                 if ($opt !~ /^--\w+/)
 | |
|                 {
 | |
|                     push @invalid_opts, $opt;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         if (@invalid_opts)
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             my $badopts = join(',', @invalid_opts);
 | |
|             $rsp->{error}->[0] = "Invalid options: $badopts";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|     ignoreEnv
 | |
| 
 | |
| 
 | |
|     Remove dsh environment variable.
 | |
| 
 | |
|     Arguments:
 | |
|         envList: indicates the env vars that seperated by comma
 | |
|     Returns:
 | |
|         None
 | |
|     Globals:
 | |
|         @dsh_valie_env
 | |
|     Error:
 | |
|         none
 | |
|     Example:
 | |
|         if ( defined $options{'ignore_env'} ) { xCAT::DSHCLI->ignoreEnv( $options{'ignore_env'}); }
 | |
|     Comments:
 | |
|         none
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #--------------------------------------------------------------------------------
 | |
| 
 | |
| sub ignoreEnv
 | |
| {
 | |
|     my ($class, $envList) = @_;
 | |
|     my @env_not_valid = ();
 | |
|     my @env_to_save = split ',', $envList;
 | |
|     my $env;
 | |
|     for $env (@env_to_save)
 | |
|     {
 | |
|         if (!grep /$env/, @dsh_valid_env)
 | |
|         {
 | |
|             push @env_not_valid, $env;
 | |
|         }
 | |
|     }
 | |
|     if (scalar @env_not_valid > 0)
 | |
|     {
 | |
|         $env = join ",", @env_not_valid;
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = "Invalid Environment Variable: $env";
 | |
|         xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|         return;
 | |
|     }
 | |
|     for $env (@dsh_valid_env)
 | |
|     {
 | |
|         if (!grep /$env/, @env_to_save)
 | |
|         {
 | |
|             delete $ENV{$env};
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #--------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|     isFdNumExceed
 | |
| 
 | |
|     check if file descriptor number exceed the max number in ulimit
 | |
| 
 | |
|     Arguments:
 | |
|         $node_fd: indicates the file descriptor number required by each nodes
 | |
|         $node_num: indicates the target nodes number
 | |
|         $fanout: indicates the fanout value
 | |
|         $remain_fd: indicates the file descriptor number remained by main processes
 | |
|     Returns:
 | |
|         0: file descriptor number does not exceed the max number
 | |
|         1: file descriptor number exceed the max number
 | |
|     Globals:
 | |
|         None
 | |
|     Error:
 | |
|         None
 | |
|     Example:
 | |
|         xCAT::DSHCLI->check_fd_num(2, scalar( keys(%resolved_targets)), $$options{'fanout'})
 | |
| 
 | |
|     Comments:
 | |
|         none
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #--------------------------------------------------------------------------------
 | |
| sub isFdNumExceed
 | |
| {
 | |
| 
 | |
|     my ($class, $node_fs, $node_num, $fanout) = @_;
 | |
| 
 | |
|     # Check the file descriptor number
 | |
|     my $ulimit_cmd;
 | |
|     my $ls_cmd;
 | |
|     if ($^O =~ /^linux$/i)
 | |
|     {
 | |
|         $ulimit_cmd =
 | |
|           "/bin/bash -c \'ulimit -n\'";    #ulimit is embedded in bash on linux
 | |
|         $ls_cmd = '/bin/ls';
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         $ulimit_cmd = '/usr/bin/ulimit -n';    #On AIX
 | |
|         $ls_cmd     = '/usr/bin/ls';
 | |
|     }
 | |
|     my $fdnum = xCAT::Utils->runcmd($ulimit_cmd);
 | |
| 
 | |
|     return 0 if ($fdnum =~ /\s*unlimited\s*/);
 | |
| 
 | |
|     if ($fdnum !~ /\s*\d+\s*/)                 #this should never happen
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] = "Unsupport ulimit return code!";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     my $pid = getpid;
 | |
| 
 | |
|     #    print "pid is $pid\n";
 | |
| 
 | |
|     #there are some pid will be remained by dsh main process, such STDIN, STDOUT and others,
 | |
|     #it can be different on different system. So need to check them everytime
 | |
|     my @remain_fds    = xCAT::Utils->runcmd($ls_cmd . " /proc/$pid/fd");
 | |
|     my $remain_fd_num = scalar(@remain_fds);
 | |
| 
 | |
|     #    print "remain fd num is $remain_fd_num\n";
 | |
|     #   sleep 1000;
 | |
|     $node_num = ($node_num < $fanout) ? $node_num : $fanout;
 | |
|     my $max_fd_req = $node_fs * $node_num;  #the max fd number required by nodes
 | |
| 
 | |
|     if (($max_fd_req + $remain_fd_num) > $fdnum)
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] = "Reached fdnum=  $fdnum";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|       usage_dsh
 | |
| 
 | |
|         puts out dsh usage message
 | |
| 
 | |
|         Arguments:
 | |
|           None
 | |
| 
 | |
|         Returns:
 | |
| 
 | |
|         Globals:
 | |
| 
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub usage_dsh
 | |
| {
 | |
| ## usage message
 | |
|     my $usagemsg1  = " xdsh -h \n xdsh -q \n xdsh -V \n";
 | |
|     my $usagemsg1a = "xdsh  <noderange> [-K] [-l logonuserid]\n";
 | |
|     my $usagemsg2  = "      [-B bypass ] [-c] [-e] [-E environment_file]
 | |
|       [--devicetype type_of_device] [-f fanout]\n";
 | |
|     my $usagemsg3 = "      [-l user_ID] [-L]  ";
 | |
|     my $usagemsg4 = "[-m] [-o options][-q] [-Q] [-r remote_shell]
 | |
|       [-i image] [-s] [-S ksh | csh] [-t timeout]\n";
 | |
|     my $usagemsg5 = "      [-T] [-X environment variables] [-v] [-z] [--sudo]\n";
 | |
|     my $usagemsg6 = "      <command_list>";
 | |
|     my $usagemsg .= $usagemsg1 .= $usagemsg1a .= $usagemsg2 .= $usagemsg3 .=
 | |
|       $usagemsg4 .= $usagemsg5 .= $usagemsg6;
 | |
| ###  end usage mesage
 | |
|     if ($::CALLBACK)
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = $usagemsg;
 | |
|         xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         xCAT::MsgUtils->message("I", $usagemsg . "\n");
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|        parse_and_run_dsh
 | |
| 
 | |
|         This parses the dsh input build the call to execute_dsh.
 | |
| 
 | |
|         Arguments:
 | |
| 		  $nodes,$args,$callback,$command,$noderange
 | |
| 		  These may exist, called from xdsh plugin
 | |
| 
 | |
|         Returns:
 | |
|            Errors if invalid options or the executed dsh command
 | |
| 
 | |
|         Globals:
 | |
| 
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub parse_and_run_dsh
 | |
| {
 | |
|     my ($class, $nodes, $args, $callback, $command, $noderange) = @_;
 | |
| 
 | |
|     $::CALLBACK = $callback;
 | |
|     if (!($args))
 | |
|     {
 | |
|         usage_dsh;
 | |
|         return;
 | |
|     }
 | |
|     @ARGV = @{$args};    # get arguments
 | |
|     if ($ENV{'XCATROOT'})
 | |
|     {
 | |
|         $::XCATROOT = $ENV{'XCATROOT'};    # setup xcatroot home directory
 | |
|     }
 | |
|     elsif (-d '/opt/xcat')
 | |
|     {
 | |
|         $::XCATROOT = "/opt/xcat";
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         $::XCATROOT = "/usr";
 | |
|     }
 | |
| 
 | |
|     # parse the arguments
 | |
|     Getopt::Long::Configure("posix_default");
 | |
|     Getopt::Long::Configure("no_gnu_compat");
 | |
|     Getopt::Long::Configure("bundling");
 | |
| 
 | |
|     my %options = ();
 | |
| 
 | |
|     # check for wrong long options
 | |
|     if (xCAT::DSHCLI->check_valid_options(\@ARGV))
 | |
|     {
 | |
|         usage_dsh;
 | |
|         return 1;
 | |
|     }
 | |
| 
 | |
|     if (
 | |
|         !GetOptions(
 | |
|             'e|execute'                => \$options{'execute'},
 | |
|             'f|fanout=i'               => \$options{'fanout'},
 | |
|             'h|help'                   => \$options{'help'},
 | |
|             'l|user=s'                 => \$options{'user'},
 | |
|             'm|monitor'                => \$options{'monitor'},
 | |
|             'o|node-options=s'         => \$options{'node-options'},
 | |
|             'q|show-config'            => \$options{'show-config'},
 | |
|             'r|node-rsh=s'             => \$options{'node-rsh'},
 | |
|             'i|rootimg=s'              => \$options{'rootimg'},
 | |
|             's|stream'                 => \$options{'streaming'},
 | |
|             't|timeout=i'              => \$options{'timeout'},
 | |
|             'v|verify'                 => \$options{'verify'},
 | |
|             'z|exit-status'            => \$options{'exit-status'},
 | |
|             'B|bypass'                 => \$options{'bypass'},
 | |
|             'c|cleanup'                => \$options{'cleanup'},
 | |
|             'E|environment=s'          => \$options{'environment'},
 | |
|             'I|ignore-sig|ignoresig=s' => \$options{'ignore-signal'},
 | |
|             'K|keysetup'               => \$options{'ssh-setup'},
 | |
|             'L|no-locale'              => \$options{'no-locale'},
 | |
|             'Q|silent'                 => \$options{'silent'},
 | |
|             'S|syntax=s'               => \$options{'syntax'},
 | |
|             'T|trace'                  => \$options{'trace'},
 | |
|             'V|version'                => \$options{'version'},
 | |
| 
 | |
|             'devicetype|devicetype=s'    => \$options{'devicetype'},
 | |
|             'nodestatus|nodestatus' => \$options{'nodestatus'},            
 | |
|             'sudo|sudo' => \$options{'sudo'},            
 | |
|             'command-name|commandName=s' => \$options{'command-name'},
 | |
|             'command-description|commandDescription=s' =>
 | |
|               \$options{'command-description'},
 | |
|             'X:s' => \$options{'ignore_env'}
 | |
| 
 | |
|         )
 | |
|       )
 | |
|     {
 | |
|         xCAT::DSHCLI->usage_dsh;
 | |
|         return 1;
 | |
|     }
 | |
| 
 | |
|     if ($options{'help'})
 | |
|     {
 | |
|         xCAT::DSHCLI->usage_dsh;
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     if ($options{'show-config'})
 | |
|     {
 | |
|         xCAT::DSHCLI->show_dsh_config;
 | |
|         return 0;
 | |
|     }
 | |
|     my $remotecommand = $options{'node-rsh'};
 | |
|     if ($options{'node-rsh'}
 | |
|         && (!-f $options{'node-rsh'} || !-x $options{'node-rsh'}))
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Remote command: $remotecommand does not exist or is not executable.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     # put rsync on a dsh command
 | |
|     if ($options{'node-rsh'}
 | |
|         && (grep /rsync/, $options{'node-rsh'}))
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Remote command: $remotecommand should be used with the dcp command. ";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (defined $options{'ignore_env'})
 | |
|     {
 | |
|         xCAT::DSHCLI->ignoreEnv($options{'ignore_env'});
 | |
|     }
 | |
| 
 | |
|     # this was determined in the xdsh client code, because non-root user
 | |
|     # actions must be taken there.  For those calling xdsh plugin, default
 | |
|     # is root
 | |
|     if (!($ENV{'DSH_TO_USERID'})) # env first
 | |
|     {
 | |
|        if (!($options{'user'})) {  # -l second
 | |
|         $options{'user'} = "root";   # default
 | |
|        }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         $options{'user'} = $ENV{'DSH_TO_USERID'};
 | |
|     }
 | |
| 
 | |
|     if ((!(defined(@$nodes))) && (!(defined($options{'rootimg'}))))
 | |
|     {    #  no nodes and not -i option, error
 | |
|         my $rsp = ();
 | |
|         $rsp->{error}->[0] = "Unless using -i option,  noderange is required.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return;
 | |
|     }
 | |
|     # Determine switch type, processing Mellanox not the same as QLogic
 | |
|     my $switchtype = $options{'devicetype'};
 | |
|     $switchtype =~ s/::/\//g;
 | |
| 
 | |
|     #
 | |
|     # build list of nodes
 | |
|     my @nodelist;
 | |
|     my $imagename;
 | |
|     if (defined($options{'rootimg'}))
 | |
|     {    # running against local host
 | |
|             # diskless image
 | |
|         # need directory for Linux,  just osimage name for AIX
 | |
|         if (xCAT::Utils->isLinux()) {
 | |
|          if (!(-e ($options{'rootimg'})))
 | |
|          {    # directory does not exist
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "Input image directory $options{'rootimg'} does not exist.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|          }
 | |
|         }
 | |
| 
 | |
|         # since we have no input nodes for running xdsh against the image
 | |
|         # we will use the create the hostname from the directory
 | |
|         # for the hostname in the output
 | |
|         my $path = $options{'rootimg'};
 | |
|         $imagename = xCAT::Utils->get_image_name($path);
 | |
|         if (@$nodes[0] eq "NO_NODE_RANGE")
 | |
|         {    # from sinv, discard this name
 | |
|             undef @$nodes;
 | |
|         }
 | |
|         if (defined(@$nodes))
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "Input noderange:@$nodes and any other xdsh flags or environment variables are not valid with -i flag.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         @nodelist = @$nodes;
 | |
|         $options{'nodes'} = join(',', @nodelist);
 | |
|     }
 | |
| 
 | |
|     #printf " node list is $options{'nodes'}";
 | |
|     # build arguments
 | |
| 
 | |
|     # get the actual xdsh command from the argument list
 | |
|     $options{'command'} = join ' ', @ARGV;
 | |
|     
 | |
| 
 | |
|     # if a Mellanox switch command we have to build
 | |
|     # the command xdsh will send special 
 | |
|     # input will look something like this
 | |
|     # xdsh mswitch -l admin --devicetype IBSwitch::Mellanox
 | |
|     #  enable;configure terminal;show ssh server host-keys
 | |
|     # We will build
 | |
|     #  ssh admin@mswitch cli
 | |
|     #     "enable" "configure terminal" "show ssh server host-keys" 
 | |
|     my @melcmds;
 | |
|     if ($switchtype =~ /Mellanox/i) {
 | |
|       $::DSH_MELLANOX_SWITCH=1;
 | |
|       @melcmds = split (/;/, $options{'command'});
 | |
|       my $newcmd;
 | |
|       foreach my $cmd (@melcmds) {
 | |
|        $newcmd .= "\"";
 | |
|        $newcmd .= $cmd;
 | |
|        $newcmd .= "\" ";
 | |
|       }
 | |
|       $options{'command'} = $newcmd;  
 | |
|     } else {
 | |
|       $::DSH_MELLANOX_SWITCH=0;
 | |
|     }
 | |
|     #
 | |
|     # -K option just sets up the ssh keys on the nodes and exits
 | |
|     #
 | |
| 
 | |
|     if (defined $options{'ssh-setup'})
 | |
|     {
 | |
|         # check if any node in the noderange is the Management Node and exit 
 | |
|         # with error, if the Management Node is in the Database and in the
 | |
|         # noderange
 | |
|         my $mname = xCAT::Utils->noderangecontainsMn(@nodelist); 
 | |
|         if ($mname) {  # MN in the nodelist
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "You must not run -K option against the Management Node:$mname.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         } 
 | |
|         # if devicetype=Mellanox,  xdsh does not setup ssh,  rspconfig does
 | |
|         if ($switchtype =~ /Mellanox/i) {
 | |
|            my $rsp = {};
 | |
|            $rsp->{error}->[0] = 
 | |
|             "You do not use xdsh -K to setup the Mellanox switch ssh keys. Use rspconfig. See man page for rspconfig option sshcfg={enable|disable}.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (defined $options{'rootimg'})
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] = "Cannot use -R and -K flag together";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
| 
 | |
|         }
 | |
| 
 | |
|         #  IF using the xdsh -K -l option then we are setting up the
 | |
|         #  --devicetype.   xdsh -K -l is not allowed for users.
 | |
|         #  This is checked for in the client code.
 | |
|         #  DSH_REMOTE_PASSWORD env variable must be set to the correct
 | |
|         #  password for the key update.  This was setup in xdsh client
 | |
|         #  frontend.  
 | |
| 
 | |
|         if (!($ENV{'DSH_REMOTE_PASSWORD'}))
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "User password for ssh key exchange has not been supplied.\n Cannot complete the -K command\n";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
| 
 | |
|         }
 | |
| 
 | |
|         if (!($ENV{'DSH_FROM_USERID'}))
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "Current Userid has not been supplied.\n Cannot complete the -K command.\n";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
| 
 | |
|         }
 | |
| 
 | |
|         if (!($ENV{'DSH_TO_USERID'}))   # id to logon to the node and update the
 | |
|                                         # keys
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "Logon  Userid has not been supplied.\n Cannot complete the -K command.\n";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
| 
 | |
|         }
 | |
| 
 | |
|         my $current_userid = $ENV{'DSH_FROM_USERID'};
 | |
|         my $to_userid      = $ENV{'DSH_TO_USERID'};
 | |
| 
 | |
|         # if current_userid ne touserid then current_userid
 | |
|         # must be root
 | |
|         if (   ($current_userid ne $to_userid)
 | |
|             && ($current_userid ne "root"))
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "When touserid:$to_userid is not the same as the current user:$current_userid. The command must be run by root id.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         # setting up IB switch ssh, different interface that ssh for
 | |
|         # userid on node.  Must build special ssh command to be sent
 | |
|         # to the IB switch to setup ssh
 | |
|         if (defined $options{'devicetype'})
 | |
|         {
 | |
|             $ENV{'DEVICETYPE'} = $options{'devicetype'};
 | |
|             my $devicename = $options{'devicetype'};
 | |
|             $devicename =~ s/::/\//g;
 | |
|             my $devicepath = "/var/opt/xcat/" . $devicename . "/config";
 | |
|             # go to backup directory if the config file 
 | |
|             # cannot be found under /var/opt/xcat/...
 | |
|             if (! -e $devicepath) { 
 | |
| 		$devicepath="$::XCATROOT/share/xcat/devicetype/" . $devicename . "/config";
 | |
| 	    }
 | |
| 	    if (-e $devicepath) 
 | |
|             {
 | |
|                 my $deviceconf = get_config($devicepath);
 | |
| 
 | |
|                 # Get ssh-setup-command attribute from configuration
 | |
|                 $ENV{'SSH_SETUP_COMMAND'} =
 | |
|                   $$deviceconf{'main'}{'ssh-setup-command'};
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         #
 | |
|         # setup ssh keys on the nodes or ib switch
 | |
|         #
 | |
|         my $rc      = xCAT::TableUtils->setupSSH($options{'nodes'});
 | |
|         my @results = "return code = $rc";
 | |
|         return (@results);
 | |
|     }
 | |
|     if (!(@ARGV))
 | |
|     {    #  no args , an error
 | |
| 
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] = "No command argument provided";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return;
 | |
|     }
 | |
|     my @results;
 | |
|     if (defined $options{'rootimg'})
 | |
|     {
 | |
|         @results = xCAT::DSHCLI->runlocal_on_rootimg(\%options, $imagename);
 | |
|         if ($::RUNCMD_RC)
 | |
|         {    # error from dsh
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] = "Error from xdsh. Return Code = $::RUNCMD_RC";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
| 
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
| 
 | |
|         #
 | |
|         # Execute the dsh api
 | |
|         #@results = xCAT::DSHCLI->runDsh_api(\%options, 0);
 | |
|         #if ($::RUNCMD_RC)
 | |
|         #{    # error from dsh
 | |
|         #   $rsp->{error}->[0] = "Error from xdsh. Return Code = $::RUNCMD_RC";
 | |
|         #  xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
| 
 | |
|         #}
 | |
|         # Execute the dsh command
 | |
|         # number of nodes failed becomes the xdsh return code
 | |
|         $::FAILED_NODES = xCAT::DSHCLI->execute_dsh(\%options);
 | |
|     }
 | |
|     return (@results);
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|       usage_dcp
 | |
| 
 | |
|         puts out dcp usage message
 | |
| 
 | |
|         Arguments:
 | |
|           None
 | |
| 
 | |
|         Returns:
 | |
| 
 | |
|         Globals:
 | |
| 
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub usage_dcp
 | |
| {
 | |
|     ### usage message
 | |
|     my $usagemsg1 = " xdcp -h \n xdcp -q\n xdcp -V \n xdcp <noderange>\n";
 | |
|     my $usagemsg2 = "      [-B bypass] [-c] [-f fanout] [-l user_ID] [--sudo]\n";
 | |
|     my $usagemsg3 =
 | |
|       "      [-m] [-o options] [-p] [-P] [-q] [-Q] [-r node_remote_copy]\n";
 | |
|     my $usagemsg4 =
 | |
|       "      [-R] [-t timeout] [-T] [-X environment variables] [-v] \n";
 | |
|     my $usagemsg5  = "      source_file... target_path\n";
 | |
|     my $usagemsg5a = " xdcp <noderange> [-s] -F <rsyncfile> ";
 | |
|     my $usagemsg5b = "[-f fanout] [-t timeout] [-o options] [-v]\n";
 | |
|     my $usagemsg5c = " xdcp <-i imagepath> -F <rsyncfile> ";
 | |
|     my $usagemsg5d = "[-o options]";
 | |
| 
 | |
|     my $usagemsg .= $usagemsg1 .= $usagemsg2 .= $usagemsg3 .= $usagemsg4 .=
 | |
|       $usagemsg5 .= $usagemsg5a .= $usagemsg5b .= $usagemsg5c .= $usagemsg5d;
 | |
| 
 | |
|     if ($::CALLBACK)
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = $usagemsg;
 | |
|         xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         xCAT::MsgUtils->message("I", $usagemsg . "\n");
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|        parse_and_run_dcp
 | |
| 
 | |
|         This parses the dcp input build the call to execute_dcp.
 | |
| 
 | |
|         Arguments:
 | |
| 		  $nodes,$args,$callback,$command,$noderange
 | |
| 		  These may exist, called from xdsh plugin
 | |
| 
 | |
|         Returns:
 | |
|            Errors if invalid options or the executed dcp command
 | |
| 
 | |
|         Globals:
 | |
| 
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub parse_and_run_dcp
 | |
| {
 | |
|     my ($class, $nodes, $args, $callback, $command, $noderange) = @_;
 | |
|     $::CALLBACK = $callback;
 | |
|     if (!($args))
 | |
|     {
 | |
|         usage_dcp;
 | |
|         return 1;
 | |
|     }
 | |
|     @ARGV = @{$args};    # get arguments
 | |
|     if ($ENV{'XCATROOT'})
 | |
|     {
 | |
|         $::XCATROOT = $ENV{'XCATROOT'};    # setup xcatroot home directory
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         $::XCATROOT = "/opt/xcat";
 | |
|     }
 | |
| 
 | |
|     # parse the arguments
 | |
|     Getopt::Long::Configure("posix_default");
 | |
|     Getopt::Long::Configure("no_gnu_compat");
 | |
|     Getopt::Long::Configure("bundling");
 | |
| 
 | |
|     my %options = ();
 | |
| 
 | |
|     # check for wrong long options
 | |
|     if (xCAT::DSHCLI->check_valid_options(\@ARGV))
 | |
|     {
 | |
|         usage_dcp;
 | |
|         return 1;
 | |
|     }
 | |
|     if (
 | |
|         !GetOptions(
 | |
|                     'f|fanout=i'       => \$options{'fanout'},
 | |
|                     'F|File=s'         => \$options{'File'},
 | |
|                     'h|help'           => \$options{'help'},
 | |
|                     'l|user=s'         => \$options{'user'},
 | |
|                     'm|monitor'                => \$options{'monitor'},
 | |
|                     'o|node-options=s' => \$options{'node-options'},
 | |
|                     'q|show-config'    => \$options{'show-config'},
 | |
|                     'p|preserve'       => \$options{'preserve'},
 | |
|                     'r|c|node-rcp=s'   => \$options{'node-rcp'},
 | |
|                     'i|rootimg=s'      => \$options{'rootimg'},
 | |
|                     's'                => \$options{'rsyncSN'},
 | |
|                     't|timeout=i'      => \$options{'timeout'},
 | |
|                     'v|verify'         => \$options{'verify'},
 | |
|                     'B|bypass'         => \$options{'bypass'},
 | |
|                     'Q|silent'         => \$options{'silent'},
 | |
|                     'P|pull'           => \$options{'pull'},
 | |
|                     'R|recursive'      => \$options{'recursive'},
 | |
|                     'T|trace'          => \$options{'trace'},
 | |
|                     'V|version'        => \$options{'version'},
 | |
|                     'devicetype=s'     => \$options{'devicetype'},
 | |
|                     'nodestatus|nodestatus' => \$options{'nodestatus'},        
 | |
|                     'sudo|sudo' => \$options{'sudo'},            
 | |
|                     'X:s'              => \$options{'ignore_env'}
 | |
|         )
 | |
|       )
 | |
|     {
 | |
|         usage_dcp;
 | |
|         return (1);
 | |
|     }
 | |
| 
 | |
|     my $rsp = {};
 | |
|     if ($options{'help'})
 | |
|     {
 | |
|         usage_dcp;
 | |
|         return (0);
 | |
|     }
 | |
|     if ($options{'show-config'})
 | |
|     {
 | |
|         xCAT::DSHCLI->show_dsh_config;
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     if (defined($options{'rootimg'}))
 | |
|     {
 | |
|         if (xCAT::Utils->isAIX())
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] = "The -i option is not supported on AIX.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
|     if ((!(defined(@$nodes))) && (!(defined($options{'rootimg'}))))
 | |
|     {    #  no nodes and not -i option, error
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] = "Unless using -i option,  noderange is required.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if ($options{'version'})
 | |
|     {
 | |
|         my $version = xCAT::Utils->Version();
 | |
|         my $rsp = {};
 | |
|         $rsp->{data}->[0] = "$version";
 | |
|         xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
 | |
|         return (0);
 | |
|     }
 | |
| 
 | |
|     if (defined $options{'ignore_env'})
 | |
|     {
 | |
|         xCAT::DSHCLI->ignoreEnv($options{'ignore_env'});
 | |
|     }
 | |
|     if (defined($options{'rootimg'}))
 | |
|     {    # running against local host
 | |
|             # diskless image
 | |
| 
 | |
|         if (!(-e ($options{'rootimg'})))
 | |
|         {    # directory does not exist
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "Input image directory $options{'rootimg'} does not exist.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         }
 | |
|         if (!($options{'File'}))
 | |
|         {    # File not given
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] =
 | |
|               "If -i option is use, then the -F option must input the file list.\nThe file will contain the list of files to rsync to the image.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     # invalid to put the -F  with the -r flag
 | |
|     if ($options{'File'} && $options{'node-rcp'})
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "If -F option is use, then -r is invalid. The command will always the rsync using ssh.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     # invalid to put the -s  without the -F flag
 | |
|     if (!($options{'File'}) && $options{'rsyncSN'})
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] =
 | |
|           "If -s option is use, then -F must point to the syncfile.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     # -s chosen or -F set rsync path
 | |
|     if ($options{'rsyncSN'} || $options{'File'})
 | |
|     {
 | |
|         if ($^O eq 'aix')
 | |
|         {
 | |
|             if (-e ("/usr/bin/rsync"))
 | |
|             {
 | |
|                 $options{'node-rcp'} = '/usr/bin/rsync';
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 $options{'node-rcp'} = '/usr/local/bin/rsync';
 | |
|             }
 | |
| 
 | |
|         }
 | |
|         elsif ($^O eq 'linux')
 | |
|         {
 | |
|             $options{'node-rcp'} = '/usr/bin/rsync';
 | |
|         }
 | |
|     }
 | |
|     my $remotecopycommand = $options{'node-rcp'};
 | |
|     if ($options{'node-rcp'}
 | |
|         && (!-f $options{'node-rcp'} || !-x $options{'node-rcp'}))
 | |
|     {   
 | |
|         my $rsp={};
 | |
|         $rsp->{error}->[0] =
 | |
|           "Remote command: $remotecopycommand does not exist or is not executable.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # build list of nodes
 | |
|     my @nodelist;
 | |
|     if (defined(@$nodes))
 | |
|     {    # there are nodes
 | |
|         @nodelist = @$nodes;
 | |
|         $options{'nodes'} = join(',', @nodelist);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         if (!($options{'rootimg'}))
 | |
|         {
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] = "Noderange missing in command input.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     #  if -F flag then we are going to process the file and use
 | |
|     #  rsync to distribute the files listed in the input file
 | |
|     #  Format of the file lines are the following, follows the rsync syntax
 | |
|     #    /.../file   /..../file2 ->  /..../directory
 | |
|     #    /....file*   /...../sample*  -> /..../directory
 | |
|     #
 | |
|     my @results;
 | |
|     $::SYNCSN = 0;
 | |
| 
 | |
|     # if updating an install image
 | |
|     # only going to run rsync locally
 | |
|     if (($options{'File'}) && ($options{'rootimg'}))
 | |
|     {
 | |
|         my $image = $options{'rootimg'};
 | |
|         my $rc = &rsync_to_image($options{'File'}, $image);
 | |
|         if ($rc != 0)
 | |
|         {    # error from dcp
 | |
|             my $rsp = {};
 | |
|             $rsp->{error}->[0] = "Error running rsync to image:$image.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
| 
 | |
|         }
 | |
|         return;
 | |
| 
 | |
|     }
 | |
|     my $synfiledir;
 | |
|     my $nodesyncfiledir;
 | |
|     # set default sync dir on service node  and node
 | |
|     # right now setting the nodes and sn syncfiledir the same, leaving
 | |
|     # the possibility that one day we may want them to be different 
 | |
|     $synfiledir = "/var/xcat/syncfiles";
 | |
|     $nodesyncfiledir = "/var/xcat/node/syncfiles";
 | |
| 
 | |
|     # get the directory on the servicenode to put the rsync files in
 | |
|     my @syndir = xCAT::TableUtils->get_site_attribute("SNsyncfiledir");
 | |
|     if ($syndir[0])
 | |
|     {
 | |
|             $synfiledir = $syndir[0];
 | |
|     }
 | |
|     # get the directory on the node to put the rsync files in
 | |
|     my @syndir = xCAT::TableUtils->get_site_attribute("nodesyncfiledir");
 | |
|     if ($syndir[0])
 | |
|     {
 | |
|           $nodesyncfiledir = $syndir[0]; 
 | |
|     }
 | |
| 
 | |
|     my $rc;
 | |
|     my $syncfile;
 | |
|     if ($options{'File'}) {
 | |
|      $syncfile = $options{'File'};
 | |
|      if (!-f $options{'File'}) 
 | |
|      {
 | |
|             my $rsp = ();
 | |
|             $rsp->{error}->[0] = "File:$syncfile does not exist.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|      }
 | |
|     }
 | |
| 
 | |
|     # if rsyncing the nodes or service nodes
 | |
| 
 | |
|     if ($options{'File'})
 | |
|     {
 | |
| 
 | |
|         # if syncing a service node
 | |
|         if ($options{'rsyncSN'})
 | |
|         {
 | |
|             $::SYNCSN = 1;
 | |
|         }
 | |
| 
 | |
|         # the parsing of the file will fill in an array of postscripts 
 | |
|         # need to be run if the associated file is updated
 | |
|         @::postscripts=();
 | |
|         @::alwayspostscripts=();
 | |
|         @::appendlines=();
 | |
|         @::mergelines=();
 | |
|         if (xCAT::Utils->isServiceNode())
 | |
|         {    # running on service node
 | |
|             $rc =
 | |
|               &parse_rsync_input_file_on_SN(\@nodelist, \%options, $syncfile,
 | |
|                                             $synfiledir,$nodesyncfiledir);
 | |
|         }
 | |
|         else
 | |
|         {    # running on MN
 | |
|             $rc =
 | |
|               &parse_rsync_input_file_on_MN(\@nodelist, \%options, $syncfile,
 | |
|                       $::SYNCSN, $synfiledir,$nodesyncfiledir);
 | |
|                            # build a temporary syncfile for the node's synclist
 | |
|               if ($::SYNCSN ==1) {  # syncing a servicenode
 | |
|                 # we need to make sure the latest is on the servicenode
 | |
|                 # for running of the syncfiles postscript, which only pulls
 | |
|                 # from the service node
 | |
|                 my $tmpsyncfile="/tmp/xdcpsynclist.$$";
 | |
|                 my $syncline = "$syncfile -> $syncfile";
 | |
|                 open(FILE, ">$tmpsyncfile")
 | |
|                   or die "cannot open file $tmpsyncfile\n";
 | |
|                 print FILE " $syncline";
 | |
|                 close FILE;
 | |
|                 # now put the original syncfile on the queue to sync to the SN's
 | |
|                 $rc =
 | |
|                 &parse_rsync_input_file_on_MN(\@nodelist, \%options,$tmpsyncfile,
 | |
|                    $::SYNCSN, $synfiledir,$nodesyncfiledir);
 | |
|                 # cleanup
 | |
|                 my $cmd = "rm $tmpsyncfile";
 | |
|                 my @output = xCAT::Utils->runcmd($cmd, 0);
 | |
|                 if ($::RUNCMD_RC != 0)
 | |
|                 {
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{error}->[0] = "Command: $cmd failed.";
 | |
|                     xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|                 }
 | |
|               }
 | |
| 
 | |
|         }
 | |
|         if ($rc == 1)
 | |
|         {
 | |
|             my $rsp={};
 | |
|             $rsp->{error}->[0] = "Error parsing the rsync file:$syncfile.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|     }
 | |
|     else    # source and destination files are from command line
 | |
|     {
 | |
|         if (@ARGV < 1)
 | |
|         {
 | |
|             my $rsp={};
 | |
|             $rsp->{error}->[0] = "Missing file arguments";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         elsif (@ARGV == 1)
 | |
|         {
 | |
|             if ($options{'pull'})
 | |
|             {
 | |
|                 my $rsp={};
 | |
|                 $rsp->{error}->[0] = "Missing target_path";
 | |
|                 xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             else
 | |
|             {
 | |
|                 # HERE:only one line of input source and target in that line
 | |
|                 # such as xdcp -R " /test/* /test"
 | |
|                 my $tmparg = pop @ARGV;
 | |
|                 my ($src,$tgt) = split " ", $tmparg;
 | |
|                 $options{'target'} = $tgt;
 | |
|                 $options{'source'} = join $::__DCP_DELIM, $src;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         elsif ($options{'pull'} && (@ARGV > 2))
 | |
|         {
 | |
|             my $rsp={};
 | |
|             $rsp->{error}->[0] = "Cannot pull more than one file from targets.";
 | |
|             xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         else
 | |
|         {
 | |
|             # Get the source and the target 
 | |
|             $options{'target'} = pop @ARGV;
 | |
|             $options{'source'} = join $::__DCP_DELIM, @ARGV;
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     # Execute the dcp api
 | |
|     # HERE:  Run xdcp  LKV
 | |
|     @results = xCAT::DSHCLI->runDcp_api(\%options, 0);
 | |
|     $::FAILED_NODES = $::RUNCMD_RC;
 | |
|     
 | |
|     # if not just syncing the service node SNsyncfiledir directory,
 | |
|     #  @::postscripts should be empty in this case anyway
 | |
|     # if postscripts to run after rsync, process the output and 
 | |
|     # create the xdsh command to run the ones needed
 | |
|     my  @results2;
 | |
|     my  @results3;
 | |
|     my  @results4;
 | |
|     my  @results5;
 | |
|     my $ranpostscripts;
 | |
|     my $ranappendscripts;
 | |
|     my $ranmergescripts;
 | |
|     if ((@::postscripts) && ($::SYNCSN == 0)) {
 | |
|        @results2 = &run_rsync_postscripts(\@results,$synfiledir); 
 | |
|        $ranpostscripts=1;
 | |
|     }
 | |
|     if ((@::alwayspostscripts) && ($::SYNCSN == 0)) {
 | |
|        @results3 = &run_always_rsync_postscripts(\@nodelist,$synfiledir); 
 | |
|     }
 | |
|     if (($::appendscript) && ($::SYNCSN == 0)) {
 | |
|        @results4 = &bld_and_run_append(\@nodelist,\@results,$synfiledir,$nodesyncfiledir); 
 | |
|        $ranappendscripts=1;
 | |
|     }
 | |
|     if (($::mergescript) && ($::SYNCSN == 0)) {
 | |
|        @results5 = &bld_and_run_merge(\@nodelist,\@results,$synfiledir,$nodesyncfiledir); 
 | |
|        $ranmergescripts=1;
 | |
|     }
 | |
|     my @newresults;
 | |
|     if (@results2) {
 | |
|       @newresults = (@results2);
 | |
|     }
 | |
|     if (@results3) {
 | |
|       @newresults = (@newresults,@results3);
 | |
|     }
 | |
|     if (@results4) {
 | |
|       @newresults = (@newresults,@results3,@results4);
 | |
|     }
 | |
|     if (@results5) {
 | |
|       @newresults = (@newresults,@results3,@results4,@results5);
 | |
|     }
 | |
|     if (@newresults) {
 | |
|       return (@newresults);
 | |
|     } else {
 | |
|       # don't report results for postscripts,appendscripts,mergescripts because
 | |
|       # you get all the rsync returned lines
 | |
|       if (($ranpostscripts == 0 ) && ($ranappendscripts == 0) 
 | |
|          && ($ranmergescripts == 0)) { 
 | |
|         return (@results);
 | |
|       }    
 | |
|     }    
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|        rsync_to_image
 | |
| 
 | |
|         This parses the -F rsync input file. and runs rsync to the input
 | |
| 		image for the files
 | |
| 		Does not process the EXECUTE or EXECUTEALWAYS statement
 | |
| 
 | |
|         File format:
 | |
|           /.../file1 ->  /.../dir1/filex
 | |
|           /.../file1 ->  /.../dir1
 | |
|           /.../file1 /..../filex  -> /...../dir1
 | |
| 
 | |
|        rsync command format
 | |
| 	   /usr/bin/rsync -Lprgotz  /etc/services  $pathtoimage/etc/services 
 | |
| 	   /usr/bin/rsync -Lprgotz  /tmp/lissa/file1 /tmp/lissa/file $pathtoimage/tmp/lissa
 | |
| 
 | |
|         Arguments:
 | |
| 		  Input:
 | |
| 		  sync file
 | |
| 		  path to image 
 | |
| 
 | |
|         Returns:
 | |
|            Errors if invalid options or the executed dcp command
 | |
| 
 | |
|         Globals:
 | |
| 
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub rsync_to_image
 | |
| {
 | |
|     use File::Basename;
 | |
| 
 | |
|     my ($input_file, $image) = @_;
 | |
|     my $rc = 0;
 | |
|     open(INPUTFILE, "< $input_file") || die "File $input_file does not exist\n";
 | |
|     while (my $line = <INPUTFILE>)
 | |
|     {
 | |
|         chomp $line;
 | |
|         if ($line =~ /^#/)    # skip commments
 | |
|         {
 | |
|             next;
 | |
|         }
 | |
| 
 | |
|         # process no more lines, do not exec
 | |
|         # do not execute postscripts when syncing images 
 | |
|         if (($line =~ /EXECUTE:/) || ($line =~ /EXECUTEALWAYS:/) 
 | |
|            || ($line =~ /APPEND:/) || ($line =~ /MERGE:/))
 | |
|         { # process no more lines
 | |
| 			last;
 | |
| 	}
 | |
|         if ($line =~ /(.+) -> (.+)/)
 | |
|         {
 | |
|             my $imageupdatedir  = $image;
 | |
|             my $imageupdatepath = $image;
 | |
|             my $src_file        = $1;
 | |
|             my $dest_file       = $2;
 | |
|             $dest_file =~ s/[\s;]//g;
 | |
|             my $dest_dir;
 | |
|             if (-d $dest_file)    # if a directory on the left side
 | |
|             {                     # if a directory , just use
 | |
|                 $dest_dir = $dest_file;
 | |
|                 $dest_dir =~ s/\s*//g;    #remove blanks
 | |
|                 $imageupdatedir  .= $dest_dir;    # save the directory
 | |
|                 $imageupdatepath .= $dest_dir;    # path is a directory
 | |
|             }
 | |
|             else                                  # if a file on the left side
 | |
|             {                                     # strip off the file
 | |
|                 $dest_dir = dirname($dest_file);
 | |
|                 $dest_dir =~ s/\s*//g;            #remove blanks
 | |
|                 $imageupdatedir  .= $dest_dir;    # save directory
 | |
|                 $imageupdatepath .= $dest_file;   # path to a file
 | |
|             }
 | |
|             my @srcfiles = (split ' ', $src_file);
 | |
|             if (!(-d $imageupdatedir))
 | |
|             {    # if it does not exist, make it
 | |
|                 my $cmd = "mkdir -p $imageupdatedir";
 | |
|                 my @output = xCAT::Utils->runcmd($cmd, 0);
 | |
|                 if ($::RUNCMD_RC != 0)
 | |
|                 {
 | |
|                     my $rsp = {};
 | |
|                     $rsp->{error}->[0] = "Command: $cmd failed.";
 | |
|                     xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             # for each file on the line
 | |
|             my $synccmd = "";
 | |
|             if ($^O eq 'aix')
 | |
|             {
 | |
|                 if (-e ("/usr/bin/rsync"))
 | |
|                 {
 | |
|                     $synccmd = "/usr/bin/rsync -Lprogtz ";
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     $synccmd = "/usr/local/bin/rsync -Lprogtz ";
 | |
|                 }
 | |
|             }
 | |
|             else    # linux
 | |
|             {
 | |
|                 $synccmd = "/usr/bin/rsync -Lprogtz ";
 | |
|             }
 | |
|             my $syncopt = "";
 | |
|             foreach my $srcfile (@srcfiles)
 | |
|             {
 | |
| 
 | |
|                 $syncopt .= $srcfile;
 | |
|                 $syncopt .= " ";
 | |
|             }
 | |
|             $syncopt .= $imageupdatepath;
 | |
|             $synccmd .= $syncopt;
 | |
| 
 | |
|             xCAT::MsgUtils->message("S", "rsync2image: $synccmd\n");
 | |
|             my @output = xCAT::Utils->runcmd($synccmd, 0);
 | |
|             if ($::RUNCMD_RC != 0)
 | |
|             {
 | |
|                 my $rsp = {};
 | |
|                 $rsp->{error}->[0] = "Command: $synccmd failed.";
 | |
|                 xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
 | |
|             }
 | |
| 
 | |
|         }    # valid line
 | |
|     }    # end reading file
 | |
| 
 | |
|     close INPUTFILE;
 | |
|     return $rc;
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|        parse_rsync_input_file_on_MN
 | |
| 	  
 | |
| 
 | |
|         This parses the -F rsync input file on the Management node.
 | |
| 
 | |
|         File format:
 | |
|           /.../file1 ->  /.../dir1/filex
 | |
|           /.../file1 ->  /.../dir1
 | |
|           /.../*     ->  /.../dir1
 | |
|           /.../file1 /..../filex  -> /...../dir1
 | |
|           /tmp/file2  ->  /tmp/file2
 | |
|           /tmp/file2.post -> /tmp/file2.post
 | |
|           EXECUTE: 
 | |
|           /tmp/file2.post
 | |
|           EXECUTEALWAYS:
 | |
|           /tmp/myscript1
 | |
|           /tmp/myscript2
 | |
|           .
 | |
|           .
 | |
| 
 | |
|         Arguments:
 | |
| 		  Input nodelist,options, pointer to the sync file,flag is 
 | |
| 		  syncing the service node and
 | |
| 		  the directory to syn the files to on the service node
 | |
| 		  based on defaults or the site.SNsyncfiledir attribute,
 | |
| 		  if syncing a service node for hierarchical support. 
 | |
| 
 | |
|         Returns:
 | |
|            Errors if invalid options or the executed dcp command
 | |
| 
 | |
|         Globals:
 | |
| 
 | |
|          $::SYNCSN  indicates we are only syncing the files to the
 | |
|                 service nodes xdcp -s flag
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
|             We also add the original synclist file for the node to be sync'd
 | |
|                 to the service node
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub parse_rsync_input_file_on_MN
 | |
| {
 | |
|     use File::Basename;
 | |
|     my ($nodes, $options, $input_file, $rsyncSN, $syncdir,$nodesyncfiledir) = @_;
 | |
|     my @dest_host    = @$nodes;
 | |
|     $::process_line = 0;
 | |
|     my $destfileisdir;
 | |
|     my $clause=0;
 | |
|     
 | |
|     open(INPUTFILE, "< $input_file") || die "File $input_file does not exist\n";
 | |
|     while (my $line = <INPUTFILE>)
 | |
|     {
 | |
|         chomp $line;
 | |
|         if (($line =~ /^#/) || ( $line =~ /^\s*$/ ))
 | |
|                         # skip commments  and blanks
 | |
|         {
 | |
|             next;
 | |
|         }
 | |
|         # Determine if processing a clause or the synclist 
 | |
|         if (($line =~ /EXECUTE:/) || ($line =~ /EXECUTEALWAYS:/)
 | |
|                  || ($line =~ /APPEND:/) || ($line =~ /MERGE:/)) { 
 | |
|              $clause=$line;
 | |
|              next;   # get the content of the clause
 | |
|         }
 | |
|         # processing a clause
 | |
|         if (($clause =~ /APPEND:/) || ($clause =~ /EXECUTEALWAYS:/)
 | |
|             || ($clause =~ /EXECUTE:/) || ($clause =~ /MERGE:/)) {
 | |
|             if (($::SYNCSN == 1) && (($clause =~ /EXECUTEALWAYS:/) ||
 | |
|                   ($clause =~ /EXECUTE:/))) {  
 | |
|                # for EXECUTE and EXECUTEALWAYS skip, if syncing SN only
 | |
|          	next;
 | |
|             } else {   # process the clause
 | |
|                if ($clause =~ /EXECUTE:/) {
 | |
|                   push @::postscripts,$line;
 | |
|                }
 | |
|                if ($clause =~ /EXECUTEALWAYS:/) {
 | |
|                   push @::alwayspostscripts,$line;
 | |
|                }
 | |
|                if ($clause =~ /APPEND:/) {
 | |
|                  # location of the base append script
 | |
|                  # for APPEND we have to sync the appendscript and the
 | |
|                  # append file to the SN
 | |
|                   my $onServiceNode=0;
 | |
|                   my $syncappendscript=0;
 | |
|                  &build_append_rsync($line,$nodes, $options, $input_file,$rsyncSN, $syncdir,$nodesyncfiledir,$onServiceNode,$syncappendscript);
 | |
|                 if ($::SYNCSN == 0) {
 | |
|                   # this triggers the running of the appendscript
 | |
|                   $::appendscript ="/opt/xcat/share/xcat/scripts/xdcpappend.sh";
 | |
|                 }
 | |
|                 # add the append script to the sync
 | |
|                 my  $appscript ="/opt/xcat/share/xcat/scripts/xdcpappend.sh";
 | |
|                 my $appendscriptline = "$appscript -> $appscript"; 
 | |
|                 $syncappendscript=1;  # syncing the xdcpappend.sh script
 | |
|                  &build_append_rsync($appendscriptline,$nodes, $options, $input_file,$rsyncSN, $syncdir,$nodesyncfiledir,$onServiceNode,$syncappendscript);
 | |
|                }
 | |
|                if ($clause =~ /MERGE:/) {
 | |
|                  # location of the base merge script
 | |
|                  # for MERGE we have to sync the mergescript and the
 | |
|                  # merge file to the SN
 | |
|                   my $onServiceNode=0;
 | |
|                   my $syncmergescript=0;
 | |
|                  &build_merge_rsync($line,$nodes, $options, $input_file,$rsyncSN, $syncdir,$nodesyncfiledir,$onServiceNode,$syncmergescript);
 | |
|                 if ($::SYNCSN == 0) {
 | |
|                   # this triggers the running of the mergescript
 | |
|                   $::mergescript ="/opt/xcat/share/xcat/scripts/xdcpmerge.sh";
 | |
|                 }
 | |
|                 # add the merge script to the sync
 | |
|                 my  $mergescript ="/opt/xcat/share/xcat/scripts/xdcpmerge.sh";
 | |
|                 my $mergescriptline = "$mergescript -> $mergescript"; 
 | |
|                 $syncmergescript=1;  # syncing the xdcpmerge.sh script
 | |
|                  &build_merge_rsync($mergescriptline,$nodes, $options, $input_file,$rsyncSN, $syncdir,$nodesyncfiledir,$onServiceNode,$syncmergescript);
 | |
|                }
 | |
|            
 | |
|            }
 | |
|          } else {  # not processing EXECUTE, EXECUTEALWAYS or APPEND
 | |
|           # otherwise it is just the synclist
 | |
|           if ($line =~ /(.+) -> (.+)/)
 | |
|           {
 | |
| 
 | |
|             $::process_line = 1;
 | |
|             my $src_file  = $1;
 | |
|             my $dest_file = $2;
 | |
|             $dest_file =~ s/[\s;]//g;
 | |
|             my @srcfiles = (split ' ', $src_file);
 | |
|             my $arraysize = scalar @srcfiles;    # of source files on the line
 | |
|             my $dest_dir;
 | |
|             $destfileisdir = 0;
 | |
|             if ($dest_file =~ /\/$/)
 | |
|             {    #  ends in /
 | |
|                 $destfileisdir = 1;
 | |
|             }
 | |
| 
 | |
|             # if more than one file on the line then
 | |
|             # or the destination file ends in /
 | |
|             #  /tmp/file1 -> /tmp/
 | |
|             # the destination  is a directory
 | |
|             # else assume a file
 | |
|             if (($arraysize > 1) || ($destfileisdir == 1))
 | |
|             {
 | |
|                 $dest_dir      = $dest_file;
 | |
|                 $destfileisdir = 1;
 | |
|             }
 | |
|             else    # get the directory name
 | |
|             {       # strip off the file
 | |
|                 $dest_dir = dirname($dest_file);
 | |
|             }
 | |
|             $dest_dir =~ s/\s*//g;    #remove blanks
 | |
| 
 | |
|             foreach my $target_node (@dest_host)
 | |
|             {
 | |
|                 $$options{'destDir_srcFile'}{$target_node} ||= {};
 | |
| 
 | |
|                 # for each file on the line
 | |
|                 foreach my $srcfile (@srcfiles)
 | |
|                 {
 | |
| 
 | |
|                     #  if syncing the Service Node, file goes to the same place
 | |
|                     #  where it was on the MN off the syncdir on the service
 | |
|                     # node
 | |
|                     if ($rsyncSN == 1)
 | |
|                     {    #  syncing the SN
 | |
|                         $dest_dir = $syncdir;    # the SN sync dir
 | |
|                         $dest_dir .= dirname($srcfile);
 | |
|                         $dest_dir =~ s/\s*//g;    #remove blanks
 | |
|                     }
 | |
|                     $$options{'destDir_srcFile'}{$target_node}{$dest_dir} ||=
 | |
|                       {};
 | |
| 
 | |
|                     # can be full file name for destination or just the
 | |
|                     # directory name
 | |
|                     my $src_basename = basename($srcfile);    # get file name
 | |
| 
 | |
|                     my $dest_basename;    # destination file name
 | |
|                                           # determine path to the file
 | |
|                     if ($destfileisdir == 1)    # if a directory
 | |
|                     {
 | |
|                         $dest_basename = $src_basename;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         $dest_basename = basename($dest_file);
 | |
|                     }
 | |
|                     if ($rsyncSN == 1)    # dest file will be the same as src
 | |
|                     {                     #  syncing the SN
 | |
|                         $dest_basename = $src_basename;
 | |
|                     }
 | |
|                     $$options{'destDir_srcFile'}{$target_node}{$dest_dir} ||=
 | |
|                       $dest_basename =~ s/[\s;]//g;
 | |
| 
 | |
|                     # if the filename will be the same at the destination
 | |
|                     if ($src_basename eq $dest_basename)
 | |
|                     {
 | |
|                         $$options{'destDir_srcFile'}{$target_node}{$dest_dir}
 | |
|                           {'same_dest_name'} ||= [];
 | |
|                         push @{$$options{'destDir_srcFile'}{$target_node}
 | |
|                               {$dest_dir}{'same_dest_name'}}, $srcfile;
 | |
|                     }
 | |
|                     else    # changing file names
 | |
|                     {
 | |
|                         $$options{'destDir_srcFile'}{$target_node}{$dest_dir}
 | |
|                           {'diff_dest_name'} ||= {};
 | |
|                         $$options{'destDir_srcFile'}{$target_node}{$dest_dir}
 | |
|                           {'diff_dest_name'}{$srcfile} = $dest_basename;
 | |
|                     }
 | |
| 
 | |
|                 } # end of each srcfile
 | |
|             }   # end of each node
 | |
|           } # if synclist line
 | |
|         } # end processing clauses EXECUTE, APPEND, etc 
 | |
|     } #end while processing file
 | |
|     close INPUTFILE;
 | |
|     if ($::process_line == 0)
 | |
|     {    # no valid lines in the file
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] = "Found no lines to process in $input_file.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return 1;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         $$options{'nodes'} = join ',', keys %{$$options{'destDir_srcFile'}};
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|       build_append_rsync 
 | |
| 
 | |
|         Handles the 
 | |
|            APPEND: clause in the synclist
 | |
|            /tmp/appendfile -> /tmp/receivefile
 | |
| 
 | |
|            Syncs the append file from the left side of the arrow
 | |
|            to the nodes into the site.nodesyncfiledir directory
 | |
|            After we find out which append file are actually changed, we 
 | |
|            will sync and run and the append script
 | |
|            /opt/xcat/share/xcat/script/xdcpappend.sh
 | |
|            See build_append_script.
 | |
| 
 | |
|         Returns:
 | |
|           Files do not exist, rsync errors. 
 | |
| 
 | |
|         Globals:
 | |
| 
 | |
| 
 | |
|         Error:
 | |
|           Files do not exist, rsync errors. 
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub build_append_rsync 
 | |
| {
 | |
|     use File::Basename;
 | |
|     my ($line,$nodes, $options,$input_file, $rsyncSN, $syncdir,$nodesyncfiledir,$onServiceNode,$syncappendscript) = @_;
 | |
|     my @dest_host    = @$nodes;
 | |
|     my $process_line = 0;
 | |
|     my $destfileisdir;
 | |
|     # add append directory to the base nodesyncfiledir
 | |
|     $nodesyncfiledir .= "/append";
 | |
|     
 | |
|     if ($line =~ /(.+) -> (.+)/)
 | |
|     {
 | |
| 
 | |
|             $::process_line = 1;
 | |
|             if ($syncappendscript == 0) { # don't add the xdcpappend.sh line 
 | |
|               push @::appendlines,$line;
 | |
|             }
 | |
|             my $src_file  = $1; # append file left of arror
 | |
|             # it will be sync'd to $nodesyncfiledir/$append_file
 | |
|             my $dest_file = $nodesyncfiledir;
 | |
|             $dest_file .= $src_file;  
 | |
|             $dest_file =~ s/[\s;]//g;
 | |
|             my     $dest_dir = dirname($dest_file);
 | |
|             $dest_dir =~ s/\s*//g;    #remove blanks
 | |
| 
 | |
|             foreach my $target_node (@dest_host)
 | |
|             {
 | |
|                 $$options{'destDir_srcFile'}{$target_node} ||= {};
 | |
| 
 | |
|                     #  if syncing the Service Node, file goes to the same place
 | |
|                     #  where it was on the MN off the syncdir on the service
 | |
|                     # node
 | |
|                     if ($rsyncSN == 1)
 | |
|                     {    #  syncing the SN
 | |
|                         $dest_dir = $syncdir;    # the SN sync dir
 | |
|                         $dest_dir .= dirname($src_file);
 | |
|                         $dest_dir =~ s/\s*//g;    #remove blanks
 | |
|                     }
 | |
|                     $$options{'destDir_srcFile'}{$target_node}{$dest_dir} ||=
 | |
|                       {};
 | |
| 
 | |
|                     my $src_basename = basename($src_file);    # get file name
 | |
|                     # if this is syncing from the Service Node then we have
 | |
|                     # to pick up files from /var/xcat/syncfiles...
 | |
|                     if ($onServiceNode == 1) {
 | |
|                       my $newsrcfile = $syncdir;    # add SN syndir on front
 | |
|                       $newsrcfile .= $src_file;
 | |
|                       $src_file=$newsrcfile;
 | |
|                     }
 | |
|                     # destination file name
 | |
|                     my  $dest_basename = basename($dest_file);
 | |
|                     if ($rsyncSN == 1)    # dest file will be the same as src
 | |
|                     {                     #  syncing the SN
 | |
|                         $dest_basename = $src_basename;
 | |
|                     }
 | |
|                     $$options{'destDir_srcFile'}{$target_node}{$dest_dir} ||=
 | |
|                       $dest_basename =~ s/[\s;]//g;
 | |
| 
 | |
|                     $$options{'destDir_srcFile'}{$target_node}{$dest_dir}
 | |
|                           {'same_dest_name'} ||= [];
 | |
|                     push @{$$options{'destDir_srcFile'}{$target_node}
 | |
|                               {$dest_dir}{'same_dest_name'}}, $src_file;
 | |
| 
 | |
|             }   # end of each node
 | |
|           } # if synclist line
 | |
|     if ($::process_line == 0)
 | |
|     {    # no valid lines in the file
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] = "Found no lines to process in $input_file APPEND Clause.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return 1;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         $$options{'nodes'} = join ',', keys %{$$options{'destDir_srcFile'}};
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|       build_merge_rsync 
 | |
| 
 | |
|         Handles the 
 | |
|            MERGE: clause in the synclist
 | |
|            /tmp/mypasswd -> /etc/passwd
 | |
|            /tmp/mygroup -> /etc/group
 | |
|            /tmp/myshadow -> /etc/shadow
 | |
| 
 | |
|           Merges the information from the files in mypasswd, mygroup,
 | |
|           myshadow into /etc/passwd, /etc/group , /etc/shadow on the nodes.
 | |
|           These are the only files supported from MERGE and only on Linux
 | |
|         Returns:
 | |
|           Files do not exist, rsync errors. 
 | |
| 
 | |
|         Globals:
 | |
| 
 | |
| 
 | |
|         Error:
 | |
|           Files do not exist, rsync errors. 
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub build_merge_rsync 
 | |
| {
 | |
|     use File::Basename;
 | |
|     my ($line,$nodes, $options,$input_file, $rsyncSN, $syncdir,$nodesyncfiledir,$onServiceNode,$syncmergescript) = @_;
 | |
|     my @dest_host    = @$nodes;
 | |
|     my $process_line = 0;
 | |
|     my $destfileisdir;
 | |
|     # add merge directory to the base nodesyncfiledir
 | |
|     if ($syncmergescript == 1) {  # syncing the xdcpmerge.sh
 | |
|       $nodesyncfiledir .= "/merge";
 | |
|     } else {   # all the other merge scripts
 | |
|        $nodesyncfiledir .= "/merge/mergefiles";
 | |
|     }
 | |
|     if ($line =~ /(.+) -> (.+)/)
 | |
|     {
 | |
| 
 | |
|             $::process_line = 1;
 | |
|             if ($syncmergescript == 0) { # don't add the xdcpmerge.sh line 
 | |
|               push @::mergelines,$line;
 | |
|             }
 | |
|             my $src_file  = $1; # merge file left of arror
 | |
|             # it will be sync'd to $nodesyncfiledir/$merge_file
 | |
|             my $dest_file = $nodesyncfiledir;
 | |
|             $dest_file .= $src_file;  
 | |
|             $dest_file =~ s/[\s;]//g;
 | |
|             my     $dest_dir = dirname($dest_file);
 | |
|             $dest_dir =~ s/\s*//g;    #remove blanks
 | |
| 
 | |
|             foreach my $target_node (@dest_host)
 | |
|             {
 | |
|                 $$options{'destDir_srcFile'}{$target_node} ||= {};
 | |
| 
 | |
|                     #  if syncing the Service Node, file goes to the same place
 | |
|                     #  where it was on the MN off the syncdir on the service
 | |
|                     # node
 | |
|                     if ($rsyncSN == 1)
 | |
|                     {    #  syncing the SN
 | |
|                         $dest_dir = $syncdir;    # the SN sync dir
 | |
|                         $dest_dir .= dirname($src_file);
 | |
|                         $dest_dir =~ s/\s*//g;    #remove blanks
 | |
|                     }
 | |
|                     $$options{'destDir_srcFile'}{$target_node}{$dest_dir} ||=
 | |
|                       {};
 | |
| 
 | |
|                     my $src_basename = basename($src_file);    # get file name
 | |
|                     # if this is syncing from the Service Node then we have
 | |
|                     # to pick up files from /var/xcat/syncfiles...
 | |
|                     if ($onServiceNode == 1) {
 | |
|                       my $newsrcfile = $syncdir;    # add SN syndir on front
 | |
|                       $newsrcfile .= $src_file;
 | |
|                       $src_file=$newsrcfile;
 | |
|                     }
 | |
|                     # destination file name
 | |
|                     my  $dest_basename = basename($dest_file);
 | |
|                     if ($rsyncSN == 1)    # dest file will be the same as src
 | |
|                     {                     #  syncing the SN
 | |
|                         $dest_basename = $src_basename;
 | |
|                     }
 | |
|                     $$options{'destDir_srcFile'}{$target_node}{$dest_dir} ||=
 | |
|                       $dest_basename =~ s/[\s;]//g;
 | |
| 
 | |
|                     $$options{'destDir_srcFile'}{$target_node}{$dest_dir}
 | |
|                           {'same_dest_name'} ||= [];
 | |
|                     push @{$$options{'destDir_srcFile'}{$target_node}
 | |
|                               {$dest_dir}{'same_dest_name'}}, $src_file;
 | |
| 
 | |
|             }   # end of each node
 | |
|           } # if synclist line
 | |
|     if ($::process_line == 0)
 | |
|     {    # no valid lines in the file
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] = "Found no lines to process in $input_file APPEND Clause.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return 1;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         $$options{'nodes'} = join ',', keys %{$$options{'destDir_srcFile'}};
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|        parse_rsync_input_file_on_SN
 | |
| 
 | |
|         This parses the -F rsync input file on the Service node.
 | |
| 
 | |
|         File format:
 | |
|           /.../file1 ->  /.../dir1/filex
 | |
|           /.../file1 ->  /.../dir1
 | |
|           /.../*     ->  /.../dir1
 | |
|           /.../file1 /..../filex  -> /...../dir1
 | |
| 
 | |
|         Arguments:
 | |
| 		  Input nodelist,options, pointer to the sync file and
 | |
| 		  the directory to syn the files from 
 | |
| 		  based on defaults or the site.SNsyncfiledir attribute,
 | |
| 
 | |
|         Returns:
 | |
|            Errors if invalid options or the executed dcp command
 | |
| 
 | |
|         Globals:
 | |
| 
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|        parse_rsync_input_file_on_SN
 | |
| 
 | |
|         This parses the -F rsync input file on the Service node.
 | |
| 
 | |
|         File format:
 | |
|           /.../file1 ->  /.../dir1/filex
 | |
|           /.../file1 ->  /.../dir1
 | |
|           /.../*     ->  /.../dir1
 | |
|           /.../file1 /..../filex  -> /...../dir1
 | |
| 
 | |
|         Arguments:
 | |
| 		  Input nodelist,options, pointer to the sync file and
 | |
| 		  the directory to syn the files from 
 | |
| 		  based on defaults or the site.SNsyncfiledir attribute,
 | |
| 
 | |
|         Returns:
 | |
|            Errors if invalid options or the executed dcp command
 | |
| 
 | |
|         Globals:
 | |
| 
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub parse_rsync_input_file_on_SN
 | |
| {
 | |
|     use File::Basename;
 | |
|     my ($nodes, $options, $input_file, $syncdir,$nodesyncfiledir) = @_;
 | |
|     my @dest_host    = @$nodes;
 | |
|     my $process_line = 0;
 | |
|     my $destfileisdir;
 | |
|     my $rsyncSN;
 | |
|     my $clause=0;
 | |
|     open(INPUTFILE, "< $input_file") || die "File $input_file does not exist\n";
 | |
|     while (my $line = <INPUTFILE>)
 | |
|     {
 | |
|         chomp $line;
 | |
|         if (($line =~ /^#/) || ( $line =~ /^\s*$/ ))
 | |
|                         # skip commments  and blanks
 | |
|         {
 | |
|             next;
 | |
|         }
 | |
|         # Determine if processing a clause or the synclist
 | |
|         if (($line =~ /EXECUTE:/) || ($line =~ /EXECUTEALWAYS:/)
 | |
|                  || ($line =~ /APPEND:/) || ($line =~ /MERGE:/)) {
 | |
|              $clause=$line;
 | |
|              next;   # get the content of the clause
 | |
|         }
 | |
|         # processing a clause
 | |
|         if (($clause =~ /APPEND:/) || ($clause =~ /MERGE:/)
 | |
|             || ($clause =~ /EXECUTEALWAYS:/)
 | |
|             || ($clause =~ /EXECUTE:/)) {
 | |
|             if (($::SYNCSN == 1) && (($clause =~ /EXECUTEALWAYS:/) ||
 | |
|                   ($clause =~ /EXECUTE:/))) {  # skip, if syncing SN only
 | |
|          	next;
 | |
|             } else {   # process the clause
 | |
|                if ($clause =~ /EXECUTE:/) {
 | |
|                   push @::postscripts,$line;
 | |
|                }
 | |
|                if ($clause =~ /EXECUTEALWAYS:/) {
 | |
|                   push @::alwayspostscripts,$line;
 | |
|                }
 | |
|                if ($clause =~ /APPEND:/) {
 | |
|                   $process_line = 1;
 | |
|                   my $onServiceNode=1;
 | |
|                   my $syncappendscript=0;
 | |
|                  &build_append_rsync($line,$nodes, $options, $input_file,$rsyncSN, $syncdir,$nodesyncfiledir,$onServiceNode,$syncappendscript);
 | |
|                 if ($::SYNCSN == 0) {
 | |
|                   # this triggers the running of the appendscript
 | |
|                   $::appendscript ="/opt/xcat/share/xcat/scripts/xdcpappend.sh";
 | |
|                 }
 | |
|                 # add the append script to the sync
 | |
|                 my  $appscript ="/opt/xcat/share/xcat/scripts/xdcpappend.sh";
 | |
|                 my $appendscriptline = "$appscript -> $appscript"; 
 | |
|                 $syncappendscript=1;  # syncing the xdcpappend.sh script
 | |
|                  &build_append_rsync($appendscriptline,$nodes, $options, $input_file,$rsyncSN, $syncdir,$nodesyncfiledir,$onServiceNode,$syncappendscript);
 | |
|                }
 | |
|                if ($clause =~ /MERGE:/) {
 | |
|                   $process_line = 1;
 | |
|                   my $onServiceNode=1;
 | |
|                   my $syncmergescript=0;
 | |
|                  &build_merge_rsync($line,$nodes, $options, $input_file,$rsyncSN, $syncdir,$nodesyncfiledir,$onServiceNode,$syncmergescript);
 | |
|                 if ($::SYNCSN == 0) {
 | |
|                   # this triggers the running of the mergescript
 | |
|                   $::mergescript ="/opt/xcat/share/xcat/scripts/xdcpmerge.sh";
 | |
|                 }
 | |
|                 # add the merge script to the sync
 | |
|                 my  $appscript ="/opt/xcat/share/xcat/scripts/xdcpmerge.sh";
 | |
|                 my $mergescriptline = "$appscript -> $appscript"; 
 | |
|                 $syncmergescript=1;  # syncing the xdcpmerge.sh script
 | |
|                  &build_merge_rsync($mergescriptline,$nodes, $options, $input_file,$rsyncSN, $syncdir,$nodesyncfiledir,$onServiceNode,$syncmergescript);
 | |
|                }
 | |
|            
 | |
|            }
 | |
|          } else {  # not processing EXECUTE, EXECUTEALWAYS or APPEND
 | |
|           # otherwise it is just the synclist
 | |
| 
 | |
|         if ($line =~ /(.+) -> (.+)/)
 | |
|         {
 | |
|             $process_line = 1;
 | |
|             my $src_file  = $1;
 | |
|             my $dest_file = $2;
 | |
|             $dest_file =~ s/[\s;]//g;    # remove blanks
 | |
| 
 | |
|             # see if destination is a directory
 | |
|             $destfileisdir = 0;
 | |
|             if ($dest_file =~ /\/$/)
 | |
|             {                            #  ends in /
 | |
|                 $destfileisdir = 1;
 | |
|             }
 | |
|             my @srcfiles = (split ' ', $src_file);
 | |
|             my $arraysize = scalar @srcfiles;    # of source files on the line
 | |
|             my $dest_dir;
 | |
| 
 | |
|             # if only more than one file on the line or ends in /
 | |
|             # then the destination  is a directory
 | |
|             # else a file,
 | |
|             if (($arraysize > 1) || ($destfileisdir == 1))
 | |
|             {
 | |
|                 $dest_dir      = $dest_file;
 | |
|                 $destfileisdir = 1;
 | |
|             }
 | |
|             else    # a file path
 | |
|             {
 | |
|                 $dest_dir = dirname($dest_file);
 | |
|             }
 | |
|             $dest_dir =~ s/\s*//g;    #remove blanks
 | |
| 
 | |
|             foreach my $target_node (@dest_host)
 | |
|             {
 | |
|                 $$options{'destDir_srcFile'}{$target_node} ||= {};
 | |
| 
 | |
|                 # for each file on the line
 | |
|                 foreach my $srcfile (@srcfiles)
 | |
|                 {
 | |
|                     my $newsrcfile = $syncdir;    # add syndir on front
 | |
|                     $newsrcfile .= $srcfile;
 | |
|                     $$options{'destDir_srcFile'}{$target_node}{$dest_dir} ||=
 | |
|                       {};
 | |
| 
 | |
|                     # can be full file name for destination or just the
 | |
|                     # directory name. For source must be full path
 | |
|                     my $src_basename = basename($newsrcfile);    # get file name
 | |
| 
 | |
|                     my $dest_basename;    # destination file name
 | |
|                     if ($destfileisdir == 1)    # is a directory
 | |
|                     {
 | |
|                         $dest_basename = $src_basename;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         $dest_basename = basename($dest_file);
 | |
|                     }
 | |
|                     $$options{'destDir_srcFile'}{$target_node}{$dest_dir} ||=
 | |
|                       $dest_basename =~ s/[\s;]//g;
 | |
| 
 | |
|                     # if the filename will be the same at the destination
 | |
|                     if ($src_basename eq $dest_basename)
 | |
|                     {
 | |
|                         $$options{'destDir_srcFile'}{$target_node}{$dest_dir}
 | |
|                           {'same_dest_name'} ||= [];
 | |
|                         push @{$$options{'destDir_srcFile'}{$target_node}
 | |
|                               {$dest_dir}{'same_dest_name'}}, $newsrcfile;
 | |
|                     }
 | |
|                     else    # changing file names
 | |
|                     {
 | |
|                         $$options{'destDir_srcFile'}{$target_node}{$dest_dir}
 | |
|                           {'diff_dest_name'} ||= {};
 | |
|                         $$options{'destDir_srcFile'}{$target_node}{$dest_dir}
 | |
|                           {'diff_dest_name'}{$newsrcfile} = $dest_basename;
 | |
|                     }
 | |
| 
 | |
|                 } # end of srcfile
 | |
|             } # end of each node
 | |
|         } # end of synclist
 | |
|       }# end processing clauses EXECUTE, APPEND, etc
 | |
|     } #end of processing file
 | |
|     close INPUTFILE;
 | |
|     if ($process_line == 0)
 | |
|     {    # no valid lines in the file
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] = "Found no lines to process in $input_file.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return 1;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         $$options{'nodes'} = join ',', keys %{$$options{'destDir_srcFile'}};
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|        run_rsync_postscripts 
 | |
| 
 | |
|         This executes the postscript file on the nodes where
 | |
| 	    the corresponding rsync file was updated
 | |
|         These are the scripts after EXECUTE: in the syncfile
 | |
|         rsync returns a list of files that have been updated
 | |
|         in the form   hostname: <full file path>
 | |
|         For example:  node1: tmp/test/file1  ( yes it leaves the first / off)
 | |
|         This routine must match that list to the input list of postscripts.
 | |
|         If there is a match, for example
 | |
|         The postscript file is /tmp/test/file1.post  and tmp/test/file1 was 
 | |
|         updated, then it builds a xdsh command to the node to run  
 | |
|         /tmp/test/file1.post.
 | |
|         On the service node, the file will be in $syncdir/tmp/test/file1.post.
 | |
|         Also the routine must preserve all other messages returned from rsync, 
 | |
|         to return to the admin.  It will remove the messages that the files 
 | |
|         were updated, that is all of from hostname: <full file path>.	
 | |
|        Input: the output from the xdcp rsync run
 | |
|             : @::postscripts  to run
 | |
| 
 | |
|         Comments:
 | |
|           Needs to remove the lines from rsync that are return to let
 | |
|           me know files were updated from the output to determine which 
 | |
|           postscripts to run and leave any other messages 
 | |
|           to return to the admin.
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub run_rsync_postscripts 
 | |
| {
 | |
|     my ($rsyncoutput,$syncdir) = @_;
 | |
|     my @rsync_output   = @$rsyncoutput;
 | |
|     my @newoutput= (); 
 | |
|     my $dshparms; 
 | |
|     my $firstpass=1;
 | |
|     foreach my $postsfile (@::postscripts) {
 | |
|        my $tmppostfile = $postsfile ;
 | |
|  
 | |
|        # if service node need to add the SNsyncfiledir to the path
 | |
|        if (xCAT::Utils->isServiceNode()) {
 | |
|          my $tmpp=$syncdir . $tmppostfile;
 | |
|          $tmppostfile = $tmpp;
 | |
|        }
 | |
|        # remove  first character for the compare, we have to do this because the
 | |
|        # return from rsync is tmp/file1  not /tmp/file1
 | |
|        substr($tmppostfile,0,1)=""; 
 | |
| 
 | |
|        # now remove .post from the postscript file for the compare
 | |
|        # with the returned file name
 | |
|        my($tp,$post) = split(/\.post/,$tmppostfile);
 | |
|        $tmppostfile = $tp;
 | |
|        foreach my $line (@rsync_output) {
 | |
|          my($hostname,$ps) = split(/: /, $line);
 | |
|          chomp $ps;
 | |
|          chomp $hostname;
 | |
|          if ($ps eq "rsync") {  # this is a line that is not an update 
 | |
|              # save output , if firstpass through output
 | |
|              if ($firstpass == 1) {
 | |
|                push @newoutput, $line;
 | |
|                $firstpass = 0;
 | |
|              }
 | |
|              next;
 | |
|          }
 | |
|          if ($tmppostfile eq $ps) { 
 | |
|            # build xdsh queue 
 | |
|            # build host and all scripts to execute
 | |
|            push (@{$dshparms->{'postscripts'} {$postsfile}}, $hostname);
 | |
|          }
 | |
|        }
 | |
|     }
 | |
|     # now if we have postscripts to run, run xdsh
 | |
|     my $out;
 | |
| 
 | |
|     # if we were called with runxcmd, like by updatenode
 | |
|     # need to save the runxcmd buffer 
 | |
|     # $::xcmd_outref
 | |
|     my $save_xcmd_outref;
 | |
|     if ($::xcmd_outref) { # this means we were called with runxcmd
 | |
|      $save_xcmd_outref = $::xcmd_outref;
 | |
|     }
 | |
|     # my $ranaps=0;   # did we run a postscript
 | |
| 
 | |
|     foreach  my $ps ( keys %{$$dshparms{'postscripts'}}) {
 | |
|         my @nodes;
 | |
|         push (@nodes, @{$$dshparms{'postscripts'}{$ps}}); 
 | |
|         # if on the service node need to add the $syncdir directory 
 | |
|         # to the path
 | |
|         if (xCAT::Utils->isServiceNode()) {
 | |
|          my $tmpp=$syncdir . $ps;
 | |
|          $ps=$tmpp;
 | |
|         }
 | |
|          $out=xCAT::Utils->runxcmd( { command => ['xdsh'],
 | |
|                                     node    => \@nodes,
 | |
|                                     arg     => [ "-e", $ps ]
 | |
|                              }, $::SUBREQ, 0,1);
 | |
|         foreach my $r (@$out){
 | |
|                 push(@newoutput, $r);
 | |
| 
 | |
|         }
 | |
|         #     $ranaps=1;
 | |
|     }
 | |
|     # restore the runxcmd buffer
 | |
|     if ($save_xcmd_outref) { # this means we were called with runxcmd
 | |
|      $::xcmd_outref = $save_xcmd_outref;
 | |
|     }
 | |
|     # TODO, will we ever need to merge
 | |
|     # if we ran a postscript and we were run
 | |
|     # using runxcmd, and there was previous output in the 
 | |
|     # runxcmd buffer and we have output from the postscript
 | |
|     # then we have to merge the outputs
 | |
|     #if (($ranaps == 1) && ($save_xcmd_outref) && ($::xcmd_outref) ) {
 | |
|     #  &mergeoutput($save_xcmd_outref);
 | |
|     #}
 | |
|     return @newoutput;
 | |
| }
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|        &bld_and_run_append 
 | |
| 
 | |
|         This builds the parm list and executes (xdsh) the append script file
 | |
|         on the nodes where
 | |
|         the corresponding append file was updated.
 | |
|         The append postscript has been previous sync'd to the nodes. 
 | |
|         These are the scripts after APPEND: in the syncfile
 | |
| 
 | |
|         rsync returns a list of files that have been updated
 | |
|         in the form   hostname: <full file path>
 | |
|         For example:  node1: tmp/test/file1  ( yes it leaves the first / off)
 | |
|         This routine must match that list to the input list of append files.
 | |
|         If there is a match, it will add the append function to 
 | |
|         the append script 
 | |
|        Input: the output from the xdcp rsync run
 | |
|             : @::appendlines  to run
 | |
| 
 | |
|         Comments:
 | |
|           Needs to remove the lines from rsync that are return to let
 | |
|           me know files were updated from the output to determine which 
 | |
|           postscripts to run and leave any other messages 
 | |
|           to return to the admin.
 | |
| 
 | |
|         Runs xdsh with input to call /opt/xcat/share/xcat/scripts/xdcpappend.sh
 | |
|         which will perform the append function on the node.  
 | |
|         Input is the nodesyncfiledir appendfile:orgfile appendfile2:orgfile2....
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub bld_and_run_append
 | |
| {
 | |
|     my ($hostnames,$rsyncoutput,$syncdir,$nodesyncfiledir) = @_;
 | |
|     my @hosts   = @$hostnames;
 | |
|     my @rsync_output   = @$rsyncoutput;
 | |
|     my @newoutput= (); 
 | |
|     my $dshparms; 
 | |
|     my $firstpass=1;
 | |
|     my $headeradded=0;
 | |
|     my $processappend=0;
 | |
|     
 | |
|     $::xdcpappendparms = "$nodesyncfiledir ";
 | |
| 
 | |
|     # directory to save the original file to append
 | |
|     my $nodesaveorgfiledir=$nodesyncfiledir;
 | |
|     $nodesaveorgfiledir .="/org";
 | |
|     # add append directory to the base nodesyncfiledir
 | |
|     $nodesyncfiledir .= "/append";
 | |
|     # build the input appendfile:orgfile parsm
 | |
|     foreach my $appendline (@::appendlines) {
 | |
|       if ($appendline =~ /(.+) -> (.+)/)
 | |
|       {
 | |
|          my $appendfile  = $1; # append file left of arrow 
 | |
|          my $filetoappend = $2; # file to append right of arrow
 | |
|          my $tmpappendfile = $appendfile;
 | |
|          # if service node need to add the syncdir to the path
 | |
|          # for the match
 | |
|          if (xCAT::Utils->isServiceNode()) {
 | |
|              my $tmpp=$syncdir . $tmpappendfile;
 | |
|              $tmpappendfile = $tmpp;
 | |
|          }
 | |
|          # remove first char for the compare, we have to do this because the
 | |
|          # return from rsync is tmp/file1  not /tmp/file1
 | |
|          substr($tmpappendfile,0,1)="";
 | |
|          # check to see if this file was rsync'd and to which hosts
 | |
|          foreach my $line (@rsync_output) {
 | |
|            my($hostname,$ps) = split(/: /, $line);
 | |
|            chomp $ps;
 | |
|            chomp $hostname;
 | |
|            if ($ps eq "rsync") {  # this is a line that is not an update
 | |
|                 # save output , if firstpass through output
 | |
|                 if ($firstpass == 1) {
 | |
|                    push @newoutput, $line;
 | |
|                   $firstpass = 0;
 | |
|                 }
 | |
|                 next;
 | |
|             }
 | |
|             # build the append script (xdcpappend.sh) parameter list,
 | |
|             # based on all the append files
 | |
|             # that were rsyn'd to at least one node
 | |
|             if ($tmpappendfile eq $ps) { 
 | |
|                my $parm="$appendfile:$filetoappend ";
 | |
| 
 | |
|                $::xdcpappendparms .= $parm;
 | |
|                $processappend=1;
 | |
| 
 | |
|             }
 | |
|          }
 | |
|       }
 | |
|       
 | |
|     }  # end for each append line
 | |
|     #  add append script to each host to execute.  
 | |
|     if ($::appendscript && ($processappend==1)) { 
 | |
|        #  the append script has been sync'd to the site.nodesynfiledir
 | |
|        my $nodeappendscript = $nodesyncfiledir;
 | |
|        $nodeappendscript .= $::appendscript;
 | |
|        foreach my $host (@hosts) {
 | |
|         push (@{$dshparms->{'appendscripts'} {$nodeappendscript}}, $host);
 | |
|        }
 | |
|        # now run xdsh
 | |
|        my $out;
 | |
|        foreach  my $ps ( keys %{$$dshparms{'appendscripts'}}) {
 | |
|          my @nodes;
 | |
|          push (@nodes, @{$$dshparms{'appendscripts'}{$ps}}); 
 | |
|          
 | |
|          $out=xCAT::Utils->runxcmd( { command => ['xdsh'],
 | |
|                                     node    => \@nodes,
 | |
|                                     arg     => [ $ps , $::xdcpappendparms ]
 | |
|                              }, $::SUBREQ, 0,1);
 | |
|          foreach my $r (@$out){
 | |
|                 push(@newoutput, $r);
 | |
| 
 | |
|          }
 | |
|        }
 | |
| 
 | |
|     }
 | |
|     return @newoutput;
 | |
| }
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|        &bld_and_run_merge 
 | |
| 
 | |
|         This builds the parm list and executes (xdsh) the merge script file
 | |
|         on the nodes where
 | |
|         the corresponding merge file was updated.
 | |
|         The merge script has been previous sync'd to the nodes. 
 | |
|         These are the scripts after MERGE: in the syncfile
 | |
| 
 | |
|         rsync returns a list of files that have been updated
 | |
|         in the form   hostname: <full file path>
 | |
|         For example:  node1: tmp/test/file1  ( yes it leaves the first / off)
 | |
|         This routine must match that list to the input list of merge files.
 | |
|         If there is a match, it will add the merge function to 
 | |
|         the merge script 
 | |
|        Input: the output from the xdcp rsync run
 | |
|             : @::mergelines  to run
 | |
| 
 | |
|         Comments:
 | |
|           Needs to remove the lines from rsync that are return to let
 | |
|           me know files were updated from the output to determine which 
 | |
|           postscripts to run and leave any other messages 
 | |
|           to return to the admin.
 | |
| 
 | |
|         Runs xdsh with input to call /opt/xcat/share/xcat/scripts/xdcpmerge.sh
 | |
|         which will perform the merge function on the node.  
 | |
|         Input is the nodesyncfiledir mergefile1:orgfile mergefile2:orgfile2....]        Note: MERGE is only support on Linux and for /etc/passwd,/etc/shadow,
 | |
|               and /etc/group
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub bld_and_run_merge
 | |
| {
 | |
|     my ($hostnames,$rsyncoutput,$syncdir,$nodesyncfiledir) = @_;
 | |
|     my @hosts   = @$hostnames;
 | |
|     my @rsync_output   = @$rsyncoutput;
 | |
|     my @newoutput= (); 
 | |
|     my $dshparms; 
 | |
|     my $firstpass=1;
 | |
|     my $headeradded=0;
 | |
|     my $processmerge=0;
 | |
|     
 | |
|     $::xdcpmergeparms = "$nodesyncfiledir ";
 | |
| 
 | |
|     # directory to save the original file to merge 
 | |
|     my $nodesaveorgfiledir=$nodesyncfiledir;
 | |
|     $nodesaveorgfiledir .="/org";
 | |
|     # add merge directory to the base nodesyncfiledir
 | |
|     $nodesyncfiledir .= "/merge";
 | |
|     # build the input mergefile:orgfile parsm
 | |
|     foreach my $mergeline (@::mergelines) {
 | |
|       if ($mergeline =~ /(.+) -> (.+)/)
 | |
|       {
 | |
|          my $mergefile  = $1; # merge file left of arrow 
 | |
|          my $filetomerge = $2; # file to merge right of arrow
 | |
|          if (($filetomerge ne "/etc/passwd")
 | |
|             && ($filetomerge ne "/etc/group")
 | |
|             && ($filetomerge ne "/etc/shadow")) {
 | |
|                 my $rsp = {};
 | |
|                 $rsp->{error}->[0] = "$filetomerge is not either /etc/passwd, /etc/group or /etc/shadow. Those are the only supported files for MERGE";
 | |
|                  xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|                  return 1;
 | |
|             
 | |
|          }
 | |
|          my $tmpmergefile = $mergefile;
 | |
|          # if service node need to add the syncdir to the path
 | |
|          # for the match
 | |
|          if (xCAT::Utils->isServiceNode()) {
 | |
|              my $tmpp=$syncdir . $tmpmergefile;
 | |
|              $tmpmergefile = $tmpp;
 | |
|          }
 | |
|          # remove first char for the compare, we have to do this because the
 | |
|          # return from rsync is tmp/file1  not /tmp/file1
 | |
|          substr($tmpmergefile,0,1)="";
 | |
|          # check to see if this file was rsync'd and to which hosts
 | |
|          foreach my $line (@rsync_output) {
 | |
|            my($hostname,$ps) = split(/: /, $line);
 | |
|            chomp $ps;
 | |
|            chomp $hostname;
 | |
|            if ($ps eq "rsync") {  # this is a line that is not an update
 | |
|                 # save output , if firstpass through output
 | |
|                 if ($firstpass == 1) {
 | |
|                    push @newoutput, $line;
 | |
|                   $firstpass = 0;
 | |
|                 }
 | |
|                 next;
 | |
|             }
 | |
|             # build the merge script (xdcpmerge.sh) parameter list,
 | |
|             # based on all the merge files
 | |
|             # that were rsyn'd to at least one node
 | |
|             if ($tmpmergefile eq $ps) { 
 | |
|                my $parm="$mergefile:$filetomerge ";
 | |
| 
 | |
|                $::xdcpmergeparms .= $parm;
 | |
|                $processmerge=1;
 | |
| 
 | |
|             }
 | |
|          }
 | |
|       }
 | |
|       
 | |
|     }  # end for each merge line
 | |
|     #  add merge script to each host to execute. If we need to run anything 
 | |
|     if ($::mergescript && ($processmerge==1)) { 
 | |
|        #  the merge script has been sync'd to the site.nodesynfiledir
 | |
|        my $nodemergescript = $nodesyncfiledir;
 | |
|        $nodemergescript .= $::mergescript;
 | |
|        foreach my $host (@hosts) {
 | |
|         push (@{$dshparms->{'mergescripts'} {$nodemergescript}}, $host);
 | |
|        }
 | |
|        # now run xdsh
 | |
|        my $out;
 | |
|        foreach  my $ps ( keys %{$$dshparms{'mergescripts'}}) {
 | |
|          my @nodes;
 | |
|          push (@nodes, @{$$dshparms{'mergescripts'}{$ps}}); 
 | |
|          
 | |
|          $out=xCAT::Utils->runxcmd( { command => ['xdsh'],
 | |
|                                     node    => \@nodes,
 | |
|                                     arg     => [ $ps , $::xdcpmergeparms ]
 | |
|                              }, $::SUBREQ, 0,1);
 | |
|          foreach my $r (@$out){
 | |
|                 push(@newoutput, $r);
 | |
| 
 | |
|          }
 | |
|        }
 | |
| 
 | |
|     }
 | |
|     return @newoutput;
 | |
| }
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|      run_always_rsync_postscript 
 | |
| 
 | |
|   This subroutine runs the xdsh command for all the scripts listed in the
 | |
|   EXECUTEALWAYS: clause of the synclist file. 
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| 
 | |
| sub run_always_rsync_postscripts 
 | |
| {
 | |
|     my ($hostnames,$syncdir) = @_;
 | |
|     my @hosts   = @$hostnames;
 | |
|     my @newoutput= (); 
 | |
|     my $dshparms; 
 | |
|     foreach my $postsfile (@::alwayspostscripts) {
 | |
|        my $tmppostfile = $postsfile ;
 | |
|  
 | |
|        # if service node need to add the SNsyncfiledir to the path
 | |
|        if (xCAT::Utils->isServiceNode()) {
 | |
|          my $tmpp=$syncdir . $tmppostfile;
 | |
|          $tmppostfile = $tmpp;
 | |
|        }
 | |
| 
 | |
|        foreach my $host (@hosts) {
 | |
|          # build xdsh queue 
 | |
|          # build host and all scripts to execute
 | |
|          push (@{$dshparms->{'postscripts'} {$postsfile}}, $host);
 | |
|        }
 | |
|     }
 | |
|     # now if we have postscripts to run, run xdsh
 | |
|     my $out;
 | |
| 
 | |
|     # if we were called with runxcmd, like by updatenode
 | |
|     # need to save the runxcmd buffer 
 | |
|     # $::xcmd_outref
 | |
|     my $save_xcmd_outref;
 | |
|     if ($::xcmd_outref) { # this means we were called with runxcmd
 | |
|      $save_xcmd_outref = $::xcmd_outref;
 | |
|     }
 | |
|     foreach  my $ps ( keys %{$$dshparms{'postscripts'}}) {
 | |
|         my @nodes;
 | |
|         push (@nodes, @{$$dshparms{'postscripts'}{$ps}}); 
 | |
|         # if on the service node need to add the $syncdir directory 
 | |
|         # to the path
 | |
|         if (xCAT::Utils->isServiceNode()) {
 | |
|          my $tmpp=$syncdir . $ps;
 | |
|          $ps=$tmpp;
 | |
|         }
 | |
|          
 | |
|          $out=xCAT::Utils->runxcmd( { command => ['xdsh'],
 | |
|                                     node    => \@nodes,
 | |
|                                     arg     => [ "-e", $ps ]
 | |
|                              }, $::SUBREQ, 0,1);
 | |
|         foreach my $r (@$out){
 | |
|                 push(@newoutput, $r);
 | |
| 
 | |
|         }
 | |
| 
 | |
| 
 | |
|     }
 | |
|     # restore the runxcmd buffer
 | |
|     if ($save_xcmd_outref) { # this means we were called with runxcmd
 | |
|      $::xcmd_outref = $save_xcmd_outref;
 | |
|     }
 | |
|     return @newoutput;
 | |
| }
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|       runlocal_on_rootimg
 | |
| 
 | |
|   This subroutine runs the xdsh command against the input image on the local
 | |
|   node.
 | |
|   Arguments:
 | |
|       $optionRef:
 | |
|          Specifies a hash in which the xdsh options are provided
 | |
|       $exitCode:
 | |
| 		  reference to an array for efficiency.
 | |
|   Example:
 | |
|       my @outref = xCAT::DSHCLI->runlocal_rootimg(\%options);
 | |
| 
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub runlocal_on_rootimg
 | |
| {
 | |
|     my ($class, $options, $imagename) = @_;
 | |
|     my $cmd;
 | |
|     if (xCAT::Utils->isAIX()) {   # use xcatchroot
 | |
|      $cmd = "$::XCATROOT/bin/xcatchroot -i $$options{'rootimg'} \"$$options{'command'}\"";
 | |
|     } else {
 | |
|      $cmd = "chroot $$options{'rootimg'} $$options{'command'}";
 | |
|     }
 | |
|     my @output = xCAT::Utils->runcmd($cmd, 0);
 | |
|     if ($::RUNCMD_RC != 0)
 | |
|     {
 | |
|         my $rsp = {};
 | |
|         $rsp->{error}->[0] = "Command: $cmd failed, unable to process image.";
 | |
|         xCAT::MsgUtils->message("E", $rsp, $::CALLBACK, 1);
 | |
|         return;
 | |
| 
 | |
|     }
 | |
|     my @newoutput;
 | |
|     foreach my $line (@output)
 | |
|     {
 | |
|         my $newline .= $imagename;
 | |
|         $newline    .= ": ";
 | |
|         $newline    .= $line;
 | |
|         $newline    .= "\n";
 | |
|         push @newoutput, $newline;
 | |
|     }
 | |
| 
 | |
|     $::DSH_API_MESSAGE = ();
 | |
|     $::DSH_API_MESSAGE = $::DSH_API_MESSAGE . join("", @newoutput);
 | |
|     return $::DSH_API_MESSAGE;
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|       runDsh_api
 | |
| 
 | |
|   This subroutine provides a concise interface to run remote command on multiple nodes.
 | |
|   Arguments:
 | |
|       $optionRef:
 | |
|          Specifies a hash in which the dsh options are provided
 | |
|       $exitCode:
 | |
|         Normally, if there is an error running the cmd,
 | |
| 		it will display the error msg
 | |
|         and exit with the cmds exit code, unless exitcode is given one of the
 | |
|         following values:
 | |
|              0:     display error msg, DO NOT exit on error, but set
 | |
|                 $::RUNCMD_RC to the exit code.
 | |
|             -1:     DO NOT display error msg and DO NOT exit on error, but set
 | |
| 	                   $::RUNCMD_RC to the exit code.
 | |
|             -2:    DO the default behavior (display error msg and exit with cmds
 | |
|                 exit code.
 | |
|         number > 0:    Display error msg and exit with the given code
 | |
|         $refoutput:
 | |
|           if refoutput is true, then the output will be returned as a
 | |
| 		  reference to an array for efficiency.
 | |
|   Example:
 | |
|       my @outref = xCAT::DSHCLI->runDsh_api(\%options, -2);
 | |
| 
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub runDsh_api
 | |
| {
 | |
|     shift;
 | |
|     my ($optionsRef, $exitCode, $refoutput) = @_;
 | |
| 
 | |
|     $::DSH_API         = 1;
 | |
|     $::DSH_API_MESSAGE = "";
 | |
|     my $verbose_old = $::VERBOSE;
 | |
|     $::VERBOSE = 0;
 | |
| 
 | |
|     #
 | |
|     # execute dsh
 | |
|     #
 | |
|     $::RUNCMD_RC = 0;
 | |
|     $::RUNCMD_RC = xCAT::DSHCLI->execute_dsh($optionsRef);
 | |
| 
 | |
|     $::DSH_API = 0;
 | |
|     $::VERBOSE = $verbose_old;
 | |
|     my $returnCode;    #command will exit with this code
 | |
|     if ($::RUNCMD_RC)
 | |
|     {
 | |
|         my $dsh_api_displayerr = 1;
 | |
|         if (defined($exitCode) && length($exitCode) && $exitCode != -2)
 | |
|         {
 | |
|             if ($exitCode > 0)
 | |
|             {
 | |
|                 $returnCode = $exitCode;
 | |
|             }
 | |
|             elsif ($exitCode <= 0)
 | |
|             {
 | |
|                 $returnCode = '';
 | |
|                 if ($exitCode < 0)
 | |
|                 {
 | |
|                     $dsh_api_displayerr = 0;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             $returnCode = $::RUNCMD_RC;
 | |
|         }
 | |
|         if ($dsh_api_displayerr)
 | |
|         {
 | |
|             my $errmsg = '';
 | |
|             if (xCAT::Utils->isLinux() && $::RUNCMD_RC == 139)
 | |
|             {
 | |
|                 $errmsg = "Return Code = 139  $errmsg";
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 $errmsg = $::DSH_API_MESSAGE;
 | |
|             }
 | |
|             if ((!$DSHCLI::NO_MESSAGES) && ($::DSH_API_NODES_FAILED))
 | |
|             {
 | |
|                 xCAT::MsgUtils->message(
 | |
|                     "E",
 | |
|                     "xdsh command: $$optionsRef{'command'} failed on  nodes:$::DSH_API_NODES_FAILED."
 | |
|                     );
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     if ($refoutput)
 | |
|     {
 | |
|         my $outputRef = [];
 | |
|         @$outputRef = split "\n", $::DSH_API_MESSAGE;
 | |
|         chomp @$outputRef;
 | |
|         return $outputRef;
 | |
|     }
 | |
|     elsif (wantarray)
 | |
|     {
 | |
|         my @outputLines = split "\n", $::DSH_API_MESSAGE;
 | |
|         chomp @outputLines;
 | |
|         return @outputLines;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         return $::DSH_API_MESSAGE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|   runDcp_api
 | |
| 
 | |
|     This subroutine provides a concise interface to run remote command
 | |
| 	on multiple nodes.
 | |
|     Arguments:
 | |
|            $optionRef:
 | |
|                Specifies a hash in which the dsh options are provided
 | |
|            $exitCode:
 | |
|               Normally, if there is an error running the cmd,
 | |
| 			  it will display the error msg
 | |
|               and exit with the cmds exit code,
 | |
| 			  unless exitcode is given one of the
 | |
|               following values:
 | |
|                        0:     display error msg, DO NOT exit on error, but set
 | |
|                               $::RUNCMD_RC to the exit code.
 | |
|                       -1:     DO NOT display error msg
 | |
| 							  and DO NOT exit on error, but set
 | |
|                               $::RUNCMD_RC to the exit code.
 | |
|                       -2:    DO the default behavior
 | |
| 							 (display error msg and exit with cmds
 | |
|                              exit code.
 | |
|               number > 0:    Display error msg and exit with the
 | |
| 							 given code
 | |
|               $refoutput:
 | |
|                              if refoutput is true, then the output
 | |
| 							 will be returned as a reference to
 | |
|                              an array for efficiency.
 | |
|      Example:
 | |
|             my @outref = xCAT::DSHCLI->runDcp_api(\%options, -2);
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub runDcp_api
 | |
| {
 | |
|     shift;
 | |
|     my ($optionsRef, $exitCode, $refoutput) = @_;
 | |
| 
 | |
|     $::DCP_API         = 1;
 | |
|     $::DCP_API_MESSAGE = "";
 | |
|     my $verbose_old = $::VERBOSE;
 | |
|     $::VERBOSE = 0;
 | |
|     if (!ref($optionsRef->{'source'}))
 | |
|     {
 | |
|         $optionsRef->{'source'} =~ s/\s/$::__DCP_DELIM/g;
 | |
|     }
 | |
|     elsif (ref($optionsRef->{'source'} eq "ARRAY"))
 | |
|     {
 | |
|         $optionsRef->{'source'} = join $::__DCP_DELIM,
 | |
|           @{$optionsRef->{'source'}};
 | |
|     }
 | |
| 
 | |
|     $::RUNCMD_RC = xCAT::DSHCLI->execute_dcp($optionsRef);
 | |
|     $::DCP_API   = 0;
 | |
|     $::VERBOSE   = $verbose_old;
 | |
|     my $returnCode;    #command will exit with this code
 | |
|     if ($::RUNCMD_RC)
 | |
|     {
 | |
|         my $dcp_api_displayerr = 1;
 | |
|         if (defined($exitCode) && length($exitCode) && $exitCode != -2)
 | |
|         {
 | |
|             if ($exitCode > 0)
 | |
|             {
 | |
|                 $returnCode = $exitCode;
 | |
|             }
 | |
|             elsif ($exitCode <= 0)
 | |
|             {
 | |
|                 $returnCode = '';
 | |
|                 if ($exitCode < 0)
 | |
|                 {
 | |
|                     $dcp_api_displayerr = 0;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             $returnCode = $::RUNCMD_RC;
 | |
|         }
 | |
|         if ($dcp_api_displayerr)
 | |
|         {
 | |
|             my $errmsg = '';
 | |
|             if (xCAT::Utils->isLinux() && $::RUNCMD_RC == 139)
 | |
|             {
 | |
|                 $errmsg = "Return code=139  $errmsg";
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 $errmsg = $::DCP_API_MESSAGE;
 | |
|             }
 | |
|             if (!$DSHCLI::NO_MESSAGES)
 | |
|             {
 | |
|                 xCAT::MsgUtils->message("E",
 | |
|                                "dcp command failed, Return code=$::RUNCMD_RC.");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     }
 | |
|     if ($refoutput)
 | |
|     {
 | |
|         my $outputRef = [];
 | |
|         @$outputRef = split "\n", $::DCP_API_MESSAGE;
 | |
|         chomp @$outputRef;
 | |
|         return $outputRef;
 | |
|     }
 | |
|     elsif (wantarray)
 | |
|     {
 | |
|         my @outputLines = split "\n", $::DCP_API_MESSAGE;
 | |
|         chomp @outputLines;
 | |
|         return @outputLines;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         return $::DCP_API_MESSAGE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3
 | |
|         show_dsh_config
 | |
| 
 | |
|         Displays the current configuration of the dsh command environment
 | |
|         and configuration information for each installed context
 | |
| 
 | |
|         Arguments:
 | |
|         	$options - options hash table describing dsh configuration options
 | |
| 
 | |
|         Returns:
 | |
|         	None
 | |
| 
 | |
|         Globals:
 | |
|         	None
 | |
| 
 | |
|         Error:
 | |
|         	None
 | |
| 
 | |
|         Example:
 | |
| 
 | |
|         Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| sub show_dsh_config
 | |
| {
 | |
|     my ($class, $options) = @_;
 | |
|     xCAT::DSHCLI->config_default_context($options);
 | |
| 
 | |
|     my $dsh_config = xCAT::DSHCLI->get_dsh_config;
 | |
| 
 | |
|     foreach my $context (sort keys(%$dsh_config))
 | |
|     {
 | |
|         my $context_properties = $$dsh_config{$context};
 | |
|         foreach my $key (sort keys(%$context_properties))
 | |
|         {
 | |
|             print STDOUT "$context:$key=$$context_properties{$key}\n";
 | |
| 
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3 
 | |
|      get_config
 | |
| 
 | |
|     Substitute specific keywords in hash
 | |
|         e.g. config file:
 | |
|         [main]
 | |
|         cachedir=/var/cache/yum
 | |
|         keepcache=1
 | |
|         [base]
 | |
|         name=Red Hat Linux $releasever - $basearch - Base
 | |
|         baseurl=http://mirror.dulug.duke.edu/pub/yum-repository/redhat/$releasev
 | |
| er/$basearch/
 | |
| 
 | |
|         %config = {
 | |
|                                 main => {
 | |
|                                         'cachedir'  => '/var/cache/yum',
 | |
|                                         'keepcache' => '1'
 | |
|                                                 },
 | |
|                                 bash => {
 | |
|                                         'name'          => 'Red Hat Linux $relea
 | |
| sever - $basearch - Base',
 | |
|                                         'baseurl'       => 'http://mirror.dulug.
 | |
| duke.edu/pub/yum-repository/redhat/$releasever/$basearch/'
 | |
|                                                 }
 | |
|                           }
 | |
| 
 | |
|     Arguments:
 | |
|           $configfile      - config file
 | |
|     Returns:
 | |
|           $config_ref    - reference to config hash
 | |
|     Comments:
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| sub get_config
 | |
| {
 | |
|     my $configfile      = shift;
 | |
|     my @content         = readFile($configfile);
 | |
|     my $current_section = "DEFAULT";
 | |
|     my %config;
 | |
|     my $xcat_use;
 | |
|     $xcat_use = 0;
 | |
|     foreach my $line (@content)
 | |
|     {
 | |
|         my ($entry, $value);
 | |
|         chomp $line;
 | |
|         if ($line =~ /\QDO NOT ERASE THIS SECTION\E/)
 | |
|         {
 | |
| 
 | |
|             # reverse flag
 | |
|             $xcat_use = !$xcat_use;
 | |
|         }
 | |
|         if ($xcat_use)
 | |
|         {
 | |
| 
 | |
|             # Remove leading "#". This line is used by xCAT
 | |
|             $line =~ s/^#//g;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
| 
 | |
|             # Remove comment line
 | |
|             $line =~ s/#.*$//g;
 | |
|         }
 | |
|         $line =~ s/^\s+//g;
 | |
|         $line =~ s/\s+$//g;
 | |
|         next unless $line;
 | |
|         if ($line =~ /^\s*\[([\w+-\.]+)\]\s*$/)
 | |
|         {
 | |
|             $current_section = $1;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
| 
 | |
|             # Ignore line doesn't key/value pair.
 | |
|             if ($line !~ /=/)
 | |
|             {
 | |
|                 next;
 | |
|             }
 | |
|             $line =~ /^\s*([^=]*)\s*=\s*(.*)\s*$/;
 | |
|             $entry = $1;
 | |
|             $value = $2;
 | |
|             $entry =~ s/^#*//g;
 | |
| 
 | |
|             # Remove leading and trailing spaces
 | |
|             $entry =~ s/^\s+//g;
 | |
|             $entry =~ s/\s+$//g;
 | |
|             $value =~ s/^\s+//g;
 | |
|             $value =~ s/\s+$//g;
 | |
|             $config{$current_section}{"$entry"} = $value;
 | |
|         }
 | |
|     }
 | |
|     return \%config;
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| =head3    readFile
 | |
| 
 | |
|     Read a file and return its content.
 | |
| 
 | |
|     Arguments:
 | |
|         filename
 | |
|     Returns:
 | |
|         file contents or undef
 | |
|     Globals:
 | |
|         none
 | |
|     Error:
 | |
|         undef
 | |
|     Example:
 | |
|         my $blah = readFile('/etc/redhat-release');
 | |
|     Comments:
 | |
|         none
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| 
 | |
| sub readFile
 | |
| {
 | |
|     my $filename = shift;
 | |
|     open(FILE, "<$filename");
 | |
|     my @contents = <FILE>;
 | |
|     close(FILE);
 | |
|     return @contents;
 | |
| }
 | |
| 
 | |
| 1;
 |