#!/usr/bin/env perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT::Utils;
require xCAT::Table;
use POSIX qw(ceil);
use Socket;
require xCAT::Schema;
require Data::Dumper;
require xCAT::NodeRange;
require DBI;

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

=head1    xCAT::Utils

=head2    Package Description

This program module file, is a set of utilities used by xCAT commands.

=cut

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

=head3    quote

    Quote a string, taking into account embedded quotes.  This function is most
    useful when passing string through the shell to another cmd.  It handles one
    level of embedded double quotes, single quotes, and dollar signs.
    Arguments:
        string to quote
    Returns:
        quoted string
    Globals:
        none
    Error:
        none
    Example:
         if (defined($$opthashref{'WhereStr'})) {
            $where = xCAT::Utils->quote($$opthashref{'WhereStr'});
        }
    Comments:
        none
=cut

#--------------------------------------------------------------------------------
sub quote
{
    my ($class, $str) = @_;

    # if the value has imbedded double quotes, use single quotes.  If it also has
    # single quotes, escape the double quotes.
    if (!($str =~ /\"/))    # no embedded double quotes
    {
        $str =~ s/\$/\\\$/sg;    # escape the dollar signs
        $str =~ s/\`/\\\`/sg;
        $str = qq("$str");
    }
    elsif (!($str =~ /\'/))
    {
        $str = qq('$str');
    }       # no embedded single quotes
    else    # has both embedded double and single quotes
    {

        # Escape the double quotes.  (Escaping single quotes does not seem to work
        # in the shells.)
        $str =~ s/\"/\\\"/sg;    #" this comment helps formating
        $str =~ s/\$/\\\$/sg;    # escape the dollar signs
        $str =~ s/\`/\\\`/sg;
        $str = qq("$str");
    }
}

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

=head3    isAIX
    returns 1 if localHost is AIX
    Arguments:
        none
    Returns:
        1 - localHost is AIX
        0 - localHost is some other platform
    Globals:
        none
    Error:
        none
    Example:
         if (xCAT::Utils->isAIX()) { blah; }
    Comments:
        none
=cut

#-------------------------------------------------------------------------------
sub isAIX
{
    if ($^O =~ /^aix/i) { return 1; }
    else { return 0; }
}

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

=head3	xfork
	forks, safely coping with open database handles
	Argumens:
		none
	Returns:
		same as fork
=cut

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

sub xfork
{
    my $rc = fork;
    unless (defined($rc))
    {
        return $rc;
    }
    unless ($rc)
    {

        #my %drivers = DBI->installed_drivers;
        foreach (values %{$::XCAT_DBHS})
        {    #@{$drh->{ChildHandles}}) {
            $_->{InactiveDestroy} = 1;
            undef $_;
        }
    }
    return $rc;
}

sub close_all_dbhs
{
    foreach (values %{$::XCAT_DBHS})
    {        #@{$drh->{ChildHandles}}) {
        $_->disconnect;
        undef $_;
    }
}

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

=head3    isLinux
    returns 1 if localHost is Linux
    Arguments:
        none
    Returns:
        1 - localHost is Linux
        0 - localHost is some other platform
    Globals:
        none
    Error:
        none
    Example:
         if (xCAT::Utils->isLinux()) { blah; }
    Comments:
        none
=cut

#-------------------------------------------------------------------------------
sub isLinux
{
    if ($^O =~ /^linux/i) { return 1; }
    else { return 0; }
}

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

=head3    make_node_list_file

        Makes a node list file.

        Arguments:
                (\@list_of_nodes) - reference to an arrary of nodes.
        Returns:
                $file_name and sets the global var: $::NODE_LIST_FILE
        Globals:
                the ENV vars: DSH_LIST,  RPOWER_LIST,  RCONSOLE_LIST
        Error:
                None documented
        Example:
                xCAT::Utils->make_node_list_file(\@nodelist);

        Comments:
                IMPORTANT:
          Make sure to cleanup afterwards with:

                         xCAT::Utils->close_delete_file($file_handle, $file_name)

=cut

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

sub make_node_list_file
{
    my ($class, $ref_node_list) = @_;
    my @node_list = @$ref_node_list;
    srand(time | $$);    #random number generator start

    my $file = "/tmp/csm_$$";
    while (-e $file)
    {
        $file = xCAT::Utils->CreateRandomName($file);
    }

    open($::NODE_LIST_FILE, ">$file")
      or MsgUtils->message("E", "Cannot write to file: $file\n");
    foreach my $node (@node_list)
    {
        print $::NODE_LIST_FILE "$node\n";
    }
    return $file;
}

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

=head3    CreateRandomName

		Create a randome file name.
				Arguments:
	  	    		Prefix of name
				Returns:
					Prefix with 8 random letters appended
				Error:
				none
				Example:
				$file = xCAT::Utils->CreateRandomName($namePrefix);
				Comments:
					None
																				=cut

#-------------------------------------------------------------------------------
sub CreateRandomName
{
my ($class, $name) = @_;

my $nI;
for ($nI = 0 ; $nI < 8 ; $nI++)
{
   my $char = ('a' .. 'z', 'A' .. 'Z')[int(rand(52)) + 1];
   $name .= $char;
}
	$name;
}

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

=head3
close_delete_file.

	Arguments:
		file handle,filename
	Returns:
	    none
	Globals:
		none
	Error:
		undef
	Example:
	   xCAT::Utils->close_delete_file($file_handle, $file_name);
	Comments:
		none

=cut

#------------------------------------------------------------------------
sub close_delete_file
{
    my ($class, $file_handle, $file_name) = @_;
    close $file_handle;

    unlink($file_name);
}

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

=head3
 list_all_nodes

	Arguments:

	Returns:
	    an array of all define nodes from the nodelist table
	Globals:
		none
	Error:
		undef
	Example:
	   @nodes=xCAT::Utils->list_all_nodes;
	Comments:
		none

=cut

#------------------------------------------------------------------------
sub list_all_nodes
{
    my @nodes;
    my @nodelist;
    my $nodelisttab;
    if ($nodelisttab = xCAT::Table->new("nodelist"))
    {
        my @attribs = ("node");
        @nodes = $nodelisttab->getAllAttribs(@attribs);
        foreach my $node (@nodes)
        {
            push @nodelist, $node->{node};
        }
    }
    else
    {
        xCAT::MsgUtils->message("E", " Could not read the nodelist table\n");
    }
    return @nodelist;
}

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

=head3
 list_all_nodegroups

	Arguments:

	Returns:
	    an array of all define node groups from the nodelist table
	Globals:
		none
	Error:
		undef
	Example:
	   @nodegrps=xCAT::Utils->list_all_nodegroups;
	Comments:
		none

=cut

#------------------------------------------------------------------------
sub list_all_node_groups
{
    my @grouplist;
    my @grouplist2;
    my @distinctgroups;
    my $nodelisttab;
    if ($nodelisttab = xCAT::Table->new("nodelist"))
    {
        my @attribs = ("groups");
        @grouplist = $nodelisttab->getAllAttribs(@attribs);

        # build a distinct list of unique group names
        foreach my $group (@grouplist)
        {
            my $gnames = $group->{groups};
            my @groupnames = split ",", $gnames;
            foreach my $groupname (@groupnames)
            {
                if (!grep(/$groupname/, @distinctgroups))
                {    # not already in list
                    push @distinctgroups, $groupname;
                }
            }
        }
    }
    else
    {
        xCAT::MsgUtils->message("E", " Could not read the nodelist table\n");
    }
    $nodelisttab->close;
    return @distinctgroups;
}

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

=head3
 list_nodes_in_nodegroup

	Arguments:  nodegroup

	Returns:
	    an array of all define nodes in the node group

	Globals:
		none
	Error:
		undef
	Example:
	   @nodes=xCAT::Utils->list_nodes_in_nodegroup($group);
	Comments:
		none

=cut

#------------------------------------------------------------------------
sub list_nodes_in_nodegroups
{
    my ($class, $group) = @_;
    $req->{noderange}->[0] = $group;
    my @nodes = xCAT::NodeRange::noderange($req->{noderange}->[0]);
    return @nodes;
}

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

=head3
  get_site_attribute

	Arguments:

	Returns:
	    The value of the attribute requested from the site table
	Globals:
		none
	Error:
		undef
	Example:
	   @attr=xCAT::Utils->get_site_attribute($attribute);
	Comments:
		none

=cut

#------------------------------------------------------------------------
sub get_site_attribute
{
    my ($class, $attr) = @_;
    my $values;

    my $sitetab = xCAT::Table->new('site');
    if ($sitetab)
    {
        (my $ref) = $sitetab->getAttribs({key => $attr}, value);
        if ($ref and $ref->{value})
        {
            $values = $ref->{value};
        }
    }
    else
    {
        xCAT::MsgUtils->message("E", " Could not read the site table\n");

    }
    $sitetab->close;
    return $values;
}

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

=head3
  add_cron_job
     This function adds a new cron job.
	Arguments:
      	    job--- string in the crontab job format.
	Returns:
	    (code, message)
	Globals:
		none
	Error:
		undef
	Example:
	    xCAT::Utils->add_cron_job("*/5 * * * * /usr/bin/myjob");
	Comments:
		none

=cut

#------------------------------------------------------------------------
sub add_cron_job
{
    $newentry = shift;
    if ($newentry =~ /xCAT::Utils/)
    {
        $newentry = shift;
    }

    #read the cron tab entries
    my @tabs    = `/usr/bin/crontab -l 2>/dev/null`;
    my @newtabs = ();
    foreach (@tabs)
    {
        chomp($_);

        # stop adding if it's already there
        if ($_ eq $newentry) { return (0, "started"); }

        #skip headers for Linux
        next
          if $_ =~
          m/^\#.+(DO NOT EDIT THIS FILE|\(.+ installed on |Cron version )/;
        push(@newtabs, $_);
    }

    #add new entries to the cron tab
    push(@newtabs, $newentry);
    my $tabname = "";
    if (xCAT::Utils::isLinux) { $tabname = "-"; }
    open(CRONTAB, "|/usr/bin/crontab $tabname")
      or return (1, "cannot open crontab.");
    foreach (@newtabs) { print CRONTAB $_ . "\n"; }
    close(CRONTAB);

    return (0, "");
}

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

=head3
  remove_cron_job
     This function removes a new cron job.
	Arguments:
      	    job--- a substring that is contained in a crontab entry.
                  (use crontab -l to see all the job entries.)
	Returns:
	    (code, message)
	Globals:
		none
	Error:
		undef
	Example:
	    xCAT::Utils->remove_cron_job("/usr/bin/myjob");
            This will remove any cron job that contains this string.
	Comments:
		none

=cut

#------------------------------------------------------------------------
sub remove_cron_job
{
    $job = shift;
    if ($job =~ /xCAT::Utils/)
    {
        $job = shift;
    }

    #read the cron tab entries and remove the one that contains $job
    my @tabs    = `/usr/bin/crontab -l 2>/dev/null`;
    my @newtabs = ();
    foreach (@tabs)
    {
        chomp($_);

        # stop adding if it's already there
        next if index($_, $job, 0) >= 0;

        #skip headers for Linux
        next
          if $_ =~
          m/^\#.+(DO NOT EDIT THIS FILE|\(.+ installed on |Cron version )/;
        push(@newtabs, $_);
    }

    #refresh the cron
    my $tabname = "";
    if (xCAT::Utils::isLinux) { $tabname = "-"; }
    open(CRONTAB, "|/usr/bin/crontab $tabname")
      or return (1, "cannot open crontab.");
    foreach (@newtabs) { print CRONTAB $_ . "\n"; }
    close(CRONTAB);

    return (0, "");
}

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

=head3    runcmd
   Run the given cmd and return the output in an array (already chopped).
   Alternately, if this function is used in a scalar context, the output
   is joined into a single string with the newlines separating the lines.

   Arguments:
	   command, exitcode and reference to output
   Returns:
	   see below
   Globals:
	   $::RUNCMD_RC  , $::CALLBACK
   Error:
      Normally, if there is an error running the cmd,it will display the
		error and exit with the cmds exit code, unless exitcode
		is given one of the following values:
            0:     display error msg, DO NOT exit on error, but set
					$::RUNCMD_RC to the exit code.
			-1:     DO NOT display error msg and DO NOT exit on error, but set
				    $::RUNCMD_RC to the exit code.
			-2:    DO the default behavior (display error msg and exit with cmds
				exit code.
             number > 0:    Display error msg and exit with the given code

   Example:
		my $outref = xCAT::Utils->runcmd($cmd, -2, 1);

   Comments:
		   If refoutput is true, then the output will be returned as a
		   reference to an array for efficiency.


=cut

#-------------------------------------------------------------------------------
sub runcmd

{

    my ($class, $cmd, $exitcode, $refoutput) = @_;
    $::RUNCMD_RC = 0;
    if (!$xCAT::Utils::NO_STDERR_REDIRECT)
    {
        if (!($cmd =~ /2>&1$/)) { $cmd .= ' 2>&1'; }

    }
    if ($::VERBOSE)
    {
        xCAT::MsgUtils->message("I", "Running Command: $cmd\n");
    }
    my $outref = [];
    @$outref = `$cmd`;
    if ($?)
    {
        $::RUNCMD_RC = $? >> 8;
        my $displayerror = 1;
        my $rc;
        if (defined($exitcode) && length($exitcode) && $exitcode != -2)
        {
            if ($exitcode > 0)
            {
                $rc = $exitcode;
            }    # if not zero, exit with specified code
            elsif ($exitcode <= 0)
            {
                $rc = '';    # if zero or negative, do not exit
                if ($exitcode < 0) { $displayerror = 0; }
            }
        }
        else
        {
            $rc = $::RUNCMD_RC;
        }    # if exitcode not specified, use cmd exit code
        if ($displayerror)
        {
            my $errmsg = '';
            if (xCAT::Utils->isLinux() && $::RUNCMD_RC == 139)
            {
                $errmsg = "Segmentation fault  $errmsg";
            }
            else
            {
                $errmsg = join('', @$outref);
                chomp $errmsg;

            }
            if ($::CALLBACK)
            {
                my %rsp;
                $rsp->{data}->[0] =
                  "Command failed: $cmd. Error message: $errmsg.\n";
                xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);

            }
            else
            {
                xCAT::MsgUtils->message("E",
                             "Command failed: $cmd. Error message: $errmsg.\n");
            }
            $xCAT::Utils::errno = 29;
        }
    }
    if ($refoutput)
    {
        chomp(@$outref);
        return $outref;
    }
    elsif (wantarray)
    {
        chomp(@$outref);
        return @$outref;
    }
    else
    {
        my $line = join('', @$outref);
        chomp $line;
        return $line;
    }

}

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

=head3    getHomeDir

        Get the path the  user home directory from /etc/passwd.

        Arguments:
                none
        Returns:
                path to  user home directory.
        Globals:
                none
        Error:
                none
        Example:
                $myHome = xCAT::Utils->getHomeDir();
        Comments:
                none

=cut

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

sub getHomeDir
{
    my ($class, $username) = @_;
    my @user = split ':', (`/bin/grep ^$username /etc/passwd 2>&1`);
    my $home = $user[5];
    return $home;
}

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

=head3   setupSSH

        Transfers the ssh keys to setup ssh to the input nodes.

        Arguments:
               Array of nodes
        Returns:

        Globals:
              $::XCATROOT  ,  $::CALLBACK
        Error:
             0=good,  1=error
        Example:
                xCAT::Utils->setupSSH(@target_nodes);
        Comments:
			Does not setup known_hosts.  Assumes automatically
			setup by SSH  ( ssh config option StrictHostKeyChecking no should
			   be set in the ssh config file).

=cut

#--------------------------------------------------------------------------------
sub setupSSH
{
    my ($class, $ref_nodes) = @_;
    my @nodes    = $ref_nodes;
    my @badnodes = ();
    my $n_str    = join ',', @nodes;
    my $SSHdir   = "/install/postscripts/.ssh";
    if ($::XCATROOT)
    {
        $::REMOTESHELL_EXPECT = "$::XCATROOT/sbin/remoteshell.expect";
    }
    else
    {
        $::REMOTESHELL_EXPECT = "/opt/xcat/sbin/remoteshell.expect";
    }
    $::REMOTE_SHELL = "/usr/bin/ssh";

    # make the directory to hold keys to transfer to the nodes
    if (!-d $SSHdir)
    {
        mkdir("/install",                  0755);
        mkdir("/install/postscripts",      0755);
        mkdir("/install/postscripts/.ssh", 0755);
    }

    # Generate the keys
    xCAT::Utils->runcmd("$::REMOTESHELL_EXPECT -k", 0);
    if ($::RUNCMD_RC != 0)
    {    # error
        my %rsp;
        $rsp->{data}->[0] = "remoteshell.expect failed generating keys.";
        xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);

    }

    # Copy the keys to the directory
    my $rc = xCAT::Utils->cpSSHFiles($SSHdir);
    if ($rc != 0)
    {    # error
        my %rsp;
        $rsp->{data}->[0] = "Error running cpSSHFiles.\n";
        xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
        return 1;

    }

    open(FILE, ">$SSHdir/copy.perl")
      or die "cannot open file $SSHdir/copy.perl\n";

    #  build the perl copy script in $SSHdir/copy.perl
    print FILE "#!/usr/bin/perl
my (\$name,\$passwd,\$uid,\$gid,\$quota,\$comment,\$gcos,\$dir,\$shell,\$expire) = getpwnam(\"root\");
my \$home = \$dir;
umask(0077);
\$dest_dir = \"\$home/.ssh/\";
if (! -d \"\$dest_dir\" ) {
    # create a local directory
    \$cmd = \"mkdir -p \$dest_dir\";
    system(\"\$cmd\");
    chmod 0700, \$dest_dir;
}
`cat /tmp/.ssh/authorized_keys >> \$home/.ssh/authorized_keys 2>&1`;
`cat /tmp/.ssh/authorized_keys2 >> \$home/.ssh/authorized_keys2 2>&1`;
`rm -f /tmp/.ssh/authorized_keys 2>&1`;
`rm -f /tmp/.ssh/authorized_keys2 2>&1`;
`rm -f /tmp/.ssh/copy.perl 2>&1`;
rmdir(\"/tmp/.ssh\");";
    close FILE;
    chmod 0744, "$SSHdir/copy.perl";

    # end build Perl code

    #set an ENV var if more than 10 nodes for remoteshell.expect
    my $num_nodes = scalar(@nodes);
    if ($num_nodes > 10)
    {
        $ENV{'XCAT_UPD_MULTNODES'} = 1;
    }

    # send the keys to the nodes
    #
    my $cmd = "$::REMOTESHELL_EXPECT -s $n_str";
    my $rc  = system("$cmd") >> 8;
    if ($rc)
    {
        my %rsp;
        $rsp->{data}->[0] = "remoteshell.expect failed sending keys.";
        xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);

    }

    # must always check to see if worked, run test
    foreach my $n (@nodes)
    {
        my $cmd    = "$::REMOTESHELL_EXPECT -t $::REMOTE_SHELL $n ";
        my @cmdout = `$cmd 2>&1`;
        chomp(@cmdout);    # take the newline off
        my $rc = $? >> 8;
        if ($rc)
        {
            push @badnodes, $n;
        }
    }

    xCAT::Utils->runcmd("/bin/stty echo", 0);
    delete $ENV{'XCAT_UPD_MULTNODES'};

    if (@badnodes)
    {
        my $nstring = join ',', @badnodes;
        my %rsp;
        $rsp->{data}->[0] =
          "SSH setup failed for the following nodes: $nstring.";
        xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
        return @badnodes;
    }
    else
    {
        my %rsp;
        $rsp->{data}->[0] = "$::REMOTE_SHELL setup is complete.";
        xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
        return 0;
    }
}

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

=head3    cpSSHFiles

        Copies the ssh keyfiles and the copy perl script into
		/install/postscripts/.ssh.

        Arguments:
               directory path
        Returns:

        Globals:
              $::CALLBACK
        Error:

        Example:
                xCAT::Utils->cpSSHFiles;

        Comments:
                none

=cut

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

sub cpSSHFiles
{
    my ($class, $SSHdir) = @_;
    my ($cmd, $rc);
    if ($::VERBOSE)
    {
        my %rsp;
        $rsp->{data}->[0] = "Copying SSH Keys";
        xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
    }
    my $home = xCAT::Utils->getHomeDir("root");

    my $authorized_keys  = "$SSHdir/authorized_keys";
    my $authorized_keys2 = "$SSHdir/authorized_keys2";
    if (   !(-e "$home/.ssh/identity.pub")
        || !(-e "$home/.ssh/id_rsa.pub")
        || !(-e "$home/.ssh/id_dsa.pub"))
    {
        return 1;
    }
    $cmd = " cp $home/.ssh/identity.pub $authorized_keys";
    xCAT::Utils->runcmd($cmd, 0);
    if ($::RUNCMD_RC != 0)
    {
        my %rsp;
        $rsp->{data}->[0] = "$cmd failed.\n";
        xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
        return (1);

    }
    else
    {
        if ($::VERBOSE)
        {
            my %rsp;
            $rsp->{data}->[0] = "$cmd succeeded.\n";
            xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
        }
    }

    $cmd = "cp $home/.ssh/id_rsa.pub $authorized_keys2";
    xCAT::Utils->runcmd($cmd, 0);
    if ($::RUNCMD_RC != 0)
    {
        my %rsp;
        $rsp->{data}->[0] = "$cmd failed.\n";
        xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
        return (1);

    }
    else
    {
        if ($::VERBOSE)
        {
            my %rsp;
            $rsp->{data}->[0] = "$cmd succeeded.\n";
            xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
        }
    }

    $cmd = "cat $home/.ssh/id_dsa.pub >> $authorized_keys2";
    xCAT::Utils->runcmd($cmd, 0);
    if ($::RUNCMD_RC != 0)
    {
        my %rsp;
        $rsp->{data}->[0] = "$cmd failed.\n";
        xCAT::MsgUtils->message("E", $rsp, $::CALLBACK);
        return (1);

    }
    else
    {
        if ($::VERBOSE)
        {
            my %rsp;
            $rsp->{data}->[0] = "$cmd succeeded.\n";
            xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
        }
    }

    if (!(-e "$authorized_keys") || !(-e "$authorized_keys2"))
    {
        return 1;
    }
    return (0);
}

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

=head3    isServiceNode
	checks for the /etc/xCATSN file

    Arguments:
        none
    Returns:
        1 - localHost is ServiceNode
        0 - localHost is not ServiceNode
    Globals:
        none
    Error:
        none
    Example:
	     %::XCATMasterPort defined in the caller.
         $return=(xCAT::Utils->isServiceNode())
    Comments:
        none
=cut

#-------------------------------------------------------------------------------
sub isServiceNode
{
    my $value;
    if (-e "/etc/xCATSN")
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

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

=head3    isMN
	checks for the /etc/xCATMN file , if it exists it is a Management Server

    Arguments:
        none
    Returns:
        1 - localHost is ServiceNode
        0 - localHost is not ServiceNode
    Globals:
        none
    Error:
        none
    Example:
         $return=(xCAT::Utils->isMN())
    Comments:
        none
=cut

#-------------------------------------------------------------------------------
sub isMN
{
    my $value;
    if (-e "/etc/xCATMN")
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

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

=head3   classful_networks_for_net_and_mask

    Arguments:
        network and mask
    Returns:
        a list of classful subnets that constitute the entire potentially classless arguments
    Globals:
        none
    Error:
        none
    Example:
    Comments:
        none
=cut

#-------------------------------------------------------------------------------
sub classful_networks_for_net_and_mask
{
    my $network    = shift;
    my $mask       = shift;
    my $given_mask = 0;
    if ($mask =~ /\./)
    {
        $given_mask = 1;
        my $masknumber = unpack("N", inet_aton($mask));
        $mask = 32;
        until ($masknumber % 2)
        {
            $masknumber = $masknumber >> 1;
            $mask--;
        }
    }

    my @results;
    my $bitstoeven = (8 - ($mask % 8));
    if ($bitstoeven eq 8) { $bitstoeven = 0; }
    my $resultmask = $mask + $bitstoeven;
    if ($given_mask)
    {
        $resultmask =
          inet_ntoa(pack("N", (2**$resultmask - 1) << (32 - $resultmask)));
    }
    push @results, $resultmask;

    my $padbits  = (32 - ($bitstoeven + $mask));
    my $numchars = int(($mask + $bitstoeven) / 4);
    my $curmask  = 2**$mask - 1 << (32 - $mask);
    my $nown     = unpack("N", inet_aton($network));
    $nown = $nown & $curmask;
    my $highn = $nown + ((2**$bitstoeven - 1) << (32 - $mask - $bitstoeven));

    while ($nown <= $highn)
    {
        push @results, inet_ntoa(pack("N", $nown));

        #$rethash->{substr($nowhex, 0, $numchars)} = $network;
        $nown += 1 << (32 - $mask - $bitstoeven);
    }
    return @results;
}

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

=head3   my_hexnets

    Arguments:
        none
    Returns:
    Globals:
        none
    Error:
        none
    Example:
    Comments:
        none
=cut

#-------------------------------------------------------------------------------
sub my_hexnets
{
    my $rethash;
    my @nets = split /\n/, `/sbin/ip addr`;
    foreach (@nets)
    {
        my @elems = split /\s+/;
        unless (/^\s*inet\s/)
        {
            next;
        }
        (my $curnet, my $maskbits) = split /\//, $elems[2];
        my $bitstoeven = (4 - ($maskbits % 4));
        if ($bitstoeven eq 4) { $bitstoeven = 0; }
        my $padbits  = (32 - ($bitstoeven + $maskbits));
        my $numchars = int(($maskbits + $bitstoeven) / 4);
        my $curmask  = 2**$maskbits - 1 << (32 - $maskbits);
        my $nown     = unpack("N", inet_aton($curnet));
        $nown = $nown & $curmask;
        my $highn =
          $nown + ((2**$bitstoeven - 1) << (32 - $maskbits - $bitstoeven));

        while ($nown <= $highn)
        {
            my $nowhex = sprintf("%08x", $nown);
            $rethash->{substr($nowhex, 0, $numchars)} = $curnet;
            $nown += 1 << (32 - $maskbits - $bitstoeven);
        }
    }
    return $rethash;
}

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

=head3   my_if_netmap
   Arguments:
      none
   Returns:
      hash of networks to interface names
   Globals:
      none
   Error:
      none
   Comments:
      none
=cut

#-------------------------------------------------------------------------------
sub my_if_netmap
{
    if (scalar(@_))
    {    #called with the other syntax
        $net = shift;
    }
    my @rtable = split /\n/, `netstat -rn`;
    if ($?)
    {
        return "Unable to run netstat, $?";
    }
    my %retmap;
    foreach (@rtable)
    {
        if (/^\D/) { next; }    #skip headers
        if (/^\S+\s+\S+\s+\S+\s+\S*G/)
        {
            next;
        }                       #Skip networks that require gateways to get to
        /^(\S+)\s.*\s(\S+)$/;
        $retmap{$1} = $2;
    }
    return \%retmap;
}

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

=head3   my_ip_facing

    Arguments:
        none
    Returns:
    Globals:
        none
    Error:
        none
    Example:
    Comments:
        none
=cut

#-------------------------------------------------------------------------------
sub my_ip_facing
{
    my $peer = shift;
    if (@_)
    {
        $peer = shift;
    }
    my $noden = unpack("N", inet_aton($peer));
    my @nets = split /\n/, `/sbin/ip addr`;
    foreach (@nets)
    {
        my @elems = split /\s+/;
        unless (/^\s*inet\s/)
        {
            next;
        }
        (my $curnet, my $maskbits) = split /\//, $elems[2];
        my $curmask = 2**$maskbits - 1 << (32 - $maskbits);
        my $curn = unpack("N", inet_aton($curnet));
        if (($noden & $curmask) == ($curn & $curmask))
        {
            return $curnet;
        }
    }
    return undef;
}

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

=head3 nodeonmynet - checks to see if node is on the network
    Arguments:
       Node name
    Returns:  1 if node is on the network
    Globals:
        none
    Error:
        none
    Example:
    Comments:
        none
=cut

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

sub nodeonmynet
{
    my $nodetocheck = shift;
    if (scalar(@_))
    {
        $nodetocheck = shift;
    }
    unless (inet_aton($nodetocheck))
    {
        return 0;
    }
    my $nodeip = inet_ntoa(inet_aton($nodetocheck));
    unless ($nodeip =~ /\d+\.\d+\.\d+\.\d+/)
    {
        return 0;    #Not supporting IPv6 here IPV6TODO
    }
    my $noden = unpack("N", inet_aton($nodeip));
    my @nets = split /\n/, `/sbin/ip route`;
    foreach (@nets)
    {
        my @elems = split /\s+/;
        unless ($elems[1] =~ /dev/)
        {
            next;
        }
        (my $curnet, my $maskbits) = split /\//, $elems[0];
        my $curmask = 2**$maskbits - 1 << (32 - $maskbits);
        my $curn = unpack("N", inet_aton($curnet));
        if (($noden & $curmask) == $curn)
        {
            return 1;
        }
    }
    return 0;
}

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

=head3   thishostisnot
    returns  0 if host is not the same
    Arguments:
       hostname
    Returns:
    Globals:
        none
    Error:
        none
    Example:
    Comments:
        none
=cut

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

sub thishostisnot
{
    my $comparison = shift;
    if (scalar(@_))
    {
        $comparison = shift;
    }

    my @ips = split /\n/, `/sbin/ip addr`;
    my $comp = inet_aton($comparison);
    foreach (@ips)
    {
        if (/^\s*inet/)
        {
            my @ents = split(/\s+/);
            my $ip   = $ents[2];
            $ip =~ s/\/.*//;
            if (inet_aton($ip) eq $comp)
            {
                return 0;
            }

            #print Dumper(inet_aton($ip));
        }
    }
    return 1;
}

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

=head3   GetMasterNodeName
        Reads the database for the Master node name for the input node
    Arguments:
		 Node
    Returns:
        MasterHostName
    Globals:
        none
    Error:
        none
    Example:
         $master=(xCAT::Utils->GetMasterNodeName($node))
    Comments:
        none
=cut

#-------------------------------------------------------------------------------
sub GetMasterNodeName
{
    my ($class, $node) = @_;
    my $master;
    my $noderestab = xCAT::Table->new('noderes');
    my $typetab    = xCAT::Table->new('nodetype');
    unless ($noderestab and $typetab)
    {
        xCAT::MsgUtils->message('S',
                                "Unable to open noderes or nodetype table.\n");
        $noderestab->close;
        $typetab->close;
        return 1;
    }
    my $sitetab = xCAT::Table->new('site');
    (my $et) = $sitetab->getAttribs({key => "master"}, 'value');
    if ($et and $et->{value})
    {
        $master = $et->{value};
    }
    $et = $noderestab->getNodeAttribs($node, ['xcatmaster']);
    if ($et and $et->{'xcatmaster'})
    {
        $master = $et->{'xcatmaster'};
    }
    unless ($master)
    {
        xCAT::MsgUtils->message('S', "Unable to identify master for $node.\n");
        $sitetab->close;
        $noderestab->close;
        $typetab->close;
        return 1;
    }

    $sitetab->close;
    $noderestab->close;
    $typetab->close;
    return $master;
}

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

=head3   GetNodeOSARCH
        Reads the database for the OS and Arch of the input Node
    Arguments:
		 Node
    Returns:
        $et->{'os'}
		$et->{'arch'}
    Globals:
        none
    Error:
        none
    Example:
         $master=(xCAT::Utils->GetNodeOSARCH($node))
    Comments:
        none
=cut

#-------------------------------------------------------------------------------
sub GetNodeOSARCH
{
    my ($class, $node) = @_;
    my $noderestab = xCAT::Table->new('noderes');
    my $typetab    = xCAT::Table->new('nodetype');
    unless ($noderestab and $typetab)
    {
        xCAT::MsgUtils->message('S',
                                "Unable to open noderes or nodetype table.\n");
        return 1;
    }
    my $et = $typetab->getNodeAttribs($node, ['os', 'arch']);
    unless ($et and $et->{'os'} and $et->{'arch'})
    {
        xCAT::MsgUtils->message('S',
                           "No os/arch setting in nodetype table for $node.\n");
        return 1;
    }

    return $et;

}

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

=head3 exportDBConfig

  Reads the /etc/xcat/cfgloc file for the DB configuration and exports it
  in $XCATCFG
=cut

#-----------------------------------------------------------------------------
sub exportDBConfig
{

    # export the xcat database configuration
    my $configfile = "/etc/xcat/cfgloc";
    if (!($ENV{'XCATCFG'}))
    {
        if (-e ($configfile))
        {
            open(CFGFILE, "<$configfile")
              or xCAT::MsgUtils->message('S',
                                   "Cannot open $configfile for DB access. \n");
            foreach my $line (<CFGFILE>)
            {
                chop $line;
                $exp .= $line;

                $ENV{'XCATCFG'} = $exp;
                close CFGFILE;
                last;
            }

        }
    }
    return 0;
}

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

=head3 readSNInfo

  Read resource, NFS server, Master node, OS an ARCH from the database
  for the service node

  Input: service nodename
  Output: Masternode, OS and ARCH
=cut

#-----------------------------------------------------------------------------
sub readSNInfo
{
    my ($class, $nodename) = @_;
    my $rc = 0;
    my $et;
    my $masternode;
    my $os;
    my $arch;
    $rc = xCAT::Utils->exportDBConfig();
    if ($rc == 0)
    {

        if ($nodename)
        {
            $masternode = xCAT::Utils->GetMasterNodeName($nodename);
            if (!($masternode))
            {
                xCAT::MsgUtils->message('S',
                                   "Could not get Master for node $nodename\n");
                return 1;
            }

            $et = xCAT::Utils->GetNodeOSARCH($nodename);
            if (!($et->{'os'} || $et->{'arch'}))
            {
                xCAT::MsgUtils->message('S',
                                  "Could not get OS/ARCH for node $nodename\n");
                return 1;
            }
        }
        $et->{'master'} = $masternode;
        return $et;
    }
    return $rc;
}

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

=head3 isServiceReq


  Checks to see if the input service is already setup on the node by
  checking the /etc/xCATSN file for the service name. This is put in the file
  by the service.pm plugin (e.g tftp.pm,dns.pm,...)
  It then:
  Checks the database to see if the input Service should be setup on the
  input service node

  Checks the noderes to see if this service node is the tftserver,nfsserver for
  and node in the table.
  The checks are as follows:
	 If this service node is the tftpserver or nfsserver, then set it up.
	 If not then, if the tftpserver or nfsserver is blank then see if this
	 service node is the xcatmaster,  if it is then setup the service.

  Checks the networks table to see if the dhcpserver or nameserver(DNS)
  should be setup.  If this
  service node is the address for the dhcpserver or DNS then set it up.

  Checks the nodehm table to see if conserver should be setup. If this service 
  node is the conserver,  then set it up.
  Input: service nodename, service,ipaddres(s) of service node
  Output:
        0 - no service required
        1 - setup service
        2 - service is setup, just start the daemon
		-1 - error
    Globals:
        none
    Error:
        none
    Example:
         if (xCAT::Utils->isServiceReq($servicenodename, $service, $serviceip) { blah; }

=cut

#-----------------------------------------------------------------------------
sub isServiceReq
{
    my ($class, $servicenodename, $service, $serviceip) = @_;
    my @ips = @$serviceip;    # list of service node ip addresses and names
    my $rc  = 0;

    # check if service is already setup
    `grep $service /etc/xCATSN`;
    if ($? == 0)
    {                         # service is already setup, just start daemon
        return 2;
    }

    $rc = xCAT::Utils->exportDBConfig();    # export DB env
    if ($rc != 0)
    {
        xCAT::MsgUtils->message('S', "Unable export DB environment.\n");
        return -1;

    }

    # have this service setup
    if (($service eq "dhcpserver") || ($service eq "nameservers"))
    {

        # get handle to networks table
        my $networkstab = xCAT::Table->new('networks');
        unless ($networkstab)
        {
            xCAT::MsgUtils->message('S', "Unable to open networks table.\n");
            $networkstab->close;
            return -1;
        }
        foreach $serviceip (@ips)
        {
            my $whereclause = "$service like '$serviceip'";
            my @netlist     =
              $networkstab->getAllAttribsWhere($whereclause, 'netname',
                                               $service);
            if (@netlist)
            {
                $networkstab->close;
                return 1;   # found an entry in the networks table for this node
            }
        }
    }
    else
    {
        if ($service eq "cons")
        {

            # get handle to nodehm table
            my $nodehmtab = xCAT::Table->new('nodehm');
            unless ($nodehmtab)
            {
                xCAT::MsgUtils->message('S', "Unable to open nodehm table.\n");
                $nodehmtab->close;
                return -1;
            }
            foreach $serviceip (@ips)
            {
                my $whereclause = "conserver like '$serviceip'";
                my @nodelist    =
                  $nodehmtab->getAllAttribsWhere($whereclause, 'node',
                                                 $service);
                foreach my $node (@nodelist)
                {
                    if ($node->{$service} ne "")    # cons defined
                    {
                        $nodehmtab->close;
                        return 1;    # found cons defined for this server
                    }
                }
            }

        }
        else                         # other service TFTP,etc
        {

            # get handle to noderes table
            my $noderestab = xCAT::Table->new('noderes');
            unless ($noderestab)
            {
                xCAT::MsgUtils->message('S', "Unable to open noderes table.\n");
                $noderestab->close;
                return -1;
            }
            foreach $serviceip (@ips)
            {
                my $whereclause = "$service like '$serviceip'";
                my @nodelist    =
                  $noderestab->getAllAttribsWhere($whereclause, 'node',
                                                  $service);
                if (@nodelist)
                {
                    $noderestab->close;
                    return 1;    # found a match
                }
            }

            # if did not find in service attribute, check xcatmaster, but
            # service attribute must be blank
            foreach $serviceip (@ips)
            {
                my $whereclause =
                  "$service is NULL and xcatmaster like '$serviceip'";
                my @nodelist =
                  $noderestab->getAllAttribsWhere($whereclause, 'node',
                                                  $service);
                if (@nodelist)
                {
                    $noderestab->close;
                    return 1;    # found a match
                }
            }
        }

        return 0;  # did not find a node using this service for this servicenode
    }

}

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

=head3 determinehostname  and ip address(s)

  Used on the service node to figure out what hostname and ip address(s)
  are valid names and addresses 
  Input: None
  Output: ipaddress(s),nodename
=cut

#-----------------------------------------------------------------------------
sub determinehostname
{
    my $hostname;
    my $hostnamecmd = "/bin/hostname";
    my @thostname = xCAT::Utils->runcmd($hostnamecmd, 0);
    if ($::RUNCMD_RC != 0)
    {    # could not get hostname
        xCAT::MsgUtils->message("S",
                              "Error $::RUNCMD_RC from $hostnamecmd command\n");
        exit $::RUNCMD_RC;
    }
    $hostname = $thostname[0];

    # strip off domain, if there
    my @shorthost = split(/\./, $hostname);
    my @ips       = xCAT::Utils->gethost_ips;
    my @hostinfo  = (@ips, $shorthost[0]);

    return @hostinfo;
}

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

=head3 update_xCATSN
  Will add the input service string to /etc/xCATSN to indicate that
  the service has been setup by the service node
  Input: service (e.g. tftp, nfs,etc)
  Output: 0 = added, 1= already there

=cut

#-----------------------------------------------------------------------------
sub update_xCATSN
{
    my ($class, $service) = @_;
    my $file = "/etc/xCATSN";
    my $rc   = 0;
    my $cmd  = " grep $service $file";
    xCAT::Utils->runcmd($cmd, -1);
    if ($::RUNCMD_RC != 0)
    {    # need to add
        `echo $service  >> /etc/xCATSN`;
    }
    else
    {
        $rc = 1;
    }
    return $rc;
}

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

=head3 gethost_ips
     Will use ifconfig to determine all possible ip addresses for the
	 host it is running on and then gethostbyaddr to get all possible hostnames

     input:
	 output: array of ipaddress(s)  and hostnames
	 example:  @ips=xCAT::gethost_ips();

=cut

#-----------------------------------------------------------------------------
sub gethost_ips
{
    my ($class) = @_;
    my $cmd;
    my @ipaddress;
    $cmd = "ifconfig" . " -a";
    $cmd = $cmd . "| grep \"inet \"";
    my @result = xCAT::Utils->runcmd($cmd, 0);
    if ($::RUNCMD_RC != 0)
    {
        xCAT::MsgUtils->message("S", "Error from $cmd\n");
        exit $::RUNCMD_RC;
    }
    foreach my $addr (@result)
    {
        my ($inet, $addr1, $Bcast, $Mask) = split(" ", $addr);
        my @ip = split(":", $addr1);
        push @ipaddress, $ip[1];
    }
    my @names = @ipaddress;
    foreach my $ipaddr (@names)
    {
        my $packedaddr = inet_aton($ipaddr);
        my $hostname = gethostbyaddr($packedaddr, AF_INET);
        if ($hostname)
        {
            my @shorthost = split(/\./, $hostname);
            push @ipaddress, $shorthost[0];
        }
    }

    return @ipaddress;
}

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

=head3 create_postscripts_tar

     This routine will tar and compress the /install/postscripts directory
	 and place in /install/autoinst/xcat_postscripts.Z

     input: none
	 output:
	 example: $rc=xCAT::create_postscripts_tar();

=cut

#-----------------------------------------------------------------------------
sub create_postscripts_tar
{
    my ($class) = @_;
    my $cmd;
    if (!(-e "/install/autoinst"))
    {
        mkdir("/install/autoinst");
    }

    $cmd =
      "cd /install/postscripts; tar -cf /install/autoinst/xcatpost.tar * .ssh/* .xcat/*; gzip -f /install/autoinst/xcatpost.tar";
    my @result = xCAT::Utils->runcmd($cmd, 0);
    if ($::RUNCMD_RC != 0)
    {
        xCAT::MsgUtils->message("S", "Error from $cmd\n");
        return $::RUNCMD_RC;
    }
    return 0;

}

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

=head3 get_site_Master

     Reads the site table for the Master attribute and returns it.
     input: none
     output : value of site.Master attribute , blank is an error
	 example: $Master =xCAT::get_site_Master();

=cut

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

sub get_site_Master
{
    my $Master;
    my $sitetab = xCAT::Table->new('site');
    (my $et) = $sitetab->getAttribs({key => "master"}, 'value');
    if ($et and $et->{value})
    {
        $Master = $et->{value};
    }
    else
    {
        xCAT::MsgUtils->message('E',
                           "Unable to read site table for Master attribute.\n");
    }
    return $Master;
}

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

=head3 get_ServiceNode

     Will get the Service node ( name or ipaddress) as known by the Management
	 Server or Node for the input nodename or ipadress of the node

     input: list of nodenames and/or node ipaddresses (array ref)
			service name
			"MN" or "Node"  determines if you want the Service node as known
			 by the Management Node  or by the node.

		recognized service names: xcat,tftpserver,
		nfsserver,conserver,monserver

        service "xcat" is used by command like xdsh that need to know the
		service node that will process the command but are not tied to a
		specific service like tftp

		Todo:  Handle  dhcpserver and nameserver from the networks table

	 output: A hash ref  of arrays, the key is the service node pointing to
			 an array of nodes that are serviced by that service node

     Globals:
        $::ERROR_RC
     Error:
         $::ERROR_RC=0 no error $::ERROR_RC=1 error

	 example: $sn =xCAT::Utils->get_ServiceNode(\@nodes,$service,"MN");

=cut

#-----------------------------------------------------------------------------
sub get_ServiceNode
{
    my ($class, $node, $service, $request) = @_;
    my @node_list = @$node;
    my $cmd;
    my %snhash;
    my $sn;
    my $nodehmtab;
    my $noderestab;
    my $snattribute;
    $::ERROR_RC = 0;

    # determine if the request is for the service node as known by the MN
    # or the node

    if ($request eq "MN")
    {
        $snattribute = "servicenode";
    }
    else    # Node
    {
        $snattribute = "xcatmaster";
    }

    my $master =
      xCAT::Utils->get_site_Master();    # read the site table, master attrib

    $noderestab = xCAT::Table->new('noderes');
    unless ($noderestab)    # no noderes table, use default site.master
    {
        xCAT::MsgUtils->message('I',
                         "Unable to open noderes table. Using site->Master.\n");
        if ($master)        # use site Master value
        {
            foreach my $node (@node_list)
            {               # no noderes table, all use site Master
                push @{$snhash{$master}}, $node;
            }
        }
        else
        {
            xCAT::MsgUtils->message('E', "Unable to read site Master value.\n");
            $::ERROR_RC = 1;
        }
        $noderestab->close;
        return \%snhash;
    }

    if ($service eq "xcat")
    {    # find all service nodes for the nodes in the list
        foreach my $node (@node_list)
        {
            $sn = $noderestab->getNodeAttribs($node, [$snattribute]);
            if ($sn and $sn->{$snattribute})
            {    # if service node defined
                my $key = $sn->{$snattribute};
                push @{$snhash{$key}}, $node;
            }
            else
            {    # use site.master
                push @{$snhash{$master}}, $node;
            }
        }
        $noderestab->close;
        return \%snhash;

    }
    else
    {
        if (
            ($service eq "tftpserver")    # all from noderes table
            || ($service eq "nfsserver") || ($service eq "monserver")
          )
        {
            foreach my $node (@node_list)
            {
                $sn =
                  $noderestab->getNodeAttribs($node, [$service, $snattribute]);
                if ($sn and $sn->{$service})
                {

                    # see if both  MN and Node address in attribute
                    my ($msattr, $nodeattr) = split ',', $sn->{$service};
                    my $key = $msattr;
                    if ($request eq "Node")
                    {
                        if ($nodeattr)    # override with Node, if it exists
                        {
                            $key = $nodeattr;
                        }
                    }
                    push @{$snhash{$key}}, $node;
                }
                else
                {
                    if ($sn and $sn->{$snattribute})    # if it exists
                    {
                        my $key = $sn->{$snattribute};
                        push @{$snhash{$key}}, $node;
                    }
                    else
                    {                                   # use site.master
                        push @{$snhash{$master}}, $node;
                    }
                }
            }
            $noderestab->close;
            return \%snhash;

        }
        else
        {
            if ($service eq "conserver")
            {

                $nodehmtab = xCAT::Table->new('nodehm');
                unless ($nodehmtab)    # no nodehm table default to site->master
                {
                    xCAT::MsgUtils->message('I',
                                            "Unable to open nodehm table.\n");

                    # use service node
                    foreach my $node (@node_list)
                    {
                        $sn =
                          $noderestab->getNodeAttribs($node, [$snattribute]);
                        if ($sn)
                        {
                            my $key = $sn->{$snattribute};
                            push @{$snhash{$key}}, $node;
                        }
                        else
                        {    # no service node use master
                            push @{$snhash{$master}}, $node;
                        }
                    }
                    $nodehmtab->close;
                    return \%snhash;
                }

                # can read the nodehm table
                foreach my $node (@node_list)
                {
                    $sn = $nodehmtab->getNodeAttribs($node, ['conserver']);
                    if ($sn and $sn->{'conserver'})
                    {

                        # see if both  MN and Node address in attribute
                        my ($msattr, $nodeattr) = split ',', $sn->{'conserver'};
                        my $key = $msattr;
                        if ($request eq "Node")
                        {
                            if ($nodeattr)    # override with Node, if it exists
                            {
                                $key = $nodeattr;
                            }
                        }
                        push @{$snhash{$key}}, $node;
                    }
                    else
                    {                         # use service node
                        $sn =
                          $noderestab->getNodeAttribs($node, [$snattribute]);
                        if ($sn and $sn->{$snattribute})
                        {
                            my $key = $sn->{$snattribute};
                            push @{$snhash{$key}}, $node;
                        }
                        else
                        {                     # no service node use master
                            push @{$snhash{$master}}, $node;
                        }
                    }
                }
                $noderestab->close;
                $nodehmtab->close;
                return \%snhash;

            }
            else
            {
                xCAT::MsgUtils->message('E',
                                        "Invalid service=$service input.\n");
                $::ERROR_RC = 1;
            }
        }
    }
    return \%snhash;

}

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

=head3 toIP 

 IPv4 function to convert hostname to IP address

=cut

#-----------------------------------------------------------------------------
sub toIP
{

    if ($_[0] =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/)
    {
        return ([0, $_[0]]);
    }
    my $packed_ip = gethostbyname($_[0]);
    if (!$packed_ip or $!)
    {
        return ([1, "Cannot Resolve: $_[0]\n"]);
    }
    return ([0, inet_ntoa($packed_ip)]);
}

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

=head3 isSN 
 
	Determines if the input node name is a service node

    returns 1 if input host is a service node 
    Arguments:
       hostname 
    Returns:
        1 - is  Service Node
        0 - is not a Service Node
    Globals:
        none
    Error:
        none
    Example:
         if (xCAT::Utils->isSN($nodename)) { blah; }
    Comments:
        none

=cut

#-----------------------------------------------------------------------------
sub isSN
{
    my ($class, $node) = @_;

    # read the node from the nodelist table and see if it
    # is a member of the service group
    my $attr        = "groups";
    my $nodelisttab = xCAT::Table->new('nodelist');
    my $sn          = $nodelisttab->getNodeAttribs($node, [$attr]);
    if ($sn)
    {
        if (grep /service/, $sn->{$attr})
        {
            return 1;
        }
    }
    $nodelisttab->close;
    return 0;
}

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

=head3 getAllSN 
 
    Returns an array of all service nodes 

    Arguments:
       none 
    Returns:
		array of Service Nodes or empty array, if none
    Globals:
        none
    Error:
        1 - error
    Example:
         @allSN=xCAT::Utils->get_AllSN
    Comments:
        none

=cut

#-----------------------------------------------------------------------------
sub getAllSN
{

    # read the node from the nodelist table and see if it
    # is a member of the service group
    my @servicenodes;
    my $nodelisttab = xCAT::Table->new('nodelist');
    my $recs        = $nodelisttab->getAllEntries();
    foreach (@$recs)
    {
        if (grep /service/, $_->{groups})
        {    # in service group
            push @servicenodes, $_->{node};
        }
    }
    $nodelisttab->close;
    return @servicenodes;
}

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

=head3 getSNandNodes 
 
    Returns an hash-array of all service nodes and the nodes they service

    Arguments:
       none 
    Returns:
	 Service Nodes and the nodes they service or empty , if none
    Globals:
        none
    Error:
        1 - error
    Example:
        $sn=xCAT::Utils->getSNandNodes()
    Comments:
        none

=cut

#-----------------------------------------------------------------------------
sub getSNandNodes
{

    # read all the nodes from the nodelist table
    #  call get_ServiceNode to find which Service Node
    # the node belongs to.
    my %sn;
    my @nodes;
    my $nodelisttab = xCAT::Table->new('nodelist');
    my $recs        = $nodelisttab->getAllEntries();
    foreach (@$recs)
    {
        push @nodes, $_->{node};
    }
    $nodelisttab->close;
    $sn = xCAT::Utils->get_ServiceNode(\@nodes, "xcat", "MN");
    return $sn;
}
1;