# IBM(c) 2014 EPL license http://www.eclipse.org/legal/epl-v10.html #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']); push @items,$ent; } } else { $allnodes=1; @items = $hmtab->getAllNodeAttribs(['node', 'serialport','cons', 'conserver']); } my @nodes=(); foreach (@items) { if (((!defined($_->{cons})) || ($_->{cons} eq "")) and !defined($_->{serialport})) { next;} #skip if 'cons' is not defined for this node, unless serialport suggests otherwise 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.. 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 @cfgents1;# = $hmtab->getAllNodeAttribs(['cons','serialport','mgt','conserver','termserver','termport']); if (($nodes and @$nodes > 0) or $req->{noderange}->[0]) { @cfgents1 = $hmtab->getNodesAttribs($nodes,['node','cons','serialport','mgt','conserver','termserver','termport','consoleondemand']); # 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) { push @tmpcfgents1, $ent->{$nodeent}->[0] ; } } @cfgents1 = @tmpcfgents1 } else { @cfgents1 = $hmtab->getAllNodeAttribs(['cons','serialport','mgt','conserver','termserver','termport','consoleondemand']); } #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 @cfgents=(); my %cfgenthash; foreach (@cfgents1) { if ($_->{cons} or defined($_->{'serialport'})) { unless ($_->{cons}) {$_->{cons} = $_->{mgt};} #populate with fallback push @cfgents, $_; $cfgenthash{$_->{node}} = $_; # also put the ref to the entry in a hash for quick look up } } 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}} =~ /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, 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 (not $cmeth) { return $node; } if ($cmeth ne 'ipmi') { $cmeth = 'xcat' . $cmeth; } my %parameters; $parameters{'console.method'} = $cmeth; if ($cmeth eq 'ipmi') { $parameters{'secret.hardwaremanagementuser'} = $ipmiauthdata->{$node}->{username}; $parameters{'secret.hardwaremanagementpassword'} = $ipmiauthdata->{$node}->{password}; my $bmc = $ipmientries->{$node}->[0]->{bmc}; $bmc =~ s/,.*//; $parameters{'hardwaremanagement.manager'} = $bmc; } if (defined($cfgent->{consoleondemand})) { if ($cfgent->{consoleondemand}) { $parameters{'console.logging'} = 'none'; } else { $parameters{'console.logging'} = 'full'; } } elsif ($::XCATSITEVALS{'consoleondemand'} and $::XCATSITEVALS{'consoleondemand'} !~ m/^n/) { $parameters{'console.logging'} = 'none'; } $parameters{'groups'} = [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;