#!/usr/bin/env perl

#---------------------------------------------------------
# Configure cumulus/onie switches
#---------------------------------------------------------

BEGIN
{
  $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
  $::XCATDIR  = $ENV{'XCATDIR'}  ? $ENV{'XCATDIR'}  : '/etc/xcat';
}
use lib "$::XCATROOT/lib/perl";


use strict;
use Socket;
use Getopt::Long;
use Expect;
use Net::Ping;
use xCAT::Usage;
use xCAT::NodeRange;
use xCAT::NetworkUtils;
use xCAT::Utils;
use xCAT::Table;
use xCAT::MsgUtils;

Getopt::Long::Configure("bundling");
$Getopt::Long::ignorecase = 0;

#global variables
my @nodes;
my @filternodes;


#---------------------------------------------------------
#Main

# parse the options
if (
    !GetOptions(
                'h|help'     => \$::HELP,
                'switches=s' => \$::SWITCH,  
                'port=s'     => \$::PORT,  
                'vlan=s'     => \$::VLAN,
                'snmp'       => \$::SNMP,
                'ssh'        => \$::SSH,
                'license=s'  => \$::LICENSE,
                'ntp'        => \$::NTP,
                'all'        => \$::ALL,
    )
  )
{
    &usage;
    exit(1);
}

# display the usage if -h or --help is specified
if ($::HELP)
{
    &usage;
    exit(0);
}

if ($::SWITCH) {
    my @filternodes = xCAT::NodeRange::noderange( $::SWITCH );
    if (nodesmissed) {
        my $nodenotdefined = join(',', nodesmissed);
        xCAT::MsgUtils->message("I","The following nodes are not defined in xCAT DB: $nodenotdefined");
    }
    # check switch type
    my $switchestab =  xCAT::Table->new('switches');
    my $switches_hash = $switchestab->getNodesAttribs(\@filternodes,['switchtype']);
    foreach my $fsw (@filternodes)  {
        if (($switches_hash->{$fsw}->[0]->{switchtype}) =~ /onie/) {
            push @nodes, $fsw;
        } else {
            xCAT::MsgUtils->message("E","The $fsw is not cumulus switch, will not config");
        }
    }
    unless (@nodes) {
        xCAT::MsgUtils->message("E","No Valid Switch to process");
        exit(1);
    }
} else {
    xCAT::MsgUtils->message("E","Invalid flag, please provide switches with --switches");
    &usage;
    exit(1);
}

my $switches = join(",",@nodes);
my $cmd;
my $vlan;
my $port;
my $sub_req;
my $rc;

if (($::SSH) || ($::ALL))
{
    config_ssh();
}

if ($::LICENSE)
{
    install_license();
}

if ($::SNMP)
{
    config_snmp();
}

if ($::NTP)
{
    config_ntp();
}
if ($::VLAN)
{
    #config_vlan();
}
sub config_ssh {
    my $password = "CumulusLinux!";
    my $userid = "cumulus";
    my $timeout = 30;
    my $keyfile = "/root/.ssh/id_rsa.pub";
    my $rootkey = `cat /root/.ssh/id_rsa.pub`;
    my $cmd;
    my @config_switches;

    # get netmask from network table
    my $nettab = xCAT::Table->new("networks");
    my @nets;
    if ($nettab) {
        @nets = $nettab->getAllAttribs('net','mask');
    }
    
    my $nodetab = xCAT::Table->new('hosts');
    my $nodehash = $nodetab->getNodesAttribs(\@nodes,['ip','otherinterfaces']);

    foreach my $switch (@nodes) {
        #remove old host key from /root/.ssh/known_hosts
        $cmd = `ssh-keygen -R $switch`;

        my $static_ip = $nodehash->{$switch}->[0]->{ip};
        my $discover_ip = $nodehash->{$switch}->[0]->{otherinterfaces};
        my $ssh_ip;

        my $p = Net::Ping->new();
        if ($p->ping($static_ip)) {
            $ssh_ip = $static_ip;
        } elsif ($p->ping($discover_ip)) {
            $ssh_ip = $discover_ip;
        } else {
            print "$switch is not reachable\n";
            next;
        }
        

        my ($exp, $errstr) = cumulus_connect($ssh_ip, $userid, $password, $timeout);
        if (!defined $exp) {
            print ("connect failed $errstr\n");
            next;
        }

        my $ret;
        my $err;

        ($ret, $err) = cumulus_exec($exp, "mkdir -p /root/.ssh");
        ($ret, $err) = cumulus_exec($exp, "chmod 700 /root/.ssh");
        ($ret, $err) = cumulus_exec($exp, "echo \"$rootkey\" >/root/.ssh/authorized_keys");
        ($ret, $err) = cumulus_exec($exp, "chmod 644 /root/.ssh/authorized_keys");

        #config dhcp ip address to static
        if ($ssh_ip eq $discover_ip) {
            #get netmask
            my $mask;
            foreach my $net (@nets) {
                if (xCAT::NetworkUtils::isInSameSubnet( $net->{'net'}, $static_ip, $net->{'mask'}, 0)) {
                    $mask=$net->{'mask'};
                    last;
                }
            }
            ($ret, $err) = cumulus_exec($exp, "ifconfig eth0 $static_ip netmask $mask");
        }

        $exp->hard_close();
        push (@config_switches, $switch);
    }

    if (@config_switches) {
        #update switch status
        my $csw = join(",",@config_switches);
        $cmd = "chdef $csw status=ssh_configured otherinterfaces=";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        print "ssh configured for $csw\n";
        if ($::ALL) {
            $cmd = "updatenode $csw -P hardeths,syslog,enablesnmp,configinterface";
            $rc= xCAT::Utils->runcmd($cmd, 0); 
            if ($::RUNCMD_RC != 0) {
                xCAT::MsgUtils->message("E","Failed to run updatenode, please check the switch");
            }
            $cmd = "chdef $csw status=configured";
            $rc= xCAT::Utils->runcmd($cmd, 0);
            print "switch: $csw configured\n";
        }
    }
}

sub cumulus_connect {
     my $server   = shift;
     my $userid   = shift;
     my $password = shift;
     my $timeout  = shift;

     my $ssh      = Expect->new;
     my $command     = 'ssh';
     my @parameters  = ($userid . "@" . $server);

     $ssh->debug(0);
     $ssh->log_stdout(0);    # suppress stdout output..
     $ssh->slave->stty(qw(sane -echo));

     unless ($ssh->spawn($command, @parameters))
     {
         my $err = $!;
         $ssh->soft_close();
         my $rsp;
         return(undef, "unable to run command $command $err\n");
     }

     $ssh->expect($timeout,
                   [ "-re", qr/WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED/, sub {die "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!\n"; } ],
                   [ "-re", qr/\(yes\/no\)\?\s*$/, sub { $ssh->send("yes\n");  exp_continue; } ],
                   [ "-re", qr/ password:/,        sub {$ssh->send("$password\n"); exp_continue; } ],
                   [ "-re", qr/:~\$/,              sub { $ssh->send("sudo su\n"); exp_continue; } ],
                   [ "-re", qr/ password for cumulus:/, sub { $ssh->send("$password\n"); exp_continue; } ],
                   [ "-re", qr/.*\/home\/cumulus#/, sub { $ssh->clear_accum(); } ],
                   [ timeout => sub { die "No login.\n"; } ]
                  );
     $ssh->clear_accum();
     return ($ssh);
}

sub cumulus_exec {
     my $exp = shift;
     my $cmd = shift;
     my $timeout    = shift;
     my $prompt =  shift;

     $timeout = 10 unless defined $timeout;
     $prompt = qr/.*\/home\/cumulus#/ unless defined $prompt;


     $exp->clear_accum();
     $exp->send("$cmd\n");
     my ($mpos, $merr, $mstr, $mbmatch, $mamatch) = $exp->expect(6,  "-re", $prompt);

     if (defined $merr) {
         return(undef,$merr);
     }
     return($mbmatch);
}

# for cumulus switch, need to set the license file
sub install_license {
    my @config_switches;
    print "install_license\n";
    my $license_file = "/root/license.txt";
    my $file_name = "/root/license.txt";

    if ($::LICENSE) {
        $license_file = $::LICENSE;
    }

    print "file = $license_file\n";
    if (!(-e $license_file) ) {
        print "license file $license_file does not exist\n";
        return;
    }

    foreach my $switch (@nodes) {
        my $cmd = "xdcp $switch $license_file $file_name";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0) {
            xCAT::MsgUtils->message("E","Failed to xscp $license_file to $switch");
            next;
        }

        $cmd = "xdsh $switch '/usr/cumulus/bin/cl-license -i $file_name' ";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0) {
            xCAT::MsgUtils->message("E","Failed to $cmd to $switch");
            next;
        }
        #restart switchd and reload interface
        $cmd = "xdsh $switch 'systemctl enable switchd;systemctl restart switchd;ifreload -a' ";
        xCAT::Utils->runcmd($cmd, 0);
        push (@config_switches, $switch);
    }
    if (@config_switches) {
        my $csw = join(",",@config_switches);
        print "license is installed on $csw\n";
    }

}


#get snmp attributes from switches tabele and setup secure SNMP v3 
sub config_snmp {

    my $switchestab =  xCAT::Table->new('switches');
    my $switches_hash = $switchestab->getNodesAttribs(\@nodes,['username','password','privacy','auth']);

    foreach my $switch (@nodes) {
        #check if xdsh works
        $cmd = "xdsh $switch date";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0) {
            xCAT::MsgUtils->message("E","xdsh command to $switch failed");
            next;
        }

        my $username = $switches_hash->{$switch}->[0]->{username};
        my $password = $switches_hash->{$switch}->[0]->{password};
        my $auth = $switches_hash->{$switch}->[0]->{auth};
        my $privacy = $switches_hash->{$switch}->[0]->{privacy};
        my $privpwd;
        if (defined $privacy) {
            $privpwd = $password;
        }
        
        my $libconf = "/var/lib/snmp/snmpd.conf";
        my $etcconf = "/etc/snmp/snmpd.conf";

        my $cmd_prefix = "xdsh $switch ";
        my $cmd;
        $cmd = $cmd . "systemctl stop snmpd.service;";
        $cmd = $cmd . "sed -i '/$username/d' $libconf;";
        $cmd = $cmd . "sed -i '/$username/d' $etcconf;";
        $cmd = $cmd . "echo 'createUser $username $auth $password $privacy $privpwd' >> $etcconf;";
        $cmd = $cmd . "echo 'rwuser $username' >> $etcconf;";
        $cmd = $cmd . "systemctl start snmpd.service;";

        my $dshcmd = $cmd_prefix . " \"" . $cmd . "\"";

        $rc= xCAT::Utils->runcmd($dshcmd, 0);
        if ($::RUNCMD_RC != 0) {
            xCAT::MsgUtils->message("E","Failed to update snmpd.conf for $switch");
            next;
        }
        print "Add username=$username, password=$password, privacy=$privacy, auth=$auth to snmp service for $switch \n";
    }
}

sub config_ntp {
    my @config_switches;
    my $cmd;
    my $master;
    my $ntpservers;
    my $timezone;


    #get ntpserver, master and timezone from site table
    my @entries = xCAT::TableUtils->get_site_attribute("master");
    my $master  = $entries[0];

    @entries = xCAT::TableUtils->get_site_attribute("timezone");
    $timezone  = $entries[0];

    @entries = xCAT::TableUtils->get_site_attribute("ntpservers");
    my $t_entry = $entries[0];
    if (defined($t_entry)) {
        $ntpservers = $t_entry;
    } else {
        $ntpservers = $master;
    }

    my @servers = split(',', $ntpservers);

    #use ntpserver from network table if available
    my $nettab = xCAT::Table->new("networks");
    my @nets;
    if ($nettab) {
        @nets = $nettab->getAllAttribs('net','mask','ntpservers');
    }


    my $file = "temp.txt";
    open(FILE , ">$file")
            or die "cannot open file $file\n";
    print FILE "#This file is created by xCAT \n";
    print FILE "driftfile /var/lib/ntp/drift\n";
    print FILE "disable auth\n";
    print FILE "restrict 127.0.0.1\n";
    print FILE "interface listen eth0\n";

    foreach my $switch (@nodes) {
        #check if xdsh works
        $cmd = "xdsh $switch date";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0) {
            xCAT::MsgUtils->message("E","xdsh command to $switch failed");
            next;
        }
        $cmd = "xdsh $switch 'echo $timezone >/etc/timezone;dpkg-reconfigure --frontend noninteractive tzdata' ";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0) {
            print "Failed to update ntp timezone\n";
            xCAT::MsgUtils->message("E","Failed to update ntp timezone for $switch");
        }
        #use ntpserver from network table if available
        my $ntpserver;
        foreach my $net (@nets) {
            if (xCAT::NetworkUtils::isInSameSubnet( $net->{'net'}, $switch, $net->{'mask'}, 0)) {
                $ntpserver=$net->{'ntpservers'};
                if (defined $ntpserver) {
                    if ($ntpserver =~ /xcatmaster/) {
                        @servers = $master;
                    } else {
                        @servers = split(',', $ntpserver);
                    }
                }
                last;
            }
        }
        foreach my $server (@servers) {
            `echo "server $server iburst" >> $file`;
        }
   
        $cmd = "xdcp $switch $file";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        $cmd = "xdsh $switch 'cp /etc/ntp.conf /etc/ntp.conf.orig;cp $file /etc/ntp.conf;rm -fr $file;systemctl restart ntp;systemctl enable ntp' ";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0) {
            xCAT::MsgUtils->message("E","Failed to update ntp for $switch");
            next;
        }
        push (@config_switches, $switch);
    }
    close FILE;
    $cmd = `rm -rf $file`;

    if (@config_switches) {
        #update switch status
        my $csw = join(",",@config_switches);
        $cmd = "chdef $csw status=ntp_configured";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        print "$csw: ntp configured\n";
    }

    
}

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

=head3    usage

        Displays message for -h option

=cut

#---------------------------------------------------------
sub usage
{
    print "Usage:
    configonie -h│--help 
    configonie --switches switchnames --ssh 
    configonie --switches switchnames --license filename  
    configonie --switches switchnames --snmp
    configonie --switches switchnames --ntp 

    To setup ssh passwordless, change ip to static, enable snmp configuration and configure basic interface :
        configonie --switches switchnames --all 
    \n";
}


