# 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 xCAT::Table;
use xCAT::Schema;
use Data::Dumper;
use xCAT::NodeRange;
use xCAT::Schema;

use Getopt::Long;

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",
            tabrestore => "tabutils",
            tabch      => "tabutils",     # not implemented yet
            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
            tabgrep    => "tabutils"
            };
}

# Each cmd now returns its own usage inside its function
#my %usage = (
    #nodech => "Usage: nodech <noderange> [table.column=value] [table.column=value] ...",
    #nodeadd => "Usage: nodeadd <noderange> [table.column=value] [table.column=value] ...",
    #noderm  => "Usage: noderm <noderange>",
    # the usage for tabdump is in the tabdump function
    #tabdump => "Usage: tabdump <tablename>\n   where <tablename> is one of the following:\n     " . join("\n     ", keys %xCAT::Schema::tabspec),
    # the usage for tabrestore is in the tabrestore client cmd
    #tabrestore => "Usage: tabrestore <tablename>.csv",
    #);

#####################################################
# 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;
    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 "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 "nodech" or $command eq "nodech")
    {
        return nodech($nodes, $args, $callback, 0);
    }
    elsif ($command eq "tabrestore")
    {
        return tabrestore($request, $callback);
    }
    elsif ($command eq "tabdump")
    {
        return tabdump($args, $callback);
    }
    elsif ($command eq "gettab")
    {
        return gettab($request, $callback);
    }
    elsif ($command eq "tabgrep")
    {
        return tabgrep($nodes, $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;

    sub gettab_usage {
    	my $exitcode = shift @_;
        my %rsp;
        push @{$rsp{data}}, "Usage: gettab key=value,...  table.attribute ...";
        push @{$rsp{data}}, "       gettab [-?|-h|--help]";
        if ($exitcode) { $rsp{errorcode} = $exitcode; }
        $callback->(\%rsp);
    }

	# Process arguments
    @ARGV = @{$req->{arg}};
    if (!GetOptions('h|?|help' => \$HELP)) { 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;
    foreach my $tabvalue (@ARGV)
    {
        (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 => ["Unkown 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}});
        foreach my $coln (keys %{$tabhash{$tabn}})
        {
            $callback->({data => ["$tabn.$coln: " . $ent->{$coln}]});
        }
        $tab->close;
    }
}

sub noderm
{
    my $nodes = shift;
    my $args  = shift;
    my $cb    = shift;
    my $VERSION;
    my $HELP;

    sub noderm_usage {
    	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);
    }

    @ARGV = @{$args};
    if (!GetOptions('h|?|help'  => \$HELP, 'v|version' => \$VERSION) ) { noderm_usage(1); return; }

    if ($HELP) { noderm_usage(0); return; }

    if ($VERSION) {
        my %rsp;
        $rsp->{data}->[0] = "2.0";
        $cb->($rsp);
        return;
    }

    if (!$nodes) { noderm_usage(1); return; }

    # 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, $_;
        }
    }
    nodech($nodes, \@tablist, $cb, 0);
}

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 $linenumber = 1;
    my $tab        = xCAT::Table->new($table, -create => 1, -autocommit => 0);
    unless ($tab) {
        $cb->({error => "Unable to open $table",errorcode=>4});
        return;
    }
    $tab->delEntries();    #Yes, delete *all* entries
    my $header = shift @{$request->{data}};
    $header =~ s/"//g;     #Strip " from overzealous CSV apps
    $header =~ s/^#//;
    $header =~ s/\s+$//;
    my @colns = split(/,/, $header);
    my $line;
    my $rollback = 0;
  LINE: foreach $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?
                $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)
                    {

                        #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;
                    }
                    $nextchar = substr($line, $offset, 1);
                    if ($nextchar eq '"')
                    {
                        $offset++;
                    }
                    elsif ($offset eq length($line) or $nextchar eq ',')
                    {
                        $ent = substr($line, 0, $offset, '');
                        $line =~ s/^,//;
                        chop $ent;
                        $ent = substr($ent, 1);
                        $ent =~ s/""/"/g;
                        $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..
                $record{$col} = $1;
                $line =~ s/^([^,]+)(,|$)//;
            }
        }
        if ($line)
        {
            $rollback = 1;
            $cb->({error => "Too many fields on line $linenumber: $origline | $line", errorcode=>4});
            next LINE;
        }

        #TODO: 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 $table = "";
    my $HELP;
    my $DESC;

    sub tabdump_usage {
    	my $exitcode = shift @_;
        my %rsp;
        push @{$rsp{data}}, "Usage: tabdump [-d] [table]";
        push @{$rsp{data}}, "       tabdump [-?|-h|--help]";
        if ($exitcode) { $rsp{errorcode} = $exitcode; }
        $cb->(\%rsp);
    }

	# Process arguments
    @ARGV = @{$args};
    if (!GetOptions('h|?|help' => \$HELP, 'd' => \$DESC)) { tabdump_usage(1); return; }

    if ($HELP) { tabdump_usage(0); return; }
    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 ($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;
    }

    $table = $ARGV[0];
    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);

    sub tabdump_header {
        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;
    }

    my $recs = $tabh->getAllEntries();
    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 in the order of the columns in the schema
    tabdump_header(@{$tabh->{colnames}});
    foreach $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
        $line = $line . $lineappend;
        push @{$rsp{data}}, $line;
    }
    $cb->(\%rsp);
}

sub nodech
{
    my $nodes    = shift;
    my $args     = shift;
    my $callback = shift;
    my $addmode  = shift;
    my $VERSION;
    my $HELP;
    my $deletemode;

    sub nodech_usage
    {
    	my $exitcode = shift @_;
    	my $addmode = shift @_;
    	my $cmdname = $addmode ? 'nodeadd' : 'nodech';
        my %rsp;
        if ($addmode) {
        	push @{$rsp{data}}, "Usage: $cmdname <noderange> groups=<groupnames> [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);
    }

    @ARGV = @{$args};
    my %options = ('h|?|help'  => \$HELP, 'v|version' => \$VERSION);
    if (!$addmode) { $options{'d|delete'} = \$deletemode; }
    if (!GetOptions(%options)) {
        nodech_usage(1, $addmode);
        return;
    }

    # Help
    if ($HELP) {
        nodech_usage(0, $addmode);
        return;
    }

    # Version
    if ($VERSION) {
        my %rsp;
        $rsp->{data}->[0] = "2.0";
        $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; }

    if ($addmode)
    {
    	my $nr = shift @ARGV;
    	$nodes = [noderange($nr, 0)];
        unless ($nodes) {
            $callback->({error => "No noderange to add.\n",errorcode=>1});
            return;
        }
    }
    my $column;
    my $value;
    my $temp;
    my %tables;
    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/=/)
        {
            $callback->({error => ["Malformed argument $_ ignored."],errorcode=>1});
            next;
        }
        ($temp, $value) = split('=', $_, 2);
        my $op = '=';
        if ($temp =~ /,$/)
        {
            $op = ',=';
            chop($temp);
        }
        elsif ($temp =~ /\^$/)
        {
            $op = '^=';
            chop($temp);
        }

        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);
    }
    foreach $tab (keys %tables)
    {
        my $tabhdl = xCAT::Table->new($tab, -create => 1, -autocommit => 0);
        if ($tabhdl)
        {
            foreach (@$nodes)
            {
                if ($deletemode)
                {
                    $tabhdl->delEntries({'node' => $_});
                }
                else
                {

                    #$tabhdl->setNodeAttribs($_,$tables{$tab});
                    my %uhsh;
                    my $node = $_;
                    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 = $_;
                        	if ($op eq '=') {
                            	$uhsh{$key} = $val;
                        	}
                        	elsif ($op eq ',=') {    #splice assignment
                        		my $curval = $uhsh{$key};    # in case it was already set
                        		if (!defined($curval)) {
                            		my $cent = $tabhdl->getNodeAttribs($node, [$key]);
                            		if ($cent) { $curval = $cent->{$key}; }
                        		}
                            	if ($curval) {
                                	my @vals = split(/,/, $curval);
                                	unless (grep /^$val$/, @vals) {
                                    	@vals = (@vals, $val);
                                    	my $newval = join(',', @vals);
                                    	$uhsh{$key} = $newval;
                                	}
                            	} else {
                                	$uhsh{$key} = $val;
                            	}
                        	}
                        	elsif ($op eq '^=') {
                        		my $curval = $uhsh{$key};    # in case it was already set
                        		if (!defined($curval)) {
                            		my $cent = $tabhdl->getNodeAttribs($node, [$key]);
                            		if ($cent) { $curval = $cent->{$key}; }
                        		}
                            	if ($curval) {
                                	my @vals = split(/,/, $curval);
                                	if (grep /^$val$/, @vals) {    #only bother if there
                                    	@vals = grep(!/^$val$/, @vals);
                                    	my $newval = join(',', @vals);
                                    	$uhsh{$key} = $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)
                    {

                        my @rc = $tabhdl->setNodeAttribs($node, \%uhsh);
                        if (not defined($rc[0]))
                        {
                            $callback->({error => "DB error " . $rc[1],errorcode=>1});
                        }
                    }
                }
            }
            $tabhdl->commit;
        }
        else
        {
            $callback->(
                 {error => ["ERROR: Unable to open table $tab in configuration"],errorcode=>1}
                 );
        }
    }
}

sub tabgrep
{
    my $node = shift;
    my @tablist;
    my $callback = shift;

    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;
    }

}

#####################################################
#  nodels command
#####################################################
sub nodels
{
    my $nodes     = shift;
    my $args      = shift;
    my $callback  = shift;
    my $noderange = shift;

    my $VERSION;
    my $HELP;

    sub nodels_usage
    {
    	my $exitcode = shift @_;
        my %rsp;
        push @{$rsp{data}}, "Usage:";
        push @{$rsp{data}}, "  nodels [noderange] [table.attribute | shortname] [...]";
        push @{$rsp{data}}, "  nodels {-v|--version}";
        push @{$rsp{data}}, "  nodels [-?|-h|--help]";
#####  xcat 1.2 nodels usage:
        #     $rsp->{data}->[1]= "  nodels [noderange] [group|pos|type|rg|install|hm|all]";
        #     $rsp->{data}->[2]= " ";
        #     $rsp->{data}->[3]= "  nodels [noderange] hm.{power|reset|cad|vitals|inv|cons}";
        #     $rsp->{data}->[4]= "                     hm.{bioscons|eventlogs|getmacs|netboot}";
        #     $rsp->{data}->[5]= "                     hm.{eth0|gcons|serialbios|beacon}";
        #     $rsp->{data}->[6]= "                     hm.{bootseq|serialbps|all}";
        #     $rsp->{data}->[7]= " ";
        #     $rsp->{data}->[8]= "  nodels [noderange] rg.{tftp|nfs_install|install_dir|serial}";
        #     $rsp->{data}->[9]= "                     rg.{usenis|install_roll|acct|gm|pbs}";
        #     $rsp->{data}->[10]="                     rg.{access|gpfs|netdevice|prinic|all}";
        if ($exitcode) { $rsp{errorcode} = $exitcode; }
        $callback->(\%rsp);
    }

    @ARGV = @{$args};
    if (!GetOptions('h|?|help'  => \$HELP, 'v|version' => \$VERSION,) ) { nodels_usage(1); return; }

    # Help
    if ($HELP) { nodels_usage(0); return; }

    # Version
    if ($VERSION)
    {
        my %rsp;
        $rsp->{data}->[0] = "2.0";
        $callback->($rsp);
        return;
    }

    # TODO -- Parse command arguments
    #  my $opt;
    #  my %attrs;
    #  foreach $opt (@ARGV) {
    #     if ($opt =~ /^group/) {
    #     }
    #  }
    my $argc = @ARGV;

    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
        if ($argc)
        {
            my %tables;
            foreach (@ARGV)
            {
                my $table;
                my $column;
                my $temp = $_;
                if ($shortnames{$temp})
                {
                    ($table, $column) = @{$shortnames{$temp}};
                } elsif ($temp =~ /\./) {
                    ($table, $column) = split('\.', $temp, 2);
                } elsif ($xCAT::Schema::tabspec{$temp}) {
                   $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];    #Mark this as something to get
                }
            }
            my $tab;
            my %noderecs;
            foreach $tab (keys %tables)
            {
                my $tabh = xCAT::Table->new($tab);
                unless ($tabh) { next; }

                #print Dumper($tables{$tab});
                my $node;
                foreach $node (@$nodes)
                {
                    my @cols;
                    my %labels;
                    foreach (@{$tables{$tab}})
                    {
                        push @cols, $_->[0];
                        $labels{$_->[0]} = $_->[1];
                    }
                    my $rec = $tabh->getNodeAttribs($node, \@cols);
                    foreach (keys %$rec)
                    {
                        my %datseg;
                        $datseg{data}->[0]->{desc}     = [$labels{$_}];
                        $datseg{data}->[0]->{contents} = [$rec->{$_}];
                        $datseg{name} = [$node]; #{}->{contents} = [$rec->{$_}];
                        push @{$noderecs{$node}}, \%datseg;
                    }
                }

                #$rsp->{node}->[0]->{data}->[0]->{desc}->[0] = $_;
                #$rsp->{node}->[0]->{data}->[0]->{contents}->[0] = $_;
                $tabh->close();
                undef $tabh;
            }
            foreach (sort (keys %noderecs))
            {
                push @{$rsp->{"node"}}, @{$noderecs{$_}};
            }
        }
        else
        {
            foreach (@$nodes)
            {
                my $noderec;
                $noderec->{name}->[0] = ($_);
                push @{$rsp->{node}}, $noderec;
            }
        }
        $callback->($rsp);
    }
    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);
            foreach (@ents)
            {
                my %rsp;
                if ($_->{node})
                {
                    $rsp->{node}->[0]->{name}->[0] = ($_->{node});

                    #              $rsp->{node}->[0]->{data}->[0]->{contents}->[0]="$_->{node} node contents";
                    #              $rsp->{node}->[0]->{data}->[0]->{desc}->[0]="$_->{node} node desc";
                    $callback->($rsp);
                }
            }
        }
    }

    return 0;
}