# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html #------------------------------------------------------- =head1 xCAT plugin package to handle BMC discovery =cut #------------------------------------------------------- package xCAT_plugin::bmcdiscover; BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; } use lib "$::XCATROOT/lib/perl"; use IO::Socket; use Thread qw(yield); use POSIX "WNOHANG"; use Storable qw(store_fd fd_retrieve); use strict; use warnings "all"; use Getopt::Long; use xCAT::Table; use xCAT::Utils; use xCAT::MsgUtils; use Getopt::Long; use Data::Dumper; use File::Basename; use File::Path; use Cwd; my $nmap_path; my $debianflag = 0; my $tempstring = xCAT::Utils->osver(); if ( $tempstring =~ /debian/ || $tempstring =~ /ubuntu/ ){ $debianflag = 1; } my $parent_fd; my $bmc_user; my $bmc_pass; #------------------------------------------------------- =head3 handled_commands Return list of commands handled by this plugin =cut #------------------------------------------------------- sub handled_commands { return { bmcdiscover => "bmcdiscover", }; } #------------------------------------------------------- =head3 process_request Process the command =cut #------------------------------------------------------- sub process_request { my $request = shift; my $callback = shift; my $request_command = shift; $::CALLBACK = $callback; #$::args = $request->{arg}; unless(defined($request->{arg})){ bmcdiscovery_usage(); return 2; } @ARGV = @{$request->{arg}}; if($#ARGV eq -1){ return 2; } my $command = $request->{command}->[0]; my $rc; if ($command eq "bmcdiscover"){ $rc = bmcdiscovery($request, $callback, $request_command); } else{ $callback->({error=>["Error: $command not found in this module."],errorcode=>[1]}); return 1; } } #---------------------------------------------------------------------------- =head3 bmcdiscover_usage Display the bmcdiscover usage =cut #----------------------------------------------------------------------------- sub bmcdiscovery_usage { my $rsp; push @{ $rsp->{data} }, "\nUsage: bmcdiscover - discover bmc using scan method,now scan_method can be nmap .\n"; push @{ $rsp->{data} }, "\n - check if BMC username or password is correct or not .\n"; push @{ $rsp->{data} }, "\n - get BMC IP Address source, DHCP Address or static Address .\n"; push @{ $rsp->{data} }, "\tbmcdiscover [-h|--help|-?]\n"; push @{ $rsp->{data} }, "\tbmcdiscover [-v|--version]\n "; push @{ $rsp->{data} }, "\tbmcdiscover [-s] scan_method [--range] ip_range [-z] [-w] [-t]\n "; push @{ $rsp->{data} }, "\tbmcdiscover [-i|--bmcip] bmc_ip [-u|--bmcuser] bmcusername [-p|--bmcpwd] bmcpassword [-c|--check]\n "; push @{ $rsp->{data} }, "\tbmcdiscover [-i|--bmcip] bmc_ip [-u|--bmcuser] bmcusername [-p|--bmcpwd] bmcpassword [--ipsource]\n "; push @{ $rsp->{data} }, "\tFor example: \n "; push @{ $rsp->{data} }, "\t1, bmcdiscover -s nmap --range \"10.4.23.100-254 50.3.15.1-2\" \n "; push @{ $rsp->{data} }, "\t Note : ip_range should be a string, can pass hostnames, IP addresses, networks, etc. \n "; push @{ $rsp->{data} }, "\t If there is bmc,bmcdiscover returns bmc ip, or else, it returns null. \n "; push @{ $rsp->{data} }, "\t Ex: scanme.nmap.org, microsoft.com/24, 192.168.0.1; 10.0.0-255.1-254 \n "; push @{ $rsp->{data} }, "\t2, bmcdiscover -s nmap --range \"10.4.23.100-254 50.3.15.1-2\" -z \n "; push @{ $rsp->{data} }, "\t3, bmcdiscover -s nmap --range \"10.4.23.100-254 50.3.15.1-2\" -w \n "; push @{ $rsp->{data} }, "\t4, bmcdiscover -i -u -p -c\n "; push @{ $rsp->{data} }, "\t Note : check if bmc username and password are correct or not. \n"; push @{ $rsp->{data} }, "\t5, bmcdiscover -i -u -p --ipsource\n "; xCAT::MsgUtils->message( "I", $rsp, $::CALLBACK ); return 0; } #---------------------------------------------------------------------------- =head3 bmcdiscovery_processargs Process the bmcdiscovery command line Returns: 0 - OK 1 - just print version 2 - just print help 3 - error =cut #----------------------------------------------------------------------------- sub bmcdiscovery_processargs { #if ( defined ($::args) && @{$::args} ){ # @ARGV = @{$::args}; #} my $request = shift; my $callback = shift; my $request_command = shift; my $rc = 0; # parse the options # options can be bundled up like -v, flag unsupported options Getopt::Long::Configure( "bundling", "no_ignore_case", "no_pass_through" ); my $getopt_success = Getopt::Long::GetOptions( 'help|h|?' => \$::opt_h, 's=s' => \$::opt_M, 'm=s' => \$::opt_M, 'range=s' => \$::opt_R, 'bmcip|i=s' => \$::opt_I, 'z' => \$::opt_Z, 'w' => \$::opt_W, 'check|c' => \$::opt_C, 'bmcuser|u=s' => \$::opt_U, 'bmcpwd|p=s' => \$::opt_P, 'ipsource' => \$::opt_S, 'version|v' => \$::opt_v, 't' => \$::opt_T, ); if (!$getopt_success) { return 3; } ######################################### # This command is for linux ######################################### if ($^O ne 'linux') { my $rsp = {}; push @{ $rsp->{data}}, "The bmcdiscovery command is only supported on Linux.\n"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 1; } ########################################## # Option -h for Help ########################################## if ( defined($::opt_h) ) { bmcdiscovery_usage; return 0; } ######################################### # Option -v for version ######################################### if ( defined($::opt_v) ) { create_version_response('bmcdiscover'); # no usage - just exit return 1; } ######################################### # Option -s -r should be together ###################################### if ( defined($::opt_R) ) { ###################################### # check if there is nmap or not ###################################### if ( -x '/usr/bin/nmap' ) { $nmap_path="/usr/bin/nmap"; } elsif ( -x '/usr/local/bin/nmap' ) { $nmap_path="/usr/local/bin/nmap"; } else { my $rsp; push @{ $rsp->{data} }, "\tThere is no nmap in /usr/bin/ or /usr/local/bin/. \n "; xCAT::MsgUtils->message( "E", $rsp, $::CALLBACK ); return 1; } ($bmc_user, $bmc_pass) = bmcaccount_from_passwd(); if ($::opt_P) { $bmc_pass = $::opt_P; if (!$::opt_U) { $bmc_user = ''; } else { $bmc_user = $::opt_U; } } scan_process($::opt_M,$::opt_R,$::opt_Z,$::opt_W,$request_command); return 0; } ######################################### # Option -i -u -p -c should be used together ###################################### if ( defined($::opt_C) ) { if ( defined($::opt_P) && defined($::opt_I) ) { if ( defined($::opt_U) ) { my $res=check_auth_process($::opt_I,$::opt_U,$::opt_P); return $res; } else { my $res=check_auth_process($::opt_I,"none",$::opt_P); return $res; } } else { my $msg = "bmc_ip or bmcuser or bmcpw is empty."; my $rsp = {}; push @{ $rsp->{data} }, "$msg"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 2; } } ######################################### # Option -i -u -p -s should be used together ###################################### if ( defined($::opt_S) ) { if ( defined($::opt_P) && defined($::opt_I) ) { if ( defined($::opt_U)) { my $res=get_bmc_ip_source($::opt_I,$::opt_U,$::opt_P); return $res; } else { my $res=get_bmc_ip_source($::opt_I,"none",$::opt_P); return $res; } } else { my $msg = "Can not get BMC IP Address source."; my $rsp = {}; push @{ $rsp->{data} }, "$msg"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 2; } } ######################################### # Other attributes are not allowed ######################################### return 4; } #---------------------------------------------------------------------------- =head3 get_bmc_ip_source get bmc ip address source Returns: 0 - OK 2 - Error =cut #----------------------------------------------------------------------------- sub get_bmc_ip_source{ my $bmcip = shift; my $bmcuser = shift; my $bmcpw = shift; my $callback = $::CALLBACK; my $bmcerror = "Can not find IP Address Source."; my $ipsource_t = "IP Address Source"; my $pcmd; if ( $bmcuser eq "none" ) { $pcmd = "/opt/xcat/bin/ipmitool-xcat -I lanplus -P $bmcpw -H $bmcip lan print "; } else { $pcmd = "/opt/xcat/bin/ipmitool-xcat -I lanplus -U $bmcuser -P $bmcpw -H $bmcip lan print "; } my $output = xCAT::Utils->runcmd("$pcmd", -1); if ( $output !~ $ipsource_t ) { my $rsp = {}; push @{ $rsp->{data} }, "$bmcerror"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 2; } else { my $rsp = {}; my $ipsource=`echo "$output"|grep "IP Address Source"|awk -F":" '{print \$2}'`; chomp($ipsource); push @{ $rsp->{data} }, "$ipsource"; xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); return 0; } } #---------------------------------------------------------------------------- =head3 check_auth_process check bmc user and password Returns: 0 - OK 2 - Error =cut #----------------------------------------------------------------------------- sub check_auth_process{ my $bmcip = shift; my $bmcuser = shift; my $bmcpw = shift; my $bmstr1 = "RAKP 2 message indicates an error : unauthorized name"; my $bmstr2 = "RAKP 2 HMAC is invalid"; my $bmstr3 = "Set Session Privilege Level to ADMINISTRATOR"; my $bmstr31 = "Correct ADMINISTRATOR"; my $bmstr21 = "Wrong bmc password"; my $bmstr11 = "Wrong bmc user"; my $bmcerror = "Not bmc"; my $othererror = "Check bmc first"; my $bmstr4 = "BMC Session ID"; my $callback = $::CALLBACK; my $icmd; if ( $bmcuser eq "none" ) { $icmd = "/opt/xcat/bin/ipmitool-xcat -vv -I lanplus -P $bmcpw -H $bmcip chassis status "; } else { $icmd = "/opt/xcat/bin/ipmitool-xcat -vv -I lanplus -U $bmcuser -P $bmcpw -H $bmcip chassis status "; } my $output = xCAT::Utils->runcmd("$icmd", -1); if ( $output =~ $bmstr1 ) { my $rsp = {}; push @{ $rsp->{data} }, "$bmstr11"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 2; } elsif ( $output =~ $bmstr2 ) { my $rsp = {}; push @{ $rsp->{data} }, "$bmstr21"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 2; } elsif ( $output =~ $bmstr3 ) { my $rsp = {}; push @{ $rsp->{data} }, "$bmstr31"; xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); return 0; } elsif ( $output !~ $bmstr4 ) { my $rsp = {}; push @{ $rsp->{data} }, "$bmcerror"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 2; } else { my $rsp = {}; push @{ $rsp->{data} }, "$othererror"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 2; } } #---------------------------------------------------------------------------- =head3 scan_process Process the bmcdiscovery command line Returns: 0 - OK 1 - just print version 2 - just print help 3 - error =cut #----------------------------------------------------------------------------- sub scan_process{ my $method = shift; my $range = shift; my $opz = shift; my $opw = shift; my $request_command = shift; my $callback = $::CALLBACK; my $children; # The number of child process my %sp_children; # Record the pid of child process my $sub_fds = new IO::Select; # Record the parent fd for each child process if ( !defined($method) ) { $method="nmap"; } my $ip_list; ############################################################ # get live ip list ########################################################### if ( $method eq "nmap" ) { my $bcmd = join(" ",$nmap_path," -sn -n $range | grep for |cut -d ' ' -f5 |tr -s '\n' ' ' "); $ip_list = xCAT::Utils->runcmd("$bcmd", -1); if ($::RUNCMD_RC != 0) { my $rsp = {}; push @{ $rsp->{data} }, "Nmap scan is failed.\n"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 2; } } else { my $rsp = {}; push @{ $rsp->{data}}, "The bmcdiscover method should be nmap.\n"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 2; } my $live_ip=split_comma_delim_str($ip_list); if ( scalar (@{$live_ip}) > 0 ) { ############################### # Set the signal handler for ^c ############################### $SIG{TERM} = $SIG{INT} = sub { foreach (keys %sp_children) { kill 2, $_; } $SIG{ALRM} = sub { while (wait() > 0) { yield; } exit @_; }; alarm(1); # wait 1s for grace exit }; ###################################################### # Set the singal handler for child process finished it's work ###################################################### $SIG{CHLD} = sub { my $cpid; while (($cpid = waitpid(-1, WNOHANG)) > 0) { if ($sp_children{$cpid}) { delete $sp_children{$cpid}; $children--; } } }; for (my $i = 0; $i < scalar (@{$live_ip}); $i ++) { # fork a sub process to handle the communication with service processor $children++; my $cfd; # the $parent_fd will be used by &send_rep() to send response from child process to parent process socketpair($parent_fd, $cfd,AF_UNIX,SOCK_STREAM,PF_UNSPEC) or die "socketpair: $!"; $cfd->autoflush(1); $parent_fd->autoflush(1); my $child = xCAT::Utils->xfork; if ($child == 0) { close($cfd); $callback = \&send_rep; bmcdiscovery_ipmi(${$live_ip}[$i],$opz,$opw,$request_command); exit 0; } else { # in the main process, record the created child process and add parent fd for the child process to an IO:Select object # the main process will check all the parent fd and receive response $sp_children{$child}=1; close ($parent_fd); $sub_fds->add($cfd); } do { sleep(1); } until ($children < 32); } ################################################# # receive data from child processes ################################################ while ($sub_fds->count > 0 or $children > 0) { forward_data($callback,$sub_fds); } while (forward_data($callback,$sub_fds)) { } } else { my $rsp = {}; push @{ $rsp->{data}}, "No bmc found.\n"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 2; } } #---------------------------------------------------------------------------- =head3 format_stanza list the stanza format for node Arguments: bmc ip Returns: lists as stanza format for nodes =cut #-------------------------------------------------------------------------------- sub format_stanza { my $node = shift; my $data = shift; my ($bmcip,$bmcmtm,$bmcserial,$bmcuser,$bmcpass,$nodetype,$hwtype) = split(/,/,$data); my $result; if (defined($bmcip)){ $result .= "$node:\n\tobjtype=node\n"; $result .= "\tgroups=all\n"; $result .= "\tbmc=$bmcip\n"; $result .= "\tcons=ipmi\n"; $result .= "\tmgt=ipmi\n"; if ($bmcmtm) { $result .= "\tmtm=$bmcmtm\n"; } if ($bmcserial) { $result .= "\tserial=$bmcserial\n"; } if ($bmcuser) { $result .= "\tbmcusername=$bmcuser\n"; } if ($bmcpass) { $result .= "\tbmcpassword=$bmcpass\n"; } if ($nodetype && $hwtype) { $result .= "\tnodetype=$nodetype\n"; $result .= "\thwtype=$hwtype\n"; } my $rsp = {}; push @{ $rsp->{data} }, "$result"; xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); } return ($result); } #---------------------------------------------------------------------------- =head3 write_to_xcatdb write node definition into xcatdb Arguments: $node_stanza: Returns: =cut #-------------------------------------------------------------------------------- sub write_to_xcatdb { my $node = shift; my $data = shift; my ($bmcip,$bmcmtm,$bmcserial,$bmcuser,$bmcpass,$nodetype,$hwtype) = split(/,/,$data); my $request_command = shift; my $ret; $ret = xCAT::Utils->runxcmd({ command => ['chdef'], arg => ['-t','node','-o',$node,"bmc=$bmcip","cons=ipmi","mgt=ipmi","mtm=$bmcmtm","serial=$bmcserial","bmcusername=$bmcuser","bmcpassword=$bmcpass","nodetype=$nodetype","hwtype=$hwtype","groups=all"] }, $request_command, 0, 1); if ($::RUNCMD_RC != 0) { my $rsp = {}; push @{ $rsp->{data} }, "create or modify node is failed.\n"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); return 2; } } #---------------------------------------------------------------------------- =head3 send_rep DESCRIPTION: Send date from forked child process to parent process. This subroutine will be replace the original $callback in the forked child process ARGUMENTS: $resp - The response which generated in xCAT::Utils->message(); =cut #---------------------------------------------------------------------------- sub send_rep { my $resp=shift; unless ($resp) { return; } store_fd($resp,$parent_fd); } #---------------------------------------------------------------------------- =head3 forward_data DESCRIPTION: Receive data from forked child process and call the original $callback to forward data to xcat client =cut #---------------------------------------------------------------------------- sub forward_data { my $callback = shift; my $fds = shift; my @ready_fds = $fds->can_read(1); my $rfh; my $rc = @ready_fds; foreach $rfh (@ready_fds) { my $data; my $responses; eval { $responses = fd_retrieve($rfh); }; if ($@ and $@ =~ /^Magic number checking on storable file/) { #this most likely means we ran over the end of available input $fds->remove($rfh); close($rfh); } else { eval { print $rfh "ACK\n"; }; #Ignore ack loss due to child giving up and exiting, we don't actually explicitly care about the acks $callback->($responses); } } yield; #Try to avoid useless iterations as much as possible return $rc; } #---------------------------------------------------------------------------- =head3 split_comma_delim_str Split comma-delimited list of strings into an array. Arguments: comma-delimited string Returns: Returns list of strings (ref) =cut #----------------------------------------------------------------------------- sub split_comma_delim_str { my $input_str = shift; my @result = split(/ /, $input_str); return \@result; } #---------------------------------------------------------------------------- =head3 create_version_response Create a response containing the command name and version =cut #----------------------------------------------------------------------------- sub create_version_response { my $command = shift; my $rsp; my $version = xCAT::Utils->Version(); push @{ $rsp->{data} }, "$command - xCAT $version"; xCAT::MsgUtils->message( "I", $rsp, $::CALLBACK ); } #---------------------------------------------------------------------------- =head3 create_error_response Create a response containing a single error message Arguments: error message =cut #----------------------------------------------------------------------------- sub create_error_response { my $error_msg = shift; my $rsp; push @{ $rsp->{data} }, $error_msg; xCAT::MsgUtils->message( "I", $rsp, $::CALLBACK ); } #---------------------------------------------------------------------------- =head3 bmcdiscovery Support for discovering bmc Returns: 0 - OK 1 - help 2 - error =cut #----------------------------------------------------------------------------- sub bmcdiscovery { my $request = shift; my $callback = shift; my $request_command = shift; my $rc = 0; ############################################################## # process the command line # 0=success, 1=version, 2=error for check_auth_, other=error ############################################################## $rc = bmcdiscovery_processargs($request,$callback,$request_command); if ( $rc != 0 ) { if ( $rc != 1 ) { if ( $rc !=2 ) { bmcdiscovery_usage(@_); } } return ( $rc - 1 ); } #scan_process($::opt_M,$::opt_R); return $rc; } #---------------------------------------------------------------------------- =head3 get bmc account in passwd table Returns: username/password pair Notes: The default username/password is ADMIN/admin =cut #---------------------------------------------------------------------------- sub bmcaccount_from_passwd { my $bmcusername = "ADMIN"; my $bmcpassword = "admin"; my $passwdtab = xCAT::Table->new("passwd", -create=>0); if ($passwdtab) { my $bmcentry = $passwdtab->getAttribs({'key'=>'ipmi'},'username','password'); if (defined($bmcentry)) { $bmcusername = $bmcentry->{'username'}; $bmcpassword = $bmcentry->{'password'}; unless ($bmcusername) { $bmcusername = ''; } unless ($bmcpassword) { $bmcpassword = ''; } } } return ($bmcusername,$bmcpassword); } #---------------------------------------------------------------------------- =head3 bmcdiscovery_ipmi Support for discovering bmc using ipmi Returns: if it is bmc, it returns bmc ip or host; if it is not bmc, it returns nothing; =cut #----------------------------------------------------------------------------- sub bmcdiscovery_ipmi { my $ip = shift; my $opz = shift; my $opw = shift; my $request_command = shift; my $node = sprintf("node-%08x", unpack("N*", inet_aton($ip))); my $bmcstr = "BMC Session ID"; my $bmcusername = ''; my $bmcpassword = ''; if ($bmc_user) { $bmcusername = "-U $bmc_user"; } if ($bmc_pass) { $bmcpassword = "-P $bmc_pass"; } my $icmd = "/opt/xcat/bin/ipmitool-xcat -vv -I lanplus $bmcusername $bmcpassword -H $ip chassis status "; my $output = xCAT::Utils->runcmd("$icmd", -1); if ( $output =~ $bmcstr ){ # The output contains System Power indicated the username/password is correct, then try to get MTMS if ($output =~ /System Power\s*:\s*\S*/) { my $mtm = ''; my $serial = ''; # For system X and Tuleta, the fru 0 will contain the MTMS my $fru0_cmd = "/opt/xcat/bin/ipmitool-xcat -I lanplus $bmcusername $bmcpassword -H $ip fru print 0"; my @fru0_output_array = xCAT::Utils->runcmd($fru0_cmd, -1); my $fru0_output = join(" ", @fru0_output_array); if (($fru0_output =~ /Product Part Number :\s*(\S*).*Product Serial :\s*(\S*)/)) { $mtm = $1; $serial = $2; } else { # For firestone and habanero, the fru 3 will contain the MTMS my $fru3_cmd = "/opt/xcat/bin/ipmitool-xcat -I lanplus $bmcusername $bmcpassword -H $ip fru print 3"; my @fru3_output_array = xCAT::Utils->runcmd($fru3_cmd, -1); my $fru3_output = join(" ", @fru3_output_array); if (($fru3_output =~ /Chassis Part Number\s*:\s*(\S*).*Chassis Serial\s*:\s*(\S*)/)) { $mtm = $1; $serial = $2; } } $ip .= ",$mtm"; $ip .= ",$serial"; if ($::opt_P) { if ($::opt_U) { $ip .= ",$::opt_U,$::opt_P"; } else { $ip .= ",,$::opt_P"; } } else { $ip .= ",,"; } if ($::opt_T) { $ip .= ",mp,bmc"; } if ($mtm and $serial) { $node = "node-$mtm-$serial"; $node =~ s/(.*)/\L$1/g; } } elsif ($output =~ /error : unauthorized name/){ xCAT::MsgUtils->message("I", {data=>["Warning: bmc username is incorrect for $node"]}, $::CALLBACK); } elsif ($output =~ /RAKP \S* \S* is invalid/) { xCAT::MsgUtils->message("I", {data=>["Warning: bmc password is incorrect for $node"]}, $::CALLBACK); } else { xCAT::MsgUtils->message("I", {data=>["Warning: other error for $node"]}, $::CALLBACK); } if ( defined($opz) || defined($opw) ) { format_stanza($node, $ip); if (defined($opw)) { write_to_xcatdb($node, $ip,$request_command); } } else{ my $rsp = {}; push @{ $rsp->{data} }, "$ip"; xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); } } } 1;