2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2025-08-31 15:28:15 +00:00
Files
xcat-core/xCAT-server/lib/perl/xCAT/OPENBMC.pm

261 lines
8.5 KiB
Perl

#!/usr/bin/perl
## IBM(c) 2107 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT::OPENBMC;
BEGIN
{
$::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use strict;
use warnings "all";
use HTTP::Async;
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;
use xCAT::TableUtils;
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 $PYTHON_AGENT_FILE = "/opt/xcat/lib/python/agent/agent.py";
my $MSG_TYPE = "message";
my $DB_TYPE = "db";
my $lock_fd;
my $header = HTTP::Headers->new('Content-Type' => 'application/json');
sub new {
my $async = shift;
$async = shift if (($async) && ($async =~ /OPENBMC/));
my $url = shift;
my $content = shift;
my $method = 'POST';
my $id = send_request( $async, $method, $url, $content );
return $id;
}
sub send_request {
my $async = shift;
$async = shift if (($async) && ($async =~ /OPENBMC/));
my $method = shift;
my $url = shift;
my $content = shift;
my $request = HTTP::Request->new( $method, $url, $header, $content );
my $id = $async->add_with_opts($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 exists_python_agent {
if ( -e $PYTHON_AGENT_FILE) {
return 1;
}
return 0;
}
sub start_python_agent {
if (!defined(acquire_lock())) {
xCAT::MsgUtils->message("S", "start_python_agent() Error: Failed to acquire lock");
return undef;
}
my $fd;
open($fd, '>', $AGENT_SOCK_PATH) && close($fd);
my $pid = fork;
if (!defined $pid) {
xCAT::MsgUtils->message("S", "start_python_agent() 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 ($PYTHON_AGENT_FILE);
if (!defined($ret)) {
xCAT::MsgUtils->message("S", "start_python_agent() Error: Failed to start the xCAT Python agent.");
exit(1);
}
}
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::SvrUtils::sendmsg([ 1, $msg->{data} ], $callback, $msg->{node});
} 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 => ["OpenBMC management is using a Python framework. An error has occurred when trying to create socket $AGENT_SOCK_PATH."] }, $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));
# send length of data first
$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;
}
# send data
$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;
}
# receive the length of data
$sz = unpack('i', $buf);
# read data with length is $sz
$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 => ["Agent exited unexpectedly. See $PYTHON_LOG_PATH for details."] }, $callback);
xCAT::MsgUtils->message("I", { data => ["To revert to Perl framework: chdef -t site clustersite openbmcperl=ALL"] }, $callback);
}
}
#--------------------------------------------------------------------------------
=head3 run_cmd_in_perl
Check if specified command should run in perl
The policy is:
Get value from `openbmcperl`, `XCAT_OPENBMC_DEVEL`, agent.py:
1. If agent.py does not exist: ==> 1: Go Perl
2. If `openbmcperl` not set or doesn't contain command: ==> 0: Go Python
3. If `openbmcperl` lists the command OR set to "ALL" ==> 1: Go Perl
4. If command is one of unsupported commands AND
a. XCAT_OPENBMC_DEVEL = YES ==> 0: Go Python
b. XCAT_OPENBMC_DEVEL = NO or not set ==> 1: Go Perl
=cut
#--------------------------------------------------------------------------------
sub run_cmd_in_perl {
my ($class, $command, $env) = @_;
if (! -e $PYTHON_AGENT_FILE) {
return (1, ''); # Go Perl: agent file is not there
}
my @entries = xCAT::TableUtils->get_site_attribute("openbmcperl");
my $site_entry = $entries[0];
my $support_obmc = undef;
if (ref($env) eq 'ARRAY' and ref($env->[0]->{XCAT_OPENBMC_DEVEL}) eq 'ARRAY') {
$support_obmc = $env->[0]->{XCAT_OPENBMC_DEVEL}->[0];
} elsif (ref($env) eq 'ARRAY') {
$support_obmc = $env->[0]->{XCAT_OPENBMC_DEVEL};
} else {
$support_obmc = $env->{XCAT_OPENBMC_DEVEL};
}
if ($support_obmc and uc($support_obmc) ne 'YES' and uc($support_obmc) ne 'NO') {
return (-1, "Invalid value $support_obmc for XCAT_OPENBMC_DEVEL, only 'YES' and 'NO' are supported.");
}
if ($site_entry and ($site_entry =~ $command or uc($site_entry) eq "ALL")) {
return (1, ''); # Go Perl: command listed in "openbmcperl" or "ALL"
}
# List of commands currently not supported in Python
my @unsupported_in_python_commands = ('rflash', 'rspconfig', 'reventlog');
if ($command ~~ @unsupported_in_python_commands) {
# Command currently not supported in Python
if ($support_obmc and uc($support_obmc) eq 'YES') {
return (0, ''); # Go Python: unsuppored command, but XCAT_OPENBMC_DEVEL=YES overrides
} else {
return (1, ''); # Go Perl: unsuppored command
}
}
return (0, ''); # Go Python: default
}
1;