snmpit.in 38.8 KB
Newer Older
1
#!/usr/bin/perl -w
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2
#
3
# EMULAB-LGPL
4
# Copyright (c) 2000-2006 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
5 6 7
# All rights reserved.
#

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

#
# Configure variables
#

use lib '@prefix@/lib';
17
my $TESTMODE = @TESTMODE@;
18
my $ELABINELAB = @ELABINELAB@;
19
my $TB = '@prefix@';
20

21 22
use libdb;
use snmpit_lib;
23
use snmpit_remote;
Kevin Atkinson's avatar
 
Kevin Atkinson committed
24
use libtblog;
25 26

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

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
# Protos
sub parseStatusString($);
sub debug($);
sub doListVlans($);
sub doListPorts($);
sub doPortStatus($@);
sub doGetStats($);
sub doVlansFromTables($@);
sub doReset($@);
sub doMakeVlan($$@);
sub doDeleteVlan($@);
sub doVlanNumber($$);
sub doPortControl($$@);
sub doRecreateVlans($);
sub doTrunkEnable($$@);
sub doTrunkDisable($$);
sub doRestorePortStatus($@);

48 49 50
#
# Defaults
#
51
my $debug = 0;
52
my $quiet = 0;
53 54 55 56 57 58 59 60 61 62

######################################################################
# 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";
63
Usage: $0 [-h] [-v] [-q] [-n] [-i device] [-S stack]
64 65
	  [-l] [-s] [-g]
	  [-m name [ports]]
66 67
	  [-T port name]
	  [-U port]
68
	  [-o name]
69
          [ -N name]
70 71
          [-r pid eid]
	  [-t pid eid]
72
	  [-d ports] [-e ports] [-a ports]
73
	  [-p <10|100|1000> ports] [-u <half|full> ports]
74
	  [-c]
75 76 77
General:
  -h          Display this help message
  -v          Verbose mode
78
  -q          Quiet mode
Robert Ricci's avatar
Robert Ricci committed
79
  -n          Test mode - don't actually make any changes
80 81
  -i <device> Operate on <device>, overriding default device list. Can be
                  given multiple times
82 83
  -S <stack>  Operate on the given stack, instead of the default of the
                  experimental network
84 85

VLAN Control:
86 87
  -t <pid> <eid>    Create all VLANs for an experiment
  -r <pid> <eid>    Remove all VLANs for an experiment
88
                        (you can provide an optional list of vlan ids)
89
  -l                List all VLANs
90
  -w                Used with -l, includes device-specific VLAN number
91
  -M                Used with -l, print MAC addresses instead of port numbers
92 93
  -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
94 95 96 97 98 99
  -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
100
  -o <name>         Delete the VLAN with name <name>
101
  -N <name>         Print out the VLAN number for the named VLAN
102 103
  -c                Delete ALL VLANs, and recreate from the database. ** USE
                        WITH EXTREME CAUTION **
104 105
  -F                Create all vlans in the given stack on the leader for
		    use in leader->{ALLVLANSONLEADER} (Internal use only)
106 107

Port Control:
108 109 110 111 112 113 114
  -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
115 116
  -T <port> <names>      Enable trunking on the given <port>, and allow VLANs
                             with the given <names> across it
117
  -E <port> <names>      Like -T, but "Equal" mode; PVID is also tagged
118
  -U <port>              Turn off trunking for the given <port>
119 120
  -b <ports>             Print out port status for a set of ports
  -B <statstring>        Pass in a stat string from -b to restore status
121 122 123 124

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.
125 126 127
END

    return 1;
128 129
}

130

131
my %opt = ();
132
Getopt::Long::Configure("no_ignore_case");
133
GetOptions(\%opt, 'a','c','d','e','b','B=s@','g','h','i=s@','l','m=s@','M','n',
134 135
    'N=s@','o=s@','p=s','q','r','s', 'S=s@','t','E=s','T=s','u=s','U','v=s','w',
    'y=s','x=s','z=s','F');
136
# Unused: f,j
137 138 139 140

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

142 143 144 145 146
if ($opt{v}) {
    $debug = $opt{v};
    print "Debug level is $debug\n";
}

147 148 149 150
if ($opt{q}) {
    $quiet = 1;
}

151 152 153
#
# Values that may have been passed on the command line
#
154 155
my $pid;
my $eid;
156
my @ports;
157
my @optvlanids = ();
158
my $equaltrunking = 0;
159 160 161 162 163 164 165 166 167

#
# 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'
    #
168
    if (@ARGV < 2) {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
169
	tberror "pid/eid reqired!";
170
	exit &usage;
171
    } else {
172
	($pid, $eid) = (shift @ARGV, shift @ARGV);
173
    }
174 175 176
    if (@ARGV) {
	@optvlanids = @ARGV;
    }
177
} elsif ($opt{d} || $opt{e} || $opt{a} || $opt{p} || $opt{u} || $opt{m}
178
         || $opt{U} || $opt{b}) {
179 180 181 182
    #
    # Options that take a list of ports
    #
    @ports = @ARGV;
183
} elsif ($opt{T} || $opt{E}) {
184 185 186 187
    #
    # Options that take both a port and a list of VLANs - we require at least
    # one VLAN to be given
    #
188 189
    if ($opt{E}) { $opt{T} = $opt{E}; $equaltrunking = 1;}
    elsif (!@ARGV) {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
190
	tberror "At least one VLAN required";
191 192 193 194 195 196 197 198
	exit &usage;
    }
    @optvlanids = @ARGV;

    #
    # Set the @ports array so that we'll do proper permission checking on it
    #
    @ports = ($opt{T});
199 200 201 202 203
} else {
    #
    # Everything else
    #
    if (@ARGV) {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
204
	tberror "Too many arguments!";
205
	exit &usage;
206
    }
207 208 209 210 211 212 213
}

#
# 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.
#
214 215 216 217 218 219 220 221 222 223
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"]; }
224
if ($opt{c}) { push @commands, ["recreate"]; }
225
if ($opt{U}) { push @commands, ["trunkdisable"]; }
226
if ($opt{b}) { push @commands, ["portstatus"]; }
227
if ($opt{F}) { push @commands, ["synchleader"]; }
228 229

#
230
# Commands that can appear once, and take an argument
231 232 233 234
#
if ($opt{d}) { push @commands, ["portcontrol","disable"]; }
if ($opt{e}) { push @commands, ["portcontrol","enable"]; }
if ($opt{a}) { push @commands, ["portcontrol","auto"]; }
235
if ($opt{T}) { push @commands, ["trunkenable", $opt{T}]; }
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251

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

252 253 254 255 256 257
if ($opt{N}) {
    foreach my $name (@{$opt{N}}) {
	push @commands, ["vlannumber",$name];
    }
}

258 259 260 261 262 263 264 265
if ($opt{B}) {
    foreach my $statstring (@{$opt{B}}) {
	push @commands, ["restorestatus",$statstring];
        # Set up the @ports variable so that we get permissions checking. Note,
        # though, that we re-parse the strings again later, this is just for
        # permissions
        my %args = parseStatusString($statstring);
        if (!$args{port}) {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
266
            tbdie "No port given in status string";
267 268
        }
        if ($args{port}) {
269
            push @ports, convertPortsFromIfaces($args{port});
270 271 272 273
        }
    }
}

274 275 276 277
#
# Commands that require 'translation' of their arguments
#
if ($opt{p}) {
278 279 280
    #
    # We'll put the argument in the form needed by the portControl function
    #
281 282 283
    if ($opt{p} =~ /^1000/) {
	push @commands, ["portcontrol","1000mbit"];
    } elsif ($opt{p} =~ /^100/) {
284
	push @commands, ["portcontrol","100mbit"];
285
    } elsif ($opt{p} =~ /^10/) {
286
	push @commands, ["portcontrol","10mbit"];
287
    } else {
288
	die "Bad port speed: $opt{p}. Valid values are 10, 100, and 1000\n";
289
    }
290 291
}
if ($opt{u}) {
292 293 294 295
    #
    # We'll put the argument in the form needed by the portControl function
    #
    if ($opt{u} =~ /half/) {
296
	push @commands, ["portcontrol","half"];
297
    } elsif ($opt{u} =~ /full/) {
298
	push @commands, ["portcontrol","full"];
299 300
    } else {
	die "Bad port duplex: $opt{u}. Valid values are full and half\n";
Mac Newbold's avatar
Mac Newbold committed
301
    }
302 303 304
}

if (!@commands) {
305 306
    die "No operation given\n";
}
Mac Newbold's avatar
Mac Newbold committed
307

Robert Ricci's avatar
Robert Ricci committed
308 309 310 311
#
# Options that affect other commands
#

312 313 314 315
#
# User-supplied switch lists
#
my @supplied_switches = ();
316 317
my @supplied_stacks = ();
my $supplied_switches = 0; # Whether -i or -S was given
318
if ($opt{i}) {
319
    $supplied_switches = 1;
320 321 322 323
    push @supplied_switches, @{$opt{i}};
}
if ($opt{S}) {
    foreach my $stack (@{$opt{S}}) {
324
	$supplied_switches = 1;
325 326
	my @switches = getSwitchesInStack($stack);
	if (@switches) {
327
	    push @supplied_stacks, $stack;
328
	} else {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
329
	    tbdie "No such switch stack: $stack";
330 331 332 333
	}
    }
}

Robert Ricci's avatar
Robert Ricci committed
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
#
# 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}) {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
351
	    tberror "-x and -z must be given when -y is $opt{y}!";
Robert Ricci's avatar
Robert Ricci committed
352 353 354 355 356 357 358 359 360 361
	    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 {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
362 363
		tbdie "The module/port format is only legal if exactly one -i " .
		    "argument has been given";
Robert Ricci's avatar
Robert Ricci committed
364 365 366 367 368
	    }
	}
	push @pvlanArgs,$opt{x},$opt{z};
    }
}
369 370 371 372 373 374 375 376 377 378 379 380 381

######################################################################
# 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
382 383 384 385 386 387
    #
    # First, make sure the experiment exists
    #
    if (!ExpState($pid,$eid)) {
	die "There is no experiment $eid in project $pid\n";
    }
388
    if ($UID && !TBExptAccessCheck($UID,$pid,$eid,TB_EXPT_MODIFY)) {
389
	die "You do not have permission to modify experiment $pid/$eid\n";
390
    }
391 392 393 394 395 396
}

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

398 399 400 401 402
# Convert ports into the correct format
if (@ports) {
    @ports = convertPortsFromIfaces(@ports);
}

403
if (@ports) {
Robert Ricci's avatar
Robert Ricci committed
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
    #
    # 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"
	}
    }

427
    if ($UID && !TBNodeAccessCheck($UID,TB_NODEACCESS_MODIFYVLANS,@nodes)) {
428 429
	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";
430
    }
Robert Ricci's avatar
Robert Ricci committed
431 432 433 434

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

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

442
#
443 444
# snmpit_lib fills out some hashes for speed of lookup later. Initialize
# them now
445
#
446 447
snmpit_lib::init($debug);

Robert Ricci's avatar
Robert Ricci committed
448
my $exitval = 0;
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
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) {
466
	(/listvlans/ || /getstats/ || /make/ || /remove/ || /vlannumber/ || /synchleader/) && do {
467
	    @devicenames = $supplied_switches?
468
			   @supplied_switches : getTestSwitches();
469
	    last;
470
	};
471
	(/listports/) && do {
472
	    @devicenames = $supplied_switches? @supplied_switches :
473
	    (@ports? getDeviceNames(@ports) : getTestSwitches());
474 475
	    last;
	};
476
	(/tables/) && do {
477
	    @vlans = getExperimentVlans($pid,$eid,@optvlanids);
478
	    @ports = getVlanPorts(@vlans);
479
	    @devicenames = $supplied_switches?
480
			   @supplied_switches : getTestSwitches();
481 482 483 484 485 486
	    last;
	};
	(/reset/) && do {
	    #
	    # When we reset, we operate on all test switches, just to be safe
	    #
487
	    @vlans = getExperimentVlans($pid,$eid,@optvlanids);
488
	    @devicenames = $supplied_switches?
489
			   @supplied_switches : getTestSwitches();
490 491
	    last;
	};
492
	(/portcontrol/ || /trunkdisable/ || /portstatus/) && do {
493
	    @devicenames = $supplied_switches?
494
	    		   @supplied_switches : getDeviceNames(@ports);
495 496
	    last;
	};
497 498 499 500 501
	(/recreate/) && do {
	    #
	    # Safety check - cannot be used with -i . We have to operate on
	    # all experimental switches
	    #
502
	    if ($supplied_switches) {
503
		die "-c and -i or -S cannot be used together\n";
504 505 506 507
	    }
	    @devicenames = getTestSwitches();
	    last;
	};
508
	(/trunkenable/) && do {
509
	    @devicenames = $supplied_switches?
510
	    		   @supplied_switches : getDeviceNames(@ports);
511 512
	    @vlans = @optvlanids;
	    last;
513 514 515 516 517 518 519
	};
        (/restorestatus/) && do {
            # We start by parsing out the options string, then we'll reset
            # @args
            my ($statusarg) = @args;
            my %args = parseStatusString($statusarg);
            if (!$args{port}) {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
520
                tbdie "No port given in status string";
521
            }
522
            @ports = convertPortsFromIfaces($args{port});
523 524 525 526 527 528 529 530 531
            if ($args{vlan}) {
                @vlans = ($args{vlan});
            }
            @devicenames = $supplied_switches?
                           @supplied_switches : getDeviceNames(@ports);

            @args = %args;
            last;
        };
532
    }
533

534 535
    debug("Device names: " . join(",",@devicenames) . "\n");
    debug("Ports: " . join(",",@ports) . "\n");
536
    if (@vlans) { debug("VLANs: " . join(",",@vlans) . "\n") };
537 538

    my %stacks = ();
539
    if (! $ELABINELAB) {
540 541 542
	#
	# First, look at the device names we've got and map them to stacks 
	#
543
	foreach my $devicename (@devicenames) {
544
	    my $stack = getSwitchPrimaryStack($devicename);
545 546 547
	    if (defined($stack)) {
		push @{$stacks{$stack}}, $devicename;
	    }
548
	}
549 550 551 552 553 554 555 556
	#
	# Next, add in stacks that were specified as a whole
	#
	foreach my $stack (@supplied_stacks) {
	    # We checked for non-existent stacks above
	    my @switches = getSwitchesInStack($stack);
	    push @{$stacks{$stack}}, @switches;
	}
557
    }
558 559

    #
560
    # Now, make the object for each stack that we discovered
561
    #
562
    my @stacks;
563
    my %stack_ids;
564
    foreach my $stack_id (keys %stacks) {
565
	my ($stack_type, $supports_private, $single_domain, $community)
566
		= getStackType($stack_id);
Robert Ricci's avatar
Robert Ricci committed
567 568 569 570 571 572 573
	#
	# 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";
	}
574

575 576 577
	my $stack;
	debug("Stack $stack_id has type $stack_type\n");
	SWITCH: for ($stack_type) {
578
	    (/cisco/ || /catalyst/) && do {
579
		require snmpit_cisco_stack;
580 581
		$stack = new snmpit_cisco_stack($stack_id,$debug,$single_domain,
		    @{$stacks{$stack_id}});
582 583 584 585
		last;
	    }; # /cisco/
	    /intel/ && do {
		require snmpit_intel_stack;
586
		$stack = new snmpit_intel_stack($stack_id,$debug,
587
		    @{$stacks{$stack_id}});
588 589
		last;
	    };
590 591 592 593 594 595
	    /generic/ && do {
		require snmpit_stack;
		$stack = new snmpit_stack($stack_id,$debug,
		    @{$stacks{$stack_id}});
		last;
	    }; # /generic/
596 597 598 599 600 601 602 603 604 605 606 607

	    # '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;
608 609 610 611 612 613 614 615 616
	    $stack_ids{$stack_id} = $stack;
	}
    }


    #
    # If we were given ports, make sure that the stack(s) that got created
    # cover all of them
    #
617
    if (@ports && !$ELABINELAB) {
618 619 620
	my %devicemap = mapPortsToDevices(@ports);
	my @devices = keys %devicemap;
	foreach my $device (@devices) {
621 622 623 624 625 626 627 628
	    my @stack_ids = getSwitchStacks($device);
	    my $matched = 0;
	    foreach my $stack_id (@stack_ids) {
		if ($stack_id && $stack_ids{$stack_id}) {
		    $matched = 1;
		}
	    }
	    if (!$matched) {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
629 630
		tbdie "One or more ports are on $device, but no " .
		    "stack containing it was specified!";
631
	    }
632
	}
633
    }
634

635
######################################################################
636
# Step 4 - Actually perfrom the operation
637 638
#
# Finally, we just call the helper function for the operation that
639
# is to be performed.
640
######################################################################
Robert Ricci's avatar
Robert Ricci committed
641 642 643 644
    if ($opt{n}) {
	print "Test mode, skipping operation\n";
	next;
    }
645

646 647 648 649 650
    SWITCH: for ($operation) {
	/listvlans/ && do {
	    $exitval += doListVlans(\@stacks);
	    last;
	}; # /listvlans/ && do 
651 652 653 654
	/synchleader/ && do {
	    $exitval += doSynchLeader(\@stacks);
	    last;
	}; # /listvlans/ && do 
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
	/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 {
682
	    my ($portcommand) = @args;
683
	    $exitval += doPortControl(\@stacks,$portcommand,@ports);
684 685 686 687 688
	    last;
	};
	/recreate/ && do {
	    $exitval += doRecreateVlans(\@stacks);
	    last;
689
	};
690 691 692 693 694 695
	/trunkenable/ && do {
	    my ($port) = @args;
	    $exitval += doTrunkEnable(\@stacks,$port,@vlans);
	    last;
	}; # /trunkenable/ && do
	/trunkdisable/ && do {
696
	    $exitval += doTrunkDisable(\@stacks,$ports[0]);
697
	    last;
698 699 700 701 702
	}; # /trunkdisable/ && do
	/portstatus/ && do {
	    $exitval += doPortStatus(\@stacks,@ports);
	    last;
	}; # /portstatus/ && do
703 704 705 706 707
	/vlannumber/ && do {
	    my ($vlan_name) = @args;
	    $exitval += doVlanNumber(\@stacks,$vlan_name);
	    last;
	}; # /portstatus/ && do
708 709 710 711
	/restorestatus/ && do {
	    $exitval += doRestorePortStatus(\@stacks,@args);
	    last;
	}; # /portstatus/ && do
712
    }
713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729
}

exit $exitval;

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

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

730 731 732 733 734 735 736 737 738 739 740
#
# Parse a port status string. Returns a key-value hash pair
#
sub parseStatusString($) {
    my ($string) = @_;
    chomp $string;

    my %pairs;
    foreach my $pair (split /;/, $string) {
        my ($key, $value) = split /=/,$pair,2;
        if (!$key || !$value) {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
741
            tbdie "Bad port status string: $string";
742 743 744 745 746 747 748 749
        } else {
            $pairs{$key} = $value;
        }
    }

    return %pairs;
}

750 751 752
#
# Lists all vlans on all stacks
#
753 754 755
sub doListVlans ($) {

    my $stacks = shift;
756 757 758 759 760 761 762
    
    my %vlans;

    #
    # We need to 'coallate' the results from each stack by putting together
    # the results from each stack, based on the VLAN identifier
    #
763
    foreach my $stack (@$stacks) {
764 765 766
	my @vlanList = $stack->listVlans();
	foreach my $vlan (@vlanList) {
	    my ($id,$ddep,$memberref) = @$vlan;
767
	    ${$vlans{$id}}[0] = $ddep;
768 769 770 771 772 773 774 775
	    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
    #
776 777 778 779 780 781 782
    my ($vlan_id,$ddep,$pideid,$vname,$members);
    #
    # Check to see if they want device-specific VLAN numbers, which makes the
    # display more cramped, but is useful for debugging
    #
    if (!$opt{w}) { 
	print << "END";
783
VLAN     Project/Experiment VName     Members
784 785
--------------------------------------------------------------------------------
END
786
	format vlanlist =
787 788
@<<<<<<< @<<<<<<<<<<<<<<<<< @<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$vlan_id,$pideid,           $vname,   $members
789 790 791
~~                                    ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                      $members
.
792 793 794 795 796 797 798 799 800 801 802 803 804 805
	$FORMAT_NAME = 'vlanlist';
    } else {
    	print << "END";
VLAN     Number Project/Experiment VName     Members
--------------------------------------------------------------------------------
END
	format vlanlist2 =
@<<<<<<< @<<<<< @<<<<<<<<<<<<<<<<< @<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$vlan_id,$ddep, $pideid,           $vname,   $members
~~                                           ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                             $members
.
	$FORMAT_NAME = 'vlanlist2';
}
806

Kirk Webb's avatar
 
Kirk Webb committed
807 808
    foreach my $vid (sort {tbsort($a,$b)} keys %vlans) {
        $vlan_id = $vid;
809
	my $memberref;
Kirk Webb's avatar
 
Kirk Webb committed
810
	($ddep,$memberref) = @{$vlans{$vid}};
811 812 813 814 815

	#
	# Find which, if any, experiment this VLAN belongs to.
	#
	my $result = DBQueryFatal("select pid, eid, virtual from " .
Kirk Webb's avatar
 
Kirk Webb committed
816
				  "vlans where id='$vid'");
817
	my ($eid,$pid);
818 819 820
	($pid,$eid,$vname) = $result->fetchrow();

	#
821
	# Permissions check - people only get to see their own VLANs
822 823 824 825 826 827
	#
	if ((!$eid) || (!$pid)) {
	    if (!TBAdmin()) {
		&debug("Failed TBAdmin check\n");
		next;
	    }
828
	} elsif ($UID && !TBExptAccessCheck($UID,$pid,$eid,TB_EXPT_READINFO)) {
829 830 831 832
	    &debug("Failed TBExptAccessCheck($UID,$pid,$eid)\n");
	    next;
	}

833 834

	if (!$vname) { $vname = ""; }
835 836 837 838 839 840 841 842 843 844 845 846 847

	#
	# Check to see if we were supposed to print out MAC addresses
	#
	if ($opt{M}) {
	    # Rather than node:port, print out node:MAC (if we know the MAC)
	    $members = join(" ", map
		{
		    macport($_)? (split /:/)[0] . ":" . macport($_) : $_
		} @$memberref);
	} else {
	    $members = join(" ",@$memberref);
	}
848 849 850 851 852

	#
	# Setup $pideid for a more compact display
	#
	if ($eid && $pid) {
853
	    $pideid = "$pid/$eid";
854
	} else {
855
	    $pideid = "";
856
	}
857
	write;
858
    }
859 860 861 862

    return 0;
}

863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891
#
# This routine does a number of illegal things.  It is used
# to make sure the leader of a stack contains all vlans on all switches
# in the stack and is only useful for moving a stack back and forth
# between types "cisco" and "generic" (where $stack->{ALLVLANSONLEADER} must be
# set to 1.
#
sub doSynchLeader ($) {

    my $stacks = shift;
    my $errors = 0;
    if (!TBAdmin()) {
	die "Only admins are allowed to synchronize stacks\n";
    }

    foreach my $stack (@$stacks) {
	my $leader = $stack->{LEADER};
	my %vlans = $stack->findVlans();
	while (my ($id,$num) = each %vlans) {
	    if (($num >= $leader->{MIN_VLAN}) &&
		($num <= $leader->{MAX_VLAN}) &&
		!($leader->vlanNumberExists($num))) {
			$errors += $leader->createVlan($id, $num);
	    }
	}
    }
    return $errors;
}

892 893 894
#
# Lists all ports on all stacks
#
895 896 897
sub doListPorts($) {

    my $stacks = shift;
898 899 900 901 902

    #
    # Get a listing from all stacks
    #
    my @portList = ();
903
    foreach my $stack (@$stacks) {
904
	push @portList, $stack->listPorts;
905
    }
906 907 908 909 910 911 912

    #
    # See perlform(1) for help with formats
    #
    my ($port,$enabled,$up,$speed,$duplex);
    print << "END";
Port      Enabled Up   Speed      Duplex
913
--------------------------------------------
914 915
END
    format portlist =
916
@<<<<<<<< @<<<<<< @<<< @<<<<<<<<< @<<<<<<<<<
917 918 919 920 921
$port,    $enabled,$up,$speed,$duplex
.
    $FORMAT_NAME = 'portlist';
    foreach my $line (sort {tbsort($$a[0],$$b[0])} @portList) {
	($port,$enabled,$up,$speed,$duplex) = @$line;
922 923 924 925 926 927 928 929 930 931 932
	#
	# 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;
	    }
933
	} elsif ($UID && !TBNodeAccessCheck($UID,TB_NODEACCESS_READINFO,$node)) {
934 935
	    next;
	}
936
	write;
937
    }
938 939 940 941

    return 0;
}

942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974
#
# Get information on a particular port - the idea is that this string can later
# be passed back to us to restore the status of the port.
# This is inefficient, because it gets information about all ports and only
# uses it for one (or a few). BUT, it doesn't require changes to the
# vendor-specific modules, so I think it's worth it for portability.
#
sub doPortStatus($@) {

    my $stacks = shift;
    my @port = @_;

    my $errors = 0;

    #
    # Get a listing from all stacks
    #
    my @portList = ();
    foreach my $stack (@$stacks) {
	push @portList, $stack->listPorts;
    }

    #
    # Find the port(s) we're looking for in the portlist
    #
    my %ports = ();
    foreach my $port (@ports) {
        $ports{$port} = undef;
    }
    foreach my $portrecord (@portList) {
	my ($port,$enabled,$up,$speed,$duplex) = @$portrecord;
        if (exists $ports{$port}) {
            $ports{$port} = "enabled=$enabled";
975 976 977 978
            $ports{$port} .= ";speed=$speed";
            if ($speed ne "autoDetect") {
                $ports{$port} .= ";duplex=$duplex";
            }
979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
        }
    }

    #
    # Now find out what VLAN it's in
    #
    foreach my $stack (@$stacks) {
	my @vlanList = $stack->listVlans();
	foreach my $vlan (@vlanList) {
	    my ($id,$ddep,$memberref) = @$vlan;
            # Now we have to look through the memberref for our ports
            foreach my $port (@$memberref) {
                if ($ports{$port}) {
                    $ports{$port} .= ";vlan=$id";
                }
            }
	}
    }

    #
    # XXX - This is gonna be a mess if the port is a trunk port, or otherwise
    # somehow more complicated. For now, we just ignore this, I guess
    #

    #
    # Check to see if we missed any
    #
    foreach my $port (keys %ports) {
        if (!$ports{$port}) {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
1008
            tberror "Unable to get status information for $port!";
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
            $errors++;
        } else {
            print "port=$port;$ports{$port}\n";
        }
    }

    if ($errors) {
        return $errors;
    }

}

#
# Restore port status. Takes a single argument, which is an array that will be
# turned back into a param hash
#
sub doRestorePortStatus($@) {

    my $stacks = shift;
    my %params = @_; # Yes, this does work

    my $port = $params{port};
    if (!$port) {
Kevin Atkinson's avatar
 
Kevin Atkinson committed
1032
        tbdie "No port passed to -B option";
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062
    }

    if (@$stacks > 1) {
	die "Port restoration accross multiple stacks is not yet supported\n" .
	    "Stacks are " . join(",",@$stacks) . "\n";
    }
    my ($stack) = @$stacks;

    my $errors = 0;

    # Put the port in the specified VLAN
    # If they didn't give us a VLAN, we assume that means they want it removed
    # from its vlan
    my $vlan = $params{vlan};
    if (!$vlan) { $vlan = "default"; }
    $errors += $stack->setPortVlan($vlan,$port);
    if ($errors) { return $errors; }

    # Enable or disable the port
    # If they didn't tell us either way, we assume they wanted it disabled
    my $enabled = $params{enabled};
    # If they are putting the port into the default VLAN, force it to be
    # disabled, so that a user cannot use this method to break into that VLAN
    if ($vlan eq "default") { $enabled = "no"; }
    if ($enabled eq "yes") {
        $errors = $stack->portControl("enable",$port);
    } else {
        $errors = $stack->portControl("disable",$port);
    }