diff --git a/xCAT-client/bin/makeconfluentcfg b/xCAT-client/bin/makeconfluentcfg new file mode 120000 index 000000000..b1c9eea11 --- /dev/null +++ b/xCAT-client/bin/makeconfluentcfg @@ -0,0 +1 @@ +xcatclient \ No newline at end of file diff --git a/xCAT-server/lib/xcat/plugins/confluent.pm b/xCAT-server/lib/xcat/plugins/confluent.pm new file mode 100644 index 000000000..1c9488ec8 --- /dev/null +++ b/xCAT-server/lib/xcat/plugins/confluent.pm @@ -0,0 +1,406 @@ +# 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); + +# 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') { + die 'TODO: non ipmi consoles...' + } + my %parameters; + $parameters{'console.method'} = $cmeth; + if ($cmeth eq 'ipmi') { + $parameters{'secret.hardwaremanagementuser'} = + $ipmiauthdata->{$node}->{username}; + $parameters{'secret.hardwaremanagementpassphrase'} = + $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'; + } + 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/bob/', 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;