snmpit.in 20 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
use libdb;
use snmpit_lib;

use English;
19
use Getopt::Long;
20
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

######################################################################
# 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]
42
43
	  [-d ports] [-e ports] [-a ports]
	  [-p <10|100> ports] [-u <half|full> ports]
44
	  [-c]
45
46
47
General:
  -h          Display this help message
  -v          Verbose mode
48
49
  -i <device> Operate on <device>, overriding default device list. Can be
                  given multiple times
50
51
52
53
54
55
56
57

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>
58
59
  -c                Delete ALL VLANs, and recreate from the database. ** USE
                        WITH EXTREME CAUTION **
60
61

Port Control:
62
63
64
65
66
67
68
69
70
71
72
  -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.
73
74
75
END

    return 1;
76
77
}

78

79
my %opt = ();
80
GetOptions(\%opt,'h','l','v','s','t','r','i=s@','m=s@','o=s@','p=s','u=s','d',
81
    'e','a','g','c');
82
83
84
85

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

87
88
89
90
91
92
93
94
if ($opt{v}) {
    $debug = $opt{v};
    print "Debug level is $debug\n";
}

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

#
# 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.
#
133
134
135
136
137
138
139
140
141
142
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"]; }
143
if ($opt{c}) { push @commands, ["recreate"]; }
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

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

if (!@commands) {
196
197
    die "No operation given\n";
}
Mac Newbold's avatar
Mac Newbold committed
198

199
200
201
202
203
204
205
206
207
208
209
210
211

######################################################################
# 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
212
213
214
215
216
217
    #
    # First, make sure the experiment exists
    #
    if (!ExpState($pid,$eid)) {
	die "There is no experiment $eid in project $pid\n";
    }
218
219
    if (!TBExptAccessCheck($UID,$pid,$eid,TB_EXPT_MODIFY)) {
	die "You do not have permission to modify experiment $pid/$eid\n";
220
    }
221
222
223
224
225
226
}

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

228
229
230
231
232
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";
233
    }
234
}
235

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

241
#
242
243
# snmpit_lib fills out some hashes for speed of lookup later. Initialize
# them now
244
#
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
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) {
	(/listvlans/ || /getstats/) && do {
	    @devicenames = $opt{i}? @{$opt{i}} : getTestSwitches();
267
	    last;
268
269
270
271
	};
	(/listports/ || /make/ || /remove/) && do {
	    @devicenames = $opt{i}? @{$opt{i}} :
	    (@ports? getDeviceNames(@ports) : getTestSwitches());
272
273
	    last;
	};
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
	(/tables/) && do {
	    @vlans = getExperimentVlans($pid,$eid);
	    @ports = getVlanPorts(@vlans);
	    @devicenames = $opt{i}? @{$opt{i}} : getDeviceNames(@ports);
	    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;
	};
292
293
294
295
296
297
298
299
300
301
302
	(/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;
	};
303
    }
304

305
306
307
308
309
310
311
312
313
314
315
316
    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;
	}
317
    }
318
319

    #
320
    # Now, make the object for each stack that we discovered
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
    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;
	}
353
    }
354

355
######################################################################
356
# Step 4 - Actually perfrom the operation
357
358
#
# Finally, we just call the helper function for the operation that
359
# is to be performed.
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
    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 {
	    my $portcommand = @args;
	    $exitval += doPortControl(\@stacks,$portcommand,@ports);
396
397
398
399
400
	    last;
	};
	/recreate/ && do {
	    $exitval += doRecreateVlans(\@stacks);
	    last;
401
402
	};
    }
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
}

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
#
423
424
425
sub doListVlans ($) {

    my $stacks = shift;
426
427
428
429
430
431
432
    
    my %vlans;

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

	#
471
	# Permissions check - people only get to see their own VLANs
472
473
474
475
476
477
478
479
480
481
482
	#
	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;
	}

483
484
485
486
487
488
489
490

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

	#
	# Setup $pideid for a more compact display
	#
	if ($eid && $pid) {
491
	    $pideid = "$pid/$eid";
492
	} else {
493
	    $pideid = "";
494
	}
495
	write;
496
    }
497
498
499
500
501
502
503

    return 0;
}

#
# Lists all ports on all stacks
#
504
505
506
sub doListPorts($) {

    my $stacks = shift;
507
508
509
510
511

    #
    # Get a listing from all stacks
    #
    my @portList = ();
512
    foreach my $stack (@$stacks) {
513
	push @portList, $stack->listPorts;
514
    }
515
516
517
518
519
520
521

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

    return 0;
}

#
# Get statistics for all ports on all stacks
#
554
555
556
sub doGetStats($) {

    my $stacks = shift;
557
558
559
560
561

    #
    # Get a listing from all stacks
    #
    my @statList = ();
562
    foreach my $stack (@$stacks) {
563
	push @statList, $stack->getStats();
564
    }
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590

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

592
593
594
595
#
# Creates all VLANs given. Looks up identifiers in the database to determine
# the membership.
#
596
597
sub doVlansFromTables($@) {
    my $stacks = shift;
598
599
600
601
    my @vlans = @_;

    my $errors = 0;

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

    foreach my $vlan (@vlans) {
Robert Ricci's avatar
Robert Ricci committed
609
610
611
	my @ports = getVlanPorts($vlan);
	if ($stack->vlanExists($vlan)) {
	    print "  VLAN $vlan already exists\n";
612
	    $errors += $stack->setPortVlan($vlan,@ports);
Robert Ricci's avatar
Robert Ricci committed
613
	} else {
614
	    if (!$stack->createVlan($vlan,@ports)) {
Robert Ricci's avatar
Robert Ricci committed
615
616
617
618
619
		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++;
620
621
	    }
	}
622

623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
	#
	# 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
639
	    }
640
641
	}
    }
642
643
644
645
646
647
648
649
650

    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
#
651
652
sub doReset($@) {
    my $stacks = shift;
653
654
655
656
657
658
659
    my @vlans = @_;

    my $errors = 0;
    foreach my $vlan (@vlans) {
	#
	# Just remove the VLAN from evey satck on which it exists
	#
660
	foreach my $stack (@$stacks) {
661
662
663
664
665
666
	    if ($stack->vlanExists($vlan)) {
		if (!$stack->removeVlan($vlan)) {
		    $errors++;
		}
	    }
	}
667
    }
668
669
670
671
672
673
674
675
    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.
#
676
677
sub doMakeVlan($$@) {
    my $stacks = shift;
678
679
680
    my $vlan_name = shift;
    my @ports = @_;

681
    my $errors = 0;
682

683
    if (@$stacks > 1) {
684
	die "VLAN creation accross multiple stacks is not yet supported\n" .
685
	    "Stacks are " . join(",",@$stacks) . "\n";
686
    }
687
    my ($stack) = @$stacks;
688
689
690
691
692
693

    #
    # Create it if it doesn't already exist
    #
    if ($stack->vlanExists($vlan_name)) {
	print "VLAN $vlan_name already exists\n";
694
695
696
697
698
699
700
701
702
703
704
	#
	# 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;

	}
705
    } else {
706
	print "Creating VLAN $vlan_name ...\n";
707
	my $ok = $stack->createVlan($vlan_name,@ports);
708
709
710
711
712
	print "VLAN creation ";
	print $ok? "succeeded":"failed",".\n";
	if (!$ok) {
	    $errors++;
	}
713
    }
714
715

    return $errors;
716
717
}

718
719
720
#
# Delete the given VLAN, if it exists
#
721
722
sub doDeleteVlan($$) {
    my $stacks = shift;
723
724
725
726
727
    my $vlan_name = shift;

    my $errors = 0;

    my $exists = 0;
728
    foreach my $stack (@$stacks) {
729
730
731
732
	if ($stack->vlanExists($vlan_name)) {
	    $exists = 1;
	    print "Deleting VLAN $vlan_name ...\n";
	    my $ok = $stack->removeVlan($vlan_name);
733
	    print "VLAN deletion ";
734
735
736
737
	    print $ok? "succeeded":"failed",".\n";
	    if (!$ok) {
		$errors++;
	    }
738
	}
739
    }
740
741
742
743

    if (!$exists) {
	print "VLAN $vlan_name does not exist\n";
	$errors++;
744
    }
745
746

    return $errors;
747
}
748
749
750
751
752

#
# Send $command to @ports.
# TODO: List of commands
#
753
754
sub doPortControl($$@) {
    my $stacks = shift;
755
756
757
    my $command = shift;
    my @ports = @_;

758
    if (@$stacks > 1) {
759
	die "Port control accross multiple stacks is not yet supported\n" .
760
	    "Stacks are " . join(",",@$stacks) . "\n";
761
    }
762
    my ($stack) = @$stacks;
763
764
765
766
767
768
769
770
771
772

    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;

}

773
774
775
776
777
778
779
780
781
782
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
#
# 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;

}