#!/usr/bin/perl
package xCAT::SwitchHandler;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use strict;
use xCAT::Table;
use xCAT::Utils;
use xCAT::MsgUtils;
use xCAT::MacMap;
use IO::Select;
use IO::Handle;
use Sys::Syslog;
use Data::Dumper;
use POSIX qw/WNOHANG/;
use SNMP;

my $sysDescr = '.1.3.6.1.2.1.1.1';

sub new {
    my $self  = {};
    my $proto = shift;
    my $class = ref($proto) || $proto;
    $self->{switch} = shift;

    bless($self, $class);
    return $self;
}


sub fill_sessionparms {
    my $self      = shift;
    my %args      = @_;
    my $community = $args{community};
    $self->{sessionparms} = $args{sessionents};
    if ($self->{sessionparms}->{snmpversion}) {
        if ($self->{sessionparms}->{snmpversion} =~ /3/) { #clean up to accept things like v3 or ver3 or 3, whatever.
            $self->{sessionparms}->{snmpversion} = 3;
            unless ($self->{sessionparms}->{auth}) {
                $self->{sessionparms}->{auth} = 'md5'; #Default to md5 auth if not specified but using v3
            }
        } elsif ($self->{sessionparms}->{snmpversion} =~ /2/) {
            $self->{sessionparms}->{snmpversion} = 2;
        } else {
            $self->{sessionparms}->{snmpversion} = 1; #Default to lowest common denominator, snmpv1
        }
    }
    unless (defined $self->{sessionparms}->{password}) { #if no password set, inherit the community
        $self->{sessionparms}->{password} = $community;
    }
}



sub setoid {
    my $session = shift;
    my $oid     = shift;
    my $offset  = shift;
    my $value   = shift;
    my $type    = shift;
    unless ($type) { $type = 'INTEGER'; }
    my $varbind = new SNMP::Varbind([ $oid, $offset, $value, $type ]);
    my $data = $session->set($varbind);
    if ($session->{ErrorStr}) { return (1, $session->{ErrorStr}); }
    return 0, $varbind;
}

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

=head3  getsnmpsession
   It gets an snmp session appropriate for a switch using the switches table
   for guidance on the hows.
   Arguments: vlan=> $vid if needed for community string indexing (optional)
=cut

#------------------------------------------------------------
sub getsnmpsession {
    my $self      = shift;
    my $community = shift;
    my $vlanid    = shift;
    my $switch    = $self->{switch};
    my $session;
    my $sessionparams;

    if (!$community) { $community = "private"; }

    $self->{sitetab} = xCAT::Table->new('site');
    my $tmp = $self->{sitetab}->getAttribs({ key => 'snmpc' }, 'value');
    if ($tmp and $tmp->{value}) { $community = $tmp->{value} }

    my $switchestab = xCAT::Table->new('switches', -create => 0);
    my $ent = $switchestab->getNodeAttribs($switch, [qw(switch snmpversion username password privacy auth)]);
    if ($ent) {
        $self->fill_sessionparms(community => $community, sessionents => $ent);
    }

    $sessionparams = $self->{sessionparms};

    my $snmpver = '1';
    if ($sessionparams) {
        $snmpver   = $sessionparams->{snmpversion};
        $community = $sessionparams->{password};
    }

    if ($snmpver ne '3') {
        if ($vlanid) { $community .= '@' . $vlanid; }
        $session = new SNMP::Session(
            DestHost   => $switch,
            Version    => $snmpver,
            Community  => $community,
            UseNumeric => 1
        );
    } else {    #we have snmp3
        my %args = (
            DestHost   => $switch,
            SecName    => $sessionparams->{username},
            AuthProto  => uc($sessionparams->{auth}),
            AuthPass   => $community,
            Version    => $snmpver,
            SecLevel   => 'authNoPriv',
            UseNumeric => 1
        );
        if ($vlanid) { $args{Context} = "vlan-" . $vlanid; }
        if ($sessionparams->{privacy}) {
            $args{SecLevel}  = 'authPriv';
            $args{PrivProto} = uc($sessionparams->{privacy});
            $args{PrivPass}  = $community;
            $args{Retries}   = 4;
            $args{Timeout}   = 1500000;
        }

        #print "args=" . Dumper(%args) . "\n";
        $session = new SNMP::Session(%args);
    }

    #print "switch=$switch\n";
    if (!$session) { return $session; }

    #get the the switch brand name
    my $tmp = xCAT::MacMap::walkoid($session, "$sysDescr", silentfail => 1);
    my $swbrand;
    if ($tmp->{0}) {

        #print "Desc=" . $tmp->{0} . "\n";
        my @switch_plugins = glob("$::XCATROOT/lib/perl/xCAT_plugin/vlan/*.pm");
        foreach my $fn (@switch_plugins) {
            $fn =~ /.*\/([^\/]*).pm$/;
            my $module = $1;

            #print "fn=$fn,modele=$module\n";
            if (!eval { require "$fn" }) {
                xCAT::MsgUtils->message("S", "Cannot load module $fn");
                next;
            } else {
                no strict 'refs';
                my $filter = ${ "xCAT_plugin::vlan::" . $module . "::" }{filter_string}->();
                my $descr = $tmp->{0};
                if ($descr =~ /$filter/) {
                    $self->{module} = $module;

                    #print "found it:$module\n";
                    last;
                }
            }
        }
    }

    if (!exists($self->{module})) {
        $self->{session} = 0;
        return 0;
    } else {
        $self->{session} = $session;
        return $session;
    }
}



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

=head3 get_vlan_ids

  It gets the existing vlan IDs for the switch.
  Returns:  an array containing all the vlan ids for the switch
=cut

#-------------------------------------------------------------
sub get_vlan_ids {
    my $self    = shift;
    my $session = $self->{session};
    if (!$self->{session}) {
        $session = $self->getsnmpsession();
    }
    unless ($session) { xCAT::MsgUtils->message("S", "Failed to communicate with " . $self->{switch} . " or find a plugin module for the switch."); return; }

    no strict 'refs';
    return ${ "xCAT_plugin::vlan::" . $self->{module} . "::" }{get_vlan_ids}->($session);
}


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

=head3 get_vlanids_for_ports
    It returns a hash pointer that contains the vlan id for each given port.
=cut

#-------------------------------------------------------------
sub get_vlanids_for_ports {
    my $self  = shift;
    my @ports = @_;

    my $session = $self->{session};
    if (!$self->{session}) {
        $session = $self->getsnmpsession();
    }
    unless ($session) { xCAT::MsgUtils->message("S", "Failed to communicate with " . $self->{switch} . " or find a plugin module for the switch."); return; }

    no strict 'refs';
    return ${ "xCAT_plugin::vlan::" . $self->{module} . "::" }{get_vlanids_for_ports}->($session, @ports);

}


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

=head3 create_vlan
    Creates a new vlan on the switch
    Returns an array. (erorcode, errormsg). When errorcode=0, means no error.
=cut

#-------------------------------------------------------------
sub create_vlan {
    my $self      = shift;
    my $vlan_id   = shift;
    my $vlan_name = "xcat_vlan_" . $vlan_id;

    #print "create vlan get called.\n";

    my $session = $self->{session};
    if (!$self->{session}) {
        $session = $self->getsnmpsession();
    }

    #my $session  = $self->getsnmpsession();
    unless ($session) { xCAT::MsgUtils->message("S", "Failed to communicate with " . $self->{switch} . " or find a plugin module for the switch."); return; }

    #print Dumper($self->{sessionparms});

    no strict 'refs';
    return ${ "xCAT_plugin::vlan::" . $self->{module} . "::" }{create_vlan}->($session, $vlan_id);
}

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

=head3 add_ports_to_vlan
    Adds the given ports to the existing vlan
    Returns an array. (erorcode, errormsg). When errorcode=0, means no error.
=cut

#-------------------------------------------------------------
sub add_ports_to_vlan {
    my $self     = shift;
    my $vlan_id  = shift;
    my $portmode = shift;
    my @ports    = @_;

    #print "vlan=$vlan_id, ports=@ports\n";

    my $session = $self->{session};
    if (!$self->{session}) {
        $session = $self->getsnmpsession();
    }

    #my $session  = $self->getsnmpsession();
    unless ($session) { xCAT::MsgUtils->message("S", "Failed to communicate with " . $self->{switch} . " or find a plugin module for the switch."); return; }

    no strict 'refs';
    return ${ "xCAT_plugin::vlan::" . $self->{module} . "::" }{add_ports_to_vlan}->($session, $vlan_id, $portmode, @ports);
}

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

=head3  add_crossover_ports_to_vlan
  It enables the vlan on the cross-over links.
    Returns an array. (erorcode, errormsg). When errorcode=0, means no error.
=cut

#-------------------------------------------------------
sub add_crossover_ports_to_vlan {
    my $self     = shift;
    my $vlan_id  = shift;
    my @switches = @_;

    if (@switches == 0) { return (0, ""); }

    my $session = $self->{session};
    if (!$self->{session}) {
        $session = $self->getsnmpsession();
    }

    #my $session  = $self->getsnmpsession();
    unless ($session) { xCAT::MsgUtils->message("S", "Failed to communicate with " . $self->{switch} . " or find a plugin module for the switch."); return; }

    no strict 'refs';
    return ${ "xCAT_plugin::vlan::" . $self->{module} . "::" }{add_crossover_ports_to_vlan}->($session, $vlan_id, $self->{switch}, @switches);
}

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

=head3 remove_vlan
    Remove a vlan from the switch
    Returns an array. (erorcode, errormsg). When errorcode=0, means no error.
=cut

#-------------------------------------------------------------
sub remove_vlan {
    my $self    = shift;
    my $vlan_id = shift;

    my $session = $self->{session};
    if (!$self->{session}) {
        $session = $self->getsnmpsession();
    }

    #my $session  = $self->getsnmpsession();
    unless ($session) { xCAT::MsgUtils->message("S", "Failed to communicate with " . $self->{switch} . " or find a plugin module for the switch."); return; }

    no strict 'refs';
    return ${ "xCAT_plugin::vlan::" . $self->{module} . "::" }{remove_vlan}->($session, $vlan_id);
}

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

=head3 remove_ports_from_vlan
    Remove ports from a vlan
    Returns an array. (erorcode, errormsg). When errorcode=0, means no error.
=cut

#-------------------------------------------------------------
sub remove_ports_from_vlan {
    my $self    = shift;
    my $vlan_id = shift;
    my @ports   = @_;

    my $session = $self->{session};
    if (!$self->{session}) {
        $session = $self->getsnmpsession();
    }

    #my $session  = $self->getsnmpsession();
    unless ($session) { xCAT::MsgUtils->message("S", "Failed to communicate with " . $self->{switch} . " or find a plugin module for the switch."); return; }

    no strict 'refs';
    return ${ "xCAT_plugin::vlan::" . $self->{module} . "::" }{remove_ports_from_vlan}->($session, $vlan_id, @ports);
}



1;
