diff --git a/xCAT-server/lib/perl/xCAT/OPENBMC.pm b/xCAT-server/lib/perl/xCAT/OPENBMC.pm index 424f83c36..225d73216 100644 --- a/xCAT-server/lib/perl/xCAT/OPENBMC.pm +++ b/xCAT-server/lib/perl/xCAT/OPENBMC.pm @@ -16,6 +16,20 @@ use HTTP::Request; use HTTP::Headers; use HTTP::Cookies; use Data::Dumper; +use Time::HiRes qw(sleep time); +use JSON; +use File::Path; +use Fcntl ":flock"; +use IO::Socket::UNIX qw( SOCK_STREAM ); +use xCAT_monitoring::monitorctrl; + +my $LOCK_DIR = "/var/lock/xcat/"; +my $LOCK_PATH = "/var/lock/xcat/agent.lock"; +my $AGENT_SOCK_PATH = "/var/run/xcat/agent.sock"; +my $PYTHON_LOG_PATH = "/var/log/xcat/agent.log"; +my $MSG_TYPE = "message"; +my $DB_TYPE = "db"; +my $lock_fd; my $header = HTTP::Headers->new('Content-Type' => 'application/json'); @@ -43,4 +57,140 @@ sub send_request { return $id; } +# if lock is released unexpectedly, python side would aware of the error after +# getting this lock +sub acquire_lock { + mkpath($LOCK_DIR); + # always create a new lock file + unlink($LOCK_PATH); + open($lock_fd, ">>", $LOCK_PATH) or return undef; + flock($lock_fd, LOCK_EX) or return undef; + return $lock_fd; +} +sub start_python_agent { + my $agent_file = "/opt/xcat/lib/python/agent/agent.py"; + if (! -e $agent_file) { + xCAT::MsgUtils->message("S", "Error: '/opt/xcat/lib/python/agent/agent.py' does not exist"); + return undef; + } + + if (!defined(acquire_lock())) { + xCAT::MsgUtils->message("S", "Error: Faild to require lock"); + return undef; + } + my $fd; + open($fd, '>', $AGENT_SOCK_PATH) && close($fd); + my $pid = fork; + if (!defined $pid) { + xCAT::MsgUtils->message("S", "Error: Unable to fork process"); + return undef; + } + $SIG{CHLD} = 'DEFAULT'; + if (!$pid) { + # child + open($fd, ">>", $PYTHON_LOG_PATH) && close($fd); + open(STDOUT, '>>', $PYTHON_LOG_PATH) or die("open: $!"); + open(STDERR, '>>&', \*STDOUT) or die("open: $!"); + my $ret = exec ("/opt/xcat/lib/python/agent/agent.py"); + if (!defined($ret)) { + xCAT::MsgUtils->message("S", "Error: Failed to start python agent"); + } + } + return $pid; +} + +sub handle_message { + my ($data, $callback) = @_; + if($data->{type} eq $MSG_TYPE) { + my $msg = $data->{msg}; + if ($msg->{type} eq 'info') { + xCAT::MsgUtils->message("I", { data => [$msg->{data}] }, $callback); + } elsif ($msg->{type} eq 'warning') { + xCAT::MsgUtils->message("W", { data => [$msg->{data}] }, $callback); + } elsif ($msg->{type} eq 'error'){ + xCAT::MsgUtils->message("E", { data => [$msg->{data}] }, $callback); + } elsif ($msg->{type} eq 'syslog'){ + xCAT::MsgUtils->message("S", $msg->{data}); + } + } elsif ($data->{type} eq $DB_TYPE) { + my $attribute = $data->{attribute}; + if ($attribute->{name} eq 'status' and $attribute->{method} eq 'set' and $attribute->{type} eq 'node') { + my %new_status = ($attribute->{value} => [$attribute->{node}]); + xCAT_monitoring::monitorctrl::setNodeStatusAttributes(\%new_status, 1) + } + } +} + +sub submit_agent_request { + my ($pid, $req, $nodeinfo, $callback) = @_; + my $sock; + my $retry = 0; + while($retry < 30) { + $sock = IO::Socket::UNIX->new(Peer => $AGENT_SOCK_PATH, Type => SOCK_STREAM, Timeout => 10, Blocking => 1); + if (!defined($sock)) { + sleep(0.1); + } else { + last; + } + $retry++; + } + if (!defined($sock)) { + xCAT::MsgUtils->message("E", { data => ["Failed to connect to the agent"] }, $callback); + kill('TERM', $pid); + return; + } + my $xcatdebugmode = 0; + if ($::XCATSITEVALS{xcatdebugmode}) { $xcatdebugmode = $::XCATSITEVALS{xcatdebugmode} } + my %env_hash = (); + $env_hash{debugmode} = $xcatdebugmode; + my ($data, $sz, $ret, $buf); + $data->{module} = 'openbmc'; + $data->{command} = $req->{command}->[0]; + $data->{args} = $req->{arg}; + $data->{cwd} = $req->{cwd}; + $data->{nodes} = $req->{node}; + $data->{nodeinfo} = $nodeinfo; + $data->{envs} = \%env_hash; + $buf = encode_json($data); + $sz = pack('i', length($buf)); + $ret = $sock->send($sz); + if (!$ret) { + xCAT::MsgUtils->message("E", { data => ["Failed to send message to the agent"] }, $callback); + $sock->close(); + kill('TERM', $pid); + return; + } + $ret = $sock->send($buf); + if (!$ret) { + xCAT::MsgUtils->message("E", { data => ["Failed to send message to the agent"] }, $callback); + $sock->close(); + kill('TERM', $pid); + return; + } + while(1) { + $ret = $sock->recv($buf, 4); + if (!$ret) { + last; + } + $sz = unpack('i', $buf); + $ret = $sock->recv($buf, $sz); + if (!$ret) { + xCAT::MsgUtils->message("E", { data => ["receive data from python agent unexpectedly"] }, $callback); + last; + } + $data = decode_json($buf); + handle_message($data, $callback); + } + # no message received, the socket on the agent side should be closed. + $sock->close(); +} + +sub wait_agent { + my ($pid, $callback) = @_; + waitpid($pid, 0); + if ($? >> 8 != 0) { + xCAT::MsgUtils->message("E", { data => ["python agent exited unexpectedly"] }, $callback); + } +} + 1; diff --git a/xCAT-server/lib/xcat/plugins/openbmc.pm b/xCAT-server/lib/xcat/plugins/openbmc.pm index c101c570f..e087ed0bd 100644 --- a/xCAT-server/lib/xcat/plugins/openbmc.pm +++ b/xCAT-server/lib/xcat/plugins/openbmc.pm @@ -679,6 +679,21 @@ sub preprocess_request { } ############################################## + # Provide a way to change to python code before formal release + if (ref($request->{environment}) eq 'ARRAY' and ref($request->{environment}->[0]->{XCAT_OPENBMC_PYTHON}) eq 'ARRAY') { + $::OPENBMC_PYTHON = $request->{environment}->[0]->{XCAT_OPENBMC_PYTHON}->[0]; + } elsif (ref($request->{environment}) eq 'ARRAY') { + $::OPENBMC_PYTHON = $request->{environment}->[0]->{XCAT_OPENBMC_PYTHON}; + } else { + $::OPENBMC_PYTHON = $request->{environment}->{XCAT_OPENBMC_PYTHON}; + } + ############################################## + + if (defined($::OPENBMC_PYTHON) and $::OPENBMC_PYTHON eq "YES") { + $request = {}; + return; + } + $callback = shift; if ($::XCATSITEVALS{xcatdebugmode}) { $xcatdebugmode = $::XCATSITEVALS{xcatdebugmode} } diff --git a/xCAT-server/lib/xcat/plugins/openbmc2.pm b/xCAT-server/lib/xcat/plugins/openbmc2.pm new file mode 100644 index 000000000..97c16c794 --- /dev/null +++ b/xCAT-server/lib/xcat/plugins/openbmc2.pm @@ -0,0 +1,229 @@ +#!/usr/bin/perl +### IBM(c) 2017 EPL license http://www.eclipse.org/legal/epl-v10.html + +package xCAT_plugin::openbmc2; + +BEGIN + { + $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; + } +use lib "$::XCATROOT/lib/perl"; +use strict; +use warnings "all"; + +use JSON; +use Getopt::Long; +use xCAT::Utils; +use xCAT::Usage; +use xCAT::SvrUtils; +use xCAT::OPENBMC; + +#------------------------------------------------------- + +=head3 handled_commands + + Return list of commands handled by this plugin + +=cut + +#------------------------------------------------------- + +sub handled_commands { + return { + rpower => 'nodehm:mgt=openbmc', + }; +} + +my %node_info = (); +my $callback; + +#------------------------------------------------------- + +=head3 preprocess_request + + preprocess the command + +=cut + +#------------------------------------------------------- +sub preprocess_request { + my $request = shift; + $callback = shift; + + # if $::OPENBMC_PYTHON is 'YES', will run this script + if (ref($request->{environment}) eq 'ARRAY' and ref($request->{environment}->[0]->{XCAT_OPENBMC_PYTHON}) eq 'ARRAY') { + $::OPENBMC_PYTHON = $request->{environment}->[0]->{XCAT_OPENBMC_PYTHON}->[0]; + } elsif (ref($request->{environment}) eq 'ARRAY') { + $::OPENBMC_PYTHON = $request->{environment}->[0]->{XCAT_OPENBMC_PYTHON}; + } else { + $::OPENBMC_PYTHON = $request->{environment}->{XCAT_OPENBMC_PYTHON}; + } + + if (! (defined($::OPENBMC_PYTHON) and $::OPENBMC_PYTHON eq "YES")) { + $request = {}; + return; + } + + my $command = $request->{command}->[0]; + my $noderange = $request->{node}; + my $extrargs = $request->{arg}; + my @exargs = ($request->{arg}); + my @requests; + + if (ref($extrargs)) { + @exargs = @$extrargs; + } + my $usage_string = xCAT::Usage->parseCommand($command, @exargs); + if ($usage_string) { + $callback->({ data => [$usage_string] }); + $request = {}; + return; + } + + my $parse_result = parse_args($command, $extrargs, $noderange); + if (ref($parse_result) eq 'ARRAY') { + my $error_data; + foreach my $node (@$noderange) { + $error_data .= "\n" if ($error_data); + $error_data .= "$node: Error: " . "$parse_result->[1]"; + } + $callback->({ errorcode => [$parse_result->[0]], data => [$error_data] }); + $request = {}; + return; + } + + my $sn = xCAT::ServiceNodeUtils->get_ServiceNode($noderange, "xcat", "MN"); + foreach my $snkey (keys %$sn) { + my $reqcopy = {%$request}; + $reqcopy->{node} = $sn->{$snkey}; + $reqcopy->{'_xcatdest'} = $snkey; + $reqcopy->{_xcatpreprocessed}->[0] = 1; + push @requests, $reqcopy; + } + + return \@requests; +} + +#------------------------------------------------------- + +=head3 process_request + + Process the command + +=cut + +#------------------------------------------------------- +sub process_request { + my $request = shift; + $callback = shift; + my $noderange = $request->{node}; + my $check = parse_node_info($noderange); + $callback->({ errorcode => [$check] }) if ($check); + my $pid = xCAT::OPENBMC::start_python_agent(); + if (!defined($pid)) { + xCAT::MsgUtils->message("E", { data => ["Failed to start python agent"] }, $callback); + return; + } + + xCAT::OPENBMC::submit_agent_request($pid, $request, \%node_info, $callback); + xCAT::OPENBMC::wait_agent($pid, $callback); +} + +#------------------------------------------------------- + +=head3 parse_args + + Parse the command line options and operands + +=cut + +#------------------------------------------------------- +sub parse_args { + my $command = shift; + my $extrargs = shift; + my $noderange = shift; + my $subcommand = undef; + + if (scalar(@ARGV) != 2 and ($command =~ /rpower/)) { + return ([ 1, "Only one option is supported at the same time for $command" ]); + } else { + $subcommand = $ARGV[0]; + } + + if ($command eq "rpower") { + unless ($subcommand =~ /^on$|^off$|^softoff$|^reset$|^boot$|^bmcreboot$|^bmcstate$|^status$|^stat$|^state$/) { + return ([ 1, "Unsupported command: $command $subcommand" ]); + } + } else { + return ([ 1, "Unsupported command: $command" ]); + } +} + +#------------------------------------------------------- + +=head3 parse_node_info + + Parse the node information: bmc, bmcip, username, password + +=cut + +#------------------------------------------------------- +sub parse_node_info { + my $noderange = shift; + my $rst = 0; + + my $passwd_table = xCAT::Table->new('passwd'); + my $passwd_hash = $passwd_table->getAttribs({ 'key' => 'openbmc' }, qw(username password)); + + my $openbmc_table = xCAT::Table->new('openbmc'); + my $openbmc_hash = $openbmc_table->getNodesAttribs(\@$noderange, ['bmc', 'username', 'password']); + + foreach my $node (@$noderange) { + if (defined($openbmc_hash->{$node}->[0])) { + if ($openbmc_hash->{$node}->[0]->{'bmc'}) { + $node_info{$node}{bmc} = $openbmc_hash->{$node}->[0]->{'bmc'}; + $node_info{$node}{bmcip} = xCAT::NetworkUtils::getNodeIPaddress($openbmc_hash->{$node}->[0]->{'bmc'}); + } + unless($node_info{$node}{bmc}) { + xCAT::SvrUtils::sendmsg("Error: Unable to get attribute bmc", $callback, $node); + $rst = 1; + next; + } + unless($node_info{$node}{bmcip}) { + xCAT::SvrUtils::sendmsg("Error: Unable to resolve ip address for bmc: $node_info{$node}{bmc}", $callback, $node); + delete $node_info{$node}; + $rst = 1; + next; + } + if ($openbmc_hash->{$node}->[0]->{'username'}) { + $node_info{$node}{username} = $openbmc_hash->{$node}->[0]->{'username'}; + } elsif ($passwd_hash and $passwd_hash->{username}) { + $node_info{$node}{username} = $passwd_hash->{username}; + } else { + xCAT::SvrUtils::sendmsg("Error: Unable to get attribute username", $callback, $node); + delete $node_info{$node}; + $rst = 1; + next; + } + + if ($openbmc_hash->{$node}->[0]->{'password'}) { + $node_info{$node}{password} = $openbmc_hash->{$node}->[0]->{'password'}; + } elsif ($passwd_hash and $passwd_hash->{password}) { + $node_info{$node}{password} = $passwd_hash->{password}; + } else { + xCAT::SvrUtils::sendmsg("Error: Unable to get attribute password", $callback, $node); + delete $node_info{$node}; + $rst = 1; + next; + } + } else { + xCAT::SvrUtils::sendmsg("Error: Unable to get information from openbmc table", $callback, $node); + $rst = 1; + next; + } + } + + return $rst; +} + +1;