snmpit.in 23.2 KB
Newer Older
1
2
#!/usr/bin/perl -w

Leigh B. Stoller's avatar
Leigh B. Stoller committed
3
4
5
6
7
8
9
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2002 University of Utah and the Flux Group.
# All rights reserved.
#


10
11
12
13
14
15
16
17
18
#
# snmpit - A tool for setting up VLANs on SNMP-controllable switches
#

#
# Configure variables
#

use lib '@prefix@/lib';
19
my $TESTMODE = @TESTMODE@;
20
my $TB = '@prefix@';
21
my $COMMUNITY ='@SNMP_RW_COMMUNITY@';
22

23
24
25
26
use libdb;
use snmpit_lib;

use English;
27
use Getopt::Long;
28
use strict;
29

30
31
32
#
# Defaults
#
33
my $debug = 0;
34

35
36
37
38
if (!$COMMUNITY) {
    $COMMUNITY = "public";
}

39
40
41
42
43
44
45
46
47
######################################################################
# 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";
Robert Ricci's avatar
Robert Ricci committed
48
Usage: $0 [-h] [-v] [-n] [-i device]
49
50
51
52
53
	  [-l] [-s] [-g]
	  [-m name [ports]]
	  [-o name]
          [-r pid eid]
	  [-t pid eid]
54
55
	  [-d ports] [-e ports] [-a ports]
	  [-p <10|100> ports] [-u <half|full> ports]
56
	  [-c]
57
58
59
General:
  -h          Display this help message
  -v          Verbose mode
Robert Ricci's avatar
Robert Ricci committed
60
  -n          Test mode - don't actually make any changes
61
62
  -i <device> Operate on <device>, overriding default device list. Can be
                  given multiple times
63
64
65
66
67
68
69

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
Robert Ricci's avatar
Robert Ricci committed
70
71
72
73
74
75
  -y <type>         When used with -m, the new VLAN becomes a private VLAN
                        of type <type>
  -x <primary>      When used with -y, assocates the new private VLAN with
                        the primary VLAN named <primary>
  -z <port>         Used with -y and -x, to specify which port is to be used
                        with the private VLAN
76
  -o <name>         Delete the VLAN with name <name>
77
78
  -c                Delete ALL VLANs, and recreate from the database. ** USE
                        WITH EXTREME CAUTION **
79
80

Port Control:
81
82
83
84
85
86
87
88
89
90
91
  -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 <10|100> <ports>    Set speed of <ports> to 10 or 100 Mbps
  -u <half|full> <ports> Set duplex of <ports> to half or full

More than one operation can be specified - However, beware that the order in
which operations will occur is undefined, and some combinations of operations
(ie. -d and -e) are non-sensical.
92
93
94
END

    return 1;
95
96
}

97

98
my %opt = ();
Robert Ricci's avatar
Robert Ricci committed
99
100
101
GetOptions(\%opt, 'a','c','d','e','g','h','i=s@','l','m=s@','n','o=s@','p=s',
    'r','s','t','u=s','v','y=s','x=s','z=s');
# Unused: b,f,j,q,w
102
103
104
105

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

107
108
109
110
111
112
113
114
if ($opt{v}) {
    $debug = $opt{v};
    print "Debug level is $debug\n";
}

#
# Values that may have been passed on the command line
#
115
116
my $pid;
my $eid;
117
118
119
120
121
122
123
124
125
126
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'
    #
127
    if (@ARGV < 2) {
128
129
	warn "ERROR: pid/eid reqired!\n";
	exit &usage;
130
    } else {
131
	($pid, $eid) = (shift @ARGV, shift @ARGV);
132
    }
133
134
135
136
137
138
139
140
141
142
143
144
} 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;
145
    }
146
147
148
149
150
151
152
}

#
# 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.
#
153
154
155
156
157
158
159
160
161
162
my @commands;

#
# Simple commands
#
if ($opt{l}) { push @commands, ["listvlans"]; }
if ($opt{s}) { push @commands, ["listports"]; }
if ($opt{g}) { push @commands, ["getstats"]; }
if ($opt{t}) { push @commands, ["tables"]; }
if ($opt{r}) { push @commands, ["reset"]; }
163
if ($opt{c}) { push @commands, ["recreate"]; }
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

#
# Commands that can appear once, and take an agurment
#
if ($opt{d}) { push @commands, ["portcontrol","disable"]; }
if ($opt{e}) { push @commands, ["portcontrol","enable"]; }
if ($opt{a}) { push @commands, ["portcontrol","auto"]; }

#
# Commands that can occur more than once
#
if ($opt{m}) {
    foreach my $name (@{$opt{m}}) {
	push @commands, ["make",$name];
    }
}

if ($opt{o}) {
    foreach my $name (@{$opt{o}}) {
	push @commands, ["remove",$name];
    }
}

#
# Commands that require 'translation' of their arguments
#
if ($opt{p}) {
191
192
193
194
    #
    # We'll put the argument in the form needed by the portControl function
    #
    if ($opt{p} =~ /^100/) {
195
	push @commands, ["portcontrol","100mbit"];
196
    } elsif ($opt{p} =~ /^10/) {
197
	push @commands, ["portcontrol","10mbit"];
198
199
    } else {
	die "Bad port speed: $opt{p}. Valid values are 10 and 100\n";
200
    }
201
202
}
if ($opt{u}) {
203
204
205
206
    #
    # We'll put the argument in the form needed by the portControl function
    #
    if ($opt{u} =~ /half/) {
207
	push @commands, ["portcontrol","half"];
208
    } elsif ($opt{u} =~ /full/) {
209
	push @commands, ["portcontrol","full"];
210
211
    } else {
	die "Bad port duplex: $opt{u}. Valid values are full and half\n";
Mac Newbold's avatar
Mac Newbold committed
212
    }
213
214
215
}

if (!@commands) {
216
217
    die "No operation given\n";
}
Mac Newbold's avatar
Mac Newbold committed
218

Robert Ricci's avatar
Robert Ricci committed
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
254
255
256
257
#
# Options that affect other commands
#

#
# Arguments for making private VLANs
#
# Build up a list of extra arguments to be passed to createVlan()
my @pvlanArgs = ();
if ($opt{y}) {
    #
    # Make sure the private VLAN type they gave is valid, and make sure they
    # gave the other required arugments for certain types
    #
    if ($opt{y} ne "primary" && $opt{y} ne "isolated" &&
	$opt{y} ne "community") {
	die "Unknown private VLAN type $opt{y}\n";
    }
    @pvlanArgs = $opt{y};
    if ($opt{y} ne "primary") {
	if (!$opt{x} || !$opt{z}) {
	    warn "**** -x and -z must be given when -y is $opt{y}!\n";
	    exit &usage;
	}
	#
	# Fix up ports given in the module/port format, like we do below for
	# ports from @ARGV
	#
	if ($opt{z} =~ /^\d+\/\d+?$/) {
	    if ($opt{i} && @{$opt{i}} == 1) {
		$opt{z} = $opt{i}->[0] . "." . $opt{z};
	    } else {
		die "The module/port format is only legal if exactly one -i " .
		    "argument has been given\n";
	    }
	}
	push @pvlanArgs,$opt{x},$opt{z};
    }
}
258
259
260
261
262
263
264
265
266
267
268
269
270

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

#
# If this is an operation on an experiment, make sure that they have permission
# to modify that experiment
#
if ($pid && $eid) {
Robert Ricci's avatar
Robert Ricci committed
271
272
273
274
275
276
    #
    # First, make sure the experiment exists
    #
    if (!ExpState($pid,$eid)) {
	die "There is no experiment $eid in project $pid\n";
    }
277
278
    if (!TBExptAccessCheck($UID,$pid,$eid,TB_EXPT_MODIFY)) {
	die "You do not have permission to modify experiment $pid/$eid\n";
279
    }
280
281
282
283
284
285
}

#
# If their operation involves a set of ports, make sure that the caller has
# access to the nodes that the ports are on
#
286

287
if (@ports) {
Robert Ricci's avatar
Robert Ricci committed
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
    #
    # Allow ports to be given in one of two forms: node:port, or switch.port.
    # Only admins can do the latter, of course...
    #
    my (@nodes, @switchports);
    foreach my $port (@ports) {
	if ($port =~ /^([^:]+):\d+$/) {
	    push @nodes, $1;
	} elsif ($port =~ /^([^.]+)\.\d+(\/\d+)?$/) {
	    push @switchports, $port;
	} elsif ($port =~ /^\d+\/\d+?$/) {
	    if ($opt{i} && @{$opt{i}} == 1) {
		$port = $opt{i}->[0] . "." . $port;
		push @switchports, $port;
	    } else {
		die "The module/port format is only legal if exactly one -i " .
		    "argument has been given\n";
	    }
	} else {
	    die "Bad format for port $port\n"
	}
    }

311
312
313
    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";
314
    }
Robert Ricci's avatar
Robert Ricci committed
315
316
317
318

    if (@switchports && !TBAdmin()) {
	die "Only admins are allowed to modify switch ports directly\n";
    }
319
}
320

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

326
#
327
328
# snmpit_lib fills out some hashes for speed of lookup later. Initialize
# them now
329
#
330
331
snmpit_lib::init($debug);

Robert Ricci's avatar
Robert Ricci committed
332
my $exitval = 0;
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
foreach my $command (@commands) {

    #
    # Pull the operation and the arugments to it.
    #
    my ($operation,@args) = @$command;

    debug("Operation is $operation\n");

    #
    # 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) {
350
	(/listvlans/ || /getstats/ || /make/ || /remove/) && do {
351
	    @devicenames = $opt{i}? @{$opt{i}} : getTestSwitches();
352
	    last;
353
	};
354
	(/listports/) && do {
355
356
	    @devicenames = $opt{i}? @{$opt{i}} :
	    (@ports? getDeviceNames(@ports) : getTestSwitches());
357
358
	    last;
	};
359
360
361
	(/tables/) && do {
	    @vlans = getExperimentVlans($pid,$eid);
	    @ports = getVlanPorts(@vlans);
362
	    @devicenames = $opt{i}? @{$opt{i}} : getTestSwitches();
363
364
365
366
367
368
369
370
371
372
373
374
375
376
	    last;
	};
	(/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();
	    last;
	};
	(/portcontrol/) && do {
	    @devicenames = $opt{i}? @{$opt{i}} : getDeviceNames(@ports);
	    last;
	};
377
378
379
380
381
382
383
384
385
386
387
	(/recreate/) && do {
	    #
	    # Safety check - cannot be used with -i . We have to operate on
	    # all experimental switches
	    #
	    if ($opt{i}) {
		die "-c and -i cannot be used together\n";
	    }
	    @devicenames = getTestSwitches();
	    last;
	};
388
    }
389

390
391
392
393
394
395
396
397
398
399
400
401
    debug("Device names: " . join(",",@devicenames) . "\n");
    debug("Ports: " . join(",",@ports) . "\n");

    #
    # 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;
	}
402
    }
403
404

    #
405
    # Now, make the object for each stack that we discovered
406
    #
407
408
    my @stacks;
    foreach my $stack_id (keys %stacks) {
Robert Ricci's avatar
Robert Ricci committed
409
410
411
412
413
414
415
416
	my ($stack_type, $supports_private) = getStackType($stack_id);
	#
	# Safety check - make sure the stack supports private VLANs if -y was
	# given
	#
	if ($opt{y} && !$supports_private) {
	    die "Switch stack $stack_id does not support private VLANs\n";
	}
417
418
419
420
421
	my $stack;
	debug("Stack $stack_id has type $stack_type\n");
	SWITCH: for ($stack_type) {
	    /cisco/ && do {
		require snmpit_cisco_stack;
422
		$stack = new snmpit_cisco_stack($stack_id,$debug,$COMMUNITY,
Robert Ricci's avatar
Robert Ricci committed
423
		    $supports_private, @{$stacks{$stack_id}});
424
425
426
427
		last;
	    }; # /cisco/
	    /intel/ && do {
		require snmpit_intel_stack;
428
429
		$stack = new snmpit_intel_stack($stack_id,$debug,$COMMUNITY,
		    @{$stacks{$stack_id}});
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
		last;
	    };

	    # 'default' case
	    die "Unknown stack type $stack_type for stack $stack_id\n";
	}

	#
	# Check for error in object creation and bail
	#
	if (!$stack) {
	    die "Unable to connect to one or more switches, exiting\n";
	} else {
	    push @stacks, $stack;
	}
445
    }
446

447
######################################################################
448
# Step 4 - Actually perfrom the operation
449
450
#
# Finally, we just call the helper function for the operation that
451
# is to be performed.
452
######################################################################
Robert Ricci's avatar
Robert Ricci committed
453
454
455
456
    if ($opt{n}) {
	print "Test mode, skipping operation\n";
	next;
    }
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
    SWITCH: for ($operation) {
	/listvlans/ && do {
	    $exitval += doListVlans(\@stacks);
	    last;
	}; # /listvlans/ && do 
	/listports/ && do {
	    $exitval += doListPorts(\@stacks);
	    last;
	}; # /listports/ && do
	/getstats/ && do {
	    $exitval += doGetStats(\@stacks);
	    last;
	}; # /ports/ && do
	/tables/ && do {
	    $exitval += doVlansFromTables(\@stacks,@vlans);
	    last;
	}; # /tables/ && do
	/reset/ && do {
	    $exitval += doReset(\@stacks,@vlans);
	    last;
	};
	/make/ && do {
	    my ($vlan_name) = @args;
	    $exitval += doMakeVlan(\@stacks,$vlan_name,@ports);
	    last;
	};
	/remove/ && do {
	    my ($vlan_name) = @args;
	    $exitval += doDeleteVlan(\@stacks,$vlan_name);
	    last;
	};
	/portcontrol/ && do {
490
	    my ($portcommand) = @args;
491
	    $exitval += doPortControl(\@stacks,$portcommand,@ports);
492
493
494
495
496
	    last;
	};
	/recreate/ && do {
	    $exitval += doRecreateVlans(\@stacks);
	    last;
497
498
	};
    }
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
}

exit $exitval;

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

#
# Print given message to STDERR, only if debug mode is on
#
sub debug($) {
    if ($debug) {
	print STDERR @_;
    }
}

#
# Lists all vlans on all stacks
#
519
520
521
sub doListVlans ($) {

    my $stacks = shift;
522
523
524
525
526
527
528
    
    my %vlans;

    #
    # We need to 'coallate' the results from each stack by putting together
    # the results from each stack, based on the VLAN identifier
    #
529
    foreach my $stack (@$stacks) {
530
531
532
533
534
535
536
537
538
539
540
541
542
543
	# 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";
544
VLAN     Project/Experiment VName     Members
545
546
547
--------------------------------------------------------------------------------
END
    format vlanlist =
548
549
@<<<<<<< @<<<<<<<<<<<<<<<<< @<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$vlan_id,$pideid,           $vname,   $members
550
551
552
553
554
555
556
557
558
559
560
561
562
563
~~                                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                      $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);
564
565
566
	($pid,$eid,$vname) = $result->fetchrow();

	#
567
	# Permissions check - people only get to see their own VLANs
568
569
570
571
572
573
574
575
576
577
578
	#
	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;
	}

579
580
581
582
583
584
585
586

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

	#
	# Setup $pideid for a more compact display
	#
	if ($eid && $pid) {
587
	    $pideid = "$pid/$eid";
588
	} else {
589
	    $pideid = "";
590
	}
591
	write;
592
    }
593
594
595
596
597
598
599

    return 0;
}

#
# Lists all ports on all stacks
#
600
601
602
sub doListPorts($) {

    my $stacks = shift;
603
604
605
606
607

    #
    # Get a listing from all stacks
    #
    my @portList = ();
608
    foreach my $stack (@$stacks) {
609
	push @portList, $stack->listPorts;
610
    }
611
612
613
614
615
616
617

    #
    # See perlform(1) for help with formats
    #
    my ($port,$enabled,$up,$speed,$duplex);
    print << "END";
Port      Enabled Up   Speed      Duplex
618
--------------------------------------------
619
620
END
    format portlist =
621
@<<<<<<<< @<<<<<< @<<< @<<<<<<<<< @<<<<<<<<<
622
623
624
625
626
$port,    $enabled,$up,$speed,$duplex
.
    $FORMAT_NAME = 'portlist';
    foreach my $line (sort {tbsort($$a[0],$$b[0])} @portList) {
	($port,$enabled,$up,$speed,$duplex) = @$line;
627
628
629
630
631
632
633
634
635
636
637
638
639
640
	#
	# 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;
	}
641
	write;
642
    }
643
644
645
646
647
648
649

    return 0;
}

#
# Get statistics for all ports on all stacks
#
650
651
652
sub doGetStats($) {

    my $stacks = shift;
653
654
655
656
657

    #
    # Get a listing from all stacks
    #
    my @statList = ();
658
    foreach my $stack (@$stacks) {
659
	push @statList, $stack->getStats();
660
    }
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686

    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;
}
687

688
689
690
691
#
# Creates all VLANs given. Looks up identifiers in the database to determine
# the membership.
#
692
693
sub doVlansFromTables($@) {
    my $stacks = shift;
694
695
696
697
    my @vlans = @_;

    my $errors = 0;

698
    if (@$stacks > 1) {
699
	die "VLAN creation accross multiple stacks is not yet supported\n" .
700
	    "Stacks are " . join(",",@$stacks) . "\n";
701
    }
702
    my ($stack) = @$stacks;
703
704

    foreach my $vlan (@vlans) {
Robert Ricci's avatar
Robert Ricci committed
705
706
707
	my @ports = getVlanPorts($vlan);
	if ($stack->vlanExists($vlan)) {
	    print "  VLAN $vlan already exists\n";
708
	    $errors += $stack->setPortVlan($vlan,@ports);
Robert Ricci's avatar
Robert Ricci committed
709
	} else {
Robert Ricci's avatar
Robert Ricci committed
710
	    if (!$stack->createVlan($vlan,\@ports)) {
Robert Ricci's avatar
Robert Ricci committed
711
712
713
714
715
		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++;
716
717
	    }
	}
718

719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
	#
	# Set the speed and duplex of each interface depending on the
	# value in the database
	#
	foreach my $port (@ports) {
	    my ($speed,$duplex) = getInterfaceSettings($port);
	    #
	    # If either is not set, we do nothing. We could make
	    # a 0 mean 'auto'
	    #
	    if ($speed) {
		my $cmd = $speed . "mbit";
		$errors += $stack->portControl($cmd, $port);
	    }
	    if ($duplex) {
		$errors += $stack->portControl($duplex, $port);
Robert Ricci's avatar
Robert Ricci committed
735
	    }
736
737
	}
    }
738
739
740
741
742
743
744
745
746

    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
#
747
748
sub doReset($@) {
    my $stacks = shift;
749
750
751
    my @vlans = @_;

    my $errors = 0;
752
753
754
755
756
757
758
    #
    # Just remove the VLAN from evey satck on which it exists. We keep a
    # list and do them all at once for efficiency.
    #
    foreach my $stack (@$stacks) {
	my @existant_vlans = ();
	foreach my $vlan (@vlans) {
759
	    if ($stack->vlanExists($vlan)) {
760
		push @existant_vlans, $vlan;
761
762
	    }
	}
763
764
765
	if (!$stack->removeVlan(@existant_vlans)) {
	    $errors++;
	}
766
    }
767
768
769
770
771
772
773
774
    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.
#
775
776
sub doMakeVlan($$@) {
    my $stacks = shift;
777
778
779
    my $vlan_name = shift;
    my @ports = @_;

780
    my $errors = 0;
781

782
    if (@$stacks > 1) {
783
	die "VLAN creation accross multiple stacks is not yet supported\n" .
784
	    "Stacks are " . join(",",@$stacks) . "\n";
785
    }
786
    my ($stack) = @$stacks;
787
788
789
790
791
792

    #
    # Create it if it doesn't already exist
    #
    if ($stack->vlanExists($vlan_name)) {
	print "VLAN $vlan_name already exists\n";
793
794
795
796
797
798
799
800
801
802
803
	#
	# 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;

	}
804
    } else {
805
	print "Creating VLAN $vlan_name ...\n";
Robert Ricci's avatar
Robert Ricci committed
806
	my $ok = $stack->createVlan($vlan_name,\@ports,@pvlanArgs);
807
808
809
810
811
	print "VLAN creation ";
	print $ok? "succeeded":"failed",".\n";
	if (!$ok) {
	    $errors++;
	}
812
    }
813
814

    return $errors;
815
816
}

817
818
819
#
# Delete the given VLAN, if it exists
#
Robert Ricci's avatar
Robert Ricci committed
820
sub doDeleteVlan($@) {
821
    my $stacks = shift;
Robert Ricci's avatar
Robert Ricci committed
822
    my @vlan_names = @_;
823
824
825

    my $errors = 0;

Robert Ricci's avatar
Robert Ricci committed
826
    my %exists = ();
827
    foreach my $stack (@$stacks) {
Robert Ricci's avatar
Robert Ricci committed
828
829
830
831
832
833
834
835
836
837
	my @existant_vlans;
	foreach my $vlan_name (@vlan_names) {
	    if ($stack->vlanExists($vlan_name)) {
		$exists{$vlan_name} = 1;
		push @existant_vlans, $vlan_name;
	    }
	}
	if (@existant_vlans) {
	    print "Deleting VLAN(s) " . join(",",@existant_vlans) . " ...\n";
	    my $ok = $stack->removeVlan(@existant_vlans);
838
	    print "VLAN deletion ";
839
840
841
842
	    print $ok? "succeeded":"failed",".\n";
	    if (!$ok) {
		$errors++;
	    }
843
	}
844
    }
845

Robert Ricci's avatar
Robert Ricci committed
846
847
848
849
850
    foreach my $vlan_name (@vlan_names) {
	if (!$exists{$vlan_name}) {
	    print "VLAN $vlan_name does not exist\n";
	    $errors++;
	}
851
    }
852
853

    return $errors;
854
}
855
856
857
858
859

#
# Send $command to @ports.
# TODO: List of commands
#
860
861
sub doPortControl($$@) {
    my $stacks = shift;
862
863
864
    my $command = shift;
    my @ports = @_;

865
    if (@$stacks > 1) {
866
	die "Port control accross multiple stacks is not yet supported\n" .
867
	    "Stacks are " . join(",",@$stacks) . "\n";
868
    }
869
    my ($stack) = @$stacks;
870
871
872
873
874
875
876
877
878
879

    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;

}

880
881
882
883
884
885
886
887
888
889
890
891
#
# Remove all VLANs from the switch, and re-create them from the database
# tables.
#
sub doRecreateVlans($) {
    my $stacks = shift;

    #
    # Make sure the user REALLY wants to do this
    #

    if (!TBAdmin()) {
892
	warn "Sorry, only admins get to use this function\n";
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
	return 0;
    }

    warn "WARNING: Using this function will cause all VLANS to be\n";
    warn "deleted and re-created. This will cause temporary disruption,\n";
    warn "and you will lose all hand-created VLANs. This function operates\n";
    warn "on ALL experimental switches.\n";
    warn "\nAre you SURE you want to do this? (yes/no)\n";

    my $doit = <>;

    if (!($doit =~ /^y/i)) {
	warn "Not recreating VLANs\n";
	return 0;
    } else {
	warn "Okay, recreating VLANs\n";
    }

    #
    # Get a list of all VLANs on all of the given switches, so that we can
    # nuke them.
    #
    my @vlansToNuke = ();
    foreach my $stack (@$stacks) {
	my @stackVlans = $stack->listVlans();
	foreach my $vlan (@stackVlans) {
	    my $id = $$vlan[0];
920
921
922
923
924
925
926
	    #
	    # Special case - don't try to delete the 'switch-control' VLAN,
	    # because that's the one we're talking to the switches on.
	    #
	    if ($id ne 'switch-control') {
		push (@vlansToNuke,$id);
	    }
927
928
929
930
931
	}
    }

    debug("Going to nuke " . join(',',@vlansToNuke) . "\n");

Robert Ricci's avatar
Robert Ricci committed
932
    doDeleteVlan($stacks,@vlansToNuke);
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947

    #
    # Get a list of all experiments, so that we can re-create their VLANs
    #
    my @expts = ();
    my $result = DBQueryFatal("select pid,eid from experiments ".
    	"where state = '". EXPTSTATE_ACTIVE. "'");
    while (my ($pid,$eid) = $result->fetchrow()) {
	my @vlans = getExperimentVlans($pid,$eid);
	doVlansFromTables($stacks,@vlans);
    }

    return 1;

}