# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html package xCAT::PPC; use strict; use lib "/opt/xcat/lib/perl"; use xCAT::Table; use xCAT::Utils; use xCAT::SvrUtils; use xCAT::Usage; use POSIX "WNOHANG"; use Storable qw(freeze thaw); use Time::HiRes qw(gettimeofday sleep); use IO::Select; use Socket; use xCAT::PPCcli; use xCAT::GlobalDef; use xCAT::DBobjUtils; use xCAT_monitoring::monitorctrl; use Thread qw(yield); ########################################## # Globals ########################################## my %modules = ( rinv => { hmc => "xCAT::PPCinv", fsp => "xCAT::FSPinv", bpa => "xCAT::FSPinv", }, rpower => { hmc => "xCAT::PPCpower", fsp => "xCAT::FSPpower", bpa => "xCAT::FSPpower", }, rvitals => { hmc => "xCAT::PPCvitals", }, rscan => { hmc => "xCAT::PPCscan", }, mkvm => { hmc => "xCAT::PPCvm", }, rmvm => { hmc => "xCAT::PPCvm", }, lsvm => { hmc => "xCAT::PPCvm", }, chvm => { hmc => "xCAT::PPCvm", }, rnetboot => { hmc => "xCAT::PPCboot", }, getmacs => { hmc => "xCAT::PPCmac", }, reventlog => { hmc => "xCAT::PPClog", }, rspconfig => { hmc => "xCAT::PPCcfg", }, rflash => { hmc => "xCAT::PPCrflash", fsp => "xCAT::FSPflash", bpa => "xCAT::FSPflash", }, mkhwconn => { hmc => "xCAT::PPCconn", }, rmhwconn => { hmc => "xCAT::PPCconn", }, lshwconn => { hmc => "xCAT::PPCconn", }, renergy => { hmc => "xCAT::PPCenergy", }, ); ########################################## # Database errors ########################################## my %errmsg = ( NODE_UNDEF =>"Node not defined in '%s' database", NO_ATTR =>"'%s' not defined in '%s' database", NO_UNDEF =>"'%s' not defined in '%s' database for '%s'", DB_UNDEF =>"'%s' database not defined" ); ########################################################################## # Invokes the callback with the specified message ########################################################################## sub send_msg { my $request = shift; my $ecode = shift; my %output; ################################################# # Called from child process - send to parent ################################################# if ( exists( $request->{pipe} )) { my $out = $request->{pipe}; $output{errorcode} = $ecode; $output{data} = \@_; print $out freeze( [\%output] ); print $out "\nENDOFFREEZE6sK4ci\n"; } ################################################# # Called from parent - invoke callback directly ################################################# elsif ( exists( $request->{callback} )) { my $callback = $request->{callback}; $output{errorcode} = $ecode; $output{data} = \@_; $callback->( \%output ); } } ########################################################################## # Fork child to execute remote commands ########################################################################## sub process_command { my $request = shift; my %nodes = (); my $callback = $request->{callback}; my $sitetab = xCAT::Table->new( 'site' ); my @site = qw(ppcmaxp ppctimeout maxssh ppcretry fsptimeout); my $start; ####################################### # Default site table attributes ####################################### $request->{ppcmaxp} = 64; $request->{ppctimeout} = 0; $request->{fsptimeout} = 0; $request->{ppcretry} = 3; $request->{maxssh} = 8; ####################################### # Get site table attributes ####################################### if ( defined( $sitetab )) { foreach ( @site ) { my ($ent) = $sitetab->getAttribs({ key=>$_},'value'); if ( defined($ent) ) { $request->{$_} = $ent->{value}; } } } if ( exists( $request->{verbose} )) { $start = Time::HiRes::gettimeofday(); } #xCAT doesn't support FSPpower,FSPinv and FSPrflash by default $request->{fsp_api} = 0; ####################################### # FSPpower, FSPinv and FSPrflash handler ######################################### my $hwtype = $request->{hwtype}; if ( $hwtype eq "fsp" or $hwtype eq "bpa") { my $fsp_api = check_fsp_api($request); if($fsp_api == 0 && ($request->{command} =~ /^(rpower)$/ || $request->{command} =~ /^rinv$/ || $request->{command} =~ /^rflash$/)) { #support FSPpower, FSPinv and FSPrflash $request->{fsp_api} = 1; } } ####################################### # Group nodes based on command ####################################### my $nodes = preprocess_nodes( $request ); if ( !defined( $nodes )) { return(1); } #get new node status my %oldnodestatus=(); #saves the old node status my @allerrornodes=(); my $check=0; my $global_check=1; if ($sitetab) { (my $ref) = $sitetab->getAttribs({key => 'nodestatus'}, 'value'); if ($ref) { if ($ref->{value} =~ /0|n|N/) { $global_check=0; } } } my $command=$request->{command}; if (($command eq 'rpower') || ($command eq 'rnetboot')) { my $subcommand="temp"; if ($command eq 'rpower') { $subcommand=$request->{op}; } if (($global_check) && ($subcommand ne 'stat') && ($subcommand ne 'status') && ($subcommand ne 'state')) { $check=1; my $noderange = $request->{node}; my @allnodes=@$noderange; #save the old status my $nodelisttab = xCAT::Table->new('nodelist'); if ($nodelisttab) { my $tabdata = $nodelisttab->getNodesAttribs(\@allnodes, ['node', 'status']); foreach my $node (@allnodes) { my $tmp1 = $tabdata->{$node}->[0]; if ($tmp1) { if ($tmp1->{status}) { $oldnodestatus{$node}=$tmp1->{status}; } else { $oldnodestatus{$node}=""; } } } } #print "oldstatus:" . Dumper(\%oldnodestatus); #set the new status to the nodelist.status my %newnodestatus=(); my $newstat; if (($subcommand eq 'off') || ($subcommand eq 'softoff')) { my $newstat=$::STATUS_POWERING_OFF; $newnodestatus{$newstat}=\@allnodes; } else { #get the current nodeset stat if (@allnodes>0) { my $nsh={}; my ($ret, $msg)=xCAT::SvrUtils->getNodesetStates(\@allnodes, $nsh); if (!$ret) { foreach (keys %$nsh) { my $newstat=xCAT_monitoring::monitorctrl->getNodeStatusFromNodesetState($_, $command); $newnodestatus{$newstat}=$nsh->{$_}; } } else { trace( $request, $msg ); } } } #print "newstatus" . Dumper(\%newnodestatus); xCAT_monitoring::monitorctrl::setNodeStatusAttributes(\%newnodestatus, 1); } } ####################################### # Fork process ####################################### my $children = 0; my $fds = new IO::Select; # For the commands getmacs and rnetboot, each time # to fork process, pick out the HMC that has the # least process number created to connect to the HMC. # After the process by preprocess_nodes, the $nodes # variable has following structure: # $nodes # |hcp # |[[hcp,node1_attr], [hcp,node2_attr] ...] # |count //node number managed by the hcp # |runprocess //the process number connect to the hcp # |index //the index of node will be forked of the hcp if ( ($request->{command} =~ /^(getmacs)$/ && exists( $request->{opt}->{D} )) || ($request->{command} =~ /^(rnetboot)$/) ) { my %pid_owner = (); $request->{maxssh} = int($request->{maxssh}/2); # Use the CHID signal to control the #connection number of certain hcp $SIG{CHLD} = sub { my $pid = 0; while (($pid = waitpid(-1, WNOHANG)) > 0) { $nodes->{$pid_owner{$pid}}{'runprocess'}--; delete $pid_owner{$pid}; $children--; } }; $SIG{INT} = $SIG{TERM} = sub { #prepare to process job termination and propogate it down foreach (keys %pid_owner) { kill 9, $_; } exit 0; }; my $hasnode = 1; while ($hasnode) { while ( $children >= $request->{ppcmaxp} ) { my $handlednodes={}; child_response( $callback, $fds, $handlednodes); #update the node status to the nodelist.status table if ($check) { updateNodeStatus($handlednodes, \@allerrornodes); } Time::HiRes::sleep(0.1); } # Pick out the hcp which has least processes my $least_processes = $request->{maxssh}; my $least_hcp; my $got_one = 0; while (!$got_one) { $hasnode = 0; foreach my $hcp (keys %$nodes) { if ($nodes->{$hcp}{'index'} < $nodes->{$hcp}{'count'}) { $hasnode = 1; if ($nodes->{$hcp}{'runprocess'} < $least_processes) { $least_processes = $nodes->{$hcp}{'runprocess'}; $least_hcp = $hcp; } } } if (!$hasnode) { # There are no node in the $nodes goto ENDOFFORK; } if ($least_processes < $request->{maxssh}) { $got_one = 1; } else { my $handlednodes={}; child_response( $callback, $fds, $handlednodes); #update the node status to the nodelist.status table if ($check) { updateNodeStatus($handlednodes, \@allerrornodes); } Time::HiRes::sleep(0.1); } } Time::HiRes::sleep(0.1); my ($pipe, $pid) = fork_cmd( $nodes->{$least_hcp}{'nodegroup'}->[$nodes->{$least_hcp}{'index'}]->[0], $nodes->{$least_hcp}{'nodegroup'}->[$nodes->{$least_hcp}{'index'}]->[1], $request ); if ($pid) { $pid_owner{$pid} = $least_hcp; $nodes->{$least_hcp}{'index'}++; $nodes->{$least_hcp}{'runprocess'}++; } if ( $pipe ) { $fds->add( $pipe ); $children++; } } } elsif ( $request->{command} =~ /^(getmacs)$/ && exists( $request->{opt}->{arp} ) ) { my $node; my $data; my $unreachable_nodes; my $noderange = join (',', @$nodes); my @output = xCAT::Utils->runcmd("/opt/xcat/bin/pping $noderange", -1); foreach my $line (@output) { my ($hostname, $result) = split ':', $line; my ($token, $status) = split ' ', $result; chomp($token); if ($token eq 'ping') { $node->{$hostname}->{reachable} = 1; } } foreach my $n ( @$nodes ) { if ( $node->{$n}->{reachable} ) { my $output; my $IP = xCAT::Utils::toIP( $n ); if ( xCAT::Utils->isAIX() ) { $output = `/usr/sbin/arp -a`; } else { $output = `/sbin/arp -n`; } my ($ip, $mac); my @lines = split /\n/, $output; foreach my $line ( @lines ) { if ( xCAT::Utils->isAIX() && $line =~ /\((\S+)\)\s+at\s+(\S+)/ ) { ($ip, $mac) = ($1,$2); ###################################################### # Change mac format to be same as linux. For example: # '0:d:60:f4:f8:22' to '00:0d:60:f4:f8:22' ###################################################### if ( $mac) { my @mac_sections = split /:/, $mac; for my $m (@mac_sections) { $m = "0$m" if ( length($m) == 1); } $mac = join ':', @mac_sections; } } elsif ( $line =~ /^(\S+)+\s+\S+\s+(\S+)\s/ ) { ($ip, $mac) = ($1,$2); } else { ($ip, $mac) = (undef,undef); } if ( @$IP[1] !~ $ip ) { ($ip, $mac) = (undef,undef); } else { last; } } if ( $ip && $mac ) { if ( !exists( $request->{opt}->{d} ) ) { ##################################### # Write adapter mac to database ##################################### my $mactab = xCAT::Table->new( "mac", -create=>1, -autocommit=>1 ); $mactab->setNodeAttribs( $n,{mac=>$mac} ); $mactab->close(); } $callback->({node=>[{name=>[$n],data=>["\n#IP MAC\n$ip $mac\n"]}]}); } } else { $unreachable_nodes = join (",", $n, $unreachable_nodes); } } $callback->({data=>["Unreachable Nodes:"]}); $callback->({data=>["$unreachable_nodes\n"]}); } elsif ( $request->{command} =~ /^rpower$/ ) { my $hw; my $sessions; my $pid_owner; my $remain_node = $nodes; while ( scalar($remain_node) ) { $remain_node = (); foreach my $hash ( @$nodes ) { $SIG{CHLD} = sub { my $pid = 0; while (($pid = waitpid(-1, WNOHANG)) > 0) { $hw->{$pid_owner->{$pid}}--; $children--; } }; while ( $children >= $request->{ppcmaxp} ) { my $handlednodes={}; child_response( $callback, $fds, $handlednodes); #update the node status to the nodelist.status table if ($check) { updateNodeStatus($handlednodes, \@allerrornodes); } Time::HiRes::sleep(0.1); } if ( $hw->{@$hash[0]} >= $request->{maxssh} ) { my $handlednodes={}; child_response( $callback, $fds, $handlednodes); #update the node status to the nodelist.status table if ($check) { updateNodeStatus($handlednodes, \@allerrornodes); } Time::HiRes::sleep(0.1); push( @$remain_node, [@$hash[0], @$hash[1]] ); next; } my ($pipe,$pid) = fork_cmd( @$hash[0], @$hash[1], $request ); if ($pid) { $pid_owner->{$pid} = @$hash[0]; $hw->{@$hash[0]}++; } if ( $pipe ) { $fds->add( $pipe ); $children++; } } $nodes = $remain_node; } } else { $SIG{CHLD} = sub { while (waitpid(-1, WNOHANG) > 0) { $children--; } }; my $hw; my $sessions; foreach ( @$nodes ) { while ( $children >= $request->{ppcmaxp} ) { my $handlednodes={}; child_response( $callback, $fds, $handlednodes); #update the node status to the nodelist.status table if ($check) { updateNodeStatus($handlednodes, \@allerrornodes); } Time::HiRes::sleep(0.1); } ################################### # sleep between connects to same # HMC/IVM so as not to overwelm it ################################### if ( $hw ne @$_[0] ) { $sessions = 1; } elsif ( $sessions++ >= $request->{maxssh} ) { sleep(1); $sessions = 1; } $hw = @$_[0]; my ($pipe) = fork_cmd( @$_[0], @$_[1], $request ); if ( $pipe ) { $fds->add( $pipe ); $children++; } } } ENDOFFORK: ####################################### # Process responses from children ####################################### while ( $fds->count > 0 or $children > 0 ) { my $handlednodes={}; child_response( $callback, $fds, $handlednodes); #update the node status to the nodelist.status table if ($check) { updateNodeStatus($handlednodes, \@allerrornodes); } Time::HiRes::sleep(0.1); } #drain one more time my $rc=1; while ( $rc>0 ) { my $handlednodes={}; $rc=child_response( $callback, $fds, $handlednodes); #update the node status to the nodelist.status table if ($check) { updateNodeStatus($handlednodes, \@allerrornodes); } } if ( exists( $request->{verbose} )) { my $elapsed = Time::HiRes::gettimeofday() - $start; my $msg = sprintf( "Total Elapsed Time: %.3f sec\n", $elapsed ); trace( $request, $msg ); } if ($check) { #print "allerrornodes=@allerrornodes\n"; #revert the status back for there is no-op for the nodes my %old=(); foreach my $node (@allerrornodes) { my $stat=$oldnodestatus{$node}; if (exists($old{$stat})) { my $pa=$old{$stat}; push(@$pa, $node); } else { $old{$stat}=[$node]; } } xCAT_monitoring::monitorctrl::setNodeStatusAttributes(\%old, 1); } return(0); } ########################################################################## # updateNodeStatus ########################################################################## sub updateNodeStatus { my $handlednodes=shift; my $allerrornodes=shift; foreach my $node (keys(%$handlednodes)) { if ($handlednodes->{$node} == -1) { push(@$allerrornodes, $node); } } } ########################################################################## # Verbose mode (-V) ########################################################################## sub trace { my $request = shift; my $msg = shift; my ($sec,$min,$hour,$mday,$mon,$yr,$wday,$yday,$dst) = localtime(time); my $formatted = sprintf "%02d:%02d:%02d %5d %s", $hour,$min,$sec,$$,$msg; my $callback = $request->{callback}; $callback->( {data=>[$formatted]} ); } ########################################################################## # Send response from child process back to xCAT client ########################################################################## sub child_response { my $callback = shift; my $fds = shift; my $errornodes=shift; my @ready_fds = $fds->can_read(1); my $rc = @ready_fds; foreach my $rfh (@ready_fds) { my $data = <$rfh>; ################################# # Read from child process ################################# if ( defined( $data )) { while ($data !~ /ENDOFFREEZE6sK4ci/) { $data .= <$rfh>; } my $responses = thaw($data); foreach ( @$responses ) { #save the nodes that has errors for node status monitoring if ((exists($_->{errorcode})) && ($_->{errorcode} != 0)) { if ($errornodes) { $errornodes->{$_->{node}->[0]->{name}->[0]}=-1; } } else { if ($errornodes) { $errornodes->{$_->{node}->[0]->{name}->[0]}=1; } } $callback->( $_ ); } next; } ################################# # Done - close handle ################################# $fds->remove($rfh); close($rfh); } yield; #Try to avoid useless iterations as much as possible return $rc; } ########################################################################## # Finds attributes for given node is various databases ########################################################################## sub resolve_hcp { my $request = shift; my $noderange = shift; my @nodegroup = (); my $tab = ($request->{hwtype} eq "fsp" or $request->{hwtype} eq "bpa") ? "ppcdirect" : "ppchcp"; my $db = xCAT::Table->new( $tab ); #################################### # Database not defined #################################### if ( !defined( $db )) { send_msg( $request, 1, sprintf( $errmsg{DB_UNDEF}, $tab )); return undef; } #################################### # Process each node #################################### foreach my $hcp ( @$noderange ) { # my ($ent) = $db->getAttribs( {hcp=>$hcp},"hcp" ); # my ($ent) = $db->getNodeAttribs( $hcp, ["hcp"]); # if ( !defined( $ent )) { # my $msg = sprintf( "$hcp: $errmsg{NODE_UNDEF}", $tab ); # send_msg( $request, 1, $msg ); # next; # } ################################ # Get userid and password ################################ my @cred = xCAT::PPCdb::credentials( $hcp, $request->{hwtype} ); $request->{$hcp}{cred} = \@cred; ################################ # Save values ################################ push @nodegroup,[$hcp]; } return( \@nodegroup ); } ########################################################################## # Group nodes depending on command ########################################################################## sub preprocess_nodes { my $request = shift; my $noderange = $request->{node}; my $method = $request->{method}; my %nodehash = (); my @nodegroup = (); my %hcpgroup = (); my %tabs = (); my $netwk; ######################################## # Special cases # rscan - Nodes are hardware control pts # FSPpower, FSPinv and FSPrflash ######################################## if (( !$request->{hwcp} && ($request->{hwcp} ne "hmc" )) and ($request->{command} !~ /^renergy$/) and (( $request->{command} =~ /^(rscan|rspconfig)$/ ) or ($request->{hwtype} eq "fsp" or $request->{hwtype} eq "bpa" ) or ($request->{command} eq 'lshwconn' and $request->{nodetype} eq 'hmc')) and ($request->{fsp_api} != 1) ) { my $result = resolve_hcp( $request, $noderange ); return( $result ); } ########################################## # Special processing - rnetboot ########################################## if ( $request->{command} eq "rnetboot" ) { $netwk = resolve_netwk( $request, $noderange ); if ( !defined( %$netwk )) { return undef; } } ########################################## # Open databases needed ########################################## foreach ( qw(ppc vpd nodetype) ) { $tabs{$_} = xCAT::Table->new($_); if ( !exists( $tabs{$_} )) { send_msg( $request, 1, sprintf( $errmsg{DB_UNDEF}, $_ )); return undef; } } ########################################## # Group nodes ########################################## foreach my $node ( @$noderange ) { my $d = resolve( $request, $node, \%tabs ); ###################################### # Error locating node attributes ###################################### if ( ref($d) ne 'ARRAY' ) { send_msg( $request, 1, "$node: $d"); next; } ###################################### # Get data values ###################################### my $hcp = @$d[3]; my $mtms = @$d[2]; ###################################### # Special case for mkhwconn ###################################### if ( $request->{command} eq "mkhwconn" and exists $request->{opt}->{p}) { $nodehash{ $request->{opt}->{p}}{$mtms}{$node} = $d; } ###################################### #The common case ###################################### else { $nodehash{$hcp}{$mtms}{$node} = $d; } } ########################################## # Get userid and password ########################################## while (my ($hcp,$hash) = each(%nodehash) ) { my @cred; if ($request->{hwcp} && ($request->{hwcp} eq "hmc" )) { @cred = xCAT::PPCdb::credentials( $hcp, $request->{hcp} ); } else { @cred = xCAT::PPCdb::credentials( $hcp, $request->{hwtype} ); } $request->{$hcp}{cred} = \@cred; } ########################################## # Group the nodes - we will fork one # process per nodegroup array element. ########################################## ########################################## # These commands are grouped on an # LPAR-by-LPAR basis - fork one process # per LPAR. ########################################## if ( ($method =~ /^(getmacs)$/ && exists( $request->{opt}->{D} )) || ($method =~ /^(rnetboot)$/) ) { while (my ($hcp,$hash) = each(%nodehash) ) { @nodegroup = (); while (my ($mtms,$h) = each(%$hash) ) { while (my ($lpar,$d) = each(%$h)) { push @$d, $lpar; ########################## # Save network info ########################## if ( $method =~ /^rnetboot$/ ) { push @$d, $netwk->{$lpar}; } push @nodegroup,[$hcp,$d]; } } $hcpgroup{$hcp}{'nodegroup'} = [@nodegroup]; $hcpgroup{$hcp}{'count'} = $#nodegroup + 1; $hcpgroup{$hcp}{'runprocess'} = 0; $hcpgroup{$hcp}{'index'} = 0; } return( \%hcpgroup ); } elsif ( $method =~ /^(getmacs)$/ && exists( $request->{opt}->{arp} ) ) { return( $noderange ); } ########################################## # Power control commands are grouped # by CEC which is the smallest entity # that commands can be sent to in parallel. # If commands are sent in parallel to a # single CEC, the CEC itself will serialize # them - fork one process per CEC. ########################################## elsif ( $method =~ /^powercmd/ || $method =~ /^renergy/ ) { while (my ($hcp,$hash) = each(%nodehash) ) { while (my ($mtms,$h) = each(%$hash) ) { push @nodegroup,[$hcp,$h]; } } return( \@nodegroup ); } ########################################## # All other commands are grouped by # hardware control point - fork one # process per hardware control point. ########################################## while (my ($hcp,$hash) = each(%nodehash) ) { push @nodegroup,[$hcp,$hash]; } return( \@nodegroup ); } ########################################################################## # Finds attributes for given node is various databases ########################################################################## sub resolve_netwk { my $request = shift; my $noderange = shift; my %nethash = xCAT::DBobjUtils->getNetwkInfo( $noderange ); my $tab = xCAT::Table->new( 'mac' ); my %result = (); my $ip; ##################################### # Network attributes undefined ##################################### if ( !%nethash ) { send_msg( $request,1,sprintf( $errmsg{NODE_UNDEF}, "networks" )); return undef; } ##################################### # mac database undefined ##################################### if ( !defined( $tab )) { send_msg( $request, 1, sprintf( $errmsg{DB_UNDEF}, "mac" )); return undef; } foreach ( @$noderange ) { ################################# # Get gateway (-G) ################################# if ( !exists( $nethash{$_} )) { my $msg = sprintf( "$_: $errmsg{NODE_UNDEF}", "networks"); send_msg( $request, 1, $msg ); next; } my $gateway = $nethash{$_}{gateway}; if ( !defined( $gateway )) { my $msg = sprintf("$_: $errmsg{NO_ATTR}","gateway","networks"); send_msg( $request, 1, $msg ); next; } $ip = xCAT::Utils::toIP( $gateway ); if ( @$ip[0] != 0 ) { send_msg( $request, 1, "$_: Cannot resolve '$gateway'" ); next; } my $gateway_ip = @$ip[1]; ################################# # Get server (-S) ################################# my $server = xCAT::Utils->GetMasterNodeName( $_ ); if ( $server == 1 ) { send_msg( $request, 1, "$_: Unable to identify master" ); next; } $ip = xCAT::Utils::toIP( $server ); if ( @$ip[0] != 0 ) { send_msg( $request, 1, "$_: Cannot resolve '$server'" ); next; } my $server_ip = @$ip[1]; ################################# # Get client (-C) ################################# $ip = xCAT::Utils::toIP( $_ ); if ( @$ip[0] != 0 ) { send_msg( $request, 1, "$_: Cannot resolve '$_'" ); next; } my $client_ip = @$ip[1]; ################################# # Get mac-address (-m) ################################# my ($ent) = $tab->getNodeAttribs( $_, ['mac'] ); if ( !defined($ent) ) { my $msg = sprintf( "$_: $errmsg{NO_ATTR}","mac","mac"); send_msg( $request, 1, $msg ); next; } ################################# # Save results ################################# $result{$_}{gateway} = $gateway_ip; $result{$_}{server} = $server_ip; $result{$_}{client} = $client_ip; $result{$_}{mac} = $ent->{mac}; } return( \%result ); } ########################################################################## # Finds attributes for given node is various databases ########################################################################## sub resolve { my $request = shift; my $node = shift; my $tabs = shift; my @attribs = qw(id pprofile parent hcp); my @values = (); ################################# # Get node type ################################# my $ent = $tabs->{nodetype}->getNodeAttribs($node,[qw(nodetype node)]); if ( !defined( $ent )) { return( sprintf( $errmsg{NODE_UNDEF}, "nodetype" )); } ################################# # Check for type ################################# if ( !exists( $ent->{nodetype} )) { return( sprintf( $errmsg{NO_ATTR}, "nodetype","nodetype" )); } ################################# # Check for valid "type" ################################# my ($type) = grep( /^$::NODETYPE_LPAR|$::NODETYPE_OSI|$::NODETYPE_BPA|$::NODETYPE_FSP$/, split /,/, $ent->{nodetype} ); if ( !defined( $type )) { return( "Invalid node type: $ent->{nodetype}" ); } ################################# # Get attributes ################################# my ($att) = $tabs->{ppc}->getNodeAttribs( $node, \@attribs ); if ( !defined( $att )) { return( sprintf( $errmsg{NODE_UNDEF}, "ppc" )); } ################################# # Special lpar processing ################################# if ( $type =~ /^$::NODETYPE_OSI|$::NODETYPE_LPAR$/ ) { $att->{bpa} = 0; $att->{type} = "lpar"; $att->{node} = $att->{parent}; if ( !exists( $att->{parent} )) { return( sprintf( $errmsg{NO_ATTR}, "parent", "ppc" )); } ############################# # Get BPA (if any) ############################# if (( $request->{command} eq "rvitals" ) && ( $request->{method} =~ /^all|temp$/ )) { my ($ent) = $tabs->{ppc}->getNodeAttribs( $att->{parent},['parent']); ############################# # Find MTMS in vpd database ############################# if (( defined( $ent )) && exists( $ent->{parent} )) { my @attrs = qw(mtm serial); my ($vpd) = $tabs->{vpd}->getNodeAttribs($ent->{parent},\@attrs); ######################## # Verify attributes ######################## foreach ( @attrs ) { if ( !defined( $vpd ) || !exists( $vpd->{$_} )) { return( sprintf( $errmsg{NO_UNDEF}, $_, "vpd", $ent->{parent} )); } } $att->{bpa} = "$vpd->{mtm}*$vpd->{serial}"; } } } ################################# # Optional and N/A fields ################################# elsif ( $type =~ /^$::NODETYPE_FSP$/ ) { $att->{pprofile} = 0; $att->{id} = 0; $att->{fsp} = 0; $att->{node} = $node; $att->{type} = $type; $att->{parent} = exists($att->{parent}) ? $att->{parent} : 0; $att->{bpa} = $att->{parent}; } elsif ( $type =~ /^$::NODETYPE_BPA$/ ) { $att->{pprofile} = 0; $att->{id} = 0; $att->{bpa} = 0; $att->{parent} = 0; $att->{fsp} = 0; $att->{node} = $node; $att->{type} = $type; } ################################# # Find MTMS in vpd database ################################# my @attrs = qw(mtm serial); my ($vpd) = $tabs->{vpd}->getNodeAttribs($att->{node}, \@attrs ); if ( !defined( $vpd )) { return( sprintf( $errmsg{NODE_UNDEF}, "vpd: ($att->{node})" )); } ################################ # Verify both vpd attributes ################################ foreach ( @attrs ) { if ( !exists( $vpd->{$_} )) { return( sprintf( $errmsg{NO_ATTR}, $_, "vpd: ($att->{node})" )); } } $att->{fsp} = "$vpd->{mtm}*$vpd->{serial}"; ################################# # Verify required attributes ################################# foreach my $at ( @attribs ) { if ( !exists( $att->{$at} )) { return( sprintf( $errmsg{NO_ATTR}, $at, "ppc" )); } } ################################# # Build array of data ################################# foreach ( qw(id pprofile fsp hcp type bpa) ) { push @values, $att->{$_}; } return( \@values ); } ########################################################################## # Forks a process to run the ssh command ########################################################################## sub fork_cmd { my $host = shift; my $nodes = shift; my $request = shift; ####################################### # Pipe childs output back to parent ####################################### my $parent; my $child; pipe $parent, $child; my $pid = xCAT::Utils->xfork; if ( !defined($pid) ) { ################################### # Fork error ################################### send_msg( $request, 1, "Fork error: $!" ); return undef; } elsif ( $pid == 0 ) { ################################### # Child process ################################### close( $parent ); $request->{pipe} = $child; invoke_cmd( $host, $nodes, $request ); exit(0); } else { ################################### # Parent process ################################### close( $child ); return( $parent, $pid ); } return(0); } ########################################################################## # Run the command, process the response, and send to parent ########################################################################## sub invoke_cmd { my $host = shift; my $nodes = shift; my $request = shift; my $hwtype = $request->{hwtype}; my $verbose = $request->{verbose}; my $cmd = $request->{command}; my $power = $request->{hwcp}; my @exp; my $verbose_log; my @outhash; ######################################## # If the request command is renergy, just # uses the xCAT CIM Client to handle it ######################################## if ( $request->{command} eq "renergy" ) { my $result = &runcmd($request, $host, $nodes); ######################################## # Format and send back to parent ######################################## foreach my $line ( @$result ) { my %output; $output{node}->[0]->{name}->[0] = @$line[0]; $output{node}->[0]->{data}->[0]->{contents}->[0] = @$line[1]; $output{errorcode} = @$line[2]; push @outhash, \%output; } my $out = $request->{pipe}; print $out freeze( [@outhash] ); print $out "\nENDOFFREEZE6sK4ci\n"; return; } ######################################## # Direct-attached FSP handler ######################################## if ( ($power ne "hmc") && ( $hwtype eq "fsp" or $hwtype eq "bpa") && $request->{fsp_api} == 0) { #################################### # Dynamically load FSP module #################################### eval { require xCAT::PPCfsp }; if ( $@ ) { send_msg( $request, 1, $@ ); return; } my @exp = xCAT::PPCfsp::connect( $request, $host ); #################################### # Error connecting #################################### if ( ref($exp[0]) ne "LWP::UserAgent" ) { send_msg( $request, 1, $exp[0] ); return; } my $result = xCAT::PPCfsp::handler( $host, $request, \@exp ); #################################### # Output verbose Perl::LWP #################################### if ( $verbose ) { $verbose_log = $exp[3]; my %output; $output{data} = [$$verbose_log]; unshift @$result, \%output; } my $out = $request->{pipe}; print $out freeze( $result ); print $out "\nENDOFFREEZE6sK4ci\n"; return; } ######################################## # HMC and IVM-managed handler # Connect to list of remote servers ######################################## foreach ( split /,/, $host ) { if ( $power ne "hmc" ) { @exp = xCAT::PPCcli::connect( $request, $hwtype, $_ ); } else { @exp = xCAT::PPCcli::connect( $request, $power, $_); } #################################### # Successfully connected #################################### if ( ref($exp[0]) eq "Expect" ) { last; } } ######################################## # Error connecting ######################################## if ( ref($exp[0]) ne "Expect" ) { send_msg( $request, 1, $exp[0] ); return; } ######################################## # Process specific command ######################################## my $result = runcmd( $request, $nodes, \@exp ); ######################################## # Close connection to remote server ######################################## xCAT::PPCcli::disconnect( \@exp ); ######################################## # Get verbose Expect output ######################################## if ( $verbose ) { $verbose_log = $exp[6]; } ######################################## # Return error ######################################## if ( ref($result) ne 'ARRAY' ) { send_msg( $request, 1, $$verbose_log.$result ); return; } ######################################## # Prepend verbose output ######################################## if ( defined( $verbose_log )) { my %output; $output{data} = [$$verbose_log]; push @outhash, \%output; } ######################################## # Send result back to parent process ######################################## if ( @$result[0] eq "FORMATDATA6sK4ci" ) { my $out = $request->{pipe}; push @outhash, @$result[1]; print $out freeze( [@outhash] ); print $out "\nENDOFFREEZE6sK4ci\n"; return; } ######################################## # Format and send back to parent ######################################## foreach ( @$result ) { my %output; $output{node}->[0]->{name}->[0] = @$_[0]; $output{node}->[0]->{data}->[0]->{contents}->[0] = @$_[1]; $output{errorcode} = @$_[2]; push @outhash, \%output; } my $out = $request->{pipe}; print $out freeze( [@outhash] ); print $out "\nENDOFFREEZE6sK4ci\n"; } ########################################################################## # Run the command method specified ########################################################################## sub runcmd { my $request = shift; my $cmd = $request->{command}; my $method = $request->{method}; my $hwtype = $request->{hwtype}; #my $modname = $modules{$cmd}; my $modname = $modules{$cmd}{$hwtype}; ###################################### # Command not supported ###################################### if ( !defined( $modname )) { return( ["$cmd not a supported command by $hwtype method"] ); } ###################################### # Load specific module ###################################### eval "require $modname"; if ( $@ ) { return( [$@] ); } ###################################### # Invoke method ###################################### no strict 'refs'; my $result = ${$modname."::"}{$method}->($request,@_); use strict; return( $result ); } ########################################################################## # Pre-process request from xCat daemon. Send the request to the the service # nodes of the HCPs. ########################################################################## sub preprocess_request { my $package = shift; my $req = shift; #if ($req->{_xcatdest}) { return [$req]; } #exit if preprocessed if ($req->{_xcatpreprocessed}->[0] == 1 ) { return [$req]; } my $callback = shift; my @requests; ##################################### # Parse arguments ##################################### my $opt = parse_args($package, $req, $callback); if ( ref($opt) eq 'ARRAY' ) { send_msg( $req, 1, @$opt ); return(1); } $req->{opt} = $opt; #################################### # Get hwtype #################################### $package =~ s/xCAT_plugin:://; #################################### # Prompt for usage if needed and on MN #################################### my $noderange = $req->{node}; #Should be arrayref my $command = $req->{command}->[0]; my $extrargs = $req->{arg}; my @exargs=($req->{arg}); if (ref($extrargs)) { @exargs=@$extrargs; } if ($ENV{'XCATBYPASS'}){ my $usage_string=xCAT::Usage->parseCommand($command, @exargs); if ($usage_string) { $callback->({data=>[$usage_string]}); $req = {}; return ; } if (!$noderange) { $usage_string="Missing noderange"; $callback->({data=>[$usage_string]}); $req = {}; return ; } } ################################################################## # get the HCPs for the LPARs in order to figure out which service # nodes to send the requests to ################################################################### my $hcptab_name = ($package eq "fsp" or $package eq "bpa") ? "ppcdirect" : "ppchcp"; my $hcptab = xCAT::Table->new( $hcptab_name ); unless ($hcptab ) { $callback->({data=>["Cannot open $hcptab_name table"]}); $req = {}; return; } # Check if each node is hcp my %hcp_hash=(); my @missednodes=(); foreach ( @$noderange ) { my ($ent) = $hcptab->getAttribs( {hcp=>$_},"hcp" ); if ( !defined( $ent )) { push @missednodes, $_; next; } push @{$hcp_hash{$_}{nodes}}, $_; } #check if the left-over nodes are lpars if (@missednodes > 0) { my $ppctab = xCAT::Table->new("ppc"); unless ($ppctab) { $callback->({data=>["Cannot open ppc table"]}); $req = {}; return; } foreach my $node (@missednodes) { my $ent=$ppctab->getNodeAttribs($node,['hcp']); if (defined($ent->{hcp})) { push @{$hcp_hash{$ent->{hcp}}{nodes}}, $node;} else { $callback->({data=>["The node $node is neither a hcp nor an lpar"]}); $req = {}; return; } } } # find service nodes for the HCPs # build an individual request for each service node my $service = "xcat"; my @hcps=keys(%hcp_hash); my $sn = xCAT::Utils->get_ServiceNode(\@hcps, $service, "MN"); # build each request for each service node foreach my $snkey (keys %$sn) { #$callback->({data=>["The service node $snkey "]}); my $reqcopy = {%$req}; $reqcopy->{'_xcatdest'} = $snkey; $reqcopy->{_xcatpreprocessed}->[0] = 1; my $hcps1=$sn->{$snkey}; my @nodes=(); foreach (@$hcps1) { push @nodes, @{$hcp_hash{$_}{nodes}}; } $reqcopy->{node} = \@nodes; #print "nodes=@nodes\n"; push @requests, $reqcopy; } return \@requests; } #################################### # Parse arguments #################################### sub parse_args { my $package = shift; my $req = shift; my $callback= shift; $package =~ s/xCAT_plugin:://; # if ( exists $req->{opt}) # { # return $req->{opt}; # } ################################# # To match the old logic ################################## my $command = $req->{command}->[0]; my $stdin = $req->{stdin}->[0]; $req->{command} = $command; $req->{stdin} = $stdin; $req->{hwtype} = $package; $req->{callback} = $callback; $req->{method} = "parse_args"; my $opt = runcmd( $req); $req->{command} = [ $command]; $req->{stdin} = [ $stdin]; return $opt; } ########################################################################## # Process request from xCat daemon ########################################################################## sub process_request { my $package = shift; my $req = shift; my $callback = shift; my $subreq = shift; #################################### # Get hwtype #################################### $package =~ s/xCAT_plugin:://; #################################### # Build hash to pass around #################################### my $request = {%$req}; $request->{command} = $req->{command}->[0]; $request->{stdin} = $req->{stdin}->[0]; # $request->{hwtype} = $package; $request->{callback}= $callback; $request->{subreq} = $subreq; ######################### #This is a special case for rspconfig and mkhwconn, #we shouldn't set hwtype as$package. and reserved for other commands. #probably for all HW ctrl commands it still true? ######################### if($request->{command} ne "rspconfig" and $request->{command} ne "mkhwconn") { $request->{hwtype} = $package; } #################################### # Option -V for verbose output #################################### if ( exists( $request->{opt}->{V} )) { $request->{verbose} = 1; } #################################### # Process remote command #################################### process_command( $request ); } ########################################################################## # connect hmc via ssh and execute remote command ########################################################################## sub sshcmds_on_hmc { my $ip = shift; my $user = shift; my $password = shift; my @cmds = @_; my %handled; my @data; my @exp; for my $cmd (@cmds) { if ( $cmd =~ /(.+?)=(.*)/) { my ($command,$value) = ($1,$2); $handled{$command} = $value; } } my %request = ( ppcretry => 1, verbose => 0, ppcmaxp => 64, ppctimeout => 0, fsptimeout => 0, ppcretry => 3, maxssh => 8 ); my $valid_ip; foreach my $individual_ip ( split /,/, $ip ) { ################################ # Get userid and password ################################ my @cred = xCAT::PPCdb::credentials( $individual_ip, "hmc" ); $request{$individual_ip}{cred} = \@cred; @exp = xCAT::PPCcli::connect( \%request, 'hmc', $individual_ip); #################################### # Successfully connected #################################### if ( ref($exp[0]) eq "Expect" ) { $valid_ip = $individual_ip; last; } } ######################################## # Error connecting ######################################## if ( ref($exp[0]) ne "Expect" ) { return ([1,@cmds]); } ######################################## # Process specific command ######################################## for my $cmd ( keys %handled) { my $result; if ($cmd eq 'network_reset') { $result = xCAT::PPCcli::network_reset( \@exp, $valid_ip, $handled{$cmd}); my $RC = shift( @$result); } push @data, @$result[0]; } ######################################## # Close connection to remote server ######################################## xCAT::PPCcli::disconnect( \@exp ); return ([0, undef, \@data]); } ########################################################################## # logon asm and update configuration ########################################################################## sub updconf_in_asm { my $ip = shift; my $target_dev = shift; my @cmds = @_; eval { require xCAT::PPCfsp }; if ( $@ ) { return ([1,@cmds]); } my %handled; for my $cmd (@cmds) { if ( $cmd =~ /(.+?)=(.*)/) { my ($command,$value) = ($1,$2); $handled{$command} = $value; } } my %request = ( ppcretry => 1, verbose => 0, ppcmaxp => 64, ppctimeout => 0, fsptimeout => 0, ppcretry => 3, maxssh => 8, arg => \@cmds, method => \%handled, command => 'rspconfig', hwtype => lc($target_dev->{'type'}), ); my $valid_ip; my @exp; foreach my $individual_ip ( split /,/, $ip ) { ################################ # Get userid and password ################################ my @cred = xCAT::PPCdb::credentials( $individual_ip, lc($target_dev->{'type'})); $request{$individual_ip}{cred} = \@cred; $request{node} = [$individual_ip]; @exp = xCAT::PPCfsp::connect(\%request, $individual_ip); #################################### # Successfully connected #################################### if ( ref($exp[0]) eq "LWP::UserAgent" ) { $valid_ip = $individual_ip; last; } } #################################### # Error connecting #################################### if ( ref($exp[0]) ne "LWP::UserAgent" ) { return ([1,@cmds]); } my $result = xCAT::PPCfsp::handler( $valid_ip, \%request, \@exp ); my $RC = shift( @$result); my @data; push @data, @$result[0]; return ([0, undef, \@data]); } sub check_fsp_api { my $request = shift; # my $fsp_api = "/opt/xcat/sbin/fsp-api"; my $fsp_api = ($::XCATROOT) ? "$::XCATROOT/sbin/fsp-api" : "/opt/xcat/sbin/fsp-api"; # my $libfsp = "/usr/lib/libfsp.a"; my $libfsp = "/opt/xcat/lib/libfsp.a"; # my $libfsp = ($::XCATROOT) ? "$::XCATROOT/lib/libfsp.a" : "/opt/xcat/lib/libfsp.a"; # my $hw_svr = "/opt/csm/csmbin/hdwr_svr"; my $msg = (); # if((-e $fsp_api) && (-x $fsp_api)&& (-e $libfsp) && (-e $hw_svr)) { if((-e $fsp_api) && (-x $fsp_api)&& (-e $libfsp)) { return 0; } return -1; } 1;