#!/usr/bin/env perl

#---------------------------------------------------------
# Configure Ethnet Mellonax switches
#---------------------------------------------------------

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


use strict;
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,  
                'config'     => \$::CONFIG,
                'ip'         => \$::IP,
                'name'       => \$::NAME,
                'snmp'       => \$::SNMP,
                'accessvlan=s' => \$::PVID,
                'allowvlan=s'  => \$::VID,
                'port=s'     => \$::PORT,
                'mode=s'     => \$::MODE,
                '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}) =~ /Mellanox/) {
            push @nodes, $fsw;
        } else {
            xCAT::MsgUtils->message("E","The $fsw is not Mellanox 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 $user;
my $cmd;
my $rc;
my $master;
my $vlan;
my $port;
my $mode;

# set community string for switch
my $community = "public";
my @snmpcs = xCAT::TableUtils->get_site_attribute("snmpc");
my $tmp    = $snmpcs[0];
if (defined($tmp)) { $community = $tmp }

# get master
my @masters = xCAT::TableUtils->get_site_attribute("master");
my $master  = $masters[0];

if (($::IP) || ($::ALL)) {
    config_ip();
}

if (($::NAME) || ($::ALL)) {
    config_hostname();
}

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

if (($::CONFIG) || ($::ALL)) {
    run_rspconfig();
}

if ( ($::PVID) || ($::VID) ) {
    config_vlan();
}

sub config_ip {
    my @config_switches;
    my @discover_switches;
    # get host table for otherinterfaces
    my $nodetab = xCAT::Table->new('hosts');
    my $nodehash = $nodetab->getNodesAttribs(\@nodes,['ip','otherinterfaces']);
    # get netmask from network table
    my $nettab = xCAT::Table->new("networks");
    my @nets;
    if ($nettab) {
        @nets = $nettab->getAllAttribs('net','mask');
    }
    foreach my $switch (@nodes) {
        my $dip= $nodehash->{$switch}->[0]->{otherinterfaces};
        if (!$dip) {
            print "Add otherinterfaces attribute for discover ip: chdef $switch otherinterfaces=x.x.x.x\n";
            next;
        }

        # Validate if this IP is reachable 
        my $p = Net::Ping->new();
        if (!$p->ping($dip)) {
            print "$dip is not reachable, will not change ip address\n";
            #clean up discovery switch deifnition if any
            my $ip_str = $dip;
            $ip_str =~ s/\./\-/g;
            my $dswitch = "switch-$ip_str";
            $cmd = "makedns -d $dswitch";
            $rc= xCAT::Utils->runcmd($cmd, 0);
            $cmd = "makehosts -d $dswitch";
            $rc= xCAT::Utils->runcmd($cmd, 0);
            $cmd = "rmdef $dswitch";
            $rc= xCAT::Utils->runcmd($cmd, 0);
            next;
        }
 
        my $static_ip= $nodehash->{$switch}->[0]->{ip};

        # don't need to set if ip addresses are same
        if ($dip eq $static_ip) {
            print "static ip $static_ip and discovery ip $dip is same, will not process command for $switch\n";
            next;
        }

        # get hostname
        my $dswitch = xCAT::NetworkUtils->gethostname($dip);

        # if hostnames are same, created different one for discovery name
        if ($dswitch eq $switch) {
            $dswitch="";
        }

        # if not defined, need to create one for xdsh to use
        if (!$dswitch) {
            my $ip_str = $dip;
            $ip_str =~ s/\./\-/g;
            $dswitch = "switch-$ip_str";
        }
        $cmd = "chdef -t node -o $dswitch groups=switch ip=$dip switchtype=Mellanox username=admin password=admin mgt=switch nodetype=switch";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        $cmd = "makehosts $dswitch";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        $cmd = "makedns $dswitch";
        $rc= xCAT::Utils->runcmd($cmd, 0);

        $cmd="rspconfig $dswitch sshcfg=enable";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0) {
            xCAT::MsgUtils->message("E"," Failed to config ssh passwordless for $dip");
            print "Failed to config ssh passwordless for $dswitch, $dip\n";
            push (@discover_switches, $dswitch);
            next;
        }

        # verify if xdsh works
        $cmd = "xdsh $dswitch -l admin --devicetype IBSwitch::Mellanox 'enable;configure terminal;exit' ";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0) {
            xCAT::MsgUtils->message("E","Couldn't communicate with $dip");
            print "$cmd failed, Couldn't communicate with $dswitch, $dip\n";
            push (@discover_switches, $dswitch);
            next;
        }
        # get netmask
        my $mask;
        foreach my $net (@nets) {
            if (xCAT::NetworkUtils::isInSameSubnet( $net->{'net'}, $static_ip, $net->{'mask'}, 0)) {
                $mask=$net->{'mask'};
            }
        }

        $cmd="xdsh $dswitch -t 10 -l admin --devicetype IBSwitch::Mellanox 'enable;configure terminal;no interface mgmt0 dhcp;interface mgmt0 ip address $static_ip $mask;configuration write;exit;exit' ";
        $rc= xCAT::Utils->runcmd($cmd, 0);

        if (!$p->ping($static_ip)) {
            print "$switch:  Failed to change ip address to $static_ip\n";
            next;
        }

        push (@discover_switches, $dswitch);
        push (@config_switches, $switch);
        print "$switch: Changing IP address to static IP $static_ip\n";
    }

    if (@config_switches) {
        # update switch status
        my $csw = join(",",@config_switches);
        $cmd = "chdef $csw status=ip_configed otherinterfaces=";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        $cmd = "makehosts $csw";
        $rc= xCAT::Utils->runcmd($cmd, 0);
    }
 
     if (@discover_switches) {
        my $dsw = join(",",@discover_switches);
        # remove discover switch from xCATdb and /etc/hosts
        $cmd = "makedns -d $dsw";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        $cmd = "makehosts -d $dsw";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        $cmd = "rmdef $dsw";
        $rc= xCAT::Utils->runcmd($cmd, 0);
    }
}

sub config_hostname {
    my @config_switches;
    my $switchtab = xCAT::Table->new('switches');
    my $switchhash = $switchtab->getNodesAttribs(\@nodes,['sshusername']);

    foreach my $switch (@nodes) {
        my $user= $switchhash->{$switch}->[0]->{sshusername};
        if (!$user) {
            print "switch ssh username is not defined, add default one\n";
            $cmd = "chdef $switch username=admin";
            $rc= xCAT::Utils->runcmd($cmd, 0);
            $user="admin";
        }
        $cmd = "makehosts $switch";
        $rc= xCAT::Utils->runcmd($cmd, 0);

        my $server = $master;
        my @servers = xCAT::NetworkUtils->my_ip_facing($switch);
        unless ($servers[0]) {
            $server = $servers[1];
        }

        $cmd="xdsh $switch -l $user --devicetype IBSwitch::Mellanox 'enable;configure terminal;hostname $switch;ip name-server $server;ip domain-list $server;ip default-gateway $server;configuration write' ";
        $rc= xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0) {
            xCAT::MsgUtils->message("E","Failed to setup hostname for $switch");
            next;
        }
        push (@config_switches, $switch);
        print "$switch: Changing host name to $switch\n";
    }
    if (@config_switches) {
        # update switch status
        my $csw = join(",",@config_switches);
        $cmd = "chdef $csw status=hostname_configed" ;
        $rc= xCAT::Utils->runcmd($cmd, 0);
    }
}

# setup secure SNMP v3
sub config_snmp {
    my $snmp_user;
    my $snmp_passwd;
    my $snmp_auth;
    my $snmp_version;
    my $cmd;

    my $switchtab = xCAT::Table->new('switches');
    my $switchhash = $switchtab->getNodesAttribs(\@nodes,['snmpversion','sshusername','sshpassword','username','password','auth','privacy']);
    foreach my $switch (@nodes) 
    {
        #enable snmp function on the switch
        $cmd=`rspconfig $switch snmpcfg=enable`;

        my $user = $switchhash->{$switch}->[0]->{sshusername};
        if (!$user) {
            print "switch ssh username is not defined, use default one\n";
            $user="admin";
        }
        $snmp_version = $switchhash->{$switch}->[0]->{snmpversion};
        $snmp_passwd = $switchhash->{$switch}->[0]->{password};

        #config snmpv1 with community string defined in the switches table 
        unless ($snmp_version =~ /3/) {
            if ($snmp_passwd) {
                $cmd=`rspconfig $switch community=$snmp_passwd`;
                print "config snmp community string $snmp_passwd\n";
                next;
            }    
        }        

        #config snmpv3 defined in the switches table
        $snmp_user = $switchhash->{$switch}->[0]->{username};
        if (!$snmp_user) {
           print "No snmp user defined for switch $switch. Will not configure snmpv3\n";
           next;
        }
        $snmp_auth = $switchhash->{$switch}->[0]->{auth};
        my $snmp_privacy = $switchhash->{$switch}->[0]->{privacy};

        my $cmd_prefix = "xdsh $switch -l $user --devicetype IBSwitch::Mellanox";
        my $cmd;
        # Build up the commands for easier readability
        $cmd = $cmd . "enable\;";
        $cmd = $cmd . "configure terminal\;";
        $cmd = $cmd . "snmp-server user $snmp_user v3 enable\;";
        if ($snmp_privacy) {
            $cmd = $cmd . "snmp-server user $snmp_user v3 auth $snmp_auth $snmp_passwd priv $snmp_privacy $snmp_passwd\;";
        } else {
            $cmd = $cmd . "snmp-server user $snmp_user v3 auth $snmp_auth $snmp_passwd\; no snmp-server user $snmp_user v3 require-privacy\;";
        }

        print "$switch: snmpv3 configured\n";

        $cmd = $cmd . "configuration write\;exit\;";
        my $final_cmd = $cmd_prefix . " \"" . $cmd . "\"";

        `$final_cmd`
    }
}

sub run_rspconfig {
    my @config_switches;
    my $switchtab = xCAT::Table->new('switches');
    my $switchhash = $switchtab->getNodesAttribs(\@nodes,['sshusername']);

    foreach my $switch (@nodes) {
        my $user= $switchhash->{$switch}->[0]->{sshusername};
        my $server = $master;
        my @servers = xCAT::NetworkUtils->my_ip_facing($switch);
        unless ($servers[0]) {
            $server = $servers[1];
        }

        # enable the snmp trap
        $cmd=`rspconfig $switch alert=enable`;

        # Logging destination:
        $cmd=`rspconfig $switch logdest=$master`;

        # config ntp
        $cmd = "xdsh $switch -l $user --devicetype IBSwitch::Mellanox 'enable;configure terminal;ntp enable;ntpdate $server; ntp server $server;configuration write;show ntp' ";
        $rc= xCAT::Utils->runcmd($cmd, 0);

        push (@config_switches, $switch);
    }
    if (@config_switches) {
        # update switch status
        my $csw = join(",",@config_switches);
        $cmd = "chdef $csw status=switch_configed" ;
        $rc= xCAT::Utils->runcmd($cmd, 0);
    }

}

sub config_vlan {
    my @ports;
    my $port_input;
    # checking for port number, switches is checked earlier
    if ($::PORT) {
       $port_input = $::PORT;
       foreach my $num_str (split /,/, $port_input) {
           if ($num_str =~ /-/) {
               my ($min, $max) = split (/-/,$num_str);
               while ($min <= $max) {
                   push (@ports,$min);
                   $min++;
               }
           } else {
               push (@ports,$num_str);
           }
       }
    } else {
        xCAT::MsgUtils->message("E"," When configuring VLAN, a port must be provided.");
        &usage;
        exit(1);
    }

    my $access_vlan = $::PVID;
    my $allowed_vlan = $::VID;

    # will default to trunk mode
    if ($::MODE) {
       $mode = $::MODE;
       if (!($mode =~ m/(access|trunk|hybrid|access-dcb|dot1q-tunnel)/) )
       {
            xCAT::MsgUtils->message("E"," Please provided supported mode");
            &usage;
            exit(1);
       }
       if ($mode =~ /hybrid/) 
       {
           if (!$access_vlan) {
               print "NOTE: Hybrid mode will change access VLAN back to 1\n"; 
               print "If other than 1, run the command again with access VLAN number: \n";
               print "    configMellanox --switches switchnames --port port --accessvlan vlan1 --allowvlan vlan2 --mode mode\n"; 
               $access_vlan = 1;
           }
       }
    } else {
       $mode = "access";
    }


    foreach my $switch (@nodes) {
        my $devicetype;

        # check if it is ethernet switch or ib switch
        my $ccmd = "snmpwalk -Os -v1 -c $community $switch 1.3.6.1.2.1.1.1";
        my $result = xCAT::Utils->runcmd($ccmd, 0);

        # only supports MSX1410 and MSX1400 for Mellanox Ethernet switch now
        if ( $result =~ /MSX14/ ) {
            $devicetype = "EthSwitch::Mellanox";
        }else {
            xCAT::MsgUtils->message("E","Config IB switch VLAN is not support yet");
            $devicetype = "IBSwitch::Mellanox";
            next;
        }

        print "Tagging access VLAN to $access_vlan and allowed VLAN to $allowed_vlan with $mode mode for $switch port $port_input\n";

        my $cmd_prefix = "xdsh $switch --devicetype $devicetype";
        foreach my $port (@ports) {
            my $cmd;
            # Build up the commands for easier readability
            $cmd = $cmd . "enable\;";
            $cmd = $cmd . "configure terminal\;";
            if ($access_vlan){
                $cmd = $cmd . "vlan $access_vlan\;";
                $cmd = $cmd . "exit\;";
            }
            if ($allowed_vlan){
                $cmd = $cmd . "vlan $allowed_vlan\;";
                $cmd = $cmd . "exit\;";
            }
            $cmd = $cmd . "interface ethernet 1/$port\;";
            $cmd = $cmd . "switchport mode $mode\;";
            if ($mode =~ /access/) {
                $cmd = $cmd . "switchport access vlan $access_vlan\;";
            } elsif ($mode =~ /hybrid/) {
                $cmd = $cmd . "switchport $mode allowed-vlan $allowed_vlan\;";
                $cmd = $cmd . "switchport access vlan $access_vlan\;";
            } else {
                $cmd = $cmd . "switchport $mode allowed-vlan $allowed_vlan\;";
            }
            $cmd = $cmd . "exit\;exit\;exit\;";
            my $final_cmd = $cmd_prefix . " \"" . $cmd . "\"";

            `$final_cmd`
        }
    }
}


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

=head3    usage

        Displays message for -h option

=cut

#---------------------------------------------------------
sub usage
{
    print "Usage:
    configMellanox -h│--help 
    configMellanox --switches switchnames --ip
    configMellanox --switches switchnames --name
    configMellanox --switches switchnames --snmp 
    
    To set the ip address, hostname, config snmp and run rspconfig command:
        configMellanox --switches switchnames --all

    To run rspconfig command: 
        configMellanox --switches switchnames --config  

    To configure VLAN on a specified port (Mellanox Ethernet switch ONLY):
        configMellanox --switches switchnames --port port --accessvlan vlan1 --allowvlan vlan2 --mode mode

            The following mode are supported for switchport:
                * access        Only untagged ingress Ethernet packets are allowed 
                * trunk         Only tagged ingress Ethernet packets are allowed
                * hybrid        Both tagged and untagged ingress Ethernet packets are allowed
                * access-dcb    Only untagged ingress Ethernet packets are allowed. Egress packets will be priority tagged
                * dot1q-tunnel  Both tagged and untagged ingress Ethernet packets are allowed. Egress packets are tagged with a second VLAN (802.1Q) header

    \n";
}


