# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#-------------------------------------------------------

=head1
  xCAT plugin package to handle mkzone,chzone,Input rmzone commands

   Supported command:
         mkzone,chzone,rmzone - manage xcat cluster zones

=cut

#-------------------------------------------------------
package xCAT_plugin::zone;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr';
}

use strict;
require xCAT::Utils;
require xCAT::Zone;
require xCAT::MsgUtils;
require xCAT::Table;
use xCAT::NodeRange;
use xCAT::NodeRange qw/noderange abbreviate_noderange/;

use Getopt::Long;


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

=head3  handled_commands

Return list of commands handled by this plugin

=cut

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

sub handled_commands
{
    return { mkzone => "zone",
        chzone => "zone",
        rmzone => "zone",
    };
}



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

=head3  process_request

  Process the command, this only runs on the management node

=cut

#-------------------------------------------------------
sub process_request
{

    my $request  = shift;
    my $callback = shift;
    my $sub_req  = shift;
    $::CALLBACK = $callback;
    my $command = $request->{command}->[0];
    my $rc      = 0;

    # the directory which will contain the zone keys
    my $keydir = "/etc/xcat/sshkeys/";

    # check if Management Node, if not error
    unless (xCAT::Utils->isMN())
    {
        my $rsp = {};
        $rsp->{error}->[0] = "The $command may only be run on the Management Node.";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        return 1;

    }

    # you may not run on AIX
    if (xCAT::Utils->isAIX()) {
        my $rsp = {};
        $rsp->{error}->[0] = "The $command may only be run on a Linux Cluster.";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        return 1;
    }

    # test to see if any parms
    if (scalar($request->{arg} == 0)) {
        my $rsp = {};
        $rsp->{error}->[0] =
"No parameters input to the $command command,  see man page for syntax.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        exit 1;
    }

    my $args = $request->{arg};
    @ARGV = @{$args};    # get arguments
                         # Get the zonename if it is in the input
    my @SaveARGV = @ARGV;
    my $zonename;
    my $arg = @SaveARGV[0];
    if (!($arg =~ /-h/) && (!($arg =~ /-v/))) { # if not -h -v,then it must be a zone name
        $zonename = @SaveARGV[0];    # here is the zonename, if there is one
        if ($zonename) { #  take zonename off the argument list so it will parse correctly
            my $tmp = shift(@SaveARGV);
            @ARGV = @SaveARGV;
        }
    }
    Getopt::Long::Configure("posix_default");
    Getopt::Long::Configure("no_gnu_compat");
    Getopt::Long::Configure("bundling");
    my %options = ();

    if (
        !GetOptions(
            'a|noderange=s'           => \$options{'addnoderange'},
            'r|noderange=s'           => \$options{'rmnoderange'},
            'defaultzone|defaultzone' => \$options{'defaultzone'},
            'g|assigngrp'             => \$options{'assigngroup'},
            'f|force'                 => \$options{'force'},
            'h|help'                  => \$options{'help'},
            'k|sshkeypath=s'          => \$options{'sshkeypath'},
            'K|genkeys'               => \$options{'gensshkeys'},
            's|sshbetweennodes=s'     => \$options{'sshbetweennodes'},
            'v|version'               => \$options{'version'},
            'V|verbose'               => \$options{'verbose'},
        )
      )
    {

        &usage($callback, $command);
        exit 1;
    }

    # if the ARGS still have data we have invalid input
    if (@ARGV) {
        my $args = join(',', @ARGV);
        my $rsp = {};
        $rsp->{error}->[0] =
"The input to the command: $command contained invalid arguments: $args.";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        exit 1;
    }

    if ($options{'help'})
    {
        &usage($callback, $command);
        exit 0;
    }
    if ($options{'version'})
    {
        my $version = xCAT::Utils->Version();
        my $rsp     = {};
        $rsp->{data}->[0] = $version;
        xCAT::MsgUtils->message("I", $rsp, $callback);
        exit 0;
    }

    # test to see if the zonename was input
    if (!$zonename) {
        my $rsp = {};
        $rsp->{error}->[0] =
          "zonename not specified, it is required for this command.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        exit 1;
    } else {
        $request->{zonename} = $zonename;
    }

    # if -s entered must be yes/1 or no/0
    if ($options{'sshbetweennodes'}) {
        if ($options{'sshbetweennodes'} =~ /^yes$/i || $options{'sshbetweennodes'} eq "1") {
            $options{'sshbetweennodes'} = "yes";
        } else {
            if ($options{'sshbetweennodes'} =~ /^no$/i || $options{'sshbetweennodes'} eq "0") {
                $options{'sshbetweennodes'} = "no";
            } else {
                my $rsp = {};
                $rsp->{error}->[0] =
"The input on the -s flag $options{'sshbetweennodes'} is not valid.";
                xCAT::MsgUtils->message("E", $rsp, $callback);
                exit 1;
            }
        }
    }

    # cannot enter -K and -k
    if (($options{'sshkeypath'}) && ($options{'gensshkeys'})) {
        my $rsp = {};
        $rsp->{error}->[0] =
          "The input of -k and -K is not valid on the command : $command.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        exit 1;
    }

    # check for site.sshbetweennodes attribute, put out a warning it will not be used as long
    # as zones are defined in the zone table.
    my @entries = xCAT::TableUtils->get_site_attribute("sshbetweennodes");
    if ($entries[0]) {
        my $rsp = {};
        $rsp->{info}->[0] =
"The site table sshbetweennodes attribute is set to $entries[0].  It is not used when zones are defined.  To get rid of this warning, remove the site table sshbetweennodes attribute.";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }

    #  -a and -r flags cannot be used together
    if (($options{'addnoderange'}) && ($options{'rmnoderange'})) {
        my $rsp = {};
        $rsp->{error}->[0] =
"You may not use the -a flag to add nodes and the -r flag to remove nodes on one command.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        exit 1;

    }

    # save input noderange  to add nodes
    if ($options{'addnoderange'}) {

        # check to see if Management Node is in the noderange, if so error
        $request->{noderange}->[0] = $options{'addnoderange'};
        my @nodes = xCAT::NodeRange::noderange($request->{noderange}->[0]);
        my @mname = xCAT::Utils->noderangecontainsMn(@nodes);
        if (@mname)
        {    # MN in the nodelist
            my $nodes = join(',', @mname);
            my $rsp = {};
            $rsp->{error}->[0] =
"You must not run $command and include the  management node: $nodes.";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            exit 1;
        }


    }

    # save input noderange  to remove nodes
    if ($options{'rmnoderange'}) {

        # check to see if Management Node is in the noderange, if so error
        $request->{noderange}->[0] = $options{'rmnoderange'};
        my @nodes = xCAT::NodeRange::noderange($request->{noderange}->[0]);
        my @mname = xCAT::Utils->noderangecontainsMn(@nodes);
        if (@mname)
        {    # MN in the nodelist
            my $nodes = join(',', @mname);
            my $rsp = {};
            $rsp->{error}->[0] =
"You must not run $command and include the  management node: $nodes.";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            exit 1;
        }


    }
    if ($options{'verbose'})
    {
        $::VERBOSE = "yes";
    }

    if ($command eq "mkzone")
    {
        $rc = mkzone($request, $callback, \%options, $keydir);
    }
    if ($command eq "chzone")
    {
        $rc = chzone($request, $callback, \%options, $keydir);
    }
    if ($command eq "rmzone")
    {
        $rc = rmzone($request, $callback, \%options);
    }
    my $rsp = {};
    if ($rc == 0) {
        $rsp->{info}->[0] = "The $command ran successfully.";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    } else {
        $rsp->{error}->[0] = "The $command had errors.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
    }
    return $rc;

}

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

=head3

   Parses and runs  mkzone
   Input
     request
     callback
     Input  arguments from the GetOpts
     zone ssh key dir

=cut

#-------------------------------------------------------
sub mkzone
{
    my ($request, $callback, $options, $keydir) = @_;
    my $rc = 0;

    # already checked but lets do it again,  need a zonename, it is the only required parm
    if (!($request->{zonename})) {

        my $rsp = {};
        $rsp->{error}->[0] =
          "zonename not specified The zonename is required.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    # test for -g, if no noderange this is an error
    if ((!defined($$options{'addnoderange'})) && ($$options{'assigngroup'})) {
        my $rsp = {};
        $rsp->{error}->[0] =
          " The -g flag requires a noderange ( -a).";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    # test for -r,  not valid
    if ($$options{'rmnoderange'}) {
        my $rsp = {};
        $rsp->{error}->[0] =
          " The -r flag Is not valid for mkzone. Use chzone.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    # check to see if the input zone already exists
    if (xCAT::Zone->iszonedefined($request->{zonename})) {
        my $rsp = {};
        $rsp->{error}->[0] =
" zonename: $request->{zonename} already defined, use chzone or rmzone to change or remove it.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    # Create path to generated ssh keys
    # keydir comes in set to /etc/xcat/sshkeys
    $keydir .= $request->{zonename};
    $keydir .= "/.ssh";


    # add new zones to the zone table
    $rc = addtozonetable($request, $callback, $options, $keydir);
    if ($rc == 0) {    # zone table setup is ok
                       # test for a noderange, if(-a) not supplied nothing to do
        if (defined($$options{'addnoderange'})) {
            $rc = addnodestozone($request, $callback, $options, $keydir);
        }
        if ($rc == 0) {    # zone table setup is ok
                           # generate root ssh keys
            $rc = gensshkeys($request, $callback, $options, $keydir);
            if ($rc != 0) {
                return 1;
            }
        }
    }


    return $rc;

}

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

=head3

   Parses and runs chzone
   Input
     request
     callback
     Input  arguments from the GetOpts


=cut

#-------------------------------------------------------
sub chzone
{
    my ($request, $callback, $options, $keydir) = @_;
    my $rc = 0;

    # Create default  path to generated ssh keys
    # keydir comes in set to /etc/xcat/sshkeys
    $keydir .= $request->{zonename};
    $keydir .= "/.ssh";
    my $zonename = $request->{zonename};

    # already checked but lets do it again,  need a zonename
    if (!($request->{zonename})) {

        my $rsp = {};
        $rsp->{error}->[0] =
          "zonename not specified The zonename is required.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    # see if they asked to do anything
    if ((!($$options{'sshkeypath'})) && (!($$options{'gensshkeys'})) &&
        (!($$options{'addnoderange'})) && (!($$options{'rmnoderange'})) &&
        (!($$options{'defaultzone'}))  &&
        (!($$options{'assigngroup'}))  && (!($$options{'sshbetweennodes'}))) {
        my $rsp = {};
        $rsp->{info}->[0] =
          "chzone was run but nothing to do.";
        xCAT::MsgUtils->message("I", $rsp, $callback);
        return 0;
    }

    # test for -g, if no noderange (-r or -a) this is an error
    if (((!defined($$options{'addnoderange'})) && (!defined($$options{'rmnoderange'}))) && ($$options{'assigngroup'})) {
        my $rsp = {};
        $rsp->{error}->[0] =
          " The -g flag requires a noderange using the -a or -r option.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    #  if -r remove nodes from zone, check to see that they are a member of the zone
    #  if not a member of the zone,  error out and do nothing
    if ($$options{'rmnoderange'}) {
        my @nodes = xCAT::NodeRange::noderange($request->{noderange}->[0]);

        foreach my $node (@nodes) {
            my $nodezonename = xCAT::Zone->getmyzonename($node);
            if ($nodezonename ne $zonename) {
                my $rsp = {};
                $rsp->{error}->[0] =
" $node does not belong to the zone:$zonename. Rerun the chzone -r  command with only nodes in the noderange that are currently assigned to the zone.";
                xCAT::MsgUtils->message("E", $rsp, $callback);
                return 1;
            }

        }
    }

    # get the zone ssh key directory. We don't have a good zone without it.
    my $sshrootkeydir = xCAT::Zone->getzonekeydir($zonename);
    if ($sshrootkeydir == 1) {    # error return
            #if we have been requested to regenerated the ssh keys continue
        if (($$options{'sshkeypath'}) || ($$options{'gensshkeys'})) {
            my $rsp = {};
            $rsp->{info}->[0] =
" sshkeydir attribute not defined for $zonename. The zone sshkeydir will be regenerated.";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        } else { # sshkeydir is missing  and they did not request to regenerate,   that is an error
            my $rsp = {};
            $rsp->{error}->[0] =
" sshkeydir attribute not defined for $zonename. The zone sshkeydir must be regenerated. Rerun this command with -k or -K options";
            xCAT::MsgUtils->message("E", $rsp, $callback);
            return 1;
        }
    } else {    # we got a sshkeydir from the database, use it
        $keydir = $sshrootkeydir;
    }

    # do we regenerate keys (-k or -K)
    if (($$options{'sshkeypath'}) || ($$options{'gensshkeys'})) {
        $rc = gensshkeys($request, $callback, $options, $keydir);
        if ($rc != 0) {
            return 1;
        }
    }

    # update the zone table
    $rc = updatezonetable($request, $callback, $options, $keydir);
    if ($rc == 0) {    # zone table setup is ok
                       # update the nodelist table
        if (defined($$options{'addnoderange'})) {
            $rc = addnodestozone($request, $callback, $options, $keydir);
        } else {       # note -a and -r are not allowed on one chzone
            if (defined($$options{'rmnoderange'})) {
                $rc = rmnodesfromzone($request, $callback, $options, $keydir);
            }
        }
    }


    return $rc;
}

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

=head3

   Parses and runs rmzone
   Input
     request
     callback
     Input  arguments from the GetOpts


=cut

#-------------------------------------------------------
sub rmzone
{
    my ($request, $callback, $options) = @_;

    # already checked but lets do it again,  need a zonename, it is the only required parm
    if (!($request->{zonename})) {

        my $rsp = {};
        $rsp->{error}->[0] =
          "zonename not specified The zonename is required.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    # see if they input invalid flags
    if (($$options{'sshkeypath'}) || ($$options{'gensshkeys'}) ||
        ($$options{'addnoderange'}) || ($$options{'rmnoderange'}) ||
        ($$options{'defaultzone'}) ||
        ($$options{'sshbetweennodes'})) {

        my $rsp = {};
        $rsp->{error}->[0] =
"The following flags are not valid input for the rmzone command: -k,-K,-a,-r,-f,-s  ";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    # check to see if the input zone already exists
    # cannot remove it if it is not defined
    my $zonename = $request->{zonename};
    if (!(xCAT::Zone->iszonedefined($zonename))) {
        my $rsp = {};
        $rsp->{error}->[0] =
          " zonename: $zonename is not defined. You cannot remove it.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    # is this zone is the default zone you must force the delete
    my $defaultzone = xCAT::Zone->getdefaultzone($callback);
    if (($defaultzone eq $zonename) && (!($$options{'force'}))) {
        my $rsp = {};
        $rsp->{error}->[0] =
" You are removing the default zone: $zonename.  You must define another default zone before deleting or use the -f flag to force the removal.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    # get the zone ssh key directory
    my $sshrootkeydir = xCAT::Zone->getzonekeydir($zonename);
    if ($sshrootkeydir == 1) {    # error return
        my $rsp = {};
        $rsp->{info}->[0] =
          " sshkeydir attribute not defined for $zonename. Cannot remove it.";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    } else {                      # remove the keys  unless it is /root/.ssh
        my $roothome = xCAT::Utils->getHomeDir("root");
        $roothome .= "\/.ssh";
        if ($sshrootkeydir eq $roothome) {    # will not delete /root/.ssh
            my $rsp = {};
            $rsp->{info}->[0] =
              "  $zonename sshkeydir is $roothome. This will not be deleted.";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        } else {                              # not roothome/.ssh
               # check to see if id_rsa.pub is there. I don't want to remove the
               # wrong directory
               # if id_rsa.pub exists remove the files
               # then remove the directory
            if (-e "$sshrootkeydir/id_rsa.pub") {
                my $cmd = "rm -rf $sshrootkeydir";
                xCAT::Utils->runcmd($cmd, 0);
                if ($::RUNCMD_RC != 0)
                {
                    my $rsp = {};
                    $rsp->{error}->[0] = "Command: $cmd failed";
                    xCAT::MsgUtils->message("E", $rsp, $callback);
                }
                my ($zonedir, $ssh) = split(/\.ssh/, $sshrootkeydir);
                $cmd = "rmdir $zonedir";
                xCAT::Utils->runcmd($cmd, 0);
                if ($::RUNCMD_RC != 0)
                {
                    my $rsp = {};
                    $rsp->{error}->[0] = "Command: $cmd failed";
                    xCAT::MsgUtils->message("E", $rsp, $callback);
                }
            } else {    #  no id_rsa.pub key will not remove the files
                my $rsp = {};
                $rsp->{info}->[0] = "$sshrootkeydir did not contain an id_rsa.pub key, will not remove files";
                xCAT::MsgUtils->message("I", $rsp, $callback);
            }
        }

    }

    # open zone table and remove this entry
    my $tab = xCAT::Table->new("zone");
    if (!defined($tab)) {
        my $rsp = {};
        $rsp->{error}->[0] =
          " Failure opening the zone table.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    # remove the table entry
    $tab->delEntries({ zonename => $zonename });

    # remove zonename and possibly group name (-g flag) from  any nodes defined in this zone
    my $rc = rmnodesfromzone($request, $callback, $options, "ALL");

    return $rc;



}

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

=head3
      usage

        puts out zone command usage message

        Arguments:
          None

        Returns:

        Globals:


        Error:
                None


=cut

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

sub usage
{
    my ($callback, $command) = @_;
    my $usagemsg1 = "";
    my $usagemsg2 = "";
    if ($command eq "mkzone") {
        $usagemsg1 = " mkzone -h \n mkzone -v \n";
        $usagemsg2 = " mkzone <zonename> [-V] [--defaultzone] [-k <full path to the ssh RSA private key>] \n        [-a <noderange>] [-g] [-f] [-s <yes/no>]";
    } else {
        if ($command eq "chzone") {
            $usagemsg1 = " chzone -h \n chzone -v \n";
            $usagemsg2 = " chzone <zonename> [-V] [--defaultzone] [-k <full path to the ssh RSA private key>] \n      [-K] [-a <noderange>] [-r <noderange>] [-g] [-s <yes/no>]";
        } else {
            if ($command eq "rmzone") {
                $usagemsg1 = " rmzone -h \n rmzone -v \n";
                $usagemsg2 = " rmzone <zonename> [-g]";
            }
        }
    }
    my $usagemsg .= $usagemsg1 .= $usagemsg2;
    if ($callback)
    {
        my $rsp = {};
        $rsp->{data}->[0] = $usagemsg;
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }
    else
    {
        xCAT::MsgUtils->message("I", $usagemsg);
    }
    return;
}

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

=head3

   generate the ssh keys and store them in /etc/xcat/sshkeys/<zonename>/.ssh


=cut

#-------------------------------------------------------
sub gensshkeys
{
    my ($request, $callback, $options, $keydir) = @_;
    my $rc = 0;

    # generate root ssh keys
    # Did they input a path to existing RSA keys
    my $rsakey;
    my $zonename = $request->{zonename};
    if ($$options{'sshkeypath'}) {

        # check to see if RSA keys exists
        $rsakey = $$options{'sshkeypath'} .= "/id_rsa";
        if (!(-e $rsakey)) {    # if it does not exist error out
            my $rsp = {};
            $rsp->{error}->[0] =
"Input $rsakey does not exist.  Cannot generate the ssh root keys for the zone.";
            xCAT::MsgUtils->message("E", $rsp, $callback);
            return 1;
        }
    }

    $rc = xCAT::Zone->genSSHRootKeys($callback, $keydir, $zonename, $rsakey);
    if ($rc != 0) {
        my $rsp = {};
        $rsp->{error}->[0] =
          " Failure generating the ssh root keys for the zone.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }

    return $rc;

}

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

=head3
    addtozonetable
    Add the new zone to the zone table, check if already there and
    error - use either chzone or -f to override default



=cut

#-------------------------------------------------------
sub addtozonetable
{
    my ($request, $callback, $options, $keydir) = @_;
    my $rc = 0;
    my $zoneentry;
    my $tab = xCAT::Table->new("zone");
    if ($tab)
    {
        # read a record from the zone table, if it is empty then add
        #  the xcatdefault entry
        my @zones = $tab->getAllAttribs('zonename');
        if (!(@zones)) {    # table empty
            my %xcatdefaultzone;
            $xcatdefaultzone{defaultzone}     = "yes";
            $xcatdefaultzone{sshbetweennodes} = "yes";
            my $roothome = xCAT::Utils->getHomeDir("root");
            $roothome .= "\/.ssh";
            $xcatdefaultzone{sshkeydir} = $roothome;
            $tab->setAttribs({ zonename => "xcatdefault" }, \%xcatdefaultzone);
        }

        # now add the users zone
        my %tb_cols;
        $tb_cols{sshkeydir} = $keydir;    # key directory
             # set sshbetweennodes attribute from -s flag or default to yes
        if ($$options{'sshbetweennodes'}) {
            $tb_cols{sshbetweennodes} = $$options{'sshbetweennodes'};
        } else {
            $tb_cols{sshbetweennodes} = "yes";
        }
        my $zonename = $request->{zonename};
        if ($$options{'defaultzone'}) {    # set the default
                # check to see if a default already defined
            my $curdefaultzone = xCAT::Zone->getdefaultzone($callback);
            if (!(defined($curdefaultzone))) {    # no default defined
                $tb_cols{defaultzone} = "yes";
                $tab->setAttribs({ zonename => $zonename }, \%tb_cols);
                $tab->commit();
                $tab->close();
            } else {                              # already a default
                if ($$options{'force'}) {         # force the default
                    $tb_cols{defaultzone} = "yes";
                    $tab->setAttribs({ zonename => $zonename }, \%tb_cols);

                    # now change the old default zone to not be the default
                    my %tb1_cols;
                    $tb1_cols{defaultzone} = "no";
                    $tab->setAttribs({ zonename => $curdefaultzone }, \%tb1_cols);
                    $tab->commit();
                    $tab->close();
                } else {                          # no force this is an error
                    my $rsp = {};
                    $rsp->{error}->[0] =
" Failure setting default zone. The defaultzone $curdefaultzone already exists. Use the -f flag if you want to override the current default zone.";
                    xCAT::MsgUtils->message("E", $rsp, $callback);
                    return 1;
                }
            }
        } else {    # not a default zone
            $tb_cols{defaultzone} = "no";
            $tab->setAttribs({ zonename => $zonename }, \%tb_cols);
            $tab->commit();
            $tab->close();
        }
    } else {
        my $rsp = {};
        $rsp->{error}->[0] =
          " Failure opening the zone table.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }


    return $rc;

}

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

=head3
    updatezonetable
    change either the sshbetweennodes or defaultzone  attribute
    or generate new keys ( -k -K)


=cut

#-------------------------------------------------------
sub updatezonetable
{
    my ($request, $callback, $options, $keydir) = @_;
    my $zoneentry;
    my $zonename = $request->{zonename};

    # check for changes
    if (($$options{'sshbetweennodes'}) || ($$options{'defaultzone'}) ||
        ($$options{'sshkeypath'}) || ($$options{'gensshkeys'})) {

        my $tab = xCAT::Table->new("zone");
        if ($tab) {

            # now add the users changes
            my %tb_cols;

            # generated keys ( -k or -K)
            if (($$options{'sshkeypath'}) || ($$options{'gensshkeys'})) {
                $tb_cols{sshkeydir} = $keydir;    # key directory
            }

            # set sshbetweennodes attribute from -s flag
            if ($$options{'sshbetweennodes'}) {
                $tb_cols{sshbetweennodes} = $$options{'sshbetweennodes'};
            }

            # if --defaultzone
            if ($$options{'defaultzone'}) {       # set the default
                    # check to see if a default already defined
                my $curdefaultzone = xCAT::Zone->getdefaultzone($callback);
                if (!(defined($curdefaultzone))) {    # no default defined
                    $tb_cols{defaultzone} = "yes";
                    $tab->setAttribs({ zonename => $zonename }, \%tb_cols);
                    $tab->commit();
                    $tab->close();
                } else {                              # already a default
                    if ($$options{'force'}) {         # force the default
                        $tb_cols{defaultzone} = "yes";
                        $tab->setAttribs({ zonename => $zonename }, \%tb_cols);

                        # now change the old default zone to not be the default
                        my %tb1_cols;
                        $tb1_cols{defaultzone} = "no";
                        $tab->setAttribs({ zonename => $curdefaultzone }, \%tb1_cols);
                        $tab->commit();
                        $tab->close();
                    } else {    # no force this is an error
                        my $rsp = {};
                        $rsp->{error}->[0] =
" Failure setting default zone. The defaultzone $curdefaultzone already exists. Use the -f flag if you want to override the current default zone.";
                        xCAT::MsgUtils->message("E", $rsp, $callback);
                        return 1;
                    }
                }
            } else {  # not a default zone change, just commit the other changes
                $tab->setAttribs({ zonename => $zonename }, \%tb_cols);
                $tab->commit();
                $tab->close();
            }
        } else {
            my $rsp = {};
            $rsp->{error}->[0] =
              " Failure opening the zone table.";
            xCAT::MsgUtils->message("E", $rsp, $callback);
            return 1;
        }
    }


    return 0;

}

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

=head3
    addnodestozone
    Add the new zonename attribute to any nodes in the noderange ( if a noderange specified)
    Add zonename group to nodes in the noderange if -g flag.



=cut

#-------------------------------------------------------
sub addnodestozone
{
    my ($request, $callback, $options, $keydir) = @_;
    my $rc       = 0;
    my $zonename = $request->{zonename};

    # if -g add zonename group also
    my @nodes = xCAT::NodeRange::noderange($request->{noderange}->[0]);

    # check to see if noderange expanded
    if (!(scalar @nodes)) {
        my $rsp = {};
        $rsp->{error}->[0] =
" The noderange $request->{noderange}->[0] is not valid. The nodes are not defined.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }
    my $tab = xCAT::Table->new("nodelist");
    if ($tab)
    {
        # if -g then add the zonename to the group attribute on each node
        if ($$options{'assigngroup'}) {
            foreach my $node (@nodes) {
                xCAT::TableUtils->updatenodegroups($node, $tab, $zonename);
            }
        }

        # set the nodelist zonename attribute to the zonename for all nodes in the range
        $tab->setNodesAttribs(\@nodes, { zonename => $zonename });
        $tab->commit();
        $tab->close();
    } else {
        my $rsp = {};
        $rsp->{error}->[0] =
          " Failure opening the nodelist table.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }
    return $rc;

}

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

=head3
    rmnodesfromzone
    removes the zonename from all nodes with their zonename the input zone or
    the noderange supplied on the -r flag
    if -g, removes zonename group from all nodes defined with their zonename the input zone.
    Note if $ALL is input it removes all nodes from the zone,
     otherwise  $request->{noderange} points to the noderange


=cut

#-------------------------------------------------------
sub rmnodesfromzone
{
    my ($request, $callback, $options, $ALL) = @_;
    my $zonename = $request->{zonename};
    my $tab      = xCAT::Table->new("nodelist");
    if ($tab)
    {
        # read all the nodes with zonename
        my @nodes;
        if ($ALL) {    # do all nodes
            @nodes = xCAT::Zone->getnodesinzone($callback, $zonename);
        } else {       # the nodes in the noderange ( -r )
            @nodes = xCAT::NodeRange::noderange($request->{noderange}->[0]);

            # check to see if noderange expanded
            if (!(scalar @nodes)) {
                my $rsp = {};
                $rsp->{error}->[0] =
" The noderange $request->{noderange}->[0] is not valid. The nodes are not defined.";
                xCAT::MsgUtils->message("E", $rsp, $callback);
                return 1;
            }
        }

        # if -g then remove the zonename  group attribute on each node
        if ($$options{'assigngroup'}) {
            foreach my $node (@nodes) {
                xCAT::TableUtils->rmnodegroups($node, $tab, $zonename);
            }
        }

        # set the nodelist zonename to nothing
        my $nozonename = "";
        $tab->setNodesAttribs(\@nodes, { zonename => $nozonename });
        $tab->commit();
        $tab->close();
    } else {
        my $rsp = {};
        $rsp->{error}->[0] =
          " Failure opening the nodelist table.";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return 1;
    }
    return 0;

}


1;