#!/usr/bin/env perl -w
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html

#####################################################
#
#   Utility subroutines that can be used to manage xCAT data object
#			definitions.
#
#
#####################################################
package xCAT::DBobjUtils;

use xCAT::NodeRange;
use xCAT::Schema;
use xCAT::Table;
use xCAT::Utils;
use xCAT::MsgUtils;
use strict;

#  IPv6 not yet implemented - need Socket6
use Socket;

#----------------------------------------------------------------------------

=head3   getObjectsOfType

        Get a list of data objects of the given type.

        Arguments:
        Returns:
                undef
                @objlist - list of objects of this type
        Globals:
        Error:
        Example:
        Comments:

		@objlist = xCAT::DBobjUtils->getObjectsOfType($type);

=cut

#-----------------------------------------------------------------------------
sub getObjectsOfType
{
    my ($class, $type) = @_;

    my @objlist;

    # special case for site table
    if ($type eq 'site')
    {
        push(@objlist, 'clustersite');
        return @objlist;
    }

    if ($::saveObjList{$type})
    {
        @objlist = @{$::saveObjList{$type}};
    }
    else
    {

        # get the key for this type object
        # 	ex. for "network" type the key is "netname"
        # get the data type spec from Schema.pm
        my $datatype = $xCAT::Schema::defspec{$type};

        # get the key for this type object
        #   ex. for "network" type the key is "netname"
        my $objkey = $datatype->{'objkey'};

        my $table;
        my $tabkey;
        foreach my $this_attr (@{$datatype->{'attrs'}})
        {
            my $attr = $this_attr->{attr_name};
            if ($attr eq $objkey)
            {

                # get the table & key for to lookup
                # get the actual attr name to use in the table
                #   - may be different then the attr name used for the object.
                ($table, $tabkey) = split('\.', $this_attr->{tabentry});
            }
        }

        # get the whole table and add each entry in the objkey column
        #   to the list of objects.

        my @TableRowArray = xCAT::DBobjUtils->getDBtable($table);

        foreach (@TableRowArray)
        {
            push(@objlist, $_->{$tabkey});

        }

		# if this is type "group" we need to check the nodelist table
		my @nodeGroupList=();
		if ($type eq 'group')
		{
			my $table = "nodelist";
			my @TableRowArray = xCAT::DBobjUtils->getDBtable($table);
			foreach (@TableRowArray)
			{
				my @tmplist = split(',', $_->{'groups'});
				push(@nodeGroupList, @tmplist);
			}
			foreach my $n (@nodeGroupList)
			{
				if (!grep(/^$n$/, @objlist) ) {
					push(@objlist, $n);
				}
			}
		}
		

        @{$::saveObjList{$type}} = @objlist;

    }

    return @objlist;
}

#----------------------------------------------------------------------------

=head3   getobjattrs

        Get data from tables 

                $type_hash: objectname=>objtype hash
                $attrs_ref: only get the specific attributes,
                            this can be useful especially for performance considerations
        Arguments:
        Returns:
                undef
                hash ref - (ex. $tabhash{$table}{$objname}{$attr} = $value)
        Globals:
        Error:
        Example:

                %tabhash = xCAT::DBobjUtils->getobjattrs(\%typehash);

        Comments:
			For now - only support tables that have 'node' as key !!!
=cut

#-----------------------------------------------------------------------------
sub getobjattrs
{
    my $class = shift;
    my $ref_hash = shift;
    my @attrs;
    # The $attrs is an optional argument
    if (ref $_[0]) {
        @attrs = @{shift()};
    }
	my %typehash = %$ref_hash;

	my %tableattrs;
	my %tabhash;

	# get a list of object names for each type
	my %objtypelist;
	foreach my $objname (sort (keys %typehash)) {
		# get list of objects for each type
		# $objtypelist{$typehash{$objname}}=$objname;
		push @{$objtypelist{$typehash{$objname}}}, $objname;
	}

	# go through each object type and look up all the info for each object
	foreach my $objtype (keys %objtypelist) {

		# only do node type for now 
	  	if ($objtype eq 'node') {

			# find the list of tables and corresponding attrs 
			#	- for this object type
			# get the object type decription from Schema.pm
        	my $datatype = $xCAT::Schema::defspec{$objtype};

        	foreach my $this_attr (@{$datatype->{'attrs'}}) {
            	my $attr = $this_attr->{attr_name};
                if (scalar(@attrs) > 0) { # Only query specific attributes
                    if (!grep(/^$attr$/, @attrs)) {
                        next; # This attribute is not needed
                    }
                }

				# table_attr is the attr that actually appears in the
            	#  table which could possibly be different then the attr
            	#  used in the node def
				# ex. 'nodetype.arch'
				my ($lookup_table, $table_attr) = split('\.', $this_attr->{tabentry});
				if (!grep(/^$table_attr$/, @{$tableattrs{$lookup_table}})) {
					push @{$tableattrs{$lookup_table}}, $table_attr;
				}
        	}

			# foreach table look up the list of attrs for this 
			#	list of object names
			foreach my $table (keys %tableattrs) {
				# open the table
				my $thistable = xCAT::Table->new($table, -create => 1, -autocommit => 0);
				if (!$thistable) {
					my $rsp;
					$rsp->{data}->[0] = "Could not get the \'$thistable\' table.";
					xCAT::MsgUtils->message("E", $rsp, $::callback);
				}

				my @objlist = @{$objtypelist{$objtype}};

				my $rec = $thistable->getNodesAttribs(\@objlist, @{$tableattrs{$table}});

				# fill in %tabhash with any values that are set
				foreach my $n (@objlist) {
					my $tmp1=$rec->{$n}->[0];
					foreach $a (@{$tableattrs{$table}}) {
						if (defined($tmp1->{$a})) {
							$tabhash{$table}{$n}{$a} = $tmp1->{$a};
#print "obj = $n, table = $table, attr =$a, val = $tabhash{$table}{$n}{$a}\n";

 
						}
					}
				}
				$thistable->commit;
			}
	  	}
	}
	return %tabhash;
}

#----------------------------------------------------------------------------

=head3   getobjdefs

        Get object definitions from the DB.

                $type_hash: objectname=>objtype hash
                $verbose: optional
                $attrs_ref: only get the specific attributes,
                            this can be useful especially for performance considerations
        Arguments:
        Returns:
                undef - error
                hash ref - $objecthash{objectname}{attrname} = value
        Globals:
        Error:
        Example:

		To use create hash for objectname and object type
            ex. $objhash{$obj} = $type;

        - then call as follows:
			%myhash = xCAT::DBobjUtils->getobjdefs(\%objhash);

        Comments:

=cut

#-----------------------------------------------------------------------------
sub getobjdefs
{
    my ($class, $hash_ref, $verbose, $attrs_ref) = @_;
    my %objhash;
    my %typehash = %$hash_ref;
    my %tabhash;
    my @attrs;
    if (ref($attrs_ref))
    {
        @attrs = @$attrs_ref;
    }

	@::foundTableList = ();
	
	if ($::ATTRLIST eq "none") {
        # just return the list of obj names
        foreach my $objname (sort (keys %typehash))
        {
            my $type = $typehash{$objname};
            $objhash{$objname}{'objtype'} = $type;
        }
        return %objhash;
    }

	# see if we need to get any objects of type 'node' 
	my $getnodes=0;
	foreach my $objname (keys %typehash) {
		if ($typehash{$objname} eq 'node') {
			$getnodes=1;
		}
	}

	# if so then get node info from tables now
    #   still may need to look up values in some tables using
    #   other keys - also need to figure out what tables to take
    #   values from when using 'only_if' - see below
	# - but this saves lots of time
	if ($getnodes) {
        if (scalar(@attrs) > 0) # Only get specific attributes of the node
        {
	    %tabhash = xCAT::DBobjUtils->getobjattrs(\%typehash, \@attrs);
            # We only need to get specific attributes of the node,
            # do not look up all the tables, 
            # looking up all the tables will cause scaling issues
            # just go through the tables returned in tabhash
            my $datatype = $xCAT::Schema::defspec{'node'};
            foreach my $objname (keys %typehash)
            {
                foreach my $lookup_table (keys %tabhash)
                {   
                    foreach my $tabattr (keys %{$tabhash{$lookup_table}{$objname}})
                    {   
                        if (defined($tabhash{$lookup_table}{$objname}{$tabattr}) )
                        {   
                            # Get the attribute name, 
                            # node attribute name may be different with the attribute in the database tables
                            my $objattr;
                            foreach my $entry (@{$datatype->{'attrs'}})
                            {
                                if ($entry->{'tabentry'} eq "$lookup_table" . "\." . "$tabattr")
                                {
                                    $objattr = $entry->{'attr_name'}; 
                                    last;
                                }
                            }
                            $objhash{$objname}{$objattr} = $tabhash{$lookup_table}{$objname}{$tabattr};
                            next;
                        }
                    }
                }
            }
        return %objhash;
        }
        else
        {
            %tabhash = xCAT::DBobjUtils->getobjattrs(\%typehash);
        }
	}

    foreach my $objname (sort (keys %typehash))
    {
        # need a special case for the site table - for now !!!!!

        if ($typehash{$objname} eq 'site')
        {
            my @TableRowArray = xCAT::DBobjUtils->getDBtable('site');

            if (defined(@TableRowArray))
            {
                my $foundinfo = 0;
                foreach (@TableRowArray)
                {

                    if ($_->{key})
                    {
						if (defined($_->{value}) ) {
                        	$foundinfo++;
							if ($verbose == 1) {
								$objhash{$objname}{$_->{key}} = "$_->{value}\t(Table:site - Key:$_->{key})";
							} else {
                        		$objhash{$objname}{$_->{key}} = $_->{value};
							}
						}
                    }
                }
                if ($foundinfo)
                {
                    $objhash{$objname}{'objtype'} = 'site';
                }
            }
            else
            {
				 my $rsp;
                 $rsp->{data}->[0] ="Could not read the \'$objname\' object from the \'site\' table.\n";
                 xCAT::MsgUtils->message("E", $rsp, $::callback);	
            }
            next;
        }

        # get data from DB
        my $type = $typehash{$objname};

        # add the type to the hash for each object
        $objhash{$objname}{'objtype'} = $type;

        # get the object type decription from Schema.pm
        my $datatype = $xCAT::Schema::defspec{$type};

        # get the key to look for, for this object type
        my $objkey = $datatype->{'objkey'};

        #  get a list of valid attr names
        #       for this type object
		my %attrlist;
        foreach my $entry (@{$datatype->{'attrs'}})
        {
            push(@{$attrlist{$type}}, $entry->{'attr_name'});
        }

        # go through the list of valid attrs
        foreach my $this_attr (@{$datatype->{'attrs'}})
        {
            my $ent;

            my $attr = $this_attr->{attr_name};

            # skip the key attr  ???
			if ($attr eq $objkey)
            {
                next;
            }

            # get table lookup info from Schema.pm
            #  !!!! some tables depend on the value of certain attrs
            #   we need to look up attrs in the correct order or we will
            #   not be able to determine what tables to look
            #	in for some attrs.

            if (exists($this_attr->{only_if}))
            {
                my ($check_attr, $check_value) = split('\=', $this_attr->{only_if});

                # if the object value is not the value we need
                #   to match then try the next only_if value
				next if ( !($objhash{$objname}{$check_attr} =~ /\b$check_value\b/) );
            }

            #  OK - get the info needed to access the DB table
            #   - i.e. table name, key name, attr names

            # need the actual table attr name corresponding
            #   to the object attr name
            #  ex. noderes.nfsdir
            my ($tab, $tabattr) = split('\.', $this_attr->{tabentry});

            # ex. 'nodelist.node', 'attr:node'
            my ($lookup_key, $lookup_value) = split('\=', $this_attr->{access_tabentry});

            # ex. 'nodelist', 'node'
            my ($lookup_table, $lookup_attr) = split('\.', $lookup_key);

            # ex. 'attr', 'node'
            my ($lookup_type, $lookup_data) = split('\:', $lookup_value);

            #
            # Get the attr values 
            #

			# lookup_attr is the key to the table we are looking in
			# if we're looking up a node attr and the key is 'node'
			#	then the value should be in %tabhash
			if ( ($lookup_attr eq 'node') && ($type eq 'node') )
            {
				if (defined($tabhash{$lookup_table}{$objname}{$tabattr}) ) {
					if ($verbose == 1) {
						$objhash{$objname}{$attr} = "$tabhash{$lookup_table}{$objname}{$tabattr}\t(Table:$lookup_table - Key:$lookup_attr - Column:$tabattr)";
					} else {
						$objhash{$objname}{$attr} = $tabhash{$lookup_table}{$objname}{$tabattr};
					}
				}

			}
            else
            {
                # look up attr values
                my @rows = xCAT::DBobjUtils->getDBtable($lookup_table);
                if (defined(@rows))
                {
                    foreach (@rows)
                    {
                        if ($_->{$lookup_attr} eq $objname)
                        {
							if (defined($_->{$tabattr}) ) {
								if ($verbose == 1) {
									$objhash{$objname}{$attr} = "$_->{$tabattr}\t(Table:$lookup_table - Key:$lookup_attr - Column:$tabattr)";
								} else {
                           			$objhash{$objname}{$attr} = $_->{$tabattr};
								}
							}
                        }
                    }
                }
                else
                {
                    my $rsp;
                    $rsp->{data}->[0] =
                          "Could not read the \'$lookup_table\' table from the xCAT database.";
                    xCAT::MsgUtils->message("E", $rsp, $::callback);
                    return undef;
                }

            }
        }

		if ($typehash{$objname} eq 'monitoring') {
		
        	# need a special case for the monitoring table
			# 	- need to check the monsetting table for entries that contain
			#	 the same name as the monitoring table entry.

        	my @TableRowArray = xCAT::DBobjUtils->getDBtable('monsetting');

        	if (defined(@TableRowArray))
            {
                my $foundinfo = 0;
                foreach (@TableRowArray)
                {

					if ($_->{name} eq $objname ) {

                    	if ($_->{key})
                    	{
                        	$foundinfo++;
							if (defined($_->{value}) ) {
								$foundinfo++;
								if ($verbose == 1) {
									$objhash{$objname}{$_->{key}} = "$_->{value}\t(Table:monsetting)";
								} else {
                        			$objhash{$objname}{$_->{key}} = $_->{value};
								}
							}
                    	}
					}
                }
                if ($foundinfo)
                {
                    $objhash{$objname}{'objtype'} = 'monitoring';
                }
            }
            else
            {
				 my $rsp;
                 $rsp->{data}->[0] ="Could not read the \'$objname\' object from the \'monsetting\' table.\n";
                 xCAT::MsgUtils->message("E", $rsp, $::callback);	
            }
            next;

		}
    }
    return %objhash;
}

#----------------------------------------------------------------------------

=head3   getDBtable

        Get a DB table, cache it , & return list of rows from the table.

        Arguments:
        Returns:
                undef - error
                @rows - of table
        Globals:
        Error:
        Example:

        call as follows
          my @TableRowArray= xCAT::DBobjUtils->getDBtable($tablename); 

        Comments:

=cut

#-----------------------------------------------------------------------------
sub getDBtable
{
    my ($class, $table) = @_;
    my @rows = [];

	# save this table info - in case this subr gets called multiple times
    if (grep(/^$table$/, @::foundTableList))
    {

        # already have this
        @rows = @{$::TableHash{$table}};

    }
    else
    {

    	# need to get info from DB
    	my $thistable = xCAT::Table->new($table, -create => 1, -autocommit => 0);
    	if (!$thistable)
    	{
        	return undef;
    	}

    	@rows = $thistable->getTable;

    	#   !!!! this routine returns rows even if the table is empty!!!!!!

		#  keep track of the fact that we checked this table
        #   - even if it's empty!
        push(@::foundTableList, $thistable->{tabname});

        @{$::TableHash{$table}} = @rows;

    	$thistable->commit;

	} # end if not cached

   	if (defined(@rows))
   	{
       	return @rows;
   	}
   	else
   	{
       	return undef;
    }
}

#----------------------------------------------------------------------------

=head3   setobjdefs

        Set the object definitions in the DB.
            - Handles the Schema lookup and updating the DB tables.

        Arguments:
        Returns:
                1 - error
                0 - OK
        Globals:
        Error:
        Example:

        To use:
		 	-create hash for objectname and object type
            	ex. $objhash{$object}{$attribute} = value;

			-then call as follows:
				if (xCAT::DBobjUtils->setobjdefs(\%objhash) != 0)

        Comments:

=cut

#-----------------------------------------------------------------------------
sub setobjdefs
{
    my ($class, $hash_ref) = @_;
    my %objhash = %$hash_ref;
    my %settableref;
    my $ret = 0;
    my %allupdates;
    my $setattrs=0;

    # get the attr=vals for these objects from the DB - if any
    #       - so we can figure out where to put additional attrs
    # The getobjdefs call was in the foreach loop,
    # it caused mkdef/chdef performance issue,
    # so it is moved out of the foreach loop

    my %DBhash;
    foreach my $objname (keys %objhash)
    {
        my $type = $objhash{$objname}{objtype};
        $DBhash{$objname} = $type;
    }

    my %DBattrvals;

    %DBattrvals = xCAT::DBobjUtils->getobjdefs(\%DBhash);

    # for each object figure out:
    #	- what tables to update
    #	- which table attrs correspond to which object attrs
    #	- what the keys are for each table
    # update the tables a row at a time
    foreach my $objname (keys %objhash)
    {

        # get attr=val that are set in the DB ??
        my $type = $objhash{$objname}{objtype};

		# handle the monitoring table as a special case !!!!!
        if ($type eq 'monitoring')
        {

                # Get the names of the attrs stored in monitoring table
                # get the object type decription from Schema.pm
        	my $datatype = $xCAT::Schema::defspec{$type};

        	#  get a list of valid attr names
        	#               for this type object
        	my @attrlist;
        	foreach my $entry (@{$datatype->{'attrs'}})
        	{
            	push(@attrlist, $entry->{'attr_name'});
        	}

			# open the tables (monitoring and monsetting)
            my $montable = xCAT::Table->new('monitoring', -create => 1, -autocommit => 0);
            if (!$montable)
            {
                my $rsp;
                $rsp->{data}->[0] = "Could not set the \'$montable\' table.";
                xCAT::MsgUtils->message("E", $rsp, $::callback);
                return 1;
            }
			# open the table
            my $monsettable = xCAT::Table->new('monsetting', -create => 1, -autocommit => 0);
            if (!$monsettable)
            {
                my $rsp;
                $rsp->{data}->[0] = "Could not set the \'$monsettable\' table.";
                xCAT::MsgUtils->message("E", $rsp, $::callback);
                return 1;
            }

			my %keyhash; 
			my %updates;

			foreach my $attr (keys %{$objhash{$objname}})
            {
				my $val;
                if ($attr eq 'objtype')
                {
                    next;
                }

				# determine the value if we have plus or minus
				if ($::plus_option)
                {
                    # add new to existing - at the end - comma separated
                    if (defined($DBattrvals{$objname}{$attr}))
                    {
                        $val =
                          "$DBattrvals{$objname}{$attr},$objhash{$objname}{$attr}";
                    }
                    else
                    {
                        $val = "$objhash{$objname}{$attr}";
                    }
                }
                elsif ($::minus_option)
				{
                    # remove the specified list of values from the current
                    #   attr values.
                    if ($DBattrvals{$objname}{$attr})
                    {
                        # get the list of attrs to remove
                        my @currentList = split(/,/, $DBattrvals{$objname}{$attr});
                        my @minusList   = split(/,/, $objhash{$objname}{$attr});

                        # make a new list without the one specified
                        my $first = 1;
                        my $newlist;
                        foreach my $i (sort @currentList)
                        {
                            chomp $i;
                            if (!grep(/^$i$/, @minusList))
                            {
                                # set new groups list for node
                                if (!$first)
                                {
                                    $newlist .= ",";
                                }
                                $newlist .= $i;
                                $first = 0;
                            }
                        }
                        $val = $newlist;
                    }
                }
                else
                {
                    #just set the attr to what was provided! - replace
                    $val = $objhash{$objname}{$attr};
                }

				if (grep(/^$attr$/, @attrlist)) {
					# if the attr belong in the monitoring tabel
					%keyhash=(name=>$objname);
					%updates=($attr=>$val);
					$montable->setAttribs(\%keyhash, \%updates);
				} else {
					# else it belongs in the monsetting table
					$keyhash{name} = $objname;
					$keyhash{key} = $attr;
                	$updates{value} = $val;
					$monsettable->setAttribs(\%keyhash, \%updates);
				}
			}

			$montable->commit;
			$monsettable->commit;
			next;
		}

        # handle the site table as a special case !!!!!
        if ($type eq 'site')
        {
            # open the table
            my $thistable =
              xCAT::Table->new('site', -create => 1, -autocommit => 0);
            if (!$thistable)
            {
                my $rsp;
                $rsp->{data}->[0] = "Could not set the \'$thistable\' table.";
                xCAT::MsgUtils->message("E", $rsp, $::callback);
                return 1;
            }

            foreach my $attr (keys %{$objhash{$objname}})
            {
                if ($attr eq 'objtype')
                {
                    next;
                }

				my %keyhash;
                $keyhash{key} = $attr;

                my $val;
                if ($::plus_option)
                {
                    # add new to existing - at the end - comma separated
                    if (defined($DBattrvals{$objname}{$attr}))
                    {
                        $val =
                          "$DBattrvals{$objname}{$attr},$objhash{$objname}{$attr}";
                    }
                    else
                    {
                        $val = "$objhash{$objname}{$attr}";
                    }
                }
                elsif ($::minus_option)
                {
                    # remove the specified list of values from the current
                    #   attr values.
                    if ($DBattrvals{$objname}{$attr})
                    {
                        # get the list of attrs to remove
                        my @currentList = split(/,/, $DBattrvals{$objname}{$attr});
                        my @minusList   = split(/,/, $objhash{$objname}{$attr});

                        # make a new list without the one specified
                        my $first = 1;
                        my $newlist;
                        foreach my $i (sort @currentList)
                        {
                            chomp $i;
                            if (!grep(/^$i$/, @minusList))
                            {
                                # set new groups list for node
                                if (!$first)
                                {
                                    $newlist .= ",";
                                }
                                $newlist .= $i;
                                $first = 0;
                            }
                        }
                        $val = $newlist;
                    }
                }
                else
                {

                    #just set the attr to what was provided! - replace
                    $val = $objhash{$objname}{$attr};

                }

				if ( $val eq "") { # delete the line

					$thistable->delEntries(\%keyhash);

				}  else { # change the attr

					my %updates;
                	$updates{value} = $val;

                	my ($rc, $str) = $thistable->setAttribs(\%keyhash, \%updates);
                	if (!defined($rc))
                	{
                    	if ($::verbose)
                    	{
                        	my $rsp;
                        	$rsp->{data}->[0] =
                          	"Could not set the \'$attr\' attribute of the \'$objname\' object in the xCAT database.\n";
                        	$rsp->{data}->[1] =
                          	"Error returned is \'$str->errstr\'.";
                        	xCAT::MsgUtils->message("I", $rsp, $::callback);
                    	}
                    	$ret = 1;
                	}
				}

            }

            $thistable->commit;

            next;
        }

		#
		#  handle the rest of the object types
		#

        # get the object type decription from Schema.pm
        my $datatype = $xCAT::Schema::defspec{$type};

		# get the object key to look for, for this object type
        my $objkey = $datatype->{'objkey'};

        #  get a list of valid attr names
        #     for this type object
        my %attrlist;
        foreach my $entry (@{$datatype->{'attrs'}})
        {
            push(@{$attrlist{$type}}, $entry->{'attr_name'});
        }

		my @attrprovided=();

        # check FINALATTRS to see if all the attrs are valid
        foreach my $attr (keys %{$objhash{$objname}})
        {

			if ($attr eq $objkey)
			{
				next;
			}

            if ($attr eq "objtype")
            {

                # objtype not stored in object definition
                next;
            }

            if (!(grep /^$attr$/, @{$attrlist{$type}}))
            {
                if ($::verbose)
                {
                    my $rsp;
                    $rsp->{data}->[0] =
                      "\'$attr\' is not a valid attribute for type \'$type\'.";
                    $rsp->{data}->[1] = "Skipping to the next attribute.";
                    xCAT::MsgUtils->message("I", $rsp, $::callback);
                }
                next;
            }
			push(@attrprovided, $attr); 

        }

        #   we need to figure out what table to
        #      store each attr
        #   And we must do this in the order given in defspec!!

		my @setattrlist=();
		my @checkedattrs;

        foreach my $this_attr (@{$datatype->{'attrs'}})
        {

            my %keyhash;
            my %updates;
			my ($lookup_table, $lookup_attr, $lookup_data);
            my $attr_name = $this_attr->{attr_name};

			if ($attr_name eq $objkey)
			{
    			next;
			}

            # if we have a value for this attribute then process it
            #   - otherwise go to the next attr
            if (defined($objhash{$objname}{$attr_name}))
            {
                # check the defspec to see where this attr goes

                # the table for this attr might depend on the
                #       value of some other attr
                # need to check the only_if entries to find one where the
                #       other attr value matches what we have
                #       ex. like if I want to set hdwctrlpoint I will have
                #               to match the right value for mgtmethod
                if (exists($this_attr->{only_if}))
                {
                    my ($check_attr, $check_value) =
                      split('\=', $this_attr->{only_if});

                    # if my attr value for the attr to check doesn't
                    #   match this then try the next one
                    # ex. say I want to set hdwctrlpoint, the table
                    #   will depend on the mgtmethod attr - so I need
                    #   to find the 'only_if' that matches the value
                    #   specified for that attr (ex. mgtmethod=hmc)

                    # need to check the attrs we are setting for the object
                    #   as well as the attrs for this object that may be
                   	#   already set in DB

					if ( !($objhash{$objname}{$check_attr})  && !($DBattrvals{$objname}{$check_attr}) ) {
                        # if I didn't already check for this attr
                        my $rsp;
                        if (!grep(/^$attr_name$/, @checkedattrs)) {
                            push @{$rsp->{data}}, "Cannot set the \'$attr_name\' attribute unless a value is provided for \'$check_attr\'.\n";

                            foreach my $tmp_attr (@{$datatype->{'attrs'}}) {
                                my $attr = $tmp_attr->{attr_name};
                                if ($attr eq $check_attr) {
                                    my ($tab, $at) = split(/\./, $tmp_attr->{tabentry});
                                    my $schema = xCAT::Table->getTableSchema($tab);
                                    my $desc = $schema->{descriptions}->{$at};
                                    push @{$rsp->{data}}, "$check_attr => $desc\n";
                                }
                            }
                        }
						xCAT::MsgUtils->message("I", $rsp, $::callback);
						push(@checkedattrs, $attr_name);
                        next;
                    }

					if ( !($objhash{$objname}{$check_attr} =~ /\b$check_value\b/) && !($DBattrvals{$objname}{$check_attr}  =~ /\b$check_value\b/) )
                    {
                        next;

                    }
                }

                #  get the info needed to write to the DB table
                #
                # get the actual attr name to use in the table
                #    - may be different then the attr name used for the object.
                ($::tab, $::tabattr) = split('\.', $this_attr->{tabentry});

                # get the lookup info from defspec in Schema.pm
                # ex. 'nodelist.node', 'attr:node'
                my ($lookup_key, $lookup_value) =
                  split('\=', $this_attr->{access_tabentry});

                # ex. 'nodelist', 'node'
                ($lookup_table, $lookup_attr) = split('\.', $lookup_key);

                # ex. 'attr', 'node'
                my ($lookup_type, $lookup_data) = split('\:', $lookup_value);

            }
            else
            {
                next;
            }

            my $val;
            my $delim = ',';
            if(($type eq 'group') && ($DBattrvals{$objname}{'grouptype'} eq 'dynamic')) 
            {
                # dynamic node group selection string use "::" as delimiter
                $delim = '::';
            }
            
            if ($::plus_option)
            {

                # add new to existing - at the end - comma separated
                if (defined($DBattrvals{$objname}{$attr_name}))
                {
                    # add the attr into the list if it's not already in the list!
                    # and avoid the duplicate values
                    my @DBattrarray = split(/$delim/, $DBattrvals{$objname}{$attr_name});
                    my @objhasharray = split(/$delim/, $objhash{$objname}{$attr_name});
                    foreach my $objattr (@objhasharray)
                    {
                        if (!grep(/^\Q$objattr\E$/, @DBattrarray))
                        {
                            push @DBattrarray, $objattr;
                        }
                     }
                     $val = join($delim, @DBattrarray);
                }
                else
                {
                    $val = "$objhash{$objname}{$attr_name}";
                }

            }
            elsif ($::minus_option)
            {

                # remove the specified list of values from the current
                #	attr values.
                if ($DBattrvals{$objname}{$attr_name})
                {

                    # get the list of attrs to remove
                    my @currentList =
                      split(/$delim/, $DBattrvals{$objname}{$attr_name});
                    my @minusList = split(/$delim/, $objhash{$objname}{$attr_name});

                    # make a new list without the one specified
                    my $first = 1;
                    my $newlist;
                    foreach my $i (sort @currentList)
                    {
                        chomp $i;
                        if (!grep(/^$i$/, @minusList))
                        {

                            # set new list for node
                            if (!$first)
                            {
                                $newlist .= "$delim";
                            }
                            $newlist .= $i;
                            $first = 0;
                        }
                    }
                    $val = $newlist;
                }

            }
            else
            {

                #just set the attr to what was provided! - replace
                $val = $objhash{$objname}{$attr_name};

            }

			$allupdates{$lookup_table}{$objname}{$lookup_attr}{$lookup_attr}=$objname;
			$allupdates{$lookup_table}{$objname}{$lookup_attr}{$::tabattr} = $val;
			$setattrs=1;

			push(@setattrlist, $attr_name);

        }    # end - foreach attribute


# TODO - need to get back to this
if (0) {
		#
		#  check to see if all the attrs got set
		#

		my @errlist;
		foreach $a (@attrprovided)
        {	
			#  is this attr was not set then add it to the error list
			if (!grep(/^$a$/, @setattrlist))
			{			
				push(@errlist, $a);
				$ret = 2;
			}

		}
		if ($ret == 2) {
			my $rsp;
			$rsp->{data}->[0] = "Could not set the following attributes for the \'$objname\' definition in the xCAT database: \'@errlist\'\n";
			xCAT::MsgUtils->message("E", $rsp, $::callback);
		}

}

    }    # end - foreach object

	# now set the attribute values in the tables
	#   - handles all except site, monitoring & monsetting for now
	if ($setattrs) {
		foreach my $table (keys %allupdates) {

			# get the keys for this table
			my $schema = xCAT::Table->getTableSchema($table);
			my $keys = $schema->{keys};

			# open the table
			my $thistable = xCAT::Table->new($table, -create => 1, -autocommit => 0);
			if (!$thistable) {
				my $rsp;
				$rsp->{data}->[0] = "Could not set the \'$thistable\' table.";
				xCAT::MsgUtils->message("E", $rsp, $::callback);
				return 1;
			}

			ROW: foreach my $row (keys %{$allupdates{$table}}) {
				my %keyhash;
				my %updates;
				foreach my $key (keys %{$allupdates{$table}{$row}}) {
					foreach my $column (keys %{$allupdates{$table}{$row}{$key}}) {
						# make sure we have a value for each key
						foreach my $k (@$keys) {
							if (!$allupdates{$table}{$row}{$key}{$k}) {
								my $rsp;
								$rsp->{data}->[0] = "\nMissing required attribute values for the \'$row\' object. The required attributes are: @$keys\n";
								xCAT::MsgUtils->message("E", $rsp, $::callback);
								$ret = 1;
								next ROW;
							}
						}

						# need to add obj name
						if ( grep(/^$key$/, @$keys)) {
							# ex. $keyhash{netname}=clstr_net;
							$keyhash{$key}=$row;
						} else {
							$updates{$key}=$row;
						}

						# if attrs are keys then add to keyhash
						if ( grep(/^$column$/, @$keys)) {
							# ex. $keyhash{node}= node1
							$keyhash{$column}=$allupdates{$table}{$row}{$key}{$column};
						} else {
							# ex.  $updates{os}= "AIX"
							$updates{$column} = $allupdates{$table}{$row}{$key}{$column};
						}
					}
				}
				# do table updates one object/row at a a time???
				my ($rc, $str) = $thistable->setAttribs(\%keyhash, \%updates);
			}
			$thistable->commit;
		}
	}
    return $ret;
}

#----------------------------------------------------------------------------

=head3   rmobjdefs

        Remove object definitions from the DB.

        Arguments:
        Returns:
                0 - OK
                1 - error
        Globals:
        Error:
        Example:

		To use create hash for object name and object type
            ex. $objhash{$obj} = $type;
        - then call as follows:
			xCAT::DBobjUtils->rmobjdefs(\%objhash);

        Comments:

=cut

#-----------------------------------------------------------------------------
sub rmobjdefs
{
    my ($class, $hash_ref) = @_;

    my %tablehash;

    my %typehash = %$hash_ref;

    # get the attr=vals for these objects so we know how to
    #   find what tables have to be modified
    my %DBattrvals = xCAT::DBobjUtils->getobjdefs(\%typehash);

	foreach my $objname (sort (keys %typehash))
    {
        my $type = $typehash{$objname};

        # special handling for site table
        if ($type eq 'site')
        {
            my $thistable =
              xCAT::Table->new('site', -create => 1, -autocommit => 0);
            my %keyhash;
            foreach my $attr (keys %{$DBattrvals{$objname}})
            {

                # ex.  key = attr
                $keyhash{key} = $attr;

                $thistable->delEntries(\%keyhash);

            }
            $thistable->commit();
            next;
        }

        # get the object type decription from Schema.pm
        my $datatype = $xCAT::Schema::defspec{$type};

        # go through the list of valid attrs
        #  - need to delete the row with a $key value of $objname from $table
        #  - make a hash containing $delhash{$table}{$key}= $objname
        foreach my $this_attr (@{$datatype->{'attrs'}})
        {
            my $attr = $this_attr->{attr_name};

            # get table lookup info from Schema.pm

            # might need to check the value of other attrs to figure out
            #   what tables some attrs are in
            if (exists($this_attr->{only_if}))
            {
                my ($check_attr, $check_value) =
                  split('\=', $this_attr->{only_if});

                # if the object attr value to check is not the value we need
                #   to match then try the next "only_if" value
                #   next if $DBattrvals{$objname}{$check_attr} ne $check_value;
				next if ( !($DBattrvals{$objname}{$check_attr} =~ /\b$check_value\b/) );

            }

            #  get the info needed to access the DB table
            # ex. 'nodelist.node', 'attr:node'
            my ($lookup_key, $lookup_value) =
              split('\=', $this_attr->{access_tabentry});

            # ex. 'nodelist', 'node'
            my ($lookup_table, $lookup_attr) = split('\.', $lookup_key);

            # ex. 'attr', 'node'
            my ($lookup_type, $lookup_data) = split('\:', $lookup_value);

            # we'll need table name, key name, & object name
            # put this info in a hash - we'll process it later - below

            push @{$tablehash{$lookup_table}{$lookup_attr}}, $objname;
        }
    }

    # now for each table - clear the entry
    foreach my $table (keys %tablehash)
    {
        my %keyhash;

        my $thistable =
          xCAT::Table->new($table, -create => 1, -autocommit => 0);

        #  some tables have multiple keys  !!??
        foreach my $key (keys %{$tablehash{$table}})
        {

            #  - may have a list of objects to remove from the table
            # ex. say key is "node" and table is "nodelist"
            foreach my $obj (@{$tablehash{$table}{$key}})
            {

                # ex. $keyhash{node}=c68m3hvp01
                $keyhash{$key} = $obj;

                # ex. delete the c68m3hvp01 entry of the node column in the
                #	nodelist table
                $thistable->delEntries(\%keyhash);
            }

        }

        $thistable->commit();
    }

    return 0;

}

#----------------------------------------------------------------------------

=head3   readFileInput

        Process the command line input piped in from a file.
		 	(Support stanza or xml format.)

        Arguments:
        Returns:
                0 - OK
                1 - error
        Globals:
        Error:
        Example:

        Comments:
			Set @::fileobjtypes, @::fileobjnames, %::FILEATTRS
				(i.e.- $::FILEATTRS{objname}{attr}=val)

=cut

#-----------------------------------------------------------------------------
sub readFileInput
{
    my ($class, $filedata) = @_;
	my ($objectname, $junk1, $junk2);

    @::fileobjnames = ();

    my @lines = split /\n/, $filedata;

    my $header = @lines[0];

    # to do
    #if ($header =~/<xCAT data object stanza file>/) {
    # do stanza file parsing
    # } elsis ($header =~/<xCAT data object XML file>/) {
    # do XML parsing
    #}

    my $look_for_colon = 1;    # start with first line that has a colon
	my $objtype;

    foreach my $l (@lines)
    {

        # skip blank and comment lines
        next if ($l =~ /^\s*$/ || $l =~ /^\s*#/);

        # see if it's a stanza name
        if (grep(/:\s*$/, $l))
        {

            $look_for_colon = 0;    # ok - we have a colon

            ($objectname, $junk2) = split(/:/, $l);

            # if $junk2 is defined or there's an = 
            if ($junk2 || grep(/=/, $objectname))
            {

                # error - invalid header $line in node definition file
                #         skipping to next node stanza
                # Skipping to the next valid header.
                $look_for_colon++;
                next;
            }

            $objectname =~ s/^\s*//;    # Remove any leading whitespace
            $objectname =~ s/\s*$//;    # Remove any trailing whitespace

            #  could have different default stanzas for different object types

            if ($objectname =~ /default/)
            {

                ($junk1, $objtype) = split(/-/, $objectname);

                if ($objtype)
                {
                    $objectname = 'default';
                }

                next;
            }

            push(@::fileobjnames, $objectname);

        }
        elsif (($l =~ /^\s*(\w+)\s*=\s*(.*)\s*/) && (!$look_for_colon))
        {
            my $attr = $1;
            my $val  = $2;
            $attr =~ s/^\s*//;    # Remove any leading whitespace
            $attr =~ s/\s*$//;    # Remove any trailing whitespace
            $val  =~ s/^\s*//;
            $val  =~ s/\s*$//;

            # remove spaces and quotes so createnode won't get upset
            $val =~ s/^\s*"\s*//;
            $val =~ s/\s*"\s*$//;

            if ($objectname eq "default")
            {

                # set the default for this attribute
                $::defAttrs{$objtype}{$attr} = $val;

            }
            else
            {

                # set the value in the hash for this object
                $::FILEATTRS{$objectname}{$attr} = $val;

                # if the attr being set is "objtype" then check
                # 	to see if we have any defaults set for this type
                # the objtype should be the first etntry in each stanza
                #	so after we set the defaults they will be overwritten
                #	by any values that appear in the rest of the stanza
                if ($attr eq 'objtype')
                {
                    push(@::fileobjtypes, $val);

                    #  $val will be the object type ex. site, node etc.
                    foreach my $a (keys %{$::defAttrs{$val}})
                    {

                        # set the default values for this object hash
                        $::FILEATTRS{$objectname}{$a} = $::defAttrs{$val}{$a};
                    }
                }
            }

        }
        else
        {

            # error - invalid line in node definition file
            $look_for_colon++;
        }

    }    # end while - go to next line

    return 0;

}

#----------------------------------------------------------------------------

=head3   getGroupMembers

        Get the list of members for the specified group.

        Arguments:
        Returns:
            undef - error
            $members - comma-separated list of group members
        Globals:
        Error:
        Example:
			To use:
            - create hash for objectname and and attr values  (need group 
				name (object), and grouptype  & members attr values at a
				minimum.)
			
                ex. $objhash{$obj}{$attr} = value;

            - then call as follows:
                xCAT::DBobjUtils->getGroupMembers($objectname, \%objhash);

        Comments:

=cut

#-----------------------------------------------------------------------------
sub getGroupMembers
{
    my ($class, $objectname, $hash_ref) = @_;

    my $members;

    my %objhash = %$hash_ref;

    if ($objhash{$objectname}{'grouptype'} eq 'static')
    {

        my $table = "nodelist";

        my @TableRowArray = xCAT::DBobjUtils->getDBtable($table);

        my $first = 1;
        foreach (@TableRowArray)
        {

            # if find the group name in the "groups" attr value then add the
            #	 node name to the member list
            #if ($_->{'groups'} =~ /$objectname/)

            my @nodeGroupList = split(',', $_->{'groups'});
            if (grep(/^$objectname$/, @nodeGroupList))

            {
                chomp($_->{'node'});
                if (!$first)
                {
                    $members .= ",";
                }
                $members .= $_->{'node'};
                $first = 0;
            }
        }

    }
    elsif ($objhash{$objectname}{'grouptype'} eq 'dynamic')
    {

        # find all nodes that satisfy the criteria specified in "wherevals"
        #	value
        my %whereHash;
        my %tabhash;
        my %nodeattrhash;

        # remove spaces and quotes so createnode won't get upset
        #$val =~ s/^\s*"\s*//;
        #$val =~ s/\s*"\s*$//;

        my @tmpWhereList = split('::', $objhash{$objectname}{'wherevals'});
        my $rc = xCAT::Utils->parse_selection_string(\@tmpWhereList, \%whereHash);
        if ($rc != 0)
        {
            my $rsp;
            $rsp->{data}->[0] =
              "The \'-w\' option has an incorrect attr*val pair.";
            xCAT::MsgUtils->message("E", $rsp, $::callback);
        }

        # see what nodes have these attr=values
        # get a list of all nodes
        my @tmplist = xCAT::DBobjUtils->getObjectsOfType('node');

        # create a hash of obj names and types
		my %tmphash;
        foreach my $n (@tmplist)
        {
            $tmphash{$n} = 'node';
        }

        # Only get the specific attributes of the node
        my @whereattrs = keys %whereHash;
        my %nodeattrhash = xCAT::DBobjUtils->getobjdefs(\%tmphash, 0, \@whereattrs);

        my $first = 1;
        foreach my $objname (keys %nodeattrhash)
        {
            if (xCAT::Utils->selection_string_match(\%nodeattrhash, $objname, \%whereHash))
            {
                chomp($objname);
                if (!$first)
                {
                    $members .= ",";
                }
                $members .= $objname;
                $first = 0;
            }
        }

    }
    return $members;
}

#----------------------------------------------------------------------------

=head3   getNetwkInfo

        Get the network info from the database for a list of nodes.

        Arguments:
        Returns:
                undef
                hash ref - ex. $nethash{nodename}{networks attr name} = value
        Globals:
        Error:
        Example:

                %nethash = xCAT::DBobjUtils->getNetwkInfo(\@targetnodes);

		Comments:
			      Not supporting IPv6 yet

=cut

#-----------------------------------------------------------------------------
sub getNetwkInfo
{
	my ($class, $ref_nodes) = @_;
	my @nodelist    = @$ref_nodes;

	my %nethash;
	my @attrnames;

	# get the current list of network attrs (networks table columns)
    my $datatype = $xCAT::Schema::defspec{'network'};
	foreach my $a (@{$datatype->{'attrs'}}) {
		my $attr = $a->{attr_name};
		push(@attrnames, $attr);
	}

	# read the networks table
	my @TableRowArray = xCAT::DBobjUtils->getDBtable('networks');
	if (!defined(@TableRowArray))
    {
		return undef;
	}

	# for each node - get the network info
	foreach my $node (@nodelist)
    {

		# get, check, split the node IP
		my $IP = inet_ntoa(inet_aton($node));
		chomp $IP;
		unless ($IP =~ /\d+\.\d+\.\d+\.\d+/)
		{
    		next;    #Not supporting IPv6 yet
		}
		my ($ia, $ib, $ic, $id) = split('\.', $IP);

		# check the entries of the networks table
		# - if the bitwise AND of the IP and the netmask gives you 
		#	the "net" name then that is the entry you want.
		foreach (@TableRowArray) {
			my $NM = $_->{'mask'};
			my $net=$_->{'net'};
			chomp $NM;
			chomp $net;
			my ($n1, $n2, $n3, $n4) = split('\.', $net);
			my ($na, $nb, $nc, $nd) = split('\.', $NM);

			# Convert to integers so the bitwise and (&) works correctly.
			int $ia;
			int $ib;
			int $ic;
			int $id;
			int $na;
			int $nb;
			int $nc;
			int $nd;
			my $sa     = ($ia & $na);
			my $sb     = ($ib & $nb);
			my $sc     = ($ic & $nc);
			my $sd     = ($id & $nd);

			# if all the octals match then we have the right network
			if ( ($n1 == $sa) && ($n2 ==$sb) && ($n3 == $sc) && ($n4 == $sd) ) {
				# fill in the hash - 
				foreach my $attr (@attrnames) {
					if ( defined($_->{$attr}) ) {
						$nethash{$node}{$attr} = $_->{$attr};
					}
				}
				next;
			}
		}

	} #end - for each node

	return %nethash;
}

1;