2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-08-26 16:46:41 +00:00
Files
xcat-core/xCAT-server/lib/xcat/plugins/confluent.pm
T
Vinícius Ferrão cc71816b90 fix(confluent): configure nodes named explicitly without a console method
preprocess_request skips any node whose nodehm entry has neither cons nor
serialport. That is the right default while scanning the whole table, but it
also drops nodes the administrator named on the command line, leaving the
dispatched node list empty. makeconfluentcfg then falls back to reading every
node, so naming one console-less node quietly configures the whole cluster
instead of the node that was asked for.

A named node with no nodehm row at all is worse: both the selection here and
the second lookup in makeconfluentcfg produce an undefined entry, which reaches
confluent as an empty node name.

Apply the skip only to the full table scan, and carry the node name through
both lookups when the nodehm row is missing. Nodes without a console method are
already handled downstream, where makeconfluentcfg keeps explicitly named
entries and donodeent falls back to hardware management credentials.

Co-authored-by: Jarrod Johnson <10814490+jjohnson42@users.noreply.github.com>
2026-08-07 22:32:43 -03:00

539 lines
20 KiB
Perl

# IBM(c) 2014 EPL license http://www.eclipse.org/legal/epl-v10.html
# Lenovo(c) 2016
#TODO: delete entries not being refreshed if no noderange
package xCAT_plugin::confluent;
use strict;
use warnings;
use xCAT::PasswordUtils;
use xCAT::Table;
use xCAT::Utils;
use xCAT::TableUtils;
use Getopt::Long;
use Sys::Hostname;
use xCAT::SvrUtils;
use Confluent::Client;
use strict;
my %termservers; #list of noted termservers
my $usage_string =
" makeconfluentcfg [-d|--delete] noderange
makeconfluentcf [-l|--local]
makeconfluentcf [-c|--confluent]
makeconfluentcf
makeconfluentcf -h|--help
makeconfluentcf -v|--version
-c|--confluent Configure confluent only on the host.
The default goes down to all the confluent instances on
the server nodes and set them up
-l|--local Configure confluent only on the local system.
The default goes down to all the confluent instances on
the server nodes and set them up
-d|--delete Conserver has the relevant entries for the given noderange removed immediately from configuration
-h|--help Display this usage statement.
-V|--verbose Verbose mode.
-v|--version Display the version number.";
my $version_string = xCAT::Utils->Version();
sub handled_commands {
return {
makeconfluentcfg => "confluent"
}
}
sub preprocess_request {
my $request = shift;
#if ($request->{_xcatdest}) { return [$request]; } #exit if preprocessed
if ($request->{_xcatpreprocessed}->[0] == 1) { return [$request]; }
my $callback = shift;
my @requests;
my $noderange = $request->{node}; #Should be arrayref
#display usage statement if -h
my $extrargs = $request->{arg};
my @exargs = ($request->{arg});
if (ref($extrargs)) {
@exargs = @$extrargs;
}
@ARGV = @exargs;
my $isSN = xCAT::Utils->isServiceNode();
my @hostinfo = xCAT::NetworkUtils->determinehostname();
my %iphash = ();
foreach (@hostinfo) { $iphash{$_} = 1; }
$Getopt::Long::ignorecase = 0;
#$Getopt::Long::pass_through=1;
if (!GetOptions(
'c|confluent' => \$::CONSERVER,
'l|local' => \$::LOCAL,
'h|help' => \$::HELP,
'D|debug' => \$::DEBUG,
'v|version' => \$::VERSION,
'V|verbose' => \$::VERBOSE)) {
$request = {};
return;
}
if ($::HELP) {
$callback->({ data => $usage_string });
$request = {};
return;
}
if ($::VERSION) {
$callback->({ data => $version_string });
$request = {};
return;
}
if ($::LOCAL) {
if ($noderange && @$noderange > 0) {
$callback->({ data => "Invalid option -l or --local when there are nodes specified." });
$request = {};
return;
}
}
if ($::CONSERVER && $::LOCAL) {
$callback->({ data => "Can not specify -l or --local together with -c or --confluent." });
$request = {};
return;
}
# get site master
my $master = xCAT::TableUtils->get_site_Master();
if (!$master) { $master = hostname(); }
my %cons_hash = ();
my $hmtab = xCAT::Table->new('nodehm');
my @items;
my $allnodes = 1;
if ($noderange && @$noderange > 0) {
$allnodes = 0;
my $hmcache = $hmtab->getNodesAttribs($noderange, [ 'node', 'serialport', 'cons', 'conserver' ]);
foreach my $node (@$noderange) {
my $ent = $hmcache->{$node}->[0]; #$hmtab->getNodeAttribs($node,['node', 'serialport','cons', 'conserver']);
#A node named on the command line may have no nodehm row at all. Carry
#its name through anyway, otherwise the entry below is an undefined
#reference and the node reaches confluent with an empty name.
unless ($ent) { $ent = { node => $node }; }
push @items, $ent;
}
} else {
$allnodes = 1;
@items = $hmtab->getAllNodeAttribs([ 'node', 'serialport', 'cons', 'conserver' ]);
}
my @nodes = ();
foreach (@items) {
#skip if 'cons' is not defined for this node, unless serialport suggests otherwise.
#This only applies while scanning every node in the table. A node named
#explicitly on the command line was asked for by the administrator, so it is
#configured whether or not it has a console method.
if ($allnodes and ((!defined($_->{cons})) || ($_->{cons} eq "")) and !defined($_->{serialport})) { next; }
if (defined($_->{conserver})) { push @{ $cons_hash{ $_->{conserver} }{nodes} }, $_->{node}; }
else { push @{ $cons_hash{$master}{nodes} }, $_->{node}; }
push @nodes, $_->{node};
}
#send all nodes to the MN
if (!$isSN && !$::CONSERVER) { #If -c flag is set, do not add the all nodes to the management node
if ($::VERBOSE) {
my $rsp;
$rsp->{data}->[0] = "Configuring nodes in confluent on the management node";
xCAT::MsgUtils->message("I", $rsp, $callback);
}
my $reqcopy = {%$request};
$reqcopy->{'_xcatdest'} = $master;
$reqcopy->{_xcatpreprocessed}->[0] = 1;
$reqcopy->{'_allnodes'} = $allnodes; # the original command comes with nodes or not
if ($allnodes == 1) { @nodes = (); }
$reqcopy->{node} = \@nodes;
push @requests, $reqcopy;
if ($::LOCAL) { return \@requests; }
}
# send to conserver hosts
foreach my $cons (keys %cons_hash) {
#print "cons=$cons\n";
my $doit = 0;
if ($isSN) {
if (exists($iphash{$cons})) { $doit = 1; }
} else {
if (!exists($iphash{$cons}) || $::CONSERVER) { $doit = 1; }
}
if ($doit) {
my $reqcopy = {%$request};
$reqcopy->{'_xcatdest'} = $cons;
$reqcopy->{_xcatpreprocessed}->[0] = 1;
$reqcopy->{'_allnodes'} = [$allnodes]; # the original command comes with nodes or not
$reqcopy->{node} = $cons_hash{$cons}{nodes};
my $no = $reqcopy->{node};
#print "node=@$no\n";
push @requests, $reqcopy;
} #end if
} #end foreach
if ($::DEBUG) {
my $rsp;
$rsp->{data}->[0] = "In preprocess_request, request is " . Dumper(@requests);
xCAT::MsgUtils->message("I", $rsp, $callback);
}
return \@requests;
}
sub process_request {
my $req = shift;
my $cb = shift;
if ($req->{command}->[0] eq "makeconfluentcfg") {
makeconfluentcfg($req, $cb);
}
}
# Read the file, get db info, update the file contents, and then write the file
sub makeconfluentcfg {
my $req = shift;
%termservers = (); #clear hash of existing entries
my $cb = shift;
my $extrargs = $req->{arg};
my @exargs = ($req->{arg});
if (ref($extrargs)) {
@exargs = @$extrargs;
}
@ARGV = @exargs;
$Getopt::Long::ignorecase = 0;
#$Getopt::Long::pass_through=1;
my $delmode;
GetOptions('d|delete' => \$delmode,
);
my $nodes = $req->{node};
my $svboot = 0;
if (exists($req->{svboot})) { $svboot = 1; }
my $confluent = Confluent::Client->new(); # just the local form for now..
unless ($confluent) {
# unable to get a connection to confluent
my $rsp;
$rsp->{data}->[0] = "Unable to open a connection to confluent, verify that confluent is running.";
xCAT::MsgUtils->message("E", $rsp, $cb);
return;
}
my $isSN = xCAT::Utils->isServiceNode();
my @hostinfo = xCAT::NetworkUtils->determinehostname();
my %iphash = ();
foreach (@hostinfo) { $iphash{$_} = 1; }
#print "process_request nodes=@$nodes\n";
# Get db info for the nodes related to console
my $hmtab = xCAT::Table->new('nodehm');
my $nodepostab = xCAT::Table->new('nodepos');
my $mptab = xCAT::Table->new('mp');
my @cfgents1; # = $hmtab->getAllNodeAttribs(['cons','serialport','mgt','conserver','termserver','termport']);
my @cfgents2;
my @cfgents3;
my $explicitnodes = 0;
if (($nodes and @$nodes > 0) or $req->{noderange}->[0]) {
$explicitnodes = 1;
@cfgents1 = $hmtab->getNodesAttribs($nodes, [ 'node', 'cons', 'mgt', 'conserver', 'termserver', 'termport', 'consoleondemand' ]);
@cfgents2 = $nodepostab->getNodesAttribs($nodes, [ 'node', 'rack', 'u', 'chassis', 'slot', 'room' ]);
@cfgents3 = $mptab->getNodesAttribs($nodes, [ 'node', 'mpa', 'id' ]);
# Adjust the data structure to make the result consistent with the getAllNodeAttribs() call we make if a noderange was not specified
my @tmpcfgents1;
foreach my $ent (@cfgents1)
{
foreach my $nodeent (keys %$ent)
{
#A named node may have no nodehm row, which leaves an undefined
#entry that reaches confluent as an empty node name. The key here
#is the node, so carry the name through instead.
my $row = $ent->{$nodeent}->[0];
$row = {} unless ($row);
$row->{node} = $nodeent unless (defined($row->{node}));
push @tmpcfgents1, $row;
}
}
@cfgents1 = @tmpcfgents1;
@tmpcfgents1 = ();
foreach my $ent (@cfgents2)
{
foreach my $nodeent (keys %$ent)
{
push @tmpcfgents1, $ent->{$nodeent}->[0];
}
}
@cfgents2 = @tmpcfgents1;
@tmpcfgents1 = ();
foreach my $ent (@cfgents3)
{
foreach my $nodeent (keys %$ent)
{
push @tmpcfgents1, $ent->{$nodeent}->[0];
}
}
@cfgents3 = @tmpcfgents1;
} else {
@cfgents1 = $hmtab->getAllNodeAttribs([ 'cons', 'serialport', 'mgt', 'conserver', 'termserver', 'termport', 'consoleondemand' ]);
@cfgents2 = $nodepostab->getAllNodeAttribs([ 'rack', 'u', 'chassis', 'slot', 'room' ]);
@cfgents3 = $nodepostab->getAllNodeAttribs([ 'mpa', 'id' ]);
}
#cfgents1 should now have all the nodes, so we can fill in the cfgents array and cfgenthash one at a time.
# skip the nodes that do not have 'cons' defined, unless a serialport setting suggests otherwise
my %cfgenthash;
foreach (@cfgents1) {
if ($_->{cons} or defined($_->{'serialport'})) {
unless ($_->{cons}) { $_->{cons} = $_->{mgt}; } #populate with fallback
$cfgenthash{ $_->{node} } = $_; # also put the ref to the entry in a hash for quick look up
} elsif ($explicitnodes) {
$cfgenthash{ $_->{node} } = $_; # also put the ref to the entry in a hash for quick look up
}
}
foreach my $nent (@cfgents2) {
foreach (keys %$nent) {
$cfgenthash{ $nent->{node} }->{$_} = $nent->{$_};
}
}
foreach my $nent (@cfgents3) {
foreach (keys %$nent) {
$cfgenthash{ $nent->{node} }->{$_} = $nent->{$_};
}
}
my @cfgents = ();
foreach (values %cfgenthash) {
push @cfgents, $_;
}
if ($::DEBUG) {
my $rsp;
$rsp->{data}->[0] = "In makeconservercf, cfgents is " . Dumper(@cfgents);
xCAT::MsgUtils->message("I", $rsp, $cb);
}
# if nodes defined, it is either on the service node or makeconserver was called with noderange on mn
if (($nodes and @$nodes > 0) or $req->{noderange}->[0]) {
# strip all xCAT configured nodes from config if the original command was for all nodes
#if (($req->{_allnodes}) && ($req->{_allnodes}->[0]==1)) {} #TODO: identify nodes that will be removed
# call donodeent to add all node entries into the file. It will return the 1st node in error.
my $node;
if ($node = donodeent(\%cfgenthash, $confluent, $delmode, $cb)) {
#$cb->({node=>[{name=>$node,error=>"Bad configuration, check attributes under the nodehm category",errorcode=>1}]});
xCAT::SvrUtils::sendmsg([ 1, "Bad configuration, check attributes under the nodehm category" ], $cb, $node);
}
} else { #no nodes specified, do em all up
#zapcfg(\@filecontent); # strip all xCAT configured nodes from config
#TODO: identify nodes to be removed
# get nodetype so we can filter out node types without console support
my $typetab = xCAT::Table->new('nodetype');
my %type;
if (defined($typetab)) {
my @ents = $typetab->getAllNodeAttribs([qw(node nodetype)]);
foreach (@ents) {
$type{ $_->{node} } = $_->{nodetype};
}
}
# remove nodes that arent for this SN or type of node doesnt have console
foreach (@cfgents) {
my $keepdoing = 0;
if ($isSN && $_->{conserver} && exists($iphash{ $_->{conserver} })) {
$keepdoing = 1; #only hanlde the nodes that use this SN as the conserver
}
if (!$isSN) { $keepdoing = 1; } #handle all for MN
if ($keepdoing) {
if ($_->{termserver} and not $termservers{ $_->{termserver} }) {
die "confluent does not currently support termserver";
$termservers{ $_->{termserver} } = 1; # dont add this one again
}
if ($type{$_->{node}} and $type{ $_->{node} } =~ /fsp|bpa|hmc|ivm/) {
$keepdoing = 0; # these types dont have consoles
}
}
if (!$keepdoing) { delete $cfgenthash{ $_->{node} }; } # remove this node from the hash so we dont process it later
}
# Now add into the file all the node entries that we kept
my $node;
if ($node = donodeent(\%cfgenthash, $confluent, undef, $cb)) {
# donodeent will return the 1st node in error
#$cb->({node=>[{name=>$node,error=>"Bad configuration, check attributes under the nodehm category",errorcode=>1}]});
xCAT::SvrUtils::sendmsg([ 1, "Bad configuration, check attributes under the nodehm category" ], $cb, $node);
}
}
}
# Add entries in the file for each node. This function used to do 1 node at a time, but was changed to do
# all nodes at once for performance reasons. If there is a problem with a nodes config, this
# function will return that node name as the one in error.
sub donodeent {
my $cfgenthash = shift;
my $confluent = shift;
my $delmode = shift;
my $cb = shift;
my $idx = 0;
my $toidx = -1;
my $skip = 0;
my $skipnext = 0;
# Delete all the previous stanzas of the nodes specified
my $isSN = xCAT::Utils->isServiceNode();
my $curnode;
# Loop till find the start of a node stanza and remove lines till get to the end of the stanza
my %currnodes;
$confluent->read('/nodes/');
my $listitem = $confluent->next_result();
while ($listitem) {
if (exists $listitem->{item}) {
my $name = $listitem->{item}->{href};
$name =~ s/\/$//;
$currnodes{$name} = 1;
}
$listitem = $confluent->next_result();
}
if ($delmode) {
foreach my $confnode (keys %currnodes) {
if ($cfgenthash->{$confnode}) {
$confluent->delete('/nodes/' . $confnode);
}
}
return;
}
my @toconfignodes = keys %{$cfgenthash};
my $ipmitab = xCAT::Table->new('ipmi', -create => 0);
my $ipmientries = {};
if ($ipmitab) {
$ipmientries = $ipmitab->getNodesAttribs(\@toconfignodes,
[qw/bmc username password/]);
}
my $ipmiauthdata = xCAT::PasswordUtils::getIPMIAuth(
noderange => \@toconfignodes, ipmihash => $ipmientries);
my $nltab = xCAT::Table->new('nodelist', -create => 0);
my $groupdata = {};
if ($nltab) {
$groupdata = $nltab->getNodesAttribs(\@toconfignodes,
[qw/groups/]);
}
my @cfgroups;
foreach (keys %$groupdata) {
push @cfgroups, grep { defined && length } split /,/, $groupdata->{$_}->[0]->{groups};
}
$confluent->read('/nodegroups/');
my $currgroup = $confluent->next_result();
my %currgroups;
while ($currgroup) {
if (exists $currgroup->{item}) {
my $groupname = $currgroup->{item}->{href};
$groupname =~ s/\/$//;
$currgroups{$groupname} = 1;
}
$currgroup = $confluent->next_result();
}
foreach (@cfgroups) {
if (not exists $currgroups{$_}) {
$confluent->create('/nodegroups/', parameters => { name => $_ });
my $rsp = $confluent->next_result();
while ($rsp) {
if (exists $rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, ":Confluent error: " . $rsp->{error} ], $cb);
}
$rsp = $confluent->next_result();
}
}
}
# Go thru all nodes specified to add them to the file
foreach my $node (sort keys %$cfgenthash) {
my $cfgent = $cfgenthash->{$node};
my $cmeth = $cfgent->{cons};
if ($cmeth and $cmeth ne 'ipmi') {
$cmeth = 'xcat' . $cmeth;
}
my %parameters;
if ($cmeth) { $parameters{'console.method'} = $cmeth; }
if (not $cmeth or $cmeth eq 'ipmi') {
$parameters{'secret.hardwaremanagementuser'} =
$ipmiauthdata->{$node}->{username};
$parameters{'secret.hardwaremanagementpassword'} =
$ipmiauthdata->{$node}->{password};
my $bmc = $ipmientries->{$node}->[0]->{bmc};
if ($bmc) {
$bmc =~ s/,.*//;
$parameters{'hardwaremanagement.manager'} = $bmc;
}
}
if (defined($cfgent->{consoleondemand})) {
if ($cfgent->{consoleondemand} == 'yes') {
$parameters{'console.logging'} = 'none';
}
else {
$parameters{'console.logging'} = 'full';
}
} elsif ($::XCATSITEVALS{'consoleondemand'} and $::XCATSITEVALS{'consoleondemand'} !~ m/^n/) {
$parameters{'console.logging'} = 'none';
}
elsif ($::XCATSITEVALS{'consoleondemand'} and $::XCATSITEVALS{'consoleondemand'} == 'no') {
$parameters{'console.logging'} = 'full';
}
# ok, now for nodepos...
if (defined $cfgent->{u}) {
$parameters{'location.u'} = $cfgent->{u};
}
if (defined $cfgent->{rack}) {
$parameters{'location.rack'} = $cfgent->{rack};
}
if (defined $cfgent->{room}) {
$parameters{'location.room'} = $cfgent->{room};
}
if (defined $cfgent->{chassis}) {
$parameters{'enclosure.manager'} = $cfgent->{chassis};
} elsif (defined $cfgent->{mpa}) {
$parameters{'enclosure.manager'} = $cfgent->{mpa};
}
if (defined $cfgent->{slot}) {
$parameters{'enclosure.bay'} = $cfgent->{slot};
} elsif (defined $cfgent->{id}) {
$parameters{'enclosure.bay'} = $cfgent->{id};
}
$parameters{'groups'} = [ grep { defined && length } split /,/, $groupdata->{$node}->[0]->{'groups'} ];
if (exists $currnodes{$node}) {
$confluent->update('/nodes/' . $node . '/attributes/current', parameters => \%parameters);
my $rsp = $confluent->next_result();
while ($rsp) {
if (exists $rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, ":Confluent error: " . $rsp->{error} ], $cb, $node);
}
$rsp = $confluent->next_result();
}
} else {
$parameters{name} = $node;
$confluent->create('/nodes/', parameters => \%parameters);
my $rsp = $confluent->next_result();
while ($rsp) {
if (exists $rsp->{error}) {
xCAT::SvrUtils::sendmsg([ 1, ":Confluent error: " . $rsp->{error} ], $cb, $node);
}
$rsp = $confluent->next_result();
}
}
}
return 0;
}
1;