snmpit.in 16.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/perl -w

#
# snmpit - A tool for setting up VLANs on SNMP-controllable switches
#

#
# Configure variables
#

use lib '@prefix@/lib';
12
my $TESTMODE = @TESTMODE@;
13
my $TB = '@prefix@';
14

15
16
17
18
19
20
use libdb;
use snmpit_lib;

use English;
use Getopt::Std;
use strict;
21

22
23
24
#
# Defaults
#
25
my $debug = 0;
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

######################################################################
# Step 1 - Process command-line arguments
#
# We have a fairly complex set of command line arguments, and we
# need to make sure that the user only specifies one command at a
# time.
######################################################################
sub usage {
    print << "END";
Usage: $0 [-h] [-v] [-i device] 
	  [-l] [-s] [-g]
	  [-m name [ports]]
	  [-o name]
          [-r pid eid]
	  [-t pid eid]
General:
  -h          Display this help message
  -v          Verbose mode
  -i <device> Operate on <device>, overriding default device list

VLAN Control:
  -t <pid> <eid>    Create all VLANs from database tables for an experiment
  -r <pid> <eid>    Remove all VLANs from database tables for an experiment
  -l                List all VLANs
  -m <name> [ports] Create a new VLAN with name <name>, if it doesn't exist,
                        and put [ports] in it
  -o <name>         Delete the VLAN with name <name>

Port Control:
  -s         List all ports, and show configuration information
  -g         Get port statistics
  -d <ports> Disable <ports>
  -e <ports> Enable <ports>
  -a <ports> Enable auto-negotiation of port speed/duplex
  -p <ports> Set speed of <ports> to 10 or 100 Mbps
  -u <ports> Set duplex of <ports> to half or full


END

    return 1;
68
69
}

70

71
72
73
74
75
76
77
my $opts = 'hlvstri:m:o:p:u:deag';
my %opt = ();
getopts($opts,\%opt);

if ($opt{h}) {
    exit &usage;
}
78

79
80
81
82
83
84
85
86
87
88
89
90
91
92
if ($opt{v}) {
    $debug = $opt{v};
    print "Debug level is $debug\n";
}

#
# Make sure the user specified exactly one operation
#
my @commandOpts = ('l','m','o','s','t','r','d','e','a','p','u','g');
if (!exactlyOne(map {$opt{$_}} @commandOpts)) {
    warn "ERROR: You must specify exactly one of " .
	    join(",", map {"-$_"} @commandOpts) . "\n";
    exit &usage;
}
93

94
95
96
#
# Values that may have been passed on the command line
#
97
98
my $pid;
my $eid;
99
100
101
102
103
104
105
106
107
108
109
110
111
my @ports;

#
# Some operations have mandatory agruments - for others, make sure that
# the user didn't give any extraneous arguments
#
if ($opt{t} || $opt{r}) {
    #
    # Options that take 'pid eid'
    #
    if (@ARGV != 2) {
	warn "ERROR: pid/eid reqired!\n";
	exit &usage;
112
    } else {
113
	($pid, $eid) = @ARGV;
114
    }
115
116
117
118
119
120
121
122
123
124
125
126
} elsif ($opt{d} || $opt{e} || $opt{a} || $opt{p} || $opt{u} || $opt{m}) {
    #
    # Options that take a list of ports
    #
    @ports = @ARGV;
} else {
    #
    # Everything else
    #
    if (@ARGV) {
	warn "ERROR: Too many arguments!\n";
	exit &usage;
127
    }
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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
}

#
# Determine which operation we're performing. This is just for convenience,
# so that we can use switch-like constructs later. While we're at it, we
# pull out any arguments that were given in the $opt{} values.
#
my $operation;
my $vlan_name;
my $portcommand;
if ($opt{l}) {
    $operation = "listvlans";
} elsif ($opt{s}) {
    $operation = "listports";
} elsif ($opt{g}) {
    $operation = "getstats";
} elsif ($opt{t}) {
    $operation = "tables";
} elsif ($opt{r}) {
    $operation = "reset";
} elsif ($opt{m}) {
    $operation = "make";
    $vlan_name = $opt{m};
} elsif ($opt{o}) {
    $operation = "remove";
    $vlan_name = $opt{o};
} elsif ($opt{d}) {
    $operation = "portcontrol";
    $portcommand = "disable";
} elsif ($opt{e}) {
    $operation = "portcontrol";
    $portcommand = "enable";
} elsif ($opt{a}) {
    $operation = "portcontrol";
    $portcommand = "auto";
} elsif ($opt{p}) {
    $operation = "portcontrol";
    #
    # We'll put the argument in the form needed by the portControl function
    #
    if ($opt{p} =~ /^100/) {
	$portcommand = "100mbit";
    } elsif ($opt{p} =~ /^10/) {
	$portcommand = "10mbit";
    } else {
	die "Bad port speed: $opt{p}. Valid values are 10 and 100\n";
174
    }
175
176
177
178
179
180
181
182
183
184
185
} elsif ($opt{u}) {
    #
    # We'll put the argument in the form needed by the portControl function
    #
    $operation = "portcontrol";
    if ($opt{u} =~ /half/) {
	$portcommand = "half";
    } elsif ($opt{u} =~ /full/) {
	$portcommand = "full";
    } else {
	die "Bad port duplex: $opt{u}. Valid values are full and half\n";
Mac Newbold's avatar
Mac Newbold committed
186
    }
187
188
189
} else {
    die "No operation given\n";
}
Mac Newbold's avatar
Mac Newbold committed
190

191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
debug("Operation is $operation\n");

######################################################################
# Step 3 - Set up the stack objects
#
# Determine which devices to talk to, and make the appropriate
# stack objects
######################################################################

if ($TESTMODE) {
    print "Test mode, exiting without touching hardware\n";
    exit(0);
}

#
# snmpit_lib fills out some hashes for speed of lookup later. Initialize
# them now
#
snmpit_lib::init($debug);

#
# Discover the set of devices we need to talk to. This differs depending
# on the operation which we're performing. We also get a list of all ports
# and vlan IDs involved in this operation, if appropriate
#
my @devicenames;
my @vlans;
SWITCH: for ($operation) {
    (/listvlans/ || /getstats/) && do {
	@devicenames = $opt{i}? $opt{i} : getTestSwitches();
    };
    (/listports/ || /make/ || /remove/) && do {
	@devicenames = $opt{i}? $opt{i} :
	    (@ports? getDeviceNames(@ports) : getTestSwitches());
	last;
    };
    (/tables/) && do {
	@vlans = getExperimentVlans($pid,$eid);
	@ports = getVlanPorts(@vlans);
	@devicenames = $opt{i}? $opt{i} : getDeviceNames(@ports);
    };
    (/reset/) && do {
	#
	# When we reset, we operate on all test switches, just to be safe
	#
	@vlans = getExperimentVlans($pid,$eid);
	@devicenames = $opt{i}? $opt{i} : getTestSwitches();
    };
    (/portcontrol/) && do {
	@devicenames = $opt{i}? $opt{i} : getDeviceNames(@ports);
    };
}

debug("Device names: " . join(",",@devicenames) . "\n");
debug("Ports: " . join(",",@ports) . "\n");

#
# If this is an operation on an experiment, make sure that they have permission
# to modify that experiment
#
if ($pid && $eid) {
    if (!TBExptAccessCheck($UID,$pid,$eid,TB_EXPT_MODIFY)) {
	die "You do not have permission to modify experiment $pid/$eid\n";
254
    }
255
256
257
258
259
260
261
262
263
264
265
}

#
# If their operation involves a set of ports, make sure that the caller has
# access to the nodes that the ports are on
#
if (@ports) {
    my @nodes = map /^([^:]+)/, @ports;
    if (!TBNodeAccessCheck($UID,TB_NODEACCESS_MODIFYVLANS,@nodes)) {
	die "You do not have permission to modify some or all of the nodes\n" .
		"that will be affected by the operation you requested\n";
266
    }
267
}
268

269
270
271
272
273
274
275
276
#
# Find out which stack each device belongs to
#
my %stacks = ();
foreach my $devicename (@devicenames) {
    my $stack = getSwitchStack($devicename);
    if (defined($stack)) {
	push @{$stacks{$stack}}, $devicename;
277
278
279
    }
}

280
281
282
283
284
285
286
287
288
289
#
# Now, make the object for each stack that we discovered
#
my @stacks;
foreach my $stack_id (keys %stacks) {
    #
    # XXX: For now, we assume that the stack is a Cisco stack. We'll need
    # more database information to be able to distinguish stack types
    #
    use snmpit_cisco_stack;
290
291
    my $stack = new snmpit_cisco_stack($stack_id,$debug,
	@{$stacks{$stack_id}});
292
    push @stacks, $stack;
293
}
294

295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
######################################################################
# Step 3 - Actually perfrom the operation
#
# Finally, we just call the helper function for the operation that
# is to be performed. Note that all functions use the global @stacks
# variable, so it isn't passed to every one.
######################################################################

my $exitval;
SWITCH: for ($operation) {
    /listvlans/ && do {
	$exitval = doListVlans();
	last;
    }; # /vlans/ && do 
    /listports/ && do {
	$exitval = doListPorts();
	last;
    }; # /ports/ && do
    /getstats/ && do {
	$exitval = doGetStats();
	last;
    }; # /ports/ && do
    /tables/ && do {
	$exitval = doVlansFromTables(@vlans);
	last;
    }; # /tables/ && do
    /reset/ && do {
	$exitval = doReset(@vlans);
	last;
    };
    /make/ && do {
	$exitval = doMakeVlan($vlan_name,@ports);
	last;
    };
    /remove/ && do {
	$exitval = doDeleteVlan($vlan_name);
	last;
    };
    /portcontrol/ && do {
	$exitval = doPortControl($portcommand,@ports);
    };
}

exit $exitval;

######################################################################
# Subs
######################################################################

#
# Return true if exactly one element of the given array is true
#
sub exactlyOne (@) {
    my $one = 0;
    foreach my $value ( @_ ) {
	if ($value) {
	    if ($one) {
		return 0; # More than one
	    } else {
		$one = 1; # This is the first
	    }
	}
    }
    return $one;
Mac Newbold's avatar
Mac Newbold committed
359
360
}

361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
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
#
# Print given message to STDERR, only if debug mode is on
#
sub debug($) {
    if ($debug) {
	print STDERR @_;
    }
}

#
# Lists all vlans on all stacks
#
sub doListVlans () {
    
    my %vlans;

    #
    # We need to 'coallate' the results from each stack by putting together
    # the results from each stack, based on the VLAN identifier
    #
    foreach my $stack (@stacks) {
	# TODO: Add a way to print ddep 
	my @vlanList = $stack->listVlans();
	foreach my $vlan (@vlanList) {
	    my ($id,$ddep,$memberref) = @$vlan;
	    push @{${$vlans{$id}}[1]}, @$memberref;
	}
    }

    #
    # These need to be declared here for the benefit of the format string
    # See perlform(1) for help with formats
    #
    my ($vlan_id,$pideid,$vname,$members);
    print << "END";
VLAN  Project/Experiment   VName      Members
--------------------------------------------------------------------------------
END
    format vlanlist =
@<<<< @<<<<<<<<<<<<<<<<<<< @<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$vlan_id,$pideid,          $vname,    $members
~~                                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                      $members
.

    $FORMAT_NAME = 'vlanlist';
    foreach $vlan_id (sort {tbsort($a,$b)} keys %vlans) {
	my ($ddep,$memberref) = @{$vlans{$vlan_id}};

	#
	# Find which, if any, experiment this VLAN belongs to.
	#
	my $result = DBQueryFatal("select pid, eid, virtual from " .
				  "vlans where id='$vlan_id'");
	my ($eid,$pid);
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
	($pid,$eid,$vname) = $result->fetchrow();

	#
	# Permissions check - people only get to see
	#
	if ((!$eid) || (!$pid)) {
	    if (!TBAdmin()) {
		&debug("Failed TBAdmin check\n");
		next;
	    }
	} elsif (!TBExptAccessCheck($UID,$pid,$eid,TB_EXPT_READINFO)) {
	    &debug("Failed TBExptAccessCheck($UID,$pid,$eid)\n");
	    next;
	}

431
432
433
434
435
436
437
438

	if (!$vname) { $vname = ""; }
	$members = join(" ",@$memberref);

	#
	# Setup $pideid for a more compact display
	#
	if ($eid && $pid) {
439
	    $pideid = "$pid/$eid";
440
	} else {
441
	    $pideid = "";
442
	}
443
	write;
444
    }
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459

    return 0;
}

#
# Lists all ports on all stacks
#
sub doListPorts() {

    #
    # Get a listing from all stacks
    #
    my @portList = ();
    foreach my $stack (@stacks) {
	push @portList, $stack->listPorts;
460
    }
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476

    #
    # See perlform(1) for help with formats
    #
    my ($port,$enabled,$up,$speed,$duplex);
    print << "END";
Port      Enabled Up   Speed      Duplex
----------------------------------------
END
    format portlist =
@<<<<<<<< @<<<<<< @<<< @<<<<<<<<< @<<<
$port,    $enabled,$up,$speed,$duplex
.
    $FORMAT_NAME = 'portlist';
    foreach my $line (sort {tbsort($$a[0],$$b[0])} @portList) {
	($port,$enabled,$up,$speed,$duplex) = @$line;
477
478
479
480
481
482
483
484
485
486
487
488
489
490
	#
	# Only let people see information about ports in their experiments
	#
	$port =~ /^(.+):/;
	my $node = $1;

	&debug("node is $node\n");
	if (!$node) {
	    if (!TBAdmin($UID)) {
		next;
	    }
	} elsif (!TBNodeAccessCheck($UID,TB_NODEACCESS_READINFO,$node)) {
	    next;
	}
491
	write;
492
    }
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507

    return 0;
}

#
# Get statistics for all ports on all stacks
#
sub doGetStats() {

    #
    # Get a listing from all stacks
    #
    my @statList = ();
    foreach my $stack (@stacks) {
	push @statList, $stack->getStats();
508
    }
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558

    my ($port, $inoctets, $inunicast, $innunicast, $indiscards, $inerr,
        $inunk, $outoctets, $outunicast, $outnunicast, $outdiscards,
	$outerr,$outq);
    #
    # See perlform(1) for help with formats
    #
    print << "END";
          In         InUnicast  InNUnicast In         In         Unknown    Out        OutUnicast OutNUcast  Out       Out         OutQueue
Port      Octets     Packets    Packets    Discards   Errors     Protocol   Octets     Packets    Packets    Discards  Errors      Length
---------------------------------------------------------------------------------------------------------------------------------------------
END
    format stats =
@<<<<<<<< @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> @>>>>>>>>> 
$port,    $inoctets, $inunicast,$innunicast,$indiscards,$inerr,  $inunk,    $outoctets,$outunicast,$outnunicast,$outdiscards,$outerr,$outq
.
    $FORMAT_NAME = 'stats';
    foreach my $line (sort {tbsort($a,$b)} @statList) {
	($port, $inoctets, $inunicast, $innunicast, $indiscards, $inerr,
	 $inunk, $outoctets, $outunicast, $outnunicast, $outdiscards,
	 $outerr, $outq) = @$line;
	write;
    }

    return 0;
}
#
# Creates all VLANs given. Looks up identifiers in the database to determine
# the membership.
#
sub doVlansFromTables(@) {
    my @vlans = @_;

    my $errors = 0;

    if (@stacks > 1) {
	die "VLAN creation accross multiple stacks is not yet supported\n";
    }
    my ($stack) = @stacks;

    foreach my $vlan (@vlans) {
	if (!$stack->createVlan($vlan)) {
    	    warn "ERROR: Failed to create VLAN with id $vlan\n";
	    #
	    # Don't try to put ports in a VLAN if it couldn't be created
	    #
	    $errors++;
    	} else {
	    my @ports = getVlanPorts($vlan);
	    $errors += $stack->setPortVlan($vlan,@ports);
559
560
	}
    }
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584

    return $errors;
}

#
# Remove all VLANs given from every switch in the stack. All ports in the
# VLANs are removed, irrespective of what the database says membership should
# be
#
sub doReset(@) {
    my @vlans = @_;

    my $errors = 0;
    foreach my $vlan (@vlans) {
	#
	# Just remove the VLAN from evey satck on which it exists
	#
	foreach my $stack (@stacks) {
	    if ($stack->vlanExists($vlan)) {
		if (!$stack->removeVlan($vlan)) {
		    $errors++;
		}
	    }
	}
585
    }
586
587
588
589
590
591
592
593
594
595
596
597
    return $errors;
}

#
# Create a vlan with name $vlan_name. It is not an error to try to create a
# VLAN that already exists, as this can be used to add ports to an existing
# VLAN. If ports are given, they are put into the VLAN.
#
sub doMakeVlan($@) {
    my $vlan_name = shift;
    my @ports = @_;

598
    my $errors = 0;
599
600
601

    if (@stacks > 1) {
	die "VLAN creation accross multiple stacks is not yet supported\n";
602
    }
603
604
605
606
607
608
609
    my ($stack) = @stacks;

    #
    # Create it if it doesn't already exist
    #
    if ($stack->vlanExists($vlan_name)) {
	print "VLAN $vlan_name already exists\n";
610
    } else {
611
612
613
614
615
616
617
	print "Creating VLAN $vlan_name ...\n";
	my $ok = $stack->createVlan($vlan_name);
	print "VLAN creation ";
	print $ok? "succeeded":"failed",".\n";
	if (!$ok) {
	    $errors++;
	}
618
    }
619
620
621
622
623
624
625
626
627
628
629

    #
    # Put requested ports into the VLAN
    #
    if (@ports) {
	print "Putting ports in VLAN ...\n";
	my $perrors = $stack->setPortVlan($vlan_name,@ports);
	print "VLAN change ";
	print $perrors? "failed":"succeeded",".\n";
	$errors += $perrors;

630
    }
631
632

    return $errors;
633
634
}

635
636
637
638
639
640
641
642
643
644
645
646
647
648
#
# Delete the given VLAN, if it exists
#
sub doDeleteVlan($) {
    my $vlan_name = shift;

    my $errors = 0;

    my $exists = 0;
    foreach my $stack (@stacks) {
	if ($stack->vlanExists($vlan_name)) {
	    $exists = 1;
	    print "Deleting VLAN $vlan_name ...\n";
	    my $ok = $stack->removeVlan($vlan_name);
649
	    print "VLAN deletion ";
650
651
652
653
	    print $ok? "succeeded":"failed",".\n";
	    if (!$ok) {
		$errors++;
	    }
654
	}
655
    }
656
657
658
659

    if (!$exists) {
	print "VLAN $vlan_name does not exist\n";
	$errors++;
660
    }
661
662

    return $errors;
663
}
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686

#
# Send $command to @ports.
# TODO: List of commands
#
sub doPortControl($@) {
    my $command = shift;
    my @ports = @_;

    if (@stacks > 1) {
	die "Port control accross multiple stacks is not yet supported\n";
    }
    my ($stack) = @stacks;

    print "Applying command '$command' to ports " . join(",",@ports) . " ...\n";
    my $errors = $stack->portControl($command,@ports);
    print "Port command ";
    print $errors? "failed":"succeeded",".\n";

    return $errors;

}