2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2025-07-01 10:25:33 +00:00

Merge pull request #2573 from xuweibj/openbmc

framework for openbmc support, support some rpower and rinv commands
This commit is contained in:
zet809
2017-03-15 16:20:37 +08:00
committed by GitHub
3 changed files with 692 additions and 1 deletions

View File

@ -443,6 +443,19 @@ passed as argument rather than by table value',
disable => "Set to 'yes' or '1' to comment out this row.",
},
},
openbmc => {
cols => [qw(node bmc username password comments disable)],
keys => [qw(node)],
table_desc => 'Setting for nodes that are controlled by an on-board OpenBmc.',
descriptions => {
node => 'The node name or group name.',
bmc => 'The hostname of the BMC adapter.',
username => 'The BMC userid.',
password => 'The BMC password.',
comments => 'Any user-written notes.',
disable => "Set to 'yes' or '1' to comment out this row.",
},
},
iscsi => {
cols => [qw(node server target lun iname file userid passwd kernel kcmdline initrd comments disable)],
keys => [qw(node)],
@ -586,7 +599,7 @@ passed as argument rather than by table value',
descriptions => {
node => 'The node name or group name.',
power => 'The method to use to control the power of the node. If not set, the mgt attribute will be used. Valid values: ipmi, blade, hmc, ivm, fsp, kvm, esx, rhevm. If "ipmi", xCAT will search for this node in the ipmi table for more info. If "blade", xCAT will search for this node in the mp table. If "hmc", "ivm", or "fsp", xCAT will search for this node in the ppc table.',
mgt => 'The method to use to do general hardware management of the node. This attribute is used as the default if power or getmac is not set. Valid values: ipmi, blade, hmc, ivm, fsp, bpa, kvm, esx, rhevm. See the power attribute for more details.',
mgt => 'The method to use to do general hardware management of the node. This attribute is used as the default if power or getmac is not set. Valid values: openbmc, ipmi, blade, hmc, ivm, fsp, bpa, kvm, esx, rhevm. See the power attribute for more details.',
cons => 'The console method. If nodehm.serialport is set, this will default to the nodehm.mgt setting, otherwise it defaults to unused. Valid values: cyclades, mrv, or the values valid for the mgt attribute.',
termserver => 'The hostname of the terminal server.',
termport => 'The port number on the terminal server that this node is connected to.',
@ -2447,6 +2460,26 @@ my @nodeattrs = (
tabentry => 'mpa.urlpath',
access_tabentry => 'mpa.mpa=attr:node',
},
#########################
# openbmc table #
##########################
{ attr_name => 'bmc',
only_if => 'mgt=openbmc',
tabentry => 'openbmc.bmc',
access_tabentry => 'openbmc.node=attr:node',
},
{ attr_name => 'bmcusername',
only_if => 'mgt=openbmc',
tabentry => 'openbmc.username',
access_tabentry => 'openbmc.node=attr:node',
},
{ attr_name => 'bmcpassword',
only_if => 'mgt=openbmc',
tabentry => 'openbmc.password',
access_tabentry => 'openbmc.node=attr:node',
},
######################
# nodepos table #
######################

View File

@ -0,0 +1,46 @@
#!/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;
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;
}
1;

View File

@ -0,0 +1,612 @@
#!/usr/bin/perl
## IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT_plugin::openbmc;
BEGIN
{
$::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use strict;
use warnings "all";
use HTTP::Async;
use HTTP::Cookies;
use xCAT::OPENBMC;
use xCAT::Utils;
use xCAT::Table;
use xCAT::Usage;
use xCAT::SvrUtils;
use File::Basename;
use Data::Dumper;
use JSON;
$::OPENBMC_DEVEL = $ENV{'OPENBMC_DEVEL'};
#-------------------------------------------------------
=head3 handled_commands
Return list of commands handled by this plugin
=cut
#-------------------------------------------------------
sub handled_commands {
return {
rpower => 'nodehm:mgt',
rinv => 'nodehm:mgt',
};
}
my $pre_url = "/org/openbmc";
#-------------------------------------------------------
# The hash table to store method and url for request,
# process function for response
#-------------------------------------------------------
my %status_info = (
LOGIN_REQUEST => {
method => "POST",
init_url => "/login",
},
LOGIN_RESPONSE => {
process => \&login_response,
},
RPOWER_ON_REQUEST => {
method => "PUT",
init_url => "/xyz/openbmc_project/state/host0/attr/RequestedHostTransition",
data => "xyz.openbmc_project.State.Host.Transition.On",
},
RPOWER_ON_RESPONSE => {
process => \&rpower_response,
},
RPOWER_OFF_REQUEST => {
method => "PUT",
init_url => "/xyz/openbmc_project/state/host0/attr/RequestedHostTransition",
data => "xyz.openbmc_project.State.Host.Transition.Off",
},
RPOWER_OFF_RESPONSE => {
process => \&rpower_response,
},
RPOWER_RESET_REQUEST => {
method => "PUT",
init_url => "/xyz/openbmc_project/state/host0/attr/RequestedHostTransition",
data => "xyz.openbmc_project.State.Host.Transition.Reboot",
},
RPOWER_RESET_RESPONSE => {
process => \&rpower_response,
},
RPOWER_STATUS_REQUEST => {
method => "GET",
init_url => "/xyz/openbmc_project/state/host0",
},
RPOWER_STATUS_RESPONSE => {
process => \&rpower_response,
},
RINV_REQUEST => {
method => "GET",
init_url => "$pre_url/inventory/enumerate",
},
RINV_RESPONSE => {
process => \&rinv_response,
},
);
#-----------------------------
=head3 %node_info
$node_info = (
$node => {
bmc => "x.x.x.x",
username => "username",
password => "password",
cur_status => "LOGIN_REQUEST",
cur_url => "",
method => "",
back_urls => (),
},
);
'cur_url', 'method', 'back_urls' used for path has a trailing-slash
=cut
#-----------------------------
my %node_info = ();
my %next_status = ();
my %handle_id_node = ();
my $wait_node_num;
my $async;
my $cookie_jar;
my $callback;
#-------------------------------------------------------
=head3 preprocess_request
preprocess the command
=cut
#-------------------------------------------------------
sub preprocess_request {
my $request = shift;
if (defined $request->{_xcatpreprocessed}->[0] and $request->{_xcatpreprocessed}->[0] == 1) {
return [$request];
}
$callback = shift;
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);
if (ref($parse_result) eq 'ARRAY') {
$callback->({ error => $parse_result->[1], errorcode => $parse_result->[0] });
$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;
my $noderange = $request->{node};
parse_node_info($noderange);
$cookie_jar = HTTP::Cookies->new({});
$async = HTTP::Async->new(
cookie_jar => $cookie_jar,
ssl_options => {
SSL_verify_mode => 0,
},
);
my $bmcip;
my $login_url;
my $handle_id;
my $content;
$wait_node_num = keys %node_info;
foreach my $node (keys %node_info) {
$bmcip = $node_info{$node}{bmc};
$login_url = "https://$bmcip/login";
$content = '{"data": [ "' . $node_info{$node}{username} .'", "' . $node_info{$node}{password} . '" ] }';
$handle_id = xCAT::OPENBMC->new($async, $login_url, $content);
$handle_id_node{$handle_id} = $node;
$node_info{$node}{cur_status} = $next_status{ $node_info{$node}{cur_status} };
print "$node: DEBUG POST $login_url -d $content\n";
}
while (1) {
last unless ($wait_node_num);
while (my ($response, $handle_id) = $async->wait_for_next_response) {
deal_with_response($handle_id, $response);
}
}
return;
}
#-------------------------------------------------------
=head3 parse_args
Parse the command line options and operands
=cut
#-------------------------------------------------------
sub parse_args {
my $command = shift;
my $extrargs = shift;
my $check = unsupported($callback);
if (ref($check) eq "ARRAY") {
return $check;
}
$next_status{LOGIN_REQUEST} = "LOGIN_RESPONSE";
if ($command eq "rpower") {
if (!defined($extrargs)) {
return ([ 1, "No option specified for rpower" ]);
}
if (scalar(@ARGV) > 1) {
return ([ 1, "Only one option is supportted at the same time" ]);
}
my $subcommand = $ARGV[0];
if ($subcommand eq "on") {
$next_status{LOGIN_RESPONSE} = "RPOWER_ON_REQUEST";
$next_status{RPOWER_ON_REQUEST} = "RPOWER_ON_RESPONSE";
} elsif ($subcommand eq "off") {
$next_status{LOGIN_RESPONSE} = "RPOWER_OFF_REQUEST";
$next_status{RPOWER_OFF_REQUEST} = "RPOWER_OFF_RESPONSE";
} elsif ($subcommand eq "reset") {
$next_status{LOGIN_RESPONSE} = "RPOWER_RESET_REQUEST";
$next_status{RPOWER_RESET_REQUEST} = "RPOWER_RESET_RESPONSE";
} elsif ($subcommand eq "status" or $subcommand eq "state" or $subcommand eq "stat") {
$next_status{LOGIN_RESPONSE} = "RPOWER_STATUS_REQUEST";
$next_status{RPOWER_STATUS_REQUEST} = "RPOWER_STATUS_RESPONSE";
} elsif ($subcommand eq "boot") {
$next_status{LOGIN_RESPONSE} = "RPOWER_STATUS_REQUEST";
$next_status{RPOWER_STATUS_REQUEST} = "RPOWER_STATUS_RESPONSE";
$next_status{RPOWER_STATUS_RESPONSE}{OFF} = "RPOWER_ON_REQUEST";
$next_status{RPOWER_ON_REQUEST} = "RPOWER_ON_RESPONSE";
$next_status{RPOWER_STATUS_RESPONSE}{ON} = "RPOWER_RESET_REQUEST";
$next_status{RPOWER_RESET_REQUEST} = "RPOWER_RESET_RESPONSE";
} else {
return ([ 1, "$subcommand is not supported for rpower" ]);
}
}
if ($command eq "rinv") {
if (!defined($extrargs)) {
return ([ 1, "No option specified for rpower" ]);
}
if (scalar(@ARGV) > 1) {
return ([ 1, "Only one option is supportted at the same time" ]);
}
my $subcommand = $ARGV[0];
if ($subcommand eq "cpu" or $subcommand eq "dimm" or $subcommand eq "bios" or $subcommand eq "all") {
$next_status{LOGIN_RESPONSE} = "RINV_REQUEST";
$next_status{RINV_REQUEST} = "RINV_RESPONSE";
$status_info{RINV_RESPONSE}{argv} = "$subcommand";
} else {
return ([ 1, "Only 'cpu','dimm', 'bios','all' are supportted currently" ]);
}
}
print Dumper(\%next_status) . "\n";
return;
}
sub unsupported {
my $callback = shift;
if ($::OPENBMC_DEVEL ne "YES") {
return ([ 1, "This function is currently not supported" ]);
} else {
xCAT::SvrUtils::sendmsg("Warning: Currently running development code, use at your own risk\n", $callback);
return;
}
}
#-------------------------------------------------------
=head3 parse_node_info
Parse the node information: bmc, username, password
=cut
#-------------------------------------------------------
sub parse_node_info {
my $noderange = shift;
my $table = xCAT::Table->new('openbmc');
my $tablehash = $table->getNodesAttribs(\@$noderange, ['bmc', 'username', 'password']);
foreach my $node (@$noderange) {
if (defined($tablehash->{$node}->[0])) {
if ($tablehash->{$node}->[0]->{'bmc'}) {
$node_info{$node}{bmc} = $tablehash->{$node}->[0]->{'bmc'};
} else {
xCAT::SvrUtils::sendmsg("Unable to get attribute bmc", $callback, $node);
next;
}
if ($tablehash->{$node}->[0]->{'username'}) {
$node_info{$node}{username} = $tablehash->{$node}->[0]->{'username'};
} else {
xCAT::SvrUtils::sendmsg("Unable to get attribute username", $callback, $node);
delete $node_info{$node};
next;
}
if ($tablehash->{$node}->[0]->{'password'}) {
$node_info{$node}{password} = $tablehash->{$node}->[0]->{'password'};
} else {
xCAT::SvrUtils::sendmsg("Unable to get attribute password", $callback, $node);
delete $node_info{$node};
next;
}
$node_info{$node}{cur_status} = "LOGIN_REQUEST";
} else {
xCAT::SvrUtils::sendmsg("Unable to get information from openbmc table", $callback, $node);
next;
}
}
print Dumper(\%node_info) ."\n";
return;
}
#-------------------------------------------------------
=head3 gen_send_request
Generate request's information
If the node has method itself, use it as request's method.
If not, use method %status_info defined.
If the node has cur_url, check whether also has sub_urls.
If has, request's url is join cur_url and one in sub_urls(use one at once to check which is needed).
If not, use method %status_info defined.
use xCAT::OPENBMC->send_request send request
store handle_id and mapping node
Input:
$node: nodename of current node
=cut
#-------------------------------------------------------
sub gen_send_request {
my $node = shift;
my $method;
my $request_url;
my $content;
if ($node_info{$node}{method}) {
$method = $node_info{$node}{method};
} else {
$method = $status_info{ $node_info{$node}{cur_status} }{method};
}
if ($status_info{ $node_info{$node}{cur_status} }{data}) {
$content = '{"data":"' . $status_info{ $node_info{$node}{cur_status} }{data} . '"}';
}
if ($node_info{$node}{cur_url}) {
$request_url = $node_info{$node}{cur_url};
} else {
$request_url = $status_info{ $node_info{$node}{cur_status} }{init_url};
}
$request_url = "https://" . $node_info{$node}{bmc} . $request_url;
my $handle_id = xCAT::OPENBMC->send_request($async, $method, $request_url, $content);
$handle_id_node{$handle_id} = $node;
$node_info{$node}{cur_status} = $next_status{ $node_info{$node}{cur_status} };
my $debug_info;
if ($method eq "GET") {
$debug_info = "$node: DEBUG $method $request_url";
} else {
$debug_info = "$node: DEBUG $method $request_url -d $content";
}
print "$debug_info\n";
return;
}
#-------------------------------------------------------
=head3 deal_with_response
Check response's status_line and
Input:
$handle_id: Async return ID with response
$response: Async return response
=cut
#-------------------------------------------------------
sub deal_with_response {
my $handle_id = shift;
my $response = shift;
my $node = $handle_id_node{$handle_id};
delete $handle_id_node{$handle_id};
if ($response->status_line ne "200 OK") {
my $error;
if ($response->status_line eq "503 Service Unavailable") {
$error = "Service Unavailable";
} else {
my $response_info = decode_json $response->content;
if ($response->status_line eq "500 Internal Server Error") {
$error = $response_info->{'data'}->{'exception'};
} elsif ($response_info->{'data'}->{'description'} =~ /path or object not found: (.+)/) {
$error = "path or object not found $1";
} else {
$error = $response_info->{'data'}->{'description'};
}
}
xCAT::SvrUtils::sendmsg([1, $error], $callback, $node);
$wait_node_num--;
return;
}
print "$node: DEBUG " . lc ($node_info{$node}{cur_status}) . " " . $response->status_line . "\n";
$status_info{ $node_info{$node}{cur_status} }->{process}->($node, $response);
return;
}
#-------------------------------------------------------
=head3 login_response
Deal with response of login
Input:
$node: nodename of current response
$response: Async return response
=cut
#-------------------------------------------------------
sub login_response {
my $node = shift;
my $response = shift;
if ($next_status{ $node_info{$node}{cur_status} }) {
$node_info{$node}{cur_status} = $next_status{ $node_info{$node}{cur_status} };
gen_send_request($node);
}
return;
}
#-------------------------------------------------------
=head3 rpower_response
Deal with response of rpower command
Input:
$node: nodename of current response
$response: Async return response
=cut
#-------------------------------------------------------
sub rpower_response {
my $node = shift;
my $response = shift;
my $response_info = decode_json $response->content;
if ($node_info{$node}{cur_status} eq "RPOWER_ON_RESPONSE") {
if ($response_info->{'message'} eq "200 OK") {
xCAT::SvrUtils::sendmsg("on", $callback, $node);
}
}
if ($node_info{$node}{cur_status} eq "RPOWER_OFF_RESPONSE") {
if ($response_info->{'message'} eq "200 OK") {
xCAT::SvrUtils::sendmsg("off", $callback, $node);
}
}
if ($node_info{$node}{cur_status} eq "RPOWER_RESET_RESPONSE") {
if ($response_info->{'message'} eq "200 OK") {
xCAT::SvrUtils::sendmsg("reset", $callback, $node);
}
}
if ($node_info{$node}{cur_status} eq "RPOWER_STATUS_RESPONSE") {
xCAT::SvrUtils::sendmsg($response_info->{'data'}->{CurrentHostState}, $callback, $node);
}
if ($next_status{ $node_info{$node}{cur_status} }) {
if ($node_info{$node}{cur_status} eq "RPOWER_STATUS_RESPONSE") {
if ($response_info->{'data'}->{CurrentHostState} =~ /Off$/) {
$node_info{$node}{cur_status} = $next_status{ $node_info{$node}{cur_status} }{OFF};
} else {
$node_info{$node}{cur_status} = $next_status{ $node_info{$node}{cur_status} }{ON};
}
} else {
$node_info{$node}{cur_status} = $next_status{ $node_info{$node}{cur_status} };
}
gen_send_request($node);
} else {
$wait_node_num--;
}
return;
}
#-------------------------------------------------------
=head3 rinv_response
Deal with response of rinv command
Input:
$node: nodename of current response
$response: Async return response
=cut
#-------------------------------------------------------
sub rinv_response {
my $node = shift;
my $response = shift;
my $response_info = decode_json $response->content;
my $grep_string = $status_info{RINV_RESPONSE}{argv};
my $src;
my $content_info;
foreach my $key_url (keys %{$response_info->{data}}) {
if ($grep_string eq "all" or $key_url =~ /\/$grep_string/) {
if ($key_url =~ /\/(cpu\d*)\/(\w+)/) {
$src = "$1 $2";
} else {
$src = basename $key_url;
}
my %content = %{ ${ $response_info->{data} }{$key_url} };
foreach my $key (keys %content) {
$content_info = uc ($src) . " " . $key . " : " . $content{$key};
xCAT::SvrUtils::sendmsg("$content_info", $callback, $node);
}
}
}
if ($next_status{ $node_info{$node}{cur_status} }) {
$node_info{$node}{cur_status} = $next_status{ $node_info{$node}{cur_status} };
gen_send_request($node);
} else {
$wait_node_num--;
}
return;
}
1;