2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-07-31 18:19:40 +00:00
Files
xcat-core/xCAT-server/lib/xcat/plugins/tabutils.pm
T
Vinícius Ferrão f71ddef6ba fix(nodech): support multiple groups in the ^= operator
nodech's ^= operator removed only a single literal value, so
"nodech n1 groups^=a,b" tried to strip the combined string "a,b" and left the
individual groups in place. Split the value on commas and remove each piece.

Recovered from the unmerged lenovobuild branch (original 95c78b33).

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

3164 lines
100 KiB
Perl

# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#####################################################
#
# xCAT plugin package to handle various commands that work with the
# xCAT tables
#
#
#####################################################
package xCAT_plugin::tabutils;
use strict;
use warnings;
use xCAT::Table;
use xCAT::Schema;
use Data::Dumper;
use xCAT::NodeRange qw/noderange abbreviate_noderange/;
use xCAT::Schema;
use xCAT::Utils;
#use XML::Simple;
use xCAT::TableUtils;
use xCAT::MsgUtils;
use xCAT::DBobjUtils;
use Getopt::Long;
my $requestcommand;
1;
#some quick aliases to table/value
my %shortnames = (
groups => [qw(nodelist groups)],
tags => [qw(nodelist groups)],
mgt => [qw(nodehm mgt)],
#switch => [qw(switch switch)],
);
#####################################################
# Return list of commands handled by this plugin
#####################################################
sub handled_commands
{
return {
gettab => "tabutils",
tabdump => "tabutils",
lsxcatd => "tabutils",
tabprune => "tabutils",
tabrestore => "tabutils",
tabch => "tabutils",
nodegrpch => "tabutils",
nodech => "tabutils",
nodeadd => "tabutils",
noderm => "tabutils",
tabls => "tabutils", # not implemented yet
nodels => "tabutils",
getnodecfg => "tabutils", # not implemented yet (?? this doesn't seem much different from gettab)
addattr => "tabutils", # not implemented yet
delattr => "tabutils", # not implemented yet
chtype => "tabutils", # not implemented yet
nr => "tabutils", # not implemented yet
rnoderange => "tabutils", # not implemented yet
tabgrep => "tabutils",
getAllEntries => "tabutils",
getNodesAttribs => "tabutils",
getTablesAllNodeAttribs => "tabutils",
getTablesNodesAttribs => "tabutils",
getTablesAllRowAttribs => "tabutils",
setNodesAttribs => "tabutils",
delEntries => "tabutils",
getAttribs => "tabutils",
setAttribs => "tabutils",
NodeRange => "tabutils",
gennr => "tabutils"
};
}
# Each cmd now returns its own usage inside its function
#####################################################
# Process the command
#####################################################
sub process_request
{
#use Getopt::Long;
Getopt::Long::Configure("bundling");
#Getopt::Long::Configure("pass_through");
Getopt::Long::Configure("no_pass_through");
my $request = shift;
my $callback = shift;
$requestcommand = shift;
my $nodes = $request->{node};
my $command = $request->{command}->[0];
my $args = $request->{arg};
#unless ($args or $nodes or $request->{data})
#{
#if ($usage{$command})
#{
#$callback->({data => [$usage{$command}]});
#return;
#}
#}
if ($command eq "nodels")
{
return nodels($nodes, $args, $callback, $request->{emptynoderange}->[0]);
}
elsif ($command eq "rnoderange")
{
return rnoderange($nodes, $args, $callback);
}
elsif ($command eq "noderm" or $command eq "rmnode")
{
return noderm($nodes, $args, $callback);
}
elsif ($command eq "nodeadd" or $command eq "addnode")
{
return nodech($nodes, $args, $callback, 1);
}
elsif ($command eq "nodegrpch" or $command eq "chnodegrp")
{
return nodech($nodes, $args, $callback, "groupch");
}
elsif ($command eq "gennr")
{
return gennr($nodes, $args, $callback);
}
elsif ($command eq "nodech" or $command eq "chnode")
{
return nodech($nodes, $args, $callback, 0);
}
elsif ($command eq "tabrestore")
{
return tabrestore($request, $callback);
}
elsif ($command eq "tabdump")
{
return tabdump($args, $callback, $request);
}
elsif ($command eq "lsxcatd")
{
return lsxcatd($args, $callback);
}
elsif ($command eq "tabprune")
{
return tabprune($args, $callback);
}
elsif ($command eq "gettab")
{
return gettab($request, $callback);
}
elsif ($command eq "tabgrep")
{
return tabgrep($nodes, $callback);
}
elsif ($command eq "tabch") {
return tabch($request, $callback);
}
elsif ($command eq "getAllEntries")
{
return getAllEntries($request, $callback);
}
elsif ($command eq "getNodesAttribs")
{
return getNodesAttribs($request, $callback);
}
elsif ($command eq "getTablesAllNodeAttribs")
{
return getTablesAllNodeAttribs($request, $callback);
}
elsif ($command eq "getTablesNodesAttribs")
{
return getTablesNodesAttribs($request, $callback);
}
elsif ($command eq "getTablesAllRowAttribs")
{
return getTablesAllRowAttribs($request, $callback);
}
elsif ($command eq "setNodesAttribs")
{
return setNodesAttribs($request, $callback);
}
elsif ($command eq "delEntries")
{
return delEntries($request, $callback);
}
elsif ($command eq "getAttribs")
{
return getAttribs($request, $callback);
}
elsif ($command eq "setAttribs")
{
return setAttribs($request, $callback);
}
elsif ($command eq "NodeRange")
{
return NodeRange($request, $callback);
}
else
{
print "$command not implemented yet\n";
return (1, "$command not written yet");
}
}
# Display particular attributes, using query strings.
sub gettab
{
my $req = shift;
my $callback = shift;
my $HELP;
my $NOTERSE;
my $gettab_usage = sub {
my $exitcode = shift @_;
my %rsp;
push @{ $rsp{data} }, "Usage: gettab [-H|--with-fieldname] key=value,... table.attribute ...";
push @{ $rsp{data} }, " gettab [-?|-h|--help]";
if ($exitcode) { $rsp{errorcode} = $exitcode; }
$callback->(\%rsp);
};
# Process arguments
if (!defined($req->{arg})) { $gettab_usage->(1); return; }
@ARGV = @{ $req->{arg} };
if (!GetOptions('h|?|help' => \$HELP, 'H|with-fieldname' => \$NOTERSE)) { $gettab_usage->(1); return; }
if ($HELP) { $gettab_usage->(0); return; }
if (scalar(@ARGV) < 2) { $gettab_usage->(1); return; }
# Get all the key/value pairs into a hash
my $keyspec = shift @ARGV;
my @keypairs = split /,/, $keyspec;
my %keyhash;
foreach (@keypairs)
{
(my $key, my $value) = split /=/, $_;
unless (defined $key) {
$gettab_usage->(1);
return;
}
$keyhash{$key} = $value;
}
# Group the columns asked for by table (so we can do 1 query per table)
my %tabhash;
my $terse = 2;
if ($NOTERSE) {
$terse = 0;
}
foreach my $tabvalue (@ARGV)
{
$terse--;
(my $table, my $column) = split /\./, $tabvalue;
$tabhash{$table}->{$column} = 1;
}
#Sanity check the key against all tables in question
foreach my $tabn (keys %tabhash) {
foreach my $kcheck (keys %keyhash) {
unless (grep /^$kcheck$/, @{ $xCAT::Schema::tabspec{$tabn}->{cols} }) {
$callback->({ error => ["Unknown key $kcheck to $tabn"], errorcode => [1] });
return;
}
}
}
# Get the requested columns from each table
foreach my $tabn (keys %tabhash)
{
my $tab = xCAT::Table->new($tabn);
(my $ent) = $tab->getAttribs(\%keyhash, keys %{ $tabhash{$tabn} });
if ($ent) {
foreach my $coln (keys %{ $tabhash{$tabn} })
{
if ($terse > 0) {
$callback->({ data => [ "" . $ent->{$coln} ] });
} else {
$callback->({ data => [ "$tabn.$coln: " . $ent->{$coln} ] });
}
}
}
$tab->close;
}
}
sub noderm
{
my $nodes = shift;
my $args = shift;
my $cb = shift;
my $VERSION;
my $HELP;
my $noderm_usage = sub {
my $exitcode = shift @_;
my %rsp;
push @{ $rsp{data} }, "Usage:";
push @{ $rsp{data} }, " noderm noderange";
push @{ $rsp{data} }, " noderm {-v|--version}";
push @{ $rsp{data} }, " noderm [-?|-h|--help]";
if ($exitcode) { $rsp{errorcode} = $exitcode; }
$cb->(\%rsp);
};
if ($args) {
@ARGV = @{$args};
} else {
@ARGV = ();
}
if (!GetOptions('h|?|help' => \$HELP, 'v|version' => \$VERSION)) { $noderm_usage->(1); return; }
if ($HELP) { $noderm_usage->(0); return; }
if ($VERSION) {
my %rsp;
my $version = xCAT::Utils->Version();
$rsp{data}->[0] = "$version";
$cb->(\%rsp);
return;
}
if (!$nodes) { $noderm_usage->(1); return; }
#my $sitetab = xCAT::Table->new('site');
#my $pdhcp = $sitetab->getAttribs({key=>'pruneservices'},['value']);
my @entries = xCAT::TableUtils->get_site_attribute("pruneservices");
my $t_entry = $entries[0];
if (defined($t_entry) and $t_entry !~ /n(\z|o)/i) {
$requestcommand->({ command => ['makedhcp'], node => $nodes, arg => ['-d'] });
}
# Build the argument list for using the -d option of nodech to do our work for us
my @tablist = ("-d");
foreach (keys %{xCAT::Schema::tabspec})
{
if (grep /^node$/, @{ $xCAT::Schema::tabspec{$_}->{cols} })
{
push @tablist, $_;
}
}
if (scalar(@$nodes)) {
for my $nn (@$nodes) {
my $nt = xCAT::DBobjUtils->getnodetype($nn);
if ($nt and $nt =~ /^(cec|frame)$/) {
my $cnodep = xCAT::DBobjUtils->getchildren($nn);
if ($cnodep) {
my $cnode = join ',', @$cnodep;
if ($cnode)
{
my $rsp;
$rsp->{data}->[0] =
"Removed a $nt node, please remove these nodes belongs to it manually: $cnode \n";
xCAT::MsgUtils->message("I", $rsp, $cb);
}
}
}
}
}
nodech($nodes, \@tablist, $cb, 0);
}
#
# restores the table from the input CSV file. Default deletes the table rows and
# replaces with the rows in the file
# If -a flag is input then it adds the rows from the CSV file to the table.
#
sub tabrestore
{
# the usage for tabrestore is in the tabrestore client cmd
#request->{data} is an array of CSV formatted lines
my $request = shift;
my $cb = shift;
my $table = $request->{table}->[0];
my $addrows = $request->{addrows}->[0];
# do not allow teal tables
if ($table =~ /^x_teal/) {
$cb->({ error => "$table is not supported in tabrestore. Use Teal maintenance commands. ", errorcode => 1 });
return 1;
}
my $tab = xCAT::Table->new($table, -create => 1, -autocommit => 0);
unless ($tab) {
$cb->({ error => "Unable to open $table", errorcode => 4 });
return;
}
if (!defined($addrows)) { # this is a replace not add rows
$tab->delEntries(); #Yes, delete *all* entries
}
my $header = shift @{ $request->{data} };
unless ($header =~ /^#/) {
$cb->({ error => "Data missing header line starting with #", errorcode => 1 });
return;
}
$header =~ s/"//g; #Strip " from overzealous CSV apps
$header =~ s/^#//;
$header =~ s/\s+$//;
my @colns = split(/,/, $header);
my $tcol;
foreach $tcol (@colns) { #validate the restore data has no invalid column names
unless (grep /^$tcol\z/, @{ $xCAT::Schema::tabspec{$table}->{cols} }) {
$cb->({ error => "The header line indicates that column '$tcol' should exist, which is not defined in the schema for '$table'", errorcode => 1 });
return;
}
#print Dumper(grep /^$tcol\z/,@{$xCAT::Schema::tabspec{$table}->{cols}});
}
#print "We passed it!\n";
my $line;
my $rollback = 0;
my @tmp = $tab->getAutoIncrementColumns(); #get the columns that are auto increment by DB.
my %auto_cols = ();
foreach (@tmp) { $auto_cols{$_} = 1; }
my $linenumber;
my $linecount = scalar(@{ $request->{data} });
LINE: for ($linenumber = 0 ; $linenumber < $linecount ; $linenumber++)
{
$line = @{ $request->{data} }[$linenumber];
$line =~ s/\s+$//;
my $origline = $line; #save for error reporting
my %record;
my $col;
foreach $col (@colns)
{
if ($line =~ /^,/ or $line eq "")
{ #Match empty, or end of line that is empty
#TODO: should we detect when there weren't enough CSV fields on a line to match colums?
if (!exists($auto_cols{$col})) {
$record{$col} = undef;
}
$line =~ s/^,//;
}
elsif ($line =~ /^[^,]*"/)
{ # We have stuff in quotes... pain...
#I don't know what I'm doing, so I'll do it a hard way....
if ($line !~ /^"/)
{
$rollback = 1;
$cb->(
{
error =>
"CSV missing opening \" for record with \" characters on line $linenumber, character "
. index($origline, $line) . ": $origline", errorcode => 4
}
);
next LINE;
}
my $offset = 1;
my $nextchar;
my $ent;
while (not defined $ent)
{
$offset = index($line, '"', $offset);
$offset++;
if ($offset <= 0)
{ #the matching quote is not on this line of the file
if ($linenumber < $linecount)
{ #it's not the end of the world, we have more lines to check
my $continuedline = @{ $request->{data} }[ ++$linenumber ];
$offset = length($line);
$line .= "\n" . $continuedline;
$line =~ s/\s+$//;
}
else
{ #the matching quote was not found before the end of the file
#MALFORMED CSV, request rollback, report an error
$rollback = 1;
$cb->(
{
error =>
"CSV unmatched \" in record on line $linenumber, character "
. index($origline, $line) . ": $origline", errorcode => 4
}
);
next LINE;
}
}
else
{ #the next quote was on the current line
$nextchar = substr($line, $offset, 1);
if ($nextchar eq '"')
{ #the case of 2 double quotes. ignore them and move on
$offset++;
}
elsif ($offset eq length($line) or $nextchar eq ',')
{ #hit the end of the line or at least the end of the column
$ent = substr($line, 0, $offset, '');
$line =~ s/^,//;
chop $ent;
$ent = substr($ent, 1);
$ent =~ s/""/"/g;
if (!exists($auto_cols{$col}))
{
$record{$col} = $ent;
}
}
else
{
$cb->(
{
error =>
"CSV unescaped \" in record on line $linenumber, character "
. index($origline, $line) . ": $origline", errorcode => 4
}
);
$rollback = 1;
next LINE;
}
}
}
}
elsif ($line =~ /^([^,]+)/)
{ #easiest case, no Text::Balanced needed..
if (!exists($auto_cols{$col}))
{
$record{$col} = $1;
}
$line =~ s/^([^,]+)(,|$)//;
}
}
if ($line)
{
$rollback = 1;
$cb->({ error => "Too many fields on line $linenumber: $origline | $line", errorcode => 4 });
next LINE;
}
#check for error from DB and rollback
my @rc = $tab->setAttribs(\%record, \%record);
if (not defined($rc[0]))
{
$rollback = 1;
$cb->({ error => "DB error " . $rc[1] . " with line $linenumber: " . $origline, errorcode => 4 });
}
}
if ($rollback)
{
$tab->rollback();
$tab->close;
undef $tab;
return;
}
else
{
$tab->commit; #Made it all the way here, commit
}
}
# Display a list of tables, or a specific table in CSV format
sub tabdump
{
my $args = shift;
my $cb = shift;
my $request = shift;
my $table = "";
my $HELP;
my $DESC;
my $OPTW;
my $VERSION;
my $FILENAME;
my $NUMBERENTRIES;
my $tabdump_usage = sub {
my $exitcode = shift @_;
my %rsp;
push @{ $rsp{data} }, "Usage: tabdump [-d] [table]";
push @{ $rsp{data} }, " tabdump [table]";
push @{ $rsp{data} }, " tabdump [-f <filename>] [table]";
push @{ $rsp{data} }, " tabdump [-n <# of records>] [auditlog | eventlog]";
push @{ $rsp{data} }, " tabdump [-w attr==val [-w attr=~val] ...] [table]";
push @{ $rsp{data} }, " tabdump [-w attr==val [-w attr=~val] ...] [-f <filename>] [table]";
push @{ $rsp{data} }, " tabdump [-?|-h|--help]";
push @{ $rsp{data} }, " tabdump {-v|--version}";
push @{ $rsp{data} }, " tabdump ";
if ($exitcode) { $rsp{errorcode} = $exitcode; }
$cb->(\%rsp);
};
# Process arguments
if ($args) {
@ARGV = @{$args};
}
Getopt::Long::Configure("posix_default");
Getopt::Long::Configure("no_gnu_compat");
Getopt::Long::Configure("bundling");
if (!GetOptions(
'h|?|help' => \$HELP,
'v|version' => \$VERSION,
'n|lines=i' => \$NUMBERENTRIES,
'd' => \$DESC,
'f=s' => \$FILENAME,
'w=s@' => \$OPTW,
)
)
{ $tabdump_usage->(1);
return;
}
if ($VERSION) {
my %rsp;
my $version = xCAT::Utils->Version();
$rsp{data}->[0] = "$version";
$cb->(\%rsp);
return;
}
if ($FILENAME and $FILENAME !~ /^\//) { $FILENAME =~ s/^/$request->{cwd}->[0]\//; }
if ($HELP) { $tabdump_usage->(0); return; }
if (($NUMBERENTRIES) && ($DESC)) {
$cb->({ error => "You cannot use the -n and -d flag together. ", errorcode => 1 });
return 1;
}
if (($NUMBERENTRIES) && ($OPTW)) {
$cb->({ error => "You cannot use the -n and -w flag together. ", errorcode => 1 });
return 1;
}
if (($NUMBERENTRIES) && ($FILENAME)) {
$cb->({ error => "You cannot use the -n and -f flag together. ", errorcode => 1 });
return 1;
}
if (scalar(@ARGV) > 1) { $tabdump_usage->(1); return; }
my %rsp;
# If no arguments given, we display a list of the tables
if (!scalar(@ARGV)) {
# if -f filename give but no table name, display error
if ($FILENAME) {
$cb->({ error => "table name missing from the command input. ", errorcode => 1 });
return 1;
}
if ($DESC) { # display the description of each table
my $tab = xCAT::Table->getDescriptions();
foreach my $key (keys %$tab) {
my $space = (length($key) < 7 ? "\t\t" : "\t");
push @{ $rsp{data} }, "$key:$space" . $tab->{$key} . "\n";
}
}
else { push @{ $rsp{data} }, xCAT::Table->getTableList(); } # if no descriptions, just display the list of table names
@{ $rsp{data} } = sort @{ $rsp{data} };
if ($DESC && scalar(@{ $rsp{data} })) { chop($rsp{data}->[ scalar(@{ $rsp{data} }) - 1 ]); } # remove the final newline
$cb->(\%rsp);
return;
}
# get the table name
$table = $ARGV[0];
# if -n can only be the auditlog or eventlog
if ($NUMBERENTRIES) {
if (!($table =~ /^auditlog/) && (!($table =~ /^eventlog/))) {
$cb->({ error => "$table table is not supported in tabdump -n. You may only use this option on the auditlog or the eventlog.", errorcode => 1 });
return 1;
}
}
# do not allow teal tables
if ($table =~ /^x_teal/) {
$cb->({ error => "$table table is not supported in tabdump. Use Teal maintenance commands. ", errorcode => 1 });
return 1;
}
if ($DESC) { # only show the attribute descriptions, not the values
my $schema = xCAT::Table->getTableSchema($table);
if (!$schema) { $cb->({ error => "table $table does not exist.", errorcode => 1 }); return; }
my $desc = $schema->{descriptions};
foreach my $c (@{ $schema->{cols} }) {
my $space = (length($c) < 7 ? "\t\t" : "\t");
push @{ $rsp{data} }, "$c:$space" . $desc->{$c} . "\n";
}
if (scalar(@{ $rsp{data} })) { chop($rsp{data}->[ scalar(@{ $rsp{data} }) - 1 ]); } # remove the final newline
$cb->(\%rsp);
return;
}
my $tabh = xCAT::Table->new($table);
my $tabdump_header = sub {
my $header = "#" . join(",", @_);
push @{ $rsp{data} }, $header;
};
# If the table does not exist yet (because its never been written to),
# at least show the header (the column names)
unless ($tabh)
{
if (defined($xCAT::Schema::tabspec{$table}))
{
$tabdump_header->(@{ $xCAT::Schema::tabspec{$table}->{cols} });
$cb->(\%rsp);
return;
}
$cb->({ error => "No such table: $table", errorcode => 1 });
return 1;
}
#
# if tabdump -n <number of recs> auditlog|eventlog
#
if (defined $NUMBERENTRIES) {
my $rc = tabdump_numberentries($table, $cb, $NUMBERENTRIES);
return $rc;
}
my $recs;
my @ents;
my @attrarray;
if (!($FILENAME)) { # not dumping to a file
if (!($OPTW)) { # if no -w flag to filter, then get all
$recs = $tabh->getAllEntries("all");
} else { # filter entries
foreach my $w (@{$OPTW}) { # get each attr=val
push @attrarray, $w;
}
my $keys = xCAT::Table::buildWhereClause(\@attrarray, "1");
if (ref($keys) ne 'ARRAY') {
$cb->({ error => ["$keys"], errorcode => [1] });
return;
} else {
foreach my $k (@$keys) {
unless (grep /$k/, @{ $xCAT::Schema::tabspec{$table}->{cols} }) {
$cb->({ error => ["No column \"$k\" in table \"$table\""], errorcode => [1] });
return;
}
}
}
@ents = $tabh->getAllAttribsWhere(\@attrarray, 'ALL');
@$recs = ();
foreach my $e (@ents) {
push @$recs, $e;
}
}
my $rec;
unless (@$recs) # table exists, but is empty. Show header.
{
if (defined($xCAT::Schema::tabspec{$table}))
{
$tabdump_header->(@{ $xCAT::Schema::tabspec{$table}->{cols} });
$cb->(\%rsp);
return;
}
}
#Display all the rows of the table the order of the columns in the schema
output_table($table, $cb, $tabh, $recs);
} else { # dump to file
my $rc1 = 0;
my $fh;
# check to see if you can open the file
unless (open($fh, " > $FILENAME")) {
$cb->({ error => "Error on tabdump of $table to $FILENAME. Unable to open the file for write. ", errorcode => 1 });
return 1;
}
close $fh;
if (!($OPTW)) { # if no -w flag to filter, then get all
$rc1 = $tabh->writeAllEntries($FILENAME);
} else { # filter entries
foreach my $w (@{$OPTW}) { # get each attr=val
push @attrarray, $w;
}
$rc1 = $tabh->writeAllAttribsWhere(\@attrarray, $FILENAME);
}
if ($rc1 != 0) {
$cb->({ error => "Error on tabdump of $table to $FILENAME ", errorcode => 1 });
return 1;
}
}
}
#
# display input number of records for the table requested tabdump -n
# note currently only supports auditlog and eventlog
#
sub tabdump_numberentries {
my $table = shift;
my $cb = shift;
my $numberentries = shift; # either number of records to display
my $VERBOSE = shift;
my $rc = 0;
my $tab = xCAT::Table->new($table);
unless ($tab) {
$cb->({ error => "Unable to open $table", errorcode => 4 });
return 1;
}
#determine recid to show all records after
my $RECID;
my $attrrecid = "recid";
my $values = $tab->getMAXMINEntries($attrrecid);
my $max = $values->{"max"};
if (defined($values->{"max"})) {
$RECID = $values->{"max"} - $numberentries;
$rc = tabdump_recid($table, $cb, $RECID, $attrrecid);
} else {
my %rsp;
push @{ $rsp{data} }, "Nothing to display from $table.";
$rsp{errorcode} = $rc;
$cb->(\%rsp);
}
return $rc;
}
# Display requested recored
# if rec id does not exist error
sub tabdump_recid {
my $table = shift;
my $cb = shift;
my $recid = shift;
my $rc = 0;
# check which database so can build the correct Where clause
my $tab = xCAT::Table->new($table);
unless ($tab) {
$cb->({ error => "Unable to open $table", errorcode => 4 });
return 1;
}
my $DBname = xCAT::Utils->get_DBName;
my @recs;
my $attrrecid = "recid";
# display the output
if ($DBname =~ /^DB2/) {
@recs = $tab->getAllAttribsWhere("\"$attrrecid\">$recid", 'ALL');
} else {
@recs = $tab->getAllAttribsWhere("$attrrecid>$recid", 'ALL');
}
output_table($table, $cb, $tab, \@recs);
return $rc;
}
# Display information from the daemon.
#
sub lsxcatd
{
my $args = shift;
my $cb = shift;
my $HELP;
my $VERSION;
my $DATABASE;
my $NODETYPE;
my $ALL;
my $rc = 0;
my $lsxcatd_usage = sub {
my $exitcode = shift @_;
my %rsp;
push @{ $rsp{data} }, " lsxcatd [-v|--version]";
push @{ $rsp{data} }, " lsxcatd [-h|--help]";
push @{ $rsp{data} }, " lsxcatd [-d|--database]";
push @{ $rsp{data} }, " lsxcatd [-t|--nodetype]";
push @{ $rsp{data} }, " lsxcatd [-a|--all]";
if ($exitcode) { $rsp{errorcode} = $exitcode; }
$cb->(\%rsp);
};
# Process arguments
if ($args) {
@ARGV = @{$args};
}
if (scalar(@ARGV) == 0) { $lsxcatd_usage->(1); return; }
if (!GetOptions('h|?|help' => \$HELP,
'v|version' => \$VERSION,
'a|all' => \$ALL,
't|type' => \$NODETYPE,
'd|database' => \$DATABASE))
{ $lsxcatd_usage->(1); return; }
if ($HELP) { $lsxcatd_usage->(0); return; }
# Version
if ($VERSION || $ALL) {
my %rsp;
my $version = xCAT::Utils->Version();
$rsp{data}->[0] = "$version";
$cb->(\%rsp);
if (!$ALL) {
return;
}
}
# nodetype MN or SN
if ($NODETYPE || $ALL) {
my %rsp;
if (xCAT::Utils->isMN()) { # if on Management Node
$rsp{data}->[0] = "This is a Management Node";
$cb->(\%rsp);
}
if (xCAT::Utils->isServiceNode()) { # if on Management Node
$rsp{data}->[0] = "This is a Service Node";
$cb->(\%rsp);
}
if (!$ALL) {
return;
}
}
# no arguments error
my $xcatcfg;
my %rsp;
if ($DATABASE || $ALL) {
$xcatcfg = xCAT::Table->get_xcatcfg();
if ($xcatcfg =~ /^SQLite:/) { # SQLite just return SQlite
$rsp{data}->[0] = "dbengine=SQLite";
$cb->(\%rsp);
}
if ($xcatcfg =~ /^DB2:/) { # for DB2 , get schema name
my @parts = split('\|', $xcatcfg);
my $cfgloc = $parts[0] . "|" . $parts[1];
my $instance;
$instance = $parts[1];
my ($db2, $databasename) = split(':', $parts[0]);
my $xcatdbhome = xCAT::Utils->getHomeDir("xcatdb");
$rsp{data}->[0] = "cfgloc=$cfgloc";
$rsp{data}->[1] = "dbengine=$db2";
$rsp{data}->[2] = "dbinstance=$instance";
$rsp{data}->[3] = "dbname=$databasename";
$rsp{data}->[4] = "dbloc=$xcatdbhome";
$cb->(\%rsp);
}
if (($xcatcfg =~ /^mysql:/) || ($xcatcfg =~ /^Pg:/)) {
my @parts = split('\|', $xcatcfg);
my $cfgloc = $parts[0] . "|" . $parts[1];
my ($host, $addr) = split('host=', $parts[0]);
my ($engine, $databasenamestr) = split(':', $host);
my ($db, $databasename) = split('=', $databasenamestr);
chop $databasename;
$rsp{data}->[0] = "cfgloc=$cfgloc";
$rsp{data}->[1] = "dbengine=$engine";
$rsp{data}->[2] = "dbname=$databasename";
$rsp{data}->[3] = "dbhost=$addr";
$rsp{data}->[4] = "dbadmin=$parts[1]";
$cb->(\%rsp);
}
}
return $rc;
}
# Prune records from the eventlog or auditlog or all records.
# Only supports eventlog and auditlog
sub tabprune
{
my $args = shift;
my $cb = shift;
my $HELP;
my $VERSION;
my $ALL;
my $NUMBERENTRIES;
my $PERCENT;
my $VERBOSE;
my $RECID;
my $NUMBERDAYS;
my $rc = 0;
my $tabprune_usage = sub {
my $exitcode = shift @_;
my %rsp;
push @{ $rsp{data} }, "Usage: tabprune <tablename> [-V] -a";
push @{ $rsp{data} }, " tabprune <tablename> [-V] -n <# of records>";
push @{ $rsp{data} }, " tabprune <tablename> [-V] -i <recid>";
push @{ $rsp{data} }, " tabprune <tablename> [-V] -p <percent>";
push @{ $rsp{data} }, " tabprune <tablename> [-V] -d <# of days>";
push @{ $rsp{data} }, " tabprune [-h|--help]";
push @{ $rsp{data} }, " tabprune [-v|--version]";
push @{ $rsp{data} }, " tables supported:eventlog,auditlog,unless -a which supports all tables";
push @{ $rsp{data} }, " -d option only supported for eventlog,auditlog";
if ($exitcode) { $rsp{errorcode} = $exitcode; }
$cb->(\%rsp);
};
# Process arguments
if ($args) {
@ARGV = @{$args};
}
if (!GetOptions('h|?|help' => \$HELP,
'v|version' => \$VERSION,
'V' => \$VERBOSE,
'p|percent=i' => \$PERCENT,
'd|days=i' => \$NUMBERDAYS,
'i|recid=s' => \$RECID,
'a' => \$ALL,
'n|number=i' => \$NUMBERENTRIES))
{ $tabprune_usage->(1); return; }
if ($HELP) { $tabprune_usage->(0); return; }
# Version
if ($VERSION) {
my %rsp;
my $version = xCAT::Utils->Version();
$rsp{data}->[0] = "$version";
$cb->(\%rsp);
return;
}
if (scalar(@ARGV) > 1) { $tabprune_usage->(1); return; }
my $table = $ARGV[0];
if (!(defined $table)) {
my %rsp;
$rsp{data}->[0] = "Table name required.";
$rsp{errorcode} = 1;
$cb->(\%rsp);
return 1;
}
$table =~ s/\s*//g; # remove blanks
if (($table ne "eventlog") && ($table ne "auditlog") && ($table ne "isnm_perf") && ($table ne "isnm_perf_sum") && (!$ALL)) {
my %rsp;
$rsp{data}->[0] = "Table $table not supported, see tabprune -h for supported tables.";
$rsp{errorcode} = 1;
$cb->(\%rsp);
return 1;
}
# only support days option for eventlog and auditlog
if (($table ne "eventlog") && ($table ne "auditlog") && (defined $NUMBERDAYS)) {
my %rsp;
$rsp{data}->[0] = "Table $table not supported for the -d option.";
$rsp{errorcode} = 1;
$cb->(\%rsp);
return 1;
}
if ((!(defined $PERCENT)) && (!(defined $NUMBERDAYS)) && (!(defined $RECID)) && (!(defined $ALL)) && (!(defined $NUMBERENTRIES))) {
my %rsp;
$rsp{data}->[0] = "One option -p or -i or -n or -a or -d must be supplied.";
$rsp{errorcode} = 1;
$cb->(\%rsp);
return 1;
}
if ((defined $PERCENT) && ((defined $RECID) || (defined $ALL) || (defined $NUMBERENTRIES))) {
my %rsp;
$rsp{data}->[0] = "Only one option -p or -i or -n or -a maybe used at a time.";
$rsp{errorcode} = 1;
$cb->(\%rsp);
return 1;
}
if ((defined $RECID) && ((defined $PERCENT) || (defined $ALL) || (defined $NUMBERENTRIES))) {
my %rsp;
$rsp{data}->[0] = "Only one option -p or -i or -n or -a maybe used at a time.";
$rsp{errorcode} = 1;
$cb->(\%rsp);
return 1;
}
if ((defined $ALL) && ((defined $PERCENT) || (defined $RECID) || (defined $NUMBERENTRIES))) {
my %rsp;
$rsp{data}->[0] = "Only one option -p or -i or -n or -a maybe used at a time.";
$rsp{errorcode} = 1;
$cb->(\%rsp);
return 1;
}
if ((defined $NUMBERENTRIES) && ((defined $PERCENT) || (defined $RECID) || (defined $ALL))) {
my %rsp;
$rsp{data}->[0] = "Only one option -p or -i or -n or -a maybe used at a time.";
$rsp{errorcode} = 1;
$cb->(\%rsp);
return 1;
}
# determine the attribute name of the recid
my $attrrecid;
if (($table eq "eventlog") || ($table eq "auditlog")) {
$attrrecid = "recid";
} else {
if ($table eq "isnm_perf") { # if ISNM These tables are really not supported in 2.8 or later
$attrrecid = "perfid";
} else {
$attrrecid = "period"; # isnm_perf_sum table
}
}
if (defined $ALL) {
$rc = tabprune_all($table, $cb, $attrrecid, $VERBOSE);
}
if (defined $NUMBERENTRIES) {
$rc = tabprune_numberentries($table, $cb, $NUMBERENTRIES, "n", $attrrecid, $VERBOSE);
}
if (defined $PERCENT) {
$rc = tabprune_numberentries($table, $cb, $PERCENT, "p", $attrrecid, $VERBOSE);
}
if (defined $RECID) {
$rc = tabprune_recid($table, $cb, $RECID, $attrrecid, $VERBOSE);
}
if (defined $NUMBERDAYS) {
$rc = tabprune_numberdays($table, $cb, $NUMBERDAYS, $attrrecid, $VERBOSE);
}
if (!($VERBOSE)) { # not putting out changes
my %rsp;
push @{ $rsp{data} }, "tabprune of $table complete.";
$rsp{errorcode} = $rc;
$cb->(\%rsp);
}
return $rc;
}
sub tabprune_all {
my $table = shift;
my $cb = shift;
my $attrrecid = shift;
my $VERBOSE = shift;
my $rc = 0;
my $tab = xCAT::Table->new($table);
unless ($tab) {
$cb->({ error => "Unable to open $table", errorcode => 4 });
return 1;
}
if ($VERBOSE) { # will output change to std
my $recs = $tab->getAllEntries("all");
output_table($table, $cb, $tab, $recs);
}
$tab->delEntries(); #Yes, delete *all* entries
$tab->commit; # commit
return $rc;
}
# prune input number of records for the table
# if number of entries > number than in the table, then remove all
# this handles the number of records or percentage to delete
sub tabprune_numberentries {
my $table = shift;
my $cb = shift;
my $numberentries = shift; # either number of entries or percent to
# remove based on the flag
my $flag = shift; # (n or p flag)
my $attrrecid = shift;
my $VERBOSE = shift;
my $rc = 0;
my $tab = xCAT::Table->new($table);
unless ($tab) {
$cb->({ error => "Unable to open $table", errorcode => 4 });
return 1;
}
my $RECID;
my $values = $tab->getMAXMINEntries($attrrecid);
if ((defined($values->{"max"})) && (defined($values->{"min"}))) {
my $largerid = $values->{"max"};
my $smallrid = $values->{"min"};
if ($flag eq "n") { # deleting number of records
#get the smalled recid and add number to delete, that is where to start removing
$RECID = $smallrid + $numberentries;
} else { # flag must be percentage
#take largest and smallest recid and percentage and determine the recid
# that will remove the requested percentage. If some are missing in the
# middle due to tabedit, we are not worried about it.
my $totalnumberrids = $largerid - $smallrid + 1;
my $percent = $numberentries / 100;
my $percentage = $totalnumberrids * $percent;
my $cnt = sprintf("%d", $percentage); # round to whole number
$RECID = $smallrid + $cnt; # get recid to remove all before
}
# Now prune starting at $RECID
$rc = tabprune_recid($table, $cb, $RECID, $attrrecid, $VERBOSE);
} else {
my %rsp;
push @{ $rsp{data} }, "Nothing to prune from $table.";
$rsp{errorcode} = $rc;
$cb->(\%rsp);
}
return $rc;
}
# prune all entries up to the record id input
# if rec id does not exist error
sub tabprune_recid {
my $table = shift;
my $cb = shift;
my $recid = shift;
my $attrrecid = shift;
my $VERBOSE = shift;
my $rc = 0;
# check which database so can build the correct Where clause
my $tab = xCAT::Table->new($table);
unless ($tab) {
$cb->({ error => "Unable to open $table", errorcode => 4 });
return 1;
}
my $DBname = xCAT::Utils->get_DBName;
# display the output
my @recs;
if ($VERBOSE) { # need to get all attributes
if ($DBname =~ /^DB2/) {
@recs = $tab->getAllAttribsWhere("\"$attrrecid\"<$recid", 'ALL');
} else {
@recs = $tab->getAllAttribsWhere("$attrrecid<$recid", 'ALL');
}
output_table($table, $cb, $tab, \@recs);
}
my @ents;
if ($DBname =~ /^DB2/) {
@ents = $tab->getAllAttribsWhere("\"$attrrecid\"<$recid", $attrrecid);
} else {
@ents = $tab->getAllAttribsWhere("$attrrecid<$recid", $attrrecid);
}
# delete them
foreach my $rid (@ents) {
$tab->delEntries($rid);
}
$tab->commit;
return $rc;
}
# prune all record up to number of days from today
sub tabprune_numberdays {
my $table = shift;
my $cb = shift;
my $numberdays = shift;
my $attrrecid = shift;
my $VERBOSE = shift;
my $rc = 0;
# check which database so can build the correct Where clause
my $tab = xCAT::Table->new($table);
unless ($tab) {
$cb->({ error => "Unable to open $table", errorcode => 4 });
return 1;
}
# get number of seconds in the day count
my $numbersecs = ($numberdays * 86400);
# get time now
my $timenow = time;
my $secsdaysago = $timenow - $numbersecs;
# Format like the database table timestamp record
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
localtime($secsdaysago);
my $daysago = sprintf("%04d-%02d-%02d %02d:%02d:%02d",
$year + 1900, $mon + 1, $mday,
$hour, $min, $sec);
# delete all records before # days ago
# display the output
# get field name for the table
my $timeattr = "audittime"; # default auditlog, most used
if ($table eq "eventlog") {
$timeattr = "eventtime";
}
my @attrarray;
push @attrarray, "$timeattr<$daysago";
my @recs;
if ($VERBOSE) { # need to get all attributes
@recs = $tab->getAllAttribsWhere(\@attrarray, 'ALL');
output_table($table, $cb, $tab, \@recs);
}
my @ents = $tab->getAllAttribsWhere(\@attrarray, $attrrecid);
# delete them
foreach my $rid (@ents) {
$tab->delEntries($rid);
}
$tab->commit;
return $rc;
}
# Dump table records to stdout.
sub output_table {
my $table = shift;
my $cb = shift;
my $tabh = shift;
my $recs = shift;
my %rsp;
my $tabdump_header = sub {
my $header = "#" . join(",", @_);
push @{ $rsp{data} }, $header;
};
# Display all the rows of the table in order of the columns in the schema
$tabdump_header->(@{ $tabh->{colnames} });
foreach my $rec (@$recs)
{
my $line = '';
foreach (@{ $tabh->{colnames} })
{
if (defined $rec->{$_})
{
$rec->{$_} =~ s/"/""/g;
$line = $line . '"' . $rec->{$_} . '",';
}
else
{
$line .= ',';
}
}
$line =~ s/,$//; # remove the extra comma at the end
push @{ $rsp{data} }, $line;
}
$cb->(\%rsp);
return;
}
sub getTableColumn {
my $string = shift;
if ($shortnames{$string}) {
return @{ $shortnames{$string} };
}
unless ($string =~ /\./) {
return undef;
}
return split /\./, $string, 2;
}
# sub groupch
# {
# my $args = shift;
# my $nodech_usage = sub
# {
# my $exitcode = shift @_;
# my $cmdname = "groupch";
# my %rsp;
# push @{$rsp{data}}, "Usage: $cmdname <group1,group2,...> table.column=value [...]";
# push @{$rsp{data}}, " $cmdname {-v | --version}";
# push @{$rsp{data}}, " $cmdname [-? | -h | --help]";
# if ($exitcode) { $rsp{errorcode} = $exitcode; }
# $callback->(\%rsp);
# };
# my @args = @{$args};
# unless (scalar @args >= 2) {
# $nodech_usage->(1);
# return;
# }
# my @groups = split /,/,shift @args;
# foreach (@args)
# {
# # if ($deletemode)
# # {
# # if (m/[=\.]/) # in delete mode they can only specify tables names
# # {
# # $callback->({error => [". and = not valid in delete mode."],errorcode=>1});
# # next;
# # }
# # $tables{$_} = 1;
# # next;
# # }
# unless (m/=/ or m/!~/)
# {
# $callback->({error => ["Malformed argument $_ ignored."],errorcode=>1});
# next;
# }
# my $stable;
# my $scolumn;
# #Check for selection criteria
# if (m/^[^=]*==/) {
# ($temp,$value)=split /==/,$_,2;
# ($stable,$scolumn)=getTableColumn($temp);
# $criteria{$stable}->{$scolumn}=[$value,'match'];
# next; #Is a selection criteria, not an assignment specification
# } elsif (m/^[^=]*!=/) {
# ($temp,$value)=split /!=/,$_,2;
# ($stable,$scolumn)=getTableColumn($temp);
# $criteria{$stable}->{$scolumn}=[$value,'natch'];
# next; #Is a selection criteria, not an assignment specification
# } elsif (m/^[^=]*=~/) {
# ($temp,$value)=split /=~/,$_,2;
# ($stable,$scolumn)=getTableColumn($temp);
# $value =~ s/^\///;
# $value =~ s/\/$//;
# $criteria{$stable}->{$scolumn}=[$value,'regex'];
# next; #Is a selection criteria, not an assignment specification
# } elsif (m/^[^=]*!~/) {
# ($temp,$value)=split /!~/,$_,2;
# ($stable,$scolumn)=getTableColumn($temp);
# $value =~ s/^\///;
# $value =~ s/\/$//;
# $criteria{$stable}->{$scolumn}=[$value,'negex'];
# next; #Is a selection criteria, not an assignment specification
# }
# #Now definitely an assignment
#
# ($temp, $value) = split('=', $_, 2);
# $value =~ s/^@//; #Allow the =@ operator to exist for an unambiguous assignmenet operator
# #So before, table.column==value meant set to =value, now it would be matching value
# #the new way would be table.column=@=value to be unambiguous
# #now a value like '@hi' would be set with table.column=@@hi
# if ($value eq '') { #If blank, force a null entry to override group settings
# $value = '|^.*$||';
# }
# my $op = '=';
# if ($temp =~ /,$/)
# {
# $op = ',=';
# chop($temp);
# }
# elsif ($temp =~ /\^$/)
# {
# $op = '^=';
# chop($temp);
# }
# my $table;
# if ($shortnames{$temp})
# {
# ($table, $column) = @{$shortnames{$temp}};
# }
# else
# {
# ($table, $column) = split('\.', $temp, 2);
# }
# unless (grep /$column/,@{$xCAT::Schema::tabspec{$table}->{cols}}) {
# $callback->({error=>"$table.$column not a valid table.column description",errorcode=>[1]});
# return;
# }
# # Keep a list of the value/op pairs, in case there is more than 1 per table.column
# #$tables{$table}->{$column} = [$value, $op];
# push @{$tables{$table}->{$column}}, ($value, $op);
# }
# }
#
# Process the nodech command, also used by the XML setNodesAttribs utility
# in tabutils
#
sub nodech
{
my $nodes = shift;
my $args = shift;
my $callback = shift;
my $addmode = shift;
my $groupmode;
if ($addmode eq "groupch") {
$addmode = 0;
$groupmode = 1;
}
my $VERSION;
my $HELP;
my $deletemode;
my $grptab;
my @grplist;
my $nodech_usage = sub
{
my $exitcode = shift @_;
my $addmode = shift @_;
my $groupmode = shift @_;
my $cmdname = $addmode ? 'nodeadd' : ($groupmode ? 'nodegrpch' : 'nodech');
my %rsp;
if ($addmode) {
push @{ $rsp{data} }, "Usage: $cmdname <noderange> groups=<groupnames> [table.column=value] [...]";
} elsif ($groupmode) {
push @{ $rsp{data} }, "Usage: $cmdname <group1,group2,...> [table.column=value] [...]";
} else {
push @{ $rsp{data} }, "Usage: $cmdname <noderange> table.column=value [...]";
push @{ $rsp{data} }, " $cmdname {-d | --delete} <noderange> <table> [...]";
}
push @{ $rsp{data} }, " $cmdname {-v | --version}";
push @{ $rsp{data} }, " $cmdname [-? | -h | --help]";
if ($exitcode) { $rsp{errorcode} = $exitcode; }
$callback->(\%rsp);
};
if ($args) {
@ARGV = @{$args};
} else {
@ARGV = ();
}
my %options = ('h|?|help' => \$HELP, 'v|version' => \$VERSION);
if (!$addmode) { $options{'d|delete'} = \$deletemode; }
if (!GetOptions(%options)) {
$nodech_usage->(1, $addmode, $groupmode);
return;
}
# Help
if ($HELP) {
$nodech_usage->(0, $addmode, $groupmode);
return;
}
# Version
if ($VERSION) {
my %rsp;
my $version = xCAT::Utils->Version();
$rsp{data}->[0] = "$version";
$callback->(\%rsp);
return;
}
# Note: the noderange comes through in $arg (and therefore @ARGV) for nodeadd,
# because it is linked to xcatclientnnr, since the nodes specified in the noderange
# do not exist yet. The nodech cmd is linked to xcatclient, so its noderange is
# put in $nodes instead of $args.
if (scalar(@ARGV) < (1 + $addmode)) { $nodech_usage->(1, $addmode); return; }
my @groups;
if ($addmode)
{
my $nr = shift @ARGV;
$nodes = [ noderange($nr, 0) ];
unless ($nodes) {
$callback->({ error => "No noderange to add.\n", errorcode => 1 });
return;
}
my $invalidnodename = ();
foreach my $node (@$nodes) {
if ($node =~ /[A-Z]/) {
$invalidnodename .= ",$node";
}
}
if ($invalidnodename) {
$invalidnodename =~ s/,//;
$callback->({ warning => "The node name \'$invalidnodename\' contains capital letters which may not be resolved correctly by the dns server." });
}
} elsif ($groupmode) {
@groups = split /,/, shift @ARGV;
}
my $column;
my $value;
my $temp;
my %tables;
my %criteria = ();
my $tab;
#print Dumper($deletemode);
foreach (@ARGV)
{
if ($deletemode)
{
if (m/[=\.]/) # in delete mode they can only specify tables names
{
$callback->({ error => [". and = not valid in delete mode."], errorcode => 1 });
next;
}
$tables{$_} = 1;
next;
}
unless (m/=/ or m/!~/)
{
$callback->({ error => ["Malformed argument $_ ignored."], errorcode => 1 });
next;
}
my $stable;
my $scolumn;
#Check for selection criteria
if (m/^[^=]*==/) {
($temp, $value) = split /==/, $_, 2;
($stable, $scolumn) = getTableColumn($temp);
$criteria{$stable}->{$scolumn} = [ $value, 'match' ];
next; #Is a selection criteria, not an assignment specification
} elsif (m/^[^=]*!=/) {
($temp, $value) = split /!=/, $_, 2;
($stable, $scolumn) = getTableColumn($temp);
$criteria{$stable}->{$scolumn} = [ $value, 'natch' ];
next; #Is a selection criteria, not an assignment specification
} elsif (m/^[^=]*=~/) {
($temp, $value) = split /=~/, $_, 2;
($stable, $scolumn) = getTableColumn($temp);
$value =~ s/^\///;
$value =~ s/\/$//;
$criteria{$stable}->{$scolumn} = [ $value, 'regex' ];
next; #Is a selection criteria, not an assignment specification
} elsif (m/^[^=]*!~/) {
($temp, $value) = split /!~/, $_, 2;
($stable, $scolumn) = getTableColumn($temp);
$value =~ s/^\///;
$value =~ s/\/$//;
$criteria{$stable}->{$scolumn} = [ $value, 'negex' ];
next; #Is a selection criteria, not an assignment specification
}
#Now definitely an assignment
($temp, $value) = split('=', $_, 2);
$value =~ s/^@//; #Allow the =@ operator to exist for an unambiguous assignmenet operator
#So before, table.column==value meant set to =value, now it would be matching value
#the new way would be table.column=@=value to be unambiguous
#now a value like '@hi' would be set with table.column=@@hi
if ($value eq '') { #If blank, force a null entry to override group settings
$value = '|^.*$||';
}
my $op = '=';
if ($temp =~ /,$/)
{
$op = ',=';
chop($temp);
}
elsif ($temp =~ /\^$/)
{
$op = '^=';
chop($temp);
}
my $table;
if ($shortnames{$temp})
{
($table, $column) = @{ $shortnames{$temp} };
}
else
{
($table, $column) = split('\.', $temp, 2);
}
unless (grep /$column/, @{ $xCAT::Schema::tabspec{$table}->{cols} }) {
$callback->({ error => "$table.$column not a valid table.column description", errorcode => [1] });
return;
}
# Keep a list of the value/op pairs, in case there is more than 1 per table.column
#$tables{$table}->{$column} = [$value, $op];
push @{ $tables{$table}->{$column} }, ($value, $op);
}
my %nodehash;
if (keys %criteria) {
foreach (@$nodes) {
$nodehash{$_} = 1;
}
}
foreach $tab (keys %criteria) {
my $tabhdl = xCAT::Table->new($tab, -create => 1, -autocommit => 0);
my @columns = keys %{ $criteria{$tab} };
my $tabhash = $tabhdl->getNodesAttribs($nodes, \@columns);
my $node;
my $col;
my $rec;
foreach $node (@$nodes) {
foreach $rec (@{ $tabhash->{$node} }) {
foreach $col (@columns) {
my $value = $criteria{$tab}->{$col}->[0];
unless (defined $value) {
$value = "";
}
my $matchtype = $criteria{$tab}->{$col}->[1];
if ($matchtype eq 'match' and not($rec->{$col} eq $value) or
$matchtype eq 'natch' and ($rec->{$col} eq $value) or
$matchtype eq 'regex' and ($rec->{$col} !~ /$value/) or
$matchtype eq 'negex' and ($rec->{$col} =~ /$value/)) {
delete $nodehash{$node};
}
}
}
}
$nodes = [ keys %nodehash ];
}
foreach $tab (keys %tables)
{
my $tabhdl = xCAT::Table->new($tab, -create => 1, -autocommit => 0);
if ($tabhdl)
{
my $changed = 0;
my @entities;
if ($groupmode) {
@entities = @groups;
} else {
@entities = @$nodes;
}
my $entity;
foreach $entity (@entities) {
if ($deletemode) {
$tabhdl->delEntries({ 'node' => $entity });
$changed = 1;
} else {
#$tabhdl->setNodeAttribs($_,$tables{$tab});
my %uhsh;
foreach (keys %{ $tables{$tab} }) # for each column specified for this table
{
#my $op = $tables{$tab}->{$_}->[1];
#my $val = $tables{$tab}->{$_}->[0];
my @valoppairs = @{ $tables{$tab}->{$_} }; #Deep copy
while (scalar(@valoppairs)) { # alternating list of value and op for this table.column
my $val = shift @valoppairs;
my $op = shift @valoppairs;
my $key = $_;
# When changing the groups of the node, check whether the new group
# is a dynamic group.
if (($key eq 'groups') && ($op eq '=')) {
if ($groupmode) {
$callback->({ error => "Group membership is not changeable via nodegrpch", errorcode => 1 });
return;
}
if (scalar(@grplist) == 0) { # Do not call $grptab->getAllEntries for each node, performance issue.
$grptab = xCAT::Table->new('nodegroup');
if ($grptab) {
@grplist = @{ $grptab->getAllEntries() };
}
}
my @grps = split(/,/, $val);
foreach my $grp (@grps) {
foreach my $grpdef_ref (@grplist) {
my %grpdef = %$grpdef_ref;
if (($grpdef{'groupname'} eq $grp) && ($grpdef{'grouptype'} eq 'dynamic')) {
my %rsp;
$rsp{data}->[0] = "nodegroup $grp is a dynamic node group, should not add a node into a dynamic node group statically.\n";
$callback->(\%rsp);
}
}
}
}
if ($op eq '=') {
$uhsh{$key} = $val;
} elsif ($op eq ',=') { #splice assignment
if ($groupmode) {
$callback->({ error => ",= and ^= are not supported by nodegrpch", errorcode => 1 });
return;
}
my $curval = $uhsh{$key}; # in case it was already set
if (!defined($curval)) {
my $cent = $tabhdl->getNodeAttribs($entity, [$key]);
if ($cent) { $curval = $cent->{$key}; }
}
if ($curval) {
foreach my $subval (split /,/, $val) {
my @vals = split(/,/, $curval);
unless (grep /^$subval$/, @vals) {
unshift @vals, $subval;
my $newval = join(',', @vals);
$uhsh{$key} = $newval;
$curval = $newval;
}
}
} else {
$uhsh{$key} = $val;
}
} elsif ($op eq '^=') {
if ($groupmode) {
$callback->({ error => ",= and ^= are not supported by nodegrpch", errorcode => 1 });
return;
}
my $curval = $uhsh{$key}; # in case it was already set
if (!defined($curval)) {
my $cent = $tabhdl->getNodeAttribs($entity, [$key]);
if ($cent) { $curval = $cent->{$key}; }
}
if ($curval) {
foreach my $subval (split /,/, $val) {
my @vals = split(/,/, $curval);
if (grep /^$subval$/, @vals) { #only bother if there
@vals = grep(!/^$subval$/, @vals);
my $newval = join(',', @vals);
$uhsh{$key} = $newval;
$curval = $newval;
}
}
} #else, what they asked for is the case alredy
}
} # end of while @valoppairs
} # end of foreach column specified for this table
if (keys %uhsh)
{
if ($groupmode) {
my $nodekey = "node";
if (defined $xCAT::Schema::tabspec{$tab}->{nodecol}) {
$nodekey = $xCAT::Schema::tabspec{$tab}->{nodecol}
}
my %clrhash; #First, we prepare to clear all nodes of their overrides on these columns
foreach (keys %uhsh) {
if ($_ eq $nodekey) { next; } #skip attempts to manipulate 'node' type columns in a groupch
$clrhash{$_} = "";
}
$tabhdl->setAttribs({ $nodekey => $entity }, \%uhsh);
$changed = 1;
$nodes = [ noderange($entity) ];
unless (scalar @$nodes) { next; }
$tabhdl->setNodesAttribs($nodes, \%clrhash);
$changed = 1;
} else {
my @rc = $tabhdl->setNodeAttribs($entity, \%uhsh);
$changed = 1;
if (not defined($rc[0])) {
$callback->({ error => "DB error " . $rc[1], errorcode => 1 });
}
}
}
}
}
if ($changed) {
$tabhdl->commit;
}
}
else
{
$callback->(
{ error => ["ERROR: Unable to open table $tab in configuration"], errorcode => 1 }
);
}
}
}
# gennr linked to xcatclientnnr and is used to generate a list of nodes
# external to the database.
sub gennr {
my $nodes = shift;
my $args = shift;
my $callback = shift;
@ARGV = @{$args};
my $nr = shift @ARGV;
$nodes = [ noderange($nr, 0) ];
my %rsp; # for output.
foreach (@$nodes) {
#print $_ . "\n";
push @{ $rsp{data} }, $_;
}
$callback->(\%rsp);
}
sub tabgrep
{
my $node = shift;
my @tablist;
my $callback = shift;
if (!defined($node) || !scalar(@$node)) {
my %rsp;
push @{ $rsp{data} }, "Usage: tabgrep nodename";
push @{ $rsp{data} }, " tabgrep [-?|-h|--help]";
$rsp{errorcode} = 1;
$callback->(\%rsp);
return;
}
foreach (keys %{xCAT::Schema::tabspec})
{
if (grep /^node$/, @{ $xCAT::Schema::tabspec{$_}->{cols} })
{
push @tablist, $_;
}
}
foreach (@tablist)
{
my $tab = xCAT::Table->new($_);
unless ($tab) { next; }
if ($tab and $tab->getNodeAttribs($node->[0], ["node"]))
{
$callback->({ data => [$_] });
}
$tab->close;
}
}
sub rnoderange
{
my $nodes = shift;
my $args = shift;
my $callback = shift;
my $data = abbreviate_noderange($nodes);
if ($data) {
$callback->({ data => [$data] });
}
}
#####################################################
# nodels command
#####################################################
sub nodels
{
my $nodes = shift;
my $args = shift;
my $callback = shift;
my $noderange = shift;
unless ($nodes) {
$nodes = [];
}
my $VERSION;
my $HELP;
my $nodenum;
my $nodels_usage = sub
{
my $exitcode = shift @_;
my %rsp;
push @{ $rsp{data} }, "Usage:";
push @{ $rsp{data} }, " nodels [noderange] [-b|--blame] [-H|--with-fieldname] [table.attribute | shortname] [-S][...]";
push @{ $rsp{data} }, " nodels {-v|--version}";
push @{ $rsp{data} }, " nodels [-?|-h|--help]";
if ($exitcode) { $rsp{errorcode} = $exitcode; }
$callback->(\%rsp);
};
if ($args) {
@ARGV = @{$args};
} else {
@ARGV = ();
}
my $NOTERSE;
my $ATTRIBUTION;
my $HIDDEN;
if (!GetOptions('h|?|help' => \$HELP, 'H|with-fieldname' => \$NOTERSE, 'b|blame' => \$ATTRIBUTION, 'v|version' => \$VERSION, 'S' => \$HIDDEN)) { $nodels_usage->(1); return; }
# Help
if ($HELP) { $nodels_usage->(0); return; }
# Version
if ($VERSION)
{
my %rsp;
my $version = xCAT::Utils->Version();
$rsp{data}->[0] = "$version";
$callback->(\%rsp);
return;
}
# TODO -- Parse command arguments
# my $opt;
# my %attrs;
# foreach $opt (@ARGV) {
# if ($opt =~ /^group/) {
# }
# }
my $argc = @ARGV;
my $terse = 2;
if ($NOTERSE) {
$terse = 0;
}
if (@$nodes > 0 or $noderange)
{ #Make sure that there are zero nodes *and* that a noderange wasn't requested
# TODO - gather data for each node
# for now just return the flattened list of nodes)
my $rsp; #build up fewer requests, be less chatty
#-S will make nodels not show FSPs and BPAs
my @newnodes = ();
if (!defined($HIDDEN))
{
my $listtab = xCAT::Table->new('nodelist');
if ($listtab) {
my $listHash = $listtab->getNodesAttribs(\@$nodes, ['hidden']);
foreach my $rnode (@$nodes) {
unless (defined($listHash->{$rnode}->[0]->{hidden})) {
push(@newnodes, $rnode);
} elsif ($listHash->{$rnode}->[0]->{hidden} != 1) {
push(@newnodes, $rnode);
}
}
}
$nodes = \@newnodes;
}
if ($argc)
{
my %tables;
foreach (@ARGV)
{
my $table;
my $column;
my $value;
my $matchtype;
my $temp = $_;
if ($temp =~ /^[^=]*\!=/) {
($temp, $value) = split /!=/, $temp, 2;
$matchtype = 'natch';
}
elsif ($temp =~ /^[^=]*=~/) {
($temp, $value) = split /=~/, $temp, 2;
$value =~ s/^\///;
$value =~ s/\/$//;
$matchtype = 'regex';
}
elsif ($temp =~ /[^=]*==/) {
($temp, $value) = split /==/, $temp, 2;
$matchtype = 'match';
}
elsif ($temp =~ /[^=]*!~/) {
($temp, $value) = split /!~/, $temp, 2;
$value =~ s/^\///;
$value =~ s/\/$//;
$matchtype = 'negex';
}
if ($shortnames{$temp})
{
($table, $column) = @{ $shortnames{$temp} };
$terse--;
} elsif ($temp =~ /\./) {
($table, $column) = split('\.', $temp, 2);
$terse--;
} elsif ($xCAT::Schema::tabspec{$temp}) {
$terse = 0;
$table = $temp;
foreach my $column (@{ $xCAT::Schema::tabspec{$table}->{cols} }) {
unless (grep /^$column$/, @{ $tables{$table} }) {
push @{ $tables{$table} }, [ $column, "$temp.$column" ];
}
}
next;
} else {
$callback->({ error => "$temp not a valid table.column description", errorcode => [1] });
next;
}
unless (grep /$column/, @{ $xCAT::Schema::tabspec{$table}->{cols} }) {
$callback->({ error => "$table.$column not a valid table.column description", errorcode => [1] });
next;
}
unless (grep /^$column$/, @{ $tables{$table} })
{
push @{ $tables{$table} },
[ $column, $temp, $value, $matchtype ]; #Mark this as something to get
}
}
my $tab;
my %noderecs;
my %filterednodes = ();
my %mustdisplaynodes = ();
my %forcedisplaykeys = ();
foreach $tab (keys %tables)
{
my $tabh = xCAT::Table->new($tab);
unless ($tabh) { next; }
#print Dumper($tables{$tab});
my $node;
my %labels;
my %values;
my %matchtypes;
my @cols = ();
foreach (@{ $tables{$tab} })
{
push @cols, $_->[0];
$labels{ $_->[0] } = $_->[1]; #Remember user supplied discreptions and use them
if (not defined $values{ $_->[0] }) { #If selection criteria not previously specified
$values{ $_->[0] } = $_->[2]; #assign selection criteria
} elsif (not defined $_->[2]) { #we already have selection criteria, but this field isn't that
$forcedisplaykeys{ $_->[0] } = 1; #allow switch.switch=~switch switch.switch, for example
} else { #User attempted multiple selection criteria on the same field, bail
$callback->({ error => [ "Multiple selection critera for " . $labels{ $_->[0] } ] });
return;
}
if (not defined $matchtypes{ $_->[0] }) {
$matchtypes{ $_->[0] } = $_->[3];
}
}
my $nodekey = "node";
if (defined $xCAT::Schema::tabspec{$tab}->{nodecol}) {
$nodekey = $xCAT::Schema::tabspec{$tab}->{nodecol}
}
my $removenodecol = 1;
if (grep /^$nodekey$/, @cols) {
$removenodecol = 0;
}
my $rechash = $tabh->getNodesAttribs($nodes, \@cols, withattribution => $ATTRIBUTION);
foreach $node (@$nodes)
{
my @cols;
my $recs = $rechash->{$node}; #$tabh->getNodeAttribs($node, \@cols);
my %satisfiedreqs = ();
foreach my $rec (@$recs) {
foreach (sort keys %$rec)
{
if ($_ eq '!!xcatgroupattribution!!') { next; }
if ($_ eq $nodekey and $removenodecol) { next; }
$satisfiedreqs{$_} = 1;
my %datseg = ();
if (defined $values{$_}) {
my $criteria = $values{$_}; #At least vim highlighting makes me worry about syntax in regex
if ($matchtypes{$_} eq 'match' and not($rec->{$_} eq $criteria) or
$matchtypes{$_} eq 'natch' and ($rec->{$_} eq $criteria) or
$matchtypes{$_} eq 'regex' and ($rec->{$_} !~ /$criteria/) or
$matchtypes{$_} eq 'negex' and ($rec->{$_} =~ /$criteria/)) {
#unless ($rec->{$_} eq $values{$_}) {
$filterednodes{$node} = 1;
next;
}
$mustdisplaynodes{$node} = 1;
unless ($forcedisplaykeys{$_}) { next; } #skip if only specified once on command line
}
unless ($terse > 0) {
$datseg{data}->[0]->{desc} = [ $labels{$_} ];
}
if ($rec->{'!!xcatgroupattribution!!'} and $rec->{'!!xcatgroupattribution!!'}->{$_}) {
$datseg{data}->[0]->{contents} = [ $rec->{$_} . " (inherited from group " . $rec->{'!!xcatgroupattribution!!'}->{$_} . ")" ];
} else {
$datseg{data}->[0]->{contents} = [ $rec->{$_} ];
}
$datseg{name} = [$node]; #{}->{contents} = [$rec->{$_}];
push @{ $noderecs{$node} }, \%datseg;
}
}
foreach (keys %labels) {
unless (defined $satisfiedreqs{$_}) {
my %dataseg;
if (defined $values{$_}) {
my $criteria = $values{$_};
if ($matchtypes{$_} eq 'match' and not("" eq $criteria) or
$matchtypes{$_} eq 'natch' and ("" eq $criteria) or
$matchtypes{$_} eq 'regex' and ("" !~ /$criteria/) or
$matchtypes{$_} eq 'negex' and ("" =~ /$criteria/)) {
#unless ("" eq $values{$_}) {
$filterednodes{$node} = 1;
next;
}
$mustdisplaynodes{$node} = 1;
unless ($forcedisplaykeys{$_}) { next; }
}
$dataseg{name} = [$node];
unless ($terse > 0) {
$dataseg{data}->[0]->{desc} = [ $labels{$_} ];
}
$dataseg{data}->[0]->{contents} = [""];
push @{ $noderecs{$node} }, \%dataseg;
}
}
}
#$rsp->{node}->[0]->{data}->[0]->{desc}->[0] = $_;
#$rsp->{node}->[0]->{data}->[0]->{contents}->[0] = $_;
$tabh->close();
undef $tabh;
}
foreach (keys %mustdisplaynodes) {
if ($filterednodes{$_} or defined $noderecs{$_}) {
next;
}
$noderecs{$_} = [ { name => [$_] } ];
}
foreach (keys %filterednodes) {
delete $noderecs{$_};
}
foreach (sort (keys %noderecs))
{
push @{ $rsp->{"node"} }, @{ $noderecs{$_} };
}
}
else
{
foreach (sort @$nodes)
{
my $noderec;
$noderec->{name}->[0] = ($_);
push @{ $rsp->{node} }, $noderec;
}
}
$callback->($rsp);
$nodenum = scalar (@$nodes);
}
else
{
# no noderange specified on command line, return list of all nodes
my $nodelisttab;
if ($nodelisttab = xCAT::Table->new("nodelist"))
{
my @attribs = ("node");
my @ents = $nodelisttab->getAllAttribs(@attribs);
my @nodes;
foreach (@ents) {
if ($_->{node}) {
push @nodes, $_->{node};
}
}
#-S will make nodels not show FSPs and BPAs
my @newnodes = ();
if (!defined($HIDDEN))
{
my $listtab = xCAT::Table->new('nodelist');
if ($listtab) {
my $listHash = $listtab->getNodesAttribs(\@nodes, ['hidden']);
foreach my $rnode (@nodes) {
unless (defined($listHash->{$rnode}->[0]->{hidden})) {
push(@newnodes, $rnode);
} elsif ($listHash->{$rnode}->[0]->{hidden} != 1) {
push(@newnodes, $rnode);
}
}
}
@nodes = ();
foreach (@newnodes) {
push(@nodes, $_);
}
}
@nodes = sort { $a cmp $b } @nodes;
foreach (@nodes) {
my $rsp;
#if ($_)
#{
$rsp->{node}->[0]->{name}->[0] = ($_);
# $rsp->{node}->[0]->{data}->[0]->{contents}->[0]="$_->{node} node contents";
# $rsp->{node}->[0]->{data}->[0]->{desc}->[0]="$_->{node} node desc";
$callback->($rsp);
#}
}
$nodenum = scalar (@nodes);
}
}
my $rsp_info;
$rsp_info->{numofnodes}->[0] = $nodenum;
$callback->($rsp_info);
return 0;
}
#########
# tabch
#########
sub tabch {
my $req = shift;
my $callback = shift;
# tabch usages message
my $tabch_usage = sub {
my $exitcode = shift @_;
my %rsp;
push @{ $rsp{data} }, "Usage: tabch";
push @{ $rsp{data} }, " To add or update rows for tables:";
push @{ $rsp{data} }, " tabch keycolname=keyvalue[,keycolname=keyvalue...] tablename.colname=newvalue [tablename.colname=newvalue...]";
push @{ $rsp{data} }, " To delete rows from tables:";
push @{ $rsp{data} }, " tabch -d|--delete keycolname=keyvalue[,keycolname=keyvalue...] tablename [tablename...]";
push @{ $rsp{data} }, " keycolname=keyvalue a column name-and-value pair ";
push @{ $rsp{data} }, " that identifies the rows in a table to be changed.";
push @{ $rsp{data} }, " tablename.colname=newvalue ";
push @{ $rsp{data} }, " the new value for the specified row and column of the table.";
push @{ $rsp{data} }, " tabch [-h|--help]";
push @{ $rsp{data} }, " tabch [-v|--version]";
if ($exitcode) { $rsp{errorcode} = $exitcode; }
$callback->(\%rsp);
};
# check for parameters
if (!defined($req->{arg})) { $tabch_usage->(1); return; }
@ARGV = @{ $req->{arg} };
# options can be bundled up like -vV
Getopt::Long::Configure("bundling");
$Getopt::Long::ignorecase = 0;
my $delete;
my $help;
my $version;
# parse the options
if (
!GetOptions(
'd|delete' => \$delete,
'h|help' => \$help,
'v|version' => \$version,
)
)
{
$tabch_usage->(1);
return;
}
if ($help) { $tabch_usage->(0); return; }
# display the version statement if -v or --verison is specified
if ($version)
{
my %rsp;
my $version = xCAT::Utils->Version();
$rsp{data}->[0] = "tabch :$version";
$callback->(\%rsp);
exit(0);
}
# now start processing the input
my $target = shift @ARGV;
unless ($target)
{
$tabch_usage->(1); return;
}
my %tables;
my %keyhash = ();
my @keypairs = split(/,/, $target);
if ($keypairs[0] !~ /([^\.\=]+)\.([^\.\=]+)\=(.+)/)
{
foreach (@keypairs)
{
m/(.*)=(.*)/;
my $key = $1;
my $val = $2;
if (!defined($key) || !defined($val))
{
my %rsp;
$rsp{data}->[0] = "Incorrect argument \"$_\".\n";
$rsp{data}->[1] = "Check man tabch or tabch -h\n";
$rsp{errorcode}->[0] = 1;
$callback->(\%rsp);
return 1;
}
$keyhash{$key} = $val;
}
}
else
{
unshift(@ARGV, $target);
}
if ($delete)
{
#delete option is specified
my @tables_to_del = @ARGV;
if (@tables_to_del == 0)
{
my %rsp;
$rsp{data}->[0] = "Missing table name.\n";
$rsp{data}->[1] = "Check man tabch or tabch -h\n";
$rsp{errorcode}->[0] = 1;
$callback->(\%rsp);
return 1;
}
for (@tables_to_del)
{
my $tab = xCAT::Table->new($_, -create => 1, -autocommit => 0);
unless ($tab) {
my %rsp;
$rsp{data}->[0] = "Table $_ does not exist.";
$rsp{errorcode}->[0] = 1;
$callback->(\%rsp);
next;
}
$tab->delEntries(\%keyhash);
$tab->commit;
}
}
else {
#update or create option
my %tableupdates;
for (@ARGV) {
my $temp;
my $table;
my $column;
my $value;
($table, $temp) = split('\.', $_, 2);
#try to create the entry if it doesn't exist
unless ($tables{$table}) {
my $tab = xCAT::Table->new($table, -create => 1, -autocommit => 0);
if ($tab) {
$tables{$table} = $tab;
} else {
my %rsp;
$rsp{data}->[0] = "Table $table does not exist.\n";
$rsp{errorcode}->[0] = 1;
$callback->(\%rsp);
return 1;
}
}
my $err_found = 0;
for my $k (keys %keyhash) {
unless (grep /$k/, @{ $xCAT::Schema::tabspec{$table}->{cols} }) {
$callback->({ error => ["No column \"$k\" in table \"$table\""], errorcode => [1] });
$err_found = 1;
}
}
if ($err_found) {
return 1;
}
#splice assignment
if (grep /\+=/, $temp) {
($column, $value) = split('\+=', $temp, 2);
#grab the current values to check against
my ($attrHash) = $tables{$table}->getAttribs(\%keyhash, $column);
my @existing = split(",", $attrHash->{$column});
#if it has values, merge the new and old ones together so no dupes
if (@existing) {
my @values = split(",", $value);
my %seen = ();
my @uniq = ();
my $item;
foreach $item (@existing, @values) {
unless ($seen{$item}) {
# if we get here, we have not seen it before
$seen{$item} = 1;
push(@uniq, $item);
}
}
$value = join(",", @uniq);
}
}
#normal non-splicing assignment
else {
($column, $value) = split("=", $temp, 2);
}
unless (grep /$column/, @{ $xCAT::Schema::tabspec{$table}->{cols} }) {
$callback->({ error => "$table.$column not a valid table.column description", errorcode => [1] });
return 1;
}
$tableupdates{$table}{$column} = $value;
}
#commit all the changes
my $rollback;
foreach (keys %tables) {
if (exists($tableupdates{$_})) {
my @rc = $tables{$_}->setAttribs(\%keyhash, \%{ $tableupdates{$_} });
if (not defined($rc[0]))
{
$rollback = 1;
$callback->({ error => "DB error " . $rc[1], errorcode => [4] });
}
}
if ($rollback)
{
$tables{$_}->rollback();
$tables{$_}->close;
undef $tables{$_};
return;
}
else
{
$tables{$_}->commit;
}
}
}
}
#
# getAllEntries
#
# Read all the rows from the input table name and returns the response, so
# that the XML will look like this
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>getAllEntries</command>
#<table>nodelist</table>
#</xcatrequest>
#<xcatresponse>
#<row>
#<attr1>value1</attr1>
#.
#.
#.
#<attrN>valueN</attrN>
#</row>
#.
#.
#.
#</xcatresponse>
#
#
sub getAllEntries
{
my $request = shift;
my $cb = shift;
my $command = $request->{command}->[0];
my $tablename = $request->{table}->[0];
my $tab = xCAT::Table->new($tablename);
my %rsp;
my $recs = $tab->getAllEntries("all");
unless (@$recs) # table exists, but is empty. Show header.
{
if (defined($xCAT::Schema::tabspec{$tablename}))
{
my $header = "#";
my @array = @{ $xCAT::Schema::tabspec{$tablename}->{cols} };
foreach my $arow (@array) {
$header .= $arow;
$header .= ",";
}
chop $header;
push @{ $rsp{row} }, $header;
$cb->(\%rsp);
return;
}
}
my %noderecs;
foreach my $rec (@$recs) {
my %datseg = ();
foreach my $key (keys %$rec) {
$datseg{$key} = $rec->{$key};
}
push @{ $noderecs{"row"} }, \%datseg;
}
push @{ $rsp{"row"} }, @{ $noderecs{"row"} };
# for checkin XML created
#my $xmlrec=XMLout(\%rsp,RootName=>'xcatresponse',NoAttr=>1,KeyAttr=>[]);
$cb->(\%rsp);
return;
}
# getNodesAttribs
# Read the array of attributes for the noderange from the input table.
# If the <attr>ALL</attr> is input then read all the attributes
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>getNodesAttribs</command>
#<table>nodelist</table>
#<noderange>blade01-blade02</noderange>
#<attr>groups</attr>
#<attr>status</attr>
#</xcatrequest>
#
#<xcatresponse>
#<node>
#<name>nodename</name>
#<attr1>value1</attr1>
#.
#.
#.
#<attrN>valueN</attrN>
#</node>
#.
#.
#.
#</xcatresponse>
#
#
sub getNodesAttribs
{
my $request = shift;
my $cb = shift;
my $node = $request->{node};
my $command = $request->{command}->[0];
my $tablename = $request->{table}->[0];
my $attr = $request->{attr};
my $tab = xCAT::Table->new($tablename);
my @nodes = @$node;
my @attrs = @$attr;
my %rsp;
my %noderecs;
if (grep (/ALL/, @attrs)) { # read the schema and build array of all attrs
@attrs = ();
my $schema = xCAT::Table->getTableSchema($tablename);
my $desc = $schema->{descriptions};
foreach my $c (@{ $schema->{cols} }) {
# my $space = (length($c)<7 ? "\t\t" : "\t");
push @attrs, $c;
}
}
my $rechash = $tab->getNodesAttribs(\@nodes, \@attrs);
foreach my $node (@nodes) {
my $recs = $rechash->{$node};
foreach my $rec (@$recs) {
my %datseg = ();
$datseg{name} = [$node];
foreach my $key (keys %$rec) {
if ($key ne "node") { # do not put in the added node attribute
$datseg{$key} = [ $rec->{$key} ];
}
}
push @{ $noderecs{$node} }, \%datseg;
}
push @{ $rsp{"node"} }, @{ $noderecs{$node} };
}
# for checkin XML created
#my $xmlrec=XMLout(\%rsp,RootName=>'xcatresponse',NoAttr=>1,KeyAttr=>[]);
$cb->(\%rsp);
return;
}
# getTablesAllNodeAttribs
# Read all the nodes from the input tables and get the input attributes
# or get ALL attributes, if the word ALL is used.
# If the <attr>ALL</attr> is input then read all the attributes
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>getTablesAllNodeAttribs</command>
#<table>
#<tablename>nodelist</tablename>
#<attr>groups</attr>
#<attr>status</attr>
#</table>
#<table>
#<tablename>nodetype</tablename>
#<attr>ALL</attr>
#</table>
# .
# .
# .
#</xcatrequest>
#
#<xcatresponse>
#<table>
#<tablename>tablename1</tablename>
#<node>
#<name>n1</name>
#<attr1>value1</attr1>
#<attr2>value1</attr2>
#.
#<attrN>valueN</attrN>
#</node>
#</table>
# .
# .
# .
#</xcatresponse>
#
sub getTablesAllNodeAttribs
{
my $request = shift;
my $cb = shift;
my $command = $request->{command}->[0];
my %rsp;
# process each table in the request
my $tables = $request->{table};
foreach my $tabhash (@$tables) {
my $tablename = $tabhash->{tablename}->[0];
my $attr = $tabhash->{attr};
my @attrs = @$attr;
my $tab = xCAT::Table->new($tablename);
my %noderecs;
my $recs;
# build the table name record
@{ $noderecs{table}->[0]->{tablename} } = $tablename;
# if request for ALL attributes
if (grep (/ALL/, @attrs)) { # read the schema and build array of all attrs
@attrs = ();
my $schema = xCAT::Table->getTableSchema($tablename);
my $desc = $schema->{descriptions};
foreach my $c (@{ $schema->{cols} }) {
# my $space = (length($c)<7 ? "\t\t" : "\t");
push @attrs, $c;
}
}
# read all the nodes and their attributes in this table
my @nodeentries = $tab->getAllNodeAttribs(\@attrs);
foreach my $node (@nodeentries) {
# build the node entrys
my %datseg = ();
$datseg{name} = $node->{node};
foreach my $at (@attrs) {
# if the attribute has a value and is not the node attribute
if (($node->{$at}) && ($at ne "node")) {
$datseg{$at} = $node->{$at};
}
}
push @{ $noderecs{table}->[0]->{node} }, \%datseg;
}
push @{ $rsp{"table"} }, @{ $noderecs{table} };
} # end of all table processing
# for checkin XML created
#my $xmlrec=XMLout(\%rsp,RootName=>'xcatresponse',NoAttr=>1,KeyAttr=>[]);
$cb->(\%rsp);
return;
}
# getTablesNodesAttribs
# Read the nodes in the noderange from the input tables
# and get the input attributes
# or get ALL attributes, if the word ALL is used.
# If the <attr>ALL</attr> is input then read all the attributes
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>getTablesNodesAttribs</command>
#<noderange>blade01-blade10</noderange>
#<table>
#<tablename>nodelist</tablename>
#<attr>groups</attr>
#<attr>status</attr>
#</table>
#<table>
#<tablename>nodetype</tablename>
#<attr>ALL</attr>
#</table>
# .
# .
# .
#</xcatrequest>
#
#<xcatresponse>
#<table>
#<tablename>tablename1</tablename>
#<node>
#<name>n1</name>
#<attr1>value1</attr1>
#<attr2>value1</attr2>
#.
#<attrN>valueN</attrN>
#</node>
#</table>
# .
# .
# .
#</xcatresponse>
#
sub getTablesNodesAttribs
{
my $request = shift;
my $cb = shift;
my $command = $request->{command}->[0];
my %rsp;
# process each table in the request
my $tables = $request->{table};
my $node = $request->{node};
my @nodes = @$node;
foreach my $tabhash (@$tables) {
my $tablename = $tabhash->{tablename}->[0];
my $attr = $tabhash->{attr};
my @attrs = @$attr;
my $tab = xCAT::Table->new($tablename);
my %noderecs;
my $recs;
# build the table name record
#@{$noderecs{table}->[0]->{tablename}} = $tablename;
# if request for ALL attributes
if (grep (/ALL/, @attrs)) { # read the schema and build array of all attrs
@attrs = ();
my $schema = xCAT::Table->getTableSchema($tablename);
my $desc = $schema->{descriptions};
foreach my $c (@{ $schema->{cols} }) {
# my $space = (length($c)<7 ? "\t\t" : "\t");
push @attrs, $c;
}
}
# read the nodes and their attributes in this table
my $rechash = $tab->getNodesAttribs(\@nodes, \@attrs);
foreach my $node (@nodes) {
my $recs = $rechash->{$node};
foreach my $rec (@$recs) {
my %datseg = ();
$datseg{name} = [$node];
foreach my $key (keys %$rec) {
if ($key ne "node") { # do not put in the added node attribute
$datseg{$key} = [ $rec->{$key} ];
}
}
push @{ $noderecs{table}->[0]->{node} }, \%datseg;
}
}
@{ $noderecs{table}->[0]->{tablename} } = $tablename;
push @{ $rsp{"table"} }, @{ $noderecs{table} };
} # end of all table processing
# for checkin XML created
#my $xmlrec=XMLout(\%rsp,RootName=>'xcatresponse',NoAttr=>1,KeyAttr=>[]);
$cb->(\%rsp);
return;
}
# getTablesALLRowAttribs
# Read all the rows from the input non-Node key'd
# tables and get the input attributes
# or get ALL attributes, if the word ALL is used.
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>getTablesALLRowAttribs</command>
#<table>
#<tablename>osimage</tablename>
#<attr>imagename</attr>
#<attr>synclists</attr>
#</table>
#<table>
#<tablename>linuximage</tablename>
#<attr>ALL</attr>
#</table>
# .
# .
# .
#</xcatrequest>
#
#<xcatresponse>
#<table>
#<tablename>osimage</tablename>
#<row>
#<synclists>value1</synclists>
#</row>
#<row>
#.
#.
#</row>
#</table>
#<table>
#<tablename>linuximage</tablename>
#<row>
#<imagename>value</imagename>
#<template>value</template>
#.
#.
#</row>
#<row>
#.
#.
#</row>
#</table>
#</xcatresponse>
#.
#.
#
sub getTablesAllRowAttribs
{
my $request = shift;
my $cb = shift;
my $command = $request->{command}->[0];
my %rsp;
# process each table in the request
my $tables = $request->{table};
foreach my $tabhash (@$tables) {
my $tablename = $tabhash->{tablename}->[0];
my $attr = $tabhash->{attr};
my @attrs = @$attr;
my $tab = xCAT::Table->new($tablename);
my %tblrecs;
# build the table name record
@{ $tblrecs{table}->[0]->{tablename} } = $tablename;
# if request for ALL attributes
if (grep (/ALL/, @attrs)) { # read the schema and build array of all attrs
@attrs = ();
my $schema = xCAT::Table->getTableSchema($tablename);
my $desc = $schema->{descriptions};
foreach my $c (@{ $schema->{cols} }) {
# my $space = (length($c)<7 ? "\t\t" : "\t");
push @attrs, $c;
}
}
# read all the attributes in this table
my @recs = $tab->getAllAttribs(@attrs);
foreach my $rec (@recs) {
my %datseg = ();
foreach my $key (keys %$rec) {
$datseg{$key} = $rec->{$key};
}
push @{ $tblrecs{table}->[0]->{row} }, \%datseg;
}
push @{ $rsp{"table"} }, @{ $tblrecs{table} };
} # end of all table processing
# for checkin XML created
# my $xmlrec=XMLout(\%rsp,RootName=>'xcatresponse',NoAttr=>1,KeyAttr=>[]);
$cb->(\%rsp);
return;
}
#
# setNodesAttribs - setNodesAttribs
# Sets Nodes attributes for noderange for each of the tables supplied
# Example of XML in for this routine
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>setNodesAttribs</command>
#<noderange>blade01-blade02</noderange>
#<arg>
# <table>
# <name>nodelist</name>
# <attr>
# <groups>test</groups>
# <comments> This is a another testx</comments>
# </attr>
# </table>
# <table>
# <name>nodetype</name>
# <attr>
# <os>Redhat2</os>
# <comments> This is a another testy</comments>
# </attr>
# </table>
#</arg>
#</xcatrequest>
#
sub setNodesAttribs
{
my $request = shift;
my $cb = shift;
my $node = $request->{node}; # added by Client.pm
my $noderange = $request->{noderange};
my $command = $request->{command}->[0];
my %rsp;
my $args = $request->{arg};
my $tables = $args->[0]->{table};
# take input an build a request for the nodech function
my $newrequest;
$newrequest->{noderange} = $request->{noderange};
$newrequest->{command}->[0] = "nodech";
foreach my $table (@$tables) {
my $tablename = $table->{name}->[0];
my %keyhash;
my $attrs = $table->{attr};
foreach my $attrhash (@$attrs) {
foreach my $key (keys %$attrhash) {
my $tblattr = $tablename;
$tblattr .= ".$key=";
$tblattr .= $table->{attr}->[0]->{$key}->[0];
push(@{ $newrequest->{arg} }, $tblattr);
}
}
}
# nodech will open the table and do all the work
if (@$node) {
&nodech(\@$node, $newrequest->{arg}, $cb, 0);
} else {
my $rsp = { errorcode => 1, error => "No nodes in noderange" };
$cb->(\%rsp);
}
return;
}
#
# delEntries
# Deletes the table entry based on the input attributes
# The attributes and AND'd to together to form the delete request
# DELETE FROM nodelist WHERE ("groups" = "compute1,test" AND "status" = "down")
# Example of XML in for this routine
#
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>delEntries</command>
#<table>
# <name>nodelist</name>
# <attr>
# <groups>compute1,test</groups>
# <status>down</status>
# </attr>
#</table>
# .
# .
#<table>
# .
# .
# .
#</table>
#</xcatrequest>
#
# To delete all entries in a table, you input no attributes
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>delEntries</command>
#<table>
# <name>nodelist</name>
#</table>
#</xcatrequest>
sub delEntries
{
my $request = shift;
my $cb = shift;
my $command = $request->{command}->[0];
my %rsp;
my $tables = $request->{table};
foreach my $table (@$tables) {
my $tablename = $table->{name}->[0];
my $tab = xCAT::Table->new($tablename);
my %keyhash;
my $attrs = $table->{attr};
foreach my $attrhash (@$attrs) {
foreach my $key (keys %$attrhash) {
$keyhash{$key} = $attrhash->{$key}->[0];
}
}
if (%keyhash) { # delete based on requested attributes
$tab->delEntries(\%keyhash); #Yes, delete *all* entries
} else { # delete all entries
$tab->delEntries(); #delete *all* entries
}
$tab->commit; # commit
}
return;
}
# getAttribs
# Read and returns array of attributes for the key from the input table.
# and attributes input. Use "ALL" in the <attr>ALL</attr> for all attributes
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>getAttribs</command>
#<table>site</table>
#<keys>
# <key>domain</key>
#</keys>
#<attr>value</attr>
#<attr>comments</attr>
#</xcatrequest>
#
#
#<xcatresponse>
#<value>{domain value}</value>
#<comments>This is a comment</comments>
#</xcatresponse>
sub getAttribs
{
my $request = shift;
my $cb = shift;
my $command = $request->{command}->[0];
my $tablename = $request->{table}->[0];
my $attr = $request->{attr};
my @attrs = @$attr;
my $tab = xCAT::Table->new($tablename);
my %rsp;
my %keyhash;
# if request for ALL attributes
if (grep (/ALL/, @attrs)) { # read the schema and build array of all attrs
@attrs = ();
my $schema = xCAT::Table->getTableSchema($tablename);
my $desc = $schema->{descriptions};
foreach my $c (@{ $schema->{cols} }) {
# my $space = (length($c)<7 ? "\t\t" : "\t");
push @attrs, $c;
}
}
foreach my $k (keys %{ $request->{keys}->[0] }) {
$keyhash{$k} = $request->{keys}->[0]->{$k}->[0];
}
my $recs = $tab->getAttribs(\%keyhash, \@attrs);
if ($recs) {
my %attrhash = %$recs;
foreach my $k (keys %attrhash) {
push @{ $rsp{$k} }, $recs->{$k};
}
}
$cb->(\%rsp);
return;
}
# setAttribs
# Set the attributes for the key(s) input in the table.
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>setAttribs</command>
#<table>site</table>
#<keys>
# <key>domain</key>
#</keys>
#<attr>
# <value>cluster.net</value>
# <comments>This is a comment</comments>
#</xcatrequest>
#
#
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>setAttribs</command>
#<table>networks</table>
#<keys>
# <net>10.0.1.0</net>
# <mask>255.255.255.0</mask>
#</keys>
#<attr>
# <netname>mynet</netname>
# <gateway>10.0.1.254</gateway>
#</attr>
#</xcatrequest>
sub setAttribs
{
my $request = shift;
my $cb = shift;
my $command = $request->{command}->[0];
my $tablename = $request->{table}->[0];
my $tab = xCAT::Table->new($tablename);
my %rsp;
my %keyhash;
my %attrhash;
foreach my $k (keys %{ $request->{keys}->[0] }) {
$keyhash{$k} = $request->{keys}->[0]->{$k}->[0];
}
foreach my $a (keys %{ $request->{attr}->[0] }) {
$attrhash{$a} = $request->{attr}->[0]->{$a}->[0];
}
$tab->setAttribs(\%keyhash, \%attrhash);
return;
}
# noderange
# Expands the input noderange into a list of nodes.
#<xcatrequest>
#<clienttype>PCM</clienttype>
#<command>noderange</command>
#<noderange>compute1-compute2</noderange>
#</xcatrequest>
#<xcatresponse>
#<node>nodename1</node>
# .
# .
#<node>nodenamern1</node>
#</xcatresponse>
sub NodeRange
{
my $request = shift;
my $cb = shift;
my $command = $request->{command}->[0];
my %rsp;
my $node = $request->{node};
my @nodes = @$node;
foreach my $node (@nodes) {
push @{ $rsp{"node"} }, $node;
}
$cb->(\%rsp);
return;
}