snmpit_mellanox.pm 47.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/perl -w

#
# Copyright (c) 2013 University of Utah and the Flux Group.
# 
# {{{EMULAB-LGPL
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
# License for more details.
# 
# You should have received a copy of the GNU Lesser General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
#

#
# snmpit module for Mellanox Ethernet networks switches
#

package snmpit_mellanox;
use strict;

$| = 1; # Turn off line buffering on output

use English;
use SNMP;
use snmpit_lib;
use MLNX_XMLGateway;

use libtestbed;
use Lan;
use Port;
use Data::Dumper;

45
# Mellanox REST API static paths
46
my $MLNX_BASE  = "/mlnxos/v1";
47
48
49
50
51
52
53
my $MLNX_VSR   = "$MLNX_BASE/vsr/default_vsr";
my $MLNX_IFC_PREFIX = "$MLNX_VSR/interfaces";
my $MLNX_VLAN_PREFIX = "$MLNX_VSR/vlans";

my $MLNX_DEF_VLAN = 1;

# status indicators for ports (ifTable entries).
54
55
my $STATUS_UP = 1;
my $STATUS_DOWN = 2;
Kirk Webb's avatar
Kirk Webb committed
56
my $SNMP_NO_INSTANCE = "NOSUCHINSTANCE";
57
58

#
59
# Port status and control.
60
61
62
63
64
#
my $PORT_ADMIN_STATUS     = "ifAdminStatus";
my $PORT_OPER_STATUS      = "ifOperStatus";
my $PORT_SPEED            = "ifHighSpeed";

65
66
67
68
69
70
my %cmdPaths =
    (
     "enable"   => ["set-modify", "enabled=true", undef],
     "disable"  => ["set-modify", "enabled=false", undef],
    );

71
72
73
74
75
76
77
78
my $PORT_FORMAT_IFINDEX   = 1;
my $PORT_FORMAT_PORT      = 2;
my $PORT_FORMAT_MLNX      = 3;

#
# Creates a new object. 
#
# usage: new($classname,$devicename,$debuglevel,$community)
79
#        returns a new object, blessed into the snmpit_mellanox class.
80
81
82
83
84
85
86
87
88
#
sub new($$$;$) {

    # The next two lines are some voodoo taken from perltoot(1)
    my $proto = shift;
    my $class = ref($proto) || $proto;

    my $name = shift;
    my $debugLevel = shift;
89
    my $authstring = shift;  # user:pass[:community] for Mellanox switches.
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

    #
    # Create the actual object
    #
    my $self = {};

    #
    # Set the defaults for this object
    # 
    if (defined($debugLevel)) {
        $self->{DEBUG} = $debugLevel;
    } else {
        $self->{DEBUG} = 0;
    }
    $self->{BLOCK} = 1;
    $self->{CONFIRM} = 1;
    $self->{NAME} = $name;

    # ifIndex mapping store
    $self->{IFINDEX} = {};
110
    $self->{POIFINDEX} = {};
111
112
113
114
115
116
117
118
119
120
121
122
123

    #
    # Get config options from the database
    #
    my $options = getDeviceOptions($self->{NAME});
    if (!$options) {
        warn "ERROR: Getting switch options for $self->{NAME}\n";
        return undef;
    }

    $self->{MIN_VLAN}         = $options->{'min_vlan'};
    $self->{MAX_VLAN}         = $options->{'max_vlan'};

124
125
    if (!$authstring) { # Allow this to over-ride the default
        $authstring = $options->{'snmp_community'};
126
    }
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

    if (!defined($authstring)) {
	warn "ERROR: Auth string must be defined for $self->{NAME}\n";
	return undef;
    }

    # Parse out the various bits we need from the auth string.
    my ($user, $pass, $community) = split(/:/, $authstring);
    if (!defined($user) || !defined($pass)) {
	warn "ERROR: Auth string must contain at least a username and ".
	     "password, separated by ':', for $self->{NAME}\n";
	return undef;
    }
    $self->{USER} = $user;
    $self->{PASS} = $pass;

    # Default the SNMP community string to "public" if not specified.
    $self->{COMMUNITY} = $community || "public";
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
 
    if ($self->{DEBUG}) {
        print "snmpit_mellanox initializing $self->{NAME}, " .
            "debug level $self->{DEBUG}\n" ;   
    }

    #
    # Set up SNMP module variables, and connect to the device
    #
    $SNMP::debugging = ($self->{DEBUG} - 2) if $self->{DEBUG} > 2;

    # Placeholder for SNMP session object
    $self->{SESS} = 0;

    # Placeholder for XML-gateway adapter
    $self->{CLT} = 0;

    # Make it a class object
    bless($self, $class);

    # Create SNMP session
    if (!$self->initSNMPSession()) {
	return undef;
    }

    # Create XML-gateway wrapper instance
    if (!$self->initRPCSession()) {
	return undef; 
    }

    #
    # Sometimes the SNMP session gets created when there is no connectivity
    # to the device so let's try something simple
    #
    my $test_case = $self->get1("sysObjectID", 0);
    if (!defined($test_case)) {
Kirk Webb's avatar
Kirk Webb committed
181
	warn "ERROR: Unable to retrieve via SNMP from $self->{NAME}\n";
182
183
184
185
186
	return undef;
    }

    # Creat ifindex interface map
    if (!$self->readifIndex()) {
Kirk Webb's avatar
Kirk Webb committed
187
	warn "ERROR: Unable to produce ifindex map for $self->{NAME}\n";
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
	return undef;
    }

    return $self;
}

#
# Create an SNMP session object for the switch this instance is wrapping.
#
sub initSNMPSession($) {
    my $self = shift;

    $self->{SESS} = new SNMP::Session(DestHost => $self->{NAME},Version => "2c",
				      Timeout => 4000000, Retries=> 12,
				      Community => $self->{COMMUNITY});

    if (!$self->{SESS}) {
	#
	# Bomb out if the session could not be established
	#
Kirk Webb's avatar
Kirk Webb committed
208
	warn "ERROR: Unable to connect via SNMP to $self->{NAME}\n";
209
210
211
212
213
214
215
216
217
218
219
220
	return 0;
    }

    return 1;
}

#
# Initialize XML-gateway object
#
sub initRPCSession($) {
    my $self = shift;

221
    $self->{CLT} = eval { MLNX_XMLGateway->new("$self->{USER}:$self->{PASS}\@".
222
223
					       "$self->{NAME}") };
    if ($@) {
Kirk Webb's avatar
Kirk Webb committed
224
	warn "ERROR: Unable to create XML-gateway object for ".
225
226
227
228
	     "$self->{NAME}: $@\n";
	return 0;
    }

229
230
231
232
233
    # Enable debugging in gateway wrapper library if set for snmpit.
    if ($self->{DEBUG} > 0) {
	$self->{CLT}->debug($self->{DEBUG});
    }

234
235
236
237
238
239
240
241
242
243
244
245
246
247
    return 1;
}


#
# Read, build, and stash away a mapping from card/port to ifindex.
#
# Pull in the description field for all ports on the switch (indexed
# by ifindex).  It's Incredibly silly that the Mellanox XML-gateway
# doesn't let you get this info directly (well, it does for regular
# ports, but not portchannels).
#
sub readifIndex($) {
    my $self = shift;
Kirk Webb's avatar
Kirk Webb committed
248
249
    my $id = $self->{NAME} . "::readifIndex";
    $self->debug("$id:\n", 2);
250

251
    my ($rows) = snmpitBulkwalkFatal($self->{SESS}, ["ifDescr"]);
252
253

    if (!@$rows) {
Kirk Webb's avatar
Kirk Webb committed
254
255
	warn "$id: ERROR: No interface description rows returned ".
	     "while attempting to build ifindex table.\n";
256
257
258
259
260
	return 0;
    }

    foreach my $rowref (@$rows) {
	my ($name,$ifindex,$descr) = @$rowref;
261
	$self->debug("got $name, $ifindex, description: $descr\n", 3);
262
	if ($name ne "ifDescr") {
Kirk Webb's avatar
Kirk Webb committed
263
	    warn "$id: WARNING: Foreign snmp var returned: $name";
264
265
266
	    return 0;
	}
	# Ethernet ports and port channels are all we care about.
267
268
269
270
271
272
273
	if ($descr =~ /^Eth\d+\/\d+$/) {
	    $self->{IFINDEX}{$descr}   = $ifindex;
	    $self->{IFINDEX}{$ifindex} = $descr;
	} elsif ($descr =~ /^Po\d+$/) {
	    $self->{POIFINDEX}{$descr}   = $ifindex;
	    $self->{POIFINDEX}{$ifindex} = $descr;
	}
274
275
276
277
278
279
280
281
282
283
    }

    # Success
    return 1;
}


#
# XML-gateway call helper.
#
284
# usage: callRPC($self, $callstack)
285
286
287
288
#        return remote method return value on success.
#        return undef and print error on failure.
#
sub callRPC {
289
    my ($self, $callstack) = @_;
290

291
    my $resp = eval { $self->{CLT}->call($callstack) };
292
    if ($@) {
293
	warn "WARNING: XML-gateway call failed to $self->{NAME}: $@\n";
294
	return undef;
295
296
    }

297
    return $resp;
298
299
300
301
}

sub PortInstance2ifindex($$) {
    my ($self, $Port) = @_;
302

303
    return $self->mlnx2ifindex($self->PortInstance2mlnx($Port));
304
305
306
307
}

sub PortInstance2mlnx($$) {
    my ($self, $Port) = @_;
308
309
310
311
312
313
314

    # Ports instances of type "other" are switch portchannels.
    if ($Port->role() eq "other") {
	return $Port->iface();
    } else {
	return "Eth". $Port->card() ."/". $Port->port();
    }
315
316
317
318
}

sub ifindex2PortInstance($$) {
    my ($self, $ifindex) = @_;
319
320
321
322
323

    if (exists($self->{IFINDEX}{$ifindex})) {
	$self->{IFINDEX}{$ifindex} =~ /^Eth(\d+)\/(\d+)$/;
	return Port->LookupByStringForced(
	    Port->Tokens2TripleString($self->{NAME}, $1, $2));
324
    } elsif (exists($self->{POIFINDEX}{$ifindex})) {
325
326
327
	return Port->LookupByStringForced(
	    Port->Tokens2IfaceString($self->{NAME}, 
				     $self->{POIFINDEX}{$ifindex}));
328
329
330
331
    }

    warn "WARNING: No such port on $self->{NAME} with ifindex: $ifindex\n";
    return undef;
332
333
334
335
}

sub ifindex2mlnx($$) {
    my ($self, $ifindex) = @_;
336
337
338

    if (exists($self->{IFINDEX}{$ifindex})) {
	return $self->{IFINDEX}{$ifindex};
339
340
    } elsif (exists($self->{POIFINDEX}{$ifindex})) {
	return $self->{POIFINDEX}{$ifindex};
341
342
    }

343
    warn "WARNING: No such port on $self->{NAME} with ident: $ifindex\n";
344
    return undef;
345
346
347
348
}

sub mlnx2PortInstance($$) {
    my ($self, $mlnx) = @_;
349
350
    
    return $self->ifindex2PortInstance($self->mlnx2ifindex($mlnx));
351
352
353
354
}

sub mlnx2ifindex($$) {
    my ($self, $mlnx) = @_;
355

356
357
358
    # The IFINDEX hash contains forward and reverse entries, so just
    # call the reverse function.
    return $self->ifindex2mlnx($mlnx);
359
360
361
362
363
364
365
366
367
368
}

#
# Converting port formats.
#
sub convertPortFormat($$@) {
    my $self = shift;
    my $output = shift;
    my @ports = @_;

Kirk Webb's avatar
Kirk Webb committed
369
370
    my $id = $self->{NAME} . "::convertPortFormat";

371
372
373
374
375
376
377
378
379
380
381
382
    #
    # Avoid warnings by exiting if no ports given
    # 
    if (!@ports) {
	return ();
    }

    #
    # We determine the type by sampling the first port given
    #
    my $sample = $ports[0];
    if (!defined($sample)) {
Kirk Webb's avatar
Kirk Webb committed
383
	warn "$id: Given a bad list of ports\n";
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
	return undef;
    }

    my $input = undef;
    if (Port->isPort($sample)) {
	$input = $PORT_FORMAT_PORT;
    } elsif ($sample =~ /^Eth/ || $sample =~ /^Po/) {
	$input = $PORT_FORMAT_MLNX;
    } else {
	$input = $PORT_FORMAT_IFINDEX;
    }
    
    #
    # It's possible the ports are already in the right format
    #
    if ($input == $output) {
	return @ports;
    }

    if ($input == $PORT_FORMAT_PORT) {
	my @swports = map $_->getEndByNode($self->{NAME}), @ports;

	if ($output == $PORT_FORMAT_IFINDEX) {
	    my @ifports = map $self->PortInstance2ifindex($_), @swports;
	    return @ifports;
	} else {
	    my @mlnxports = map $self->PortInstance2mlnx($_), @swports;
	    return @mlnxports;
	}
    } elsif ($input == $PORT_FORMAT_IFINDEX) {
	if ($output == $PORT_FORMAT_PORT) {
	    my @swports = map $self->ifindex2PortInstance($_), @ports;
	    return @swports;
	} else { # output is PORT_FORMAT_MLNX
	    my @mlnxports = map $self->ifindex2mlnx($_), @ports;
	    return @mlnxports;
	}
	
    } else { # input is $PORT_FORMAT_MLNX
	if ($output == $PORT_FORMAT_IFINDEX) {
	    my @ifports = map $self->mlnx2ifindex($_), @ports;
	    return @ifports;
	} else { # output is PORT_FORMAT_PORT
	    my @swports = map $self->mlnx2PortInstance($_), @ports;
	    return @swports
	}	
    }

    #
    # Some combination we don't know how to handle
    #
Kirk Webb's avatar
Kirk Webb committed
435
    warn "$id: Bad input/output combination ($input/$output)\n";
436
437
438
    return undef;    
}

439
# SNMP helpers imported from the beyond.
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502

sub hammer($$$;$) {
    my ($self, $closure, $id, $retries) = @_;

    if (!defined($retries)) { $retries = 12; }
    for my $i (1 .. $retries) {
	my $result = $closure->();
	if (defined($result) || ($retries == 1)) { return $result; }
	warn $id . " ... will try again\n";
	sleep 1;
    }
    warn  $id . " .. giving up\n";
    return undef;
}

# SNMP shorthand

sub get1($$$) {
    my ($self, $obj, $instance) = @_;
    my $id = $self->{NAME} . "::get1($obj.$instance)";
    my $closure = sub () {
	my $RetVal = snmpitGet($self->{SESS}, [$obj, $instance], 1);
	if (!defined($RetVal)) { sleep 4;}
	return $RetVal;
    };
    my $RetVal = $self->hammer($closure, $id, 40);
    if (!defined($RetVal)) {
	warn "$id failed - $snmpit_lib::snmpitErrorString\n";
    }

    #
    # Accoding to my testing on Extreme switch, no-instance will
    # still return a string "NOSUCHINSTANCE";
    #
    if ($SNMP_NO_INSTANCE eq $RetVal) {
	return undef;
    }
    return $RetVal;
}


sub set($$;$$) {
    my ($self, $varbind, $id, $retries) = @_;
    if (!defined($id)) { $id = $self->{NAME} . ":set "; }
    if (!defined($retries)) { $retries = 2; }
    my $sess = $self->{SESS};
    my $closure = sub () {
	my $RetVal = $sess->set($varbind);
	my $status = $RetVal;
	if (!defined($RetVal)) {
	    $status = "(undefined)";
	    if ($sess->{ErrorNum}) {
		my $bad = "$id had error number " . $sess->{ErrorNum} .
			  " and had error string " . $sess->{ErrorStr} . "\n";
		print $bad;
	    }
	}
	return $RetVal;
    };
    my $RetVal = $self->hammer($closure, $id, $retries);
    return $RetVal;
}

503
504
505
506
507
508
509
510
#
# Helper function.  Grab all vlans tag numbers on the switch.
#
sub getAllVlanNumbers($) {
    my ($self,) = @_;

    my $resp = $self->callRPC(["get", "$MLNX_VLAN_PREFIX/*"]);
    return () if !defined($resp);
Kirk Webb's avatar
Kirk Webb committed
511

512
513
514
515
516
    my @vlnums = ();
    foreach my $rv (@$resp) {
	push @vlnums, $rv->[2];
    }

517
518
    $self->debug("Found VLANS: @vlnums\n", 2);
    
519
520
521
522
523
524
525
526
527
528
529
530
    return @vlnums;
}

#
# Helper function.  Extract information on all ports and return this
# to the caller.  This function expects a reference to an array of
# ifindex (integer) numbers, or the string "ALL".
#
sub getPortState($$) {
    my ($self, $ports) = @_;

    my %ret = ();
Kirk Webb's avatar
Kirk Webb committed
531
    my $id = $self->{NAME} . "::getPortState";
532
533
534
535

    if (ref($ports) eq "ARRAY") {
	# nothing to do - just a valid case.
    } elsif ($ports eq "ALL") {
536
	$self->debug("$id: state for all ports requested.\n");
537
	$ports = [];
538
539
	@{$ports} = grep {/^\d+$/} ((keys %{$self->{IFINDEX}}), 
				    (keys %{$self->{POIFINDEX}}));
540
    } else {
Kirk Webb's avatar
Kirk Webb committed
541
	warn "$id: WARNING: Invalid argument\n";
542
543
544
	return undef;
    }

545
546
    $self->debug("$id: getting state for ifindexes: @{$ports}\n",2);

547
548
549
550
551
552
553
554
555
556
    my @getcmds = ();
    foreach my $ifindex (@{$ports}) {
	push @getcmds, ["get", "$MLNX_IFC_PREFIX/$ifindex/vlans/pvid"];
	push @getcmds, ["get", "$MLNX_IFC_PREFIX/$ifindex/vlans/allowed/*"];
	push @getcmds, ["get", "$MLNX_IFC_PREFIX/$ifindex/vlans/mode"];
	push @getcmds, ["get", "$MLNX_IFC_PREFIX/$ifindex/enabled"];
    }

    my $resp = $self->callRPC(\@getcmds);
    if (!defined($resp)) {
Kirk Webb's avatar
Kirk Webb committed
557
558
	warn "$id: WARNING: Failed to obtain information for requested ".
	     "ports.\n";
559
560
561
562
563
564
565
	return undef;
    }

    foreach my $rv (@$resp) {
	my ($path, $type, $val) = @$rv;
        RETPATH: for ($path) {
	    my $ifindex;
566
	    /^$MLNX_IFC_PREFIX\/(\d+)\// && do {
567
		$ifindex = $1;
568
569
570
571
572
573
574
		if (!exists($ret{$ifindex})) {
		    $ret{$ifindex} = {};
		    $ret{$ifindex}{PVID} = 0;
		    $ret{$ifindex}{ALLOWED} = {};
		    $ret{$ifindex}{MODE} = "*UNKNOWN*";
		    $ret{$ifindex}{ENABLED} = "*UNKNOWN*";
		}
575
576
577
578
		# fall through to next tests.
	    };
	    goto DEFCASE unless defined($ifindex);

579
	    /vlans\/pvid$/ && do {
580
581
582
583
		$ret{$ifindex}{PVID} = $val;
		last RETPATH;
	    };

584
	    /vlans\/allowed\/\d+$/ && do {
585
586
587
588
		$ret{$ifindex}{ALLOWED}{$val} = 1;
		last RETPATH;
	    };

589
	    /vlans\/mode$/ && do {
590
591
592
593
		$ret{$ifindex}{MODE} = $val;
		last RETPATH;
	    };

594
	    /enabled$/ && do {
595
596
597
598
599
		$ret{$ifindex}{ENABLED} = $val;
		last RETPATH;
	    };

	    DEFCASE:
Kirk Webb's avatar
Kirk Webb committed
600
	    warn "$id: WARNING: Unexpected path found in response.";
601
602
603
604
605
606
	}
    }

    return \%ret;
}

607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629

############# Standard snmpit driver interface APIs:################

#
# Set a variable associated with a port. The commands to execute are given
# in the cmdOIs hash above
#
# usage: portControl($self, $command, @ports)
#	 returns 0 on success.
#	 returns number of failed ports on failure.
#	 returns -1 if the operation is unsupported
#
# Note:  The Mellanox XML-gateway doesn't (yet) support setting the
#        interface speed.  We just lie to the caller that the change
#        went through and hope that the database only lists the speed
#        actually set on the ports.  ALso, duplex is meaningless in
#        the post-FE world, so is ignored.
# 
sub portControl ($$@) {
    my $self = shift;
    my $cmd = shift;
    my @ports = @_;

Kirk Webb's avatar
Kirk Webb committed
630
    my $id = $self->{NAME} . "::portControl";
631
632
633
634
635
636
637
638
639
640
    my $errors = 0;
    
    $self->debug("portControl: $cmd -> (".Port->toStrings(@ports).")\n");

    my @ifports = $self->convertPortFormat($PORT_FORMAT_IFINDEX, @ports);

    # The Mellanox XML-gateway API doesn't support setting speed at
    # all, so we just pretend ...
    my %fakeCmds = (
	'auto'      => 1,
641
        '1000mbit'  => 1,
642
	'10000mbit' => 1,
643
	'40000mbit' => 1,
644
645
646
	'full'      => 1,
	);

647
648
    if (defined $cmdPaths{$cmd}) {
	my $path = $cmdPaths{$cmd};
649
	foreach my $ifport (@ifports) {
Kirk Webb's avatar
Kirk Webb committed
650
	    my $cmd = [$path->[0], "$MLNX_IFC_PREFIX/$ifport/$path->[1]", 
651
652
		       $path->[2]];
	    my $retval = $self->callRPC($cmd);
653
	    if (!defined($retval)) {
Kirk Webb's avatar
Kirk Webb committed
654
		warn "$id: WARNING: Failed to execute $cmd on $ifport.\n";
655
656
657
		$errors++;
	    }
	}
658
    } elsif (!defined $fakeCmds{$cmd}) {
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
	#
	# Command not supported, not even a fake command.
	#
	$self->debug("Unsupported port control command '$cmd' ignored.\n");
    }

    return $errors;
}

# 
# Check to see if the given 802.1Q VLAN tag exists on the switch
#
# usage: vlanNumberExists($self, $vlan_number)
#        returns 1 if the VLAN exists, 0 otherwise
#
sub vlanNumberExists($$) {
    my ($self, $vlan_number) = @_;
    my $id = $self->{NAME}."::vlanNumberExists($vlan_number)";

    $self->debug($id."\n");

680
681
    foreach my $vltag ($self->getAllVlanNumbers()) {
	return 1 if $vltag == $vlan_number;
682
683
    }

684
    $self->debug($id." VLAN #$vlan_number does not exist.\n");
685
686
687
688
689
    return 0;
}

#
# Given VLAN indentifiers from the database, finds the 802.1Q VLAN
690
# number for them. If no VLAN id is given, returns mappings for the entire
691
692
693
694
695
696
697
698
699
700
701
702
# switch.
# 
# usage: findVlans($self, @vlan_ids)
#        returns a hash mapping VLAN ids to 802.1Q VLAN numbers
#        any VLANs not found have NULL VLAN numbers
#
sub findVlans($@) {
    my $self = shift;
    my @vlan_ids = @_;
    my $id = $self->{NAME} . "::findVlans";
    $self->debug("$id\n");

703
    my %all = ();
704
    my %mps = ();
705

706
    my @getcmds = ();
707
    foreach my $vlnum ($self->getAllVlanNumbers()) {
708
709
710
711
712
713
714
715
716
	push @getcmds, ["get", "$MLNX_VLAN_PREFIX/$vlnum/name"];
    }

    my $resp = $self->callRPC(\@getcmds);
    if (defined($resp) && @$resp) {
	foreach my $rv (@$resp) {
	    $rv->[0] =~ qr|^$MLNX_VLAN_PREFIX/(\d+)/|;
	    my $vlnum = $1;
	    my $vlid = $rv->[2] ? $rv->[2] : "unnamed-$vlnum";
717
718
719
720
721
722
723
724
725
726
727
	    $self->debug("$id: Adding $vlid => $vlnum\n",2);
	    $all{$vlid} = $vlnum;
	}
    }

    # Filter through looking for those vlans that we care about.
    #
    # XXX: if the return value is not defined (indicating an error
    # talking to the switch), should we return undef, or just a null
    # mapping?  I'm thinking the former...
    foreach my $vlid (@vlan_ids) {
728
729
730
	if ($vlid eq "default") {
	    $mps{$vlid} = $MLNX_DEF_VLAN;
	} elsif (exists($all{$vlid})) {
731
	    $mps{$vlid} = $all{$vlid};
732
	} else {
733
	    $mps{$vlid} = undef;
734
735
	}
    }
736
737
738
739
740
741

    # Did caller ask for info on all vlans?
    if (!@vlan_ids) {
	%mps = %all;
    }

Kirk Webb's avatar
Kirk Webb committed
742
    $self->debug("$id RPC results: " . Dumper(\%mps), 2);
743
    return %mps;
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
}

#
# Given a VLAN identifier from the database, find the 802.1Q VLAN
# number that is assigned to that VLAN. Retries several times (to account
# for propagation delays) unless the $no_retry option is given.
#
# usage: findVlan($self, $vlan_id,$no_retry)
#        returns the VLAN number for the given vlan_id if it exists
#        returns undef if the VLAN id is not found
#
sub findVlan($$;$) {
    my $self = shift;
    my $vlan_id = shift;
    my $no_retry = shift; # ignored here
Kirk Webb's avatar
Kirk Webb committed
759
    my $id = $self->{NAME} . "::findVlan";
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782

    $self->debug("$id ( $vlan_id )\n",2);

    my %mps = $self->findVlans($vlan_id);
    if (exists($mps{$vlan_id})) {
	return $mps{$vlan_id};
    }
    
    return undef;
}

#   
# Create a VLAN on this switch, with the given identifier (which comes from
# the database) and given 802.1Q tag number.
#
# usage: createVlan($self, $vlan_id, $vlan_number)
#        returns the new VLAN number on success
#        returns 0 on failure
#
sub createVlan($$$) {
    my $self = shift;
    my $vlan_id = shift;
    my $vlan_number = shift;
Kirk Webb's avatar
Kirk Webb committed
783
    my $id = $self->{NAME} . "::createVlan";
784
785

    if (!defined($vlan_number)) {
Kirk Webb's avatar
Kirk Webb committed
786
	warn "$id: WARNING: called without supplying vlan_number";
787
788
	return 0;
    }
789
790

    $self->lock();
791
792
793
    my $check_number = $self->findVlan($vlan_id,1);
    if (defined($check_number)) {
	if ($check_number != $vlan_number) {
Kirk Webb's avatar
Kirk Webb committed
794
	    warn "$id: WARNING: Not creating $vlan_id because it already ".
795
	         "exists with name $check_number\n";
796
	    $self->unlock();
797
798
799
800
801
802
803
804
805
            return 0;
	}
    }
    
    $self->debug("createVlan: name $vlan_id number $vlan_number \n");

    print "  Creating VLAN $vlan_id as VLAN #$vlan_number on " .
	"$self->{NAME} ...\n";

806
    my $crcmd = ["action", "$MLNX_VLAN_PREFIX/add", {vlan_id => $vlan_number}];
807
    my $nmcmd = ["set-modify","$MLNX_VLAN_PREFIX/$vlan_number/name=$vlan_id"];
808
    my $resp = $self->callRPC([$crcmd, $nmcmd]);
809
810
    $self->unlock();
    
811
    if (!defined($resp)) {
Kirk Webb's avatar
Kirk Webb committed
812
	warn "$id: WARNING: Creating VLAN $vlan_id as VLAN #$vlan_number on ".
813
814
	     "$self->{NAME} failed.\n";
	# XXX: Why shouldn't this be a hard failure?
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
    }
    
    return $vlan_number;
}

#
# Put the given ports in the given VLAN. The VLAN is given as an 802.1Q 
# tag number.
############################################################
# Semantics:
#
#   Case mode(port):
#      'free' or 'in default untagged':
#          add port to vlan_number untagged.
#      'in use(not in default) untagged':
#          add port to vlan_number untagged.
#      'in use(not in default) all tagged':
#          add port to vlan_number tagged.
#      'in use(may in default) native tagged':
#          add port to vlan_number tagged;
#          if native_vlan == default:
#              remove native_vlan
#
838
# Mellanox 'free': switchportMode='access' AND vlan tag = 1
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
#
############################################################
# usage: setPortVlan($self, $vlan_number, @ports)
#	 returns 0 on sucess.
#	 returns the number of failed ports on failure.
#
sub setPortVlan($$@) {
    my $self = shift;
    my $vlan_number = shift;
    my @ports = @_;
    my $errors = 0;

    my $id = $self->{NAME} . "::setPortVlan($vlan_number)";
    $self->debug($id."\n");

Kirk Webb's avatar
Kirk Webb committed
854
    # Vlan must exist before being used in this function.
855
    if (!$self->vlanNumberExists($vlan_number)) {
Kirk Webb's avatar
Kirk Webb committed
856
	warn "$id: WARNING: VLAN $vlan_number does not exist.\n";
857
858
859
	return 1;
    }

Kirk Webb's avatar
Kirk Webb committed
860
    # Anything to do?
861
862
    return 0 unless(@ports);

863
    my @swports = $self->convertPortFormat($PORT_FORMAT_IFINDEX, @ports);
864

Kirk Webb's avatar
Kirk Webb committed
865
    $self->lock();
866
867
    my $pstates = $self->getPortState(\@swports);
    if (!defined($pstates)) {
Kirk Webb's avatar
Kirk Webb committed
868
869
	warn "$id: WARNING: Failed to get port states.\n";
	$self->unlock();
870
871
872
873
	return scalar(@ports);
    }

    my @setcmds = ();
Kirk Webb's avatar
Kirk Webb committed
874
875
    # Figure out what to do based on the mode each port is in.  Queue up the
    # right command(s).
876
    foreach my $ifindex (@swports) {
877
        MODESW: for ($pstates->{$ifindex}{MODE}) {
878
	    /^access$/ && do {
879
880
		push @setcmds, ["set-modify", "$MLNX_IFC_PREFIX/$ifindex/vlans/pvid=$vlan_number"]
		    if $pstates->{$ifindex}{PVID} != $vlan_number;
881
882
883
884
		last MODESW;
	    };

	    /^trunk$/ && do {
Kirk Webb's avatar
Kirk Webb committed
885
886
		# Only attempt to add the vlan if it isn't already in the
		# port's allowed list.
887
888
		if (!exists($pstates->{$ifindex}{ALLOWED}{$vlan_number})) {
		    push @setcmds, ["action", "$MLNX_IFC_PREFIX/$ifindex/vlans/allowed/add", {vlan_ids => $vlan_number}];
889
		    $pstates->{$ifindex}{ALLOWED}{$vlan_number} = 1;
Kirk Webb's avatar
Kirk Webb committed
890
		    # Remove the default vlan sentinel if it's present.
891
892
		    if ($vlan_number != $MLNX_DEF_VLAN &&
			exists($pstates->{$ifindex}{ALLOWED}{$MLNX_DEF_VLAN})) {
Kirk Webb's avatar
Kirk Webb committed
893
			push @setcmds, ["action", "$MLNX_IFC_PREFIX/$ifindex/vlans/allowed/delete", {vlan_ids => $MLNX_DEF_VLAN}];
894
			delete $pstates->{$ifindex}{ALLOWED}{$MLNX_DEF_VLAN};
Kirk Webb's avatar
Kirk Webb committed
895
		    }
896
897
898
		}
		last MODESW;
	    };
899

900
	    /^hybrid$/ && do {
Kirk Webb's avatar
Kirk Webb committed
901
902
		# Only add the vlan to the port's list if it isn't already
		# there, or if it isn't the native vlan.
903
904
		if (!exists($pstates->{$ifindex}{ALLOWED}{$vlan_number})
		    && $pstates->{$ifindex}{PVID} != $vlan_number) {
905
		    push @setcmds, ["action", "$MLNX_IFC_PREFIX/$ifindex/vlans/allowed/add", {vlan_ids => $vlan_number}];
906
		    $pstates->{$ifindex}{ALLOWED}{$vlan_number} = 1;
907
		}
908
		last MODESW;
909
910
911
	    };

	    # default case
Kirk Webb's avatar
Kirk Webb committed
912
	    warn "$id: WARNING: Unknown port mode for ifindex $ifindex: $_\n";
913
914
	    $errors++;
	}
915
916
917
918
919
	  
	# enable/disable ports: if the vlan is 'default', then disable
	# ports (which means deleting the ports from some vlan).
	my $truth = $vlan_number eq $MLNX_DEF_VLAN ? "false" : "true";
	push @setcmds, ["set-modify", "$MLNX_IFC_PREFIX/$ifindex/enabled=$truth"];
920
921
    }

922
923
924
925
926
    if (@setcmds) {
	my $resp = $self->callRPC(\@setcmds);
	if (!defined($resp)) {
	    $errors = scalar(@ports);
	}
927
928
    }

929
    $self->unlock();
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
    return $errors;
}


#
# Remove the given ports from the given VLAN. The VLAN is given as an 802.1Q 
# tag number.
#
# usage: delPortVlan($self, $vlan_number, @ports)
#	 returns 0 on sucess.
#	 returns the number of failed ports on failure.
#
sub delPortVlan($$@) {
    my $self = shift;
    my $vlan_number = shift;
    my @ports = @_;
    my $errors = 0;

    my $id = $self->{NAME}."::delPortVlan($vlan_number)";
    $self->debug($id."\n");

951
    if (!$self->vlanNumberExists($vlan_number)) {
Kirk Webb's avatar
Kirk Webb committed
952
	warn "$id: WARNING: VLAN $vlan_number does not exist.\n";
953
954
955
956
957
	return 1;
    }
    
    return 0 unless(@ports);

958
959
    $self->debug("$id: incoming ports list: @ports\n",2);

960
    my @swports = $self->convertPortFormat($PORT_FORMAT_IFINDEX, @ports);
961

Kirk Webb's avatar
Kirk Webb committed
962
    $self->lock();
963
964
    my $pstates = $self->getPortState(\@swports);
    if (!defined($pstates)) {
Kirk Webb's avatar
Kirk Webb committed
965
966
	warn "$id: WARNING: Failed to get port states.\n";
	$self->unlock();
967
968
969
970
971
	return scalar(@ports);
    }

    my @setcmds = ();
    foreach my $ifindex (@swports) {
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
	my %ifc = %{$pstates->{$ifindex}};

	# Figuring out what to do with the port is something of a
	# nasty business that depends on what mode it's in, and how
	# the given vlan is affiliated with it.
        MODESW: for ($ifc{MODE}) {

	    /^access$/ && do {
		# Disable the port if the access vlan matches.
		if ($ifc{PVID} == $vlan_number) {
		    push @setcmds, ["set-modify", "$MLNX_IFC_PREFIX/$ifindex/vlans/pvid=$MLNX_DEF_VLAN"];
		    push @setcmds, ["set-modify", "$MLNX_IFC_PREFIX/$ifindex/enabled=false"];
		}
		last MODESW;
	    };

	    /^trunk$/ && do {
		# If the vlan is in the allowed list for this trunk
		# link, then remove it from the list.
		if (exists($ifc{ALLOWED}{$vlan_number})) {
Kirk Webb's avatar
Kirk Webb committed
992
		    if (keys(%{$ifc{ALLOWED}}) == 1) {
993
994
995
996
			# If we are removing the last vlan in the
			# list, then emit a warning and add the
			# default vlan since a port in "trunk" mode
			# must be a member of at least one vlan.
997
			push @setcmds, ["action", "$MLNX_IFC_PREFIX/$ifindex/vlans/allowed/add", { vlan_ids => $MLNX_DEF_VLAN }];
998
			$ifc{ALLOWED}{$MLNX_DEF_VLAN} = 1;
Kirk Webb's avatar
Kirk Webb committed
999
1000
			warn "$id: WARNING: Removing last vlan from an ".
			     "equal-mode trunk's allowed list ".
1001
1002
1003
			     "(ifindex: $ifindex).\n";
		    }
		    push @setcmds, ["action", "$MLNX_IFC_PREFIX/$ifindex/vlans/allowed/delete", {vlan_ids => $vlan_number}];
1004
		    delete $ifc{ALLOWED}{$vlan_number};
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
		}
		last MODESW;
	    };

	    /^hybrid$/ && do {
		# Zap the untagged access vlan back to the default if
		# the given vlan is the same as what is currently set
		# on the port.  Emit a warning when doing this.
		if ($ifc{PVID} == $vlan_number) {
		    push @setcmds, ["set-modify", "$MLNX_IFC_PREFIX/$ifindex/vlans/pvid=$MLNX_DEF_VLAN"];
Kirk Webb's avatar
Kirk Webb committed
1015
		    warn "$id: WARNING: native vlan removal requested on ".
1016
1017
1018
1019
1020
1021
1022
1023
1024
			 "dual-mode trunk port on $self->{NAME} ".
			 "(ifindex: $ifindex).\n";
		}
		# Remove the vlan from the allowed list if it's there.
		# Note that the native vlan CANNOT show up in the
		# allowed list on a Mellanox switch (thus the elsif here).
		# When in dual/hybrid mode, the "allow" list CAN be empty.
		elsif (exists($ifc{ALLOWED}{$vlan_number})) {
		    push @setcmds, ["action", "$MLNX_IFC_PREFIX/$ifindex/vlans/allowed/delete", {vlan_ids => $vlan_number}];
1025
		    delete $ifc{ALLOWED}{$vlan_number};
1026
1027
1028
1029
		}
		last MODESW;
	    };

1030
1031
1032
1033
1034
	    # default - if we get here, it's probably because the port is
	    # a member of a portchannel.  Querying the mode seems to not
	    # return anything in this case.  We don't want to do anything
	    # directly to these ports in any case.
	    $self->debug("$id: Unknown port mode ($_) for ifindex $ifindex.\n");
1035
1036
1037
	}
    }

1038
1039
1040
1041
1042
    if (@setcmds) {
	my $resp = $self->callRPC(\@setcmds);
	if (!defined($resp)) {
	    $errors = scalar(@ports);
	}
1043
1044
    }

1045
    $self->unlock();
1046
1047
1048
1049
    return $errors;
}

#
1050
# Removes all ports from the given VLANs. Each VLAN is given as a VLAN
1051
1052
1053
# 802.1Q tag value.
#
# usage: removePortsFromVlan(self,@vlan)
1054
#	 returns 0 on success.
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
#	 returns the number of failed ports on failure.
#
sub removePortsFromVlan($@) {
    my $self = shift;
    my @vlan_numbers = @_;
    my $errors = 0;
    my $id = $self->{NAME} . "::removePortsFromVlan";

    $self->debug($id."\n");
    
1065
1066
1067
    return 0 unless(@vlan_numbers);

    # Just the ifindexes ma'am (filter out the reverse mappings).
1068
    my @allports = grep {/^\d+$/} keys %{$self->{IFINDEX}};
1069

1070
    foreach my $vlan_number (@vlan_numbers) {
1071
	$errors += $self->delPortVlan($vlan_number, @allports);
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
    }

    return $errors;
}

#
# Removes and disables some ports in a given VLAN.
# The VLAN is given as a VLAN 802.1Q tag value.
#
# Semantics:
#     Case:
#         untagged:
#                       move to default VLAN, put port down
#         alltagged:
#                       untag port
#         nativetagged:
#              remove native vlan:
#                       clear native
#              nonative vlan:
#                       untag               
#
# usage: removeSomePortsFromVlan(self,vlan,@ports)
#	 returns 0 on sucess.
#	 returns the number of failed ports on failure.
#
1097
1098
# XXX: why does this function exist?  It seems to have the exact same
#      semantics as delPortVlan(). In fact, that's how it's implemented here.
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
sub removeSomePortsFromVlan($$@) {
    my ($self, $vlan_number, @ports) = @_;
    my $id = $self->{NAME} . "::removeSomePortsFromVlan";

    $self->debug($id."\n");
    return $self->delPortVlan($vlan_number, @ports);
}

#
# Remove the given VLANs from this switch. Removes all ports from the VLAN,
# The VLAN is given as a VLAN identifier from the database.
#
# usage: removeVlan(self,int vlan)
#	 returns 1 on success
#	 returns 0 on failure
#
sub removeVlan($@) {
    my $self = shift;
    my @vlan_numbers = @_;
    my $errors = 0;
    my $id = $self->{NAME} . "::removeVlan";
    
Kirk Webb's avatar
Kirk Webb committed
1121
1122
1123
1124
1125
    $self->removePortsFromVlan(@vlan_numbers);

    my @setcmds = ();
    $self->lock();
    my @curvlans = $self->getAllVlanNumbers();
1126
    foreach my $vlan_number (@vlan_numbers) {
Kirk Webb's avatar
Kirk Webb committed
1127
1128
1129
1130
1131
1132
1133
	push @setcmds, ["action", "$MLNX_VLAN_PREFIX/delete", {vlan_id => $vlan_number}]
	    if grep(/^$vlan_number$/, @curvlans);
    }

    my $resp = $self->callRPC(\@setcmds);
    if (!defined($resp)) {
	warn "$id: failed on $self->{NAME}.\n";
1134
	$errors = scalar(@vlan_numbers);
1135
    }
Kirk Webb's avatar
Kirk Webb committed
1136

1137
    $self->unlock();
1138
    return $errors ? 0 : 1;
1139
1140
1141
}

#
1142
1143
1144
# Not something we need to support with Mellanox switches.  Only port
# enable and disable are supported, and both can be done inside
# portControl().
1145
1146
#
sub UpdateField($$$@) {
Kirk Webb's avatar
Kirk Webb committed
1147
    warn "WARNING: snmpit_mellanox does not support UpdateField().\n";
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
    return 0;
}

#
# Determine if a VLAN has any members 
# (Used by stack->switchesWithPortsInVlan())
#
sub vlanHasPorts($$) {
    my ($self, $vlan_number) = @_;
    my $id = $self->{NAME}."::vlanHasPorts($vlan_number)";

1159
1160
    my $pstates = $self->getPortState("ALL");
    if (!defined($pstates)) {
Kirk Webb's avatar
Kirk Webb committed
1161
	warn "$id: WARNING: Failed to get port states.\n";
1162
1163
1164
1165
1166
1167
1168
1169
	return 0;
    }

    foreach my $ifindex (keys %$pstates) {
	if ($pstates->{$ifindex}{PVID} == $vlan_number ||
	    exists($pstates->{$ifindex}{ALLOWED}{$vlan_number})) {
	    return 1;
	}
1170
1171
1172
    }
    
    return 0;
1173
}
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187

#
# List all VLANs on the device
#
# usage: listVlans($self)
# see snmpit_cisco_stack.pm for a description of return value format
#
sub listVlans($) {
    my $self = shift;
    my $id = $self->{NAME} . "::listVlans()";
    my @list = ();

    $self->debug($id,1);

1188
1189
    my $pstates = $self->getPortState("ALL");
    if (!defined($pstates)) {
Kirk Webb's avatar
Kirk Webb committed
1190
	warn "$id: WARNING: Failed to get port states.\n";
1191
1192
1193
	return undef;
    }

1194
    my @vlcmds = ();
1195
    foreach my $vlan ($self->getAllVlanNumbers()) {
1196
1197
1198
1199
1200
	push @vlcmds, ["get","$MLNX_VLAN_PREFIX/$vlan/name"];
    }
    
    my $resp = $self->callRPC(\@vlcmds);
    if (!defined($resp) || !@$resp) {
Kirk Webb's avatar
Kirk Webb committed
1201
	warn "$id: WARNING: Unable to get vlan names.\n";
1202
1203
1204
1205
1206
	return undef;
    }

    foreach my $rv (@$resp) {
	my ($path, $type, $vlname) = @{$rv};
Kirk Webb's avatar
Kirk Webb committed
1207
	$path =~ qr|^$MLNX_VLAN_PREFIX/(\d+)/|;
1208
	my $vlnum = $1;
1209
1210
	my @vlifindexes = ();
	foreach my $ifindex (keys %$pstates) {
1211
1212
	    if ($pstates->{$ifindex}{PVID} == $vlnum ||
		exists($pstates->{$ifindex}{ALLOWED}{$vlnum})) {
1213
1214
		push @vlifindexes, $ifindex;
	    }
1215
	}
1216
1217
	my @ports = map {$_->getOtherEndPort()} 
	            $self->convertPortFormat($PORT_FORMAT_PORT, @vlifindexes);
1218
	push @list, [$vlname, $vlnum, \@ports];
1219
    }
1220
1221

    $self->debug("vlan list:\n".Dumper(\@list), 2);
1222
1223
1224
1225
1226
1227
    return @list;
}

#
# List all ports on the device
#
1228
# For Mellanox switches: All ports are always full duplex.
1229
1230
1231
1232
1233
1234
1235
1236
1237
#
# usage: listPorts($self)
# see snmpit_cisco_stack.pm for a description of return value format
#
sub listPorts($) {
    my $self = shift;
    
    my @rv = ();
    my %Able = ();
1238
    my %NodePorts = ();
1239
1240
1241
1242

    my $id = $self->{NAME}."::listPorts";

    my ($varname, $modport, $ifIndex, $portIndex, $status, $portname);
1243
1244
1245
1246
1247

    for (my $ifTable = [$PORT_ADMIN_STATUS, 0]; 
	 $self->{SESS}->getnext($ifTable);
	 $varname =~ /^$PORT_ADMIN_STATUS/) 
    {
1248
1249
	($varname,$ifIndex,$status) = @{$ifTable};

1250
1251
1252
1253
	# Skip non-Ethernet ports.
	next unless exists($self->{IFINDEX}{$ifIndex})
	    && $self->{IFINDEX}{$ifIndex} =~ /^Eth/;			   

1254
	# Make sure this port is wired up and connecting to a node.
1255
1256
	my ($port) = $self->convertPortFormat($PORT_FORMAT_PORT, $ifIndex);
	if (defined($port) && defined($port->getOtherEndPort())) {
1257
1258
	    $self->debug("$varname $ifIndex $status\n");
	    if ($varname =~ /$PORT_ADMIN_STATUS/) { 
1259
		$Able{$ifIndex} = ($status =~ /up/ || "$status" eq $STATUS_UP)  ? "yes" : "no";
1260
		$NodePorts{$ifIndex} = $port->getOtherEndPort();
1261
	    }
1262
	}
1263
    }
1264
1265

    foreach $ifIndex (keys %Able) {
1266
	my ($link, $speed);
1267
1268
	$status = $self->get1($PORT_OPER_STATUS, $ifIndex);
	if (defined($status)) {
1269
	    $link = ($status =~ /up/ || "$status" eq $STATUS_UP) ? "yes" : "no";
1270
1271
1272
1273
	}

	$status = $self->get1($PORT_SPEED, $ifIndex);
	if (defined($status)) {
1274
	    $speed = "$status"."Mbps";
1275
	}
1276
1277

	push @rv, [$NodePorts{$ifIndex}, $Able{$ifIndex},
1278
		   $link, $speed, "full"];
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
    }

    return @rv;
}

# 
# Get statistics for ports on the switch
#
# usage: getStats($self)
# see snmpit_cisco_stack.pm for a description of return value format
#
sub getStats() {
    my $self = shift;

    #
    # Walk the tree for the VLAN members
    #
    my $vars = new SNMP::VarList(['ifInOctets'],['ifInUcastPkts'],
				 ['ifInNUcastPkts'],['ifInDiscards'],
				 ['ifInErrors'],['ifInUnknownProtos'],
				 ['ifOutOctets'],['ifOutUcastPkts'],
				 ['ifOutNUcastPkts'],['ifOutDiscards'],
				 ['ifOutErrors'],['ifOutQLen']);
    my @stats = $self->{SESS}->bulkwalk(0,32,$vars);

    my %allports = ();
    
    #
    # We need to flip the two-dimentional array we got from bulkwalk on
    # its side, and convert ifindexes into Port instance
    #
    my $i = 0;
    my %stats;
    foreach my $array (@stats) {
	while (@$array) {
	    my ($name,$ifindex,$value) = @{shift @$array};

1316
1317
	    # Skip if this isn't an Ethernet port.
	    next unless exists($self->{IFINDEX}{$ifindex});
1318

1319
	    # Make sure this port is wired up and connecting to a node.
1320
1321
1322
	    my ($swport) = $self->convertPortFormat($PORT_FORMAT_PORT, $ifindex);
	    if (defined($swport) && defined($swport->getOtherEndPort())) {
		my $nportstr = $swport->getOtherEndPort()->toTripleString();
1323
1324
		$allports{$nportstr} = $swport;
		${$stats{$nportstr}}[$i] = $value;
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
	    }
	}
	$i++;
    }

    return map [$allports{$_}, @{$stats{$_}}], sort {tbsort($a,$b)} keys %stats;
}

#
# Used to flush FDB entries easily
#
# usage: resetVlanIfOnTrunk(self, modport, vlan)
#
# note: the modport here is most likely a channel ifindex.
# 
sub resetVlanIfOnTrunk($$$) {
    my ($self, $modport, $vlan) = @_;

    #
    # MAYBE-TODO: check like snmpit_hp?
    #

    $self->setVlansOnTrunk($modport, 0, $vlan);
    $self->setVlansOnTrunk($modport, 1, $vlan);

    return 0;
}

#
# Get the ifindex for an EtherChannel (trunk given as a list of ports)
#
# usage: getChannelIfIndex(self, ports)
#        Returns: undef if more than one port is given, and no channel is found
#           an ifindex if a channel is found and/or only one port is given
#
sub getChannelIfIndex($@) {
    my $self = shift;
    my @ports = @_;
    my $id = $self->{NAME}."::getChannelIfIndex";

1365
1366
    my $chifindex = undef;

1367
    my @swports = $self->convertPortFormat($PORT_FORMAT_IFINDEX, @ports);
1368
1369
1370
1371
    my @getcmds = ();
    foreach my $ifindex (@swports) {
	push @getcmds, ["get","$MLNX_IFC_PREFIX/$ifindex/lag/membership"];
    }
1372

1373
    my $resp = $self->callRPC(\@getcmds);
1374

1375
    if (!defined($resp)) {
Kirk Webb's avatar
Kirk Webb committed
1376
	warn "$id: WARNING: Failed to lookup port channel membership.\n";
1377
	return undef;
1378
1379
    }

1380
1381
1382
1383
1384
1385
1386
1387
    # Go through the LAG membership for each port.  If it's '0', then
    # the port doesn't belong to a LAG.  Otherwise, lookup the LAG's
    # ifindex and set it to be returned.  The index returned by the
    # above "get" calls corresponds to the interface number,
    # e.g. "Po1", not to the ifindex of the LAG interface. As with other
    # snmpit modules, we'll take the first valid interface we find here.
    foreach my $rv (@$resp) {
	my (undef, undef, $chidx) = @$rv;
1388
1389
	if ($chidx != 0 && exists($self->{POIFINDEX}{"Po${chidx}"})) {
	    $chifindex = $self->{POIFINDEX}{"Po${chidx}"};
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
	    last;
	}
    }

    # If no port channel was found, but only one port was provided, attempt
    # to set the return value to its ifindex.  This follows the semantics in
    # snmpit_cisco.pm
    if (!defined($chifindex) && scalar(@swports) == 1 && 
	exists($self->{IFINDEX}{$swports[0]})) {
	$chifindex = $self->{IFINDEX}{$swports[0]};
    }

Kirk Webb's avatar
Kirk Webb committed
1402
1403
    $self->debug("$id gets $chifindex\n",1);
    return $chifindex;
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
}


#
# Enable, or disable,  port on a trunk
#
# usage: setVlansOnTrunk(self, modport, value, vlan_numbers)
#        modport: module.port of the trunk to operate on
#        value: 0 to disallow the VLAN on the trunk, 1 to allow it
#	 vlan_numbers: An array of 802.1Q VLAN numbers to operate on
#        Returns 1 on success, 0 otherwise
#
sub setVlansOnTrunk($$$$) {
    my ($self, $modport, $value, @vlan_numbers) = @_;
    my $id = $self->{NAME} . "::setVlansOnTrunk";
Kirk Webb's avatar
Kirk Webb committed
1419
    my $errors = 0;
1420
1421
1422
1423
1424

    #
    # Some error checking (from HP)
    #
    if (($value != 1) && ($value != 0)) {
Kirk Webb's avatar
Kirk Webb committed
1425
	warn "$id: WARNING: Invalid value $value passed to function.\n";
1426
1427
1428
	return 0;
    }
    if (grep(/^1$/,@vlan_numbers)) {
Kirk Webb's avatar
Kirk Webb committed
1429
	warn "$id: WARNING: VLAN 1 passed to function.\n";
1430
1431
1432
	return 0;
    }

1433
    my ($poifindex) = $self->convertPortFormat($PORT_FORMAT_IFINDEX, $modport);
1434
    if (!exists($self->{POIFINDEX}{$poifindex})) {