snmpit.in 20.3 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

22
23
24
25
use libdb;
use snmpit_lib;

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

29
30
31
#
# Defaults
#
32
my $debug = 0;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

######################################################################
# 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]
49
50
	  [-d ports] [-e ports] [-a ports]
	  [-p <10|100> ports] [-u <half|full> ports]
51
	  [-c]
52
53
54
General:
  -h          Display this help message
  -v          Verbose mode
55
56
  -i <device> Operate on <device>, overriding default device list. Can be
                  given multiple times
57
58
59
60
61
62
63
64

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>
65
66
  -c                Delete ALL VLANs, and recreate from the database. ** USE
                        WITH EXTREME CAUTION **
67
68

Port Control:
69
70
71
72
73
74
75
76
77
78
79
  -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.
80
81
82
END

    return 1;
83
84
}

85

86
my %opt = ();
87
GetOptions(\%opt,'h','l','v','s','t','r','i=s@','m=s@','o=s@','p=s','u=s','d',
88
    'e','a','g','c');
89
90
91
92

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

94
95
96
97
98
99
100
101
if ($opt{v}) {
    $debug = $opt{v};
    print "Debug level is $debug\n";
}

#
# Values that may have been passed on the command line
#
102
103
my $pid;
my $eid;
104
105
106
107
108
109
110
111
112
113
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'
    #
114
    if (@ARGV < 2) {
115
116
	warn "ERROR: pid/eid reqired!\n";
	exit &usage;
117
    } else {
118
	($pid, $eid) = (shift @ARGV, shift @ARGV);
119
    }
120
121
122
123
124
125
126
127
128
129
130
131
} 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;
132
    }
133
134
135
136
137
138
139
}

#
# 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.
#
140
141
142
143
144
145
146
147
148
149
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"]; }
150
if ($opt{c}) { push @commands, ["recreate"]; }
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

#
# 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}) {
178
179
180
181
    #
    # We'll put the argument in the form needed by the portControl function
    #
    if ($opt{p} =~ /^100/) {
182
	push @commands, ["portcontrol","100mbit"];
183
    } elsif ($opt{p} =~ /^10/) {
184
	push @commands, ["portcontrol","10mbit"];
185
186
    } else {
	die "Bad port speed: $opt{p}. Valid values are 10 and 100\n";
187
    }
188
189
}
if ($opt{u}) {
190
191
192
193
    #
    # We'll put the argument in the form needed by the portControl function
    #
    if ($opt{u} =~ /half/) {
194
	push @commands, ["portcontrol","half"];
195
    } elsif ($opt{u} =~ /full/) {
196
	push @commands, ["portcontrol","full"];
197
198
    } else {
	die "Bad port duplex: $opt{u}. Valid values are full and half\n";
Mac Newbold's avatar
Mac Newbold committed
199
    }
200
201
202
}

if (!@commands) {
203
204
    die "No operation given\n";
}
Mac Newbold's avatar
Mac Newbold committed
205

206
207
208
209
210
211
212
213
214
215
216
217
218

######################################################################
# 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
219
220
221
222
223
224
    #
    # First, make sure the experiment exists
    #
    if (!ExpState($pid,$eid)) {
	die "There is no experiment $eid in project $pid\n";
    }
225
226
    if (!TBExptAccessCheck($UID,$pid,$eid,TB_EXPT_MODIFY)) {
	die "You do not have permission to modify experiment $pid/$eid\n";
227
    }
228
229
230
231
232
233
}

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

235
236
237
238
239
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";
240
    }
241
}
242

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

248
#
249
250
# snmpit_lib fills out some hashes for speed of lookup later. Initialize
# them now
251
#
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
snmpit_lib::init($debug);

my $exitval;
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) {
272
	(/listvlans/ || /getstats/ || /make/ || /remove/) && do {
273
	    @devicenames = $opt{i}? @{$opt{i}} : getTestSwitches();
274
	    last;
275
	};
276
	(/listports/) && do {
277
278
	    @devicenames = $opt{i}? @{$opt{i}} :
	    (@ports? getDeviceNames(@ports) : getTestSwitches());
279
280
	    last;
	};
281
282
283
	(/tables/) && do {
	    @vlans = getExperimentVlans($pid,$eid);
	    @ports = getVlanPorts(@vlans);
284
	    @devicenames = $opt{i}? @{$opt{i}} : getTestSwitches();
285
286
287
288
289
290
291
292
293
294
295
296
297
298
	    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;
	};
299
300
301
302
303
304
305
306
307
308
309
	(/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;
	};
310
    }
311

312
313
314
315
316
317
318
319
320
321
322
323
    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;
	}
324
    }
325
326

    #
327
    # Now, make the object for each stack that we discovered
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
359
    my @stacks;
    foreach my $stack_id (keys %stacks) {
	my $stack_type = getStackType($stack_id);
	my $stack;
	debug("Stack $stack_id has type $stack_type\n");
	SWITCH: for ($stack_type) {
	    /cisco/ && do {
		require snmpit_cisco_stack;
		$stack = new snmpit_cisco_stack($stack_id,$debug,
		@{$stacks{$stack_id}});
		last;
	    }; # /cisco/
	    /intel/ && do {
		require snmpit_intel_stack;
		$stack = new snmpit_intel_stack($stack_id,$debug,
		@{$stacks{$stack_id}});
		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;
	}
360
    }
361

362
######################################################################
363
# Step 4 - Actually perfrom the operation
364
365
#
# Finally, we just call the helper function for the operation that
366
# is to be performed.
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
    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 {
401
	    my ($portcommand) = @args;
402
	    $exitval += doPortControl(\@stacks,$portcommand,@ports);
403
404
405
406
407
	    last;
	};
	/recreate/ && do {
	    $exitval += doRecreateVlans(\@stacks);
	    last;
408
409
	};
    }
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
}

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
#
430
431
432
sub doListVlans ($) {

    my $stacks = shift;
433
434
435
436
437
438
439
    
    my %vlans;

    #
    # We need to 'coallate' the results from each stack by putting together
    # the results from each stack, based on the VLAN identifier
    #
440
    foreach my $stack (@$stacks) {
441
442
443
444
445
446
447
448
449
450
451
452
453
454
	# 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";
455
VLAN     Project/Experiment VName     Members
456
457
458
--------------------------------------------------------------------------------
END
    format vlanlist =
459
460
@<<<<<<< @<<<<<<<<<<<<<<<<< @<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$vlan_id,$pideid,           $vname,   $members
461
462
463
464
465
466
467
468
469
470
471
472
473
474
~~                                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                      $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);
475
476
477
	($pid,$eid,$vname) = $result->fetchrow();

	#
478
	# Permissions check - people only get to see their own VLANs
479
480
481
482
483
484
485
486
487
488
489
	#
	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;
	}

490
491
492
493
494
495
496
497

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

	#
	# Setup $pideid for a more compact display
	#
	if ($eid && $pid) {
498
	    $pideid = "$pid/$eid";
499
	} else {
500
	    $pideid = "";
501
	}
502
	write;
503
    }
504
505
506
507
508
509
510

    return 0;
}

#
# Lists all ports on all stacks
#
511
512
513
sub doListPorts($) {

    my $stacks = shift;
514
515
516
517
518

    #
    # Get a listing from all stacks
    #
    my @portList = ();
519
    foreach my $stack (@$stacks) {
520
	push @portList, $stack->listPorts;
521
    }
522
523
524
525
526
527
528

    #
    # See perlform(1) for help with formats
    #
    my ($port,$enabled,$up,$speed,$duplex);
    print << "END";
Port      Enabled Up   Speed      Duplex
529
--------------------------------------------
530
531
END
    format portlist =
532
@<<<<<<<< @<<<<<< @<<< @<<<<<<<<< @<<<<<<<<<
533
534
535
536
537
$port,    $enabled,$up,$speed,$duplex
.
    $FORMAT_NAME = 'portlist';
    foreach my $line (sort {tbsort($$a[0],$$b[0])} @portList) {
	($port,$enabled,$up,$speed,$duplex) = @$line;
538
539
540
541
542
543
544
545
546
547
548
549
550
551
	#
	# 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;
	}
552
	write;
553
    }
554
555
556
557
558
559
560

    return 0;
}

#
# Get statistics for all ports on all stacks
#
561
562
563
sub doGetStats($) {

    my $stacks = shift;
564
565
566
567
568

    #
    # Get a listing from all stacks
    #
    my @statList = ();
569
    foreach my $stack (@$stacks) {
570
	push @statList, $stack->getStats();
571
    }
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597

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

599
600
601
602
#
# Creates all VLANs given. Looks up identifiers in the database to determine
# the membership.
#
603
604
sub doVlansFromTables($@) {
    my $stacks = shift;
605
606
607
608
    my @vlans = @_;

    my $errors = 0;

609
    if (@$stacks > 1) {
610
	die "VLAN creation accross multiple stacks is not yet supported\n" .
611
	    "Stacks are " . join(",",@$stacks) . "\n";
612
    }
613
    my ($stack) = @$stacks;
614
615

    foreach my $vlan (@vlans) {
Robert Ricci's avatar
Robert Ricci committed
616
617
618
	my @ports = getVlanPorts($vlan);
	if ($stack->vlanExists($vlan)) {
	    print "  VLAN $vlan already exists\n";
619
	    $errors += $stack->setPortVlan($vlan,@ports);
Robert Ricci's avatar
Robert Ricci committed
620
	} else {
621
	    if (!$stack->createVlan($vlan,@ports)) {
Robert Ricci's avatar
Robert Ricci committed
622
623
624
625
626
		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++;
627
628
	    }
	}
629

630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
	#
	# 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
646
	    }
647
648
	}
    }
649
650
651
652
653
654
655
656
657

    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
#
658
659
sub doReset($@) {
    my $stacks = shift;
660
661
662
    my @vlans = @_;

    my $errors = 0;
663
664
665
666
667
668
669
    #
    # 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) {
670
	    if ($stack->vlanExists($vlan)) {
671
		push @existant_vlans, $vlan;
672
673
	    }
	}
674
675
676
	if (!$stack->removeVlan(@existant_vlans)) {
	    $errors++;
	}
677
    }
678
679
680
681
682
683
684
685
    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.
#
686
687
sub doMakeVlan($$@) {
    my $stacks = shift;
688
689
690
    my $vlan_name = shift;
    my @ports = @_;

691
    my $errors = 0;
692

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

    #
    # Create it if it doesn't already exist
    #
    if ($stack->vlanExists($vlan_name)) {
	print "VLAN $vlan_name already exists\n";
704
705
706
707
708
709
710
711
712
713
714
	#
	# 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;

	}
715
    } else {
716
	print "Creating VLAN $vlan_name ...\n";
717
	my $ok = $stack->createVlan($vlan_name,@ports);
718
719
720
721
722
	print "VLAN creation ";
	print $ok? "succeeded":"failed",".\n";
	if (!$ok) {
	    $errors++;
	}
723
    }
724
725

    return $errors;
726
727
}

728
729
730
#
# Delete the given VLAN, if it exists
#
731
732
sub doDeleteVlan($$) {
    my $stacks = shift;
733
734
735
736
737
    my $vlan_name = shift;

    my $errors = 0;

    my $exists = 0;
738
    foreach my $stack (@$stacks) {
739
740
741
742
	if ($stack->vlanExists($vlan_name)) {
	    $exists = 1;
	    print "Deleting VLAN $vlan_name ...\n";
	    my $ok = $stack->removeVlan($vlan_name);
743
	    print "VLAN deletion ";
744
745
746
747
	    print $ok? "succeeded":"failed",".\n";
	    if (!$ok) {
		$errors++;
	    }
748
	}
749
    }
750
751
752
753

    if (!$exists) {
	print "VLAN $vlan_name does not exist\n";
	$errors++;
754
    }
755
756

    return $errors;
757
}
758
759
760
761
762

#
# Send $command to @ports.
# TODO: List of commands
#
763
764
sub doPortControl($$@) {
    my $stacks = shift;
765
766
767
    my $command = shift;
    my @ports = @_;

768
    if (@$stacks > 1) {
769
	die "Port control accross multiple stacks is not yet supported\n" .
770
	    "Stacks are " . join(",",@$stacks) . "\n";
771
    }
772
    my ($stack) = @$stacks;
773
774
775
776
777
778
779
780
781
782

    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;

}

783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
#
# 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()) {
	warn "Sorry, only admins get to use this functions\n";
	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];
	    push (@vlansToNuke,$id);
	}
    }

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

    foreach my $vlan (@vlansToNuke) {
	doDeleteVlan($stacks,$vlan);
    }

    #
    # 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;

}