2007-10-26 22:44:33 +00:00
#!/usr/bin/env perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT::Client ;
2007-12-11 19:15:28 +00:00
BEGIN
{
2008-01-14 16:58:23 +00:00
$ ::XCATROOT = $ ENV { 'XCATROOT' } ? $ ENV { 'XCATROOT' } : - d '/opt/xcat' ? '/opt/xcat' : '/usr' ;
2008-03-24 16:14:59 +00:00
}
2007-12-03 12:53:41 +00:00
2009-07-28 17:29:16 +00:00
# if AIX - make sure we include perl 5.8.2 in INC path.
# Needed to find perl dependencies shipped in deps tarball.
if ( $^O =~ /^aix/i ) {
use lib "/usr/opt/perl5/lib/5.8.2/aix-thread-multi" ;
use lib "/usr/opt/perl5/lib/5.8.2" ;
use lib "/usr/opt/perl5/lib/site_perl/5.8.2/aix-thread-multi" ;
use lib "/usr/opt/perl5/lib/site_perl/5.8.2" ;
}
2008-03-10 18:05:26 +00:00
my $ inet6support ;
2009-06-02 11:38:34 +00:00
if ( $^O =~ /^aix/i ) { # disable AIX IPV6 TODO fix
2009-06-01 20:39:29 +00:00
$ inet6support = 0 ;
2009-06-02 18:39:33 +00:00
} else {
$ inet6support = eval { require Socket6 } ;
2009-06-01 20:39:29 +00:00
}
2008-03-10 18:05:26 +00:00
if ( $ inet6support ) {
$ inet6support = eval { require IO::Socket::INET6 } ;
}
if ( $ inet6support ) {
2009-05-29 13:53:49 +00:00
$ inet6support = eval { require IO::Socket::SSL ; IO::Socket::SSL - > import ( 'inet6' ) ; } ;
2008-03-10 18:05:26 +00:00
}
unless ( $ inet6support ) {
eval { require Socket } ;
eval { require IO::Socket::INET } ;
2009-11-21 17:44:46 +00:00
eval { require IO::Socket::SSL ; IO::Socket::SSL - > import ( 'inet4' ) } ;
2007-12-03 12:53:41 +00:00
}
2008-05-22 18:57:51 +00:00
2007-10-26 22:44:33 +00:00
use XML::Simple ;
2009-07-16 14:44:50 +00:00
$ XML:: Simple:: PREFERRED_PARSER = 'XML::Parser' ;
2008-04-05 14:53:35 +00:00
require Data::Dumper ;
2007-10-26 22:44:33 +00:00
use Storable qw( dclone ) ;
my $ xcathost = 'localhost:3001' ;
2007-12-03 20:29:30 +00:00
my $ plugins_dir ;
2007-10-26 22:44:33 +00:00
my % resps ;
2008-02-14 14:25:49 +00:00
my $ EXITCODE ; # save the bitmask of all exit codes returned by calls to handle_response()
2007-10-26 22:44:33 +00:00
1 ;
#################################
# submit_request will take an xCAT command and pass it to the xCAT
# server for execution.
2008-02-14 14:25:49 +00:00
#
2007-10-26 22:44:33 +00:00
# If the XCATBYPASS env var is set, the connection to the server/daemon
# will be bypassed and the plugin will be called directly. If it is
2008-02-14 14:25:49 +00:00
# set to one or more directories (separated by ":"), all perl modules
# in those directories will be loaded in as plugins (for duplicate
# commands, last one in wins). If it is set to any other value
# (e.g. "yes", "default", whatever string you want) the default plugin
2007-12-11 19:15:28 +00:00
# directory /opt/xcat/lib/perl/xCAT_plugin will be used.
2007-10-26 22:44:33 +00:00
#
# Input:
# Request hash - A hash ref containing the input command and args to be
# passed to the plugin. The the xcatd daemon (or this routine when
# XCATBYPASS) reads the {noderange} entry and builds a flattened array
# of nodes that gets added as request->{node}
# The format for the request hash is:
# { command => [ 'xcatcmd' ],
# noderange => [ 'noderange_string' ],
# arg => [ 'arg1', 'arg2', '...', 'argn' ]
# }
# Callback - A subroutine ref that will be called to process the output
# from the plugin.
2008-02-14 14:25:49 +00:00
#
2007-10-26 22:44:33 +00:00
# NOTE: The request hash will get converted to XML when passed to the
# xcatd daemon, and will get converted back to a hash before being
# passed to the plugin. The XMLin ForceArray option is used to
# force all XML constructs to be arrays so that the plugin code
2008-02-14 14:25:49 +00:00
# and callback routines can access the data consistently.
2007-10-26 22:44:33 +00:00
# The input request and the response hash created by the plugin should
# always create hashes with array values.
#################################
sub submit_request {
my $ request = shift ;
my $ callback = shift ;
2008-01-25 15:07:53 +00:00
my $ keyfile = shift ;
my $ certfile = shift ;
my $ cafile = shift ;
2009-03-30 14:19:17 +00:00
require xCAT::Utils ;
unless ( $ keyfile ) { $ keyfile = xCAT::Utils - > getHomeDir ( ) . "/.xcat/client-cred.pem" ; }
unless ( $ certfile ) { $ certfile = xCAT::Utils - > getHomeDir ( ) . "/.xcat/client-cred.pem" ; }
unless ( $ cafile ) { $ cafile = xCAT::Utils - > getHomeDir ( ) . "/.xcat/ca.pem" ; }
2008-02-14 14:25:49 +00:00
$ xCAT:: Client:: EXITCODE = 0 ; # clear out exit code before invoking the plugin
2010-03-09 16:10:25 +00:00
$ request - > { clienttype } - > [ 0 ] = "cli" ; # setup clienttype for auditlog
2007-10-26 22:44:33 +00:00
# If XCATBYPASS is set, invoke the plugin process_request method directly
# without going through the socket connection to the xcatd daemon
if ( $ ENV { XCATBYPASS } ) {
2010-03-12 18:36:55 +00:00
#add current userid to the request
if ( ! ( defined ( $ request - > { username } ) ) ) {
$ request - > { username } - > [ 0 ] = getpwuid ( $> ) ;
}
2007-10-26 22:44:33 +00:00
# Load plugins from either specified or default dir
2008-04-17 12:46:39 +00:00
require xCAT::Table ;
2007-10-26 22:44:33 +00:00
my % cmd_handlers ;
my @ plugins_dirs = split ( '\:' , $ ENV { XCATBYPASS } ) ;
if ( - d $ plugins_dirs [ 0 ] ) {
foreach ( @ plugins_dirs ) {
$ plugins_dir = $ _ ;
scan_plugins ( ) ;
}
} else {
2007-12-03 20:29:30 +00:00
# figure out default plugins dir
my $ sitetab = xCAT::Table - > new ( 'site' ) ;
unless ( $ sitetab ) {
print ( "ERROR: Unable to open basic site table for configuration\n" ) ;
}
2007-12-11 19:15:28 +00:00
$ plugins_dir = $ ::XCATROOT . '/lib/perl/xCAT_plugin' ;
2007-10-26 22:44:33 +00:00
scan_plugins ( ) ;
}
# don't do XML transformation -- assume request is well-formed
# my $xmlreq=XMLout($request,RootName=>xcatrequest,NoAttr=>1,KeyAttr=>[]);
# $request = XMLin($xmlreq,SuppressEmpty=>undef,ForceArray=>1) ;
# Call the plugin directly
# ${"xCAT_plugin::".$modname."::"}{process_request}->($request,$callback);
plugin_command ( $ request , undef , $ callback ) ;
return 0 ;
}
# No XCATBYPASS, so establish a socket connection with the xcatd daemon
# and submit the request
if ( $ ENV { XCATHOST } ) {
$ xcathost = $ ENV { XCATHOST } ;
}
2008-03-28 15:55:41 +00:00
my $ client ;
if ( - r $ keyfile and - r $ certfile and - r $ cafile ) {
$ client = IO::Socket::SSL - > new (
2007-10-26 22:44:33 +00:00
PeerAddr = > $ xcathost ,
2008-01-25 15:07:53 +00:00
SSL_key_file = > $ keyfile ,
SSL_cert_file = > $ certfile ,
SSL_ca_file = > $ cafile ,
2007-10-26 22:44:33 +00:00
SSL_use_cert = > 1 ,
2009-09-01 18:03:46 +00:00
Timeout = > 15 ,
2007-10-26 22:44:33 +00:00
) ;
2008-03-28 15:55:41 +00:00
} else {
$ client = IO::Socket::SSL - > new (
2009-09-01 18:03:46 +00:00
PeerAddr = > $ xcathost ,
Timeout = > 15 ,
2008-03-28 15:55:41 +00:00
) ;
}
unless ( $ client ) {
if ( $@ =~ /SSL Timeout/ ) {
die "Connection failure: SSL Timeout or incorrect certificates in ~/.xcat" ;
} else {
die "Connection failure: $@"
}
}
2008-04-14 13:55:17 +00:00
my $ msg = XMLout ( $ request , RootName = > 'xcatrequest' , NoAttr = > 1 , KeyAttr = > [] ) ;
2010-04-27 20:27:16 +00:00
if ( $ ENV { XCATXMLTRACE } ) { print $ msg ; }
2008-04-21 15:28:28 +00:00
$ SIG { TERM } = $ SIG { INT } = sub { print $ client XMLout ( { abortcommand = > 1 } , RootName = > 'xcatrequest' , NoAttr = > 1 , KeyAttr = > [] ) ; exit 0 ; } ;
2007-10-26 22:44:33 +00:00
print $ client $ msg ;
my $ response ;
my $ rsp ;
2008-04-25 21:09:18 +00:00
my $ cleanexit = 0 ;
2007-10-26 22:44:33 +00:00
while ( <$client> ) {
$ response . = $ _ ;
2009-08-09 15:58:41 +00:00
if ( m/<\/xcatresponse>/ ) {
2009-10-09 17:51:31 +00:00
#replace ESC with xxxxESCxxx because XMLin cannot handle it
$ response =~ s/\e/xxxxESCxxxx/g ;
2010-04-27 20:27:16 +00:00
if ( $ ENV { XCATXMLTRACE } ) { print $ response ; }
2007-10-26 22:44:33 +00:00
$ rsp = XMLin ( $ response , SuppressEmpty = > undef , ForceArray = > 1 ) ;
2009-10-09 17:51:31 +00:00
#add ESC back
foreach my $ key ( keys %$ rsp ) {
2009-11-24 15:12:39 +00:00
if ( ref ( $ rsp - > { $ key } ) eq 'ARRAY' ) { foreach my $ text ( @ { $ rsp - > { $ key } } ) { next unless defined $ text ; $ text =~ s/xxxxESCxxxx/\e/g ; } }
2009-10-12 13:31:27 +00:00
else { $ rsp - > { $ key } =~ s/xxxxESCxxxx/\e/g ; }
2009-10-09 17:51:31 +00:00
}
2007-10-26 22:44:33 +00:00
$ response = '' ;
$ callback - > ( $ rsp ) ;
if ( $ rsp - > { serverdone } ) {
2008-04-25 21:09:18 +00:00
$ cleanexit = 1 ;
2007-10-26 22:44:33 +00:00
last ;
}
}
}
2008-04-25 21:09:18 +00:00
unless ( $ cleanexit ) {
print STDERR "ERROR/WARNING: communication with the xCAT server seems to have been ended prematurely\n" ;
}
2007-10-26 22:44:33 +00:00
###################################
# scan_plugins
2008-02-14 14:25:49 +00:00
# will load all plugin perl modules and build a list of supported
2007-10-26 22:44:33 +00:00
# commands
#
2009-11-23 16:16:33 +00:00
# NOTE: This is copied from xcatd (last merge 11/23/09).
2008-05-22 18:57:51 +00:00
# TODO: Will eventually move to using common source....
2007-10-26 22:44:33 +00:00
###################################
2009-11-23 16:16:33 +00:00
sub scan_plugins {
my @ plugins = glob ( $ plugins_dir . "/*.pm" ) ;
foreach ( @ plugins ) {
/.*\/([^\/]*).pm$/ ;
my $ modname = $ 1 ;
unless ( eval { require "$_" } ) {
# xCAT::MsgUtils->message("S","Error loading module ".$_." ...skipping");
print "Error loading module $_ ...skipping\n" ;
next ;
}
no strict 'refs' ;
my $ cmd_adds = $ { "xCAT_plugin::" . $ modname . "::" } { handled_commands } - > ( ) ;
foreach ( keys %$ cmd_adds ) {
my $ value = $ _ ;
if ( defined ( $ cmd_handlers { $ _ } ) ) {
2009-11-24 15:12:39 +00:00
push @ { $ cmd_handlers { $ _ } } , [ $ modname , $ cmd_adds - > { $ _ } ] ;
2009-11-23 16:16:33 +00:00
} else {
$ cmd_handlers { $ _ } = [ [ $ modname , $ cmd_adds - > { $ _ } ] ] ;
}
}
}
foreach ( @ plugins ) {
no strict 'refs' ;
/.*\/([^\/]*).pm$/ ;
my $ modname = $ 1 ;
unless ( defined ( $ { "xCAT_plugin::" . $ modname . "::" } { init_plugin } ) ) {
next ;
}
$ { "xCAT_plugin::" . $ modname . "::" } { init_plugin } - > ( \ & do_request ) ;
}
}
2007-10-26 22:44:33 +00:00
###################################
# plugin_command
2008-02-14 14:25:49 +00:00
# will invoke the correct plugin
2007-10-26 22:44:33 +00:00
#
2009-11-23 16:16:33 +00:00
# NOTE: This is copied from xcatd (last merge 11/23/09).
2008-05-22 18:57:51 +00:00
# TODO: Will eventually move to using common source....
2007-10-26 22:44:33 +00:00
###################################
sub plugin_command {
my $ req = shift ;
my $ sock = shift ;
my $ callback = shift ;
my % handler_hash ;
2009-11-23 16:16:33 +00:00
my $ usesiteglobal = 0 ;
2008-04-17 12:46:39 +00:00
# We require these only in bypass mode to reduce start up time for the normal case
#use lib "$::XCATROOT/lib/perl";
2008-03-27 15:32:21 +00:00
#use xCAT::NodeRange;
2008-04-17 12:46:39 +00:00
require lib ;
lib - > import ( "$::XCATROOT/lib/perl" ) ;
require xCAT::NodeRange ;
require xCAT::Table ;
2007-10-26 22:44:33 +00:00
$ Main:: resps = { } ;
my @ nodes ;
if ( $ req - > { node } ) {
@ nodes = @ { $ req - > { node } } ;
2008-05-22 18:57:51 +00:00
} elsif ( $ req - > { noderange } and $ req - > { noderange } - > [ 0 ] ) {
2008-04-05 14:53:35 +00:00
@ nodes = xCAT::NodeRange:: noderange ( $ req - > { noderange } - > [ 0 ] ) ;
if ( xCAT::NodeRange:: nodesmissed ( ) ) {
# my $rsp = {errorcode=>1,error=>"Invalid nodes in noderange:".join(',',xCAT::NodeRange::nodesmissed)};
2009-03-25 14:31:39 +00:00
# my $rsp->{serverdone} = {};
2009-07-15 19:33:01 +00:00
print "Invalid nodes in noderange:" . join ( ',' , xCAT::NodeRange:: nodesmissed ( ) ) . "\n" ;
2007-10-26 22:44:33 +00:00
# if ($sock) {
# print $sock XMLout($rsp,RootName=>'xcatresponse' ,NoAttr=>1);
# }
# return ($rsp);
return 1 ;
}
2008-05-22 18:57:51 +00:00
unless ( @ nodes ) {
$ req - > { emptynoderange } = [ 1 ] ;
}
2007-10-26 22:44:33 +00:00
}
if ( @ nodes ) { $ req - > { node } = \ @ nodes ; }
2008-05-22 18:57:51 +00:00
my % unhandled_nodes ;
foreach ( @ nodes ) {
$ unhandled_nodes { $ _ } = 1 ;
}
my $ useunhandled = 0 ;
2007-10-26 22:44:33 +00:00
if ( defined ( $ cmd_handlers { $ req - > { command } - > [ 0 ] } ) ) {
my $ hdlspec ;
2009-11-23 16:16:33 +00:00
my @ globalhandlers = ( ) ;
my $ useglobals = 1 ; #If it stays 1, then use globals normally, if 0, use only for 'unhandled_nodes, if -1, don't do at all
2007-10-26 22:44:33 +00:00
foreach ( @ { $ cmd_handlers { $ req - > { command } - > [ 0 ] } } ) {
$ hdlspec = $ _ - > [ 1 ] ;
my $ ownmod = $ _ - > [ 0 ] ;
2009-11-23 16:16:33 +00:00
if ( $ hdlspec =~ /^site:/ ) { #A site entry specifies a plugin
my $ sitekey = $ hdlspec ;
$ sitekey =~ s/^site:// ;
$ sitetab = xCAT::Table - > new ( 'site' ) ;
my $ sent = $ sitetab - > getAttribs ( { key = > $ sitekey } , [ 'value' ] ) ;
if ( $ sent and $ sent - > { value } ) { #A site style plugin specification is just like
#a static global, it grabs all nodes rather than some
$ useglobals = - 1 ; #If they tried to specify anything, don't use the default global handlers at all
unless ( @ nodes ) {
$ handler_hash { $ sent - > { value } } = 1 ;
$ usesiteglobal = 1 ;
}
foreach ( @ nodes ) { #Specified a specific plugin, not a table lookup
$ handler_hash { $ sent - > { value } } - > { $ _ } = 1 ;
}
}
} elsif ( $ hdlspec =~ /:/ ) { #Specificed a table lookup path for plugin name
2009-11-24 15:12:39 +00:00
if ( @ nodes ) { # only use table lookup plugin if nodelist exists
# Usage will be handled in common AAAhelp plugin
$ useglobals = 0 ; #Only contemplate nodes that aren't caught through searching below in the global handler
$ useunhandled = 1 ;
my $ table ;
my $ cols ;
( $ table , $ cols ) = split ( /:/ , $ hdlspec ) ;
my @ colmns = split ( /,/ , $ cols ) ;
my @ columns ;
my $ hdlrtable = xCAT::Table - > new ( $ table ) ;
unless ( $ hdlrtable ) {
#TODO: proper error handling
}
my $ node ;
my $ colvals = { } ;
foreach my $ colu ( @ colmns ) {
if ( $ colu =~ /=/ ) { #a value redirect to a pattern/specific name
my $ coln ; my $ colv ;
( $ coln , $ colv ) = split ( /=/ , $ colu , 2 ) ;
$ colvals - > { $ coln } = $ colv ;
push ( @ columns , $ coln ) ;
} else {
push ( @ columns , $ colu ) ;
}
2007-10-26 22:44:33 +00:00
}
2009-11-23 16:16:33 +00:00
2009-11-24 15:12:39 +00:00
unless ( @ nodes ) { #register the plugin in the event of usage
$ handler_hash { $ ownmod } = 1 ;
$ useglobals = 1 ;
}
my $ hdlrcache ;
if ( $ hdlrtable ) {
$ hdlrcache = $ hdlrtable - > getNodesAttribs ( \ @ nodes , \ @ columns ) ;
}
foreach $ node ( @ nodes ) {
unless ( $ hdlrcache ) { next ; }
my $ attribs = $ hdlrcache - > { $ node } - > [ 0 ] ; #$hdlrtable->getNodeAttribs($node,\@columns);
unless ( defined ( $ attribs ) ) { next ; }
foreach ( @ columns ) {
my $ col = $ _ ;
if ( defined ( $ attribs - > { $ col } ) ) {
if ( $ colvals - > { $ col } ) { #A pattern match style request.
if ( $ attribs - > { $ col } =~ /$colvals->{$col}/ ) {
$ handler_hash { $ ownmod } - > { $ node } = 1 ;
delete $ unhandled_nodes { $ node } ;
last ;
}
} else {
# call the plugin that matches the table value for that node
if ( $ attribs - > { $ col } =~ /$ownmod/ ) {
$ handler_hash { $ attribs - > { $ col } } - > { $ node } = 1 ;
delete $ unhandled_nodes { $ node } ;
last ;
}
2007-10-26 22:44:33 +00:00
}
}
}
}
2009-11-24 15:12:39 +00:00
$ hdlrtable - > close ;
} # end if (@nodes)
2007-10-26 22:44:33 +00:00
} else {
2009-11-23 16:16:33 +00:00
push @ globalhandlers , $ hdlspec ;
2007-10-26 22:44:33 +00:00
}
}
2009-11-23 16:16:33 +00:00
if ( $ useglobals == 1 ) { #Behavior when globals have not been overriden
my $ hdlspec ;
foreach $ hdlspec ( @ globalhandlers ) {
unless ( @ nodes ) {
$ handler_hash { $ hdlspec } = 1 ;
}
foreach ( @ nodes ) { #Specified a specific plugin, not a table lookup
$ handler_hash { $ hdlspec } - > { $ _ } = 1 ;
}
}
} elsif ( $ useglobals == 0 ) {
unless ( @ nodes or $ usesiteglobal ) { #if something like 'makedhcp -n',
foreach ( keys % handler_hash ) {
if ( $ handler_hash { $ _ } == 1 ) {
delete ( $ handler_hash { $ _ } )
}
}
}
foreach $ hdlspec ( @ globalhandlers ) {
unless ( @ nodes or $ usesiteglobal ) {
$ handler_hash { $ hdlspec } = 1 ;
}
foreach ( keys % unhandled_nodes ) { #Specified a specific plugin, not a table lookup
$ handler_hash { $ hdlspec } - > { $ _ } = 1 ;
}
}
} #Otherwise, global handler is implicitly disabled
2007-10-26 22:44:33 +00:00
} else {
return 1 ; #TODO: error back that request has no known plugin for it
}
2008-05-22 18:57:51 +00:00
if ( $ useunhandled ) {
my $ queuelist ;
2009-11-24 15:12:39 +00:00
foreach ( @ { $ cmd_handlers { $ req - > { command } - > [ 0 ] } } ) {
my $ queueitem = $ _ - > [ 1 ] ;
if ( ( $ queueitem =~ /:/ ) and ! ( $ queuelist =~ /($queueitem)/ ) ) {
$ queuelist . = "$_->[1];" ;
}
2008-05-22 18:57:51 +00:00
}
2009-11-24 15:12:39 +00:00
$ queuelist =~ s/;$// ;
2008-05-22 18:57:51 +00:00
$ queuelist =~ s/:/./g ;
foreach ( keys % unhandled_nodes ) {
# if ($sock) {
# print $sock XMLout({node=>[{name=>[$_],data=>["Unable to identify plugin for this command, check relevant tables: $queuelist"],errorcode=>[1]}]},NoAttr=>1,RootName=>'xcatresponse');
# } else {
2009-11-23 16:16:33 +00:00
my $ tabdesc = $ queuelist ;
$ tabdesc =~ s/=.*$// ;
$ callback - > ( { node = > [ { name = > [ $ _ ] , error = > [ 'Unable to identify plugin for this command, check relevant tables: ' . $ tabdesc ] , errorcode = > [ 1 ] } ] } ) ;
2008-05-22 18:57:51 +00:00
# }
}
}
2007-10-26 22:44:33 +00:00
## FOR NOW, DON'T FORK CHILD PROCESS TO MAKE BYPASS SIMPLER AND EASIER TO DEBUG
2008-05-22 18:57:51 +00:00
# $plugin_numchildren=0;
# %plugin_children=();
# $SIG{CHLD} = \&plugin_reaper; #sub {my $plugpid; while (($plugpid = waitpid(-1, WNOHANG)) > 0) { if ($plugin_children{$plugpid}) { delete $plugin_children{$plugpid}; $plugin_numchildren--; } } };
2007-10-26 22:44:33 +00:00
# my $check_fds;
# if ($sock) {
# $check_fds = new IO::Select;
# }
foreach ( keys % handler_hash ) {
my $ modname = $ _ ;
2009-11-23 16:16:33 +00:00
# my $shouldbealivepid=$$;
2007-10-26 22:44:33 +00:00
if ( - r $ plugins_dir . "/" . $ modname . ".pm" ) {
require $ plugins_dir . "/" . $ modname . ".pm" ;
2008-05-22 18:57:51 +00:00
# $plugin_numchildren++;
2007-10-26 22:44:33 +00:00
# my $pfd; #will be referenced for inter-process messaging.
2008-05-22 18:57:51 +00:00
# my $parfd; #not causing a problem that I discern yet, but theoretically
2007-10-26 22:44:33 +00:00
# my $child;
# if ($sock) { #If $sock not passed in, don't fork..
2008-05-22 18:57:51 +00:00
# socketpair($pfd, $parfd,AF_UNIX,SOCK_STREAM,PF_UNSPEC) or die "socketpair: $!";
2007-10-26 22:44:33 +00:00
# #pipe($pfd,$cfd);
2008-05-22 18:57:51 +00:00
# $parfd->autoflush(1);
2007-10-26 22:44:33 +00:00
# $pfd->autoflush(1);
2008-05-22 18:57:51 +00:00
# $child = xCAT::Utils->xfork;
2007-10-26 22:44:33 +00:00
# } else {
# $child = 0;
# }
# unless (defined $child) { die "Fork failed"; }
# if ($child == 0) {
2009-11-23 16:16:33 +00:00
# if ($parfd) { #If xCAT is doing multiple requests in same communication PID, things would get unfortunate otherwise
# $parent_fd = $parfd;
# }
2008-05-22 18:57:51 +00:00
my $ oldprogname = $$ progname ;
$$ progname = $ oldprogname . ": $modname instance" ;
2007-10-26 22:44:33 +00:00
# if ($sock) { close $pfd; }
unless ( $ handler_hash { $ _ } == 1 ) {
my @ nodes = sort { ( $ a =~ /(\d+)/ ) [ 0 ] <=> ( $ b =~ /(\d+)/ ) [ 0 ] || $ a cmp $ b } ( keys % { $ handler_hash { $ _ } } ) ;
$ req - > { node } = \ @ nodes ;
}
no strict "refs" ;
2009-11-23 16:16:33 +00:00
# eval { #REMOVEEVALFORDEBUG
2008-05-22 18:57:51 +00:00
# if ($dispatch_requests) {
dispatch_request ( $ req , $ callback , $ modname ) ;
# } else {
2009-11-23 16:16:33 +00:00
# $SIG{CHLD}='DEFAULT';
2008-05-22 18:57:51 +00:00
# ${"xCAT_plugin::".$modname."::"}{process_request}->($req,$callback,\&do_request);
# }
$$ progname = $ oldprogname ;
2007-10-26 22:44:33 +00:00
# if ($sock) {
# close($parent_fd);
2008-05-22 18:57:51 +00:00
# xexit(0);
2007-10-26 22:44:33 +00:00
# }
2009-11-23 16:16:33 +00:00
# }; #REMOVEEVALFORDEBUG
# if ($sock or $shouldbealivepid != $$) { #We shouldn't still be alive, try to send as much detail to parent as possible as to why
# my $error= "$modname plugin bug, pid $$, process description: '$$progname'";
# if ($@) {
# $error .= " with error '$@'";
# } else { #Sys::Virt and perhaps Net::SNMP sometimes crashes in a way $@ won't catch..
# $error .= " with missing eval error, probably due to special manipulation of $@ or strange circumstances in an XS library, remove evals in xcatd marked 'REMOVEEVALFORDEBUG and run xcatd -f for more info";
# }
# if (scalar (@nodes)) { #Don't know which of the nodes, so one error message warning about the possibliity..
# $error .= " while trying to fulfill request for the following nodes: ".join(",",@nodes);
# }
# xCAT::MsgUtils->message("S","xcatd: $error");
# $callback->({error=>[$error],errorcode=>[1]});
# xexit(0); #Die like we should have done
# } elsif ($@) { #We are still alive, should be alive, but yet we have an error. This means we are in the case of 'do_request' or something similar. Forward up the death since our communication channel is intact..
# die $@;
# }
2007-10-26 22:44:33 +00:00
# } else {
2008-05-22 18:57:51 +00:00
# $plugin_children{$child}=1;
# close $parfd;
2007-10-26 22:44:33 +00:00
# $check_fds->add($pfd);
# }
2009-03-25 14:31:39 +00:00
} else {
my $ pm_name = $ plugins_dir . "/" . $ modname . ".pm" ;
foreach my $ node ( keys % { $ handler_hash { $ _ } } ) {
if ( $ sock ) {
print $ sock XMLout ( { node = > [ { name = > [ $ node ] , data = > [ "Cannot find the perl module to complete the operation: $pm_name" ] , errorcode = > [ 1 ] } ] } , NoAttr = > 1 , RootName = > 'xcatresponse' ) ;
} else {
$ callback - > ( { node = > [ { name = > [ $ node ] , data = > [ "Cannot find the perl module to complete the operation: $pm_name" ] , errorcode = > [ 1 ] } ] } ) ;
}
}
2007-10-26 22:44:33 +00:00
}
}
unless ( $ sock ) { return $ Main:: resps } ;
2008-05-22 18:57:51 +00:00
# while (($plugin_numchildren > 0) and ($check_fds->count > 0)) { #this tracks end of useful data from children much more closely
2007-10-26 22:44:33 +00:00
# relay_fds($check_fds,$sock);
# }
# #while (relay_fds($check_fds,$sock)) {}
# my %done;
# $done{serverdone} = {};
# if ($req->{transid}) {
# $done{transid}=$req->{transid}->[0];
# }
2009-11-23 16:16:33 +00:00
# if ($sock) {
# my $clientpresence = new IO::Select; #The client may have gone away without confirmation, don't PIPE over this trivial thing
# $clientpresence->add($sock);
# if ($clientpresence->can_write(5)) {
# print $sock XMLout(\%done,RootName => 'xcatresponse',NoAttr=>1);
# }
# }
2007-10-26 22:44:33 +00:00
}
2008-05-22 18:57:51 +00:00
###################################
# dispatch_request
# dispatch the requested command
#
2009-11-23 16:16:33 +00:00
# NOTE: This is copied from xcatd (last merge 11/23/09).
2008-05-22 18:57:51 +00:00
# All we really need from this subroutine is to call preprocess_request
# and to only run the command for nodes handled by the local server
# Will eventually move to using common source....
###################################
sub dispatch_request {
# %dispatched_children=();
my $ req = shift ;
$ dispatch_cb = shift ;
my $ modname = shift ;
my $ reqs = [] ;
# my $child_fdset = new IO::Select;
no strict "refs" ;
#Hierarchy support. Originally, the default scope for noderange commands was
#going to be the servicenode associated unless overriden.
#However, assume for example that you have blades and a blade is the service node
#rpower being executed by the servicenode for one of its subnodes would have to
#reach it's own management module. This has the potential to be non-trivial for some quite possible network configurations.
#Since plugins may commonly experience this, a preprocess_request implementation
#will for now be required for a command to be scaled through service nodes
#If the plugin offers a preprocess method, use it to set the request array
if ( defined ( $ { "xCAT_plugin::" . $ modname . "::" } { preprocess_request } ) ) {
2009-11-23 16:16:33 +00:00
$ SIG { CHLD } = 'DEFAULT' ;
2008-05-22 18:57:51 +00:00
$ reqs = $ { "xCAT_plugin::" . $ modname . "::" } { preprocess_request } - > ( $ req , $ dispatch_cb , \ & do_request ) ;
} else { #otherwise, pass it in without hierarchy support
$ reqs = [ $ req ] ;
}
# $dispatch_children=0;
# $SIG{CHLD} = \&dispatch_reaper; #sub {my $cpid; while (($cpid =waitpid(-1, WNOHANG)) > 0) { if ($dispatched_children{$cpid}) { delete $dispatched_children{$cpid}; $dispatch_children--; } } };
2009-11-23 16:16:33 +00:00
my $ onlyone = 0 ;
if ( defined $ reqs and ( scalar ( @ { $ reqs } ) == 1 ) ) {
$ onlyone = 1 ;
}
2008-05-22 18:57:51 +00:00
foreach ( @ { $ reqs } ) {
# my $pfd;
# my $parfd; #use a private variable so it won't trounce itself recursively
# my $child;
delete $ _ - > { noderange } ;
#----- added to Client.pm -----#
if ( $ _ - > { node } ) {
$ _ - > { noderange } - > [ 0 ] = join ( ',' , @ { $ _ - > { node } } ) ;
}
2009-11-23 16:16:33 +00:00
#----- end added to Client.pm -----#
if ( ref $ _ - > { '_xcatdest' } and ( ref $ _ - > { '_xcatdest' } ) eq 'ARRAY' ) {
_ - > { '_xcatdest' } = $ _ - > { '_xcatdest' } - > [ 0 ] ;
}
if ( $ onlyone and not ( $ _ - > { '_xcatdest' } and xCAT::Utils - > thishostisnot ( $ _ - > { '_xcatdest' } ) ) ) {
$ SIG { CHLD } = 'DEFAULT' ;
$ { "xCAT_plugin::" . $ modname . "::" } { process_request } - > ( $ _ , $ dispatch_cb , \ & do_request ) ;
return ;
}
2008-05-22 18:57:51 +00:00
# socketpair($pfd, $parfd,AF_UNIX,SOCK_STREAM,PF_UNSPEC) or die "socketpair: $!";
# $parfd->autoflush(1);
# $pfd->autoflush(1);
# $child = xCAT::Utils->xfork;
# if ($child) {
# $dispatch_children++;
# $dispatched_children{$child}=1;
# $child_fdset->add($pfd);
# next;
# }
# unless (defined $child) {
# $dispatch_cb->({error=>['Fork failure dispatching request'],errorcode=>[1]});
# }
# undef $SIG{CHLD};
# $dispatch_parentfd = $parfd;
2009-11-23 16:16:33 +00:00
my @ prexcatdests = ( ) ;
my @ xcatdests = ( ) ;
if ( ref ( $ _ - > { '_xcatdest' } ) eq 'ARRAY' ) { #If array, consider it an 'anycast' operation, broadcast done through dupe
#requests, or an alternative join '&' maybe?
@ prexcatdests = @ { $ _ - > { '_xcatdest' } } ;
} else {
@ prexcatdests = ( $ _ - > { '_xcatdest' } ) ;
}
foreach ( @ prexcatdests ) {
if ( $ _ and /,/ ) {
push @ xcatdests , split /,/ , $ _ ;
} else {
push @ xcatdests , $ _ ;
}
}
my $ xcatdest ;
my $ numdests = scalar ( @ xcatdests ) ;
my $ request_satisfied = 0 ;
foreach $ xcatdest ( @ xcatdests ) {
my $ dlock ;
if ( $ xcatdest and xCAT::Utils - > thishostisnot ( $ xcatdest ) ) {
2008-05-22 18:57:51 +00:00
#----- added to Client.pm -----#
$ dispatch_cb - > ( { warning = > [ 'XCATBYPASS is set, skipping hierarchy call to ' . $ _ - > { '_xcatdest' } . '' ] } ) ;
2009-11-23 16:16:33 +00:00
#----- end added to Client.pm -----#
# #mkpath("/var/lock/xcat/"); #For now, limit intra-xCAT requests to one at a time, to mitigate DB handle usage
# #open($dlock,">","/var/lock/xcat/dispatchto_$xcatdest");
# #flock($dlock,LOCK_EX);
# $ENV{XCATHOST} = ($xcatdest =~ /:/ ? $xcatdest : $xcatdest.":3001" );
# $$progname.=": connection to ".$ENV{XCATHOST};
# my $errstr;
# eval {
# undef $_->{'_xcatdest'};
# xCAT::Client::submit_request($_,\&dispatch_callback,$xcatdir."/cert/server-cred.pem",$xcatdir."/cert/server-cred.pem",$xcatdir."/cert/ca.pem");
# };
# if ($@) {
# $errstr=$@;
# }
# #unlink("/var/lock/xcat/dispatchto_$xcatdest");
# #flock($dlock,LOCK_UN);
# if ($errstr) {
# if ($numdests == 1) {
# dispatch_callback({error=>["Unable to dispatch command to ".$ENV{XCATHOST}.", command will not make changes to that server ($errstr)"],errorcode=>[1]});
# xCAT::MsgUtils->message("S","Error dispatching request to ".$ENV{XCATHOST}.": ".$errstr);
# } else {
# xCAT::MsgUtils->message("S","Error dispatching request to ".$ENV{XCATHOST}.", trying other service nodes: ".$errstr);
# }
# next;
# } else {
# $request_satisfied=1;
# last;
# }
} else {
$$ progname . = ": locally executing" ;
$ SIG { CHLD } = 'DEFAULT' ;
# ${"xCAT_plugin::".$modname."::"}{process_request}->($_,\&dispatch_callback,\&do_request);
#----- changed in Client.pm -----#
$ { "xCAT_plugin::" . $ modname . "::" } { process_request } - > ( $ _ , \ & dispatch_cb , \ & do_request ) ;
#----- end changed in Client.pm -----#
last ;
}
2008-05-22 18:57:51 +00:00
}
2009-11-23 16:16:33 +00:00
# if ($numdests > 1 and not $request_satisfied) {
# xCAT::MsgUtils->message("S","Error dispatching a request to all possible service nodes for request");
# dispatch_callback({error=>["Failed to dispatch command to any of the following service nodes: ".join(",",@xcatdests)],errorcode=>[1]});
# }
2008-05-22 18:57:51 +00:00
# xexit;
}
#while (($dispatch_children > 0) and ($child_fdset->count > 0)) { relay_dispatch($child_fdset) }
#while (relay_dispatch($child_fdset)) { } #Potentially useless drain.
}
2009-11-23 16:16:33 +00:00
2007-10-26 22:44:33 +00:00
###################################
# do_request
# called from a plugin to execute another xCAT plugin command internally
#
2009-11-23 16:16:33 +00:00
# NOTE: This is copied from xcatd (last merge 11/23/09).
2007-10-26 22:44:33 +00:00
# Will eventually move to using common source....
###################################
sub do_request {
my $ req = shift ;
my $ second = shift ;
my $ rsphandler = \ & build_response ;
my $ sock = undef ;
if ( $ second ) {
if ( ref ( $ second ) eq "CODE" ) {
$ rsphandler = $ second ;
} elsif ( ref ( $ second ) eq "GLOB" ) {
$ sock = $ second ;
}
}
#my $sock = shift; #If no sock, will return a response hash
if ( $ cmd_handlers { $ req - > { command } - > [ 0 ] } ) {
return plugin_command ( $ req , $ sock , $ rsphandler ) ;
} elsif ( $ req - > { command } - > [ 0 ] eq "noderange" and $ req - > { noderange } ) {
2008-04-05 14:53:35 +00:00
my @ nodes = xCAT::NodeRange:: noderange ( $ req - > { noderange } - > [ 0 ] ) ;
2007-10-26 22:44:33 +00:00
my % resp ;
2008-04-05 14:53:35 +00:00
if ( xCAT::NodeRange:: nodesmissed ( ) ) {
$ resp { warning } = "Invalid nodes in noderange:" . join ',' , xCAT::NodeRange:: nodesmissed ( ) . "\n" ;
2007-10-26 22:44:33 +00:00
}
$ resp { serverdone } = { } ;
@ { $ resp { node } } = @ nodes ;
if ( $ req - > { transid } ) {
$ resp { transid } = $ req - > { transid } - > [ 0 ] ;
}
if ( $ sock ) {
print $ sock XMLout ( \ % resp , RootName = > 'xcatresponse' , NoAttr = > 1 ) ;
} else {
return ( \ % resp ) ;
}
} else {
my % resp = ( error = > "Unsupported request" ) ;
$ resp { serverdone } = { } ;
if ( $ req - > { transid } ) {
$ resp { transid } = $ req - > { transid } - > [ 0 ] ;
}
if ( $ sock ) {
print $ sock XMLout ( \ % resp , RootName = > 'xcatresponse' , NoAttr = > 1 ) ;
} else {
return ( \ % resp ) ;
}
}
}
###################################
# build_response
# This callback handles responses from nested level plugin calls.
# It builds a merged hash of all responses that gets passed back
# to the calling plugin.
# Note: Need to create a "deep clone" of this response to add to the
# return, otherwise next time through the referenced data is overwritten
#
###################################
sub build_response {
my $ rsp = shift ;
foreach ( keys %$ rsp ) {
my $ subresp = dclone ( $ rsp - > { $ _ } ) ;
push ( @ { $ Main:: resps - > { $ _ } } , @ { $ subresp } ) ;
}
}
2008-02-14 14:25:49 +00:00
} # end of submit_request()
##########################################
# handle_response is a default callback that can be passed into submit_response()
# It is invoked repeatedly by submit_response() to print out the data returned by
# the plugin.
#
# The normal flow is:
# -> client cmd (e.g. nodels, which is just a link to xcatclient)
# -> xcatclient
# -> submit_request()
# -> send xml request to xcatd
# -> xcatd
# -> process_request() of the plugin
# <- plugin callback
# <- xcatd
# <- xcatd sends xml response to client
# <- submit_request() read response
# <- handle_response() prints responses and saves exit codes
# <- xcatclient gets exit code and exits
#
# But in XCATBYPASS mode, the flow is:
# -> client cmd (e.g. nodels, which is just a link to xcatclient)
# -> xcatclient
# -> submit_request()
# -> process_request() of the plugin
# <- handle_response() prints responses and saves exit codes
# <- xcatclient gets exit code and exits
#
# Format of the response hash:
# {data => [ 'data str1', 'data str2', '...' ] }
#
# Results are printed as:
# data str1
# data str2
#
# or:
# {data => [ {desc => [ 'desc1' ],
# contents => [ 'contents1' ] },
# {desc => [ 'desc2 ],
# contents => [ 'contents2' ] }
# :
# ] }
# NOTE: In this format, only the data array can have more than one
# element. All other arrays are assumed to be a single element.
# Results are printed as:
# desc1: contents1
# desc2: contents2
#
# or:
# {node => [ {name => ['node1'],
# data => [ {desc => [ 'node1 desc' ],
# contents => [ 'node1 contents' ] } ] },
# {name => ['node2'],
# data => [ {desc => [ 'node2 desc' ],
# contents => [ 'node2 contents' ] } ] },
# :
# ] }
# NOTE: Only the node array can have more than one element.
# All other arrays are assumed to be a single element.
#
# This was generated from the corresponding XML:
# <xcatrequest>
# <node>
# <name>node1</name>
# <data>
# <desc>node1 desc</desc>
# <contents>node1 contents</contents>
# </data>
# </node>
# <node>
# <name>node2</name>
# <data>
# <desc>node2 desc</desc>
# <contents>node2 contents</contents>
# </data>
# </node>
# </xcatrequest>
#
# Results are printed as:
# node_name: desc: contents
##########################################
sub handle_response {
my $ rsp = shift ;
2008-02-18 15:57:25 +00:00
#print "in handle_response\n";
2008-02-14 14:25:49 +00:00
# Handle errors
if ( $ rsp - > { errorcode } ) {
if ( ref ( $ rsp - > { errorcode } ) eq 'ARRAY' ) { foreach my $ ecode ( @ { $ rsp - > { errorcode } } ) { $ xCAT:: Client:: EXITCODE |= $ ecode ; } }
else { $ xCAT:: Client:: EXITCODE |= $ rsp - > { errorcode } ; } # assume it is a non-reference scalar
}
2008-02-18 15:57:25 +00:00
if ( $ rsp - > { error } ) {
#print "printing error\n";
2008-04-07 13:09:39 +00:00
if ( ref ( $ rsp - > { error } ) eq 'ARRAY' ) { foreach my $ text ( @ { $ rsp - > { error } } ) { print STDERR "Error: $text\n" ; } }
2008-02-18 15:57:25 +00:00
else { print ( "Error: " . $ rsp - > { error } . "\n" ) ; }
}
2008-02-14 14:25:49 +00:00
if ( $ rsp - > { warning } ) {
2008-02-18 15:57:25 +00:00
#print "printing warning\n";
2008-04-07 13:09:39 +00:00
if ( ref ( $ rsp - > { warning } ) eq 'ARRAY' ) { foreach my $ text ( @ { $ rsp - > { warning } } ) { print STDERR "Warning: $text\n" ; } }
2008-02-14 14:25:49 +00:00
else { print ( "Warning: " . $ rsp - > { warning } . "\n" ) ; }
}
2008-02-18 15:57:25 +00:00
if ( $ rsp - > { info } ) {
#print "printing info\n";
if ( ref ( $ rsp - > { info } ) eq 'ARRAY' ) { foreach my $ text ( @ { $ rsp - > { info } } ) { print "$text\n" ; } }
else { print ( $ rsp - > { info } . "\n" ) ; }
2008-02-14 14:25:49 +00:00
}
2008-06-02 20:28:43 +00:00
if ( $ rsp - > { sinfo } ) {
if ( ref ( $ rsp - > { sinfo } ) eq 'ARRAY' ) { foreach my $ text ( @ { $ rsp - > { sinfo } } ) { print "$text\r" ; $| + + ; } }
else { print ( $ rsp - > { sinfo } . "\r" ) ; $| + + ; }
}
2008-02-14 14:25:49 +00:00
# Handle {node} structure
2008-04-08 11:49:06 +00:00
my $ errflg = 0 ;
2008-02-18 15:57:25 +00:00
if ( scalar @ { $ rsp - > { node } } ) {
#print "printing node\n";
2008-02-14 14:25:49 +00:00
my $ nodes = ( $ rsp - > { node } ) ;
my $ node ;
2010-02-08 04:19:37 +00:00
foreach $ node ( @$ nodes ) {
2008-02-14 14:25:49 +00:00
my $ desc = $ node - > { name } - > [ 0 ] ;
if ( $ node - > { errorcode } ) {
if ( ref ( $ node - > { errorcode } ) eq 'ARRAY' ) { foreach my $ ecode ( @ { $ node - > { errorcode } } ) { $ xCAT:: Client:: EXITCODE |= $ ecode ; } }
else { $ xCAT:: Client:: EXITCODE |= $ node - > { errorcode } ; } # assume it is a non-reference scalar
}
2008-03-04 19:38:16 +00:00
if ( $ node - > { error } ) {
$ desc . = ": Error: " . $ node - > { error } - > [ 0 ] ;
2008-04-08 11:49:06 +00:00
$ errflg = 1 ;
2008-03-04 19:38:16 +00:00
}
2008-02-14 14:25:49 +00:00
if ( $ node - > { data } ) {
if ( ref ( \ ( $ node - > { data } - > [ 0 ] ) ) eq 'SCALAR' ) {
$ desc = $ desc . ": " . $ node - > { data } - > [ 0 ] ;
} else {
if ( $ node - > { data } - > [ 0 ] - > { desc } ) {
$ desc = $ desc . ": " . $ node - > { data } - > [ 0 ] - > { desc } - > [ 0 ] ;
}
if ( $ node - > { data } - > [ 0 ] - > { contents } ) {
$ desc = "$desc: " . $ node - > { data } - > [ 0 ] - > { contents } - > [ 0 ] ;
}
}
}
if ( $ desc ) {
2008-04-08 11:49:06 +00:00
if ( $ errflg == 1 ) {
print STDERR ( "$desc\n" ) ;
} else {
print "$desc\n" ;
}
2008-02-14 14:25:49 +00:00
}
}
}
# Handle {data} structure with no nodes
2008-02-21 21:10:35 +00:00
if ( $ rsp - > { data } ) {
2008-02-18 15:57:25 +00:00
#print "printing data\n";
2008-02-14 14:25:49 +00:00
my $ data = ( $ rsp - > { data } ) ;
my $ data_entry ;
foreach $ data_entry ( @$ data ) {
my $ desc ;
if ( ref ( \ ( $ data_entry ) ) eq 'SCALAR' ) {
$ desc = $ data_entry ;
} else {
if ( $ data_entry - > { desc } ) {
$ desc = $ data_entry - > { desc } - > [ 0 ] ;
}
if ( $ data_entry - > { contents } ) {
if ( $ desc ) {
$ desc = "$desc: " . $ data_entry - > { contents } - > [ 0 ] ;
} else {
$ desc = $ data_entry - > { contents } - > [ 0 ] ;
}
}
}
2008-02-21 21:10:35 +00:00
if ( $ desc ) { print "$desc\n" ; }
2008-02-14 14:25:49 +00:00
}
}
} # end of handle_response
2007-10-26 22:44:33 +00:00